impel-cli 0.20.25 → 0.20.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Release notes
2
2
 
3
+ ## 0.20.26 — Native task-board navigation
4
+
5
+ - Embeds Tasks in each managed desktop app's content view instead of a
6
+ separate always-on-top window, so selecting another sidebar destination
7
+ reliably dismisses the tenant board and completes the vendor navigation.
8
+ - Removes the redundant desktop frame around the MCP App so the task-board
9
+ toolbar and workspace occupy the complete host content surface.
10
+ - Rebuilds existing managed bundles onto the corrected embedded-view contract.
11
+
3
12
  ## 0.20.25 — Embedded tenant task boards
4
13
 
5
14
  - Adds a persistent Tasks destination to the exact managed Claude and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.25",
3
+ "version": "0.20.27",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -144,6 +144,27 @@ function rolloutSessionMetadata(events) {
144
144
  } : { threadId: null, parentThreadId: null };
145
145
  }
146
146
 
147
+ function nativeTerminalStatus(output) {
148
+ if (typeof output !== "string") return null;
149
+ const objectStart = output.indexOf("{");
150
+ if (objectStart < 0) return null;
151
+ let value;
152
+ try {
153
+ value = JSON.parse(output.slice(objectStart));
154
+ } catch {
155
+ return null;
156
+ }
157
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
158
+ if (value.schema === "impel.native-agent-answer.v1" && value.status === "succeeded") {
159
+ return "succeeded";
160
+ }
161
+ if (value.schema === "impel.native-agent-result.v1"
162
+ && ["succeeded", "failed", "cancelled", "canceled"].includes(value.status)) {
163
+ return value.status;
164
+ }
165
+ return null;
166
+ }
167
+
147
168
  export function summarizeCodexRollouts(rollouts) {
148
169
  const toolCounts = {};
149
170
  const parentToolCounts = {};
@@ -156,6 +177,10 @@ export function summarizeCodexRollouts(rollouts) {
156
177
  let outputTokens = 0;
157
178
  let mcpDurationMs = 0;
158
179
  let mcpCompletedCalls = 0;
180
+ let terminalResults = 0;
181
+ let successfulTerminalResults = 0;
182
+ let parentSuccessfulTerminalResults = 0;
183
+ let childSuccessfulTerminalResults = 0;
159
184
 
160
185
  for (const events of rollouts) {
161
186
  eventCount += events.length;
@@ -194,6 +219,17 @@ export function summarizeCodexRollouts(rollouts) {
194
219
  mcpStarts.delete(payload.call_id);
195
220
  }
196
221
  }
222
+ if (event.type === "response_item" && payload.type === "function_call_output") {
223
+ const terminalStatus = nativeTerminalStatus(payload.output);
224
+ if (terminalStatus) {
225
+ terminalResults += 1;
226
+ if (terminalStatus === "succeeded") {
227
+ successfulTerminalResults += 1;
228
+ if (metadata.parentThreadId) childSuccessfulTerminalResults += 1;
229
+ else parentSuccessfulTerminalResults += 1;
230
+ }
231
+ }
232
+ }
197
233
  if (event.type === "event_msg" && payload.type === "token_count") {
198
234
  const usage = payload.info?.total_token_usage;
199
235
  if (usage && typeof usage === "object") latestUsage = usage;
@@ -219,6 +255,10 @@ export function summarizeCodexRollouts(rollouts) {
219
255
  outputTokens,
220
256
  mcpDurationMs,
221
257
  mcpCompletedCalls,
258
+ terminalResults,
259
+ successfulTerminalResults,
260
+ parentSuccessfulTerminalResults,
261
+ childSuccessfulTerminalResults,
222
262
  };
223
263
  }
224
264
 
@@ -281,6 +321,10 @@ export function summarizeRawAttempt({ stdoutText, telemetryText = "", rolloutTex
281
321
  outputTokens: rollout.outputTokens,
282
322
  mcpDurationMs: rollout.mcpDurationMs,
283
323
  mcpCompletedCalls: rollout.mcpCompletedCalls,
324
+ terminalResults: rollout.terminalResults,
325
+ successfulTerminalResults: rollout.successfulTerminalResults,
326
+ parentSuccessfulTerminalResults: rollout.parentSuccessfulTerminalResults,
327
+ childSuccessfulTerminalResults: rollout.childSuccessfulTerminalResults,
284
328
  } : {}),
285
329
  parentToolCounts: rollout.parentToolCounts,
286
330
  childToolCounts: rollout.childToolCounts,
@@ -336,6 +380,9 @@ function directGate(report) {
336
380
  structure: report.structure.maximumThreadCount <= 1
337
381
  && report.structure.totalCodeCells === 0
338
382
  && report.structure.totalCodeWaits === 0
383
+ && report.structure.successfulTerminalResults === report.successfulSamples
384
+ && report.structure.parentSuccessfulTerminalResults === report.successfulSamples
385
+ && report.structure.childSuccessfulTerminalResults === 0
339
386
  && ["spawn_agent", "wait_agent", "send_message", "exec", "wait", "tool_search", "ALL_TOOLS"]
340
387
  .every((tool) => !report.structure.allToolCounts[tool])
341
388
  && (report.structure.toolCounts.answer_native_agent || 0) === report.successfulSamples
@@ -350,6 +397,8 @@ function compatibleGate(report) {
350
397
  && report.timings.durationMs.p50 <= (report.sloClass === "cibi" ? 55_000 : 100_000),
351
398
  childStructure: report.structure.totalCodeCells === 0
352
399
  && report.structure.totalCodeWaits === 0
400
+ && report.structure.successfulTerminalResults === report.successfulSamples
401
+ && report.structure.childSuccessfulTerminalResults === report.successfulSamples
353
402
  && ["exec", "wait", "send_message", "tool_search", "ALL_TOOLS"]
354
403
  .every((tool) => !report.structure.childToolCounts[tool]),
355
404
  };
@@ -405,6 +454,19 @@ export function analyzeAggregates(aggregates) {
405
454
  allToolCounts: mergedCounts(attempts, "toolCounts"),
406
455
  parentToolCounts: mergedCounts(successful, "parentToolCounts"),
407
456
  childToolCounts: mergedCounts(successful, "childToolCounts"),
457
+ terminalResults: successful.reduce((total, attempt) => total + (attempt.terminalResults || 0), 0),
458
+ successfulTerminalResults: successful.reduce(
459
+ (total, attempt) => total + (attempt.successfulTerminalResults || 0),
460
+ 0,
461
+ ),
462
+ parentSuccessfulTerminalResults: successful.reduce(
463
+ (total, attempt) => total + (attempt.parentSuccessfulTerminalResults || 0),
464
+ 0,
465
+ ),
466
+ childSuccessfulTerminalResults: successful.reduce(
467
+ (total, attempt) => total + (attempt.childSuccessfulTerminalResults || 0),
468
+ 0,
469
+ ),
408
470
  maximumThreadCount: successful.reduce((maximum, attempt) => Math.max(maximum, attempt.threadCount || 0), 0),
409
471
  totalCodeCells: successful.reduce((total, attempt) => total + (attempt.codeCells || 0), 0),
410
472
  totalCodeWaits: successful.reduce((total, attempt) => total + (attempt.codeWaits || 0), 0),
@@ -390,6 +390,7 @@ export function safeAttemptRecord({ options, index, processResult, summary, json
390
390
  && jsonValid
391
391
  && codex.turnCompleted === true
392
392
  && (codex.agentMessages || 0) >= 1
393
+ && (codex.successfulTerminalResults || 0) === 1
393
394
  && (telemetry.localFailures || 0) === 0
394
395
  && startCalls === 1;
395
396
  return {
@@ -420,6 +421,10 @@ export function safeAttemptRecord({ options, index, processResult, summary, json
420
421
  childToolCounts: codex.childToolCounts || {},
421
422
  codeCells: codex.codeCells || 0,
422
423
  codeWaits: codex.codeWaits || 0,
424
+ terminalResults: codex.terminalResults || 0,
425
+ successfulTerminalResults: codex.successfulTerminalResults || 0,
426
+ parentSuccessfulTerminalResults: codex.parentSuccessfulTerminalResults || 0,
427
+ childSuccessfulTerminalResults: codex.childSuccessfulTerminalResults || 0,
423
428
  inputTokens: codex.inputTokens || 0,
424
429
  cachedInputTokens: codex.cachedInputTokens || 0,
425
430
  outputTokens: codex.outputTokens || 0,
package/src/agents.js CHANGED
@@ -16,6 +16,8 @@ import {
16
16
  redactSecretText,
17
17
  } from "./config.js";
18
18
  import {
19
+ CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS,
20
+ IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV,
19
21
  IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES,
20
22
  IMPEL_NATIVE_AGENT_MCP_TARGET,
21
23
  impelNativeAgentMcpInvocation,
@@ -52,7 +54,7 @@ export const NATIVE_AGENT_RUN_TOOL = "run_native_agent";
52
54
  export const NATIVE_AGENT_RESUME_TOOL = "resume_native_agent_run";
53
55
  export const NATIVE_AGENT_RECOVER_TOOL = "recover_native_agent_runs";
54
56
  export const MANAGED_AGENT_MCP_SERVER = "impel_agent";
55
- export const MANAGED_AGENT_MANIFEST_VERSION = 16;
57
+ export const MANAGED_AGENT_MANIFEST_VERSION = 17;
56
58
 
57
59
  // The host model only selects the fixed MCP tool and faithfully returns its
58
60
  // result. Luna preserves deterministic direct-only code-mode routing while the
@@ -748,9 +750,11 @@ function renderClaudeAgent({ tenantId, agent, name, invocation, recoveryOnly = f
748
750
 
749
751
  function codexInvocationEnvironment(invocation, { durableProfile = false } = {}) {
750
752
  const entries = Object.entries(invocation.env || {});
751
- if (!durableProfile) return entries;
752
753
  const transient = new Set(IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES);
753
- return entries.filter(([key]) => !transient.has(key));
754
+ return [
755
+ ...(durableProfile ? entries.filter(([key]) => !transient.has(key)) : entries),
756
+ [IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV, String(CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS)],
757
+ ];
754
758
  }
755
759
 
756
760
  function renderCodexConfiguration({
@@ -800,6 +804,7 @@ function renderCodexConfiguration({
800
804
  `[mcp_servers.${MANAGED_AGENT_MCP_SERVER}]`,
801
805
  `command = ${JSON.stringify(invocation.command)}`,
802
806
  `args = [${invocation.args.map((argument) => JSON.stringify(argument)).join(", ")}]`,
807
+ "tool_timeout_sec = 120",
803
808
  ...(eager
804
809
  ? [`enabled_tools = [${toolNames.map((tool) => JSON.stringify(tool)).join(", ")}]`]
805
810
  : []),
package/src/apps.js CHANGED
@@ -301,7 +301,7 @@ export const CURRENT_CONFIG_VERSION = 32;
301
301
  // — which is what made every `impel update` re-trigger macOS permission
302
302
  // prompts. Bump this ONLY when a code change alters the bytes of a built
303
303
  // bundle; leave it alone for changes that don't touch bundle contents.
304
- export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-08-08.1";
304
+ export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-08-08.2";
305
305
 
306
306
  /** Parse the tenant's install manifest, or null when absent/corrupt. */
307
307
  export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
@@ -254,7 +254,7 @@ const NAVIGATION_SOURCE = String.raw`(() => {
254
254
  const sidebar = findSidebar();
255
255
  if (!sidebar || !(event.target instanceof Node) || !sidebar.contains(event.target)) return;
256
256
  select(false);
257
- signal("hide");
257
+ setTimeout(() => signal("hide"), 0);
258
258
  };
259
259
  const onResize = () => {
260
260
  if (active) signal("layout");
@@ -480,11 +480,12 @@ if (
480
480
  const {
481
481
  app,
482
482
  BrowserWindow,
483
+ WebContentsView,
483
484
  ipcMain,
484
485
  shell,
485
486
  webContents: electronWebContents,
486
487
  } = electron;
487
- if (!app || !BrowserWindow || !ipcMain || !electronWebContents) {
488
+ if (!app || !BrowserWindow || !WebContentsView || !ipcMain || !electronWebContents) {
488
489
  record("unsupported-electron-surface");
489
490
  return;
490
491
  }
@@ -540,16 +541,7 @@ if (
540
541
  const createView = (window) => {
541
542
  const state = stateFor(window);
542
543
  if (state.view && !state.view.webContents.isDestroyed()) return state;
543
- const view = createInternalWindow({
544
- frame: false,
545
- show: false,
546
- resizable: false,
547
- movable: false,
548
- minimizable: false,
549
- maximizable: false,
550
- fullscreenable: false,
551
- skipTaskbar: true,
552
- backgroundColor: "#181818",
544
+ const view = new WebContentsView({
553
545
  webPreferences: {
554
546
  preload: viewPreload,
555
547
  contextIsolation: true,
@@ -558,7 +550,9 @@ if (
558
550
  spellcheck: false,
559
551
  },
560
552
  });
561
- view.setAlwaysOnTop(true, "floating");
553
+ window.contentView.addChildView(view);
554
+ view.setVisible(false);
555
+ view.setBackgroundColor("#181818");
562
556
  allowedViewContents.add(view.webContents);
563
557
  internalViewContents.add(view.webContents);
564
558
  view.webContents.setWindowOpenHandler(() => ({ action: "deny" }));
@@ -568,63 +562,45 @@ if (
568
562
  view.webContents.on("destroyed", () => {
569
563
  allowedViewContents.delete(view.webContents);
570
564
  internalViewContents.delete(view.webContents);
571
- });
572
- view.on("closed", () => {
565
+ try { window.contentView.removeChildView(view); } catch {}
573
566
  if (state.view === view) state.view = null;
574
567
  });
575
- view.on("blur", () => setTimeout(() => {
576
- const focused = BrowserWindow.getFocusedWindow();
577
- if (focused === window || focused === state.nativeNav || focused === state.view) return;
578
- state.visible = false;
579
- state.view?.hide();
580
- state.nativeNav?.hide();
581
- }, 100));
582
568
  view.webContents.loadURL(hostUrl).catch((error) => record("view-load-failed", error));
583
569
  state.view = view;
584
570
  return state;
585
571
  };
586
572
 
587
- const screenBounds = (window, bounds) => {
588
- const content = window.getContentBounds();
589
- return {
590
- x: content.x + bounds.x,
591
- y: content.y + bounds.y,
592
- width: bounds.width,
593
- height: bounds.height,
594
- };
595
- };
596
-
597
573
  const show = (window, bounds) => {
598
574
  const state = createView(window);
599
575
  state.bounds = bounds;
600
576
  state.visible = true;
601
- state.view.setBounds(screenBounds(window, bounds));
602
- state.view.show();
603
- state.view.focus();
577
+ state.view.setBounds(bounds);
578
+ state.view.setVisible(true);
579
+ state.view.webContents.focus();
604
580
  const boardStatus = () => ({
605
581
  parentBounds: window.getBounds(),
606
582
  parentContentBounds: window.getContentBounds(),
607
583
  boardBounds: state.view.getBounds(),
608
- boardVisible: state.view.isVisible(),
609
- boardFocused: state.view.isFocused(),
584
+ boardVisible: state.visible,
585
+ boardFocused: state.view.webContents.isFocused(),
610
586
  });
611
587
  record("board-shown", JSON.stringify(boardStatus()));
612
588
  setTimeout(() => {
613
- if (state.view && !state.view.isDestroyed()) record("board-visible-check", JSON.stringify(boardStatus()));
589
+ if (state.view && !state.view.webContents.isDestroyed()) record("board-visible-check", JSON.stringify(boardStatus()));
614
590
  }, 2000);
615
591
  };
616
592
  const hide = (window) => {
617
593
  const state = windows.get(window.id);
618
594
  if (!state) return;
619
595
  state.visible = false;
620
- state.view?.hide();
596
+ state.view?.setVisible(false);
621
597
  window.webContents.focus();
622
598
  };
623
599
 
624
600
  const layoutBoard = (window) => {
625
601
  const state = windows.get(window.id);
626
- if (!state?.visible || !state.view || !state.bounds || state.view.isDestroyed()) return;
627
- state.view.setBounds(screenBounds(window, state.bounds));
602
+ if (!state?.visible || !state.view || !state.bounds || state.view.webContents.isDestroyed()) return;
603
+ state.view.setBounds(state.bounds);
628
604
  };
629
605
 
630
606
  const nativeBoardBounds = (window) => {
@@ -800,24 +776,24 @@ if (
800
776
  state.nativeNav.showInactive();
801
777
  state.nativeNav.moveTop();
802
778
  }
803
- if (state?.visible && state.view && !state.view.isDestroyed()) state.view.show();
779
+ if (state?.visible && state.view && !state.view.webContents.isDestroyed()) state.view.setVisible(true);
804
780
  });
805
781
  window.on("blur", () => setTimeout(() => {
806
782
  const state = windows.get(window.id);
807
783
  const focused = BrowserWindow.getFocusedWindow();
808
- if (focused === state?.nativeNav || focused === state?.view) return;
784
+ if (focused === state?.nativeNav) return;
809
785
  state?.nativeNav?.hide();
810
- state?.view?.hide();
786
+ state?.view?.setVisible(false);
811
787
  }, 1000));
812
788
  window.on("minimize", () => {
813
789
  const state = windows.get(window.id);
814
790
  state?.nativeNav?.hide();
815
- state?.view?.hide();
791
+ state?.view?.setVisible(false);
816
792
  });
817
793
  window.on("restore", () => {
818
794
  const state = windows.get(window.id);
819
795
  state?.nativeNav?.showInactive();
820
- if (state?.visible) state.view?.show();
796
+ if (state?.visible) state.view?.setVisible(true);
821
797
  });
822
798
  window.on("closed", () => {
823
799
  const state = windows.get(window.id);
@@ -830,7 +806,8 @@ if (
830
806
  if (state.nativeNav) nativeNavOwners.delete(state.nativeNav.webContents);
831
807
  try {
832
808
  if (state.view) {
833
- state.view.destroy();
809
+ window.contentView.removeChildView(state.view);
810
+ state.view.webContents.close();
834
811
  }
835
812
  if (state.nativeNav) {
836
813
  state.nativeNav.destroy();
@@ -900,7 +877,7 @@ if (
900
877
  if (focused && (windows.has(focused.id) || internalViewContents.has(focused.webContents))) return;
901
878
  for (const state of windows.values()) {
902
879
  state.nativeNav?.hide();
903
- state.view?.hide();
880
+ state.view?.setVisible(false);
904
881
  }
905
882
  };
906
883
 
@@ -949,19 +926,13 @@ export function desktopTasksHostHtml() {
949
926
  :root { color-scheme: light dark; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
950
927
  * { box-sizing: border-box; }
951
928
  html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: Canvas; color: CanvasText; }
952
- body { display: grid; grid-template-rows: 36px minmax(0, 1fr); }
953
- header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 10px 0 14px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, transparent); }
954
- header strong { font-size: 13px; font-weight: 600; }
955
- button { border: 0; border-radius: 7px; padding: 5px 9px; background: color-mix(in srgb, CanvasText 8%, Canvas); color: CanvasText; font: inherit; cursor: pointer; }
956
- button:hover { background: color-mix(in srgb, CanvasText 13%, Canvas); }
957
- button:focus-visible { outline: 2px solid #7c8cff; outline-offset: 2px; }
929
+ body { display: grid; grid-template-rows: minmax(0, 1fr); }
958
930
  iframe { width: 100%; height: 100%; border: 0; background: Canvas; }
959
931
  #status { display: grid; place-items: center; padding: 24px; color: color-mix(in srgb, CanvasText 65%, transparent); text-align: center; }
960
932
  #status[hidden] { display: none; }
961
933
  </style>
962
934
  </head>
963
935
  <body>
964
- <header><strong>Tasks</strong><button id="close" type="button" aria-label="Close Tasks">Close</button></header>
965
936
  <div id="status" role="status">Loading Impel Tasks…</div>
966
937
  <iframe id="widget" title="Impel Tasks" sandbox="allow-scripts allow-forms" hidden></iframe>
967
938
  <script>
@@ -1024,7 +995,9 @@ export function desktopTasksHostHtml() {
1024
995
  }
1025
996
  });
1026
997
 
1027
- document.getElementById("close").addEventListener("click", () => bridge.close());
998
+ window.addEventListener("keydown", (event) => {
999
+ if (event.key === "Escape") bridge.close();
1000
+ });
1028
1001
  Promise.all([
1029
1002
  bridge.rpc({ method: "resources/read", params: { uri: ${JSON.stringify(DESKTOP_TASKS_RESOURCE_URI)} } }),
1030
1003
  bridge.rpc({ method: "tools/call", params: { name: "show_tasks", arguments: { scope: "visible", limit: 50 } } }),
@@ -21,6 +21,9 @@ import {
21
21
  normalizeNativeAgentCatalog,
22
22
  } from "./agents.js";
23
23
  import { extractAnswerFinalText } from "./directAnswer.js";
24
+ import {
25
+ IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV,
26
+ } from "./selfInvocation.js";
24
27
  import { normalizeTenantId } from "./tenants.js";
25
28
  import { renameWithWindowsRetry } from "./windowsFs.js";
26
29
 
@@ -62,6 +65,19 @@ const CTOS_START_TIMEOUT_MESSAGE = `MCP tool call failed for ${NATIVE_AGENT_STAR
62
65
  const MCP_TOOL_RESULT_ERROR = Symbol("native-agent MCP tool result error");
63
66
  const TERMINAL_STATUSES = new Set(["succeeded", "failed", "cancelled", "canceled"]);
64
67
 
68
+ export function nativeAgentAttachmentWindowMs(environment = process.env) {
69
+ const configured = environment?.[IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV];
70
+ if (configured === undefined) return DEFAULT_ATTACHMENT_WINDOW_MS;
71
+ if (!/^\d{1,6}$/u.test(configured)) {
72
+ throw new Error(`${IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV} must be an integer from 1000 through 300000`);
73
+ }
74
+ const parsed = Number(configured);
75
+ if (!Number.isSafeInteger(parsed) || parsed < 1_000 || parsed > 300_000) {
76
+ throw new Error(`${IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV} must be an integer from 1000 through 300000`);
77
+ }
78
+ return parsed;
79
+ }
80
+
65
81
  function stableValue(value) {
66
82
  if (Array.isArray(value)) return value.map(stableValue);
67
83
  if (!value || typeof value !== "object") return value;
@@ -773,7 +789,7 @@ export class NativeAgentCompositeTransport {
773
789
  gatewayUrl,
774
790
  credential,
775
791
  runsRoot = path.join(CONFIG_DIR, "native-agent-runs"),
776
- attachmentWindowMs = DEFAULT_ATTACHMENT_WINDOW_MS,
792
+ attachmentWindowMs = nativeAgentAttachmentWindowMs(),
777
793
  maxPolls = DEFAULT_MAX_POLLS,
778
794
  now = Date.now,
779
795
  randomUUID = crypto.randomUUID,
@@ -72,6 +72,8 @@ export const IMPEL_NATIVE_AGENT_TELEMETRY_ENV_NAMES = [
72
72
  "IMPEL_NATIVE_HOST",
73
73
  "IMPEL_NATIVE_HOST_BUILD",
74
74
  ];
75
+ export const IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_ENV = "IMPEL_NATIVE_AGENT_ATTACHMENT_WINDOW_MS";
76
+ export const CODEX_NATIVE_AGENT_ATTACHMENT_WINDOW_MS = 100_000;
75
77
 
76
78
  function managedMcpEnvironment(environment = process.env) {
77
79
  const telemetry = Object.fromEntries(