codex-chatgpt-control 0.5.1-alpha.2 → 0.5.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +27 -0
  3. package/contracts/v1/fixtures/backend-capabilities.json +40 -1
  4. package/contracts/v1/fixtures/backend-compatibility.json +21 -0
  5. package/contracts/v1/fixtures/backend-version.json +6 -1
  6. package/contracts/v1/fixtures/operation-action-prepared-event.json +42 -0
  7. package/contracts/v1/fixtures/operation-action.json +13 -0
  8. package/contracts/v1/fixtures/operation-artifact-receipt.json +14 -0
  9. package/contracts/v1/fixtures/operation-artifact-transfer-intent-event.json +24 -0
  10. package/contracts/v1/fixtures/operation-artifact-transfer-receipt-event.json +26 -0
  11. package/contracts/v1/fixtures/operation-artifact-transfer-state.json +178 -0
  12. package/contracts/v1/fixtures/operation-blocker.json +10 -0
  13. package/contracts/v1/fixtures/operation-collect-request.json +17 -0
  14. package/contracts/v1/fixtures/operation-collect-result.json +42 -0
  15. package/contracts/v1/fixtures/operation-control-receipt.json +13 -0
  16. package/contracts/v1/fixtures/operation-control-request.json +18 -0
  17. package/contracts/v1/fixtures/operation-control-result.json +31 -0
  18. package/contracts/v1/fixtures/operation-event.json +18 -0
  19. package/contracts/v1/fixtures/operation-handle.json +10 -0
  20. package/contracts/v1/fixtures/operation-inspect-request.json +13 -0
  21. package/contracts/v1/fixtures/operation-inspect-result.json +205 -0
  22. package/contracts/v1/fixtures/operation-ownership-baseline-event.json +34 -0
  23. package/contracts/v1/fixtures/operation-receipt.json +32 -0
  24. package/contracts/v1/fixtures/operation-recovery-decision.json +7 -0
  25. package/contracts/v1/fixtures/operation-recovery-observation.json +12 -0
  26. package/contracts/v1/fixtures/operation-request.json +33 -0
  27. package/contracts/v1/fixtures/operation-state.json +144 -0
  28. package/contracts/v1/fixtures/operation-submission-witness-event.json +20 -0
  29. package/contracts/v1/fixtures/operation-submission-witness.json +11 -0
  30. package/contracts/v1/fixtures/operation-submit-result.json +16 -0
  31. package/contracts/v1/fixtures/operation-target-established-event.json +21 -0
  32. package/contracts/v1/manifest.json +155 -1
  33. package/contracts/v1/parity-suite.json +96 -1
  34. package/contracts/v1/schemas/backend-compatibility.schema.json +65 -0
  35. package/contracts/v1/schemas/backend-request.schema.json +8 -2
  36. package/contracts/v1/schemas/capabilities.schema.json +81 -1
  37. package/contracts/v1/schemas/manifest.schema.json +57 -0
  38. package/contracts/v1/schemas/operation-action.schema.json +155 -0
  39. package/contracts/v1/schemas/operation-artifact-receipt.schema.json +89 -0
  40. package/contracts/v1/schemas/operation-blocker.schema.json +70 -0
  41. package/contracts/v1/schemas/operation-collect-request.schema.json +46 -0
  42. package/contracts/v1/schemas/operation-collect-result.schema.json +74 -0
  43. package/contracts/v1/schemas/operation-control-receipt.schema.json +108 -0
  44. package/contracts/v1/schemas/operation-control-request.schema.json +111 -0
  45. package/contracts/v1/schemas/operation-control-result.schema.json +112 -0
  46. package/contracts/v1/schemas/operation-event.schema.json +756 -0
  47. package/contracts/v1/schemas/operation-handle.schema.json +55 -0
  48. package/contracts/v1/schemas/operation-inspect-request.schema.json +42 -0
  49. package/contracts/v1/schemas/operation-inspect-result.schema.json +1662 -0
  50. package/contracts/v1/schemas/operation-receipt.schema.json +153 -0
  51. package/contracts/v1/schemas/operation-recovery.schema.json +314 -0
  52. package/contracts/v1/schemas/operation-request.schema.json +158 -0
  53. package/contracts/v1/schemas/operation-state.schema.json +691 -0
  54. package/contracts/v1/schemas/operation-submission-witness.schema.json +48 -0
  55. package/contracts/v1/schemas/operation-submit-result.schema.json +94 -0
  56. package/contracts/v1/surface-drift-policy.json +50 -0
  57. package/contracts/v1/vectors/operation-request-digest-v1.json +43 -0
  58. package/dist/codex-chatgpt-control-backend.mjs +43897 -9094
  59. package/dist/codex-chatgpt-control.bundle.mjs +46360 -10262
  60. package/dist/src/backend/client.d.ts +103 -2
  61. package/dist/src/backend/client.js +1436 -97
  62. package/dist/src/backend/compatibility.d.ts +12 -0
  63. package/dist/src/backend/compatibility.js +208 -0
  64. package/dist/src/backend/protocol.d.ts +84 -1
  65. package/dist/src/backend/protocol.js +31 -5
  66. package/dist/src/backend/runtime-identity.d.ts +10 -0
  67. package/dist/src/backend/runtime-identity.js +141 -0
  68. package/dist/src/backend/session.d.ts +10 -2
  69. package/dist/src/backend/session.js +478 -7
  70. package/dist/src/backend/stdio-server.d.ts +4 -1
  71. package/dist/src/backend/stdio-server.js +240 -28
  72. package/dist/src/browser/active-composer-file-input.d.ts +9 -0
  73. package/dist/src/browser/active-composer-file-input.js +33 -0
  74. package/dist/src/browser/attach.d.ts +4 -1
  75. package/dist/src/browser/attach.js +136 -28
  76. package/dist/src/browser/downloads.d.ts +2 -0
  77. package/dist/src/client.d.ts +38 -0
  78. package/dist/src/client.js +1222 -59
  79. package/dist/src/commands/configuration.js +13 -7
  80. package/dist/src/commands/doctor.d.ts +1 -1
  81. package/dist/src/commands/doctor.js +26 -3
  82. package/dist/src/commands/experience.js +35 -0
  83. package/dist/src/commands/files.js +4 -27
  84. package/dist/src/commands/modes.js +49 -17
  85. package/dist/src/commands/power-discovery.d.ts +130 -0
  86. package/dist/src/commands/power-discovery.js +627 -0
  87. package/dist/src/commands/project-sources.js +325 -31
  88. package/dist/src/commands/sequence.js +59 -32
  89. package/dist/src/commands/session.js +7 -2
  90. package/dist/src/commands/work.js +31 -0
  91. package/dist/src/errors.d.ts +9 -0
  92. package/dist/src/errors.js +22 -0
  93. package/dist/src/index.d.ts +6 -0
  94. package/dist/src/index.js +9 -0
  95. package/dist/src/operations/artifact-output.d.ts +69 -0
  96. package/dist/src/operations/artifact-output.js +1748 -0
  97. package/dist/src/operations/artifact-stream.d.ts +19 -0
  98. package/dist/src/operations/artifact-stream.js +35 -0
  99. package/dist/src/operations/artifact-transfer.d.ts +149 -0
  100. package/dist/src/operations/artifact-transfer.js +1312 -0
  101. package/dist/src/operations/browser-adapter.d.ts +161 -0
  102. package/dist/src/operations/browser-adapter.js +2023 -0
  103. package/dist/src/operations/browser-observation.d.ts +110 -0
  104. package/dist/src/operations/browser-observation.js +1208 -0
  105. package/dist/src/operations/browser-target.d.ts +103 -0
  106. package/dist/src/operations/browser-target.js +489 -0
  107. package/dist/src/operations/canonical.d.ts +4 -0
  108. package/dist/src/operations/canonical.js +412 -0
  109. package/dist/src/operations/chatgpt-runtime.d.ts +72 -0
  110. package/dist/src/operations/chatgpt-runtime.js +1150 -0
  111. package/dist/src/operations/client.d.ts +138 -0
  112. package/dist/src/operations/client.js +1143 -0
  113. package/dist/src/operations/collector.d.ts +195 -0
  114. package/dist/src/operations/collector.js +1039 -0
  115. package/dist/src/operations/control.d.ts +372 -0
  116. package/dist/src/operations/control.js +1498 -0
  117. package/dist/src/operations/file-identity.d.ts +33 -0
  118. package/dist/src/operations/file-identity.js +153 -0
  119. package/dist/src/operations/handle.d.ts +19 -0
  120. package/dist/src/operations/handle.js +727 -0
  121. package/dist/src/operations/index.d.ts +22 -0
  122. package/dist/src/operations/index.js +22 -0
  123. package/dist/src/operations/journal.d.ts +142 -0
  124. package/dist/src/operations/journal.js +1931 -0
  125. package/dist/src/operations/production-attachments.d.ts +116 -0
  126. package/dist/src/operations/production-attachments.js +1018 -0
  127. package/dist/src/operations/production-chatgpt-artifacts.d.ts +74 -0
  128. package/dist/src/operations/production-chatgpt-artifacts.js +1146 -0
  129. package/dist/src/operations/production-chatgpt-attachments.d.ts +71 -0
  130. package/dist/src/operations/production-chatgpt-attachments.js +1563 -0
  131. package/dist/src/operations/production-configuration.d.ts +42 -0
  132. package/dist/src/operations/production-configuration.js +1363 -0
  133. package/dist/src/operations/production-primitives.d.ts +53 -0
  134. package/dist/src/operations/production-primitives.js +1073 -0
  135. package/dist/src/operations/production-work-steer.d.ts +203 -0
  136. package/dist/src/operations/production-work-steer.js +1218 -0
  137. package/dist/src/operations/recovery.d.ts +80 -0
  138. package/dist/src/operations/recovery.js +174 -0
  139. package/dist/src/operations/runtime-adapter.d.ts +118 -0
  140. package/dist/src/operations/runtime-adapter.js +786 -0
  141. package/dist/src/operations/send-once.d.ts +224 -0
  142. package/dist/src/operations/send-once.js +1081 -0
  143. package/dist/src/operations/service.d.ts +281 -0
  144. package/dist/src/operations/service.js +2810 -0
  145. package/dist/src/operations/staging.d.ts +136 -0
  146. package/dist/src/operations/staging.js +630 -0
  147. package/dist/src/operations/state-machine.d.ts +29 -0
  148. package/dist/src/operations/state-machine.js +2047 -0
  149. package/dist/src/operations/submission.d.ts +423 -0
  150. package/dist/src/operations/submission.js +1676 -0
  151. package/dist/src/operations/turn-ownership.d.ts +202 -0
  152. package/dist/src/operations/turn-ownership.js +700 -0
  153. package/dist/src/operations/types.d.ts +454 -0
  154. package/dist/src/operations/types.js +20 -0
  155. package/dist/src/operations/wire-requests.d.ts +21 -0
  156. package/dist/src/operations/wire-requests.js +396 -0
  157. package/dist/src/operations/wire-results.d.ts +97 -0
  158. package/dist/src/operations/wire-results.js +818 -0
  159. package/dist/src/runner/responses.d.ts +3 -1
  160. package/dist/src/runner/responses.js +19 -1
  161. package/dist/src/runner/result.js +227 -55
  162. package/dist/src/runner/types.d.ts +12 -0
  163. package/dist/src/runtime/command-routing.d.ts +149 -0
  164. package/dist/src/runtime/command-routing.js +431 -0
  165. package/dist/src/runtime/coordinated-browser.d.ts +24 -0
  166. package/dist/src/runtime/coordinated-browser.js +315 -0
  167. package/dist/src/runtime/coordinated-page.d.ts +39 -0
  168. package/dist/src/runtime/coordinated-page.js +634 -0
  169. package/dist/src/runtime/operation-context.d.ts +142 -0
  170. package/dist/src/runtime/operation-context.js +410 -0
  171. package/dist/src/runtime/runtime-session.d.ts +95 -0
  172. package/dist/src/runtime/runtime-session.js +314 -0
  173. package/dist/src/runtime/tab-coordinator.d.ts +200 -0
  174. package/dist/src/runtime/tab-coordinator.js +1200 -0
  175. package/dist/src/runtime/value-boundaries.d.ts +18 -0
  176. package/dist/src/runtime/value-boundaries.js +49 -0
  177. package/dist/src/safety/untrusted-output.js +2 -1
  178. package/dist/src/scripts/backend-server.js +4 -1
  179. package/dist/src/scripts/live-smoke/harness.js +79 -7
  180. package/dist/src/scripts/live-smoke/scenarios.d.ts +28 -0
  181. package/dist/src/scripts/live-smoke/scenarios.js +110 -18
  182. package/dist/src/scripts/live-smoke/types.d.ts +3 -0
  183. package/dist/src/scripts/release-canary-module.js +17 -4
  184. package/dist/src/types.d.ts +38 -1
  185. package/package.json +1 -1
  186. package/references/2026-08-16-transactional-operations.md +459 -0
  187. package/references/agents-runner.md +34 -0
  188. package/references/backend-protocol.md +73 -0
  189. package/references/python-parity.md +72 -0
  190. package/references/responses-adapter.md +19 -0
  191. package/references/streaming.md +6 -0
@@ -0,0 +1,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;