apex-code 0.0.1-alpha.4 → 0.0.1-alpha.6

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 (142) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +10 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  6. package/dist/core/agent-session-runtime.js +2 -2
  7. package/dist/core/agent-session-runtime.js.map +1 -1
  8. package/dist/core/agent-session-services.d.ts +20 -1
  9. package/dist/core/agent-session-services.d.ts.map +1 -1
  10. package/dist/core/agent-session-services.js +91 -1
  11. package/dist/core/agent-session-services.js.map +1 -1
  12. package/dist/core/agent-session.d.ts +17 -0
  13. package/dist/core/agent-session.d.ts.map +1 -1
  14. package/dist/core/agent-session.js +28 -4
  15. package/dist/core/agent-session.js.map +1 -1
  16. package/dist/core/lsp/client.d.ts +76 -0
  17. package/dist/core/lsp/client.d.ts.map +1 -0
  18. package/dist/core/lsp/client.js +469 -0
  19. package/dist/core/lsp/client.js.map +1 -0
  20. package/dist/core/lsp/diagnostics.d.ts +47 -0
  21. package/dist/core/lsp/diagnostics.d.ts.map +1 -0
  22. package/dist/core/lsp/diagnostics.js +224 -0
  23. package/dist/core/lsp/diagnostics.js.map +1 -0
  24. package/dist/core/lsp/pool.d.ts +48 -0
  25. package/dist/core/lsp/pool.d.ts.map +1 -0
  26. package/dist/core/lsp/pool.js +150 -0
  27. package/dist/core/lsp/pool.js.map +1 -0
  28. package/dist/core/lsp/registry.d.ts +49 -0
  29. package/dist/core/lsp/registry.d.ts.map +1 -0
  30. package/dist/core/lsp/registry.js +215 -0
  31. package/dist/core/lsp/registry.js.map +1 -0
  32. package/dist/core/lsp/status.d.ts +23 -0
  33. package/dist/core/lsp/status.d.ts.map +1 -0
  34. package/dist/core/lsp/status.js +35 -0
  35. package/dist/core/lsp/status.js.map +1 -0
  36. package/dist/core/lsp/sync.d.ts +13 -0
  37. package/dist/core/lsp/sync.d.ts.map +1 -0
  38. package/dist/core/lsp/sync.js +71 -0
  39. package/dist/core/lsp/sync.js.map +1 -0
  40. package/dist/core/package-manager.d.ts.map +1 -1
  41. package/dist/core/package-manager.js +15 -0
  42. package/dist/core/package-manager.js.map +1 -1
  43. package/dist/core/project-trust.d.ts.map +1 -1
  44. package/dist/core/project-trust.js +1 -1
  45. package/dist/core/project-trust.js.map +1 -1
  46. package/dist/core/sandbox/cli-launch.d.ts +43 -0
  47. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  48. package/dist/core/sandbox/cli-launch.js +70 -3
  49. package/dist/core/sandbox/cli-launch.js.map +1 -1
  50. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  51. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  52. package/dist/core/sandbox/cli-supervisor.js +1 -0
  53. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  54. package/dist/core/sdk.d.ts +4 -0
  55. package/dist/core/sdk.d.ts.map +1 -1
  56. package/dist/core/sdk.js +10 -1
  57. package/dist/core/sdk.js.map +1 -1
  58. package/dist/core/settings-manager.d.ts +3 -0
  59. package/dist/core/settings-manager.d.ts.map +1 -1
  60. package/dist/core/settings-manager.js +3 -0
  61. package/dist/core/settings-manager.js.map +1 -1
  62. package/dist/core/skills.d.ts +19 -7
  63. package/dist/core/skills.d.ts.map +1 -1
  64. package/dist/core/skills.js +77 -19
  65. package/dist/core/skills.js.map +1 -1
  66. package/dist/core/slash-commands.d.ts.map +1 -1
  67. package/dist/core/slash-commands.js +1 -1
  68. package/dist/core/slash-commands.js.map +1 -1
  69. package/dist/core/system-prompt.d.ts.map +1 -1
  70. package/dist/core/system-prompt.js +3 -3
  71. package/dist/core/system-prompt.js.map +1 -1
  72. package/dist/core/tools/contract.d.ts +29 -2
  73. package/dist/core/tools/contract.d.ts.map +1 -1
  74. package/dist/core/tools/contract.js.map +1 -1
  75. package/dist/core/tools/diagnostics.d.ts +39 -0
  76. package/dist/core/tools/diagnostics.d.ts.map +1 -0
  77. package/dist/core/tools/diagnostics.js +62 -0
  78. package/dist/core/tools/diagnostics.js.map +1 -0
  79. package/dist/core/tools/edit.d.ts +10 -0
  80. package/dist/core/tools/edit.d.ts.map +1 -1
  81. package/dist/core/tools/edit.js +17 -8
  82. package/dist/core/tools/edit.js.map +1 -1
  83. package/dist/core/tools/index.d.ts +20 -3
  84. package/dist/core/tools/index.d.ts.map +1 -1
  85. package/dist/core/tools/index.js +18 -0
  86. package/dist/core/tools/index.js.map +1 -1
  87. package/dist/core/tools/lsp.d.ts +62 -0
  88. package/dist/core/tools/lsp.d.ts.map +1 -0
  89. package/dist/core/tools/lsp.js +216 -0
  90. package/dist/core/tools/lsp.js.map +1 -0
  91. package/dist/core/tools/skill-search.d.ts +32 -0
  92. package/dist/core/tools/skill-search.d.ts.map +1 -0
  93. package/dist/core/tools/skill-search.js +56 -0
  94. package/dist/core/tools/skill-search.js.map +1 -0
  95. package/dist/core/tools/write.d.ts +10 -0
  96. package/dist/core/tools/write.d.ts.map +1 -1
  97. package/dist/core/tools/write.js +16 -3
  98. package/dist/core/tools/write.js.map +1 -1
  99. package/dist/index.d.ts +1 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/modes/interactive/components/custom-editor.d.ts +49 -1
  103. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  104. package/dist/modes/interactive/components/custom-editor.js +235 -1
  105. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  106. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  107. package/dist/modes/interactive/components/first-time-setup.js +2 -1
  108. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  109. package/dist/modes/interactive/components/index.d.ts +1 -0
  110. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  111. package/dist/modes/interactive/components/index.js +1 -0
  112. package/dist/modes/interactive/components/index.js.map +1 -1
  113. package/dist/modes/interactive/components/model-selector.d.ts +19 -6
  114. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  115. package/dist/modes/interactive/components/model-selector.js +172 -89
  116. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  117. package/dist/modes/interactive/components/splash-header.d.ts +41 -0
  118. package/dist/modes/interactive/components/splash-header.d.ts.map +1 -0
  119. package/dist/modes/interactive/components/splash-header.js +145 -0
  120. package/dist/modes/interactive/components/splash-header.js.map +1 -0
  121. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  122. package/dist/modes/interactive/interactive-mode.js +19 -15
  123. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  124. package/dist/modes/interactive/theme/apex.json +94 -0
  125. package/dist/modes/interactive/theme/light.json +1 -1
  126. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  127. package/dist/modes/interactive/theme/theme.js +6 -1
  128. package/dist/modes/interactive/theme/theme.js.map +1 -1
  129. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  130. package/dist/modes/rpc/rpc-mode.js +2 -1
  131. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  132. package/dist/themes/apex-logo.d.ts +21 -0
  133. package/dist/themes/apex-logo.d.ts.map +1 -0
  134. package/dist/themes/apex-logo.js +31 -0
  135. package/dist/themes/apex-logo.js.map +1 -0
  136. package/dist/utils/changelog.d.ts +7 -2
  137. package/dist/utils/changelog.d.ts.map +1 -1
  138. package/dist/utils/changelog.js +82 -13
  139. package/dist/utils/changelog.js.map +1 -1
  140. package/docs/skills.md +54 -4
  141. package/npm-shrinkwrap.json +5 -5
  142. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"project-trust.d.ts","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAIN,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,4BAA4B;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,iBAAiB,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDnG","sourcesContent":["import { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasTrustRequiringProjectResources,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasTrustRequiringProjectResources(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
1
+ {"version":3,"file":"project-trust.d.ts","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAIN,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,4BAA4B;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,iBAAiB,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDnG","sourcesContent":["import { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasTrustRequiringProjectResources,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, execute project extensions, and start configured language servers.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasTrustRequiringProjectResources(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
@@ -2,7 +2,7 @@ import { CONFIG_DIR_NAME } from "../config.js";
2
2
  import { emitProjectTrustEvent } from "./extensions/runner.js";
3
3
  import { getProjectTrustOptions, hasTrustRequiringProjectResources, } from "./trust-manager.js";
4
4
  function formatProjectTrustPrompt(cwd) {
5
- return `Trust project folder?\n${cwd}\n\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;
5
+ return `Trust project folder?\n${cwd}\n\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, execute project extensions, and start configured language servers.`;
6
6
  }
7
7
  async function selectProjectTrustOption(cwd, ctx) {
8
8
  const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
@@ -1 +1 @@
1
- {"version":3,"file":"project-trust.js","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasTrustRequiringProjectResources,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, and execute project extensions.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasTrustRequiringProjectResources(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
1
+ {"version":3,"file":"project-trust.js","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasTrustRequiringProjectResources,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Apex Code to load ${CONFIG_DIR_NAME} settings and resources, install missing project packages, execute project extensions, and start configured language servers.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasTrustRequiringProjectResources(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
@@ -18,6 +18,41 @@ export interface SandboxedCliLaunch extends SandboxLaunch {
18
18
  * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.
19
19
  */
20
20
  export declare function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined;
21
+ /**
22
+ * The host's two user-scope skill roots, each present only when it exists on the
23
+ * host. Kept as two named slots rather than one list because
24
+ * `core/package-manager.ts` discovers them in different modes -- root `.md` files
25
+ * count as skills under `agentSkills` ("pi" mode) and are ignored under
26
+ * `agentsHomeSkills` ("agents" mode), per `docs/skills.md` -- and a flat list of 0-2
27
+ * paths cannot tell the child which root a lone survivor was.
28
+ */
29
+ export interface HostSkillPaths {
30
+ /** Host `<agentDir>/skills`. */
31
+ readonly agentSkills?: string;
32
+ /** Host `<home>/.agents/skills`. */
33
+ readonly agentsHomeSkills?: string;
34
+ }
35
+ /** A candidate skill root that exists but was excluded, and why. */
36
+ export interface HostSkillPathRefusal {
37
+ readonly root: keyof HostSkillPaths;
38
+ readonly path: string;
39
+ readonly reason: string;
40
+ }
41
+ export interface ResolvedHostSkillPaths {
42
+ readonly paths: HostSkillPaths;
43
+ readonly refusals: readonly HostSkillPathRefusal[];
44
+ }
45
+ /**
46
+ * Resolve the host's user-scope skill directories, before the sandbox exists to hide
47
+ * them. Read only from the runtime environment and the host agent directory --
48
+ * never from project files -- matching ADR 0016's rule that supervisor policy is
49
+ * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two
50
+ * sides agree on where a skill lives. A candidate that resolves (directly or via a
51
+ * symlink) onto the host home or an ancestor of it is refused rather than mounted --
52
+ * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup
53
+ * diagnostic instead of silently mounting or silently skipping it.
54
+ */
55
+ export declare function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths;
21
56
  /**
22
57
  * Allocate agent-owned state under the sole writable workspace mount. The child does
23
58
  * not inherit a host home or a host session/config directory, preventing the sandbox
@@ -32,5 +67,13 @@ export declare function buildSandboxedCliLaunch(options: {
32
67
  readOnlyPaths?: readonly string[];
33
68
  authPath?: string;
34
69
  toolBinaries?: readonly HostToolBinary[];
70
+ /**
71
+ * Host user-scope skill directories, pre-filtered by the caller to those that
72
+ * exist and pass the host-home escape check (SKILL.4). Mounted read-only at their
73
+ * original host location -- Seatbelt cannot remap a path, so this must hold for
74
+ * both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its
75
+ * discovery can find them under the sandbox's own repointed `HOME`/agent dir.
76
+ */
77
+ skillPaths?: HostSkillPaths;
35
78
  }): SandboxedCliLaunch;
36
79
  //# sourceMappingURL=cli-launch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-launch.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKrD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAKvE;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAK1G;AAmGD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACzC,GAAG,kBAAkB,CA+CrB","sourcesContent":["import { mkdirSync, readdirSync, rmSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\n\nconst NON_SESSION_COMMANDS = new Set([\"auth\", \"config\", \"install\", \"remove\", \"uninstall\", \"update\", \"list\"]);\nconst METADATA_FLAGS = new Set([\"--version\", \"-v\", \"--export\", \"--list-models\"]);\n\n/**\n * OS containment is the normal startup path for every command that can construct an\n * agent session. Commands that only inspect or maintain host configuration do not\n * create a runtime and therefore remain outside this child boundary.\n */\nexport function requiresSandboxedChild(args: readonly string[]): boolean {\n\tif (NON_SESSION_COMMANDS.has(args[0] ?? \"\")) return false;\n\tif (args.some((argument) => METADATA_FLAGS.has(argument))) return false;\n\tif (args.includes(\"--help\") || args.includes(\"-h\")) return false;\n\treturn true;\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst network = SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured])];\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n}): SandboxedCliLaunch {\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = join(options.workspace, \".apex-code\", \"sandbox-sessions\");\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? [])];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\treadOnlyBinaries,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"cli-launch.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAKrD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAKvE;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAK1G;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oCAAoC;IACpC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,cAAc,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACnD;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAY/F;AAmGD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B,GAAG,kBAAkB,CAsDrB","sourcesContent":["import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\n\nconst NON_SESSION_COMMANDS = new Set([\"auth\", \"config\", \"install\", \"remove\", \"uninstall\", \"update\", \"list\"]);\nconst METADATA_FLAGS = new Set([\"--version\", \"-v\", \"--export\", \"--list-models\"]);\n\n/**\n * OS containment is the normal startup path for every command that can construct an\n * agent session. Commands that only inspect or maintain host configuration do not\n * create a runtime and therefore remain outside this child boundary.\n */\nexport function requiresSandboxedChild(args: readonly string[]): boolean {\n\tif (NON_SESSION_COMMANDS.has(args[0] ?? \"\")) return false;\n\tif (args.some((argument) => METADATA_FLAGS.has(argument))) return false;\n\tif (args.includes(\"--help\") || args.includes(\"-h\")) return false;\n\treturn true;\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst network = SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured])];\n}\n\n/**\n * The host's two user-scope skill roots, each present only when it exists on the\n * host. Kept as two named slots rather than one list because\n * `core/package-manager.ts` discovers them in different modes -- root `.md` files\n * count as skills under `agentSkills` (\"pi\" mode) and are ignored under\n * `agentsHomeSkills` (\"agents\" mode), per `docs/skills.md` -- and a flat list of 0-2\n * paths cannot tell the child which root a lone survivor was.\n */\nexport interface HostSkillPaths {\n\t/** Host `<agentDir>/skills`. */\n\treadonly agentSkills?: string;\n\t/** Host `<home>/.agents/skills`. */\n\treadonly agentsHomeSkills?: string;\n}\n\n/** A candidate skill root that exists but was excluded, and why. */\nexport interface HostSkillPathRefusal {\n\treadonly root: keyof HostSkillPaths;\n\treadonly path: string;\n\treadonly reason: string;\n}\n\nexport interface ResolvedHostSkillPaths {\n\treadonly paths: HostSkillPaths;\n\treadonly refusals: readonly HostSkillPathRefusal[];\n}\n\n/**\n * True when `candidate` is the host home directory itself, or an ancestor of it.\n * Both paths must already be resolved (`realpathSync`), so a symlink can't disguise\n * either side. Mounting such a candidate read-only would re-expose the entire home\n * tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)\n * deliberately hides -- SSH keys, cloud credentials, other projects -- not just a\n * skills subtree.\n */\nfunction isHomeOrAncestorOfHome(candidate: string, home: string): boolean {\n\tif (candidate === home) return true;\n\tconst prefix = candidate.endsWith(sep) ? candidate : candidate + sep;\n\treturn home.startsWith(prefix);\n}\n\n/** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */\nfunction resolveHostSkillRoot(\n\troot: keyof HostSkillPaths,\n\tcandidate: string,\n\thomeDir: string,\n): { path?: string; refusal?: HostSkillPathRefusal } {\n\tif (!existsSync(candidate)) return {};\n\tlet realCandidate: string;\n\tlet realHome: string;\n\ttry {\n\t\trealCandidate = realpathSync(candidate);\n\t\trealHome = realpathSync(homeDir);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };\n\t}\n\tif (isHomeOrAncestorOfHome(realCandidate, realHome)) {\n\t\treturn {\n\t\t\trefusal: {\n\t\t\t\troot,\n\t\t\t\tpath: candidate,\n\t\t\t\treason: \"resolves to the host home directory or an ancestor of it\",\n\t\t\t},\n\t\t};\n\t}\n\treturn { path: candidate };\n}\n\n/**\n * Resolve the host's user-scope skill directories, before the sandbox exists to hide\n * them. Read only from the runtime environment and the host agent directory --\n * never from project files -- matching ADR 0016's rule that supervisor policy is\n * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two\n * sides agree on where a skill lives. A candidate that resolves (directly or via a\n * symlink) onto the host home or an ancestor of it is refused rather than mounted --\n * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup\n * diagnostic instead of silently mounting or silently skipping it.\n */\nexport function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths {\n\tconst agentSkills = resolveHostSkillRoot(\"agentSkills\", join(agentDir, \"skills\"), homeDir);\n\tconst agentsHomeSkills = resolveHostSkillRoot(\"agentsHomeSkills\", join(homeDir, \".agents\", \"skills\"), homeDir);\n\treturn {\n\t\tpaths: {\n\t\t\t...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),\n\t\t\t...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),\n\t\t},\n\t\trefusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter(\n\t\t\t(refusal): refusal is HostSkillPathRefusal => refusal !== undefined,\n\t\t),\n\t};\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\t/**\n\t * Host user-scope skill directories, pre-filtered by the caller to those that\n\t * exist and pass the host-home escape check (SKILL.4). Mounted read-only at their\n\t * original host location -- Seatbelt cannot remap a path, so this must hold for\n\t * both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its\n\t * discovery can find them under the sandbox's own repointed `HOME`/agent dir.\n\t */\n\tskillPaths?: HostSkillPaths;\n}): SandboxedCliLaunch {\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = join(options.workspace, \".apex-code\", \"sandbox-sessions\");\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};\n\tconst skillMountPaths = [agentSkills, agentsHomeSkills].filter((path): path is string => path !== undefined);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\treadOnlyBinaries,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\t// After childEnvironment: these come only from the supervisor's own\n\t\t\t// resolution, never from the invoking shell, so their value always wins over\n\t\t\t// anything of the same name that childEnvironment's allowlist let through.\n\t\t\t...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),\n\t\t\t...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
2
- import { join } from "node:path";
1
+ import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
2
+ import { join, sep } from "node:path";
3
3
  import { SettingsManager } from "../settings-manager.js";
4
4
  import { resolveDefaultAllowedHosts } from "./default-hosts.js";
5
5
  const NON_SESSION_COMMANDS = new Set(["auth", "config", "install", "remove", "uninstall", "update", "list"]);
@@ -33,6 +33,66 @@ export function resolveSupervisorAllowedHosts(cwd, agentDir) {
33
33
  return configured;
34
34
  return [...new Set([...resolveDefaultAllowedHosts(), ...configured])];
35
35
  }
36
+ /**
37
+ * True when `candidate` is the host home directory itself, or an ancestor of it.
38
+ * Both paths must already be resolved (`realpathSync`), so a symlink can't disguise
39
+ * either side. Mounting such a candidate read-only would re-expose the entire home
40
+ * tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)
41
+ * deliberately hides -- SSH keys, cloud credentials, other projects -- not just a
42
+ * skills subtree.
43
+ */
44
+ function isHomeOrAncestorOfHome(candidate, home) {
45
+ if (candidate === home)
46
+ return true;
47
+ const prefix = candidate.endsWith(sep) ? candidate : candidate + sep;
48
+ return home.startsWith(prefix);
49
+ }
50
+ /** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */
51
+ function resolveHostSkillRoot(root, candidate, homeDir) {
52
+ if (!existsSync(candidate))
53
+ return {};
54
+ let realCandidate;
55
+ let realHome;
56
+ try {
57
+ realCandidate = realpathSync(candidate);
58
+ realHome = realpathSync(homeDir);
59
+ }
60
+ catch (error) {
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ return { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };
63
+ }
64
+ if (isHomeOrAncestorOfHome(realCandidate, realHome)) {
65
+ return {
66
+ refusal: {
67
+ root,
68
+ path: candidate,
69
+ reason: "resolves to the host home directory or an ancestor of it",
70
+ },
71
+ };
72
+ }
73
+ return { path: candidate };
74
+ }
75
+ /**
76
+ * Resolve the host's user-scope skill directories, before the sandbox exists to hide
77
+ * them. Read only from the runtime environment and the host agent directory --
78
+ * never from project files -- matching ADR 0016's rule that supervisor policy is
79
+ * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two
80
+ * sides agree on where a skill lives. A candidate that resolves (directly or via a
81
+ * symlink) onto the host home or an ancestor of it is refused rather than mounted --
82
+ * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup
83
+ * diagnostic instead of silently mounting or silently skipping it.
84
+ */
85
+ export function resolveHostSkillPaths(agentDir, homeDir) {
86
+ const agentSkills = resolveHostSkillRoot("agentSkills", join(agentDir, "skills"), homeDir);
87
+ const agentsHomeSkills = resolveHostSkillRoot("agentsHomeSkills", join(homeDir, ".agents", "skills"), homeDir);
88
+ return {
89
+ paths: {
90
+ ...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),
91
+ ...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),
92
+ },
93
+ refusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter((refusal) => refusal !== undefined),
94
+ };
95
+ }
36
96
  const SAFE_CHILD_ENVIRONMENT_KEYS = new Set([
37
97
  "PATH",
38
98
  "LANG",
@@ -152,7 +212,9 @@ export function buildSandboxedCliLaunch(options) {
152
212
  }
153
213
  clearStaleToolMountpoints(toolsDirectory);
154
214
  const childEnvironment = buildChildEnvironment(options.environment);
155
- const readOnlyPaths = [...(options.readOnlyPaths ?? [])];
215
+ const { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};
216
+ const skillMountPaths = [agentSkills, agentsHomeSkills].filter((path) => path !== undefined);
217
+ const readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];
156
218
  const readOnlyFiles = options.authPath ? [options.authPath] : [];
157
219
  const readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({
158
220
  source: binary.path,
@@ -168,6 +230,11 @@ export function buildSandboxedCliLaunch(options) {
168
230
  environment: {
169
231
  ...childEnvironment,
170
232
  ...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),
233
+ // After childEnvironment: these come only from the supervisor's own
234
+ // resolution, never from the invoking shell, so their value always wins over
235
+ // anything of the same name that childEnvironment's allowlist let through.
236
+ ...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),
237
+ ...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),
171
238
  APEX_CODE_CODING_AGENT_DIR: agentDirectory,
172
239
  APEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,
173
240
  HOME: stateDirectory,
@@ -1 +1 @@
1
- {"version":3,"file":"cli-launch.js","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, readdirSync, rmSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\n\nconst NON_SESSION_COMMANDS = new Set([\"auth\", \"config\", \"install\", \"remove\", \"uninstall\", \"update\", \"list\"]);\nconst METADATA_FLAGS = new Set([\"--version\", \"-v\", \"--export\", \"--list-models\"]);\n\n/**\n * OS containment is the normal startup path for every command that can construct an\n * agent session. Commands that only inspect or maintain host configuration do not\n * create a runtime and therefore remain outside this child boundary.\n */\nexport function requiresSandboxedChild(args: readonly string[]): boolean {\n\tif (NON_SESSION_COMMANDS.has(args[0] ?? \"\")) return false;\n\tif (args.some((argument) => METADATA_FLAGS.has(argument))) return false;\n\tif (args.includes(\"--help\") || args.includes(\"-h\")) return false;\n\treturn true;\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst network = SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured])];\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n}): SandboxedCliLaunch {\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = join(options.workspace, \".apex-code\", \"sandbox-sessions\");\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? [])];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\treadOnlyBinaries,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"cli-launch.js","sourceRoot":"","sources":["../../../src/core/sandbox/cli-launch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAGhE,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAC7G,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;AAEjF;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAuB,EAAW;IACxE,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACxE,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACjE,OAAO,IAAI,CAAC;AAAA,CACZ;AAMD;;;;;;;GAOG;AACH,MAAM,UAAU,6BAA6B,CAAC,GAAW,EAAE,QAAgB,EAAiC;IAC3G,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACtG,MAAM,UAAU,GAAG,OAAO,EAAE,YAAY,IAAI,EAAE,CAAC;IAC/C,IAAI,OAAO,EAAE,iBAAiB,KAAK,KAAK;QAAE,OAAO,UAAU,CAAC;IAC5D,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,0BAA0B,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;AAAA,CACtE;AA6BD;;;;;;;GAOG;AACH,SAAS,sBAAsB,CAAC,SAAiB,EAAE,IAAY,EAAW;IACzE,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC;IACrE,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AAAA,CAC/B;AAED,6FAA6F;AAC7F,SAAS,oBAAoB,CAC5B,IAA0B,EAC1B,SAAiB,EACjB,OAAe,EACqC;IACpD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,aAAqB,CAAC;IAC1B,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACJ,aAAa,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QACxC,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,gCAAgC,OAAO,EAAE,EAAE,EAAE,CAAC;IAClG,CAAC;IACD,IAAI,sBAAsB,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC;QACrD,OAAO;YACN,OAAO,EAAE;gBACR,IAAI;gBACJ,IAAI,EAAE,SAAS;gBACf,MAAM,EAAE,0DAA0D;aAClE;SACD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAAA,CAC3B;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,OAAe,EAA0B;IAChG,MAAM,WAAW,GAAG,oBAAoB,CAAC,aAAa,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3F,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,kBAAkB,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC/G,OAAO;QACN,KAAK,EAAE;YACN,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC7E;QACD,QAAQ,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,CAC/D,CAAC,OAAO,EAAmC,EAAE,CAAC,OAAO,KAAK,SAAS,CACnE;KACD,CAAC;AAAA,CACF;AAED,MAAM,2BAA2B,GAAG,IAAI,GAAG,CAAC;IAC3C,MAAM;IACN,MAAM;IACN,QAAQ;IACR,UAAU;IACV,MAAM;IACN,WAAW;IACX,UAAU;IACV,aAAa;IACb,mBAAmB;IACnB,mBAAmB;IACnB,8BAA8B;IAC9B,wBAAwB;IACxB,6BAA6B;IAC7B,kBAAkB;IAClB,2BAA2B;IAC3B,2BAA2B;IAC3B,6BAA6B;IAC7B,4BAA4B;IAC5B,QAAQ;IACR,QAAQ;CACR,CAAC,CAAC;AAEH,wFAAwF;AACxF,sFAAsF;AACtF,MAAM,6BAA6B,GAAG,IAAI,GAAG,CAAC;IAC7C,mBAAmB;IACnB,sBAAsB;IACtB,uBAAuB;IACvB,kBAAkB;IAClB,gBAAgB;IAChB,sBAAsB;IACtB,uBAAuB;IACvB,4BAA4B;IAC5B,0BAA0B;IAC1B,kCAAkC;IAClC,kBAAkB;IAClB,gBAAgB;IAChB,gBAAgB;IAChB,cAAc;IACd,kBAAkB;IAClB,aAAa;IACb,mBAAmB;IACnB,kBAAkB;IAClB,iBAAiB;IACjB,oBAAoB;IACpB,oBAAoB;IACpB,aAAa;IACb,uBAAuB;IACvB,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,cAAc;IACd,oBAAoB;IACpB,uBAAuB;IACvB,uBAAuB;IACvB,yBAAyB;IACzB,4BAA4B;IAC5B,gBAAgB;IAChB,8BAA8B;IAC9B,+BAA+B;IAC/B,+BAA+B;IAC/B,aAAa;IACb,mBAAmB;IACnB,uBAAuB;IACvB,0BAA0B;IAC1B,YAAY;CACZ,CAAC,CAAC;AAEH,SAAS,qBAAqB,CAAC,WAA8B,EAAqB;IACjF,OAAO,MAAM,CAAC,WAAW,CACxB,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,MAAM,CACjC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,6BAA6B,CAAC,GAAG,CAAC,GAAG,CAAC,CACzF,CACD,CAAC;AAAA,CACF;AAED;;;;;;;;GAQG;AACH,SAAS,yBAAyB,CAAC,cAAsB,EAAQ;IAChE,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC1E,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,SAAS;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC;IACF,CAAC;AAAA,CACD;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAiBvC,EAAsB;IACtB,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;IAC9E,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;IAC9E,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,kBAAkB,CAAC,CAAC;IACnF,oFAAoF;IACpF,iFAAiF;IACjF,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACnD,MAAM,cAAc,GAAG;QACtB,eAAe,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC;QAC/C,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;QAC7C,aAAa,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC;QAC3C,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC;KAC7C,CAAC;IACF,KAAK,MAAM,SAAS,IAAI;QACvB,cAAc;QACd,cAAc;QACd,gBAAgB;QAChB,cAAc;QACd,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC;KAChC,EAAE,CAAC;QACH,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,yBAAyB,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACpE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;IACnE,MAAM,eAAe,GAAG,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAC7G,MAAM,aAAa,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,EAAE,GAAG,eAAe,CAAC,CAAC;IAC7E,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,MAAM,gBAAgB,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,EAAE,MAAM,CAAC,IAAI;QACnB,WAAW,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC;KAC9C,CAAC,CAAC,CAAC;IACJ,OAAO;QACN,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;QACvB,MAAM,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE,EAAE;QAClF,aAAa;QACb,aAAa;QACb,gBAAgB;QAChB,WAAW,EAAE;YACZ,GAAG,gBAAgB;YACnB,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,oEAAoE;YACpE,6EAA6E;YAC7E,2EAA2E;YAC3E,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,0BAA0B,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnF,0BAA0B,EAAE,cAAc;YAC1C,kCAAkC,EAAE,gBAAgB;YACpD,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,cAAc;YACtB,GAAG,cAAc;SACjB;KACD,CAAC;AAAA,CACF","sourcesContent":["import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { SettingsManager } from \"../settings-manager.ts\";\nimport { resolveDefaultAllowedHosts } from \"./default-hosts.ts\";\nimport type { SandboxLaunch } from \"./supervisor.ts\";\n\nconst NON_SESSION_COMMANDS = new Set([\"auth\", \"config\", \"install\", \"remove\", \"uninstall\", \"update\", \"list\"]);\nconst METADATA_FLAGS = new Set([\"--version\", \"-v\", \"--export\", \"--list-models\"]);\n\n/**\n * OS containment is the normal startup path for every command that can construct an\n * agent session. Commands that only inspect or maintain host configuration do not\n * create a runtime and therefore remain outside this child boundary.\n */\nexport function requiresSandboxedChild(args: readonly string[]): boolean {\n\tif (NON_SESSION_COMMANDS.has(args[0] ?? \"\")) return false;\n\tif (args.some((argument) => METADATA_FLAGS.has(argument))) return false;\n\tif (args.includes(\"--help\") || args.includes(\"-h\")) return false;\n\treturn true;\n}\n\nexport interface SandboxedCliLaunch extends SandboxLaunch {\n\treadonly environment: NodeJS.ProcessEnv;\n}\n\n/**\n * Read supervisor policy only from global settings; project settings are untrusted here.\n *\n * The built-in provider hosts are added unless explicitly refused, because a deny-all\n * default made a fresh install unable to reach any model while giving the user no way to\n * learn which host to permit. Configured hosts are additive on top, and\n * `allowDefaultHosts: false` restores the strict behaviour for anyone who wants it.\n */\nexport function resolveSupervisorAllowedHosts(cwd: string, agentDir: string): readonly string[] | undefined {\n\tconst network = SettingsManager.create(cwd, agentDir, { projectTrusted: false }).getNetworkSettings();\n\tconst configured = network?.allowedHosts ?? [];\n\tif (network?.allowDefaultHosts === false) return configured;\n\treturn [...new Set([...resolveDefaultAllowedHosts(), ...configured])];\n}\n\n/**\n * The host's two user-scope skill roots, each present only when it exists on the\n * host. Kept as two named slots rather than one list because\n * `core/package-manager.ts` discovers them in different modes -- root `.md` files\n * count as skills under `agentSkills` (\"pi\" mode) and are ignored under\n * `agentsHomeSkills` (\"agents\" mode), per `docs/skills.md` -- and a flat list of 0-2\n * paths cannot tell the child which root a lone survivor was.\n */\nexport interface HostSkillPaths {\n\t/** Host `<agentDir>/skills`. */\n\treadonly agentSkills?: string;\n\t/** Host `<home>/.agents/skills`. */\n\treadonly agentsHomeSkills?: string;\n}\n\n/** A candidate skill root that exists but was excluded, and why. */\nexport interface HostSkillPathRefusal {\n\treadonly root: keyof HostSkillPaths;\n\treadonly path: string;\n\treadonly reason: string;\n}\n\nexport interface ResolvedHostSkillPaths {\n\treadonly paths: HostSkillPaths;\n\treadonly refusals: readonly HostSkillPathRefusal[];\n}\n\n/**\n * True when `candidate` is the host home directory itself, or an ancestor of it.\n * Both paths must already be resolved (`realpathSync`), so a symlink can't disguise\n * either side. Mounting such a candidate read-only would re-expose the entire home\n * tree the sandbox's `--tmpfs /home` (Linux) / `(deny file-read* USER_HOME)` (macOS)\n * deliberately hides -- SSH keys, cloud credentials, other projects -- not just a\n * skills subtree.\n */\nfunction isHomeOrAncestorOfHome(candidate: string, home: string): boolean {\n\tif (candidate === home) return true;\n\tconst prefix = candidate.endsWith(sep) ? candidate : candidate + sep;\n\treturn home.startsWith(prefix);\n}\n\n/** Resolve one candidate root: absent, refused (symlinked onto the host home), or usable. */\nfunction resolveHostSkillRoot(\n\troot: keyof HostSkillPaths,\n\tcandidate: string,\n\thomeDir: string,\n): { path?: string; refusal?: HostSkillPathRefusal } {\n\tif (!existsSync(candidate)) return {};\n\tlet realCandidate: string;\n\tlet realHome: string;\n\ttry {\n\t\trealCandidate = realpathSync(candidate);\n\t\trealHome = realpathSync(homeDir);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn { refusal: { root, path: candidate, reason: `could not resolve real path: ${message}` } };\n\t}\n\tif (isHomeOrAncestorOfHome(realCandidate, realHome)) {\n\t\treturn {\n\t\t\trefusal: {\n\t\t\t\troot,\n\t\t\t\tpath: candidate,\n\t\t\t\treason: \"resolves to the host home directory or an ancestor of it\",\n\t\t\t},\n\t\t};\n\t}\n\treturn { path: candidate };\n}\n\n/**\n * Resolve the host's user-scope skill directories, before the sandbox exists to hide\n * them. Read only from the runtime environment and the host agent directory --\n * never from project files -- matching ADR 0016's rule that supervisor policy is\n * trust-first. Mirrors `core/package-manager.ts`'s own user-scope roots so the two\n * sides agree on where a skill lives. A candidate that resolves (directly or via a\n * symlink) onto the host home or an ancestor of it is refused rather than mounted --\n * see `isHomeOrAncestorOfHome` -- and reported so the caller can surface a startup\n * diagnostic instead of silently mounting or silently skipping it.\n */\nexport function resolveHostSkillPaths(agentDir: string, homeDir: string): ResolvedHostSkillPaths {\n\tconst agentSkills = resolveHostSkillRoot(\"agentSkills\", join(agentDir, \"skills\"), homeDir);\n\tconst agentsHomeSkills = resolveHostSkillRoot(\"agentsHomeSkills\", join(homeDir, \".agents\", \"skills\"), homeDir);\n\treturn {\n\t\tpaths: {\n\t\t\t...(agentSkills.path ? { agentSkills: agentSkills.path } : {}),\n\t\t\t...(agentsHomeSkills.path ? { agentsHomeSkills: agentsHomeSkills.path } : {}),\n\t\t},\n\t\trefusals: [agentSkills.refusal, agentsHomeSkills.refusal].filter(\n\t\t\t(refusal): refusal is HostSkillPathRefusal => refusal !== undefined,\n\t\t),\n\t};\n}\n\nconst SAFE_CHILD_ENVIRONMENT_KEYS = new Set([\n\t\"PATH\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"LC_CTYPE\",\n\t\"TERM\",\n\t\"COLORTERM\",\n\t\"NO_COLOR\",\n\t\"FORCE_COLOR\",\n\t\"TSX_TSCONFIG_PATH\",\n\t\"APEX_CODE_OFFLINE\",\n\t\"APEX_CODE_SKIP_VERSION_CHECK\",\n\t\"APEX_CODE_EXPERIMENTAL\",\n\t\"APEX_CODE_STARTUP_BENCHMARK\",\n\t\"APEX_CODE_TIMING\",\n\t\"APEX_CODE_CLEAR_ON_SHRINK\",\n\t\"APEX_CODE_HARDWARE_CURSOR\",\n\t\"APEX_CODE_MODEL_CATALOG_URL\",\n\t\"APEX_CODE_SHARE_VIEWER_URL\",\n\t\"VISUAL\",\n\t\"EDITOR\",\n]);\n\n// Provider API keys are explicit credential inputs, unlike arbitrary ambient variables.\n// Keep this list in sync with the documented provider environment-variable reference.\nconst SAFE_PROVIDER_CREDENTIAL_KEYS = new Set([\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANT_LING_API_KEY\",\n\t\"OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_API_KEY\",\n\t\"AZURE_OPENAI_BASE_URL\",\n\t\"AZURE_OPENAI_RESOURCE_NAME\",\n\t\"AZURE_OPENAI_API_VERSION\",\n\t\"AZURE_OPENAI_DEPLOYMENT_NAME_MAP\",\n\t\"DEEPSEEK_API_KEY\",\n\t\"NVIDIA_API_KEY\",\n\t\"GEMINI_API_KEY\",\n\t\"GROQ_API_KEY\",\n\t\"CEREBRAS_API_KEY\",\n\t\"XAI_API_KEY\",\n\t\"FIREWORKS_API_KEY\",\n\t\"TOGETHER_API_KEY\",\n\t\"BASETEN_API_KEY\",\n\t\"OPENROUTER_API_KEY\",\n\t\"AI_GATEWAY_API_KEY\",\n\t\"ZAI_API_KEY\",\n\t\"ZAI_CODING_CN_API_KEY\",\n\t\"MISTRAL_API_KEY\",\n\t\"MINIMAX_API_KEY\",\n\t\"MOONSHOT_API_KEY\",\n\t\"OPENCODE_API_KEY\",\n\t\"KIMI_API_KEY\",\n\t\"CLOUDFLARE_API_KEY\",\n\t\"CLOUDFLARE_ACCOUNT_ID\",\n\t\"CLOUDFLARE_GATEWAY_ID\",\n\t\"QWEN_TOKEN_PLAN_API_KEY\",\n\t\"QWEN_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_CN_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_AMS_API_KEY\",\n\t\"XIAOMI_TOKEN_PLAN_SGP_API_KEY\",\n\t\"AWS_PROFILE\",\n\t\"AWS_ACCESS_KEY_ID\",\n\t\"AWS_SECRET_ACCESS_KEY\",\n\t\"AWS_BEARER_TOKEN_BEDROCK\",\n\t\"AWS_REGION\",\n]);\n\nfunction buildChildEnvironment(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n\treturn Object.fromEntries(\n\t\tObject.entries(environment).filter(\n\t\t\t([key]) => SAFE_CHILD_ENVIRONMENT_KEYS.has(key) || SAFE_PROVIDER_CREDENTIAL_KEYS.has(key),\n\t\t),\n\t);\n}\n\n/**\n * Drop empty files left in the child's tools directory by a previous launch.\n *\n * A projected tool is bind-mounted over a file there, and bwrap materialises that\n * mountpoint as an empty file on the host which outlives the namespace. If the host\n * tool later disappears, nothing is projected over the stub and the child would\n * otherwise find a 0-byte file where its binary should be. A real downloaded binary\n * is never empty, so size is a safe discriminator.\n */\nfunction clearStaleToolMountpoints(toolsDirectory: string): void {\n\tfor (const entry of readdirSync(toolsDirectory, { withFileTypes: true })) {\n\t\tif (!entry.isFile()) continue;\n\t\tconst entryPath = join(toolsDirectory, entry.name);\n\t\tif (statSync(entryPath).size === 0) {\n\t\t\trmSync(entryPath, { force: true });\n\t\t}\n\t}\n}\n\n/**\n * Allocate agent-owned state under the sole writable workspace mount. The child does\n * not inherit a host home or a host session/config directory, preventing the sandbox\n * from presenting a write boundary while its own state quietly escapes it.\n */\nexport function buildSandboxedCliLaunch(options: {\n\tworkspace: string;\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\t/**\n\t * Host user-scope skill directories, pre-filtered by the caller to those that\n\t * exist and pass the host-home escape check (SKILL.4). Mounted read-only at their\n\t * original host location -- Seatbelt cannot remap a path, so this must hold for\n\t * both backends -- and named to the child via `APEX_CODE_SKILL_PATH_*` so its\n\t * discovery can find them under the sandbox's own repointed `HOME`/agent dir.\n\t */\n\tskillPaths?: HostSkillPaths;\n}): SandboxedCliLaunch {\n\tconst stateDirectory = join(options.workspace, \".apex-code\", \"sandbox-state\");\n\tconst agentDirectory = join(options.workspace, \".apex-code\", \"sandbox-agent\");\n\tconst sessionDirectory = join(options.workspace, \".apex-code\", \"sandbox-sessions\");\n\t// Mirrors getBinDir() as the child will compute it from APEX_CODE_CODING_AGENT_DIR,\n\t// so a projected tool lands exactly where the child's own lookup already checks.\n\tconst toolsDirectory = join(agentDirectory, \"bin\");\n\tconst xdgDirectories = {\n\t\tXDG_CONFIG_HOME: join(stateDirectory, \"config\"),\n\t\tXDG_CACHE_HOME: join(stateDirectory, \"cache\"),\n\t\tXDG_DATA_HOME: join(stateDirectory, \"data\"),\n\t\tXDG_STATE_HOME: join(stateDirectory, \"state\"),\n\t};\n\tfor (const directory of [\n\t\tstateDirectory,\n\t\tagentDirectory,\n\t\tsessionDirectory,\n\t\ttoolsDirectory,\n\t\t...Object.values(xdgDirectories),\n\t]) {\n\t\tmkdirSync(directory, { recursive: true });\n\t}\n\tclearStaleToolMountpoints(toolsDirectory);\n\tconst childEnvironment = buildChildEnvironment(options.environment);\n\tconst { agentSkills, agentsHomeSkills } = options.skillPaths ?? {};\n\tconst skillMountPaths = [agentSkills, agentsHomeSkills].filter((path): path is string => path !== undefined);\n\tconst readOnlyPaths = [...(options.readOnlyPaths ?? []), ...skillMountPaths];\n\tconst readOnlyFiles = options.authPath ? [options.authPath] : [];\n\tconst readOnlyBinaries = (options.toolBinaries ?? []).map((binary) => ({\n\t\tsource: binary.path,\n\t\tdestination: join(toolsDirectory, binary.name),\n\t}));\n\treturn {\n\t\tcommand: options.command,\n\t\targs: [...options.args],\n\t\tpolicy: { workspace: options.workspace, allowedHosts: options.allowedHosts ?? [] },\n\t\treadOnlyPaths,\n\t\treadOnlyFiles,\n\t\treadOnlyBinaries,\n\t\tenvironment: {\n\t\t\t...childEnvironment,\n\t\t\t...(options.authPath ? { APEX_CODE_AUTH_PATH: options.authPath } : {}),\n\t\t\t// After childEnvironment: these come only from the supervisor's own\n\t\t\t// resolution, never from the invoking shell, so their value always wins over\n\t\t\t// anything of the same name that childEnvironment's allowlist let through.\n\t\t\t...(agentSkills ? { APEX_CODE_SKILL_PATH_AGENT: agentSkills } : {}),\n\t\t\t...(agentsHomeSkills ? { APEX_CODE_SKILL_PATH_AGENTS_HOME: agentsHomeSkills } : {}),\n\t\t\tAPEX_CODE_CODING_AGENT_DIR: agentDirectory,\n\t\t\tAPEX_CODE_CODING_AGENT_SESSION_DIR: sessionDirectory,\n\t\t\tHOME: stateDirectory,\n\t\t\tTMPDIR: stateDirectory,\n\t\t\t...xdgDirectories,\n\t\t},\n\t};\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { HostToolBinary } from "../../utils/tools-manager.ts";
2
+ import { type HostSkillPaths } from "./cli-launch.ts";
2
3
  import { type SandboxBackend } from "./supervisor.ts";
3
4
  import { SandboxViolationStore } from "./violations.ts";
4
5
  export interface CliSandboxDependencies {
@@ -22,6 +23,7 @@ export declare function launchSandboxedCli(options: {
22
23
  readOnlyPaths?: readonly string[];
23
24
  authPath?: string;
24
25
  toolBinaries?: readonly HostToolBinary[];
26
+ skillPaths?: HostSkillPaths;
25
27
  dependencies?: Partial<CliSandboxDependencies>;
26
28
  }): Promise<number>;
27
29
  //# sourceMappingURL=cli-supervisor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-supervisor.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAKnE,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,cAAc,EAAE,qBAAqB,CAAA;KAAE,KAAK,cAAc,CAAC;IACtF,MAAM,EAAE;QAAE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAC5C;AAeD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC,YAAY,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC/C,GAAG,OAAO,CAAC,MAAM,CAAC,CAkClB","sourcesContent":["import type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { buildSandboxedCliLaunch } from \"./cli-launch.ts\";\nimport { createLinuxSandboxBackend } from \"./linux-backend.ts\";\nimport { createMacosSandboxBackend } from \"./macos-backend.ts\";\nimport { createSandboxPolicy } from \"./policy.ts\";\nimport { createSandboxSupervisor, type SandboxBackend } from \"./supervisor.ts\";\nimport { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface CliSandboxDependencies {\n\tcreateBackend: (options: { violationStore: SandboxViolationStore }) => SandboxBackend;\n\tstderr: { write(message: string): boolean };\n}\n\n/** Routes to the platform adapter for the running OS; each adapter self-reports\n * `unavailable` on any other platform, so this only needs to pick the one whose\n * enforcement is actually reachable here. */\nfunction createDefaultSandboxBackend(options: { violationStore: SandboxViolationStore }): SandboxBackend {\n\tif (process.platform === \"darwin\") return createMacosSandboxBackend(options);\n\treturn createLinuxSandboxBackend(options);\n}\n\nconst defaultDependencies: CliSandboxDependencies = {\n\tcreateBackend: createDefaultSandboxBackend,\n\tstderr: process.stderr,\n};\n\n/**\n * Start a normal CLI runtime beneath the whole-process boundary. The caller returns\n * the result directly so the outer process never continues into `main()` afterward.\n */\nexport async function launchSandboxedCli(options: {\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\tdependencies?: Partial<CliSandboxDependencies>;\n}): Promise<number> {\n\tconst dependencies = { ...defaultDependencies, ...options.dependencies };\n\tconst policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });\n\tif (policyResult.kind === \"invalid\") {\n\t\tdependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\\n`);\n\t\treturn 1;\n\t}\n\tconst violationStore = new SandboxViolationStore();\n\tconst backend = dependencies.createBackend({ violationStore });\n\tconst supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });\n\tconst launch = buildSandboxedCliLaunch({\n\t\tworkspace: policyResult.policy.workspace,\n\t\tcommand: options.command,\n\t\targs: options.args,\n\t\tenvironment: options.environment,\n\t\tallowedHosts: options.allowedHosts,\n\t\treadOnlyPaths: options.readOnlyPaths,\n\t\tauthPath: options.authPath,\n\t\ttoolBinaries: options.toolBinaries,\n\t});\n\ttry {\n\t\treturn await supervisor.launch(launch);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Failed to start OS sandbox.\";\n\t\tdependencies.stderr.write(`Error: ${message}\\n`);\n\t\treturn 1;\n\t} finally {\n\t\tfor (const violation of violationStore.list()) {\n\t\t\tdependencies.stderr.write(\n\t\t\t\t`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\\n`,\n\t\t\t);\n\t\t}\n\t\tawait supervisor.close();\n\t}\n}\n"]}
1
+ {"version":3,"file":"cli-supervisor.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/cli-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAI/E,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,sBAAsB;IACtC,aAAa,EAAE,CAAC,OAAO,EAAE;QAAE,cAAc,EAAE,qBAAqB,CAAA;KAAE,KAAK,cAAc,CAAC;IACtF,MAAM,EAAE;QAAE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAC5C;AAeD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACzC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC/C,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClB","sourcesContent":["import type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { buildSandboxedCliLaunch, type HostSkillPaths } from \"./cli-launch.ts\";\nimport { createLinuxSandboxBackend } from \"./linux-backend.ts\";\nimport { createMacosSandboxBackend } from \"./macos-backend.ts\";\nimport { createSandboxPolicy } from \"./policy.ts\";\nimport { createSandboxSupervisor, type SandboxBackend } from \"./supervisor.ts\";\nimport { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface CliSandboxDependencies {\n\tcreateBackend: (options: { violationStore: SandboxViolationStore }) => SandboxBackend;\n\tstderr: { write(message: string): boolean };\n}\n\n/** Routes to the platform adapter for the running OS; each adapter self-reports\n * `unavailable` on any other platform, so this only needs to pick the one whose\n * enforcement is actually reachable here. */\nfunction createDefaultSandboxBackend(options: { violationStore: SandboxViolationStore }): SandboxBackend {\n\tif (process.platform === \"darwin\") return createMacosSandboxBackend(options);\n\treturn createLinuxSandboxBackend(options);\n}\n\nconst defaultDependencies: CliSandboxDependencies = {\n\tcreateBackend: createDefaultSandboxBackend,\n\tstderr: process.stderr,\n};\n\n/**\n * Start a normal CLI runtime beneath the whole-process boundary. The caller returns\n * the result directly so the outer process never continues into `main()` afterward.\n */\nexport async function launchSandboxedCli(options: {\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\tskillPaths?: HostSkillPaths;\n\tdependencies?: Partial<CliSandboxDependencies>;\n}): Promise<number> {\n\tconst dependencies = { ...defaultDependencies, ...options.dependencies };\n\tconst policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });\n\tif (policyResult.kind === \"invalid\") {\n\t\tdependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\\n`);\n\t\treturn 1;\n\t}\n\tconst violationStore = new SandboxViolationStore();\n\tconst backend = dependencies.createBackend({ violationStore });\n\tconst supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });\n\tconst launch = buildSandboxedCliLaunch({\n\t\tworkspace: policyResult.policy.workspace,\n\t\tcommand: options.command,\n\t\targs: options.args,\n\t\tenvironment: options.environment,\n\t\tallowedHosts: options.allowedHosts,\n\t\treadOnlyPaths: options.readOnlyPaths,\n\t\tauthPath: options.authPath,\n\t\ttoolBinaries: options.toolBinaries,\n\t\tskillPaths: options.skillPaths,\n\t});\n\ttry {\n\t\treturn await supervisor.launch(launch);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Failed to start OS sandbox.\";\n\t\tdependencies.stderr.write(`Error: ${message}\\n`);\n\t\treturn 1;\n\t} finally {\n\t\tfor (const violation of violationStore.list()) {\n\t\t\tdependencies.stderr.write(\n\t\t\t\t`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\\n`,\n\t\t\t);\n\t\t}\n\t\tawait supervisor.close();\n\t}\n}\n"]}
@@ -39,6 +39,7 @@ export async function launchSandboxedCli(options) {
39
39
  readOnlyPaths: options.readOnlyPaths,
40
40
  authPath: options.authPath,
41
41
  toolBinaries: options.toolBinaries,
42
+ skillPaths: options.skillPaths,
42
43
  });
43
44
  try {
44
45
  return await supervisor.launch(launch);
@@ -1 +1 @@
1
- {"version":3,"file":"cli-supervisor.js","sourceRoot":"","sources":["../../../src/core/sandbox/cli-supervisor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAuB,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAOxD;;6CAE6C;AAC7C,SAAS,2BAA2B,CAAC,OAAkD,EAAkB;IACxG,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC7E,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAAA,CAC1C;AAED,MAAM,mBAAmB,GAA2B;IACnD,aAAa,EAAE,2BAA2B;IAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAUxC,EAAmB;IACnB,MAAM,YAAY,GAAG,EAAE,GAAG,mBAAmB,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IACzE,MAAM,YAAY,GAAG,mBAAmB,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/G,IAAI,YAAY,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,YAAY,CAAC,MAAM,IAAI,CAAC,CAAC;QAC7G,OAAO,CAAC,CAAC;IACV,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,qBAAqB,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,uBAAuB,CAAC;QACtC,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,SAAS;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;KAClC,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;QACvF,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;QACjD,OAAO,CAAC,CAAC;IACV,CAAC;YAAS,CAAC;QACV,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/C,YAAY,CAAC,MAAM,CAAC,KAAK,CACxB,sBAAsB,SAAS,CAAC,IAAI,MAAM,SAAS,CAAC,OAAO,QAAM,SAAS,CAAC,MAAM,IAAI,CACrF,CAAC;QACH,CAAC;QACD,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;AAAA,CACD","sourcesContent":["import type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { buildSandboxedCliLaunch } from \"./cli-launch.ts\";\nimport { createLinuxSandboxBackend } from \"./linux-backend.ts\";\nimport { createMacosSandboxBackend } from \"./macos-backend.ts\";\nimport { createSandboxPolicy } from \"./policy.ts\";\nimport { createSandboxSupervisor, type SandboxBackend } from \"./supervisor.ts\";\nimport { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface CliSandboxDependencies {\n\tcreateBackend: (options: { violationStore: SandboxViolationStore }) => SandboxBackend;\n\tstderr: { write(message: string): boolean };\n}\n\n/** Routes to the platform adapter for the running OS; each adapter self-reports\n * `unavailable` on any other platform, so this only needs to pick the one whose\n * enforcement is actually reachable here. */\nfunction createDefaultSandboxBackend(options: { violationStore: SandboxViolationStore }): SandboxBackend {\n\tif (process.platform === \"darwin\") return createMacosSandboxBackend(options);\n\treturn createLinuxSandboxBackend(options);\n}\n\nconst defaultDependencies: CliSandboxDependencies = {\n\tcreateBackend: createDefaultSandboxBackend,\n\tstderr: process.stderr,\n};\n\n/**\n * Start a normal CLI runtime beneath the whole-process boundary. The caller returns\n * the result directly so the outer process never continues into `main()` afterward.\n */\nexport async function launchSandboxedCli(options: {\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\tdependencies?: Partial<CliSandboxDependencies>;\n}): Promise<number> {\n\tconst dependencies = { ...defaultDependencies, ...options.dependencies };\n\tconst policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });\n\tif (policyResult.kind === \"invalid\") {\n\t\tdependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\\n`);\n\t\treturn 1;\n\t}\n\tconst violationStore = new SandboxViolationStore();\n\tconst backend = dependencies.createBackend({ violationStore });\n\tconst supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });\n\tconst launch = buildSandboxedCliLaunch({\n\t\tworkspace: policyResult.policy.workspace,\n\t\tcommand: options.command,\n\t\targs: options.args,\n\t\tenvironment: options.environment,\n\t\tallowedHosts: options.allowedHosts,\n\t\treadOnlyPaths: options.readOnlyPaths,\n\t\tauthPath: options.authPath,\n\t\ttoolBinaries: options.toolBinaries,\n\t});\n\ttry {\n\t\treturn await supervisor.launch(launch);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Failed to start OS sandbox.\";\n\t\tdependencies.stderr.write(`Error: ${message}\\n`);\n\t\treturn 1;\n\t} finally {\n\t\tfor (const violation of violationStore.list()) {\n\t\t\tdependencies.stderr.write(\n\t\t\t\t`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\\n`,\n\t\t\t);\n\t\t}\n\t\tawait supervisor.close();\n\t}\n}\n"]}
1
+ {"version":3,"file":"cli-supervisor.js","sourceRoot":"","sources":["../../../src/core/sandbox/cli-supervisor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAuB,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAuB,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAOxD;;6CAE6C;AAC7C,SAAS,2BAA2B,CAAC,OAAkD,EAAkB;IACxG,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC7E,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAAA,CAC1C;AAED,MAAM,mBAAmB,GAA2B;IACnD,aAAa,EAAE,2BAA2B;IAC1C,MAAM,EAAE,OAAO,CAAC,MAAM;CACtB,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAWxC,EAAmB;IACnB,MAAM,YAAY,GAAG,EAAE,GAAG,mBAAmB,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IACzE,MAAM,YAAY,GAAG,mBAAmB,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/G,IAAI,YAAY,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACrC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,YAAY,CAAC,MAAM,IAAI,CAAC,CAAC;QAC7G,OAAO,CAAC,CAAC;IACV,CAAC;IACD,MAAM,cAAc,GAAG,IAAI,qBAAqB,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,uBAAuB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,uBAAuB,CAAC;QACtC,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,SAAS;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,UAAU,EAAE,OAAO,CAAC,UAAU;KAC9B,CAAC,CAAC;IACH,IAAI,CAAC;QACJ,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC;QACvF,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,IAAI,CAAC,CAAC;QACjD,OAAO,CAAC,CAAC;IACV,CAAC;YAAS,CAAC;QACV,KAAK,MAAM,SAAS,IAAI,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/C,YAAY,CAAC,MAAM,CAAC,KAAK,CACxB,sBAAsB,SAAS,CAAC,IAAI,MAAM,SAAS,CAAC,OAAO,QAAM,SAAS,CAAC,MAAM,IAAI,CACrF,CAAC;QACH,CAAC;QACD,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;AAAA,CACD","sourcesContent":["import type { HostToolBinary } from \"../../utils/tools-manager.ts\";\nimport { buildSandboxedCliLaunch, type HostSkillPaths } from \"./cli-launch.ts\";\nimport { createLinuxSandboxBackend } from \"./linux-backend.ts\";\nimport { createMacosSandboxBackend } from \"./macos-backend.ts\";\nimport { createSandboxPolicy } from \"./policy.ts\";\nimport { createSandboxSupervisor, type SandboxBackend } from \"./supervisor.ts\";\nimport { SandboxViolationStore } from \"./violations.ts\";\n\nexport interface CliSandboxDependencies {\n\tcreateBackend: (options: { violationStore: SandboxViolationStore }) => SandboxBackend;\n\tstderr: { write(message: string): boolean };\n}\n\n/** Routes to the platform adapter for the running OS; each adapter self-reports\n * `unavailable` on any other platform, so this only needs to pick the one whose\n * enforcement is actually reachable here. */\nfunction createDefaultSandboxBackend(options: { violationStore: SandboxViolationStore }): SandboxBackend {\n\tif (process.platform === \"darwin\") return createMacosSandboxBackend(options);\n\treturn createLinuxSandboxBackend(options);\n}\n\nconst defaultDependencies: CliSandboxDependencies = {\n\tcreateBackend: createDefaultSandboxBackend,\n\tstderr: process.stderr,\n};\n\n/**\n * Start a normal CLI runtime beneath the whole-process boundary. The caller returns\n * the result directly so the outer process never continues into `main()` afterward.\n */\nexport async function launchSandboxedCli(options: {\n\tcommand: string;\n\targs: readonly string[];\n\tenvironment: NodeJS.ProcessEnv;\n\tworkspace: string;\n\tallowedHosts?: readonly string[];\n\treadOnlyPaths?: readonly string[];\n\tauthPath?: string;\n\ttoolBinaries?: readonly HostToolBinary[];\n\tskillPaths?: HostSkillPaths;\n\tdependencies?: Partial<CliSandboxDependencies>;\n}): Promise<number> {\n\tconst dependencies = { ...defaultDependencies, ...options.dependencies };\n\tconst policyResult = createSandboxPolicy({ workspace: options.workspace, allowedHosts: options.allowedHosts });\n\tif (policyResult.kind === \"invalid\") {\n\t\tdependencies.stderr.write(`Error: OS sandbox is not enforcing this agent session: ${policyResult.reason}\\n`);\n\t\treturn 1;\n\t}\n\tconst violationStore = new SandboxViolationStore();\n\tconst backend = dependencies.createBackend({ violationStore });\n\tconst supervisor = createSandboxSupervisor({ backend, policy: policyResult.policy });\n\tconst launch = buildSandboxedCliLaunch({\n\t\tworkspace: policyResult.policy.workspace,\n\t\tcommand: options.command,\n\t\targs: options.args,\n\t\tenvironment: options.environment,\n\t\tallowedHosts: options.allowedHosts,\n\t\treadOnlyPaths: options.readOnlyPaths,\n\t\tauthPath: options.authPath,\n\t\ttoolBinaries: options.toolBinaries,\n\t\tskillPaths: options.skillPaths,\n\t});\n\ttry {\n\t\treturn await supervisor.launch(launch);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Failed to start OS sandbox.\";\n\t\tdependencies.stderr.write(`Error: ${message}\\n`);\n\t\treturn 1;\n\t} finally {\n\t\tfor (const violation of violationStore.list()) {\n\t\t\tdependencies.stderr.write(\n\t\t\t\t`Sandbox violation (${violation.kind}): ${violation.command} — ${violation.detail}\\n`,\n\t\t\t);\n\t\t}\n\t\tawait supervisor.close();\n\t}\n}\n"]}
@@ -57,6 +57,10 @@ export interface CreateAgentSessionOptions {
57
57
  permissionGate?: AgentSessionConfig["permissionGate"];
58
58
  /** Optional destination for source-level evidence; defaults to the durable session sink. */
59
59
  evidenceSink?: EvidenceSink;
60
+ /** Optional post-mutation diagnostics injected into `edit`/`write` (LSP.4). */
61
+ diagnosticsOperations?: AgentSessionConfig["diagnosticsOperations"];
62
+ /** Optional `lsp` navigation tool transport (LSP.5); registers and activates `lsp` when present. */
63
+ lspOperations?: AgentSessionConfig["lspOperations"];
60
64
  /**
61
65
  * Delegation entry point (roadmap Phase 5, ADR 0008). When set, `delegate`
62
66
  * executes real child sessions through `resolveAgent`, with the child's
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"apex-code-agent-core\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type AgentSessionConfig } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createAgentDefinitionResolver } from \"./delegation/agents.ts\";\nimport type { AgentDefinitionResolver, DelegationRuntimeOptions } from \"./delegation/runtime.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { DerivedPermissionRuleStore } from \"./permissions/store.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport type { ApexToolDefinition, Capability, EvidenceSink } from \"./tools/contract.ts\";\nimport {\n\tcreateAllToolDefinitions,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateDelegateToolDefinition,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.apex-code/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/** Authorization configuration for every registered tool call. */\n\tpermissionGate?: AgentSessionConfig[\"permissionGate\"];\n\t/** Optional destination for source-level evidence; defaults to the durable session sink. */\n\tevidenceSink?: EvidenceSink;\n\t/**\n\t * Delegation entry point (roadmap Phase 5, ADR 0008). When set, `delegate`\n\t * executes real child sessions through `resolveAgent`, with the child's\n\t * authority derived from this session's live active tools and permission\n\t * store -- never reconstructed. When omitted, `delegate` stays inert (no agent\n\t * types resolve). Agent discovery is injected here as a caller-supplied\n\t * resolver rather than built in, until Phase 5 task 5.5 lands real\n\t * markdown/frontmatter discovery. Requires `permissionGate` to be set too: a\n\t * child's authority has nothing to derive from otherwise.\n\t */\n\tdelegation?: { resolveAgent?: AgentDefinitionResolver };\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\n/** The most recent assistant text in a completed session -- what a delegated child returns to its parent as its result. */\nfunction extractFinalAssistantText(session: AgentSession): string {\n\tconst messages = session.agent.state.messages;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tconst text = message.content\n\t\t\t.filter(\n\t\t\t\t(content): content is Extract<AgentMessage, { role: \"assistant\" }>[\"content\"][number] & { type: \"text\" } =>\n\t\t\t\t\tcontent.type === \"text\",\n\t\t\t)\n\t\t\t.map((content) => content.text)\n\t\t\t.join(\"\\n\");\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && modelRuntime.hasConfiguredAuth(restoredModel.provider)) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: ToolName[] = [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\t// Forward ref, mirroring extensionRunnerRef above: the delegation runtime's\n\t// closures are needed to build `customTools` before `session` exists, but they\n\t// must read the *live* session (active tools can change after construction),\n\t// not a snapshot frozen at this point.\n\tconst parentSessionRef: { current?: AgentSession } = {};\n\tconst customTools: ToolDefinition[] = options.customTools ? [...options.customTools] : [];\n\t// A configured permission gate supplies the authority a child must derive, so\n\t// discovery is enabled by default in real sessions. Callers that need fixture\n\t// definitions may still provide an explicit resolver.\n\tconst delegation = options.delegation ?? (options.permissionGate ? {} : undefined);\n\tif (delegation) {\n\t\tconst parentPermissionGate = options.permissionGate;\n\t\tconst resolveAgent =\n\t\t\tdelegation.resolveAgent ??\n\t\t\tcreateAgentDefinitionResolver({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tisProjectTrusted: () => settingsManager.isProjectTrusted(),\n\t\t\t});\n\t\tconst delegationRuntime: DelegationRuntimeOptions = {\n\t\t\tresolveAgent,\n\t\t\tgetParentCapabilities: () => {\n\t\t\t\tconst capabilities = new Set<Capability>();\n\t\t\t\tfor (const name of parentSessionRef.current?.getActiveToolNames() ?? []) {\n\t\t\t\t\tconst contract = (\n\t\t\t\t\t\tparentSessionRef.current?.getToolDefinition(name) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t\t)?.contract;\n\t\t\t\t\tif (contract) for (const capability of contract.capabilities) capabilities.add(capability);\n\t\t\t\t}\n\t\t\t\treturn capabilities;\n\t\t\t},\n\t\t\tgetToolCapabilities: (toolName) => {\n\t\t\t\t// Prefer the parent's own live registry first (it also covers\n\t\t\t\t// extension/custom tools an agent definition might name), but fall back\n\t\t\t\t// to the canonical built-in registry: \"what capability does tool X\n\t\t\t\t// need\" is a property of the tool itself, independent of whether the\n\t\t\t\t// parent's own session restricted it out of its active/allowed set via\n\t\t\t\t// `tools`/`excludeTools` -- that restriction affects the parent's own\n\t\t\t\t// *authority* (getParentCapabilities, above), not what a named tool is.\n\t\t\t\tconst fromParent = (\n\t\t\t\t\tparentSessionRef.current?.getToolDefinition(toolName) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t)?.contract?.capabilities;\n\t\t\t\tif (fromParent) return fromParent;\n\t\t\t\treturn (createAllToolDefinitions(cwd)[toolName as ToolName] as ApexToolDefinition | undefined)?.contract\n\t\t\t\t\t?.capabilities;\n\t\t\t},\n\t\t\t// Read fresh each call, not captured once: depth is a live property of\n\t\t\t// the (possibly delegated) session this createAgentSession call is\n\t\t\t// building, via the same sessionManager this closure already captures.\n\t\t\tgetDelegationDepth: () => sessionManager.getDelegationDepth(),\n\t\t\tmaxDelegationDepth: settingsManager.getDelegationMaxDepth(),\n\t\t\tgetParentSessionDir: () => sessionManager.getSessionDir(),\n\t\t\tbuildChildSession: async ({ definition, toolNames, depth, sessionId, artifactDir }) => {\n\t\t\t\tif (!parentPermissionGate) {\n\t\t\t\t\tthrow new Error(\"delegate requires a permission gate configured on the parent session (ADR 0008).\");\n\t\t\t\t}\n\t\t\t\tconst childModel =\n\t\t\t\t\t(definition.model && modelRuntime.getAvailableSnapshot().find((m) => m.id === definition.model)) ||\n\t\t\t\t\tmodel;\n\t\t\t\tif (!childModel) {\n\t\t\t\t\tthrow new Error(`Cannot delegate to \"${definition.name}\": no model is available for the child session.`);\n\t\t\t\t}\n\t\t\t\tconst childSessionManager = artifactDir\n\t\t\t\t\t? SessionManager.create(cwd, artifactDir, {\n\t\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t})\n\t\t\t\t\t: SessionManager.inMemory(cwd, {\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t});\n\t\t\t\tconst derivedStore = new DerivedPermissionRuleStore({ parent: parentPermissionGate.store });\n\t\t\t\tconst { session: childSession } = await createAgentSession({\n\t\t\t\t\tcwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\tmodel: childModel,\n\t\t\t\t\tmodelRuntime,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\tsessionManager: childSessionManager,\n\t\t\t\t\tresourceLoader,\n\t\t\t\t\ttools: toolNames,\n\t\t\t\t\t// No responder: a child's `ask` fails closed rather than prompting the\n\t\t\t\t\t// human for a call the human did not make (ADR 0008, \"Who answers a\n\t\t\t\t\t// child's ask\").\n\t\t\t\t\tpermissionGate: { store: derivedStore, getMode: parentPermissionGate.getMode },\n\t\t\t\t\t// Forwarded so a child that itself holds `delegate` can delegate again\n\t\t\t\t\t// (bounded by the depth guard above, read fresh from its own session\n\t\t\t\t\t// header on its next createAgentSession call) -- otherwise recursion\n\t\t\t\t\t// would be silently capped at one level regardless of maxDelegationDepth.\n\t\t\t\t\tdelegation,\n\t\t\t\t});\n\t\t\t\tawait childSession.bindExtensions({});\n\t\t\t\treturn {\n\t\t\t\t\tasync run(task: string) {\n\t\t\t\t\t\tconst prompt = definition.systemPrompt\n\t\t\t\t\t\t\t? `${definition.systemPrompt}\\n\\nTask: ${task}`\n\t\t\t\t\t\t\t: `Task: ${task}`;\n\t\t\t\t\t\tawait childSession.prompt(prompt);\n\t\t\t\t\t\treturn { output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t},\n\t\t\t\t\tdispose() {\n\t\t\t\t\t\tchildSession.dispose();\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tcustomTools.push(createDelegateToolDefinition(delegationRuntime) as ToolDefinition<any, any>);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools,\n\t\tmodelRuntime,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tpermissionGate: options.permissionGate,\n\t\tevidenceSink: options.evidenceSink,\n\t});\n\tparentSessionRef.current = session;\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"AACA,OAAO,EAAoC,KAAK,KAAK,EAAgB,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAAgD,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGxG,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAI3E,OAAO,KAAK,EAAE,uBAAuB,EAA4B,MAAM,yBAAyB,CAAC;AACjG,OAAO,KAAK,EAAmB,oBAAoB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGtH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAwB,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAkC,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAEN,cAAc,EACd,iBAAiB,EAEjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,cAAc,EACd,eAAe,EAEf,qBAAqB,EACrB,MAAM,kBAAkB,CAAC;AAO1B,MAAM,WAAW,yBAAyB;IACzC,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,oGAAoG;IACpG,YAAY,CAAC,EAAE,YAAY,CAAC;IAE5B,iEAAiE;IACjE,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,4FAA4F;IAC5F,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gEAAgE;IAChE,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAE3E;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAC5B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,gEAAgE;IAChE,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAE/B,oEAAoE;IACpE,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,uEAAuE;IACvE,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,kEAAkE;IAClE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,kEAAkE;IAClE,cAAc,CAAC,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IACtD,4FAA4F;IAC5F,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,kBAAkB,CAAC,uBAAuB,CAAC,CAAC;IACpE,oGAAoG;IACpG,aAAa,CAAC,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACpD;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,uBAAuB,CAAA;KAAE,CAAC;CACxD;AAED,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACxC,0BAA0B;IAC1B,OAAO,EAAE,YAAY,CAAC;IACtB,mEAAmE;IACnE,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAID,cAAc,4BAA4B,CAAC;AAC3C,YAAY,EACX,YAAY,EACZ,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,GACd,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACN,qBAAqB,EAErB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,cAAc,EACd,YAAY,GACZ,CAAC;AA6DF,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,OAAO,CAAC,wBAAwB,CAAC,CA+VnH","sourcesContent":["import { join } from \"node:path\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"apex-code-agent-core\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type AgentSessionConfig } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createAgentDefinitionResolver } from \"./delegation/agents.ts\";\nimport type { AgentDefinitionResolver, DelegationRuntimeOptions } from \"./delegation/runtime.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { DerivedPermissionRuleStore } from \"./permissions/store.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport type { ApexToolDefinition, Capability, EvidenceSink } from \"./tools/contract.ts\";\nimport {\n\tcreateAllToolDefinitions,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateDelegateToolDefinition,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.apex-code/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi enables the default built-in tools (read, bash, edit, write)\n\t * and leaves extension/custom tools enabled unless `noTools` changes that default.\n\t * When provided, only the listed tool names are enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/** Authorization configuration for every registered tool call. */\n\tpermissionGate?: AgentSessionConfig[\"permissionGate\"];\n\t/** Optional destination for source-level evidence; defaults to the durable session sink. */\n\tevidenceSink?: EvidenceSink;\n\t/** Optional post-mutation diagnostics injected into `edit`/`write` (LSP.4). */\n\tdiagnosticsOperations?: AgentSessionConfig[\"diagnosticsOperations\"];\n\t/** Optional `lsp` navigation tool transport (LSP.5); registers and activates `lsp` when present. */\n\tlspOperations?: AgentSessionConfig[\"lspOperations\"];\n\t/**\n\t * Delegation entry point (roadmap Phase 5, ADR 0008). When set, `delegate`\n\t * executes real child sessions through `resolveAgent`, with the child's\n\t * authority derived from this session's live active tools and permission\n\t * store -- never reconstructed. When omitted, `delegate` stays inert (no agent\n\t * types resolve). Agent discovery is injected here as a caller-supplied\n\t * resolver rather than built in, until Phase 5 task 5.5 lands real\n\t * markdown/frontmatter discovery. Requires `permissionGate` to be set too: a\n\t * child's authority has nothing to derive from otherwise.\n\t */\n\tdelegation?: { resolveAgent?: AgentDefinitionResolver };\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\n/** The most recent assistant text in a completed session -- what a delegated child returns to its parent as its result. */\nfunction extractFinalAssistantText(session: AgentSession): string {\n\tconst messages = session.agent.state.messages;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tconst text = message.content\n\t\t\t.filter(\n\t\t\t\t(content): content is Extract<AgentMessage, { role: \"assistant\" }>[\"content\"][number] & { type: \"text\" } =>\n\t\t\t\t\tcontent.type === \"text\",\n\t\t\t)\n\t\t\t.map((content) => content.text)\n\t\t\t.join(\"\\n\");\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && modelRuntime.hasConfiguredAuth(restoredModel.provider)) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to settings default\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\tconst defaultActiveToolNames: string[] = options.lspOperations\n\t\t? ([\"read\", \"bash\", \"edit\", \"write\", \"lsp\"] satisfies (ToolName | \"lsp\")[])\n\t\t: ([\"read\", \"bash\", \"edit\", \"write\"] satisfies ToolName[]);\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames: string[] = (\n\t\toptions.tools ? [...options.tools] : options.noTools ? [] : defaultActiveToolNames\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\tlet agent: Agent;\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\t// Forward ref, mirroring extensionRunnerRef above: the delegation runtime's\n\t// closures are needed to build `customTools` before `session` exists, but they\n\t// must read the *live* session (active tools can change after construction),\n\t// not a snapshot frozen at this point.\n\tconst parentSessionRef: { current?: AgentSession } = {};\n\tconst customTools: ToolDefinition[] = options.customTools ? [...options.customTools] : [];\n\t// A configured permission gate supplies the authority a child must derive, so\n\t// discovery is enabled by default in real sessions. Callers that need fixture\n\t// definitions may still provide an explicit resolver.\n\tconst delegation = options.delegation ?? (options.permissionGate ? {} : undefined);\n\tif (delegation) {\n\t\tconst parentPermissionGate = options.permissionGate;\n\t\tconst resolveAgent =\n\t\t\tdelegation.resolveAgent ??\n\t\t\tcreateAgentDefinitionResolver({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tisProjectTrusted: () => settingsManager.isProjectTrusted(),\n\t\t\t});\n\t\tconst delegationRuntime: DelegationRuntimeOptions = {\n\t\t\tresolveAgent,\n\t\t\tgetParentCapabilities: () => {\n\t\t\t\tconst capabilities = new Set<Capability>();\n\t\t\t\tfor (const name of parentSessionRef.current?.getActiveToolNames() ?? []) {\n\t\t\t\t\tconst contract = (\n\t\t\t\t\t\tparentSessionRef.current?.getToolDefinition(name) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t\t)?.contract;\n\t\t\t\t\tif (contract) for (const capability of contract.capabilities) capabilities.add(capability);\n\t\t\t\t}\n\t\t\t\treturn capabilities;\n\t\t\t},\n\t\t\tgetToolCapabilities: (toolName) => {\n\t\t\t\t// Prefer the parent's own live registry first (it also covers\n\t\t\t\t// extension/custom tools an agent definition might name), but fall back\n\t\t\t\t// to the canonical built-in registry: \"what capability does tool X\n\t\t\t\t// need\" is a property of the tool itself, independent of whether the\n\t\t\t\t// parent's own session restricted it out of its active/allowed set via\n\t\t\t\t// `tools`/`excludeTools` -- that restriction affects the parent's own\n\t\t\t\t// *authority* (getParentCapabilities, above), not what a named tool is.\n\t\t\t\tconst fromParent = (\n\t\t\t\t\tparentSessionRef.current?.getToolDefinition(toolName) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t)?.contract?.capabilities;\n\t\t\t\tif (fromParent) return fromParent;\n\t\t\t\treturn (createAllToolDefinitions(cwd)[toolName as ToolName] as ApexToolDefinition | undefined)?.contract\n\t\t\t\t\t?.capabilities;\n\t\t\t},\n\t\t\t// Read fresh each call, not captured once: depth is a live property of\n\t\t\t// the (possibly delegated) session this createAgentSession call is\n\t\t\t// building, via the same sessionManager this closure already captures.\n\t\t\tgetDelegationDepth: () => sessionManager.getDelegationDepth(),\n\t\t\tmaxDelegationDepth: settingsManager.getDelegationMaxDepth(),\n\t\t\tgetParentSessionDir: () => sessionManager.getSessionDir(),\n\t\t\tbuildChildSession: async ({ definition, toolNames, depth, sessionId, artifactDir }) => {\n\t\t\t\tif (!parentPermissionGate) {\n\t\t\t\t\tthrow new Error(\"delegate requires a permission gate configured on the parent session (ADR 0008).\");\n\t\t\t\t}\n\t\t\t\tconst childModel =\n\t\t\t\t\t(definition.model && modelRuntime.getAvailableSnapshot().find((m) => m.id === definition.model)) ||\n\t\t\t\t\tmodel;\n\t\t\t\tif (!childModel) {\n\t\t\t\t\tthrow new Error(`Cannot delegate to \"${definition.name}\": no model is available for the child session.`);\n\t\t\t\t}\n\t\t\t\tconst childSessionManager = artifactDir\n\t\t\t\t\t? SessionManager.create(cwd, artifactDir, {\n\t\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t})\n\t\t\t\t\t: SessionManager.inMemory(cwd, {\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t});\n\t\t\t\tconst derivedStore = new DerivedPermissionRuleStore({ parent: parentPermissionGate.store });\n\t\t\t\tconst { session: childSession } = await createAgentSession({\n\t\t\t\t\tcwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\tmodel: childModel,\n\t\t\t\t\tmodelRuntime,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\tsessionManager: childSessionManager,\n\t\t\t\t\tresourceLoader,\n\t\t\t\t\ttools: toolNames,\n\t\t\t\t\t// No responder: a child's `ask` fails closed rather than prompting the\n\t\t\t\t\t// human for a call the human did not make (ADR 0008, \"Who answers a\n\t\t\t\t\t// child's ask\").\n\t\t\t\t\tpermissionGate: { store: derivedStore, getMode: parentPermissionGate.getMode },\n\t\t\t\t\t// A delegated child shares the parent's cwd-bound LSP pool (spec, \"no\n\t\t\t\t\t// multi-root LSP connection and no sharing across cwd-bound service\n\t\t\t\t\t// lifetimes\" -- delegated children are the one sharing exception).\n\t\t\t\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\t\t\t\tlspOperations: options.lspOperations,\n\t\t\t\t\t// Forwarded so a child that itself holds `delegate` can delegate again\n\t\t\t\t\t// (bounded by the depth guard above, read fresh from its own session\n\t\t\t\t\t// header on its next createAgentSession call) -- otherwise recursion\n\t\t\t\t\t// would be silently capped at one level regardless of maxDelegationDepth.\n\t\t\t\t\tdelegation,\n\t\t\t\t});\n\t\t\t\tawait childSession.bindExtensions({});\n\t\t\t\treturn {\n\t\t\t\t\tasync run(task: string) {\n\t\t\t\t\t\tconst prompt = definition.systemPrompt\n\t\t\t\t\t\t\t? `${definition.systemPrompt}\\n\\nTask: ${task}`\n\t\t\t\t\t\t\t: `Task: ${task}`;\n\t\t\t\t\t\tawait childSession.prompt(prompt);\n\t\t\t\t\t\treturn { output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t},\n\t\t\t\t\tdispose() {\n\t\t\t\t\t\tchildSession.dispose();\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tcustomTools.push(createDelegateToolDefinition(delegationRuntime) as ToolDefinition<any, any>);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools,\n\t\tmodelRuntime,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tpermissionGate: options.permissionGate,\n\t\tevidenceSink: options.evidenceSink,\n\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\tlspOperations: options.lspOperations,\n\t});\n\tparentSessionRef.current = session;\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}