@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.
- package/out/agentpack/index.js +2 -1
- package/out/agentpack/paths.js +23 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
- package/out/analytics/dashboard/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +293 -82
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +23 -25
- package/out/analytics/sessionEvents.js +109 -22
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +49 -15
- package/out/auth/onboardingPrompt.js +112 -0
- package/out/auth/provider.js +9 -22
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +54 -4
- package/out/auth-broker/server.js +152 -20
- package/out/auth-broker/store.js +25 -24
- package/out/authority/warrant.js +206 -0
- package/out/branding.js +27 -1
- package/out/cli/commands/ask.js +3 -0
- package/out/cli/commands/chat.js +3 -0
- package/out/cli/commands/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +67 -0
- package/out/cli/commands/deploy.js +50 -10
- package/out/cli/commands/gateway.js +58 -0
- package/out/cli/commands/idp.js +85 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +332 -0
- package/out/cli/commands/serve.js +1165 -82
- package/out/cli/commands/store.js +12 -4
- package/out/cli/credential-context.js +42 -80
- package/out/cli/data-plane/broker.js +78 -0
- package/out/cli/data-plane/connector-host.js +121 -0
- package/out/cli/data-plane/egress.js +189 -0
- package/out/cli/data-plane/gateway.js +218 -0
- package/out/cli/data-plane/index.js +152 -0
- package/out/cli/data-plane/registry.js +89 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +161 -0
- package/out/cli/deploy/blueprints/registry.js +84 -0
- package/out/cli/deploy/layers/identity.js +172 -0
- package/out/cli/deploy-init.js +1182 -347
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +338 -0
- package/out/cli/index.js +192 -23
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +45 -3
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +44 -3
- package/out/cli/serve-hardening.js +139 -3
- package/out/cli/state-sync.js +16 -15
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +47 -8
- package/out/engines/codex.js +125 -22
- package/out/engines/custom.js +54 -12
- package/out/evidence/sessionShip.js +610 -0
- package/out/extension.js +13 -2
- package/out/gateway/bridge.js +40 -2
- package/out/gateway/client.js +34 -1
- package/out/gateway/installSource.js +68 -0
- package/out/gateway/lifecycle.js +185 -51
- package/out/gateway/outboundPaths.js +4 -1
- package/out/gateway/outboundSend.js +6 -6
- package/out/gateway/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +40 -9
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/live-probe/connector.mjs +8 -0
- package/out/live-probe/env-report.mjs +8 -0
- package/out/mcp.js +115 -67
- package/out/pack/caps.js +271 -0
- package/out/pack/governor.js +309 -0
- package/out/pack/lease-schema.js +270 -0
- package/out/pack/lease.js +640 -0
- package/out/pack/reconcile.js +217 -0
- package/out/pack/signature.js +839 -0
- package/out/remote/handleRemoteChat.js +44 -3
- package/out/remote/remoteTrust.js +277 -0
- package/out/remote/serveClient.js +52 -5
- package/out/runner/host.js +131 -0
- package/out/runner/protocol.js +108 -0
- package/out/runner/remoteEngine.js +130 -0
- package/out/runtime/interfaces/edit-tracker.js +2 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
- package/out/sandbox/containerDriver.js +570 -0
- package/out/sandbox/processDriver.js +315 -0
- package/out/sandbox/sandboxRunner.js +191 -0
- package/out/sandbox/support.js +301 -0
- package/out/sandbox/types.js +55 -0
- package/out/scripts/agentide-litellm.sh +67 -5
- package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
- package/out/scripts/analytics/setup-analytics.ps1 +37 -13
- package/out/scripts/analytics/setup-analytics.sh +93 -13
- package/out/scripts/analytics/start-analytics.sh +86 -14
- package/out/scripts/gateway/setup-gateway.sh +72 -32
- package/out/shared/models.js +12 -111
- package/out/shared/providers.js +2 -2
- package/out/spotlight/install.js +7 -1
- package/out/store/index.js +41 -10
- package/out/sync.js +3 -1
- package/out/test/analytics.test.js +198 -34
- package/out/test/auth-baseurl.test.js +132 -0
- package/out/test/auth-bootstrapApply.test.js +27 -6
- package/out/test/auth-broker-hardening.test.js +227 -0
- package/out/test/auth-broker.test.js +112 -9
- package/out/test/blueprint-registry.test.js +105 -0
- package/out/test/branding.test.js +13 -0
- package/out/test/caps-governor.test.js +455 -0
- package/out/test/codex-new-file-survival.test.js +119 -0
- package/out/test/configuration-scopes.test.js +131 -0
- package/out/test/credential-context.test.js +23 -28
- package/out/test/custom-env-layering.test.js +151 -0
- package/out/test/data-plane-engine-wiring.test.js +250 -0
- package/out/test/data-plane.test.js +311 -0
- package/out/test/deploy-init.test.js +295 -4
- package/out/test/egress-broker.test.js +172 -0
- package/out/test/fleetDisplayUtils.test.js +4 -0
- package/out/test/gateway-client.test.js +28 -0
- package/out/test/gateway-install-source.test.js +111 -0
- package/out/test/gateway-lifecycle.test.js +1 -1
- package/out/test/gateway-outbound.test.js +7 -0
- package/out/test/gateway-secrets-preflight.test.js +127 -0
- package/out/test/gateway-session-routing.test.js +18 -0
- package/out/test/governor-hardening.test.js +217 -0
- package/out/test/headless-sync.test.js +22 -0
- package/out/test/idp-init.test.js +151 -0
- package/out/test/lease-signature.test.js +357 -0
- package/out/test/lease-trust-root.test.js +273 -0
- package/out/test/lease-validation.test.js +169 -0
- package/out/test/lease.test.js +114 -0
- package/out/test/mcp-obo.test.js +52 -27
- package/out/test/mcp.test.js +69 -3
- package/out/test/model-routing.test.js +133 -0
- package/out/test/pack-admission.test.js +216 -0
- package/out/test/pack-digest-parity.test.js +140 -0
- package/out/test/pack-reconcile.test.js +183 -0
- package/out/test/pack-signature-artifact.test.js +103 -0
- package/out/test/pack-signature.test.js +176 -0
- package/out/test/pack-trust-root.test.js +202 -0
- package/out/test/remote-trust.test.js +242 -0
- package/out/test/runner-protocol.test.js +249 -0
- package/out/test/sandbox-driver.test.js +553 -0
- package/out/test/sandbox-hardening.test.js +395 -0
- package/out/test/sandbox-runner.test.js +343 -0
- package/out/test/serve-gateway-turn.test.js +267 -0
- package/out/test/serve-hardening.test.js +125 -6
- package/out/test/serveClient.test.js +38 -5
- package/out/test/sessionShip.test.js +560 -0
- package/out/test/spotlightPublish.test.js +28 -0
- package/out/test/state-sync-peer.test.js +18 -5
- package/out/test/store-install-safe.test.js +69 -0
- package/out/test/tls-bypass.test.js +90 -0
- package/out/test/warrant.test.js +138 -0
- package/out/utils.js +73 -20
- package/package.json +1 -1
package/out/engines/codex.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/out/engines/custom.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|