@agenthippo-ai/agenthippo 0.2.3-dataplane → 1.110.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/out/agentpack/index.js +3 -1
  2. package/out/agentpack/paths.js +25 -2
  3. package/out/analytics/dashboard/localFleetAdapter.js +82 -2
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +3 -2
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +29 -25
  8. package/out/analytics/sessionEvents.js +100 -24
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +48 -14
  12. package/out/auth/provider.js +8 -21
  13. package/out/auth/sessionStore.js +3 -40
  14. package/out/auth/signInFlow.js +2 -1
  15. package/out/auth-broker/config.js +50 -5
  16. package/out/auth-broker/server.js +126 -26
  17. package/out/auth-broker/store.js +25 -24
  18. package/out/authority/warrant.js +206 -0
  19. package/out/branding.js +27 -1
  20. package/out/cli/commands/conformance.js +249 -0
  21. package/out/cli/commands/data-plane.js +151 -0
  22. package/out/cli/commands/deploy.js +1 -0
  23. package/out/cli/commands/gateway.js +4 -0
  24. package/out/cli/commands/pack.js +182 -3
  25. package/out/cli/commands/sandbox.js +351 -0
  26. package/out/cli/commands/serve.js +1145 -130
  27. package/out/cli/commands/store.js +12 -4
  28. package/out/cli/credential-context.js +17 -0
  29. package/out/cli/data-plane/gateway.js +28 -5
  30. package/out/cli/data-plane/index.js +18 -2
  31. package/out/cli/data-plane/registry.js +9 -0
  32. package/out/cli/data-plane/remote.js +106 -0
  33. package/out/cli/data-plane/sidecar.js +225 -0
  34. package/out/cli/deploy/layers/identity.js +88 -4
  35. package/out/cli/deploy-init.js +89 -24
  36. package/out/cli/deps.js +106 -2
  37. package/out/cli/idp-init.js +35 -0
  38. package/out/cli/index.js +171 -17
  39. package/out/cli/pack-install-ref.js +15 -0
  40. package/out/cli/pack-loader.js +8 -1
  41. package/out/cli/pack-runtime.js +61 -0
  42. package/out/cli/runtime.js +35 -1
  43. package/out/cli/serve-hardening.js +260 -14
  44. package/out/cli/utils.js +12 -1
  45. package/out/engines/base.js +32 -3
  46. package/out/engines/claude.js +33 -15
  47. package/out/engines/codex.js +111 -27
  48. package/out/engines/custom.js +15 -1
  49. package/out/evidence/sessionShip.js +643 -0
  50. package/out/extension.js +11 -2
  51. package/out/gateway/bridge.js +40 -2
  52. package/out/gateway/client.js +34 -1
  53. package/out/gateway/installSource.js +68 -0
  54. package/out/gateway/lifecycle.js +185 -51
  55. package/out/gateway/outboundPaths.js +4 -1
  56. package/out/gateway/secretsPreflight.js +90 -0
  57. package/out/gateway/service.js +7 -2
  58. package/out/headless/index.js +25 -2
  59. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  60. package/out/mcp.js +30 -0
  61. package/out/pack/caps.js +271 -0
  62. package/out/pack/governor.js +326 -0
  63. package/out/pack/lease-schema.js +273 -0
  64. package/out/pack/lease.js +682 -0
  65. package/out/pack/reconcile.js +217 -0
  66. package/out/pack/signature.js +839 -0
  67. package/out/remote/handleRemoteChat.js +44 -3
  68. package/out/remote/remoteTrust.js +277 -0
  69. package/out/remote/serveClient.js +52 -5
  70. package/out/runner/host.js +141 -0
  71. package/out/runner/protocol.js +128 -0
  72. package/out/runner/remoteEngine.js +166 -0
  73. package/out/runtime/interfaces/edit-tracker.js +2 -0
  74. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  75. package/out/sandbox/containerDriver.js +572 -0
  76. package/out/sandbox/processDriver.js +318 -0
  77. package/out/sandbox/sandboxRunner.js +303 -0
  78. package/out/sandbox/support.js +301 -0
  79. package/out/sandbox/types.js +55 -0
  80. package/out/scripts/agentide-litellm.sh +67 -5
  81. package/out/scripts/agentide_litellm_hooks_v1.py +266 -21
  82. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +98 -132
  83. package/out/scripts/analytics/setup-analytics.ps1 +45 -14
  84. package/out/scripts/analytics/setup-analytics.sh +106 -15
  85. package/out/scripts/analytics/start-analytics.sh +86 -14
  86. package/out/scripts/gateway/setup-gateway.sh +72 -32
  87. package/out/shared/models.js +11 -110
  88. package/out/spotlight/install.js +7 -1
  89. package/out/store/index.js +41 -10
  90. package/out/sync.js +3 -1
  91. package/out/test/analytics.test.js +162 -23
  92. package/out/test/auth-baseurl.test.js +132 -0
  93. package/out/test/auth-bootstrapApply.test.js +27 -6
  94. package/out/test/auth-broker-hardening.test.js +227 -0
  95. package/out/test/auth-broker.test.js +59 -9
  96. package/out/test/branding.test.js +13 -0
  97. package/out/test/caps-governor.test.js +460 -0
  98. package/out/test/codex-new-file-survival.test.js +119 -0
  99. package/out/test/configuration-scopes.test.js +131 -0
  100. package/out/test/data-plane-engine-wiring.test.js +32 -0
  101. package/out/test/data-plane.test.js +380 -0
  102. package/out/test/deploy-init.test.js +47 -1
  103. package/out/test/gateway-client.test.js +28 -0
  104. package/out/test/gateway-install-source.test.js +111 -0
  105. package/out/test/gateway-lifecycle.test.js +1 -1
  106. package/out/test/gateway-outbound.test.js +7 -0
  107. package/out/test/gateway-secrets-preflight.test.js +127 -0
  108. package/out/test/gateway-session-routing.test.js +18 -0
  109. package/out/test/governor-hardening.test.js +222 -0
  110. package/out/test/headless-sync.test.js +22 -0
  111. package/out/test/lease-signature.test.js +357 -0
  112. package/out/test/lease-trust-root.test.js +273 -0
  113. package/out/test/lease-validation.test.js +224 -0
  114. package/out/test/lease.test.js +114 -0
  115. package/out/test/localFleetAdapter.test.js +91 -0
  116. package/out/test/mcp.test.js +27 -1
  117. package/out/test/pack-admission.test.js +216 -0
  118. package/out/test/pack-digest-parity.test.js +140 -0
  119. package/out/test/pack-reconcile.test.js +183 -0
  120. package/out/test/pack-signature-artifact.test.js +103 -0
  121. package/out/test/pack-signature.test.js +176 -0
  122. package/out/test/pack-trust-root.test.js +202 -0
  123. package/out/test/remote-trust.test.js +242 -0
  124. package/out/test/runner-protocol.test.js +249 -0
  125. package/out/test/sandbox-driver.test.js +553 -0
  126. package/out/test/sandbox-hardening.test.js +406 -0
  127. package/out/test/sandbox-runner.test.js +343 -0
  128. package/out/test/sandbox-scope.test.js +449 -0
  129. package/out/test/serve-cron-acceptance.test.js +370 -0
  130. package/out/test/serve-gateway-turn.test.js +122 -0
  131. package/out/test/serve-hardening.test.js +227 -15
  132. package/out/test/serveClient.test.js +6 -5
  133. package/out/test/sessionShip.test.js +618 -0
  134. package/out/test/spotlightPublish.test.js +28 -0
  135. package/out/test/store-install-safe.test.js +69 -0
  136. package/out/test/tls-bypass.test.js +90 -0
  137. package/out/test/warrant.test.js +138 -0
  138. package/out/utils.js +46 -9
  139. package/package.json +1 -1
package/out/cli/index.js CHANGED
@@ -75,6 +75,7 @@ const commander_1 = require("commander");
75
75
  const chat_1 = require("./commands/chat");
76
76
  const ask_1 = require("./commands/ask");
77
77
  const serve_1 = require("./commands/serve");
78
+ const data_plane_1 = require("./commands/data-plane");
78
79
  const auth_broker_1 = require("./commands/auth-broker");
79
80
  const deploy_1 = require("./commands/deploy");
80
81
  const idp_1 = require("./commands/idp");
@@ -86,7 +87,36 @@ const probe_custom_engine_1 = require("./commands/probe-custom-engine");
86
87
  const deps_1 = require("./deps");
87
88
  const branding_1 = require("../branding");
88
89
  const agentEnvFile_1 = require("../utils/agentEnvFile");
89
- const VERSION = '1.110.8';
90
+ const caps_1 = require("../pack/caps");
91
+ /**
92
+ * `--max-concurrent-sandboxes`, honouring the deprecated `--max-concurrent-cells`.
93
+ *
94
+ * The current spelling wins outright when both are given; the old one is honoured with a loud
95
+ * notice rather than dropped, because dropping it would silently REMOVE a blast-radius cap from a
96
+ * deployment that believes it still has one.
97
+ */
98
+ function resolveMaxConcurrentSandboxes(options) {
99
+ const current = parseInt(options.maxConcurrentSandboxes ?? '', 10);
100
+ if (Number.isFinite(current) && current > 0) {
101
+ return current;
102
+ }
103
+ const legacy = parseInt(options.maxConcurrentCells ?? '', 10);
104
+ if (Number.isFinite(legacy) && legacy > 0) {
105
+ console.warn(`Warning: --max-concurrent-cells is DEPRECATED; use ${caps_1.DEPRECATED_CAP_SPELLINGS['--max-concurrent-cells']}. Honouring it for now.`);
106
+ return legacy;
107
+ }
108
+ return undefined;
109
+ }
110
+ /** Single source: extensions/agentide/package.json (bundled into the compiled CLI by Bun). */
111
+ function resolveCliVersion() {
112
+ try {
113
+ return require('../../package.json').version ?? 'dev';
114
+ }
115
+ catch {
116
+ return 'dev';
117
+ }
118
+ }
119
+ const VERSION = resolveCliVersion();
90
120
  const CLI_DESCRIPTION = [
91
121
  `${branding_1.AGENTIDE_BRAND_NAME} CLI - Run agent packs from the command line`,
92
122
  '',
@@ -102,14 +132,22 @@ const CLI_DESCRIPTION = [
102
132
  ' AGENTHIPPO_STORE_REPO Override store GitHub repo (owner/repo; CLI store commands)',
103
133
  '',
104
134
  'Quickstart:',
105
- ` AGENTHIPPO_LITELLM_BASE_URL="http://localhost:4000" AGENTHIPPO_LITELLM_API_KEY="dummy" AGENTHIPPO_DEFAULT_MODEL="litellm/gpt-5.3-codex" AGENTHIPPO_DEFAULT_ENGINE="codex" ${branding_1.AGENTIDE_CLI_NAME} ask "Reply with exactly: CLI_TEST_OK"`,
135
+ ` AGENTHIPPO_LITELLM_BASE_URL="http://localhost:4000" AGENTHIPPO_LITELLM_API_KEY="dummy" AGENTHIPPO_DEFAULT_MODEL="litellm/gpt-5.5" AGENTHIPPO_DEFAULT_ENGINE="codex" ${branding_1.AGENTIDE_CLI_NAME} ask "Reply with exactly: CLI_TEST_OK"`,
106
136
  '',
107
137
  'Agent Packs:',
108
138
  ` Loaded from ~/${branding_1.AGENTIDE_DIR_NAME}/agents/<name>/ or absolute/relative paths.`,
109
139
  ' Pack permissions control filesystem access scope.',
110
140
  ].join('\n');
111
141
  const program = new commander_1.Command();
112
- program.hook('preAction', async () => {
142
+ program.hook('preAction', async (_thisCommand, actionCommand) => {
143
+ // A SANDBOX must never absorb ambient credentials. Every other command merges ~/.agent-hippo/.env
144
+ // as a convenience, but doing that inside a sandbox would silently hand the sandboxed engine
145
+ // every key on the host — defeating the driver's curated env allowlist (observed live: the
146
+ // cursor engine picked up CURSOR_API_KEY this way). A sandbox receives only what its driver
147
+ // explicitly passes.
148
+ if (actionCommand?.name() === 'sandbox') {
149
+ return;
150
+ }
113
151
  await (0, agentEnvFile_1.mergeAgentHomeEnvIntoProcessEnv)();
114
152
  });
115
153
  program
@@ -126,7 +164,7 @@ program
126
164
  .description('Start interactive chat session')
127
165
  .option('-a, --agent <name>', 'Agent pack to use')
128
166
  .option('-e, --engine <id>', 'Override pack engine: claude|codex|<custom-engine-id>')
129
- .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-4.1-nano)')
167
+ .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-5.6-luna)')
130
168
  .option('-w, --workspace <path>', 'Workspace directory', process.cwd())
131
169
  .action(async (options) => {
132
170
  const verbose = program.opts().verbose;
@@ -145,7 +183,7 @@ program
145
183
  .description('Send a single message and exit')
146
184
  .option('-a, --agent <name>', 'Agent pack to use')
147
185
  .option('-e, --engine <id>', 'Override pack engine: claude|codex|<custom-engine-id>')
148
- .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-4.1-nano)')
186
+ .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-5.6-luna)')
149
187
  .option('-w, --workspace <path>', 'Workspace directory', process.cwd())
150
188
  .option('-f, --file <path>', 'Include file content in context')
151
189
  .option('--json', 'Output as JSON')
@@ -176,7 +214,7 @@ program
176
214
  .requiredOption('--prompt <message>', 'Prompt text')
177
215
  .option('--pack <name>', 'Agent pack to use')
178
216
  .option('-e, --engine <id>', 'Override pack engine: claude|codex|<custom-engine-id>')
179
- .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-4.1-nano)')
217
+ .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-5.6-luna)')
180
218
  .option('-w, --workspace <path>', 'Workspace directory', process.cwd())
181
219
  .option('--session-id <id>', 'Optional session id for the caller')
182
220
  .option('--system-prompt <text>', 'Optional system prompt override')
@@ -204,7 +242,7 @@ program
204
242
  .description('Start HTTP server')
205
243
  .requiredOption('-a, --agent <name>', 'Agent pack to use (required)')
206
244
  .option('-e, --engine <id>', 'Override pack engine: claude|codex|<custom-engine-id>')
207
- .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-4.1-nano)')
245
+ .option('-m, --model <id>', 'Override pack model (e.g. litellm/gpt-5.6-luna)')
208
246
  .option('-p, --port <port>', 'Port to listen on', '3000')
209
247
  .option('--host <address>', 'Address to bind the HTTP server (default 127.0.0.1; use 0.0.0.0 in containers/behind a proxy)')
210
248
  .option('-w, --workspace <path>', 'Default workspace directory', process.cwd())
@@ -213,18 +251,56 @@ program
213
251
  .option('--state-dir <path>', 'Persist session state here (honors AGENTHIPPO_HOME; replaces the tmpdir default)')
214
252
  .option('--max-turns-per-session <n>', 'Cap turns per session (abuse/runaway guard); 0 = unlimited', '0')
215
253
  .option('--www-dir <path>', 'Serve deploy/www/index.html at GET / (static browser chat UI)')
216
- // Deprecated no-ops: kept so deploy artifacts generated by older CLIs (which baked these
217
- // into the serve command line) don't crash with "unknown option" after a runtime upgrade.
218
- // Serve now trusts its front proxy for auth/CORS; these flags no longer do anything.
219
- .option('--production', 'Deprecated: no-op (serve trusts its front proxy; hardening moved to the deploy edge)')
220
- .option('--cors-origin <origins>', 'Deprecated: no-op (CORS is handled by the front proxy)')
254
+ .option('--sandbox <mode>', 'Run each session\'s engine in a sandbox: process (tier 0, no boundary) or container (tier 2, one disposable container per session with only its declared routes). Unset = in-process (default, unchanged)')
255
+ .option('--sandbox-image <ref>', '--sandbox container: sandbox image pinned BY DIGEST (image@sha256:...)')
256
+ .option('--sandbox-scope <scope>', '--sandbox: what one sandbox holds — session (default, one per conversation) or user (one per verified user on this instance, so their sessions share a container, workspace, TTL and kill). Unidentified callers get one each either way')
257
+ .option('--pull-packs', 'Fetch the version the lease pins from the store instead of only refusing others. Always verifies the signature; a fetch or verification failure keeps serving the current version')
258
+ .option('--require-signed-pack', 'Refuse to start a sandbox unless the pack is signed by a trusted key (the lease may also require this; neither side can waive the other). Now the DEFAULT — kept so existing invocations still mean something')
259
+ .option('--allow-unsigned-pack', 'LOCAL DEVELOPMENT ONLY: run a pack this instance cannot verify. Signature checking is required by default; this disables it and warns loudly on every boot. A lease that sets requireSignedPack still overrides it')
260
+ .option('--require-signed-lease', 'Refuse to act on a lease that is not signed (ES256 compact JWS) by a trusted key. Tighten-only with lease.requireSignedLease, and sticky once a signed lease has been seen')
261
+ .option('--lease-max-staleness <seconds>', 'Refuse turns after this long with no successful lease refresh, so network denial cannot hold a revoked fleet open (default 86400; 0 disables)')
262
+ .option('--sandbox-ttl <seconds>', '--sandbox: reap an idle sandbox after N seconds; the next message recreates it and resumes the session', '1800')
263
+ .option('--shared-workspace', '--sandbox container: mount ONE workspace into every session\'s sandbox (default: one per session). Sessions can then read and overwrite each other\'s files — only for a single trust domain')
264
+ // Control plane (serve-v4-clean §5). Caps are enforced by revoking a sandbox's routes, so they
265
+ // need --sandbox; a lease may impose tighter values than these, and never looser ones.
266
+ .option('--max-session-seconds <seconds>', '--sandbox: cap one session\'s wall-clock lifetime; a breach revokes or destroys its sandbox')
267
+ .option('--max-concurrent-sandboxes <n>', '--sandbox: cap live sandboxes on this instance (blast radius)')
268
+ // Deprecated pre-rename spelling. Hidden so it does not advertise itself, honoured so an
269
+ // existing deployment does not silently lose its cap, and loud so it gets migrated.
270
+ .addOption(new commander_1.Option('--max-concurrent-cells <n>', 'DEPRECATED alias for --max-concurrent-sandboxes').hideHelp())
271
+ .option('--cap-on-breach <action>', '--sandbox: what a cap breach does — revoke-inference (default) or destroy-sandbox. An unrecognised value fails CLOSED to destroy-sandbox')
272
+ .option('--lease-poll <seconds>', '--sandbox: how often to re-check the lease, so a kill switch stops RUNNING sandboxes. Worst-case kill latency is 2x this (poll + cache); an admin refresh is immediate', '30')
221
273
  .action(async (options) => {
222
- if (options.production || options.corsOrigin) {
223
- console.warn('[serve] --production/--cors-origin are deprecated no-ops; auth and CORS are enforced by the deploy edge proxy.');
224
- }
225
274
  const verbose = program.opts().verbose;
226
275
  await (0, deps_1.ensureDeps)(verbose);
227
276
  await (0, serve_1.serveCommand)({
277
+ sandbox: options.sandbox === 'container' ? 'container' : options.sandbox === 'process' ? 'process' : undefined,
278
+ sandboxImage: options.sandboxImage,
279
+ // Anything but an explicit `user` means session scope: an unrecognised value must not
280
+ // quietly widen what a sandbox holds.
281
+ sandboxScope: options.sandboxScope === 'user' ? 'user' : 'session',
282
+ requireSignedPack: Boolean(options.requireSignedPack),
283
+ allowUnsignedPack: Boolean(options.allowUnsignedPack),
284
+ requireSignedLease: Boolean(options.requireSignedLease),
285
+ leaseMaxStalenessSeconds: options.leaseMaxStaleness !== undefined
286
+ ? (Number.isFinite(Number(options.leaseMaxStaleness)) ? Number(options.leaseMaxStaleness) : undefined)
287
+ : undefined,
288
+ pullPacks: Boolean(options.pullPacks),
289
+ sandboxTtlSeconds: parseInt(options.sandboxTtl, 10) || undefined,
290
+ sharedWorkspace: Boolean(options.sharedWorkspace),
291
+ maxSessionSeconds: parseInt(options.maxSessionSeconds, 10) || undefined,
292
+ maxConcurrentSandboxes: resolveMaxConcurrentSandboxes(options),
293
+ // Shared with the env path (pack/caps.ts) so a value that is valid in one is valid in the
294
+ // other. An unrecognised action no longer becomes `undefined` — which mergeCaps then
295
+ // defaulted to the WEAKER revoke-inference, silently downgrading what the operator typed.
296
+ capOnBreach: (() => {
297
+ const r = (0, caps_1.resolveOnBreach)(options.capOnBreach, '--cap-on-breach');
298
+ for (const w of r.warnings) {
299
+ console.warn(`Warning: ${w}`);
300
+ }
301
+ return r.action;
302
+ })(),
303
+ leasePollSeconds: parseInt(options.leasePoll, 10) || undefined,
228
304
  agent: options.agent,
229
305
  engine: options.engine,
230
306
  model: options.model,
@@ -239,6 +315,70 @@ program
239
315
  verbose,
240
316
  });
241
317
  });
318
+ // Conformance — the deployment proves its own claims (serve-v4-clean.md §11 step 5).
319
+ program
320
+ .command('conformance')
321
+ .description('Probe a deployment against its own claims: credential confinement, isolation tier, cost-cap binding, warrant integrity, rollback/kill switch. Exit 1 on any failure, 2 when no deployment was probed (--sandbox/--tier omitted) — only exit 0 means a real deployment was measured.')
322
+ .option('--sandbox <id>', 'Running sandbox container id/name — required to probe INSIDE a sandbox')
323
+ .option('--tier <n>', 'Declared isolation tier (0-3); probed, not trusted')
324
+ .option('--runtime <bin>', 'Container runtime binary', 'docker')
325
+ .option('--json', 'Emit JSON')
326
+ .action(async (options) => {
327
+ const { conformanceCommand } = await import('./commands/conformance.js');
328
+ const code = await conformanceCommand({
329
+ sandbox: options.sandbox,
330
+ tier: options.tier === undefined ? undefined : parseInt(options.tier, 10),
331
+ runtime: options.runtime,
332
+ json: Boolean(options.json),
333
+ });
334
+ process.exitCode = code;
335
+ });
336
+ // Sandbox runner — runs INSIDE a sandbox; holds no credential (serve-v4-clean.md §3).
337
+ program
338
+ .command('sandbox')
339
+ .description('Run the in-sandbox engine host (sandbox runner). Speaks the frame protocol to serve over a unix socket or stdio; holds no data or model credential.')
340
+ .requiredOption('-a, --agent <path>', 'Pack directory mounted into the sandbox')
341
+ .option('--socket <path>', 'Unix socket to listen on (container driver)')
342
+ .option('--stdio', 'Speak the protocol on stdin/stdout instead (process driver)')
343
+ // No default: it must fall back to <state-dir>/workspace inside the command, or a
344
+ // host-launched sandbox (where --state-dir is a real host path) inherits the container-image
345
+ // path and dies on mkdir.
346
+ .option('-w, --workspace <path>', 'Workspace directory (default: <state-dir>/workspace)')
347
+ .option('--state-dir <path>', 'State directory', '/state')
348
+ .option('--sandbox-id <id>', 'Sandbox id (joins evidence records)')
349
+ .action(async (options) => {
350
+ const { sandboxCommand } = await import('./commands/sandbox.js');
351
+ await sandboxCommand({
352
+ agent: options.agent,
353
+ socket: options.socket,
354
+ stdio: Boolean(options.stdio),
355
+ workspace: options.workspace,
356
+ stateDir: options.stateDir,
357
+ sandboxId: options.sandboxId,
358
+ });
359
+ });
360
+ program
361
+ .command('data-plane')
362
+ .description('Run the connector as a sidecar (holds the backend base credential; the agent container does not)')
363
+ .requiredOption('-a, --agent <name>', 'Agent pack (reads spec.dataPlane connector + provider)')
364
+ .option('-w, --workspace <path>', 'Workspace directory', process.cwd())
365
+ .option('--gateway-port <port>', 'Port the engine dials (nonce-auth MCP)', '9090')
366
+ .option('--control-port <port>', 'Port serve dials to register turns (bearer-auth)', '9091')
367
+ .option('--host <address>', 'Bind address (default 0.0.0.0 for the pod network)')
368
+ .option('--state-dir <path>', 'Where data-plane audit events are written. Same precedence as serve: --state-dir, then AGENTHIPPO_HOME, then tmpdir')
369
+ .action(async (options) => {
370
+ const verbose = program.opts().verbose;
371
+ await (0, deps_1.ensureDeps)(verbose);
372
+ await (0, data_plane_1.dataPlaneSidecarCommand)({
373
+ agent: options.agent,
374
+ workspace: options.workspace,
375
+ gatewayPort: parseInt(options.gatewayPort, 10),
376
+ controlPort: parseInt(options.controlPort, 10),
377
+ host: options.host,
378
+ stateDir: options.stateDir,
379
+ verbose,
380
+ });
381
+ });
242
382
  program
243
383
  .command('auth-broker')
244
384
  .description('Start the AgentHippo auth broker (OAuth + IDE bootstrap API)')
@@ -262,6 +402,7 @@ program
262
402
  .option('--idp-url <url>', 'edge-fed: central IdP base URL browsers redirect to for login (e.g. https://auth.agenthippo.ai)', 'https://auth.agenthippo.ai')
263
403
  .option('--cookie-domain <domain>', 'edge-fed: parent domain the shared auth cookie is scoped to (e.g. .agenthippo.ai)', '.agenthippo.ai')
264
404
  .option('--colocate', 'edge-fed + vm/compose only: ride the org IdP\'s already-running Caddy + auth-broker instead of shipping this agent\'s own (PLAN/deploy.md §4 state 4)')
405
+ .option('--edge-turn-connector <addr>', 'EDGE turn registration (P1 fix): connector sidecar control-API address (e.g. mypack-connector:9091); the generated route registers each chat turn via forward_auth /edge/turn and copies X-Agent-Turn-Nonce — the agent container never holds the register token')
265
406
  .option('--model-routing <mode>', 'Cloud Run + edge-fed: sidecar (in-pod LiteLLM+OTEL, default) or gateway (agent → gateway.agenthippo.ai directly, lean)')
266
407
  .option('--force', 'Overwrite existing files')
267
408
  .action(async (action, pack, options) => {
@@ -283,6 +424,7 @@ program
283
424
  cookieDomain: options.cookieDomain,
284
425
  colocate: Boolean(options.colocate),
285
426
  modelRouting,
427
+ edgeTurnConnector: options.edgeTurnConnector,
286
428
  });
287
429
  });
288
430
  // IdP command - generate the central IdP stack (org identity infra, set up once)
@@ -304,16 +446,28 @@ program
304
446
  // Pack command - pack management
305
447
  program
306
448
  .command('pack <action>')
307
- .description('Manage agent packs (list, validate, install)')
449
+ .description('Manage agent packs (list, validate, sign, verify, digest, keygen, install)')
308
450
  .argument('[name]', 'Pack name or path')
309
- .option('-o, --output <path>', 'Output path for export')
451
+ .option('-o, --output <path>', 'Output path for export, or for a generated signing key')
452
+ .option('--key <path>', 'pack sign: PEM file holding the ES256 private key')
453
+ .option('--kid <id>', 'pack sign/keygen: key id published in JWKS so verifiers can find the public half')
454
+ .option('--keys <path>', 'pack verify: JSON file of kid -> public PEM')
455
+ .option('--jwks <url>', 'pack verify: JWKS endpoint to fetch public halves from')
456
+ .option('--to <dir>', 'pack install: destination folder (flat layout for serve/deploy)')
457
+ .option('-f, --force', 'pack install: overwrite an existing folder')
310
458
  .option('-w, --workspace <path>', 'Workspace directory for local packs', process.cwd())
311
459
  .action(async (action, name, options) => {
312
460
  await (0, pack_1.packCommand)({
313
461
  action,
314
462
  name,
315
463
  output: options.output,
464
+ to: options.to,
465
+ force: options.force,
316
466
  workspace: options.workspace,
467
+ key: options.key,
468
+ kid: options.kid,
469
+ keys: options.keys,
470
+ jwks: options.jwks,
317
471
  verbose: program.opts().verbose,
318
472
  });
319
473
  });
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parsePackInstallRef = parsePackInstallRef;
7
+ /** Parse `slug` or `slug@version` from `pack install` positional arg. */
8
+ function parsePackInstallRef(name) {
9
+ const trimmed = name.trim();
10
+ const at = trimmed.lastIndexOf('@');
11
+ if (at > 0) {
12
+ return { slug: trimmed.slice(0, at), version: trimmed.slice(at + 1) || undefined };
13
+ }
14
+ return { slug: trimmed };
15
+ }
@@ -373,7 +373,14 @@ function getApiConfig() {
373
373
  const litellmBaseUrl = getEnvVar('AGENTHIPPO_LITELLM_BASE_URL', 'AGENTIDE_LITELLM_BASE_URL');
374
374
  const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_API_KEY;
375
375
  const openaiApiKey = process.env.OPENAI_API_KEY;
376
- const useLiteLLM = Boolean(litellmApiKey && litellmBaseUrl);
376
+ // The URL alone defines the trust domain. When a LiteLLM/gateway URL is configured, ALL
377
+ // engine traffic routes there — so it must authenticate with the LiteLLM key (or
378
+ // resolveApiRouting's 'sk-dummy' fallback), NEVER a provider secret, and must carry the
379
+ // analytics metadata headers. Requiring key+URL here once created a silent half-state:
380
+ // URL-only routed every call to the gateway with the REAL provider key as auth and no
381
+ // metadata at all (verified live 2026-07-27 with a header-capture stub). A missing key now
382
+ // yields a clean 401 against a strict gateway instead of a leaked credential.
383
+ const useLiteLLM = Boolean(litellmBaseUrl);
377
384
  return {
378
385
  // Kept only for the "is any credential present at all?" boot check; routing never uses it.
379
386
  apiKey: litellmApiKey || anthropicApiKey || openaiApiKey,
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createPackRunner = createPackRunner;
7
+ /**
8
+ * One way to stand up a `HeadlessRunner` for an agent pack.
9
+ *
10
+ * `serve` and `sandbox` run the SAME engines against the SAME pack; the only difference is where
11
+ * the process sits. Every time the sandbox re-derived a piece of that setup itself it drifted from
12
+ * serve and broke live (the hand-rolled agent.yaml parser, the engine executable paths, the
13
+ * skipped `ensureDeps`). So the wiring lives here once and both call it: engine discovery,
14
+ * bundled prompts, the child env patch, and the credential shape.
15
+ *
16
+ * What is NOT here is deliberately not shared: credentials are the caller's business. serve
17
+ * holds provider keys; a sandbox only ever gets a per-sandbox, budget-capped model token that arrives
18
+ * on the run frame. Both express that through `credentials`, and nothing in this module can
19
+ * widen it.
20
+ */
21
+ const headless_1 = require("../headless");
22
+ const runtime_1 = require("./runtime");
23
+ const utils_1 = require("./utils");
24
+ /**
25
+ * Build the runner. Engine executables are resolved here rather than by the SDKs themselves:
26
+ * the engines ship WITH agenthippo, and a runtime image does not carry the SDK's own bundled
27
+ * cli.js — without these paths every turn inside a sandbox fails with "executable not found".
28
+ */
29
+ function createPackRunner(options) {
30
+ return (0, headless_1.createHeadlessRunner)({
31
+ storagePath: options.storagePath,
32
+ analyticsSessionsDir: options.analyticsSessionsDir,
33
+ packId: options.packId,
34
+ packVersion: options.packVersion,
35
+ provenance: options.provenance,
36
+ bundledPromptsDir: (0, utils_1.findBundledBasePromptsDir)(),
37
+ apiKey: options.credentials.apiKey,
38
+ baseUrl: options.credentials.baseUrl,
39
+ useLiteLLM: options.credentials.useLiteLLM,
40
+ litellmApiKey: options.credentials.litellmApiKey,
41
+ openaiApiKey: options.credentials.openaiApiKey,
42
+ anthropicApiKey: options.credentials.anthropicApiKey,
43
+ logger: options.logger,
44
+ freshState: options.freshState ?? false,
45
+ claudeCodeExecutablePath: (0, utils_1.findClaudeCodePath)(),
46
+ codexExecutablePath: (0, utils_1.findCodexPath)(),
47
+ permissionHandler: options.permissionHandler,
48
+ // Gives engine children AGENTHIPPO_WORKSPACE_ROOT / AGENTHIPPO_ACTIVE_AGENT and puts the
49
+ // agenthippo binary on their PATH — without it a pack skill that shells out to the CLI
50
+ // cannot find it, which is exactly the kind of silent difference a sandbox must not have.
51
+ cliEnvPatch: (0, runtime_1.buildCliEnvPatch)({
52
+ cliPath: process.execPath,
53
+ workspaceRoot: options.workspaceRoot,
54
+ agentId: options.packId,
55
+ chatSessionId: options.chatSessionId,
56
+ baseEnv: process.env,
57
+ omitGatewayToken: options.omitGatewayToken,
58
+ }),
59
+ ...(options.claudeCheapModel ? { engineSettings: { claudeCheapModel: options.claudeCheapModel } } : {}),
60
+ });
61
+ }
@@ -42,6 +42,8 @@ exports.getBundledToolsHealth = getBundledToolsHealth;
42
42
  exports.getCliHealth = getCliHealth;
43
43
  exports.buildSessionEnvPatch = buildSessionEnvPatch;
44
44
  exports.buildCliEnvPatch = buildCliEnvPatch;
45
+ exports.readProvenanceEnvFromProcess = readProvenanceEnvFromProcess;
46
+ exports.buildProvenanceEnv = buildProvenanceEnv;
45
47
  exports.runCli = runCli;
46
48
  const fs = __importStar(require("fs"));
47
49
  const os = __importStar(require("os"));
@@ -320,7 +322,7 @@ function buildCliEnvPatch(args) {
320
322
  // nonce. AGENTHIPPO_EGRESS_URL in the process env is that signal — with it set, never bake
321
323
  // the token into a child env (this closes §7.4 gap 1 for custom engines; codex/claude are
322
324
  // covered by serve stripping the token from its own process.env).
323
- if (!process.env.AGENTHIPPO_EGRESS_URL?.trim()) {
325
+ if (!args.omitGatewayToken && !process.env.AGENTHIPPO_EGRESS_URL?.trim()) {
324
326
  const gatewayToken = readOpenClawGatewayToken(args.baseEnv);
325
327
  if (gatewayToken) {
326
328
  env[exports.OPENCLAW_GATEWAY_TOKEN_ENV] = gatewayToken;
@@ -368,6 +370,38 @@ function buildCliEnvPatch(args) {
368
370
  }
369
371
  return env;
370
372
  }
373
+ /** Read provenance stamp env vars injected into sandbox children by serve. */
374
+ function readProvenanceEnvFromProcess() {
375
+ const pick = (key) => process.env[key]?.trim() || undefined;
376
+ const tierRaw = pick('AGENTIDE_ANALYTICS_TIER');
377
+ const tier = tierRaw !== undefined ? Number(tierRaw) : undefined;
378
+ return {
379
+ policyVersion: pick('AGENTIDE_ANALYTICS_POLICY_VERSION'),
380
+ packDigest: pick('AGENTIDE_ANALYTICS_PACK_DIGEST'),
381
+ engineVersion: pick('AGENTIDE_ANALYTICS_ENGINE_VERSION'),
382
+ tier: tier !== undefined && Number.isFinite(tier) ? tier : undefined,
383
+ instanceId: pick('AGENTIDE_ANALYTICS_INSTANCE_ID'),
384
+ };
385
+ }
386
+ function buildProvenanceEnv(stamp) {
387
+ const env = {};
388
+ if (stamp.policyVersion?.trim()) {
389
+ env.AGENTIDE_ANALYTICS_POLICY_VERSION = stamp.policyVersion.trim();
390
+ }
391
+ if (stamp.packDigest?.trim()) {
392
+ env.AGENTIDE_ANALYTICS_PACK_DIGEST = stamp.packDigest.trim();
393
+ }
394
+ if (stamp.engineVersion?.trim()) {
395
+ env.AGENTIDE_ANALYTICS_ENGINE_VERSION = stamp.engineVersion.trim();
396
+ }
397
+ if (stamp.tier !== undefined) {
398
+ env.AGENTIDE_ANALYTICS_TIER = String(stamp.tier);
399
+ }
400
+ if (stamp.instanceId?.trim()) {
401
+ env.AGENTIDE_ANALYTICS_INSTANCE_ID = stamp.instanceId.trim();
402
+ }
403
+ return env;
404
+ }
371
405
  async function runCli(binaryPath, args, opts) {
372
406
  return await new Promise((resolve, reject) => {
373
407
  const child = (0, child_process_1.spawn)(binaryPath, args, {