@spotpatch/runtime 1.9.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.
@@ -0,0 +1,1685 @@
1
+ import {
2
+ getExternalHandoffExtension,
3
+ registerExternalHandoffExtension
4
+ } from "./chunk-BXRMVHNT.js";
5
+ import {
6
+ createButton,
7
+ createMarkedElement
8
+ } from "./chunk-7ES63LA7.js";
9
+
10
+ // src/controller/external-handoff-workflow.ts
11
+ import {
12
+ ERROR_CODES as ERROR_CODES2,
13
+ EXTERNAL_AGENT_CONTROL_LIMITS as EXTERNAL_AGENT_CONTROL_LIMITS2,
14
+ EXTERNAL_AGENT_MANAGED_PROFILE,
15
+ EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION,
16
+ EXTERNAL_HANDOFF_LIMITS,
17
+ EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
18
+ SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
19
+ SPOTPATCH_TOKEN_HEADER as SPOTPATCH_TOKEN_HEADER2,
20
+ isErrorCode
21
+ } from "@spotpatch/shared/external-handoff-browser";
22
+
23
+ // src/controller/browser-api.ts
24
+ function record(value) {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+ function exactKeys(value, keys) {
28
+ const actual = Object.keys(value).sort();
29
+ const expected = [...keys].sort();
30
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
31
+ }
32
+ function validTimestamp(value) {
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
+ });
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
+ };
401
+ function parseActiveAdapter(value) {
402
+ if (value === null) return null;
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)) {
404
+ throw new ExternalHandoffApiError();
405
+ }
406
+ return Object.freeze({
407
+ kind: value.kind,
408
+ state: value.state,
409
+ canDispatch: value.canDispatch,
410
+ connectedAt: value.connectedAt,
411
+ updatedAt: value.updatedAt
412
+ });
413
+ }
414
+ function parseDispatch(value) {
415
+ if (value === null) return null;
416
+ if (!record(value)) throw new ExternalHandoffApiError();
417
+ if (!exactKeys(value, ["adapterKind", "phase", "revision", "updatedAt"]) || value.adapterKind !== "claude-channel" && value.adapterKind !== "codex-app-server" || !Number.isSafeInteger(value.revision) || value.revision <= 0 || value.phase !== "queued" && value.phase !== "dispatching" && value.phase !== "dispatched" && value.phase !== "working" && value.phase !== "completed" && value.phase !== "failed" && value.phase !== "delivery-unknown" || !validTimestamp(value.updatedAt)) {
418
+ throw new ExternalHandoffApiError();
419
+ }
420
+ return Object.freeze({
421
+ adapterKind: value.adapterKind,
422
+ revision: value.revision,
423
+ phase: value.phase,
424
+ updatedAt: value.updatedAt
425
+ });
426
+ }
427
+ function parseCapability(value) {
428
+ if (!record(value) || !exactKeys(value, [
429
+ "activeAdapter",
430
+ "activeWaitCount",
431
+ "brokerProtocolVersion",
432
+ "brokerReady",
433
+ "dispatch",
434
+ "enabled",
435
+ "snapshotSchemaVersion"
436
+ ]) || value.enabled !== true || typeof value.brokerReady !== "boolean" || !Number.isSafeInteger(value.activeWaitCount) || value.activeWaitCount < 0 || value.activeWaitCount > EXTERNAL_HANDOFF_LIMITS.maximumWaiters || value.snapshotSchemaVersion !== EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION || value.brokerProtocolVersion !== EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION) {
437
+ throw new ExternalHandoffApiError();
438
+ }
439
+ return Object.freeze({
440
+ enabled: true,
441
+ brokerReady: value.brokerReady,
442
+ activeWaitCount: value.activeWaitCount,
443
+ activeAdapter: parseActiveAdapter(value.activeAdapter),
444
+ dispatch: parseDispatch(value.dispatch),
445
+ snapshotSchemaVersion: EXTERNAL_HANDOFF_SNAPSHOT_SCHEMA_VERSION,
446
+ brokerProtocolVersion: EXTERNAL_HANDOFF_BROKER_PROTOCOL_VERSION
447
+ });
448
+ }
449
+ function parsePublishResult(value) {
450
+ if (!record(value) || !exactKeys(value, ["delivery", "handoff", "replayed"]) || typeof value.replayed !== "boolean" || !record(value.delivery)) {
451
+ throw new ExternalHandoffApiError();
452
+ }
453
+ const handoff = parseSummary(value.handoff);
454
+ if (value.delivery.mode === "inbox") {
455
+ if (!exactKeys(value.delivery, ["mode"])) {
456
+ throw new ExternalHandoffApiError();
457
+ }
458
+ return Object.freeze({
459
+ handoff,
460
+ delivery: Object.freeze({ mode: "inbox" }),
461
+ replayed: value.replayed
462
+ });
463
+ }
464
+ if (value.delivery.mode !== "active" || !exactKeys(value.delivery, ["adapter", "dispatch", "mode"])) {
465
+ throw new ExternalHandoffApiError();
466
+ }
467
+ const adapter = parseActiveAdapter(value.delivery.adapter);
468
+ const dispatch = parseDispatch(value.delivery.dispatch);
469
+ if (adapter === null || dispatch?.revision !== handoff.revision) {
470
+ throw new ExternalHandoffApiError();
471
+ }
472
+ return Object.freeze({
473
+ handoff,
474
+ delivery: Object.freeze({ mode: "active", adapter, dispatch }),
475
+ replayed: value.replayed
476
+ });
477
+ }
478
+ function parseStatusResult(value) {
479
+ if (!record(value) || !exactKeys(value, ["activeAdapter", "dispatch", "handoff"])) {
480
+ throw new ExternalHandoffApiError();
481
+ }
482
+ const handoff = parseSummary(value.handoff);
483
+ const dispatch = parseDispatch(value.dispatch);
484
+ if (dispatch !== null && dispatch.revision !== handoff.revision) {
485
+ throw new ExternalHandoffApiError();
486
+ }
487
+ return Object.freeze({
488
+ handoff,
489
+ activeAdapter: parseActiveAdapter(value.activeAdapter),
490
+ dispatch
491
+ });
492
+ }
493
+ function parseSummary(value) {
494
+ if (!record(value)) throw new ExternalHandoffApiError();
495
+ const expectedKeys = [
496
+ "cursor",
497
+ "expiresAt",
498
+ "framework",
499
+ "page",
500
+ "pickupCount",
501
+ "publishedAt",
502
+ "revision",
503
+ "sessionId",
504
+ "state",
505
+ "targetCount",
506
+ ...value.pickedUpAt === void 0 ? [] : ["pickedUpAt"]
507
+ ];
508
+ 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 > EXTERNAL_HANDOFF_LIMITS.maximumConnectorReceipts || value.pickedUpAt !== void 0 && !validTimestamp(value.pickedUpAt)) {
509
+ throw new ExternalHandoffApiError();
510
+ }
511
+ return Object.freeze({
512
+ sessionId: value.sessionId,
513
+ framework: value.framework,
514
+ revision: value.revision,
515
+ cursor: value.cursor,
516
+ targetCount: value.targetCount,
517
+ page: Object.freeze({ origin: value.page.origin, pathname: value.page.pathname }),
518
+ publishedAt: value.publishedAt,
519
+ expiresAt: value.expiresAt,
520
+ state: value.state,
521
+ pickupCount: value.pickupCount,
522
+ ...value.pickedUpAt === void 0 ? {} : { pickedUpAt: value.pickedUpAt }
523
+ });
524
+ }
525
+ function failureCode(value) {
526
+ return record(value) && value.ok === false && record(value.error) && isErrorCode(value.error.code) ? value.error.code : void 0;
527
+ }
528
+ function omitBrowserCode(annotation) {
529
+ return {
530
+ ...annotation,
531
+ targets: annotation.targets.map((target) => ({
532
+ instruction: target.instruction,
533
+ ...target.page === void 0 ? {} : { page: target.page },
534
+ source: target.source,
535
+ react: target.react,
536
+ element: target.element,
537
+ styles: target.styles,
538
+ warnings: target.warnings
539
+ }))
540
+ };
541
+ }
542
+ function createRequestId(window) {
543
+ const bytes = new Uint8Array(24);
544
+ window.crypto.getRandomValues(bytes);
545
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
546
+ }
547
+ function dispatchIsPending(dispatch) {
548
+ return dispatch !== null && dispatch.phase !== "completed" && dispatch.phase !== "failed" && dispatch.phase !== "delivery-unknown";
549
+ }
550
+ function api(options) {
551
+ const pending = /* @__PURE__ */ new Set();
552
+ const request = async (endpoint, body) => {
553
+ const controller = new AbortController();
554
+ pending.add(controller);
555
+ try {
556
+ const response = await options.fetch(endpoint, {
557
+ method: "POST",
558
+ headers: {
559
+ "Content-Type": "application/json",
560
+ [SPOTPATCH_TOKEN_HEADER2]: options.sessionToken
561
+ },
562
+ body: JSON.stringify(body),
563
+ signal: controller.signal
564
+ });
565
+ let envelope;
566
+ try {
567
+ envelope = await readBoundedJson(response, MAX_SUMMARY_RESPONSE_BYTES);
568
+ } catch {
569
+ throw new ExternalHandoffApiError();
570
+ }
571
+ if (!response.ok) throw new ExternalHandoffApiError(failureCode(envelope));
572
+ try {
573
+ return successData(envelope);
574
+ } catch {
575
+ throw new ExternalHandoffApiError();
576
+ }
577
+ } finally {
578
+ pending.delete(controller);
579
+ }
580
+ };
581
+ return Object.freeze({
582
+ cancel() {
583
+ for (const controller of pending) controller.abort();
584
+ pending.clear();
585
+ },
586
+ async capability() {
587
+ return parseCapability(
588
+ await request(SPOTPATCH_ENDPOINTS2.externalHandoffCapability, {})
589
+ );
590
+ },
591
+ async publish(requestId, annotation) {
592
+ return parsePublishResult(
593
+ await request(SPOTPATCH_ENDPOINTS2.externalHandoffPublish, {
594
+ annotation: omitBrowserCode(annotation),
595
+ requestId
596
+ })
597
+ );
598
+ },
599
+ async status(cursor) {
600
+ return parseStatusResult(
601
+ await request(
602
+ SPOTPATCH_ENDPOINTS2.externalHandoffStatus,
603
+ cursor === void 0 ? {} : { cursor }
604
+ )
605
+ );
606
+ },
607
+ async resolveDelivery(cursor) {
608
+ return parseStatusResult(
609
+ await request(SPOTPATCH_ENDPOINTS2.externalHandoffResolveDelivery, {
610
+ confirmation: "workspace-reviewed",
611
+ cursor
612
+ })
613
+ );
614
+ }
615
+ });
616
+ }
617
+ function createExternalHandoffWorkflow(fetch, panel, selectedAnnotation, sessionToken, window) {
618
+ const options = {
619
+ fetch,
620
+ panel,
621
+ selectedAnnotation,
622
+ sessionToken,
623
+ window
624
+ };
625
+ const client = api(options);
626
+ const controlClient = createExternalAgentControlClient(fetch, sessionToken);
627
+ const timers = /* @__PURE__ */ new Set();
628
+ const lifecycle = {
629
+ disposed: false,
630
+ mounted: false,
631
+ operation: 0,
632
+ userActionPending: false
633
+ };
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;
642
+ let current;
643
+ let retryablePublish;
644
+ const isDisposed = () => lifecycle.disposed;
645
+ const clearTimers = () => {
646
+ for (const timer of timers) options.window.clearTimeout(timer);
647
+ timers.clear();
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
+ };
781
+ const errorCode = (error) => error instanceof ExternalHandoffApiError ? error.code : void 0;
782
+ const restorePanel = () => {
783
+ if (current !== void 0) options.panel.renderStatus(current);
784
+ else if (capability !== void 0) options.panel.renderCapability(capability);
785
+ };
786
+ const refreshCapability = async (revision) => {
787
+ try {
788
+ const result = await client.capability();
789
+ if (lifecycle.disposed || revision !== lifecycle.operation) return;
790
+ capability = result;
791
+ options.panel.renderCapability(result);
792
+ if (result.dispatch !== null) {
793
+ const status = await client.status();
794
+ if (revision !== lifecycle.operation) return;
795
+ current = status;
796
+ options.panel.renderStatus(status);
797
+ }
798
+ } catch (error) {
799
+ if (lifecycle.disposed || revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError")
800
+ return;
801
+ options.panel.renderError(errorCode(error));
802
+ }
803
+ };
804
+ const refreshStatus = async (revision) => {
805
+ if (current === void 0) {
806
+ await refreshCapability(revision);
807
+ return;
808
+ }
809
+ try {
810
+ const result = await client.status(current.handoff.cursor);
811
+ if (lifecycle.disposed || revision !== lifecycle.operation) return;
812
+ current = result;
813
+ options.panel.renderStatus(result);
814
+ } catch (error) {
815
+ if (lifecycle.disposed || revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError")
816
+ return;
817
+ options.panel.renderError(errorCode(error));
818
+ }
819
+ };
820
+ const scheduleRefresh = (delay, continueWhilePending) => {
821
+ const revision = lifecycle.operation;
822
+ const timer = options.window.setTimeout(() => {
823
+ timers.delete(timer);
824
+ void refreshStatus(revision).then(() => {
825
+ if (continueWhilePending && !lifecycle.disposed && revision === lifecycle.operation && !lifecycle.userActionPending && current !== void 0 && dispatchIsPending(current.dispatch)) {
826
+ scheduleRefresh(
827
+ EXTERNAL_HANDOFF_LIMITS.activeStatusPollMs,
828
+ continueWhilePending
829
+ );
830
+ }
831
+ });
832
+ }, delay);
833
+ timers.add(timer);
834
+ };
835
+ const handleSend = async () => {
836
+ if (lifecycle.userActionPending) return;
837
+ lifecycle.userActionPending = true;
838
+ let pending = retryablePublish;
839
+ if (pending === void 0) {
840
+ const annotation = options.selectedAnnotation();
841
+ if (annotation === void 0) {
842
+ lifecycle.userActionPending = false;
843
+ options.panel.renderError(ERROR_CODES2.HANDOFF_VALIDATION_FAILED);
844
+ return;
845
+ }
846
+ const disclosureRevision = lifecycle.operation;
847
+ options.panel.setBusy(true);
848
+ const confirmed = await options.panel.confirmDisclosure(annotation);
849
+ if (!confirmed || disclosureRevision !== lifecycle.operation) {
850
+ lifecycle.userActionPending = false;
851
+ options.panel.setBusy(false);
852
+ return;
853
+ }
854
+ pending = Object.freeze({
855
+ annotation,
856
+ requestId: createRequestId(options.window)
857
+ });
858
+ }
859
+ retryablePublish = pending;
860
+ lifecycle.operation += 1;
861
+ const revision = lifecycle.operation;
862
+ clearTimers();
863
+ client.cancel();
864
+ options.panel.renderPublishing();
865
+ try {
866
+ const result = await client.publish(pending.requestId, pending.annotation);
867
+ if (revision !== lifecycle.operation) return;
868
+ retryablePublish = void 0;
869
+ current = Object.freeze({
870
+ handoff: result.handoff,
871
+ activeAdapter: result.delivery.mode === "active" ? result.delivery.adapter : null,
872
+ dispatch: result.delivery.mode === "active" ? result.delivery.dispatch : null
873
+ });
874
+ options.panel.renderPublishResult(result);
875
+ scheduleRefresh(
876
+ result.delivery.mode === "active" ? EXTERNAL_HANDOFF_LIMITS.activeStatusPollMs : 500,
877
+ result.delivery.mode === "active"
878
+ );
879
+ } catch (error) {
880
+ if (revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError")
881
+ return;
882
+ const code = errorCode(error);
883
+ const retryable = code === void 0;
884
+ if (!retryable) retryablePublish = void 0;
885
+ options.panel.renderError(code, retryable);
886
+ } finally {
887
+ if (revision === lifecycle.operation) {
888
+ lifecycle.userActionPending = false;
889
+ options.panel.setBusy(false);
890
+ }
891
+ }
892
+ };
893
+ const handleRefresh = async () => {
894
+ if (lifecycle.userActionPending) return;
895
+ lifecycle.userActionPending = true;
896
+ lifecycle.operation += 1;
897
+ const revision = lifecycle.operation;
898
+ clearTimers();
899
+ client.cancel();
900
+ options.panel.setBusy(true);
901
+ try {
902
+ await refreshStatus(revision);
903
+ } finally {
904
+ if (!lifecycle.disposed && revision === lifecycle.operation) {
905
+ lifecycle.userActionPending = false;
906
+ options.panel.setBusy(false);
907
+ }
908
+ }
909
+ };
910
+ const handleResolveDelivery = async () => {
911
+ if (lifecycle.userActionPending || current === void 0 || current.dispatch?.phase !== "delivery-unknown") {
912
+ return;
913
+ }
914
+ lifecycle.userActionPending = true;
915
+ lifecycle.operation += 1;
916
+ const revision = lifecycle.operation;
917
+ clearTimers();
918
+ client.cancel();
919
+ options.panel.setBusy(true);
920
+ try {
921
+ const result = await client.resolveDelivery(current.handoff.cursor);
922
+ if (lifecycle.disposed || revision !== lifecycle.operation) return;
923
+ current = result;
924
+ options.panel.renderStatus(result);
925
+ } catch (error) {
926
+ if (lifecycle.disposed || revision !== lifecycle.operation || error instanceof DOMException && error.name === "AbortError") {
927
+ return;
928
+ }
929
+ options.panel.renderError(errorCode(error));
930
+ } finally {
931
+ if (!lifecycle.disposed && revision === lifecycle.operation) {
932
+ lifecycle.userActionPending = false;
933
+ options.panel.setBusy(false);
934
+ }
935
+ }
936
+ };
937
+ const handleSendClick = () => {
938
+ void handleSend();
939
+ };
940
+ const handleRefreshClick = () => {
941
+ void handleRefresh();
942
+ };
943
+ const handleResolveClick = () => {
944
+ void handleResolveDelivery();
945
+ };
946
+ const cancelPending = () => {
947
+ lifecycle.operation += 1;
948
+ lifecycle.userActionPending = false;
949
+ retryablePublish = void 0;
950
+ clearTimers();
951
+ client.cancel();
952
+ restorePanel();
953
+ if (capability === void 0 && lifecycle.mounted && !lifecycle.disposed) {
954
+ void refreshCapability(lifecycle.operation);
955
+ }
956
+ };
957
+ return Object.freeze({
958
+ mount() {
959
+ if (lifecycle.mounted || lifecycle.disposed) return;
960
+ lifecycle.mounted = true;
961
+ options.panel.sendButton.addEventListener("click", handleSendClick);
962
+ options.panel.refreshButton.addEventListener("click", handleRefreshClick);
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
+ );
971
+ void refreshCapability(lifecycle.operation);
972
+ void bootstrapControl();
973
+ },
974
+ cancelPending,
975
+ dispose() {
976
+ if (lifecycle.disposed) return;
977
+ lifecycle.disposed = true;
978
+ lifecycle.operation += 1;
979
+ lifecycle.userActionPending = false;
980
+ retryablePublish = void 0;
981
+ clearTimers();
982
+ clearControlReconnect();
983
+ controlEventController?.abort("workflow-disposed");
984
+ controlEventController = void 0;
985
+ client.cancel();
986
+ options.panel.sendButton.removeEventListener("click", handleSendClick);
987
+ options.panel.refreshButton.removeEventListener("click", handleRefreshClick);
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
+ );
999
+ }
1000
+ });
1001
+ }
1002
+
1003
+ // src/ui/external-handoff-panel.ts
1004
+ var MESSAGES = Object.freeze({
1005
+ "en-US": Object.freeze({
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`,
1056
+ description: "Send through a ready active adapter, or publish to the project Agent inbox when no active adapter is connected.",
1057
+ ready: "Local Broker ready. No Agent connection is assumed until a connector reads or waits.",
1058
+ readyWaiting: (count) => `${String(count)} active connector wait request(s); publishing can wake them immediately.`,
1059
+ activeReady: (agent) => `${agent} is connected and idle. The next send will dispatch immediately.`,
1060
+ publishing: "Authorizing current source and publishing the handoff\u2026",
1061
+ published: (revision, expiresAt) => `Revision ${String(revision)} is available until ${expiresAt}. It has not been claimed as edited.`,
1062
+ queued: (agent, revision) => `Revision ${String(revision)} is reserved for ${agent}.`,
1063
+ dispatching: (agent, revision) => `Dispatching revision ${String(revision)} to ${agent}\u2026`,
1064
+ dispatched: (agent, revision) => agent === "Claude Code" ? `Revision ${String(revision)} was written to the Claude Channel transport; there is no model ACK yet.` : `Codex App Server accepted revision ${String(revision)}; waiting for the matching turn to start.`,
1065
+ working: (agent, revision) => `${agent} is working on revision ${String(revision)}.`,
1066
+ completed: (agent, revision) => `${agent} ended revision ${String(revision)} normally. Review the diff and checks; this does not prove the requested change is correct.`,
1067
+ failed: (agent, revision) => `${agent} reported revision ${String(revision)} as failed. The connector is idle again.`,
1068
+ unknown: (agent, revision) => `Delivery of revision ${String(revision)} to ${agent} is uncertain. The managed writer stopped; review the workspace before allowing another task.`,
1069
+ unknownResolved: (agent, revision) => `Revision ${String(revision)} for ${agent} remains delivery-unknown, but the workspace review was confirmed. A new connector or inbox task may now be used.`,
1070
+ pickedUp: (revision, count, time) => `Revision ${String(revision)} was picked up by ${String(count)} connector instance(s)${time === void 0 ? "." : `; last pickup ${time}.`} This does not prove a code change.`,
1071
+ expired: (revision) => `Revision ${String(revision)} expired. Review the current selection and publish again.`,
1072
+ superseded: (revision) => `Revision ${String(revision)} was superseded by a newer handoff.`,
1073
+ send: "Send to Agent",
1074
+ inboxSend: "Publish to Agent inbox",
1075
+ retry: "Retry same send",
1076
+ refresh: "Refresh pickup status",
1077
+ resolveUnknown: "Workspace reviewed \u2014 allow a new task",
1078
+ settingsTitle: "Project connection setup",
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.",
1080
+ disclosureTitle: "Confirm external Agent handoff",
1081
+ disclosureIntro: (targets, files) => `Publish ${String(targets)} selected target(s) referencing ${String(files)} relative file(s).`,
1082
+ disclosureData: "The handoff includes your instructions, component/page context, sanitized DOM/CSS, and bounded source read again by the local Node service.",
1083
+ disclosureInbox: "Any SpotPatch connector configured for this project may read it during the 15-minute lifetime.",
1084
+ disclosureProvider: "The Agent host may send the content to its cloud provider under that product's data policy.",
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.",
1087
+ cancel: "Cancel",
1088
+ confirm: "Confirm and send",
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."
1090
+ }),
1091
+ "zh-CN": Object.freeze({
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`,
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",
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",
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`,
1145
+ activeReady: (agent) => `${agent} \u5DF2\u8FDE\u63A5\u5E76\u5904\u4E8E\u7A7A\u95F2\u72B6\u6001\uFF0C\u4E0B\u6B21\u53D1\u9001\u4F1A\u7ACB\u5373\u6D3E\u53D1\u3002`,
1146
+ publishing: "\u6B63\u5728\u91CD\u65B0\u6388\u6743\u5F53\u524D\u6E90\u7801\u5E76\u53D1\u5E03\u4EA4\u63A5\u2026\u2026",
1147
+ published: (revision, expiresAt) => `\u4EA4\u63A5 revision ${String(revision)} \u53EF\u8BFB\u53D6\u81F3 ${expiresAt}\uFF1B\u8FD9\u4E0D\u8868\u793A\u4EE3\u7801\u5DF2\u88AB\u4FEE\u6539\u3002`,
1148
+ queued: (agent, revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u4E3A ${agent} \u9884\u7559\u3002`,
1149
+ dispatching: (agent, revision) => `\u6B63\u5728\u628A revision ${String(revision)} \u6D3E\u53D1\u7ED9 ${agent}\u2026\u2026`,
1150
+ dispatched: (agent, revision) => agent === "Claude Code" ? `revision ${String(revision)} \u5DF2\u5199\u5165 Claude Channel transport\uFF1B\u5F53\u524D\u6CA1\u6709\u6A21\u578B ACK\u3002` : `Codex App Server \u5DF2\u63A5\u53D7 revision ${String(revision)}\uFF0C\u6B63\u5728\u7B49\u5F85\u5339\u914D turn \u542F\u52A8\u3002`,
1151
+ working: (agent, revision) => `${agent} \u6B63\u5728\u5904\u7406 revision ${String(revision)}\u3002`,
1152
+ completed: (agent, revision) => `${agent} \u5DF2\u6B63\u5E38\u7ED3\u675F revision ${String(revision)}\u3002\u8BF7\u6838\u5BF9 diff \u548C\u68C0\u67E5\u7ED3\u679C\uFF1B\u8FD9\u4E0D\u8BC1\u660E\u4FEE\u6539\u8981\u6C42\u5DF2\u7ECF\u6B63\u786E\u5B8C\u6210\u3002`,
1153
+ failed: (agent, revision) => `${agent} \u5DF2\u5C06 revision ${String(revision)} \u62A5\u544A\u4E3A\u5931\u8D25\uFF0C\u8FDE\u63A5\u5668\u5DF2\u6062\u590D\u7A7A\u95F2\u3002`,
1154
+ unknown: (agent, revision) => `revision ${String(revision)} \u5411 ${agent} \u7684\u6295\u9012\u7ED3\u679C\u4E0D\u786E\u5B9A\u3002\u53D7\u7BA1 writer \u5DF2\u505C\u6B62\uFF0C\u8BF7\u5148\u6838\u5BF9\u5DE5\u4F5C\u533A\u518D\u5141\u8BB8\u65B0\u4EFB\u52A1\u3002`,
1155
+ unknownResolved: (agent, revision) => `${agent} \u7684 revision ${String(revision)} \u4ECD\u8BB0\u4E3A\u6295\u9012\u672A\u77E5\uFF0C\u4F46\u5DE5\u4F5C\u533A\u6838\u5BF9\u5DF2\u7ECF\u786E\u8BA4\uFF1B\u73B0\u5728\u53EF\u4EE5\u91CD\u65B0\u8FDE\u63A5\u6216\u53D1\u5E03\u65B0\u7684\u6536\u4EF6\u7BB1\u4EFB\u52A1\u3002`,
1156
+ pickedUp: (revision, count, time) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u88AB ${String(count)} \u4E2A\u8FDE\u63A5\u5668\u5B9E\u4F8B\u53D6\u8D70${time === void 0 ? "\u3002" : `\uFF0C\u6700\u8FD1\u53D6\u4EF6\u4E8E ${time}\u3002`}\u8FD9\u4E0D\u80FD\u8BC1\u660E\u4EE3\u7801\u5DF2\u7ECF\u4FEE\u6539\u3002`,
1157
+ expired: (revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6838\u5BF9\u5F53\u524D\u9009\u62E9\u540E\u91CD\u65B0\u53D1\u5E03\u3002`,
1158
+ superseded: (revision) => `\u4EA4\u63A5 revision ${String(revision)} \u5DF2\u88AB\u66F4\u65B0\u7248\u672C\u53D6\u4EE3\u3002`,
1159
+ send: "\u53D1\u9001\u7ED9 Agent",
1160
+ inboxSend: "\u53D1\u5E03\u5230 Agent \u6536\u4EF6\u7BB1",
1161
+ retry: "\u91CD\u8BD5\u540C\u4E00\u6B21\u53D1\u9001",
1162
+ refresh: "\u5237\u65B0\u53D6\u4EF6\u72B6\u6001",
1163
+ resolveUnknown: "\u5DF2\u6838\u5BF9\u5DE5\u4F5C\u533A\uFF0C\u5141\u8BB8\u65B0\u4EFB\u52A1",
1164
+ settingsTitle: "\u9879\u76EE\u8FDE\u63A5\u8BBE\u7F6E",
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",
1166
+ disclosureTitle: "\u786E\u8BA4\u53D1\u5E03\u5230\u5916\u90E8 Agent",
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`,
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",
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",
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",
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",
1173
+ cancel: "\u53D6\u6D88",
1174
+ confirm: "\u786E\u8BA4\u5E76\u53D1\u9001",
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"
1176
+ })
1177
+ });
1178
+ function createStyles(document) {
1179
+ const styles = document.createElement("style");
1180
+ styles.textContent = `
1181
+ .spotpatch-external-handoff { margin-top: 12px; padding: 12px; border: 1px solid var(--spotpatch-border); border-radius: var(--spotpatch-radius-card); background: rgb(82 168 255 / 5%); }
1182
+ .spotpatch-external-handoff[hidden] { display: none; }
1183
+ .spotpatch-external-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
1184
+ .spotpatch-external-heading strong { font-size: 12px; font-weight: 680; color: var(--spotpatch-text); }
1185
+ .spotpatch-external-description, .spotpatch-external-status, .spotpatch-external-settings p { margin: 6px 0 0; font-size: 11px; line-height: 1.5; color: var(--spotpatch-text-secondary); }
1186
+ .spotpatch-external-status { padding-left: 10px; border-left: 2px solid var(--spotpatch-accent-cyan); color: #cbd5e1; }
1187
+ .spotpatch-external-status[data-state="error"] { border-color: var(--spotpatch-danger); color: #fecdd3; }
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; }
1198
+ .spotpatch-external-settings { margin-top: 9px; }
1199
+ .spotpatch-external-settings summary { cursor: pointer; color: #c4b5fd; font-size: 11px; }
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; }
1201
+ .spotpatch-external-refresh:disabled { cursor: default; opacity: .45; }
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; }
1203
+ .spotpatch-external-resolve[hidden] { display: none; }
1204
+ .spotpatch-external-resolve:disabled { cursor: default; opacity: .45; }
1205
+ .spotpatch-external-disclosure { position: fixed; inset: 0; z-index: 4; display: grid; place-items: center; padding: 20px; background: rgb(3 3 8 / 72%); backdrop-filter: blur(3px); }
1206
+ .spotpatch-external-disclosure[hidden] { display: none; }
1207
+ .spotpatch-external-disclosure-card { width: min(420px, calc(100vw - 40px)); max-height: calc(100vh - 40px); overflow: auto; padding: 18px; border: 1px solid rgb(139 123 255 / 55%); border-radius: 14px; background: var(--spotpatch-bg-raised); box-shadow: var(--spotpatch-shadow-panel); }
1208
+ .spotpatch-external-disclosure-card h3 { margin: 0; color: var(--spotpatch-text); font-size: 15px; }
1209
+ .spotpatch-external-disclosure-card p { margin: 9px 0 0; color: #c4c7d0; font-size: 11.5px; line-height: 1.55; }
1210
+ .spotpatch-external-files { max-height: 96px; overflow: auto; color: #a5b4fc; font: 10px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }
1211
+ .spotpatch-external-disclosure-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 15px; }
1212
+ .spotpatch-external-disclosure-actions button { padding: 7px 10px; border: 1px solid var(--spotpatch-border); border-radius: 7px; background: var(--spotpatch-bg-active); color: var(--spotpatch-text); font: inherit; cursor: pointer; }
1213
+ .spotpatch-external-disclosure-actions .spotpatch-primary { border-color: transparent; background: var(--spotpatch-accent); color: var(--spotpatch-text-on-accent); }
1214
+ `;
1215
+ return styles;
1216
+ }
1217
+ function formatTime(value, locale) {
1218
+ const date = new Date(value);
1219
+ return Number.isNaN(date.valueOf()) ? value : new Intl.DateTimeFormat(locale, {
1220
+ hour: "2-digit",
1221
+ minute: "2-digit",
1222
+ second: "2-digit"
1223
+ }).format(date);
1224
+ }
1225
+ function agentName(kind) {
1226
+ return kind === "claude-channel" ? "Claude Code" : "Codex";
1227
+ }
1228
+ function dispatchMessage(messages, dispatch) {
1229
+ const agent = agentName(dispatch.adapterKind);
1230
+ if (dispatch.phase === "queued") {
1231
+ return messages.queued(agent, dispatch.revision);
1232
+ }
1233
+ if (dispatch.phase === "dispatching") {
1234
+ return messages.dispatching(agent, dispatch.revision);
1235
+ }
1236
+ if (dispatch.phase === "dispatched") {
1237
+ return messages.dispatched(agent, dispatch.revision);
1238
+ }
1239
+ if (dispatch.phase === "working") {
1240
+ return messages.working(agent, dispatch.revision);
1241
+ }
1242
+ if (dispatch.phase === "completed") {
1243
+ return messages.completed(agent, dispatch.revision);
1244
+ }
1245
+ if (dispatch.phase === "failed") {
1246
+ return messages.failed(agent, dispatch.revision);
1247
+ }
1248
+ return messages.unknown(agent, dispatch.revision);
1249
+ }
1250
+ function disclosurePaths(annotation) {
1251
+ return Object.freeze([
1252
+ ...new Set(
1253
+ annotation.targets.flatMap((target) => {
1254
+ const relativePath = target.code?.relativePath ?? target.source.relativePath;
1255
+ return relativePath === void 0 ? [] : [relativePath];
1256
+ })
1257
+ )
1258
+ ]);
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
+ }
1298
+ function createExternalHandoffPanel(document, framework, locale, sessionId, subscribeLocale, onViewChange) {
1299
+ const options = {
1300
+ document,
1301
+ framework,
1302
+ locale,
1303
+ onViewChange,
1304
+ sessionId,
1305
+ subscribeLocale
1306
+ };
1307
+ let messages = MESSAGES[options.locale()];
1308
+ let activeAdapter = null;
1309
+ let brokerReady = false;
1310
+ let dispatchBlocksSend = false;
1311
+ let operationBusy = false;
1312
+ let retryable = false;
1313
+ let unknownDelivery = false;
1314
+ let contextReady = false;
1315
+ let controlOperationBusy = false;
1316
+ let visible = false;
1317
+ let control;
1318
+ let managedResultValue;
1319
+ let pendingDisclosure;
1320
+ let previousFocus;
1321
+ const consentKey = `spotpatch:external-handoff-consent:${options.sessionId}`;
1322
+ const root = createMarkedElement(document, "section");
1323
+ root.className = "spotpatch-external-handoff";
1324
+ root.hidden = true;
1325
+ const heading = createMarkedElement(document, "div");
1326
+ heading.className = "spotpatch-external-heading";
1327
+ const title = createMarkedElement(document, "strong");
1328
+ const refreshButton = createButton(document, "", "spotpatch-external-refresh");
1329
+ heading.append(title, refreshButton);
1330
+ const description = createMarkedElement(document, "p");
1331
+ description.className = "spotpatch-external-description";
1332
+ const status = createMarkedElement(document, "p");
1333
+ status.className = "spotpatch-external-status";
1334
+ status.setAttribute("role", "status");
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);
1368
+ const resolveButton = createButton(document, "", "spotpatch-external-resolve");
1369
+ resolveButton.hidden = true;
1370
+ const settings = createMarkedElement(document, "details");
1371
+ settings.className = "spotpatch-external-settings";
1372
+ const settingsTitle = createMarkedElement(document, "summary");
1373
+ const settingsHelp = createMarkedElement(document, "p");
1374
+ settings.append(settingsTitle, settingsHelp);
1375
+ root.append(heading, description, controlRoot, status, resolveButton, settings);
1376
+ const sendButton = createButton(document, "", "spotpatch-primary");
1377
+ sendButton.hidden = true;
1378
+ const disclosure = createMarkedElement(document, "div");
1379
+ disclosure.className = "spotpatch-external-disclosure";
1380
+ disclosure.hidden = true;
1381
+ const disclosureCard = createMarkedElement(document, "section");
1382
+ disclosureCard.className = "spotpatch-external-disclosure-card";
1383
+ disclosureCard.tabIndex = -1;
1384
+ disclosureCard.setAttribute("role", "alertdialog");
1385
+ disclosureCard.setAttribute("aria-modal", "true");
1386
+ const disclosureTitle = createMarkedElement(document, "h3");
1387
+ const disclosureIntro = createMarkedElement(document, "p");
1388
+ const disclosureFiles = createMarkedElement(document, "p");
1389
+ disclosureFiles.className = "spotpatch-external-files";
1390
+ const disclosureData = createMarkedElement(document, "p");
1391
+ const disclosureInbox = createMarkedElement(document, "p");
1392
+ const disclosureProvider = createMarkedElement(document, "p");
1393
+ const disclosureNoGuarantee = createMarkedElement(document, "p");
1394
+ const disclosureActions = createMarkedElement(document, "div");
1395
+ disclosureActions.className = "spotpatch-external-disclosure-actions";
1396
+ const cancelButton = createButton(document, "");
1397
+ const confirmButton = createButton(document, "", "spotpatch-primary");
1398
+ disclosureActions.append(cancelButton, confirmButton);
1399
+ disclosureCard.append(
1400
+ disclosureTitle,
1401
+ disclosureIntro,
1402
+ disclosureFiles,
1403
+ disclosureData,
1404
+ disclosureInbox,
1405
+ disclosureProvider,
1406
+ disclosureNoGuarantee,
1407
+ disclosureActions
1408
+ );
1409
+ disclosure.append(disclosureCard);
1410
+ root.append(disclosure);
1411
+ const hasConsent = () => {
1412
+ try {
1413
+ return document.defaultView?.sessionStorage.getItem(consentKey) === "confirmed";
1414
+ } catch {
1415
+ return false;
1416
+ }
1417
+ };
1418
+ const rememberConsent = () => {
1419
+ try {
1420
+ document.defaultView?.sessionStorage.setItem(consentKey, "confirmed");
1421
+ } catch {
1422
+ }
1423
+ };
1424
+ const refreshActions = () => {
1425
+ sendButton.textContent = retryable ? messages.retry : activeAdapter?.canDispatch === true ? messages.send : messages.inboxSend;
1426
+ sendButton.disabled = !visible || !brokerReady || !contextReady || operationBusy || !retryable && dispatchBlocksSend;
1427
+ sendButton.setAttribute("aria-busy", String(operationBusy));
1428
+ refreshButton.disabled = !visible || operationBusy;
1429
+ resolveButton.hidden = !unknownDelivery;
1430
+ resolveButton.disabled = !visible || operationBusy || !unknownDelivery;
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
+ };
1449
+ const settleDisclosure = (confirmed) => {
1450
+ if (pendingDisclosure === void 0) return;
1451
+ const resolve = pendingDisclosure;
1452
+ pendingDisclosure = void 0;
1453
+ disclosure.hidden = true;
1454
+ if (confirmed) rememberConsent();
1455
+ previousFocus?.focus({ preventScroll: true });
1456
+ previousFocus = void 0;
1457
+ resolve(confirmed);
1458
+ options.onViewChange();
1459
+ };
1460
+ const handleDisclosureKeydown = (event) => {
1461
+ if (event.key === "Escape") {
1462
+ event.preventDefault();
1463
+ settleDisclosure(false);
1464
+ return;
1465
+ }
1466
+ if (event.key !== "Tab") return;
1467
+ const first = cancelButton;
1468
+ const last = confirmButton;
1469
+ const rootNode = disclosure.getRootNode();
1470
+ const activeElement = "activeElement" in rootNode ? rootNode.activeElement : document.activeElement;
1471
+ if (event.shiftKey && activeElement === first) {
1472
+ event.preventDefault();
1473
+ last.focus();
1474
+ } else if (!event.shiftKey && activeElement === last) {
1475
+ event.preventDefault();
1476
+ first.focus();
1477
+ }
1478
+ };
1479
+ const applyMessages = () => {
1480
+ messages = MESSAGES[options.locale()];
1481
+ title.textContent = messages.title;
1482
+ description.textContent = messages.description;
1483
+ refreshButton.textContent = messages.refresh;
1484
+ refreshButton.title = messages.refresh;
1485
+ settingsTitle.textContent = messages.settingsTitle;
1486
+ settingsHelp.textContent = messages.settingsHelp;
1487
+ disclosureTitle.textContent = messages.disclosureTitle;
1488
+ disclosureData.textContent = messages.disclosureData;
1489
+ disclosureInbox.textContent = messages.disclosureInbox;
1490
+ disclosureProvider.textContent = messages.disclosureProvider;
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
+ }
1509
+ cancelButton.textContent = messages.cancel;
1510
+ confirmButton.textContent = messages.confirm;
1511
+ resolveButton.textContent = messages.resolveUnknown;
1512
+ refreshActions();
1513
+ refreshControlActions();
1514
+ };
1515
+ cancelButton.addEventListener("click", () => {
1516
+ settleDisclosure(false);
1517
+ });
1518
+ confirmButton.addEventListener("click", () => {
1519
+ settleDisclosure(true);
1520
+ });
1521
+ disclosure.addEventListener("keydown", handleDisclosureKeydown);
1522
+ settings.addEventListener("toggle", options.onViewChange);
1523
+ managedResult.addEventListener("toggle", options.onViewChange);
1524
+ const unsubscribeLocale = options.subscribeLocale(applyMessages);
1525
+ applyMessages();
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";
1528
+ refreshActions();
1529
+ refreshControlActions();
1530
+ const summaryMessage = (summary) => summary.state === "expired" ? messages.expired(summary.revision) : summary.state === "superseded" ? messages.superseded(summary.revision) : summary.pickupCount > 0 ? messages.pickedUp(
1531
+ summary.revision,
1532
+ summary.pickupCount,
1533
+ summary.pickedUpAt === void 0 ? void 0 : formatTime(summary.pickedUpAt, options.locale())
1534
+ ) : messages.published(
1535
+ summary.revision,
1536
+ formatTime(summary.expiresAt, options.locale())
1537
+ );
1538
+ const applyStatus = (result) => {
1539
+ operationBusy = false;
1540
+ retryable = false;
1541
+ activeAdapter = result.activeAdapter;
1542
+ unknownDelivery = result.dispatch?.phase === "delivery-unknown" && result.activeAdapter?.state === "blocked";
1543
+ dispatchBlocksSend = unknownDelivery || result.activeAdapter !== null && !result.activeAdapter.canDispatch;
1544
+ if (result.dispatch !== null) {
1545
+ status.dataset.state = result.dispatch.phase;
1546
+ status.textContent = result.dispatch.phase === "delivery-unknown" && !unknownDelivery ? messages.unknownResolved(
1547
+ agentName(result.dispatch.adapterKind),
1548
+ result.dispatch.revision
1549
+ ) : dispatchMessage(messages, result.dispatch);
1550
+ } else {
1551
+ status.dataset.state = result.handoff.state === "available" && result.handoff.pickupCount > 0 ? "picked-up" : result.handoff.state;
1552
+ status.textContent = summaryMessage(result.handoff);
1553
+ }
1554
+ refreshActions();
1555
+ options.onViewChange();
1556
+ };
1557
+ return Object.freeze({
1558
+ root,
1559
+ styles: createStyles(document),
1560
+ sendButton,
1561
+ refreshButton,
1562
+ resolveButton,
1563
+ cancelManagedButton,
1564
+ connectButton,
1565
+ disconnectButton,
1566
+ revokeButton,
1567
+ confirmDisclosure(annotation) {
1568
+ if (hasConsent()) return Promise.resolve(true);
1569
+ if (pendingDisclosure !== void 0) return Promise.resolve(false);
1570
+ const paths = disclosurePaths(annotation);
1571
+ disclosureIntro.textContent = messages.disclosureIntro(
1572
+ annotation.targets.length,
1573
+ paths.length
1574
+ );
1575
+ disclosureFiles.textContent = paths.length === 0 ? "\u2014" : paths.join("\n");
1576
+ disclosure.hidden = false;
1577
+ const rootNode = root.getRootNode();
1578
+ const activeElement = "activeElement" in rootNode ? rootNode.activeElement : document.activeElement;
1579
+ previousFocus = activeElement instanceof HTMLElement ? activeElement : void 0;
1580
+ options.onViewChange();
1581
+ disclosureCard.focus({ preventScroll: true });
1582
+ return new Promise((resolve) => {
1583
+ pendingDisclosure = resolve;
1584
+ });
1585
+ },
1586
+ renderCapability(capability) {
1587
+ brokerReady = capability.brokerReady;
1588
+ operationBusy = false;
1589
+ retryable = false;
1590
+ activeAdapter = capability.activeAdapter;
1591
+ unknownDelivery = false;
1592
+ dispatchBlocksSend = capability.activeAdapter !== null && !capability.activeAdapter.canDispatch;
1593
+ status.dataset.state = capability.brokerReady ? "ready" : "error";
1594
+ status.textContent = capability.brokerReady ? capability.activeAdapter?.canDispatch === true ? messages.activeReady(agentName(capability.activeAdapter.kind)) : capability.dispatch !== null && capability.activeAdapter !== null ? dispatchMessage(messages, capability.dispatch) : capability.activeWaitCount > 0 ? messages.readyWaiting(capability.activeWaitCount) : messages.ready : messages.error("EXTERNAL_HANDOFF_UNAVAILABLE");
1595
+ refreshActions();
1596
+ options.onViewChange();
1597
+ },
1598
+ renderPublishing() {
1599
+ operationBusy = true;
1600
+ retryable = false;
1601
+ status.dataset.state = "publishing";
1602
+ status.textContent = messages.publishing;
1603
+ refreshActions();
1604
+ options.onViewChange();
1605
+ },
1606
+ renderPublishResult(result) {
1607
+ applyStatus(
1608
+ Object.freeze({
1609
+ handoff: result.handoff,
1610
+ activeAdapter: result.delivery.mode === "active" ? result.delivery.adapter : null,
1611
+ dispatch: result.delivery.mode === "active" ? result.delivery.dispatch : null
1612
+ })
1613
+ );
1614
+ },
1615
+ renderStatus(result) {
1616
+ applyStatus(result);
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
+ },
1642
+ renderError(code, canRetry = false) {
1643
+ operationBusy = false;
1644
+ retryable = canRetry;
1645
+ if (code === "EXTERNAL_AGENT_BUSY") dispatchBlocksSend = true;
1646
+ status.dataset.state = "error";
1647
+ status.textContent = canRetry ? `${messages.error(code)} ${messages.retry}.` : messages.error(code);
1648
+ refreshActions();
1649
+ options.onViewChange();
1650
+ },
1651
+ setBusy(nextBusy) {
1652
+ operationBusy = nextBusy;
1653
+ refreshActions();
1654
+ },
1655
+ setControlBusy(nextBusy) {
1656
+ controlOperationBusy = nextBusy;
1657
+ refreshControlActions();
1658
+ },
1659
+ setContextReady(ready) {
1660
+ contextReady = ready;
1661
+ refreshActions();
1662
+ },
1663
+ setSelectionVisible(nextVisible) {
1664
+ visible = nextVisible;
1665
+ root.hidden = !nextVisible;
1666
+ sendButton.hidden = !nextVisible;
1667
+ if (!nextVisible) settleDisclosure(false);
1668
+ refreshActions();
1669
+ },
1670
+ dispose() {
1671
+ settleDisclosure(false);
1672
+ disclosure.removeEventListener("keydown", handleDisclosureKeydown);
1673
+ settings.removeEventListener("toggle", options.onViewChange);
1674
+ managedResult.removeEventListener("toggle", options.onViewChange);
1675
+ unsubscribeLocale();
1676
+ }
1677
+ });
1678
+ }
1679
+ export {
1680
+ createExternalHandoffPanel,
1681
+ createExternalHandoffWorkflow,
1682
+ getExternalHandoffExtension,
1683
+ registerExternalHandoffExtension
1684
+ };
1685
+ //# sourceMappingURL=external-handoff-panel.js.map