@agenthippo-ai/agenthippo 0.2.3-dataplane → 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/analytics/dashboard/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +3 -2
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +23 -25
- package/out/analytics/sessionEvents.js +98 -23
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +48 -14
- package/out/auth/provider.js +8 -21
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +50 -5
- package/out/auth-broker/server.js +122 -26
- 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/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +67 -0
- package/out/cli/commands/deploy.js +1 -0
- package/out/cli/commands/gateway.js +4 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +332 -0
- package/out/cli/commands/serve.js +875 -52
- package/out/cli/commands/store.js +12 -4
- package/out/cli/data-plane/gateway.js +28 -5
- package/out/cli/data-plane/index.js +17 -2
- package/out/cli/data-plane/registry.js +9 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +161 -0
- package/out/cli/deploy/layers/identity.js +85 -4
- package/out/cli/deploy-init.js +89 -24
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +35 -0
- package/out/cli/index.js +155 -16
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +8 -1
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +35 -1
- package/out/cli/serve-hardening.js +80 -3
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +33 -15
- package/out/engines/codex.js +110 -26
- package/out/engines/custom.js +8 -0
- package/out/evidence/sessionShip.js +610 -0
- package/out/extension.js +11 -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/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +17 -1
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/mcp.js +30 -0
- 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 -132
- 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 +11 -110
- 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 +162 -23
- 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 +59 -9
- 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/data-plane.test.js +67 -0
- package/out/test/deploy-init.test.js +40 -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/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.test.js +27 -1
- 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-hardening.test.js +45 -6
- package/out/test/serveClient.test.js +6 -5
- package/out/test/sessionShip.test.js +560 -0
- package/out/test/spotlightPublish.test.js +28 -0
- 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 +46 -9
- package/package.json +1 -1
package/out/engines/base.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*---------------------------------------------------------------------------------------------
|
|
3
3
|
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License. See LICENSE.txt in the project root for license information.
|
|
4
5
|
*--------------------------------------------------------------------------------------------*/
|
|
5
6
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
7
|
if (k2 === undefined) k2 = k;
|
|
@@ -42,6 +43,7 @@ exports.generateRequestId = generateRequestId;
|
|
|
42
43
|
exports.persistSessionIfNeeded = persistSessionIfNeeded;
|
|
43
44
|
exports.touchSessionIfNeeded = touchSessionIfNeeded;
|
|
44
45
|
exports.getPersistedSessionId = getPersistedSessionId;
|
|
46
|
+
const identity_1 = require("../analytics/identity");
|
|
45
47
|
const session_1 = require("../session");
|
|
46
48
|
const path = __importStar(require("path"));
|
|
47
49
|
/**
|
|
@@ -54,16 +56,26 @@ const path = __importStar(require("path"));
|
|
|
54
56
|
* Langfuse passthrough: langfuse_session_id / langfuse_trace_user_id headers
|
|
55
57
|
* are included so users who add Langfuse as a LiteLLM callback get session
|
|
56
58
|
* grouping automatically.
|
|
59
|
+
*
|
|
60
|
+
* IDENTITY: the user id is a salted hash (see analytics/identity.ts). It used to be the raw
|
|
61
|
+
* value — usually an email — sent on two headers to a proxy that logs every request and forwards
|
|
62
|
+
* to a third-party observability backend. The hash is stable, so grouping and per-user analytics
|
|
63
|
+
* still work; it is just no longer a directory of who works here. Set
|
|
64
|
+
* AGENTHIPPO_EVIDENCE_RAW_IDS=1 to send the raw value again.
|
|
65
|
+
*
|
|
66
|
+
* The provider never sees either form: `additional_drop_params: ["user"]` in the LiteLLM config
|
|
67
|
+
* strips the end-user field before dispatch.
|
|
57
68
|
*/
|
|
58
69
|
function buildLiteLLMMetadataHeaders(useLiteLLM, metadata) {
|
|
59
70
|
if (!useLiteLLM) {
|
|
60
71
|
return [];
|
|
61
72
|
}
|
|
62
73
|
const headers = [];
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
const userId = (0, identity_1.hashIdentity)(metadata.analyticsUserId);
|
|
75
|
+
if (userId) {
|
|
76
|
+
headers.push(`x-litellm-metadata-user-id: ${userId}`);
|
|
65
77
|
// Langfuse passthrough: maps to Langfuse trace user_id when Langfuse callback is enabled
|
|
66
|
-
headers.push(`langfuse_trace_user_id: ${
|
|
78
|
+
headers.push(`langfuse_trace_user_id: ${userId}`);
|
|
67
79
|
}
|
|
68
80
|
if (metadata.sessionId?.trim()) {
|
|
69
81
|
headers.push(`x-litellm-metadata-session-id: ${metadata.sessionId.trim()}`);
|
|
@@ -88,6 +100,23 @@ function buildLiteLLMMetadataHeaders(useLiteLLM, metadata) {
|
|
|
88
100
|
if (metadata.requestId?.trim()) {
|
|
89
101
|
headers.push(`x-litellm-metadata-request-id: ${metadata.requestId.trim()}`);
|
|
90
102
|
}
|
|
103
|
+
// Provenance stamp (PLAN/compliance.md §3.1): lands in requester_metadata on every OTEL
|
|
104
|
+
// span, which is where the evidence index reads policy_version / pack_digest from.
|
|
105
|
+
if (metadata.policyVersion?.trim()) {
|
|
106
|
+
headers.push(`x-litellm-metadata-policy-version: ${metadata.policyVersion.trim()}`);
|
|
107
|
+
}
|
|
108
|
+
if (metadata.packDigest?.trim()) {
|
|
109
|
+
headers.push(`x-litellm-metadata-pack-digest: ${metadata.packDigest.trim()}`);
|
|
110
|
+
}
|
|
111
|
+
if (metadata.engineVersion?.trim()) {
|
|
112
|
+
headers.push(`x-litellm-metadata-engine-version: ${metadata.engineVersion.trim()}`);
|
|
113
|
+
}
|
|
114
|
+
if (metadata.tier !== undefined) {
|
|
115
|
+
headers.push(`x-litellm-metadata-tier: ${metadata.tier}`);
|
|
116
|
+
}
|
|
117
|
+
if (metadata.instanceId?.trim()) {
|
|
118
|
+
headers.push(`x-litellm-metadata-instance-id: ${metadata.instanceId.trim()}`);
|
|
119
|
+
}
|
|
91
120
|
return headers;
|
|
92
121
|
}
|
|
93
122
|
/**
|
package/out/engines/claude.js
CHANGED
|
@@ -39,14 +39,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.ClaudeEngine = void 0;
|
|
40
40
|
const fs_1 = require("fs");
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const branding_1 = require("../branding");
|
|
42
43
|
const base_1 = require("./base");
|
|
43
44
|
const utils_1 = require("../utils");
|
|
44
45
|
const permissions_1 = require("../permissions");
|
|
45
46
|
const mcp_1 = require("../mcp");
|
|
46
|
-
const data_plane_1 = require("../cli/data-plane");
|
|
47
47
|
const runtime_1 = require("../cli/runtime");
|
|
48
48
|
const session_1 = require("../session");
|
|
49
49
|
const agentpack_1 = require("../agentpack");
|
|
50
|
+
const credential_context_1 = require("../cli/credential-context");
|
|
50
51
|
const shared_1 = require("../shared");
|
|
51
52
|
// File tools we track for edit diffs via hooks (Set for O(1) lookup)
|
|
52
53
|
// Includes Claude SDK tools, Computer Use tools, and common variations
|
|
@@ -261,6 +262,11 @@ class ClaudeEngine {
|
|
|
261
262
|
source: 'agenthippo',
|
|
262
263
|
engine: 'claude',
|
|
263
264
|
requestId: options.requestId,
|
|
265
|
+
policyVersion: options.policyVersion,
|
|
266
|
+
packDigest: options.packDigest,
|
|
267
|
+
instanceId: options.instanceId,
|
|
268
|
+
engineVersion: options.engineVersion,
|
|
269
|
+
tier: options.tier,
|
|
264
270
|
});
|
|
265
271
|
// Build environment for Claude SDK.
|
|
266
272
|
// Setup-token auth must use Claude's OAuth token env var instead of ANTHROPIC_API_KEY.
|
|
@@ -270,11 +276,13 @@ class ClaudeEngine {
|
|
|
270
276
|
'DEBUG_CLAUDE_AGENT_SDK': '1',
|
|
271
277
|
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC': '1',
|
|
272
278
|
};
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
279
|
+
// The gateway token stays broker-side; scrub it from the child env so it never reaches
|
|
280
|
+
// the model (secure-obo-design §7.6 / §7.4 gap 1). UNCONDITIONALLY (serve-v4 §10 hole 2):
|
|
281
|
+
// gating this on the egress broker let a deployment without --gateway-bridge re-inherit
|
|
282
|
+
// the token, including via buildCliEnvPatch's bake from the host's ~/.openclaw config.
|
|
283
|
+
delete claudeEnv[runtime_1.OPENCLAW_GATEWAY_TOKEN_ENV];
|
|
284
|
+
// Data-plane sidecar control token: never the engine's to hold (secure-obo-design §7.6).
|
|
285
|
+
delete claudeEnv['AGENTHIPPO_DATA_PLANE_TOKEN'];
|
|
278
286
|
const isSetupTokenAuth = (0, shared_1.isAnthropicSetupToken)(apiKey);
|
|
279
287
|
if (isSetupTokenAuth) {
|
|
280
288
|
const normalizedSetupToken = (0, shared_1.normalizeAnthropicSetupToken)(apiKey);
|
|
@@ -303,8 +311,13 @@ class ClaudeEngine {
|
|
|
303
311
|
// Note: cwd is ALWAYS workspaceRoot - active agent content is synced to workspace's .claude/.codex
|
|
304
312
|
const agentId = options.agentPackId ?? 'default';
|
|
305
313
|
const runtimePaths = (0, agentpack_1.resolveRuntimePaths)(options.workspaceRoot, this.config.storagePath, agentId, options.agentPackVersion);
|
|
306
|
-
// Claude config directory (isolated per agent for session/state isolation)
|
|
307
|
-
|
|
314
|
+
// Claude config directory (isolated per agent for session/state isolation). In
|
|
315
|
+
// multi-tenant serve, additionally key it by the verified request user — the same
|
|
316
|
+
// §8 Phase 0 scoping codex.ts applies to CODEX_HOME. A shared per-agent dir lets
|
|
317
|
+
// concurrent turns of different users race each other's per-turn credential state
|
|
318
|
+
// (the agentpack/paths.ts race); per-user scoping makes any remaining race
|
|
319
|
+
// same-user. No credential frame (IDE, single-tenant CLI) ⇒ unchanged path.
|
|
320
|
+
const claudeConfigDir = (0, agentpack_1.userScopedEngineHome)(runtimePaths.claudeConfigDir, (0, credential_context_1.currentCredential)()?.userId);
|
|
308
321
|
claudeEnv['CLAUDE_CONFIG_DIR'] = claudeConfigDir;
|
|
309
322
|
await fs_1.promises.mkdir(claudeConfigDir, { recursive: true });
|
|
310
323
|
// Always set AGENT_ID for agent to know its identity
|
|
@@ -549,9 +562,10 @@ class ClaudeEngine {
|
|
|
549
562
|
logger.debug(`[AgentIDE] SessionStart hook called! event=${input.hook_event_name}`);
|
|
550
563
|
return CONTINUE;
|
|
551
564
|
};
|
|
552
|
-
// Load MCP servers for the active agent and pass directly to SDK.
|
|
553
|
-
//
|
|
554
|
-
//
|
|
565
|
+
// Load MCP servers for the active agent and pass directly to SDK. URL servers carry a
|
|
566
|
+
// `${NAME}` bearer reference the CLI expands from claudeEnv at connect time, so the
|
|
567
|
+
// turn nonce rides neither a shared config file nor the SDK's --mcp-config argv —
|
|
568
|
+
// claudeEnv is passed for the rare non-expandable-name fallback only.
|
|
555
569
|
const mcpServers = await this.loadMcpServersForClaude(logger, options.workspaceRoot, agentId, claudeEnv);
|
|
556
570
|
// Determine cwd and settingSources
|
|
557
571
|
// cwd is ALWAYS workspaceRoot - active agent content has been synced to workspace's .claude/
|
|
@@ -1068,7 +1082,7 @@ class ClaudeEngine {
|
|
|
1068
1082
|
logger.info(`[AgentIDE] Claude thinking summary: startBlocks=${thinkingStartBlockCount}, redactedBlocks=${redactedThinkingBlockCount}, deltas=${thinkingDeltaCount}, chars=${thinkingCharCount}`);
|
|
1069
1083
|
logger.info(`[AgentIDE] Claude tool summary: toolBlocks=${toolUseBlockCount}, toolEmits=${toolUseEmitCount}, toolProgressMsgs=${toolProgressCount}, inputJsonDeltas=${inputJsonDeltaCount}`);
|
|
1070
1084
|
if (thinkingStartBlockCount === 0 && thinkingDeltaCount === 0 && redactedThinkingBlockCount === 0) {
|
|
1071
|
-
logger.debug(
|
|
1085
|
+
logger.debug(`[AgentIDE] Claude emitted no thinking blocks/deltas. Ensure model supports extended thinking and set ${(0, branding_1.toSettingsDisplayKey)('agentide.claude.maxThinkingTokens')} >= 1024.`);
|
|
1072
1086
|
}
|
|
1073
1087
|
if (toolUseBlockCount === 0 && toolProgressCount === 0) {
|
|
1074
1088
|
logger.debug('[AgentIDE] Claude emitted no tool usage events. If tools were used, ensure includePartialMessages=true and parse tool_use blocks.');
|
|
@@ -1202,8 +1216,12 @@ class ClaudeEngine {
|
|
|
1202
1216
|
* - Pack agents: uses pack's mcp/ directory
|
|
1203
1217
|
*
|
|
1204
1218
|
* The SDK accepts mcpServers directly in query options — including HTTP Streamable
|
|
1205
|
-
* servers (`{type:'http', url, headers}`).
|
|
1206
|
-
*
|
|
1219
|
+
* servers (`{type:'http', url, headers}`). A URL server's `bearerTokenEnv` (e.g.
|
|
1220
|
+
* AGENTHIPPO_TURN_NONCE) becomes a `${NAME}` reference the CLI expands from its own env,
|
|
1221
|
+
* NOT a resolved header: the SDK serializes mcpServers into `--mcp-config` argv, and a
|
|
1222
|
+
* resolved bearer there is world-readable via /proc/<pid>/cmdline (see
|
|
1223
|
+
* referenceHttpMcpHeaders). `turnEnv` is the per-turn child env, used only for the
|
|
1224
|
+
* fallback when a name is outside the CLI's expansion grammar.
|
|
1207
1225
|
*/
|
|
1208
1226
|
async loadMcpServersForClaude(logger, workspaceRoot, agentId = 'default', turnEnv) {
|
|
1209
1227
|
const basePromptsDir = this.getBundledBasePromptsDir();
|
|
@@ -1214,7 +1232,7 @@ class ClaudeEngine {
|
|
|
1214
1232
|
const mcpServers = {};
|
|
1215
1233
|
for (const s of servers) {
|
|
1216
1234
|
if (s.url) {
|
|
1217
|
-
const headers = (0, mcp_1.
|
|
1235
|
+
const headers = (0, mcp_1.referenceHttpMcpHeaders)(s, turnEnv ?? process.env);
|
|
1218
1236
|
mcpServers[s.name] = {
|
|
1219
1237
|
type: 'http',
|
|
1220
1238
|
url: s.url,
|
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");
|
|
@@ -47,8 +48,8 @@ const permissions_1 = require("../permissions");
|
|
|
47
48
|
const session_1 = require("../session");
|
|
48
49
|
const agentpack_1 = require("../agentpack");
|
|
49
50
|
const credential_context_1 = require("../cli/credential-context");
|
|
50
|
-
const data_plane_1 = require("../cli/data-plane");
|
|
51
51
|
const runtime_1 = require("../cli/runtime");
|
|
52
|
+
const types_1 = require("../sandbox/types");
|
|
52
53
|
// Note: HTTP edit server removed - file changes are now handled directly via file_change events
|
|
53
54
|
// -----------------------------------------------------------------------------
|
|
54
55
|
// Constants
|
|
@@ -65,11 +66,56 @@ const AGENTIDE_CODEX_AGENT_VERSION_ENV_VAR = 'AGENTIDE_ANALYTICS_AGENT_VERSION';
|
|
|
65
66
|
const AGENTIDE_CODEX_SOURCE_ENV_VAR = 'AGENTIDE_ANALYTICS_SOURCE';
|
|
66
67
|
const AGENTIDE_CODEX_ENGINE_ENV_VAR = 'AGENTIDE_ANALYTICS_ENGINE';
|
|
67
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';
|
|
68
74
|
// Bump when we change generated config semantics so long-lived sessions regenerate config.toml.
|
|
69
|
-
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
|
|
70
76
|
// Per-process runtime context for agents (not analytics): lets the agent locate its per-session context export.
|
|
71
77
|
const AGENTIDE_CONTEXT_EXPORT_PATH_ENV_VAR = 'AGENTIDE_CONTEXT_EXPORT_PATH';
|
|
72
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
|
+
}
|
|
73
119
|
function isCodexItemEvent(event) {
|
|
74
120
|
return event.type === 'item.started' || event.type === 'item.updated' || event.type === 'item.completed';
|
|
75
121
|
}
|
|
@@ -130,13 +176,17 @@ class CodexEngine {
|
|
|
130
176
|
...(runtime.cliEnvPatch ?? {}),
|
|
131
177
|
OPENAI_LOG: 'debug',
|
|
132
178
|
};
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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'];
|
|
140
190
|
// Always expose the VS Code chat session id to the agent process so it can locate:
|
|
141
191
|
// .agentide/sessions/<sessionId>/context.md
|
|
142
192
|
const rawChatSessionId = sessionId?.trim();
|
|
@@ -179,7 +229,15 @@ class CodexEngine {
|
|
|
179
229
|
agentId, // Pass agent ID for correct skills path
|
|
180
230
|
runtime.permissionHandler instanceof interfaces_1.AutoApprovePermissionHandler);
|
|
181
231
|
// LiteLLM analytics metadata (enriched - see PLAN/analytics-design.md)
|
|
182
|
-
|
|
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) {
|
|
183
241
|
if (analyticsUserId?.trim()) {
|
|
184
242
|
codexEnv[AGENTIDE_CODEX_USER_ENV_VAR] = analyticsUserId.trim();
|
|
185
243
|
}
|
|
@@ -197,6 +255,21 @@ class CodexEngine {
|
|
|
197
255
|
if (options.requestId) {
|
|
198
256
|
codexEnv[AGENTIDE_CODEX_REQUEST_ID_ENV_VAR] = options.requestId;
|
|
199
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
|
+
}
|
|
200
273
|
}
|
|
201
274
|
// For custom providers (OpenRouter, LiteLLM), set API key in CODEX_API_KEY.
|
|
202
275
|
// The config.toml's `env_key = "CODEX_API_KEY"` tells the CLI to read from this variable.
|
|
@@ -225,6 +298,16 @@ class CodexEngine {
|
|
|
225
298
|
];
|
|
226
299
|
// Map unified permissions to Codex SDK options
|
|
227
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
|
+
}
|
|
228
311
|
logger.info(`[AgentIDE] Codex mapped: sandboxMode=${codexPermOpts.sandboxMode}, approvalPolicy=${codexPermOpts.approvalPolicy}`);
|
|
229
312
|
// Build thread options
|
|
230
313
|
// workingDirectory is ALWAYS workspaceRoot - pack content is synced to workspace's .codex/
|
|
@@ -411,21 +494,7 @@ class CodexEngine {
|
|
|
411
494
|
catch { /* ignore */ }
|
|
412
495
|
pendingOperations.delete(item.id);
|
|
413
496
|
}
|
|
414
|
-
|
|
415
|
-
// Skip binary files - they would be corrupted by UTF-8 read/write
|
|
416
|
-
for (const f of pendingNewFiles.get(item.id) ?? []) {
|
|
417
|
-
try {
|
|
418
|
-
if ((0, utils_1.isBinaryFile)(f)) {
|
|
419
|
-
continue; // Don't track binary files for edit diffs
|
|
420
|
-
}
|
|
421
|
-
if (await (0, utils_1.pathExists)(f)) {
|
|
422
|
-
const code = await fs_1.promises.readFile(f, 'utf-8');
|
|
423
|
-
await fs_1.promises.unlink(f);
|
|
424
|
-
await editTracker.applyEdit(rawChatSessionId, f, code, true);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
catch { /* ignore */ }
|
|
428
|
-
}
|
|
497
|
+
await registerNewFilesWithTracker(pendingNewFiles.get(item.id) ?? [], editTracker, rawChatSessionId);
|
|
429
498
|
pendingNewFiles.delete(item.id);
|
|
430
499
|
}
|
|
431
500
|
}
|
|
@@ -637,6 +706,16 @@ class CodexEngine {
|
|
|
637
706
|
// Codex CLI loads a single "model instructions" file that acts like a system/developer prompt overlay.
|
|
638
707
|
// Note: experimental_instructions_file is deprecated and ignored by newer Codex CLI versions.
|
|
639
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
|
+
: []),
|
|
640
719
|
];
|
|
641
720
|
// Add custom provider section for LiteLLM or custom providers (e.g., OpenRouter)
|
|
642
721
|
if (customBaseUrl) {
|
|
@@ -655,7 +734,12 @@ class CodexEngine {
|
|
|
655
734
|
`"x-litellm-metadata-agent-version" = "${AGENTIDE_CODEX_AGENT_VERSION_ENV_VAR}", ` +
|
|
656
735
|
`"x-litellm-metadata-source" = "${AGENTIDE_CODEX_SOURCE_ENV_VAR}", ` +
|
|
657
736
|
`"x-litellm-metadata-engine" = "${AGENTIDE_CODEX_ENGINE_ENV_VAR}", ` +
|
|
658
|
-
`"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}" }`);
|
|
659
743
|
}
|
|
660
744
|
}
|
|
661
745
|
// Add MCP servers section (pack-aware)
|
package/out/engines/custom.js
CHANGED
|
@@ -47,6 +47,7 @@ const fs_1 = require("fs");
|
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const url_1 = require("url");
|
|
49
49
|
const base_1 = require("./base");
|
|
50
|
+
const runtime_1 = require("../cli/runtime");
|
|
50
51
|
const customRegistry_1 = require("./customRegistry");
|
|
51
52
|
const paths_1 = require("../agentpack/paths");
|
|
52
53
|
const utils_1 = require("../utils");
|
|
@@ -534,6 +535,8 @@ class CustomEngine {
|
|
|
534
535
|
AGENTHIPPO_MODEL_ID: modelId,
|
|
535
536
|
AGENTHIPPO_USE_LITELLM: routing.useLiteLLM ? 'true' : 'false',
|
|
536
537
|
};
|
|
538
|
+
delete env[runtime_1.OPENCLAW_GATEWAY_TOKEN_ENV];
|
|
539
|
+
delete env['AGENTHIPPO_DATA_PLANE_TOKEN'];
|
|
537
540
|
prependNodePath(env, manifest.manifestDir);
|
|
538
541
|
if (routingApiKey) {
|
|
539
542
|
env.AGENTHIPPO_API_KEY = routingApiKey;
|
|
@@ -560,6 +563,11 @@ class CustomEngine {
|
|
|
560
563
|
source: 'agenthippo',
|
|
561
564
|
engine: `custom:${manifest.id}`,
|
|
562
565
|
requestId: options.requestId,
|
|
566
|
+
policyVersion: options.policyVersion,
|
|
567
|
+
packDigest: options.packDigest,
|
|
568
|
+
instanceId: options.instanceId,
|
|
569
|
+
engineVersion: options.engineVersion,
|
|
570
|
+
tier: options.tier,
|
|
563
571
|
});
|
|
564
572
|
if (litellmMetadataHeaders.length > 0) {
|
|
565
573
|
env.AGENTHIPPO_LITELLM_METADATA_HEADERS = litellmMetadataHeaders.join('\n');
|