codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +27 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +155 -1
- package/contracts/v1/parity-suite.json +96 -1
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
- package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/downloads.d.ts +2 -0
- package/dist/src/client.d.ts +38 -0
- package/dist/src/client.js +1222 -59
- package/dist/src/commands/configuration.js +13 -7
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +26 -3
- package/dist/src/commands/experience.js +35 -0
- package/dist/src/commands/files.js +4 -27
- package/dist/src/commands/modes.js +49 -17
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +627 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +7 -2
- package/dist/src/commands/work.js +31 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +161 -0
- package/dist/src/operations/browser-adapter.js +2023 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1208 -0
- package/dist/src/operations/browser-target.d.ts +103 -0
- package/dist/src/operations/browser-target.js +489 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1150 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1143 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +22 -0
- package/dist/src/operations/index.js +22 -0
- package/dist/src/operations/journal.d.ts +142 -0
- package/dist/src/operations/journal.js +1931 -0
- package/dist/src/operations/production-attachments.d.ts +116 -0
- package/dist/src/operations/production-attachments.js +1018 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1363 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +1073 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +118 -0
- package/dist/src/operations/runtime-adapter.js +786 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +281 -0
- package/dist/src/operations/service.js +2810 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +39 -0
- package/dist/src/runtime/coordinated-page.js +634 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
- package/dist/src/scripts/live-smoke/scenarios.js +110 -18
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +1 -1
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +73 -0
- package/references/python-parity.md +72 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
|
@@ -3,9 +3,17 @@ import { resultError, resultOk } from "../errors.js";
|
|
|
3
3
|
import { contextFromPage } from "./context.js";
|
|
4
4
|
import { preflightFiles } from "./files.js";
|
|
5
5
|
import { bootstrap } from "./session.js";
|
|
6
|
+
import { coordinatedEventRegistrationBarrier } from "../runtime/coordinated-page.js";
|
|
6
7
|
const CHATGPT_ORIGIN = "https://chatgpt.com";
|
|
7
8
|
const DEFAULT_PROJECT_SOURCE_BATCH_SIZE = 10;
|
|
8
9
|
const PROJECT_SOURCE_CANDIDATE_LIMIT = 20;
|
|
10
|
+
// The chooser transition is a short UI handoff. It must not inherit the much
|
|
11
|
+
// longer file-handoff/refresh timeout: doing so can leave a rejected browser
|
|
12
|
+
// event pending for minutes and can make us try a second UI path after an
|
|
13
|
+
// ambiguous first action. The public timeout can shorten this budget, but
|
|
14
|
+
// never extend it.
|
|
15
|
+
const PROJECT_SOURCE_CHOOSER_TRANSITION_TIMEOUT_MS = 2500;
|
|
16
|
+
const PROJECT_SOURCE_CHOOSER_POLL_INTERVAL_MS = 25;
|
|
9
17
|
export function normalizeProjectSourcesUrl(value) {
|
|
10
18
|
let parsed;
|
|
11
19
|
try {
|
|
@@ -415,16 +423,97 @@ async function uploadProjectSourceBatch(page, batch, timeoutMs) {
|
|
|
415
423
|
if (typeof page.waitForEvent !== "function") {
|
|
416
424
|
throw new Error("The active Project Sources page does not expose file chooser events.");
|
|
417
425
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
426
|
+
const chooserTransitionTimeoutMs = Math.min(timeoutMs, PROJECT_SOURCE_CHOOSER_TRANSITION_TIMEOUT_MS);
|
|
427
|
+
const chooserDeadline = Date.now() + chooserTransitionTimeoutMs;
|
|
428
|
+
// Validate the first visible mutation before subscribing to the chooser.
|
|
429
|
+
// A missing or ambiguous control must not leave a 2.5s bridge waiter alive.
|
|
430
|
+
const addSource = await waitForUniqueVisibleProjectSourceControl(page, localeLabels.projectSourcesAddSource, "button", chooserDeadline);
|
|
431
|
+
if (addSource.kind !== "ready") {
|
|
432
|
+
throw new Error(projectSourceControlDiscoveryMessage(addSource, "button", localeLabels.projectSourcesAddSource));
|
|
424
433
|
}
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
434
|
+
const remainingChooserTimeoutMs = chooserDeadline - Date.now();
|
|
435
|
+
if (remainingChooserTimeoutMs <= 0) {
|
|
436
|
+
throw new Error("Project Sources Add source control was not ready before the chooser transition deadline.");
|
|
437
|
+
}
|
|
438
|
+
// Start exactly one handled waiter synchronously immediately before the
|
|
439
|
+
// first visible mutation. The listener is therefore installed before the
|
|
440
|
+
// click can deliver a chooser event, while synchronous bridge throws are
|
|
441
|
+
// converted into an already-handled terminal outcome.
|
|
442
|
+
const chooserWait = startFileChooserWait(page, remainingChooserTimeoutMs);
|
|
443
|
+
// Fence coordinated provider registration and drain already-scheduled
|
|
444
|
+
// settlement before crossing the first visible mutation boundary.
|
|
445
|
+
const beforeAddOutcome = await settleChooserBeforeMutation(chooserWait, chooserDeadline);
|
|
446
|
+
if (beforeAddOutcome !== undefined) {
|
|
447
|
+
throw chooserOutcomeError(beforeAddOutcome, undefined, "Add source");
|
|
448
|
+
}
|
|
449
|
+
let addSourceError;
|
|
450
|
+
try {
|
|
451
|
+
await clickProjectSourceControlLocator(page, addSource.locator, chooserDeadline);
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
addSourceError = error;
|
|
455
|
+
}
|
|
456
|
+
// A click can deliver the browser gesture and still reject at the bridge
|
|
457
|
+
// boundary. Reconcile a late chooser success instead of issuing a second
|
|
458
|
+
// Add/Upload action. A rejection is returned deterministically as the
|
|
459
|
+
// chooser outcome, with the click failure retained as context.
|
|
460
|
+
if (addSourceError !== undefined) {
|
|
461
|
+
const outcome = await awaitFileChooserOutcome(chooserWait, chooserDeadline);
|
|
462
|
+
if (outcome.kind === "success") {
|
|
463
|
+
await setProjectSourceChooserFiles(outcome.chooser, paths, timeoutMs);
|
|
464
|
+
return resultOk({ files: batch.files.map(file => ({ name: file.name, bytes: file.bytes })) }, await contextFromPage(page));
|
|
465
|
+
}
|
|
466
|
+
throw chooserOutcomeError(outcome, addSourceError, "Add source");
|
|
467
|
+
}
|
|
468
|
+
// Let a chooser event delivered during the Add click finish its handled
|
|
469
|
+
// microtask before deciding whether the optional menu path is necessary.
|
|
470
|
+
await Promise.resolve();
|
|
471
|
+
const afterAddOutcome = chooserWait.outcome;
|
|
472
|
+
if (afterAddOutcome?.kind === "success") {
|
|
473
|
+
await setProjectSourceChooserFiles(afterAddOutcome.chooser, paths, timeoutMs);
|
|
474
|
+
return resultOk({ files: batch.files.map(file => ({ name: file.name, bytes: file.bytes })) }, await contextFromPage(page));
|
|
475
|
+
}
|
|
476
|
+
if (afterAddOutcome?.kind === "rejected") {
|
|
477
|
+
throw chooserOutcomeError(afterAddOutcome, undefined, "Add source");
|
|
478
|
+
}
|
|
479
|
+
const uploadControl = await waitForChooserOrUploadControl(page, localeLabels.projectSourcesUploadFiles, "button", chooserWait, chooserDeadline);
|
|
480
|
+
if (uploadControl.kind === "chooser") {
|
|
481
|
+
if (uploadControl.outcome.kind === "success") {
|
|
482
|
+
await setProjectSourceChooserFiles(uploadControl.outcome.chooser, paths, timeoutMs);
|
|
483
|
+
return resultOk({ files: batch.files.map(file => ({ name: file.name, bytes: file.bytes })) }, await contextFromPage(page));
|
|
484
|
+
}
|
|
485
|
+
throw chooserOutcomeError(uploadControl.outcome, undefined, "Add source");
|
|
486
|
+
}
|
|
487
|
+
if (uploadControl.kind !== "ready") {
|
|
488
|
+
throw new Error(uploadControl.kind === "timeout"
|
|
489
|
+
? uploadControl.message
|
|
490
|
+
: projectSourceControlDiscoveryMessage(uploadControl, "button", localeLabels.projectSourcesUploadFiles));
|
|
491
|
+
}
|
|
492
|
+
// Final settled-outcome gate: discovery may have yielded while the
|
|
493
|
+
// chooser promise settled. Do not click Upload when the original waiter
|
|
494
|
+
// has already produced either terminal outcome.
|
|
495
|
+
await Promise.resolve();
|
|
496
|
+
const settledBeforeUpload = chooserWait.outcome;
|
|
497
|
+
if (settledBeforeUpload?.kind === "success") {
|
|
498
|
+
await setProjectSourceChooserFiles(settledBeforeUpload.chooser, paths, timeoutMs);
|
|
499
|
+
return resultOk({ files: batch.files.map(file => ({ name: file.name, bytes: file.bytes })) }, await contextFromPage(page));
|
|
500
|
+
}
|
|
501
|
+
if (settledBeforeUpload?.kind === "rejected") {
|
|
502
|
+
throw chooserOutcomeError(settledBeforeUpload, undefined, "Add source");
|
|
503
|
+
}
|
|
504
|
+
let uploadError;
|
|
505
|
+
try {
|
|
506
|
+
await clickProjectSourceControlLocator(page, uploadControl.locator, chooserDeadline);
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
uploadError = error;
|
|
510
|
+
}
|
|
511
|
+
const outcome = await awaitFileChooserOutcome(chooserWait, chooserDeadline);
|
|
512
|
+
if (outcome.kind === "success") {
|
|
513
|
+
await setProjectSourceChooserFiles(outcome.chooser, paths, timeoutMs);
|
|
514
|
+
return resultOk({ files: batch.files.map(file => ({ name: file.name, bytes: file.bytes })) }, await contextFromPage(page));
|
|
515
|
+
}
|
|
516
|
+
throw chooserOutcomeError(outcome, uploadError, "Upload files");
|
|
428
517
|
}
|
|
429
518
|
catch (error) {
|
|
430
519
|
return {
|
|
@@ -457,36 +546,241 @@ async function clickSourcesTabIfAvailable(page, timeoutMs) {
|
|
|
457
546
|
}
|
|
458
547
|
}
|
|
459
548
|
async function clickProjectSourceControl(page, labels, role, timeoutMs) {
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
549
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
550
|
+
// The Sources tab is a navigation aid, not part of the chooser transition.
|
|
551
|
+
// Keep its best-effort lookup bounded to one DOM observation so a page that
|
|
552
|
+
// lacks the tab still reaches the existing selector-drift fallback promptly.
|
|
553
|
+
const candidate = await discoverUniqueVisibleProjectSourceControl(page, labels, role);
|
|
554
|
+
if (candidate.kind !== "ready") {
|
|
555
|
+
throw new Error(projectSourceControlDiscoveryMessage(candidate, role, labels));
|
|
556
|
+
}
|
|
557
|
+
await clickProjectSourceControlLocator(page, candidate.locator, deadline);
|
|
558
|
+
if (role === "tab") {
|
|
463
559
|
await page.waitForTimeout?.(250);
|
|
464
|
-
return;
|
|
465
560
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
561
|
+
}
|
|
562
|
+
function startFileChooserWait(page, timeoutMs) {
|
|
563
|
+
const wait = {
|
|
564
|
+
promise: Promise.resolve({
|
|
565
|
+
kind: "rejected",
|
|
566
|
+
error: new Error("Project Sources file chooser wait was not initialized.")
|
|
567
|
+
})
|
|
568
|
+
};
|
|
569
|
+
let rawWait;
|
|
570
|
+
try {
|
|
571
|
+
// Invoke waitForEvent in this call stack. Deferring registration through a
|
|
572
|
+
// Promise.resolve().then() can let an immediately-triggered Add click race
|
|
573
|
+
// the listener and lose the chooser event.
|
|
574
|
+
rawWait = page.waitForEvent?.("filechooser", { timeout: timeoutMs, timeoutMs });
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
const outcome = { kind: "rejected", error };
|
|
578
|
+
wait.outcome = outcome;
|
|
579
|
+
wait.promise = Promise.resolve(outcome);
|
|
580
|
+
return wait;
|
|
581
|
+
}
|
|
582
|
+
const registration = coordinatedEventRegistrationBarrier(rawWait);
|
|
583
|
+
if (registration !== undefined)
|
|
584
|
+
wait.registration = registration;
|
|
585
|
+
wait.promise = Promise.resolve(rawWait).then(rawChooser => {
|
|
586
|
+
const outcome = isProjectSourceFileChooserLike(rawChooser)
|
|
587
|
+
? { kind: "success", chooser: rawChooser }
|
|
588
|
+
: {
|
|
589
|
+
kind: "rejected",
|
|
590
|
+
error: new Error("Project Sources file chooser did not expose setFiles().")
|
|
591
|
+
};
|
|
592
|
+
wait.outcome = outcome;
|
|
593
|
+
return outcome;
|
|
594
|
+
}, error => {
|
|
595
|
+
const outcome = { kind: "rejected", error };
|
|
596
|
+
wait.outcome = outcome;
|
|
597
|
+
return outcome;
|
|
598
|
+
});
|
|
599
|
+
return wait;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Prove that a coordinated event listener finished registration before a
|
|
603
|
+
* click. One host turn then drains any outcome that was already scheduled;
|
|
604
|
+
* such an outcome cannot causally belong to the not-yet-issued click and
|
|
605
|
+
* therefore blocks the handoff.
|
|
606
|
+
*/
|
|
607
|
+
async function settleChooserBeforeMutation(chooserWait, deadline) {
|
|
608
|
+
if (chooserWait.outcome !== undefined)
|
|
609
|
+
return chooserWait.outcome;
|
|
610
|
+
if (chooserWait.registration !== undefined) {
|
|
611
|
+
const remainingMs = deadline - Date.now();
|
|
612
|
+
if (remainingMs <= 0)
|
|
613
|
+
return { kind: "timeout" };
|
|
614
|
+
let timer;
|
|
615
|
+
const registered = await Promise.race([
|
|
616
|
+
chooserWait.registration.then(() => true),
|
|
617
|
+
new Promise(resolve => { timer = setTimeout(() => resolve(false), remainingMs); })
|
|
618
|
+
]).finally(() => {
|
|
619
|
+
if (timer !== undefined)
|
|
620
|
+
clearTimeout(timer);
|
|
621
|
+
});
|
|
622
|
+
if (!registered)
|
|
623
|
+
return { kind: "timeout" };
|
|
624
|
+
}
|
|
625
|
+
if (chooserWait.outcome !== undefined)
|
|
626
|
+
return chooserWait.outcome;
|
|
627
|
+
if (deadline <= Date.now())
|
|
628
|
+
return { kind: "timeout" };
|
|
629
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
630
|
+
if (chooserWait.outcome !== undefined)
|
|
631
|
+
return chooserWait.outcome;
|
|
632
|
+
return deadline <= Date.now() ? { kind: "timeout" } : undefined;
|
|
633
|
+
}
|
|
634
|
+
async function awaitFileChooserOutcome(chooserWait, deadline) {
|
|
635
|
+
if (chooserWait.outcome !== undefined) {
|
|
636
|
+
return chooserWait.outcome;
|
|
637
|
+
}
|
|
638
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
639
|
+
if (remainingMs === 0) {
|
|
640
|
+
return { kind: "timeout" };
|
|
641
|
+
}
|
|
642
|
+
return new Promise(resolve => {
|
|
643
|
+
let finished = false;
|
|
644
|
+
const finish = (outcome) => {
|
|
645
|
+
if (finished)
|
|
646
|
+
return;
|
|
647
|
+
finished = true;
|
|
648
|
+
clearTimeout(timer);
|
|
649
|
+
resolve(outcome);
|
|
650
|
+
};
|
|
651
|
+
const timer = setTimeout(() => finish({ kind: "timeout" }), remainingMs);
|
|
652
|
+
// The chooser promise is always handled and never rejects. The timer is
|
|
653
|
+
// cleared when the event wins, avoiding a dangling transition timer.
|
|
654
|
+
void chooserWait.promise.then(outcome => finish(outcome));
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function projectSourceControlDiscoveryMessage(discovery, role, labels) {
|
|
658
|
+
if (discovery.kind === "invalid") {
|
|
659
|
+
return discovery.message;
|
|
660
|
+
}
|
|
661
|
+
return `Project Sources ${role} was not uniquely visible for labels: ${labels.join(", ")}`;
|
|
662
|
+
}
|
|
663
|
+
async function waitForUniqueVisibleProjectSourceControl(page, labels, role, deadline) {
|
|
664
|
+
while (true) {
|
|
665
|
+
const candidate = await discoverUniqueVisibleProjectSourceControl(page, labels, role);
|
|
666
|
+
if (candidate.kind !== "absent") {
|
|
667
|
+
return candidate;
|
|
668
|
+
}
|
|
669
|
+
const remainingMs = deadline - Date.now();
|
|
670
|
+
if (remainingMs <= 0) {
|
|
671
|
+
return {
|
|
672
|
+
kind: "invalid",
|
|
673
|
+
message: `Project Sources ${role} was not uniquely visible before the chooser transition deadline for labels: ${labels.join(", ")}`
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
await waitForProjectSourceTransitionTick(page, Math.min(PROJECT_SOURCE_CHOOSER_POLL_INTERVAL_MS, remainingMs));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function discoverUniqueVisibleProjectSourceControl(page, labels, role) {
|
|
680
|
+
const pattern = anyLabelPattern(labels);
|
|
681
|
+
const primary = page.getByRole?.(role, { name: pattern });
|
|
682
|
+
const candidate = primary !== undefined && await locatorCount(primary) > 0
|
|
683
|
+
? primary
|
|
684
|
+
: page.locator?.(labels
|
|
685
|
+
.map(label => `button[aria-label*='${cssString(label)}'], [role='${role}'][aria-label*='${cssString(label)}']`)
|
|
686
|
+
.join(", "));
|
|
687
|
+
if (candidate === undefined) {
|
|
688
|
+
return { kind: "absent" };
|
|
689
|
+
}
|
|
690
|
+
const count = await locatorCount(candidate);
|
|
691
|
+
if (count === 0) {
|
|
692
|
+
return { kind: "absent" };
|
|
693
|
+
}
|
|
694
|
+
if (count !== 1) {
|
|
695
|
+
return {
|
|
696
|
+
kind: "invalid",
|
|
697
|
+
message: `Project Sources ${role} was not uniquely available for labels: ${labels.join(", ")} (found ${count} controls).`
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
if (typeof candidate.isVisible !== "function") {
|
|
701
|
+
return {
|
|
702
|
+
kind: "invalid",
|
|
703
|
+
message: `Project Sources ${role} visibility could not be verified for labels: ${labels.join(", ")}.`
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
if (!await candidate.isVisible().catch(() => false)) {
|
|
707
|
+
return {
|
|
708
|
+
kind: "invalid",
|
|
709
|
+
message: `Project Sources ${role} was present but hidden for labels: ${labels.join(", ")}.`
|
|
710
|
+
};
|
|
474
711
|
}
|
|
475
|
-
|
|
712
|
+
return { kind: "ready", locator: candidate };
|
|
476
713
|
}
|
|
477
|
-
async function
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
714
|
+
async function waitForChooserOrUploadControl(page, labels, role, chooserWait, deadline) {
|
|
715
|
+
while (true) {
|
|
716
|
+
if (chooserWait.outcome !== undefined) {
|
|
717
|
+
return { kind: "chooser", outcome: chooserWait.outcome };
|
|
718
|
+
}
|
|
719
|
+
const candidate = await discoverUniqueVisibleProjectSourceControl(page, labels, role);
|
|
720
|
+
if (candidate.kind !== "absent") {
|
|
721
|
+
// A chooser rejection may race the final control observation. Prefer the
|
|
722
|
+
// already-settled browser outcome over reporting stale menu ambiguity.
|
|
723
|
+
if (chooserWait.outcome !== undefined) {
|
|
724
|
+
return { kind: "chooser", outcome: chooserWait.outcome };
|
|
725
|
+
}
|
|
726
|
+
return candidate;
|
|
727
|
+
}
|
|
728
|
+
const remainingMs = deadline - Date.now();
|
|
729
|
+
if (remainingMs <= 0) {
|
|
730
|
+
return {
|
|
731
|
+
kind: "timeout",
|
|
732
|
+
message: `Project Sources file chooser did not settle and Upload control did not become uniquely visible before the ${PROJECT_SOURCE_CHOOSER_TRANSITION_TIMEOUT_MS}ms chooser transition deadline.`
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
await waitForChooserOrTransitionTick(page, chooserWait, Math.min(PROJECT_SOURCE_CHOOSER_POLL_INTERVAL_MS, remainingMs));
|
|
481
736
|
}
|
|
482
|
-
return rawChooser;
|
|
483
737
|
}
|
|
484
|
-
async function
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
738
|
+
async function waitForChooserOrTransitionTick(page, chooserWait, waitMs) {
|
|
739
|
+
if (chooserWait.outcome !== undefined)
|
|
740
|
+
return;
|
|
741
|
+
await Promise.race([
|
|
742
|
+
chooserWait.promise.then(() => undefined),
|
|
743
|
+
waitForProjectSourceTransitionTick(page, waitMs)
|
|
488
744
|
]);
|
|
489
745
|
}
|
|
746
|
+
async function waitForProjectSourceTransitionTick(page, waitMs) {
|
|
747
|
+
if (typeof page.waitForTimeout === "function") {
|
|
748
|
+
await page.waitForTimeout(waitMs);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
752
|
+
}
|
|
753
|
+
async function clickProjectSourceControlLocator(page, locator, deadline) {
|
|
754
|
+
if (typeof locator.click !== "function") {
|
|
755
|
+
throw new Error("Project Sources control does not expose click().");
|
|
756
|
+
}
|
|
757
|
+
const remainingMs = Math.max(1, deadline - Date.now());
|
|
758
|
+
await locator.click({ timeout: Math.min(remainingMs, 10_000) });
|
|
759
|
+
}
|
|
760
|
+
async function setProjectSourceChooserFiles(chooser, paths, timeoutMs) {
|
|
761
|
+
await chooser.setFiles(paths, { timeoutMs });
|
|
762
|
+
}
|
|
763
|
+
function chooserOutcomeError(outcome, clickError, controlLabel) {
|
|
764
|
+
if (outcome.kind === "rejected") {
|
|
765
|
+
const chooserMessage = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
|
|
766
|
+
const clickMessage = clickError === undefined
|
|
767
|
+
? ""
|
|
768
|
+
: ` ${controlLabel} click also failed: ${clickError instanceof Error ? clickError.message : String(clickError)}`;
|
|
769
|
+
return new Error(`Project Sources file chooser was rejected: ${chooserMessage}.${clickMessage}`);
|
|
770
|
+
}
|
|
771
|
+
if (outcome.kind === "timeout") {
|
|
772
|
+
const clickMessage = clickError === undefined
|
|
773
|
+
? ""
|
|
774
|
+
: ` ${controlLabel} click failed: ${clickError instanceof Error ? clickError.message : String(clickError)}.`;
|
|
775
|
+
return new Error(`Project Sources file chooser did not settle before the ${PROJECT_SOURCE_CHOOSER_TRANSITION_TIMEOUT_MS}ms chooser transition deadline.${clickMessage}`);
|
|
776
|
+
}
|
|
777
|
+
return new Error(`Project Sources file chooser settled before the ${controlLabel} click; no file handoff was attempted.`);
|
|
778
|
+
}
|
|
779
|
+
function isProjectSourceFileChooserLike(value) {
|
|
780
|
+
return value !== null
|
|
781
|
+
&& typeof value === "object"
|
|
782
|
+
&& typeof value.setFiles === "function";
|
|
783
|
+
}
|
|
490
784
|
async function locatorCount(locator) {
|
|
491
785
|
if (typeof locator.count !== "function") {
|
|
492
786
|
return 0;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resultError } from "../errors.js";
|
|
2
|
+
import { assertOperationAwareDispatchAllowed, CommandRoutingError, classifyCommandRouting, routeCommandRuntimeEnv } from "../runtime/command-routing.js";
|
|
1
3
|
import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./artifacts.js";
|
|
2
4
|
import { applyConfiguration, inspectConfiguration } from "./configuration.js";
|
|
3
5
|
import { detectExperience, openExperience } from "./experience.js";
|
|
@@ -43,71 +45,96 @@ export async function runSequenceWithExecutor(plan, executor, env = {}) {
|
|
|
43
45
|
return withCommandOutputText({ ...finalResult, steps: stepResults });
|
|
44
46
|
}
|
|
45
47
|
export async function executeStep(step, env, previousResults) {
|
|
48
|
+
// The sequence executor is a direct dispatch seam as well as a helper for
|
|
49
|
+
// backend workflows. Keep it fail-closed if a runtime caller supplies a
|
|
50
|
+
// command outside the explicit inventory. This guard does not acquire a
|
|
51
|
+
// coordinator: legacy commands retain their existing behavior, while an
|
|
52
|
+
// operation-aware request may use only an explicitly migrated facade seam.
|
|
53
|
+
if (classifyCommandRouting(step.command) === undefined) {
|
|
54
|
+
return resultError(new CommandRoutingError("unclassified_command"));
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
assertOperationAwareDispatchAllowed(step.command, step.args);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return resultError(error instanceof Error ? error : new Error(String(error)));
|
|
61
|
+
}
|
|
62
|
+
let routedEnv;
|
|
63
|
+
try {
|
|
64
|
+
// Legacy sequence steps receive a fresh coordinated facade. The facade
|
|
65
|
+
// routes each browser/page method through the process-scoped coordinator;
|
|
66
|
+
// this function never wraps the complete step, so waits, polling, report
|
|
67
|
+
// work, and caller callbacks do not retain a tab actor.
|
|
68
|
+
routedEnv = routeCommandRuntimeEnv(step.command, env);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return resultError(error instanceof Error ? error : new Error(String(error)));
|
|
72
|
+
}
|
|
46
73
|
switch (step.command) {
|
|
47
74
|
case "session.bootstrap":
|
|
48
|
-
return bootstrap(
|
|
75
|
+
return bootstrap(routedEnv, step.args);
|
|
49
76
|
case "experience.detect":
|
|
50
|
-
return detectExperience(
|
|
77
|
+
return detectExperience(routedEnv, step.args);
|
|
51
78
|
case "experience.open":
|
|
52
|
-
return openExperience(
|
|
79
|
+
return openExperience(routedEnv, step.args);
|
|
53
80
|
case "configuration.inspect":
|
|
54
|
-
return inspectConfiguration(
|
|
81
|
+
return inspectConfiguration(routedEnv, step.args);
|
|
55
82
|
case "configuration.apply":
|
|
56
|
-
return applyConfiguration(
|
|
83
|
+
return applyConfiguration(routedEnv, step.args);
|
|
57
84
|
case "work.start":
|
|
58
|
-
return startWork(
|
|
85
|
+
return startWork(routedEnv, step.args);
|
|
59
86
|
case "work.status":
|
|
60
|
-
return workStatus(
|
|
87
|
+
return workStatus(routedEnv, step.args);
|
|
61
88
|
case "work.wait":
|
|
62
|
-
return waitForWork(
|
|
89
|
+
return waitForWork(routedEnv, step.args);
|
|
63
90
|
case "work.steer":
|
|
64
|
-
return steerWork(
|
|
91
|
+
return steerWork(routedEnv, step.args);
|
|
65
92
|
case "work.readLatest":
|
|
66
|
-
return readLatestWork(
|
|
93
|
+
return readLatestWork(routedEnv, step.args);
|
|
67
94
|
case "threads.search":
|
|
68
|
-
return searchThreads(
|
|
95
|
+
return searchThreads(routedEnv, step.args);
|
|
69
96
|
case "threads.open":
|
|
70
|
-
return openThread(
|
|
97
|
+
return openThread(routedEnv, step.args, previousResults);
|
|
71
98
|
case "threads.new":
|
|
72
|
-
return newThread(
|
|
99
|
+
return newThread(routedEnv, step.args);
|
|
73
100
|
case "messages.compose":
|
|
74
|
-
return composeMessage(
|
|
101
|
+
return composeMessage(routedEnv, step.args);
|
|
75
102
|
case "messages.submit":
|
|
76
|
-
return submitMessage(
|
|
103
|
+
return submitMessage(routedEnv, step.args);
|
|
77
104
|
case "messages.ask":
|
|
78
|
-
return askMessage(
|
|
105
|
+
return askMessage(routedEnv, step.args);
|
|
79
106
|
case "messages.wait":
|
|
80
|
-
return waitForMessage(
|
|
107
|
+
return waitForMessage(routedEnv, step.args);
|
|
81
108
|
case "messages.readLatest":
|
|
82
|
-
return readLatest(
|
|
109
|
+
return readLatest(routedEnv, step.args);
|
|
83
110
|
case "messages.status":
|
|
84
|
-
return messageStatus(
|
|
111
|
+
return messageStatus(routedEnv, step.args);
|
|
85
112
|
case "messages.stop":
|
|
86
|
-
return stopGeneration(
|
|
113
|
+
return stopGeneration(routedEnv, step.args);
|
|
87
114
|
case "messages.waitAndRead":
|
|
88
|
-
return waitAndRead(
|
|
115
|
+
return waitAndRead(routedEnv, step.args);
|
|
89
116
|
case "artifacts.listLatest":
|
|
90
|
-
return listLatestArtifacts(
|
|
117
|
+
return listLatestArtifacts(routedEnv, step.args);
|
|
91
118
|
case "artifacts.wait":
|
|
92
|
-
return waitForArtifact(
|
|
119
|
+
return waitForArtifact(routedEnv, step.args);
|
|
93
120
|
case "artifacts.downloadLatest":
|
|
94
|
-
return downloadLatestArtifact(
|
|
121
|
+
return downloadLatestArtifact(routedEnv, step.args);
|
|
95
122
|
case "files.attach":
|
|
96
|
-
return attachFiles(
|
|
123
|
+
return attachFiles(routedEnv, step.args);
|
|
97
124
|
case "files.downloadLatest":
|
|
98
|
-
return downloadLatestFile(
|
|
125
|
+
return downloadLatestFile(routedEnv, step.args);
|
|
99
126
|
case "projects.sources.list":
|
|
100
|
-
return listProjectSources(
|
|
127
|
+
return listProjectSources(routedEnv, step.args);
|
|
101
128
|
case "projects.sources.planAdd":
|
|
102
|
-
return buildProjectSourceAddPlan(
|
|
129
|
+
return buildProjectSourceAddPlan(routedEnv, step.args);
|
|
103
130
|
case "projects.sources.add":
|
|
104
|
-
return addProjectSources(
|
|
131
|
+
return addProjectSources(routedEnv, step.args);
|
|
105
132
|
case "response.copy":
|
|
106
|
-
return copyResponse(
|
|
133
|
+
return copyResponse(routedEnv, step.args);
|
|
107
134
|
case "modes.set":
|
|
108
|
-
return setMode(
|
|
135
|
+
return setMode(routedEnv, step.args);
|
|
109
136
|
case "tools.select":
|
|
110
|
-
return selectTool(
|
|
137
|
+
return selectTool(routedEnv, step.args);
|
|
111
138
|
}
|
|
112
139
|
}
|
|
113
140
|
export function normalizePolicy(policy) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { attachChatGPTBrowser, isChatGPTUrl, tabIdFromPage } from "../browser/attach.js";
|
|
2
2
|
import { readPageState } from "../browser/page-state.js";
|
|
3
3
|
import { resultError, resultOk } from "../errors.js";
|
|
4
|
+
import { unwrapCoordinatedPage } from "../runtime/coordinated-page.js";
|
|
4
5
|
import { contextFromPage } from "./context.js";
|
|
5
6
|
export async function bootstrap(env, args = {}) {
|
|
6
7
|
try {
|
|
@@ -71,7 +72,11 @@ export async function verifyTabAffinity(env) {
|
|
|
71
72
|
if (env.expectedTabId === undefined || env.page === undefined) {
|
|
72
73
|
return undefined;
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
+
// The coordinated facade intentionally snapshots its public identity when
|
|
76
|
+
// it is created so its coordinator key cannot drift underneath queued work.
|
|
77
|
+
// Affinity verification has a different job: it must inspect the provider
|
|
78
|
+
// page that the facade protects and detect a changed/reused tab claim.
|
|
79
|
+
const actualTabId = tabIdFromPage(unwrapCoordinatedPage(env.page));
|
|
75
80
|
if (actualTabId === env.expectedTabId) {
|
|
76
81
|
return undefined;
|
|
77
82
|
}
|
|
@@ -99,7 +104,7 @@ export async function verifyTabAffinity(env) {
|
|
|
99
104
|
context: await contextFromPage(env.page, tabContext(env, actualTabId))
|
|
100
105
|
};
|
|
101
106
|
}
|
|
102
|
-
function tabContext(env, actualTabId = tabIdFromPage(env.page)) {
|
|
107
|
+
function tabContext(env, actualTabId = tabIdFromPage(unwrapCoordinatedPage(env.page))) {
|
|
103
108
|
const tabId = actualTabId ?? env.expectedTabId;
|
|
104
109
|
return tabId === undefined ? {} : { tabId };
|
|
105
110
|
}
|
|
@@ -10,6 +10,9 @@ import { askMessage, composeMessage, messageStatus, readLatest, submitMessage, w
|
|
|
10
10
|
import { ensurePage } from "./session.js";
|
|
11
11
|
const NEW_WORK_LABELS = localeLabels.newWork;
|
|
12
12
|
export async function startWork(env, args) {
|
|
13
|
+
if (args.operationId !== undefined) {
|
|
14
|
+
return transactionalWorkRoutingBlocker(args.operationId, "start");
|
|
15
|
+
}
|
|
13
16
|
const prompt = args.prompt.trim();
|
|
14
17
|
if (prompt.length === 0) {
|
|
15
18
|
return {
|
|
@@ -172,6 +175,9 @@ export async function waitForWork(env, args = {}) {
|
|
|
172
175
|
return markWorkResult(await waitForMessage(env, args), ready.data?.selectorProfile);
|
|
173
176
|
}
|
|
174
177
|
export async function steerWork(env, args) {
|
|
178
|
+
if (args.operationId !== undefined || args.handle !== undefined || args.controlActionId !== undefined || args.expectedAssistantTurnId !== undefined) {
|
|
179
|
+
return transactionalWorkRoutingBlocker(args.operationId ?? args.handle?.operationId, "steer");
|
|
180
|
+
}
|
|
175
181
|
const ready = await requireWork(env);
|
|
176
182
|
if (!ready.ok)
|
|
177
183
|
return forwardCommandFailure(ready);
|
|
@@ -182,6 +188,31 @@ export async function steerWork(env, args) {
|
|
|
182
188
|
...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs })
|
|
183
189
|
}), ready.data?.selectorProfile);
|
|
184
190
|
}
|
|
191
|
+
function transactionalWorkRoutingBlocker(operationId, action) {
|
|
192
|
+
const message = `Transactional Work ${action} must be invoked through chatgpt.work.${action}; the low-level command has no operations facade.`;
|
|
193
|
+
const data = action === "start"
|
|
194
|
+
? {
|
|
195
|
+
task: {},
|
|
196
|
+
submitted: { submitted: false, submissionState: "not_submitted" },
|
|
197
|
+
...(operationId === undefined ? {} : { operationId })
|
|
198
|
+
}
|
|
199
|
+
: operationId === undefined ? {} : { operationId };
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
status: "unsupported",
|
|
203
|
+
data,
|
|
204
|
+
warnings: [],
|
|
205
|
+
error: { name: "OperationRoutingError", message, recoverable: false },
|
|
206
|
+
blocker: {
|
|
207
|
+
kind: "unknown",
|
|
208
|
+
code: "transactional_work_requires_client",
|
|
209
|
+
fieldPath: action === "start" ? "operationId" : "handle",
|
|
210
|
+
message,
|
|
211
|
+
resumable: false
|
|
212
|
+
},
|
|
213
|
+
context: { timestamp: new Date().toISOString(), experience: "work" }
|
|
214
|
+
};
|
|
215
|
+
}
|
|
185
216
|
export async function readLatestWork(env, args = {}) {
|
|
186
217
|
const ready = await requireWork(env);
|
|
187
218
|
if (!ready.ok)
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -2,6 +2,15 @@ import type { BlockerKind, CommandContext, CommandResult } from "./types.js";
|
|
|
2
2
|
type BlockerDetails = Partial<Omit<NonNullable<CommandResult["blocker"]>, "kind" | "message" | "visibleText">>;
|
|
3
3
|
export declare const BROWSER_BRIDGE_UNAVAILABLE_MESSAGE = "Codex cannot access the ChatGPT browser bridge from this backend process. In an ordinary shell this is expected; for a live Codex Chrome run, assign the Chrome plugin runtime returned by setupBrowserRuntime() to globalThis.agent before using it.";
|
|
4
4
|
export declare const BROWSER_BRIDGE_REMEDIATION: NonNullable<NonNullable<CommandResult["blocker"]>["remediation"]>;
|
|
5
|
+
/**
|
|
6
|
+
* Read a Node-style errno code without relying on realm-sensitive
|
|
7
|
+
* `instanceof Error` checks or invoking an untrusted accessor.
|
|
8
|
+
*
|
|
9
|
+
* Browser-hosted modules can receive filesystem errors created by the host
|
|
10
|
+
* realm. Those values are genuine Node errno objects but are not necessarily
|
|
11
|
+
* instances of this bundle's `Error` constructor.
|
|
12
|
+
*/
|
|
13
|
+
export declare function nodeErrorCode(error: unknown): string | undefined;
|
|
5
14
|
export declare class ChatGPTControlError extends Error {
|
|
6
15
|
readonly kind: BlockerKind;
|
|
7
16
|
readonly recoverable: boolean;
|
package/dist/src/errors.js
CHANGED
|
@@ -21,6 +21,28 @@ export const BROWSER_BRIDGE_REMEDIATION = [
|
|
|
21
21
|
userActionRequired: true
|
|
22
22
|
}
|
|
23
23
|
];
|
|
24
|
+
/**
|
|
25
|
+
* Read a Node-style errno code without relying on realm-sensitive
|
|
26
|
+
* `instanceof Error` checks or invoking an untrusted accessor.
|
|
27
|
+
*
|
|
28
|
+
* Browser-hosted modules can receive filesystem errors created by the host
|
|
29
|
+
* realm. Those values are genuine Node errno objects but are not necessarily
|
|
30
|
+
* instances of this bundle's `Error` constructor.
|
|
31
|
+
*/
|
|
32
|
+
export function nodeErrorCode(error) {
|
|
33
|
+
if (error === null || (typeof error !== "object" && typeof error !== "function"))
|
|
34
|
+
return undefined;
|
|
35
|
+
try {
|
|
36
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
37
|
+
if (descriptor === undefined || !("value" in descriptor) || typeof descriptor.value !== "string") {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return descriptor.value;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
24
46
|
export class ChatGPTControlError extends Error {
|
|
25
47
|
kind;
|
|
26
48
|
recoverable;
|