@spotpatch/runtime 1.15.1 → 1.15.3

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.
@@ -197,6 +197,9 @@ function parseTask(value) {
197
197
  ...value.resultExpiresAt === void 0 ? {} : { resultExpiresAt: value.resultExpiresAt }
198
198
  });
199
199
  }
200
+ function validModel(value) {
201
+ return typeof value === "string" && value.length > 0 && value.length <= import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumModelCharacters && value.trim() === value;
202
+ }
200
203
  function parseExternalAgentControlStatus(value) {
201
204
  if (!record(value) || !optionalKeys(
202
205
  value,
@@ -210,8 +213,8 @@ function parseExternalAgentControlStatus(value) {
210
213
  "sequence",
211
214
  "updatedAt"
212
215
  ],
213
- ["effectiveModel", "error", "requestedModel", "task"]
214
- ) || value.schemaVersion !== 1 || !integer(value.sequence) || !member(import_external_handoff_browser.EXTERNAL_AGENT_MODES, value.mode) || !member(import_external_handoff_browser.EXTERNAL_AGENT_CONNECTION_STATES, value.connectionState) || !member(import_external_handoff_browser.EXTERNAL_AGENT_AUTH_READINESS, value.authReadiness) || !member(import_external_handoff_browser.EXTERNAL_AGENT_GRANT_STATES, value.grantState) || !validTimestamp(value.updatedAt) || value.requestedModel !== void 0 && (typeof value.requestedModel !== "string" || value.requestedModel.length === 0 || value.requestedModel.length > 128) || value.effectiveModel !== void 0 && (typeof value.effectiveModel !== "string" || value.effectiveModel.length === 0 || value.effectiveModel.length > 128) || !record(value.adapter) || !exactKeys(value.adapter, ["availability", "kind", "maturity"]) || value.adapter.kind !== "codex" || value.adapter.maturity !== "experimental" || value.adapter.availability !== "available" && value.adapter.availability !== "unavailable") {
216
+ ["effectiveModel", "error", "requestedModel", "task", "models"]
217
+ ) || value.schemaVersion !== 1 || !integer(value.sequence) || !member(import_external_handoff_browser.EXTERNAL_AGENT_MODES, value.mode) || !member(import_external_handoff_browser.EXTERNAL_AGENT_CONNECTION_STATES, value.connectionState) || !member(import_external_handoff_browser.EXTERNAL_AGENT_AUTH_READINESS, value.authReadiness) || !member(import_external_handoff_browser.EXTERNAL_AGENT_GRANT_STATES, value.grantState) || !validTimestamp(value.updatedAt) || value.requestedModel !== void 0 && !validModel(value.requestedModel) || value.effectiveModel !== void 0 && !validModel(value.effectiveModel) || value.models !== void 0 && (!Array.isArray(value.models) || value.models.length === 0 || value.models.length > import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumModels || !value.models.every(validModel) || new Set(value.models).size !== value.models.length) || !record(value.adapter) || !exactKeys(value.adapter, ["availability", "kind", "maturity"]) || value.adapter.kind !== "codex" || value.adapter.maturity !== "experimental" || value.adapter.availability !== "available" && value.adapter.availability !== "unavailable") {
215
218
  throw new TypeError("Invalid external Agent control status.");
216
219
  }
217
220
  let error;
@@ -238,6 +241,7 @@ function parseExternalAgentControlStatus(value) {
238
241
  connectionState: value.connectionState,
239
242
  authReadiness: value.authReadiness,
240
243
  grantState: value.grantState,
244
+ ...value.models === void 0 ? {} : { models: Object.freeze([...value.models]) },
241
245
  ...value.requestedModel === void 0 ? {} : { requestedModel: value.requestedModel },
242
246
  ...value.effectiveModel === void 0 ? {} : { effectiveModel: value.effectiveModel },
243
247
  ...value.task === void 0 ? {} : { task: parseTask(value.task) },
@@ -736,11 +740,13 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
736
740
  }
737
741
  };
738
742
  const handleConnectClick = () => {
743
+ const model = options.panel.readModel();
739
744
  void performControlAction(
740
745
  () => controlClient.connect({
741
746
  requestId: createRequestId(options.window),
742
747
  adapterKind: "codex",
743
- profile: import_external_handoff_browser2.EXTERNAL_AGENT_MANAGED_PROFILE
748
+ profile: import_external_handoff_browser2.EXTERNAL_AGENT_MANAGED_PROFILE,
749
+ ...model === void 0 ? {} : { model }
744
750
  })
745
751
  );
746
752
  };
@@ -1024,24 +1030,201 @@ function createButton(document, label, className = "") {
1024
1030
  return button;
1025
1031
  }
1026
1032
 
1033
+ // src/ui/ask-picker.ts
1034
+ var SELECT_PICKER_STYLES = `
1035
+ .spotpatch-select-picker { position: relative; display: grid; gap: 5px; min-width: 0; }
1036
+ .spotpatch-select-trigger { box-sizing: border-box; display: flex; width: 100%; min-height: 38px; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 0 11px; overflow: hidden; color: var(--spotpatch-text); background: var(--spotpatch-bg-input); cursor: pointer; font: inherit; outline: none; text-align: left; }
1037
+ .spotpatch-select-trigger > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1038
+ .spotpatch-select-trigger[data-expandable="true"]::after { width: 7px; height: 7px; flex: 0 0 auto; border-right: 1.5px solid #8f96a3; border-bottom: 1.5px solid #8f96a3; content: ""; transform: translateY(-2px) rotate(45deg); }
1039
+ .spotpatch-select-trigger[aria-expanded="true"]::after { transform: translateY(2px) rotate(225deg); }
1040
+ .spotpatch-select-trigger:focus-visible { border-color: rgb(139 123 255 / 68%); box-shadow: 0 0 0 3px rgb(139 123 255 / 12%); }
1041
+ .spotpatch-select-trigger:disabled { color: var(--spotpatch-text); background: rgb(255 255 255 / 3%); cursor: default; opacity: 1; }
1042
+ .spotpatch-select-trigger[data-empty="true"]:disabled { color: var(--spotpatch-text-muted); }
1043
+ .spotpatch-select-menu { position: relative; z-index: 1; display: grid; gap: 3px; max-height: 224px; overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 4px; background: var(--spotpatch-bg-input); box-shadow: 0 10px 28px rgb(0 0 0 / 28%); scrollbar-width: thin; }
1044
+ .spotpatch-select-menu[hidden] { display: none; }
1045
+ .spotpatch-select-option { box-sizing: border-box; min-height: 34px; border: 0; border-radius: 6px; padding: 7px 9px; overflow-wrap: anywhere; color: var(--spotpatch-text-secondary); background: transparent; cursor: pointer; font: inherit; text-align: left; }
1046
+ .spotpatch-select-option[data-active="true"], .spotpatch-select-option:hover { color: #fff; background: rgb(139 123 255 / 13%); outline: none; }
1047
+ .spotpatch-select-option[aria-selected="true"] { color: #f4f1ff; background: rgb(139 123 255 / 20%); }
1048
+ .spotpatch-select-native { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; border: 0; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
1049
+ `;
1050
+ function createSelectPicker(document, onViewChange) {
1051
+ const root = createMarkedElement(document, "div");
1052
+ root.className = "spotpatch-select-picker";
1053
+ const trigger = createButton(document, "", "spotpatch-select-trigger");
1054
+ trigger.id = `spotpatch-select-picker-${Math.random().toString(36).slice(2)}`;
1055
+ trigger.setAttribute("role", "combobox");
1056
+ trigger.setAttribute("aria-haspopup", "listbox");
1057
+ trigger.setAttribute("aria-expanded", "false");
1058
+ const text = createMarkedElement(document, "span");
1059
+ trigger.append(text);
1060
+ const menu = createMarkedElement(document, "div");
1061
+ menu.id = `${trigger.id}-menu`;
1062
+ menu.className = "spotpatch-select-menu";
1063
+ menu.setAttribute("role", "listbox");
1064
+ menu.hidden = true;
1065
+ trigger.setAttribute("aria-controls", menu.id);
1066
+ const select = createMarkedElement(document, "select");
1067
+ select.className = "spotpatch-select-native";
1068
+ select.tabIndex = -1;
1069
+ select.setAttribute("aria-hidden", "true");
1070
+ root.append(trigger, menu, select);
1071
+ function close(focus = false) {
1072
+ if (menu.hidden) return;
1073
+ menu.hidden = true;
1074
+ trigger.setAttribute("aria-expanded", "false");
1075
+ trigger.removeAttribute("aria-activedescendant");
1076
+ if (focus) trigger.focus({ preventScroll: true });
1077
+ onViewChange();
1078
+ }
1079
+ function activate(index) {
1080
+ const options = [...menu.children];
1081
+ const active = options[index];
1082
+ if (active === void 0) return;
1083
+ for (const option of options) option.dataset.active = String(option === active);
1084
+ trigger.setAttribute("aria-activedescendant", active.id);
1085
+ const itemRect = active.getBoundingClientRect();
1086
+ const menuRect = menu.getBoundingClientRect();
1087
+ if (itemRect.top < menuRect.top) menu.scrollTop -= menuRect.top - itemRect.top;
1088
+ else if (itemRect.bottom > menuRect.bottom)
1089
+ menu.scrollTop += itemRect.bottom - menuRect.bottom;
1090
+ }
1091
+ function open() {
1092
+ if (trigger.disabled || !menu.hidden) return;
1093
+ menu.hidden = false;
1094
+ trigger.setAttribute("aria-expanded", "true");
1095
+ activate(Math.max(0, select.selectedIndex));
1096
+ onViewChange();
1097
+ }
1098
+ function sync() {
1099
+ text.textContent = select.selectedOptions[0]?.textContent ?? "";
1100
+ trigger.title = text.textContent;
1101
+ trigger.dataset.empty = String(select.value.length === 0);
1102
+ for (const option of [...menu.children]) {
1103
+ option.setAttribute(
1104
+ "aria-selected",
1105
+ String(option.dataset.value === select.value)
1106
+ );
1107
+ }
1108
+ }
1109
+ function choose(value) {
1110
+ if (trigger.disabled) return;
1111
+ select.value = value;
1112
+ sync();
1113
+ close(true);
1114
+ select.dispatchEvent(new Event("change", { bubbles: true }));
1115
+ }
1116
+ function rebuild() {
1117
+ const expanded = !menu.hidden;
1118
+ const nativeOptions = [...select.options];
1119
+ menu.replaceChildren();
1120
+ for (const [index, option] of nativeOptions.entries()) {
1121
+ const item = createMarkedElement(document, "div");
1122
+ item.id = `${menu.id}-${String(index)}`;
1123
+ item.className = "spotpatch-select-option";
1124
+ item.setAttribute("role", "option");
1125
+ item.dataset.value = option.value;
1126
+ item.textContent = option.textContent;
1127
+ item.title = option.textContent;
1128
+ item.addEventListener("pointerdown", (event) => {
1129
+ event.preventDefault();
1130
+ });
1131
+ item.addEventListener("click", () => {
1132
+ choose(option.value);
1133
+ });
1134
+ menu.append(item);
1135
+ }
1136
+ sync();
1137
+ if (expanded) {
1138
+ activate(Math.max(0, select.selectedIndex));
1139
+ onViewChange();
1140
+ }
1141
+ }
1142
+ trigger.addEventListener("click", () => {
1143
+ if (menu.hidden) open();
1144
+ else close();
1145
+ });
1146
+ trigger.addEventListener("keydown", (event) => {
1147
+ if (event.key === "Escape" && !menu.hidden) {
1148
+ event.preventDefault();
1149
+ event.stopPropagation();
1150
+ close();
1151
+ return;
1152
+ }
1153
+ if (event.key === "Tab") {
1154
+ close();
1155
+ return;
1156
+ }
1157
+ if (["Enter", " "].includes(event.key) && !menu.hidden) {
1158
+ event.preventDefault();
1159
+ const active = [...menu.children].find(
1160
+ (item) => item.id === trigger.getAttribute("aria-activedescendant")
1161
+ );
1162
+ if (active?.dataset.value !== void 0) choose(active.dataset.value);
1163
+ return;
1164
+ }
1165
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
1166
+ event.preventDefault();
1167
+ if (menu.hidden) {
1168
+ open();
1169
+ return;
1170
+ }
1171
+ const options = [...menu.children];
1172
+ const index = options.findIndex(
1173
+ (item) => item.id === trigger.getAttribute("aria-activedescendant")
1174
+ );
1175
+ activate(
1176
+ event.key === "Home" ? 0 : event.key === "End" ? options.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + options.length) % options.length
1177
+ );
1178
+ });
1179
+ root.addEventListener("focusout", (event) => {
1180
+ if (!(event.relatedTarget instanceof Node) || !root.contains(event.relatedTarget))
1181
+ close();
1182
+ });
1183
+ const outside = (event) => {
1184
+ if (!event.composedPath().includes(root)) close();
1185
+ };
1186
+ document.addEventListener("pointerdown", outside);
1187
+ select.addEventListener("change", sync);
1188
+ return {
1189
+ root,
1190
+ trigger,
1191
+ select,
1192
+ rebuild,
1193
+ close,
1194
+ setDisabled(disabled) {
1195
+ select.disabled = disabled;
1196
+ trigger.disabled = disabled || select.options.length < 2;
1197
+ trigger.dataset.expandable = String(!trigger.disabled);
1198
+ if (trigger.disabled) close();
1199
+ },
1200
+ dispose() {
1201
+ document.removeEventListener("pointerdown", outside);
1202
+ }
1203
+ };
1204
+ }
1205
+
1027
1206
  // src/ui/external-handoff-panel.ts
1028
1207
  var MESSAGES = Object.freeze({
1029
1208
  "en-US": Object.freeze({
1030
1209
  title: "External Agent connection",
1031
1210
  agentLabel: "Agent",
1032
- codexManaged: "Codex \xB7 managed (experimental)",
1211
+ modelLabel: "Managed Codex model",
1212
+ modelLoading: "Connect to load available models",
1213
+ modelHint: "Applies only to this managed connection, not API Key settings. Apply changes before publishing; switching reconnects Codex.",
1214
+ applyModel: "Apply model",
1215
+ codexManaged: "Codex · managed (experimental)",
1033
1216
  connectManaged: "Connect Codex",
1034
1217
  disconnectManaged: "Disconnect",
1035
1218
  revokeManaged: "Revoke grant",
1036
1219
  cancelManaged: "Cancel managed task",
1037
1220
  resultTitle: "Managed result",
1038
- controlConnection: (state, mode) => `Connection: ${state} \xB7 Mode: ${mode}`,
1039
- controlAuth: (auth, grant) => `Auth: ${auth} \xB7 Grant: ${grant}`,
1221
+ controlConnection: (state, mode) => `Connection: ${state} · Mode: ${mode}`,
1222
+ controlAuth: (auth, grant) => `Auth: ${auth} · Grant: ${grant}`,
1040
1223
  controlConsentRequired: (command) => `Initialize project access once: run \`${command}\` from this project, then connect again. No dev-terminal confirmation is required. The grant allows isolated snapshot writes and validated application; revoke it here at any time.`,
1041
1224
  controlModel: (model) => `Model: ${model}`,
1042
1225
  controlRevision: (task) => `Revision ${String(task.revision)}: ${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
1043
1226
  controlValidation: (outcome) => `Validation: ${outcome}`,
1044
- controlFailure: (error, action) => `Error: ${error} \xB7 Next: ${action}`,
1227
+ controlFailure: (error, action) => `Error: ${error} · Next: ${action}`,
1045
1228
  controlUnavailable: "Page connection management is unavailable; external handoffs still fall back honestly to the Agent inbox.",
1046
1229
  controlErrorText: Object.freeze({
1047
1230
  AGENT_BINARY_NOT_FOUND: "Codex is not installed or is not on PATH.",
@@ -1067,7 +1250,7 @@ var MESSAGES = Object.freeze({
1067
1250
  "install-agent": "Install Codex and retry.",
1068
1251
  "use-supported-version": "Use a schema-compatible Codex version.",
1069
1252
  "sign-in": "Sign in with Codex, then retry.",
1070
- "choose-available-model": "Choose an available model in Codex configuration.",
1253
+ "choose-available-model": "Choose an available model and reconnect Codex.",
1071
1254
  "use-inbox": "Continue with the Agent inbox fallback.",
1072
1255
  "confirm-managed-access": "Revoke the invalid grant and confirm access again.",
1073
1256
  "review-candidate-diff": "Review the candidate diff and validation output.",
@@ -1075,17 +1258,17 @@ var MESSAGES = Object.freeze({
1075
1258
  retry: "Retry the managed connection.",
1076
1259
  "inspect-cleanup-warning": "Inspect cleanup status before reconnecting."
1077
1260
  }),
1078
- resultHeader: (revision, outcome) => `Revision ${String(revision)} \xB7 Validation ${outcome}`,
1261
+ resultHeader: (revision, outcome) => `Revision ${String(revision)} · Validation ${outcome}`,
1079
1262
  resultDisposition: (outcome) => outcome === "passed" ? "Applied to the project after validation passed." : `Candidate only; it was not applied because validation is ${outcome}.`,
1080
1263
  resultTiming: (stage, durationMs) => `Timing ${stage}: ${String(durationMs)} ms`,
1081
1264
  description: "Send through a ready active adapter, or publish to the project Agent inbox when no active adapter is connected.",
1082
1265
  ready: "Local Broker ready. No Agent connection is assumed until a connector reads or waits.",
1083
1266
  readyWaiting: (count) => `${String(count)} active connector wait request(s); publishing can wake them immediately.`,
1084
1267
  activeReady: (agent) => `${agent} is connected and idle. The next send will dispatch immediately.`,
1085
- publishing: "Authorizing current source and publishing the handoff\u2026",
1268
+ publishing: "Authorizing current source and publishing the handoff…",
1086
1269
  published: (revision, expiresAt) => `Revision ${String(revision)} is available until ${expiresAt}. It has not been claimed as edited.`,
1087
1270
  queued: (agent, revision) => `Revision ${String(revision)} is reserved for ${agent}.`,
1088
- dispatching: (agent, revision) => `Dispatching revision ${String(revision)} to ${agent}\u2026`,
1271
+ dispatching: (agent, revision) => `Dispatching revision ${String(revision)} to ${agent}…`,
1089
1272
  dispatched: (agent, revision) => agent === "Claude Code" ? `Revision ${String(revision)} was written to the Claude Channel transport; there is no model ACK yet.` : `Codex App Server accepted revision ${String(revision)}; waiting for the matching turn to start.`,
1090
1273
  working: (agent, revision) => `${agent} is working on revision ${String(revision)}.`,
1091
1274
  completed: (agent, revision) => `${agent} ended revision ${String(revision)} normally. Review the diff and checks; this does not prove the requested change is correct.`,
@@ -1099,7 +1282,7 @@ var MESSAGES = Object.freeze({
1099
1282
  inboxSend: "Publish to Agent inbox",
1100
1283
  retry: "Retry same send",
1101
1284
  refresh: "Refresh pickup status",
1102
- resolveUnknown: "Workspace reviewed \u2014 allow a new task",
1285
+ resolveUnknown: "Workspace reviewed — allow a new task",
1103
1286
  settingsTitle: "Project connection setup",
1104
1287
  settingsHelp: "Codex managed mode is owned by the current development server. The browser never receives vendor credentials or arbitrary command access. Generic and attached adapters remain honest Inbox fallbacks.",
1105
1288
  disclosureTitle: "Confirm external Agent handoff",
@@ -1114,96 +1297,101 @@ var MESSAGES = Object.freeze({
1114
1297
  error: (code) => code === "EXTERNAL_AGENT_BUSY" ? "The active Agent is still busy or blocked by an uncertain delivery." : code === "ACTIVE_DISPATCH_INVALID" ? "The active delivery state changed. Refresh before continuing." : code === "HANDOFF_SOURCE_STALE" ? "The selected source changed. Select the current component again." : code === "HANDOFF_RESPONSE_TOO_LARGE" ? "The handoff is too large. Reduce the selected context." : code === "EXTERNAL_HANDOFF_DISABLED" ? "External Agent handoff is disabled in trusted project configuration." : code === "EXTERNAL_HANDOFF_UNAVAILABLE" || code === "SESSION_CLOSED" ? "The local external Agent Broker is unavailable. Restart the development server." : "The external Agent handoff request failed without publishing a partial revision."
1115
1298
  }),
1116
1299
  "zh-CN": Object.freeze({
1117
- title: "\u5916\u90E8 Agent \u8FDE\u63A5",
1300
+ title: "外部 Agent 连接",
1118
1301
  agentLabel: "Agent",
1119
- codexManaged: "Codex \xB7 \u53D7\u7BA1\u6A21\u5F0F\uFF08\u5B9E\u9A8C\u6027\uFF09",
1120
- connectManaged: "\u8FDE\u63A5 Codex",
1121
- disconnectManaged: "\u65AD\u5F00\u8FDE\u63A5",
1122
- revokeManaged: "\u64A4\u9500\u6388\u6743",
1123
- cancelManaged: "\u53D6\u6D88\u53D7\u7BA1\u4EFB\u52A1",
1124
- resultTitle: "\u53D7\u7BA1\u6267\u884C\u7ED3\u679C",
1125
- controlConnection: (state, mode) => `\u8FDE\u63A5\uFF1A${state} \xB7 \u6A21\u5F0F\uFF1A${mode}`,
1126
- controlAuth: (auth, grant) => `\u8BA4\u8BC1\uFF1A${auth} \xB7 \u6388\u6743\uFF1A${grant}`,
1127
- controlConsentRequired: (command) => `\u8BF7\u5728\u5F53\u524D\u9879\u76EE\u6267\u884C\u4E00\u6B21\u521D\u59CB\u5316\uFF1A\`${command}\`\uFF0C\u7136\u540E\u91CD\u65B0\u8FDE\u63A5\uFF0C\u65E0\u9700\u5728\u5F00\u53D1\u7EC8\u7AEF\u8F93\u5165 yes\u3002\u6388\u6743\u5141\u8BB8\u9694\u79BB\u5FEB\u7167\u4FEE\u6539\u53CA\u6821\u9A8C\u540E\u7684\u5E94\u7528\uFF0C\u53EF\u968F\u65F6\u5728\u6B64\u64A4\u9500\u3002`,
1128
- controlModel: (model) => `\u6A21\u578B\uFF1A${model}`,
1129
- controlRevision: (task) => `revision ${String(task.revision)}\uFF1A${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
1130
- controlValidation: (outcome) => `\u9A8C\u8BC1\uFF1A${outcome}`,
1131
- controlFailure: (error, action) => `\u9519\u8BEF\uFF1A${error} \xB7 \u4E0B\u4E00\u6B65\uFF1A${action}`,
1132
- controlUnavailable: "\u9875\u9762\u8FDE\u63A5\u7BA1\u7406\u4E0D\u53EF\u7528\uFF1B\u5916\u90E8\u4EA4\u63A5\u4ECD\u4F1A\u6309\u771F\u5B9E\u80FD\u529B\u964D\u7EA7\u4E3A Agent \u6536\u4EF6\u7BB1\u3002",
1302
+ modelLabel: "受管 Codex 模型",
1303
+ modelLoading: "连接后加载可用模型",
1304
+ modelHint: "仅作用于此受管连接,不修改 API Key 配置。更换后请先应用模型,再发布任务;应用时会重新连接 Codex。",
1305
+ applyModel: "应用模型",
1306
+ codexManaged: "Codex · 受管模式(实验性)",
1307
+ connectManaged: "连接 Codex",
1308
+ disconnectManaged: "断开连接",
1309
+ revokeManaged: "撤销授权",
1310
+ cancelManaged: "取消受管任务",
1311
+ resultTitle: "受管执行结果",
1312
+ controlConnection: (state, mode) => `连接:${state} · 模式:${mode}`,
1313
+ controlAuth: (auth, grant) => `认证:${auth} · 授权:${grant}`,
1314
+ controlConsentRequired: (command) => `请在当前项目执行一次初始化:\`${command}\`,然后重新连接,无需在开发终端输入 yes。授权允许隔离快照修改及校验后的应用,可随时在此撤销。`,
1315
+ controlModel: (model) => `模型:${model}`,
1316
+ controlRevision: (task) => `revision ${String(task.revision)}:${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
1317
+ controlValidation: (outcome) => `验证:${outcome}`,
1318
+ controlFailure: (error, action) => `错误:${error} · 下一步:${action}`,
1319
+ controlUnavailable: "页面连接管理不可用;外部交接仍会按真实能力降级为 Agent 收件箱。",
1133
1320
  controlErrorText: Object.freeze({
1134
- AGENT_BINARY_NOT_FOUND: "\u672A\u5B89\u88C5 Codex\uFF0C\u6216 Codex \u4E0D\u5728 PATH \u4E2D\u3002",
1135
- AGENT_BINARY_UNTRUSTED: "\u89E3\u6790\u5230\u7684 Codex \u53EF\u6267\u884C\u6587\u4EF6\u4E0D\u53EF\u4FE1\u3002",
1136
- AGENT_VERSION_UNSUPPORTED: "\u5DF2\u5B89\u88C5\u7684 Codex \u7248\u672C\u4F4E\u4E8E SpotPatch \u517C\u5BB9\u57FA\u7EBF\u3002",
1137
- APP_SERVER_HANDSHAKE_FAILED: "Codex App Server \u672A\u5B8C\u6210\u542F\u52A8\u63E1\u624B\u3002",
1138
- AGENT_AUTH_REQUIRED: "Codex \u9700\u8981\u5DF2\u767B\u5F55\u7684\u8D26\u6237\u3002",
1139
- AGENT_MODEL_UNAVAILABLE: "Codex \u914D\u7F6E\u7684\u6A21\u578B\u5F53\u524D\u4E0D\u53EF\u7528\u3002",
1140
- AGENT_PROTOCOL_INCOMPATIBLE: "Codex \u751F\u6210\u7684 App Server Schema \u4E0D\u6EE1\u8DB3 SpotPatch \u6240\u9700\u534F\u8BAE\u5B50\u96C6\u3002",
1141
- CODEX_CONFIG_ISOLATION_UNSUPPORTED: "\u5F53\u524D Codex \u7248\u672C\u65E0\u6CD5\u8BC1\u660E\u53D7\u7BA1\u914D\u7F6E\u9694\u79BB\u3002",
1142
- MANAGED_GRANT_INVALID: "\u4FDD\u5B58\u7684\u9879\u76EE\u6388\u6743\u672A\u901A\u8FC7\u6821\u9A8C\u3002",
1143
- MANAGED_PLATFORM_UNSUPPORTED: "\u5F53\u524D\u5E73\u53F0\u5C1A\u672A\u8BC1\u660E\u53EF\u5B89\u5168\u8FD0\u884C\u53D7\u7BA1\u6267\u884C\u3002",
1144
- MANAGED_GIT_REQUIRED: "\u53D7\u7BA1\u6267\u884C\u8981\u6C42\u9879\u76EE\u4F4D\u4E8E Git \u4ED3\u5E93\u4E2D\u3002",
1145
- MANAGED_SNAPSHOT_FAILED: "\u65E0\u6CD5\u521B\u5EFA\u72EC\u7ACB\u5DE5\u4F5C\u533A\u5FEB\u7167\u3002",
1146
- MANAGED_SCOPE_VIOLATION: "\u5019\u9009\u4FEE\u6539\u8D85\u51FA\u6388\u6743\u8DEF\u5F84\u3002",
1147
- MANAGED_CHANGE_LIMIT_EXCEEDED: "\u5019\u9009\u4FEE\u6539\u8D85\u8FC7\u5B89\u5168\u4E0A\u9650\u3002",
1148
- MANAGED_VALIDATION_FAILED: "\u5019\u9009\u4FEE\u6539\u672A\u901A\u8FC7\u5BA1\u8BA1\u6216\u9A8C\u8BC1\u3002",
1149
- MANAGED_WORKSPACE_CONFLICT: "\u6267\u884C\u671F\u95F4\u6388\u6743\u6E90\u7801\u53D1\u751F\u4E86\u53D8\u5316\u3002",
1150
- MANAGED_APPLY_FAILED: "\u5DF2\u5BA1\u8BA1\u4FEE\u6539\u65E0\u6CD5\u5B89\u5168\u5199\u5165\u3002",
1151
- MANAGED_CLEANUP_INCOMPLETE: "\u53D7\u7BA1 thread \u6216\u5DE5\u4F5C\u533A\u6E05\u7406\u4E0D\u5B8C\u6574\u3002"
1321
+ AGENT_BINARY_NOT_FOUND: "未安装 Codex,或 Codex 不在 PATH 中。",
1322
+ AGENT_BINARY_UNTRUSTED: "解析到的 Codex 可执行文件不可信。",
1323
+ AGENT_VERSION_UNSUPPORTED: "已安装的 Codex 版本低于 SpotPatch 兼容基线。",
1324
+ APP_SERVER_HANDSHAKE_FAILED: "Codex App Server 未完成启动握手。",
1325
+ AGENT_AUTH_REQUIRED: "Codex 需要已登录的账户。",
1326
+ AGENT_MODEL_UNAVAILABLE: "Codex 配置的模型当前不可用。",
1327
+ AGENT_PROTOCOL_INCOMPATIBLE: "Codex 生成的 App Server Schema 不满足 SpotPatch 所需协议子集。",
1328
+ CODEX_CONFIG_ISOLATION_UNSUPPORTED: "当前 Codex 版本无法证明受管配置隔离。",
1329
+ MANAGED_GRANT_INVALID: "保存的项目授权未通过校验。",
1330
+ MANAGED_PLATFORM_UNSUPPORTED: "当前平台尚未证明可安全运行受管执行。",
1331
+ MANAGED_GIT_REQUIRED: "受管执行要求项目位于 Git 仓库中。",
1332
+ MANAGED_SNAPSHOT_FAILED: "无法创建独立工作区快照。",
1333
+ MANAGED_SCOPE_VIOLATION: "候选修改超出授权路径。",
1334
+ MANAGED_CHANGE_LIMIT_EXCEEDED: "候选修改超过安全上限。",
1335
+ MANAGED_VALIDATION_FAILED: "候选修改未通过审计或验证。",
1336
+ MANAGED_WORKSPACE_CONFLICT: "执行期间授权源码发生了变化。",
1337
+ MANAGED_APPLY_FAILED: "已审计修改无法安全写入。",
1338
+ MANAGED_CLEANUP_INCOMPLETE: "受管 thread 或工作区清理不完整。"
1152
1339
  }),
1153
1340
  controlActionText: Object.freeze({
1154
- "install-agent": "\u5B89\u88C5 Codex \u540E\u91CD\u8BD5\u3002",
1155
- "use-supported-version": "\u6539\u7528 Schema \u517C\u5BB9\u7684 Codex \u7248\u672C\u3002",
1156
- "sign-in": "\u767B\u5F55 Codex \u540E\u91CD\u8BD5\u3002",
1157
- "choose-available-model": "\u5728 Codex \u914D\u7F6E\u4E2D\u9009\u62E9\u53EF\u7528\u6A21\u578B\u3002",
1158
- "use-inbox": "\u7EE7\u7EED\u4F7F\u7528 Agent \u6536\u4EF6\u7BB1\u964D\u7EA7\u8DEF\u5F84\u3002",
1159
- "confirm-managed-access": "\u64A4\u9500\u65E0\u6548\u6388\u6743\u5E76\u91CD\u65B0\u786E\u8BA4\u3002",
1160
- "review-candidate-diff": "\u68C0\u67E5\u5019\u9009 diff \u548C\u9A8C\u8BC1\u8F93\u51FA\u3002",
1161
- "review-workspace-conflict": "\u68C0\u67E5\u5DE5\u4F5C\u533A\u53D8\u5316\u540E\u518D\u91CD\u8BD5\u3002",
1162
- retry: "\u91CD\u8BD5\u53D7\u7BA1\u8FDE\u63A5\u3002",
1163
- "inspect-cleanup-warning": "\u786E\u8BA4\u6E05\u7406\u72B6\u6001\u540E\u518D\u8FDE\u63A5\u3002"
1341
+ "install-agent": "安装 Codex 后重试。",
1342
+ "use-supported-version": "改用 Schema 兼容的 Codex 版本。",
1343
+ "sign-in": "登录 Codex 后重试。",
1344
+ "choose-available-model": "选择可用模型后重新连接 Codex。",
1345
+ "use-inbox": "继续使用 Agent 收件箱降级路径。",
1346
+ "confirm-managed-access": "撤销无效授权并重新确认。",
1347
+ "review-candidate-diff": "检查候选 diff 和验证输出。",
1348
+ "review-workspace-conflict": "检查工作区变化后再重试。",
1349
+ retry: "重试受管连接。",
1350
+ "inspect-cleanup-warning": "确认清理状态后再连接。"
1164
1351
  }),
1165
- resultHeader: (revision, outcome) => `revision ${String(revision)} \xB7 \u9A8C\u8BC1 ${outcome}`,
1166
- resultDisposition: (outcome) => outcome === "passed" ? "\u9A8C\u8BC1\u901A\u8FC7\uFF0C\u4FEE\u6539\u5DF2\u5199\u5165\u9879\u76EE\u3002" : `\u8FD9\u53EA\u662F\u5019\u9009 diff\uFF1B\u9A8C\u8BC1\u72B6\u6001\u4E3A ${outcome}\uFF0C\u56E0\u6B64\u6CA1\u6709\u5199\u5165\u9879\u76EE\u3002`,
1167
- resultTiming: (stage, durationMs) => `\u8017\u65F6 ${stage}: ${String(durationMs)} ms`,
1168
- description: "\u6709\u4E3B\u52A8\u9002\u914D\u5668\u5C31\u7ACB\u5373\u6D3E\u53D1\uFF1B\u672A\u8FDE\u63A5\u4E3B\u52A8\u9002\u914D\u5668\u65F6\uFF0C\u8BDA\u5B9E\u964D\u7EA7\u4E3A\u9879\u76EE Agent \u6536\u4EF6\u7BB1\u3002",
1169
- ready: "\u672C\u5730 Broker \u5DF2\u5C31\u7EEA\u3002\u53EA\u6709\u8FDE\u63A5\u5668\u8BFB\u53D6\u6216\u7B49\u5F85\u540E\uFF0C\u624D\u80FD\u8BC1\u660E\u53D1\u751F\u8FC7\u8FDE\u63A5\u3002",
1170
- readyWaiting: (count) => `\u5F53\u524D\u6709 ${String(count)} \u4E2A\u8FDE\u63A5\u5668\u7B49\u5F85\u8BF7\u6C42\uFF0C\u53D1\u5E03\u540E\u53EF\u7ACB\u5373\u5524\u9192\u3002`,
1171
- activeReady: (agent) => `${agent} \u5DF2\u8FDE\u63A5\u5E76\u5904\u4E8E\u7A7A\u95F2\u72B6\u6001\uFF0C\u4E0B\u6B21\u53D1\u9001\u4F1A\u7ACB\u5373\u6D3E\u53D1\u3002`,
1172
- publishing: "\u6B63\u5728\u91CD\u65B0\u6388\u6743\u5F53\u524D\u6E90\u7801\u5E76\u53D1\u5E03\u4EA4\u63A5\u2026\u2026",
1173
- published: (revision, expiresAt) => `\u4EA4\u63A5 revision ${String(revision)} \u53EF\u8BFB\u53D6\u81F3 ${expiresAt}\uFF1B\u8FD9\u4E0D\u8868\u793A\u4EE3\u7801\u5DF2\u88AB\u4FEE\u6539\u3002`,
1174
- queued: (agent, revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u4E3A ${agent} \u9884\u7559\u3002`,
1175
- dispatching: (agent, revision) => `\u6B63\u5728\u628A revision ${String(revision)} \u6D3E\u53D1\u7ED9 ${agent}\u2026\u2026`,
1176
- dispatched: (agent, revision) => agent === "Claude Code" ? `revision ${String(revision)} \u5DF2\u5199\u5165 Claude Channel transport\uFF1B\u5F53\u524D\u6CA1\u6709\u6A21\u578B ACK\u3002` : `Codex App Server \u5DF2\u63A5\u53D7 revision ${String(revision)}\uFF0C\u6B63\u5728\u7B49\u5F85\u5339\u914D turn \u542F\u52A8\u3002`,
1177
- working: (agent, revision) => `${agent} \u6B63\u5728\u5904\u7406 revision ${String(revision)}\u3002`,
1178
- completed: (agent, revision) => `${agent} \u5DF2\u6B63\u5E38\u7ED3\u675F revision ${String(revision)}\u3002\u8BF7\u6838\u5BF9 diff \u548C\u68C0\u67E5\u7ED3\u679C\uFF1B\u8FD9\u4E0D\u8BC1\u660E\u4FEE\u6539\u8981\u6C42\u5DF2\u7ECF\u6B63\u786E\u5B8C\u6210\u3002`,
1179
- failed: (agent, revision) => `${agent} \u5DF2\u5C06 revision ${String(revision)} \u62A5\u544A\u4E3A\u5931\u8D25\uFF0C\u8FDE\u63A5\u5668\u5DF2\u6062\u590D\u7A7A\u95F2\u3002`,
1180
- unknown: (agent, revision) => `revision ${String(revision)} \u5411 ${agent} \u7684\u6295\u9012\u7ED3\u679C\u4E0D\u786E\u5B9A\u3002\u53D7\u7BA1 writer \u5DF2\u505C\u6B62\uFF0C\u8BF7\u5148\u6838\u5BF9\u5DE5\u4F5C\u533A\u518D\u5141\u8BB8\u65B0\u4EFB\u52A1\u3002`,
1181
- unknownResolved: (agent, revision) => `${agent} \u7684 revision ${String(revision)} \u4ECD\u8BB0\u4E3A\u6295\u9012\u672A\u77E5\uFF0C\u4F46\u5DE5\u4F5C\u533A\u6838\u5BF9\u5DF2\u7ECF\u786E\u8BA4\uFF1B\u73B0\u5728\u53EF\u4EE5\u91CD\u65B0\u8FDE\u63A5\u6216\u53D1\u5E03\u65B0\u7684\u6536\u4EF6\u7BB1\u4EFB\u52A1\u3002`,
1182
- pickedUp: (revision, count, time) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u88AB ${String(count)} \u4E2A\u8FDE\u63A5\u5668\u5B9E\u4F8B\u53D6\u8D70${time === void 0 ? "\u3002" : `\uFF0C\u6700\u8FD1\u53D6\u4EF6\u4E8E ${time}\u3002`}\u8FD9\u4E0D\u80FD\u8BC1\u660E\u4EE3\u7801\u5DF2\u7ECF\u4FEE\u6539\u3002`,
1183
- expired: (revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6838\u5BF9\u5F53\u524D\u9009\u62E9\u540E\u91CD\u65B0\u53D1\u5E03\u3002`,
1184
- superseded: (revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u88AB\u66F4\u65B0\u7248\u672C\u53D6\u4EE3\u3002`,
1185
- send: "\u53D1\u9001\u7ED9 Agent",
1186
- inboxSend: "\u53D1\u5E03\u5230 Agent \u6536\u4EF6\u7BB1",
1187
- retry: "\u91CD\u8BD5\u540C\u4E00\u6B21\u53D1\u9001",
1188
- refresh: "\u5237\u65B0\u53D6\u4EF6\u72B6\u6001",
1189
- resolveUnknown: "\u5DF2\u6838\u5BF9\u5DE5\u4F5C\u533A\uFF0C\u5141\u8BB8\u65B0\u4EFB\u52A1",
1190
- settingsTitle: "\u9879\u76EE\u8FDE\u63A5\u8BBE\u7F6E",
1191
- settingsHelp: "Codex \u53D7\u7BA1\u6A21\u5F0F\u7531\u5F53\u524D\u5F00\u53D1\u670D\u52A1\u6258\u7BA1\uFF0C\u6D4F\u89C8\u5668\u4E0D\u4F1A\u83B7\u5F97\u5382\u5546\u51ED\u8BC1\u6216\u4EFB\u610F\u547D\u4EE4\u6743\u9650\uFF1B\u901A\u7528\u4E0E attached adapter \u4ECD\u6309\u771F\u5B9E\u80FD\u529B\u964D\u7EA7\u4E3A\u6536\u4EF6\u7BB1\u3002",
1192
- disclosureTitle: "\u786E\u8BA4\u53D1\u5E03\u5230\u5916\u90E8 Agent",
1193
- disclosureIntro: (targets, files) => `\u5C06\u53D1\u5E03 ${String(targets)} \u4E2A\u76EE\u6807\uFF0C\u6D89\u53CA ${String(files)} \u4E2A\u9879\u76EE\u76F8\u5BF9\u6587\u4EF6\u3002`,
1194
- disclosureData: "\u4EA4\u63A5\u5305\u542B\u4FEE\u6539\u8BF4\u660E\u3001\u7EC4\u4EF6\u4E0E\u9875\u9762\u4E0A\u4E0B\u6587\u3001\u5DF2\u6E05\u6D17\u7684 DOM/CSS\uFF0C\u4EE5\u53CA\u672C\u5730 Node \u670D\u52A1\u91CD\u65B0\u8BFB\u53D6\u7684\u6709\u754C\u6E90\u7801\u3002",
1195
- disclosureInbox: "15 \u5206\u949F\u6709\u6548\u671F\u5185\uFF0C\u672C\u9879\u76EE\u4E2D\u4EFB\u4F55\u5DF2\u914D\u7F6E\u7684 SpotPatch Connector \u90FD\u53EF\u80FD\u8BFB\u53D6\u3002",
1196
- disclosureProvider: "Agent \u5BBF\u4E3B\u53EF\u80FD\u4F9D\u636E\u5176\u4EA7\u54C1\u6570\u636E\u7B56\u7565\uFF0C\u5C06\u5185\u5BB9\u53D1\u9001\u7ED9\u4E91\u7AEF\u6A21\u578B\u670D\u52A1\u3002",
1197
- disclosureNoGuarantee: "\u5916\u90E8\u4FEE\u6539\u4E0D\u5177\u5907 SpotPatch \u5185\u5EFA Agent \u7684 worktree\u3001Apply \u6216 Revert \u4FDD\u8BC1\u3002",
1198
- disclosureManagedGuarantee: "\u53D7\u7BA1 Codex \u53EA\u5199\u72EC\u7ACB\u4E34\u65F6\u5FEB\u7167\u3002SpotPatch \u4F1A\u5BA1\u8BA1 diff\uFF0C\u4E14\u4EC5\u5728\u6240\u6709\u53EF\u4FE1 required checks \u901A\u8FC7\u540E\u624D\u5199\u5165\u4E1A\u52A1\u4ED3\u5E93\u3002",
1199
- cancel: "\u53D6\u6D88",
1200
- confirm: "\u786E\u8BA4\u5E76\u53D1\u9001",
1201
- error: (code) => code === "EXTERNAL_AGENT_BUSY" ? "\u4E3B\u52A8 Agent \u4ECD\u5728\u5DE5\u4F5C\uFF0C\u6216\u5B58\u5728\u5C1A\u672A\u6838\u5BF9\u7684\u6295\u9012\u672A\u77E5\u72B6\u6001\u3002" : code === "ACTIVE_DISPATCH_INVALID" ? "\u4E3B\u52A8\u6295\u9012\u72B6\u6001\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u7EE7\u7EED\u3002" : code === "HANDOFF_SOURCE_STALE" ? "\u9009\u4E2D\u6E90\u7801\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u5F53\u524D\u7EC4\u4EF6\u3002" : code === "HANDOFF_RESPONSE_TOO_LARGE" ? "\u4EA4\u63A5\u5185\u5BB9\u8D85\u8FC7\u4E0A\u9650\uFF0C\u8BF7\u51CF\u5C11\u6240\u9009\u4E0A\u4E0B\u6587\u3002" : code === "EXTERNAL_HANDOFF_DISABLED" ? "\u53EF\u4FE1\u9879\u76EE\u914D\u7F6E\u6CA1\u6709\u542F\u7528\u5916\u90E8 Agent \u4EA4\u63A5\u3002" : code === "EXTERNAL_HANDOFF_UNAVAILABLE" || code === "SESSION_CLOSED" ? "\u672C\u5730\u5916\u90E8 Agent Broker \u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CD\u542F\u5F00\u53D1\u670D\u52A1\u3002" : "\u5916\u90E8 Agent \u4EA4\u63A5\u8BF7\u6C42\u5931\u8D25\uFF0C\u672A\u53D1\u5E03\u4EFB\u4F55\u90E8\u5206 revision\u3002"
1352
+ resultHeader: (revision, outcome) => `revision ${String(revision)} · 验证 ${outcome}`,
1353
+ resultDisposition: (outcome) => outcome === "passed" ? "验证通过,修改已写入项目。" : `这只是候选 diff;验证状态为 ${outcome},因此没有写入项目。`,
1354
+ resultTiming: (stage, durationMs) => `耗时 ${stage}: ${String(durationMs)} ms`,
1355
+ description: "有主动适配器就立即派发;未连接主动适配器时,诚实降级为项目 Agent 收件箱。",
1356
+ ready: "本地 Broker 已就绪。只有连接器读取或等待后,才能证明发生过连接。",
1357
+ readyWaiting: (count) => `当前有 ${String(count)} 个连接器等待请求,发布后可立即唤醒。`,
1358
+ activeReady: (agent) => `${agent} 已连接并处于空闲状态,下次发送会立即派发。`,
1359
+ publishing: "正在重新授权当前源码并发布交接……",
1360
+ published: (revision, expiresAt) => `交接 revision ${String(revision)} 可读取至 ${expiresAt};这不表示代码已被修改。`,
1361
+ queued: (agent, revision) => `交接 revision ${String(revision)} 已为 ${agent} 预留。`,
1362
+ dispatching: (agent, revision) => `正在把 revision ${String(revision)} 派发给 ${agent}……`,
1363
+ dispatched: (agent, revision) => agent === "Claude Code" ? `revision ${String(revision)} 已写入 Claude Channel transport;当前没有模型 ACK。` : `Codex App Server 已接受 revision ${String(revision)},正在等待匹配 turn 启动。`,
1364
+ working: (agent, revision) => `${agent} 正在处理 revision ${String(revision)}。`,
1365
+ completed: (agent, revision) => `${agent} 已正常结束 revision ${String(revision)}。请核对 diff 和检查结果;这不证明修改要求已经正确完成。`,
1366
+ failed: (agent, revision) => `${agent} 已将 revision ${String(revision)} 报告为失败,连接器已恢复空闲。`,
1367
+ unknown: (agent, revision) => `revision ${String(revision)} 向 ${agent} 的投递结果不确定。受管 writer 已停止,请先核对工作区再允许新任务。`,
1368
+ unknownResolved: (agent, revision) => `${agent} 的 revision ${String(revision)} 仍记为投递未知,但工作区核对已经确认;现在可以重新连接或发布新的收件箱任务。`,
1369
+ pickedUp: (revision, count, time) => `交接 revision ${String(revision)} 已被 ${String(count)} 个连接器实例取走${time === void 0 ? "。" : `,最近取件于 ${time}。`}这不能证明代码已经修改。`,
1370
+ expired: (revision) => `交接 revision ${String(revision)} 已过期,请核对当前选择后重新发布。`,
1371
+ superseded: (revision) => `交接 revision ${String(revision)} 已被更新版本取代。`,
1372
+ send: "发送给 Agent",
1373
+ inboxSend: "发布到 Agent 收件箱",
1374
+ retry: "重试同一次发送",
1375
+ refresh: "刷新取件状态",
1376
+ resolveUnknown: "已核对工作区,允许新任务",
1377
+ settingsTitle: "项目连接设置",
1378
+ settingsHelp: "Codex 受管模式由当前开发服务托管,浏览器不会获得厂商凭证或任意命令权限;通用与 attached adapter 仍按真实能力降级为收件箱。",
1379
+ disclosureTitle: "确认发布到外部 Agent",
1380
+ disclosureIntro: (targets, files) => `将发布 ${String(targets)} 个目标,涉及 ${String(files)} 个项目相对文件。`,
1381
+ disclosureData: "交接包含修改说明、组件与页面上下文、已清洗的 DOM/CSS,以及本地 Node 服务重新读取的有界源码。",
1382
+ disclosureInbox: "15 分钟有效期内,本项目中任何已配置的 SpotPatch Connector 都可能读取。",
1383
+ disclosureProvider: "Agent 宿主可能依据其产品数据策略,将内容发送给云端模型服务。",
1384
+ disclosureNoGuarantee: "外部修改不具备 SpotPatch 内建 Agent 的 worktree、Apply 或 Revert 保证。",
1385
+ disclosureManagedGuarantee: "受管 Codex 只写独立临时快照。SpotPatch 会审计 diff,且仅在所有可信 required checks 通过后才写入业务仓库。",
1386
+ cancel: "取消",
1387
+ confirm: "确认并发送",
1388
+ error: (code) => code === "EXTERNAL_AGENT_BUSY" ? "主动 Agent 仍在工作,或存在尚未核对的投递未知状态。" : code === "ACTIVE_DISPATCH_INVALID" ? "主动投递状态已经变化,请刷新后再继续。" : code === "HANDOFF_SOURCE_STALE" ? "选中源码已经变化,请重新选择当前组件。" : code === "HANDOFF_RESPONSE_TOO_LARGE" ? "交接内容超过上限,请减少所选上下文。" : code === "EXTERNAL_HANDOFF_DISABLED" ? "可信项目配置没有启用外部 Agent 交接。" : code === "EXTERNAL_HANDOFF_UNAVAILABLE" || code === "SESSION_CLOSED" ? "本地外部 Agent Broker 不可用,请重启开发服务。" : "外部 Agent 交接请求失败,未发布任何部分 revision。"
1202
1389
  })
1203
1390
  });
1204
1391
  function createStyles(document) {
1205
1392
  const styles = document.createElement("style");
1206
1393
  styles.textContent = `
1394
+ ${SELECT_PICKER_STYLES}
1207
1395
  .spotpatch-external-handoff { margin-top: 12px; padding: 12px; border: 1px solid var(--spotpatch-border); border-radius: var(--spotpatch-radius-card); background: rgb(82 168 255 / 5%); }
1208
1396
  .spotpatch-external-handoff[hidden] { display: none; }
1209
1397
  .spotpatch-external-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
@@ -1213,8 +1401,10 @@ function createStyles(document) {
1213
1401
  .spotpatch-external-status[data-state="error"] { border-color: var(--spotpatch-danger); color: #fecdd3; }
1214
1402
  .spotpatch-external-status[data-state="picked-up"] { border-color: var(--spotpatch-success); color: #a7f3d0; }
1215
1403
  .spotpatch-external-control { margin-top: 9px; padding: 9px; border: 1px solid var(--spotpatch-border); border-radius: 8px; background: rgb(3 7 18 / 28%); }
1216
- .spotpatch-external-control label { display: grid; gap: 4px; color: var(--spotpatch-text-secondary); font-size: 10px; }
1217
- .spotpatch-external-control select { width: 100%; padding: 6px 8px; border: 1px solid var(--spotpatch-border); border-radius: 6px; background: var(--spotpatch-bg-input); color: var(--spotpatch-text); font: inherit; }
1404
+ .spotpatch-external-control-field { display: grid; gap: 5px; min-width: 0; }
1405
+ .spotpatch-external-control-field + .spotpatch-external-control-field { margin-top: 9px; }
1406
+ .spotpatch-external-control-field > label { color: var(--spotpatch-text-secondary); font-size: 10px; }
1407
+ .spotpatch-external-agent-value { box-sizing: border-box; display: flex; width: 100%; min-height: 38px; align-items: center; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 0 11px; overflow: hidden; color: var(--spotpatch-text); background: rgb(255 255 255 / 3%); font: inherit; text-overflow: ellipsis; white-space: nowrap; }
1218
1408
  .spotpatch-external-control-status { margin: 7px 0 0; color: #cbd5e1; font-size: 10.5px; line-height: 1.5; white-space: pre-wrap; }
1219
1409
  .spotpatch-external-control-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
1220
1410
  .spotpatch-external-control-actions button { padding: 5px 8px; border: 1px solid var(--spotpatch-border); border-radius: 6px; background: var(--spotpatch-bg-active); color: var(--spotpatch-text); font: inherit; font-size: 10px; cursor: pointer; }
@@ -1369,13 +1559,24 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1369
1559
  status.setAttribute("aria-live", "polite");
1370
1560
  const controlRoot = createMarkedElement(document, "div");
1371
1561
  controlRoot.className = "spotpatch-external-control";
1562
+ const agentField = createMarkedElement(document, "div");
1563
+ agentField.className = "spotpatch-external-control-field";
1372
1564
  const agentLabel = createMarkedElement(document, "label");
1373
1565
  const agentLabelText = createMarkedElement(document, "span");
1374
- const agentSelect = createMarkedElement(document, "select");
1375
- const codexOption = createMarkedElement(document, "option");
1376
- codexOption.value = "codex";
1377
- agentSelect.append(codexOption);
1378
- agentLabel.append(agentLabelText, agentSelect);
1566
+ const agentValue = createMarkedElement(document, "div");
1567
+ agentValue.className = "spotpatch-external-agent-value";
1568
+ agentLabel.append(agentLabelText);
1569
+ agentField.append(agentLabel, agentValue);
1570
+ const modelField = createMarkedElement(document, "div");
1571
+ modelField.className = "spotpatch-external-control-field";
1572
+ const modelLabel = createMarkedElement(document, "label");
1573
+ const modelLabelText = createMarkedElement(document, "span");
1574
+ const modelPicker = createSelectPicker(document, options.onViewChange);
1575
+ const modelSelect = modelPicker.select;
1576
+ modelLabel.htmlFor = modelPicker.trigger.id;
1577
+ const modelHint = createMarkedElement(document, "p");
1578
+ modelLabel.append(modelLabelText);
1579
+ modelField.append(modelLabel, modelPicker.root);
1379
1580
  const controlStatus = createMarkedElement(document, "p");
1380
1581
  controlStatus.className = "spotpatch-external-control-status";
1381
1582
  controlStatus.setAttribute("role", "status");
@@ -1398,7 +1599,14 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1398
1599
  const managedResultSummary = createMarkedElement(document, "p");
1399
1600
  const managedResultDiff = createMarkedElement(document, "pre");
1400
1601
  managedResult.append(managedResultTitle, managedResultSummary, managedResultDiff);
1401
- controlRoot.append(agentLabel, controlStatus, controlActions, managedResult);
1602
+ controlRoot.append(
1603
+ agentField,
1604
+ modelField,
1605
+ modelHint,
1606
+ controlStatus,
1607
+ controlActions,
1608
+ managedResult
1609
+ );
1402
1610
  const resolveButton = createButton(document, "", "spotpatch-external-resolve");
1403
1611
  resolveButton.hidden = true;
1404
1612
  const settings = createMarkedElement(document, "details");
@@ -1455,16 +1663,21 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1455
1663
  } catch {
1456
1664
  }
1457
1665
  };
1666
+ const modelPending = () => control?.mode === "managed" && modelSelect.value !== "" && modelSelect.value !== control.requestedModel;
1667
+ const modelChangeBlocksSend = () => activeAdapter?.canDispatch === true && modelPending();
1668
+ const controlPending = () => controlOperationBusy || control?.connectionState === "diagnosing" || control?.connectionState === "connecting" || control?.connectionState === "disconnecting";
1458
1669
  const refreshActions = () => {
1459
1670
  sendButton.textContent = retryable ? messages.retry : activeAdapter?.canDispatch === true ? messages.send : messages.inboxSend;
1460
- sendButton.disabled = !visible || !brokerReady || !contextReady || operationBusy || !retryable && dispatchBlocksSend;
1671
+ sendButton.disabled = !visible || !brokerReady || !contextReady || operationBusy || modelChangeBlocksSend() || !retryable && dispatchBlocksSend;
1461
1672
  sendButton.setAttribute("aria-busy", String(operationBusy));
1462
1673
  refreshButton.disabled = !visible || operationBusy;
1463
1674
  resolveButton.hidden = !unknownDelivery;
1464
1675
  resolveButton.disabled = !visible || operationBusy || !unknownDelivery;
1465
1676
  };
1466
1677
  const refreshControlActions = () => {
1678
+ connectButton.textContent = modelPending() ? messages.applyModel : messages.connectManaged;
1467
1679
  if (control === void 0) {
1680
+ modelPicker.setDisabled(true);
1468
1681
  connectButton.disabled = true;
1469
1682
  disconnectButton.disabled = true;
1470
1683
  revokeButton.disabled = true;
@@ -1472,8 +1685,11 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1472
1685
  return;
1473
1686
  }
1474
1687
  const state = control.connectionState;
1475
- const operationPending = controlOperationBusy || state === "diagnosing" || state === "connecting" || state === "disconnecting";
1476
- connectButton.disabled = operationPending || state === "ready" || state === "busy";
1688
+ const operationPending = controlPending();
1689
+ modelPicker.setDisabled(
1690
+ controlOperationBusy || state === "busy" || !control.models?.length
1691
+ );
1692
+ connectButton.disabled = operationPending || state === "ready" && !modelPending() || state === "busy";
1477
1693
  disconnectButton.disabled = operationPending || state === "disconnected";
1478
1694
  revokeButton.disabled = operationPending || control.grantState !== "valid";
1479
1695
  const phase = control.task?.managedPhase;
@@ -1524,7 +1740,12 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1524
1740
  disclosureProvider.textContent = messages.disclosureProvider;
1525
1741
  disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
1526
1742
  agentLabelText.textContent = messages.agentLabel;
1527
- codexOption.textContent = messages.codexManaged;
1743
+ modelLabelText.textContent = messages.modelLabel;
1744
+ modelHint.textContent = messages.modelHint;
1745
+ if (modelSelect.options.length === 1 && modelSelect.options[0]?.value === "") {
1746
+ modelSelect.options[0].textContent = messages.modelLoading;
1747
+ }
1748
+ agentValue.textContent = messages.codexManaged;
1528
1749
  connectButton.textContent = messages.connectManaged;
1529
1750
  disconnectButton.textContent = messages.disconnectManaged;
1530
1751
  revokeButton.textContent = messages.revokeManaged;
@@ -1555,10 +1776,30 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1555
1776
  disclosure.addEventListener("keydown", handleDisclosureKeydown);
1556
1777
  settings.addEventListener("toggle", options.onViewChange);
1557
1778
  managedResult.addEventListener("toggle", options.onViewChange);
1779
+ const handleModelChange = () => {
1780
+ refreshControlActions();
1781
+ refreshActions();
1782
+ };
1783
+ modelSelect.addEventListener("change", handleModelChange);
1784
+ const renderModels = () => {
1785
+ const previous = modelSelect.value;
1786
+ const models = control?.models ?? [];
1787
+ modelSelect.replaceChildren(
1788
+ ...(models.length ? models : [""]).map((model) => {
1789
+ const option = createMarkedElement(document, "option");
1790
+ option.value = model;
1791
+ option.textContent = model || messages.modelLoading;
1792
+ return option;
1793
+ })
1794
+ );
1795
+ modelSelect.value = models.includes(previous) ? previous : control?.requestedModel ?? models[0] ?? "";
1796
+ modelPicker.rebuild();
1797
+ };
1798
+ renderModels();
1558
1799
  const unsubscribeLocale = options.subscribeLocale(applyMessages);
1559
1800
  applyMessages();
1560
1801
  status.textContent = messages.ready;
1561
- controlStatus.textContent = options.locale() === "zh-CN" ? "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8FDE\u63A5\u72B6\u6001\u2026\u2026" : "Reading local connection status\u2026";
1802
+ controlStatus.textContent = options.locale() === "zh-CN" ? "正在读取本地连接状态……" : "Reading local connection status…";
1562
1803
  refreshActions();
1563
1804
  refreshControlActions();
1564
1805
  const summaryMessage = (summary) => summary.state === "expired" ? messages.expired(summary.revision) : summary.state === "superseded" ? messages.superseded(summary.revision) : summary.pickupCount > 0 ? messages.pickedUp(
@@ -1597,6 +1838,7 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1597
1838
  resolveButton,
1598
1839
  cancelManagedButton,
1599
1840
  connectButton,
1841
+ readModel: () => modelSelect.value || void 0,
1600
1842
  disconnectButton,
1601
1843
  revokeButton,
1602
1844
  confirmDisclosure(annotation) {
@@ -1607,7 +1849,7 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1607
1849
  annotation.targets.length,
1608
1850
  paths.length
1609
1851
  );
1610
- disclosureFiles.textContent = paths.length === 0 ? "\u2014" : paths.join("\n");
1852
+ disclosureFiles.textContent = paths.length === 0 ? "—" : paths.join("\n");
1611
1853
  disclosure.hidden = false;
1612
1854
  const rootNode = root.getRootNode();
1613
1855
  const activeElement = "activeElement" in rootNode ? rootNode.activeElement : document.activeElement;
@@ -1653,20 +1895,24 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1653
1895
  },
1654
1896
  renderControlStatus(value) {
1655
1897
  control = value;
1898
+ renderModels();
1656
1899
  controlStatus.textContent = controlStatusText(value, messages, framework);
1657
1900
  controlStatus.dataset.state = value.connectionState;
1658
1901
  disclosureNoGuarantee.textContent = value.mode === "managed" ? messages.disclosureManagedGuarantee : messages.disclosureNoGuarantee;
1659
1902
  refreshControlActions();
1903
+ refreshActions();
1660
1904
  options.onControlChange?.(value);
1661
1905
  options.onViewChange();
1662
1906
  },
1663
1907
  renderControlUnavailable() {
1664
1908
  control = void 0;
1909
+ renderModels();
1665
1910
  controlStatus.dataset.state = "unavailable";
1666
1911
  controlStatus.textContent = messages.controlUnavailable;
1667
1912
  disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
1668
1913
  refreshControlActions();
1669
1914
  options.onControlChange?.(void 0);
1915
+ refreshActions();
1670
1916
  options.onViewChange();
1671
1917
  },
1672
1918
  renderManagedResult(result) {
@@ -1694,6 +1940,7 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1694
1940
  setControlBusy(nextBusy) {
1695
1941
  controlOperationBusy = nextBusy;
1696
1942
  refreshControlActions();
1943
+ refreshActions();
1697
1944
  },
1698
1945
  setContextReady(ready) {
1699
1946
  contextReady = ready;
@@ -1711,6 +1958,8 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
1711
1958
  disclosure.removeEventListener("keydown", handleDisclosureKeydown);
1712
1959
  settings.removeEventListener("toggle", options.onViewChange);
1713
1960
  managedResult.removeEventListener("toggle", options.onViewChange);
1961
+ modelSelect.removeEventListener("change", handleModelChange);
1962
+ modelPicker.dispose();
1714
1963
  unsubscribeLocale();
1715
1964
  }
1716
1965
  });