codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +27 -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/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 +155 -1
- package/contracts/v1/parity-suite.json +96 -1
- 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 +43897 -9094
- package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
- 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/downloads.d.ts +2 -0
- package/dist/src/client.d.ts +38 -0
- package/dist/src/client.js +1222 -59
- package/dist/src/commands/configuration.js +13 -7
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +26 -3
- package/dist/src/commands/experience.js +35 -0
- package/dist/src/commands/files.js +4 -27
- package/dist/src/commands/modes.js +49 -17
- package/dist/src/commands/power-discovery.d.ts +130 -0
- package/dist/src/commands/power-discovery.js +627 -0
- package/dist/src/commands/project-sources.js +325 -31
- package/dist/src/commands/sequence.js +59 -32
- package/dist/src/commands/session.js +7 -2
- package/dist/src/commands/work.js +31 -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 +161 -0
- package/dist/src/operations/browser-adapter.js +2023 -0
- package/dist/src/operations/browser-observation.d.ts +110 -0
- package/dist/src/operations/browser-observation.js +1208 -0
- package/dist/src/operations/browser-target.d.ts +103 -0
- package/dist/src/operations/browser-target.js +489 -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 +1150 -0
- package/dist/src/operations/client.d.ts +138 -0
- package/dist/src/operations/client.js +1143 -0
- package/dist/src/operations/collector.d.ts +195 -0
- package/dist/src/operations/collector.js +1039 -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 +22 -0
- package/dist/src/operations/index.js +22 -0
- package/dist/src/operations/journal.d.ts +142 -0
- package/dist/src/operations/journal.js +1931 -0
- package/dist/src/operations/production-attachments.d.ts +116 -0
- package/dist/src/operations/production-attachments.js +1018 -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 +71 -0
- package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
- package/dist/src/operations/production-configuration.d.ts +42 -0
- package/dist/src/operations/production-configuration.js +1363 -0
- package/dist/src/operations/production-primitives.d.ts +53 -0
- package/dist/src/operations/production-primitives.js +1073 -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 +118 -0
- package/dist/src/operations/runtime-adapter.js +786 -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 +281 -0
- package/dist/src/operations/service.js +2810 -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/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 +39 -0
- package/dist/src/runtime/coordinated-page.js +634 -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/untrusted-output.js +2 -1
- package/dist/src/scripts/backend-server.js +4 -1
- package/dist/src/scripts/live-smoke/harness.js +79 -7
- package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
- package/dist/src/scripts/live-smoke/scenarios.js +110 -18
- 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 +1 -1
- package/references/2026-08-16-transactional-operations.md +459 -0
- package/references/agents-runner.md +34 -0
- package/references/backend-protocol.md +73 -0
- package/references/python-parity.md +72 -0
- package/references/responses-adapter.md +19 -0
- package/references/streaming.md +6 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { PageLike } from "../types.js";
|
|
2
|
+
import { type BrowserResourceKey, type CoordinatorAcquisitionContext, type CoordinatorOwner, type CoordinatorPriority, type CoordinatorResourceKind, type ProcessTabCoordinator, type TabResourceKey } from "../runtime/tab-coordinator.js";
|
|
3
|
+
import type { OperationRuntimeCapabilities } from "../runtime/operation-context.js";
|
|
4
|
+
import type { OperationTargetBindingV1, OperationTargetLifecycle } from "./types.js";
|
|
5
|
+
import type { OwnershipTargetEvidence } from "./turn-ownership.js";
|
|
6
|
+
/** A target HMAC implementation must be keyed by the journal/runtime secret. */
|
|
7
|
+
export type BrowserTargetEvidenceDigest = (domain: string, material: unknown) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Provider claims are intentionally separate from the browser-observation
|
|
10
|
+
* target. Observation exposes only the opaque token; the epoch is retained
|
|
11
|
+
* here for fencing and is never written into user-visible diagnostics.
|
|
12
|
+
*/
|
|
13
|
+
export type BrowserTargetClaim = Readonly<{
|
|
14
|
+
token: string;
|
|
15
|
+
epoch: number;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* The capability matrix is conservative by construction. Missing fields are
|
|
19
|
+
* false, so an older provider can never accidentally advertise concurrent-tab
|
|
20
|
+
* or cross-process safety.
|
|
21
|
+
*/
|
|
22
|
+
export type BrowserTargetCapabilities = Readonly<OperationRuntimeCapabilities>;
|
|
23
|
+
export type BrowserTargetBindingInput<Page extends PageLike = PageLike> = Readonly<{
|
|
24
|
+
/** An explicit page captured by the caller; never read from RuntimeEnv. */
|
|
25
|
+
page: Readonly<Page>;
|
|
26
|
+
/** The normalized target from `observeBrowserPage(...).snapshot.target`. */
|
|
27
|
+
evidence: OwnershipTargetEvidence;
|
|
28
|
+
/** Fixed is the compatibility default; new_pending carries a blank-task anchor. */
|
|
29
|
+
targetLifecycle?: OperationTargetLifecycle;
|
|
30
|
+
/** Required keyed evidence for a pending new-task anchor. */
|
|
31
|
+
newTargetAnchorDigest?: string;
|
|
32
|
+
blankTaskEvidenceDigest?: string;
|
|
33
|
+
/** Optional provider claim/fencing evidence. Omission means process scope. */
|
|
34
|
+
authoritativeClaim?: BrowserTargetClaim;
|
|
35
|
+
capabilities?: Partial<BrowserTargetCapabilities>;
|
|
36
|
+
evidenceDigest: BrowserTargetEvidenceDigest;
|
|
37
|
+
owner: CoordinatorOwner;
|
|
38
|
+
coordinator: ProcessTabCoordinator;
|
|
39
|
+
userTurnBaselineDigest?: string;
|
|
40
|
+
assistantTurnBaselineDigest?: string;
|
|
41
|
+
configurationReceiptDigest?: string;
|
|
42
|
+
}>;
|
|
43
|
+
export type BrowserTargetResource = Readonly<{
|
|
44
|
+
/** `provider` is reserved for validated provider claim + advertised overlap. */
|
|
45
|
+
scope: OperationTargetBindingV1["coordinationScope"];
|
|
46
|
+
resourceKind: CoordinatorResourceKind;
|
|
47
|
+
resourceKey: BrowserResourceKey | TabResourceKey;
|
|
48
|
+
/** True only when different tabs may use independent coordinator actors. */
|
|
49
|
+
concurrentTabs: boolean;
|
|
50
|
+
authoritativeClaimValidated: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
export type BrowserTargetTransactionOptions = Readonly<{
|
|
53
|
+
priority?: CoordinatorPriority;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
deadlineAt?: number;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
label?: string;
|
|
58
|
+
}>;
|
|
59
|
+
export type BrowserTargetTransactionContext<Page extends PageLike = PageLike> = Readonly<{
|
|
60
|
+
page: Readonly<Page>;
|
|
61
|
+
target: OperationTargetBindingV1;
|
|
62
|
+
acquisition: CoordinatorAcquisitionContext;
|
|
63
|
+
/**
|
|
64
|
+
* Validate a fresh read-only observation before any mutation. The read and
|
|
65
|
+
* mutation should remain one short transaction; polling/sleeping belongs
|
|
66
|
+
* after the promise returned by `withTabTransaction` settles.
|
|
67
|
+
*/
|
|
68
|
+
assertCurrent: (evidence: OwnershipTargetEvidence, claim?: BrowserTargetClaim, allowNewTargetEstablishment?: boolean) => void;
|
|
69
|
+
}>;
|
|
70
|
+
export type BrowserTargetBinding<Page extends PageLike = PageLike> = Readonly<{
|
|
71
|
+
page: Readonly<Page>;
|
|
72
|
+
target: OperationTargetBindingV1;
|
|
73
|
+
/**
|
|
74
|
+
* Evidence for this live observation only. This is deliberately distinct
|
|
75
|
+
* from the journal's canonical targetBindingDigest, which is computed from
|
|
76
|
+
* the complete durable OperationTargetBindingV1 by OperationJournal.
|
|
77
|
+
*/
|
|
78
|
+
targetEvidenceDigest: string;
|
|
79
|
+
evidence: OwnershipTargetEvidence;
|
|
80
|
+
capabilities: BrowserTargetCapabilities;
|
|
81
|
+
resource: BrowserTargetResource;
|
|
82
|
+
owner: CoordinatorOwner;
|
|
83
|
+
assertPage: (page: unknown) => void;
|
|
84
|
+
assertCurrent: (evidence: OwnershipTargetEvidence, claim?: BrowserTargetClaim, allowNewTargetEstablishment?: boolean) => void;
|
|
85
|
+
/** Internal one-way latch set after a post-Send identity proof. */
|
|
86
|
+
markTargetEstablished?: (establishment: Readonly<{
|
|
87
|
+
conversationId: string;
|
|
88
|
+
canonicalThreadUrl: string;
|
|
89
|
+
}>) => void;
|
|
90
|
+
withTabTransaction: <T>(options: BrowserTargetTransactionOptions, callback: (context: BrowserTargetTransactionContext<Page>) => T | PromiseLike<T>) => Promise<T>;
|
|
91
|
+
}>;
|
|
92
|
+
export type BrowserTargetErrorCode = "invalid_target_evidence" | "invalid_capabilities" | "invalid_claim" | "invalid_owner" | "invalid_digest" | "navigation_mismatch" | "claim_mismatch" | "page_mismatch";
|
|
93
|
+
/** Errors intentionally contain no caller-controlled IDs, URLs, or digests. */
|
|
94
|
+
export declare class BrowserTargetError extends Error {
|
|
95
|
+
readonly code: BrowserTargetErrorCode;
|
|
96
|
+
constructor(code: BrowserTargetErrorCode, message: string);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Bind one explicit page and one normalized observation to an immutable target.
|
|
100
|
+
* This adapter is deliberately browser-agnostic: it never reads or mutates a
|
|
101
|
+
* legacy `RuntimeEnv`, and it never performs polling or sleeps itself.
|
|
102
|
+
*/
|
|
103
|
+
export declare function bindBrowserTarget<Page extends PageLike = PageLike>(input: BrowserTargetBindingInput<Page>): BrowserTargetBinding<Page>;
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { createBrowserResourceKey, createTabResourceKey } from "../runtime/tab-coordinator.js";
|
|
2
|
+
/** The exact digest shape accepted by every target-evidence boundary. */
|
|
3
|
+
const DIGEST_PATTERN = /^hmac-sha256:[0-9a-f]{64}$/;
|
|
4
|
+
/** Keep IDs opaque: the adapter never interprets provider-specific semantics. */
|
|
5
|
+
const OPAQUE_ID_PATTERN = /^[A-Za-z0-9._:-]{1,512}$/;
|
|
6
|
+
/** `browser-observation.ts` deliberately emits this non-routable URL identity. */
|
|
7
|
+
const OPAQUE_THREAD_URL_PATTERN = /^https:\/\/opaque\.invalid\/thread\/[0-9a-f]{64}$/;
|
|
8
|
+
const INVALID_ID_VALUES = new Set(["unknown", "undefined", "null", "n/a", "na"]);
|
|
9
|
+
const TARGET_EVIDENCE_DIGEST_DOMAIN = "codex-chatgpt-control/operation-target-evidence/v1";
|
|
10
|
+
const CLAIM_EVIDENCE_DIGEST_DOMAIN = "codex-chatgpt-control/tab-claim-evidence/v1";
|
|
11
|
+
/** Errors intentionally contain no caller-controlled IDs, URLs, or digests. */
|
|
12
|
+
export class BrowserTargetError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "BrowserTargetError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function isPlainRecord(value) {
|
|
21
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
const prototype = Object.getPrototypeOf(value);
|
|
25
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
26
|
+
return false;
|
|
27
|
+
for (const key of Object.keys(value)) {
|
|
28
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
29
|
+
if (descriptor?.get !== undefined || descriptor?.set !== undefined)
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function fail(code, message) {
|
|
39
|
+
throw new BrowserTargetError(code, message);
|
|
40
|
+
}
|
|
41
|
+
function assertPlainRecord(value, code, message) {
|
|
42
|
+
if (!isPlainRecord(value))
|
|
43
|
+
fail(code, message);
|
|
44
|
+
}
|
|
45
|
+
function assertExactKeys(value, keys, code) {
|
|
46
|
+
const allowed = new Set(keys);
|
|
47
|
+
try {
|
|
48
|
+
for (const key of Object.keys(value)) {
|
|
49
|
+
if (!allowed.has(key))
|
|
50
|
+
fail(code, "Unsupported target field.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error instanceof BrowserTargetError)
|
|
55
|
+
throw error;
|
|
56
|
+
fail(code, "Unsupported target field.");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function stableId(value, code = "invalid_target_evidence") {
|
|
60
|
+
if (typeof value !== "string")
|
|
61
|
+
fail(code, "Stable target identity is invalid.");
|
|
62
|
+
const normalized = value.trim();
|
|
63
|
+
if (normalized !== value
|
|
64
|
+
||
|
|
65
|
+
normalized.length === 0
|
|
66
|
+
|| normalized.length > 512
|
|
67
|
+
|| INVALID_ID_VALUES.has(normalized.toLowerCase())
|
|
68
|
+
|| !OPAQUE_ID_PATTERN.test(normalized)) {
|
|
69
|
+
fail(code, "Stable target identity is invalid.");
|
|
70
|
+
}
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
function digest(value, code = "invalid_digest") {
|
|
74
|
+
if (typeof value !== "string" || !DIGEST_PATTERN.test(value))
|
|
75
|
+
fail(code, "Target evidence digest is invalid.");
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
function normalizeIdentity(value, label, required) {
|
|
79
|
+
assertPlainRecord(value, "invalid_target_evidence", "Target identity evidence is invalid.");
|
|
80
|
+
assertExactKeys(value, ["status", "value", "reason"], "invalid_target_evidence");
|
|
81
|
+
if (value.status === "available") {
|
|
82
|
+
if (value.value === undefined)
|
|
83
|
+
fail("invalid_target_evidence", "Required target identity is unavailable.");
|
|
84
|
+
return Object.freeze({ status: "available", value: stableId(value.value) });
|
|
85
|
+
}
|
|
86
|
+
if (value.status === "unavailable") {
|
|
87
|
+
if (required)
|
|
88
|
+
fail("invalid_target_evidence", `Required ${label} evidence is unavailable.`);
|
|
89
|
+
if (value.reason !== "not_exposed" && value.reason !== "not_observed" && value.reason !== "redacted") {
|
|
90
|
+
fail("invalid_target_evidence", "Target identity evidence is invalid.");
|
|
91
|
+
}
|
|
92
|
+
return Object.freeze({ status: "unavailable", reason: value.reason });
|
|
93
|
+
}
|
|
94
|
+
fail("invalid_target_evidence", "Target identity evidence is invalid.");
|
|
95
|
+
}
|
|
96
|
+
function normalizeCanonicalThreadUrl(value, required = true) {
|
|
97
|
+
assertPlainRecord(value, "invalid_target_evidence", "Canonical thread evidence is invalid.");
|
|
98
|
+
assertExactKeys(value, ["status", "value", "reason"], "invalid_target_evidence");
|
|
99
|
+
if (value.status === "unavailable" && !required) {
|
|
100
|
+
if (value.reason !== "not_exposed" && value.reason !== "not_observed" && value.reason !== "redacted") {
|
|
101
|
+
fail("invalid_target_evidence", "Canonical thread evidence is invalid.");
|
|
102
|
+
}
|
|
103
|
+
return Object.freeze({ status: "unavailable", reason: value.reason });
|
|
104
|
+
}
|
|
105
|
+
if (value.status !== "available" || typeof value.value !== "string" || !OPAQUE_THREAD_URL_PATTERN.test(value.value)) {
|
|
106
|
+
fail("invalid_target_evidence", "Opaque canonical thread evidence is required.");
|
|
107
|
+
}
|
|
108
|
+
return Object.freeze({ status: "available", value: value.value });
|
|
109
|
+
}
|
|
110
|
+
function normalizeTargetEvidence(value, allowPendingIdentity = false) {
|
|
111
|
+
assertPlainRecord(value, "invalid_target_evidence", "Target evidence is invalid.");
|
|
112
|
+
assertExactKeys(value, ["provider", "browser", "tab", "thread", "conversation", "canonicalThreadUrl", "authoritativeTabClaim", "coordinationScope"], "invalid_target_evidence");
|
|
113
|
+
if (value.coordinationScope !== "process" && value.coordinationScope !== "provider") {
|
|
114
|
+
fail("invalid_target_evidence", "Target coordination scope is invalid.");
|
|
115
|
+
}
|
|
116
|
+
const provider = normalizeIdentity(value.provider, "provider", true);
|
|
117
|
+
const browser = normalizeIdentity(value.browser, "browser", true);
|
|
118
|
+
const tab = normalizeIdentity(value.tab, "tab", true);
|
|
119
|
+
const thread = normalizeIdentity(value.thread, "thread", !allowPendingIdentity);
|
|
120
|
+
const conversation = normalizeIdentity(value.conversation, "conversation", !allowPendingIdentity);
|
|
121
|
+
const canonicalThreadUrl = normalizeCanonicalThreadUrl(value.canonicalThreadUrl, !allowPendingIdentity);
|
|
122
|
+
const authoritativeTabClaim = normalizeIdentity(value.authoritativeTabClaim, "authoritative tab claim", false);
|
|
123
|
+
return Object.freeze({
|
|
124
|
+
provider,
|
|
125
|
+
browser,
|
|
126
|
+
tab,
|
|
127
|
+
thread,
|
|
128
|
+
conversation,
|
|
129
|
+
canonicalThreadUrl,
|
|
130
|
+
authoritativeTabClaim,
|
|
131
|
+
coordinationScope: value.coordinationScope
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function normalizeClaim(value) {
|
|
135
|
+
if (value === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
assertPlainRecord(value, "invalid_claim", "Authoritative claim is invalid.");
|
|
138
|
+
assertExactKeys(value, ["token", "epoch"], "invalid_claim");
|
|
139
|
+
const token = stableId(value.token, "invalid_claim");
|
|
140
|
+
if (!Number.isSafeInteger(value.epoch) || value.epoch < 0) {
|
|
141
|
+
fail("invalid_claim", "Authoritative claim is invalid.");
|
|
142
|
+
}
|
|
143
|
+
return Object.freeze({ token, epoch: value.epoch });
|
|
144
|
+
}
|
|
145
|
+
function normalizeCapabilities(value) {
|
|
146
|
+
if (value === undefined) {
|
|
147
|
+
return Object.freeze({
|
|
148
|
+
stableProviderId: false,
|
|
149
|
+
stableBrowserId: false,
|
|
150
|
+
stableTabId: false,
|
|
151
|
+
authoritativeTabClaim: false,
|
|
152
|
+
concurrentTabs: false
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
assertPlainRecord(value, "invalid_capabilities", "Target capabilities are invalid.");
|
|
156
|
+
assertExactKeys(value, ["stableProviderId", "stableBrowserId", "stableTabId", "authoritativeTabClaim", "concurrentTabs"], "invalid_capabilities");
|
|
157
|
+
const result = {
|
|
158
|
+
stableProviderId: value.stableProviderId === undefined ? false : value.stableProviderId,
|
|
159
|
+
stableBrowserId: value.stableBrowserId === undefined ? false : value.stableBrowserId,
|
|
160
|
+
stableTabId: value.stableTabId === undefined ? false : value.stableTabId,
|
|
161
|
+
authoritativeTabClaim: value.authoritativeTabClaim === undefined ? false : value.authoritativeTabClaim,
|
|
162
|
+
concurrentTabs: value.concurrentTabs === undefined ? false : value.concurrentTabs
|
|
163
|
+
};
|
|
164
|
+
if (Object.values(result).some(item => typeof item !== "boolean")) {
|
|
165
|
+
fail("invalid_capabilities", "Target capabilities are invalid.");
|
|
166
|
+
}
|
|
167
|
+
return Object.freeze(result);
|
|
168
|
+
}
|
|
169
|
+
function normalizeOwner(value) {
|
|
170
|
+
assertPlainRecord(value, "invalid_owner", "Coordinator owner is invalid.");
|
|
171
|
+
assertExactKeys(value, ["backendSessionId", "ownerId", "operationId"], "invalid_owner");
|
|
172
|
+
const backendSessionId = stableId(value.backendSessionId, "invalid_owner");
|
|
173
|
+
const ownerId = value.ownerId === undefined ? undefined : stableId(value.ownerId, "invalid_owner");
|
|
174
|
+
const operationId = value.operationId === undefined ? undefined : stableId(value.operationId, "invalid_owner");
|
|
175
|
+
return Object.freeze({
|
|
176
|
+
backendSessionId,
|
|
177
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
178
|
+
...(operationId === undefined ? {} : { operationId })
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function assertPageLike(value) {
|
|
182
|
+
if (value === null
|
|
183
|
+
|| typeof value !== "object"
|
|
184
|
+
|| Array.isArray(value)
|
|
185
|
+
|| typeof value.evaluate !== "function") {
|
|
186
|
+
fail("invalid_target_evidence", "An explicit page is required.");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function availableValue(identity, label) {
|
|
190
|
+
if (identity.status !== "available")
|
|
191
|
+
fail("invalid_target_evidence", `${label} evidence is unavailable.`);
|
|
192
|
+
return identity.value;
|
|
193
|
+
}
|
|
194
|
+
function claimEvidenceValue(target) {
|
|
195
|
+
return target.authoritativeTabClaim.status === "available" ? target.authoritativeTabClaim.value : undefined;
|
|
196
|
+
}
|
|
197
|
+
function validateDigestFunction(fn) {
|
|
198
|
+
if (typeof fn !== "function")
|
|
199
|
+
fail("invalid_digest", "Target evidence digest function is required.");
|
|
200
|
+
}
|
|
201
|
+
function safeDigest(fn, domain, material) {
|
|
202
|
+
try {
|
|
203
|
+
return digest(fn(domain, material));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (error instanceof BrowserTargetError && error.code === "invalid_digest")
|
|
207
|
+
throw error;
|
|
208
|
+
fail("invalid_digest", "Target evidence digest is invalid.");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Stable identities plus the provider's explicit concurrent-tab capability
|
|
213
|
+
* are sufficient for independent actors inside this SDK process. This does
|
|
214
|
+
* not make the key a cross-process lock: ProcessTabCoordinator is deliberately
|
|
215
|
+
* process-local, and a provider claim is still required before upgrading the
|
|
216
|
+
* durable target to provider scope.
|
|
217
|
+
*/
|
|
218
|
+
function supportsProcessTabConcurrency(capabilities) {
|
|
219
|
+
return capabilities.stableProviderId
|
|
220
|
+
&& capabilities.stableBrowserId
|
|
221
|
+
&& capabilities.stableTabId
|
|
222
|
+
&& capabilities.concurrentTabs;
|
|
223
|
+
}
|
|
224
|
+
function supportsProviderTabConcurrency(capabilities, claimValidated) {
|
|
225
|
+
return supportsProcessTabConcurrency(capabilities)
|
|
226
|
+
&& claimValidated
|
|
227
|
+
&& capabilities.authoritativeTabClaim;
|
|
228
|
+
}
|
|
229
|
+
function computeResource(target, capabilities, claimValidated) {
|
|
230
|
+
if (supportsProviderTabConcurrency(capabilities, claimValidated)) {
|
|
231
|
+
return Object.freeze({
|
|
232
|
+
scope: "provider",
|
|
233
|
+
resourceKind: "tab",
|
|
234
|
+
resourceKey: createTabResourceKey(target.providerId, target.browserId, target.tabId),
|
|
235
|
+
concurrentTabs: true,
|
|
236
|
+
authoritativeClaimValidated: true
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (supportsProcessTabConcurrency(capabilities)) {
|
|
240
|
+
return Object.freeze({
|
|
241
|
+
scope: "process",
|
|
242
|
+
resourceKind: "tab",
|
|
243
|
+
resourceKey: createTabResourceKey(target.providerId, target.browserId, target.tabId),
|
|
244
|
+
concurrentTabs: true,
|
|
245
|
+
authoritativeClaimValidated: claimValidated
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
// Without provider fencing, serialize the whole browser actor in this
|
|
249
|
+
// process. A per-tab key is safe only when stable identities and the
|
|
250
|
+
// provider's concurrentTabs capability are both present.
|
|
251
|
+
return Object.freeze({
|
|
252
|
+
scope: "process",
|
|
253
|
+
resourceKind: "browser",
|
|
254
|
+
resourceKey: createBrowserResourceKey(target.providerId, target.browserId),
|
|
255
|
+
concurrentTabs: false,
|
|
256
|
+
authoritativeClaimValidated: claimValidated
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function targetMaterial(evidence, target, claim) {
|
|
260
|
+
return {
|
|
261
|
+
provider: evidence.provider,
|
|
262
|
+
browser: evidence.browser,
|
|
263
|
+
tab: evidence.tab,
|
|
264
|
+
thread: evidence.thread,
|
|
265
|
+
conversation: evidence.conversation,
|
|
266
|
+
canonicalThreadUrl: evidence.canonicalThreadUrl,
|
|
267
|
+
authoritativeTabClaim: evidence.authoritativeTabClaim,
|
|
268
|
+
coordinationScope: target.coordinationScope,
|
|
269
|
+
...(claim === undefined ? {} : { claimEpoch: claim.epoch })
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function compareAvailable(expected, observed, code) {
|
|
273
|
+
const expectedValue = availableValue(expected, "Bound target");
|
|
274
|
+
if (observed.status !== "available" || expectedValue !== observed.value) {
|
|
275
|
+
fail(code, code === "claim_mismatch" ? "Authoritative claim changed." : "Observed target changed.");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function compareAvailableIdentityValue(expected, observed, code) {
|
|
279
|
+
if (expected === undefined || observed.status !== "available" || observed.value !== expected) {
|
|
280
|
+
fail(code, code === "claim_mismatch" ? "Authoritative claim changed." : "Observed target changed.");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function makeTransactionOptions(owner, options) {
|
|
284
|
+
const result = {
|
|
285
|
+
owner,
|
|
286
|
+
priority: options.priority ?? "mutation"
|
|
287
|
+
};
|
|
288
|
+
if (options.signal !== undefined)
|
|
289
|
+
result.signal = options.signal;
|
|
290
|
+
if (options.deadlineAt !== undefined)
|
|
291
|
+
result.deadlineAt = options.deadlineAt;
|
|
292
|
+
if (options.timeoutMs !== undefined)
|
|
293
|
+
result.timeoutMs = options.timeoutMs;
|
|
294
|
+
if (options.label !== undefined)
|
|
295
|
+
result.label = options.label;
|
|
296
|
+
return Object.freeze(result);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Bind one explicit page and one normalized observation to an immutable target.
|
|
300
|
+
* This adapter is deliberately browser-agnostic: it never reads or mutates a
|
|
301
|
+
* legacy `RuntimeEnv`, and it never performs polling or sleeps itself.
|
|
302
|
+
*/
|
|
303
|
+
export function bindBrowserTarget(input) {
|
|
304
|
+
assertPlainRecord(input, "invalid_target_evidence", "Target binding input is invalid.");
|
|
305
|
+
assertExactKeys(input, ["page", "evidence", "targetLifecycle", "newTargetAnchorDigest", "blankTaskEvidenceDigest", "authoritativeClaim", "capabilities", "evidenceDigest", "owner", "coordinator", "userTurnBaselineDigest", "assistantTurnBaselineDigest", "configurationReceiptDigest"], "invalid_target_evidence");
|
|
306
|
+
assertPageLike(input.page);
|
|
307
|
+
validateDigestFunction(input.evidenceDigest);
|
|
308
|
+
if (input.coordinator === null
|
|
309
|
+
|| typeof input.coordinator !== "object"
|
|
310
|
+
|| typeof input.coordinator.withTabTransaction !== "function"
|
|
311
|
+
|| typeof input.coordinator.withBrowserAcquisition !== "function") {
|
|
312
|
+
fail("invalid_target_evidence", "A process tab coordinator is required.");
|
|
313
|
+
}
|
|
314
|
+
const targetLifecycle = input.targetLifecycle ?? "fixed";
|
|
315
|
+
if (targetLifecycle !== "fixed" && targetLifecycle !== "new_pending" && targetLifecycle !== "new_established") {
|
|
316
|
+
fail("invalid_target_evidence", "Target lifecycle is invalid.");
|
|
317
|
+
}
|
|
318
|
+
const pending = targetLifecycle === "new_pending";
|
|
319
|
+
const evidence = normalizeTargetEvidence(input.evidence, pending);
|
|
320
|
+
const claim = normalizeClaim(input.authoritativeClaim);
|
|
321
|
+
const capabilities = normalizeCapabilities(input.capabilities);
|
|
322
|
+
const owner = normalizeOwner(input.owner);
|
|
323
|
+
const providerId = availableValue(evidence.provider, "Provider");
|
|
324
|
+
const browserId = availableValue(evidence.browser, "Browser");
|
|
325
|
+
const tabId = availableValue(evidence.tab, "Tab");
|
|
326
|
+
const conversationId = pending ? undefined : availableValue(evidence.conversation, "Conversation");
|
|
327
|
+
const canonicalThreadUrl = pending ? undefined : availableValue(evidence.canonicalThreadUrl, "Canonical thread URL");
|
|
328
|
+
const observedClaim = claimEvidenceValue(evidence);
|
|
329
|
+
if (claim !== undefined && observedClaim !== claim.token) {
|
|
330
|
+
fail("claim_mismatch", "Authoritative claim does not match the observed target.");
|
|
331
|
+
}
|
|
332
|
+
const claimValidated = claim !== undefined && observedClaim === claim.token;
|
|
333
|
+
for (const value of [input.userTurnBaselineDigest, input.assistantTurnBaselineDigest, input.configurationReceiptDigest]) {
|
|
334
|
+
if (value !== undefined)
|
|
335
|
+
digest(value);
|
|
336
|
+
}
|
|
337
|
+
if (pending) {
|
|
338
|
+
if (evidence.thread.status !== "unavailable"
|
|
339
|
+
|| evidence.conversation.status !== "unavailable"
|
|
340
|
+
|| evidence.canonicalThreadUrl.status !== "unavailable") {
|
|
341
|
+
fail("invalid_target_evidence", "A pending new target cannot contain provider conversation identity.");
|
|
342
|
+
}
|
|
343
|
+
if (input.newTargetAnchorDigest === undefined || input.blankTaskEvidenceDigest === undefined) {
|
|
344
|
+
fail("invalid_digest", "A pending new target requires blank-task anchor evidence.");
|
|
345
|
+
}
|
|
346
|
+
digest(input.newTargetAnchorDigest);
|
|
347
|
+
digest(input.blankTaskEvidenceDigest);
|
|
348
|
+
}
|
|
349
|
+
else if (input.newTargetAnchorDigest !== undefined || input.blankTaskEvidenceDigest !== undefined) {
|
|
350
|
+
fail("invalid_target_evidence", "Blank-task anchor evidence is only valid for a pending new target.");
|
|
351
|
+
}
|
|
352
|
+
const providerScope = supportsProviderTabConcurrency(capabilities, claimValidated);
|
|
353
|
+
const targetWithoutDigest = Object.freeze({
|
|
354
|
+
providerId,
|
|
355
|
+
browserId,
|
|
356
|
+
tabId,
|
|
357
|
+
coordinationScope: providerScope ? "provider" : "process",
|
|
358
|
+
...(claimValidated ? {
|
|
359
|
+
tabClaimEvidenceDigest: safeDigest(input.evidenceDigest, CLAIM_EVIDENCE_DIGEST_DOMAIN, {
|
|
360
|
+
token: claim?.token,
|
|
361
|
+
epoch: claim?.epoch
|
|
362
|
+
})
|
|
363
|
+
} : {}),
|
|
364
|
+
...(canonicalThreadUrl === undefined ? {} : { canonicalThreadUrl }),
|
|
365
|
+
...(conversationId === undefined ? {} : { conversationId }),
|
|
366
|
+
...(input.userTurnBaselineDigest === undefined ? {} : { userTurnBaselineDigest: input.userTurnBaselineDigest }),
|
|
367
|
+
...(input.assistantTurnBaselineDigest === undefined ? {} : { assistantTurnBaselineDigest: input.assistantTurnBaselineDigest }),
|
|
368
|
+
...(input.configurationReceiptDigest === undefined ? {} : { configurationReceiptDigest: input.configurationReceiptDigest }),
|
|
369
|
+
evidenceProfile: Object.freeze({
|
|
370
|
+
providerIdentity: "required",
|
|
371
|
+
stableTabId: "required",
|
|
372
|
+
stableConversationId: pending ? "unavailable" : "required",
|
|
373
|
+
// Turn IDs are established by the subsequent ownership observation;
|
|
374
|
+
// target binding stores only their baseline evidence digests.
|
|
375
|
+
stableUserTurnId: "unavailable",
|
|
376
|
+
authoritativeTabClaim: providerScope ? "required" : "unavailable",
|
|
377
|
+
replacementTabRecovery: false
|
|
378
|
+
}),
|
|
379
|
+
...(pending ? {
|
|
380
|
+
targetLifecycle: "new_pending",
|
|
381
|
+
newTargetAnchorDigest: input.newTargetAnchorDigest,
|
|
382
|
+
blankTaskEvidenceDigest: input.blankTaskEvidenceDigest
|
|
383
|
+
} : {})
|
|
384
|
+
});
|
|
385
|
+
const targetEvidenceDigest = safeDigest(input.evidenceDigest, TARGET_EVIDENCE_DIGEST_DOMAIN, targetMaterial(evidence, targetWithoutDigest, claim));
|
|
386
|
+
const target = Object.freeze({ ...targetWithoutDigest });
|
|
387
|
+
let activeTarget = target;
|
|
388
|
+
const resource = computeResource(target, capabilities, claimValidated);
|
|
389
|
+
const page = input.page;
|
|
390
|
+
const coordinator = input.coordinator;
|
|
391
|
+
let targetEstablished = false;
|
|
392
|
+
const assertPage = (observedPage) => {
|
|
393
|
+
if (observedPage !== page)
|
|
394
|
+
fail("page_mismatch", "The supplied page is not the bound operation page.");
|
|
395
|
+
};
|
|
396
|
+
const assertCurrent = (current, currentClaim, allowNewTargetEstablishment = false) => {
|
|
397
|
+
const observed = normalizeTargetEvidence(current, pending);
|
|
398
|
+
compareAvailable(evidence.provider, observed.provider, "navigation_mismatch");
|
|
399
|
+
compareAvailable(evidence.browser, observed.browser, "navigation_mismatch");
|
|
400
|
+
compareAvailable(evidence.tab, observed.tab, "navigation_mismatch");
|
|
401
|
+
if (!pending || targetEstablished) {
|
|
402
|
+
if (!targetEstablished)
|
|
403
|
+
compareAvailable(evidence.thread, observed.thread, "navigation_mismatch");
|
|
404
|
+
if (targetEstablished) {
|
|
405
|
+
compareAvailableIdentityValue(activeTarget.conversationId, observed.conversation, "navigation_mismatch");
|
|
406
|
+
compareAvailableIdentityValue(activeTarget.canonicalThreadUrl, observed.canonicalThreadUrl, "navigation_mismatch");
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
compareAvailable(evidence.conversation, observed.conversation, "navigation_mismatch");
|
|
410
|
+
compareAvailable(evidence.canonicalThreadUrl, observed.canonicalThreadUrl, "navigation_mismatch");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
const identities = [observed.thread, observed.conversation, observed.canonicalThreadUrl];
|
|
415
|
+
const availableCount = identities.filter(identity => identity.status === "available").length;
|
|
416
|
+
if (availableCount !== 0 && availableCount !== identities.length) {
|
|
417
|
+
fail("navigation_mismatch", "Observed new-target identity is incomplete.");
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (claimValidated) {
|
|
421
|
+
compareAvailable(evidence.authoritativeTabClaim, observed.authoritativeTabClaim, "claim_mismatch");
|
|
422
|
+
const normalizedCurrentClaim = normalizeClaim(currentClaim);
|
|
423
|
+
if (normalizedCurrentClaim === undefined
|
|
424
|
+
|| normalizedCurrentClaim.token !== claim?.token
|
|
425
|
+
|| normalizedCurrentClaim.epoch !== claim?.epoch) {
|
|
426
|
+
fail("claim_mismatch", "Authoritative claim changed.");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const withTabTransaction = async (options, callback) => {
|
|
431
|
+
if (typeof callback !== "function")
|
|
432
|
+
fail("invalid_target_evidence", "A transaction callback is required.");
|
|
433
|
+
if (!isPlainRecord(options))
|
|
434
|
+
fail("invalid_target_evidence", "Transaction options are invalid.");
|
|
435
|
+
assertExactKeys(options, ["priority", "signal", "deadlineAt", "timeoutMs", "label"], "invalid_target_evidence");
|
|
436
|
+
const requestOptions = makeTransactionOptions(owner, options);
|
|
437
|
+
const run = (acquisition) => callback(Object.freeze({
|
|
438
|
+
page,
|
|
439
|
+
target: activeTarget,
|
|
440
|
+
acquisition,
|
|
441
|
+
assertCurrent
|
|
442
|
+
}));
|
|
443
|
+
if (resource.resourceKind === "tab") {
|
|
444
|
+
return coordinator.withTabTransaction(resource.resourceKey, requestOptions, run);
|
|
445
|
+
}
|
|
446
|
+
return coordinator.withBrowserAcquisition(resource.resourceKey, requestOptions, run);
|
|
447
|
+
};
|
|
448
|
+
const binding = {
|
|
449
|
+
page,
|
|
450
|
+
get target() { return activeTarget; },
|
|
451
|
+
targetEvidenceDigest,
|
|
452
|
+
evidence,
|
|
453
|
+
capabilities,
|
|
454
|
+
resource,
|
|
455
|
+
owner,
|
|
456
|
+
assertPage,
|
|
457
|
+
assertCurrent,
|
|
458
|
+
...(pending ? {
|
|
459
|
+
markTargetEstablished: (establishment) => {
|
|
460
|
+
if (!isPlainRecord(establishment)
|
|
461
|
+
|| typeof establishment.conversationId !== "string"
|
|
462
|
+
|| !OPAQUE_ID_PATTERN.test(establishment.conversationId)
|
|
463
|
+
|| typeof establishment.canonicalThreadUrl !== "string"
|
|
464
|
+
|| !OPAQUE_THREAD_URL_PATTERN.test(establishment.canonicalThreadUrl))
|
|
465
|
+
return;
|
|
466
|
+
activeTarget = Object.freeze({
|
|
467
|
+
providerId: target.providerId,
|
|
468
|
+
browserId: target.browserId,
|
|
469
|
+
tabId: target.tabId,
|
|
470
|
+
coordinationScope: target.coordinationScope,
|
|
471
|
+
...(target.tabClaimEvidenceDigest === undefined ? {} : { tabClaimEvidenceDigest: target.tabClaimEvidenceDigest }),
|
|
472
|
+
canonicalThreadUrl: establishment.canonicalThreadUrl,
|
|
473
|
+
conversationId: establishment.conversationId,
|
|
474
|
+
...(target.userTurnBaselineDigest === undefined ? {} : { userTurnBaselineDigest: target.userTurnBaselineDigest }),
|
|
475
|
+
...(target.assistantTurnBaselineDigest === undefined ? {} : { assistantTurnBaselineDigest: target.assistantTurnBaselineDigest }),
|
|
476
|
+
...(target.configurationReceiptDigest === undefined ? {} : { configurationReceiptDigest: target.configurationReceiptDigest }),
|
|
477
|
+
evidenceProfile: Object.freeze({
|
|
478
|
+
...target.evidenceProfile,
|
|
479
|
+
stableConversationId: "required",
|
|
480
|
+
stableUserTurnId: "required"
|
|
481
|
+
})
|
|
482
|
+
});
|
|
483
|
+
targetEstablished = true;
|
|
484
|
+
}
|
|
485
|
+
} : {}),
|
|
486
|
+
withTabTransaction
|
|
487
|
+
};
|
|
488
|
+
return Object.freeze(binding);
|
|
489
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OperationRequestIdentityInput } from "./types.js";
|
|
2
|
+
export declare function canonicalJson(value: unknown): string;
|
|
3
|
+
export declare function hmacDigest(key: Uint8Array, domain: string, value: unknown): string;
|
|
4
|
+
export declare function operationRequestDigest(key: Uint8Array, input: OperationRequestIdentityInput): string;
|