@harness-mix/cli 0.1.4 → 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 (146) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +26 -96
  3. package/docs/chatgpt-web-sidebar.md +7 -1
  4. package/docs/cross-platform.md +15 -0
  5. package/docs/harness-handoff-design.md +377 -0
  6. package/docs/harness-management.md +28 -1
  7. package/docs/multi-agent-collaboration.md +9 -9
  8. package/docs/native-codex.md +21 -10
  9. package/output/native-build/harness-mix-shim.exe +0 -0
  10. package/output/native-build/renderer-extension.js +2596 -135
  11. package/package.json +20 -10
  12. package/scripts/chatgpt-context-ui-smoke.cjs +16 -0
  13. package/scripts/codex-accounts-test.cjs +81 -0
  14. package/scripts/codex-accounts-ui-smoke.cjs +70 -0
  15. package/scripts/collaboration-test.cjs +8 -3
  16. package/scripts/collaboration-ui-smoke.cjs +40 -26
  17. package/scripts/e2e-integrations.cjs +52 -0
  18. package/scripts/e2e-native-host.cjs +39 -0
  19. package/scripts/handoff-checkpoint-test.cjs +83 -0
  20. package/scripts/install-native.cjs +4 -1
  21. package/scripts/integrations-test.cjs +123 -0
  22. package/scripts/integrations-ui-smoke.cjs +124 -0
  23. package/scripts/native-protocol-test.cjs +180 -5
  24. package/scripts/native-secret-test.cjs +2 -2
  25. package/scripts/native-shim-test.cjs +20 -4
  26. package/scripts/native-sidebar-test.cjs +7 -0
  27. package/scripts/native-skill-roots-test.cjs +90 -0
  28. package/scripts/native-ui-smoke.cjs +58 -2
  29. package/scripts/publish-native-package.cjs +8 -5
  30. package/scripts/support/integrations-mcp-fixture.cjs +15 -0
  31. package/scripts/switch-harness-test.cjs +27 -1
  32. package/scripts/test-live-mentions.cjs +152 -0
  33. package/src/main/adapters/acp.js +3 -3
  34. package/src/main/adapters/antigravity.js +17 -17
  35. package/src/main/adapters/claude.js +50 -10
  36. package/src/main/adapters/codebuddy.js +1 -0
  37. package/src/main/adapters/codex-app-server.js +20 -13
  38. package/src/main/adapters/codex.js +25 -5
  39. package/src/main/adapters/cursor.js +5 -1
  40. package/src/main/adapters/dsh.js +9 -1
  41. package/src/main/adapters/grok.js +7 -3
  42. package/src/main/adapters/hermes.js +8 -0
  43. package/src/main/adapters/kiro.js +2 -1
  44. package/src/main/adapters/managed-mcp.js +25 -0
  45. package/src/main/adapters/native-acp.js +4 -4
  46. package/src/main/adapters/omp.js +11 -0
  47. package/src/main/adapters/openclaw.js +6 -0
  48. package/src/main/adapters/opencode.js +13 -3
  49. package/src/main/adapters/pi.js +5 -0
  50. package/src/main/adapters/qoder.js +1 -0
  51. package/src/main/adapters/trae.js +4 -0
  52. package/src/main/adapters/zcode.js +3 -0
  53. package/src/main/harness-adapter/manifest.js +17 -2
  54. package/src/main/host/collaboration.js +57 -11
  55. package/src/main/host/handoff-access.js +53 -0
  56. package/src/main/host/handoff-checkpoints.js +206 -0
  57. package/src/main/host/handoff-mcp.cjs +38 -0
  58. package/src/main/host/handoff-tools.js +13 -0
  59. package/src/main/host/handoff.js +28 -6
  60. package/src/main/host/integrations.js +339 -0
  61. package/src/main/host/runtime.js +95 -15
  62. package/src/main/host/store.js +4 -4
  63. package/src/main/native/codex-accounts.js +260 -0
  64. package/src/main/native/host.js +1 -1
  65. package/src/main/native/launcher.js +2 -2
  66. package/src/main/native/protocol.js +176 -14
  67. package/src/main/native/redact.js +1 -1
  68. package/src/main/native/rs/crates/shim/src/main.rs +155 -12
  69. package/src/main/native/thread-list.js +3 -0
  70. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  71. package/src/native-ui/renderer-extension/dist/types/harness-mix-settings.d.ts.map +1 -1
  72. package/src/native-ui/renderer-extension/dist/types/index.d.ts +4 -0
  73. package/src/native-ui/renderer-extension/dist/types/index.d.ts.map +1 -1
  74. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts +2 -0
  75. package/src/native-ui/renderer-extension/dist/types/renderer-agent-picker.d.ts.map +1 -1
  76. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  77. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts +36 -0
  78. package/src/native-ui/renderer-extension/dist/types/renderer-chatgpt-context.d.ts.map +1 -0
  79. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts +1 -0
  80. package/src/native-ui/renderer-extension/dist/types/renderer-codex-account-state.d.ts.map +1 -1
  81. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts +3 -1
  82. package/src/native-ui/renderer-extension/dist/types/renderer-composer-dom.d.ts.map +1 -1
  83. package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts +53 -0
  84. package/src/native-ui/renderer-extension/dist/types/renderer-harness-handoff.d.ts.map +1 -0
  85. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts +11 -0
  86. package/src/native-ui/renderer-extension/dist/types/renderer-harness-mentions.d.ts.map +1 -1
  87. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts +105 -0
  88. package/src/native-ui/renderer-extension/dist/types/renderer-integrations-client.d.ts.map +1 -0
  89. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +7 -1
  90. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  91. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +1 -0
  92. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  93. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts +2 -0
  94. package/src/native-ui/renderer-extension/dist/types/settings/accounts-list.d.ts.map +1 -1
  95. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts +1 -0
  96. package/src/native-ui/renderer-extension/dist/types/settings/accounts-page.d.ts.map +1 -1
  97. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts +6 -0
  98. package/src/native-ui/renderer-extension/dist/types/settings/integrations-page.d.ts.map +1 -0
  99. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts +5 -0
  100. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  101. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +8 -4
  102. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  103. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  104. package/src/native-ui/renderer-extension/src/harness-mix-settings.ts +4 -1
  105. package/src/native-ui/renderer-extension/src/index.ts +28 -0
  106. package/src/native-ui/renderer-extension/src/renderer-agent-icon.ts +4 -4
  107. package/src/native-ui/renderer-extension/src/renderer-agent-picker.ts +46 -6
  108. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +115 -18
  109. package/src/native-ui/renderer-extension/src/renderer-chatgpt-context.ts +87 -0
  110. package/src/native-ui/renderer-extension/src/renderer-codex-account-state.ts +10 -0
  111. package/src/native-ui/renderer-extension/src/renderer-composer-dom.ts +10 -0
  112. package/src/native-ui/renderer-extension/src/renderer-harness-handoff.ts +330 -0
  113. package/src/native-ui/renderer-extension/src/renderer-harness-mentions.ts +556 -123
  114. package/src/native-ui/renderer-extension/src/renderer-integrations-client.ts +59 -0
  115. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +17 -1
  116. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +2 -0
  117. package/src/native-ui/renderer-extension/src/settings/accounts-list.ts +16 -1
  118. package/src/native-ui/renderer-extension/src/settings/accounts-page.ts +32 -3
  119. package/src/native-ui/renderer-extension/src/settings/integrations-page.ts +1474 -0
  120. package/src/native-ui/renderer-extension/src/settings/localization.ts +32 -13
  121. package/src/native-ui/renderer-extension/src/settings/pages.ts +49 -18
  122. package/src/native-ui/renderer-extension/src/settings/shell.css +103 -0
  123. package/src/native-ui/renderer-extension/test/renderer-agent-picker.test.ts +44 -13
  124. package/src/native-ui/renderer-extension/test/renderer-chatgpt-context.test.ts +21 -0
  125. package/src/native-ui/renderer-extension/test/renderer-codex-account-state.test.ts +13 -1
  126. package/src/native-ui/renderer-extension/test/renderer-harness-handoff.test.ts +20 -0
  127. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +9 -2
  128. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -1
  129. package/src/native-ui/renderer-extension/test/settings/pages.test.ts +77 -8
  130. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +6 -0
  131. package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts +19 -4
  132. package/src/native-ui/shared-contracts/dist/codex-accounts.d.ts.map +1 -1
  133. package/src/native-ui/shared-contracts/dist/codex-accounts.js +2 -0
  134. package/src/native-ui/shared-contracts/dist/codex-accounts.js.map +1 -1
  135. package/src/native-ui/shared-contracts/dist/index.d.ts +2 -2
  136. package/src/native-ui/shared-contracts/dist/index.d.ts.map +1 -1
  137. package/src/native-ui/shared-contracts/dist/index.js +1 -1
  138. package/src/native-ui/shared-contracts/dist/index.js.map +1 -1
  139. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts +29 -0
  140. package/src/native-ui/shared-contracts/dist/thread-harness-switch.d.ts.map +1 -1
  141. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js +13 -0
  142. package/src/native-ui/shared-contracts/dist/thread-harness-switch.js.map +1 -1
  143. package/src/native-ui/shared-contracts/dist/tsconfig.tsbuildinfo +1 -1
  144. package/src/native-ui/shared-contracts/src/codex-accounts.ts +2 -0
  145. package/src/native-ui/shared-contracts/src/index.ts +8 -1
  146. package/src/native-ui/shared-contracts/src/thread-harness-switch.ts +17 -0
@@ -0,0 +1,330 @@
1
+ import type { ExternalRendererAgent } from "./agent-selection-state.js";
2
+ import type { HarnessHandoffIncludes, HarnessHandoffIntent } from "@codexhost/shared-contracts";
3
+ import { createRendererAgentIcon, RENDERER_AGENT_LABELS } from "./renderer-agent-icon.js";
4
+ import type { RendererSettingsLocale } from "./settings/localization.js";
5
+
6
+ export const HARNESS_HANDOFF_NOTE_MAX_LENGTH = 2000;
7
+
8
+ export interface RendererHarnessHandoffRequest {
9
+ from: ExternalRendererAgent;
10
+ to: ExternalRendererAgent;
11
+ note: string;
12
+ intent: HarnessHandoffIntent;
13
+ includes: HarnessHandoffIncludes;
14
+ }
15
+
16
+ export interface RendererHarnessHandoffControl {
17
+ dialog: HTMLDialogElement;
18
+ note: HTMLTextAreaElement;
19
+ intent: HTMLSelectElement;
20
+ includeInputs: Record<keyof HarnessHandoffIncludes, HTMLInputElement>;
21
+ confirm: HTMLButtonElement;
22
+ cancel: HTMLButtonElement;
23
+ error: HTMLElement;
24
+ open(from: ExternalRendererAgent, to: ExternalRendererAgent, locale: RendererSettingsLocale): void;
25
+ close(): void;
26
+ setSubmitting(submitting: boolean): void;
27
+ showError(message: string): void;
28
+ dispose(): void;
29
+ }
30
+
31
+ export function rendererHarnessHandoffMessages(locale: RendererSettingsLocale) {
32
+ return locale === "zh-CN"
33
+ ? {
34
+ title: "接力当前任务",
35
+ description: "保留此任务窗口、对话记录和文件现场,只更换继续执行的 Harness。",
36
+ noteLabel: "交接说明(可选)",
37
+ intentLabel: "接力方式",
38
+ intentOptions: { continue: "继续执行", "execute-plan": "执行上一方案", review: "独立审查", reanalyze: "重新分析" },
39
+ includeLabel: "将要交接",
40
+ includeOptions: { conversation: "近期对话", plan: "当前计划", evidence: "测试与工具证据", files: "文件变更", unresolved: "未解决问题" },
41
+ notePlaceholder: "例如:按上面的方案继续实现,先核对当前文件状态和未完成步骤",
42
+ context: "下一条消息会自动携带近期对话、最新方案、计划和变更文件。",
43
+ running: "若当前回合仍在运行,请先停止或等待完成。",
44
+ cancel: "取消",
45
+ confirm: "确认接力",
46
+ submitting: "正在切换…",
47
+ }
48
+ : {
49
+ title: "Hand off this task",
50
+ description: "Keep this task, transcript, and working tree; only change the Harness that continues it.",
51
+ noteLabel: "Handoff note (optional)",
52
+ intentLabel: "Handoff mode",
53
+ intentOptions: { continue: "Continue", "execute-plan": "Execute prior plan", review: "Independent review", reanalyze: "Reanalyze" },
54
+ includeLabel: "Include",
55
+ includeOptions: { conversation: "Recent conversation", plan: "Current plan", evidence: "Test and tool evidence", files: "File changes", unresolved: "Open issues" },
56
+ notePlaceholder: "For example: implement the plan above; verify the working tree and remaining steps first",
57
+ context: "Your next message will include recent context, the latest solution, plan, and changed files.",
58
+ running: "If a turn is still running, stop it or wait for it to finish first.",
59
+ cancel: "Cancel",
60
+ confirm: "Hand off",
61
+ submitting: "Switching…",
62
+ };
63
+ }
64
+
65
+ function setButtonChrome(button: HTMLButtonElement, primary = false): void {
66
+ button.style.height = "34px";
67
+ button.style.padding = "0 14px";
68
+ button.style.border = primary ? "1px solid color-mix(in srgb, #5b8cff 70%, CanvasText 30%)" : "1px solid color-mix(in srgb, CanvasText 18%, transparent)";
69
+ button.style.borderRadius = "7px";
70
+ button.style.background = primary ? "#4f7ff0" : "color-mix(in srgb, Canvas 94%, CanvasText 6%)";
71
+ button.style.color = primary ? "white" : "CanvasText";
72
+ button.style.font = "500 13px/1 system-ui, sans-serif";
73
+ button.style.cursor = "pointer";
74
+ }
75
+
76
+ export function mountRendererHarnessHandoff(
77
+ composerId: string,
78
+ onConfirm: (request: RendererHarnessHandoffRequest) => void,
79
+ ): RendererHarnessHandoffControl {
80
+ const dialog = document.createElement("dialog");
81
+ dialog.id = `${composerId}-harness-handoff`;
82
+ dialog.setAttribute("aria-modal", "true");
83
+ dialog.style.width = "min(440px, calc(100vw - 32px))";
84
+ dialog.style.boxSizing = "border-box";
85
+ dialog.style.padding = "0";
86
+ dialog.style.border = "1px solid color-mix(in srgb, CanvasText 18%, transparent)";
87
+ dialog.style.borderRadius = "14px";
88
+ dialog.style.background = "Canvas";
89
+ dialog.style.color = "CanvasText";
90
+ dialog.style.boxShadow = "0 24px 70px rgba(0, 0, 0, 0.42)";
91
+ dialog.style.font = "13px/1.45 system-ui, sans-serif";
92
+ dialog.style.zIndex = "2147483647";
93
+
94
+ const frame = document.createElement("div");
95
+ frame.style.display = "flex";
96
+ frame.style.flexDirection = "column";
97
+ frame.style.gap = "14px";
98
+ frame.style.padding = "20px";
99
+
100
+ const title = document.createElement("h2");
101
+ title.style.margin = "0";
102
+ title.style.font = "600 17px/1.25 system-ui, sans-serif";
103
+ const description = document.createElement("p");
104
+ description.style.margin = "-6px 0 0";
105
+ description.style.opacity = "0.72";
106
+
107
+ const route = document.createElement("div");
108
+ route.style.display = "grid";
109
+ route.style.gridTemplateColumns = "1fr auto 1fr";
110
+ route.style.alignItems = "center";
111
+ route.style.gap = "10px";
112
+ route.style.padding = "12px";
113
+ route.style.borderRadius = "10px";
114
+ route.style.background = "color-mix(in srgb, CanvasText 6%, transparent)";
115
+ const fromBox = document.createElement("div");
116
+ const toBox = document.createElement("div");
117
+ for (const box of [fromBox, toBox]) {
118
+ box.style.display = "flex";
119
+ box.style.alignItems = "center";
120
+ box.style.gap = "8px";
121
+ box.style.minWidth = "0";
122
+ box.style.fontWeight = "600";
123
+ }
124
+ const arrow = document.createElement("span");
125
+ arrow.textContent = "→";
126
+ arrow.setAttribute("aria-hidden", "true");
127
+ arrow.style.opacity = "0.55";
128
+ route.append(fromBox, arrow, toBox);
129
+
130
+ const intentLabel = document.createElement("label");
131
+ intentLabel.style.display = "flex";
132
+ intentLabel.style.flexDirection = "column";
133
+ intentLabel.style.gap = "7px";
134
+ const intentLabelText = document.createElement("span");
135
+ intentLabelText.style.fontWeight = "600";
136
+ const intent = document.createElement("select");
137
+ intent.style.height = "36px";
138
+ intent.style.padding = "0 10px";
139
+ intent.style.border = "1px solid color-mix(in srgb, CanvasText 20%, transparent)";
140
+ intent.style.borderRadius = "8px";
141
+ intent.style.background = "Canvas";
142
+ intent.style.color = "CanvasText";
143
+ intent.style.font = "13px/1 system-ui, sans-serif";
144
+ for (const value of ["continue", "execute-plan", "review", "reanalyze"] as const) {
145
+ const option = document.createElement("option");
146
+ option.value = value;
147
+ intent.append(option);
148
+ }
149
+ intentLabel.append(intentLabelText, intent);
150
+
151
+ const includeSection = document.createElement("fieldset");
152
+ includeSection.style.margin = "0";
153
+ includeSection.style.padding = "10px 11px";
154
+ includeSection.style.border = "1px solid color-mix(in srgb, CanvasText 14%, transparent)";
155
+ includeSection.style.borderRadius = "8px";
156
+ const includeLegend = document.createElement("legend");
157
+ includeLegend.style.padding = "0 5px";
158
+ includeLegend.style.fontWeight = "600";
159
+ includeSection.append(includeLegend);
160
+ const includeGrid = document.createElement("div");
161
+ includeGrid.style.display = "grid";
162
+ includeGrid.style.gridTemplateColumns = "repeat(2, minmax(0, 1fr))";
163
+ includeGrid.style.gap = "8px 12px";
164
+ const includeInputs = {} as Record<keyof HarnessHandoffIncludes, HTMLInputElement>;
165
+ const includeLabels = {} as Record<keyof HarnessHandoffIncludes, Text>;
166
+ for (const key of ["conversation", "plan", "evidence", "files", "unresolved"] as const) {
167
+ const row = document.createElement("label");
168
+ row.style.display = "flex";
169
+ row.style.alignItems = "center";
170
+ row.style.gap = "7px";
171
+ const input = document.createElement("input");
172
+ input.type = "checkbox";
173
+ input.checked = true;
174
+ const text = document.createTextNode("");
175
+ row.append(input, text);
176
+ includeGrid.append(row);
177
+ includeInputs[key] = input;
178
+ includeLabels[key] = text;
179
+ }
180
+ includeSection.append(includeGrid);
181
+
182
+ const label = document.createElement("label");
183
+ label.style.display = "flex";
184
+ label.style.flexDirection = "column";
185
+ label.style.gap = "7px";
186
+ const labelText = document.createElement("span");
187
+ labelText.style.fontWeight = "600";
188
+ const note = document.createElement("textarea");
189
+ note.rows = 3;
190
+ note.maxLength = HARNESS_HANDOFF_NOTE_MAX_LENGTH;
191
+ note.style.boxSizing = "border-box";
192
+ note.style.width = "100%";
193
+ note.style.minHeight = "76px";
194
+ note.style.resize = "vertical";
195
+ note.style.padding = "10px 11px";
196
+ note.style.border = "1px solid color-mix(in srgb, CanvasText 20%, transparent)";
197
+ note.style.borderRadius = "8px";
198
+ note.style.outline = "none";
199
+ note.style.background = "color-mix(in srgb, Canvas 96%, CanvasText 4%)";
200
+ note.style.color = "CanvasText";
201
+ note.style.font = "13px/1.45 system-ui, sans-serif";
202
+ label.append(labelText, note);
203
+
204
+ const context = document.createElement("p");
205
+ context.style.margin = "0";
206
+ context.style.padding = "9px 10px";
207
+ context.style.borderRadius = "8px";
208
+ context.style.background = "color-mix(in srgb, #5b8cff 10%, transparent)";
209
+ const running = document.createElement("p");
210
+ running.style.margin = "-8px 0 0";
211
+ running.style.opacity = "0.65";
212
+
213
+ const error = document.createElement("p");
214
+ error.setAttribute("role", "alert");
215
+ error.hidden = true;
216
+ error.style.margin = "-8px 0 0";
217
+ error.style.color = "#ef6666";
218
+
219
+ const actions = document.createElement("div");
220
+ actions.style.display = "flex";
221
+ actions.style.justifyContent = "flex-end";
222
+ actions.style.gap = "8px";
223
+ const cancel = document.createElement("button");
224
+ cancel.type = "button";
225
+ setButtonChrome(cancel);
226
+ const confirm = document.createElement("button");
227
+ confirm.type = "button";
228
+ setButtonChrome(confirm, true);
229
+ actions.append(cancel, confirm);
230
+ frame.append(title, description, route, intentLabel, includeSection, label, context, running, error, actions);
231
+ dialog.append(frame);
232
+ document.body.append(dialog);
233
+
234
+ let from: ExternalRendererAgent = "pi";
235
+ let to: ExternalRendererAgent = "claude-code";
236
+ let locale: RendererSettingsLocale = "en";
237
+ let submitting = false;
238
+ const render = (): void => {
239
+ const messages = rendererHarnessHandoffMessages(locale);
240
+ title.textContent = messages.title;
241
+ description.textContent = messages.description;
242
+ labelText.textContent = messages.noteLabel;
243
+ intentLabelText.textContent = messages.intentLabel;
244
+ includeLegend.textContent = messages.includeLabel;
245
+ for (const [value, text] of Object.entries(messages.intentOptions)) {
246
+ const option = [...intent.options].find(candidate => candidate.value === value);
247
+ if (option) option.textContent = text;
248
+ }
249
+ for (const key of Object.keys(includeInputs) as Array<keyof HarnessHandoffIncludes>) includeLabels[key].data = messages.includeOptions[key];
250
+ note.placeholder = messages.notePlaceholder;
251
+ context.textContent = messages.context;
252
+ running.textContent = messages.running;
253
+ cancel.textContent = messages.cancel;
254
+ confirm.textContent = submitting ? messages.submitting : messages.confirm;
255
+ fromBox.replaceChildren(createRendererAgentIcon(from, 24), document.createTextNode(RENDERER_AGENT_LABELS[from]));
256
+ toBox.replaceChildren(createRendererAgentIcon(to, 24), document.createTextNode(RENDERER_AGENT_LABELS[to]));
257
+ note.disabled = submitting;
258
+ intent.disabled = submitting;
259
+ includeSection.disabled = submitting;
260
+ cancel.disabled = submitting;
261
+ confirm.disabled = submitting;
262
+ cancel.style.cursor = submitting ? "not-allowed" : "pointer";
263
+ confirm.style.cursor = submitting ? "wait" : "pointer";
264
+ confirm.setAttribute("aria-busy", String(submitting));
265
+ };
266
+ const close = (): void => {
267
+ if (submitting) return;
268
+ if (dialog.open) dialog.close();
269
+ };
270
+ cancel.addEventListener("click", close);
271
+ confirm.addEventListener("click", () => {
272
+ if (submitting) return;
273
+ onConfirm({
274
+ from,
275
+ to,
276
+ note: note.value.trim(),
277
+ intent: intent.value as HarnessHandoffIntent,
278
+ includes: Object.fromEntries(Object.entries(includeInputs).map(([key, input]) => [key, input.checked])) as unknown as HarnessHandoffIncludes,
279
+ });
280
+ });
281
+ note.addEventListener("keydown", (event) => {
282
+ if ((event.ctrlKey || event.metaKey) && event.key === "Enter" && !submitting) {
283
+ event.preventDefault();
284
+ confirm.click();
285
+ }
286
+ });
287
+ dialog.addEventListener("cancel", (event) => {
288
+ if (submitting) event.preventDefault();
289
+ });
290
+
291
+ const control: RendererHarnessHandoffControl = {
292
+ dialog,
293
+ note,
294
+ intent,
295
+ includeInputs,
296
+ confirm,
297
+ cancel,
298
+ error,
299
+ open(nextFrom, nextTo, nextLocale) {
300
+ from = nextFrom;
301
+ to = nextTo;
302
+ locale = nextLocale;
303
+ submitting = false;
304
+ note.value = "";
305
+ intent.value = "continue";
306
+ for (const input of Object.values(includeInputs)) input.checked = true;
307
+ error.hidden = true;
308
+ error.textContent = "";
309
+ render();
310
+ if (!dialog.open) dialog.showModal();
311
+ queueMicrotask(() => note.focus());
312
+ },
313
+ close,
314
+ setSubmitting(nextSubmitting) {
315
+ submitting = nextSubmitting;
316
+ render();
317
+ },
318
+ showError(message) {
319
+ error.textContent = message;
320
+ error.hidden = false;
321
+ },
322
+ dispose() {
323
+ submitting = false;
324
+ if (dialog.open) dialog.close();
325
+ dialog.remove();
326
+ },
327
+ };
328
+ render();
329
+ return control;
330
+ }