@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
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * "Junk is dropped rather than trusted" — restored under the sandbox vocabulary.
4
+ *
5
+ * `normalizeCaps` was the ONLY runtime validation a lease ever received, and it was deleted along
6
+ * with the cell→sandbox back-compat it happened to also carry. What replaced it was
7
+ * `cached = (await res.json()) as Lease` — a compile-time cast that checks nothing. These pin the
8
+ * properties that were lost, plus the one that was always wrong: an unrecognised breach action
9
+ * must fail closed to the STRICTER action, never silently to the weaker one.
10
+ *--------------------------------------------------------------------------------------------*/
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ require("mocha");
46
+ const assert = __importStar(require("assert"));
47
+ const caps_1 = require("../pack/caps");
48
+ const caps_2 = require("../pack/caps");
49
+ const lease_schema_1 = require("../pack/lease-schema");
50
+ const lease_1 = require("../pack/lease");
51
+ const base = { pack: 'analyst', enabled: true, expiresAt: 1_800_003_600 };
52
+ suite('lease validation — structural junk is refused, not trusted', () => {
53
+ const rejected = [
54
+ ['a string cap', { ...base, caps: { maxTurnsPerSession: 'lots' } }],
55
+ ['a negative cap', { ...base, caps: { maxSessionSeconds: -1 } }],
56
+ ['a zero cap', { ...base, caps: { maxConcurrentSandboxes: 0 } }],
57
+ ['a fractional cap', { ...base, caps: { maxTurnsPerSession: 1.5 } }],
58
+ ['an infinite cap', { ...base, caps: { maxTurnsPerSession: Number.POSITIVE_INFINITY } }],
59
+ ['a string enabled', { ...base, enabled: 'false' }],
60
+ ['a missing pack', { enabled: true, expiresAt: 1 }],
61
+ ['a string expiresAt', { ...base, expiresAt: '1800003600' }],
62
+ ['a non-object caps', { ...base, caps: 'none' }],
63
+ ['a non-array deny', { ...base, deny: { scope: 'pack' } }],
64
+ ['an unknown deny scope', { ...base, deny: [{ scope: 'tenant', match: 'x', mode: 'kill' }] }],
65
+ ['a deny rule with no match', { ...base, deny: [{ scope: 'pack', match: '', mode: 'kill' }] }],
66
+ ['a non-boolean requireSignedPack', { ...base, requireSignedPack: 'yes' }],
67
+ ['a non-boolean allowServiceTurns', { ...base, allowServiceTurns: 'true' }],
68
+ ['not an object at all', 'nope'],
69
+ ];
70
+ for (const [name, doc] of rejected) {
71
+ test(`${name} rejects the whole document`, () => {
72
+ const res = (0, lease_schema_1.parseLease)(doc);
73
+ assert.strictEqual(res.ok, false, `expected ${name} to be refused`);
74
+ });
75
+ }
76
+ test('NaN never reaches the meter, where every comparison against it is false', () => {
77
+ // The failure this prevents: `usage.turns >= NaN` is false for every value, so the cap is
78
+ // reported in the console and enforced nowhere.
79
+ assert.strictEqual((0, lease_schema_1.parseLease)({ ...base, caps: { maxTurnsPerSession: NaN } }).ok, false);
80
+ const meter = new caps_2.CapMeter({ maxTurnsPerSession: NaN });
81
+ meter.record('s1');
82
+ meter.record('s1');
83
+ meter.record('s1');
84
+ assert.strictEqual(meter.check('s1', 0).allowed, true, 'demonstrating why the validator has to catch it');
85
+ });
86
+ test('a well-formed lease survives intact', () => {
87
+ const res = (0, lease_schema_1.parseLease)({
88
+ ...base,
89
+ version: '1.4.1',
90
+ caps: { maxTurnsPerSession: 10, onBreach: 'destroy-sandbox' },
91
+ deny: [{ scope: 'session', match: 's1', mode: 'kill', reason: 'runaway', until: 1_800_004_000 }],
92
+ requireSignedPack: true,
93
+ });
94
+ assert.ok(res.ok);
95
+ assert.strictEqual(res.lease.caps?.maxTurnsPerSession, 10);
96
+ assert.strictEqual(res.lease.deny?.[0].mode, 'kill');
97
+ assert.strictEqual(res.lease.requireSignedPack, true);
98
+ });
99
+ test('an unknown cap field is tolerated but never silent', () => {
100
+ const res = (0, lease_schema_1.parseCapPolicy)({ maxSomethingNew: 5 });
101
+ assert.ok(res.ok);
102
+ assert.ok(res.warnings.some((w) => /NOT being enforced/.test(w)));
103
+ });
104
+ });
105
+ suite('unrecognised enums fail CLOSED to the stricter value', () => {
106
+ /**
107
+ * The concrete bug: an unrecognised `onBreach` fell through `mergeCaps`'s
108
+ * `=== 'destroy-sandbox'` test, so `governor.enforce` defaulted to `revoke-inference`. A typo
109
+ * in the control plane SOFTENED the breach action from "destroy the session" to "take away its
110
+ * model route", while the console displayed the stricter value the operator had entered.
111
+ */
112
+ test('an unknown lease onBreach clamps UP and warns', () => {
113
+ const res = (0, lease_schema_1.parseCapPolicy)({ onBreach: 'destroy_cell_typo' });
114
+ assert.ok(res.ok);
115
+ assert.strictEqual(res.caps.onBreach, lease_schema_1.STRICTEST_ON_BREACH);
116
+ assert.strictEqual(res.caps.onBreach, 'destroy-sandbox');
117
+ assert.ok(res.warnings.some((w) => /Failing CLOSED/.test(w)));
118
+ });
119
+ test('and the clamped value survives mergeCaps rather than being softened by it', () => {
120
+ const parsed = (0, lease_schema_1.parseCapPolicy)({ onBreach: 'nonsense' });
121
+ assert.ok(parsed.ok);
122
+ assert.strictEqual((0, caps_1.mergeCaps)(parsed.caps, { onBreach: 'revoke-inference' }).onBreach, 'destroy-sandbox');
123
+ });
124
+ test('an unknown deny mode clamps to kill rather than being dropped', () => {
125
+ const res = (0, lease_schema_1.parseLease)({ ...base, deny: [{ scope: 'pack', match: 'analyst', mode: 'pause' }] });
126
+ assert.ok(res.ok);
127
+ assert.strictEqual(res.lease.deny?.[0].mode, 'kill');
128
+ assert.ok(res.warnings.some((w) => /Failing CLOSED/.test(w)));
129
+ });
130
+ test('env: an unknown AGENTHIPPO_CAP_ON_BREACH fails closed instead of becoming undefined', () => {
131
+ const warnings = [];
132
+ const caps = (0, caps_1.capsFromEnv)({ AGENTHIPPO_CAP_ON_BREACH: 'destroy-everything' }, (m) => warnings.push(m));
133
+ assert.strictEqual(caps.onBreach, 'destroy-sandbox');
134
+ assert.ok(warnings.some((w) => /FAILING CLOSED/.test(w)));
135
+ });
136
+ test('CLI: the same resolver, so a flag and an env var cannot disagree', () => {
137
+ const r = (0, caps_1.resolveOnBreach)('destroy-everything', '--cap-on-breach');
138
+ assert.strictEqual(r.action, 'destroy-sandbox');
139
+ assert.ok(r.warnings.some((w) => /FAILING CLOSED/.test(w)));
140
+ assert.strictEqual((0, caps_1.resolveOnBreach)(undefined, '--cap-on-breach').action, undefined, 'unset stays unset');
141
+ assert.strictEqual((0, caps_1.resolveOnBreach)('revoke-inference', '--cap-on-breach').action, 'revoke-inference');
142
+ });
143
+ });
144
+ suite('deprecated cell vocabulary is aliased, loudly', () => {
145
+ test('destroy-cell maps to destroy-sandbox rather than softening enforcement', () => {
146
+ assert.strictEqual(lease_schema_1.ON_BREACH_ALIASES['destroy-cell'], 'destroy-sandbox');
147
+ const r = (0, caps_1.resolveOnBreach)('destroy-cell', 'AGENTHIPPO_CAP_ON_BREACH');
148
+ assert.strictEqual(r.action, 'destroy-sandbox');
149
+ assert.ok(r.warnings.some((w) => /DEPRECATED/.test(w)));
150
+ });
151
+ test('a lease using the old cap spelling is still enforced', () => {
152
+ assert.strictEqual(lease_schema_1.LEASE_CAP_ALIASES.maxConcurrentCells, 'maxConcurrentSandboxes');
153
+ const res = (0, lease_schema_1.parseCapPolicy)({ maxConcurrentCells: 4 });
154
+ assert.ok(res.ok);
155
+ assert.strictEqual(res.caps.maxConcurrentSandboxes, 4);
156
+ });
157
+ test('AGENTHIPPO_MAX_CONCURRENT_CELLS is honoured with a deprecation notice', () => {
158
+ const warnings = [];
159
+ const caps = (0, caps_1.capsFromEnv)({ AGENTHIPPO_MAX_CONCURRENT_CELLS: '3' }, (m) => warnings.push(m));
160
+ assert.strictEqual(caps.maxConcurrentSandboxes, 3);
161
+ assert.ok(warnings.some((w) => /DEPRECATED/.test(w)));
162
+ assert.strictEqual(caps_1.CAP_ENV_ALIASES.AGENTHIPPO_MAX_CONCURRENT_CELLS, 'AGENTHIPPO_MAX_CONCURRENT_SANDBOXES');
163
+ });
164
+ test('the current spelling wins when both are present', () => {
165
+ const caps = (0, caps_1.capsFromEnv)({
166
+ AGENTHIPPO_MAX_CONCURRENT_SANDBOXES: '7',
167
+ AGENTHIPPO_MAX_CONCURRENT_CELLS: '3',
168
+ }, () => undefined);
169
+ assert.strictEqual(caps.maxConcurrentSandboxes, 7);
170
+ });
171
+ });
172
+ /**
173
+ * PLAN/agent-identity.md §10.3 — the lease flag that governs unattended runs.
174
+ *
175
+ * Its polarity is the INVERSE of every other flag on the lease. `requireSignedPack` is
176
+ * tighten-only, so absent safely means "nobody asked for the restriction". `allowServiceTurns`
177
+ * GRANTS a standing unattended identity, so absent has to mean deny — otherwise a control plane
178
+ * that has not been taught the field silently authorises unsupervised runs across a fleet.
179
+ */
180
+ suite('lease: allowServiceTurns (PR-2 — a grant, so absent means deny)', () => {
181
+ test('parseLease carries a real boolean through', () => {
182
+ const res = (0, lease_schema_1.parseLease)({ ...base, allowServiceTurns: true });
183
+ assert.ok(res.ok);
184
+ assert.strictEqual(res.lease.allowServiceTurns, true);
185
+ });
186
+ test('the string "false" is refused, not coerced — it would flip deny into allow', () => {
187
+ // `Boolean("false") === true`. This is the single most dangerous coercion on the document,
188
+ // which is why the flag joined the existing strict-boolean loop rather than getting its own.
189
+ assert.strictEqual((0, lease_schema_1.parseLease)({ ...base, allowServiceTurns: 'false' }).ok, false);
190
+ });
191
+ test('an unknown field is ignored, so an OLDER serve simply never runs service turns', () => {
192
+ // Forward compatibility with no lockstep hazard: field-by-field parsing means a serve that
193
+ // predates the flag reads a lease containing it without erroring, and — because it never
194
+ // consults the flag — denies. Fail-safe in the skew direction that matters.
195
+ const res = (0, lease_schema_1.parseLease)({ ...base, someFutureField: { nested: true } });
196
+ assert.ok(res.ok);
197
+ assert.strictEqual(res.lease.someFutureField, undefined);
198
+ });
199
+ test('managed + lease says true ⇒ allowed', () => {
200
+ assert.deepStrictEqual((0, lease_1.resolveAllowServiceTurns)({ lease: true, managed: true }), { allowed: true, gate: 'lease_allows' });
201
+ });
202
+ test('managed + field absent ⇒ DENIED, and the reason distinguishes it from an explicit false', () => {
203
+ const absent = (0, lease_1.resolveAllowServiceTurns)({ lease: undefined, managed: true });
204
+ assert.strictEqual(absent.allowed, false);
205
+ assert.strictEqual(absent.gate, 'lease_absent_field');
206
+ const explicit = (0, lease_1.resolveAllowServiceTurns)({ lease: false, managed: true });
207
+ assert.strictEqual(explicit.allowed, false);
208
+ assert.strictEqual(explicit.gate, 'lease_denies');
209
+ // Same outcome, different explanation: an operator reading the audit row must be able to
210
+ // tell "nobody enabled it" from "somebody disabled it".
211
+ assert.notStrictEqual((0, lease_1.describeServiceTurnGate)(absent.gate), (0, lease_1.describeServiceTurnGate)(explicit.gate));
212
+ });
213
+ test('unmanaged (no control plane) ⇒ permitted, local env is the only gate', () => {
214
+ const r = (0, lease_1.resolveAllowServiceTurns)({ lease: undefined, managed: false });
215
+ assert.strictEqual(r.allowed, true);
216
+ assert.strictEqual(r.gate, 'unmanaged_no_lease');
217
+ assert.match((0, lease_1.describeServiceTurnGate)(r.gate), /AGENTHIPPO_SERVICE_USER/);
218
+ });
219
+ test('every gate has an operator-facing explanation naming what to change', () => {
220
+ for (const gate of ['lease_allows', 'unmanaged_no_lease', 'lease_absent_field', 'lease_denies']) {
221
+ assert.ok((0, lease_1.describeServiceTurnGate)(gate).length > 20, `${gate} needs a real explanation`);
222
+ }
223
+ });
224
+ });
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Lease admission — kill switch + rollback (PLAN/serve-v4-clean.md §5).
4
+ *
5
+ * One mechanism, two jobs. The tests pin the operational contract, especially the
6
+ * fail-open-on-transport / fail-closed-on-content asymmetry, which is exactly the kind of
7
+ * property that silently inverts during a refactor.
8
+ *--------------------------------------------------------------------------------------------*/
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ require("mocha");
44
+ const assert = __importStar(require("assert"));
45
+ const lease_1 = require("../pack/lease");
46
+ const NOW = 1_800_000_000;
47
+ const valid = { pack: 'analyst', expiresAt: NOW + 3600, enabled: true };
48
+ suite('lease admission — kill switch', () => {
49
+ test('an enabled, unexpired lease admits', () => {
50
+ assert.doesNotThrow(() => (0, lease_1.assertAdmitted)(valid, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW }));
51
+ });
52
+ test('no lease configured admits — self-hosted dev and free tier keep working', () => {
53
+ assert.doesNotThrow(() => (0, lease_1.assertAdmitted)(undefined, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW }));
54
+ });
55
+ test('the kill switch refuses new sandboxes immediately, with the operator reason', () => {
56
+ assert.throws(() => (0, lease_1.assertAdmitted)({ ...valid, enabled: false, reason: 'incident 4412' }, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW }), (e) => e instanceof lease_1.LeaseDeniedError && /kill switch active/.test(e.message) && /incident 4412/.test(e.message));
57
+ });
58
+ test('an expired lease is refused — fail closed on content', () => {
59
+ assert.throws(() => (0, lease_1.assertAdmitted)({ ...valid, expiresAt: NOW - 1 }, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW }), /expired/);
60
+ });
61
+ test("a lease for another pack does not admit this one", () => {
62
+ assert.throws(() => (0, lease_1.assertAdmitted)({ ...valid, pack: 'other' }, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW }), /lease is for pack other/);
63
+ });
64
+ });
65
+ suite('lease admission — rollback', () => {
66
+ test('a pinned version refuses any other version (rollback in force)', () => {
67
+ const pinned = { ...valid, version: '1.4.1' };
68
+ assert.doesNotThrow(() => (0, lease_1.assertAdmitted)(pinned, { pack: 'analyst', version: '1.4.1', nowSeconds: NOW }));
69
+ assert.throws(() => (0, lease_1.assertAdmitted)(pinned, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW }), /pinned to 1\.4\.1.*refusing to start 1\.4\.2/);
70
+ });
71
+ test('an image that does not match the leased digest is refused (supply-chain pin)', () => {
72
+ const pinned = { ...valid, imageDigest: 'sha256:' + 'a'.repeat(64) };
73
+ assert.doesNotThrow(() => (0, lease_1.assertAdmitted)(pinned, {
74
+ pack: 'analyst', version: '1.4.2', image: `ghcr.io/x/analyst@sha256:${'a'.repeat(64)}`, nowSeconds: NOW,
75
+ }));
76
+ assert.throws(() => (0, lease_1.assertAdmitted)(pinned, {
77
+ pack: 'analyst', version: '1.4.2', image: `ghcr.io/x/analyst@sha256:${'b'.repeat(64)}`, nowSeconds: NOW,
78
+ }), /does not match the leased digest/);
79
+ });
80
+ });
81
+ suite('lease source — fail-open transport, fail-closed content', () => {
82
+ test('a network failure keeps serving on the cached lease', async () => {
83
+ let calls = 0;
84
+ const src = (0, lease_1.createLeaseSource)({
85
+ url: 'http://control.invalid/lease',
86
+ cacheSeconds: 0,
87
+ fetchImpl: (async () => {
88
+ calls++;
89
+ if (calls === 1) {
90
+ return { ok: true, json: async () => valid };
91
+ }
92
+ throw new Error('control plane unreachable');
93
+ }),
94
+ });
95
+ assert.deepStrictEqual(await src.get(NOW), valid, 'first fetch populates the cache');
96
+ const during = await src.get(NOW + 1);
97
+ assert.deepStrictEqual(during, valid, 'an outage must not take a customer fleet down');
98
+ });
99
+ test('an outage cannot extend an entitlement — the cached lease still expires', async () => {
100
+ const shortLease = { pack: 'analyst', expiresAt: NOW + 10, enabled: true };
101
+ const src = (0, lease_1.createLeaseSource)({
102
+ url: 'http://control.invalid/lease',
103
+ cacheSeconds: 0,
104
+ fetchImpl: (async () => ({ ok: true, json: async () => shortLease })),
105
+ });
106
+ const lease = await src.get(NOW);
107
+ // Later, past its own expiry, the cached lease must still be refused by admission.
108
+ assert.throws(() => (0, lease_1.assertAdmitted)(lease, { pack: 'analyst', version: '1.4.2', nowSeconds: NOW + 11 }), /expired/);
109
+ });
110
+ test('a static lease works with no control plane at all (air-gapped)', async () => {
111
+ const src = (0, lease_1.createLeaseSource)({ staticLease: valid });
112
+ assert.deepStrictEqual(await src.get(NOW), valid);
113
+ });
114
+ });
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const assert = __importStar(require("assert"));
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const fleetRowDefaults_1 = require("../analytics/dashboard/fleetRowDefaults");
44
+ const localFleetAdapter_1 = require("../analytics/dashboard/localFleetAdapter");
45
+ suite('localFleetAdapter', () => {
46
+ test('mergeSessionJsonlIntoFleetRows adds completed one-shot session to Done', () => {
47
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'fleet-jsonl-merge-'));
48
+ const sessionId = '2e156640-96ed-4106-8e75-44018d221758';
49
+ const scheduleId = 'sched-then-why';
50
+ const started = Date.now() - 2 * 60 * 60_000;
51
+ const ended = started + 60_000;
52
+ fs.writeFileSync(path.join(dir, `${sessionId}.jsonl`), [
53
+ JSON.stringify({ session_id: sessionId, request_id: 'req-1', timestamp: started, event_type: 'turn_start', payload: {} }),
54
+ JSON.stringify({ session_id: sessionId, request_id: 'req-1', timestamp: ended, event_type: 'turn_end', payload: { success: true } }),
55
+ ].join('\n') + '\n', 'utf8');
56
+ fs.utimesSync(path.join(dir, `${sessionId}.jsonl`), ended / 1000, ended / 1000);
57
+ const sessionsById = new Map();
58
+ sessionsById.set(`schedule:${scheduleId}`, (0, fleetRowDefaults_1.buildFleetSessionRow)('local', {
59
+ sessionId: `schedule:${scheduleId}`,
60
+ status: 'scheduled',
61
+ startedAt: new Date(started - 60_000).toISOString(),
62
+ endedAt: '',
63
+ taskTitle: 'then why',
64
+ model: 'agentide-cursor::auto',
65
+ engine: 'cursor',
66
+ channelType: 'none',
67
+ channelTarget: sessionId,
68
+ turns: 0,
69
+ totalTokens: 0,
70
+ disconnectCapable: false,
71
+ }));
72
+ const scheduleByChatSessionId = new Map([
73
+ [sessionId, {
74
+ id: scheduleId,
75
+ sessionId,
76
+ sessionResource: `vscode-chat-session://local/${sessionId}`,
77
+ messageText: 'then why',
78
+ runAt: started - 60_000,
79
+ model: 'agentide-cursor::auto',
80
+ engine: 'cursor',
81
+ }],
82
+ ]);
83
+ (0, localFleetAdapter_1.mergeSessionJsonlIntoFleetRows)(sessionsById, scheduleByChatSessionId, { scope: 'global' }, dir);
84
+ assert.strictEqual(sessionsById.has(`schedule:${scheduleId}`), false, 'schedule card should be replaced');
85
+ const row = sessionsById.get(sessionId);
86
+ assert.ok(row, 'expected chat session row');
87
+ assert.strictEqual(row.status, 'done');
88
+ assert.strictEqual(row.turns, 1);
89
+ assert.strictEqual(row.taskTitle, 'then why');
90
+ });
91
+ });
@@ -331,7 +331,7 @@ suite('MCP Server Loading', () => {
331
331
  remote: { type: 'http', url: 'https://mcp.example.com/sse', headers: { Authorization: 'Bearer abc' } },
332
332
  });
333
333
  });
334
- test('bearerTokenEnv resolves against the PER-TURN env into an Authorization header (claude/custom path)', () => {
334
+ test('bearerTokenEnv resolves against the PER-TURN env into an Authorization header (cursor/custom path)', () => {
335
335
  const server = {
336
336
  name: 'data', label: 'data', command: '', args: [],
337
337
  url: 'http://127.0.0.1:39999/mcp', bearerTokenEnv: 'AGENTHIPPO_TURN_NONCE',
@@ -346,6 +346,32 @@ suite('MCP Server Loading', () => {
346
346
  data: { type: 'http', url: 'http://127.0.0.1:39999/mcp' },
347
347
  });
348
348
  });
349
+ /**
350
+ * The claude path must ship a `${NAME}` REFERENCE, never the resolved secret: the Agent
351
+ * SDK serializes mcpServers into `--mcp-config <json>` argv, and /proc/<pid>/cmdline is
352
+ * world-readable — a resolved bearer there leaks the turn nonce to every uid on the host.
353
+ * The CLI expands the reference from its own env (verified live on 2.1.220).
354
+ */
355
+ test('referenceHttpMcpHeaders emits a ${NAME} reference, never the secret (claude path)', () => {
356
+ const headers = (0, mcp_1.referenceHttpMcpHeaders)({ bearerTokenEnv: 'AGENTHIPPO_TURN_NONCE', headers: { 'X-Static': 'kept' } }, { AGENTHIPPO_TURN_NONCE: 'nonce-123' });
357
+ assert.deepStrictEqual(headers, {
358
+ 'X-Static': 'kept',
359
+ 'Authorization': 'Bearer ${AGENTHIPPO_TURN_NONCE}',
360
+ });
361
+ // The reference is emitted even when the env has no value yet: expansion happens in
362
+ // the CLI at connect time, and an unset name passes through literally, which the data
363
+ // gateway rejects — fail closed, never a silently-authorized request.
364
+ const unresolved = (0, mcp_1.referenceHttpMcpHeaders)({ bearerTokenEnv: 'AGENTHIPPO_TURN_NONCE' }, {});
365
+ assert.deepStrictEqual(unresolved, { Authorization: 'Bearer ${AGENTHIPPO_TURN_NONCE}' });
366
+ });
367
+ test('referenceHttpMcpHeaders falls back to in-memory resolution for a non-expandable name', () => {
368
+ // A name outside the CLI's `${NAME}` grammar would be sent verbatim as garbage; the
369
+ // fallback resolves it here instead, accepting environ-level exposure over a broken header.
370
+ const headers = (0, mcp_1.referenceHttpMcpHeaders)({ bearerTokenEnv: 'weird-name.v2' }, { 'weird-name.v2': 'tok-1' });
371
+ assert.deepStrictEqual(headers, { Authorization: 'Bearer tok-1' });
372
+ // No bearerTokenEnv and no static headers → no headers object at all.
373
+ assert.strictEqual((0, mcp_1.referenceHttpMcpHeaders)({}, {}), undefined);
374
+ });
349
375
  test('serialize and parse round-trip inline MCP', () => {
350
376
  const inline = (0, mcp_1.formatInlineMcpServers)([
351
377
  { name: 'echo', label: 'echo', command: 'echo', args: ['hi'] },