@harness-mix/cli 0.1.5 → 0.1.6

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 (60) hide show
  1. package/README.md +1 -1
  2. package/docs/harness-management.md +4 -4
  3. package/docs/multi-agent-collaboration.md +9 -9
  4. package/output/native-build/renderer-extension.js +1959 -435
  5. package/package.json +9 -8
  6. package/scripts/collaboration-test.cjs +8 -3
  7. package/scripts/collaboration-ui-smoke.cjs +40 -26
  8. package/scripts/integrations-test.cjs +63 -4
  9. package/scripts/integrations-ui-smoke.cjs +77 -16
  10. package/scripts/native-protocol-test.cjs +102 -1
  11. package/scripts/native-sidebar-test.cjs +7 -0
  12. package/scripts/native-skill-roots-test.cjs +90 -0
  13. package/scripts/test-live-mentions.cjs +152 -0
  14. package/src/main/adapters/antigravity.js +20 -17
  15. package/src/main/adapters/claude.js +41 -8
  16. package/src/main/adapters/codebuddy.js +1 -0
  17. package/src/main/adapters/codex.js +20 -2
  18. package/src/main/adapters/cursor.js +5 -1
  19. package/src/main/adapters/dsh.js +8 -1
  20. package/src/main/adapters/grok.js +4 -1
  21. package/src/main/adapters/hermes.js +8 -0
  22. package/src/main/adapters/kiro.js +2 -1
  23. package/src/main/adapters/managed-mcp.js +15 -2
  24. package/src/main/adapters/omp.js +11 -0
  25. package/src/main/adapters/openclaw.js +6 -0
  26. package/src/main/adapters/opencode.js +5 -1
  27. package/src/main/adapters/pi.js +5 -1
  28. package/src/main/adapters/qoder.js +1 -0
  29. package/src/main/adapters/trae.js +4 -0
  30. package/src/main/adapters/zcode.js +3 -0
  31. package/src/main/host/collaboration.js +57 -11
  32. package/src/main/host/integrations.js +164 -26
  33. package/src/main/host/runtime.js +126 -86
  34. package/src/main/native/protocol.js +189 -78
  35. package/src/main/native/thread-list.js +3 -0
  36. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
  37. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  38. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts.map +1 -1
  39. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +11 -0
  40. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  41. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts +29 -4
  42. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts.map +1 -1
  43. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts +2 -1
  44. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts.map +1 -1
  45. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  46. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +1 -1
  47. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  48. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  49. package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +3 -2
  50. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +149 -171
  51. package/src/native-ui/renderer-extension/src/renderer-chatgpt-context.ts +5 -1
  52. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +556 -123
  53. package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +36 -3
  54. package/src/native-ui/renderer-extension/src/settings/integrations-page.ts +1434 -62
  55. package/src/native-ui/renderer-extension/src/settings/localization.ts +4 -2
  56. package/src/native-ui/renderer-extension/src/settings/pages.ts +5 -3
  57. package/src/native-ui/renderer-extension/src/settings/shell.css +96 -6
  58. package/src/native-ui/renderer-extension/test/renderer-chatgpt-context.test.ts +4 -1
  59. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -1
  60. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +5 -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 };
@@ -744,12 +748,12 @@ export function installRendererBindingProbe(
744
748
  const settingsLifecycle = installRendererSettingsLifecycle(window, {
745
749
  getUpdateClient: () => modelControl,
746
750
  getAccountClient: () => modelControl,
747
- getConnectionDiagnostics: () => connectionDiagnostics,
748
- getIntegrationsClient: () => {
749
- 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) };
752
- },
751
+ getConnectionDiagnostics: () => connectionDiagnostics,
752
+ getIntegrationsClient: () => {
753
+ const client = modelClientForHost('local');
754
+ if (!client?.integrationCatalog || !client.listIntegrations || !client.saveMcp || !client.removeMcp || !client.changeSkill) return null;
755
+ return { integrationCatalog: () => client.integrationCatalog!(), listIntegrations: input => client.listIntegrations!(input), saveMcp: input => client.saveMcp!(input), removeMcp: input => client.removeMcp!(input), changeSkill: input => client.changeSkill!(input) };
756
+ },
753
757
  getSessionImportClient: () => {
754
758
  const client = modelClientForHost("local");
755
759
  const sources = client?.listSessionImportSources;
@@ -824,33 +828,8 @@ export function installRendererBindingProbe(
824
828
  }
825
829
  return state.codexAccounts;
826
830
  };
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
- });
831
+ const composerCodexAccounts = (composer: Element): RendererCodexAccountState | null =>
832
+ codexAccountsForHost(mountedByComposer.get(composer)?.hostId ?? null);
854
833
  let activeAvailabilityHostId = "local";
855
834
  const activeHarnessAvailabilityState = (): HostHarnessAvailabilityState =>
856
835
  hostHarnessAvailabilityState(activeAvailabilityHostId);
@@ -907,7 +886,7 @@ export function installRendererBindingProbe(
907
886
  );
908
887
  };
909
888
 
910
- const renderMounted = (mounted: MountedComposer): void => {
889
+ const renderMounted = (mounted: MountedComposer): void => {
911
890
  const state = controller.get(mounted.composer);
912
891
  const accounts = composerCodexAccounts(mounted.composer);
913
892
  const selectedCodexAccountId =
@@ -919,10 +898,10 @@ export function installRendererBindingProbe(
919
898
  mounted.control,
920
899
  controller.get(mounted.composer),
921
900
  adapterStatus.state,
922
- accounts?.switching === true ||
923
- controller.isSwitching(mounted.composer) ||
924
- mounted.harnessSwitching ||
925
- mounted.ownershipStatus === "loading",
901
+ accounts?.switching === true ||
902
+ controller.isSwitching(mounted.composer) ||
903
+ mounted.harnessSwitching ||
904
+ mounted.ownershipStatus === "loading",
926
905
  activeHarnessAvailabilityState().availability,
927
906
  mounted.modelView,
928
907
  mounted.permissionModeView,
@@ -1207,7 +1186,7 @@ export function installRendererBindingProbe(
1207
1186
  );
1208
1187
  };
1209
1188
 
1210
- const loadThreadOwnership = async (mounted: MountedComposer): Promise<void> => {
1189
+ const loadThreadOwnership = async (mounted: MountedComposer): Promise<void> => {
1211
1190
  const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1212
1191
  if (!threadId) {
1213
1192
  mounted.ownershipStatus = "not-required";
@@ -1298,70 +1277,70 @@ export function installRendererBindingProbe(
1298
1277
  }
1299
1278
  }
1300
1279
  }
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
- };
1280
+ };
1281
+
1282
+ const handoffComposerThread = async (
1283
+ mounted: MountedComposer,
1284
+ request: RendererHarnessHandoffRequest,
1285
+ ): Promise<void> => {
1286
+ const threadId = threadIdFromComposerModelTarget(mounted.modelTarget);
1287
+ const current = controller.get(mounted.composer);
1288
+ if (
1289
+ !threadId ||
1290
+ current.phase !== "locked" ||
1291
+ current.agent === "codex" ||
1292
+ current.agent !== request.from ||
1293
+ request.to === current.agent ||
1294
+ mounted.harnessSwitching
1295
+ ) {
1296
+ return;
1297
+ }
1298
+ const client = modelClientForHostFrom(modelControl, mounted.hostId);
1299
+ if (!client?.switchHarness) {
1300
+ mounted.control.harnessHandoff.showError(
1301
+ settingsLifecycle.locale === "zh-CN" ? "当前任务无法使用 Harness 接力。" : "Harness handoff is unavailable for this task.",
1302
+ );
1303
+ return;
1304
+ }
1305
+ mounted.harnessSwitching = true;
1306
+ mounted.control.harnessHandoff.setSubmitting(true);
1307
+ renderMounted(mounted);
1308
+ try {
1309
+ await client.switchHarness({
1310
+ threadId,
1311
+ harnessId: externalHarnessIds[request.to],
1312
+ ...(request.note ? { note: request.note } : {}),
1313
+ intent: request.intent,
1314
+ includes: request.includes,
1315
+ });
1316
+ if (
1317
+ disposed ||
1318
+ mountedByComposer.get(mounted.composer) !== mounted ||
1319
+ threadIdFromComposerModelTarget(mounted.modelTarget) !== threadId
1320
+ ) {
1321
+ return;
1322
+ }
1323
+ mounted.harnessSwitching = false;
1324
+ mounted.control.harnessHandoff.setSubmitting(false);
1325
+ mounted.control.harnessHandoff.close();
1326
+ mounted.modelView = { status: "loading" };
1327
+ mounted.permissionModeView = { status: "loading" };
1328
+ mounted.threadConfiguration = undefined;
1329
+ mounted.usage = null;
1330
+ mounted.accountCredits = null;
1331
+ await loadThreadOwnership(mounted);
1332
+ } catch (error) {
1333
+ mounted.control.harnessHandoff.showError(
1334
+ error instanceof Error ? error.message : String(error),
1335
+ );
1336
+ } finally {
1337
+ if (mountedByComposer.get(mounted.composer) === mounted) {
1338
+ mounted.harnessSwitching = false;
1339
+ mounted.control.harnessHandoff.setSubmitting(false);
1340
+ renderMounted(mounted);
1341
+ }
1342
+ }
1343
+ };
1365
1344
 
1366
1345
  const refreshMountedConversationTarget = (mounted: MountedComposer): boolean => {
1367
1346
  const currentTarget = findComposerModelTarget(mounted.composer);
@@ -2199,9 +2178,9 @@ export function installRendererBindingProbe(
2199
2178
  await policy.clear();
2200
2179
  if (!isCurrent() || window.__codexhostDraftPrewarmPolicyV1 !== policy) return;
2201
2180
  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;
2181
+ policy.selectAccount(codexAccountRouteOverride(accounts.accounts, accountId));
2182
+ accounts.overrideAccountId =
2183
+ accountId === accounts.selection.activeAccountId ? null : accountId;
2205
2184
  } catch {
2206
2185
  if (isCurrent()) void loadCodexAccounts();
2207
2186
  } finally {
@@ -2537,17 +2516,17 @@ export function installRendererBindingProbe(
2537
2516
  state.composerId,
2538
2517
  sendButton,
2539
2518
  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);
2519
+ (agent) => {
2520
+ const mounted = mountedByComposer.get(composer);
2521
+ if (!composer.isConnected || !mounted) return;
2522
+ const current = controller.get(composer);
2523
+ if (current.phase === "locked") {
2524
+ if (current.agent !== "codex" && agent !== "codex") {
2525
+ mounted.control.harnessHandoff.open(current.agent, agent, settingsLifecycle.locale);
2526
+ }
2527
+ return;
2528
+ }
2529
+ void switchComposerAgent(mounted, agent);
2551
2530
  },
2552
2531
  openInstallPage,
2553
2532
  async (accountId) => {
@@ -2573,16 +2552,16 @@ export function installRendererBindingProbe(
2573
2552
  if (!composer.isConnected || !mounted) return;
2574
2553
  void selectPermissionMode(mounted, permissionModeId);
2575
2554
  },
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
- );
2555
+ (command) => {
2556
+ const mounted = mountedByComposer.get(composer);
2557
+ if (mounted) selectCommand(mounted, command);
2558
+ },
2559
+ (request) => {
2560
+ const mounted = mountedByComposer.get(composer);
2561
+ if (!composer.isConnected || !mounted) return;
2562
+ void handoffComposerThread(mounted, request);
2563
+ },
2564
+ );
2586
2565
  const mounted: MountedComposer = {
2587
2566
  composer,
2588
2567
  composerId: state.composerId,
@@ -2600,9 +2579,9 @@ export function installRendererBindingProbe(
2600
2579
  accountCredits: inherited?.accountCredits ?? null,
2601
2580
  hostId: inherited?.hostId ?? hostId,
2602
2581
  usageRequestGeneration: 0,
2603
- commandRequestGeneration: 0,
2604
- harnessSwitching: false,
2605
- };
2582
+ commandRequestGeneration: 0,
2583
+ harnessSwitching: false,
2584
+ };
2606
2585
  mountedByComposer.set(composer, mounted);
2607
2586
  if (isComposerModelWriteAllowed(modelTarget)) {
2608
2587
  const model = controller.modelForAgent(composer, state.agent);
@@ -2811,10 +2790,10 @@ export function installRendererBindingProbe(
2811
2790
  controller.isSubmissionPending(composer) && current.codexAccountId
2812
2791
  ? current.codexAccountId
2813
2792
  : selection?.selectedAccountId;
2814
- const override = codexAccountRouteOverride(
2815
- composerCodexAccounts(composer)?.accounts ?? [],
2816
- accountId,
2817
- );
2793
+ const override = codexAccountRouteOverride(
2794
+ composerCodexAccounts(composer)?.accounts ?? [],
2795
+ accountId,
2796
+ );
2818
2797
  const policy = window.__codexhostDraftPrewarmPolicyV1;
2819
2798
  if (override !== null && (policy?.hostId !== mounted.hostId || !policy.selectAccount)) {
2820
2799
  return false;
@@ -2841,9 +2820,9 @@ export function installRendererBindingProbe(
2841
2820
  controller.clearPendingSubmission(composer);
2842
2821
  const mounted = mountedByComposer.get(composer);
2843
2822
  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);
2823
+ // Draft editing must remain available while the native connection recovers.
2824
+ // Submission still goes through prepareComposer and fails closed.
2825
+ if (!controller.isSwitching(composer)) applyComposerAgent(composer);
2847
2826
  };
2848
2827
  const onSubmit = (event: Event): void => {
2849
2828
  const element = eventElement(event.target);
@@ -2851,9 +2830,9 @@ export function installRendererBindingProbe(
2851
2830
  const composer = candidate && isMountedComposer(candidate) ? candidate : null;
2852
2831
  if (!composer) return;
2853
2832
  const prepared = prepareComposer(composer);
2854
- if (prepared === null) return;
2855
- if (!prepared) {
2856
- blockEvent(event);
2833
+ if (prepared === null) return;
2834
+ if (!prepared) {
2835
+ blockEvent(event);
2857
2836
  return;
2858
2837
  }
2859
2838
  notifySubmission(composer, "submit");
@@ -2862,20 +2841,20 @@ export function installRendererBindingProbe(
2862
2841
  const composer = isComposerInputIntent(event) ? composerForTarget(event.target) : null;
2863
2842
  const mounted = composer ? mountedByComposer.get(composer) : undefined;
2864
2843
  if (
2865
- composer &&
2866
- (composerCodexAccounts(composer)?.switching ||
2867
- controller.isSwitching(composer) ||
2868
- mounted?.harnessSwitching)
2869
- ) {
2870
- if (isComposerSubmissionKey(event)) blockEvent(event);
2844
+ composer &&
2845
+ (composerCodexAccounts(composer)?.switching ||
2846
+ controller.isSwitching(composer) ||
2847
+ mounted?.harnessSwitching)
2848
+ ) {
2849
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2871
2850
  return;
2872
2851
  }
2873
2852
  if (composer && mounted && isOwnershipSubmissionBlocked(mounted.ownershipStatus)) {
2874
2853
  if (isComposerSubmissionKey(event)) blockEvent(event);
2875
2854
  return;
2876
2855
  }
2877
- if (composer && !applyComposerAgent(composer)) {
2878
- if (isComposerSubmissionKey(event)) blockEvent(event);
2856
+ if (composer && !applyComposerAgent(composer)) {
2857
+ if (isComposerSubmissionKey(event)) blockEvent(event);
2879
2858
  return;
2880
2859
  }
2881
2860
  if (!isComposerSubmissionKey(event) || !composer) return;
@@ -3084,11 +3063,10 @@ export function installRendererBindingProbe(
3084
3063
  applyAdapterAgent = null;
3085
3064
  modelControl = null;
3086
3065
  mutationObserver.disconnect();
3087
- sidebarAgentIcons.dispose();
3088
- harnessMentions.dispose();
3089
- collabCards.dispose();
3090
- chatGptContext.dispose();
3091
- settingsLifecycle.dispose();
3066
+ sidebarAgentIcons.dispose();
3067
+ harnessMentions.dispose();
3068
+ collabCards.dispose();
3069
+ settingsLifecycle.dispose();
3092
3070
  document.removeEventListener("beforeinput", onBeforeInput, true);
3093
3071
  document.removeEventListener("submit", onSubmit, true);
3094
3072
  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 {