@spotpatch/runtime 1.10.0 → 1.11.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-XBD55BCF.js → chunk-BXRMVHNT.js} +1 -1
- package/dist/chunk-BXRMVHNT.js.map +1 -0
- package/dist/external-handoff-panel.cjs +795 -88
- package/dist/external-handoff-panel.cjs.map +1 -1
- package/dist/external-handoff-panel.d.cts +9 -1
- package/dist/external-handoff-panel.d.ts +9 -1
- package/dist/external-handoff-panel.js +811 -85
- package/dist/external-handoff-panel.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-XBD55BCF.js.map +0 -1
|
@@ -28,7 +28,359 @@ __export(external_handoff_panel_entry_exports, {
|
|
|
28
28
|
module.exports = __toCommonJS(external_handoff_panel_entry_exports);
|
|
29
29
|
|
|
30
30
|
// src/controller/external-handoff-workflow.ts
|
|
31
|
+
var import_external_handoff_browser2 = require("@spotpatch/shared/external-handoff-browser");
|
|
32
|
+
|
|
33
|
+
// src/controller/browser-api.ts
|
|
34
|
+
function record(value) {
|
|
35
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
function exactKeys(value, keys) {
|
|
38
|
+
const actual = Object.keys(value).sort();
|
|
39
|
+
const expected = [...keys].sort();
|
|
40
|
+
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
41
|
+
}
|
|
42
|
+
function validTimestamp(value) {
|
|
43
|
+
if (typeof value !== "string") return false;
|
|
44
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?Z$/u.exec(
|
|
45
|
+
value
|
|
46
|
+
);
|
|
47
|
+
if (match === null) return false;
|
|
48
|
+
const timestamp = Date.parse(value);
|
|
49
|
+
if (!Number.isFinite(timestamp)) return false;
|
|
50
|
+
const date = new Date(timestamp);
|
|
51
|
+
return date.getUTCFullYear() === Number(match[1]) && date.getUTCMonth() + 1 === Number(match[2]) && date.getUTCDate() === Number(match[3]) && date.getUTCHours() === Number(match[4]) && date.getUTCMinutes() === Number(match[5]) && date.getUTCSeconds() === Number(match[6]);
|
|
52
|
+
}
|
|
53
|
+
async function readBoundedJson(response, maximumBytes) {
|
|
54
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
55
|
+
if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
|
|
56
|
+
await response.body?.cancel();
|
|
57
|
+
throw new TypeError("SpotPatch API response exceeds its safety limit.");
|
|
58
|
+
}
|
|
59
|
+
if (response.body === null) {
|
|
60
|
+
throw new TypeError("SpotPatch API response body is unavailable.");
|
|
61
|
+
}
|
|
62
|
+
const reader = response.body.getReader();
|
|
63
|
+
const chunks = [];
|
|
64
|
+
let total = 0;
|
|
65
|
+
try {
|
|
66
|
+
for (; ; ) {
|
|
67
|
+
const chunk = await reader.read();
|
|
68
|
+
if (chunk.done) break;
|
|
69
|
+
total += chunk.value.byteLength;
|
|
70
|
+
if (total > maximumBytes) {
|
|
71
|
+
await reader.cancel();
|
|
72
|
+
throw new TypeError("SpotPatch API response exceeds its safety limit.");
|
|
73
|
+
}
|
|
74
|
+
chunks.push(chunk.value);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
reader.releaseLock();
|
|
78
|
+
}
|
|
79
|
+
const payload = new Uint8Array(total);
|
|
80
|
+
let offset = 0;
|
|
81
|
+
for (const chunk of chunks) {
|
|
82
|
+
payload.set(chunk, offset);
|
|
83
|
+
offset += chunk.byteLength;
|
|
84
|
+
}
|
|
85
|
+
return JSON.parse(
|
|
86
|
+
new TextDecoder("utf-8", { fatal: true }).decode(payload)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function successData(value) {
|
|
90
|
+
if (!record(value) || !exactKeys(value, ["data", "ok"]) || value.ok !== true) {
|
|
91
|
+
throw new TypeError("SpotPatch API success envelope is invalid.");
|
|
92
|
+
}
|
|
93
|
+
return value.data;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/controller/external-agent-control-client.ts
|
|
31
97
|
var import_external_handoff_browser = require("@spotpatch/shared/external-handoff-browser");
|
|
98
|
+
var RESPONSE_OVERHEAD_BYTES = 64 * 1024;
|
|
99
|
+
function member(values, value) {
|
|
100
|
+
return typeof value === "string" && values.includes(value);
|
|
101
|
+
}
|
|
102
|
+
function integer(value, minimum = 0) {
|
|
103
|
+
return Number.isSafeInteger(value) && value >= minimum;
|
|
104
|
+
}
|
|
105
|
+
function optionalKeys(value, base, optional) {
|
|
106
|
+
return exactKeys(value, [
|
|
107
|
+
...base,
|
|
108
|
+
...optional.filter((key) => value[key] !== void 0)
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
function safePath(value) {
|
|
112
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096 || value.startsWith("/") || value.includes("\\")) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return value.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
116
|
+
}
|
|
117
|
+
function parseFiles(value) {
|
|
118
|
+
if (!Array.isArray(value) || value.length > import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumChangedFiles) {
|
|
119
|
+
throw new TypeError("Invalid managed file summaries.");
|
|
120
|
+
}
|
|
121
|
+
return value.map((file) => {
|
|
122
|
+
if (!record(file) || !exactKeys(file, ["additions", "deletions", "path"]) || !safePath(file.path) || !integer(file.additions) || !integer(file.deletions)) {
|
|
123
|
+
throw new TypeError("Invalid managed file summary.");
|
|
124
|
+
}
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
path: file.path,
|
|
127
|
+
additions: file.additions,
|
|
128
|
+
deletions: file.deletions
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function parseChecks(value) {
|
|
133
|
+
if (!Array.isArray(value) || value.length > import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumChecks) {
|
|
134
|
+
throw new TypeError("Invalid managed check summaries.");
|
|
135
|
+
}
|
|
136
|
+
return value.map((check) => {
|
|
137
|
+
if (!record(check) || !optionalKeys(check, ["durationMs", "id", "outcome"], ["exitCode"]) || typeof check.id !== "string" || !/^[A-Za-z0-9._-]{1,128}$/u.test(check.id) || !member(import_external_handoff_browser.EXTERNAL_AGENT_CHECK_OUTCOMES, check.outcome) || !integer(check.durationMs) || check.exitCode !== void 0 && !Number.isSafeInteger(check.exitCode)) {
|
|
138
|
+
throw new TypeError("Invalid managed check summary.");
|
|
139
|
+
}
|
|
140
|
+
return Object.freeze({
|
|
141
|
+
id: check.id,
|
|
142
|
+
outcome: check.outcome,
|
|
143
|
+
durationMs: check.durationMs,
|
|
144
|
+
...check.exitCode === void 0 ? {} : { exitCode: check.exitCode }
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function parseTimings(value) {
|
|
149
|
+
const keys = [
|
|
150
|
+
"preparing",
|
|
151
|
+
"agent",
|
|
152
|
+
"auditing",
|
|
153
|
+
"validating",
|
|
154
|
+
"applying",
|
|
155
|
+
"total"
|
|
156
|
+
];
|
|
157
|
+
if (!record(value) || !optionalKeys(value, [], keys)) {
|
|
158
|
+
throw new TypeError("Invalid managed timings.");
|
|
159
|
+
}
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
if (value[key] !== void 0 && !integer(value[key])) {
|
|
162
|
+
throw new TypeError("Invalid managed timing.");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return Object.freeze(
|
|
166
|
+
Object.fromEntries(
|
|
167
|
+
keys.flatMap((key) => value[key] === void 0 ? [] : [[key, value[key]]])
|
|
168
|
+
)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
function parseTask(value) {
|
|
172
|
+
if (!record(value) || !optionalKeys(
|
|
173
|
+
value,
|
|
174
|
+
[
|
|
175
|
+
"checks",
|
|
176
|
+
"deliveryStatus",
|
|
177
|
+
"executionStatus",
|
|
178
|
+
"files",
|
|
179
|
+
"managedPhase",
|
|
180
|
+
"revision",
|
|
181
|
+
"timings"
|
|
182
|
+
],
|
|
183
|
+
["resultExpiresAt", "validationOutcome"]
|
|
184
|
+
) || !integer(value.revision, 1) || !member(import_external_handoff_browser.EXTERNAL_AGENT_DELIVERY_STATUSES, value.deliveryStatus) || !member(import_external_handoff_browser.EXTERNAL_AGENT_EXECUTION_STATUSES, value.executionStatus) || !member(import_external_handoff_browser.EXTERNAL_AGENT_MANAGED_PHASES, value.managedPhase) || value.validationOutcome !== void 0 && !member(import_external_handoff_browser.EXTERNAL_AGENT_VALIDATION_OUTCOMES, value.validationOutcome) || value.resultExpiresAt !== void 0 && !validTimestamp(value.resultExpiresAt)) {
|
|
185
|
+
throw new TypeError("Invalid managed task status.");
|
|
186
|
+
}
|
|
187
|
+
return Object.freeze({
|
|
188
|
+
revision: value.revision,
|
|
189
|
+
deliveryStatus: value.deliveryStatus,
|
|
190
|
+
executionStatus: value.executionStatus,
|
|
191
|
+
managedPhase: value.managedPhase,
|
|
192
|
+
...value.validationOutcome === void 0 ? {} : { validationOutcome: value.validationOutcome },
|
|
193
|
+
files: parseFiles(value.files),
|
|
194
|
+
checks: parseChecks(value.checks),
|
|
195
|
+
timings: parseTimings(value.timings),
|
|
196
|
+
...value.resultExpiresAt === void 0 ? {} : { resultExpiresAt: value.resultExpiresAt }
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function parseExternalAgentControlStatus(value) {
|
|
200
|
+
if (!record(value) || !optionalKeys(
|
|
201
|
+
value,
|
|
202
|
+
[
|
|
203
|
+
"adapter",
|
|
204
|
+
"authReadiness",
|
|
205
|
+
"connectionState",
|
|
206
|
+
"grantState",
|
|
207
|
+
"mode",
|
|
208
|
+
"schemaVersion",
|
|
209
|
+
"sequence",
|
|
210
|
+
"updatedAt"
|
|
211
|
+
],
|
|
212
|
+
["effectiveModel", "error", "requestedModel", "task"]
|
|
213
|
+
) || 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") {
|
|
214
|
+
throw new TypeError("Invalid external Agent control status.");
|
|
215
|
+
}
|
|
216
|
+
let error;
|
|
217
|
+
if (value.error !== void 0) {
|
|
218
|
+
if (!record(value.error) || !exactKeys(value.error, ["action", "code", "recoverability", "stage"]) || !member(import_external_handoff_browser.EXTERNAL_AGENT_ERROR_CODES, value.error.code) || !member(import_external_handoff_browser.EXTERNAL_AGENT_ERROR_STAGES, value.error.stage) || !member(import_external_handoff_browser.EXTERNAL_AGENT_ERROR_RECOVERABILITY, value.error.recoverability) || !member(import_external_handoff_browser.EXTERNAL_AGENT_ACTIONS, value.error.action)) {
|
|
219
|
+
throw new TypeError("Invalid external Agent error.");
|
|
220
|
+
}
|
|
221
|
+
error = Object.freeze({
|
|
222
|
+
code: value.error.code,
|
|
223
|
+
stage: value.error.stage,
|
|
224
|
+
recoverability: value.error.recoverability,
|
|
225
|
+
action: value.error.action
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
schemaVersion: 1,
|
|
230
|
+
sequence: value.sequence,
|
|
231
|
+
mode: value.mode,
|
|
232
|
+
adapter: Object.freeze({
|
|
233
|
+
kind: "codex",
|
|
234
|
+
maturity: "experimental",
|
|
235
|
+
availability: value.adapter.availability
|
|
236
|
+
}),
|
|
237
|
+
connectionState: value.connectionState,
|
|
238
|
+
authReadiness: value.authReadiness,
|
|
239
|
+
grantState: value.grantState,
|
|
240
|
+
...value.requestedModel === void 0 ? {} : { requestedModel: value.requestedModel },
|
|
241
|
+
...value.effectiveModel === void 0 ? {} : { effectiveModel: value.effectiveModel },
|
|
242
|
+
...value.task === void 0 ? {} : { task: parseTask(value.task) },
|
|
243
|
+
...error === void 0 ? {} : { error },
|
|
244
|
+
updatedAt: value.updatedAt
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function parseExternalAgentManagedResult(value) {
|
|
248
|
+
if (!record(value) || !exactKeys(value, [
|
|
249
|
+
"checks",
|
|
250
|
+
"diff",
|
|
251
|
+
"expiresAt",
|
|
252
|
+
"files",
|
|
253
|
+
"revision",
|
|
254
|
+
"timings",
|
|
255
|
+
"validationOutcome"
|
|
256
|
+
]) || !integer(value.revision, 1) || typeof value.diff !== "string" || value.diff.length > import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumResultDiffBytes || !validTimestamp(value.expiresAt) || !member(import_external_handoff_browser.EXTERNAL_AGENT_VALIDATION_OUTCOMES, value.validationOutcome)) {
|
|
257
|
+
throw new TypeError("Invalid managed Agent result.");
|
|
258
|
+
}
|
|
259
|
+
return Object.freeze({
|
|
260
|
+
revision: value.revision,
|
|
261
|
+
diff: value.diff,
|
|
262
|
+
files: parseFiles(value.files),
|
|
263
|
+
checks: parseChecks(value.checks),
|
|
264
|
+
timings: parseTimings(value.timings),
|
|
265
|
+
validationOutcome: value.validationOutcome,
|
|
266
|
+
expiresAt: value.expiresAt
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
function parseEvent(value) {
|
|
270
|
+
if (!record(value) || typeof value.type !== "string") {
|
|
271
|
+
throw new TypeError("Invalid external Agent event.");
|
|
272
|
+
}
|
|
273
|
+
if (value.type === "status" && exactKeys(value, ["data", "type"])) {
|
|
274
|
+
return Object.freeze({
|
|
275
|
+
type: "status",
|
|
276
|
+
data: parseExternalAgentControlStatus(value.data)
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (value.type === "heartbeat" && exactKeys(value, ["emittedAt", "sequence", "type"]) && integer(value.sequence) && validTimestamp(value.emittedAt)) {
|
|
280
|
+
return Object.freeze({
|
|
281
|
+
type: "heartbeat",
|
|
282
|
+
sequence: value.sequence,
|
|
283
|
+
emittedAt: value.emittedAt
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
throw new TypeError("Invalid external Agent event.");
|
|
287
|
+
}
|
|
288
|
+
function createExternalAgentControlClient(fetch, sessionToken) {
|
|
289
|
+
const request = async (endpoint, body, maximumBytes = RESPONSE_OVERHEAD_BYTES) => {
|
|
290
|
+
const response = await fetch(endpoint, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: {
|
|
293
|
+
"Content-Type": "application/json",
|
|
294
|
+
[import_external_handoff_browser.SPOTPATCH_TOKEN_HEADER]: sessionToken
|
|
295
|
+
},
|
|
296
|
+
body: JSON.stringify(body)
|
|
297
|
+
});
|
|
298
|
+
const envelope = await readBoundedJson(response, maximumBytes);
|
|
299
|
+
if (!response.ok) throw new TypeError("External Agent control request failed.");
|
|
300
|
+
return successData(envelope);
|
|
301
|
+
};
|
|
302
|
+
return Object.freeze({
|
|
303
|
+
async status() {
|
|
304
|
+
return parseExternalAgentControlStatus(
|
|
305
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalAgentControlStatus, {})
|
|
306
|
+
);
|
|
307
|
+
},
|
|
308
|
+
async connect(value) {
|
|
309
|
+
return parseExternalAgentControlStatus(
|
|
310
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalAgentControlConnect, value)
|
|
311
|
+
);
|
|
312
|
+
},
|
|
313
|
+
async disconnect(value) {
|
|
314
|
+
return parseExternalAgentControlStatus(
|
|
315
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect, value)
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
async cancel(value) {
|
|
319
|
+
return parseExternalAgentControlStatus(
|
|
320
|
+
await request(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalAgentControlCancel, value)
|
|
321
|
+
);
|
|
322
|
+
},
|
|
323
|
+
async result(revision) {
|
|
324
|
+
return parseExternalAgentManagedResult(
|
|
325
|
+
await request(
|
|
326
|
+
import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalAgentResult,
|
|
327
|
+
{ revision },
|
|
328
|
+
import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumResultDiffBytes + RESPONSE_OVERHEAD_BYTES
|
|
329
|
+
)
|
|
330
|
+
);
|
|
331
|
+
},
|
|
332
|
+
async events(afterSequence, signal, onEvent) {
|
|
333
|
+
const response = await fetch(import_external_handoff_browser.SPOTPATCH_ENDPOINTS.externalAgentEvents, {
|
|
334
|
+
method: "POST",
|
|
335
|
+
headers: {
|
|
336
|
+
"Content-Type": "application/json",
|
|
337
|
+
[import_external_handoff_browser.SPOTPATCH_TOKEN_HEADER]: sessionToken
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify(afterSequence === void 0 ? {} : { afterSequence }),
|
|
340
|
+
signal
|
|
341
|
+
});
|
|
342
|
+
if (!response.ok || response.body === null) {
|
|
343
|
+
await response.body?.cancel();
|
|
344
|
+
throw new TypeError("External Agent event stream is unavailable.");
|
|
345
|
+
}
|
|
346
|
+
const reader = response.body.getReader();
|
|
347
|
+
let pending = new Uint8Array(0);
|
|
348
|
+
try {
|
|
349
|
+
for (; ; ) {
|
|
350
|
+
const chunk = await reader.read();
|
|
351
|
+
if (chunk.done) break;
|
|
352
|
+
const combined = new Uint8Array(pending.byteLength + chunk.value.byteLength);
|
|
353
|
+
combined.set(pending);
|
|
354
|
+
combined.set(chunk.value, pending.byteLength);
|
|
355
|
+
pending = combined;
|
|
356
|
+
for (; ; ) {
|
|
357
|
+
const newline = pending.indexOf(10);
|
|
358
|
+
if (newline === -1) break;
|
|
359
|
+
if (newline === 0 || newline > import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventLineBytes) {
|
|
360
|
+
throw new TypeError("External Agent event line is invalid.");
|
|
361
|
+
}
|
|
362
|
+
const line = pending.slice(0, newline);
|
|
363
|
+
pending = pending.slice(newline + 1);
|
|
364
|
+
const value = JSON.parse(
|
|
365
|
+
new TextDecoder("utf-8", { fatal: true }).decode(line)
|
|
366
|
+
);
|
|
367
|
+
onEvent(parseEvent(value));
|
|
368
|
+
}
|
|
369
|
+
if (pending.byteLength > import_external_handoff_browser.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventLineBytes) {
|
|
370
|
+
throw new TypeError("External Agent event line exceeds its limit.");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (pending.byteLength !== 0) {
|
|
374
|
+
throw new TypeError("External Agent event stream ended mid-record.");
|
|
375
|
+
}
|
|
376
|
+
} finally {
|
|
377
|
+
reader.releaseLock();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/controller/external-handoff-workflow.ts
|
|
32
384
|
var MAX_SUMMARY_RESPONSE_BYTES = 64 * 1024;
|
|
33
385
|
var OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{22,128}$/u;
|
|
34
386
|
var ExternalHandoffApiError = class extends Error {
|
|
@@ -39,17 +391,6 @@ var ExternalHandoffApiError = class extends Error {
|
|
|
39
391
|
}
|
|
40
392
|
code;
|
|
41
393
|
};
|
|
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
394
|
function parseActiveAdapter(value) {
|
|
54
395
|
if (value === null) return null;
|
|
55
396
|
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)) {
|
|
@@ -85,7 +426,7 @@ function parseCapability(value) {
|
|
|
85
426
|
"dispatch",
|
|
86
427
|
"enabled",
|
|
87
428
|
"snapshotSchemaVersion"
|
|
88
|
-
]) || value.enabled !== true || typeof value.brokerReady !== "boolean" || !Number.isSafeInteger(value.activeWaitCount) || value.activeWaitCount < 0 || value.activeWaitCount >
|
|
429
|
+
]) || value.enabled !== true || typeof value.brokerReady !== "boolean" || !Number.isSafeInteger(value.activeWaitCount) || value.activeWaitCount < 0 || value.activeWaitCount > import_external_handoff_browser2.EXTERNAL_HANDOFF_LIMITS.maximumWaiters || value.snapshotSchemaVersion !== import_external_handoff_browser2.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION || value.brokerProtocolVersion !== import_external_handoff_browser2.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION) {
|
|
89
430
|
throw new ExternalHandoffApiError();
|
|
90
431
|
}
|
|
91
432
|
return Object.freeze({
|
|
@@ -94,8 +435,8 @@ function parseCapability(value) {
|
|
|
94
435
|
activeWaitCount: value.activeWaitCount,
|
|
95
436
|
activeAdapter: parseActiveAdapter(value.activeAdapter),
|
|
96
437
|
dispatch: parseDispatch(value.dispatch),
|
|
97
|
-
snapshotSchemaVersion:
|
|
98
|
-
brokerProtocolVersion:
|
|
438
|
+
snapshotSchemaVersion: import_external_handoff_browser2.EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
|
|
439
|
+
brokerProtocolVersion: import_external_handoff_browser2.EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION
|
|
99
440
|
});
|
|
100
441
|
}
|
|
101
442
|
function parsePublishResult(value) {
|
|
@@ -157,7 +498,7 @@ function parseSummary(value) {
|
|
|
157
498
|
"targetCount",
|
|
158
499
|
...value.pickedUpAt === void 0 ? [] : ["pickedUpAt"]
|
|
159
500
|
];
|
|
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 >
|
|
501
|
+
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_browser2.EXTERNAL_HANDOFF_LIMITS.maximumConnectorReceipts || value.pickedUpAt !== void 0 && !validTimestamp(value.pickedUpAt)) {
|
|
161
502
|
throw new ExternalHandoffApiError();
|
|
162
503
|
}
|
|
163
504
|
return Object.freeze({
|
|
@@ -174,52 +515,8 @@ function parseSummary(value) {
|
|
|
174
515
|
...value.pickedUpAt === void 0 ? {} : { pickedUpAt: value.pickedUpAt }
|
|
175
516
|
});
|
|
176
517
|
}
|
|
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
518
|
function failureCode(value) {
|
|
216
|
-
return record(value) && value.ok === false && record(value.error) && (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;
|
|
519
|
+
return record(value) && value.ok === false && record(value.error) && (0, import_external_handoff_browser2.isErrorCode)(value.error.code) ? value.error.code : void 0;
|
|
223
520
|
}
|
|
224
521
|
function omitBrowserCode(annotation) {
|
|
225
522
|
return {
|
|
@@ -253,14 +550,23 @@ function api(options) {
|
|
|
253
550
|
method: "POST",
|
|
254
551
|
headers: {
|
|
255
552
|
"Content-Type": "application/json",
|
|
256
|
-
[
|
|
553
|
+
[import_external_handoff_browser2.SPOTPATCH_TOKEN_HEADER]: options.sessionToken
|
|
257
554
|
},
|
|
258
555
|
body: JSON.stringify(body),
|
|
259
556
|
signal: controller.signal
|
|
260
557
|
});
|
|
261
|
-
|
|
558
|
+
let envelope;
|
|
559
|
+
try {
|
|
560
|
+
envelope = await readBoundedJson(response, MAX_SUMMARY_RESPONSE_BYTES);
|
|
561
|
+
} catch {
|
|
562
|
+
throw new ExternalHandoffApiError();
|
|
563
|
+
}
|
|
262
564
|
if (!response.ok) throw new ExternalHandoffApiError(failureCode(envelope));
|
|
263
|
-
|
|
565
|
+
try {
|
|
566
|
+
return successData(envelope);
|
|
567
|
+
} catch {
|
|
568
|
+
throw new ExternalHandoffApiError();
|
|
569
|
+
}
|
|
264
570
|
} finally {
|
|
265
571
|
pending.delete(controller);
|
|
266
572
|
}
|
|
@@ -272,12 +578,12 @@ function api(options) {
|
|
|
272
578
|
},
|
|
273
579
|
async capability() {
|
|
274
580
|
return parseCapability(
|
|
275
|
-
await request(
|
|
581
|
+
await request(import_external_handoff_browser2.SPOTPATCH_ENDPOINTS.externalHandoffCapability, {})
|
|
276
582
|
);
|
|
277
583
|
},
|
|
278
584
|
async publish(requestId, annotation) {
|
|
279
585
|
return parsePublishResult(
|
|
280
|
-
await request(
|
|
586
|
+
await request(import_external_handoff_browser2.SPOTPATCH_ENDPOINTS.externalHandoffPublish, {
|
|
281
587
|
annotation: omitBrowserCode(annotation),
|
|
282
588
|
requestId
|
|
283
589
|
})
|
|
@@ -286,14 +592,14 @@ function api(options) {
|
|
|
286
592
|
async status(cursor) {
|
|
287
593
|
return parseStatusResult(
|
|
288
594
|
await request(
|
|
289
|
-
|
|
595
|
+
import_external_handoff_browser2.SPOTPATCH_ENDPOINTS.externalHandoffStatus,
|
|
290
596
|
cursor === void 0 ? {} : { cursor }
|
|
291
597
|
)
|
|
292
598
|
);
|
|
293
599
|
},
|
|
294
600
|
async resolveDelivery(cursor) {
|
|
295
601
|
return parseStatusResult(
|
|
296
|
-
await request(
|
|
602
|
+
await request(import_external_handoff_browser2.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery, {
|
|
297
603
|
confirmation: "workspace-reviewed",
|
|
298
604
|
cursor
|
|
299
605
|
})
|
|
@@ -310,6 +616,7 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
310
616
|
window
|
|
311
617
|
};
|
|
312
618
|
const client = api(options);
|
|
619
|
+
const controlClient = createExternalAgentControlClient(fetch, sessionToken);
|
|
313
620
|
const timers = /* @__PURE__ */ new Set();
|
|
314
621
|
const lifecycle = {
|
|
315
622
|
disposed: false,
|
|
@@ -318,12 +625,152 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
318
625
|
userActionPending: false
|
|
319
626
|
};
|
|
320
627
|
let capability;
|
|
628
|
+
let controlStatus;
|
|
629
|
+
let controlEventController;
|
|
630
|
+
let controlReconnectDelay = import_external_handoff_browser2.EXTERNAL_AGENT_CONTROL_LIMITS.eventReconnectMinimumMs;
|
|
631
|
+
let controlReconnectTimer;
|
|
632
|
+
let controlActionPending = false;
|
|
633
|
+
let managedResultRevision;
|
|
634
|
+
let managedResultLoadingRevision;
|
|
321
635
|
let current;
|
|
322
636
|
let retryablePublish;
|
|
637
|
+
const isDisposed = () => lifecycle.disposed;
|
|
323
638
|
const clearTimers = () => {
|
|
324
639
|
for (const timer of timers) options.window.clearTimeout(timer);
|
|
325
640
|
timers.clear();
|
|
326
641
|
};
|
|
642
|
+
const clearControlReconnect = () => {
|
|
643
|
+
if (controlReconnectTimer === void 0) return;
|
|
644
|
+
options.window.clearTimeout(controlReconnectTimer);
|
|
645
|
+
controlReconnectTimer = void 0;
|
|
646
|
+
};
|
|
647
|
+
const renderManagedResult = async (revision) => {
|
|
648
|
+
if (lifecycle.disposed || managedResultRevision === revision || managedResultLoadingRevision === revision) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
managedResultLoadingRevision = revision;
|
|
652
|
+
try {
|
|
653
|
+
const result = await controlClient.result(revision);
|
|
654
|
+
if (isDisposed() || controlStatus?.task?.revision !== revision) return;
|
|
655
|
+
managedResultRevision = revision;
|
|
656
|
+
options.panel.renderManagedResult(result);
|
|
657
|
+
} catch {
|
|
658
|
+
} finally {
|
|
659
|
+
if (managedResultLoadingRevision === revision) {
|
|
660
|
+
managedResultLoadingRevision = void 0;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const applyControlStatus = (value) => {
|
|
665
|
+
if (controlStatus !== void 0 && value.sequence < controlStatus.sequence) return;
|
|
666
|
+
controlStatus = value;
|
|
667
|
+
controlReconnectDelay = import_external_handoff_browser2.EXTERNAL_AGENT_CONTROL_LIMITS.eventReconnectMinimumMs;
|
|
668
|
+
options.panel.renderControlStatus(value);
|
|
669
|
+
const resultRevision = value.task?.resultExpiresAt === void 0 ? void 0 : value.task.revision;
|
|
670
|
+
if (resultRevision !== void 0) void renderManagedResult(resultRevision);
|
|
671
|
+
};
|
|
672
|
+
const handleControlEvent = (event) => {
|
|
673
|
+
if (event.type === "status") {
|
|
674
|
+
applyControlStatus(event.data);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const resultRevision = controlStatus?.task?.resultExpiresAt === void 0 ? void 0 : controlStatus.task.revision;
|
|
678
|
+
if (resultRevision !== void 0) void renderManagedResult(resultRevision);
|
|
679
|
+
};
|
|
680
|
+
const startControlEventStream = () => {
|
|
681
|
+
if (lifecycle.disposed || !lifecycle.mounted || controlEventController !== void 0) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
clearControlReconnect();
|
|
685
|
+
const controller = new AbortController();
|
|
686
|
+
controlEventController = controller;
|
|
687
|
+
void controlClient.events(controlStatus?.sequence, controller.signal, handleControlEvent).catch((error) => {
|
|
688
|
+
if (lifecycle.disposed || error instanceof DOMException && error.name === "AbortError") {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (controlStatus === void 0) options.panel.renderControlUnavailable();
|
|
692
|
+
}).finally(() => {
|
|
693
|
+
if (controlEventController === controller) {
|
|
694
|
+
controlEventController = void 0;
|
|
695
|
+
}
|
|
696
|
+
if (lifecycle.disposed || !lifecycle.mounted) return;
|
|
697
|
+
const delay = controlReconnectDelay;
|
|
698
|
+
controlReconnectDelay = Math.min(
|
|
699
|
+
delay * 2,
|
|
700
|
+
import_external_handoff_browser2.EXTERNAL_AGENT_CONTROL_LIMITS.eventReconnectMaximumMs
|
|
701
|
+
);
|
|
702
|
+
controlReconnectTimer = options.window.setTimeout(() => {
|
|
703
|
+
controlReconnectTimer = void 0;
|
|
704
|
+
startControlEventStream();
|
|
705
|
+
}, delay);
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
const refreshControlStatus = async () => {
|
|
709
|
+
applyControlStatus(await controlClient.status());
|
|
710
|
+
};
|
|
711
|
+
const bootstrapControl = async () => {
|
|
712
|
+
try {
|
|
713
|
+
await refreshControlStatus();
|
|
714
|
+
} catch {
|
|
715
|
+
if (!lifecycle.disposed) options.panel.renderControlUnavailable();
|
|
716
|
+
} finally {
|
|
717
|
+
if (!lifecycle.disposed) startControlEventStream();
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
const performControlAction = async (action) => {
|
|
721
|
+
if (lifecycle.disposed || controlActionPending) return;
|
|
722
|
+
controlActionPending = true;
|
|
723
|
+
options.panel.setControlBusy(true);
|
|
724
|
+
try {
|
|
725
|
+
applyControlStatus(await action());
|
|
726
|
+
} catch {
|
|
727
|
+
try {
|
|
728
|
+
await refreshControlStatus();
|
|
729
|
+
} catch {
|
|
730
|
+
if (!isDisposed()) options.panel.renderControlUnavailable();
|
|
731
|
+
}
|
|
732
|
+
} finally {
|
|
733
|
+
controlActionPending = false;
|
|
734
|
+
if (!isDisposed()) options.panel.setControlBusy(false);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
const handleConnectClick = () => {
|
|
738
|
+
void performControlAction(
|
|
739
|
+
() => controlClient.connect({
|
|
740
|
+
requestId: createRequestId(options.window),
|
|
741
|
+
adapterKind: "codex",
|
|
742
|
+
profile: import_external_handoff_browser2.EXTERNAL_AGENT_MANAGED_PROFILE
|
|
743
|
+
})
|
|
744
|
+
);
|
|
745
|
+
};
|
|
746
|
+
const handleDisconnectClick = () => {
|
|
747
|
+
void performControlAction(
|
|
748
|
+
() => controlClient.disconnect({
|
|
749
|
+
requestId: createRequestId(options.window),
|
|
750
|
+
adapterKind: "codex",
|
|
751
|
+
revokeGrant: false
|
|
752
|
+
})
|
|
753
|
+
);
|
|
754
|
+
};
|
|
755
|
+
const handleRevokeClick = () => {
|
|
756
|
+
void performControlAction(
|
|
757
|
+
() => controlClient.disconnect({
|
|
758
|
+
requestId: createRequestId(options.window),
|
|
759
|
+
adapterKind: "codex",
|
|
760
|
+
revokeGrant: true
|
|
761
|
+
})
|
|
762
|
+
);
|
|
763
|
+
};
|
|
764
|
+
const handleManagedCancelClick = () => {
|
|
765
|
+
const revision = controlStatus?.task?.revision;
|
|
766
|
+
if (revision === void 0) return;
|
|
767
|
+
void performControlAction(
|
|
768
|
+
() => controlClient.cancel({
|
|
769
|
+
requestId: createRequestId(options.window),
|
|
770
|
+
revision
|
|
771
|
+
})
|
|
772
|
+
);
|
|
773
|
+
};
|
|
327
774
|
const errorCode = (error) => error instanceof ExternalHandoffApiError ? error.code : void 0;
|
|
328
775
|
const restorePanel = () => {
|
|
329
776
|
if (current !== void 0) options.panel.renderStatus(current);
|
|
@@ -370,7 +817,7 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
370
817
|
void refreshStatus(revision).then(() => {
|
|
371
818
|
if (continueWhilePending && !lifecycle.disposed && revision === lifecycle.operation && !lifecycle.userActionPending && current !== void 0 && dispatchIsPending(current.dispatch)) {
|
|
372
819
|
scheduleRefresh(
|
|
373
|
-
|
|
820
|
+
import_external_handoff_browser2.EXTERNAL_HANDOFF_LIMITS.activeStatusPollMs,
|
|
374
821
|
continueWhilePending
|
|
375
822
|
);
|
|
376
823
|
}
|
|
@@ -386,7 +833,7 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
386
833
|
const annotation = options.selectedAnnotation();
|
|
387
834
|
if (annotation === void 0) {
|
|
388
835
|
lifecycle.userActionPending = false;
|
|
389
|
-
options.panel.renderError(
|
|
836
|
+
options.panel.renderError(import_external_handoff_browser2.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
|
|
390
837
|
return;
|
|
391
838
|
}
|
|
392
839
|
const disclosureRevision = lifecycle.operation;
|
|
@@ -419,7 +866,7 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
419
866
|
});
|
|
420
867
|
options.panel.renderPublishResult(result);
|
|
421
868
|
scheduleRefresh(
|
|
422
|
-
result.delivery.mode === "active" ?
|
|
869
|
+
result.delivery.mode === "active" ? import_external_handoff_browser2.EXTERNAL_HANDOFF_LIMITS.activeStatusPollMs : 500,
|
|
423
870
|
result.delivery.mode === "active"
|
|
424
871
|
);
|
|
425
872
|
} catch (error) {
|
|
@@ -507,7 +954,15 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
507
954
|
options.panel.sendButton.addEventListener("click", handleSendClick);
|
|
508
955
|
options.panel.refreshButton.addEventListener("click", handleRefreshClick);
|
|
509
956
|
options.panel.resolveButton.addEventListener("click", handleResolveClick);
|
|
957
|
+
options.panel.connectButton.addEventListener("click", handleConnectClick);
|
|
958
|
+
options.panel.disconnectButton.addEventListener("click", handleDisconnectClick);
|
|
959
|
+
options.panel.revokeButton.addEventListener("click", handleRevokeClick);
|
|
960
|
+
options.panel.cancelManagedButton.addEventListener(
|
|
961
|
+
"click",
|
|
962
|
+
handleManagedCancelClick
|
|
963
|
+
);
|
|
510
964
|
void refreshCapability(lifecycle.operation);
|
|
965
|
+
void bootstrapControl();
|
|
511
966
|
},
|
|
512
967
|
cancelPending,
|
|
513
968
|
dispose() {
|
|
@@ -517,10 +972,23 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
517
972
|
lifecycle.userActionPending = false;
|
|
518
973
|
retryablePublish = void 0;
|
|
519
974
|
clearTimers();
|
|
975
|
+
clearControlReconnect();
|
|
976
|
+
controlEventController?.abort("workflow-disposed");
|
|
977
|
+
controlEventController = void 0;
|
|
520
978
|
client.cancel();
|
|
521
979
|
options.panel.sendButton.removeEventListener("click", handleSendClick);
|
|
522
980
|
options.panel.refreshButton.removeEventListener("click", handleRefreshClick);
|
|
523
981
|
options.panel.resolveButton.removeEventListener("click", handleResolveClick);
|
|
982
|
+
options.panel.connectButton.removeEventListener("click", handleConnectClick);
|
|
983
|
+
options.panel.disconnectButton.removeEventListener(
|
|
984
|
+
"click",
|
|
985
|
+
handleDisconnectClick
|
|
986
|
+
);
|
|
987
|
+
options.panel.revokeButton.removeEventListener("click", handleRevokeClick);
|
|
988
|
+
options.panel.cancelManagedButton.removeEventListener(
|
|
989
|
+
"click",
|
|
990
|
+
handleManagedCancelClick
|
|
991
|
+
);
|
|
524
992
|
}
|
|
525
993
|
});
|
|
526
994
|
}
|
|
@@ -550,6 +1018,55 @@ function createButton(document, label, className = "") {
|
|
|
550
1018
|
var MESSAGES = Object.freeze({
|
|
551
1019
|
"en-US": Object.freeze({
|
|
552
1020
|
title: "External Agent connection",
|
|
1021
|
+
agentLabel: "Agent",
|
|
1022
|
+
codexManaged: "Codex \xB7 managed (experimental)",
|
|
1023
|
+
connectManaged: "Connect Codex",
|
|
1024
|
+
disconnectManaged: "Disconnect",
|
|
1025
|
+
revokeManaged: "Revoke grant",
|
|
1026
|
+
cancelManaged: "Cancel managed task",
|
|
1027
|
+
resultTitle: "Managed result",
|
|
1028
|
+
controlConnection: (state, mode) => `Connection: ${state} \xB7 Mode: ${mode}`,
|
|
1029
|
+
controlAuth: (auth, grant) => `Auth: ${auth} \xB7 Grant: ${grant}`,
|
|
1030
|
+
controlModel: (model) => `Model: ${model}`,
|
|
1031
|
+
controlRevision: (task) => `Revision ${String(task.revision)}: ${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
|
|
1032
|
+
controlValidation: (outcome) => `Validation: ${outcome}`,
|
|
1033
|
+
controlFailure: (error, action) => `Error: ${error} \xB7 Next: ${action}`,
|
|
1034
|
+
controlUnavailable: "Page connection management is unavailable; external handoffs still fall back honestly to the Agent inbox.",
|
|
1035
|
+
controlErrorText: Object.freeze({
|
|
1036
|
+
AGENT_BINARY_NOT_FOUND: "Codex is not installed or is not on PATH.",
|
|
1037
|
+
AGENT_BINARY_UNTRUSTED: "The resolved Codex executable is not trusted.",
|
|
1038
|
+
AGENT_VERSION_UNSUPPORTED: "The installed Codex version is unsupported.",
|
|
1039
|
+
APP_SERVER_HANDSHAKE_FAILED: "Codex App Server did not complete startup.",
|
|
1040
|
+
AGENT_AUTH_REQUIRED: "Codex requires an authenticated account.",
|
|
1041
|
+
AGENT_MODEL_UNAVAILABLE: "The configured Codex model is unavailable.",
|
|
1042
|
+
AGENT_PROTOCOL_INCOMPATIBLE: "The Codex App Server protocol is incompatible.",
|
|
1043
|
+
CODEX_CONFIG_ISOLATION_UNSUPPORTED: "This Codex version cannot prove managed configuration isolation.",
|
|
1044
|
+
MANAGED_GRANT_INVALID: "The saved project grant failed validation.",
|
|
1045
|
+
MANAGED_PLATFORM_UNSUPPORTED: "Managed execution is not proven safe on this platform.",
|
|
1046
|
+
MANAGED_GIT_REQUIRED: "Managed execution requires a Git repository.",
|
|
1047
|
+
MANAGED_SNAPSHOT_FAILED: "The independent workspace snapshot could not be created.",
|
|
1048
|
+
MANAGED_SCOPE_VIOLATION: "The candidate change exceeded its authorized paths.",
|
|
1049
|
+
MANAGED_CHANGE_LIMIT_EXCEEDED: "The candidate change exceeded safety limits.",
|
|
1050
|
+
MANAGED_VALIDATION_FAILED: "A required check failed or changed the candidate diff.",
|
|
1051
|
+
MANAGED_WORKSPACE_CONFLICT: "An authorized source file changed during execution.",
|
|
1052
|
+
MANAGED_APPLY_FAILED: "The audited change could not be applied safely.",
|
|
1053
|
+
MANAGED_CLEANUP_INCOMPLETE: "Managed thread or workspace cleanup is incomplete."
|
|
1054
|
+
}),
|
|
1055
|
+
controlActionText: Object.freeze({
|
|
1056
|
+
"install-agent": "Install Codex and retry.",
|
|
1057
|
+
"use-supported-version": "Use a supported Codex version.",
|
|
1058
|
+
"sign-in": "Sign in with Codex, then retry.",
|
|
1059
|
+
"choose-available-model": "Choose an available model in Codex configuration.",
|
|
1060
|
+
"use-inbox": "Continue with the Agent inbox fallback.",
|
|
1061
|
+
"confirm-managed-access": "Revoke the invalid grant and confirm access again.",
|
|
1062
|
+
"review-candidate-diff": "Review the candidate diff and validation output.",
|
|
1063
|
+
"review-workspace-conflict": "Review the workspace changes before retrying.",
|
|
1064
|
+
retry: "Retry the managed connection.",
|
|
1065
|
+
"inspect-cleanup-warning": "Inspect cleanup status before reconnecting."
|
|
1066
|
+
}),
|
|
1067
|
+
resultHeader: (revision, outcome) => `Revision ${String(revision)} \xB7 Validation ${outcome}`,
|
|
1068
|
+
resultDisposition: (outcome) => outcome === "passed" ? "Applied to the project after validation passed." : `Candidate only; it was not applied because validation is ${outcome}.`,
|
|
1069
|
+
resultTiming: (stage, durationMs) => `Timing ${stage}: ${String(durationMs)} ms`,
|
|
553
1070
|
description: "Send through a ready active adapter, or publish to the project Agent inbox when no active adapter is connected.",
|
|
554
1071
|
ready: "Local Broker ready. No Agent connection is assumed until a connector reads or waits.",
|
|
555
1072
|
readyWaiting: (count) => `${String(count)} active connector wait request(s); publishing can wake them immediately.`,
|
|
@@ -573,19 +1090,69 @@ var MESSAGES = Object.freeze({
|
|
|
573
1090
|
refresh: "Refresh pickup status",
|
|
574
1091
|
resolveUnknown: "Workspace reviewed \u2014 allow a new task",
|
|
575
1092
|
settingsTitle: "Project connection setup",
|
|
576
|
-
settingsHelp: "
|
|
1093
|
+
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.",
|
|
577
1094
|
disclosureTitle: "Confirm external Agent handoff",
|
|
578
1095
|
disclosureIntro: (targets, files) => `Publish ${String(targets)} selected target(s) referencing ${String(files)} relative file(s).`,
|
|
579
1096
|
disclosureData: "The handoff includes your instructions, component/page context, sanitized DOM/CSS, and bounded source read again by the local Node service.",
|
|
580
1097
|
disclosureInbox: "Any SpotPatch connector configured for this project may read it during the 15-minute lifetime.",
|
|
581
1098
|
disclosureProvider: "The Agent host may send the content to its cloud provider under that product's data policy.",
|
|
582
1099
|
disclosureNoGuarantee: "External edits do not receive SpotPatch's built-in worktree, Apply, or Revert guarantees.",
|
|
1100
|
+
disclosureManagedGuarantee: "Managed Codex edits run in an independent temporary snapshot. SpotPatch audits the diff and only applies it when every trusted required check passes.",
|
|
583
1101
|
cancel: "Cancel",
|
|
584
1102
|
confirm: "Confirm and send",
|
|
585
1103
|
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
1104
|
}),
|
|
587
1105
|
"zh-CN": Object.freeze({
|
|
588
1106
|
title: "\u5916\u90E8 Agent \u8FDE\u63A5",
|
|
1107
|
+
agentLabel: "Agent",
|
|
1108
|
+
codexManaged: "Codex \xB7 \u53D7\u7BA1\u6A21\u5F0F\uFF08\u5B9E\u9A8C\u6027\uFF09",
|
|
1109
|
+
connectManaged: "\u8FDE\u63A5 Codex",
|
|
1110
|
+
disconnectManaged: "\u65AD\u5F00\u8FDE\u63A5",
|
|
1111
|
+
revokeManaged: "\u64A4\u9500\u6388\u6743",
|
|
1112
|
+
cancelManaged: "\u53D6\u6D88\u53D7\u7BA1\u4EFB\u52A1",
|
|
1113
|
+
resultTitle: "\u53D7\u7BA1\u6267\u884C\u7ED3\u679C",
|
|
1114
|
+
controlConnection: (state, mode) => `\u8FDE\u63A5\uFF1A${state} \xB7 \u6A21\u5F0F\uFF1A${mode}`,
|
|
1115
|
+
controlAuth: (auth, grant) => `\u8BA4\u8BC1\uFF1A${auth} \xB7 \u6388\u6743\uFF1A${grant}`,
|
|
1116
|
+
controlModel: (model) => `\u6A21\u578B\uFF1A${model}`,
|
|
1117
|
+
controlRevision: (task) => `revision ${String(task.revision)}\uFF1A${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
|
|
1118
|
+
controlValidation: (outcome) => `\u9A8C\u8BC1\uFF1A${outcome}`,
|
|
1119
|
+
controlFailure: (error, action) => `\u9519\u8BEF\uFF1A${error} \xB7 \u4E0B\u4E00\u6B65\uFF1A${action}`,
|
|
1120
|
+
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",
|
|
1121
|
+
controlErrorText: Object.freeze({
|
|
1122
|
+
AGENT_BINARY_NOT_FOUND: "\u672A\u5B89\u88C5 Codex\uFF0C\u6216 Codex \u4E0D\u5728 PATH \u4E2D\u3002",
|
|
1123
|
+
AGENT_BINARY_UNTRUSTED: "\u89E3\u6790\u5230\u7684 Codex \u53EF\u6267\u884C\u6587\u4EF6\u4E0D\u53EF\u4FE1\u3002",
|
|
1124
|
+
AGENT_VERSION_UNSUPPORTED: "\u5DF2\u5B89\u88C5\u7684 Codex \u7248\u672C\u4E0D\u53D7\u652F\u6301\u3002",
|
|
1125
|
+
APP_SERVER_HANDSHAKE_FAILED: "Codex App Server \u672A\u5B8C\u6210\u542F\u52A8\u63E1\u624B\u3002",
|
|
1126
|
+
AGENT_AUTH_REQUIRED: "Codex \u9700\u8981\u5DF2\u767B\u5F55\u7684\u8D26\u6237\u3002",
|
|
1127
|
+
AGENT_MODEL_UNAVAILABLE: "Codex \u914D\u7F6E\u7684\u6A21\u578B\u5F53\u524D\u4E0D\u53EF\u7528\u3002",
|
|
1128
|
+
AGENT_PROTOCOL_INCOMPATIBLE: "Codex App Server \u534F\u8BAE\u4E0D\u517C\u5BB9\u3002",
|
|
1129
|
+
CODEX_CONFIG_ISOLATION_UNSUPPORTED: "\u5F53\u524D Codex \u7248\u672C\u65E0\u6CD5\u8BC1\u660E\u53D7\u7BA1\u914D\u7F6E\u9694\u79BB\u3002",
|
|
1130
|
+
MANAGED_GRANT_INVALID: "\u4FDD\u5B58\u7684\u9879\u76EE\u6388\u6743\u672A\u901A\u8FC7\u6821\u9A8C\u3002",
|
|
1131
|
+
MANAGED_PLATFORM_UNSUPPORTED: "\u5F53\u524D\u5E73\u53F0\u5C1A\u672A\u8BC1\u660E\u53EF\u5B89\u5168\u8FD0\u884C\u53D7\u7BA1\u6267\u884C\u3002",
|
|
1132
|
+
MANAGED_GIT_REQUIRED: "\u53D7\u7BA1\u6267\u884C\u8981\u6C42\u9879\u76EE\u4F4D\u4E8E Git \u4ED3\u5E93\u4E2D\u3002",
|
|
1133
|
+
MANAGED_SNAPSHOT_FAILED: "\u65E0\u6CD5\u521B\u5EFA\u72EC\u7ACB\u5DE5\u4F5C\u533A\u5FEB\u7167\u3002",
|
|
1134
|
+
MANAGED_SCOPE_VIOLATION: "\u5019\u9009\u4FEE\u6539\u8D85\u51FA\u6388\u6743\u8DEF\u5F84\u3002",
|
|
1135
|
+
MANAGED_CHANGE_LIMIT_EXCEEDED: "\u5019\u9009\u4FEE\u6539\u8D85\u8FC7\u5B89\u5168\u4E0A\u9650\u3002",
|
|
1136
|
+
MANAGED_VALIDATION_FAILED: "required check \u5931\u8D25\u6216\u6539\u53D8\u4E86\u5019\u9009 diff\u3002",
|
|
1137
|
+
MANAGED_WORKSPACE_CONFLICT: "\u6267\u884C\u671F\u95F4\u6388\u6743\u6E90\u7801\u53D1\u751F\u4E86\u53D8\u5316\u3002",
|
|
1138
|
+
MANAGED_APPLY_FAILED: "\u5DF2\u5BA1\u8BA1\u4FEE\u6539\u65E0\u6CD5\u5B89\u5168\u5199\u5165\u3002",
|
|
1139
|
+
MANAGED_CLEANUP_INCOMPLETE: "\u53D7\u7BA1 thread \u6216\u5DE5\u4F5C\u533A\u6E05\u7406\u4E0D\u5B8C\u6574\u3002"
|
|
1140
|
+
}),
|
|
1141
|
+
controlActionText: Object.freeze({
|
|
1142
|
+
"install-agent": "\u5B89\u88C5 Codex \u540E\u91CD\u8BD5\u3002",
|
|
1143
|
+
"use-supported-version": "\u6539\u7528\u53D7\u652F\u6301\u7684 Codex \u7248\u672C\u3002",
|
|
1144
|
+
"sign-in": "\u767B\u5F55 Codex \u540E\u91CD\u8BD5\u3002",
|
|
1145
|
+
"choose-available-model": "\u5728 Codex \u914D\u7F6E\u4E2D\u9009\u62E9\u53EF\u7528\u6A21\u578B\u3002",
|
|
1146
|
+
"use-inbox": "\u7EE7\u7EED\u4F7F\u7528 Agent \u6536\u4EF6\u7BB1\u964D\u7EA7\u8DEF\u5F84\u3002",
|
|
1147
|
+
"confirm-managed-access": "\u64A4\u9500\u65E0\u6548\u6388\u6743\u5E76\u91CD\u65B0\u786E\u8BA4\u3002",
|
|
1148
|
+
"review-candidate-diff": "\u68C0\u67E5\u5019\u9009 diff \u548C\u9A8C\u8BC1\u8F93\u51FA\u3002",
|
|
1149
|
+
"review-workspace-conflict": "\u68C0\u67E5\u5DE5\u4F5C\u533A\u53D8\u5316\u540E\u518D\u91CD\u8BD5\u3002",
|
|
1150
|
+
retry: "\u91CD\u8BD5\u53D7\u7BA1\u8FDE\u63A5\u3002",
|
|
1151
|
+
"inspect-cleanup-warning": "\u786E\u8BA4\u6E05\u7406\u72B6\u6001\u540E\u518D\u8FDE\u63A5\u3002"
|
|
1152
|
+
}),
|
|
1153
|
+
resultHeader: (revision, outcome) => `revision ${String(revision)} \xB7 \u9A8C\u8BC1 ${outcome}`,
|
|
1154
|
+
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`,
|
|
1155
|
+
resultTiming: (stage, durationMs) => `\u8017\u65F6 ${stage}: ${String(durationMs)} ms`,
|
|
589
1156
|
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
1157
|
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
1158
|
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`,
|
|
@@ -609,13 +1176,14 @@ var MESSAGES = Object.freeze({
|
|
|
609
1176
|
refresh: "\u5237\u65B0\u53D6\u4EF6\u72B6\u6001",
|
|
610
1177
|
resolveUnknown: "\u5DF2\u6838\u5BF9\u5DE5\u4F5C\u533A\uFF0C\u5141\u8BB8\u65B0\u4EFB\u52A1",
|
|
611
1178
|
settingsTitle: "\u9879\u76EE\u8FDE\u63A5\u8BBE\u7F6E",
|
|
612
|
-
settingsHelp: "
|
|
1179
|
+
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",
|
|
613
1180
|
disclosureTitle: "\u786E\u8BA4\u53D1\u5E03\u5230\u5916\u90E8 Agent",
|
|
614
1181
|
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
1182
|
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
1183
|
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
1184
|
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
1185
|
disclosureNoGuarantee: "\u5916\u90E8\u4FEE\u6539\u4E0D\u5177\u5907 SpotPatch \u5185\u5EFA Agent \u7684 worktree\u3001Apply \u6216 Revert \u4FDD\u8BC1\u3002",
|
|
1186
|
+
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",
|
|
619
1187
|
cancel: "\u53D6\u6D88",
|
|
620
1188
|
confirm: "\u786E\u8BA4\u5E76\u53D1\u9001",
|
|
621
1189
|
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"
|
|
@@ -632,9 +1200,17 @@ function createStyles(document) {
|
|
|
632
1200
|
.spotpatch-external-status { padding-left: 10px; border-left: 2px solid var(--spotpatch-accent-cyan); color: #cbd5e1; }
|
|
633
1201
|
.spotpatch-external-status[data-state="error"] { border-color: var(--spotpatch-danger); color: #fecdd3; }
|
|
634
1202
|
.spotpatch-external-status[data-state="picked-up"] { border-color: var(--spotpatch-success); color: #a7f3d0; }
|
|
1203
|
+
.spotpatch-external-control { margin-top: 9px; padding: 9px; border: 1px solid var(--spotpatch-border); border-radius: 8px; background: rgb(3 7 18 / 28%); }
|
|
1204
|
+
.spotpatch-external-control label { display: grid; gap: 4px; color: var(--spotpatch-text-secondary); font-size: 10px; }
|
|
1205
|
+
.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; }
|
|
1206
|
+
.spotpatch-external-control-status { margin: 7px 0 0; color: #cbd5e1; font-size: 10.5px; line-height: 1.5; white-space: pre-wrap; }
|
|
1207
|
+
.spotpatch-external-control-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
1208
|
+
.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; }
|
|
1209
|
+
.spotpatch-external-control-actions button:disabled { cursor: default; opacity: .45; }
|
|
1210
|
+
.spotpatch-external-result { margin-top: 8px; color: var(--spotpatch-text-secondary); font-size: 10px; }
|
|
1211
|
+
.spotpatch-external-result pre { max-height: 180px; overflow: auto; margin: 6px 0 0; padding: 7px; border-radius: 6px; background: var(--spotpatch-bg-input); color: #d8d6ff; font: 9px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre; }
|
|
635
1212
|
.spotpatch-external-settings { margin-top: 9px; }
|
|
636
1213
|
.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
1214
|
.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
1215
|
.spotpatch-external-refresh:disabled { cursor: default; opacity: .45; }
|
|
640
1216
|
.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; }
|
|
@@ -695,6 +1271,44 @@ function disclosurePaths(annotation) {
|
|
|
695
1271
|
)
|
|
696
1272
|
]);
|
|
697
1273
|
}
|
|
1274
|
+
function controlStatusText(value, messages) {
|
|
1275
|
+
const model = value.effectiveModel ?? value.requestedModel;
|
|
1276
|
+
const task = value.task;
|
|
1277
|
+
const parts = [
|
|
1278
|
+
messages.controlConnection(value.connectionState, value.mode),
|
|
1279
|
+
messages.controlAuth(value.authReadiness, value.grantState),
|
|
1280
|
+
...model === void 0 ? [] : [messages.controlModel(model)],
|
|
1281
|
+
...task === void 0 ? [] : [
|
|
1282
|
+
messages.controlRevision(task),
|
|
1283
|
+
...task.validationOutcome === void 0 ? [] : [messages.controlValidation(task.validationOutcome)]
|
|
1284
|
+
],
|
|
1285
|
+
...value.error === void 0 ? [] : [
|
|
1286
|
+
messages.controlFailure(
|
|
1287
|
+
messages.controlErrorText[value.error.code],
|
|
1288
|
+
messages.controlActionText[value.error.action]
|
|
1289
|
+
)
|
|
1290
|
+
]
|
|
1291
|
+
];
|
|
1292
|
+
return parts.join("\n");
|
|
1293
|
+
}
|
|
1294
|
+
function managedResultText(result, messages) {
|
|
1295
|
+
const files = result.files.map(
|
|
1296
|
+
(file) => `${file.path} +${String(file.additions)} -${String(file.deletions)}`
|
|
1297
|
+
);
|
|
1298
|
+
const checks = result.checks.map(
|
|
1299
|
+
(check) => `${check.id}: ${check.outcome} (${String(check.durationMs)} ms${check.exitCode === void 0 ? "" : `, exit ${String(check.exitCode)}`})`
|
|
1300
|
+
);
|
|
1301
|
+
const timings = Object.entries(result.timings).flatMap(
|
|
1302
|
+
([stage, durationMs]) => durationMs === void 0 ? [] : [messages.resultTiming(stage, durationMs)]
|
|
1303
|
+
);
|
|
1304
|
+
return [
|
|
1305
|
+
messages.resultHeader(result.revision, result.validationOutcome),
|
|
1306
|
+
messages.resultDisposition(result.validationOutcome),
|
|
1307
|
+
...files,
|
|
1308
|
+
...checks,
|
|
1309
|
+
...timings
|
|
1310
|
+
].join("\n");
|
|
1311
|
+
}
|
|
698
1312
|
function createExternalHandoffPanel(document, framework, locale, sessionId, subscribeLocale, onViewChange) {
|
|
699
1313
|
const options = {
|
|
700
1314
|
document,
|
|
@@ -712,7 +1326,10 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
712
1326
|
let retryable = false;
|
|
713
1327
|
let unknownDelivery = false;
|
|
714
1328
|
let contextReady = false;
|
|
1329
|
+
let controlOperationBusy = false;
|
|
715
1330
|
let visible = false;
|
|
1331
|
+
let control;
|
|
1332
|
+
let managedResultValue;
|
|
716
1333
|
let pendingDisclosure;
|
|
717
1334
|
let previousFocus;
|
|
718
1335
|
const consentKey = `spotpatch:external-handoff-consent:${options.sessionId}`;
|
|
@@ -730,27 +1347,46 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
730
1347
|
status.className = "spotpatch-external-status";
|
|
731
1348
|
status.setAttribute("role", "status");
|
|
732
1349
|
status.setAttribute("aria-live", "polite");
|
|
1350
|
+
const controlRoot = createMarkedElement(document, "div");
|
|
1351
|
+
controlRoot.className = "spotpatch-external-control";
|
|
1352
|
+
const agentLabel = createMarkedElement(document, "label");
|
|
1353
|
+
const agentLabelText = createMarkedElement(document, "span");
|
|
1354
|
+
const agentSelect = createMarkedElement(document, "select");
|
|
1355
|
+
const codexOption = createMarkedElement(document, "option");
|
|
1356
|
+
codexOption.value = "codex";
|
|
1357
|
+
agentSelect.append(codexOption);
|
|
1358
|
+
agentLabel.append(agentLabelText, agentSelect);
|
|
1359
|
+
const controlStatus = createMarkedElement(document, "p");
|
|
1360
|
+
controlStatus.className = "spotpatch-external-control-status";
|
|
1361
|
+
controlStatus.setAttribute("role", "status");
|
|
1362
|
+
const controlActions = createMarkedElement(document, "div");
|
|
1363
|
+
controlActions.className = "spotpatch-external-control-actions";
|
|
1364
|
+
const connectButton = createButton(document, "");
|
|
1365
|
+
const disconnectButton = createButton(document, "");
|
|
1366
|
+
const revokeButton = createButton(document, "");
|
|
1367
|
+
const cancelManagedButton = createButton(document, "");
|
|
1368
|
+
controlActions.append(
|
|
1369
|
+
connectButton,
|
|
1370
|
+
disconnectButton,
|
|
1371
|
+
revokeButton,
|
|
1372
|
+
cancelManagedButton
|
|
1373
|
+
);
|
|
1374
|
+
const managedResult = createMarkedElement(document, "details");
|
|
1375
|
+
managedResult.className = "spotpatch-external-result";
|
|
1376
|
+
managedResult.hidden = true;
|
|
1377
|
+
const managedResultTitle = createMarkedElement(document, "summary");
|
|
1378
|
+
const managedResultSummary = createMarkedElement(document, "p");
|
|
1379
|
+
const managedResultDiff = createMarkedElement(document, "pre");
|
|
1380
|
+
managedResult.append(managedResultTitle, managedResultSummary, managedResultDiff);
|
|
1381
|
+
controlRoot.append(agentLabel, controlStatus, controlActions, managedResult);
|
|
733
1382
|
const resolveButton = createButton(document, "", "spotpatch-external-resolve");
|
|
734
1383
|
resolveButton.hidden = true;
|
|
735
1384
|
const settings = createMarkedElement(document, "details");
|
|
736
1385
|
settings.className = "spotpatch-external-settings";
|
|
737
1386
|
const settingsTitle = createMarkedElement(document, "summary");
|
|
738
1387
|
const settingsHelp = createMarkedElement(document, "p");
|
|
739
|
-
|
|
740
|
-
|
|
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);
|
|
1388
|
+
settings.append(settingsTitle, settingsHelp);
|
|
1389
|
+
root.append(heading, description, controlRoot, status, resolveButton, settings);
|
|
754
1390
|
const sendButton = createButton(document, "", "spotpatch-primary");
|
|
755
1391
|
sendButton.hidden = true;
|
|
756
1392
|
const disclosure = createMarkedElement(document, "div");
|
|
@@ -807,6 +1443,23 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
807
1443
|
resolveButton.hidden = !unknownDelivery;
|
|
808
1444
|
resolveButton.disabled = !visible || operationBusy || !unknownDelivery;
|
|
809
1445
|
};
|
|
1446
|
+
const refreshControlActions = () => {
|
|
1447
|
+
if (control === void 0) {
|
|
1448
|
+
connectButton.disabled = true;
|
|
1449
|
+
disconnectButton.disabled = true;
|
|
1450
|
+
revokeButton.disabled = true;
|
|
1451
|
+
cancelManagedButton.hidden = true;
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const state = control.connectionState;
|
|
1455
|
+
const operationPending = controlOperationBusy || state === "diagnosing" || state === "connecting" || state === "disconnecting";
|
|
1456
|
+
connectButton.disabled = operationPending || state === "ready" || state === "busy";
|
|
1457
|
+
disconnectButton.disabled = operationPending || state === "disconnected";
|
|
1458
|
+
revokeButton.disabled = operationPending || control.grantState !== "valid";
|
|
1459
|
+
const phase = control.task?.managedPhase;
|
|
1460
|
+
cancelManagedButton.hidden = phase === void 0 || phase === "completed" || phase === "review-required" || phase === "failed" || phase === "cancelled" || phase === "cleanup-warning";
|
|
1461
|
+
cancelManagedButton.disabled = operationPending;
|
|
1462
|
+
};
|
|
810
1463
|
const settleDisclosure = (confirmed) => {
|
|
811
1464
|
if (pendingDisclosure === void 0) return;
|
|
812
1465
|
const resolve = pendingDisclosure;
|
|
@@ -850,10 +1503,28 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
850
1503
|
disclosureInbox.textContent = messages.disclosureInbox;
|
|
851
1504
|
disclosureProvider.textContent = messages.disclosureProvider;
|
|
852
1505
|
disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
|
|
1506
|
+
agentLabelText.textContent = messages.agentLabel;
|
|
1507
|
+
codexOption.textContent = messages.codexManaged;
|
|
1508
|
+
connectButton.textContent = messages.connectManaged;
|
|
1509
|
+
disconnectButton.textContent = messages.disconnectManaged;
|
|
1510
|
+
revokeButton.textContent = messages.revokeManaged;
|
|
1511
|
+
cancelManagedButton.textContent = messages.cancelManaged;
|
|
1512
|
+
managedResultTitle.textContent = messages.resultTitle;
|
|
1513
|
+
if (control !== void 0) {
|
|
1514
|
+
controlStatus.textContent = controlStatusText(control, messages);
|
|
1515
|
+
disclosureNoGuarantee.textContent = control.mode === "managed" ? messages.disclosureManagedGuarantee : messages.disclosureNoGuarantee;
|
|
1516
|
+
}
|
|
1517
|
+
if (managedResultValue !== void 0) {
|
|
1518
|
+
managedResultSummary.textContent = managedResultText(
|
|
1519
|
+
managedResultValue,
|
|
1520
|
+
messages
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
853
1523
|
cancelButton.textContent = messages.cancel;
|
|
854
1524
|
confirmButton.textContent = messages.confirm;
|
|
855
1525
|
resolveButton.textContent = messages.resolveUnknown;
|
|
856
1526
|
refreshActions();
|
|
1527
|
+
refreshControlActions();
|
|
857
1528
|
};
|
|
858
1529
|
cancelButton.addEventListener("click", () => {
|
|
859
1530
|
settleDisclosure(false);
|
|
@@ -863,10 +1534,13 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
863
1534
|
});
|
|
864
1535
|
disclosure.addEventListener("keydown", handleDisclosureKeydown);
|
|
865
1536
|
settings.addEventListener("toggle", options.onViewChange);
|
|
1537
|
+
managedResult.addEventListener("toggle", options.onViewChange);
|
|
866
1538
|
const unsubscribeLocale = options.subscribeLocale(applyMessages);
|
|
867
1539
|
applyMessages();
|
|
868
1540
|
status.textContent = messages.ready;
|
|
1541
|
+
controlStatus.textContent = options.locale() === "zh-CN" ? "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8FDE\u63A5\u72B6\u6001\u2026\u2026" : "Reading local connection status\u2026";
|
|
869
1542
|
refreshActions();
|
|
1543
|
+
refreshControlActions();
|
|
870
1544
|
const summaryMessage = (summary) => summary.state === "expired" ? messages.expired(summary.revision) : summary.state === "superseded" ? messages.superseded(summary.revision) : summary.pickupCount > 0 ? messages.pickedUp(
|
|
871
1545
|
summary.revision,
|
|
872
1546
|
summary.pickupCount,
|
|
@@ -900,6 +1574,10 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
900
1574
|
sendButton,
|
|
901
1575
|
refreshButton,
|
|
902
1576
|
resolveButton,
|
|
1577
|
+
cancelManagedButton,
|
|
1578
|
+
connectButton,
|
|
1579
|
+
disconnectButton,
|
|
1580
|
+
revokeButton,
|
|
903
1581
|
confirmDisclosure(annotation) {
|
|
904
1582
|
if (hasConsent()) return Promise.resolve(true);
|
|
905
1583
|
if (pendingDisclosure !== void 0) return Promise.resolve(false);
|
|
@@ -951,6 +1629,30 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
951
1629
|
renderStatus(result) {
|
|
952
1630
|
applyStatus(result);
|
|
953
1631
|
},
|
|
1632
|
+
renderControlStatus(value) {
|
|
1633
|
+
control = value;
|
|
1634
|
+
controlStatus.textContent = controlStatusText(value, messages);
|
|
1635
|
+
controlStatus.dataset.state = value.connectionState;
|
|
1636
|
+
disclosureNoGuarantee.textContent = value.mode === "managed" ? messages.disclosureManagedGuarantee : messages.disclosureNoGuarantee;
|
|
1637
|
+
refreshControlActions();
|
|
1638
|
+
options.onViewChange();
|
|
1639
|
+
},
|
|
1640
|
+
renderControlUnavailable() {
|
|
1641
|
+
control = void 0;
|
|
1642
|
+
controlStatus.dataset.state = "unavailable";
|
|
1643
|
+
controlStatus.textContent = messages.controlUnavailable;
|
|
1644
|
+
disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
|
|
1645
|
+
refreshControlActions();
|
|
1646
|
+
options.onViewChange();
|
|
1647
|
+
},
|
|
1648
|
+
renderManagedResult(result) {
|
|
1649
|
+
managedResultValue = result;
|
|
1650
|
+
managedResult.hidden = false;
|
|
1651
|
+
managedResult.open = true;
|
|
1652
|
+
managedResultSummary.textContent = managedResultText(result, messages);
|
|
1653
|
+
managedResultDiff.textContent = result.diff;
|
|
1654
|
+
options.onViewChange();
|
|
1655
|
+
},
|
|
954
1656
|
renderError(code, canRetry = false) {
|
|
955
1657
|
operationBusy = false;
|
|
956
1658
|
retryable = canRetry;
|
|
@@ -964,6 +1666,10 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
964
1666
|
operationBusy = nextBusy;
|
|
965
1667
|
refreshActions();
|
|
966
1668
|
},
|
|
1669
|
+
setControlBusy(nextBusy) {
|
|
1670
|
+
controlOperationBusy = nextBusy;
|
|
1671
|
+
refreshControlActions();
|
|
1672
|
+
},
|
|
967
1673
|
setContextReady(ready) {
|
|
968
1674
|
contextReady = ready;
|
|
969
1675
|
refreshActions();
|
|
@@ -979,6 +1685,7 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
979
1685
|
settleDisclosure(false);
|
|
980
1686
|
disclosure.removeEventListener("keydown", handleDisclosureKeydown);
|
|
981
1687
|
settings.removeEventListener("toggle", options.onViewChange);
|
|
1688
|
+
managedResult.removeEventListener("toggle", options.onViewChange);
|
|
982
1689
|
unsubscribeLocale();
|
|
983
1690
|
}
|
|
984
1691
|
});
|