@harness-mix/cli 0.1.5 → 0.1.7

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 (92) hide show
  1. package/README.md +86 -56
  2. package/docs/harness-management.md +4 -4
  3. package/docs/multi-agent-collaboration.md +9 -9
  4. package/docs/storage-verification-design.md +49 -0
  5. package/output/native-build/renderer-extension.js +4210 -2273
  6. package/package.json +11 -9
  7. package/scripts/codebuddy-migration-test.cjs +1 -1
  8. package/scripts/collaboration-test.cjs +8 -3
  9. package/scripts/collaboration-ui-smoke.cjs +40 -26
  10. package/scripts/core-runtime-test.cjs +9 -1
  11. package/scripts/integrations-test.cjs +63 -4
  12. package/scripts/integrations-ui-smoke.cjs +77 -16
  13. package/scripts/native-protocol-test.cjs +113 -2
  14. package/scripts/native-sidebar-test.cjs +7 -0
  15. package/scripts/native-skill-roots-test.cjs +90 -0
  16. package/scripts/native-ui-smoke.cjs +23 -4
  17. package/scripts/projector-test.cjs +13 -0
  18. package/scripts/storage-verification-test.cjs +78 -0
  19. package/scripts/switch-harness-test.cjs +1 -1
  20. package/scripts/test-live-mentions.cjs +152 -0
  21. package/src/main/adapters/antigravity.js +20 -17
  22. package/src/main/adapters/claude.js +41 -8
  23. package/src/main/adapters/codebuddy.js +1 -0
  24. package/src/main/adapters/codex.js +20 -2
  25. package/src/main/adapters/cursor.js +5 -1
  26. package/src/main/adapters/dsh.js +8 -1
  27. package/src/main/adapters/grok.js +4 -1
  28. package/src/main/adapters/hermes.js +8 -0
  29. package/src/main/adapters/kiro.js +2 -1
  30. package/src/main/adapters/managed-mcp.js +15 -2
  31. package/src/main/adapters/omp.js +11 -0
  32. package/src/main/adapters/openclaw.js +6 -0
  33. package/src/main/adapters/opencode.js +5 -1
  34. package/src/main/adapters/pi.js +5 -1
  35. package/src/main/adapters/qoder.js +1 -0
  36. package/src/main/adapters/trae.js +4 -0
  37. package/src/main/adapters/zcode.js +3 -0
  38. package/src/main/host/collaboration.js +59 -11
  39. package/src/main/host/core-session.js +7 -1
  40. package/src/main/host/handoff-checkpoints.js +8 -1
  41. package/src/main/host/integrations.js +164 -26
  42. package/src/main/host/runtime.js +270 -116
  43. package/src/main/host/store.js +32 -11
  44. package/src/main/host/thread-storage.js +73 -0
  45. package/src/main/host/thread-store.js +123 -0
  46. package/src/main/host/verification-gates.js +118 -0
  47. package/src/main/native/protocol.js +209 -83
  48. package/src/main/native/thread-list.js +3 -0
  49. package/src/main/protocol-core/projector.js +20 -3
  50. package/src/main/shared-contracts/event.js +2 -1
  51. package/src/main/shared-contracts/item.js +1 -0
  52. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts +2 -2
  53. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
  54. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  55. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts.map +1 -1
  56. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +11 -0
  57. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  58. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts +29 -4
  59. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts.map +1 -1
  60. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +5 -0
  61. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  62. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +2 -1
  63. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  64. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +1 -1
  65. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -1
  66. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts +2 -1
  67. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts.map +1 -1
  68. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  69. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +5 -3
  70. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  71. package/src/native-ui/renderer-extension/dist/types/settings/storage-page.d.ts +20 -0
  72. package/src/native-ui/renderer-extension/dist/types/settings/storage-page.d.ts.map +1 -0
  73. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  74. package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +8 -88
  75. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +150 -168
  76. package/src/native-ui/renderer-extension/src/renderer-chatgpt-context.ts +5 -1
  77. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +556 -123
  78. package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +36 -3
  79. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +4 -0
  80. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +12 -0
  81. package/src/native-ui/renderer-extension/src/settings/icons.ts +6 -3
  82. package/src/native-ui/renderer-extension/src/settings/integrations-page.ts +1434 -62
  83. package/src/native-ui/renderer-extension/src/settings/localization.ts +8 -4
  84. package/src/native-ui/renderer-extension/src/settings/pages.ts +17 -8
  85. package/src/native-ui/renderer-extension/src/settings/shell.css +103 -8
  86. package/src/native-ui/renderer-extension/src/settings/storage-page.ts +74 -0
  87. package/src/native-ui/renderer-extension/test/renderer-chatgpt-context.test.ts +4 -1
  88. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +2 -0
  89. package/src/native-ui/renderer-extension/test/settings/harness-settings.test.ts +5 -0
  90. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -1
  91. package/src/native-ui/renderer-extension/test/settings/pages.test.ts +50 -4
  92. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +8 -2
@@ -45,10 +45,10 @@ import {
45
45
  type ExternalPermissionModeControlView,
46
46
  } from "./renderer-composer-dom.js";
47
47
  import { rendererHarnessMessages } from "./renderer-harness-localization.js";
48
- import {
49
- codexAccountRouteOverride,
50
- RendererCodexAccountState,
51
- } from "./renderer-codex-account-state.js";
48
+ import {
49
+ codexAccountRouteOverride,
50
+ RendererCodexAccountState,
51
+ } from "./renderer-codex-account-state.js";
52
52
  import {
53
53
  decodeAntigravityTransportModelId,
54
54
  decodeClaudeTransportModelId,
@@ -79,20 +79,19 @@ import {
79
79
  writeNewThreadExternalConfigurationPreference,
80
80
  } from "./renderer-new-thread-preference.js";
81
81
  import { installRendererSidebarAgentIcons } from "./renderer-sidebar-agent-icons.js";
82
- import { installHarnessMentions } from './renderer-harness-mentions.js';
83
- import { installCollabCards } from './renderer-collab-cards.js';
84
- import { installChatGptContextBridge } from './renderer-chatgpt-context.js';
82
+ import { installHarnessMentions } from './renderer-harness-mentions.js';
83
+ import { installCollabCards } from './renderer-collab-cards.js';
85
84
  import {
86
85
  rendererHarnessCommandExecutesDirectly,
87
86
  routeRendererHarnessCommandSelection,
88
87
  } from "./renderer-harness-command-claim.js";
89
88
  import { installRendererSettingsLifecycle } from "./harness-mix-settings.js";
90
89
  import { openRendererThread } from "./renderer-fork-control.js";
91
- import type {
92
- RendererConnectionDiagnostics,
93
- RendererConnectionSnapshot,
94
- } from "./settings/pages.js";
95
- import type { RendererHarnessHandoffRequest } from "./renderer-harness-handoff.js";
90
+ import type {
91
+ RendererConnectionDiagnostics,
92
+ RendererConnectionSnapshot,
93
+ } from "./settings/pages.js";
94
+ import type { RendererHarnessHandoffRequest } from "./renderer-harness-handoff.js";
96
95
 
97
96
  const externalHarnessIds = {
98
97
  pi: harnessIdSchema.parse("pi"),
@@ -530,7 +529,7 @@ export function isOwnershipSubmissionBlocked(status: ComposerOwnershipStatus): b
530
529
  return status === "loading" || status === "error";
531
530
  }
532
531
 
533
- interface MountedComposer {
532
+ interface MountedComposer {
534
533
  composer: Element;
535
534
  composerId: string;
536
535
  control: ComposerAgentControl;
@@ -543,9 +542,9 @@ interface MountedComposer {
543
542
  accountCredits: AccountCreditsSnapshot | null;
544
543
  hostId: string | null;
545
544
  usageRequestGeneration: number;
546
- commandRequestGeneration: number;
547
- harnessSwitching: boolean;
548
- }
545
+ commandRequestGeneration: number;
546
+ harnessSwitching: boolean;
547
+ }
549
548
 
550
549
  interface PendingComposerReplacement {
551
550
  source: MountedComposer;
@@ -715,14 +714,19 @@ export function installRendererBindingProbe(
715
714
  const composer = editor.closest('[data-codex-composer-root]');
716
715
  if (!composer) return { agents: [], sessions: [] };
717
716
  const state = controller.get(composer);
718
- // Stock Codex tasks bypass HostRuntime; do not advertise Host tools there.
719
- if (state.agent === 'codex') return { agents: [], sessions: [] };
720
717
  const client = modelClientForHost(modelControl?.currentHostId?.() ?? 'local');
721
718
  const [agentResult, sessionResult] = await Promise.allSettled([
722
719
  client?.listCollaborationAgents?.() ?? Promise.resolve([]),
723
720
  client?.listHarnessSessions?.({ harnessId: harnessIdSchema.parse('all-harnesses'), query, offset: 0, limit: 12 }) ?? Promise.resolve({ candidates: [], total: 0 }),
724
721
  ]);
725
722
  const agents = agentResult.status === 'fulfilled' ? agentResult.value : [];
723
+ (window as any).__lastMentionDebug = {
724
+ hasClient: !!client,
725
+ agentStatus: agentResult.status,
726
+ agentValue: agentResult.status === 'fulfilled' ? agentResult.value : (agentResult as any).reason?.message,
727
+ sessionStatus: sessionResult.status,
728
+ sessionValue: sessionResult.status === 'fulfilled' ? sessionResult.value : (sessionResult as any).reason?.message,
729
+ };
726
730
  const sessions = sessionResult.status === 'fulfilled' ? sessionResult.value.candidates.map(candidate => {
727
731
  const match = /^\[([^\]]+)\]\s*/.exec(candidate.title ?? '');
728
732
  return { id: candidate.nativeSessionId, title: (candidate.title ?? '未命名会话').replace(/^\[[^\]]+\]\s*/, ''), harnessId: match?.[1] ?? 'codex', cwd: candidate.cwd, running: candidate.running };
@@ -745,11 +749,15 @@ export function installRendererBindingProbe(
745
749
  getUpdateClient: () => modelControl,
746
750
  getAccountClient: () => modelControl,
747
751
  getConnectionDiagnostics: () => connectionDiagnostics,
748
- getIntegrationsClient: () => {
752
+ getStorageClient: () => {
749
753
  const client = modelClientForHost('local');
750
- if (!client?.integrationCatalog || !client.listIntegrations || !client.saveMcp || !client.removeMcp || !client.changeSkill) return null;
751
- return { integrationCatalog: () => client.integrationCatalog!(), listIntegrations: input => client.listIntegrations!(input), saveMcp: input => client.saveMcp!(input), removeMcp: input => client.removeMcp!(input), changeSkill: input => client.changeSkill!(input) };
754
+ return client?.inspectStorage && client.optimizeStorage ? { inspectStorage: () => client.inspectStorage!(), optimizeStorage: () => client.optimizeStorage!() } : null;
752
755
  },
756
+ getIntegrationsClient: () => {
757
+ const client = modelClientForHost('local');
758
+ if (!client?.integrationCatalog || !client.listIntegrations || !client.saveMcp || !client.removeMcp || !client.changeSkill) return null;
759
+ return { integrationCatalog: () => client.integrationCatalog!(), listIntegrations: input => client.listIntegrations!(input), saveMcp: input => client.saveMcp!(input), removeMcp: input => client.removeMcp!(input), changeSkill: input => client.changeSkill!(input) };
760
+ },
753
761
  getSessionImportClient: () => {
754
762
  const client = modelClientForHost("local");
755
763
  const sources = client?.listSessionImportSources;
@@ -824,33 +832,8 @@ export function installRendererBindingProbe(
824
832
  }
825
833
  return state.codexAccounts;
826
834
  };
827
- const composerCodexAccounts = (composer: Element): RendererCodexAccountState | null =>
828
- codexAccountsForHost(mountedByComposer.get(composer)?.hostId ?? null);
829
- const chatGptContext = installChatGptContextBridge({
830
- locale: () => settingsLifecycle.locale,
831
- resolveTarget: (panel) => {
832
- const mounted = [...mountedByComposer.values()].filter(
833
- (candidate) =>
834
- candidate.composer.isConnected &&
835
- candidate.control.root.isConnected &&
836
- !panel.contains(candidate.composer),
837
- ).at(-1);
838
- if (!mounted) return null;
839
- const editor = mounted.composer.querySelector<HTMLElement>(EDITOR_SELECTOR);
840
- if (!editor) return null;
841
- const state = controller.get(mounted.composer);
842
- if (state.agent !== "codex") return { editor, kind: "harness", label: state.agent };
843
- const accounts = composerCodexAccounts(mounted.composer);
844
- const selectedId = accounts?.selection.selectedAccountId;
845
- const account = accounts?.accounts.find((candidate) => candidate.accountId === selectedId);
846
- return {
847
- editor,
848
- kind: "codex",
849
- label: "Codex",
850
- ...(account ? { accountLabel: account.email ?? account.label } : {}),
851
- };
852
- },
853
- });
835
+ const composerCodexAccounts = (composer: Element): RendererCodexAccountState | null =>
836
+ codexAccountsForHost(mountedByComposer.get(composer)?.hostId ?? null);
854
837
  let activeAvailabilityHostId = "local";
855
838
  const activeHarnessAvailabilityState = (): HostHarnessAvailabilityState =>
856
839
  hostHarnessAvailabilityState(activeAvailabilityHostId);
@@ -907,7 +890,7 @@ export function installRendererBindingProbe(
907
890
  );
908
891
  };
909
892
 
910
- const renderMounted = (mounted: MountedComposer): void => {
893
+ const renderMounted = (mounted: MountedComposer): void => {
911
894
  const state = controller.get(mounted.composer);
912
895
  const accounts = composerCodexAccounts(mounted.composer);
913
896
  const selectedCodexAccountId =
@@ -919,10 +902,10 @@ export function installRendererBindingProbe(
919
902
  mounted.control,
920
903
  controller.get(mounted.composer),
921
904
  adapterStatus.state,
922
- accounts?.switching === true ||
923
- controller.isSwitching(mounted.composer) ||
924
- mounted.harnessSwitching ||
925
- mounted.ownershipStatus === "loading",
905
+ accounts?.switching === true ||
906
+ controller.isSwitching(mounted.composer) ||
907
+ mounted.harnessSwitching ||
908
+ mounted.ownershipStatus === "loading",
926
909
  activeHarnessAvailabilityState().availability,
927
910
  mounted.modelView,
928
911
  mounted.permissionModeView,
@@ -1207,7 +1190,7 @@ export function installRendererBindingProbe(
1207
1190
  );
1208
1191
  };
1209
1192
 
1210
- const loadThreadOwnership = async (mounted: MountedComposer): Promise<void> => {
1193
+ const loadThreadOwnership = async (mounted: MountedComposer): Promise<void> => {
1211
1194
  const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1212
1195
  if (!threadId) {
1213
1196
  mounted.ownershipStatus = "not-required";
@@ -1298,70 +1281,70 @@ export function installRendererBindingProbe(
1298
1281
  }
1299
1282
  }
1300
1283
  }
1301
- };
1302
-
1303
- const handoffComposerThread = async (
1304
- mounted: MountedComposer,
1305
- request: RendererHarnessHandoffRequest,
1306
- ): Promise<void> => {
1307
- const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1308
- const current = controller.get(mounted.composer);
1309
- if (
1310
- !threadId ||
1311
- current.phase !== "locked" ||
1312
- current.agent === "codex" ||
1313
- current.agent !== request.from ||
1314
- request.to === current.agent ||
1315
- mounted.harnessSwitching
1316
- ) {
1317
- return;
1318
- }
1319
- const client = modelClientForHostFrom(modelControl, mounted.hostId);
1320
- if (!client?.switchHarness) {
1321
- mounted.control.harnessHandoff.showError(
1322
- settingsLifecycle.locale === "zh-CN" ? "当前任务无法使用 Harness 接力。" : "Harness handoff is unavailable for this task.",
1323
- );
1324
- return;
1325
- }
1326
- mounted.harnessSwitching = true;
1327
- mounted.control.harnessHandoff.setSubmitting(true);
1328
- renderMounted(mounted);
1329
- try {
1330
- await client.switchHarness({
1331
- threadId,
1332
- harnessId: externalHarnessIds[request.to],
1333
- ...(request.note ? { note: request.note } : {}),
1334
- intent: request.intent,
1335
- includes: request.includes,
1336
- });
1337
- if (
1338
- disposed ||
1339
- mountedByComposer.get(mounted.composer) !== mounted ||
1340
- threadIdFromComposerModelTarget(mounted.modelTarget) !== threadId
1341
- ) {
1342
- return;
1343
- }
1344
- mounted.harnessSwitching = false;
1345
- mounted.control.harnessHandoff.setSubmitting(false);
1346
- mounted.control.harnessHandoff.close();
1347
- mounted.modelView = { status: "loading" };
1348
- mounted.permissionModeView = { status: "loading" };
1349
- mounted.threadConfiguration = undefined;
1350
- mounted.usage = null;
1351
- mounted.accountCredits = null;
1352
- await loadThreadOwnership(mounted);
1353
- } catch (error) {
1354
- mounted.control.harnessHandoff.showError(
1355
- error instanceof Error ? error.message : String(error),
1356
- );
1357
- } finally {
1358
- if (mountedByComposer.get(mounted.composer) === mounted) {
1359
- mounted.harnessSwitching = false;
1360
- mounted.control.harnessHandoff.setSubmitting(false);
1361
- renderMounted(mounted);
1362
- }
1363
- }
1364
- };
1284
+ };
1285
+
1286
+ const handoffComposerThread = async (
1287
+ mounted: MountedComposer,
1288
+ request: RendererHarnessHandoffRequest,
1289
+ ): Promise<void> => {
1290
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1291
+ const current = controller.get(mounted.composer);
1292
+ if (
1293
+ !threadId ||
1294
+ current.phase !== "locked" ||
1295
+ current.agent === "codex" ||
1296
+ current.agent !== request.from ||
1297
+ request.to === current.agent ||
1298
+ mounted.harnessSwitching
1299
+ ) {
1300
+ return;
1301
+ }
1302
+ const client = modelClientForHostFrom(modelControl, mounted.hostId);
1303
+ if (!client?.switchHarness) {
1304
+ mounted.control.harnessHandoff.showError(
1305
+ settingsLifecycle.locale === "zh-CN" ? "当前任务无法使用 Harness 接力。" : "Harness handoff is unavailable for this task.",
1306
+ );
1307
+ return;
1308
+ }
1309
+ mounted.harnessSwitching = true;
1310
+ mounted.control.harnessHandoff.setSubmitting(true);
1311
+ renderMounted(mounted);
1312
+ try {
1313
+ await client.switchHarness({
1314
+ threadId,
1315
+ harnessId: externalHarnessIds[request.to],
1316
+ ...(request.note ? { note: request.note } : {}),
1317
+ intent: request.intent,
1318
+ includes: request.includes,
1319
+ });
1320
+ if (
1321
+ disposed ||
1322
+ mountedByComposer.get(mounted.composer) !== mounted ||
1323
+ threadIdFromComposerModelTarget(mounted.modelTarget) !== threadId
1324
+ ) {
1325
+ return;
1326
+ }
1327
+ mounted.harnessSwitching = false;
1328
+ mounted.control.harnessHandoff.setSubmitting(false);
1329
+ mounted.control.harnessHandoff.close();
1330
+ mounted.modelView = { status: "loading" };
1331
+ mounted.permissionModeView = { status: "loading" };
1332
+ mounted.threadConfiguration = undefined;
1333
+ mounted.usage = null;
1334
+ mounted.accountCredits = null;
1335
+ await loadThreadOwnership(mounted);
1336
+ } catch (error) {
1337
+ mounted.control.harnessHandoff.showError(
1338
+ error instanceof Error ? error.message : String(error),
1339
+ );
1340
+ } finally {
1341
+ if (mountedByComposer.get(mounted.composer) === mounted) {
1342
+ mounted.harnessSwitching = false;
1343
+ mounted.control.harnessHandoff.setSubmitting(false);
1344
+ renderMounted(mounted);
1345
+ }
1346
+ }
1347
+ };
1365
1348
 
1366
1349
  const refreshMountedConversationTarget = (mounted: MountedComposer): boolean => {
1367
1350
  const currentTarget = findComposerModelTarget(mounted.composer);
@@ -2199,9 +2182,9 @@ export function installRendererBindingProbe(
2199
2182
  await policy.clear();
2200
2183
  if (!isCurrent() || window.__codexhostDraftPrewarmPolicyV1 !== policy) return;
2201
2184
  if (!policy.selectAccount) throw new Error("Codex Account selection is unavailable");
2202
- policy.selectAccount(codexAccountRouteOverride(accounts.accounts, accountId));
2203
- accounts.overrideAccountId =
2204
- accountId === accounts.selection.activeAccountId ? null : accountId;
2185
+ policy.selectAccount(codexAccountRouteOverride(accounts.accounts, accountId));
2186
+ accounts.overrideAccountId =
2187
+ accountId === accounts.selection.activeAccountId ? null : accountId;
2205
2188
  } catch {
2206
2189
  if (isCurrent()) void loadCodexAccounts();
2207
2190
  } finally {
@@ -2537,17 +2520,17 @@ export function installRendererBindingProbe(
2537
2520
  state.composerId,
2538
2521
  sendButton,
2539
2522
  enabledAgents,
2540
- (agent) => {
2541
- const mounted = mountedByComposer.get(composer);
2542
- if (!composer.isConnected || !mounted) return;
2543
- const current = controller.get(composer);
2544
- if (current.phase === "locked") {
2545
- if (current.agent !== "codex" && agent !== "codex") {
2546
- mounted.control.harnessHandoff.open(current.agent, agent, settingsLifecycle.locale);
2547
- }
2548
- return;
2549
- }
2550
- void switchComposerAgent(mounted, agent);
2523
+ (agent) => {
2524
+ const mounted = mountedByComposer.get(composer);
2525
+ if (!composer.isConnected || !mounted) return;
2526
+ const current = controller.get(composer);
2527
+ if (current.phase === "locked") {
2528
+ if (current.agent !== "codex" && agent !== "codex") {
2529
+ mounted.control.harnessHandoff.open(current.agent, agent, settingsLifecycle.locale);
2530
+ }
2531
+ return;
2532
+ }
2533
+ void switchComposerAgent(mounted, agent);
2551
2534
  },
2552
2535
  openInstallPage,
2553
2536
  async (accountId) => {
@@ -2573,16 +2556,16 @@ export function installRendererBindingProbe(
2573
2556
  if (!composer.isConnected || !mounted) return;
2574
2557
  void selectPermissionMode(mounted, permissionModeId);
2575
2558
  },
2576
- (command) => {
2577
- const mounted = mountedByComposer.get(composer);
2578
- if (mounted) selectCommand(mounted, command);
2579
- },
2580
- (request) => {
2581
- const mounted = mountedByComposer.get(composer);
2582
- if (!composer.isConnected || !mounted) return;
2583
- void handoffComposerThread(mounted, request);
2584
- },
2585
- );
2559
+ (command) => {
2560
+ const mounted = mountedByComposer.get(composer);
2561
+ if (mounted) selectCommand(mounted, command);
2562
+ },
2563
+ (request) => {
2564
+ const mounted = mountedByComposer.get(composer);
2565
+ if (!composer.isConnected || !mounted) return;
2566
+ void handoffComposerThread(mounted, request);
2567
+ },
2568
+ );
2586
2569
  const mounted: MountedComposer = {
2587
2570
  composer,
2588
2571
  composerId: state.composerId,
@@ -2600,9 +2583,9 @@ export function installRendererBindingProbe(
2600
2583
  accountCredits: inherited?.accountCredits ?? null,
2601
2584
  hostId: inherited?.hostId ?? hostId,
2602
2585
  usageRequestGeneration: 0,
2603
- commandRequestGeneration: 0,
2604
- harnessSwitching: false,
2605
- };
2586
+ commandRequestGeneration: 0,
2587
+ harnessSwitching: false,
2588
+ };
2606
2589
  mountedByComposer.set(composer, mounted);
2607
2590
  if (isComposerModelWriteAllowed(modelTarget)) {
2608
2591
  const model = controller.modelForAgent(composer, state.agent);
@@ -2811,10 +2794,10 @@ export function installRendererBindingProbe(
2811
2794
  controller.isSubmissionPending(composer) && current.codexAccountId
2812
2795
  ? current.codexAccountId
2813
2796
  : selection?.selectedAccountId;
2814
- const override = codexAccountRouteOverride(
2815
- composerCodexAccounts(composer)?.accounts ?? [],
2816
- accountId,
2817
- );
2797
+ const override = codexAccountRouteOverride(
2798
+ composerCodexAccounts(composer)?.accounts ?? [],
2799
+ accountId,
2800
+ );
2818
2801
  const policy = window.__codexhostDraftPrewarmPolicyV1;
2819
2802
  if (override !== null && (policy?.hostId !== mounted.hostId || !policy.selectAccount)) {
2820
2803
  return false;
@@ -2841,9 +2824,9 @@ export function installRendererBindingProbe(
2841
2824
  controller.clearPendingSubmission(composer);
2842
2825
  const mounted = mountedByComposer.get(composer);
2843
2826
  if (mounted && isOwnershipSubmissionBlocked(mounted.ownershipStatus)) return;
2844
- // Draft editing must remain available while the native connection recovers.
2845
- // Submission still goes through prepareComposer and fails closed.
2846
- if (!controller.isSwitching(composer)) applyComposerAgent(composer);
2827
+ // Draft editing must remain available while the native connection recovers.
2828
+ // Submission still goes through prepareComposer and fails closed.
2829
+ if (!controller.isSwitching(composer)) applyComposerAgent(composer);
2847
2830
  };
2848
2831
  const onSubmit = (event: Event): void => {
2849
2832
  const element = eventElement(event.target);
@@ -2851,9 +2834,9 @@ export function installRendererBindingProbe(
2851
2834
  const composer = candidate && isMountedComposer(candidate) ? candidate : null;
2852
2835
  if (!composer) return;
2853
2836
  const prepared = prepareComposer(composer);
2854
- if (prepared === null) return;
2855
- if (!prepared) {
2856
- blockEvent(event);
2837
+ if (prepared === null) return;
2838
+ if (!prepared) {
2839
+ blockEvent(event);
2857
2840
  return;
2858
2841
  }
2859
2842
  notifySubmission(composer, "submit");
@@ -2862,20 +2845,20 @@ export function installRendererBindingProbe(
2862
2845
  const composer = isComposerInputIntent(event) ? composerForTarget(event.target) : null;
2863
2846
  const mounted = composer ? mountedByComposer.get(composer) : undefined;
2864
2847
  if (
2865
- composer &&
2866
- (composerCodexAccounts(composer)?.switching ||
2867
- controller.isSwitching(composer) ||
2868
- mounted?.harnessSwitching)
2869
- ) {
2870
- if (isComposerSubmissionKey(event)) blockEvent(event);
2848
+ composer &&
2849
+ (composerCodexAccounts(composer)?.switching ||
2850
+ controller.isSwitching(composer) ||
2851
+ mounted?.harnessSwitching)
2852
+ ) {
2853
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2871
2854
  return;
2872
2855
  }
2873
2856
  if (composer && mounted && isOwnershipSubmissionBlocked(mounted.ownershipStatus)) {
2874
2857
  if (isComposerSubmissionKey(event)) blockEvent(event);
2875
2858
  return;
2876
2859
  }
2877
- if (composer && !applyComposerAgent(composer)) {
2878
- if (isComposerSubmissionKey(event)) blockEvent(event);
2860
+ if (composer && !applyComposerAgent(composer)) {
2861
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2879
2862
  return;
2880
2863
  }
2881
2864
  if (!isComposerSubmissionKey(event) || !composer) return;
@@ -3084,11 +3067,10 @@ export function installRendererBindingProbe(
3084
3067
  applyAdapterAgent = null;
3085
3068
  modelControl = null;
3086
3069
  mutationObserver.disconnect();
3087
- sidebarAgentIcons.dispose();
3088
- harnessMentions.dispose();
3089
- collabCards.dispose();
3090
- chatGptContext.dispose();
3091
- settingsLifecycle.dispose();
3070
+ sidebarAgentIcons.dispose();
3071
+ harnessMentions.dispose();
3072
+ collabCards.dispose();
3073
+ settingsLifecycle.dispose();
3092
3074
  document.removeEventListener("beforeinput", onBeforeInput, true);
3093
3075
  document.removeEventListener("submit", onSubmit, true);
3094
3076
  document.removeEventListener("keydown", onKeyDown, true);
@@ -44,7 +44,11 @@ export function buildChatGptContextDraft(capture: ChatGptContextCapture): string
44
44
  return `${header.join("\n")}\n${body.length > available ? body.slice(body.length - available) : body}${suffix}`;
45
45
  }
46
46
  function elementDescription(element: Element): string { return [element.getAttribute("placeholder"), element.getAttribute("aria-label"), element.getAttribute("title"), element.getAttribute("data-testid")].filter((value): value is string => typeof value === "string").join(" "); }
47
- export function isChatGptQuickChatEditor(element: Element): boolean { return element.matches('textarea, [contenteditable="true"], [role="textbox"]') && CHATGPT_EDITOR_PATTERN.test(elementDescription(element)); }
47
+ export function isChatGptQuickChatEditor(element: Element): boolean {
48
+ const candidate = element as Element & { matches?: unknown; getAttribute?: unknown };
49
+ if (typeof candidate.matches !== "function" || typeof candidate.getAttribute !== "function") return false;
50
+ return candidate.matches('textarea, [contenteditable="true"], [role="textbox"]') && CHATGPT_EDITOR_PATTERN.test(elementDescription(candidate));
51
+ }
48
52
  function directText(element: Element): string { return compactText([...element.childNodes].filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => node.textContent ?? "").join(" ")); }
49
53
  function titleElementWithin(root: Element): Element | null { return [...root.querySelectorAll("h1,h2,h3,h4,[role=heading],span,div")].find((element) => CHAT_TITLE_PATTERN.test(directText(element))) ?? null; }
50
54
  export function findChatGptQuickChatPanel(editor: Element): Element | null {