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.
Files changed (231) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +43 -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/download-blocked-by-browser.json +22 -0
  7. package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
  8. package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
  9. package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
  10. package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
  11. package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
  12. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  13. package/contracts/v1/fixtures/operation-action.json +13 -0
  14. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  15. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  16. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  17. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  18. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  19. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  20. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  21. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  22. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  23. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  24. package/contracts/v1/fixtures/operation-event.json +18 -0
  25. package/contracts/v1/fixtures/operation-handle.json +10 -0
  26. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  27. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  28. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  29. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  30. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  31. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  32. package/contracts/v1/fixtures/operation-request.json +33 -0
  33. package/contracts/v1/fixtures/operation-state.json +144 -0
  34. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  35. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  36. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  37. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  38. package/contracts/v1/manifest.json +185 -1
  39. package/contracts/v1/parity-suite.json +106 -8
  40. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  41. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  42. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  43. package/contracts/v1/schemas/manifest.schema.json +57 -0
  44. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  45. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  46. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  47. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  48. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  49. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  50. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  51. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  52. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  53. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  54. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  55. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  56. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  57. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  58. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  59. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  60. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  61. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  62. package/contracts/v1/surface-drift-policy.json +50 -0
  63. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  64. package/dist/codex-chatgpt-control-backend.mjs +45882 -9361
  65. package/dist/codex-chatgpt-control-journal.mjs +5547 -0
  66. package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
  67. package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
  68. package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
  69. package/dist/src/backend/client.d.ts +103 -2
  70. package/dist/src/backend/client.js +1436 -97
  71. package/dist/src/backend/compatibility.d.ts +12 -0
  72. package/dist/src/backend/compatibility.js +208 -0
  73. package/dist/src/backend/protocol.d.ts +84 -1
  74. package/dist/src/backend/protocol.js +31 -5
  75. package/dist/src/backend/runtime-identity.d.ts +10 -0
  76. package/dist/src/backend/runtime-identity.js +141 -0
  77. package/dist/src/backend/session.d.ts +10 -2
  78. package/dist/src/backend/session.js +478 -7
  79. package/dist/src/backend/stdio-server.d.ts +4 -1
  80. package/dist/src/backend/stdio-server.js +240 -28
  81. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  82. package/dist/src/browser/active-composer-file-input.js +33 -0
  83. package/dist/src/browser/attach.d.ts +4 -1
  84. package/dist/src/browser/attach.js +136 -28
  85. package/dist/src/browser/chatgpt-url.d.ts +2 -0
  86. package/dist/src/browser/chatgpt-url.js +9 -0
  87. package/dist/src/browser/downloads.d.ts +17 -1
  88. package/dist/src/browser/downloads.js +150 -28
  89. package/dist/src/browser/page-state.js +6 -0
  90. package/dist/src/client.d.ts +43 -0
  91. package/dist/src/client.js +1279 -59
  92. package/dist/src/commands/artifacts.js +6 -4
  93. package/dist/src/commands/chat-popover.d.ts +37 -0
  94. package/dist/src/commands/chat-popover.js +537 -0
  95. package/dist/src/commands/configuration.d.ts +5 -0
  96. package/dist/src/commands/configuration.js +134 -21
  97. package/dist/src/commands/context.js +10 -3
  98. package/dist/src/commands/doctor.d.ts +1 -1
  99. package/dist/src/commands/doctor.js +27 -4
  100. package/dist/src/commands/experience.d.ts +5 -0
  101. package/dist/src/commands/experience.js +104 -21
  102. package/dist/src/commands/files.js +14 -33
  103. package/dist/src/commands/modes.js +85 -21
  104. package/dist/src/commands/power-discovery.d.ts +130 -0
  105. package/dist/src/commands/power-discovery.js +669 -0
  106. package/dist/src/commands/project-sources.js +325 -31
  107. package/dist/src/commands/sequence.js +59 -32
  108. package/dist/src/commands/session.js +19 -6
  109. package/dist/src/commands/work.js +31 -0
  110. package/dist/src/dom/composer-text.d.ts +6 -0
  111. package/dist/src/dom/composer-text.js +85 -0
  112. package/dist/src/errors.d.ts +9 -0
  113. package/dist/src/errors.js +22 -0
  114. package/dist/src/index.d.ts +6 -0
  115. package/dist/src/index.js +9 -0
  116. package/dist/src/operations/artifact-output.d.ts +69 -0
  117. package/dist/src/operations/artifact-output.js +1748 -0
  118. package/dist/src/operations/artifact-stream.d.ts +19 -0
  119. package/dist/src/operations/artifact-stream.js +35 -0
  120. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  121. package/dist/src/operations/artifact-transfer.js +1312 -0
  122. package/dist/src/operations/browser-adapter.d.ts +167 -0
  123. package/dist/src/operations/browser-adapter.js +2103 -0
  124. package/dist/src/operations/browser-observation.d.ts +110 -0
  125. package/dist/src/operations/browser-observation.js +1256 -0
  126. package/dist/src/operations/browser-target.d.ts +105 -0
  127. package/dist/src/operations/browser-target.js +531 -0
  128. package/dist/src/operations/canonical.d.ts +4 -0
  129. package/dist/src/operations/canonical.js +412 -0
  130. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  131. package/dist/src/operations/chatgpt-runtime.js +1232 -0
  132. package/dist/src/operations/client.d.ts +138 -0
  133. package/dist/src/operations/client.js +1145 -0
  134. package/dist/src/operations/collector.d.ts +195 -0
  135. package/dist/src/operations/collector.js +1039 -0
  136. package/dist/src/operations/configuration-routing.d.ts +4 -0
  137. package/dist/src/operations/configuration-routing.js +14 -0
  138. package/dist/src/operations/control.d.ts +372 -0
  139. package/dist/src/operations/control.js +1498 -0
  140. package/dist/src/operations/file-identity.d.ts +33 -0
  141. package/dist/src/operations/file-identity.js +153 -0
  142. package/dist/src/operations/handle.d.ts +19 -0
  143. package/dist/src/operations/handle.js +727 -0
  144. package/dist/src/operations/index.d.ts +25 -0
  145. package/dist/src/operations/index.js +25 -0
  146. package/dist/src/operations/journal-authority.d.ts +12 -0
  147. package/dist/src/operations/journal-authority.js +1 -0
  148. package/dist/src/operations/journal-rpc-client.d.ts +12 -0
  149. package/dist/src/operations/journal-rpc-client.js +225 -0
  150. package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
  151. package/dist/src/operations/journal-rpc-protocol.js +214 -0
  152. package/dist/src/operations/journal-rpc-server.d.ts +15 -0
  153. package/dist/src/operations/journal-rpc-server.js +272 -0
  154. package/dist/src/operations/journal.d.ts +143 -0
  155. package/dist/src/operations/journal.js +1957 -0
  156. package/dist/src/operations/production-attachments.d.ts +118 -0
  157. package/dist/src/operations/production-attachments.js +1035 -0
  158. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  159. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  160. package/dist/src/operations/production-chatgpt-attachments.d.ts +80 -0
  161. package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
  162. package/dist/src/operations/production-configuration.d.ts +42 -0
  163. package/dist/src/operations/production-configuration.js +1544 -0
  164. package/dist/src/operations/production-primitives.d.ts +53 -0
  165. package/dist/src/operations/production-primitives.js +856 -0
  166. package/dist/src/operations/production-work-steer.d.ts +203 -0
  167. package/dist/src/operations/production-work-steer.js +1218 -0
  168. package/dist/src/operations/recovery.d.ts +80 -0
  169. package/dist/src/operations/recovery.js +174 -0
  170. package/dist/src/operations/runtime-adapter.d.ts +126 -0
  171. package/dist/src/operations/runtime-adapter.js +802 -0
  172. package/dist/src/operations/send-once.d.ts +224 -0
  173. package/dist/src/operations/send-once.js +1081 -0
  174. package/dist/src/operations/service.d.ts +286 -0
  175. package/dist/src/operations/service.js +2831 -0
  176. package/dist/src/operations/staging.d.ts +136 -0
  177. package/dist/src/operations/staging.js +630 -0
  178. package/dist/src/operations/state-machine.d.ts +29 -0
  179. package/dist/src/operations/state-machine.js +2047 -0
  180. package/dist/src/operations/submission.d.ts +423 -0
  181. package/dist/src/operations/submission.js +1676 -0
  182. package/dist/src/operations/transactional-chat-power.d.ts +17 -0
  183. package/dist/src/operations/transactional-chat-power.js +212 -0
  184. package/dist/src/operations/turn-ownership.d.ts +202 -0
  185. package/dist/src/operations/turn-ownership.js +700 -0
  186. package/dist/src/operations/types.d.ts +454 -0
  187. package/dist/src/operations/types.js +20 -0
  188. package/dist/src/operations/wire-requests.d.ts +21 -0
  189. package/dist/src/operations/wire-requests.js +396 -0
  190. package/dist/src/operations/wire-results.d.ts +97 -0
  191. package/dist/src/operations/wire-results.js +818 -0
  192. package/dist/src/runner/responses.d.ts +3 -1
  193. package/dist/src/runner/responses.js +19 -1
  194. package/dist/src/runner/result.js +227 -55
  195. package/dist/src/runner/types.d.ts +12 -0
  196. package/dist/src/runtime/command-routing.d.ts +149 -0
  197. package/dist/src/runtime/command-routing.js +431 -0
  198. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  199. package/dist/src/runtime/coordinated-browser.js +315 -0
  200. package/dist/src/runtime/coordinated-page.d.ts +41 -0
  201. package/dist/src/runtime/coordinated-page.js +651 -0
  202. package/dist/src/runtime/operation-context.d.ts +142 -0
  203. package/dist/src/runtime/operation-context.js +410 -0
  204. package/dist/src/runtime/runtime-session.d.ts +95 -0
  205. package/dist/src/runtime/runtime-session.js +314 -0
  206. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  207. package/dist/src/runtime/tab-coordinator.js +1200 -0
  208. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  209. package/dist/src/runtime/value-boundaries.js +49 -0
  210. package/dist/src/safety/blockers.js +8 -1
  211. package/dist/src/safety/untrusted-output.js +2 -1
  212. package/dist/src/scripts/backend-server.js +4 -1
  213. package/dist/src/scripts/capture-surface-profile.js +7 -2
  214. package/dist/src/scripts/journal-server.d.ts +2 -0
  215. package/dist/src/scripts/journal-server.js +37 -0
  216. package/dist/src/scripts/live-smoke/harness.js +79 -7
  217. package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
  218. package/dist/src/scripts/live-smoke/scenarios.js +147 -30
  219. package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
  220. package/dist/src/scripts/live-smoke/transactional.js +254 -0
  221. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  222. package/dist/src/scripts/release-canary-module.js +17 -4
  223. package/dist/src/types.d.ts +38 -1
  224. package/package.json +8 -3
  225. package/references/2026-08-16-transactional-operations.md +459 -0
  226. package/references/2026-09-06-journal-service.md +140 -0
  227. package/references/agents-runner.md +34 -0
  228. package/references/backend-protocol.md +176 -0
  229. package/references/python-parity.md +137 -0
  230. package/references/responses-adapter.md +19 -0
  231. package/references/streaming.md +6 -0
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
1
3
  import { downloadLatestArtifact, listLatestArtifacts, waitForArtifact } from "./commands/artifacts.js";
2
4
  import { attachFiles, downloadLatestFile, preflightFiles } from "./commands/files.js";
3
5
  import { addProjectSources, buildProjectSourceAddPlan, listProjectSources } from "./commands/project-sources.js";
@@ -20,11 +22,57 @@ import { responseFromRunResult, responsesCreateArgsToRunInput, unsupportedRespon
20
22
  import { streamFromRunResult } from "./runner/stream.js";
21
23
  import { redactReportValue } from "./safety/report-redaction.js";
22
24
  import { explainCommandBlocker } from "./diagnostics/blockers.js";
25
+ import { OperationClient, OperationClientError } from "./operations/client.js";
26
+ import { JOURNAL_RUNTIME_UNAVAILABLE_MESSAGE, OperationJournal, OperationJournalError } from "./operations/journal.js";
27
+ import { createJournalRpcClientFromDescriptor } from "./operations/journal-rpc-client.js";
28
+ import { OperationService } from "./operations/service.js";
29
+ import { OPERATION_CONTROL_REQUEST_SCHEMA_VERSION, OPERATION_REQUEST_SCHEMA_VERSION } from "./operations/types.js";
30
+ import { createRuntimeEnvSession } from "./runtime/runtime-session.js";
31
+ import { coordinateRuntimeEnv } from "./runtime/coordinated-browser.js";
32
+ import { createChatGPTOperationAdapterFactory, createChatGPTOperationControlAdapterFactory, createChatGPTOperationHandleAdapterFactory } from "./operations/chatgpt-runtime.js";
23
33
  export function createChatGPT(options = {}) {
24
- const env = runtimeEnv(options);
34
+ // RuntimeEnv remains mutable for the legacy command implementations, but it
35
+ // must never be shared by two public invocations. The session owns only the
36
+ // browser/page/tab snapshot; provider references are copied once and each
37
+ // call receives its own mutable capture for the whole workflow.
38
+ const runtimeEnvironment = runtimeEnv(options);
39
+ const runtime = createRuntimeEnvSession(runtimeEnvironment);
40
+ // The cached OperationClient must remain stable so request-local adapters
41
+ // (including file/output closures) survive a submit -> collect sequence.
42
+ // Its default ChatGPT factories, however, must see the invocation that is
43
+ // currently using them. Async-local scope keeps concurrent calls from
44
+ // replacing one another's browser/page snapshot.
45
+ const operationRuntime = new AsyncLocalStorage();
46
+ // A direct in-process client is one runtime lifetime. Keep its coordinator
47
+ // identity stable across every operation while ensuring separately created
48
+ // clients remain diagnostically distinguishable.
49
+ const operationOwner = Object.freeze({
50
+ backendSessionId: randomUUID()
51
+ });
25
52
  const limits = normalizeLimits(options.limits);
53
+ // Keep one promise for the lifetime of this client. Opening the journal is
54
+ // deliberately deferred until an operation is actually requested, while
55
+ // concurrent first calls still converge on one authenticated service/key.
56
+ let operationClientPromise;
57
+ const operationClient = () => {
58
+ operationClientPromise ??= createOperationClientForChatGPT(options, () => operationRuntime.getStore() ?? runtimeEnvironment, operationOwner);
59
+ return operationClientPromise;
60
+ };
61
+ const runOperationInvocation = (callback) => {
62
+ const active = operationRuntime.getStore();
63
+ if (active !== undefined)
64
+ return callback();
65
+ return runtime.run(env => operationRuntime.run(env, callback));
66
+ };
67
+ const operations = Object.freeze({
68
+ submit: (request, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.submit(request, operationOptions))),
69
+ collect: (handle, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.collect(handle, operationOptions))),
70
+ inspect: handle => runOperationInvocation(() => operationClient().then(client => client.inspect(handle))),
71
+ control: (request, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.control(request, operationOptions))),
72
+ run: (request, operationOptions) => runOperationInvocation(() => operationClient().then(client => client.run(request, operationOptions)))
73
+ });
26
74
  const runnerRun = ((agent, input, runnerOptions) => {
27
- const run = () => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting);
75
+ const run = () => runtime.run(env => operationRuntime.run(env, () => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting, options, operations)));
28
76
  return runnerOptions?.stream === true ? streamFromRunResult(run) : run();
29
77
  });
30
78
  const runner = {
@@ -35,24 +83,36 @@ export function createChatGPT(options = {}) {
35
83
  agent: config => createChatGPTAgent(config),
36
84
  run: runner.run,
37
85
  runner,
86
+ operations,
38
87
  responses: {
39
- create: args => createResponse(args, runner, env.now)
88
+ // Keep the entire Responses adapter, including its runner invocation,
89
+ // inside one capture. Passing a runner bound to this env avoids a
90
+ // nested session capture for the same public workflow.
91
+ create: args => runtime.run(env => operationRuntime.run(env, () => createResponse(args, (agent, input) => runAgentWorkflow(agent, input, env, limits, options.defaults, options.reporting, options, operations), env.now)))
40
92
  },
41
- ask: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
42
- askInThread: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
43
- askWithFiles: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
44
- askAndDownload: args => runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
45
- runMessages: args => runGuarded(planRunMessages(args, options.defaults), env, limits, reportOptions(args.report, options.reporting)),
46
- openThread: thread => runSequence(planOpenThread(thread), env),
47
- readLatest: args => readLatest(env, args),
48
- copyLatest: args => copyResponse(env, args),
49
- downloadLatest: args => downloadLatestFile(env, args),
50
- runPlan: plan => runPlanInvocation(plan, env, limits, options.defaults, options.reporting),
51
- doctor: args => doctor(env, args),
52
- createReport: (result, args) => createRunReport(env, result, args ?? options.reporting ?? {}),
93
+ ask: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
94
+ ? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
95
+ : runTransactionalAsk(args, options.defaults, options, operations))),
96
+ askInThread: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
97
+ ? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
98
+ : runTransactionalAsk(args, options.defaults, options, operations))),
99
+ askWithFiles: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
100
+ ? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
101
+ : runTransactionalAsk(args, options.defaults, options, operations))),
102
+ askAndDownload: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
103
+ ? runGuarded(planAskWorkflow(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))
104
+ : runTransactionalAsk(args, options.defaults, options, operations))),
105
+ runMessages: args => runtime.run(env => runGuarded(planRunMessages(args, options.defaults), env, limits, reportOptions(args.report, options.reporting))),
106
+ openThread: thread => runtime.run(env => runSequence(planOpenThread(thread), env)),
107
+ readLatest: args => runtime.run(env => readLatest(env, args)),
108
+ copyLatest: args => runtime.run(env => copyResponse(env, args)),
109
+ downloadLatest: args => runtime.run(env => downloadLatestFile(env, args)),
110
+ runPlan: plan => runtime.run(env => runPlanInvocation(plan, env, limits, options.defaults, options.reporting)),
111
+ doctor: args => runtime.run(env => doctor(env, args)),
112
+ createReport: (result, args) => runtime.run(env => createRunReport(env, result, args ?? options.reporting ?? {})),
53
113
  explainBlocker: (resultOrBlocker, args) => explainCommandBlocker(resultOrBlocker, args),
54
114
  reports: {
55
- create: (result, args) => createRunReport(env, result, args ?? options.reporting ?? {}),
115
+ create: (result, args) => runtime.run(env => createRunReport(env, result, args ?? options.reporting ?? {})),
56
116
  redact: async (value, args) => resultOk(redactReportValue(value, args), {}),
57
117
  summarize: async (result, args) => resultOk(redactReportValue(resultSummary(result), args), {})
58
118
  },
@@ -61,69 +121,73 @@ export function createChatGPT(options = {}) {
61
121
  describe: name => describeCommand(name),
62
122
  help: topic => helpText(topic),
63
123
  session: {
64
- bootstrap: args => bootstrap(env, args)
124
+ bootstrap: args => runtime.run(env => bootstrap(env, args))
65
125
  },
66
126
  experience: {
67
- detect: args => detectExperience(env, args),
68
- open: args => openExperience(env, args)
127
+ detect: args => runtime.run(env => detectExperience(env, args)),
128
+ open: args => runtime.run(env => openExperience(env, args))
69
129
  },
70
130
  configuration: {
71
- inspect: args => inspectConfiguration(env, args),
72
- apply: args => applyConfiguration(env, args)
131
+ inspect: args => runtime.run(env => inspectConfiguration(env, args)),
132
+ apply: args => runtime.run(env => applyConfiguration(env, args))
73
133
  },
74
134
  work: {
75
- start: args => startWork(env, args),
76
- status: args => workStatus(env, args),
77
- wait: args => waitForWork(env, args),
78
- steer: args => steerWork(env, args),
79
- readLatest: args => readLatestWork(env, args),
135
+ start: args => runtime.run(env => operationRuntime.run(env, () => args.operationId === undefined
136
+ ? startWork(env, args)
137
+ : runTransactionalWorkStart(args, options.defaults, operations))),
138
+ status: args => runtime.run(env => workStatus(env, args)),
139
+ wait: args => runtime.run(env => waitForWork(env, args)),
140
+ steer: args => runtime.run(env => operationRuntime.run(env, () => hasTransactionalWorkControl(args)
141
+ ? runTransactionalWorkSteer(args, operations)
142
+ : steerWork(env, args))),
143
+ readLatest: args => runtime.run(env => readLatestWork(env, args)),
80
144
  artifacts: {
81
- listLatest: args => listLatestArtifacts(env, args),
82
- wait: args => waitForArtifact(env, args),
83
- downloadLatest: args => downloadLatestArtifact(env, args)
145
+ listLatest: args => runtime.run(env => listLatestArtifacts(env, args)),
146
+ wait: args => runtime.run(env => waitForArtifact(env, args)),
147
+ downloadLatest: args => runtime.run(env => downloadLatestArtifact(env, args))
84
148
  }
85
149
  },
86
150
  threads: {
87
- new: args => newThread(env, args),
88
- search: args => searchThreads(env, args),
89
- open: args => openThread(env, args)
151
+ new: args => runtime.run(env => newThread(env, args)),
152
+ search: args => runtime.run(env => searchThreads(env, args)),
153
+ open: args => runtime.run(env => openThread(env, args))
90
154
  },
91
155
  messages: {
92
- compose: args => composeMessage(env, args),
93
- submit: args => submitMessage(env, args),
94
- ask: args => askMessage(env, args),
95
- wait: args => waitForMessage(env, args),
96
- readLatest: args => readLatest(env, args),
97
- status: args => messageStatus(env, args),
98
- stop: args => stopGeneration(env, args),
99
- waitAndRead: args => waitAndRead(env, args)
156
+ compose: args => runtime.run(env => composeMessage(env, args)),
157
+ submit: args => runtime.run(env => submitMessage(env, args)),
158
+ ask: args => runtime.run(env => askMessage(env, args)),
159
+ wait: args => runtime.run(env => waitForMessage(env, args)),
160
+ readLatest: args => runtime.run(env => readLatest(env, args)),
161
+ status: args => runtime.run(env => messageStatus(env, args)),
162
+ stop: args => runtime.run(env => stopGeneration(env, args)),
163
+ waitAndRead: args => runtime.run(env => waitAndRead(env, args))
100
164
  },
101
165
  files: {
102
- preflight: args => preflightFiles(env, args),
103
- attach: args => attachFiles(env, args),
104
- downloadLatest: args => downloadLatestFile(env, args)
166
+ preflight: args => runtime.run(env => preflightFiles(env, args)),
167
+ attach: args => runtime.run(env => attachFiles(env, args)),
168
+ downloadLatest: args => runtime.run(env => downloadLatestFile(env, args))
105
169
  },
106
170
  projects: {
107
171
  sources: {
108
- list: args => listProjectSources(env, args),
109
- planAdd: args => buildProjectSourceAddPlan(env, args),
110
- add: args => addProjectSources(env, args)
172
+ list: args => runtime.run(env => listProjectSources(env, args)),
173
+ planAdd: args => runtime.run(env => buildProjectSourceAddPlan(env, args)),
174
+ add: args => runtime.run(env => addProjectSources(env, args))
111
175
  }
112
176
  },
113
177
  artifacts: {
114
- listLatest: args => listLatestArtifacts(env, args),
115
- wait: args => waitForArtifact(env, args),
116
- downloadLatest: args => downloadLatestArtifact(env, args)
178
+ listLatest: args => runtime.run(env => listLatestArtifacts(env, args)),
179
+ wait: args => runtime.run(env => waitForArtifact(env, args)),
180
+ downloadLatest: args => runtime.run(env => downloadLatestArtifact(env, args))
117
181
  },
118
182
  modes: {
119
- set: args => setMode(env, args),
120
- get: args => getMode(env, args)
183
+ set: args => runtime.run(env => setMode(env, args)),
184
+ get: args => runtime.run(env => getMode(env, args))
121
185
  },
122
186
  tools: {
123
- select: args => selectTool(env, args)
187
+ select: args => runtime.run(env => selectTool(env, args))
124
188
  },
125
189
  response: {
126
- copy: args => copyResponse(env, args)
190
+ copy: args => runtime.run(env => copyResponse(env, args))
127
191
  }
128
192
  };
129
193
  }
@@ -277,11 +341,11 @@ function capReportOptions(report, limits) {
277
341
  maxPreviewChars: Math.min(report.maxPreviewChars ?? limits.maxReportPreviewChars, limits.maxReportPreviewChars)
278
342
  };
279
343
  }
280
- async function createResponse(args, runner, now) {
344
+ async function createResponse(args, run, now) {
281
345
  const validation = validateResponsesCreateArgs(args);
282
346
  const timestamp = now?.() ?? new Date();
283
347
  if (!validation.ok) {
284
- return unsupportedResponse(validation.unsupported, timestamp);
348
+ return unsupportedResponse(validation.unsupported, timestamp, readOperationIdFromUnknown(args));
285
349
  }
286
350
  const responseArgs = args;
287
351
  const agentConfig = {
@@ -292,20 +356,73 @@ async function createResponse(args, runner, now) {
292
356
  agentConfig.instructions = responseArgs.instructions;
293
357
  }
294
358
  const agent = createChatGPTAgent(agentConfig);
295
- const result = await runner.run(agent, responsesCreateArgsToRunInput(responseArgs));
359
+ const result = await run(agent, responsesCreateArgsToRunInput(responseArgs));
296
360
  return responseFromRunResult(result, now?.() ?? timestamp);
297
361
  }
298
- async function runAgentWorkflow(agent, input, env, limits, defaults, reporting) {
362
+ async function runAgentWorkflow(agent, input, env, limits, defaults, reporting, clientOptions, operations) {
363
+ const requestedOperationId = runnerOperationId(input);
299
364
  try {
300
365
  const normalized = normalizeRunnerInput(agent, input);
366
+ if (normalized.operationId !== undefined) {
367
+ const result = await runTransactionalRunnerWorkflow(agent, normalized, defaults, clientOptions, operations);
368
+ return toRunResult(agent, result);
369
+ }
301
370
  const plan = planAgentWorkflowFromNormalized(agent, normalized, defaults);
302
371
  const report = reportOptions(normalized.report ?? agent.defaults.report, reporting);
303
372
  const result = await runGuarded(plan, env, limits, report);
304
373
  return toRunResult(agent, result);
305
374
  }
306
375
  catch (error) {
307
- return toRunResult(agent, resultError(error instanceof Error ? error : new Error(String(error)), {}));
376
+ const result = requestedOperationId === undefined
377
+ ? resultError(error instanceof Error ? error : new Error(String(error)), {})
378
+ : transactionalAskError(requestedOperationId, error);
379
+ return toRunResult(agent, result);
380
+ }
381
+ }
382
+ /**
383
+ * Runner opt-in adapter. It deliberately funnels through the same
384
+ * transactional ask preparation/result mapping used by the high-level Chat
385
+ * helpers so request identity, file manifests, action journaling, and
386
+ * structured blocker handling cannot drift between surfaces.
387
+ */
388
+ async function runTransactionalRunnerWorkflow(agent, input, defaults, clientOptions, operations) {
389
+ const operationId = input.operationId;
390
+ if (operationId === undefined) {
391
+ return transactionalAskError("invalid-operation", new Error("Transactional runner input is missing operationId."));
392
+ }
393
+ if (agent.instructionsMode === "visible_setup_message" && hasInstructions(agent)) {
394
+ const unsupported = transactionalUnsupported(operationId, "visible_setup_message requires a separate setup turn and is not supported by one transactional operation.", "agent.instructionsMode");
395
+ return unsupported.ok ? transactionalAskError(operationId, new Error("Invalid transactional runner input.")) : unsupported.result;
396
+ }
397
+ if (input.copy !== undefined && input.copy !== false) {
398
+ const unsupported = transactionalUnsupported(operationId, "copy is not supported by the transactional runner path; use operations.collect and an explicit response action.", "copy");
399
+ return unsupported.ok ? transactionalAskError(operationId, new Error("Invalid transactional runner input.")) : unsupported.result;
308
400
  }
401
+ // Match the legacy precedence exactly: input values win, then agent
402
+ // defaults, then client defaults. The transactional mapper performs the
403
+ // same merge for target/configuration/wait/read as the Chat ask surface.
404
+ const effectiveDefaults = {
405
+ ...(defaults ?? {}),
406
+ ...agent.defaults
407
+ };
408
+ const report = input.report ?? agent.defaults.report;
409
+ const result = await runTransactionalAsk({
410
+ operationId,
411
+ prompt: renderRunnerPrompt(agent, input.prompt),
412
+ ...(input.thread === undefined ? {} : { thread: input.thread }),
413
+ ...(input.existingTab === undefined ? {} : { existingTab: input.existingTab }),
414
+ ...(input.preferExistingTab === undefined ? {} : { preferExistingTab: input.preferExistingTab }),
415
+ ...(input.experience === undefined ? {} : { experience: input.experience }),
416
+ ...(input.configuration === undefined ? {} : { configuration: input.configuration }),
417
+ ...(input.mode === undefined ? {} : { mode: input.mode }),
418
+ ...(input.tools.length === 0 ? {} : { tools: input.tools }),
419
+ ...(input.files.length === 0 ? {} : { files: input.files }),
420
+ ...(input.wait === undefined ? {} : { wait: input.wait }),
421
+ ...(input.read === undefined ? {} : { read: input.read }),
422
+ ...(report === undefined ? {} : { report }),
423
+ ...(input.download === undefined || input.download === false ? {} : { download: input.download })
424
+ }, effectiveDefaults, clientOptions, operations);
425
+ return result;
309
426
  }
310
427
  function planAgentWorkflow(agent, input, defaults = {}) {
311
428
  return planAgentWorkflowFromNormalized(agent, normalizeRunnerInput(agent, input), defaults);
@@ -382,6 +499,8 @@ function normalizeRunnerInput(agent, input) {
382
499
  tools: args.tools ?? [],
383
500
  files: [...collected.files, ...attachments]
384
501
  };
502
+ if (args.operationId !== undefined)
503
+ normalized.operationId = args.operationId;
385
504
  if (args.thread !== undefined)
386
505
  normalized.thread = args.thread;
387
506
  if (args.existingTab !== undefined)
@@ -407,6 +526,9 @@ function normalizeRunnerInput(agent, input) {
407
526
  }
408
527
  return normalized;
409
528
  }
529
+ function runnerOperationId(input) {
530
+ return typeof input === "string" ? undefined : input.operationId;
531
+ }
410
532
  function collectRunnerInput(input) {
411
533
  if (typeof input === "string") {
412
534
  return { prompt: input, files: [] };
@@ -504,7 +626,1069 @@ function runtimeEnv(options) {
504
626
  env.clipboard = options.clipboard;
505
627
  if (options.now !== undefined)
506
628
  env.now = options.now;
507
- return env;
629
+ if (options.expectedTabId !== undefined)
630
+ env.expectedTabId = options.expectedTabId;
631
+ // Coordinate only the browser/page snapshot. The runtime session still
632
+ // owns invocation isolation and compatibility mutation semantics for the
633
+ // legacy fields; browser methods themselves use the process-wide actor.
634
+ return coordinateRuntimeEnv(env);
635
+ }
636
+ async function createOperationClientForChatGPT(options, runtimeEnvironment, owner) {
637
+ const operationOptions = options.operations ?? {};
638
+ if (operationOptions.journalService !== undefined && operationOptions.stateRoot !== undefined) {
639
+ throw new OperationJournalError("journal_service_configuration_conflict", "The journal service owns its state root; configure either journalService or stateRoot.");
640
+ }
641
+ const journal = operationOptions.journalService === undefined
642
+ ? await OperationJournal.open(operationOptions.stateRoot === undefined ? {} : { stateRoot: operationOptions.stateRoot })
643
+ : await createJournalRpcClientFromDescriptor(operationOptions.journalService.descriptorPath, {
644
+ ...(operationOptions.journalService.timeoutMs === undefined ? {} : { timeoutMs: operationOptions.journalService.timeoutMs })
645
+ });
646
+ const serviceOptions = {
647
+ ...(operationOptions.maxCasRetries === undefined ? {} : { maxCasRetries: operationOptions.maxCasRetries }),
648
+ ...(options.now === undefined ? {} : { now: () => options.now().getTime() })
649
+ };
650
+ const service = new OperationService(journal, serviceOptions);
651
+ const adapter = operationOptions.adapter ?? unavailableOperationAdapter();
652
+ // Supplying any custom adapter seam is an explicit integration choice. Do
653
+ // not combine one half of a custom provider with the default ChatGPT
654
+ // recovery path. With no customization, however, browser-touching
655
+ // operations work out of the box and remain lazy until target resolution.
656
+ const hasCustomAdapter = operationOptions.adapter !== undefined
657
+ || operationOptions.adapterFactory !== undefined
658
+ || operationOptions.handleAdapterFactory !== undefined
659
+ || operationOptions.controlAdapterFactory !== undefined;
660
+ const evidenceDigest = (domain, material) => {
661
+ // Provider primitives use both the journal's short labels and their own
662
+ // versioned slash-separated domains. Preserve short labels verbatim so
663
+ // service-side identities (notably file manifests) remain identical;
664
+ // envelope provider domains inside one bounded journal namespace rather
665
+ // than allowing the provider label to violate the journal API.
666
+ if (/^[a-z][a-z0-9-]{0,63}$/u.test(domain)) {
667
+ return journal.evidenceDigest(domain, material);
668
+ }
669
+ return journal.evidenceDigest("provider-evidence", { domain, material });
670
+ };
671
+ const adapterFactory = hasCustomAdapter
672
+ ? operationOptions.adapterFactory
673
+ : async (context) => createChatGPTOperationAdapterFactory({
674
+ env: runtimeEnvironment(),
675
+ owner,
676
+ evidenceDigest
677
+ })(context);
678
+ const handleAdapterFactory = hasCustomAdapter
679
+ ? operationOptions.handleAdapterFactory
680
+ : async (context) => createChatGPTOperationHandleAdapterFactory({
681
+ env: runtimeEnvironment(),
682
+ owner,
683
+ evidenceDigest
684
+ })(context);
685
+ const controlAdapterFactory = hasCustomAdapter
686
+ ? operationOptions.controlAdapterFactory
687
+ : async (context) => createChatGPTOperationControlAdapterFactory({
688
+ env: runtimeEnvironment(),
689
+ owner,
690
+ evidenceDigest
691
+ })(context);
692
+ return new OperationClient(service, adapter, {
693
+ ...(adapterFactory === undefined ? {} : { adapterFactory }),
694
+ ...(handleAdapterFactory === undefined ? {} : { handleAdapterFactory }),
695
+ ...(controlAdapterFactory === undefined ? {} : { controlAdapterFactory }),
696
+ ...(operationOptions.maxCachedAdapters === undefined ? {} : { maxCachedAdapters: operationOptions.maxCachedAdapters })
697
+ });
698
+ }
699
+ /**
700
+ * A conservative placeholder keeps the public facade constructible for
701
+ * browser-free inspection and for callers that provide factories later. It
702
+ * never probes a page, claims capabilities, or synthesizes a successful
703
+ * operation result; all browser-touching paths fail closed.
704
+ */
705
+ function unavailableOperationAdapter() {
706
+ const unavailable = async () => {
707
+ throw new OperationClientError("adapter_unavailable", "A browser-bound operation adapter is required for this operation.");
708
+ };
709
+ return {
710
+ resolveTarget: unavailable,
711
+ submission: {
712
+ observeStaging: unavailable,
713
+ executeFileHandoffOnce: unavailable,
714
+ observeAttachments: unavailable,
715
+ // Send is deliberately exposed as explicit phases. The placeholder
716
+ // has no browser capability, so every phase fails closed; retaining the
717
+ // legacy method below is source compatibility only and is never used by
718
+ // the transactional coordinator.
719
+ prepareSend: unavailable,
720
+ executePreparedSend: unavailable,
721
+ verifyPreparedSend: unavailable,
722
+ recoverSend: unavailable,
723
+ executeFinalTabTransaction: unavailable
724
+ },
725
+ collector: {
726
+ readContext: unavailable,
727
+ observe: unavailable,
728
+ sleep: unavailable
729
+ }
730
+ };
731
+ }
732
+ const TRANSACTIONAL_OPERATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
733
+ async function runTransactionalAsk(args, defaults, clientOptions, operations) {
734
+ const prepared = prepareTransactionalAsk(args, defaults, clientOptions);
735
+ if (!prepared.ok)
736
+ return prepared.result;
737
+ try {
738
+ // Route through the public, stable facade so this path has exactly the
739
+ // same identity/journal semantics as callers using chatgpt.operations.run.
740
+ const run = await operations.run(prepared.value.request, prepared.value.options);
741
+ const handle = await freshestOperationHandle(operations, run.submit.handle);
742
+ return transactionalAskCommandResult(prepared.value, run, handle);
743
+ }
744
+ catch (error) {
745
+ // OperationClient and adapter errors are intentionally converted to a
746
+ // bounded structured result. Their native messages may include a path,
747
+ // URL, prompt, or provider-private text and must not cross this boundary.
748
+ return transactionalAskError(args.operationId, error);
749
+ }
750
+ }
751
+ /**
752
+ * Work's public workflow keeps its existing command implementation as the
753
+ * compatibility path. An explicit operation ID opts only this client call
754
+ * into the durable submit/collect protocol; the low-level `startWork` command
755
+ * remains available to sequence callers unchanged.
756
+ */
757
+ async function runTransactionalWorkStart(args, defaults, operations) {
758
+ const prepared = prepareTransactionalWorkStart(args, defaults);
759
+ if (!prepared.ok)
760
+ return prepared.result;
761
+ try {
762
+ const run = await operations.run(prepared.value.request, prepared.value.options);
763
+ const handle = await freshestOperationHandle(operations, run.submit.handle);
764
+ const task = await transactionalWorkTask(operations, handle);
765
+ return transactionalWorkStartResult(prepared.value, run, handle, task);
766
+ }
767
+ catch (error) {
768
+ return transactionalWorkError(args.operationId, error);
769
+ }
770
+ }
771
+ function prepareTransactionalWorkStart(args, defaults) {
772
+ const operationId = args.operationId;
773
+ if (operationId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)) {
774
+ return {
775
+ ok: false,
776
+ result: transactionalWorkStartUnsupported(operationId, "operationId must be a canonical UUID.", "operationId")
777
+ };
778
+ }
779
+ if (args.prompt.trim().length === 0) {
780
+ return {
781
+ ok: false,
782
+ result: transactionalWorkStartUnsupported(operationId, "prompt must be non-empty.", "prompt")
783
+ };
784
+ }
785
+ if (args.timeoutMs !== undefined && (!Number.isSafeInteger(args.timeoutMs) || args.timeoutMs < 1 || args.timeoutMs > 86_400_000)) {
786
+ return {
787
+ ok: false,
788
+ result: transactionalWorkStartUnsupported(operationId, "timeoutMs must be between 1 and 86400000.", "timeoutMs")
789
+ };
790
+ }
791
+ const wait = args.wait ?? false;
792
+ const read = args.read ?? false;
793
+ const waitOptions = transactionalWorkWaitOptions(wait, read);
794
+ if (!waitOptions.ok) {
795
+ return { ok: false, result: transactionalWorkStartUnsupported(operationId, waitOptions.message, waitOptions.fieldPath) };
796
+ }
797
+ const configuration = transactionalWorkConfiguration(args.configuration ?? defaults?.configuration);
798
+ if (!configuration.ok) {
799
+ return { ok: false, result: transactionalWorkStartUnsupported(operationId, configuration.message, configuration.fieldPath) };
800
+ }
801
+ const files = (args.files ?? []).map(path => ({ path }));
802
+ const request = {
803
+ schemaVersion: OPERATION_REQUEST_SCHEMA_VERSION,
804
+ operationId,
805
+ surface: "work",
806
+ prompt: args.prompt,
807
+ target: args.newTask === false ? { type: "selected_tab" } : { type: "new" },
808
+ ...(configuration.value === undefined ? {} : { configuration: configuration.value }),
809
+ ...(files.length === 0 ? {} : { files }),
810
+ capture: {
811
+ responseContent: waitOptions.responseContent,
812
+ responseFormat: waitOptions.responseFormat,
813
+ artifacts: "receipt_only"
814
+ }
815
+ };
816
+ return {
817
+ ok: true,
818
+ value: {
819
+ request,
820
+ options: {
821
+ ...waitOptions.options,
822
+ ...(args.timeoutMs === undefined || typeof wait !== "object" || wait.timeoutMs !== undefined
823
+ ? {}
824
+ : { timeoutMs: args.timeoutMs })
825
+ },
826
+ readRequested: waitOptions.readRequested,
827
+ responseFormat: waitOptions.responseFormat,
828
+ ...(waitOptions.maxResponseChars === undefined ? {} : { maxResponseChars: waitOptions.maxResponseChars })
829
+ }
830
+ };
831
+ }
832
+ function transactionalWorkConfiguration(selected) {
833
+ if (selected === undefined)
834
+ return { ok: true };
835
+ const value = { experience: "work" };
836
+ if (selected.model !== undefined)
837
+ value.model = selected.model;
838
+ const modelVersion = selected.modelVersion ?? selected.version;
839
+ if (modelVersion !== undefined)
840
+ value.modelVersion = modelVersion;
841
+ const additional = {};
842
+ if (selected.effort !== undefined)
843
+ additional.effort = selected.effort;
844
+ if (selected.speed !== undefined)
845
+ additional.speed = selected.speed;
846
+ if (selected.intelligence !== undefined) {
847
+ return {
848
+ ok: false,
849
+ message: "configuration.intelligence is not supported by the transactional Work path.",
850
+ fieldPath: "configuration.intelligence"
851
+ };
852
+ }
853
+ if (Object.keys(additional).length > 0)
854
+ value.additional = additional;
855
+ return { ok: true, value };
856
+ }
857
+ function transactionalWorkWaitOptions(wait, read) {
858
+ const readRequested = read === true || typeof read === "object";
859
+ const responseFormat = typeof read === "object" && read.format === "text"
860
+ ? "text"
861
+ : "markdown";
862
+ const maxResponseChars = typeof read === "object" ? read.maxChars : undefined;
863
+ if (maxResponseChars !== undefined
864
+ && (!Number.isSafeInteger(maxResponseChars) || maxResponseChars < 0 || maxResponseChars > 8 * 1024 * 1024)) {
865
+ return { ok: false, message: "read.maxChars must be between 0 and 8388608.", fieldPath: "read.maxChars" };
866
+ }
867
+ if (typeof read === "object") {
868
+ if (read.role !== undefined && read.role !== "assistant") {
869
+ return { ok: false, message: "read.role=user is not supported by the transactional Work path.", fieldPath: "read.role" };
870
+ }
871
+ if (read.format !== undefined && read.format !== "markdown" && read.format !== "text") {
872
+ return { ok: false, message: "read.format must be markdown or text on the transactional Work path.", fieldPath: "read.format" };
873
+ }
874
+ }
875
+ if (typeof wait === "object") {
876
+ if (wait.timeoutMs !== undefined && (!Number.isSafeInteger(wait.timeoutMs) || wait.timeoutMs < 1 || wait.timeoutMs > 86_400_000)) {
877
+ return { ok: false, message: "wait.timeoutMs must be between 1 and 86400000.", fieldPath: "wait.timeoutMs" };
878
+ }
879
+ if (wait.pollMs !== undefined && (!Number.isSafeInteger(wait.pollMs) || wait.pollMs < 0 || wait.pollMs > 60_000)) {
880
+ return { ok: false, message: "wait.pollMs must be between 0 and 60000.", fieldPath: "wait.pollMs" };
881
+ }
882
+ if (wait.responseContent !== undefined && wait.responseContent !== "include" && wait.responseContent !== "metadata") {
883
+ return { ok: false, message: "wait.responseContent must be include or metadata.", fieldPath: "wait.responseContent" };
884
+ }
885
+ for (const [key, value] of [
886
+ ["afterTurnCount", wait.afterTurnCount],
887
+ ["afterAssistantTurnCount", wait.afterAssistantTurnCount],
888
+ ["afterStep", wait.afterStep],
889
+ ["stableMs", wait.stableMs],
890
+ ["mode", wait.mode]
891
+ ]) {
892
+ if (value !== undefined) {
893
+ return { ok: false, message: `wait.${key} is not supported by the transactional Work path.`, fieldPath: `wait.${key}` };
894
+ }
895
+ }
896
+ }
897
+ // A Work read is collect-only. If the caller asks to read without an
898
+ // explicit wait, collect still waits for the exact owned turn rather than
899
+ // falling back to a page-wide latest response.
900
+ const waitForOwnedTurn = wait !== false || readRequested;
901
+ const responseContent = typeof wait === "object" && wait.responseContent !== undefined
902
+ ? wait.responseContent
903
+ : readRequested ? "include" : "metadata";
904
+ const options = {
905
+ wait: waitForOwnedTurn,
906
+ responseContent,
907
+ ...(typeof wait === "object" && wait.timeoutMs === undefined ? {} : typeof wait === "object" ? { timeoutMs: wait.timeoutMs } : {}),
908
+ ...(typeof wait === "object" && wait.pollMs === undefined ? {} : typeof wait === "object" ? { pollIntervalMs: wait.pollMs } : {})
909
+ };
910
+ return {
911
+ ok: true,
912
+ options,
913
+ responseContent,
914
+ readRequested,
915
+ responseFormat,
916
+ ...(maxResponseChars === undefined ? {} : { maxResponseChars })
917
+ };
918
+ }
919
+ async function transactionalWorkTask(operations, handle) {
920
+ try {
921
+ const inspected = await operations.inspect(handle);
922
+ const target = inspected.state.target;
923
+ return {
924
+ ...(target?.canonicalThreadUrl === undefined ? {} : { url: target.canonicalThreadUrl }),
925
+ ...(target?.conversationId === undefined ? {} : { conversationId: target.conversationId })
926
+ };
927
+ }
928
+ catch {
929
+ return {};
930
+ }
931
+ }
932
+ function transactionalWorkStartResult(prepared, run, handle, task) {
933
+ const base = {
934
+ task,
935
+ responseFormat: prepared.responseFormat,
936
+ operationId: handle.operationId,
937
+ handle,
938
+ requestDigest: handle.requestDigest,
939
+ submitted: {
940
+ submitted: false,
941
+ submissionState: transactionalSubmissionState(handle, "blocker" in run.submit.submission ? run.submit.submission.blocker.mutationBoundary : handle.mutationBoundary),
942
+ completionState: transactionalCompletionState(handle.phase),
943
+ generationActive: handle.phase === "generating"
944
+ }
945
+ };
946
+ const submission = run.submit.submission;
947
+ if (submission.kind === "blocked" || submission.kind === "uncertain" || submission.kind === "cancelled") {
948
+ const submitted = {
949
+ ...base,
950
+ submitted: {
951
+ ...base.submitted,
952
+ submitted: base.submitted.submissionState !== "not_submitted"
953
+ }
954
+ };
955
+ return transactionalWorkBlockerResult(submitted, submission.blocker.code, submission.kind === "uncertain" || submission.blocker.mutationBoundary !== "none", submission.blocker.observationRequired);
956
+ }
957
+ base.submitted = {
958
+ ...base.submitted,
959
+ submitted: true,
960
+ submissionState: "submitted",
961
+ completionState: handle.phase === "generating" ? "generating" : handle.phase === "completed" ? "complete" : "unknown",
962
+ generationActive: handle.phase === "generating"
963
+ };
964
+ const collected = run.collect;
965
+ if (collected === undefined) {
966
+ return {
967
+ ok: true,
968
+ status: "ok",
969
+ data: { ...base, pending: true, complete: handle.phase === "completed" },
970
+ warnings: ["Work was submitted through the transactional path; collect the returned handle to observe its exact assistant turn."],
971
+ context: { timestamp: new Date().toISOString(), experience: "work" }
972
+ };
973
+ }
974
+ if (collected.kind === "completed") {
975
+ const rawText = prepared.request.capture?.responseContent === "include" ? collected.response.rawText : undefined;
976
+ const responseText = rawText === undefined
977
+ ? undefined
978
+ : prepared.maxResponseChars === undefined
979
+ ? rawText
980
+ : rawText.slice(0, prepared.maxResponseChars);
981
+ const response = prepared.readRequested && responseText !== undefined
982
+ ? {
983
+ role: "assistant",
984
+ text: responseText,
985
+ format: prepared.responseFormat === "text" ? "normalized_text" : "markdown",
986
+ completionState: "complete",
987
+ generationActive: false
988
+ }
989
+ : undefined;
990
+ const data = {
991
+ ...base,
992
+ complete: true,
993
+ ...(collected.response.text?.digest === undefined ? {} : { responseDigest: collected.response.text.digest }),
994
+ ...(collected.response.text?.bytes === undefined ? {} : { responseBytes: collected.response.text.bytes }),
995
+ ...(response === undefined ? {} : { response }),
996
+ submitted: {
997
+ ...base.submitted,
998
+ completionState: "complete",
999
+ generationActive: false
1000
+ }
1001
+ };
1002
+ return { ok: true, status: "ok", data, warnings: [], context: { timestamp: new Date().toISOString(), experience: "work" } };
1003
+ }
1004
+ if (collected.kind === "pending") {
1005
+ return {
1006
+ ok: false,
1007
+ status: "partial",
1008
+ data: {
1009
+ ...base,
1010
+ pending: true,
1011
+ complete: false,
1012
+ submitted: {
1013
+ ...base.submitted,
1014
+ completionState: collected.phase === "generating" ? "generating" : "unknown",
1015
+ generationActive: collected.phase === "generating"
1016
+ }
1017
+ },
1018
+ warnings: ["Work was submitted exactly once, but completion was not verified."],
1019
+ context: { timestamp: new Date().toISOString(), experience: "work" }
1020
+ };
1021
+ }
1022
+ return transactionalWorkBlockerResult(base, collected.blocker.code, collected.blocker.mutationBoundary !== "none", true);
1023
+ }
1024
+ function hasTransactionalWorkControl(args) {
1025
+ return args.operationId !== undefined
1026
+ || args.handle !== undefined
1027
+ || args.controlActionId !== undefined
1028
+ || args.expectedAssistantTurnId !== undefined;
1029
+ }
1030
+ async function runTransactionalWorkSteer(args, operations) {
1031
+ const operationId = args.operationId ?? args.handle?.operationId;
1032
+ if (args.prompt.trim().length === 0) {
1033
+ return transactionalWorkSteerUnsupported(operationId, "prompt must be non-empty.", "prompt");
1034
+ }
1035
+ if (args.handle === undefined) {
1036
+ return transactionalWorkSteerUnsupported(operationId, "a durable parent handle is required for transactional Work steer.", "handle");
1037
+ }
1038
+ if (args.handle.surface !== "work") {
1039
+ return transactionalWorkSteerUnsupported(operationId, "the parent handle must belong to the Work surface.", "handle.surface", args.handle);
1040
+ }
1041
+ if (args.handle.phase !== "generating") {
1042
+ return transactionalWorkSteerUnsupported(operationId, "the parent handle must identify a generating Work operation.", "handle.phase", args.handle);
1043
+ }
1044
+ if (args.handle.targetBindingDigest === undefined) {
1045
+ return transactionalWorkSteerUnsupported(operationId, "the parent handle must carry an exact target binding.", "handle.targetBindingDigest", args.handle);
1046
+ }
1047
+ if (operationId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)) {
1048
+ return transactionalWorkSteerUnsupported(operationId, "operationId must be a canonical UUID.", "operationId", args.handle);
1049
+ }
1050
+ if (args.handle.operationId !== operationId) {
1051
+ return transactionalWorkSteerUnsupported(operationId, "operationId must match handle.operationId.", "operationId", args.handle);
1052
+ }
1053
+ if (args.controlActionId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(args.controlActionId)) {
1054
+ return transactionalWorkSteerUnsupported(operationId, "controlActionId must be a canonical UUID.", "controlActionId", args.handle);
1055
+ }
1056
+ if (args.expectedAssistantTurnId === undefined || !/^[A-Za-z0-9._:-]{1,512}$/u.test(args.expectedAssistantTurnId)) {
1057
+ return transactionalWorkSteerUnsupported(operationId, "expectedAssistantTurnId must identify one exact assistant turn.", "expectedAssistantTurnId", args.handle);
1058
+ }
1059
+ if (args.wait !== undefined && args.wait !== false) {
1060
+ return transactionalWorkSteerUnsupported(operationId, "wait is not supported by transactional Work steer; collect the parent operation separately.", "wait", args.handle);
1061
+ }
1062
+ if (args.read !== undefined && args.read !== false) {
1063
+ return transactionalWorkSteerUnsupported(operationId, "read is not supported by transactional Work steer; collect the parent operation separately.", "read", args.handle);
1064
+ }
1065
+ if (args.timeoutMs !== undefined && (!Number.isSafeInteger(args.timeoutMs) || args.timeoutMs < 0 || args.timeoutMs > 86_400_000)) {
1066
+ return transactionalWorkSteerUnsupported(operationId, "timeoutMs must be between 0 and 86400000.", "timeoutMs", args.handle);
1067
+ }
1068
+ const request = {
1069
+ schemaVersion: OPERATION_CONTROL_REQUEST_SCHEMA_VERSION,
1070
+ controlActionId: args.controlActionId,
1071
+ parent: args.handle,
1072
+ action: "steer",
1073
+ expectedAssistantTurnId: args.expectedAssistantTurnId,
1074
+ steerPrompt: args.prompt,
1075
+ ...(args.timeoutMs === undefined ? {} : { timeoutMs: args.timeoutMs })
1076
+ };
1077
+ try {
1078
+ const result = await operations.control(request);
1079
+ const data = {
1080
+ operationId,
1081
+ controlActionId: result.controlActionId,
1082
+ requestDigest: result.requestDigest,
1083
+ handle: args.handle,
1084
+ parentHandle: args.handle,
1085
+ ...(result.kind === "completed" || result.receipt === undefined ? {} : { control: result.receipt }),
1086
+ ...(result.kind === "completed" ? { control: result.receipt } : {})
1087
+ };
1088
+ if (result.kind === "completed") {
1089
+ return {
1090
+ ok: true,
1091
+ status: "ok",
1092
+ data,
1093
+ warnings: ["Work steer was bound to the supplied assistant turn and authorized at most one browser action."],
1094
+ context: { timestamp: new Date().toISOString(), experience: "work" }
1095
+ };
1096
+ }
1097
+ return transactionalWorkBlockerResult(data, result.blocker.code, result.kind === "uncertain" || result.blocker.mutationBoundary !== "none", result.blocker.observationRequired);
1098
+ }
1099
+ catch (error) {
1100
+ return transactionalWorkError(operationId, error, args.controlActionId, args.handle);
1101
+ }
1102
+ }
1103
+ function transactionalWorkStartUnsupported(operationId, message, fieldPath) {
1104
+ const unsupported = transactionalUnsupported(operationId, message, fieldPath);
1105
+ if (unsupported.ok) {
1106
+ return {
1107
+ ok: false,
1108
+ status: "error",
1109
+ data: {
1110
+ task: {},
1111
+ submitted: { submitted: false, submissionState: "not_submitted" },
1112
+ ...(operationId === undefined ? {} : { operationId })
1113
+ },
1114
+ warnings: [],
1115
+ error: { name: "OperationInputError", message, recoverable: false },
1116
+ context: { timestamp: new Date().toISOString(), experience: "work" }
1117
+ };
1118
+ }
1119
+ return {
1120
+ ...unsupported.result,
1121
+ data: {
1122
+ task: {},
1123
+ submitted: { submitted: false, submissionState: "not_submitted" },
1124
+ ...(operationId === undefined ? {} : { operationId })
1125
+ }
1126
+ };
1127
+ }
1128
+ function transactionalWorkSteerUnsupported(operationId, message, fieldPath, handle) {
1129
+ const unsupported = transactionalUnsupported(operationId, message, fieldPath);
1130
+ if (unsupported.ok) {
1131
+ return {
1132
+ ok: false,
1133
+ status: "error",
1134
+ data: {
1135
+ ...(operationId === undefined ? {} : { operationId }),
1136
+ ...(handle === undefined ? {} : { handle, parentHandle: handle })
1137
+ },
1138
+ warnings: [],
1139
+ error: { name: "OperationInputError", message, recoverable: false },
1140
+ context: { timestamp: new Date().toISOString(), experience: "work" }
1141
+ };
1142
+ }
1143
+ return {
1144
+ ...unsupported.result,
1145
+ data: {
1146
+ ...(operationId === undefined ? {} : { operationId }),
1147
+ ...(handle === undefined ? {} : { handle, parentHandle: handle })
1148
+ }
1149
+ };
1150
+ }
1151
+ function transactionalWorkBlockerResult(data, code, uncertain, recoverable) {
1152
+ const message = `Transactional Work operation ${uncertain ? "is uncertain" : "was blocked"} (${code.replaceAll("_", " ")}).`;
1153
+ return {
1154
+ ok: false,
1155
+ status: uncertain ? "partial" : "blocked",
1156
+ data,
1157
+ warnings: [],
1158
+ blocker: {
1159
+ kind: transactionalBlockerKind(code),
1160
+ code,
1161
+ message,
1162
+ resumable: recoverable
1163
+ },
1164
+ context: { timestamp: new Date().toISOString(), experience: "work" }
1165
+ };
1166
+ }
1167
+ function transactionalWorkError(operationId, error, controlActionId, parentHandle) {
1168
+ const code = safeOwnErrorCode(error) ?? "operation_error";
1169
+ const runtimeUnavailable = code === "journal_runtime_unavailable";
1170
+ const serviceUnavailable = JOURNAL_SERVICE_FAILURE_CODES.has(code);
1171
+ const indeterminate = code === "journal_rpc_outcome_indeterminate";
1172
+ const message = runtimeUnavailable ? JOURNAL_RUNTIME_UNAVAILABLE_MESSAGE : serviceUnavailable ? journalServiceFailureMessage(code) : `Transactional Work operation failed (${code.replaceAll("_", " ")}).`;
1173
+ const blocker = code === "adapter_unavailable"
1174
+ || code === "browser_bridge_unavailable"
1175
+ || code === "target_evidence_unavailable"
1176
+ || code === "backend_unavailable"
1177
+ || runtimeUnavailable || serviceUnavailable;
1178
+ const data = parentHandle === undefined
1179
+ ? {
1180
+ operationId,
1181
+ task: {},
1182
+ submitted: { submitted: false, submissionState: "not_submitted" }
1183
+ }
1184
+ : {
1185
+ operationId,
1186
+ controlActionId,
1187
+ handle: parentHandle,
1188
+ parentHandle
1189
+ };
1190
+ return {
1191
+ ok: false,
1192
+ status: indeterminate ? "partial" : blocker ? "blocked" : "error",
1193
+ data,
1194
+ warnings: [],
1195
+ ...(blocker ? { blocker: {
1196
+ kind: transactionalBlockerKind(code), code, message, resumable: !runtimeUnavailable && !serviceUnavailable,
1197
+ ...(runtimeUnavailable ? { remediation: [{
1198
+ label: "Use a supported transactional host",
1199
+ instruction: "Start the packaged journal service in a supported Node host and configure operations.journalService.descriptorPath in the browser client.",
1200
+ userActionRequired: false
1201
+ }] } : serviceUnavailable ? { remediation: [{
1202
+ ...journalServiceRemediation(code),
1203
+ userActionRequired: false
1204
+ }] } : {})
1205
+ } } : {}),
1206
+ error: { name: "OperationError", message, recoverable: blocker && !runtimeUnavailable && !serviceUnavailable },
1207
+ context: { timestamp: new Date().toISOString(), experience: "work" }
1208
+ };
1209
+ }
1210
+ async function freshestOperationHandle(operations, candidate) {
1211
+ try {
1212
+ const inspected = await operations.inspect(candidate);
1213
+ return inspected.handle;
1214
+ }
1215
+ catch {
1216
+ // The submit/collect result is still an authenticated handle and is the
1217
+ // freshest locator available if a browser-free reload is unavailable.
1218
+ return candidate;
1219
+ }
1220
+ }
1221
+ function prepareTransactionalAsk(args, defaults, clientOptions) {
1222
+ const operationId = args.operationId;
1223
+ if (operationId === undefined || !TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)) {
1224
+ return transactionalUnsupported(operationId, "operationId must be a canonical UUID.", "operationId");
1225
+ }
1226
+ if (args.prompt.trim().length === 0) {
1227
+ return transactionalUnsupported(operationId, "prompt must be non-empty.", "prompt");
1228
+ }
1229
+ if (args.download !== undefined) {
1230
+ return transactionalUnsupported(operationId, "download is not supported by the transactional ask path; use operations.collect and an explicit artifact transfer.", "download");
1231
+ }
1232
+ if (args.report !== undefined && args.report !== false) {
1233
+ return transactionalUnsupported(operationId, "report is not supported by the transactional ask path.", "report");
1234
+ }
1235
+ if (clientOptions.reporting?.enabled === true) {
1236
+ return transactionalUnsupported(operationId, "client reporting must be disabled for the transactional ask path.", "reporting");
1237
+ }
1238
+ const target = transactionalTarget(args, defaults);
1239
+ if (!target.ok)
1240
+ return transactionalUnsupported(operationId, target.message, target.fieldPath);
1241
+ const configuration = transactionalConfiguration(args, defaults);
1242
+ if (!configuration.ok)
1243
+ return transactionalUnsupported(operationId, configuration.message, configuration.fieldPath);
1244
+ const wait = args.wait ?? defaults?.wait ?? true;
1245
+ const read = args.read ?? defaults?.read ?? { format: "markdown" };
1246
+ const waitOptions = transactionalWaitOptions(wait, read);
1247
+ if (!waitOptions.ok)
1248
+ return transactionalUnsupported(operationId, waitOptions.message, waitOptions.fieldPath);
1249
+ const files = [
1250
+ ...(args.files ?? []),
1251
+ ...(args.attachments ?? [])
1252
+ ].map(file => typeof file === "string" ? { path: file } : { path: file.path });
1253
+ const request = {
1254
+ schemaVersion: OPERATION_REQUEST_SCHEMA_VERSION,
1255
+ operationId,
1256
+ surface: "chat",
1257
+ prompt: args.prompt,
1258
+ target: target.target,
1259
+ ...(configuration.value === undefined ? {} : { configuration: configuration.value }),
1260
+ ...(files.length === 0 ? {} : { files }),
1261
+ capture: {
1262
+ responseContent: waitOptions.responseContent,
1263
+ responseFormat: waitOptions.responseFormat,
1264
+ artifacts: "receipt_only"
1265
+ }
1266
+ };
1267
+ return {
1268
+ ok: true,
1269
+ value: {
1270
+ request,
1271
+ options: waitOptions.options,
1272
+ responseFormat: waitOptions.responseFormat,
1273
+ ...(waitOptions.maxResponseChars === undefined ? {} : { maxResponseChars: waitOptions.maxResponseChars })
1274
+ }
1275
+ };
1276
+ }
1277
+ function transactionalTarget(args, defaults) {
1278
+ const thread = args.thread ?? defaults?.thread;
1279
+ const existingTab = args.existingTab ?? defaults?.existingTab;
1280
+ const preferExistingTab = args.preferExistingTab ?? defaults?.preferExistingTab;
1281
+ if (thread !== undefined) {
1282
+ if (existingTab !== undefined || preferExistingTab === true) {
1283
+ return {
1284
+ ok: false,
1285
+ message: "thread cannot be combined with existingTab or preferExistingTab on the transactional ask path.",
1286
+ fieldPath: "thread"
1287
+ };
1288
+ }
1289
+ return targetFromWorkflowThread(thread);
1290
+ }
1291
+ if (preferExistingTab === true) {
1292
+ if (existingTab !== undefined) {
1293
+ return {
1294
+ ok: false,
1295
+ message: "preferExistingTab cannot be combined with existingTab on the transactional ask path.",
1296
+ fieldPath: "preferExistingTab"
1297
+ };
1298
+ }
1299
+ return { ok: true, target: { type: "selected_tab" } };
1300
+ }
1301
+ if (existingTab !== undefined)
1302
+ return targetFromExistingTab(existingTab);
1303
+ return { ok: true, target: { type: "new" } };
1304
+ }
1305
+ function targetFromWorkflowThread(thread) {
1306
+ if (isTypedThread(thread)) {
1307
+ switch (thread.type) {
1308
+ case "new":
1309
+ return { ok: true, target: { type: "new" } };
1310
+ case "current":
1311
+ return { ok: true, target: { type: "selected_tab" } };
1312
+ case "url":
1313
+ return thread.url.length > 0
1314
+ ? { ok: true, target: { type: "url", url: thread.url } }
1315
+ : { ok: false, message: "thread.url must be non-empty.", fieldPath: "thread.url" };
1316
+ case "conversationId":
1317
+ case "conversation_id":
1318
+ return thread.conversationId.length > 0
1319
+ ? { ok: true, target: { type: "conversation_id", conversationId: thread.conversationId } }
1320
+ : { ok: false, message: "thread.conversationId must be non-empty.", fieldPath: "thread.conversationId" };
1321
+ case "search":
1322
+ case "title":
1323
+ return {
1324
+ ok: false,
1325
+ message: "thread search/title selection is not supported by the transactional ask path; supply a URL or conversationId.",
1326
+ fieldPath: "thread"
1327
+ };
1328
+ }
1329
+ }
1330
+ if (thread.url !== undefined) {
1331
+ return thread.url.length > 0
1332
+ ? { ok: true, target: { type: "url", url: thread.url } }
1333
+ : { ok: false, message: "thread.url must be non-empty.", fieldPath: "thread.url" };
1334
+ }
1335
+ if (thread.conversationId !== undefined) {
1336
+ return thread.conversationId.length > 0
1337
+ ? { ok: true, target: { type: "conversation_id", conversationId: thread.conversationId } }
1338
+ : { ok: false, message: "thread.conversationId must be non-empty.", fieldPath: "thread.conversationId" };
1339
+ }
1340
+ if (thread.query !== undefined || thread.title !== undefined) {
1341
+ return {
1342
+ ok: false,
1343
+ message: "thread search/title selection is not supported by the transactional ask path; supply a URL or conversationId.",
1344
+ fieldPath: "thread"
1345
+ };
1346
+ }
1347
+ return { ok: true, target: { type: "new" } };
1348
+ }
1349
+ function targetFromExistingTab(existingTab) {
1350
+ if (existingTab === true)
1351
+ return { ok: true, target: { type: "selected_tab" } };
1352
+ if (existingTab === false)
1353
+ return { ok: true, target: { type: "new" } };
1354
+ if (existingTab.ifMissing !== undefined && existingTab.ifMissing !== "block") {
1355
+ return { ok: false, message: "existingTab.ifMissing must be block on the transactional ask path.", fieldPath: "existingTab.ifMissing" };
1356
+ }
1357
+ if (existingTab.ifMultiple !== undefined && existingTab.ifMultiple !== "block") {
1358
+ return { ok: false, message: "existingTab.ifMultiple must be block on the transactional ask path.", fieldPath: "existingTab.ifMultiple" };
1359
+ }
1360
+ if (existingTab.requireChatGPT === false) {
1361
+ return { ok: false, message: "existingTab.requireChatGPT=false is not supported by the transactional ask path.", fieldPath: "existingTab.requireChatGPT" };
1362
+ }
1363
+ const target = existingTab.target;
1364
+ if (target === undefined || target.type === "selected")
1365
+ return { ok: true, target: { type: "selected_tab" } };
1366
+ switch (target.type) {
1367
+ case "tabId":
1368
+ return target.tabId.length > 0
1369
+ ? { ok: true, target: { type: "tab_id", tabId: target.tabId } }
1370
+ : { ok: false, message: "existingTab.target.tabId must be non-empty.", fieldPath: "existingTab.target.tabId" };
1371
+ case "conversationId":
1372
+ case "conversation_id":
1373
+ return target.conversationId.length > 0
1374
+ ? { ok: true, target: { type: "conversation_id", conversationId: target.conversationId } }
1375
+ : { ok: false, message: "existingTab.target.conversationId must be non-empty.", fieldPath: "existingTab.target.conversationId" };
1376
+ case "url":
1377
+ return target.url.length > 0
1378
+ ? { ok: true, target: { type: "url", url: target.url } }
1379
+ : { ok: false, message: "existingTab.target.url must be non-empty.", fieldPath: "existingTab.target.url" };
1380
+ case "title":
1381
+ return { ok: false, message: "existingTab title selection is not supported by the transactional ask path; supply a tabId, URL, or conversationId.", fieldPath: "existingTab.target" };
1382
+ }
1383
+ }
1384
+ function transactionalConfiguration(args, defaults) {
1385
+ const experience = args.experience ?? defaults?.experience;
1386
+ if (experience === "work") {
1387
+ return { ok: false, message: "experience=work is not supported by the transactional chat ask path.", fieldPath: "experience" };
1388
+ }
1389
+ const selected = args.configuration ?? defaults?.configuration;
1390
+ const mode = args.mode ?? defaults?.mode;
1391
+ if (mode?.timeoutMs !== undefined) {
1392
+ return { ok: false, message: "mode.timeoutMs is not supported by the transactional ask path.", fieldPath: "mode.timeoutMs" };
1393
+ }
1394
+ const values = {};
1395
+ if (experience !== undefined)
1396
+ values.experience = experience;
1397
+ const model = mergeConfigurationValue("model", selected?.model, mode?.model);
1398
+ if (!model.ok)
1399
+ return model;
1400
+ if (model.value !== undefined)
1401
+ values.model = model.value;
1402
+ const modelVersion = mergeConfigurationValue("modelVersion", selected?.modelVersion ?? selected?.version, mode?.modelVersion ?? mode?.version);
1403
+ if (!modelVersion.ok)
1404
+ return modelVersion;
1405
+ if (modelVersion.value !== undefined)
1406
+ values.modelVersion = modelVersion.value;
1407
+ const additional = {};
1408
+ for (const [axis, first, second] of [
1409
+ ["intelligence", selected?.intelligence, mode?.intelligence],
1410
+ ["effort", selected?.effort, mode?.effort],
1411
+ ["speed", selected?.speed, undefined]
1412
+ ]) {
1413
+ const merged = mergeConfigurationValue(axis, first, second);
1414
+ if (!merged.ok)
1415
+ return merged;
1416
+ if (merged.value !== undefined)
1417
+ additional[axis] = merged.value;
1418
+ }
1419
+ const tools = args.tools;
1420
+ if (tools !== undefined) {
1421
+ for (const [index, tool] of tools.entries()) {
1422
+ if (tool.tool.trim().length === 0) {
1423
+ return { ok: false, message: "tool must be non-empty.", fieldPath: `tools[${index}].tool` };
1424
+ }
1425
+ if (tool.timeoutMs !== undefined) {
1426
+ return { ok: false, message: "tool timeoutMs is not supported by the transactional ask path.", fieldPath: `tools[${index}].timeoutMs` };
1427
+ }
1428
+ }
1429
+ if (tools.length > 0)
1430
+ values.tools = tools.map(tool => tool.tool);
1431
+ }
1432
+ if (Object.keys(additional).length > 0)
1433
+ values.additional = additional;
1434
+ return Object.keys(values).length === 0 ? { ok: true } : { ok: true, value: values };
1435
+ }
1436
+ function mergeConfigurationValue(axis, first, second) {
1437
+ if (first !== undefined && second !== undefined && first !== second) {
1438
+ return {
1439
+ ok: false,
1440
+ message: `configuration and mode disagree for ${axis}; provide one value.`,
1441
+ fieldPath: axis === "modelVersion" ? "configuration.modelVersion" : `configuration.${axis}`
1442
+ };
1443
+ }
1444
+ if (first === undefined && second === undefined)
1445
+ return { ok: true };
1446
+ const value = first ?? second;
1447
+ return value === undefined ? { ok: true } : { ok: true, value };
1448
+ }
1449
+ function transactionalWaitOptions(wait, read) {
1450
+ const readRequested = read === true || typeof read === "object";
1451
+ const responseFormat = typeof read === "object" && read.format === "text"
1452
+ ? "text"
1453
+ : "markdown";
1454
+ const maxResponseChars = typeof read === "object" ? read.maxChars : undefined;
1455
+ if (maxResponseChars !== undefined && (!Number.isSafeInteger(maxResponseChars) || maxResponseChars < 0 || maxResponseChars > 8 * 1024 * 1024)) {
1456
+ return { ok: false, message: "read.maxChars must be between 0 and 8388608.", fieldPath: "read.maxChars" };
1457
+ }
1458
+ if (typeof read === "object") {
1459
+ if (read.role !== undefined && read.role !== "assistant") {
1460
+ return { ok: false, message: "read.role=user is not supported by the transactional ask path.", fieldPath: "read.role" };
1461
+ }
1462
+ if (read.format !== undefined && read.format !== "markdown" && read.format !== "text") {
1463
+ return { ok: false, message: "read.format must be markdown or text on the transactional ask path.", fieldPath: "read.format" };
1464
+ }
1465
+ }
1466
+ if (typeof wait === "object") {
1467
+ if (wait.timeoutMs !== undefined && (!Number.isSafeInteger(wait.timeoutMs) || wait.timeoutMs < 1 || wait.timeoutMs > 86_400_000)) {
1468
+ return { ok: false, message: "wait.timeoutMs must be between 1 and 86400000.", fieldPath: "wait.timeoutMs" };
1469
+ }
1470
+ if (wait.pollMs !== undefined && (!Number.isSafeInteger(wait.pollMs) || wait.pollMs < 0 || wait.pollMs > 60_000)) {
1471
+ return { ok: false, message: "wait.pollMs must be between 0 and 60000.", fieldPath: "wait.pollMs" };
1472
+ }
1473
+ if (wait.responseContent !== undefined && wait.responseContent !== "include" && wait.responseContent !== "metadata") {
1474
+ return { ok: false, message: "wait.responseContent must be include or metadata.", fieldPath: "wait.responseContent" };
1475
+ }
1476
+ for (const [key, value] of [
1477
+ ["afterTurnCount", wait.afterTurnCount],
1478
+ ["afterAssistantTurnCount", wait.afterAssistantTurnCount],
1479
+ ["afterStep", wait.afterStep],
1480
+ ["stableMs", wait.stableMs],
1481
+ ["mode", wait.mode]
1482
+ ]) {
1483
+ if (value !== undefined) {
1484
+ return { ok: false, message: `wait.${key} is not supported by the transactional ask path.`, fieldPath: `wait.${key}` };
1485
+ }
1486
+ }
1487
+ }
1488
+ const responseContent = typeof wait === "object" && wait.responseContent !== undefined
1489
+ ? wait.responseContent
1490
+ : readRequested ? "include" : "metadata";
1491
+ const options = {
1492
+ wait: wait !== false,
1493
+ responseContent,
1494
+ ...(typeof wait === "object" && wait.timeoutMs === undefined ? {} : typeof wait === "object" ? { timeoutMs: wait.timeoutMs } : {}),
1495
+ ...(typeof wait === "object" && wait.pollMs === undefined ? {} : typeof wait === "object" ? { pollIntervalMs: wait.pollMs } : {})
1496
+ };
1497
+ return {
1498
+ ok: true,
1499
+ options,
1500
+ responseContent,
1501
+ readRequested,
1502
+ responseFormat,
1503
+ ...(maxResponseChars === undefined ? {} : { maxResponseChars })
1504
+ };
1505
+ }
1506
+ function transactionalUnsupported(operationId, message, fieldPath) {
1507
+ const data = operationId === undefined ? {} : { operationId };
1508
+ return {
1509
+ ok: false,
1510
+ result: {
1511
+ ok: false,
1512
+ status: "unsupported",
1513
+ data,
1514
+ warnings: [],
1515
+ error: { name: "OperationInputError", message, recoverable: false },
1516
+ blocker: { kind: "unknown", code: "unsupported_operation_input", fieldPath, message },
1517
+ context: { timestamp: new Date().toISOString() }
1518
+ }
1519
+ };
1520
+ }
1521
+ function transactionalAskCommandResult(prepared, run, handle) {
1522
+ const base = {
1523
+ operationId: handle.operationId,
1524
+ responseFormat: prepared.responseFormat,
1525
+ handle,
1526
+ requestDigest: handle.requestDigest
1527
+ };
1528
+ const submission = run.submit.submission;
1529
+ if (submission.kind === "blocked" || submission.kind === "uncertain" || submission.kind === "cancelled") {
1530
+ return transactionalBlockerResult({
1531
+ ...base,
1532
+ submissionState: transactionalSubmissionState(handle, submission.blocker.mutationBoundary),
1533
+ complete: false,
1534
+ completionState: transactionalCompletionState(handle.phase),
1535
+ generationActive: handle.phase === "generating"
1536
+ }, submission.blocker.code, submission.blocker.mutationBoundary !== "none", submission.blocker.observationRequired);
1537
+ }
1538
+ if (run.collect === undefined) {
1539
+ if (submission.kind === "completed_receipt") {
1540
+ return {
1541
+ ok: true,
1542
+ status: "ok",
1543
+ data: { ...base, submissionState: "submitted", complete: true, completionState: "complete", generationActive: false },
1544
+ warnings: [],
1545
+ context: { timestamp: new Date().toISOString() }
1546
+ };
1547
+ }
1548
+ return {
1549
+ ok: false,
1550
+ status: "partial",
1551
+ data: { ...base, pending: true, complete: false, completionState: "generating", generationActive: true, submissionState: "submitted_generating" },
1552
+ warnings: [],
1553
+ context: { timestamp: new Date().toISOString() }
1554
+ };
1555
+ }
1556
+ const collected = run.collect;
1557
+ if (collected.kind === "completed") {
1558
+ const rawText = prepared.request.capture?.responseContent === "include" ? collected.response.rawText : undefined;
1559
+ const responseText = rawText === undefined
1560
+ ? undefined
1561
+ : prepared.maxResponseChars === undefined
1562
+ ? rawText
1563
+ : rawText.slice(0, prepared.maxResponseChars);
1564
+ const data = {
1565
+ ...base,
1566
+ submissionState: "submitted",
1567
+ complete: true,
1568
+ completionState: "complete",
1569
+ generationActive: false,
1570
+ ...(responseText === undefined ? {} : { responseText }),
1571
+ ...(collected.response.text?.digest === undefined ? {} : { responseDigest: collected.response.text.digest }),
1572
+ ...(collected.response.text?.bytes === undefined ? {} : { responseBytes: collected.response.text.bytes }),
1573
+ artifacts: collected.response.artifacts
1574
+ };
1575
+ return { ok: true, status: "ok", data, warnings: [], context: { timestamp: new Date().toISOString() } };
1576
+ }
1577
+ if (collected.kind === "pending") {
1578
+ return {
1579
+ ok: false,
1580
+ status: "partial",
1581
+ data: { ...base, pending: true, complete: false, completionState: collected.phase === "generating" ? "generating" : "unknown", generationActive: collected.phase === "generating", submissionState: "submitted_generating" },
1582
+ warnings: [],
1583
+ context: { timestamp: new Date().toISOString() }
1584
+ };
1585
+ }
1586
+ return transactionalBlockerResult({
1587
+ ...base,
1588
+ submissionState: transactionalSubmissionState(handle, collected.blocker.mutationBoundary),
1589
+ complete: false,
1590
+ completionState: transactionalCompletionState(handle.phase),
1591
+ generationActive: handle.phase === "generating"
1592
+ }, collected.blocker.code, collected.blocker.mutationBoundary !== "none", true);
1593
+ }
1594
+ function transactionalSubmissionState(handle, boundary) {
1595
+ if (["submitted", "generating", "capturing", "completed"].includes(handle.phase)) {
1596
+ return handle.phase === "generating" ? "submitted_generating" : "submitted";
1597
+ }
1598
+ return boundary === "send_may_have_occurred" || boundary === "control_may_have_occurred"
1599
+ ? "submitted_unconfirmed"
1600
+ : "not_submitted";
1601
+ }
1602
+ function transactionalCompletionState(phase) {
1603
+ if (phase === "completed")
1604
+ return "complete";
1605
+ if (phase === "generating")
1606
+ return "generating";
1607
+ if (phase === "uncertain" || phase === "capturing")
1608
+ return "partial";
1609
+ return "unknown";
1610
+ }
1611
+ function transactionalBlockerResult(data, code, uncertain, recoverable) {
1612
+ const kind = transactionalBlockerKind(code);
1613
+ const message = `Transactional operation ${uncertain ? "is uncertain" : "was blocked"} (${code.replaceAll("_", " ")}).`;
1614
+ return {
1615
+ ok: false,
1616
+ status: uncertain ? "partial" : "blocked",
1617
+ data,
1618
+ warnings: [],
1619
+ blocker: { kind, code, message, resumable: recoverable },
1620
+ context: { timestamp: new Date().toISOString() }
1621
+ };
1622
+ }
1623
+ function transactionalBlockerKind(code) {
1624
+ if (code === "browser_bridge_unavailable")
1625
+ return "browser_bridge_unavailable";
1626
+ if (code === "login_required")
1627
+ return "login_required";
1628
+ if (code === "captcha")
1629
+ return "captcha";
1630
+ if (code === "rate_limited")
1631
+ return "rate_limit";
1632
+ if (code === "permission_required" || code === "input_file_changed")
1633
+ return "permission";
1634
+ if (code === "needs_confirmation")
1635
+ return "confirmation";
1636
+ if (code.includes("artifact"))
1637
+ return "artifact_unavailable";
1638
+ if (code.includes("selector") || code.includes("configuration") || code.includes("target") || code.includes("turn"))
1639
+ return "selector_drift";
1640
+ if (code.includes("file") || code.includes("attachment"))
1641
+ return "upload_failed";
1642
+ return "unknown";
1643
+ }
1644
+ const JOURNAL_SERVICE_FAILURE_CODES = new Set([
1645
+ "journal_rpc_unavailable", "journal_rpc_authentication_failed", "journal_rpc_protocol_error",
1646
+ "journal_rpc_limit_exceeded", "journal_rpc_outcome_indeterminate", "journal_rpc_request_rejected",
1647
+ "journal_rpc_unsupported_platform"
1648
+ ]);
1649
+ function journalServiceFailureMessage(code) {
1650
+ if (code === "journal_rpc_unsupported_platform") {
1651
+ return "The private-file journal service requires POSIX file ownership and permissions and is unavailable on Windows.";
1652
+ }
1653
+ return code === "journal_rpc_outcome_indeterminate"
1654
+ ? "The journal service connection ended without confirming a durable write. Preserve the operation identity and reconcile its state before any further browser action."
1655
+ : "The configured journal service could not provide authenticated operation state. Restore its private connection before continuing.";
1656
+ }
1657
+ function journalServiceRemediation(code) {
1658
+ return code === "journal_rpc_unsupported_platform" ? {
1659
+ label: "Use a supported journal host",
1660
+ instruction: "On Windows, use the local journal in an ordinary Node browser host without operations.journalService. The private-file journal service requires a POSIX host. Preserve any existing operation identity."
1661
+ } : {
1662
+ label: "Reconnect the same journal",
1663
+ instruction: "Restore the private journal-service connection, then inspect or reconcile the same operation identity. Never create a new operation ID to retry an uncertain Send."
1664
+ };
1665
+ }
1666
+ function transactionalAskError(operationId, error) {
1667
+ const code = safeOwnErrorCode(error) ?? "operation_error";
1668
+ const runtimeUnavailable = code === "journal_runtime_unavailable";
1669
+ const serviceUnavailable = JOURNAL_SERVICE_FAILURE_CODES.has(code);
1670
+ const indeterminate = code === "journal_rpc_outcome_indeterminate";
1671
+ const blocker = runtimeUnavailable || serviceUnavailable || code === "adapter_unavailable" || code === "browser_bridge_unavailable" || code === "target_evidence_unavailable";
1672
+ const message = runtimeUnavailable ? JOURNAL_RUNTIME_UNAVAILABLE_MESSAGE : serviceUnavailable ? journalServiceFailureMessage(code) : `Transactional operation failed (${code.replaceAll("_", " ")}).`;
1673
+ return {
1674
+ ok: false,
1675
+ status: indeterminate ? "partial" : blocker ? "blocked" : "error",
1676
+ data: { operationId },
1677
+ warnings: [],
1678
+ ...(blocker ? { blocker: {
1679
+ kind: transactionalBlockerKind(code), code, message, resumable: !runtimeUnavailable && !serviceUnavailable,
1680
+ ...(runtimeUnavailable ? { remediation: [{
1681
+ label: "Use a supported transactional host",
1682
+ instruction: "Start the packaged journal service in a supported Node host and configure operations.journalService.descriptorPath in the browser client.",
1683
+ userActionRequired: false
1684
+ }] } : serviceUnavailable ? { remediation: [{
1685
+ ...journalServiceRemediation(code),
1686
+ userActionRequired: false
1687
+ }] } : {})
1688
+ } } : {}),
1689
+ error: { name: "OperationError", message, recoverable: blocker && !runtimeUnavailable && !serviceUnavailable },
1690
+ context: { timestamp: new Date().toISOString() }
1691
+ };
508
1692
  }
509
1693
  function planAskWorkflow(args, defaults = {}) {
510
1694
  const thread = args.thread ?? { type: "new" };
@@ -786,6 +1970,42 @@ function normalizeFileInputs(files) {
786
1970
  function isRecord(value) {
787
1971
  return value !== null && typeof value === "object" && !Array.isArray(value);
788
1972
  }
1973
+ /**
1974
+ * Read only a bounded own data property. Provider/runtime failures are an
1975
+ * untrusted boundary: consulting an inherited property or accessor can run
1976
+ * arbitrary code and can surface private diagnostics while formatting a
1977
+ * public result.
1978
+ */
1979
+ function safeOwnErrorCode(value) {
1980
+ if (!isRecord(value))
1981
+ return undefined;
1982
+ let descriptor;
1983
+ try {
1984
+ descriptor = Object.getOwnPropertyDescriptor(value, "code");
1985
+ }
1986
+ catch {
1987
+ return undefined;
1988
+ }
1989
+ if (descriptor === undefined || !("value" in descriptor))
1990
+ return undefined;
1991
+ return typeof descriptor.value === "string" && /^[a-z][a-z0-9_]{0,63}$/u.test(descriptor.value)
1992
+ ? descriptor.value
1993
+ : undefined;
1994
+ }
1995
+ function readOperationIdFromUnknown(value) {
1996
+ if (!isRecord(value))
1997
+ return undefined;
1998
+ try {
1999
+ const descriptor = Object.getOwnPropertyDescriptor(value, "operationId");
2000
+ const operationId = descriptor !== undefined && "value" in descriptor ? descriptor.value : undefined;
2001
+ return typeof operationId === "string" && TRANSACTIONAL_OPERATION_ID_PATTERN.test(operationId)
2002
+ ? operationId
2003
+ : undefined;
2004
+ }
2005
+ catch {
2006
+ return undefined;
2007
+ }
2008
+ }
789
2009
  function stringInput(input, key) {
790
2010
  const value = input[key];
791
2011
  if (typeof value !== "string" || value.length === 0) {