@agenthippo-ai/agenthippo 0.1.1 → 0.2.3-dataplane
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/webview/panel.js +290 -80
- package/out/analytics/sessionEvents.js +21 -9
- package/out/auth/brokerClient.js +1 -1
- package/out/auth/onboardingPrompt.js +112 -0
- package/out/auth/provider.js +1 -1
- package/out/auth-broker/config.js +8 -3
- package/out/auth-broker/server.js +36 -0
- package/out/cli/commands/ask.js +3 -0
- package/out/cli/commands/chat.js +3 -0
- package/out/cli/commands/deploy.js +49 -10
- package/out/cli/commands/gateway.js +54 -0
- package/out/cli/commands/idp.js +85 -0
- package/out/cli/commands/serve.js +315 -55
- 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 +195 -0
- package/out/cli/data-plane/index.js +137 -0
- package/out/cli/data-plane/registry.js +80 -0
- package/out/cli/deploy/blueprints/registry.js +84 -0
- package/out/cli/deploy/layers/identity.js +91 -0
- package/out/cli/deploy-init.js +1105 -335
- package/out/cli/idp-init.js +303 -0
- package/out/cli/index.js +37 -7
- package/out/cli/pack-loader.js +37 -2
- package/out/cli/runtime.js +10 -3
- package/out/cli/serve-hardening.js +60 -1
- package/out/cli/state-sync.js +16 -15
- package/out/engines/claude.js +26 -5
- package/out/engines/codex.js +24 -5
- package/out/engines/custom.js +46 -12
- package/out/extension.js +2 -0
- package/out/gateway/outboundSend.js +6 -6
- package/out/headless/index.js +24 -9
- package/out/live-probe/connector.mjs +8 -0
- package/out/live-probe/env-report.mjs +8 -0
- package/out/mcp.js +85 -67
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +14 -0
- package/out/shared/models.js +1 -1
- package/out/shared/providers.js +2 -2
- package/out/test/analytics.test.js +42 -17
- package/out/test/auth-broker.test.js +53 -0
- package/out/test/blueprint-registry.test.js +105 -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 +244 -0
- package/out/test/deploy-init.test.js +255 -4
- package/out/test/egress-broker.test.js +172 -0
- package/out/test/fleetDisplayUtils.test.js +4 -0
- package/out/test/idp-init.test.js +151 -0
- package/out/test/mcp-obo.test.js +52 -27
- package/out/test/mcp.test.js +43 -3
- package/out/test/model-routing.test.js +133 -0
- package/out/test/serve-gateway-turn.test.js +267 -0
- package/out/test/serve-hardening.test.js +80 -0
- package/out/test/serveClient.test.js +32 -0
- package/out/test/state-sync-peer.test.js +18 -5
- package/out/utils.js +27 -11
- package/package.json +1 -1
package/out/engines/claude.js
CHANGED
|
@@ -43,6 +43,8 @@ const base_1 = require("./base");
|
|
|
43
43
|
const utils_1 = require("../utils");
|
|
44
44
|
const permissions_1 = require("../permissions");
|
|
45
45
|
const mcp_1 = require("../mcp");
|
|
46
|
+
const data_plane_1 = require("../cli/data-plane");
|
|
47
|
+
const runtime_1 = require("../cli/runtime");
|
|
46
48
|
const session_1 = require("../session");
|
|
47
49
|
const agentpack_1 = require("../agentpack");
|
|
48
50
|
const shared_1 = require("../shared");
|
|
@@ -241,7 +243,8 @@ class ClaudeEngine {
|
|
|
241
243
|
// Edit tracker from runtime context (optional)
|
|
242
244
|
const editTracker = runtime.editTracker;
|
|
243
245
|
// Resolve API routing (LiteLLM proxy vs direct Anthropic)
|
|
244
|
-
|
|
246
|
+
// claude routes to Anthropic when direct (or the LiteLLM proxy when configured) — never OpenAI.
|
|
247
|
+
const { apiKey, baseUrl, useLiteLLM } = (0, utils_1.resolveApiRouting)(options, this.config, 'anthropic');
|
|
245
248
|
if (!apiKey) {
|
|
246
249
|
throw new Error('Claude API key not provided');
|
|
247
250
|
}
|
|
@@ -267,6 +270,11 @@ class ClaudeEngine {
|
|
|
267
270
|
'DEBUG_CLAUDE_AGENT_SDK': '1',
|
|
268
271
|
'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC': '1',
|
|
269
272
|
};
|
|
273
|
+
// Egress broker active ⇒ the gateway token stays broker-side; scrub it from the child
|
|
274
|
+
// env so it never reaches the model (secure-obo-design §7.6 / §7.4 gap 1).
|
|
275
|
+
if (claudeEnv[data_plane_1.EGRESS_URL_ENV]) {
|
|
276
|
+
delete claudeEnv[runtime_1.OPENCLAW_GATEWAY_TOKEN_ENV];
|
|
277
|
+
}
|
|
270
278
|
const isSetupTokenAuth = (0, shared_1.isAnthropicSetupToken)(apiKey);
|
|
271
279
|
if (isSetupTokenAuth) {
|
|
272
280
|
const normalizedSetupToken = (0, shared_1.normalizeAnthropicSetupToken)(apiKey);
|
|
@@ -541,8 +549,10 @@ class ClaudeEngine {
|
|
|
541
549
|
logger.debug(`[AgentIDE] SessionStart hook called! event=${input.hook_event_name}`);
|
|
542
550
|
return CONTINUE;
|
|
543
551
|
};
|
|
544
|
-
// Load MCP servers for the active agent and pass directly to SDK
|
|
545
|
-
|
|
552
|
+
// Load MCP servers for the active agent and pass directly to SDK. claudeEnv is the
|
|
553
|
+
// per-turn child env — URL servers resolve their bearerTokenEnv (turn nonce) from it,
|
|
554
|
+
// in memory, so no per-turn value ever lands in a shared config file.
|
|
555
|
+
const mcpServers = await this.loadMcpServersForClaude(logger, options.workspaceRoot, agentId, claudeEnv);
|
|
546
556
|
// Determine cwd and settingSources
|
|
547
557
|
// cwd is ALWAYS workspaceRoot - active agent content has been synced to workspace's .claude/
|
|
548
558
|
// Agent packs: settingSources is 'project' only (no user ~/.claude/ mixing)
|
|
@@ -1191,9 +1201,11 @@ class ClaudeEngine {
|
|
|
1191
1201
|
* - Default agent: uses bundled MCP servers from extension
|
|
1192
1202
|
* - Pack agents: uses pack's mcp/ directory
|
|
1193
1203
|
*
|
|
1194
|
-
* The SDK accepts mcpServers directly in query options
|
|
1204
|
+
* The SDK accepts mcpServers directly in query options — including HTTP Streamable
|
|
1205
|
+
* servers (`{type:'http', url, headers}`). `turnEnv` is the per-turn child env; a URL
|
|
1206
|
+
* server's `bearerTokenEnv` (e.g. AGENTHIPPO_TURN_NONCE) resolves against it in memory.
|
|
1195
1207
|
*/
|
|
1196
|
-
async loadMcpServersForClaude(logger, workspaceRoot, agentId = 'default') {
|
|
1208
|
+
async loadMcpServersForClaude(logger, workspaceRoot, agentId = 'default', turnEnv) {
|
|
1197
1209
|
const basePromptsDir = this.getBundledBasePromptsDir();
|
|
1198
1210
|
const servers = await (0, mcp_1.loadMcpServers)(agentId, workspaceRoot, basePromptsDir);
|
|
1199
1211
|
if (servers.length === 0) {
|
|
@@ -1201,6 +1213,15 @@ class ClaudeEngine {
|
|
|
1201
1213
|
}
|
|
1202
1214
|
const mcpServers = {};
|
|
1203
1215
|
for (const s of servers) {
|
|
1216
|
+
if (s.url) {
|
|
1217
|
+
const headers = (0, mcp_1.resolveHttpMcpHeaders)(s, turnEnv ?? process.env);
|
|
1218
|
+
mcpServers[s.name] = {
|
|
1219
|
+
type: 'http',
|
|
1220
|
+
url: s.url,
|
|
1221
|
+
...(headers ? { headers } : {}),
|
|
1222
|
+
};
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1204
1225
|
mcpServers[s.name] = {
|
|
1205
1226
|
command: s.command,
|
|
1206
1227
|
args: s.args,
|
package/out/engines/codex.js
CHANGED
|
@@ -46,6 +46,9 @@ const utils_1 = require("../utils");
|
|
|
46
46
|
const permissions_1 = require("../permissions");
|
|
47
47
|
const session_1 = require("../session");
|
|
48
48
|
const agentpack_1 = require("../agentpack");
|
|
49
|
+
const credential_context_1 = require("../cli/credential-context");
|
|
50
|
+
const data_plane_1 = require("../cli/data-plane");
|
|
51
|
+
const runtime_1 = require("../cli/runtime");
|
|
49
52
|
// Note: HTTP edit server removed - file changes are now handled directly via file_change events
|
|
50
53
|
// -----------------------------------------------------------------------------
|
|
51
54
|
// Constants
|
|
@@ -63,7 +66,7 @@ const AGENTIDE_CODEX_SOURCE_ENV_VAR = 'AGENTIDE_ANALYTICS_SOURCE';
|
|
|
63
66
|
const AGENTIDE_CODEX_ENGINE_ENV_VAR = 'AGENTIDE_ANALYTICS_ENGINE';
|
|
64
67
|
const AGENTIDE_CODEX_REQUEST_ID_ENV_VAR = 'AGENTIDE_ANALYTICS_REQUEST_ID';
|
|
65
68
|
// Bump when we change generated config semantics so long-lived sessions regenerate config.toml.
|
|
66
|
-
const AGENTIDE_CODEX_CONFIG_VERSION =
|
|
69
|
+
const AGENTIDE_CODEX_CONFIG_VERSION = 20; // v20: default_tools_approval_mode "auto"→"approve" ("auto" is invalid on codex ≥0.131 and cancels every headless MCP tool call)
|
|
67
70
|
// Per-process runtime context for agents (not analytics): lets the agent locate its per-session context export.
|
|
68
71
|
const AGENTIDE_CONTEXT_EXPORT_PATH_ENV_VAR = 'AGENTIDE_CONTEXT_EXPORT_PATH';
|
|
69
72
|
const AGENTIDE_TERMINAL_LOG_PATH_ENV_VAR = 'AGENTIDE_TERMINAL_LOG_PATH';
|
|
@@ -100,7 +103,8 @@ class CodexEngine {
|
|
|
100
103
|
const threadModelId = options.modelId;
|
|
101
104
|
// Resolve API routing (LiteLLM proxy vs direct OpenAI)
|
|
102
105
|
// Use openaiBaseUrl which has overrideOpenAIBaseUrl applied
|
|
103
|
-
|
|
106
|
+
// codex routes to OpenAI when direct (or the LiteLLM proxy when configured) — never Anthropic.
|
|
107
|
+
const routing = (0, utils_1.resolveApiRouting)(options, this.config, 'openai');
|
|
104
108
|
const apiKey = routing.apiKey;
|
|
105
109
|
const baseUrl = routing.openaiBaseUrl; // Use OpenAI-specific URL
|
|
106
110
|
const useLiteLLM = routing.useLiteLLM;
|
|
@@ -126,6 +130,13 @@ class CodexEngine {
|
|
|
126
130
|
...(runtime.cliEnvPatch ?? {}),
|
|
127
131
|
OPENAI_LOG: 'debug',
|
|
128
132
|
};
|
|
133
|
+
// When serve runs the egress broker, the OpenClaw gateway token must never enter the
|
|
134
|
+
// agent's trust domain (secure-obo-design §7.6 / §7.4 gap 1). Scrub it from the child env
|
|
135
|
+
// at the spawn boundary — belt-and-suspenders with serve's boot-time process.env strip,
|
|
136
|
+
// so a token in process.env (e.g. captured before the strip) still cannot reach the model.
|
|
137
|
+
if (codexEnv[data_plane_1.EGRESS_URL_ENV]) {
|
|
138
|
+
delete codexEnv[runtime_1.OPENCLAW_GATEWAY_TOKEN_ENV];
|
|
139
|
+
}
|
|
129
140
|
// Always expose the VS Code chat session id to the agent process so it can locate:
|
|
130
141
|
// .agentide/sessions/<sessionId>/context.md
|
|
131
142
|
const rawChatSessionId = sessionId?.trim();
|
|
@@ -141,8 +152,12 @@ class CodexEngine {
|
|
|
141
152
|
// Note: workingDirectory is ALWAYS workspaceRoot - active agent content is synced to workspace's .codex/
|
|
142
153
|
const agentId = options.agentPackId ?? 'default';
|
|
143
154
|
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
|
-
|
|
155
|
+
// Use agent-isolated CODEX_HOME (for session/state isolation). In multi-tenant serve,
|
|
156
|
+
// additionally key it by the verified request user: config.toml is rewritten per turn,
|
|
157
|
+
// and a shared per-agent home lets concurrent turns of different users race it
|
|
158
|
+
// (secure-obo-design §1 defect 1 / §8 Phase 0). No credential frame (IDE,
|
|
159
|
+
// single-tenant CLI) ⇒ unchanged per-agent path.
|
|
160
|
+
const codexHome = (0, agentpack_1.userScopedEngineHome)(runtimePaths.codexHome, (0, credential_context_1.currentCredential)()?.userId);
|
|
146
161
|
codexEnv['CODEX_HOME'] = codexHome;
|
|
147
162
|
await fs_1.promises.mkdir(codexHome, { recursive: true });
|
|
148
163
|
// Always set AGENT_ID for agent to know its identity
|
|
@@ -601,7 +616,11 @@ class CodexEngine {
|
|
|
601
616
|
// Codex CLI requires wire_api=responses (chat wire was removed in newer Codex releases).
|
|
602
617
|
const wireApi = 'responses';
|
|
603
618
|
const routingType = useLiteLLM ? 'litellm' : (customBaseUrl ? 'custom' : 'openai');
|
|
604
|
-
|
|
619
|
+
// No per-turn component in this signature: config.toml must be byte-identical across
|
|
620
|
+
// turns (per-turn auth rides the process env via bearer_token_env_var, never the file),
|
|
621
|
+
// so concurrent same-home turns write identical bytes and the old cross-turn race is
|
|
622
|
+
// gone by construction (secure-obo-design §3.2).
|
|
623
|
+
const signature = `${AGENTIDE_CODEX_CONFIG_VERSION}:${routingType}:${customBaseUrl ?? ''}:${wireApi}:${agentId}:${codexInstructionsFile ?? ''}`;
|
|
605
624
|
if (this.lastWrittenConfigSignature === signature) {
|
|
606
625
|
return;
|
|
607
626
|
}
|
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");
|
|
@@ -83,10 +85,45 @@ function prependNodePath(env, engineDir) {
|
|
|
83
85
|
? `${engineNodeModules}${path.delimiter}${existing}`
|
|
84
86
|
: engineNodeModules;
|
|
85
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Deliver the per-turn model-routing values into the env vars a manifest declares
|
|
90
|
+
* (`model.apiKeyEnvVar` / `baseUrlEnvVar` / `openaiBaseUrlEnvVar`).
|
|
91
|
+
*
|
|
92
|
+
* NON-CLOBBERING: a var already set in the host's own ambient env is left alone. Two reasons:
|
|
93
|
+
* (1) it preserves the effective behavior deployments already relied on — the old
|
|
94
|
+
* `{...turn.env, ...process.env}` child-env spread accidentally let ambient values win,
|
|
95
|
+
* and operators' engine-owned keys (e.g. a real CURSOR_API_KEY vs a stale manifest that
|
|
96
|
+
* wrongly declared it as a delivery slot) must keep winning after that spread is fixed;
|
|
97
|
+
* (2) it makes the manifest contract safe by construction: mis-declaring an operator-owned
|
|
98
|
+
* credential as a delivery slot can shadow nothing.
|
|
99
|
+
* The flip side is documented on CustomEngineModelConfig: ambient-exporting a genuine
|
|
100
|
+
* delivery var (e.g. LITELLM_API_KEY in serve's shell) shadows per-turn rotation.
|
|
101
|
+
*/
|
|
102
|
+
function deliverModelRoutingEnv(env, manifest, routing, ambientEnv, logger) {
|
|
103
|
+
const deliver = (envVar, value, label) => {
|
|
104
|
+
if (!envVar || !value) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (ambientEnv[envVar]?.trim()) {
|
|
108
|
+
logger?.info(`[AgentIDE] Custom engine "${manifest.id}": ${envVar} already set in ambient env — keeping the operator's value, skipping per-turn ${label} delivery`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
env[envVar] = value;
|
|
112
|
+
};
|
|
113
|
+
deliver(manifest.model?.apiKeyEnvVar, routing.apiKey, 'API key');
|
|
114
|
+
deliver(manifest.model?.baseUrlEnvVar, routing.baseUrl, 'base URL');
|
|
115
|
+
deliver(manifest.model?.openaiBaseUrlEnvVar, routing.openaiBaseUrl, 'OpenAI base URL');
|
|
116
|
+
}
|
|
86
117
|
function buildRunnerChildEnv(manifest, turn, probeImport) {
|
|
87
118
|
const childEnv = {
|
|
88
|
-
|
|
119
|
+
// Per-turn values WIN over the host's ambient env. turn.env starts from a spread of
|
|
120
|
+
// process.env, so for undeclared vars this is identical either way; the difference is
|
|
121
|
+
// deliberate per-turn state (AGENTHIPPO_* control vars, the data-plane turn nonce,
|
|
122
|
+
// session paths, manifest-declared deliveries) which must never be reverted by a
|
|
123
|
+
// same-named var in the host's own environment. Ambient-vs-delivery precedence for
|
|
124
|
+
// manifest credential vars is decided earlier, in deliverModelRoutingEnv (non-clobber).
|
|
89
125
|
...process.env,
|
|
126
|
+
...turn.env,
|
|
90
127
|
AGENTHIPPO_ENGINE_ENTRY: manifest.entryPath,
|
|
91
128
|
AGENTHIPPO_MESSAGE: turn.message,
|
|
92
129
|
AGENTHIPPO_WORKSPACE_ROOT: turn.workspaceRoot,
|
|
@@ -466,7 +503,10 @@ class CustomEngine {
|
|
|
466
503
|
const attachmentsDir = path.join(workspaceSessionDir, 'attachments');
|
|
467
504
|
const nativeSessionId = this.activeSessions.get(conversationKey)?.nativeSessionId ??
|
|
468
505
|
(0, base_1.getPersistedSessionId)(`custom:${manifest.id}`, conversationKey);
|
|
469
|
-
|
|
506
|
+
// A custom engine isn't pinned to one vendor — infer its direct provider from the model
|
|
507
|
+
// (e.g. openclaw's litellm/* routes through the proxy anyway; a claude-* model → Anthropic).
|
|
508
|
+
// The engine's manifest still decides which env var receives the resolved key.
|
|
509
|
+
const routing = (0, utils_1.resolveApiRouting)(options, this.config, (0, utils_1.inferModelProvider)(options.modelId), runtime.configProvider);
|
|
470
510
|
const routingApiKey = routing.apiKey?.trim() || undefined;
|
|
471
511
|
const routingBaseUrl = routing.baseUrl?.trim() || undefined;
|
|
472
512
|
const routingOpenAIBaseUrl = routing.openaiBaseUrl?.trim() || undefined;
|
|
@@ -532,15 +572,7 @@ class CustomEngine {
|
|
|
532
572
|
if (manifest.homeEnvVar) {
|
|
533
573
|
env[manifest.homeEnvVar] = engineHomeDir;
|
|
534
574
|
}
|
|
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
|
-
}
|
|
575
|
+
deliverModelRoutingEnv(env, manifest, { apiKey: routingApiKey, baseUrl: routingBaseUrl, openaiBaseUrl: routingOpenAIBaseUrl }, process.env, runtime.logger);
|
|
544
576
|
if ((routingBaseUrl && (0, utils_1.applyLocalTlsBypass)(env, routingBaseUrl))
|
|
545
577
|
|| (routingOpenAIBaseUrl && routingOpenAIBaseUrl !== routingBaseUrl && (0, utils_1.applyLocalTlsBypass)(env, routingOpenAIBaseUrl))) {
|
|
546
578
|
runtime.logger.info(`[AgentIDE] Disabling TLS verification for local HTTPS (${manifest.id})`);
|
|
@@ -550,7 +582,9 @@ class CustomEngine {
|
|
|
550
582
|
: (routingBaseUrl ? 'custom provider' : 'native provider');
|
|
551
583
|
runtime.logger.info(`[AgentIDE] Custom engine "${manifest.id}" routing: ${routingDescription}, baseUrl=${routingBaseUrl || 'default'}, model=${modelId}`);
|
|
552
584
|
const resolvedMcp = await (0, mcp_1.loadMcpServers)(agentId, options.workspaceRoot, this.getBundledBasePromptsDir());
|
|
553
|
-
|
|
585
|
+
// `env` is this turn's child env — URL servers resolve bearerTokenEnv (turn nonce)
|
|
586
|
+
// from it in memory, so per-turn auth never lands in a shared file.
|
|
587
|
+
const mcpServers = (0, mcp_1.formatInlineMcpServers)(resolvedMcp, env);
|
|
554
588
|
if (resolvedMcp.length > 0) {
|
|
555
589
|
runtime.logger.debug(`[AgentIDE] Custom engine "${manifest.id}" MCP (${agentId}): ${resolvedMcp.map(s => s.name).join(', ')}`);
|
|
556
590
|
}
|
package/out/extension.js
CHANGED
|
@@ -68,6 +68,7 @@ const serveClient_js_1 = require("./remote/serveClient.js");
|
|
|
68
68
|
const remotePackStatusBar_js_1 = require("./remote/remotePackStatusBar.js");
|
|
69
69
|
const provider_js_1 = require("./auth/provider.js");
|
|
70
70
|
const signInFlow_js_1 = require("./auth/signInFlow.js");
|
|
71
|
+
const onboardingPrompt_js_1 = require("./auth/onboardingPrompt.js");
|
|
71
72
|
const githubStoreGate_js_1 = require("./auth/githubStoreGate.js");
|
|
72
73
|
const sessionEvents_1 = require("./analytics/sessionEvents");
|
|
73
74
|
const base_1 = require("./engines/base");
|
|
@@ -323,6 +324,7 @@ async function activate(context) {
|
|
|
323
324
|
(0, provider_js_1.registerAgentHippoAuth)(context);
|
|
324
325
|
void (0, provider_js_1.refreshBootstrapIfSignedIn)(context);
|
|
325
326
|
context.subscriptions.push((0, provider_js_1.startAgentHippoTokenRefreshTimer)(context));
|
|
327
|
+
(0, onboardingPrompt_js_1.scheduleSignInOnboardingPrompt)(context);
|
|
326
328
|
// Register commands
|
|
327
329
|
registerCommands(context, _state);
|
|
328
330
|
const remotePackStatusBar = new remotePackStatusBar_js_1.RemotePackStatusBar(syncService, () => getWorkspaceRoot(outputChannel).workspaceRoot, outputChannel);
|
|
@@ -172,12 +172,12 @@ function buildGatewaySendParams(handle, message, media) {
|
|
|
172
172
|
function buildGatewayRpcCommand(handle, message, media) {
|
|
173
173
|
return [exports.GATEWAY_OUTBOUND_RPC_METHOD, JSON.stringify(buildGatewaySendParams(handle, message, media))];
|
|
174
174
|
}
|
|
175
|
-
async function sendViaGatewayRpc(handle, message, media) {
|
|
175
|
+
async function sendViaGatewayRpc(handle, message, media, explicitToken) {
|
|
176
176
|
const gatewayUrl = process.env.OPENCLAW_GATEWAY_URL?.trim() || `ws://127.0.0.1:${DEFAULT_GATEWAY_PORT}`;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
// Prefer the broker-supplied token; the env re-population that used to happen here is gone
|
|
178
|
+
// deliberately — under the egress broker it would re-leak the token into process.env, and
|
|
179
|
+
// every engine child spreads process.env (secure-obo-design §7.6).
|
|
180
|
+
const token = explicitToken?.trim() || (0, runtime_js_1.readOpenClawGatewayToken)();
|
|
181
181
|
const { client, clientInfo } = (0, client_js_1.createEphemeralGatewayClient)(gatewayUrl, token);
|
|
182
182
|
await client.connect(clientInfo);
|
|
183
183
|
try {
|
|
@@ -218,7 +218,7 @@ async function sendGatewayOutboundMessage(handle, options) {
|
|
|
218
218
|
const cliCommand = [cli, ...cliArgs];
|
|
219
219
|
const rpcCommand = buildGatewayRpcCommand(handle, options.message, media);
|
|
220
220
|
try {
|
|
221
|
-
const rpc = await sendViaGatewayRpc(handle, options.message, media);
|
|
221
|
+
const rpc = await sendViaGatewayRpc(handle, options.message, media, options.token);
|
|
222
222
|
return {
|
|
223
223
|
ok: true,
|
|
224
224
|
via: 'gateway-rpc',
|
package/out/headless/index.js
CHANGED
|
@@ -175,6 +175,8 @@ const session_1 = require("../session");
|
|
|
175
175
|
const sync_1 = require("./sync");
|
|
176
176
|
const sessionEvents_1 = require("../analytics/sessionEvents");
|
|
177
177
|
const runtime_1 = require("../cli/runtime");
|
|
178
|
+
const credential_context_1 = require("../cli/credential-context");
|
|
179
|
+
const paths_1 = require("../utils/paths");
|
|
178
180
|
function resolveHeadlessAnalyticsUserId(runOverride, configOverride) {
|
|
179
181
|
const fromRun = runOverride?.trim();
|
|
180
182
|
if (fromRun) {
|
|
@@ -223,13 +225,18 @@ function createHeadlessRunner(config) {
|
|
|
223
225
|
// the legacy single file at provider-sessions.json on first load.
|
|
224
226
|
(0, session_1.initSessionStoreWithStore)(new interfaces_1.DirectorySessionStore(path.join(config.storagePath, 'provider-sessions')));
|
|
225
227
|
}
|
|
226
|
-
//
|
|
228
|
+
// Per-destination credentials. When a caller supplies the split keys (serve), use them so
|
|
229
|
+
// each engine picks the key for the endpoint it dials. Otherwise fall back to the single
|
|
230
|
+
// `apiKey` as the LiteLLM key (back-compat: eval/CLI runners configured one key).
|
|
231
|
+
const hasSplitKeys = Boolean(config.litellmApiKey || config.openaiApiKey || config.anthropicApiKey);
|
|
227
232
|
const engineConfig = {
|
|
228
233
|
storagePath: config.storagePath,
|
|
229
234
|
codexHome: undefined,
|
|
230
235
|
claudeConfigDir: undefined,
|
|
231
|
-
litellmApiKey: config.apiKey,
|
|
236
|
+
litellmApiKey: config.litellmApiKey ?? (hasSplitKeys ? undefined : config.apiKey),
|
|
232
237
|
litellmBaseUrl: config.baseUrl,
|
|
238
|
+
openaiApiKey: config.openaiApiKey,
|
|
239
|
+
anthropicApiKey: config.anthropicApiKey,
|
|
233
240
|
claudeCodeExecutablePath: config.claudeCodeExecutablePath,
|
|
234
241
|
codexExecutablePath: config.codexExecutablePath,
|
|
235
242
|
};
|
|
@@ -283,11 +290,12 @@ function createHeadlessRunner(config) {
|
|
|
283
290
|
}
|
|
284
291
|
return customEngine;
|
|
285
292
|
};
|
|
286
|
-
// Shared session event writer for Layer 2 analytics.
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
293
|
+
// Shared session event writer for Layer 2 analytics. Defaults to getAgentideHome() — the
|
|
294
|
+
// exact directory sessionJsonlFleet.ts and query-analytics.sh already scan, so tool-call/
|
|
295
|
+
// warrant events stay discoverable — but honors an explicit override (config.analyticsSessionsDir)
|
|
296
|
+
// so callers with their own state-dir precedence (serve.ts) don't regress durability when
|
|
297
|
+
// no AGENTHIPPO_HOME-style env var is set but a durable --state-dir was given explicitly.
|
|
298
|
+
const sessionEventWriter = new sessionEvents_1.SessionEventWriter(config.analyticsSessionsDir ?? path.join((0, paths_1.getAgentideHome)(), 'analytics', 'sessions'));
|
|
291
299
|
return {
|
|
292
300
|
async run(options) {
|
|
293
301
|
const result = {
|
|
@@ -339,8 +347,10 @@ function createHeadlessRunner(config) {
|
|
|
339
347
|
const requestId = (0, base_1.generateRequestId)();
|
|
340
348
|
// Wrap emitter with observability (Layer 2 session events)
|
|
341
349
|
// Session events are written to ~/.agent-hippo/analytics/sessions/<session_id>.jsonl
|
|
350
|
+
// Capture warrant userId here (inside serve's runWithCredential continuation), not
|
|
351
|
+
// later at tool emit — custom-engine IPC callbacks can lose/stick ambient ALS.
|
|
342
352
|
const sessionId = options.sessionId ?? `headless-${Date.now()}`;
|
|
343
|
-
const observableEmitter = (0, sessionEvents_1.wrapEmitterWithObservability)(baseEmitter, sessionEventWriter, sessionId, requestId);
|
|
353
|
+
const observableEmitter = (0, sessionEvents_1.wrapEmitterWithObservability)(baseEmitter, sessionEventWriter, sessionId, requestId, (0, credential_context_1.currentCredential)()?.userId);
|
|
344
354
|
const requestOptions = {
|
|
345
355
|
modelId: options.modelId,
|
|
346
356
|
engineId: options.engineId,
|
|
@@ -348,7 +358,11 @@ function createHeadlessRunner(config) {
|
|
|
348
358
|
additionalDirectories: options.additionalDirectories,
|
|
349
359
|
sessionId,
|
|
350
360
|
analyticsUserId: resolveHeadlessAnalyticsUserId(options.analyticsUserId, config.analyticsUserId),
|
|
351
|
-
|
|
361
|
+
// With split keys (serve), leave options.apiKey UNSET so resolveApiRouting selects
|
|
362
|
+
// the key for the engine's destination — passing the collapsed key here would let
|
|
363
|
+
// it win over the correct provider key (the original bug). Back-compat single-key
|
|
364
|
+
// callers keep options.apiKey.
|
|
365
|
+
apiKey: hasSplitKeys ? undefined : config.apiKey,
|
|
352
366
|
baseUrl: config.baseUrl,
|
|
353
367
|
useLiteLLM: config.useLiteLLM,
|
|
354
368
|
agentPackId: packId,
|
|
@@ -367,6 +381,7 @@ function createHeadlessRunner(config) {
|
|
|
367
381
|
chatSessionId: sessionId,
|
|
368
382
|
gatewaySessionKey: options.gatewaySessionKey,
|
|
369
383
|
}),
|
|
384
|
+
...(options.turnEnv ?? {}),
|
|
370
385
|
},
|
|
371
386
|
};
|
|
372
387
|
try {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
const server = new Server({ name: 'probe', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
6
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: 'probe_tool', description: 'x', inputSchema: { type: 'object' } }] }));
|
|
7
|
+
server.setRequestHandler(CallToolRequestSchema, async () => ({ content: [{ type: 'text', text: 'MAGIC=zebra42' }] }));
|
|
8
|
+
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
const server = new Server({ name: 'env-report', version: '0.0.1' }, { capabilities: { tools: {} } });
|
|
6
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [{ name: 'report_env', description: 'reports a probe env var', inputSchema: { type: 'object' } }] }));
|
|
7
|
+
server.setRequestHandler(CallToolRequestSchema, async () => ({ content: [{ type: 'text', text: 'PROBE_SEEN=' + (process.env.AGENTHIPPO_PROBE_ENV || 'ABSENT') }] }));
|
|
8
|
+
await server.connect(new StdioServerTransport());
|