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