@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getExternalHandoffExtension,
|
|
3
3
|
registerExternalHandoffExtension
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BXRMVHNT.js";
|
|
5
5
|
import {
|
|
6
6
|
createButton,
|
|
7
7
|
createMarkedElement
|
|
@@ -9,24 +9,18 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/controller/external-handoff-workflow.ts
|
|
11
11
|
import {
|
|
12
|
-
ERROR_CODES,
|
|
12
|
+
ERROR_CODES as ERROR_CODES2,
|
|
13
|
+
EXTERNAL_AGENT_CONTROL_LIMITS as EXTERNAL_AGENT_CONTROL_LIMITS2,
|
|
14
|
+
EXTERNAL_AGENT_MANAGED_PROFILE,
|
|
13
15
|
EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
|
|
14
16
|
EXTERNAL_HANDOFF_LIMITS,
|
|
15
17
|
EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
|
|
16
|
-
SPOTPATCH_ENDPOINTS,
|
|
17
|
-
SPOTPATCH_TOKEN_HEADER,
|
|
18
|
+
SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
|
|
19
|
+
SPOTPATCH_TOKEN_HEADER as SPOTPATCH_TOKEN_HEADER2,
|
|
18
20
|
isErrorCode
|
|
19
21
|
} from "@spotpatch/shared/external-handoff-browser";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var ExternalHandoffApiError = class extends Error {
|
|
23
|
-
constructor(code) {
|
|
24
|
-
super("SpotPatch external handoff API request failed.");
|
|
25
|
-
this.code = code;
|
|
26
|
-
this.name = "ExternalHandoffApiError";
|
|
27
|
-
}
|
|
28
|
-
code;
|
|
29
|
-
};
|
|
22
|
+
|
|
23
|
+
// src/controller/browser-api.ts
|
|
30
24
|
function record(value) {
|
|
31
25
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
26
|
}
|
|
@@ -36,8 +30,374 @@ function exactKeys(value, keys) {
|
|
|
36
30
|
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
|
|
37
31
|
}
|
|
38
32
|
function validTimestamp(value) {
|
|
39
|
-
|
|
33
|
+
if (typeof value !== "string") return false;
|
|
34
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?Z$/u.exec(
|
|
35
|
+
value
|
|
36
|
+
);
|
|
37
|
+
if (match === null) return false;
|
|
38
|
+
const timestamp = Date.parse(value);
|
|
39
|
+
if (!Number.isFinite(timestamp)) return false;
|
|
40
|
+
const date = new Date(timestamp);
|
|
41
|
+
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]);
|
|
42
|
+
}
|
|
43
|
+
async function readBoundedJson(response, maximumBytes) {
|
|
44
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
45
|
+
if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
|
|
46
|
+
await response.body?.cancel();
|
|
47
|
+
throw new TypeError("SpotPatch API response exceeds its safety limit.");
|
|
48
|
+
}
|
|
49
|
+
if (response.body === null) {
|
|
50
|
+
throw new TypeError("SpotPatch API response body is unavailable.");
|
|
51
|
+
}
|
|
52
|
+
const reader = response.body.getReader();
|
|
53
|
+
const chunks = [];
|
|
54
|
+
let total = 0;
|
|
55
|
+
try {
|
|
56
|
+
for (; ; ) {
|
|
57
|
+
const chunk = await reader.read();
|
|
58
|
+
if (chunk.done) break;
|
|
59
|
+
total += chunk.value.byteLength;
|
|
60
|
+
if (total > maximumBytes) {
|
|
61
|
+
await reader.cancel();
|
|
62
|
+
throw new TypeError("SpotPatch API response exceeds its safety limit.");
|
|
63
|
+
}
|
|
64
|
+
chunks.push(chunk.value);
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
reader.releaseLock();
|
|
68
|
+
}
|
|
69
|
+
const payload = new Uint8Array(total);
|
|
70
|
+
let offset = 0;
|
|
71
|
+
for (const chunk of chunks) {
|
|
72
|
+
payload.set(chunk, offset);
|
|
73
|
+
offset += chunk.byteLength;
|
|
74
|
+
}
|
|
75
|
+
return JSON.parse(
|
|
76
|
+
new TextDecoder("utf-8", { fatal: true }).decode(payload)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
function successData(value) {
|
|
80
|
+
if (!record(value) || !exactKeys(value, ["data", "ok"]) || value.ok !== true) {
|
|
81
|
+
throw new TypeError("SpotPatch API success envelope is invalid.");
|
|
82
|
+
}
|
|
83
|
+
return value.data;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/controller/external-agent-control-client.ts
|
|
87
|
+
import {
|
|
88
|
+
EXTERNAL_AGENT_ACTIONS as ACTIONS,
|
|
89
|
+
EXTERNAL_AGENT_AUTH_READINESS as AUTH_STATES,
|
|
90
|
+
EXTERNAL_AGENT_CHECK_OUTCOMES as CHECK_OUTCOMES,
|
|
91
|
+
EXTERNAL_AGENT_CONNECTION_STATES as CONNECTION_STATES,
|
|
92
|
+
EXTERNAL_AGENT_CONTROL_LIMITS,
|
|
93
|
+
EXTERNAL_AGENT_DELIVERY_STATUSES as DELIVERY_STATES,
|
|
94
|
+
EXTERNAL_AGENT_ERROR_CODES as ERROR_CODES,
|
|
95
|
+
EXTERNAL_AGENT_ERROR_RECOVERABILITY as RECOVERABILITY,
|
|
96
|
+
EXTERNAL_AGENT_ERROR_STAGES as ERROR_STAGES,
|
|
97
|
+
EXTERNAL_AGENT_EXECUTION_STATUSES as EXECUTION_STATES,
|
|
98
|
+
EXTERNAL_AGENT_GRANT_STATES as GRANT_STATES,
|
|
99
|
+
EXTERNAL_AGENT_MANAGED_PHASES as MANAGED_PHASES,
|
|
100
|
+
EXTERNAL_AGENT_MODES as MODES,
|
|
101
|
+
EXTERNAL_AGENT_VALIDATION_OUTCOMES as VALIDATION_OUTCOMES,
|
|
102
|
+
SPOTPATCH_ENDPOINTS,
|
|
103
|
+
SPOTPATCH_TOKEN_HEADER
|
|
104
|
+
} from "@spotpatch/shared/external-handoff-browser";
|
|
105
|
+
var RESPONSE_OVERHEAD_BYTES = 64 * 1024;
|
|
106
|
+
function member(values, value) {
|
|
107
|
+
return typeof value === "string" && values.includes(value);
|
|
108
|
+
}
|
|
109
|
+
function integer(value, minimum = 0) {
|
|
110
|
+
return Number.isSafeInteger(value) && value >= minimum;
|
|
111
|
+
}
|
|
112
|
+
function optionalKeys(value, base, optional) {
|
|
113
|
+
return exactKeys(value, [
|
|
114
|
+
...base,
|
|
115
|
+
...optional.filter((key) => value[key] !== void 0)
|
|
116
|
+
]);
|
|
117
|
+
}
|
|
118
|
+
function safePath(value) {
|
|
119
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096 || value.startsWith("/") || value.includes("\\")) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return value.split("/").every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
123
|
+
}
|
|
124
|
+
function parseFiles(value) {
|
|
125
|
+
if (!Array.isArray(value) || value.length > EXTERNAL_AGENT_CONTROL_LIMITS.maximumChangedFiles) {
|
|
126
|
+
throw new TypeError("Invalid managed file summaries.");
|
|
127
|
+
}
|
|
128
|
+
return value.map((file) => {
|
|
129
|
+
if (!record(file) || !exactKeys(file, ["additions", "deletions", "path"]) || !safePath(file.path) || !integer(file.additions) || !integer(file.deletions)) {
|
|
130
|
+
throw new TypeError("Invalid managed file summary.");
|
|
131
|
+
}
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
path: file.path,
|
|
134
|
+
additions: file.additions,
|
|
135
|
+
deletions: file.deletions
|
|
136
|
+
});
|
|
137
|
+
});
|
|
40
138
|
}
|
|
139
|
+
function parseChecks(value) {
|
|
140
|
+
if (!Array.isArray(value) || value.length > EXTERNAL_AGENT_CONTROL_LIMITS.maximumChecks) {
|
|
141
|
+
throw new TypeError("Invalid managed check summaries.");
|
|
142
|
+
}
|
|
143
|
+
return value.map((check) => {
|
|
144
|
+
if (!record(check) || !optionalKeys(check, ["durationMs", "id", "outcome"], ["exitCode"]) || typeof check.id !== "string" || !/^[A-Za-z0-9._-]{1,128}$/u.test(check.id) || !member(CHECK_OUTCOMES, check.outcome) || !integer(check.durationMs) || check.exitCode !== void 0 && !Number.isSafeInteger(check.exitCode)) {
|
|
145
|
+
throw new TypeError("Invalid managed check summary.");
|
|
146
|
+
}
|
|
147
|
+
return Object.freeze({
|
|
148
|
+
id: check.id,
|
|
149
|
+
outcome: check.outcome,
|
|
150
|
+
durationMs: check.durationMs,
|
|
151
|
+
...check.exitCode === void 0 ? {} : { exitCode: check.exitCode }
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function parseTimings(value) {
|
|
156
|
+
const keys = [
|
|
157
|
+
"preparing",
|
|
158
|
+
"agent",
|
|
159
|
+
"auditing",
|
|
160
|
+
"validating",
|
|
161
|
+
"applying",
|
|
162
|
+
"total"
|
|
163
|
+
];
|
|
164
|
+
if (!record(value) || !optionalKeys(value, [], keys)) {
|
|
165
|
+
throw new TypeError("Invalid managed timings.");
|
|
166
|
+
}
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
if (value[key] !== void 0 && !integer(value[key])) {
|
|
169
|
+
throw new TypeError("Invalid managed timing.");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return Object.freeze(
|
|
173
|
+
Object.fromEntries(
|
|
174
|
+
keys.flatMap((key) => value[key] === void 0 ? [] : [[key, value[key]]])
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
function parseTask(value) {
|
|
179
|
+
if (!record(value) || !optionalKeys(
|
|
180
|
+
value,
|
|
181
|
+
[
|
|
182
|
+
"checks",
|
|
183
|
+
"deliveryStatus",
|
|
184
|
+
"executionStatus",
|
|
185
|
+
"files",
|
|
186
|
+
"managedPhase",
|
|
187
|
+
"revision",
|
|
188
|
+
"timings"
|
|
189
|
+
],
|
|
190
|
+
["resultExpiresAt", "validationOutcome"]
|
|
191
|
+
) || !integer(value.revision, 1) || !member(DELIVERY_STATES, value.deliveryStatus) || !member(EXECUTION_STATES, value.executionStatus) || !member(MANAGED_PHASES, value.managedPhase) || value.validationOutcome !== void 0 && !member(VALIDATION_OUTCOMES, value.validationOutcome) || value.resultExpiresAt !== void 0 && !validTimestamp(value.resultExpiresAt)) {
|
|
192
|
+
throw new TypeError("Invalid managed task status.");
|
|
193
|
+
}
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
revision: value.revision,
|
|
196
|
+
deliveryStatus: value.deliveryStatus,
|
|
197
|
+
executionStatus: value.executionStatus,
|
|
198
|
+
managedPhase: value.managedPhase,
|
|
199
|
+
...value.validationOutcome === void 0 ? {} : { validationOutcome: value.validationOutcome },
|
|
200
|
+
files: parseFiles(value.files),
|
|
201
|
+
checks: parseChecks(value.checks),
|
|
202
|
+
timings: parseTimings(value.timings),
|
|
203
|
+
...value.resultExpiresAt === void 0 ? {} : { resultExpiresAt: value.resultExpiresAt }
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function parseExternalAgentControlStatus(value) {
|
|
207
|
+
if (!record(value) || !optionalKeys(
|
|
208
|
+
value,
|
|
209
|
+
[
|
|
210
|
+
"adapter",
|
|
211
|
+
"authReadiness",
|
|
212
|
+
"connectionState",
|
|
213
|
+
"grantState",
|
|
214
|
+
"mode",
|
|
215
|
+
"schemaVersion",
|
|
216
|
+
"sequence",
|
|
217
|
+
"updatedAt"
|
|
218
|
+
],
|
|
219
|
+
["effectiveModel", "error", "requestedModel", "task"]
|
|
220
|
+
) || value.schemaVersion !== 1 || !integer(value.sequence) || !member(MODES, value.mode) || !member(CONNECTION_STATES, value.connectionState) || !member(AUTH_STATES, value.authReadiness) || !member(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") {
|
|
221
|
+
throw new TypeError("Invalid external Agent control status.");
|
|
222
|
+
}
|
|
223
|
+
let error;
|
|
224
|
+
if (value.error !== void 0) {
|
|
225
|
+
if (!record(value.error) || !exactKeys(value.error, ["action", "code", "recoverability", "stage"]) || !member(ERROR_CODES, value.error.code) || !member(ERROR_STAGES, value.error.stage) || !member(RECOVERABILITY, value.error.recoverability) || !member(ACTIONS, value.error.action)) {
|
|
226
|
+
throw new TypeError("Invalid external Agent error.");
|
|
227
|
+
}
|
|
228
|
+
error = Object.freeze({
|
|
229
|
+
code: value.error.code,
|
|
230
|
+
stage: value.error.stage,
|
|
231
|
+
recoverability: value.error.recoverability,
|
|
232
|
+
action: value.error.action
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return Object.freeze({
|
|
236
|
+
schemaVersion: 1,
|
|
237
|
+
sequence: value.sequence,
|
|
238
|
+
mode: value.mode,
|
|
239
|
+
adapter: Object.freeze({
|
|
240
|
+
kind: "codex",
|
|
241
|
+
maturity: "experimental",
|
|
242
|
+
availability: value.adapter.availability
|
|
243
|
+
}),
|
|
244
|
+
connectionState: value.connectionState,
|
|
245
|
+
authReadiness: value.authReadiness,
|
|
246
|
+
grantState: value.grantState,
|
|
247
|
+
...value.requestedModel === void 0 ? {} : { requestedModel: value.requestedModel },
|
|
248
|
+
...value.effectiveModel === void 0 ? {} : { effectiveModel: value.effectiveModel },
|
|
249
|
+
...value.task === void 0 ? {} : { task: parseTask(value.task) },
|
|
250
|
+
...error === void 0 ? {} : { error },
|
|
251
|
+
updatedAt: value.updatedAt
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function parseExternalAgentManagedResult(value) {
|
|
255
|
+
if (!record(value) || !exactKeys(value, [
|
|
256
|
+
"checks",
|
|
257
|
+
"diff",
|
|
258
|
+
"expiresAt",
|
|
259
|
+
"files",
|
|
260
|
+
"revision",
|
|
261
|
+
"timings",
|
|
262
|
+
"validationOutcome"
|
|
263
|
+
]) || !integer(value.revision, 1) || typeof value.diff !== "string" || value.diff.length > EXTERNAL_AGENT_CONTROL_LIMITS.maximumResultDiffBytes || !validTimestamp(value.expiresAt) || !member(VALIDATION_OUTCOMES, value.validationOutcome)) {
|
|
264
|
+
throw new TypeError("Invalid managed Agent result.");
|
|
265
|
+
}
|
|
266
|
+
return Object.freeze({
|
|
267
|
+
revision: value.revision,
|
|
268
|
+
diff: value.diff,
|
|
269
|
+
files: parseFiles(value.files),
|
|
270
|
+
checks: parseChecks(value.checks),
|
|
271
|
+
timings: parseTimings(value.timings),
|
|
272
|
+
validationOutcome: value.validationOutcome,
|
|
273
|
+
expiresAt: value.expiresAt
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function parseEvent(value) {
|
|
277
|
+
if (!record(value) || typeof value.type !== "string") {
|
|
278
|
+
throw new TypeError("Invalid external Agent event.");
|
|
279
|
+
}
|
|
280
|
+
if (value.type === "status" && exactKeys(value, ["data", "type"])) {
|
|
281
|
+
return Object.freeze({
|
|
282
|
+
type: "status",
|
|
283
|
+
data: parseExternalAgentControlStatus(value.data)
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (value.type === "heartbeat" && exactKeys(value, ["emittedAt", "sequence", "type"]) && integer(value.sequence) && validTimestamp(value.emittedAt)) {
|
|
287
|
+
return Object.freeze({
|
|
288
|
+
type: "heartbeat",
|
|
289
|
+
sequence: value.sequence,
|
|
290
|
+
emittedAt: value.emittedAt
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
throw new TypeError("Invalid external Agent event.");
|
|
294
|
+
}
|
|
295
|
+
function createExternalAgentControlClient(fetch, sessionToken) {
|
|
296
|
+
const request = async (endpoint, body, maximumBytes = RESPONSE_OVERHEAD_BYTES) => {
|
|
297
|
+
const response = await fetch(endpoint, {
|
|
298
|
+
method: "POST",
|
|
299
|
+
headers: {
|
|
300
|
+
"Content-Type": "application/json",
|
|
301
|
+
[SPOTPATCH_TOKEN_HEADER]: sessionToken
|
|
302
|
+
},
|
|
303
|
+
body: JSON.stringify(body)
|
|
304
|
+
});
|
|
305
|
+
const envelope = await readBoundedJson(response, maximumBytes);
|
|
306
|
+
if (!response.ok) throw new TypeError("External Agent control request failed.");
|
|
307
|
+
return successData(envelope);
|
|
308
|
+
};
|
|
309
|
+
return Object.freeze({
|
|
310
|
+
async status() {
|
|
311
|
+
return parseExternalAgentControlStatus(
|
|
312
|
+
await request(SPOTPATCH_ENDPOINTS.externalAgentControlStatus, {})
|
|
313
|
+
);
|
|
314
|
+
},
|
|
315
|
+
async connect(value) {
|
|
316
|
+
return parseExternalAgentControlStatus(
|
|
317
|
+
await request(SPOTPATCH_ENDPOINTS.externalAgentControlConnect, value)
|
|
318
|
+
);
|
|
319
|
+
},
|
|
320
|
+
async disconnect(value) {
|
|
321
|
+
return parseExternalAgentControlStatus(
|
|
322
|
+
await request(SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect, value)
|
|
323
|
+
);
|
|
324
|
+
},
|
|
325
|
+
async cancel(value) {
|
|
326
|
+
return parseExternalAgentControlStatus(
|
|
327
|
+
await request(SPOTPATCH_ENDPOINTS.externalAgentControlCancel, value)
|
|
328
|
+
);
|
|
329
|
+
},
|
|
330
|
+
async result(revision) {
|
|
331
|
+
return parseExternalAgentManagedResult(
|
|
332
|
+
await request(
|
|
333
|
+
SPOTPATCH_ENDPOINTS.externalAgentResult,
|
|
334
|
+
{ revision },
|
|
335
|
+
EXTERNAL_AGENT_CONTROL_LIMITS.maximumResultDiffBytes + RESPONSE_OVERHEAD_BYTES
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
},
|
|
339
|
+
async events(afterSequence, signal, onEvent) {
|
|
340
|
+
const response = await fetch(SPOTPATCH_ENDPOINTS.externalAgentEvents, {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: {
|
|
343
|
+
"Content-Type": "application/json",
|
|
344
|
+
[SPOTPATCH_TOKEN_HEADER]: sessionToken
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify(afterSequence === void 0 ? {} : { afterSequence }),
|
|
347
|
+
signal
|
|
348
|
+
});
|
|
349
|
+
if (!response.ok || response.body === null) {
|
|
350
|
+
await response.body?.cancel();
|
|
351
|
+
throw new TypeError("External Agent event stream is unavailable.");
|
|
352
|
+
}
|
|
353
|
+
const reader = response.body.getReader();
|
|
354
|
+
let pending = new Uint8Array(0);
|
|
355
|
+
try {
|
|
356
|
+
for (; ; ) {
|
|
357
|
+
const chunk = await reader.read();
|
|
358
|
+
if (chunk.done) break;
|
|
359
|
+
const combined = new Uint8Array(pending.byteLength + chunk.value.byteLength);
|
|
360
|
+
combined.set(pending);
|
|
361
|
+
combined.set(chunk.value, pending.byteLength);
|
|
362
|
+
pending = combined;
|
|
363
|
+
for (; ; ) {
|
|
364
|
+
const newline = pending.indexOf(10);
|
|
365
|
+
if (newline === -1) break;
|
|
366
|
+
if (newline === 0 || newline > EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventLineBytes) {
|
|
367
|
+
throw new TypeError("External Agent event line is invalid.");
|
|
368
|
+
}
|
|
369
|
+
const line = pending.slice(0, newline);
|
|
370
|
+
pending = pending.slice(newline + 1);
|
|
371
|
+
const value = JSON.parse(
|
|
372
|
+
new TextDecoder("utf-8", { fatal: true }).decode(line)
|
|
373
|
+
);
|
|
374
|
+
onEvent(parseEvent(value));
|
|
375
|
+
}
|
|
376
|
+
if (pending.byteLength > EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventLineBytes) {
|
|
377
|
+
throw new TypeError("External Agent event line exceeds its limit.");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (pending.byteLength !== 0) {
|
|
381
|
+
throw new TypeError("External Agent event stream ended mid-record.");
|
|
382
|
+
}
|
|
383
|
+
} finally {
|
|
384
|
+
reader.releaseLock();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/controller/external-handoff-workflow.ts
|
|
391
|
+
var MAX_SUMMARY_RESPONSE_BYTES = 64 * 1024;
|
|
392
|
+
var OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{22,128}$/u;
|
|
393
|
+
var ExternalHandoffApiError = class extends Error {
|
|
394
|
+
constructor(code) {
|
|
395
|
+
super("SpotPatch external handoff API request failed.");
|
|
396
|
+
this.code = code;
|
|
397
|
+
this.name = "ExternalHandoffApiError";
|
|
398
|
+
}
|
|
399
|
+
code;
|
|
400
|
+
};
|
|
41
401
|
function parseActiveAdapter(value) {
|
|
42
402
|
if (value === null) return null;
|
|
43
403
|
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)) {
|
|
@@ -162,53 +522,9 @@ function parseSummary(value) {
|
|
|
162
522
|
...value.pickedUpAt === void 0 ? {} : { pickedUpAt: value.pickedUpAt }
|
|
163
523
|
});
|
|
164
524
|
}
|
|
165
|
-
async function boundedJson(response) {
|
|
166
|
-
const declaredLength = Number(response.headers.get("content-length"));
|
|
167
|
-
if (Number.isFinite(declaredLength) && declaredLength > MAX_SUMMARY_RESPONSE_BYTES) {
|
|
168
|
-
await response.body?.cancel();
|
|
169
|
-
throw new ExternalHandoffApiError();
|
|
170
|
-
}
|
|
171
|
-
if (response.body === null) throw new ExternalHandoffApiError();
|
|
172
|
-
const reader = response.body.getReader();
|
|
173
|
-
const chunks = [];
|
|
174
|
-
let total = 0;
|
|
175
|
-
try {
|
|
176
|
-
for (; ; ) {
|
|
177
|
-
const chunk = await reader.read();
|
|
178
|
-
if (chunk.done) break;
|
|
179
|
-
total += chunk.value.byteLength;
|
|
180
|
-
if (total > MAX_SUMMARY_RESPONSE_BYTES) {
|
|
181
|
-
await reader.cancel();
|
|
182
|
-
throw new ExternalHandoffApiError();
|
|
183
|
-
}
|
|
184
|
-
chunks.push(chunk.value);
|
|
185
|
-
}
|
|
186
|
-
} finally {
|
|
187
|
-
reader.releaseLock();
|
|
188
|
-
}
|
|
189
|
-
const payload = new Uint8Array(total);
|
|
190
|
-
let offset = 0;
|
|
191
|
-
for (const chunk of chunks) {
|
|
192
|
-
payload.set(chunk, offset);
|
|
193
|
-
offset += chunk.byteLength;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
return JSON.parse(
|
|
197
|
-
new TextDecoder("utf-8", { fatal: true }).decode(payload)
|
|
198
|
-
);
|
|
199
|
-
} catch {
|
|
200
|
-
throw new ExternalHandoffApiError();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
525
|
function failureCode(value) {
|
|
204
526
|
return record(value) && value.ok === false && record(value.error) && isErrorCode(value.error.code) ? value.error.code : void 0;
|
|
205
527
|
}
|
|
206
|
-
function successData(value) {
|
|
207
|
-
if (!record(value) || !exactKeys(value, ["data", "ok"]) || value.ok !== true) {
|
|
208
|
-
throw new ExternalHandoffApiError();
|
|
209
|
-
}
|
|
210
|
-
return value.data;
|
|
211
|
-
}
|
|
212
528
|
function omitBrowserCode(annotation) {
|
|
213
529
|
return {
|
|
214
530
|
...annotation,
|
|
@@ -241,14 +557,23 @@ function api(options) {
|
|
|
241
557
|
method: "POST",
|
|
242
558
|
headers: {
|
|
243
559
|
"Content-Type": "application/json",
|
|
244
|
-
[
|
|
560
|
+
[SPOTPATCH_TOKEN_HEADER2]: options.sessionToken
|
|
245
561
|
},
|
|
246
562
|
body: JSON.stringify(body),
|
|
247
563
|
signal: controller.signal
|
|
248
564
|
});
|
|
249
|
-
|
|
565
|
+
let envelope;
|
|
566
|
+
try {
|
|
567
|
+
envelope = await readBoundedJson(response, MAX_SUMMARY_RESPONSE_BYTES);
|
|
568
|
+
} catch {
|
|
569
|
+
throw new ExternalHandoffApiError();
|
|
570
|
+
}
|
|
250
571
|
if (!response.ok) throw new ExternalHandoffApiError(failureCode(envelope));
|
|
251
|
-
|
|
572
|
+
try {
|
|
573
|
+
return successData(envelope);
|
|
574
|
+
} catch {
|
|
575
|
+
throw new ExternalHandoffApiError();
|
|
576
|
+
}
|
|
252
577
|
} finally {
|
|
253
578
|
pending.delete(controller);
|
|
254
579
|
}
|
|
@@ -260,12 +585,12 @@ function api(options) {
|
|
|
260
585
|
},
|
|
261
586
|
async capability() {
|
|
262
587
|
return parseCapability(
|
|
263
|
-
await request(
|
|
588
|
+
await request(SPOTPATCH_ENDPOINTS2.externalHandoffCapability, {})
|
|
264
589
|
);
|
|
265
590
|
},
|
|
266
591
|
async publish(requestId, annotation) {
|
|
267
592
|
return parsePublishResult(
|
|
268
|
-
await request(
|
|
593
|
+
await request(SPOTPATCH_ENDPOINTS2.externalHandoffPublish, {
|
|
269
594
|
annotation: omitBrowserCode(annotation),
|
|
270
595
|
requestId
|
|
271
596
|
})
|
|
@@ -274,14 +599,14 @@ function api(options) {
|
|
|
274
599
|
async status(cursor) {
|
|
275
600
|
return parseStatusResult(
|
|
276
601
|
await request(
|
|
277
|
-
|
|
602
|
+
SPOTPATCH_ENDPOINTS2.externalHandoffStatus,
|
|
278
603
|
cursor === void 0 ? {} : { cursor }
|
|
279
604
|
)
|
|
280
605
|
);
|
|
281
606
|
},
|
|
282
607
|
async resolveDelivery(cursor) {
|
|
283
608
|
return parseStatusResult(
|
|
284
|
-
await request(
|
|
609
|
+
await request(SPOTPATCH_ENDPOINTS2.externalHandoffResolveDelivery, {
|
|
285
610
|
confirmation: "workspace-reviewed",
|
|
286
611
|
cursor
|
|
287
612
|
})
|
|
@@ -298,6 +623,7 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
298
623
|
window
|
|
299
624
|
};
|
|
300
625
|
const client = api(options);
|
|
626
|
+
const controlClient = createExternalAgentControlClient(fetch, sessionToken);
|
|
301
627
|
const timers = /* @__PURE__ */ new Set();
|
|
302
628
|
const lifecycle = {
|
|
303
629
|
disposed: false,
|
|
@@ -306,12 +632,152 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
306
632
|
userActionPending: false
|
|
307
633
|
};
|
|
308
634
|
let capability;
|
|
635
|
+
let controlStatus;
|
|
636
|
+
let controlEventController;
|
|
637
|
+
let controlReconnectDelay = EXTERNAL_AGENT_CONTROL_LIMITS2.eventReconnectMinimumMs;
|
|
638
|
+
let controlReconnectTimer;
|
|
639
|
+
let controlActionPending = false;
|
|
640
|
+
let managedResultRevision;
|
|
641
|
+
let managedResultLoadingRevision;
|
|
309
642
|
let current;
|
|
310
643
|
let retryablePublish;
|
|
644
|
+
const isDisposed = () => lifecycle.disposed;
|
|
311
645
|
const clearTimers = () => {
|
|
312
646
|
for (const timer of timers) options.window.clearTimeout(timer);
|
|
313
647
|
timers.clear();
|
|
314
648
|
};
|
|
649
|
+
const clearControlReconnect = () => {
|
|
650
|
+
if (controlReconnectTimer === void 0) return;
|
|
651
|
+
options.window.clearTimeout(controlReconnectTimer);
|
|
652
|
+
controlReconnectTimer = void 0;
|
|
653
|
+
};
|
|
654
|
+
const renderManagedResult = async (revision) => {
|
|
655
|
+
if (lifecycle.disposed || managedResultRevision === revision || managedResultLoadingRevision === revision) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
managedResultLoadingRevision = revision;
|
|
659
|
+
try {
|
|
660
|
+
const result = await controlClient.result(revision);
|
|
661
|
+
if (isDisposed() || controlStatus?.task?.revision !== revision) return;
|
|
662
|
+
managedResultRevision = revision;
|
|
663
|
+
options.panel.renderManagedResult(result);
|
|
664
|
+
} catch {
|
|
665
|
+
} finally {
|
|
666
|
+
if (managedResultLoadingRevision === revision) {
|
|
667
|
+
managedResultLoadingRevision = void 0;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
const applyControlStatus = (value) => {
|
|
672
|
+
if (controlStatus !== void 0 && value.sequence < controlStatus.sequence) return;
|
|
673
|
+
controlStatus = value;
|
|
674
|
+
controlReconnectDelay = EXTERNAL_AGENT_CONTROL_LIMITS2.eventReconnectMinimumMs;
|
|
675
|
+
options.panel.renderControlStatus(value);
|
|
676
|
+
const resultRevision = value.task?.resultExpiresAt === void 0 ? void 0 : value.task.revision;
|
|
677
|
+
if (resultRevision !== void 0) void renderManagedResult(resultRevision);
|
|
678
|
+
};
|
|
679
|
+
const handleControlEvent = (event) => {
|
|
680
|
+
if (event.type === "status") {
|
|
681
|
+
applyControlStatus(event.data);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const resultRevision = controlStatus?.task?.resultExpiresAt === void 0 ? void 0 : controlStatus.task.revision;
|
|
685
|
+
if (resultRevision !== void 0) void renderManagedResult(resultRevision);
|
|
686
|
+
};
|
|
687
|
+
const startControlEventStream = () => {
|
|
688
|
+
if (lifecycle.disposed || !lifecycle.mounted || controlEventController !== void 0) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
clearControlReconnect();
|
|
692
|
+
const controller = new AbortController();
|
|
693
|
+
controlEventController = controller;
|
|
694
|
+
void controlClient.events(controlStatus?.sequence, controller.signal, handleControlEvent).catch((error) => {
|
|
695
|
+
if (lifecycle.disposed || error instanceof DOMException && error.name === "AbortError") {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (controlStatus === void 0) options.panel.renderControlUnavailable();
|
|
699
|
+
}).finally(() => {
|
|
700
|
+
if (controlEventController === controller) {
|
|
701
|
+
controlEventController = void 0;
|
|
702
|
+
}
|
|
703
|
+
if (lifecycle.disposed || !lifecycle.mounted) return;
|
|
704
|
+
const delay = controlReconnectDelay;
|
|
705
|
+
controlReconnectDelay = Math.min(
|
|
706
|
+
delay * 2,
|
|
707
|
+
EXTERNAL_AGENT_CONTROL_LIMITS2.eventReconnectMaximumMs
|
|
708
|
+
);
|
|
709
|
+
controlReconnectTimer = options.window.setTimeout(() => {
|
|
710
|
+
controlReconnectTimer = void 0;
|
|
711
|
+
startControlEventStream();
|
|
712
|
+
}, delay);
|
|
713
|
+
});
|
|
714
|
+
};
|
|
715
|
+
const refreshControlStatus = async () => {
|
|
716
|
+
applyControlStatus(await controlClient.status());
|
|
717
|
+
};
|
|
718
|
+
const bootstrapControl = async () => {
|
|
719
|
+
try {
|
|
720
|
+
await refreshControlStatus();
|
|
721
|
+
} catch {
|
|
722
|
+
if (!lifecycle.disposed) options.panel.renderControlUnavailable();
|
|
723
|
+
} finally {
|
|
724
|
+
if (!lifecycle.disposed) startControlEventStream();
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
const performControlAction = async (action) => {
|
|
728
|
+
if (lifecycle.disposed || controlActionPending) return;
|
|
729
|
+
controlActionPending = true;
|
|
730
|
+
options.panel.setControlBusy(true);
|
|
731
|
+
try {
|
|
732
|
+
applyControlStatus(await action());
|
|
733
|
+
} catch {
|
|
734
|
+
try {
|
|
735
|
+
await refreshControlStatus();
|
|
736
|
+
} catch {
|
|
737
|
+
if (!isDisposed()) options.panel.renderControlUnavailable();
|
|
738
|
+
}
|
|
739
|
+
} finally {
|
|
740
|
+
controlActionPending = false;
|
|
741
|
+
if (!isDisposed()) options.panel.setControlBusy(false);
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
const handleConnectClick = () => {
|
|
745
|
+
void performControlAction(
|
|
746
|
+
() => controlClient.connect({
|
|
747
|
+
requestId: createRequestId(options.window),
|
|
748
|
+
adapterKind: "codex",
|
|
749
|
+
profile: EXTERNAL_AGENT_MANAGED_PROFILE
|
|
750
|
+
})
|
|
751
|
+
);
|
|
752
|
+
};
|
|
753
|
+
const handleDisconnectClick = () => {
|
|
754
|
+
void performControlAction(
|
|
755
|
+
() => controlClient.disconnect({
|
|
756
|
+
requestId: createRequestId(options.window),
|
|
757
|
+
adapterKind: "codex",
|
|
758
|
+
revokeGrant: false
|
|
759
|
+
})
|
|
760
|
+
);
|
|
761
|
+
};
|
|
762
|
+
const handleRevokeClick = () => {
|
|
763
|
+
void performControlAction(
|
|
764
|
+
() => controlClient.disconnect({
|
|
765
|
+
requestId: createRequestId(options.window),
|
|
766
|
+
adapterKind: "codex",
|
|
767
|
+
revokeGrant: true
|
|
768
|
+
})
|
|
769
|
+
);
|
|
770
|
+
};
|
|
771
|
+
const handleManagedCancelClick = () => {
|
|
772
|
+
const revision = controlStatus?.task?.revision;
|
|
773
|
+
if (revision === void 0) return;
|
|
774
|
+
void performControlAction(
|
|
775
|
+
() => controlClient.cancel({
|
|
776
|
+
requestId: createRequestId(options.window),
|
|
777
|
+
revision
|
|
778
|
+
})
|
|
779
|
+
);
|
|
780
|
+
};
|
|
315
781
|
const errorCode = (error) => error instanceof ExternalHandoffApiError ? error.code : void 0;
|
|
316
782
|
const restorePanel = () => {
|
|
317
783
|
if (current !== void 0) options.panel.renderStatus(current);
|
|
@@ -374,7 +840,7 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
374
840
|
const annotation = options.selectedAnnotation();
|
|
375
841
|
if (annotation === void 0) {
|
|
376
842
|
lifecycle.userActionPending = false;
|
|
377
|
-
options.panel.renderError(
|
|
843
|
+
options.panel.renderError(ERROR_CODES2.HANDOFF_VALIDATION_FAILED);
|
|
378
844
|
return;
|
|
379
845
|
}
|
|
380
846
|
const disclosureRevision = lifecycle.operation;
|
|
@@ -495,7 +961,15 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
495
961
|
options.panel.sendButton.addEventListener("click", handleSendClick);
|
|
496
962
|
options.panel.refreshButton.addEventListener("click", handleRefreshClick);
|
|
497
963
|
options.panel.resolveButton.addEventListener("click", handleResolveClick);
|
|
964
|
+
options.panel.connectButton.addEventListener("click", handleConnectClick);
|
|
965
|
+
options.panel.disconnectButton.addEventListener("click", handleDisconnectClick);
|
|
966
|
+
options.panel.revokeButton.addEventListener("click", handleRevokeClick);
|
|
967
|
+
options.panel.cancelManagedButton.addEventListener(
|
|
968
|
+
"click",
|
|
969
|
+
handleManagedCancelClick
|
|
970
|
+
);
|
|
498
971
|
void refreshCapability(lifecycle.operation);
|
|
972
|
+
void bootstrapControl();
|
|
499
973
|
},
|
|
500
974
|
cancelPending,
|
|
501
975
|
dispose() {
|
|
@@ -505,10 +979,23 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
505
979
|
lifecycle.userActionPending = false;
|
|
506
980
|
retryablePublish = void 0;
|
|
507
981
|
clearTimers();
|
|
982
|
+
clearControlReconnect();
|
|
983
|
+
controlEventController?.abort("workflow-disposed");
|
|
984
|
+
controlEventController = void 0;
|
|
508
985
|
client.cancel();
|
|
509
986
|
options.panel.sendButton.removeEventListener("click", handleSendClick);
|
|
510
987
|
options.panel.refreshButton.removeEventListener("click", handleRefreshClick);
|
|
511
988
|
options.panel.resolveButton.removeEventListener("click", handleResolveClick);
|
|
989
|
+
options.panel.connectButton.removeEventListener("click", handleConnectClick);
|
|
990
|
+
options.panel.disconnectButton.removeEventListener(
|
|
991
|
+
"click",
|
|
992
|
+
handleDisconnectClick
|
|
993
|
+
);
|
|
994
|
+
options.panel.revokeButton.removeEventListener("click", handleRevokeClick);
|
|
995
|
+
options.panel.cancelManagedButton.removeEventListener(
|
|
996
|
+
"click",
|
|
997
|
+
handleManagedCancelClick
|
|
998
|
+
);
|
|
512
999
|
}
|
|
513
1000
|
});
|
|
514
1001
|
}
|
|
@@ -517,6 +1004,55 @@ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, session
|
|
|
517
1004
|
var MESSAGES = Object.freeze({
|
|
518
1005
|
"en-US": Object.freeze({
|
|
519
1006
|
title: "External Agent connection",
|
|
1007
|
+
agentLabel: "Agent",
|
|
1008
|
+
codexManaged: "Codex \xB7 managed (experimental)",
|
|
1009
|
+
connectManaged: "Connect Codex",
|
|
1010
|
+
disconnectManaged: "Disconnect",
|
|
1011
|
+
revokeManaged: "Revoke grant",
|
|
1012
|
+
cancelManaged: "Cancel managed task",
|
|
1013
|
+
resultTitle: "Managed result",
|
|
1014
|
+
controlConnection: (state, mode) => `Connection: ${state} \xB7 Mode: ${mode}`,
|
|
1015
|
+
controlAuth: (auth, grant) => `Auth: ${auth} \xB7 Grant: ${grant}`,
|
|
1016
|
+
controlModel: (model) => `Model: ${model}`,
|
|
1017
|
+
controlRevision: (task) => `Revision ${String(task.revision)}: ${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
|
|
1018
|
+
controlValidation: (outcome) => `Validation: ${outcome}`,
|
|
1019
|
+
controlFailure: (error, action) => `Error: ${error} \xB7 Next: ${action}`,
|
|
1020
|
+
controlUnavailable: "Page connection management is unavailable; external handoffs still fall back honestly to the Agent inbox.",
|
|
1021
|
+
controlErrorText: Object.freeze({
|
|
1022
|
+
AGENT_BINARY_NOT_FOUND: "Codex is not installed or is not on PATH.",
|
|
1023
|
+
AGENT_BINARY_UNTRUSTED: "The resolved Codex executable is not trusted.",
|
|
1024
|
+
AGENT_VERSION_UNSUPPORTED: "The installed Codex version is unsupported.",
|
|
1025
|
+
APP_SERVER_HANDSHAKE_FAILED: "Codex App Server did not complete startup.",
|
|
1026
|
+
AGENT_AUTH_REQUIRED: "Codex requires an authenticated account.",
|
|
1027
|
+
AGENT_MODEL_UNAVAILABLE: "The configured Codex model is unavailable.",
|
|
1028
|
+
AGENT_PROTOCOL_INCOMPATIBLE: "The Codex App Server protocol is incompatible.",
|
|
1029
|
+
CODEX_CONFIG_ISOLATION_UNSUPPORTED: "This Codex version cannot prove managed configuration isolation.",
|
|
1030
|
+
MANAGED_GRANT_INVALID: "The saved project grant failed validation.",
|
|
1031
|
+
MANAGED_PLATFORM_UNSUPPORTED: "Managed execution is not proven safe on this platform.",
|
|
1032
|
+
MANAGED_GIT_REQUIRED: "Managed execution requires a Git repository.",
|
|
1033
|
+
MANAGED_SNAPSHOT_FAILED: "The independent workspace snapshot could not be created.",
|
|
1034
|
+
MANAGED_SCOPE_VIOLATION: "The candidate change exceeded its authorized paths.",
|
|
1035
|
+
MANAGED_CHANGE_LIMIT_EXCEEDED: "The candidate change exceeded safety limits.",
|
|
1036
|
+
MANAGED_VALIDATION_FAILED: "A required check failed or changed the candidate diff.",
|
|
1037
|
+
MANAGED_WORKSPACE_CONFLICT: "An authorized source file changed during execution.",
|
|
1038
|
+
MANAGED_APPLY_FAILED: "The audited change could not be applied safely.",
|
|
1039
|
+
MANAGED_CLEANUP_INCOMPLETE: "Managed thread or workspace cleanup is incomplete."
|
|
1040
|
+
}),
|
|
1041
|
+
controlActionText: Object.freeze({
|
|
1042
|
+
"install-agent": "Install Codex and retry.",
|
|
1043
|
+
"use-supported-version": "Use a supported Codex version.",
|
|
1044
|
+
"sign-in": "Sign in with Codex, then retry.",
|
|
1045
|
+
"choose-available-model": "Choose an available model in Codex configuration.",
|
|
1046
|
+
"use-inbox": "Continue with the Agent inbox fallback.",
|
|
1047
|
+
"confirm-managed-access": "Revoke the invalid grant and confirm access again.",
|
|
1048
|
+
"review-candidate-diff": "Review the candidate diff and validation output.",
|
|
1049
|
+
"review-workspace-conflict": "Review the workspace changes before retrying.",
|
|
1050
|
+
retry: "Retry the managed connection.",
|
|
1051
|
+
"inspect-cleanup-warning": "Inspect cleanup status before reconnecting."
|
|
1052
|
+
}),
|
|
1053
|
+
resultHeader: (revision, outcome) => `Revision ${String(revision)} \xB7 Validation ${outcome}`,
|
|
1054
|
+
resultDisposition: (outcome) => outcome === "passed" ? "Applied to the project after validation passed." : `Candidate only; it was not applied because validation is ${outcome}.`,
|
|
1055
|
+
resultTiming: (stage, durationMs) => `Timing ${stage}: ${String(durationMs)} ms`,
|
|
520
1056
|
description: "Send through a ready active adapter, or publish to the project Agent inbox when no active adapter is connected.",
|
|
521
1057
|
ready: "Local Broker ready. No Agent connection is assumed until a connector reads or waits.",
|
|
522
1058
|
readyWaiting: (count) => `${String(count)} active connector wait request(s); publishing can wake them immediately.`,
|
|
@@ -540,19 +1076,69 @@ var MESSAGES = Object.freeze({
|
|
|
540
1076
|
refresh: "Refresh pickup status",
|
|
541
1077
|
resolveUnknown: "Workspace reviewed \u2014 allow a new task",
|
|
542
1078
|
settingsTitle: "Project connection setup",
|
|
543
|
-
settingsHelp: "
|
|
1079
|
+
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.",
|
|
544
1080
|
disclosureTitle: "Confirm external Agent handoff",
|
|
545
1081
|
disclosureIntro: (targets, files) => `Publish ${String(targets)} selected target(s) referencing ${String(files)} relative file(s).`,
|
|
546
1082
|
disclosureData: "The handoff includes your instructions, component/page context, sanitized DOM/CSS, and bounded source read again by the local Node service.",
|
|
547
1083
|
disclosureInbox: "Any SpotPatch connector configured for this project may read it during the 15-minute lifetime.",
|
|
548
1084
|
disclosureProvider: "The Agent host may send the content to its cloud provider under that product's data policy.",
|
|
549
1085
|
disclosureNoGuarantee: "External edits do not receive SpotPatch's built-in worktree, Apply, or Revert guarantees.",
|
|
1086
|
+
disclosureManagedGuarantee: "Managed Codex edits run in an independent temporary snapshot. SpotPatch audits the diff and only applies it when every trusted required check passes.",
|
|
550
1087
|
cancel: "Cancel",
|
|
551
1088
|
confirm: "Confirm and send",
|
|
552
1089
|
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."
|
|
553
1090
|
}),
|
|
554
1091
|
"zh-CN": Object.freeze({
|
|
555
1092
|
title: "\u5916\u90E8 Agent \u8FDE\u63A5",
|
|
1093
|
+
agentLabel: "Agent",
|
|
1094
|
+
codexManaged: "Codex \xB7 \u53D7\u7BA1\u6A21\u5F0F\uFF08\u5B9E\u9A8C\u6027\uFF09",
|
|
1095
|
+
connectManaged: "\u8FDE\u63A5 Codex",
|
|
1096
|
+
disconnectManaged: "\u65AD\u5F00\u8FDE\u63A5",
|
|
1097
|
+
revokeManaged: "\u64A4\u9500\u6388\u6743",
|
|
1098
|
+
cancelManaged: "\u53D6\u6D88\u53D7\u7BA1\u4EFB\u52A1",
|
|
1099
|
+
resultTitle: "\u53D7\u7BA1\u6267\u884C\u7ED3\u679C",
|
|
1100
|
+
controlConnection: (state, mode) => `\u8FDE\u63A5\uFF1A${state} \xB7 \u6A21\u5F0F\uFF1A${mode}`,
|
|
1101
|
+
controlAuth: (auth, grant) => `\u8BA4\u8BC1\uFF1A${auth} \xB7 \u6388\u6743\uFF1A${grant}`,
|
|
1102
|
+
controlModel: (model) => `\u6A21\u578B\uFF1A${model}`,
|
|
1103
|
+
controlRevision: (task) => `revision ${String(task.revision)}\uFF1A${task.deliveryStatus} / ${task.executionStatus} / ${task.managedPhase}`,
|
|
1104
|
+
controlValidation: (outcome) => `\u9A8C\u8BC1\uFF1A${outcome}`,
|
|
1105
|
+
controlFailure: (error, action) => `\u9519\u8BEF\uFF1A${error} \xB7 \u4E0B\u4E00\u6B65\uFF1A${action}`,
|
|
1106
|
+
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",
|
|
1107
|
+
controlErrorText: Object.freeze({
|
|
1108
|
+
AGENT_BINARY_NOT_FOUND: "\u672A\u5B89\u88C5 Codex\uFF0C\u6216 Codex \u4E0D\u5728 PATH \u4E2D\u3002",
|
|
1109
|
+
AGENT_BINARY_UNTRUSTED: "\u89E3\u6790\u5230\u7684 Codex \u53EF\u6267\u884C\u6587\u4EF6\u4E0D\u53EF\u4FE1\u3002",
|
|
1110
|
+
AGENT_VERSION_UNSUPPORTED: "\u5DF2\u5B89\u88C5\u7684 Codex \u7248\u672C\u4E0D\u53D7\u652F\u6301\u3002",
|
|
1111
|
+
APP_SERVER_HANDSHAKE_FAILED: "Codex App Server \u672A\u5B8C\u6210\u542F\u52A8\u63E1\u624B\u3002",
|
|
1112
|
+
AGENT_AUTH_REQUIRED: "Codex \u9700\u8981\u5DF2\u767B\u5F55\u7684\u8D26\u6237\u3002",
|
|
1113
|
+
AGENT_MODEL_UNAVAILABLE: "Codex \u914D\u7F6E\u7684\u6A21\u578B\u5F53\u524D\u4E0D\u53EF\u7528\u3002",
|
|
1114
|
+
AGENT_PROTOCOL_INCOMPATIBLE: "Codex App Server \u534F\u8BAE\u4E0D\u517C\u5BB9\u3002",
|
|
1115
|
+
CODEX_CONFIG_ISOLATION_UNSUPPORTED: "\u5F53\u524D Codex \u7248\u672C\u65E0\u6CD5\u8BC1\u660E\u53D7\u7BA1\u914D\u7F6E\u9694\u79BB\u3002",
|
|
1116
|
+
MANAGED_GRANT_INVALID: "\u4FDD\u5B58\u7684\u9879\u76EE\u6388\u6743\u672A\u901A\u8FC7\u6821\u9A8C\u3002",
|
|
1117
|
+
MANAGED_PLATFORM_UNSUPPORTED: "\u5F53\u524D\u5E73\u53F0\u5C1A\u672A\u8BC1\u660E\u53EF\u5B89\u5168\u8FD0\u884C\u53D7\u7BA1\u6267\u884C\u3002",
|
|
1118
|
+
MANAGED_GIT_REQUIRED: "\u53D7\u7BA1\u6267\u884C\u8981\u6C42\u9879\u76EE\u4F4D\u4E8E Git \u4ED3\u5E93\u4E2D\u3002",
|
|
1119
|
+
MANAGED_SNAPSHOT_FAILED: "\u65E0\u6CD5\u521B\u5EFA\u72EC\u7ACB\u5DE5\u4F5C\u533A\u5FEB\u7167\u3002",
|
|
1120
|
+
MANAGED_SCOPE_VIOLATION: "\u5019\u9009\u4FEE\u6539\u8D85\u51FA\u6388\u6743\u8DEF\u5F84\u3002",
|
|
1121
|
+
MANAGED_CHANGE_LIMIT_EXCEEDED: "\u5019\u9009\u4FEE\u6539\u8D85\u8FC7\u5B89\u5168\u4E0A\u9650\u3002",
|
|
1122
|
+
MANAGED_VALIDATION_FAILED: "required check \u5931\u8D25\u6216\u6539\u53D8\u4E86\u5019\u9009 diff\u3002",
|
|
1123
|
+
MANAGED_WORKSPACE_CONFLICT: "\u6267\u884C\u671F\u95F4\u6388\u6743\u6E90\u7801\u53D1\u751F\u4E86\u53D8\u5316\u3002",
|
|
1124
|
+
MANAGED_APPLY_FAILED: "\u5DF2\u5BA1\u8BA1\u4FEE\u6539\u65E0\u6CD5\u5B89\u5168\u5199\u5165\u3002",
|
|
1125
|
+
MANAGED_CLEANUP_INCOMPLETE: "\u53D7\u7BA1 thread \u6216\u5DE5\u4F5C\u533A\u6E05\u7406\u4E0D\u5B8C\u6574\u3002"
|
|
1126
|
+
}),
|
|
1127
|
+
controlActionText: Object.freeze({
|
|
1128
|
+
"install-agent": "\u5B89\u88C5 Codex \u540E\u91CD\u8BD5\u3002",
|
|
1129
|
+
"use-supported-version": "\u6539\u7528\u53D7\u652F\u6301\u7684 Codex \u7248\u672C\u3002",
|
|
1130
|
+
"sign-in": "\u767B\u5F55 Codex \u540E\u91CD\u8BD5\u3002",
|
|
1131
|
+
"choose-available-model": "\u5728 Codex \u914D\u7F6E\u4E2D\u9009\u62E9\u53EF\u7528\u6A21\u578B\u3002",
|
|
1132
|
+
"use-inbox": "\u7EE7\u7EED\u4F7F\u7528 Agent \u6536\u4EF6\u7BB1\u964D\u7EA7\u8DEF\u5F84\u3002",
|
|
1133
|
+
"confirm-managed-access": "\u64A4\u9500\u65E0\u6548\u6388\u6743\u5E76\u91CD\u65B0\u786E\u8BA4\u3002",
|
|
1134
|
+
"review-candidate-diff": "\u68C0\u67E5\u5019\u9009 diff \u548C\u9A8C\u8BC1\u8F93\u51FA\u3002",
|
|
1135
|
+
"review-workspace-conflict": "\u68C0\u67E5\u5DE5\u4F5C\u533A\u53D8\u5316\u540E\u518D\u91CD\u8BD5\u3002",
|
|
1136
|
+
retry: "\u91CD\u8BD5\u53D7\u7BA1\u8FDE\u63A5\u3002",
|
|
1137
|
+
"inspect-cleanup-warning": "\u786E\u8BA4\u6E05\u7406\u72B6\u6001\u540E\u518D\u8FDE\u63A5\u3002"
|
|
1138
|
+
}),
|
|
1139
|
+
resultHeader: (revision, outcome) => `revision ${String(revision)} \xB7 \u9A8C\u8BC1 ${outcome}`,
|
|
1140
|
+
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`,
|
|
1141
|
+
resultTiming: (stage, durationMs) => `\u8017\u65F6 ${stage}: ${String(durationMs)} ms`,
|
|
556
1142
|
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",
|
|
557
1143
|
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",
|
|
558
1144
|
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`,
|
|
@@ -576,13 +1162,14 @@ var MESSAGES = Object.freeze({
|
|
|
576
1162
|
refresh: "\u5237\u65B0\u53D6\u4EF6\u72B6\u6001",
|
|
577
1163
|
resolveUnknown: "\u5DF2\u6838\u5BF9\u5DE5\u4F5C\u533A\uFF0C\u5141\u8BB8\u65B0\u4EFB\u52A1",
|
|
578
1164
|
settingsTitle: "\u9879\u76EE\u8FDE\u63A5\u8BBE\u7F6E",
|
|
579
|
-
settingsHelp: "
|
|
1165
|
+
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",
|
|
580
1166
|
disclosureTitle: "\u786E\u8BA4\u53D1\u5E03\u5230\u5916\u90E8 Agent",
|
|
581
1167
|
disclosureIntro: (targets, files) => `\u5C06\u53D1\u5E03 ${String(targets)} \u4E2A\u76EE\u6807\uFF0C\u6D89\u53CA ${String(files)} \u4E2A\u9879\u76EE\u76F8\u5BF9\u6587\u4EF6\u3002`,
|
|
582
1168
|
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",
|
|
583
1169
|
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",
|
|
584
1170
|
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",
|
|
585
1171
|
disclosureNoGuarantee: "\u5916\u90E8\u4FEE\u6539\u4E0D\u5177\u5907 SpotPatch \u5185\u5EFA Agent \u7684 worktree\u3001Apply \u6216 Revert \u4FDD\u8BC1\u3002",
|
|
1172
|
+
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",
|
|
586
1173
|
cancel: "\u53D6\u6D88",
|
|
587
1174
|
confirm: "\u786E\u8BA4\u5E76\u53D1\u9001",
|
|
588
1175
|
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"
|
|
@@ -599,9 +1186,17 @@ function createStyles(document) {
|
|
|
599
1186
|
.spotpatch-external-status { padding-left: 10px; border-left: 2px solid var(--spotpatch-accent-cyan); color: #cbd5e1; }
|
|
600
1187
|
.spotpatch-external-status[data-state="error"] { border-color: var(--spotpatch-danger); color: #fecdd3; }
|
|
601
1188
|
.spotpatch-external-status[data-state="picked-up"] { border-color: var(--spotpatch-success); color: #a7f3d0; }
|
|
1189
|
+
.spotpatch-external-control { margin-top: 9px; padding: 9px; border: 1px solid var(--spotpatch-border); border-radius: 8px; background: rgb(3 7 18 / 28%); }
|
|
1190
|
+
.spotpatch-external-control label { display: grid; gap: 4px; color: var(--spotpatch-text-secondary); font-size: 10px; }
|
|
1191
|
+
.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; }
|
|
1192
|
+
.spotpatch-external-control-status { margin: 7px 0 0; color: #cbd5e1; font-size: 10.5px; line-height: 1.5; white-space: pre-wrap; }
|
|
1193
|
+
.spotpatch-external-control-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
1194
|
+
.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; }
|
|
1195
|
+
.spotpatch-external-control-actions button:disabled { cursor: default; opacity: .45; }
|
|
1196
|
+
.spotpatch-external-result { margin-top: 8px; color: var(--spotpatch-text-secondary); font-size: 10px; }
|
|
1197
|
+
.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; }
|
|
602
1198
|
.spotpatch-external-settings { margin-top: 9px; }
|
|
603
1199
|
.spotpatch-external-settings summary { cursor: pointer; color: #c4b5fd; font-size: 11px; }
|
|
604
|
-
.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; }
|
|
605
1200
|
.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; }
|
|
606
1201
|
.spotpatch-external-refresh:disabled { cursor: default; opacity: .45; }
|
|
607
1202
|
.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; }
|
|
@@ -662,6 +1257,44 @@ function disclosurePaths(annotation) {
|
|
|
662
1257
|
)
|
|
663
1258
|
]);
|
|
664
1259
|
}
|
|
1260
|
+
function controlStatusText(value, messages) {
|
|
1261
|
+
const model = value.effectiveModel ?? value.requestedModel;
|
|
1262
|
+
const task = value.task;
|
|
1263
|
+
const parts = [
|
|
1264
|
+
messages.controlConnection(value.connectionState, value.mode),
|
|
1265
|
+
messages.controlAuth(value.authReadiness, value.grantState),
|
|
1266
|
+
...model === void 0 ? [] : [messages.controlModel(model)],
|
|
1267
|
+
...task === void 0 ? [] : [
|
|
1268
|
+
messages.controlRevision(task),
|
|
1269
|
+
...task.validationOutcome === void 0 ? [] : [messages.controlValidation(task.validationOutcome)]
|
|
1270
|
+
],
|
|
1271
|
+
...value.error === void 0 ? [] : [
|
|
1272
|
+
messages.controlFailure(
|
|
1273
|
+
messages.controlErrorText[value.error.code],
|
|
1274
|
+
messages.controlActionText[value.error.action]
|
|
1275
|
+
)
|
|
1276
|
+
]
|
|
1277
|
+
];
|
|
1278
|
+
return parts.join("\n");
|
|
1279
|
+
}
|
|
1280
|
+
function managedResultText(result, messages) {
|
|
1281
|
+
const files = result.files.map(
|
|
1282
|
+
(file) => `${file.path} +${String(file.additions)} -${String(file.deletions)}`
|
|
1283
|
+
);
|
|
1284
|
+
const checks = result.checks.map(
|
|
1285
|
+
(check) => `${check.id}: ${check.outcome} (${String(check.durationMs)} ms${check.exitCode === void 0 ? "" : `, exit ${String(check.exitCode)}`})`
|
|
1286
|
+
);
|
|
1287
|
+
const timings = Object.entries(result.timings).flatMap(
|
|
1288
|
+
([stage, durationMs]) => durationMs === void 0 ? [] : [messages.resultTiming(stage, durationMs)]
|
|
1289
|
+
);
|
|
1290
|
+
return [
|
|
1291
|
+
messages.resultHeader(result.revision, result.validationOutcome),
|
|
1292
|
+
messages.resultDisposition(result.validationOutcome),
|
|
1293
|
+
...files,
|
|
1294
|
+
...checks,
|
|
1295
|
+
...timings
|
|
1296
|
+
].join("\n");
|
|
1297
|
+
}
|
|
665
1298
|
function createExternalHandoffPanel(document, framework, locale, sessionId, subscribeLocale, onViewChange) {
|
|
666
1299
|
const options = {
|
|
667
1300
|
document,
|
|
@@ -679,7 +1312,10 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
679
1312
|
let retryable = false;
|
|
680
1313
|
let unknownDelivery = false;
|
|
681
1314
|
let contextReady = false;
|
|
1315
|
+
let controlOperationBusy = false;
|
|
682
1316
|
let visible = false;
|
|
1317
|
+
let control;
|
|
1318
|
+
let managedResultValue;
|
|
683
1319
|
let pendingDisclosure;
|
|
684
1320
|
let previousFocus;
|
|
685
1321
|
const consentKey = `spotpatch:external-handoff-consent:${options.sessionId}`;
|
|
@@ -697,27 +1333,46 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
697
1333
|
status.className = "spotpatch-external-status";
|
|
698
1334
|
status.setAttribute("role", "status");
|
|
699
1335
|
status.setAttribute("aria-live", "polite");
|
|
1336
|
+
const controlRoot = createMarkedElement(document, "div");
|
|
1337
|
+
controlRoot.className = "spotpatch-external-control";
|
|
1338
|
+
const agentLabel = createMarkedElement(document, "label");
|
|
1339
|
+
const agentLabelText = createMarkedElement(document, "span");
|
|
1340
|
+
const agentSelect = createMarkedElement(document, "select");
|
|
1341
|
+
const codexOption = createMarkedElement(document, "option");
|
|
1342
|
+
codexOption.value = "codex";
|
|
1343
|
+
agentSelect.append(codexOption);
|
|
1344
|
+
agentLabel.append(agentLabelText, agentSelect);
|
|
1345
|
+
const controlStatus = createMarkedElement(document, "p");
|
|
1346
|
+
controlStatus.className = "spotpatch-external-control-status";
|
|
1347
|
+
controlStatus.setAttribute("role", "status");
|
|
1348
|
+
const controlActions = createMarkedElement(document, "div");
|
|
1349
|
+
controlActions.className = "spotpatch-external-control-actions";
|
|
1350
|
+
const connectButton = createButton(document, "");
|
|
1351
|
+
const disconnectButton = createButton(document, "");
|
|
1352
|
+
const revokeButton = createButton(document, "");
|
|
1353
|
+
const cancelManagedButton = createButton(document, "");
|
|
1354
|
+
controlActions.append(
|
|
1355
|
+
connectButton,
|
|
1356
|
+
disconnectButton,
|
|
1357
|
+
revokeButton,
|
|
1358
|
+
cancelManagedButton
|
|
1359
|
+
);
|
|
1360
|
+
const managedResult = createMarkedElement(document, "details");
|
|
1361
|
+
managedResult.className = "spotpatch-external-result";
|
|
1362
|
+
managedResult.hidden = true;
|
|
1363
|
+
const managedResultTitle = createMarkedElement(document, "summary");
|
|
1364
|
+
const managedResultSummary = createMarkedElement(document, "p");
|
|
1365
|
+
const managedResultDiff = createMarkedElement(document, "pre");
|
|
1366
|
+
managedResult.append(managedResultTitle, managedResultSummary, managedResultDiff);
|
|
1367
|
+
controlRoot.append(agentLabel, controlStatus, controlActions, managedResult);
|
|
700
1368
|
const resolveButton = createButton(document, "", "spotpatch-external-resolve");
|
|
701
1369
|
resolveButton.hidden = true;
|
|
702
1370
|
const settings = createMarkedElement(document, "details");
|
|
703
1371
|
settings.className = "spotpatch-external-settings";
|
|
704
1372
|
const settingsTitle = createMarkedElement(document, "summary");
|
|
705
1373
|
const settingsHelp = createMarkedElement(document, "p");
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const commands = [
|
|
709
|
-
`${bridgePrefix} setup --client claude --scope project --mode active`,
|
|
710
|
-
"MCP_PROTOCOL_NEGOTIATION=legacy claude --dangerously-load-development-channels server:spotpatch",
|
|
711
|
-
`${cliPrefix} connect codex --allow-workspace-write`,
|
|
712
|
-
`${bridgePrefix} setup --client cursor --scope project`
|
|
713
|
-
].map((value) => {
|
|
714
|
-
const command = createMarkedElement(document, "code");
|
|
715
|
-
command.className = "spotpatch-external-command";
|
|
716
|
-
command.textContent = value;
|
|
717
|
-
return command;
|
|
718
|
-
});
|
|
719
|
-
settings.append(settingsTitle, settingsHelp, ...commands);
|
|
720
|
-
root.append(heading, description, status, resolveButton, settings);
|
|
1374
|
+
settings.append(settingsTitle, settingsHelp);
|
|
1375
|
+
root.append(heading, description, controlRoot, status, resolveButton, settings);
|
|
721
1376
|
const sendButton = createButton(document, "", "spotpatch-primary");
|
|
722
1377
|
sendButton.hidden = true;
|
|
723
1378
|
const disclosure = createMarkedElement(document, "div");
|
|
@@ -774,6 +1429,23 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
774
1429
|
resolveButton.hidden = !unknownDelivery;
|
|
775
1430
|
resolveButton.disabled = !visible || operationBusy || !unknownDelivery;
|
|
776
1431
|
};
|
|
1432
|
+
const refreshControlActions = () => {
|
|
1433
|
+
if (control === void 0) {
|
|
1434
|
+
connectButton.disabled = true;
|
|
1435
|
+
disconnectButton.disabled = true;
|
|
1436
|
+
revokeButton.disabled = true;
|
|
1437
|
+
cancelManagedButton.hidden = true;
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
const state = control.connectionState;
|
|
1441
|
+
const operationPending = controlOperationBusy || state === "diagnosing" || state === "connecting" || state === "disconnecting";
|
|
1442
|
+
connectButton.disabled = operationPending || state === "ready" || state === "busy";
|
|
1443
|
+
disconnectButton.disabled = operationPending || state === "disconnected";
|
|
1444
|
+
revokeButton.disabled = operationPending || control.grantState !== "valid";
|
|
1445
|
+
const phase = control.task?.managedPhase;
|
|
1446
|
+
cancelManagedButton.hidden = phase === void 0 || phase === "completed" || phase === "review-required" || phase === "failed" || phase === "cancelled" || phase === "cleanup-warning";
|
|
1447
|
+
cancelManagedButton.disabled = operationPending;
|
|
1448
|
+
};
|
|
777
1449
|
const settleDisclosure = (confirmed) => {
|
|
778
1450
|
if (pendingDisclosure === void 0) return;
|
|
779
1451
|
const resolve = pendingDisclosure;
|
|
@@ -817,10 +1489,28 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
817
1489
|
disclosureInbox.textContent = messages.disclosureInbox;
|
|
818
1490
|
disclosureProvider.textContent = messages.disclosureProvider;
|
|
819
1491
|
disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
|
|
1492
|
+
agentLabelText.textContent = messages.agentLabel;
|
|
1493
|
+
codexOption.textContent = messages.codexManaged;
|
|
1494
|
+
connectButton.textContent = messages.connectManaged;
|
|
1495
|
+
disconnectButton.textContent = messages.disconnectManaged;
|
|
1496
|
+
revokeButton.textContent = messages.revokeManaged;
|
|
1497
|
+
cancelManagedButton.textContent = messages.cancelManaged;
|
|
1498
|
+
managedResultTitle.textContent = messages.resultTitle;
|
|
1499
|
+
if (control !== void 0) {
|
|
1500
|
+
controlStatus.textContent = controlStatusText(control, messages);
|
|
1501
|
+
disclosureNoGuarantee.textContent = control.mode === "managed" ? messages.disclosureManagedGuarantee : messages.disclosureNoGuarantee;
|
|
1502
|
+
}
|
|
1503
|
+
if (managedResultValue !== void 0) {
|
|
1504
|
+
managedResultSummary.textContent = managedResultText(
|
|
1505
|
+
managedResultValue,
|
|
1506
|
+
messages
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
820
1509
|
cancelButton.textContent = messages.cancel;
|
|
821
1510
|
confirmButton.textContent = messages.confirm;
|
|
822
1511
|
resolveButton.textContent = messages.resolveUnknown;
|
|
823
1512
|
refreshActions();
|
|
1513
|
+
refreshControlActions();
|
|
824
1514
|
};
|
|
825
1515
|
cancelButton.addEventListener("click", () => {
|
|
826
1516
|
settleDisclosure(false);
|
|
@@ -830,10 +1520,13 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
830
1520
|
});
|
|
831
1521
|
disclosure.addEventListener("keydown", handleDisclosureKeydown);
|
|
832
1522
|
settings.addEventListener("toggle", options.onViewChange);
|
|
1523
|
+
managedResult.addEventListener("toggle", options.onViewChange);
|
|
833
1524
|
const unsubscribeLocale = options.subscribeLocale(applyMessages);
|
|
834
1525
|
applyMessages();
|
|
835
1526
|
status.textContent = messages.ready;
|
|
1527
|
+
controlStatus.textContent = options.locale() === "zh-CN" ? "\u6B63\u5728\u8BFB\u53D6\u672C\u5730\u8FDE\u63A5\u72B6\u6001\u2026\u2026" : "Reading local connection status\u2026";
|
|
836
1528
|
refreshActions();
|
|
1529
|
+
refreshControlActions();
|
|
837
1530
|
const summaryMessage = (summary) => summary.state === "expired" ? messages.expired(summary.revision) : summary.state === "superseded" ? messages.superseded(summary.revision) : summary.pickupCount > 0 ? messages.pickedUp(
|
|
838
1531
|
summary.revision,
|
|
839
1532
|
summary.pickupCount,
|
|
@@ -867,6 +1560,10 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
867
1560
|
sendButton,
|
|
868
1561
|
refreshButton,
|
|
869
1562
|
resolveButton,
|
|
1563
|
+
cancelManagedButton,
|
|
1564
|
+
connectButton,
|
|
1565
|
+
disconnectButton,
|
|
1566
|
+
revokeButton,
|
|
870
1567
|
confirmDisclosure(annotation) {
|
|
871
1568
|
if (hasConsent()) return Promise.resolve(true);
|
|
872
1569
|
if (pendingDisclosure !== void 0) return Promise.resolve(false);
|
|
@@ -918,6 +1615,30 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
918
1615
|
renderStatus(result) {
|
|
919
1616
|
applyStatus(result);
|
|
920
1617
|
},
|
|
1618
|
+
renderControlStatus(value) {
|
|
1619
|
+
control = value;
|
|
1620
|
+
controlStatus.textContent = controlStatusText(value, messages);
|
|
1621
|
+
controlStatus.dataset.state = value.connectionState;
|
|
1622
|
+
disclosureNoGuarantee.textContent = value.mode === "managed" ? messages.disclosureManagedGuarantee : messages.disclosureNoGuarantee;
|
|
1623
|
+
refreshControlActions();
|
|
1624
|
+
options.onViewChange();
|
|
1625
|
+
},
|
|
1626
|
+
renderControlUnavailable() {
|
|
1627
|
+
control = void 0;
|
|
1628
|
+
controlStatus.dataset.state = "unavailable";
|
|
1629
|
+
controlStatus.textContent = messages.controlUnavailable;
|
|
1630
|
+
disclosureNoGuarantee.textContent = messages.disclosureNoGuarantee;
|
|
1631
|
+
refreshControlActions();
|
|
1632
|
+
options.onViewChange();
|
|
1633
|
+
},
|
|
1634
|
+
renderManagedResult(result) {
|
|
1635
|
+
managedResultValue = result;
|
|
1636
|
+
managedResult.hidden = false;
|
|
1637
|
+
managedResult.open = true;
|
|
1638
|
+
managedResultSummary.textContent = managedResultText(result, messages);
|
|
1639
|
+
managedResultDiff.textContent = result.diff;
|
|
1640
|
+
options.onViewChange();
|
|
1641
|
+
},
|
|
921
1642
|
renderError(code, canRetry = false) {
|
|
922
1643
|
operationBusy = false;
|
|
923
1644
|
retryable = canRetry;
|
|
@@ -931,6 +1652,10 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
931
1652
|
operationBusy = nextBusy;
|
|
932
1653
|
refreshActions();
|
|
933
1654
|
},
|
|
1655
|
+
setControlBusy(nextBusy) {
|
|
1656
|
+
controlOperationBusy = nextBusy;
|
|
1657
|
+
refreshControlActions();
|
|
1658
|
+
},
|
|
934
1659
|
setContextReady(ready) {
|
|
935
1660
|
contextReady = ready;
|
|
936
1661
|
refreshActions();
|
|
@@ -946,6 +1671,7 @@ function createExternalHandoffPanel(document, framework, locale, sessionId, subs
|
|
|
946
1671
|
settleDisclosure(false);
|
|
947
1672
|
disclosure.removeEventListener("keydown", handleDisclosureKeydown);
|
|
948
1673
|
settings.removeEventListener("toggle", options.onViewChange);
|
|
1674
|
+
managedResult.removeEventListener("toggle", options.onViewChange);
|
|
949
1675
|
unsubscribeLocale();
|
|
950
1676
|
}
|
|
951
1677
|
});
|