codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.4
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 +42 -0
- package/README.md +43 -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/download-blocked-by-browser.json +22 -0
- package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
- package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
- package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
- package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
- package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
- 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 +185 -1
- package/contracts/v1/parity-suite.json +106 -8
- 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 +45882 -9361
- package/dist/codex-chatgpt-control-journal.mjs +5547 -0
- package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
- package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
- package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
- 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/chatgpt-url.d.ts +2 -0
- package/dist/src/browser/chatgpt-url.js +9 -0
- package/dist/src/browser/downloads.d.ts +17 -1
- package/dist/src/browser/downloads.js +150 -28
- package/dist/src/browser/page-state.js +6 -0
- package/dist/src/client.d.ts +43 -0
- package/dist/src/client.js +1279 -59
- package/dist/src/commands/artifacts.js +6 -4
- package/dist/src/commands/chat-popover.d.ts +37 -0
- package/dist/src/commands/chat-popover.js +537 -0
- package/dist/src/commands/configuration.d.ts +5 -0
- package/dist/src/commands/configuration.js +134 -21
- package/dist/src/commands/context.js +10 -3
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +27 -4
- package/dist/src/commands/experience.d.ts +5 -0
- package/dist/src/commands/experience.js +104 -21
- package/dist/src/commands/files.js +14 -33
- package/dist/src/commands/modes.js +85 -21
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +669 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +19 -6
- package/dist/src/commands/work.js +31 -0
- package/dist/src/dom/composer-text.d.ts +6 -0
- package/dist/src/dom/composer-text.js +85 -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 +167 -0
- package/dist/src/operations/browser-adapter.js +2103 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1256 -0
- package/dist/src/operations/browser-target.d.ts +105 -0
- package/dist/src/operations/browser-target.js +531 -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 +1232 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1145 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/configuration-routing.d.ts +4 -0
- package/dist/src/operations/configuration-routing.js +14 -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 +25 -0
- package/dist/src/operations/index.js +25 -0
- package/dist/src/operations/journal-authority.d.ts +12 -0
- package/dist/src/operations/journal-authority.js +1 -0
- package/dist/src/operations/journal-rpc-client.d.ts +12 -0
- package/dist/src/operations/journal-rpc-client.js +225 -0
- package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
- package/dist/src/operations/journal-rpc-protocol.js +214 -0
- package/dist/src/operations/journal-rpc-server.d.ts +15 -0
- package/dist/src/operations/journal-rpc-server.js +272 -0
- package/dist/src/operations/journal.d.ts +143 -0
- package/dist/src/operations/journal.js +1957 -0
- package/dist/src/operations/production-attachments.d.ts +118 -0
- package/dist/src/operations/production-attachments.js +1035 -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 +80 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1544 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +856 -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 +126 -0
- package/dist/src/operations/runtime-adapter.js +802 -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 +286 -0
- package/dist/src/operations/service.js +2831 -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/transactional-chat-power.d.ts +17 -0
- package/dist/src/operations/transactional-chat-power.js +212 -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 +41 -0
- package/dist/src/runtime/coordinated-page.js +651 -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/blockers.js +8 -1
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/capture-surface-profile.js +7 -2
- package/dist/src/scripts/journal-server.d.ts +2 -0
- package/dist/src/scripts/journal-server.js +37 -0
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
- package/dist/src/scripts/live-smoke/scenarios.js +147 -30
- package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
- package/dist/src/scripts/live-smoke/transactional.js +254 -0
- 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 +8 -3
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/2026-09-06-journal-service.md +140 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +176 -0
- package/references/python-parity.md +137 -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
|
+
}
|
|
@@ -45,7 +45,14 @@ export function classifyVisibleText(text) {
|
|
|
45
45
|
const visibleText = compactVisibleText(text);
|
|
46
46
|
const lowerable = visibleText.length > 0 ? visibleText : text;
|
|
47
47
|
for (const rule of RULES) {
|
|
48
|
-
|
|
48
|
+
// The Work effort picker describes higher effort with this informational
|
|
49
|
+
// copy. It is not an exhausted quota, even on an otherwise empty page.
|
|
50
|
+
// Inspect the full rate-limit text so stripping this copy cannot hide a
|
|
51
|
+
// real notice later in the same surface behind the display-text truncation.
|
|
52
|
+
const candidate = rule.kind === "rate_limit"
|
|
53
|
+
? text.replace(/\bConsumes\s+usage\s+limits\s+faster\b/gi, "")
|
|
54
|
+
: lowerable;
|
|
55
|
+
if (rule.patterns.some(pattern => pattern.test(candidate))) {
|
|
49
56
|
return { kind: rule.kind, message: rule.message, visibleText };
|
|
50
57
|
}
|
|
51
58
|
}
|
|
@@ -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
|
});
|
|
@@ -281,10 +281,15 @@ export function parseArgs(argv) {
|
|
|
281
281
|
})) {
|
|
282
282
|
assertNormalizedSlug(value, `--${field.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}`);
|
|
283
283
|
}
|
|
284
|
-
|
|
284
|
+
if (out === undefined && typeof process === "undefined") {
|
|
285
|
+
throw new UsageError("--out is required in a browser host without a working directory.");
|
|
286
|
+
}
|
|
287
|
+
const outputPath = out === undefined
|
|
288
|
+
? resolve(process.cwd(), "..", "..", "outputs", "surface-profiles", `${new Date().toISOString().slice(0, 10)}-${id}.json`)
|
|
289
|
+
: resolve(out);
|
|
285
290
|
return {
|
|
286
291
|
id,
|
|
287
|
-
out:
|
|
292
|
+
out: outputPath,
|
|
288
293
|
...(locale === undefined ? {} : { locale }),
|
|
289
294
|
region,
|
|
290
295
|
accountScope,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startJournalRpcServer } from "../operations/journal-rpc-server.js";
|
|
3
|
+
async function main() {
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
let stateRoot;
|
|
6
|
+
let directory;
|
|
7
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
8
|
+
const option = args[index];
|
|
9
|
+
const value = args[index + 1];
|
|
10
|
+
if (value === undefined || (option !== "--state-root" && option !== "--directory"))
|
|
11
|
+
throw new Error();
|
|
12
|
+
if (option === "--state-root") {
|
|
13
|
+
if (stateRoot !== undefined)
|
|
14
|
+
throw new Error();
|
|
15
|
+
stateRoot = value;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (directory !== undefined)
|
|
19
|
+
throw new Error();
|
|
20
|
+
directory = value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (stateRoot === undefined || directory === undefined)
|
|
24
|
+
throw new Error();
|
|
25
|
+
const server = await startJournalRpcServer({ stateRoot, directory });
|
|
26
|
+
// The private descriptor contains authentication material. It is never printed.
|
|
27
|
+
process.stdout.write(`${JSON.stringify({ status: "ready", transport: "file" })}\n`);
|
|
28
|
+
const stop = () => {
|
|
29
|
+
void server.close().then(() => { process.exitCode = 0; }, () => { process.exitCode = 2; });
|
|
30
|
+
};
|
|
31
|
+
process.once("SIGINT", stop);
|
|
32
|
+
process.once("SIGTERM", stop);
|
|
33
|
+
}
|
|
34
|
+
void main().catch(() => {
|
|
35
|
+
process.stderr.write("Journal authority startup failed. Supply absolute --state-root and a new private --directory.\n");
|
|
36
|
+
process.exitCode = 2;
|
|
37
|
+
});
|
|
@@ -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,47 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createChatGPT } from "../../index.js";
|
|
2
|
+
import type { AskReadData, CommandResult, ConfigurationAxis, 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 WorkConfigurationRestoreResult = {
|
|
7
|
+
command: CommandResult<unknown>;
|
|
8
|
+
verified: boolean;
|
|
9
|
+
attempts: number;
|
|
10
|
+
observedSelection?: ConfigurationSelection;
|
|
11
|
+
};
|
|
12
|
+
export type WorkEffortRestoreResult = {
|
|
13
|
+
command: CommandResult<unknown>;
|
|
14
|
+
verified: boolean;
|
|
15
|
+
attempts: number;
|
|
16
|
+
observedEffort?: string;
|
|
17
|
+
};
|
|
18
|
+
export type ChatExperienceRestoreResult = {
|
|
19
|
+
command: CommandResult<unknown>;
|
|
20
|
+
verified: boolean;
|
|
21
|
+
attempts: number;
|
|
22
|
+
observedExperience?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function restoreChatExperience(experience: ExperienceCommands, options?: {
|
|
25
|
+
attempts?: number;
|
|
26
|
+
delayMs?: number;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
29
|
+
}): Promise<ChatExperienceRestoreResult>;
|
|
30
|
+
export declare function restoreWorkConfiguration(configuration: WorkConfigurationCommands, desired: ConfigurationSelection, options?: {
|
|
31
|
+
attempts?: number;
|
|
32
|
+
delayMs?: number;
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
35
|
+
}): Promise<WorkConfigurationRestoreResult>;
|
|
36
|
+
export declare function restoreWorkEffort(configuration: WorkConfigurationCommands, effort: string, options?: Parameters<typeof restoreWorkConfiguration>[2]): Promise<WorkEffortRestoreResult>;
|
|
37
|
+
/** Prefer the smallest observed setting change; never invent an effort label. */
|
|
38
|
+
export declare function workMutationCandidate(inspection: ConfigurationInspectionData): {
|
|
39
|
+
axis: ConfigurationAxis;
|
|
40
|
+
original: string;
|
|
41
|
+
alternative: string;
|
|
42
|
+
} | undefined;
|
|
3
43
|
export declare const requiredScenarios: LiveSmokeScenario[];
|
|
4
44
|
export declare const optionalScenarios: LiveSmokeScenario[];
|
|
5
45
|
export declare function generatedFileAskCanProceed(result: CommandResult<AskReadData>): boolean;
|
|
6
46
|
export declare function chatActiveSelection(inspection: ConfigurationInspectionData): ConfigurationSelection;
|
|
47
|
+
export {};
|
|
@@ -1,9 +1,113 @@
|
|
|
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
|
+
import { transactionalSubmitOnceScenario } from "./transactional.js";
|
|
8
|
+
export async function restoreChatExperience(experience, options = {}) {
|
|
9
|
+
const attempts = Math.max(1, Math.min(5, options.attempts ?? 3));
|
|
10
|
+
const delayMs = Math.max(0, Math.min(5000, options.delayMs ?? 750));
|
|
11
|
+
const timeoutMs = Math.max(1000, Math.min(120000, options.timeoutMs ?? 60000));
|
|
12
|
+
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
13
|
+
await new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
14
|
+
});
|
|
15
|
+
let terminal;
|
|
16
|
+
let observedExperience;
|
|
17
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
18
|
+
const detectedBefore = await experience.detect({ timeoutMs });
|
|
19
|
+
terminal = asCommand(detectedBefore);
|
|
20
|
+
observedExperience = detectedBefore.data?.experience;
|
|
21
|
+
if (detectedBefore.ok && observedExperience === "chat") {
|
|
22
|
+
return { command: terminal, verified: true, attempts: attempt, observedExperience };
|
|
23
|
+
}
|
|
24
|
+
const opened = await experience.open({ experience: "chat", timeoutMs });
|
|
25
|
+
terminal = asCommand(opened);
|
|
26
|
+
if (opened.ok && opened.data?.experience === "chat") {
|
|
27
|
+
const detectedAfter = await experience.detect({ timeoutMs });
|
|
28
|
+
terminal = asCommand(detectedAfter);
|
|
29
|
+
observedExperience = detectedAfter.data?.experience;
|
|
30
|
+
if (detectedAfter.ok && observedExperience === "chat") {
|
|
31
|
+
return { command: terminal, verified: true, attempts: attempt, observedExperience };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (attempt < attempts) {
|
|
35
|
+
await sleep(delayMs);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
command: terminal,
|
|
40
|
+
verified: false,
|
|
41
|
+
attempts,
|
|
42
|
+
...(observedExperience === undefined ? {} : { observedExperience })
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function restoreWorkConfiguration(configuration, desired, options = {}) {
|
|
46
|
+
const attempts = Math.max(1, Math.min(5, options.attempts ?? 3));
|
|
47
|
+
const delayMs = Math.max(0, Math.min(5000, options.delayMs ?? 750));
|
|
48
|
+
const timeoutMs = Math.max(1000, Math.min(120000, options.timeoutMs ?? 60000));
|
|
49
|
+
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
51
|
+
});
|
|
52
|
+
let terminal;
|
|
53
|
+
let observedSelection;
|
|
54
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
55
|
+
const applied = await configuration.apply({
|
|
56
|
+
experience: "work",
|
|
57
|
+
desired,
|
|
58
|
+
strict: true,
|
|
59
|
+
timeoutMs
|
|
60
|
+
});
|
|
61
|
+
terminal = asCommand(applied);
|
|
62
|
+
if (applied.ok && applied.data?.verified === true) {
|
|
63
|
+
const inspected = await configuration.inspect({
|
|
64
|
+
experience: "work",
|
|
65
|
+
includeOptions: false,
|
|
66
|
+
timeoutMs
|
|
67
|
+
});
|
|
68
|
+
terminal = asCommand(inspected);
|
|
69
|
+
observedSelection = inspected.data === undefined ? undefined : activeSelection(inspected.data, Object.keys(desired));
|
|
70
|
+
if (inspected.ok
|
|
71
|
+
&& inspected.data !== undefined
|
|
72
|
+
&& configurationMatchesSelection(inspected.data, desired)) {
|
|
73
|
+
return {
|
|
74
|
+
command: terminal,
|
|
75
|
+
verified: true,
|
|
76
|
+
attempts: attempt,
|
|
77
|
+
...(observedSelection === undefined ? {} : { observedSelection })
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (attempt < attempts) {
|
|
82
|
+
await sleep(delayMs);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
command: terminal,
|
|
87
|
+
verified: false,
|
|
88
|
+
attempts,
|
|
89
|
+
...(observedSelection === undefined ? {} : { observedSelection })
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function restoreWorkEffort(configuration, effort, options = {}) {
|
|
93
|
+
const restored = await restoreWorkConfiguration(configuration, { effort }, options);
|
|
94
|
+
const observedEffort = restored.observedSelection?.effort;
|
|
95
|
+
return { command: restored.command, verified: restored.verified, attempts: restored.attempts,
|
|
96
|
+
...(observedEffort === undefined ? {} : { observedEffort }) };
|
|
97
|
+
}
|
|
98
|
+
/** Prefer the smallest observed setting change; never invent an effort label. */
|
|
99
|
+
export function workMutationCandidate(inspection) {
|
|
100
|
+
for (const axis of ["effort", "speed", "model"]) {
|
|
101
|
+
const original = inspection.active[axis];
|
|
102
|
+
if (original === undefined || !inspection.availableAxes.includes(axis))
|
|
103
|
+
continue;
|
|
104
|
+
const option = inspection.options[axis]?.find(option => option.selected !== true && option.disabled !== true
|
|
105
|
+
&& !configurationMatchesSelection(inspection, { [axis]: option.label }));
|
|
106
|
+
if (option !== undefined)
|
|
107
|
+
return { axis, original, alternative: option.label };
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
7
111
|
export const requiredScenarios = [
|
|
8
112
|
scenario("bootstrap-new-tab", true, () => true, async (context, meta) => {
|
|
9
113
|
const env = envFor(context);
|
|
@@ -167,9 +271,10 @@ export const requiredScenarios = [
|
|
|
167
271
|
requireLiveCommand("configuration.inspect.work", workConfiguration, workConfiguration.ok
|
|
168
272
|
&& workConfiguration.data?.experience === "work"
|
|
169
273
|
&& workConfiguration.data.verified === true
|
|
170
|
-
&& hasAxes(workConfiguration.data, ["model", "effort"
|
|
171
|
-
const
|
|
172
|
-
|
|
274
|
+
&& hasAxes(workConfiguration.data, ["model", "effort"]));
|
|
275
|
+
const observedWorkAxes = ["model", "effort", "speed"].filter(axis => workConfiguration.data.availableAxes.includes(axis));
|
|
276
|
+
const workDesired = activeSelection(workConfiguration.data, observedWorkAxes);
|
|
277
|
+
requireLiveCommand("configuration.inspect.work.active", workConfiguration, hasSelectionAxes(workDesired, observedWorkAxes));
|
|
173
278
|
const verifiedWorkConfiguration = await chatgpt.configuration.apply({
|
|
174
279
|
experience: "work",
|
|
175
280
|
desired: workDesired,
|
|
@@ -265,10 +370,11 @@ export const requiredScenarios = [
|
|
|
265
370
|
}
|
|
266
371
|
finally {
|
|
267
372
|
if (booted) {
|
|
268
|
-
const restored = await chatgpt.experience
|
|
269
|
-
terminal =
|
|
270
|
-
|
|
271
|
-
|
|
373
|
+
const restored = await restoreChatExperience(chatgpt.experience);
|
|
374
|
+
terminal = restored.command;
|
|
375
|
+
details.experienceRestoreAttempts = restored.attempts;
|
|
376
|
+
if (!restored.verified) {
|
|
377
|
+
failure = new LiveSmokeCommandFailure("experience.restore.chat", restored.command);
|
|
272
378
|
}
|
|
273
379
|
else {
|
|
274
380
|
details.restoredExperience = "chat";
|
|
@@ -605,12 +711,13 @@ export const requiredScenarios = [
|
|
|
605
711
|
})
|
|
606
712
|
];
|
|
607
713
|
export const optionalScenarios = [
|
|
714
|
+
transactionalSubmitOnceScenario,
|
|
608
715
|
scenario("configuration-mutate-restore", false, context => contextEnvFlag(context, "CHATGPT_E2E_CONFIGURATION_MUTATION"), async (context, meta) => {
|
|
609
716
|
const chatgpt = createChatGPT(clientOptionsFor(context));
|
|
610
717
|
const details = {};
|
|
611
718
|
let booted = false;
|
|
612
719
|
let restoreNeeded = false;
|
|
613
|
-
let
|
|
720
|
+
let originalSelection;
|
|
614
721
|
let terminal = syntheticCommand(meta.startedAt);
|
|
615
722
|
let failure;
|
|
616
723
|
try {
|
|
@@ -628,21 +735,24 @@ export const optionalScenarios = [
|
|
|
628
735
|
});
|
|
629
736
|
terminal = asCommand(inspected);
|
|
630
737
|
requireLiveCommand("configuration.inspect.work", inspected, inspected.ok && inspected.data?.verified === true);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
requireLiveCommand("configuration.inspect.work.alternative", inspected,
|
|
738
|
+
const candidate = inspected.data === undefined ? undefined : workMutationCandidate(inspected.data);
|
|
739
|
+
originalSelection = inspected.data === undefined ? undefined : activeSelection(inspected.data, ["model", "effort", "speed"]);
|
|
740
|
+
requireLiveCommand("configuration.inspect.work.alternative", inspected, candidate !== undefined && originalSelection !== undefined);
|
|
634
741
|
restoreNeeded = true;
|
|
635
742
|
const changed = await chatgpt.configuration.apply({
|
|
636
743
|
experience: "work",
|
|
637
|
-
desired: {
|
|
744
|
+
desired: { [candidate.axis]: candidate.alternative },
|
|
638
745
|
strict: true,
|
|
639
746
|
timeoutMs: 60000
|
|
640
747
|
});
|
|
641
748
|
terminal = asCommand(changed);
|
|
642
749
|
requireLiveCommand("configuration.apply.work.mutate", changed, changed.ok && changed.data?.verified === true);
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
750
|
+
const observedChange = await chatgpt.configuration.inspect({ experience: "work", includeOptions: false, timeoutMs: 60000 });
|
|
751
|
+
terminal = asCommand(observedChange);
|
|
752
|
+
requireLiveCommand("configuration.inspect.work.changed", observedChange, observedChange.ok && observedChange.data !== undefined && configurationMatchesSelection(observedChange.data, { [candidate.axis]: candidate.alternative }));
|
|
753
|
+
details.axis = candidate.axis;
|
|
754
|
+
details.changedFrom = candidate.original;
|
|
755
|
+
details.changedTo = candidate.alternative;
|
|
646
756
|
}
|
|
647
757
|
catch (error) {
|
|
648
758
|
if (error instanceof LiveSmokeCommandFailure) {
|
|
@@ -653,26 +763,26 @@ export const optionalScenarios = [
|
|
|
653
763
|
}
|
|
654
764
|
}
|
|
655
765
|
finally {
|
|
656
|
-
if (booted && restoreNeeded &&
|
|
657
|
-
const restoredConfiguration = await chatgpt.configuration
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
failure = new LiveSmokeCommandFailure("configuration.restore.work", asCommand(restoredConfiguration));
|
|
766
|
+
if (booted && restoreNeeded && originalSelection !== undefined) {
|
|
767
|
+
const restoredConfiguration = await restoreWorkConfiguration(chatgpt.configuration, originalSelection);
|
|
768
|
+
terminal = restoredConfiguration.command;
|
|
769
|
+
details.configurationRestoreAttempts = restoredConfiguration.attempts;
|
|
770
|
+
if (restoredConfiguration.observedSelection !== undefined) {
|
|
771
|
+
details.restoredConfiguration = restoredConfiguration.observedSelection;
|
|
772
|
+
}
|
|
773
|
+
if (!restoredConfiguration.verified) {
|
|
774
|
+
failure = new LiveSmokeCommandFailure("configuration.restore.work", restoredConfiguration.command);
|
|
666
775
|
}
|
|
667
776
|
else {
|
|
668
777
|
details.configurationRestored = true;
|
|
669
778
|
}
|
|
670
779
|
}
|
|
671
780
|
if (booted) {
|
|
672
|
-
const restoredExperience = await chatgpt.experience
|
|
673
|
-
terminal =
|
|
674
|
-
|
|
675
|
-
|
|
781
|
+
const restoredExperience = await restoreChatExperience(chatgpt.experience);
|
|
782
|
+
terminal = restoredExperience.command;
|
|
783
|
+
details.experienceRestoreAttempts = restoredExperience.attempts;
|
|
784
|
+
if (!restoredExperience.verified) {
|
|
785
|
+
failure = new LiveSmokeCommandFailure("experience.restore.chat", restoredExperience.command);
|
|
676
786
|
}
|
|
677
787
|
else {
|
|
678
788
|
details.restoredExperience = "chat";
|
|
@@ -890,6 +1000,13 @@ async function bootNewThread(context, meta) {
|
|
|
890
1000
|
const env = await boot(context, meta);
|
|
891
1001
|
if ("status" in env)
|
|
892
1002
|
return env;
|
|
1003
|
+
const chat = await restoreChatExperience({
|
|
1004
|
+
detect: args => detectExperience(env, args),
|
|
1005
|
+
open: args => openExperience(env, args)
|
|
1006
|
+
});
|
|
1007
|
+
if (!chat.verified) {
|
|
1008
|
+
return fail(meta, chat.command, { failedStage: "experience.open.chat" });
|
|
1009
|
+
}
|
|
893
1010
|
const created = await newThread(env);
|
|
894
1011
|
return created.ok ? env : fail(meta, created);
|
|
895
1012
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ChatGPTClient } from "../../client.js";
|
|
2
|
+
import type { LiveSmokeScenario } from "./types.js";
|
|
3
|
+
type TransactionalSmokeClient = Pick<ChatGPTClient, "ask" | "operations" | "messages">;
|
|
4
|
+
type TransactionalSmokeEvidence = {
|
|
5
|
+
passed: boolean;
|
|
6
|
+
details: Record<string, string | number | boolean>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Explicit opt-in: one new synthetic conversation, zero attachments, and at
|
|
10
|
+
* most one Send. Uses the production runtime with an optional explicit journal service.
|
|
11
|
+
* An ambiguous submission permits one guarded, observation-only recovery.
|
|
12
|
+
* A completed receipt is required before the final same-identity replay.
|
|
13
|
+
*/
|
|
14
|
+
export declare const transactionalSubmitOnceScenario: LiveSmokeScenario;
|
|
15
|
+
/** The injected client is solely a harness test seam; the live entrypoint uses the public production facade. */
|
|
16
|
+
export declare function qualifyTransactionalSubmitOnce(chatgpt: TransactionalSmokeClient): Promise<TransactionalSmokeEvidence>;
|
|
17
|
+
export {};
|