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,254 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { createChatGPT } from "../../client.js";
|
|
3
|
+
import { contextEnvFlag, contextEnvText } from "./harness.js";
|
|
4
|
+
const EXPECTED_RESPONSE = "ISSUE41_OK";
|
|
5
|
+
const PROMPT = `Reply with exactly this token and no other text:\n\n${EXPECTED_RESPONSE}`;
|
|
6
|
+
/**
|
|
7
|
+
* Explicit opt-in: one new synthetic conversation, zero attachments, and at
|
|
8
|
+
* most one Send. Uses the production runtime with an optional explicit journal service.
|
|
9
|
+
* An ambiguous submission permits one guarded, observation-only recovery.
|
|
10
|
+
* A completed receipt is required before the final same-identity replay.
|
|
11
|
+
*/
|
|
12
|
+
export const transactionalSubmitOnceScenario = {
|
|
13
|
+
name: "transactional-submit-once",
|
|
14
|
+
required: false,
|
|
15
|
+
enabled: context => contextEnvFlag(context, "CHATGPT_E2E_TRANSACTIONAL"),
|
|
16
|
+
run: async (context) => {
|
|
17
|
+
const startedMs = Date.now();
|
|
18
|
+
const startedAt = new Date(startedMs).toISOString();
|
|
19
|
+
const descriptorPath = contextEnvText(context, "CHATGPT_E2E_JOURNAL_DESCRIPTOR");
|
|
20
|
+
const chatgpt = createChatGPT({
|
|
21
|
+
agent: context.agent,
|
|
22
|
+
...(descriptorPath === undefined ? {} : { operations: { journalService: { descriptorPath } } }),
|
|
23
|
+
...(context.browser === undefined ? {} : { browser: context.browser })
|
|
24
|
+
});
|
|
25
|
+
const evidence = await qualifyTransactionalSubmitOnce(chatgpt);
|
|
26
|
+
return {
|
|
27
|
+
name: "transactional-submit-once",
|
|
28
|
+
required: false,
|
|
29
|
+
status: evidence.passed ? "pass" : "fail",
|
|
30
|
+
startedAt,
|
|
31
|
+
endedAt: new Date().toISOString(),
|
|
32
|
+
durationMs: Date.now() - startedMs,
|
|
33
|
+
details: evidence.details
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/** The injected client is solely a harness test seam; the live entrypoint uses the public production facade. */
|
|
38
|
+
export async function qualifyTransactionalSubmitOnce(chatgpt) {
|
|
39
|
+
const args = {
|
|
40
|
+
operationId: randomUUID(),
|
|
41
|
+
prompt: PROMPT,
|
|
42
|
+
thread: { type: "new" },
|
|
43
|
+
experience: "chat",
|
|
44
|
+
files: [],
|
|
45
|
+
wait: false,
|
|
46
|
+
read: { format: "text" },
|
|
47
|
+
report: false
|
|
48
|
+
};
|
|
49
|
+
const details = {
|
|
50
|
+
productionRuntime: true,
|
|
51
|
+
filesRequested: 0,
|
|
52
|
+
multilinePrompt: true,
|
|
53
|
+
duplicateRequestAttempted: false,
|
|
54
|
+
recoveryAttempted: false
|
|
55
|
+
};
|
|
56
|
+
let stage = "ask.submit";
|
|
57
|
+
const fail = () => ({ passed: false, details: { ...details, failedStage: stage } });
|
|
58
|
+
try {
|
|
59
|
+
let submitted = await chatgpt.ask(args);
|
|
60
|
+
details.submitStatus = submitted.status;
|
|
61
|
+
if (submitted.blocker !== undefined) {
|
|
62
|
+
details.blockerCode = safeCode(submitted.blocker.code);
|
|
63
|
+
if (submitted.blocker.code !== "ambiguous_submit")
|
|
64
|
+
return fail();
|
|
65
|
+
}
|
|
66
|
+
const handle = handleFromCommand(submitted);
|
|
67
|
+
if (handle === undefined || handle.operationId !== args.operationId
|
|
68
|
+
|| (submitted.status !== "ok" && submitted.status !== "partial"))
|
|
69
|
+
return fail();
|
|
70
|
+
stage = "operations.inspect.submitted";
|
|
71
|
+
let initial = await chatgpt.operations.inspect(handle);
|
|
72
|
+
let initialSend = sendActions(initial.state);
|
|
73
|
+
details.initialPhase = initial.state.phase;
|
|
74
|
+
details.sendActionCount = initialSend.length;
|
|
75
|
+
if (submitted.blocker?.code === "ambiguous_submit") {
|
|
76
|
+
const intent = initialSend[0];
|
|
77
|
+
const target = initial.state.target;
|
|
78
|
+
details.recoveryEligible = sameOperationHandle(handle, initial.handle)
|
|
79
|
+
&& matchesHandleState(initial.handle, initial.state)
|
|
80
|
+
&& (initial.state.phase === "uncertain" || initial.state.phase === "submitted")
|
|
81
|
+
&& initial.state.mutationBoundary === "send_may_have_occurred"
|
|
82
|
+
&& initialSend.length === 1 && intent !== undefined
|
|
83
|
+
&& intent.repeatPolicy === "observe_only_after_intent"
|
|
84
|
+
&& intent.requestDigest === handle.requestDigest
|
|
85
|
+
&& intent.targetDigest === handle.targetBindingDigest
|
|
86
|
+
&& Number.isSafeInteger(intent.intentRevision) && intent.intentRevision > 0
|
|
87
|
+
&& typeof intent.actionId === "string" && intent.actionId.length > 0
|
|
88
|
+
&& target !== undefined
|
|
89
|
+
&& [target.providerId, target.browserId, target.tabId].every(value => typeof value === "string" && value.length > 0);
|
|
90
|
+
if (details.recoveryEligible !== true || intent === undefined || target === undefined)
|
|
91
|
+
return fail();
|
|
92
|
+
// Capture immutable identity before calling recovery. New conversations
|
|
93
|
+
// may establish a conversation ID, but cannot change their tab/anchor.
|
|
94
|
+
const before = {
|
|
95
|
+
handle: { ...initial.handle }, intent: { ...intent },
|
|
96
|
+
providerId: target.providerId, browserId: target.browserId, tabId: target.tabId,
|
|
97
|
+
conversationId: target.conversationId
|
|
98
|
+
};
|
|
99
|
+
stage = "ask.recover_submission";
|
|
100
|
+
details.recoveryAttempted = true;
|
|
101
|
+
const recovered = await chatgpt.ask(args);
|
|
102
|
+
details.recoveryStatus = recovered.status;
|
|
103
|
+
if (recovered.blocker !== undefined) {
|
|
104
|
+
details.recoveryBlockerCode = safeCode(recovered.blocker.code);
|
|
105
|
+
return fail();
|
|
106
|
+
}
|
|
107
|
+
const recoveredHandle = handleFromCommand(recovered);
|
|
108
|
+
if (recoveredHandle === undefined || !sameOperationHandle(before.handle, recoveredHandle)
|
|
109
|
+
|| (recovered.status !== "ok" && recovered.status !== "partial"))
|
|
110
|
+
return fail();
|
|
111
|
+
stage = "operations.inspect.recovered";
|
|
112
|
+
const recoveredState = await chatgpt.operations.inspect(recoveredHandle);
|
|
113
|
+
const recoveredSend = sendActions(recoveredState.state);
|
|
114
|
+
const afterIntent = recoveredSend[0];
|
|
115
|
+
const afterTarget = recoveredState.state.target;
|
|
116
|
+
details.recoverySameTarget = sameOperationHandle(before.handle, recoveredState.handle)
|
|
117
|
+
&& matchesHandleState(recoveredState.handle, recoveredState.state)
|
|
118
|
+
&& afterTarget?.providerId === before.providerId
|
|
119
|
+
&& afterTarget?.browserId === before.browserId
|
|
120
|
+
&& afterTarget?.tabId === before.tabId
|
|
121
|
+
&& (before.conversationId === undefined || afterTarget?.conversationId === before.conversationId);
|
|
122
|
+
details.recoverySameSendAction = recoveredSend.length === 1
|
|
123
|
+
&& afterIntent?.actionId === before.intent.actionId
|
|
124
|
+
&& afterIntent?.intentRevision === before.intent.intentRevision
|
|
125
|
+
&& afterIntent?.requestDigest === before.intent.requestDigest
|
|
126
|
+
&& afterIntent?.targetDigest === before.intent.targetDigest
|
|
127
|
+
&& afterIntent?.repeatPolicy === before.intent.repeatPolicy;
|
|
128
|
+
if (details.recoverySameTarget !== true || details.recoverySameSendAction !== true)
|
|
129
|
+
return fail();
|
|
130
|
+
details.recoveredPhase = recoveredState.state.phase;
|
|
131
|
+
submitted = recovered;
|
|
132
|
+
initial = recoveredState;
|
|
133
|
+
initialSend = recoveredSend;
|
|
134
|
+
}
|
|
135
|
+
if (initialSend.length !== 1 || initialSend[0]?.outcome !== "satisfied")
|
|
136
|
+
return fail();
|
|
137
|
+
stage = "operations.collect";
|
|
138
|
+
const collected = await chatgpt.operations.collect(initial.handle, {
|
|
139
|
+
wait: true,
|
|
140
|
+
timeoutMs: 60_000,
|
|
141
|
+
maxAttempts: 120,
|
|
142
|
+
pollIntervalMs: 500,
|
|
143
|
+
responseContent: "include",
|
|
144
|
+
responseFormat: "text"
|
|
145
|
+
});
|
|
146
|
+
details.collectKind = collected.kind;
|
|
147
|
+
if (collected.kind !== "completed") {
|
|
148
|
+
if (collected.kind === "blocked")
|
|
149
|
+
details.blockerCode = safeCode(collected.blocker.code);
|
|
150
|
+
return fail();
|
|
151
|
+
}
|
|
152
|
+
// A fast response may already be captured by ask's first observation.
|
|
153
|
+
// Completed receipt replay deliberately does not persist or return raw text.
|
|
154
|
+
const initialData = submitted.data;
|
|
155
|
+
const text = collected.response.rawText ?? (initial.state.phase === "completed"
|
|
156
|
+
&& typeof initialData?.responseText === "string" ? initialData.responseText : undefined);
|
|
157
|
+
details.responseMatches = text?.trim() === EXPECTED_RESPONSE;
|
|
158
|
+
details.responseBytes = text === undefined ? 0 : new TextEncoder().encode(text).length;
|
|
159
|
+
if (text !== undefined)
|
|
160
|
+
details.responseHash = createHash("sha256").update(text).digest("hex");
|
|
161
|
+
if (details.responseMatches !== true)
|
|
162
|
+
return fail();
|
|
163
|
+
stage = "operations.inspect.completed";
|
|
164
|
+
const completed = await chatgpt.operations.inspect(initial.handle);
|
|
165
|
+
const receipt = completed.state.receipt;
|
|
166
|
+
if (completed.state.phase !== "completed" || receipt === undefined
|
|
167
|
+
|| receipt.userTurnId !== collected.turn.userTurnId
|
|
168
|
+
|| receipt.assistantTurnId !== collected.turn.assistantTurnId)
|
|
169
|
+
return fail();
|
|
170
|
+
details.ownedReceiptVerified = true;
|
|
171
|
+
stage = "messages.status.completed";
|
|
172
|
+
const before = await chatgpt.messages.status({ maxPreviewChars: 0 });
|
|
173
|
+
details.initialConversationVerified = matchesEstablishedConversation(before, completed.state);
|
|
174
|
+
if (!hasExactlyOneExchange(before) || details.initialConversationVerified !== true)
|
|
175
|
+
return fail();
|
|
176
|
+
// This is unreachable after unresolved uncertainty, a pending collect,
|
|
177
|
+
// a host blocker, or a missing receipt. Never use a new ID as a retry.
|
|
178
|
+
stage = "ask.same_identity";
|
|
179
|
+
details.duplicateRequestAttempted = true;
|
|
180
|
+
const replayed = await chatgpt.ask(args);
|
|
181
|
+
details.replayStatus = replayed.status;
|
|
182
|
+
if (!replayed.ok)
|
|
183
|
+
return fail();
|
|
184
|
+
const replayHandle = handleFromCommand(replayed);
|
|
185
|
+
details.sameOperationHandle = replayHandle !== undefined
|
|
186
|
+
&& replayHandle.operationId === args.operationId
|
|
187
|
+
&& sameOperationHandle(replayHandle, completed.handle);
|
|
188
|
+
if (details.sameOperationHandle !== true)
|
|
189
|
+
return fail();
|
|
190
|
+
stage = "operations.inspect.replayed";
|
|
191
|
+
const replayState = await chatgpt.operations.inspect(completed.handle);
|
|
192
|
+
const replaySend = sendActions(replayState.state);
|
|
193
|
+
const replayReceipt = replayState.state.receipt;
|
|
194
|
+
details.sameSendAction = replaySend.length === 1
|
|
195
|
+
&& replaySend[0]?.actionId === initialSend[0]?.actionId
|
|
196
|
+
&& replaySend[0]?.intentRevision === initialSend[0]?.intentRevision
|
|
197
|
+
&& replaySend[0]?.receiptRevision === initialSend[0]?.receiptRevision;
|
|
198
|
+
details.sameOwnedTurns = replayReceipt?.userTurnId === receipt.userTurnId
|
|
199
|
+
&& replayReceipt?.assistantTurnId === receipt.assistantTurnId;
|
|
200
|
+
if (details.sameSendAction !== true || details.sameOwnedTurns !== true)
|
|
201
|
+
return fail();
|
|
202
|
+
stage = "messages.status.replayed";
|
|
203
|
+
const after = await chatgpt.messages.status({ maxPreviewChars: 0 });
|
|
204
|
+
details.exactlyOneExchange = hasExactlyOneExchange(after);
|
|
205
|
+
details.sameConversation = matchesEstablishedConversation(after, completed.state);
|
|
206
|
+
if (details.exactlyOneExchange !== true || details.sameConversation !== true)
|
|
207
|
+
return fail();
|
|
208
|
+
details.finalPhase = replayState.state.phase;
|
|
209
|
+
return { passed: true, details };
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Host/provider failures can contain private paths or identifiers. Only
|
|
213
|
+
// bounded structural evidence crosses the live report boundary.
|
|
214
|
+
details.exception = true;
|
|
215
|
+
return fail();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function sendActions(state) {
|
|
219
|
+
return Object.values(state.actions).filter(action => action.kind === "send");
|
|
220
|
+
}
|
|
221
|
+
function sameOperationHandle(first, second) {
|
|
222
|
+
return typeof first.operationId === "string" && first.operationId.length > 0
|
|
223
|
+
&& first.operationId === second.operationId
|
|
224
|
+
&& typeof first.requestDigest === "string" && first.requestDigest.length > 0
|
|
225
|
+
&& first.requestDigest === second.requestDigest
|
|
226
|
+
&& first.surface === "chat" && first.surface === second.surface
|
|
227
|
+
&& typeof first.targetBindingDigest === "string" && first.targetBindingDigest.length > 0
|
|
228
|
+
&& first.targetBindingDigest === second.targetBindingDigest;
|
|
229
|
+
}
|
|
230
|
+
function matchesHandleState(handle, state) {
|
|
231
|
+
return state.operationId === handle.operationId && state.requestDigest === handle.requestDigest
|
|
232
|
+
&& state.surface === handle.surface;
|
|
233
|
+
}
|
|
234
|
+
function handleFromCommand(command) {
|
|
235
|
+
if (typeof command.data !== "object" || command.data === null || !("handle" in command.data))
|
|
236
|
+
return undefined;
|
|
237
|
+
const value = command.data.handle;
|
|
238
|
+
return typeof value === "object" && value !== null && "operationId" in value
|
|
239
|
+
? value
|
|
240
|
+
: undefined;
|
|
241
|
+
}
|
|
242
|
+
function hasExactlyOneExchange(command) {
|
|
243
|
+
const data = command.data;
|
|
244
|
+
return command.ok && data?.turnCount === 2 && data.assistantTurnCount === 1;
|
|
245
|
+
}
|
|
246
|
+
function matchesEstablishedConversation(command, state) {
|
|
247
|
+
const target = state.target;
|
|
248
|
+
return typeof target?.conversationId === "string" && target.conversationId.length > 0
|
|
249
|
+
&& command.context.conversationId === target.conversationId
|
|
250
|
+
&& (command.context.tabId === undefined || command.context.tabId === target.tabId);
|
|
251
|
+
}
|
|
252
|
+
function safeCode(value) {
|
|
253
|
+
return value !== undefined && /^[a-z0-9_]{1,100}$/u.test(value) ? value : "unavailable";
|
|
254
|
+
}
|
|
@@ -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>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-chatgpt-control",
|
|
3
|
-
"version": "0.5.1-alpha.
|
|
3
|
+
"version": "0.5.1-alpha.4",
|
|
4
4
|
"description": "Unofficial SDK for controlling visible ChatGPT Chat and Work sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"smoke:search": "tsx src/scripts/smoke-search-open-read.ts",
|
|
45
45
|
"smoke:attach": "tsx src/scripts/smoke-attach-file.ts",
|
|
46
46
|
"smoke:live": "tsx src/scripts/live-smoke.ts",
|
|
47
|
-
"smoke:release:build": "npm run bundle:release-canary"
|
|
47
|
+
"smoke:release:build": "npm run bundle:release-canary",
|
|
48
|
+
"bundle:journal": "esbuild src/scripts/journal-server.ts --bundle --platform=node --format=esm --outfile=dist/codex-chatgpt-control-journal.mjs"
|
|
48
49
|
},
|
|
49
50
|
"exports": {
|
|
50
51
|
".": {
|
|
@@ -59,13 +60,17 @@
|
|
|
59
60
|
},
|
|
60
61
|
"types": "./dist/src/index.d.ts",
|
|
61
62
|
"bin": {
|
|
62
|
-
"codex-chatgpt-control-backend": "dist/src/scripts/backend-server.js"
|
|
63
|
+
"codex-chatgpt-control-backend": "dist/src/scripts/backend-server.js",
|
|
64
|
+
"codex-chatgpt-control-journal": "dist/src/scripts/journal-server.js"
|
|
63
65
|
},
|
|
64
66
|
"files": [
|
|
65
67
|
"dist/src/**/*.js",
|
|
66
68
|
"dist/src/**/*.d.ts",
|
|
67
69
|
"dist/codex-chatgpt-control.bundle.mjs",
|
|
68
70
|
"dist/codex-chatgpt-control-backend.mjs",
|
|
71
|
+
"dist/codex-chatgpt-control-journal.mjs",
|
|
72
|
+
"dist/codex-chatgpt-control-live-smoke.bundle.mjs",
|
|
73
|
+
"dist/codex-chatgpt-control-release-canary.bundle.mjs",
|
|
69
74
|
"contracts/v1/**/*.json",
|
|
70
75
|
"contracts/v1/**/*.ndjson",
|
|
71
76
|
"references/**/*.md",
|