@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
@@ -3,7 +3,7 @@
3
3
  * Copyright (c) AgentHippo.ai. All rights reserved.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.checkGitHubUpdate = exports.installFromGitHub = exports.validateForPublish = exports.publishToGitHub = exports.getGitHubUser = exports.isGitHubAuthenticated = exports.getGitHubToken = exports.formatGitHubSource = exports.parseGitHubSource = exports.resetDefaultAgent = exports.duplicateAgent = exports.updateFromUserLibrary = exports.checkForUpdate = exports.readAgentSource = exports.promoteToUserLibrary = exports.hydrateFromUserLibrary = exports.isAgentInUserLibrary = exports.isWorkspaceAgentAvailable = exports.listUserLibraryAgents = exports.updateManifestName = exports.parseAgentManifest = exports.loadAgentManifest = exports.stringifyYaml = exports.parseYaml = exports.AgentPackManager = exports.getUserAgentCurrentPath = exports.getUserAgentVersionDir = exports.getUserAgentRoot = exports.getUserAgentsDir = exports.getAgentideHome = exports.parseAgentIdFromPath = exports.getSessionsDir = exports.getAgentMcpDir = exports.getAgentRulesDir = exports.getAgentSkillsDir = exports.getAgentContextPath = exports.getAgentAgentsMdPath = exports.getAgentManifestPath = exports.isDefaultAgent = exports.userScopedEngineHome = exports.resolveRuntimePaths = exports.resolveAgentRuntimePaths = exports.getAgentRoot = exports.getAgentsDir = exports.getAgentideDir = exports.AGENT_CONSTANTS = void 0;
6
+ exports.checkGitHubUpdate = exports.installFromGitHub = exports.validateForPublish = exports.publishToGitHub = exports.getGitHubUser = exports.isGitHubAuthenticated = exports.getGitHubToken = exports.formatGitHubSource = exports.parseGitHubSource = exports.resetDefaultAgent = exports.duplicateAgent = exports.updateFromUserLibrary = exports.checkForUpdate = exports.readAgentSource = exports.promoteToUserLibrary = exports.hydrateFromUserLibrary = exports.isAgentInUserLibrary = exports.isWorkspaceAgentAvailable = exports.listUserLibraryAgents = exports.updateManifestName = exports.parseAgentManifest = exports.loadAgentManifest = exports.stringifyYaml = exports.parseYaml = exports.AgentPackManager = exports.getUserAgentCurrentPath = exports.getUserAgentVersionDir = exports.getUserAgentRoot = exports.getUserAgentsDir = exports.getAgentideHome = exports.parseAgentIdFromPath = exports.getSessionsDir = exports.getAgentMcpDir = exports.getAgentRulesDir = exports.getAgentSkillsDir = exports.getAgentContextPath = exports.getAgentAgentsMdPath = exports.getAgentManifestPath = exports.isDefaultAgent = exports.engineHomeUserKey = exports.userScopedEngineHomeFromKey = exports.userScopedEngineHome = exports.resolveRuntimePaths = exports.resolveAgentRuntimePaths = exports.getAgentRoot = exports.getAgentsDir = exports.getAgentideDir = exports.AGENT_CONSTANTS = void 0;
7
7
  // Path utilities
8
8
  var paths_1 = require("./paths");
9
9
  Object.defineProperty(exports, "AGENT_CONSTANTS", { enumerable: true, get: function () { return paths_1.AGENT_CONSTANTS; } });
@@ -13,6 +13,8 @@ Object.defineProperty(exports, "getAgentRoot", { enumerable: true, get: function
13
13
  Object.defineProperty(exports, "resolveAgentRuntimePaths", { enumerable: true, get: function () { return paths_1.resolveAgentRuntimePaths; } });
14
14
  Object.defineProperty(exports, "resolveRuntimePaths", { enumerable: true, get: function () { return paths_1.resolveRuntimePaths; } });
15
15
  Object.defineProperty(exports, "userScopedEngineHome", { enumerable: true, get: function () { return paths_1.userScopedEngineHome; } });
16
+ Object.defineProperty(exports, "userScopedEngineHomeFromKey", { enumerable: true, get: function () { return paths_1.userScopedEngineHomeFromKey; } });
17
+ Object.defineProperty(exports, "engineHomeUserKey", { enumerable: true, get: function () { return paths_1.engineHomeUserKey; } });
16
18
  Object.defineProperty(exports, "isDefaultAgent", { enumerable: true, get: function () { return paths_1.isDefaultAgent; } });
17
19
  Object.defineProperty(exports, "getAgentManifestPath", { enumerable: true, get: function () { return paths_1.getAgentManifestPath; } });
18
20
  Object.defineProperty(exports, "getAgentAgentsMdPath", { enumerable: true, get: function () { return paths_1.getAgentAgentsMdPath; } });
@@ -52,6 +52,8 @@ exports.getAgentRoot = getAgentRoot;
52
52
  exports.resolveAgentRuntimePaths = resolveAgentRuntimePaths;
53
53
  exports.resolveRuntimePaths = resolveRuntimePaths;
54
54
  exports.userScopedEngineHome = userScopedEngineHome;
55
+ exports.engineHomeUserKey = engineHomeUserKey;
56
+ exports.userScopedEngineHomeFromKey = userScopedEngineHomeFromKey;
55
57
  exports.isDefaultAgent = isDefaultAgent;
56
58
  exports.getAgentManifestPath = getAgentManifestPath;
57
59
  exports.getAgentAgentsMdPath = getAgentAgentsMdPath;
@@ -227,12 +229,33 @@ function resolveRuntimePaths(workspaceRoot, storageUri, agentId, agentVersion) {
227
229
  * The user id is hashed — it is an email/subject, never safe as a raw path segment.
228
230
  */
229
231
  function userScopedEngineHome(baseHome, verifiedUserId) {
232
+ return userScopedEngineHomeFromKey(baseHome, engineHomeUserKey(verifiedUserId));
233
+ }
234
+ /**
235
+ * The opaque per-user directory segment, derived from a verified identity.
236
+ *
237
+ * Split out from `userScopedEngineHome` so it can cross a process boundary. A sandbox child has
238
+ * no `runWithCredential` frame — the credential deliberately never leaves broker memory — so
239
+ * before this existed every engine inside a tier-0 sandbox resolved the UNSCOPED base dir and
240
+ * every user's transcripts landed in one directory. The key is safe to send where the identity is
241
+ * not: it is a one-way hash, it authorizes nothing, and the child only ever uses it as a path
242
+ * segment.
243
+ */
244
+ function engineHomeUserKey(verifiedUserId) {
230
245
  const uid = verifiedUserId?.trim();
231
246
  if (!uid) {
247
+ return undefined;
248
+ }
249
+ return crypto.createHash('sha256').update(uid).digest('hex').slice(0, 16);
250
+ }
251
+ /** `userScopedEngineHome` for a caller that already holds the key (i.e. inside a sandbox). */
252
+ function userScopedEngineHomeFromKey(baseHome, userKey) {
253
+ // A key from the wire is a path segment, so it is checked rather than trusted: anything but
254
+ // the hex this function produces would let a caller aim an engine home anywhere on disk.
255
+ if (!userKey || !/^[0-9a-f]{16}$/.test(userKey)) {
232
256
  return baseHome;
233
257
  }
234
- const hash = crypto.createHash('sha256').update(uid).digest('hex').slice(0, 16);
235
- return path.join(baseHome, 'users', hash);
258
+ return path.join(baseHome, 'users', userKey);
236
259
  }
237
260
  /**
238
261
  * Check if an agent is the default workspace agent.
@@ -36,12 +36,14 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.mergeSessionJsonlIntoFleetRows = mergeSessionJsonlIntoFleetRows;
39
40
  exports.getScheduledFleetRequests = getScheduledFleetRequests;
40
41
  exports.createLocalFleetAdapter = createLocalFleetAdapter;
41
42
  const vscode = __importStar(require("vscode"));
42
43
  const fleetTypes_1 = require("./fleetTypes");
43
44
  const fleetDisplayUtils_1 = require("./fleetDisplayUtils");
44
45
  const fleetRowDefaults_1 = require("./fleetRowDefaults");
46
+ const sessionJsonlFleet_1 = require("./sessionJsonlFleet");
45
47
  const scheduledFleetStorage_1 = require("./scheduledFleetStorage");
46
48
  const LOCAL_SOURCE_ID = 'local';
47
49
  const FUTURE_START_SCHEDULED_MS = 60_000;
@@ -101,12 +103,87 @@ function toRow(sessionId, agentId, startedAt, endedAt, status, taskTitle, model,
101
103
  disconnectCapable: false,
102
104
  });
103
105
  }
106
+ function scheduleSnapshotByChatSessionId(scheduled) {
107
+ const map = new Map();
108
+ for (const item of scheduled) {
109
+ const sessionId = String(item.sessionId ?? '').trim();
110
+ if (!sessionId) {
111
+ continue;
112
+ }
113
+ const existing = map.get(sessionId);
114
+ if (!existing || item.runAt >= existing.runAt) {
115
+ map.set(sessionId, item);
116
+ }
117
+ }
118
+ return map;
119
+ }
120
+ function applyJsonlHintToRow(row, hint) {
121
+ row.turns = Math.max(row.turns, hint.turns);
122
+ if (hint.startedAt) {
123
+ row.startedAt = hint.startedAt;
124
+ }
125
+ if (hint.endedAt) {
126
+ row.endedAt = hint.endedAt;
127
+ }
128
+ // JSONL turn events are authoritative when traces are missing or still show in_progress.
129
+ if (hint.turns > 0 || hint.status === 'done') {
130
+ row.status = hint.status;
131
+ }
132
+ else if (row.status === 'in_progress' && hint.status !== 'in_progress') {
133
+ row.status = hint.status;
134
+ }
135
+ }
136
+ function jsonlHintToRow(hint, schedule) {
137
+ const messagePreview = schedule?.messageText
138
+ ? (schedule.messageText.length > 80
139
+ ? `${schedule.messageText.slice(0, 77)}...`
140
+ : schedule.messageText)
141
+ : hint.sessionId;
142
+ return toRow(hint.sessionId, schedule?.agentId, hint.startedAt, hint.endedAt, hint.status, messagePreview, schedule?.model ?? 'unknown', schedule?.engine ?? (0, fleetDisplayUtils_1.inferFleetEngineId)(schedule?.model), hint.turns, 0);
143
+ }
144
+ /** Drop schedule:* cards when the linked chat session is live or finished. */
145
+ function dropScheduleRowsSupersededByChatSession(sessionsById) {
146
+ for (const key of [...sessionsById.keys()]) {
147
+ if (!key.startsWith('schedule:')) {
148
+ continue;
149
+ }
150
+ const scheduleRow = sessionsById.get(key);
151
+ if (!scheduleRow) {
152
+ continue;
153
+ }
154
+ const linked = String(scheduleRow.channelTarget ?? '').trim();
155
+ if (!linked) {
156
+ continue;
157
+ }
158
+ const chatRow = sessionsById.get(linked);
159
+ if (chatRow && chatRow.status !== 'scheduled') {
160
+ sessionsById.delete(key);
161
+ }
162
+ }
163
+ }
164
+ /** @internal Exported for unit tests. */
165
+ function mergeSessionJsonlIntoFleetRows(sessionsById, scheduleByChatSessionId, filters, sessionsDir) {
166
+ for (const hint of (0, sessionJsonlFleet_1.scanSessionJsonlFleetHints)(sessionsDir)) {
167
+ const schedule = scheduleByChatSessionId.get(hint.sessionId);
168
+ if (filters.agentId && schedule?.agentId && schedule.agentId !== filters.agentId) {
169
+ continue;
170
+ }
171
+ const existing = sessionsById.get(hint.sessionId);
172
+ if (existing) {
173
+ applyJsonlHintToRow(existing, hint);
174
+ continue;
175
+ }
176
+ sessionsById.set(hint.sessionId, jsonlHintToRow(hint, schedule));
177
+ }
178
+ dropScheduleRowsSupersededByChatSession(sessionsById);
179
+ }
104
180
  /** Returns scheduled chat requests for fleet merge (used by orchestrator for formal local script). */
105
181
  async function getScheduledFleetRequests() {
106
182
  return (0, scheduledFleetStorage_1.loadScheduledFleetRequests)();
107
183
  }
108
184
  /**
109
- * Local fleet source adapter: trace-derived sessions + scheduled chat requests, merged and status-normalized.
185
+ * Local fleet source adapter: trace-derived sessions, session JSONL hints, and scheduled chat
186
+ * requests, merged and status-normalized.
110
187
  */
111
188
  function createLocalFleetAdapter(queryRunner) {
112
189
  return {
@@ -120,7 +197,9 @@ function createLocalFleetAdapter(queryRunner) {
120
197
  // Trace DB may be missing; continue with scheduled only.
121
198
  }
122
199
  const scheduledRequests = await getScheduledFleetRequests();
123
- const scheduleHintsByChatSessionId = (0, fleetDisplayUtils_1.buildScheduleHintsByChatSessionId)(await (0, scheduledFleetStorage_1.loadScheduledFleetEnrichmentHints)());
200
+ const scheduleEnrichment = await (0, scheduledFleetStorage_1.loadScheduledFleetEnrichmentHints)();
201
+ const scheduleByChatSessionId = scheduleSnapshotByChatSessionId(scheduleEnrichment);
202
+ const scheduleHintsByChatSessionId = (0, fleetDisplayUtils_1.buildScheduleHintsByChatSessionId)(scheduleEnrichment);
124
203
  const sessionsById = new Map();
125
204
  for (const session of sessions) {
126
205
  const status = normalizeStatus(session);
@@ -153,6 +232,7 @@ function createLocalFleetAdapter(queryRunner) {
153
232
  });
154
233
  sessionsById.set(scheduleRowKey, scheduleRow);
155
234
  }
235
+ mergeSessionJsonlIntoFleetRows(sessionsById, scheduleByChatSessionId, filters);
156
236
  const sessionIdsForTitles = [...new Set(Array.from(sessionsById.values()).flatMap(row => {
157
237
  const ids = [];
158
238
  if (row.sessionId && !row.sessionId.startsWith('schedule:')) {
@@ -2058,12 +2058,23 @@ class AnalyticsDashboardProvider {
2058
2058
  await fs_1.promises.mkdir(panelDir, { recursive: true });
2059
2059
  const webviewBasePath = this.getWebviewPath();
2060
2060
  const bundledDir = path.join(this.extensionContext.extensionPath, webviewBasePath);
2061
- // Bundled source → user destination filename
2061
+ // Bundled source → user destination filename.
2062
+ // panel.js is deliberately NOT seeded: getHtmlContent always loads the bundled panel.js,
2063
+ // so a seeded copy is never executed. Writing one only created a file that looks editable,
2064
+ // silently diverges from the bundle, and makes "my dashboard edit did nothing" reports.
2062
2065
  const seedMap = [
2063
- { bundled: 'panel.js', userFile: 'panel.js' },
2064
2066
  { bundled: 'styles.css', userFile: 'panel.css' },
2065
2067
  { bundled: 'analytics-panel.html', userFile: 'panel.html' },
2066
2068
  ];
2069
+ // Explain a pre-existing seeded copy rather than deleting the user's file.
2070
+ try {
2071
+ await fs_1.promises.access(path.join(panelDir, 'panel.js'));
2072
+ this.outputChannel.appendLine(`[Dashboard] Note: ${path.join(panelDir, 'panel.js')} is not used — the bundled panel.js always loads. ` +
2073
+ `Edit panel.html / panel.css to customize the dashboard.`);
2074
+ }
2075
+ catch {
2076
+ // No stale copy — nothing to explain.
2077
+ }
2067
2078
  for (const { bundled, userFile } of seedMap) {
2068
2079
  const dest = path.join(panelDir, userFile);
2069
2080
  try {
@@ -5,8 +5,9 @@
5
5
  * Handles artifact display, rendering, and interactions.
6
6
  * Uses panelApi (provided by runtime-bridge.js) instead of raw VS Code API.
7
7
  *
8
- * This file is seeded to .agent-hippo/analytics/panel/panel.js on first use.
9
- * Users can edit the seeded copy to customize the dashboard UI.
8
+ * This bundled file is the ONLY panel.js ever executed (see provider.getHtmlContent).
9
+ * It is not seeded to .agent-hippo/analytics/panel/ — a copy there would never run.
10
+ * Dashboard UI is customized via panel.html / panel.css, which ARE read from that directory.
10
11
  * ----------------------------------------------------------------------------- */
11
12
 
12
13
  // @ts-check
Binary file
@@ -800,21 +800,22 @@ class AnalyticsService {
800
800
  '-ConfigPath',
801
801
  options.configPath,
802
802
  ]);
803
+ // A missing config is not fatal: the launcher re-seeds the managed default
804
+ // (~/.agent-hippo/litellm-otel.yaml) from its bundled template before starting.
803
805
  return [
804
806
  'Write-Host "Starting LiteLLM proxy..."',
805
807
  `if (-not (Test-Path ${q(options.configPath)})) {`,
806
- ` Write-Host ${q(`[WARN] LiteLLM config not found: ${options.configPath}`)} -ForegroundColor Yellow`,
807
- '} else {',
808
- ` $env:LITELLM_CONFIG_PATH = ${q(options.configPath)}`,
809
- ' try {',
810
- ` $proc = Start-Process -FilePath ${q(getWindowsPwshOrPowerShell())} -WindowStyle Hidden -WorkingDirectory ${q(path.dirname(options.litellmScript))} -ArgumentList ${q(launcherArgumentLine)} -RedirectStandardOutput ${q(options.stdoutPath)} -RedirectStandardError ${q(options.stderrPath)} -PassThru -ErrorAction Stop`,
811
- ' Write-Host "[OK] LiteLLM launcher started (PID: $($proc.Id))"',
812
- ` Write-Host ${q(` Logs: ${options.stdoutPath}`)}`,
813
- ` Write-Host ${q(` Errors: ${options.stderrPath}`)}`,
814
- ' } catch {',
815
- ' Write-Host "[ERROR] Failed to start LiteLLM launcher." -ForegroundColor Red',
816
- ' Write-Host $_.Exception.Message -ForegroundColor Red',
817
- ' }',
808
+ ` Write-Host ${q(`[INFO] LiteLLM config missing: ${options.configPath} — launcher will re-create it from the bundled template.`)}`,
809
+ '}',
810
+ `$env:LITELLM_CONFIG_PATH = ${q(options.configPath)}`,
811
+ 'try {',
812
+ ` $proc = Start-Process -FilePath ${q(getWindowsPwshOrPowerShell())} -WindowStyle Hidden -WorkingDirectory ${q(path.dirname(options.litellmScript))} -ArgumentList ${q(launcherArgumentLine)} -RedirectStandardOutput ${q(options.stdoutPath)} -RedirectStandardError ${q(options.stderrPath)} -PassThru -ErrorAction Stop`,
813
+ ' Write-Host "[OK] LiteLLM launcher started (PID: $($proc.Id))"',
814
+ ` Write-Host ${q(` Logs: ${options.stdoutPath}`)}`,
815
+ ` Write-Host ${q(` Errors: ${options.stderrPath}`)}`,
816
+ '} catch {',
817
+ ' Write-Host "[ERROR] Failed to start LiteLLM launcher." -ForegroundColor Red',
818
+ ' Write-Host $_.Exception.Message -ForegroundColor Red',
818
819
  '}',
819
820
  ];
820
821
  }
@@ -854,17 +855,18 @@ class AnalyticsService {
854
855
  if (!options.litellmScript) {
855
856
  return ['echo "[WARN] LiteLLM launcher script not found. Skipping LiteLLM start." >&2'];
856
857
  }
858
+ // A missing config is not fatal: the launcher re-seeds the managed default
859
+ // (~/.agent-hippo/litellm-otel.yaml) from its bundled template before starting.
857
860
  return [
858
861
  'echo "Starting LiteLLM proxy..."',
859
862
  `if [ ! -f ${q(options.configPath)} ]; then`,
860
- ` echo ${q(`[WARN] LiteLLM config not found: ${options.configPath}`)} >&2`,
861
- 'else',
862
- ` export LITELLM_CONFIG_PATH=${q(options.configPath)}`,
863
- ` nohup ${q(options.litellmScript)} --config ${q(options.configPath)} >> ${q(options.stdoutPath)} 2>> ${q(options.stderrPath)} &`,
864
- ' echo "[OK] LiteLLM launcher started (PID: $!)"',
865
- ` echo ${q(` Logs: ${options.stdoutPath}`)}`,
866
- ` echo ${q(` Errors: ${options.stderrPath}`)}`,
863
+ ` echo ${q(`[INFO] LiteLLM config missing: ${options.configPath} — launcher will re-create it from the bundled template.`)}`,
867
864
  'fi',
865
+ `export LITELLM_CONFIG_PATH=${q(options.configPath)}`,
866
+ `nohup ${q(options.litellmScript)} --config ${q(options.configPath)} >> ${q(options.stdoutPath)} 2>> ${q(options.stderrPath)} &`,
867
+ 'echo "[OK] LiteLLM launcher started (PID: $!)"',
868
+ `echo ${q(` Logs: ${options.stdoutPath}`)}`,
869
+ `echo ${q(` Errors: ${options.stderrPath}`)}`,
868
870
  ];
869
871
  }
870
872
  getLifecycleTerminalIntro(action) {
@@ -893,13 +895,9 @@ class AnalyticsService {
893
895
  * Start LiteLLM in a detached background process. Logs go to .agent-hippo/analytics/litellm.{log,error.log}.
894
896
  */
895
897
  async startLiteLLMDirectly() {
898
+ // No existence check: a missing managed config is re-seeded from the bundled
899
+ // template by the launcher itself (deleting it is a "reset to defaults" gesture).
896
900
  const configPath = path.join((0, index_1.getAgentideHome)(), 'litellm-otel.yaml');
897
- try {
898
- await fs_1.promises.access(configPath);
899
- }
900
- catch {
901
- return false;
902
- }
903
901
  const isWindows = process.platform === 'win32';
904
902
  const args = isWindows
905
903
  ? ['-ConfigPath', configPath]
@@ -922,6 +920,12 @@ class AnalyticsService {
922
920
  ? ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, ...args]
923
921
  : [scriptPath, ...args];
924
922
  const env = { ...process.env, AGENT_HOME_DIR: (0, index_1.getAgentideHome)() };
923
+ // IDE-launched proxies default to content capture ON so Spotlight can show prompts and
924
+ // correlate sessions; serve/gateway deployments launch LiteLLM elsewhere and keep the
925
+ // metadata-only default. An explicit AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=0 still wins.
926
+ if (!env.AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE) {
927
+ env.AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE = '1';
928
+ }
925
929
  if (opts.configPath) {
926
930
  env.LITELLM_CONFIG_PATH = opts.configPath;
927
931
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  /*---------------------------------------------------------------------------------------------
3
3
  * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ * Licensed under the MIT License. See LICENSE.txt in the project root for license information.
4
5
  *--------------------------------------------------------------------------------------------*/
5
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
7
  if (k2 === undefined) k2 = k;
@@ -36,7 +37,10 @@ var __importStar = (this && this.__importStar) || (function () {
36
37
  };
37
38
  })();
38
39
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.ObservableStreamEmitter = exports.SessionEventWriter = void 0;
40
+ exports.ObservableStreamEmitter = exports.SessionEventWriter = exports.EVIDENCE_REQUIRED_ENV = void 0;
41
+ exports.getSessionEventWriterStats = getSessionEventWriterStats;
42
+ exports.resetSessionEventWriterStatsForTest = resetSessionEventWriterStatsForTest;
43
+ exports.evidenceRequired = evidenceRequired;
40
44
  exports.wrapEmitterWithObservability = wrapEmitterWithObservability;
41
45
  /**
42
46
  * Session Event Writer & Observable Stream Emitter
@@ -47,24 +51,51 @@ exports.wrapEmitterWithObservability = wrapEmitterWithObservability;
47
51
  *
48
52
  * Events are written to ~/.agent-hippo/analytics/sessions/<session_id>.jsonl
49
53
  * and queryable by DuckDB alongside OTEL traces.
54
+ *
55
+ * PRIVACY: user identifiers are salted hashes and tool I/O is a sha256 + length, unless an
56
+ * operator opts in — see ./identity.ts. These records are shipped wholesale to the evidence
57
+ * server, so whatever lands in this file leaves the machine.
50
58
  */
51
59
  const path = __importStar(require("path"));
52
60
  const fs_1 = require("fs");
53
61
  const branding_1 = require("../branding");
54
62
  const os = __importStar(require("os"));
55
- // -----------------------------------------------------------------------------
56
- // Session Event Writer
57
- // -----------------------------------------------------------------------------
63
+ const identity_1 = require("./identity");
64
+ /** Process-wide counters, aggregated across every writer instance. */
65
+ const globalStats = {
66
+ written: 0,
67
+ failed: 0,
68
+ };
58
69
  /**
59
- * Append-only JSONL writer for session events.
60
- * One file per session: ~/.agent-hippo/analytics/sessions/<session_id>.jsonl
70
+ * Process-wide evidence-write health, for /health-style output.
61
71
  *
62
- * Design: write-through (no buffering). Each event appends immediately.
63
- * JSONL is safe for concurrent writes and crash-safe.
72
+ * `failed > 0` means at least one consequential act produced no record (R-1 completeness).
73
+ */
74
+ function getSessionEventWriterStats() {
75
+ return { written: globalStats.written, failed: globalStats.failed, lastError: globalStats.lastError };
76
+ }
77
+ /** Reset the process-wide counters. Tests only. */
78
+ function resetSessionEventWriterStatsForTest() {
79
+ globalStats.written = 0;
80
+ globalStats.failed = 0;
81
+ globalStats.lastError = undefined;
82
+ }
83
+ /**
84
+ * Set when evidence is REQUIRED: a failed write then rejects rather than returning, so the turn
85
+ * fails instead of proceeding unrecorded. Off by default — an IDE user should not lose a turn
86
+ * because a disk was full — but a regulated deployment wants the opposite trade.
64
87
  */
88
+ exports.EVIDENCE_REQUIRED_ENV = 'AGENTHIPPO_EVIDENCE_REQUIRED';
89
+ function evidenceRequired(env = process.env) {
90
+ const v = (env[exports.EVIDENCE_REQUIRED_ENV] ?? '').trim().toLowerCase();
91
+ return v === '1' || v === 'true' || v === 'yes' || v === 'on';
92
+ }
65
93
  class SessionEventWriter {
66
94
  baseDir;
67
95
  ensuredDirs = new Set();
96
+ written = 0;
97
+ failed = 0;
98
+ lastError;
68
99
  sanitizeSessionId(sessionId) {
69
100
  const trimmed = sessionId.trim();
70
101
  if (!trimmed) {
@@ -78,6 +109,14 @@ class SessionEventWriter {
78
109
  }
79
110
  /**
80
111
  * Append a session event to the session's JSONL file.
112
+ *
113
+ * A failed write is a HOLE IN THE RECORD, not a nuisance: the act still happened, and nothing
114
+ * downstream can tell the difference between "no tool ran" and "the tool event never landed".
115
+ * So every failure is logged at error level and counted into /health-visible state. With
116
+ * AGENTHIPPO_EVIDENCE_REQUIRED set it also rejects, so the caller fails the turn rather than
117
+ * completing it unrecorded.
118
+ *
119
+ * This used to swallow every error in an empty catch block — silence on every failure.
81
120
  */
82
121
  async append(sessionId, requestId, eventType, payload) {
83
122
  try {
@@ -97,11 +136,27 @@ class SessionEventWriter {
97
136
  const filePath = path.join(this.baseDir, `${safeSessionId}.jsonl`);
98
137
  const line = JSON.stringify(event) + '\n';
99
138
  await fs_1.promises.appendFile(filePath, line);
139
+ this.written++;
140
+ globalStats.written++;
100
141
  }
101
- catch {
102
- // Best-effort: don't crash the agent if event writing fails
142
+ catch (err) {
143
+ const message = err instanceof Error ? err.message : String(err);
144
+ this.failed++;
145
+ globalStats.failed++;
146
+ const record = { at: Date.now(), message, eventType };
147
+ this.lastError = record;
148
+ globalStats.lastError = record;
149
+ console.error(`[evidence] LOST session event ${eventType} (session=${sessionId} request=${requestId}): ` +
150
+ `${message} — ${globalStats.failed} evidence write(s) lost since start`);
151
+ if (evidenceRequired()) {
152
+ throw new Error(`evidence write failed for ${eventType}: ${message}`);
153
+ }
103
154
  }
104
155
  }
156
+ /** Per-writer evidence-write health. See {@link getSessionEventWriterStats} for the process view. */
157
+ getStats() {
158
+ return { written: this.written, failed: this.failed, lastError: this.lastError };
159
+ }
105
160
  /**
106
161
  * Get the sessions directory path (for DuckDB queries).
107
162
  */
@@ -126,9 +181,15 @@ class ObservableStreamEmitter {
126
181
  writer;
127
182
  sessionId;
128
183
  requestId;
129
- userId;
184
+ stamp;
130
185
  missingCallIdCounter = 0;
131
186
  pendingSyntheticCallIds = new Map();
187
+ /**
188
+ * Salted pseudonym of the acting user — the warrant, in the form the evidence index wants
189
+ * (PLAN/compliance.md §2 "salted identity hashes"). Raw only under
190
+ * AGENTHIPPO_EVIDENCE_RAW_IDS=1. Derived in the constructor; the raw id is never a field.
191
+ */
192
+ actorId;
132
193
  constructor(inner, writer, sessionId, requestId,
133
194
  /**
134
195
  * Acting user for the warrant audit trail on tool events. Captured once at
@@ -138,13 +199,25 @@ class ObservableStreamEmitter {
138
199
  * parent IPC callback whose ALS can stick to the spawn request; constructor
139
200
  * capture keeps attribution correct for every engine. Undefined outside a
140
201
  * serve credential scope (IDE-local / CLI without runWithCredential).
202
+ *
203
+ * NOT a field: it is hashed into `actorId` in the constructor and the raw value is then
204
+ * unreachable from any emit path.
205
+ */
206
+ userId,
207
+ /**
208
+ * Provenance stamp merged into turn_start (policy_version, pack_digest, engine_version,
209
+ * tier) — which rules and which artifacts governed this turn, recorded at the moment of
210
+ * the act rather than reconstructed later (PLAN/compliance.md §3.1).
141
211
  */
142
- userId) {
212
+ stamp) {
143
213
  this.inner = inner;
144
214
  this.writer = writer;
145
215
  this.sessionId = sessionId;
146
216
  this.requestId = requestId;
147
- this.userId = userId;
217
+ this.stamp = stamp;
218
+ // Hash once at construction: the raw id is never stored on this object, so no later edit
219
+ // to an emit path can leak it by reaching for `this.userId`.
220
+ this.actorId = (0, identity_1.hashIdentity)(userId);
148
221
  }
149
222
  makeCallIdKey(toolName) {
150
223
  return `${this.sessionId}:${this.requestId}:${toolName}`;
@@ -185,10 +258,12 @@ class ObservableStreamEmitter {
185
258
  await this.writer.append(this.sessionId, this.requestId, 'tool_start', {
186
259
  toolName,
187
260
  callId: loggedCallId,
188
- // Truncate input to avoid huge JSONL files
189
- inputPreview: input?.slice(0, 500),
261
+ // Tool arguments are content — paths, queries, credentials people paste into prompts.
262
+ // Digest by default (input_sha256 + input_length); `inputPreview` returns only under
263
+ // AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1.
264
+ ...(0, identity_1.describeContent)('input', input),
190
265
  // The warrant: who this tool call ran as (captured at wrap; see constructor).
191
- userId: this.userId,
266
+ userId: this.actorId,
192
267
  });
193
268
  return this.inner.toolStart(toolName, input, toolCallId, metadata);
194
269
  }
@@ -200,9 +275,10 @@ class ObservableStreamEmitter {
200
275
  toolName,
201
276
  callId: loggedCallId,
202
277
  isError: isError ?? false,
203
- // Truncate result to avoid huge JSONL files
204
- resultPreview: result?.slice(0, 500),
205
- userId: this.userId,
278
+ // Tool output is content — file contents, command output, customer data. Digest by
279
+ // default; `resultPreview` returns only under AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1.
280
+ ...(0, identity_1.describeContent)('result', result),
281
+ userId: this.actorId,
206
282
  });
207
283
  return this.inner.toolEnd(toolName, toolCallId, result, isError, metadata);
208
284
  }
@@ -215,7 +291,7 @@ class ObservableStreamEmitter {
215
291
  async error(message) {
216
292
  await this.writer.append(this.sessionId, this.requestId, 'error', {
217
293
  message,
218
- userId: this.userId,
294
+ userId: this.actorId,
219
295
  });
220
296
  return this.inner.error(message);
221
297
  }
@@ -226,7 +302,7 @@ class ObservableStreamEmitter {
226
302
  * Emit a turn_start event. Call at the beginning of stream().
227
303
  */
228
304
  async emitTurnStart() {
229
- await this.writer.append(this.sessionId, this.requestId, 'turn_start', { userId: this.userId });
305
+ await this.writer.append(this.sessionId, this.requestId, 'turn_start', { userId: this.actorId, ...this.stamp });
230
306
  }
231
307
  /**
232
308
  * Emit a turn_end event. Call at the end of stream().
@@ -234,7 +310,7 @@ class ObservableStreamEmitter {
234
310
  async emitTurnEnd(success) {
235
311
  await this.writer.append(this.sessionId, this.requestId, 'turn_end', {
236
312
  success,
237
- userId: this.userId,
313
+ userId: this.actorId,
238
314
  });
239
315
  }
240
316
  }
@@ -243,6 +319,6 @@ exports.ObservableStreamEmitter = ObservableStreamEmitter;
243
319
  * Create an ObservableStreamEmitter that wraps the given emitter.
244
320
  * Returns both the wrapped emitter and emit helpers for turn boundaries.
245
321
  */
246
- function wrapEmitterWithObservability(inner, writer, sessionId, requestId, userId) {
247
- return new ObservableStreamEmitter(inner, writer, sessionId, requestId, userId);
322
+ function wrapEmitterWithObservability(inner, writer, sessionId, requestId, userId, stamp) {
323
+ return new ObservableStreamEmitter(inner, writer, sessionId, requestId, userId, stamp);
248
324
  }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) AgentHippo.ai. All rights reserved.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.assertSafeAuthBaseUrl = void 0;
7
+ exports.pickTrustedAuthBaseUrl = pickTrustedAuthBaseUrl;
8
+ exports.resolveAuthBrokerBaseUrl = resolveAuthBrokerBaseUrl;
9
+ /**
10
+ * Where `agentide.auth.baseUrl` is allowed to come from.
11
+ *
12
+ * The setting is declared `"scope": "application"` in package.json, which is what actually stops a
13
+ * repo's `.vscode/settings.json` from setting it. This module is the defence in depth behind that
14
+ * declaration: it re-derives the value from `inspect()` and takes ONLY the user/application/default
15
+ * layers, so even if the contribution scope is ever loosened by accident (or the value arrives
16
+ * through a remote/folder layer this extension does not expect), a workspace-supplied endpoint is
17
+ * dropped rather than trusted.
18
+ *
19
+ * Why this matters concretely: the resolved URL receives the PKCE authorization code, then
20
+ * `Authorization: Bearer <AgentHippo access token>` on `/api/ide/bootstrap`, and the response
21
+ * configures team store settings plus `agentide.AgentHippoModelGateway.url` (application-scoped).
22
+ * The access token is also synced to the `agenthippo` model provider. A workspace that could
23
+ * steer the auth endpoint would capture the token AND every subsequent prompt.
24
+ */
25
+ const brokerClient_js_1 = require("./brokerClient.js");
26
+ Object.defineProperty(exports, "assertSafeAuthBaseUrl", { enumerable: true, get: function () { return brokerClient_js_1.assertSafeAuthBaseUrl; } });
27
+ /**
28
+ * Pick the auth base URL from the trusted layers only.
29
+ *
30
+ * Precedence matches VS Code's own (most specific trusted layer wins): remote user → local user →
31
+ * user → default. `workspaceValue` / `workspaceFolderValue` are never consulted for the result;
32
+ * they are only reported so the caller can warn.
33
+ */
34
+ function pickTrustedAuthBaseUrl(inspect) {
35
+ if (!inspect) {
36
+ return {};
37
+ }
38
+ const ignored = inspect.workspaceFolderValue?.trim() || inspect.workspaceValue?.trim() || undefined;
39
+ const trusted = inspect.globalRemoteValue?.trim() ||
40
+ inspect.globalLocalValue?.trim() ||
41
+ inspect.globalValue?.trim() ||
42
+ inspect.defaultValue?.trim() ||
43
+ undefined;
44
+ // A workspace value that merely repeats the trusted value is not an override worth warning about.
45
+ const ignoredWorkspaceValue = ignored && ignored !== trusted ? ignored : undefined;
46
+ return { value: trusted, ...(ignoredWorkspaceValue ? { ignoredWorkspaceValue } : {}) };
47
+ }
48
+ /**
49
+ * Resolve the auth broker base URL from configuration, dropping workspace-scoped values and
50
+ * enforcing the https/loopback requirement. Throws (with an actionable message) on a bad URL.
51
+ */
52
+ function resolveAuthBrokerBaseUrl(config, warn) {
53
+ const picked = pickTrustedAuthBaseUrl(config?.inspect('auth.baseUrl'));
54
+ if (picked.ignoredWorkspaceValue) {
55
+ warn?.(`[AgentIDE] Ignoring workspace-provided "agentide.auth.baseUrl" (${picked.ignoredWorkspaceValue}). ` +
56
+ `This endpoint receives your AgentHippo access token, so it is only honoured from user/application settings.`);
57
+ }
58
+ return (0, brokerClient_js_1.getAuthBrokerBaseUrl)(picked.value);
59
+ }