@spotpatch/runtime 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-AXKYJRAM.js → chunk-7ES63LA7.js} +2 -13
- package/dist/chunk-7ES63LA7.js.map +1 -0
- package/dist/chunk-NII5OBAN.js +14 -0
- package/dist/chunk-NII5OBAN.js.map +1 -0
- package/dist/chunk-XBD55BCF.js +14 -0
- package/dist/chunk-XBD55BCF.js.map +1 -0
- package/dist/data-flow-panel.js +5 -3
- package/dist/data-flow-panel.js.map +1 -1
- package/dist/external-handoff-panel.cjs +1002 -0
- package/dist/external-handoff-panel.cjs.map +1 -0
- package/dist/external-handoff-panel.d.cts +36 -0
- package/dist/external-handoff-panel.d.ts +36 -0
- package/dist/external-handoff-panel.js +959 -0
- package/dist/external-handoff-panel.js.map +1 -0
- package/dist/index.cjs +102 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +104 -7
- package/dist/index.js.map +1 -1
- package/package.json +15 -2
- package/dist/chunk-AXKYJRAM.js.map +0 -1
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/external-handoff-panel-entry.ts
|
|
21
|
+
var external_handoff_panel_entry_exports = {};
|
|
22
|
+
__export(external_handoff_panel_entry_exports, {
|
|
23
|
+
createExternalHandoffPanel: () => createExternalHandoffPanel,
|
|
24
|
+
createExternalHandoffWorkflow: () => createExternalHandoffWorkflow,
|
|
25
|
+
getExternalHandoffExtension: () => getExternalHandoffExtension,
|
|
26
|
+
registerExternalHandoffExtension: () => registerExternalHandoffExtension
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(external_handoff_panel_entry_exports);
|
|
29
|
+
|
|
30
|
+
// src/controller/external-handoff-workflow.ts
|
|
31
|
+
var import_external_handoff_browser = require("@spotpatch/shared/external-handoff-browser");
|
|
32
|
+
var MAX_SUMMARY_RESPONSE_BYTES = 64 * 1024;
|
|
33
|
+
var OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{22,128}$/u;
|
|
34
|
+
var ExternalHandoffApiError = class extends Error {
|
|
35
|
+
constructor(code) {
|
|
36
|
+
super("SpotPatch external handoff API request failed.");
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.name = "ExternalHandoffApiError";
|
|
39
|
+
}
|
|
40
|
+
code;
|
|
41
|
+
};
|
|
42
|
+
function record(value) {
|
|
43
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
function exactKeys(value, keys) {
|
|
46
|
+
const actual = Object.keys(value).sort();
|
|
47
|
+
const expected = [...keys].sort();
|
|
48
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
49
|
+
}
|
|
50
|
+
function validTimestamp(value) {
|
|
51
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
52
|
+
}
|
|
53
|
+
function parseActiveAdapter(value) {
|
|
54
|
+
if (value === null) return null;
|
|
55
|
+
if (!record(value) || !exactKeys(value, ["canDispatch", "connectedAt", "kind", "state", "updatedAt"]) || value.kind !== "claude-channel" && value.kind !== "codex-app-server" || value.state !== "ready" && value.state !== "busy" && value.state !== "blocked" || typeof value.canDispatch !== "boolean" || value.canDispatch !== (value.state === "ready") || !validTimestamp(value.connectedAt) || !validTimestamp(value.updatedAt)) {
|
|
56
|
+
throw new ExternalHandoffApiError();
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
kind: value.kind,
|
|
60
|
+
state: value.state,
|
|
61
|
+
canDispatch: value.canDispatch,
|
|
62
|
+
connectedAt: value.connectedAt,
|
|
63
|
+
updatedAt: value.updatedAt
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function parseDispatch(value) {
|
|
67
|
+
if (value === null) return null;
|
|
68
|
+
if (!record(value)) throw new ExternalHandoffApiError();
|
|
69
|
+
if (!exactKeys(value, ["adapterKind", "phase", "revision", "updatedAt"]) || value.adapterKind !== "claude-channel" && value.adapterKind !== "codex-app-server" || !Number.isSafeInteger(value.revision) || value.revision <= 0 || value.phase !== "queued" && value.phase !== "dispatching" && value.phase !== "dispatched" && value.phase !== "working" && value.phase !== "completed" && value.phase !== "failed" && value.phase !== "delivery-unknown" || !validTimestamp(value.updatedAt)) {
|
|
70
|
+
throw new ExternalHandoffApiError();
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
adapterKind: value.adapterKind,
|
|
74
|
+
revision: value.revision,
|
|
75
|
+
phase: value.phase,
|
|
76
|
+
updatedAt: value.updatedAt
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function parseCapability(value) {
|
|
80
|
+
if (!record(value) || !exactKeys(value, [
|
|
81
|
+
"activeAdapter",
|
|
82
|
+
"activeWaitCount",
|
|
83
|
+
"brokerProtocolVersion",
|
|
84
|
+
"brokerReady",
|
|
85
|
+
"dispatch",
|
|
86
|
+
"enabled",
|
|
87
|
+
"snapshotSchemaVersion"
|
|
88
|
+
]) || value.enabled !== true || typeof value.brokerReady !== "boolean" || !Number.isSafeInteger(value.activeWaitCount) || value.activeWaitCount < 0 || value.activeWaitCount > import_external_handoff_browser.EXTERNAL_HANDOFF_LIMITS.maximumWaiters || value.snapshotSchemaVersion !== import_external_handoff_browser.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION || value.brokerProtocolVersion !== import_external_handoff_browser.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION) {
|
|
89
|
+
throw new ExternalHandoffApiError();
|
|
90
|
+
}
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
enabled: true,
|
|
93
|
+
brokerReady: value.brokerReady,
|
|
94
|
+
activeWaitCount: value.activeWaitCount,
|
|
95
|
+
activeAdapter: parseActiveAdapter(value.activeAdapter),
|
|
96
|
+
dispatch: parseDispatch(value.dispatch),
|
|
97
|
+
snapshotSchemaVersion: import_external_handoff_browser.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
|
|
98
|
+
brokerProtocolVersion: import_external_handoff_browser.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function parsePublishResult(value) {
|
|
102
|
+
if (!record(value) || !exactKeys(value, ["delivery", "handoff", "replayed"]) || typeof value.replayed !== "boolean" || !record(value.delivery)) {
|
|
103
|
+
throw new ExternalHandoffApiError();
|
|
104
|
+
}
|
|
105
|
+
const handoff = parseSummary(value.handoff);
|
|
106
|
+
if (value.delivery.mode === "inbox") {
|
|
107
|
+
if (!exactKeys(value.delivery, ["mode"])) {
|
|
108
|
+
throw new ExternalHandoffApiError();
|
|
109
|
+
}
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
handoff,
|
|
112
|
+
delivery: Object.freeze({ mode: "inbox" }),
|
|
113
|
+
replayed: value.replayed
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (value.delivery.mode !== "active" || !exactKeys(value.delivery, ["adapter", "dispatch", "mode"])) {
|
|
117
|
+
throw new ExternalHandoffApiError();
|
|
118
|
+
}
|
|
119
|
+
const adapter = parseActiveAdapter(value.delivery.adapter);
|
|
120
|
+
const dispatch = parseDispatch(value.delivery.dispatch);
|
|
121
|
+
if (adapter === null || dispatch?.revision !== handoff.revision) {
|
|
122
|
+
throw new ExternalHandoffApiError();
|
|
123
|
+
}
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
handoff,
|
|
126
|
+
delivery: Object.freeze({ mode: "active", adapter, dispatch }),
|
|
127
|
+
replayed: value.replayed
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function parseStatusResult(value) {
|
|
131
|
+
if (!record(value) || !exactKeys(value, ["activeAdapter", "dispatch", "handoff"])) {
|
|
132
|
+
throw new ExternalHandoffApiError();
|
|
133
|
+
}
|
|
134
|
+
const handoff = parseSummary(value.handoff);
|
|
135
|
+
const dispatch = parseDispatch(value.dispatch);
|
|
136
|
+
if (dispatch !== null && dispatch.revision !== handoff.revision) {
|
|
137
|
+
throw new ExternalHandoffApiError();
|
|
138
|
+
}
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
handoff,
|
|
141
|
+
activeAdapter: parseActiveAdapter(value.activeAdapter),
|
|
142
|
+
dispatch
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function parseSummary(value) {
|
|
146
|
+
if (!record(value)) throw new ExternalHandoffApiError();
|
|
147
|
+
const expectedKeys = [
|
|
148
|
+
"cursor",
|
|
149
|
+
"expiresAt",
|
|
150
|
+
"framework",
|
|
151
|
+
"page",
|
|
152
|
+
"pickupCount",
|
|
153
|
+
"publishedAt",
|
|
154
|
+
"revision",
|
|
155
|
+
"sessionId",
|
|
156
|
+
"state",
|
|
157
|
+
"targetCount",
|
|
158
|
+
...value.pickedUpAt === void 0 ? [] : ["pickedUpAt"]
|
|
159
|
+
];
|
|
160
|
+
if (!exactKeys(value, expectedKeys) || typeof value.sessionId !== "string" || !OPAQUE_ID_PATTERN.test(value.sessionId) || value.framework !== "vite" && value.framework !== "next" || !Number.isSafeInteger(value.revision) || value.revision <= 0 || typeof value.cursor !== "string" || !OPAQUE_ID_PATTERN.test(value.cursor) || !Number.isSafeInteger(value.targetCount) || value.targetCount <= 0 || !record(value.page) || !exactKeys(value.page, ["origin", "pathname"]) || typeof value.page.origin !== "string" || typeof value.page.pathname !== "string" || !validTimestamp(value.publishedAt) || !validTimestamp(value.expiresAt) || value.state !== "available" && value.state !== "expired" && value.state !== "superseded" || !Number.isSafeInteger(value.pickupCount) || value.pickupCount < 0 || value.pickupCount > import_external_handoff_browser.EXTERNAL_HANDOFF_LIMITS.maximumConnectorReceipts || value.pickedUpAt !== void 0 && !validTimestamp(value.pickedUpAt)) {
|
|
161
|
+
throw new ExternalHandoffApiError();
|
|
162
|
+
}
|
|
163
|
+
return Object.freeze({
|
|
164
|
+
sessionId: value.sessionId,
|
|
165
|
+
framework: value.framework,
|
|
166
|
+
revision: value.revision,
|
|
167
|
+
cursor: value.cursor,
|
|
168
|
+
targetCount: value.targetCount,
|
|
169
|
+
page: Object.freeze({ origin: value.page.origin, pathname: value.page.pathname }),
|
|
170
|
+
publishedAt: value.publishedAt,
|
|
171
|
+
expiresAt: value.expiresAt,
|
|
172
|
+
state: value.state,
|
|
173
|
+
pickupCount: value.pickupCount,
|
|
174
|
+
...value.pickedUpAt === void 0 ? {} : { pickedUpAt: value.pickedUpAt }
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async function boundedJson(response) {
|
|
178
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
179
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_SUMMARY_RESPONSE_BYTES) {
|
|
180
|
+
await response.body?.cancel();
|
|
181
|
+
throw new ExternalHandoffApiError();
|
|
182
|
+
}
|
|
183
|
+
if (response.body === null) throw new ExternalHandoffApiError();
|
|
184
|
+
const reader = response.body.getReader();
|
|
185
|
+
const chunks = [];
|
|
186
|
+
let total = 0;
|
|
187
|
+
try {
|
|
188
|
+
for (; ; ) {
|
|
189
|
+
const chunk = await reader.read();
|
|
190
|
+
if (chunk.done) break;
|
|
191
|
+
total += chunk.value.byteLength;
|
|
192
|
+
if (total > MAX_SUMMARY_RESPONSE_BYTES) {
|
|
193
|
+
await reader.cancel();
|
|
194
|
+
throw new ExternalHandoffApiError();
|
|
195
|
+
}
|
|
196
|
+
chunks.push(chunk.value);
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
reader.releaseLock();
|
|
200
|
+
}
|
|
201
|
+
const payload = new Uint8Array(total);
|
|
202
|
+
let offset = 0;
|
|
203
|
+
for (const chunk of chunks) {
|
|
204
|
+
payload.set(chunk, offset);
|
|
205
|
+
offset += chunk.byteLength;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
return JSON.parse(
|
|
209
|
+
new TextDecoder("utf-8", { fatal: true }).decode(payload)
|
|
210
|
+
);
|
|
211
|
+
} catch {
|
|
212
|
+
throw new ExternalHandoffApiError();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function failureCode(value) {
|
|
216
|
+
return record(value) && value.ok === false && record(value.error) && (0, import_external_handoff_browser.isErrorCode)(value.error.code) ? value.error.code : void 0;
|
|
217
|
+
}
|
|
218
|
+
function successData(value) {
|
|
219
|
+
if (!record(value) || !exactKeys(value, ["data", "ok"]) || value.ok !== true) {
|
|
220
|
+
throw new ExternalHandoffApiError();
|
|
221
|
+
}
|
|
222
|
+
return value.data;
|
|
223
|
+
}
|
|
224
|
+
function omitBrowserCode(annotation) {
|
|
225
|
+
return {
|
|
226
|
+
...annotation,
|
|
227
|
+
targets: annotation.targets.map((target) => ({
|
|
228
|
+
instruction: target.instruction,
|
|
229
|
+
...target.page === void 0 ? {} : { page: target.page },
|
|
230
|
+
source: target.source,
|
|
231
|
+
react: target.react,
|
|
232
|
+
element: target.element,
|
|
233
|
+
styles: target.styles,
|
|
234
|
+
warnings: target.warnings
|
|
235
|
+
}))
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function createRequestId(window) {
|
|
239
|
+
const bytes = new Uint8Array(24);
|
|
240
|
+
window.crypto.getRandomValues(bytes);
|
|
241
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
242
|
+
}
|
|
243
|
+
function dispatchIsPending(dispatch) {
|
|
244
|
+
return dispatch !== null && dispatch.phase !== "completed" && dispatch.phase !== "failed" && dispatch.phase !== "delivery-unknown";
|
|
245
|
+
}
|
|
246
|
+
function api(options) {
|
|
247
|
+
const pending = /* @__PURE__ */ new Set();
|
|
248
|
+
const request = async (endpoint, body) => {
|
|
249
|
+
const controller = new AbortController();
|
|
250
|
+
pending.add(controller);
|
|
251
|
+
try {
|
|
252
|
+
const response = await options.fetch(endpoint, {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: {
|
|
255
|
+
"Content-Type": "application/json",
|
|
256
|
+
[import_external_handoff_browser.SPOTPATCH_TOKEN_HEADER]: options.sessionToken
|
|
257
|
+
},
|
|
258
|
+
body: JSON.stringify(body),
|
|
259
|
+
signal: controller.signal
|
|
260
|
+
});
|
|
261
|
+
const envelope = await boundedJson(response);
|
|
262
|
+
if (!response.ok) throw new ExternalHandoffApiError(failureCode(envelope));
|
|
263
|
+
return successData(envelope);
|
|
264
|
+
} finally {
|
|
265
|
+
pending.delete(controller);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
return Object.freeze({
|
|
269
|
+
cancel() {
|
|
270
|
+
for (const controller of pending) controller.abort();
|
|
271
|
+
pending.clear();
|
|
272
|
+
},
|
|
273
|
+
async capability() {
|
|
274
|
+
return parseCapability(
|
|
275
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalHandoffCapability, {})
|
|
276
|
+
);
|
|
277
|
+
},
|
|
278
|
+
async publish(requestId, annotation) {
|
|
279
|
+
return parsePublishResult(
|
|
280
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalHandoffPublish, {
|
|
281
|
+
annotation: omitBrowserCode(annotation),
|
|
282
|
+
requestId
|
|
283
|
+
})
|
|
284
|
+
);
|
|
285
|
+
},
|
|
286
|
+
async status(cursor) {
|
|
287
|
+
return parseStatusResult(
|
|
288
|
+
await request(
|
|
289
|
+
import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalHandoffStatus,
|
|
290
|
+
cursor === void 0 ? {} : { cursor }
|
|
291
|
+
)
|
|
292
|
+
);
|
|
293
|
+
},
|
|
294
|
+
async resolveDelivery(cursor) {
|
|
295
|
+
return parseStatusResult(
|
|
296
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery, {
|
|
297
|
+
confirmation: "workspace-reviewed",
|
|
298
|
+
cursor
|
|
299
|
+
})
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, sessionToken, window) {
|
|
305
|
+
const options = {
|
|
306
|
+
fetch,
|
|
307
|
+
panel,
|
|
308
|
+
selectedAnnotation,
|
|
309
|
+
sessionToken,
|
|
310
|
+
window
|
|
311
|
+
};
|
|
312
|
+
const client = api(options);
|
|
313
|
+
const timers = /* @__PURE__ */ new Set();
|
|
314
|
+
const lifecycle = {
|
|
315
|
+
disposed: false,
|
|
316
|
+
mounted: false,
|
|
317
|
+
operation: 0,
|
|
318
|
+
userActionPending: false
|
|
319
|
+
};
|
|
320
|
+
let capability;
|
|
321
|
+
let current;
|
|
322
|
+
let retryablePublish;
|
|
323
|
+
const clearTimers = () => {
|
|
324
|
+
for (const timer of timers) options.window.clearTimeout(timer);
|
|
325
|
+
timers.clear();
|
|
326
|
+
};
|
|
327
|
+
const errorCode = (error) => error instanceof ExternalHandoffApiError ? error.code : void 0;
|
|
328
|
+
const restorePanel = () => {
|
|
329
|
+
if (current !== void 0) options.panel.renderStatus(current);
|
|
330
|
+
else if (capability !== void 0) options.panel.renderCapability(capability);
|
|
331
|
+
};
|
|
332
|
+
const refreshCapability = async (revision) => {
|
|
333
|
+
try {
|
|
334
|
+
const result = await client.capability();
|
|
335
|
+
if (lifecycle.disposed || revision !== lifecycle.operation) return;
|
|
336
|
+
capability = result;
|
|
337
|
+
options.panel.renderCapability(result);
|
|
338
|
+
if (result.dispatch !== null) {
|
|
339
|
+
const status = await client.status();
|
|
340
|
+
if (revision !== lifecycle.operation) return;
|
|
341
|
+
current = status;
|
|
342
|
+
options.panel.renderStatus(status);
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (lifecycle.disposed || revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError")
|
|
346
|
+
return;
|
|
347
|
+
options.panel.renderError(errorCode(error));
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
const refreshStatus = async (revision) => {
|
|
351
|
+
if (current === void 0) {
|
|
352
|
+
await refreshCapability(revision);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
const result = await client.status(current.handoff.cursor);
|
|
357
|
+
if (lifecycle.disposed || revision !== lifecycle.operation) return;
|
|
358
|
+
current = result;
|
|
359
|
+
options.panel.renderStatus(result);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (lifecycle.disposed || revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError")
|
|
362
|
+
return;
|
|
363
|
+
options.panel.renderError(errorCode(error));
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const scheduleRefresh = (delay, continueWhilePending) => {
|
|
367
|
+
const revision = lifecycle.operation;
|
|
368
|
+
const timer = options.window.setTimeout(() => {
|
|
369
|
+
timers.delete(timer);
|
|
370
|
+
void refreshStatus(revision).then(() => {
|
|
371
|
+
if (continueWhilePending && !lifecycle.disposed && revision === lifecycle.operation && !lifecycle.userActionPending && current !== void 0 && dispatchIsPending(current.dispatch)) {
|
|
372
|
+
scheduleRefresh(
|
|
373
|
+
import_external_handoff_browser.EXTERNAL_HANDOFF_LIMITS.activeStatusPollMs,
|
|
374
|
+
continueWhilePending
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}, delay);
|
|
379
|
+
timers.add(timer);
|
|
380
|
+
};
|
|
381
|
+
const handleSend = async () => {
|
|
382
|
+
if (lifecycle.userActionPending) return;
|
|
383
|
+
lifecycle.userActionPending = true;
|
|
384
|
+
let pending = retryablePublish;
|
|
385
|
+
if (pending === void 0) {
|
|
386
|
+
const annotation = options.selectedAnnotation();
|
|
387
|
+
if (annotation === void 0) {
|
|
388
|
+
lifecycle.userActionPending = false;
|
|
389
|
+
options.panel.renderError(import_external_handoff_browser.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
const disclosureRevision = lifecycle.operation;
|
|
393
|
+
options.panel.setBusy(true);
|
|
394
|
+
const confirmed = await options.panel.confirmDisclosure(annotation);
|
|
395
|
+
if (!confirmed || disclosureRevision !== lifecycle.operation) {
|
|
396
|
+
lifecycle.userActionPending = false;
|
|
397
|
+
options.panel.setBusy(false);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
pending = Object.freeze({
|
|
401
|
+
annotation,
|
|
402
|
+
requestId: createRequestId(options.window)
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
retryablePublish = pending;
|
|
406
|
+
lifecycle.operation += 1;
|
|
407
|
+
const revision = lifecycle.operation;
|
|
408
|
+
clearTimers();
|
|
409
|
+
client.cancel();
|
|
410
|
+
options.panel.renderPublishing();
|
|
411
|
+
try {
|
|
412
|
+
const result = await client.publish(pending.requestId, pending.annotation);
|
|
413
|
+
if (revision !== lifecycle.operation) return;
|
|
414
|
+
retryablePublish = void 0;
|
|
415
|
+
current = Object.freeze({
|
|
416
|
+
handoff: result.handoff,
|
|
417
|
+
activeAdapter: result.delivery.mode === "active" ? result.delivery.adapter : null,
|
|
418
|
+
dispatch: result.delivery.mode === "active" ? result.delivery.dispatch : null
|
|
419
|
+
});
|
|
420
|
+
options.panel.renderPublishResult(result);
|
|
421
|
+
scheduleRefresh(
|
|
422
|
+
result.delivery.mode === "active" ? import_external_handoff_browser.EXTERNAL_HANDOFF_LIMITS.activeStatusPollMs : 500,
|
|
423
|
+
result.delivery.mode === "active"
|
|
424
|
+
);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
if (revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError")
|
|
427
|
+
return;
|
|
428
|
+
const code = errorCode(error);
|
|
429
|
+
const retryable = code === void 0;
|
|
430
|
+
if (!retryable) retryablePublish = void 0;
|
|
431
|
+
options.panel.renderError(code, retryable);
|
|
432
|
+
} finally {
|
|
433
|
+
if (revision === lifecycle.operation) {
|
|
434
|
+
lifecycle.userActionPending = false;
|
|
435
|
+
options.panel.setBusy(false);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const handleRefresh = async () => {
|
|
440
|
+
if (lifecycle.userActionPending) return;
|
|
441
|
+
lifecycle.userActionPending = true;
|
|
442
|
+
lifecycle.operation += 1;
|
|
443
|
+
const revision = lifecycle.operation;
|
|
444
|
+
clearTimers();
|
|
445
|
+
client.cancel();
|
|
446
|
+
options.panel.setBusy(true);
|
|
447
|
+
try {
|
|
448
|
+
await refreshStatus(revision);
|
|
449
|
+
} finally {
|
|
450
|
+
if (!lifecycle.disposed && revision === lifecycle.operation) {
|
|
451
|
+
lifecycle.userActionPending = false;
|
|
452
|
+
options.panel.setBusy(false);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
const handleResolveDelivery = async () => {
|
|
457
|
+
if (lifecycle.userActionPending || current === void 0 || current.dispatch?.phase !== "delivery-unknown") {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
lifecycle.userActionPending = true;
|
|
461
|
+
lifecycle.operation += 1;
|
|
462
|
+
const revision = lifecycle.operation;
|
|
463
|
+
clearTimers();
|
|
464
|
+
client.cancel();
|
|
465
|
+
options.panel.setBusy(true);
|
|
466
|
+
try {
|
|
467
|
+
const result = await client.resolveDelivery(current.handoff.cursor);
|
|
468
|
+
if (lifecycle.disposed || revision !== lifecycle.operation) return;
|
|
469
|
+
current = result;
|
|
470
|
+
options.panel.renderStatus(result);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
if (lifecycle.disposed || revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError") {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
options.panel.renderError(errorCode(error));
|
|
476
|
+
} finally {
|
|
477
|
+
if (!lifecycle.disposed && revision === lifecycle.operation) {
|
|
478
|
+
lifecycle.userActionPending = false;
|
|
479
|
+
options.panel.setBusy(false);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const handleSendClick = () => {
|
|
484
|
+
void handleSend();
|
|
485
|
+
};
|
|
486
|
+
const handleRefreshClick = () => {
|
|
487
|
+
void handleRefresh();
|
|
488
|
+
};
|
|
489
|
+
const handleResolveClick = () => {
|
|
490
|
+
void handleResolveDelivery();
|
|
491
|
+
};
|
|
492
|
+
const cancelPending = () => {
|
|
493
|
+
lifecycle.operation += 1;
|
|
494
|
+
lifecycle.userActionPending = false;
|
|
495
|
+
retryablePublish = void 0;
|
|
496
|
+
clearTimers();
|
|
497
|
+
client.cancel();
|
|
498
|
+
restorePanel();
|
|
499
|
+
if (capability === void 0 && lifecycle.mounted && !lifecycle.disposed) {
|
|
500
|
+
void refreshCapability(lifecycle.operation);
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
return Object.freeze({
|
|
504
|
+
mount() {
|
|
505
|
+
if (lifecycle.mounted || lifecycle.disposed) return;
|
|
506
|
+
lifecycle.mounted = true;
|
|
507
|
+
options.panel.sendButton.addEventListener("click", handleSendClick);
|
|
508
|
+
options.panel.refreshButton.addEventListener("click", handleRefreshClick);
|
|
509
|
+
options.panel.resolveButton.addEventListener("click", handleResolveClick);
|
|
510
|
+
void refreshCapability(lifecycle.operation);
|
|
511
|
+
},
|
|
512
|
+
cancelPending,
|
|
513
|
+
dispose() {
|
|
514
|
+
if (lifecycle.disposed) return;
|
|
515
|
+
lifecycle.disposed = true;
|
|
516
|
+
lifecycle.operation += 1;
|
|
517
|
+
lifecycle.userActionPending = false;
|
|
518
|
+
retryablePublish = void 0;
|
|
519
|
+
clearTimers();
|
|
520
|
+
client.cancel();
|
|
521
|
+
options.panel.sendButton.removeEventListener("click", handleSendClick);
|
|
522
|
+
options.panel.refreshButton.removeEventListener("click", handleRefreshClick);
|
|
523
|
+
options.panel.resolveButton.removeEventListener("click", handleResolveClick);
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/ui/ui-constants.ts
|
|
529
|
+
var UI_MARKER_ATTRIBUTE = "data-spotpatch-ui";
|
|
530
|
+
var UI_Z_INDEX = Object.freeze({
|
|
531
|
+
highlight: 2147483646,
|
|
532
|
+
controls: 2147483647
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
// src/ui/dom.ts
|
|
536
|
+
function createMarkedElement(document, tagName) {
|
|
537
|
+
const element = document.createElement(tagName);
|
|
538
|
+
element.setAttribute(UI_MARKER_ATTRIBUTE, "");
|
|
539
|
+
return element;
|
|
540
|
+
}
|
|
541
|
+
function createButton(document, label, className = "") {
|
|
542
|
+
const button = createMarkedElement(document, "button");
|
|
543
|
+
button.type = "button";
|
|
544
|
+
button.className = className;
|
|
545
|
+
button.textContent = label;
|
|
546
|
+
return button;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/ui/external-handoff-panel.ts
|
|
550
|
+
var MESSAGES = Object.freeze({
|
|
551
|
+
"en-US": Object.freeze({
|
|
552
|
+
title: "External Agent connection",
|
|
553
|
+
description: "Send through a ready active adapter, or publish to the project Agent inbox when no active adapter is connected.",
|
|
554
|
+
ready: "Local Broker ready. No Agent connection is assumed until a connector reads or waits.",
|
|
555
|
+
readyWaiting: (count) => `${String(count)} active connector wait request(s); publishing can wake them immediately.`,
|
|
556
|
+
activeReady: (agent) => `${agent} is connected and idle. The next send will dispatch immediately.`,
|
|
557
|
+
publishing: "Authorizing current source and publishing the handoff\u2026",
|
|
558
|
+
published: (revision, expiresAt) => `Revision ${String(revision)} is available until ${expiresAt}. It has not been claimed as edited.`,
|
|
559
|
+
queued: (agent, revision) => `Revision ${String(revision)} is reserved for ${agent}.`,
|
|
560
|
+
dispatching: (agent, revision) => `Dispatching revision ${String(revision)} to ${agent}\u2026`,
|
|
561
|
+
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.`,
|
|
562
|
+
working: (agent, revision) => `${agent} is working on revision ${String(revision)}.`,
|
|
563
|
+
completed: (agent, revision) => `${agent} ended revision ${String(revision)} normally. Review the diff and checks; this does not prove the requested change is correct.`,
|
|
564
|
+
failed: (agent, revision) => `${agent} reported revision ${String(revision)} as failed. The connector is idle again.`,
|
|
565
|
+
unknown: (agent, revision) => `Delivery of revision ${String(revision)} to ${agent} is uncertain. The managed writer stopped; review the workspace before allowing another task.`,
|
|
566
|
+
unknownResolved: (agent, revision) => `Revision ${String(revision)} for ${agent} remains delivery-unknown, but the workspace review was confirmed. A new connector or inbox task may now be used.`,
|
|
567
|
+
pickedUp: (revision, count, time) => `Revision ${String(revision)} was picked up by ${String(count)} connector instance(s)${time === void 0 ? "." : `; last pickup ${time}.`} This does not prove a code change.`,
|
|
568
|
+
expired: (revision) => `Revision ${String(revision)} expired. Review the current selection and publish again.`,
|
|
569
|
+
superseded: (revision) => `Revision ${String(revision)} was superseded by a newer handoff.`,
|
|
570
|
+
send: "Send to Agent",
|
|
571
|
+
inboxSend: "Publish to Agent inbox",
|
|
572
|
+
retry: "Retry same send",
|
|
573
|
+
refresh: "Refresh pickup status",
|
|
574
|
+
resolveUnknown: "Workspace reviewed \u2014 allow a new task",
|
|
575
|
+
settingsTitle: "Project connection setup",
|
|
576
|
+
settingsHelp: "Setup commands are project-scoped dry runs; append --write only after review. Claude active mode also needs the shown Research Preview launch flag. Codex active mode is zero-setup: run its explicit connector command and keep it open.",
|
|
577
|
+
disclosureTitle: "Confirm external Agent handoff",
|
|
578
|
+
disclosureIntro: (targets, files) => `Publish ${String(targets)} selected target(s) referencing ${String(files)} relative file(s).`,
|
|
579
|
+
disclosureData: "The handoff includes your instructions, component/page context, sanitized DOM/CSS, and bounded source read again by the local Node service.",
|
|
580
|
+
disclosureInbox: "Any SpotPatch connector configured for this project may read it during the 15-minute lifetime.",
|
|
581
|
+
disclosureProvider: "The Agent host may send the content to its cloud provider under that product's data policy.",
|
|
582
|
+
disclosureNoGuarantee: "External edits do not receive SpotPatch's built-in worktree, Apply, or Revert guarantees.",
|
|
583
|
+
cancel: "Cancel",
|
|
584
|
+
confirm: "Confirm and send",
|
|
585
|
+
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."
|
|
586
|
+
}),
|
|
587
|
+
"zh-CN": Object.freeze({
|
|
588
|
+
title: "\u5916\u90E8 Agent \u8FDE\u63A5",
|
|
589
|
+
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",
|
|
590
|
+
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",
|
|
591
|
+
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`,
|
|
592
|
+
activeReady: (agent) => `${agent} \u5DF2\u8FDE\u63A5\u5E76\u5904\u4E8E\u7A7A\u95F2\u72B6\u6001\uFF0C\u4E0B\u6B21\u53D1\u9001\u4F1A\u7ACB\u5373\u6D3E\u53D1\u3002`,
|
|
593
|
+
publishing: "\u6B63\u5728\u91CD\u65B0\u6388\u6743\u5F53\u524D\u6E90\u7801\u5E76\u53D1\u5E03\u4EA4\u63A5\u2026\u2026",
|
|
594
|
+
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`,
|
|
595
|
+
queued: (agent, revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u4E3A ${agent} \u9884\u7559\u3002`,
|
|
596
|
+
dispatching: (agent, revision) => `\u6B63\u5728\u628A revision ${String(revision)} \u6D3E\u53D1\u7ED9 ${agent}\u2026\u2026`,
|
|
597
|
+
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`,
|
|
598
|
+
working: (agent, revision) => `${agent} \u6B63\u5728\u5904\u7406 revision ${String(revision)}\u3002`,
|
|
599
|
+
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`,
|
|
600
|
+
failed: (agent, revision) => `${agent} \u5DF2\u5C06 revision ${String(revision)} \u62A5\u544A\u4E3A\u5931\u8D25\uFF0C\u8FDE\u63A5\u5668\u5DF2\u6062\u590D\u7A7A\u95F2\u3002`,
|
|
601
|
+
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`,
|
|
602
|
+
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`,
|
|
603
|
+
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`,
|
|
604
|
+
expired: (revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6838\u5BF9\u5F53\u524D\u9009\u62E9\u540E\u91CD\u65B0\u53D1\u5E03\u3002`,
|
|
605
|
+
superseded: (revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u88AB\u66F4\u65B0\u7248\u672C\u53D6\u4EE3\u3002`,
|
|
606
|
+
send: "\u53D1\u9001\u7ED9 Agent",
|
|
607
|
+
inboxSend: "\u53D1\u5E03\u5230 Agent \u6536\u4EF6\u7BB1",
|
|
608
|
+
retry: "\u91CD\u8BD5\u540C\u4E00\u6B21\u53D1\u9001",
|
|
609
|
+
refresh: "\u5237\u65B0\u53D6\u4EF6\u72B6\u6001",
|
|
610
|
+
resolveUnknown: "\u5DF2\u6838\u5BF9\u5DE5\u4F5C\u533A\uFF0C\u5141\u8BB8\u65B0\u4EFB\u52A1",
|
|
611
|
+
settingsTitle: "\u9879\u76EE\u8FDE\u63A5\u8BBE\u7F6E",
|
|
612
|
+
settingsHelp: "setup \u547D\u4EE4\u5747\u4E3A\u9879\u76EE\u7EA7 dry-run\uFF0C\u6838\u5BF9\u540E\u624D\u8FFD\u52A0 --write\u3002Claude \u4E3B\u52A8\u6A21\u5F0F\u8FD8\u9700\u4F7F\u7528\u4E0B\u65B9 Research Preview \u542F\u52A8\u53C2\u6570\uFF1BCodex \u4E3B\u52A8\u6A21\u5F0F\u65E0\u9700 setup\uFF0C\u53EA\u9700\u663E\u5F0F\u542F\u52A8\u5E76\u4FDD\u6301 Connector \u547D\u4EE4\u8FD0\u884C\u3002",
|
|
613
|
+
disclosureTitle: "\u786E\u8BA4\u53D1\u5E03\u5230\u5916\u90E8 Agent",
|
|
614
|
+
disclosureIntro: (targets, files) => `\u5C06\u53D1\u5E03 ${String(targets)} \u4E2A\u76EE\u6807\uFF0C\u6D89\u53CA ${String(files)} \u4E2A\u9879\u76EE\u76F8\u5BF9\u6587\u4EF6\u3002`,
|
|
615
|
+
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",
|
|
616
|
+
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",
|
|
617
|
+
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",
|
|
618
|
+
disclosureNoGuarantee: "\u5916\u90E8\u4FEE\u6539\u4E0D\u5177\u5907 SpotPatch \u5185\u5EFA Agent \u7684 worktree\u3001Apply \u6216 Revert \u4FDD\u8BC1\u3002",
|
|
619
|
+
cancel: "\u53D6\u6D88",
|
|
620
|
+
confirm: "\u786E\u8BA4\u5E76\u53D1\u9001",
|
|
621
|
+
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"
|
|
622
|
+
})
|
|
623
|
+
});
|
|
624
|
+
function createStyles(document) {
|
|
625
|
+
const styles = document.createElement("style");
|
|
626
|
+
styles.textContent = `
|
|
627
|
+
.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%); }
|
|
628
|
+
.spotpatch-external-handoff[hidden] { display: none; }
|
|
629
|
+
.spotpatch-external-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
630
|
+
.spotpatch-external-heading strong { font-size: 12px; font-weight: 680; color: var(--spotpatch-text); }
|
|
631
|
+
.spotpatch-external-description, .spotpatch-external-status, .spotpatch-external-settings p { margin: 6px 0 0; font-size: 11px; line-height: 1.5; color: var(--spotpatch-text-secondary); }
|
|
632
|
+
.spotpatch-external-status { padding-left: 10px; border-left: 2px solid var(--spotpatch-accent-cyan); color: #cbd5e1; }
|
|
633
|
+
.spotpatch-external-status[data-state="error"] { border-color: var(--spotpatch-danger); color: #fecdd3; }
|
|
634
|
+
.spotpatch-external-status[data-state="picked-up"] { border-color: var(--spotpatch-success); color: #a7f3d0; }
|
|
635
|
+
.spotpatch-external-settings { margin-top: 9px; }
|
|
636
|
+
.spotpatch-external-settings summary { cursor: pointer; color: #c4b5fd; font-size: 11px; }
|
|
637
|
+
.spotpatch-external-command { display: block; margin-top: 6px; padding: 6px 8px; overflow: auto; border-radius: 6px; background: var(--spotpatch-bg-input); color: #d8d6ff; font: 10px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
|
|
638
|
+
.spotpatch-external-refresh { padding: 3px 7px; border: 1px solid var(--spotpatch-border); border-radius: 6px; background: transparent; color: var(--spotpatch-text-secondary); font: inherit; font-size: 10px; cursor: pointer; }
|
|
639
|
+
.spotpatch-external-refresh:disabled { cursor: default; opacity: .45; }
|
|
640
|
+
.spotpatch-external-resolve { margin-top: 8px; padding: 6px 8px; border: 1px solid #f59e0b; border-radius: 6px; background: rgb(245 158 11 / 10%); color: #fde68a; font: inherit; font-size: 10px; cursor: pointer; }
|
|
641
|
+
.spotpatch-external-resolve[hidden] { display: none; }
|
|
642
|
+
.spotpatch-external-resolve:disabled { cursor: default; opacity: .45; }
|
|
643
|
+
.spotpatch-external-disclosure { position: fixed; inset: 0; z-index: 4; display: grid; place-items: center; padding: 20px; background: rgb(3 3 8 / 72%); backdrop-filter: blur(3px); }
|
|
644
|
+
.spotpatch-external-disclosure[hidden] { display: none; }
|
|
645
|
+
.spotpatch-external-disclosure-card { width: min(420px, calc(100vw - 40px)); max-height: calc(100vh - 40px); overflow: auto; padding: 18px; border: 1px solid rgb(139 123 255 / 55%); border-radius: 14px; background: var(--spotpatch-bg-raised); box-shadow: var(--spotpatch-shadow-panel); }
|
|
646
|
+
.spotpatch-external-disclosure-card h3 { margin: 0; color: var(--spotpatch-text); font-size: 15px; }
|
|
647
|
+
.spotpatch-external-disclosure-card p { margin: 9px 0 0; color: #c4c7d0; font-size: 11.5px; line-height: 1.55; }
|
|
648
|
+
.spotpatch-external-files { max-height: 96px; overflow: auto; color: #a5b4fc; font: 10px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }
|
|
649
|
+
.spotpatch-external-disclosure-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 15px; }
|
|
650
|
+
.spotpatch-external-disclosure-actions button { padding: 7px 10px; border: 1px solid var(--spotpatch-border); border-radius: 7px; background: var(--spotpatch-bg-active); color: var(--spotpatch-text); font: inherit; cursor: pointer; }
|
|
651
|
+
.spotpatch-external-disclosure-actions .spotpatch-primary { border-color: transparent; background: var(--spotpatch-accent); color: var(--spotpatch-text-on-accent); }
|
|
652
|
+
`;
|
|
653
|
+
return styles;
|
|
654
|
+
}
|
|
655
|
+
function formatTime(value, locale) {
|
|
656
|
+
const date = new Date(value);
|
|
657
|
+
return Number.isNaN(date.valueOf()) ? value : new Intl.DateTimeFormat(locale, {
|
|
658
|
+
hour: "2-digit",
|
|
659
|
+
minute: "2-digit",
|
|
660
|
+
second: "2-digit"
|
|
661
|
+
}).format(date);
|
|
662
|
+
}
|
|
663
|
+
function agentName(kind) {
|
|
664
|
+
return kind === "claude-channel" ? "Claude Code" : "Codex";
|
|
665
|
+
}
|
|
666
|
+
function dispatchMessage(messages, dispatch) {
|
|
667
|
+
const agent = agentName(dispatch.adapterKind);
|
|
668
|
+
if (dispatch.phase === "queued") {
|
|
669
|
+
return messages.queued(agent, dispatch.revision);
|
|
670
|
+
}
|
|
671
|
+
if (dispatch.phase === "dispatching") {
|
|
672
|
+
return messages.dispatching(agent, dispatch.revision);
|
|
673
|
+
}
|
|
674
|
+
if (dispatch.phase === "dispatched") {
|
|
675
|
+
return messages.dispatched(agent, dispatch.revision);
|
|
676
|
+
}
|
|
677
|
+
if (dispatch.phase === "working") {
|
|
678
|
+
return messages.working(agent, dispatch.revision);
|
|
679
|
+
}
|
|
680
|
+
if (dispatch.phase === "completed") {
|
|
681
|
+
return messages.completed(agent, dispatch.revision);
|
|
682
|
+
}
|
|
683
|
+
if (dispatch.phase === "failed") {
|
|
684
|
+
return messages.failed(agent, dispatch.revision);
|
|
685
|
+
}
|
|
686
|
+
return messages.unknown(agent, dispatch.revision);
|
|
687
|
+
}
|
|
688
|
+
function disclosurePaths(annotation) {
|
|
689
|
+
return Object.freeze([
|
|
690
|
+
...new Set(
|
|
691
|
+
annotation.targets.flatMap((target) => {
|
|
692
|
+
const relativePath = target.code?.relativePath ?? target.source.relativePath;
|
|
693
|
+
return relativePath === void 0 ? [] : [relativePath];
|
|
694
|
+
})
|
|
695
|
+
)
|
|
696
|
+
]);
|
|
697
|
+
}
|
|
698
|
+
function createExternalHandoffPanel(document, framework, locale, sessionId, subscribeLocale, onViewChange) {
|
|
699
|
+
const options = {
|
|
700
|
+
document,
|
|
701
|
+
framework,
|
|
702
|
+
locale,
|
|
703
|
+
onViewChange,
|
|
704
|
+
sessionId,
|
|
705
|
+
subscribeLocale
|
|
706
|
+
};
|
|
707
|
+
let messages = MESSAGES[options.locale()];
|
|
708
|
+
let activeAdapter = null;
|
|
709
|
+
let brokerReady = false;
|
|
710
|
+
let dispatchBlocksSend = false;
|
|
711
|
+
let operationBusy = false;
|
|
712
|
+
let retryable = false;
|
|
713
|
+
let unknownDelivery = false;
|
|
714
|
+
let contextReady = false;
|
|
715
|
+
let visible = false;
|
|
716
|
+
let pendingDisclosure;
|
|
717
|
+
let previousFocus;
|
|
718
|
+
const consentKey = `spotpatch:external-handoff-consent:${options.sessionId}`;
|
|
719
|
+
const root = createMarkedElement(document, "section");
|
|
720
|
+
root.className = "spotpatch-external-handoff";
|
|
721
|
+
root.hidden = true;
|
|
722
|
+
const heading = createMarkedElement(document, "div");
|
|
723
|
+
heading.className = "spotpatch-external-heading";
|
|
724
|
+
const title = createMarkedElement(document, "strong");
|
|
725
|
+
const refreshButton = createButton(document, "", "spotpatch-external-refresh");
|
|
726
|
+
heading.append(title, refreshButton);
|
|
727
|
+
const description = createMarkedElement(document, "p");
|
|
728
|
+
description.className = "spotpatch-external-description";
|
|
729
|
+
const status = createMarkedElement(document, "p");
|
|
730
|
+
status.className = "spotpatch-external-status";
|
|
731
|
+
status.setAttribute("role", "status");
|
|
732
|
+
status.setAttribute("aria-live", "polite");
|
|
733
|
+
const resolveButton = createButton(document, "", "spotpatch-external-resolve");
|
|
734
|
+
resolveButton.hidden = true;
|
|
735
|
+
const settings = createMarkedElement(document, "details");
|
|
736
|
+
settings.className = "spotpatch-external-settings";
|
|
737
|
+
const settingsTitle = createMarkedElement(document, "summary");
|
|
738
|
+
const settingsHelp = createMarkedElement(document, "p");
|
|
739
|
+
const cliPrefix = `node ./node_modules/@spotpatch/${options.framework}/dist/cli.js`;
|
|
740
|
+
const bridgePrefix = `${cliPrefix} bridge`;
|
|
741
|
+
const commands = [
|
|
742
|
+
`${bridgePrefix} setup --client claude --scope project --mode active`,
|
|
743
|
+
"MCP_PROTOCOL_NEGOTIATION=legacy claude --dangerously-load-development-channels server:spotpatch",
|
|
744
|
+
`${cliPrefix} connect codex --allow-workspace-write`,
|
|
745
|
+
`${bridgePrefix} setup --client cursor --scope project`
|
|
746
|
+
].map((value) => {
|
|
747
|
+
const command = createMarkedElement(document, "code");
|
|
748
|
+
command.className = "spotpatch-external-command";
|
|
749
|
+
command.textContent = value;
|
|
750
|
+
return command;
|
|
751
|
+
});
|
|
752
|
+
settings.append(settingsTitle, settingsHelp, ...commands);
|
|
753
|
+
root.append(heading, description, status, resolveButton, settings);
|
|
754
|
+
const sendButton = createButton(document, "", "spotpatch-primary");
|
|
755
|
+
sendButton.hidden = true;
|
|
756
|
+
const disclosure = createMarkedElement(document, "div");
|
|
757
|
+
disclosure.className = "spotpatch-external-disclosure";
|
|
758
|
+
disclosure.hidden = true;
|
|
759
|
+
const disclosureCard = createMarkedElement(document, "section");
|
|
760
|
+
disclosureCard.className = "spotpatch-external-disclosure-card";
|
|
761
|
+
disclosureCard.tabIndex = -1;
|
|
762
|
+
disclosureCard.setAttribute("role", "alertdialog");
|
|
763
|
+
disclosureCard.setAttribute("aria-modal", "true");
|
|
764
|
+
const disclosureTitle = createMarkedElement(document, "h3");
|
|
765
|
+
const disclosureIntro = createMarkedElement(document, "p");
|
|
766
|
+
const disclosureFiles = createMarkedElement(document, "p");
|
|
767
|
+
disclosureFiles.className = "spotpatch-external-files";
|
|
768
|
+
const disclosureData = createMarkedElement(document, "p");
|
|
769
|
+
const disclosureInbox = createMarkedElement(document, "p");
|
|
770
|
+
const disclosureProvider = createMarkedElement(document, "p");
|
|
771
|
+
const disclosureNoGuarantee = createMarkedElement(document, "p");
|
|
772
|
+
const disclosureActions = createMarkedElement(document, "div");
|
|
773
|
+
disclosureActions.className = "spotpatch-external-disclosure-actions";
|
|
774
|
+
const cancelButton = createButton(document, "");
|
|
775
|
+
const confirmButton = createButton(document, "", "spotpatch-primary");
|
|
776
|
+
disclosureActions.append(cancelButton, confirmButton);
|
|
777
|
+
disclosureCard.append(
|
|
778
|
+
disclosureTitle,
|
|
779
|
+
disclosureIntro,
|
|
780
|
+
disclosureFiles,
|
|
781
|
+
disclosureData,
|
|
782
|
+
disclosureInbox,
|
|
783
|
+
disclosureProvider,
|
|
784
|
+
disclosureNoGuarantee,
|
|
785
|
+
disclosureActions
|
|
786
|
+
);
|
|
787
|
+
disclosure.append(disclosureCard);
|
|
788
|
+
root.append(disclosure);
|
|
789
|
+
const hasConsent = () => {
|
|
790
|
+
try {
|
|
791
|
+
return document.defaultView?.sessionStorage.getItem(consentKey) === "confirmed";
|
|
792
|
+
} catch {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
const rememberConsent = () => {
|
|
797
|
+
try {
|
|
798
|
+
document.defaultView?.sessionStorage.setItem(consentKey, "confirmed");
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
const refreshActions = () => {
|
|
803
|
+
sendButton.textContent = retryable ? messages.retry : activeAdapter?.canDispatch === true ? messages.send : messages.inboxSend;
|
|
804
|
+
sendButton.disabled = !visible || !brokerReady || !contextReady || operationBusy || !retryable && dispatchBlocksSend;
|
|
805
|
+
sendButton.setAttribute("aria-busy", String(operationBusy));
|
|
806
|
+
refreshButton.disabled = !visible || operationBusy;
|
|
807
|
+
resolveButton.hidden = !unknownDelivery;
|
|
808
|
+
resolveButton.disabled = !visible || operationBusy || !unknownDelivery;
|
|
809
|
+
};
|
|
810
|
+
const settleDisclosure = (confirmed) => {
|
|
811
|
+
if (pendingDisclosure === void 0) return;
|
|
812
|
+
const resolve = pendingDisclosure;
|
|
813
|
+
pendingDisclosure = void 0;
|
|
814
|
+
disclosure.hidden = true;
|
|
815
|
+
if (confirmed) rememberConsent();
|
|
816
|
+
previousFocus?.focus({ preventScroll: true });
|
|
817
|
+
previousFocus = void 0;
|
|
818
|
+
resolve(confirmed);
|
|
819
|
+
options.onViewChange();
|
|
820
|
+
};
|
|
821
|
+
const handleDisclosureKeydown = (event) => {
|
|
822
|
+
if (event.key === "Escape") {
|
|
823
|
+
event.preventDefault();
|
|
824
|
+
settleDisclosure(false);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
if (event.key !== "Tab") return;
|
|
828
|
+
const first = cancelButton;
|
|
829
|
+
const last = confirmButton;
|
|
830
|
+
const rootNode = disclosure.getRootNode();
|
|
831
|
+
const activeElement = "activeElement" in rootNode ? rootNode.activeElement : document.activeElement;
|
|
832
|
+
if (event.shiftKey && activeElement === first) {
|
|
833
|
+
event.preventDefault();
|
|
834
|
+
last.focus();
|
|
835
|
+
} else if (!event.shiftKey && activeElement === last) {
|
|
836
|
+
event.preventDefault();
|
|
837
|
+
first.focus();
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
const applyMessages = () => {
|
|
841
|
+
messages = MESSAGES[options.locale()];
|
|
842
|
+
title.textContent = messages.title;
|
|
843
|
+
description.textContent = messages.description;
|
|
844
|
+
refreshButton.textContent = messages.refresh;
|
|
845
|
+
refreshButton.title = messages.refresh;
|
|
846
|
+
settingsTitle.textContent = messages.settingsTitle;
|
|
847
|
+
settingsHelp.textContent = messages.settingsHelp;
|
|
848
|
+
disclosureTitle.textContent = messages.disclosureTitle;
|
|
849
|
+
disclosureData.textContent = messages.disclosureData;
|
|
850
|
+
disclosureInbox.textContent = messages.disclosureInbox;
|
|
851
|
+
disclosureProvider.textContent = messages.disclosureProvider;
|
|
852
|
+
disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
|
|
853
|
+
cancelButton.textContent = messages.cancel;
|
|
854
|
+
confirmButton.textContent = messages.confirm;
|
|
855
|
+
resolveButton.textContent = messages.resolveUnknown;
|
|
856
|
+
refreshActions();
|
|
857
|
+
};
|
|
858
|
+
cancelButton.addEventListener("click", () => {
|
|
859
|
+
settleDisclosure(false);
|
|
860
|
+
});
|
|
861
|
+
confirmButton.addEventListener("click", () => {
|
|
862
|
+
settleDisclosure(true);
|
|
863
|
+
});
|
|
864
|
+
disclosure.addEventListener("keydown", handleDisclosureKeydown);
|
|
865
|
+
settings.addEventListener("toggle", options.onViewChange);
|
|
866
|
+
const unsubscribeLocale = options.subscribeLocale(applyMessages);
|
|
867
|
+
applyMessages();
|
|
868
|
+
status.textContent = messages.ready;
|
|
869
|
+
refreshActions();
|
|
870
|
+
const summaryMessage = (summary) => summary.state === "expired" ? messages.expired(summary.revision) : summary.state === "superseded" ? messages.superseded(summary.revision) : summary.pickupCount > 0 ? messages.pickedUp(
|
|
871
|
+
summary.revision,
|
|
872
|
+
summary.pickupCount,
|
|
873
|
+
summary.pickedUpAt === void 0 ? void 0 : formatTime(summary.pickedUpAt, options.locale())
|
|
874
|
+
) : messages.published(
|
|
875
|
+
summary.revision,
|
|
876
|
+
formatTime(summary.expiresAt, options.locale())
|
|
877
|
+
);
|
|
878
|
+
const applyStatus = (result) => {
|
|
879
|
+
operationBusy = false;
|
|
880
|
+
retryable = false;
|
|
881
|
+
activeAdapter = result.activeAdapter;
|
|
882
|
+
unknownDelivery = result.dispatch?.phase === "delivery-unknown" && result.activeAdapter?.state === "blocked";
|
|
883
|
+
dispatchBlocksSend = unknownDelivery || result.activeAdapter !== null && !result.activeAdapter.canDispatch;
|
|
884
|
+
if (result.dispatch !== null) {
|
|
885
|
+
status.dataset.state = result.dispatch.phase;
|
|
886
|
+
status.textContent = result.dispatch.phase === "delivery-unknown" && !unknownDelivery ? messages.unknownResolved(
|
|
887
|
+
agentName(result.dispatch.adapterKind),
|
|
888
|
+
result.dispatch.revision
|
|
889
|
+
) : dispatchMessage(messages, result.dispatch);
|
|
890
|
+
} else {
|
|
891
|
+
status.dataset.state = result.handoff.state === "available" && result.handoff.pickupCount > 0 ? "picked-up" : result.handoff.state;
|
|
892
|
+
status.textContent = summaryMessage(result.handoff);
|
|
893
|
+
}
|
|
894
|
+
refreshActions();
|
|
895
|
+
options.onViewChange();
|
|
896
|
+
};
|
|
897
|
+
return Object.freeze({
|
|
898
|
+
root,
|
|
899
|
+
styles: createStyles(document),
|
|
900
|
+
sendButton,
|
|
901
|
+
refreshButton,
|
|
902
|
+
resolveButton,
|
|
903
|
+
confirmDisclosure(annotation) {
|
|
904
|
+
if (hasConsent()) return Promise.resolve(true);
|
|
905
|
+
if (pendingDisclosure !== void 0) return Promise.resolve(false);
|
|
906
|
+
const paths = disclosurePaths(annotation);
|
|
907
|
+
disclosureIntro.textContent = messages.disclosureIntro(
|
|
908
|
+
annotation.targets.length,
|
|
909
|
+
paths.length
|
|
910
|
+
);
|
|
911
|
+
disclosureFiles.textContent = paths.length === 0 ? "\u2014" : paths.join("\n");
|
|
912
|
+
disclosure.hidden = false;
|
|
913
|
+
const rootNode = root.getRootNode();
|
|
914
|
+
const activeElement = "activeElement" in rootNode ? rootNode.activeElement : document.activeElement;
|
|
915
|
+
previousFocus = activeElement instanceof HTMLElement ? activeElement : void 0;
|
|
916
|
+
options.onViewChange();
|
|
917
|
+
disclosureCard.focus({ preventScroll: true });
|
|
918
|
+
return new Promise((resolve) => {
|
|
919
|
+
pendingDisclosure = resolve;
|
|
920
|
+
});
|
|
921
|
+
},
|
|
922
|
+
renderCapability(capability) {
|
|
923
|
+
brokerReady = capability.brokerReady;
|
|
924
|
+
operationBusy = false;
|
|
925
|
+
retryable = false;
|
|
926
|
+
activeAdapter = capability.activeAdapter;
|
|
927
|
+
unknownDelivery = false;
|
|
928
|
+
dispatchBlocksSend = capability.activeAdapter !== null && !capability.activeAdapter.canDispatch;
|
|
929
|
+
status.dataset.state = capability.brokerReady ? "ready" : "error";
|
|
930
|
+
status.textContent = capability.brokerReady ? capability.activeAdapter?.canDispatch === true ? messages.activeReady(agentName(capability.activeAdapter.kind)) : capability.dispatch !== null && capability.activeAdapter !== null ? dispatchMessage(messages, capability.dispatch) : capability.activeWaitCount > 0 ? messages.readyWaiting(capability.activeWaitCount) : messages.ready : messages.error("EXTERNAL_HANDOFF_UNAVAILABLE");
|
|
931
|
+
refreshActions();
|
|
932
|
+
options.onViewChange();
|
|
933
|
+
},
|
|
934
|
+
renderPublishing() {
|
|
935
|
+
operationBusy = true;
|
|
936
|
+
retryable = false;
|
|
937
|
+
status.dataset.state = "publishing";
|
|
938
|
+
status.textContent = messages.publishing;
|
|
939
|
+
refreshActions();
|
|
940
|
+
options.onViewChange();
|
|
941
|
+
},
|
|
942
|
+
renderPublishResult(result) {
|
|
943
|
+
applyStatus(
|
|
944
|
+
Object.freeze({
|
|
945
|
+
handoff: result.handoff,
|
|
946
|
+
activeAdapter: result.delivery.mode === "active" ? result.delivery.adapter : null,
|
|
947
|
+
dispatch: result.delivery.mode === "active" ? result.delivery.dispatch : null
|
|
948
|
+
})
|
|
949
|
+
);
|
|
950
|
+
},
|
|
951
|
+
renderStatus(result) {
|
|
952
|
+
applyStatus(result);
|
|
953
|
+
},
|
|
954
|
+
renderError(code, canRetry = false) {
|
|
955
|
+
operationBusy = false;
|
|
956
|
+
retryable = canRetry;
|
|
957
|
+
if (code === "EXTERNAL_AGENT_BUSY") dispatchBlocksSend = true;
|
|
958
|
+
status.dataset.state = "error";
|
|
959
|
+
status.textContent = canRetry ? `${messages.error(code)} ${messages.retry}.` : messages.error(code);
|
|
960
|
+
refreshActions();
|
|
961
|
+
options.onViewChange();
|
|
962
|
+
},
|
|
963
|
+
setBusy(nextBusy) {
|
|
964
|
+
operationBusy = nextBusy;
|
|
965
|
+
refreshActions();
|
|
966
|
+
},
|
|
967
|
+
setContextReady(ready) {
|
|
968
|
+
contextReady = ready;
|
|
969
|
+
refreshActions();
|
|
970
|
+
},
|
|
971
|
+
setSelectionVisible(nextVisible) {
|
|
972
|
+
visible = nextVisible;
|
|
973
|
+
root.hidden = !nextVisible;
|
|
974
|
+
sendButton.hidden = !nextVisible;
|
|
975
|
+
if (!nextVisible) settleDisclosure(false);
|
|
976
|
+
refreshActions();
|
|
977
|
+
},
|
|
978
|
+
dispose() {
|
|
979
|
+
settleDisclosure(false);
|
|
980
|
+
disclosure.removeEventListener("keydown", handleDisclosureKeydown);
|
|
981
|
+
settings.removeEventListener("toggle", options.onViewChange);
|
|
982
|
+
unsubscribeLocale();
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/ui/external-handoff-contract.ts
|
|
988
|
+
var EXTERNAL_HANDOFF_EXTENSION_KEY = /* @__PURE__ */ Symbol.for("spotpatch.external-handoff.v1");
|
|
989
|
+
function registerExternalHandoffExtension(extension) {
|
|
990
|
+
globalThis[EXTERNAL_HANDOFF_EXTENSION_KEY] = extension;
|
|
991
|
+
}
|
|
992
|
+
function getExternalHandoffExtension() {
|
|
993
|
+
return globalThis[EXTERNAL_HANDOFF_EXTENSION_KEY];
|
|
994
|
+
}
|
|
995
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
996
|
+
0 && (module.exports = {
|
|
997
|
+
createExternalHandoffPanel,
|
|
998
|
+
createExternalHandoffWorkflow,
|
|
999
|
+
getExternalHandoffExtension,
|
|
1000
|
+
registerExternalHandoffExtension
|
|
1001
|
+
});
|
|
1002
|
+
//# sourceMappingURL=external-handoff-panel.cjs.map
|