@agenthippo-ai/agenthippo 0.1.1 → 1.110.9

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 (158) hide show
  1. package/out/agentpack/index.js +2 -1
  2. package/out/agentpack/paths.js +23 -0
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +293 -82
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +23 -25
  8. package/out/analytics/sessionEvents.js +109 -22
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +49 -15
  12. package/out/auth/onboardingPrompt.js +112 -0
  13. package/out/auth/provider.js +9 -22
  14. package/out/auth/sessionStore.js +3 -40
  15. package/out/auth/signInFlow.js +2 -1
  16. package/out/auth-broker/config.js +54 -4
  17. package/out/auth-broker/server.js +152 -20
  18. package/out/auth-broker/store.js +25 -24
  19. package/out/authority/warrant.js +206 -0
  20. package/out/branding.js +27 -1
  21. package/out/cli/commands/ask.js +3 -0
  22. package/out/cli/commands/chat.js +3 -0
  23. package/out/cli/commands/conformance.js +249 -0
  24. package/out/cli/commands/data-plane.js +67 -0
  25. package/out/cli/commands/deploy.js +50 -10
  26. package/out/cli/commands/gateway.js +58 -0
  27. package/out/cli/commands/idp.js +85 -0
  28. package/out/cli/commands/pack.js +182 -3
  29. package/out/cli/commands/sandbox.js +332 -0
  30. package/out/cli/commands/serve.js +1165 -82
  31. package/out/cli/commands/store.js +12 -4
  32. package/out/cli/credential-context.js +42 -80
  33. package/out/cli/data-plane/broker.js +78 -0
  34. package/out/cli/data-plane/connector-host.js +121 -0
  35. package/out/cli/data-plane/egress.js +189 -0
  36. package/out/cli/data-plane/gateway.js +218 -0
  37. package/out/cli/data-plane/index.js +152 -0
  38. package/out/cli/data-plane/registry.js +89 -0
  39. package/out/cli/data-plane/remote.js +106 -0
  40. package/out/cli/data-plane/sidecar.js +161 -0
  41. package/out/cli/deploy/blueprints/registry.js +84 -0
  42. package/out/cli/deploy/layers/identity.js +172 -0
  43. package/out/cli/deploy-init.js +1182 -347
  44. package/out/cli/deps.js +106 -2
  45. package/out/cli/idp-init.js +338 -0
  46. package/out/cli/index.js +192 -23
  47. package/out/cli/pack-install-ref.js +15 -0
  48. package/out/cli/pack-loader.js +45 -3
  49. package/out/cli/pack-runtime.js +61 -0
  50. package/out/cli/runtime.js +44 -3
  51. package/out/cli/serve-hardening.js +139 -3
  52. package/out/cli/state-sync.js +16 -15
  53. package/out/cli/utils.js +12 -1
  54. package/out/engines/base.js +32 -3
  55. package/out/engines/claude.js +47 -8
  56. package/out/engines/codex.js +125 -22
  57. package/out/engines/custom.js +54 -12
  58. package/out/evidence/sessionShip.js +610 -0
  59. package/out/extension.js +13 -2
  60. package/out/gateway/bridge.js +40 -2
  61. package/out/gateway/client.js +34 -1
  62. package/out/gateway/installSource.js +68 -0
  63. package/out/gateway/lifecycle.js +185 -51
  64. package/out/gateway/outboundPaths.js +4 -1
  65. package/out/gateway/outboundSend.js +6 -6
  66. package/out/gateway/secretsPreflight.js +90 -0
  67. package/out/gateway/service.js +7 -2
  68. package/out/headless/index.js +40 -9
  69. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  70. package/out/live-probe/connector.mjs +8 -0
  71. package/out/live-probe/env-report.mjs +8 -0
  72. package/out/mcp.js +115 -67
  73. package/out/pack/caps.js +271 -0
  74. package/out/pack/governor.js +309 -0
  75. package/out/pack/lease-schema.js +270 -0
  76. package/out/pack/lease.js +640 -0
  77. package/out/pack/reconcile.js +217 -0
  78. package/out/pack/signature.js +839 -0
  79. package/out/remote/handleRemoteChat.js +44 -3
  80. package/out/remote/remoteTrust.js +277 -0
  81. package/out/remote/serveClient.js +52 -5
  82. package/out/runner/host.js +131 -0
  83. package/out/runner/protocol.js +108 -0
  84. package/out/runner/remoteEngine.js +130 -0
  85. package/out/runtime/interfaces/edit-tracker.js +2 -0
  86. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  87. package/out/sandbox/containerDriver.js +570 -0
  88. package/out/sandbox/processDriver.js +315 -0
  89. package/out/sandbox/sandboxRunner.js +191 -0
  90. package/out/sandbox/support.js +301 -0
  91. package/out/sandbox/types.js +55 -0
  92. package/out/scripts/agentide-litellm.sh +67 -5
  93. package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
  94. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
  95. package/out/scripts/analytics/setup-analytics.ps1 +37 -13
  96. package/out/scripts/analytics/setup-analytics.sh +93 -13
  97. package/out/scripts/analytics/start-analytics.sh +86 -14
  98. package/out/scripts/gateway/setup-gateway.sh +72 -32
  99. package/out/shared/models.js +12 -111
  100. package/out/shared/providers.js +2 -2
  101. package/out/spotlight/install.js +7 -1
  102. package/out/store/index.js +41 -10
  103. package/out/sync.js +3 -1
  104. package/out/test/analytics.test.js +198 -34
  105. package/out/test/auth-baseurl.test.js +132 -0
  106. package/out/test/auth-bootstrapApply.test.js +27 -6
  107. package/out/test/auth-broker-hardening.test.js +227 -0
  108. package/out/test/auth-broker.test.js +112 -9
  109. package/out/test/blueprint-registry.test.js +105 -0
  110. package/out/test/branding.test.js +13 -0
  111. package/out/test/caps-governor.test.js +455 -0
  112. package/out/test/codex-new-file-survival.test.js +119 -0
  113. package/out/test/configuration-scopes.test.js +131 -0
  114. package/out/test/credential-context.test.js +23 -28
  115. package/out/test/custom-env-layering.test.js +151 -0
  116. package/out/test/data-plane-engine-wiring.test.js +250 -0
  117. package/out/test/data-plane.test.js +311 -0
  118. package/out/test/deploy-init.test.js +295 -4
  119. package/out/test/egress-broker.test.js +172 -0
  120. package/out/test/fleetDisplayUtils.test.js +4 -0
  121. package/out/test/gateway-client.test.js +28 -0
  122. package/out/test/gateway-install-source.test.js +111 -0
  123. package/out/test/gateway-lifecycle.test.js +1 -1
  124. package/out/test/gateway-outbound.test.js +7 -0
  125. package/out/test/gateway-secrets-preflight.test.js +127 -0
  126. package/out/test/gateway-session-routing.test.js +18 -0
  127. package/out/test/governor-hardening.test.js +217 -0
  128. package/out/test/headless-sync.test.js +22 -0
  129. package/out/test/idp-init.test.js +151 -0
  130. package/out/test/lease-signature.test.js +357 -0
  131. package/out/test/lease-trust-root.test.js +273 -0
  132. package/out/test/lease-validation.test.js +169 -0
  133. package/out/test/lease.test.js +114 -0
  134. package/out/test/mcp-obo.test.js +52 -27
  135. package/out/test/mcp.test.js +69 -3
  136. package/out/test/model-routing.test.js +133 -0
  137. package/out/test/pack-admission.test.js +216 -0
  138. package/out/test/pack-digest-parity.test.js +140 -0
  139. package/out/test/pack-reconcile.test.js +183 -0
  140. package/out/test/pack-signature-artifact.test.js +103 -0
  141. package/out/test/pack-signature.test.js +176 -0
  142. package/out/test/pack-trust-root.test.js +202 -0
  143. package/out/test/remote-trust.test.js +242 -0
  144. package/out/test/runner-protocol.test.js +249 -0
  145. package/out/test/sandbox-driver.test.js +553 -0
  146. package/out/test/sandbox-hardening.test.js +395 -0
  147. package/out/test/sandbox-runner.test.js +343 -0
  148. package/out/test/serve-gateway-turn.test.js +267 -0
  149. package/out/test/serve-hardening.test.js +125 -6
  150. package/out/test/serveClient.test.js +38 -5
  151. package/out/test/sessionShip.test.js +560 -0
  152. package/out/test/spotlightPublish.test.js +28 -0
  153. package/out/test/state-sync-peer.test.js +18 -5
  154. package/out/test/store-install-safe.test.js +69 -0
  155. package/out/test/tls-bypass.test.js +90 -0
  156. package/out/test/warrant.test.js +138 -0
  157. package/out/utils.js +73 -20
  158. package/package.json +1 -1
@@ -37,6 +37,7 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.CodexEngine = void 0;
40
+ exports.registerNewFilesWithTracker = registerNewFilesWithTracker;
40
41
  const fs_1 = require("fs");
41
42
  const path = __importStar(require("path"));
42
43
  const mcp_1 = require("../mcp");
@@ -46,6 +47,9 @@ const utils_1 = require("../utils");
46
47
  const permissions_1 = require("../permissions");
47
48
  const session_1 = require("../session");
48
49
  const agentpack_1 = require("../agentpack");
50
+ const credential_context_1 = require("../cli/credential-context");
51
+ const runtime_1 = require("../cli/runtime");
52
+ const types_1 = require("../sandbox/types");
49
53
  // Note: HTTP edit server removed - file changes are now handled directly via file_change events
50
54
  // -----------------------------------------------------------------------------
51
55
  // Constants
@@ -62,11 +66,56 @@ const AGENTIDE_CODEX_AGENT_VERSION_ENV_VAR = 'AGENTIDE_ANALYTICS_AGENT_VERSION';
62
66
  const AGENTIDE_CODEX_SOURCE_ENV_VAR = 'AGENTIDE_ANALYTICS_SOURCE';
63
67
  const AGENTIDE_CODEX_ENGINE_ENV_VAR = 'AGENTIDE_ANALYTICS_ENGINE';
64
68
  const AGENTIDE_CODEX_REQUEST_ID_ENV_VAR = 'AGENTIDE_ANALYTICS_REQUEST_ID';
69
+ const AGENTIDE_CODEX_POLICY_VERSION_ENV_VAR = 'AGENTIDE_ANALYTICS_POLICY_VERSION';
70
+ const AGENTIDE_CODEX_PACK_DIGEST_ENV_VAR = 'AGENTIDE_ANALYTICS_PACK_DIGEST';
71
+ const AGENTIDE_CODEX_ENGINE_VERSION_ENV_VAR = 'AGENTIDE_ANALYTICS_ENGINE_VERSION';
72
+ const AGENTIDE_CODEX_TIER_ENV_VAR = 'AGENTIDE_ANALYTICS_TIER';
73
+ const AGENTIDE_CODEX_INSTANCE_ID_ENV_VAR = 'AGENTIDE_ANALYTICS_INSTANCE_ID';
65
74
  // Bump when we change generated config semantics so long-lived sessions regenerate config.toml.
66
- const AGENTIDE_CODEX_CONFIG_VERSION = 18; // v18: Codex CLI dropped wire_api=chat; always use responses
75
+ const AGENTIDE_CODEX_CONFIG_VERSION = 21; // v21: sandbox_mode "danger-full-access" now requires tier 2, not merely being inside a sandbox — a tier-0 home written by v20 must be rewritten
67
76
  // Per-process runtime context for agents (not analytics): lets the agent locate its per-session context export.
68
77
  const AGENTIDE_CONTEXT_EXPORT_PATH_ENV_VAR = 'AGENTIDE_CONTEXT_EXPORT_PATH';
69
78
  const AGENTIDE_TERMINAL_LOG_PATH_ENV_VAR = 'AGENTIDE_TERMINAL_LOG_PATH';
79
+ /**
80
+ * Hand files codex just CREATED to the edit tracker so the IDE can render them against an empty
81
+ * original — without ever risking the agent's work.
82
+ *
83
+ * The IDE trick is read → delete → `applyEdit`. That delete caused live data loss: headless
84
+ * (`serve`/`ask`/`chat` and every sandbox) gets `NoOpEditTracker`, whose `applyEdit` does nothing, so
85
+ * every new file codex created was silently destroyed. The command exited 0, the file was there
86
+ * during the command, and it was gone by the next one — while standalone `codex exec` wrote it
87
+ * fine. Two guards now: skip trackers that do not write back, and restore the file if it is
88
+ * missing when we are done, whatever the tracker did or threw.
89
+ *
90
+ * Exported so the invariant "a file the agent created still exists after the turn" is testable.
91
+ */
92
+ async function registerNewFilesWithTracker(files, editTracker, sessionId) {
93
+ // A tracker that does not write the file back must never see a delete.
94
+ if (editTracker.appliesEdits === false) {
95
+ return;
96
+ }
97
+ for (const f of files) {
98
+ try {
99
+ if ((0, utils_1.isBinaryFile)(f)) {
100
+ continue; // Binary would be corrupted by the UTF-8 read/write round trip.
101
+ }
102
+ if (!(await (0, utils_1.pathExists)(f))) {
103
+ continue;
104
+ }
105
+ const code = await fs_1.promises.readFile(f, 'utf-8');
106
+ try {
107
+ await fs_1.promises.unlink(f);
108
+ await editTracker.applyEdit(sessionId, f, code, true);
109
+ }
110
+ finally {
111
+ if (!(await (0, utils_1.pathExists)(f))) {
112
+ await fs_1.promises.writeFile(f, code, 'utf-8');
113
+ }
114
+ }
115
+ }
116
+ catch { /* ignore — never fail a turn over edit-diff bookkeeping */ }
117
+ }
118
+ }
70
119
  function isCodexItemEvent(event) {
71
120
  return event.type === 'item.started' || event.type === 'item.updated' || event.type === 'item.completed';
72
121
  }
@@ -100,7 +149,8 @@ class CodexEngine {
100
149
  const threadModelId = options.modelId;
101
150
  // Resolve API routing (LiteLLM proxy vs direct OpenAI)
102
151
  // Use openaiBaseUrl which has overrideOpenAIBaseUrl applied
103
- const routing = (0, utils_1.resolveApiRouting)(options, this.config.litellmApiKey, this.config.litellmBaseUrl);
152
+ // codex routes to OpenAI when direct (or the LiteLLM proxy when configured) — never Anthropic.
153
+ const routing = (0, utils_1.resolveApiRouting)(options, this.config, 'openai');
104
154
  const apiKey = routing.apiKey;
105
155
  const baseUrl = routing.openaiBaseUrl; // Use OpenAI-specific URL
106
156
  const useLiteLLM = routing.useLiteLLM;
@@ -126,6 +176,17 @@ class CodexEngine {
126
176
  ...(runtime.cliEnvPatch ?? {}),
127
177
  OPENAI_LOG: 'debug',
128
178
  };
179
+ // The OpenClaw gateway token must never enter the agent's trust domain (secure-obo-design
180
+ // §7.6 / §7.4 gap 1). UNCONDITIONALLY (serve-v4 §10 hole 2): the scrub used to depend on
181
+ // the egress broker being up, so a deployment without --gateway-bridge re-inherited the
182
+ // token — and buildCliEnvPatch can bake it in from the host's ~/.openclaw config, not just
183
+ // process.env. This engine never needs it: outbound goes through the broker's turn nonce,
184
+ // and the openclaw CLI reads its own config file on single-tenant hosts.
185
+ delete codexEnv[runtime_1.OPENCLAW_GATEWAY_TOKEN_ENV];
186
+ // Data-plane sidecar control token: the engine only needs the gateway URL + its per-turn
187
+ // nonce, never the register token — scrub it so a compromised agent can't forge a turn as
188
+ // another user (secure-obo-design §7.6). Harmless when absent (in-process/no data plane).
189
+ delete codexEnv['AGENTHIPPO_DATA_PLANE_TOKEN'];
129
190
  // Always expose the VS Code chat session id to the agent process so it can locate:
130
191
  // .agentide/sessions/<sessionId>/context.md
131
192
  const rawChatSessionId = sessionId?.trim();
@@ -141,8 +202,12 @@ class CodexEngine {
141
202
  // Note: workingDirectory is ALWAYS workspaceRoot - active agent content is synced to workspace's .codex/
142
203
  const agentId = options.agentPackId ?? 'default';
143
204
  const runtimePaths = (0, agentpack_1.resolveRuntimePaths)(options.workspaceRoot, this.config.storagePath, agentId, options.agentPackVersion);
144
- // Use agent-isolated CODEX_HOME (for session/state isolation)
145
- const codexHome = runtimePaths.codexHome;
205
+ // Use agent-isolated CODEX_HOME (for session/state isolation). In multi-tenant serve,
206
+ // additionally key it by the verified request user: config.toml is rewritten per turn,
207
+ // and a shared per-agent home lets concurrent turns of different users race it
208
+ // (secure-obo-design §1 defect 1 / §8 Phase 0). No credential frame (IDE,
209
+ // single-tenant CLI) ⇒ unchanged per-agent path.
210
+ const codexHome = (0, agentpack_1.userScopedEngineHome)(runtimePaths.codexHome, (0, credential_context_1.currentCredential)()?.userId);
146
211
  codexEnv['CODEX_HOME'] = codexHome;
147
212
  await fs_1.promises.mkdir(codexHome, { recursive: true });
148
213
  // Always set AGENT_ID for agent to know its identity
@@ -164,7 +229,15 @@ class CodexEngine {
164
229
  agentId, // Pass agent ID for correct skills path
165
230
  runtime.permissionHandler instanceof interfaces_1.AutoApprovePermissionHandler);
166
231
  // LiteLLM analytics metadata (enriched - see PLAN/analytics-design.md)
167
- if (useLiteLLM && this.config.codexHome) {
232
+ //
233
+ // Gate on the codexHome actually in use (resolved above), NOT `this.config.codexHome`:
234
+ // headless/index.ts passes `codexHome: undefined`, so under serve/ask/chat and inside
235
+ // every sandbox this block was skipped and codex sent NO metadata headers at all — no
236
+ // session-id, user-id, agent-id, source, request-id, or engine. Spotlight lost codex
237
+ // attribution outside the IDE, and the gateway could not see `x-litellm-metadata-engine`,
238
+ // so engine-conditional ah-auto routing never fired. config.toml was written correctly
239
+ // the whole time (it uses this same resolved home) — only the env vars it reads were missing.
240
+ if (useLiteLLM && codexHome) {
168
241
  if (analyticsUserId?.trim()) {
169
242
  codexEnv[AGENTIDE_CODEX_USER_ENV_VAR] = analyticsUserId.trim();
170
243
  }
@@ -182,6 +255,21 @@ class CodexEngine {
182
255
  if (options.requestId) {
183
256
  codexEnv[AGENTIDE_CODEX_REQUEST_ID_ENV_VAR] = options.requestId;
184
257
  }
258
+ if (options.policyVersion?.trim()) {
259
+ codexEnv[AGENTIDE_CODEX_POLICY_VERSION_ENV_VAR] = options.policyVersion.trim();
260
+ }
261
+ if (options.packDigest?.trim()) {
262
+ codexEnv[AGENTIDE_CODEX_PACK_DIGEST_ENV_VAR] = options.packDigest.trim();
263
+ }
264
+ if (options.engineVersion?.trim()) {
265
+ codexEnv[AGENTIDE_CODEX_ENGINE_VERSION_ENV_VAR] = options.engineVersion.trim();
266
+ }
267
+ if (options.tier !== undefined) {
268
+ codexEnv[AGENTIDE_CODEX_TIER_ENV_VAR] = String(options.tier);
269
+ }
270
+ if (options.instanceId?.trim()) {
271
+ codexEnv[AGENTIDE_CODEX_INSTANCE_ID_ENV_VAR] = options.instanceId.trim();
272
+ }
185
273
  }
186
274
  // For custom providers (OpenRouter, LiteLLM), set API key in CODEX_API_KEY.
187
275
  // The config.toml's `env_key = "CODEX_API_KEY"` tells the CLI to read from this variable.
@@ -210,6 +298,16 @@ class CodexEngine {
210
298
  ];
211
299
  // Map unified permissions to Codex SDK options
212
300
  const codexPermOpts = (0, permissions_1.toCodexOptions)(effectivePerms, workspaceFolders);
301
+ // One boundary, not two — but only when the OTHER boundary is real. Inside a tier-2
302
+ // container Codex's own bubblewrap sandbox cannot start (it needs a user namespace
303
+ // `--cap-drop ALL` + `no-new-privileges` deny), so standing it down is the difference
304
+ // between working shell commands and silent failures. The tier-0 process driver
305
+ // provides NO boundary — same uid, same filesystem as serve — so there Codex's own
306
+ // sandbox is the only thing between the agent and the host and must stay on.
307
+ // The SDK's ThreadOptions.sandboxMode overrides config.toml, so force it here too.
308
+ if ((0, types_1.isContainerSandbox)()) {
309
+ codexPermOpts.sandboxMode = 'danger-full-access';
310
+ }
213
311
  logger.info(`[AgentIDE] Codex mapped: sandboxMode=${codexPermOpts.sandboxMode}, approvalPolicy=${codexPermOpts.approvalPolicy}`);
214
312
  // Build thread options
215
313
  // workingDirectory is ALWAYS workspaceRoot - pack content is synced to workspace's .codex/
@@ -396,21 +494,7 @@ class CodexEngine {
396
494
  catch { /* ignore */ }
397
495
  pendingOperations.delete(item.id);
398
496
  }
399
- // New files: read, delete, apply (so VS Code sees original=empty)
400
- // Skip binary files - they would be corrupted by UTF-8 read/write
401
- for (const f of pendingNewFiles.get(item.id) ?? []) {
402
- try {
403
- if ((0, utils_1.isBinaryFile)(f)) {
404
- continue; // Don't track binary files for edit diffs
405
- }
406
- if (await (0, utils_1.pathExists)(f)) {
407
- const code = await fs_1.promises.readFile(f, 'utf-8');
408
- await fs_1.promises.unlink(f);
409
- await editTracker.applyEdit(rawChatSessionId, f, code, true);
410
- }
411
- }
412
- catch { /* ignore */ }
413
- }
497
+ await registerNewFilesWithTracker(pendingNewFiles.get(item.id) ?? [], editTracker, rawChatSessionId);
414
498
  pendingNewFiles.delete(item.id);
415
499
  }
416
500
  }
@@ -601,7 +685,11 @@ class CodexEngine {
601
685
  // Codex CLI requires wire_api=responses (chat wire was removed in newer Codex releases).
602
686
  const wireApi = 'responses';
603
687
  const routingType = useLiteLLM ? 'litellm' : (customBaseUrl ? 'custom' : 'openai');
604
- const signature = `${AGENTIDE_CODEX_CONFIG_VERSION}:${routingType}:${customBaseUrl ?? ''}:${wireApi}:${agentId}:${codexInstructionsFile ?? ''}:${(0, mcp_1.mcpConfigCacheSignatureSuffix)()}`;
688
+ // No per-turn component in this signature: config.toml must be byte-identical across
689
+ // turns (per-turn auth rides the process env via bearer_token_env_var, never the file),
690
+ // so concurrent same-home turns write identical bytes and the old cross-turn race is
691
+ // gone by construction (secure-obo-design §3.2).
692
+ const signature = `${AGENTIDE_CODEX_CONFIG_VERSION}:${routingType}:${customBaseUrl ?? ''}:${wireApi}:${agentId}:${codexInstructionsFile ?? ''}`;
605
693
  if (this.lastWrittenConfigSignature === signature) {
606
694
  return;
607
695
  }
@@ -618,6 +706,16 @@ class CodexEngine {
618
706
  // Codex CLI loads a single "model instructions" file that acts like a system/developer prompt overlay.
619
707
  // Note: experimental_instructions_file is deprecated and ignored by newer Codex CLI versions.
620
708
  codexInstructionsFile ? `model_instructions_file = ${JSON.stringify(codexInstructionsFile)}` : undefined,
709
+ // Inside a TIER-2 sandbox the container is the boundary, so Codex's own sandbox is not
710
+ // just redundant — it cannot start. It shells out to bubblewrap, which needs to create
711
+ // a user namespace and scratch space under /tmp; a container drops those capabilities by
712
+ // design (`--cap-drop ALL`, `no-new-privileges`), so every shell command and file
713
+ // write failed while still reporting exit 0. One boundary, not two — but the tier-0
714
+ // process driver is not a boundary at all, so it never earns this. Keyed on the tier
715
+ // the driver reports, not on merely being inside a sandbox.
716
+ ...((0, types_1.isContainerSandbox)()
717
+ ? ['sandbox_mode = "danger-full-access"']
718
+ : []),
621
719
  ];
622
720
  // Add custom provider section for LiteLLM or custom providers (e.g., OpenRouter)
623
721
  if (customBaseUrl) {
@@ -636,7 +734,12 @@ class CodexEngine {
636
734
  `"x-litellm-metadata-agent-version" = "${AGENTIDE_CODEX_AGENT_VERSION_ENV_VAR}", ` +
637
735
  `"x-litellm-metadata-source" = "${AGENTIDE_CODEX_SOURCE_ENV_VAR}", ` +
638
736
  `"x-litellm-metadata-engine" = "${AGENTIDE_CODEX_ENGINE_ENV_VAR}", ` +
639
- `"x-litellm-metadata-request-id" = "${AGENTIDE_CODEX_REQUEST_ID_ENV_VAR}" }`);
737
+ `"x-litellm-metadata-request-id" = "${AGENTIDE_CODEX_REQUEST_ID_ENV_VAR}", ` +
738
+ `"x-litellm-metadata-policy-version" = "${AGENTIDE_CODEX_POLICY_VERSION_ENV_VAR}", ` +
739
+ `"x-litellm-metadata-pack-digest" = "${AGENTIDE_CODEX_PACK_DIGEST_ENV_VAR}", ` +
740
+ `"x-litellm-metadata-engine-version" = "${AGENTIDE_CODEX_ENGINE_VERSION_ENV_VAR}", ` +
741
+ `"x-litellm-metadata-tier" = "${AGENTIDE_CODEX_TIER_ENV_VAR}", ` +
742
+ `"x-litellm-metadata-instance-id" = "${AGENTIDE_CODEX_INSTANCE_ID_ENV_VAR}" }`);
640
743
  }
641
744
  }
642
745
  // Add MCP servers section (pack-aware)
@@ -38,6 +38,8 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.CustomEngine = void 0;
40
40
  exports.resolveCustomEngineLoadStrategy = resolveCustomEngineLoadStrategy;
41
+ exports.deliverModelRoutingEnv = deliverModelRoutingEnv;
42
+ exports.buildRunnerChildEnv = buildRunnerChildEnv;
41
43
  exports.probeCustomEngineImport = probeCustomEngineImport;
42
44
  const node_child_process_1 = require("node:child_process");
43
45
  const node_readline_1 = require("node:readline");
@@ -45,6 +47,7 @@ const fs_1 = require("fs");
45
47
  const path = __importStar(require("path"));
46
48
  const url_1 = require("url");
47
49
  const base_1 = require("./base");
50
+ const runtime_1 = require("../cli/runtime");
48
51
  const customRegistry_1 = require("./customRegistry");
49
52
  const paths_1 = require("../agentpack/paths");
50
53
  const utils_1 = require("../utils");
@@ -83,10 +86,45 @@ function prependNodePath(env, engineDir) {
83
86
  ? `${engineNodeModules}${path.delimiter}${existing}`
84
87
  : engineNodeModules;
85
88
  }
89
+ /**
90
+ * Deliver the per-turn model-routing values into the env vars a manifest declares
91
+ * (`model.apiKeyEnvVar` / `baseUrlEnvVar` / `openaiBaseUrlEnvVar`).
92
+ *
93
+ * NON-CLOBBERING: a var already set in the host's own ambient env is left alone. Two reasons:
94
+ * (1) it preserves the effective behavior deployments already relied on — the old
95
+ * `{...turn.env, ...process.env}` child-env spread accidentally let ambient values win,
96
+ * and operators' engine-owned keys (e.g. a real CURSOR_API_KEY vs a stale manifest that
97
+ * wrongly declared it as a delivery slot) must keep winning after that spread is fixed;
98
+ * (2) it makes the manifest contract safe by construction: mis-declaring an operator-owned
99
+ * credential as a delivery slot can shadow nothing.
100
+ * The flip side is documented on CustomEngineModelConfig: ambient-exporting a genuine
101
+ * delivery var (e.g. LITELLM_API_KEY in serve's shell) shadows per-turn rotation.
102
+ */
103
+ function deliverModelRoutingEnv(env, manifest, routing, ambientEnv, logger) {
104
+ const deliver = (envVar, value, label) => {
105
+ if (!envVar || !value) {
106
+ return;
107
+ }
108
+ if (ambientEnv[envVar]?.trim()) {
109
+ logger?.info(`[AgentIDE] Custom engine "${manifest.id}": ${envVar} already set in ambient env — keeping the operator's value, skipping per-turn ${label} delivery`);
110
+ return;
111
+ }
112
+ env[envVar] = value;
113
+ };
114
+ deliver(manifest.model?.apiKeyEnvVar, routing.apiKey, 'API key');
115
+ deliver(manifest.model?.baseUrlEnvVar, routing.baseUrl, 'base URL');
116
+ deliver(manifest.model?.openaiBaseUrlEnvVar, routing.openaiBaseUrl, 'OpenAI base URL');
117
+ }
86
118
  function buildRunnerChildEnv(manifest, turn, probeImport) {
87
119
  const childEnv = {
88
- ...turn.env,
120
+ // Per-turn values WIN over the host's ambient env. turn.env starts from a spread of
121
+ // process.env, so for undeclared vars this is identical either way; the difference is
122
+ // deliberate per-turn state (AGENTHIPPO_* control vars, the data-plane turn nonce,
123
+ // session paths, manifest-declared deliveries) which must never be reverted by a
124
+ // same-named var in the host's own environment. Ambient-vs-delivery precedence for
125
+ // manifest credential vars is decided earlier, in deliverModelRoutingEnv (non-clobber).
89
126
  ...process.env,
127
+ ...turn.env,
90
128
  AGENTHIPPO_ENGINE_ENTRY: manifest.entryPath,
91
129
  AGENTHIPPO_MESSAGE: turn.message,
92
130
  AGENTHIPPO_WORKSPACE_ROOT: turn.workspaceRoot,
@@ -466,7 +504,10 @@ class CustomEngine {
466
504
  const attachmentsDir = path.join(workspaceSessionDir, 'attachments');
467
505
  const nativeSessionId = this.activeSessions.get(conversationKey)?.nativeSessionId ??
468
506
  (0, base_1.getPersistedSessionId)(`custom:${manifest.id}`, conversationKey);
469
- const routing = (0, utils_1.resolveApiRouting)(options, this.config.litellmApiKey, this.config.litellmBaseUrl, runtime.configProvider);
507
+ // A custom engine isn't pinned to one vendor — infer its direct provider from the model
508
+ // (e.g. openclaw's litellm/* routes through the proxy anyway; a claude-* model → Anthropic).
509
+ // The engine's manifest still decides which env var receives the resolved key.
510
+ const routing = (0, utils_1.resolveApiRouting)(options, this.config, (0, utils_1.inferModelProvider)(options.modelId), runtime.configProvider);
470
511
  const routingApiKey = routing.apiKey?.trim() || undefined;
471
512
  const routingBaseUrl = routing.baseUrl?.trim() || undefined;
472
513
  const routingOpenAIBaseUrl = routing.openaiBaseUrl?.trim() || undefined;
@@ -494,6 +535,8 @@ class CustomEngine {
494
535
  AGENTHIPPO_MODEL_ID: modelId,
495
536
  AGENTHIPPO_USE_LITELLM: routing.useLiteLLM ? 'true' : 'false',
496
537
  };
538
+ delete env[runtime_1.OPENCLAW_GATEWAY_TOKEN_ENV];
539
+ delete env['AGENTHIPPO_DATA_PLANE_TOKEN'];
497
540
  prependNodePath(env, manifest.manifestDir);
498
541
  if (routingApiKey) {
499
542
  env.AGENTHIPPO_API_KEY = routingApiKey;
@@ -520,6 +563,11 @@ class CustomEngine {
520
563
  source: 'agenthippo',
521
564
  engine: `custom:${manifest.id}`,
522
565
  requestId: options.requestId,
566
+ policyVersion: options.policyVersion,
567
+ packDigest: options.packDigest,
568
+ instanceId: options.instanceId,
569
+ engineVersion: options.engineVersion,
570
+ tier: options.tier,
523
571
  });
524
572
  if (litellmMetadataHeaders.length > 0) {
525
573
  env.AGENTHIPPO_LITELLM_METADATA_HEADERS = litellmMetadataHeaders.join('\n');
@@ -532,15 +580,7 @@ class CustomEngine {
532
580
  if (manifest.homeEnvVar) {
533
581
  env[manifest.homeEnvVar] = engineHomeDir;
534
582
  }
535
- if (manifest.model?.apiKeyEnvVar && routingApiKey) {
536
- env[manifest.model.apiKeyEnvVar] = routingApiKey;
537
- }
538
- if (manifest.model?.baseUrlEnvVar && routingBaseUrl) {
539
- env[manifest.model.baseUrlEnvVar] = routingBaseUrl;
540
- }
541
- if (manifest.model?.openaiBaseUrlEnvVar && routingOpenAIBaseUrl) {
542
- env[manifest.model.openaiBaseUrlEnvVar] = routingOpenAIBaseUrl;
543
- }
583
+ deliverModelRoutingEnv(env, manifest, { apiKey: routingApiKey, baseUrl: routingBaseUrl, openaiBaseUrl: routingOpenAIBaseUrl }, process.env, runtime.logger);
544
584
  if ((routingBaseUrl && (0, utils_1.applyLocalTlsBypass)(env, routingBaseUrl))
545
585
  || (routingOpenAIBaseUrl && routingOpenAIBaseUrl !== routingBaseUrl && (0, utils_1.applyLocalTlsBypass)(env, routingOpenAIBaseUrl))) {
546
586
  runtime.logger.info(`[AgentIDE] Disabling TLS verification for local HTTPS (${manifest.id})`);
@@ -550,7 +590,9 @@ class CustomEngine {
550
590
  : (routingBaseUrl ? 'custom provider' : 'native provider');
551
591
  runtime.logger.info(`[AgentIDE] Custom engine "${manifest.id}" routing: ${routingDescription}, baseUrl=${routingBaseUrl || 'default'}, model=${modelId}`);
552
592
  const resolvedMcp = await (0, mcp_1.loadMcpServers)(agentId, options.workspaceRoot, this.getBundledBasePromptsDir());
553
- const mcpServers = (0, mcp_1.formatInlineMcpServers)(resolvedMcp);
593
+ // `env` is this turn's child env — URL servers resolve bearerTokenEnv (turn nonce)
594
+ // from it in memory, so per-turn auth never lands in a shared file.
595
+ const mcpServers = (0, mcp_1.formatInlineMcpServers)(resolvedMcp, env);
554
596
  if (resolvedMcp.length > 0) {
555
597
  runtime.logger.debug(`[AgentIDE] Custom engine "${manifest.id}" MCP (${agentId}): ${resolvedMcp.map(s => s.name).join(', ')}`);
556
598
  }