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.
Files changed (191) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  7. package/contracts/v1/fixtures/operation-action.json +13 -0
  8. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  9. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  10. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  11. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  12. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  13. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  14. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  15. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  16. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  17. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  18. package/contracts/v1/fixtures/operation-event.json +18 -0
  19. package/contracts/v1/fixtures/operation-handle.json +10 -0
  20. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  21. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  22. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  23. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  24. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  25. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  26. package/contracts/v1/fixtures/operation-request.json +33 -0
  27. package/contracts/v1/fixtures/operation-state.json +144 -0
  28. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  29. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  30. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  31. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  32. package/contracts/v1/manifest.json +155 -1
  33. package/contracts/v1/parity-suite.json +96 -1
  34. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  35. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  36. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  37. package/contracts/v1/schemas/manifest.schema.json +57 -0
  38. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  39. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  40. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  41. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  42. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  43. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  44. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  45. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  46. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  47. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  48. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  49. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  50. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  51. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  52. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  53. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  54. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  55. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  56. package/contracts/v1/surface-drift-policy.json +50 -0
  57. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  58. package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
  59. package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
  60. package/dist/src/backend/client.d.ts +103 -2
  61. package/dist/src/backend/client.js +1436 -97
  62. package/dist/src/backend/compatibility.d.ts +12 -0
  63. package/dist/src/backend/compatibility.js +208 -0
  64. package/dist/src/backend/protocol.d.ts +84 -1
  65. package/dist/src/backend/protocol.js +31 -5
  66. package/dist/src/backend/runtime-identity.d.ts +10 -0
  67. package/dist/src/backend/runtime-identity.js +141 -0
  68. package/dist/src/backend/session.d.ts +10 -2
  69. package/dist/src/backend/session.js +478 -7
  70. package/dist/src/backend/stdio-server.d.ts +4 -1
  71. package/dist/src/backend/stdio-server.js +240 -28
  72. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  73. package/dist/src/browser/active-composer-file-input.js +33 -0
  74. package/dist/src/browser/attach.d.ts +4 -1
  75. package/dist/src/browser/attach.js +136 -28
  76. package/dist/src/browser/downloads.d.ts +2 -0
  77. package/dist/src/client.d.ts +38 -0
  78. package/dist/src/client.js +1222 -59
  79. package/dist/src/commands/configuration.js +13 -7
  80. package/dist/src/commands/doctor.d.ts +1 -1
  81. package/dist/src/commands/doctor.js +26 -3
  82. package/dist/src/commands/experience.js +35 -0
  83. package/dist/src/commands/files.js +4 -27
  84. package/dist/src/commands/modes.js +49 -17
  85. package/dist/src/commands/power-discovery.d.ts +130 -0
  86. package/dist/src/commands/power-discovery.js +627 -0
  87. package/dist/src/commands/project-sources.js +325 -31
  88. package/dist/src/commands/sequence.js +59 -32
  89. package/dist/src/commands/session.js +7 -2
  90. package/dist/src/commands/work.js +31 -0
  91. package/dist/src/errors.d.ts +9 -0
  92. package/dist/src/errors.js +22 -0
  93. package/dist/src/index.d.ts +6 -0
  94. package/dist/src/index.js +9 -0
  95. package/dist/src/operations/artifact-output.d.ts +69 -0
  96. package/dist/src/operations/artifact-output.js +1748 -0
  97. package/dist/src/operations/artifact-stream.d.ts +19 -0
  98. package/dist/src/operations/artifact-stream.js +35 -0
  99. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  100. package/dist/src/operations/artifact-transfer.js +1312 -0
  101. package/dist/src/operations/browser-adapter.d.ts +161 -0
  102. package/dist/src/operations/browser-adapter.js +2023 -0
  103. package/dist/src/operations/browser-observation.d.ts +110 -0
  104. package/dist/src/operations/browser-observation.js +1208 -0
  105. package/dist/src/operations/browser-target.d.ts +103 -0
  106. package/dist/src/operations/browser-target.js +489 -0
  107. package/dist/src/operations/canonical.d.ts +4 -0
  108. package/dist/src/operations/canonical.js +412 -0
  109. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  110. package/dist/src/operations/chatgpt-runtime.js +1150 -0
  111. package/dist/src/operations/client.d.ts +138 -0
  112. package/dist/src/operations/client.js +1143 -0
  113. package/dist/src/operations/collector.d.ts +195 -0
  114. package/dist/src/operations/collector.js +1039 -0
  115. package/dist/src/operations/control.d.ts +372 -0
  116. package/dist/src/operations/control.js +1498 -0
  117. package/dist/src/operations/file-identity.d.ts +33 -0
  118. package/dist/src/operations/file-identity.js +153 -0
  119. package/dist/src/operations/handle.d.ts +19 -0
  120. package/dist/src/operations/handle.js +727 -0
  121. package/dist/src/operations/index.d.ts +22 -0
  122. package/dist/src/operations/index.js +22 -0
  123. package/dist/src/operations/journal.d.ts +142 -0
  124. package/dist/src/operations/journal.js +1931 -0
  125. package/dist/src/operations/production-attachments.d.ts +116 -0
  126. package/dist/src/operations/production-attachments.js +1018 -0
  127. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  128. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  129. package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
  130. package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
  131. package/dist/src/operations/production-configuration.d.ts +42 -0
  132. package/dist/src/operations/production-configuration.js +1363 -0
  133. package/dist/src/operations/production-primitives.d.ts +53 -0
  134. package/dist/src/operations/production-primitives.js +1073 -0
  135. package/dist/src/operations/production-work-steer.d.ts +203 -0
  136. package/dist/src/operations/production-work-steer.js +1218 -0
  137. package/dist/src/operations/recovery.d.ts +80 -0
  138. package/dist/src/operations/recovery.js +174 -0
  139. package/dist/src/operations/runtime-adapter.d.ts +118 -0
  140. package/dist/src/operations/runtime-adapter.js +786 -0
  141. package/dist/src/operations/send-once.d.ts +224 -0
  142. package/dist/src/operations/send-once.js +1081 -0
  143. package/dist/src/operations/service.d.ts +281 -0
  144. package/dist/src/operations/service.js +2810 -0
  145. package/dist/src/operations/staging.d.ts +136 -0
  146. package/dist/src/operations/staging.js +630 -0
  147. package/dist/src/operations/state-machine.d.ts +29 -0
  148. package/dist/src/operations/state-machine.js +2047 -0
  149. package/dist/src/operations/submission.d.ts +423 -0
  150. package/dist/src/operations/submission.js +1676 -0
  151. package/dist/src/operations/turn-ownership.d.ts +202 -0
  152. package/dist/src/operations/turn-ownership.js +700 -0
  153. package/dist/src/operations/types.d.ts +454 -0
  154. package/dist/src/operations/types.js +20 -0
  155. package/dist/src/operations/wire-requests.d.ts +21 -0
  156. package/dist/src/operations/wire-requests.js +396 -0
  157. package/dist/src/operations/wire-results.d.ts +97 -0
  158. package/dist/src/operations/wire-results.js +818 -0
  159. package/dist/src/runner/responses.d.ts +3 -1
  160. package/dist/src/runner/responses.js +19 -1
  161. package/dist/src/runner/result.js +227 -55
  162. package/dist/src/runner/types.d.ts +12 -0
  163. package/dist/src/runtime/command-routing.d.ts +149 -0
  164. package/dist/src/runtime/command-routing.js +431 -0
  165. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  166. package/dist/src/runtime/coordinated-browser.js +315 -0
  167. package/dist/src/runtime/coordinated-page.d.ts +39 -0
  168. package/dist/src/runtime/coordinated-page.js +634 -0
  169. package/dist/src/runtime/operation-context.d.ts +142 -0
  170. package/dist/src/runtime/operation-context.js +410 -0
  171. package/dist/src/runtime/runtime-session.d.ts +95 -0
  172. package/dist/src/runtime/runtime-session.js +314 -0
  173. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  174. package/dist/src/runtime/tab-coordinator.js +1200 -0
  175. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  176. package/dist/src/runtime/value-boundaries.js +49 -0
  177. package/dist/src/safety/untrusted-output.js +2 -1
  178. package/dist/src/scripts/backend-server.js +4 -1
  179. package/dist/src/scripts/live-smoke/harness.js +79 -7
  180. package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
  181. package/dist/src/scripts/live-smoke/scenarios.js +110 -18
  182. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  183. package/dist/src/scripts/release-canary-module.js +17 -4
  184. package/dist/src/types.d.ts +38 -1
  185. package/package.json +1 -1
  186. package/references/2026-08-16-transactional-operations.md +459 -0
  187. package/references/agents-runner.md +34 -0
  188. package/references/backend-protocol.md +73 -0
  189. package/references/python-parity.md +72 -0
  190. package/references/responses-adapter.md +19 -0
  191. package/references/streaming.md +6 -0
@@ -0,0 +1,12 @@
1
+ import { type BackendCompatibilityReport, type BackendRuntimeIdentity } from "./protocol.js";
2
+ export type BackendCompatibilityExpectedIdentity = Partial<Pick<BackendRuntimeIdentity, "backendSessionId" | "packageName" | "packageVersion" | "runtime" | "runtimeVersion" | "buildDigest">>;
3
+ /**
4
+ * Compare only explicitly supplied, bounded provenance. Package/runtime
5
+ * differences are diagnostic warnings; protocol/capability rejection is
6
+ * handled by the handshake before browser commands are admitted.
7
+ */
8
+ export declare function compatibilityReportFromHello(value: Record<string, unknown>, expected: BackendCompatibilityExpectedIdentity | undefined, mode: "multiplexed" | "single-flight" | "legacy"): BackendCompatibilityReport;
9
+ export declare function compatibilityReportFromLegacy(version: Record<string, unknown>, expected: BackendCompatibilityExpectedIdentity | undefined): BackendCompatibilityReport;
10
+ export declare function blockedCompatibilityReport(message?: string): BackendCompatibilityReport;
11
+ /** Validate transport-owned diagnostics before exposing them through a facade. */
12
+ export declare function validateBackendCompatibilityReport(value: unknown): BackendCompatibilityReport;
@@ -0,0 +1,208 @@
1
+ import { BACKEND_COMPATIBILITY_SCHEMA_VERSION } from "./protocol.js";
2
+ const MAX_WARNINGS = 16;
3
+ const MAX_TEXT_LENGTH = 512;
4
+ const UNKNOWN_PROVENANCE = "unknown";
5
+ /**
6
+ * Compare only explicitly supplied, bounded provenance. Package/runtime
7
+ * differences are diagnostic warnings; protocol/capability rejection is
8
+ * handled by the handshake before browser commands are admitted.
9
+ */
10
+ export function compatibilityReportFromHello(value, expected, mode) {
11
+ const warnings = [];
12
+ const fields = [
13
+ ["backendSessionId", "backend session identity"],
14
+ ["packageName", "package name"],
15
+ ["packageVersion", "package version"],
16
+ ["runtime", "runtime"],
17
+ ["runtimeVersion", "runtime version"],
18
+ ["buildDigest", "build digest"]
19
+ ];
20
+ for (const [field, label] of fields) {
21
+ const received = boundedString(value[field]);
22
+ const expectedValue = boundedString(expected?.[field]);
23
+ if (received === undefined || received === UNKNOWN_PROVENANCE) {
24
+ warnings.push({
25
+ code: "provenance_unknown",
26
+ ...(field === "backendSessionId" ? {} : { field }),
27
+ ...(received === undefined ? {} : { received }),
28
+ message: `Backend ${label} provenance is unknown.`
29
+ });
30
+ continue;
31
+ }
32
+ if (expectedValue !== undefined && expectedValue !== UNKNOWN_PROVENANCE && expectedValue !== received) {
33
+ const code = mismatchCode(field);
34
+ warnings.push({
35
+ code,
36
+ ...(field === "backendSessionId" ? {} : { field }),
37
+ expected: expectedValue,
38
+ received,
39
+ message: `Backend ${label} differs from the expected runtime (${expectedValue} versus ${received}).`
40
+ });
41
+ }
42
+ }
43
+ const boundedWarnings = warnings.slice(0, MAX_WARNINGS);
44
+ const hasUnknown = boundedWarnings.some(warning => warning.code === "provenance_unknown");
45
+ const protocolVersion = boundedString(value.protocolVersion);
46
+ return snapshot({
47
+ schemaVersion: BACKEND_COMPATIBILITY_SCHEMA_VERSION,
48
+ status: boundedWarnings.some(warning => warning.code !== "provenance_unknown")
49
+ ? "warning"
50
+ : hasUnknown
51
+ ? "unknown"
52
+ : "compatible",
53
+ mode,
54
+ ...(protocolVersion === undefined ? {} : { protocolVersion }),
55
+ ...optionalIdentity(value, "backendSessionId"),
56
+ ...optionalIdentity(value, "packageName"),
57
+ ...optionalIdentity(value, "packageVersion"),
58
+ ...optionalIdentity(value, "runtime"),
59
+ ...optionalIdentity(value, "runtimeVersion"),
60
+ ...optionalIdentity(value, "buildDigest"),
61
+ warnings: boundedWarnings
62
+ });
63
+ }
64
+ export function compatibilityReportFromLegacy(version, expected) {
65
+ const base = compatibilityReportFromHello({
66
+ protocolVersion: version.protocolVersion,
67
+ backendSessionId: version.backendSessionId,
68
+ packageName: version.packageName,
69
+ packageVersion: version.packageVersion,
70
+ runtime: version.runtime,
71
+ runtimeVersion: version.runtimeVersion,
72
+ buildDigest: version.buildDigest
73
+ }, expected, "legacy");
74
+ const warnings = [
75
+ {
76
+ code: "legacy_backend",
77
+ message: "Backend negotiated legacy single-flight compatibility; multiplexing was not advertised."
78
+ },
79
+ ...base.warnings
80
+ ].slice(0, MAX_WARNINGS);
81
+ return snapshot({
82
+ ...base,
83
+ status: "warning",
84
+ warnings
85
+ });
86
+ }
87
+ export function blockedCompatibilityReport(message = "Backend compatibility negotiation was rejected.") {
88
+ return snapshot({
89
+ schemaVersion: BACKEND_COMPATIBILITY_SCHEMA_VERSION,
90
+ status: "blocked",
91
+ mode: "unknown",
92
+ warnings: [{ code: "negotiation_rejected", message: boundedMessage(message) }]
93
+ });
94
+ }
95
+ /** Validate transport-owned diagnostics before exposing them through a facade. */
96
+ export function validateBackendCompatibilityReport(value) {
97
+ if (!isRecord(value)
98
+ || value.schemaVersion !== BACKEND_COMPATIBILITY_SCHEMA_VERSION
99
+ || !isStatus(value.status)
100
+ || !isMode(value.mode)
101
+ || !Array.isArray(value.warnings)
102
+ || value.warnings.length > MAX_WARNINGS) {
103
+ throw new TypeError("Backend compatibility report is malformed.");
104
+ }
105
+ for (const field of ["protocolVersion", "backendSessionId", "packageName", "packageVersion", "runtime", "runtimeVersion", "buildDigest"]) {
106
+ if (value[field] !== undefined && boundedString(value[field]) === undefined) {
107
+ throw new TypeError("Backend compatibility report contains an invalid identity field.");
108
+ }
109
+ }
110
+ const warnings = [];
111
+ for (const warning of value.warnings) {
112
+ const message = isRecord(warning) ? boundedString(warning.message) : undefined;
113
+ if (!isRecord(warning)
114
+ || !isWarningCode(warning.code)
115
+ || (warning.field !== undefined && !isField(warning.field))
116
+ || (warning.expected !== undefined && boundedString(warning.expected) === undefined)
117
+ || (warning.received !== undefined && boundedString(warning.received) === undefined)
118
+ || message === undefined) {
119
+ throw new TypeError("Backend compatibility report contains an invalid warning.");
120
+ }
121
+ const expected = boundedString(warning.expected);
122
+ const received = boundedString(warning.received);
123
+ warnings.push({
124
+ code: warning.code,
125
+ ...(warning.field === undefined ? {} : { field: warning.field }),
126
+ ...(expected === undefined ? {} : { expected }),
127
+ ...(received === undefined ? {} : { received }),
128
+ message
129
+ });
130
+ }
131
+ const protocolVersion = boundedString(value.protocolVersion);
132
+ return snapshot({
133
+ schemaVersion: BACKEND_COMPATIBILITY_SCHEMA_VERSION,
134
+ status: value.status,
135
+ mode: value.mode,
136
+ ...(protocolVersion === undefined ? {} : { protocolVersion }),
137
+ ...copyOptionalIdentity(value, "backendSessionId"),
138
+ ...copyOptionalIdentity(value, "packageName"),
139
+ ...copyOptionalIdentity(value, "packageVersion"),
140
+ ...copyOptionalIdentity(value, "runtime"),
141
+ ...copyOptionalIdentity(value, "runtimeVersion"),
142
+ ...copyOptionalIdentity(value, "buildDigest"),
143
+ warnings
144
+ });
145
+ }
146
+ function snapshot(report) {
147
+ const warnings = Object.freeze(report.warnings.map(warning => Object.freeze({ ...warning })));
148
+ return Object.freeze({ ...report, warnings });
149
+ }
150
+ function mismatchCode(field) {
151
+ switch (field) {
152
+ case "packageName": return "package_name_mismatch";
153
+ case "packageVersion": return "package_version_mismatch";
154
+ case "runtime": return "runtime_mismatch";
155
+ case "runtimeVersion": return "runtime_version_mismatch";
156
+ case "buildDigest": return "build_digest_mismatch";
157
+ default: return "provenance_unknown";
158
+ }
159
+ }
160
+ function optionalIdentity(value, field) {
161
+ const result = boundedString(value[field]);
162
+ return result === undefined ? {} : { [field]: result };
163
+ }
164
+ function copyOptionalIdentity(value, field) {
165
+ const result = boundedString(value[field]);
166
+ return result === undefined ? {} : { [field]: result };
167
+ }
168
+ function boundedString(value) {
169
+ return typeof value === "string"
170
+ && value.length > 0
171
+ && value.length <= MAX_TEXT_LENGTH
172
+ && value.trim() === value
173
+ && !/[\u0000-\u001f\u007f]/u.test(value)
174
+ ? value
175
+ : undefined;
176
+ }
177
+ function boundedMessage(value) {
178
+ return value.length <= MAX_TEXT_LENGTH ? value : "Backend compatibility negotiation was rejected.";
179
+ }
180
+ function isStatus(value) {
181
+ return value === "compatible" || value === "warning" || value === "unknown" || value === "blocked";
182
+ }
183
+ function isMode(value) {
184
+ return value === "multiplexed" || value === "single-flight" || value === "legacy" || value === "unknown";
185
+ }
186
+ function isField(value) {
187
+ return value === "packageName"
188
+ || value === "packageVersion"
189
+ || value === "runtime"
190
+ || value === "runtimeVersion"
191
+ || value === "buildDigest";
192
+ }
193
+ function isWarningCode(value) {
194
+ return value === "package_name_mismatch"
195
+ || value === "package_version_mismatch"
196
+ || value === "runtime_mismatch"
197
+ || value === "runtime_version_mismatch"
198
+ || value === "build_digest_mismatch"
199
+ || value === "provenance_unknown"
200
+ || value === "legacy_backend"
201
+ || value === "negotiation_rejected";
202
+ }
203
+ function isRecord(value) {
204
+ if (value === null || typeof value !== "object" || Array.isArray(value))
205
+ return false;
206
+ const prototype = Object.getPrototypeOf(value);
207
+ return prototype === Object.prototype || prototype === null;
208
+ }
@@ -1,7 +1,39 @@
1
1
  export declare const BACKEND_REQUEST_SCHEMA_VERSION: "chatgpt.browser_control.backend_request.v1";
2
2
  export declare const BACKEND_RESPONSE_SCHEMA_VERSION: "chatgpt.browser_control.backend_response.v1";
3
3
  export declare const BACKEND_EVENT_SCHEMA_VERSION: "chatgpt.browser_control.backend_event.v1";
4
- export declare const backendCommands: readonly ["backend.version", "backend.health", "backend.capabilities", "runner.run", "runner.plan", "runner.stream", "responses.create", "ask", "askInThread", "askWithFiles", "askAndDownload", "runMessages", "openThread", "readLatest", "copyLatest", "downloadLatest", "runPlan", "doctor", "createReport", "reports.create", "reports.redact", "reports.summarize", "commands", "describe", "help", "session.bootstrap", "experience.detect", "experience.open", "configuration.inspect", "configuration.apply", "work.start", "work.status", "work.wait", "work.steer", "work.readLatest", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "messages.status", "messages.stop", "messages.waitAndRead", "artifacts.listLatest", "artifacts.wait", "artifacts.downloadLatest", "files.preflight", "files.attach", "files.downloadLatest", "projects.sources.list", "projects.sources.planAdd", "projects.sources.add", "modes.set", "modes.get", "tools.select", "response.copy"];
4
+ export declare const BACKEND_HELLO_COMMAND: "backend.hello";
5
+ export declare const BACKEND_UNKNOWN_ID_QUARANTINE_LIMIT = 256;
6
+ export declare const BACKEND_UNKNOWN_ID_QUARANTINE_TTL_MS = 60000;
7
+ /** Maximum encoded NDJSON frame size, excluding the trailing newline. */
8
+ export declare const BACKEND_NDJSON_FRAME_LIMIT_BYTES: number;
9
+ /** Maximum requestId length accepted on any backend protocol message. */
10
+ export declare const BACKEND_REQUEST_ID_MAX_LENGTH = 4096;
11
+ /** Reserved for transport-owned hello/legacy control probes. */
12
+ export declare const BACKEND_CONTROL_REQUEST_ID_PREFIX = "__backend_control__";
13
+ export declare const BACKEND_COMPATIBILITY_SCHEMA_VERSION: "chatgpt.browser_control.backend_compatibility.v1";
14
+ /** A bounded, redacted comparison retained by a lifecycle-owned backend transport. */
15
+ export type BackendCompatibilityWarningCode = "package_name_mismatch" | "package_version_mismatch" | "runtime_mismatch" | "runtime_version_mismatch" | "build_digest_mismatch" | "provenance_unknown" | "legacy_backend" | "negotiation_rejected";
16
+ export type BackendCompatibilityWarning = Readonly<{
17
+ code: BackendCompatibilityWarningCode;
18
+ field?: "packageName" | "packageVersion" | "runtime" | "runtimeVersion" | "buildDigest";
19
+ expected?: string;
20
+ received?: string;
21
+ message: string;
22
+ }>;
23
+ export type BackendCompatibilityReport = Readonly<{
24
+ schemaVersion: typeof BACKEND_COMPATIBILITY_SCHEMA_VERSION;
25
+ status: "compatible" | "warning" | "unknown" | "blocked";
26
+ mode: "multiplexed" | "single-flight" | "legacy" | "unknown";
27
+ protocolVersion?: string;
28
+ backendSessionId?: string;
29
+ packageName?: string;
30
+ packageVersion?: string;
31
+ runtime?: string;
32
+ runtimeVersion?: string;
33
+ buildDigest?: string;
34
+ warnings: readonly BackendCompatibilityWarning[];
35
+ }>;
36
+ export declare const backendCommands: readonly ["backend.version", "backend.health", "backend.capabilities", "backend.hello", "runner.run", "runner.plan", "runner.stream", "responses.create", "ask", "askInThread", "askWithFiles", "askAndDownload", "runMessages", "openThread", "readLatest", "copyLatest", "downloadLatest", "runPlan", "doctor", "createReport", "reports.create", "reports.redact", "reports.summarize", "commands", "describe", "help", "session.bootstrap", "experience.detect", "experience.open", "configuration.inspect", "configuration.apply", "work.start", "work.status", "work.wait", "work.steer", "work.readLatest", "threads.new", "threads.search", "threads.open", "messages.compose", "messages.submit", "messages.ask", "messages.wait", "messages.readLatest", "messages.status", "messages.stop", "messages.waitAndRead", "artifacts.listLatest", "artifacts.wait", "artifacts.downloadLatest", "files.preflight", "files.attach", "files.downloadLatest", "projects.sources.list", "projects.sources.planAdd", "projects.sources.add", "modes.set", "modes.get", "tools.select", "response.copy", "operations.submit", "operations.collect", "operations.inspect", "operations.control"];
5
37
  export type BackendCommand = typeof backendCommands[number];
6
38
  export type BackendRequest = {
7
39
  schemaVersion: typeof BACKEND_REQUEST_SCHEMA_VERSION;
@@ -34,12 +66,62 @@ export type BackendResponseError = {
34
66
  export type BackendResponse<TResult = unknown> = BackendResponseOk<TResult> | BackendResponseError;
35
67
  export type BackendCapabilities = {
36
68
  protocolVersion: typeof BACKEND_REQUEST_SCHEMA_VERSION;
69
+ backendSessionId: string;
70
+ packageName: string;
71
+ packageVersion: string;
72
+ runtime: "node";
73
+ runtimeVersion: string;
74
+ buildDigest: string;
75
+ supportedProtocolVersions: string[];
37
76
  commands: BackendCommand[];
38
77
  transports: Array<"stdio" | "http">;
39
78
  streaming: {
40
79
  modes: Array<"ndjson" | "sse">;
41
80
  tokenDeltas: false;
42
81
  };
82
+ requestIds: {
83
+ required: boolean;
84
+ scope: "connection" | "process" | "none";
85
+ };
86
+ multiplexing: {
87
+ unary: boolean;
88
+ streams: boolean;
89
+ };
90
+ cancellation: {
91
+ supported: boolean;
92
+ requests: boolean;
93
+ streams: boolean;
94
+ };
95
+ tabs: {
96
+ stableProviderIdentity: boolean;
97
+ stableBrowserIdentity: boolean;
98
+ stableTabIdentity: boolean;
99
+ coordinationScope: "none" | "process" | "provider";
100
+ authoritativeClaim: boolean;
101
+ fencing: boolean;
102
+ concurrentTabs: boolean;
103
+ /** Deprecated aliases retained for older capability consumers. */
104
+ stableIdentity?: boolean;
105
+ coordination?: boolean;
106
+ concurrent?: boolean;
107
+ };
108
+ };
109
+ export type BackendRuntimeIdentity = {
110
+ backendSessionId: string;
111
+ packageName: string;
112
+ packageVersion: string;
113
+ runtime: "node";
114
+ runtimeVersion: string;
115
+ buildDigest: string;
116
+ protocolVersion: typeof BACKEND_REQUEST_SCHEMA_VERSION;
117
+ };
118
+ export type BackendHelloPayload = {
119
+ protocolVersion?: string;
120
+ capabilities?: Partial<BackendCapabilities>;
121
+ };
122
+ export type BackendHelloResult = BackendRuntimeIdentity & {
123
+ accepted: boolean;
124
+ capabilities: BackendCapabilities;
43
125
  };
44
126
  export type BackendRunItemStreamEvent = {
45
127
  type: "run_item_stream_event";
@@ -68,6 +150,7 @@ export type BackendEvent = BackendEventPayload & {
68
150
  requestId?: string;
69
151
  };
70
152
  export declare function parseBackendRequest(raw: unknown): BackendRequest;
153
+ export declare function isValidBackendRequestId(value: unknown): value is string;
71
154
  export declare function backendResponseOk<TResult>(requestId: string | undefined, result: TResult): BackendResponseOk<TResult>;
72
155
  export declare function backendResponseError(requestId: string | undefined, error: ProtocolError | Error): BackendResponseError;
73
156
  export declare function backendEvent(requestId: string | undefined, payload: BackendEventPayload): BackendEvent;
@@ -1,10 +1,21 @@
1
1
  export const BACKEND_REQUEST_SCHEMA_VERSION = "chatgpt.browser_control.backend_request.v1";
2
2
  export const BACKEND_RESPONSE_SCHEMA_VERSION = "chatgpt.browser_control.backend_response.v1";
3
3
  export const BACKEND_EVENT_SCHEMA_VERSION = "chatgpt.browser_control.backend_event.v1";
4
+ export const BACKEND_HELLO_COMMAND = "backend.hello";
5
+ export const BACKEND_UNKNOWN_ID_QUARANTINE_LIMIT = 256;
6
+ export const BACKEND_UNKNOWN_ID_QUARANTINE_TTL_MS = 60_000;
7
+ /** Maximum encoded NDJSON frame size, excluding the trailing newline. */
8
+ export const BACKEND_NDJSON_FRAME_LIMIT_BYTES = 16 * 1024 * 1024;
9
+ /** Maximum requestId length accepted on any backend protocol message. */
10
+ export const BACKEND_REQUEST_ID_MAX_LENGTH = 4096;
11
+ /** Reserved for transport-owned hello/legacy control probes. */
12
+ export const BACKEND_CONTROL_REQUEST_ID_PREFIX = "__backend_control__";
13
+ export const BACKEND_COMPATIBILITY_SCHEMA_VERSION = "chatgpt.browser_control.backend_compatibility.v1";
4
14
  export const backendCommands = [
5
15
  "backend.version",
6
16
  "backend.health",
7
17
  "backend.capabilities",
18
+ "backend.hello",
8
19
  "runner.run",
9
20
  "runner.plan",
10
21
  "runner.stream",
@@ -60,7 +71,11 @@ export const backendCommands = [
60
71
  "modes.set",
61
72
  "modes.get",
62
73
  "tools.select",
63
- "response.copy"
74
+ "response.copy",
75
+ "operations.submit",
76
+ "operations.collect",
77
+ "operations.inspect",
78
+ "operations.control"
64
79
  ];
65
80
  export class ProtocolError extends Error {
66
81
  code;
@@ -91,13 +106,20 @@ export function parseBackendRequest(raw) {
91
106
  payload: normalizePayload(raw.payload)
92
107
  };
93
108
  if (raw.requestId !== undefined) {
94
- if (typeof raw.requestId !== "string" || raw.requestId.length === 0) {
109
+ if (!isValidBackendRequestId(raw.requestId)) {
95
110
  throw new ProtocolError("invalid_request", "Backend request requestId must be a non-empty string when provided.", false);
96
111
  }
97
112
  request.requestId = raw.requestId;
98
113
  }
99
114
  return request;
100
115
  }
116
+ export function isValidBackendRequestId(value) {
117
+ return typeof value === "string"
118
+ && value.length > 0
119
+ && value.length <= BACKEND_REQUEST_ID_MAX_LENGTH
120
+ && value.trim() === value
121
+ && !/[\u0000-\u001f\u007f]/u.test(value);
122
+ }
101
123
  export function backendResponseOk(requestId, result) {
102
124
  const response = {
103
125
  schemaVersion: BACKEND_RESPONSE_SCHEMA_VERSION,
@@ -109,13 +131,17 @@ export function backendResponseOk(requestId, result) {
109
131
  return response;
110
132
  }
111
133
  export function backendResponseError(requestId, error) {
134
+ // Arbitrary command/provider errors can contain prompts, local paths, page
135
+ // text, or bridge diagnostics. Only deliberately constructed ProtocolError
136
+ // messages are safe to cross the backend boundary.
137
+ const protocolError = error instanceof ProtocolError;
112
138
  const response = {
113
139
  schemaVersion: BACKEND_RESPONSE_SCHEMA_VERSION,
114
140
  ok: false,
115
141
  error: {
116
- code: error instanceof ProtocolError ? error.code : "invalid_request",
117
- message: error.message,
118
- recoverable: error instanceof ProtocolError ? error.recoverable : false
142
+ code: protocolError ? error.code : "invalid_request",
143
+ message: protocolError ? error.message : "Backend command failed safely.",
144
+ recoverable: protocolError ? error.recoverable : false
119
145
  }
120
146
  };
121
147
  if (requestId !== undefined)
@@ -0,0 +1,10 @@
1
+ import type { BackendSessionOptions } from "./session.js";
2
+ /**
3
+ * Resolve truthful provenance for the exact backend entry artifact.
4
+ *
5
+ * The digest is computed from the loaded file, so copied/sanitized plugin
6
+ * bundles do not accidentally inherit the source bundle's identity. Package
7
+ * metadata is discovered from a bounded ancestor walk and is optional:
8
+ * unknown is preferable to guessing when a custom embedding has no manifest.
9
+ */
10
+ export declare function detectPackagedBackendIdentity(moduleUrl: string | URL): Promise<BackendSessionOptions["backendIdentity"]>;
@@ -0,0 +1,141 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat, open } from "node:fs/promises";
3
+ import { createHash } from "node:crypto";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const MAX_METADATA_BYTES = 1024 * 1024;
7
+ const MAX_BACKEND_ARTIFACT_BYTES = 128 * 1024 * 1024;
8
+ const MAX_ANCESTORS = 8;
9
+ const MAX_IDENTITY_FIELD_LENGTH = 512;
10
+ /**
11
+ * Resolve truthful provenance for the exact backend entry artifact.
12
+ *
13
+ * The digest is computed from the loaded file, so copied/sanitized plugin
14
+ * bundles do not accidentally inherit the source bundle's identity. Package
15
+ * metadata is discovered from a bounded ancestor walk and is optional:
16
+ * unknown is preferable to guessing when a custom embedding has no manifest.
17
+ */
18
+ export async function detectPackagedBackendIdentity(moduleUrl) {
19
+ const artifactPath = modulePath(moduleUrl);
20
+ const [metadata, buildDigest] = await Promise.all([
21
+ findPackageMetadata(dirname(artifactPath)),
22
+ digestArtifact(artifactPath)
23
+ ]);
24
+ return {
25
+ ...(metadata.packageName === undefined ? {} : { packageName: metadata.packageName }),
26
+ ...(metadata.packageVersion === undefined ? {} : { packageVersion: metadata.packageVersion }),
27
+ ...(buildDigest === undefined ? {} : { buildDigest })
28
+ };
29
+ }
30
+ function modulePath(moduleUrl) {
31
+ try {
32
+ const url = moduleUrl instanceof URL ? moduleUrl : new URL(moduleUrl);
33
+ if (url.protocol !== "file:")
34
+ throw new TypeError("Backend module URL must use file protocol.");
35
+ return resolve(fileURLToPath(url));
36
+ }
37
+ catch (error) {
38
+ throw new TypeError(`Backend module URL is invalid: ${error instanceof Error ? error.message : "unknown URL error"}`);
39
+ }
40
+ }
41
+ async function digestArtifact(path) {
42
+ const bytes = await readStableRegularFile(path, MAX_BACKEND_ARTIFACT_BYTES);
43
+ return bytes === undefined
44
+ ? undefined
45
+ : `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
46
+ }
47
+ async function findPackageMetadata(start) {
48
+ let current = resolve(start);
49
+ for (let depth = 0; depth < MAX_ANCESTORS; depth += 1) {
50
+ for (const candidate of [join(current, "package.json"), join(current, ".codex-plugin", "plugin.json")]) {
51
+ const parsed = await readBoundedJson(candidate);
52
+ if (parsed === undefined)
53
+ continue;
54
+ const packageName = identityField(parsed.name);
55
+ const packageVersion = identityField(parsed.version);
56
+ if (packageName !== undefined || packageVersion !== undefined) {
57
+ return {
58
+ ...(packageName === undefined ? {} : { packageName }),
59
+ ...(packageVersion === undefined ? {} : { packageVersion })
60
+ };
61
+ }
62
+ }
63
+ const parent = dirname(current);
64
+ if (parent === current)
65
+ break;
66
+ current = parent;
67
+ }
68
+ return {};
69
+ }
70
+ async function readBoundedJson(path) {
71
+ const bytes = await readStableRegularFile(path, MAX_METADATA_BYTES);
72
+ if (bytes === undefined)
73
+ return undefined;
74
+ try {
75
+ const parsed = JSON.parse(bytes.toString("utf8"));
76
+ return isRecord(parsed) ? parsed : undefined;
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ /**
83
+ * Read one bounded regular file without trusting pathname-only checks.
84
+ * Windows does not expose O_NOFOLLOW, so lstat must reject links explicitly;
85
+ * the handle/path identity comparisons also fail closed if the entry is
86
+ * replaced between the checks and the read.
87
+ */
88
+ async function readStableRegularFile(path, maxBytes) {
89
+ let handle;
90
+ try {
91
+ const before = await lstat(path);
92
+ if (!isBoundedRegularFile(before, maxBytes))
93
+ return undefined;
94
+ handle = await open(path, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
95
+ const opened = await handle.stat();
96
+ if (!isBoundedRegularFile(opened, maxBytes) || !sameFileIdentity(before, opened))
97
+ return undefined;
98
+ const bytes = await handle.readFile();
99
+ const finalHandle = await handle.stat();
100
+ const finalPath = await lstat(path);
101
+ if (!isBoundedRegularFile(finalHandle, maxBytes)
102
+ || !isBoundedRegularFile(finalPath, maxBytes)
103
+ || !sameFileIdentity(opened, finalHandle)
104
+ || !sameFileIdentity(finalHandle, finalPath)
105
+ || bytes.byteLength !== finalHandle.size) {
106
+ return undefined;
107
+ }
108
+ return bytes;
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ finally {
114
+ await handle?.close();
115
+ }
116
+ }
117
+ function isBoundedRegularFile(metadata, maxBytes) {
118
+ return !metadata.isSymbolicLink()
119
+ && metadata.isFile()
120
+ && Number.isSafeInteger(metadata.size)
121
+ && metadata.size >= 0
122
+ && metadata.size <= maxBytes;
123
+ }
124
+ function sameFileIdentity(left, right) {
125
+ return left.dev === right.dev && left.ino === right.ino;
126
+ }
127
+ function identityField(value) {
128
+ return typeof value === "string"
129
+ && value.length > 0
130
+ && value.length <= MAX_IDENTITY_FIELD_LENGTH
131
+ && value.trim() === value
132
+ && !/[\u0000-\u001f\u007f]/u.test(value)
133
+ ? value
134
+ : undefined;
135
+ }
136
+ function isRecord(value) {
137
+ if (value === null || typeof value !== "object" || Array.isArray(value))
138
+ return false;
139
+ const prototype = Object.getPrototypeOf(value);
140
+ return prototype === Object.prototype || prototype === null;
141
+ }
@@ -1,11 +1,19 @@
1
1
  import { type ChatGPTClientOptions } from "../client.js";
2
- import { type BackendEvent, type BackendRequest, type BackendResponse } from "./protocol.js";
3
- export type BackendSessionOptions = ChatGPTClientOptions;
2
+ import { type BackendEvent, type BackendRuntimeIdentity, type BackendRequest, type BackendResponse } from "./protocol.js";
3
+ export type BackendSessionOptions = ChatGPTClientOptions & {
4
+ /**
5
+ * Optional build metadata injected by a packaged backend. Unknown values are
6
+ * deliberately retained as "unknown" instead of being guessed.
7
+ */
8
+ backendIdentity?: Partial<Omit<BackendRuntimeIdentity, "protocolVersion" | "runtime">>;
9
+ };
4
10
  export declare class BackendSession {
5
11
  private readonly options;
6
12
  private clientInstance;
13
+ private readonly identity;
7
14
  constructor(options?: BackendSessionOptions);
8
15
  dispatch(request: BackendRequest): Promise<BackendResponse>;
9
16
  stream(request: BackendRequest): AsyncIterable<BackendEvent>;
17
+ private hello;
10
18
  private client;
11
19
  }