codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +43 -0
- package/contracts/v1/fixtures/backend-capabilities.json +40 -1
- package/contracts/v1/fixtures/backend-compatibility.json +21 -0
- package/contracts/v1/fixtures/backend-version.json +6 -1
- package/contracts/v1/fixtures/download-blocked-by-browser.json +22 -0
- package/contracts/v1/fixtures/download-receipt-failed.json +22 -0
- package/contracts/v1/fixtures/download-receipt-timeout.json +22 -0
- package/contracts/v1/fixtures/journal-rpc-indeterminate.json +32 -0
- package/contracts/v1/fixtures/journal-rpc-unsupported-platform.json +32 -0
- package/contracts/v1/fixtures/journal-runtime-unavailable.json +32 -0
- package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
- package/contracts/v1/fixtures/operation-action.json +13 -0
- package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
- package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
- package/contracts/v1/fixtures/operation-blocker.json +10 -0
- package/contracts/v1/fixtures/operation-collect-request.json +17 -0
- package/contracts/v1/fixtures/operation-collect-result.json +42 -0
- package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
- package/contracts/v1/fixtures/operation-control-request.json +18 -0
- package/contracts/v1/fixtures/operation-control-result.json +31 -0
- package/contracts/v1/fixtures/operation-event.json +18 -0
- package/contracts/v1/fixtures/operation-handle.json +10 -0
- package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
- package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
- package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
- package/contracts/v1/fixtures/operation-receipt.json +32 -0
- package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
- package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
- package/contracts/v1/fixtures/operation-request.json +33 -0
- package/contracts/v1/fixtures/operation-state.json +144 -0
- package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
- package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
- package/contracts/v1/fixtures/operation-submit-result.json +16 -0
- package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
- package/contracts/v1/manifest.json +185 -1
- package/contracts/v1/parity-suite.json +106 -8
- package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
- package/contracts/v1/schemas/backend-request.schema.json +8 -2
- package/contracts/v1/schemas/capabilities.schema.json +81 -1
- package/contracts/v1/schemas/manifest.schema.json +57 -0
- package/contracts/v1/schemas/operation-action.schema.json +155 -0
- package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
- package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
- package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
- package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
- package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
- package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
- package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
- package/contracts/v1/schemas/operation-event.schema.json +756 -0
- package/contracts/v1/schemas/operation-handle.schema.json +55 -0
- package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
- package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
- package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
- package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
- package/contracts/v1/schemas/operation-request.schema.json +158 -0
- package/contracts/v1/schemas/operation-state.schema.json +691 -0
- package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
- package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
- package/contracts/v1/surface-drift-policy.json +50 -0
- package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
- package/dist/codex-chatgpt-control-backend.mjs +45882 -9361
- package/dist/codex-chatgpt-control-journal.mjs +5547 -0
- package/dist/codex-chatgpt-control-live-smoke.bundle.mjs +51677 -0
- package/dist/codex-chatgpt-control-release-canary.bundle.mjs +52104 -0
- package/dist/codex-chatgpt-control.bundle.mjs +48392 -10260
- package/dist/src/backend/client.d.ts +103 -2
- package/dist/src/backend/client.js +1436 -97
- package/dist/src/backend/compatibility.d.ts +12 -0
- package/dist/src/backend/compatibility.js +208 -0
- package/dist/src/backend/protocol.d.ts +84 -1
- package/dist/src/backend/protocol.js +31 -5
- package/dist/src/backend/runtime-identity.d.ts +10 -0
- package/dist/src/backend/runtime-identity.js +141 -0
- package/dist/src/backend/session.d.ts +10 -2
- package/dist/src/backend/session.js +478 -7
- package/dist/src/backend/stdio-server.d.ts +4 -1
- package/dist/src/backend/stdio-server.js +240 -28
- package/dist/src/browser/active-composer-file-input.d.ts +9 -0
- package/dist/src/browser/active-composer-file-input.js +33 -0
- package/dist/src/browser/attach.d.ts +4 -1
- package/dist/src/browser/attach.js +136 -28
- package/dist/src/browser/chatgpt-url.d.ts +2 -0
- package/dist/src/browser/chatgpt-url.js +9 -0
- package/dist/src/browser/downloads.d.ts +17 -1
- package/dist/src/browser/downloads.js +150 -28
- package/dist/src/browser/page-state.js +6 -0
- package/dist/src/client.d.ts +43 -0
- package/dist/src/client.js +1279 -59
- package/dist/src/commands/artifacts.js +6 -4
- package/dist/src/commands/chat-popover.d.ts +37 -0
- package/dist/src/commands/chat-popover.js +537 -0
- package/dist/src/commands/configuration.d.ts +5 -0
- package/dist/src/commands/configuration.js +134 -21
- package/dist/src/commands/context.js +10 -3
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +27 -4
- package/dist/src/commands/experience.d.ts +5 -0
- package/dist/src/commands/experience.js +104 -21
- package/dist/src/commands/files.js +14 -33
- package/dist/src/commands/modes.js +85 -21
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +669 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +19 -6
- package/dist/src/commands/work.js +31 -0
- package/dist/src/dom/composer-text.d.ts +6 -0
- package/dist/src/dom/composer-text.js +85 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +22 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +9 -0
- package/dist/src/operations/artifact-output.d.ts +69 -0
- package/dist/src/operations/artifact-output.js +1748 -0
- package/dist/src/operations/artifact-stream.d.ts +19 -0
- package/dist/src/operations/artifact-stream.js +35 -0
- package/dist/src/operations/artifact-transfer.d.ts +149 -0
- package/dist/src/operations/artifact-transfer.js +1312 -0
- package/dist/src/operations/browser-adapter.d.ts +167 -0
- package/dist/src/operations/browser-adapter.js +2103 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1256 -0
- package/dist/src/operations/browser-target.d.ts +105 -0
- package/dist/src/operations/browser-target.js +531 -0
- package/dist/src/operations/canonical.d.ts +4 -0
- package/dist/src/operations/canonical.js +412 -0
- package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
- package/dist/src/operations/chatgpt-runtime.js +1232 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1145 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -0
- package/dist/src/operations/configuration-routing.d.ts +4 -0
- package/dist/src/operations/configuration-routing.js +14 -0
- package/dist/src/operations/control.d.ts +372 -0
- package/dist/src/operations/control.js +1498 -0
- package/dist/src/operations/file-identity.d.ts +33 -0
- package/dist/src/operations/file-identity.js +153 -0
- package/dist/src/operations/handle.d.ts +19 -0
- package/dist/src/operations/handle.js +727 -0
- package/dist/src/operations/index.d.ts +25 -0
- package/dist/src/operations/index.js +25 -0
- package/dist/src/operations/journal-authority.d.ts +12 -0
- package/dist/src/operations/journal-authority.js +1 -0
- package/dist/src/operations/journal-rpc-client.d.ts +12 -0
- package/dist/src/operations/journal-rpc-client.js +225 -0
- package/dist/src/operations/journal-rpc-protocol.d.ts +32 -0
- package/dist/src/operations/journal-rpc-protocol.js +214 -0
- package/dist/src/operations/journal-rpc-server.d.ts +15 -0
- package/dist/src/operations/journal-rpc-server.js +272 -0
- package/dist/src/operations/journal.d.ts +143 -0
- package/dist/src/operations/journal.js +1957 -0
- package/dist/src/operations/production-attachments.d.ts +118 -0
- package/dist/src/operations/production-attachments.js +1035 -0
- package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
- package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
- package/dist/src/operations/production-chatgpt-attachments.d.ts +80 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1639 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1544 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +856 -0
- package/dist/src/operations/production-work-steer.d.ts +203 -0
- package/dist/src/operations/production-work-steer.js +1218 -0
- package/dist/src/operations/recovery.d.ts +80 -0
- package/dist/src/operations/recovery.js +174 -0
- package/dist/src/operations/runtime-adapter.d.ts +126 -0
- package/dist/src/operations/runtime-adapter.js +802 -0
- package/dist/src/operations/send-once.d.ts +224 -0
- package/dist/src/operations/send-once.js +1081 -0
- package/dist/src/operations/service.d.ts +286 -0
- package/dist/src/operations/service.js +2831 -0
- package/dist/src/operations/staging.d.ts +136 -0
- package/dist/src/operations/staging.js +630 -0
- package/dist/src/operations/state-machine.d.ts +29 -0
- package/dist/src/operations/state-machine.js +2047 -0
- package/dist/src/operations/submission.d.ts +423 -0
- package/dist/src/operations/submission.js +1676 -0
- package/dist/src/operations/transactional-chat-power.d.ts +17 -0
- package/dist/src/operations/transactional-chat-power.js +212 -0
- package/dist/src/operations/turn-ownership.d.ts +202 -0
- package/dist/src/operations/turn-ownership.js +700 -0
- package/dist/src/operations/types.d.ts +454 -0
- package/dist/src/operations/types.js +20 -0
- package/dist/src/operations/wire-requests.d.ts +21 -0
- package/dist/src/operations/wire-requests.js +396 -0
- package/dist/src/operations/wire-results.d.ts +97 -0
- package/dist/src/operations/wire-results.js +818 -0
- package/dist/src/runner/responses.d.ts +3 -1
- package/dist/src/runner/responses.js +19 -1
- package/dist/src/runner/result.js +227 -55
- package/dist/src/runner/types.d.ts +12 -0
- package/dist/src/runtime/command-routing.d.ts +149 -0
- package/dist/src/runtime/command-routing.js +431 -0
- package/dist/src/runtime/coordinated-browser.d.ts +24 -0
- package/dist/src/runtime/coordinated-browser.js +315 -0
- package/dist/src/runtime/coordinated-page.d.ts +41 -0
- package/dist/src/runtime/coordinated-page.js +651 -0
- package/dist/src/runtime/operation-context.d.ts +142 -0
- package/dist/src/runtime/operation-context.js +410 -0
- package/dist/src/runtime/runtime-session.d.ts +95 -0
- package/dist/src/runtime/runtime-session.js +314 -0
- package/dist/src/runtime/tab-coordinator.d.ts +200 -0
- package/dist/src/runtime/tab-coordinator.js +1200 -0
- package/dist/src/runtime/value-boundaries.d.ts +18 -0
- package/dist/src/runtime/value-boundaries.js +49 -0
- package/dist/src/safety/blockers.js +8 -1
- package/dist/src/safety/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/capture-surface-profile.js +7 -2
- package/dist/src/scripts/journal-server.d.ts +2 -0
- package/dist/src/scripts/journal-server.js +37 -0
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +42 -1
- package/dist/src/scripts/live-smoke/scenarios.js +147 -30
- package/dist/src/scripts/live-smoke/transactional.d.ts +17 -0
- package/dist/src/scripts/live-smoke/transactional.js +254 -0
- package/dist/src/scripts/live-smoke/types.d.ts +3 -0
- package/dist/src/scripts/release-canary-module.js +17 -4
- package/dist/src/types.d.ts +38 -1
- package/package.json +8 -3
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/2026-09-06-journal-service.md +140 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +176 -0
- package/references/python-parity.md +137 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
package/dist/src/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|