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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accept genuine one-byte typed-array views across JavaScript realms.
|
|
3
|
+
*
|
|
4
|
+
* `instanceof Uint8Array` is realm-sensitive. `ArrayBuffer.isView` checks the
|
|
5
|
+
* internal view slot across realms; the one-byte width and length relation
|
|
6
|
+
* then exclude DataView and wider typed arrays without trusting a type tag.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isByteArrayView(value: unknown): value is Uint8Array;
|
|
9
|
+
/**
|
|
10
|
+
* Accept descriptor-safe plain records across JavaScript realms.
|
|
11
|
+
*
|
|
12
|
+
* Comparing a prototype directly with this realm's `Object.prototype`
|
|
13
|
+
* rejects ordinary objects returned by a VM or browser bridge. A foreign
|
|
14
|
+
* intrinsic Object prototype is still a direct child of `null`; custom class
|
|
15
|
+
* instances have at least one additional layer. Only own data descriptors
|
|
16
|
+
* are accepted, so getters are never invoked by the boundary.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isPlainDataRecord(value: unknown): value is Record<string, unknown>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accept genuine one-byte typed-array views across JavaScript realms.
|
|
3
|
+
*
|
|
4
|
+
* `instanceof Uint8Array` is realm-sensitive. `ArrayBuffer.isView` checks the
|
|
5
|
+
* internal view slot across realms; the one-byte width and length relation
|
|
6
|
+
* then exclude DataView and wider typed arrays without trusting a type tag.
|
|
7
|
+
*/
|
|
8
|
+
export function isByteArrayView(value) {
|
|
9
|
+
if (!ArrayBuffer.isView(value))
|
|
10
|
+
return false;
|
|
11
|
+
try {
|
|
12
|
+
const view = value;
|
|
13
|
+
return view.BYTES_PER_ELEMENT === 1
|
|
14
|
+
&& Number.isSafeInteger(view.length)
|
|
15
|
+
&& view.length === view.byteLength;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Accept descriptor-safe plain records across JavaScript realms.
|
|
23
|
+
*
|
|
24
|
+
* Comparing a prototype directly with this realm's `Object.prototype`
|
|
25
|
+
* rejects ordinary objects returned by a VM or browser bridge. A foreign
|
|
26
|
+
* intrinsic Object prototype is still a direct child of `null`; custom class
|
|
27
|
+
* instances have at least one additional layer. Only own data descriptors
|
|
28
|
+
* are accepted, so getters are never invoked by the boundary.
|
|
29
|
+
*/
|
|
30
|
+
export function isPlainDataRecord(value) {
|
|
31
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
32
|
+
return false;
|
|
33
|
+
try {
|
|
34
|
+
const prototype = Object.getPrototypeOf(value);
|
|
35
|
+
if (prototype !== null
|
|
36
|
+
&& prototype !== Object.prototype
|
|
37
|
+
&& Object.getPrototypeOf(prototype) !== null) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
for (const descriptor of Object.values(Object.getOwnPropertyDescriptors(value))) {
|
|
41
|
+
if (!("value" in descriptor) || descriptor.get !== undefined || descriptor.set !== undefined)
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
3
|
import { link, mkdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname } from "node:path";
|
|
5
|
+
import { nodeErrorCode } from "../errors.js";
|
|
5
6
|
export const UNTRUSTED_OUTPUT_INLINE_LIMIT_BYTES = 12_000;
|
|
6
7
|
export const UNTRUSTED_OUTPUT_SCHEMA_VERSION = "chatgpt.browser_control.untrusted_output_return.v1";
|
|
7
8
|
export const INTEGRITY_SCHEMA_VERSION = "chatgpt.browser_control.integrity.v1";
|
|
@@ -195,5 +196,5 @@ function isNotFoundError(error) {
|
|
|
195
196
|
return isNodeError(error) && error.code === "ENOENT";
|
|
196
197
|
}
|
|
197
198
|
function isNodeError(error) {
|
|
198
|
-
return error
|
|
199
|
+
return nodeErrorCode(error) !== undefined;
|
|
199
200
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runBackendStdioServer } from "../backend/stdio-server.js";
|
|
3
|
+
import { detectPackagedBackendIdentity } from "../backend/runtime-identity.js";
|
|
4
|
+
const backendIdentity = await detectPackagedBackendIdentity(import.meta.url);
|
|
3
5
|
await runBackendStdioServer({
|
|
4
6
|
input: process.stdin,
|
|
5
7
|
output: process.stdout,
|
|
6
|
-
error: process.stderr
|
|
8
|
+
error: process.stderr,
|
|
9
|
+
backendIdentity
|
|
7
10
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { tabIdFromPage } from "../../browser/attach.js";
|
|
3
4
|
import { redactReportValue } from "../../safety/report-redaction.js";
|
|
4
5
|
const CLEANUP_TIMEOUT_MS = 10_000;
|
|
5
6
|
export function envFlag(name) {
|
|
@@ -24,6 +25,7 @@ function readEnv(name) {
|
|
|
24
25
|
export async function runScenario(scenario, context) {
|
|
25
26
|
const startedAt = new Date().toISOString();
|
|
26
27
|
const startedMs = Date.now();
|
|
28
|
+
const tabBaseline = await snapshotBrowserTabIds(context.browser);
|
|
27
29
|
let result;
|
|
28
30
|
if (!scenario.enabled(context)) {
|
|
29
31
|
result = {
|
|
@@ -55,7 +57,7 @@ export async function runScenario(scenario, context) {
|
|
|
55
57
|
};
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
const cleanup = await finalizeBrowserTabs(context.browser);
|
|
60
|
+
const cleanup = await finalizeBrowserTabs(context.cleanupBrowser ?? context.browser, context.browser, tabBaseline);
|
|
59
61
|
return { ...result, cleanup };
|
|
60
62
|
}
|
|
61
63
|
export async function runLiveSmoke(context, scenarios) {
|
|
@@ -108,19 +110,89 @@ export function filterScenarios(scenarios, namesCsv) {
|
|
|
108
110
|
.filter(Boolean));
|
|
109
111
|
return scenarios.filter(scenario => wanted.has(scenario.name));
|
|
110
112
|
}
|
|
111
|
-
async function finalizeBrowserTabs(
|
|
112
|
-
const
|
|
113
|
-
const finalize =
|
|
113
|
+
async function finalizeBrowserTabs(finalizerBrowser, behaviorBrowser, baseline) {
|
|
114
|
+
const finalizerTabs = finalizerBrowser?.tabs;
|
|
115
|
+
const finalize = finalizerTabs?.finalize;
|
|
114
116
|
if (typeof finalize !== "function") {
|
|
117
|
+
return closeNewExactTabs(behaviorBrowser, baseline);
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await withTimeout(finalize.call(finalizerTabs, { keep: [] }), CLEANUP_TIMEOUT_MS, `browser.tabs.finalize timed out after ${CLEANUP_TIMEOUT_MS}ms`);
|
|
121
|
+
return { attempted: true, ok: true };
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return {
|
|
125
|
+
attempted: true,
|
|
126
|
+
ok: false,
|
|
127
|
+
error: {
|
|
128
|
+
name: error instanceof Error ? error.name : "Error",
|
|
129
|
+
message: error instanceof Error ? error.message : String(error)
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function snapshotBrowserTabIds(browser) {
|
|
135
|
+
const tabs = browser?.tabs;
|
|
136
|
+
const list = tabs?.list;
|
|
137
|
+
if (tabs === undefined || typeof list !== "function") {
|
|
138
|
+
return { ok: false, reason: "browser.tabs.list unavailable" };
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const pages = await list.call(tabs);
|
|
142
|
+
const ids = new Set();
|
|
143
|
+
for (const page of pages) {
|
|
144
|
+
const id = tabIdFromPage(page);
|
|
145
|
+
if (id === undefined) {
|
|
146
|
+
return { ok: false, reason: "browser.tabs.list returned a tab without an exact id" };
|
|
147
|
+
}
|
|
148
|
+
ids.add(id);
|
|
149
|
+
}
|
|
150
|
+
return { ok: true, ids };
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
reason: `browser.tabs.list failed: ${error instanceof Error ? error.message : String(error)}`
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function closeNewExactTabs(browser, baseline) {
|
|
160
|
+
if (!baseline.ok) {
|
|
161
|
+
return { attempted: false, ok: false, reason: baseline.reason };
|
|
162
|
+
}
|
|
163
|
+
const tabs = browser?.tabs;
|
|
164
|
+
const list = tabs?.list;
|
|
165
|
+
const get = tabs?.get;
|
|
166
|
+
if (tabs === undefined || typeof list !== "function" || typeof get !== "function") {
|
|
115
167
|
return {
|
|
116
168
|
attempted: false,
|
|
117
169
|
ok: false,
|
|
118
|
-
reason: "browser.tabs.finalize unavailable"
|
|
170
|
+
reason: "browser.tabs.finalize unavailable and exact tabs.list/get cleanup is unavailable"
|
|
119
171
|
};
|
|
120
172
|
}
|
|
121
173
|
try {
|
|
122
|
-
await
|
|
123
|
-
|
|
174
|
+
const pages = await list.call(tabs);
|
|
175
|
+
const newTabIds = [];
|
|
176
|
+
for (const page of pages) {
|
|
177
|
+
const id = tabIdFromPage(page);
|
|
178
|
+
if (id === undefined) {
|
|
179
|
+
throw new Error("browser.tabs.list returned a tab without an exact id");
|
|
180
|
+
}
|
|
181
|
+
if (!baseline.ids.has(id) && !newTabIds.includes(id)) {
|
|
182
|
+
newTabIds.push(id);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const id of newTabIds) {
|
|
186
|
+
const page = await get.call(tabs, id);
|
|
187
|
+
if (tabIdFromPage(page) !== id) {
|
|
188
|
+
throw new Error(`browser.tabs.get did not preserve exact cleanup affinity for tab ${id}`);
|
|
189
|
+
}
|
|
190
|
+
if (typeof page.close !== "function") {
|
|
191
|
+
throw new Error(`browser tab ${id} does not expose close()`);
|
|
192
|
+
}
|
|
193
|
+
await withTimeout(Promise.resolve(page.close()), CLEANUP_TIMEOUT_MS, `browser tab ${id} close timed out after ${CLEANUP_TIMEOUT_MS}ms`);
|
|
194
|
+
}
|
|
195
|
+
return { attempted: true, ok: true, closedTabCount: newTabIds.length };
|
|
124
196
|
}
|
|
125
197
|
catch (error) {
|
|
126
198
|
return {
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
+
import { createChatGPT } from "../../index.js";
|
|
1
2
|
import type { AskReadData, CommandResult, ConfigurationInspectionData, ConfigurationSelection } from "../../types.js";
|
|
2
3
|
import type { LiveSmokeScenario } from "./types.js";
|
|
4
|
+
type WorkConfigurationCommands = Pick<ReturnType<typeof createChatGPT>["configuration"], "apply" | "inspect">;
|
|
5
|
+
type ExperienceCommands = Pick<ReturnType<typeof createChatGPT>["experience"], "detect" | "open">;
|
|
6
|
+
export type WorkEffortRestoreResult = {
|
|
7
|
+
command: CommandResult<unknown>;
|
|
8
|
+
verified: boolean;
|
|
9
|
+
attempts: number;
|
|
10
|
+
observedEffort?: string;
|
|
11
|
+
};
|
|
12
|
+
export type ChatExperienceRestoreResult = {
|
|
13
|
+
command: CommandResult<unknown>;
|
|
14
|
+
verified: boolean;
|
|
15
|
+
attempts: number;
|
|
16
|
+
observedExperience?: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function restoreChatExperience(experience: ExperienceCommands, options?: {
|
|
19
|
+
attempts?: number;
|
|
20
|
+
delayMs?: number;
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
23
|
+
}): Promise<ChatExperienceRestoreResult>;
|
|
24
|
+
export declare function restoreWorkEffort(configuration: WorkConfigurationCommands, effort: string, options?: {
|
|
25
|
+
attempts?: number;
|
|
26
|
+
delayMs?: number;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
29
|
+
}): Promise<WorkEffortRestoreResult>;
|
|
3
30
|
export declare const requiredScenarios: LiveSmokeScenario[];
|
|
4
31
|
export declare const optionalScenarios: LiveSmokeScenario[];
|
|
5
32
|
export declare function generatedFileAskCanProceed(result: CommandResult<AskReadData>): boolean;
|
|
6
33
|
export declare function chatActiveSelection(inspection: ConfigurationInspectionData): ConfigurationSelection;
|
|
34
|
+
export {};
|
|
@@ -1,9 +1,93 @@
|
|
|
1
1
|
import { mkdtemp, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { attachAskRead, attachFiles, askMessage, bootstrap, composeMessage, createChatGPT, copyResponse, downloadLatestAttachment, messageStatus, newThread, openExperience, openThread, readLatest, runSequence, searchThreads, selectTool, setMode, stopGeneration, submitMessage, twoTurnExchange, waitAndRead, waitForMessage } from "../../index.js";
|
|
4
|
+
import { attachAskRead, attachFiles, configurationMatchesSelection, askMessage, bootstrap, composeMessage, createChatGPT, copyResponse, detectExperience, downloadLatestAttachment, messageStatus, newThread, openExperience, openThread, readLatest, runSequence, searchThreads, selectTool, setMode, stopGeneration, submitMessage, twoTurnExchange, waitAndRead, waitForMessage } from "../../index.js";
|
|
5
5
|
import { EMPTY_GENERATION_STATE, readAssistantGenerationState } from "../../dom/generation-state.js";
|
|
6
6
|
import { contextEnvFlag, contextEnvText } from "./harness.js";
|
|
7
|
+
export async function restoreChatExperience(experience, options = {}) {
|
|
8
|
+
const attempts = Math.max(1, Math.min(5, options.attempts ?? 3));
|
|
9
|
+
const delayMs = Math.max(0, Math.min(5000, options.delayMs ?? 750));
|
|
10
|
+
const timeoutMs = Math.max(1000, Math.min(120000, options.timeoutMs ?? 60000));
|
|
11
|
+
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
12
|
+
await new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
13
|
+
});
|
|
14
|
+
let terminal;
|
|
15
|
+
let observedExperience;
|
|
16
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
17
|
+
const detectedBefore = await experience.detect({ timeoutMs });
|
|
18
|
+
terminal = asCommand(detectedBefore);
|
|
19
|
+
observedExperience = detectedBefore.data?.experience;
|
|
20
|
+
if (detectedBefore.ok && observedExperience === "chat") {
|
|
21
|
+
return { command: terminal, verified: true, attempts: attempt, observedExperience };
|
|
22
|
+
}
|
|
23
|
+
const opened = await experience.open({ experience: "chat", timeoutMs });
|
|
24
|
+
terminal = asCommand(opened);
|
|
25
|
+
if (opened.ok && opened.data?.experience === "chat") {
|
|
26
|
+
const detectedAfter = await experience.detect({ timeoutMs });
|
|
27
|
+
terminal = asCommand(detectedAfter);
|
|
28
|
+
observedExperience = detectedAfter.data?.experience;
|
|
29
|
+
if (detectedAfter.ok && observedExperience === "chat") {
|
|
30
|
+
return { command: terminal, verified: true, attempts: attempt, observedExperience };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (attempt < attempts) {
|
|
34
|
+
await sleep(delayMs);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
command: terminal,
|
|
39
|
+
verified: false,
|
|
40
|
+
attempts,
|
|
41
|
+
...(observedExperience === undefined ? {} : { observedExperience })
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export async function restoreWorkEffort(configuration, effort, options = {}) {
|
|
45
|
+
const attempts = Math.max(1, Math.min(5, options.attempts ?? 3));
|
|
46
|
+
const delayMs = Math.max(0, Math.min(5000, options.delayMs ?? 750));
|
|
47
|
+
const timeoutMs = Math.max(1000, Math.min(120000, options.timeoutMs ?? 60000));
|
|
48
|
+
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
50
|
+
});
|
|
51
|
+
let terminal;
|
|
52
|
+
let observedEffort;
|
|
53
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
54
|
+
const applied = await configuration.apply({
|
|
55
|
+
experience: "work",
|
|
56
|
+
desired: { effort },
|
|
57
|
+
strict: true,
|
|
58
|
+
timeoutMs
|
|
59
|
+
});
|
|
60
|
+
terminal = asCommand(applied);
|
|
61
|
+
if (applied.ok && applied.data?.verified === true) {
|
|
62
|
+
const inspected = await configuration.inspect({
|
|
63
|
+
experience: "work",
|
|
64
|
+
includeOptions: false,
|
|
65
|
+
timeoutMs
|
|
66
|
+
});
|
|
67
|
+
terminal = asCommand(inspected);
|
|
68
|
+
observedEffort = inspected.data?.active.effort;
|
|
69
|
+
if (inspected.ok
|
|
70
|
+
&& inspected.data !== undefined
|
|
71
|
+
&& configurationMatchesSelection(inspected.data, { effort })) {
|
|
72
|
+
return {
|
|
73
|
+
command: terminal,
|
|
74
|
+
verified: true,
|
|
75
|
+
attempts: attempt,
|
|
76
|
+
...(observedEffort === undefined ? {} : { observedEffort })
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (attempt < attempts) {
|
|
81
|
+
await sleep(delayMs);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
command: terminal,
|
|
86
|
+
verified: false,
|
|
87
|
+
attempts,
|
|
88
|
+
...(observedEffort === undefined ? {} : { observedEffort })
|
|
89
|
+
};
|
|
90
|
+
}
|
|
7
91
|
export const requiredScenarios = [
|
|
8
92
|
scenario("bootstrap-new-tab", true, () => true, async (context, meta) => {
|
|
9
93
|
const env = envFor(context);
|
|
@@ -265,10 +349,11 @@ export const requiredScenarios = [
|
|
|
265
349
|
}
|
|
266
350
|
finally {
|
|
267
351
|
if (booted) {
|
|
268
|
-
const restored = await chatgpt.experience
|
|
269
|
-
terminal =
|
|
270
|
-
|
|
271
|
-
|
|
352
|
+
const restored = await restoreChatExperience(chatgpt.experience);
|
|
353
|
+
terminal = restored.command;
|
|
354
|
+
details.experienceRestoreAttempts = restored.attempts;
|
|
355
|
+
if (!restored.verified) {
|
|
356
|
+
failure = new LiveSmokeCommandFailure("experience.restore.chat", restored.command);
|
|
272
357
|
}
|
|
273
358
|
else {
|
|
274
359
|
details.restoredExperience = "chat";
|
|
@@ -654,25 +739,25 @@ export const optionalScenarios = [
|
|
|
654
739
|
}
|
|
655
740
|
finally {
|
|
656
741
|
if (booted && restoreNeeded && originalEffort !== undefined) {
|
|
657
|
-
const restoredConfiguration = await chatgpt.configuration
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
failure = new LiveSmokeCommandFailure("configuration.restore.work", asCommand(restoredConfiguration));
|
|
742
|
+
const restoredConfiguration = await restoreWorkEffort(chatgpt.configuration, originalEffort);
|
|
743
|
+
terminal = restoredConfiguration.command;
|
|
744
|
+
details.configurationRestoreAttempts = restoredConfiguration.attempts;
|
|
745
|
+
if (restoredConfiguration.observedEffort !== undefined) {
|
|
746
|
+
details.restoredEffort = restoredConfiguration.observedEffort;
|
|
747
|
+
}
|
|
748
|
+
if (!restoredConfiguration.verified) {
|
|
749
|
+
failure = new LiveSmokeCommandFailure("configuration.restore.work", restoredConfiguration.command);
|
|
666
750
|
}
|
|
667
751
|
else {
|
|
668
752
|
details.configurationRestored = true;
|
|
669
753
|
}
|
|
670
754
|
}
|
|
671
755
|
if (booted) {
|
|
672
|
-
const restoredExperience = await chatgpt.experience
|
|
673
|
-
terminal =
|
|
674
|
-
|
|
675
|
-
|
|
756
|
+
const restoredExperience = await restoreChatExperience(chatgpt.experience);
|
|
757
|
+
terminal = restoredExperience.command;
|
|
758
|
+
details.experienceRestoreAttempts = restoredExperience.attempts;
|
|
759
|
+
if (!restoredExperience.verified) {
|
|
760
|
+
failure = new LiveSmokeCommandFailure("experience.restore.chat", restoredExperience.command);
|
|
676
761
|
}
|
|
677
762
|
else {
|
|
678
763
|
details.restoredExperience = "chat";
|
|
@@ -890,6 +975,13 @@ async function bootNewThread(context, meta) {
|
|
|
890
975
|
const env = await boot(context, meta);
|
|
891
976
|
if ("status" in env)
|
|
892
977
|
return env;
|
|
978
|
+
const chat = await restoreChatExperience({
|
|
979
|
+
detect: args => detectExperience(env, args),
|
|
980
|
+
open: args => openExperience(env, args)
|
|
981
|
+
});
|
|
982
|
+
if (!chat.verified) {
|
|
983
|
+
return fail(meta, chat.command, { failedStage: "experience.open.chat" });
|
|
984
|
+
}
|
|
893
985
|
const created = await newThread(env);
|
|
894
986
|
return created.ok ? env : fail(meta, created);
|
|
895
987
|
}
|
|
@@ -3,6 +3,7 @@ export type LiveSmokeStatus = "pass" | "fail" | "skip";
|
|
|
3
3
|
export type LiveSmokeCleanupResult = {
|
|
4
4
|
attempted: boolean;
|
|
5
5
|
ok: boolean;
|
|
6
|
+
closedTabCount?: number;
|
|
6
7
|
reason?: string;
|
|
7
8
|
error?: {
|
|
8
9
|
name: string;
|
|
@@ -34,6 +35,8 @@ export type LiveSmokeBrowser = BrowserLike & {
|
|
|
34
35
|
export type LiveSmokeContext = {
|
|
35
36
|
agent: unknown;
|
|
36
37
|
browser?: LiveSmokeBrowser;
|
|
38
|
+
/** Optional tool-scoped browser that owns tab finalization but is not used for behavior. */
|
|
39
|
+
cleanupBrowser?: LiveSmokeBrowser;
|
|
37
40
|
env?: Record<string, string | undefined>;
|
|
38
41
|
reportDir: string;
|
|
39
42
|
knownThreadQuery?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { resolveChatGPTBrowser } from "../browser/attach.js";
|
|
3
4
|
import { main as captureSurfaceProfile } from "./capture-surface-profile.js";
|
|
4
5
|
import { filterScenarios, runLiveSmoke } from "./live-smoke/harness.js";
|
|
5
6
|
import { optionalScenarios, requiredScenarios } from "./live-smoke/scenarios.js";
|
|
@@ -15,6 +16,11 @@ export async function runReleaseCanary(runtime, options) {
|
|
|
15
16
|
if (options.tabId.trim().length === 0) {
|
|
16
17
|
throw new Error("runReleaseCanary requires an exact dedicated ChatGPT tab id.");
|
|
17
18
|
}
|
|
19
|
+
// Acquire through the agent once so bridge capability proxies are normalized
|
|
20
|
+
// before coordination. Passing globalThis.browser back through RuntimeEnv
|
|
21
|
+
// bypasses that acquisition boundary and can lose user-open-tab visibility
|
|
22
|
+
// or private-field receiver bindings on Chrome bridge methods.
|
|
23
|
+
const managedBrowser = await resolveChatGPTBrowser({ agent: runtime.agent });
|
|
18
24
|
const reportDir = resolve(options.reportDir ?? join(process.cwd(), "reports", "release-canary"));
|
|
19
25
|
const profileDir = join(reportDir, "surface-profiles");
|
|
20
26
|
await mkdir(profileDir, { recursive: true });
|
|
@@ -32,7 +38,7 @@ export async function runReleaseCanary(runtime, options) {
|
|
|
32
38
|
"--if-missing", "block",
|
|
33
39
|
"--out", profilePaths[index],
|
|
34
40
|
"--provenance", "Sanitized release canary capture from a dedicated visible ChatGPT tab."
|
|
35
|
-
], runtime);
|
|
41
|
+
], { agent: runtime.agent });
|
|
36
42
|
if (exitCode !== 0) {
|
|
37
43
|
return {
|
|
38
44
|
ok: false,
|
|
@@ -44,14 +50,18 @@ export async function runReleaseCanary(runtime, options) {
|
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
finally {
|
|
47
|
-
await closeDedicatedProfileTab(
|
|
53
|
+
await closeDedicatedProfileTab(managedBrowser, options.tabId);
|
|
48
54
|
}
|
|
49
55
|
const names = options.includeUpload === true
|
|
50
56
|
? [...CORE_SCENARIOS, "attach-one-file"]
|
|
51
57
|
: CORE_SCENARIOS;
|
|
52
58
|
const context = {
|
|
53
59
|
agent: runtime.agent,
|
|
54
|
-
|
|
60
|
+
browser: managedBrowser,
|
|
61
|
+
// The agent-acquired browser is authoritative for tab discovery and page
|
|
62
|
+
// behavior. The bridge-hosted global browser separately owns finalize(),
|
|
63
|
+
// which closes only this tool call's temporary tabs after each scenario.
|
|
64
|
+
...(runtime.browser === undefined ? {} : { cleanupBrowser: runtime.browser }),
|
|
55
65
|
reportDir: join(reportDir, "live-smoke"),
|
|
56
66
|
env: {
|
|
57
67
|
CHATGPT_E2E_CONFIGURATION_MUTATION: "1",
|
|
@@ -63,7 +73,10 @@ export async function runReleaseCanary(runtime, options) {
|
|
|
63
73
|
throw new Error(`Release canary scenario registration drift: expected ${names.length}, found ${scenarios.length}.`);
|
|
64
74
|
}
|
|
65
75
|
const smoke = await runLiveSmoke(context, scenarios);
|
|
66
|
-
const failures = smoke.results.
|
|
76
|
+
const failures = smoke.results.flatMap(result => [
|
|
77
|
+
...(result.status === "pass" ? [] : [result.name]),
|
|
78
|
+
...(result.cleanup?.ok === true ? [] : [`${result.name}:cleanup`])
|
|
79
|
+
]);
|
|
67
80
|
return {
|
|
68
81
|
ok: failures.length === 0,
|
|
69
82
|
profilePaths,
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { OperationControlReceiptV1, OperationHandleV1 } from "./operations/types.js";
|
|
2
|
+
import type { BackendCompatibilityReport } from "./backend/protocol.js";
|
|
1
3
|
export type CommandStatus = "ok" | "partial" | "timeout" | "blocked" | "needs_confirmation" | "not_found" | "unsupported" | "error";
|
|
2
4
|
export type SubmissionState = "not_submitted" | "submitted" | "submitted_unconfirmed" | "submitted_generating";
|
|
3
5
|
export type CompletionState = "complete" | "generating" | "stopped" | "partial" | "unknown";
|
|
@@ -663,6 +665,8 @@ export type WorkTaskRef = {
|
|
|
663
665
|
};
|
|
664
666
|
export type StartWorkArgs = {
|
|
665
667
|
prompt: string;
|
|
668
|
+
/** Caller-owned durable identity. Supplying it opts Work start into operations.run. */
|
|
669
|
+
operationId?: string;
|
|
666
670
|
/** Start from a blank Work task when possible. Defaults to true. */
|
|
667
671
|
newTask?: boolean;
|
|
668
672
|
files?: string[];
|
|
@@ -674,6 +678,17 @@ export type StartWorkArgs = {
|
|
|
674
678
|
export type StartWorkData = {
|
|
675
679
|
task: WorkTaskRef;
|
|
676
680
|
submitted: SubmitData;
|
|
681
|
+
/** Requested runner output format for the transactional Work path. */
|
|
682
|
+
responseFormat?: Extract<ResponseFormat, "markdown" | "text">;
|
|
683
|
+
/** Effective durable operation identity when the transactional path was selected. */
|
|
684
|
+
operationId?: string;
|
|
685
|
+
/** Durable locator returned by the transactional Work path. */
|
|
686
|
+
handle?: OperationHandleV1;
|
|
687
|
+
requestDigest?: string;
|
|
688
|
+
pending?: boolean;
|
|
689
|
+
responseDigest?: string;
|
|
690
|
+
responseBytes?: number;
|
|
691
|
+
complete?: boolean;
|
|
677
692
|
configuration?: ApplyConfigurationData;
|
|
678
693
|
wait?: WaitData;
|
|
679
694
|
response?: ReadLatestData;
|
|
@@ -691,11 +706,31 @@ export type WorkWaitArgs = WaitArgs;
|
|
|
691
706
|
export type WorkWaitData = WaitData;
|
|
692
707
|
export type SteerWorkArgs = {
|
|
693
708
|
prompt: string;
|
|
709
|
+
/**
|
|
710
|
+
* Caller-owned parent operation identity. A transactional steer must also
|
|
711
|
+
* provide the exact parent handle, control action ID, and assistant turn;
|
|
712
|
+
* omitting all operation fields preserves the legacy primitive path.
|
|
713
|
+
*/
|
|
714
|
+
operationId?: string;
|
|
715
|
+
handle?: OperationHandleV1;
|
|
716
|
+
/** Caller-owned immutable identity for this one steer action. */
|
|
717
|
+
controlActionId?: string;
|
|
718
|
+
expectedAssistantTurnId?: string;
|
|
694
719
|
wait?: boolean | WaitArgs;
|
|
695
720
|
read?: boolean | ReadLatestArgs;
|
|
696
721
|
timeoutMs?: number;
|
|
697
722
|
};
|
|
698
|
-
export type SteerWorkData = AskReadData
|
|
723
|
+
export type SteerWorkData = Omit<AskReadData, "prompt"> & {
|
|
724
|
+
/** Legacy steering returns the prompt; transactional errors never echo it. */
|
|
725
|
+
prompt?: string;
|
|
726
|
+
operationId?: string;
|
|
727
|
+
controlActionId?: string;
|
|
728
|
+
requestDigest?: string;
|
|
729
|
+
/** Exact parent locator used for a transactional steer. */
|
|
730
|
+
handle?: OperationHandleV1;
|
|
731
|
+
parentHandle?: OperationHandleV1;
|
|
732
|
+
control?: OperationControlReceiptV1;
|
|
733
|
+
};
|
|
699
734
|
export type ReadWorkLatestArgs = ReadLatestArgs;
|
|
700
735
|
export type ReadWorkLatestData = ReadLatestData;
|
|
701
736
|
export type SelectToolArgs = {
|
|
@@ -854,6 +889,8 @@ export type RuntimeEnv = {
|
|
|
854
889
|
clipboard?: ClipboardLike;
|
|
855
890
|
now?: () => Date;
|
|
856
891
|
expectedTabId?: string;
|
|
892
|
+
/** Optional transport-owned compatibility diagnostics for doctor. */
|
|
893
|
+
compatibility?: BackendCompatibilityReport;
|
|
857
894
|
};
|
|
858
895
|
export type ClipboardLike = {
|
|
859
896
|
read: () => Promise<string>;
|