@floegence/redevplugin-ui 0.7.27 → 1.1.0

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/dist/surface.js CHANGED
@@ -11,26 +11,7 @@ export const pluginSurfaceContextSchemaVersion = "redevplugin.surface_context.v1
11
11
  const opaquePluginBridgeGlobalKey = "__redevpluginWorkerBridge";
12
12
  const maxPendingPluginBridgeRequests = 256;
13
13
  const maxPluginBridgeMessageBytes = opaqueSurfaceRenderLimits.max_message_bytes;
14
- const maxRetainedPluginStreamHandles = 128;
15
14
  const maxPluginJSONStructuralNodes = 32 * 1024;
16
- const streamCredentialInvalidatingErrorCodes = new Set([
17
- "PLUGIN_BRIDGE_DISPOSED",
18
- "PLUGIN_BRIDGE_HANDSHAKE_FAILED",
19
- "PLUGIN_BRIDGE_HANDSHAKE_REQUIRED",
20
- "PLUGIN_BRIDGE_TIMEOUT",
21
- "PLUGIN_CONTRACT_MISMATCH",
22
- "PLUGIN_GATEWAY_TOKEN_CHANNEL_MISMATCH",
23
- "PLUGIN_GATEWAY_TOKEN_INVALID",
24
- "PLUGIN_GATEWAY_TOKEN_REPLAYED",
25
- "PLUGIN_GRANT_INVALID",
26
- "PLUGIN_LEASE_INVALID",
27
- "PLUGIN_LEASE_REPLAYED",
28
- "PLUGIN_MANAGEMENT_REVISION_MISMATCH",
29
- "PLUGIN_STREAM_CANCELLED",
30
- "PLUGIN_STREAM_TICKET_INVALID",
31
- "PLUGIN_TOKEN_EXPIRED",
32
- "PLUGIN_TOKEN_REPLAY",
33
- ]);
34
15
  const maxOpaqueSurfaceLazyAssets = 128;
35
16
  const maxOpaqueSurfaceLazyBytes = 32 * 1024 * 1024;
36
17
  const maxConcurrentAssetReads = 4;
@@ -62,8 +43,6 @@ export class PluginBridgeClient {
62
43
  #pendingRender;
63
44
  #renderLoop;
64
45
  #controlEditRevisions = new Map();
65
- #pendingStreamDeliveries = new Map();
66
- #streamReadTails = new Map();
67
46
  #onMessage = (event) => {
68
47
  void this.#handleMessage(event);
69
48
  };
@@ -111,81 +90,45 @@ export class PluginBridgeClient {
111
90
  request,
112
91
  }, { mutation, signal: options.signal });
113
92
  }
114
- readStream(streamHandle, options = {}) {
93
+ executionEvents(executionID, afterCursor, options = {}) {
115
94
  this.#assertActive();
116
- if (!validOpaqueHandle(streamHandle, "stream")) {
117
- throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin stream handle is invalid");
95
+ if (!validOpaqueHandle(executionID, "execution") || !Number.isSafeInteger(afterCursor) || afterCursor < 0) {
96
+ throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin execution event cursor is invalid");
118
97
  }
119
- const previous = this.#streamReadTails.get(streamHandle);
120
- const read = (previous ? previous.catch(() => undefined) : Promise.resolve()).then(() => {
121
- this.#assertActive();
122
- if (options.signal?.aborted)
123
- throw streamReadAbortedError();
124
- return this.#readStream(streamHandle, options.signal);
125
- });
126
- this.#streamReadTails.set(streamHandle, read);
127
- void read.finally(() => {
128
- if (this.#streamReadTails.get(streamHandle) === read)
129
- this.#streamReadTails.delete(streamHandle);
130
- }).catch(() => undefined);
131
- return abortableStreamRead(read, options.signal);
132
- }
133
- async #readStream(streamHandle, signal) {
134
- const pending = this.#pendingStreamDeliveries.get(streamHandle);
135
- if (pending) {
136
- await this.#acknowledgeStream(streamHandle, pending.deliveryID, signal);
137
- this.#pendingStreamDeliveries.delete(streamHandle);
138
- return pending.result;
139
- }
140
- const id = this.#requestID("stream");
141
- const privateResult = await this.#request(id, {
142
- type: "redevplugin.bridge.stream.read",
143
- id,
144
- stream_handle: streamHandle,
145
- }, { cancellationKind: "stream", signal });
146
- const { delivery_id: deliveryID, ...result } = privateResult;
147
- if (!deliveryID)
148
- return result;
149
- this.#pendingStreamDeliveries.set(streamHandle, { deliveryID, result });
150
- await this.#acknowledgeStream(streamHandle, deliveryID, signal);
151
- this.#pendingStreamDeliveries.delete(streamHandle);
152
- return result;
153
- }
154
- async #acknowledgeStream(streamHandle, deliveryID, signal) {
155
- const id = this.#requestID("stream_ack");
156
- await this.#request(id, {
157
- type: "redevplugin.bridge.stream.ack",
98
+ const id = this.#requestID("execution");
99
+ return this.#request(id, {
100
+ type: "redevplugin.bridge.execution.events",
158
101
  id,
159
- stream_handle: streamHandle,
160
- delivery_id: deliveryID,
161
- }, { cancellationKind: "stream", signal });
102
+ execution_id: executionID,
103
+ after_cursor: afterCursor,
104
+ }, { signal: options.signal });
162
105
  }
163
- cancelOperation(operationID, reason, options = {}) {
106
+ cancelExecution(executionID, reason, options = {}) {
164
107
  this.#assertActive();
165
- if (!validOpaqueHandle(operationID, "operation") || (reason !== undefined && (typeof reason !== "string" || reason.length > 256))) {
166
- throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin operation cancellation is invalid");
108
+ if (!validOpaqueHandle(executionID, "execution") || (reason !== undefined && (typeof reason !== "string" || reason.length > 256))) {
109
+ throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin execution cancellation is invalid");
167
110
  }
168
- const id = this.#requestID("operation");
111
+ const id = this.#requestID("execution");
169
112
  return this.#request(id, removeUndefined({
170
- type: "redevplugin.bridge.operation.cancel",
113
+ type: "redevplugin.bridge.execution.cancel",
171
114
  id,
172
- operation_id: operationID,
115
+ execution_id: executionID,
173
116
  reason,
174
117
  }), { mutation: true, signal: options.signal });
175
118
  }
176
- operationSnapshot(operationID, options = {}) {
119
+ executionSnapshot(executionID, options = {}) {
177
120
  this.#assertActive();
178
- if (!validOpaqueHandle(operationID, "operation")) {
179
- throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin operation handle is invalid");
121
+ if (!validOpaqueHandle(executionID, "execution")) {
122
+ throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin execution handle is invalid");
180
123
  }
181
- const id = this.#requestID("operation");
124
+ const id = this.#requestID("execution");
182
125
  return this.#request(id, {
183
- type: "redevplugin.bridge.operation.snapshot",
126
+ type: "redevplugin.bridge.execution.query",
184
127
  id,
185
- operation_id: operationID,
128
+ execution_id: executionID,
186
129
  }, { signal: options.signal }).then((snapshot) => {
187
- if (!isPluginOperationSnapshot(snapshot) || snapshot.operation_id !== operationID) {
188
- throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin operation snapshot is invalid");
130
+ if (!isPluginExecutionSnapshot(snapshot) || snapshot.execution_id !== executionID) {
131
+ throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin execution snapshot is invalid");
189
132
  }
190
133
  return snapshot;
191
134
  });
@@ -313,7 +256,6 @@ export class PluginBridgeClient {
313
256
  pending.reject(new PluginBridgeError("PLUGIN_BRIDGE_DISPOSED", `Plugin bridge request ${id} was disposed`));
314
257
  }
315
258
  this.#pending.clear();
316
- this.#pendingStreamDeliveries.clear();
317
259
  this.#actionHandlers.clear();
318
260
  this.#canvasInputHandlers.clear();
319
261
  this.#lifecycleHandlers.clear();
@@ -655,18 +597,19 @@ export function isPluginRiskPlan(plan) {
655
597
  (plan.effect == null || isPluginRiskEffect(plan.effect)) &&
656
598
  (plan.details == null || isRecord(plan.details));
657
599
  }
658
- export function decodePluginStreamText(event) {
659
- if (!event.data)
600
+ export function decodePluginEventText(event) {
601
+ const data = event.payload?.data;
602
+ if (typeof data !== "string" || data === "")
660
603
  return "";
661
604
  if (typeof TextDecoder === "function" && typeof atob === "function") {
662
- const binary = atob(event.data);
605
+ const binary = atob(data);
663
606
  const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
664
607
  return new TextDecoder().decode(bytes);
665
608
  }
666
609
  const bufferLike = globalThis.Buffer;
667
610
  if (bufferLike)
668
- return bufferLike.from(event.data, "base64").toString("utf8");
669
- throw new PluginBridgeError("PLUGIN_STREAM_FAILED", "No base64 decoder is available for plugin stream data");
611
+ return bufferLike.from(data, "base64").toString("utf8");
612
+ throw new PluginBridgeError("PLUGIN_STREAM_FAILED", "No base64 decoder is available for plugin execution event data");
670
613
  }
671
614
  export async function trustedParentBridgeHandshakeTranscriptSHA256(handshake, bridgeChannelID) {
672
615
  const subtle = globalThis.crypto?.subtle;
@@ -771,8 +714,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
771
714
  throw new Error("scriptNonce must contain 8-128 URL-safe characters");
772
715
  }
773
716
  const uiProtocolVersion = options.uiProtocolVersion ?? pluginUIProtocolVersion;
774
- if (uiProtocolVersion !== "plugin-ui-v5" && uiProtocolVersion !== "plugin-ui-v6" && uiProtocolVersion !== "plugin-ui-v7") {
775
- throw new Error("uiProtocolVersion must be plugin-ui-v5, plugin-ui-v6, or plugin-ui-v7");
717
+ if (uiProtocolVersion !== pluginUIProtocolVersion) {
718
+ throw new Error(`uiProtocolVersion must be ${pluginUIProtocolVersion}`);
776
719
  }
777
720
  const csp = [
778
721
  "default-src 'none'",
@@ -863,7 +806,6 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
863
806
  const validIdentifier = (value) => typeof value === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(value);
864
807
  const validResourceIdentifier = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value);
865
808
  const validOpaqueHandle = (value, prefix) => typeof value === "string" && value.startsWith(prefix + "_") && /^[A-Za-z0-9_-]{8,160}$/.test(value);
866
- const validDeliveryID = (value) => typeof value === "string" && /^delivery_[A-Za-z0-9_-]{8,128}$/.test(value);
867
809
  const validDigest = (value) => typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
868
810
  const validPath = (value) => typeof value === "string" && value.length > 0 && value.length <= 512 && !value.startsWith("/") && !value.includes("\\\\") && !value.split("/").some((part) => !part || part === "." || part === "..");
869
811
  const validAttribute = (tag, name, value) => {
@@ -928,14 +870,14 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
928
870
  let workerHeartbeatTimeout;
929
871
  let pendingQuiesceID;
930
872
  const pendingWorkerRequests = new Set();
931
- const requestSequence = { rpc: 0, stream: 0, stream_ack: 0, render: 0, operation: 0, canvas: 0, asset: 0 };
932
- let operationSnapshotSurfaceTokens = 8;
933
- let operationSnapshotSurfaceRefillAt = performance.now();
934
- const operationSnapshotStateIdleMS = 120000;
935
- let operationSnapshotNextPruneAt = performance.now() + 30000;
936
- const maxOperationSnapshotStates = 1024;
937
- const operationSnapshotStates = new Map();
938
- const operationSnapshotRequests = new Map();
873
+ const requestSequence = { rpc: 0, execution: 0, render: 0, canvas: 0, asset: 0 };
874
+ let executionQuerySurfaceTokens = 8;
875
+ let executionQuerySurfaceRefillAt = performance.now();
876
+ const executionQueryStateIdleMS = 120000;
877
+ let executionQueryNextPruneAt = performance.now() + 30000;
878
+ const maxExecutionQueryStates = 1024;
879
+ const executionQueryStates = new Map();
880
+ const executionQueryRequests = new Map();
939
881
  let renderWindowStartedAt = 0;
940
882
  let renderCount = 0;
941
883
  let uiRevision = 0;
@@ -1045,8 +987,8 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
1045
987
  for (const url of blobURLs) URL.revokeObjectURL(url);
1046
988
  blobURLs.clear();
1047
989
  pendingWorkerRequests.clear();
1048
- operationSnapshotStates.clear();
1049
- operationSnapshotRequests.clear();
990
+ executionQueryStates.clear();
991
+ executionQueryRequests.clear();
1050
992
  pendingAssets.clear();
1051
993
  queuedAssets.length = 0;
1052
994
  activeAssetReads = 0;
@@ -2041,7 +1983,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2041
1983
  const validCall = (value) => exactKeys(value, ["type", "request"]) && value.type === "redevplugin.bridge.call" && isRecord(value.request) && Object.keys(value.request).every((key) => ["id", "method", "params"].includes(key)) && typeof value.request.id === "string" && value.request.id.length <= 128 && typeof value.request.method === "string" && /^[A-Za-z0-9._:-]{1,256}$/.test(value.request.method) && (value.request.params === undefined || isRecord(value.request.params));
2042
1984
  const requestID = (value, expectedKind) => {
2043
1985
  if (typeof value !== "string") return undefined;
2044
- const match = /^(rpc|stream|stream_ack|render|operation|canvas|asset)_([1-9][0-9]{0,15})$/.exec(value);
1986
+ const match = /^(rpc|execution|render|canvas|asset)_([1-9][0-9]{0,15})$/.exec(value);
2045
1987
  if (!match || match[1] !== expectedKind) return undefined;
2046
1988
  const sequence = Number(match[2]);
2047
1989
  return Number.isSafeInteger(sequence) ? { kind: match[1], sequence } : undefined;
@@ -2054,10 +1996,10 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2054
1996
  return true;
2055
1997
  };
2056
1998
  const completeWorkerRequest = (id) => pendingWorkerRequests.delete(id);
2057
- const completeOperationSnapshotRequest = (id) => {
2058
- const operationID = operationSnapshotRequests.get(id);
2059
- operationSnapshotRequests.delete(id);
2060
- const state = operationSnapshotStates.get(operationID);
1999
+ const completeExecutionQueryRequest = (id) => {
2000
+ const executionID = executionQueryRequests.get(id);
2001
+ executionQueryRequests.delete(id);
2002
+ const state = executionQueryStates.get(executionID);
2061
2003
  if (state) { state.inFlight = false; state.lastSeen = performance.now(); }
2062
2004
  completeWorkerRequest(id);
2063
2005
  };
@@ -2065,24 +2007,24 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2065
2007
  tokens: Math.min(burst, tokens + Math.max(0, now - refillAt) * rate / 1000),
2066
2008
  refillAt: Math.max(refillAt, now),
2067
2009
  });
2068
- const operationSnapshotRateLimit = (operationID, requestID) => {
2010
+ const executionQueryRateLimit = (executionID, requestID) => {
2069
2011
  const now = performance.now();
2070
- const surface = refillSnapshotTokens(operationSnapshotSurfaceTokens, operationSnapshotSurfaceRefillAt, 8, 8, now);
2071
- operationSnapshotSurfaceTokens = surface.tokens;
2072
- operationSnapshotSurfaceRefillAt = surface.refillAt;
2073
- if (operationSnapshotSurfaceTokens < 1) return Math.max(500, Math.min(10000, Math.ceil((1 - operationSnapshotSurfaceTokens) / 8 * 1000)));
2074
- operationSnapshotSurfaceTokens -= 1;
2075
- let existing = operationSnapshotStates.get(operationID);
2012
+ const surface = refillSnapshotTokens(executionQuerySurfaceTokens, executionQuerySurfaceRefillAt, 8, 8, now);
2013
+ executionQuerySurfaceTokens = surface.tokens;
2014
+ executionQuerySurfaceRefillAt = surface.refillAt;
2015
+ if (executionQuerySurfaceTokens < 1) return Math.max(500, Math.min(10000, Math.ceil((1 - executionQuerySurfaceTokens) / 8 * 1000)));
2016
+ executionQuerySurfaceTokens -= 1;
2017
+ let existing = executionQueryStates.get(executionID);
2076
2018
  if (!existing) {
2077
- if (now >= operationSnapshotNextPruneAt) {
2078
- for (const [retainedOperationID, state] of operationSnapshotStates) {
2079
- if (!state.inFlight && now - state.lastSeen >= operationSnapshotStateIdleMS) operationSnapshotStates.delete(retainedOperationID);
2019
+ if (now >= executionQueryNextPruneAt) {
2020
+ for (const [retainedExecutionID, state] of executionQueryStates) {
2021
+ if (!state.inFlight && now - state.lastSeen >= executionQueryStateIdleMS) executionQueryStates.delete(retainedExecutionID);
2080
2022
  }
2081
- operationSnapshotNextPruneAt = now + 30000;
2023
+ executionQueryNextPruneAt = now + 30000;
2082
2024
  }
2083
- if (operationSnapshotStates.size >= maxOperationSnapshotStates) return 10000;
2025
+ if (executionQueryStates.size >= maxExecutionQueryStates) return 10000;
2084
2026
  existing = { tokens: 2, refillAt: now, lastSeen: now, inFlight: false };
2085
- operationSnapshotStates.set(operationID, existing);
2027
+ executionQueryStates.set(executionID, existing);
2086
2028
  }
2087
2029
  const operation = refillSnapshotTokens(existing.tokens, existing.refillAt, 2, 2, now);
2088
2030
  existing.tokens = operation.tokens;
@@ -2093,7 +2035,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2093
2035
  if (retryAfterMS > 0) return Math.max(500, Math.min(10000, retryAfterMS));
2094
2036
  existing.tokens -= 1;
2095
2037
  existing.inFlight = true;
2096
- operationSnapshotRequests.set(requestID, operationID);
2038
+ executionQueryRequests.set(requestID, executionID);
2097
2039
  return 0;
2098
2040
  };
2099
2041
  const renderRateAllowed = () => {
@@ -2309,36 +2251,30 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2309
2251
  sendParent(message);
2310
2252
  return;
2311
2253
  }
2312
- if (exactKeys(message, ["type", "id", "stream_handle"]) && message.type === "redevplugin.bridge.stream.read" && typeof message.id === "string" && validOpaqueHandle(message.stream_handle, "stream")) {
2313
- if (!acceptWorkerRequest(message.id, "stream")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2314
- sendParent(message);
2315
- return;
2316
- }
2317
- if (exactKeys(message, ["type", "id", "stream_handle", "delivery_id"]) && message.type === "redevplugin.bridge.stream.ack" &&
2318
- requestID(message.id, "stream_ack") && validOpaqueHandle(message.stream_handle, "stream") && validDeliveryID(message.delivery_id)) {
2319
- if (!acceptWorkerRequest(message.id, "stream_ack")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2320
- sendParent(message);
2321
- return;
2322
- }
2323
- if (isRecord(message) && Object.keys(message).every((key) => ["type", "id", "operation_id", "reason"].includes(key)) &&
2324
- message.type === "redevplugin.bridge.operation.cancel" && typeof message.id === "string" &&
2325
- validOpaqueHandle(message.operation_id, "operation") && (message.reason === undefined || (typeof message.reason === "string" && message.reason.length <= 256))) {
2326
- if (!acceptWorkerRequest(message.id, "operation")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2254
+ if (isRecord(message) && Object.keys(message).every((key) => ["type", "id", "execution_id", "reason"].includes(key)) &&
2255
+ message.type === "redevplugin.bridge.execution.cancel" && typeof message.id === "string" &&
2256
+ validOpaqueHandle(message.execution_id, "execution") && (message.reason === undefined || (typeof message.reason === "string" && message.reason.length <= 256))) {
2257
+ if (!acceptWorkerRequest(message.id, "execution")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2327
2258
  sendParent(message);
2328
2259
  return;
2329
2260
  }
2330
- if (exactKeys(message, ["type", "id", "operation_id"]) && message.type === "redevplugin.bridge.operation.snapshot" &&
2331
- typeof message.id === "string" && validOpaqueHandle(message.operation_id, "operation")) {
2332
- if (protocolVersion !== "plugin-ui-v6" && protocolVersion !== "plugin-ui-v7") return rejectWorkerRequest(message.id, "plugin operation observation requires plugin-ui-v6 or plugin-ui-v7");
2333
- if (!acceptWorkerRequest(message.id, "operation")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2334
- const retryAfterMS = operationSnapshotRateLimit(message.operation_id, message.id);
2261
+ if (exactKeys(message, ["type", "id", "execution_id"]) && message.type === "redevplugin.bridge.execution.query" &&
2262
+ typeof message.id === "string" && validOpaqueHandle(message.execution_id, "execution")) {
2263
+ if (!acceptWorkerRequest(message.id, "execution")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2264
+ const retryAfterMS = executionQueryRateLimit(message.execution_id, message.id);
2335
2265
  if (retryAfterMS > 0) {
2336
2266
  completeWorkerRequest(message.id);
2337
- return sendWorker({ type: "redevplugin.bridge.response", id: message.id, ok: false, error_code: "PLUGIN_OPERATION_RATE_LIMITED", error: "plugin operation snapshot rate limited", error_details: { retry_after_ms: retryAfterMS } });
2267
+ return sendWorker({ type: "redevplugin.bridge.response", id: message.id, ok: false, error_code: "PLUGIN_EXECUTION_BLOCKED", error: "plugin execution query rate limited" });
2338
2268
  }
2339
2269
  sendParent(message);
2340
2270
  return;
2341
2271
  }
2272
+ if (exactKeys(message, ["type", "id", "execution_id", "after_cursor"]) && message.type === "redevplugin.bridge.execution.events" &&
2273
+ typeof message.id === "string" && validOpaqueHandle(message.execution_id, "execution") && Number.isSafeInteger(message.after_cursor) && message.after_cursor >= 0) {
2274
+ if (!acceptWorkerRequest(message.id, "execution")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2275
+ sendParent(message);
2276
+ return;
2277
+ }
2342
2278
  if (exactKeys(message, ["type", "id", "canvas_id"]) && message.type === "redevplugin.ui.canvas.open" && typeof message.id === "string" && validResourceIdentifier(message.canvas_id)) {
2343
2279
  if (!acceptWorkerRequest(message.id, "canvas")) return rejectWorkerRequest(message.id, "duplicate, replayed, or excessive plugin request");
2344
2280
  openCanvas(message.id, message.canvas_id);
@@ -2392,7 +2328,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2392
2328
  completeWorkerRequest(message.id);
2393
2329
  return;
2394
2330
  }
2395
- if (operationSnapshotRequests.has(message.id)) completeOperationSnapshotRequest(message.id);
2331
+ if (executionQueryRequests.has(message.id)) completeExecutionQueryRequest(message.id);
2396
2332
  else completeWorkerRequest(message.id);
2397
2333
  sendParent(message);
2398
2334
  return;
@@ -2401,13 +2337,12 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2401
2337
  const onParentMessage = async (event) => {
2402
2338
  const message = event.data;
2403
2339
  if (!initialized) {
2404
- const initKeys = ["type", "frame_generation_id", "surface_handle", "document"];
2405
- if (protocolVersion === "plugin-ui-v7") initKeys.push("context");
2406
- if (!isRecord(message) || !Object.keys(message).every((key) => initKeys.includes(key)) || !initKeys.slice(0, 4).every((key) => Object.prototype.hasOwnProperty.call(message, key)) || message.type !== "redevplugin.surface.initialize" || message.frame_generation_id !== frameGenerationID || !validOpaqueHandle(message.surface_handle, "surface") || !validDocument(message.document) || (protocolVersion === "plugin-ui-v7" && message.context !== undefined && !validSurfaceContext(message.context))) return fail("invalid private initialize message");
2340
+ const initKeys = ["type", "frame_generation_id", "surface_handle", "document", "context"];
2341
+ if (!isRecord(message) || !Object.keys(message).every((key) => initKeys.includes(key)) || !initKeys.slice(0, 4).every((key) => Object.prototype.hasOwnProperty.call(message, key)) || message.type !== "redevplugin.surface.initialize" || message.frame_generation_id !== frameGenerationID || !validOpaqueHandle(message.surface_handle, "surface") || !validDocument(message.document) || (message.context !== undefined && !validSurfaceContext(message.context))) return fail("invalid private initialize message");
2407
2342
  initialized = true;
2408
2343
  surfaceHandle = message.surface_handle;
2409
2344
  currentDocument = message.document;
2410
- currentContext = protocolVersion === "plugin-ui-v7" ? message.context : undefined;
2345
+ currentContext = message.context;
2411
2346
  try { applyStaticDocument(currentDocument); if (currentContext) applySurfaceContext(currentContext); startWorker(currentDocument); }
2412
2347
  catch (error) { return fail(error); }
2413
2348
  requestAnimationFrame(() => requestAnimationFrame(() => {
@@ -2416,7 +2351,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2416
2351
  }));
2417
2352
  return;
2418
2353
  }
2419
- if (protocolVersion === "plugin-ui-v7" && isRecord(message) && exactKeys(message, ["type", "frame_generation_id", "surface_handle", "context"]) && message.type === "redevplugin.surface.context" && message.frame_generation_id === frameGenerationID && message.surface_handle === surfaceHandle && validSurfaceContext(message.context)) {
2354
+ if (isRecord(message) && exactKeys(message, ["type", "frame_generation_id", "surface_handle", "context"]) && message.type === "redevplugin.surface.context" && message.frame_generation_id === frameGenerationID && message.surface_handle === surfaceHandle && validSurfaceContext(message.context)) {
2420
2355
  if (currentContext && message.context.revision <= currentContext.revision) return;
2421
2356
  currentContext = message.context;
2422
2357
  applySurfaceContext(currentContext);
@@ -2424,7 +2359,7 @@ export function createOpaquePluginBootstrapHTML(options = {}) {
2424
2359
  return;
2425
2360
  }
2426
2361
  if (message && message.type === "redevplugin.bridge.response" && typeof message.id === "string" && pendingWorkerRequests.has(message.id) && withinLimit(message)) {
2427
- if (operationSnapshotRequests.has(message.id)) completeOperationSnapshotRequest(message.id);
2362
+ if (executionQueryRequests.has(message.id)) completeExecutionQueryRequest(message.id);
2428
2363
  else completeWorkerRequest(message.id);
2429
2364
  sendWorker(message);
2430
2365
  return;
@@ -2502,7 +2437,6 @@ class PluginSurfaceHostImplementation {
2502
2437
  #document;
2503
2438
  #surfaceContext;
2504
2439
  #assets = new Map();
2505
- #streamCredentials = new Map();
2506
2440
  #pendingRequestControllers = new Map();
2507
2441
  #activeTransportRequests = 0;
2508
2442
  #activeAssetReads = 0;
@@ -2639,7 +2573,7 @@ class PluginSurfaceHostImplementation {
2639
2573
  frame_generation_id: this.frameGenerationId,
2640
2574
  surface_handle: this.surfaceHandle,
2641
2575
  document: preparation.document,
2642
- context: this.bootstrap.uiProtocolVersion === "plugin-ui-v7" ? this.#surfaceContext : undefined,
2576
+ context: this.#surfaceContext,
2643
2577
  }));
2644
2578
  this.#rendererInitialized = true;
2645
2579
  await Promise.all([signals.firstPaint.promise, signals.workerReady.promise]);
@@ -2658,9 +2592,6 @@ class PluginSurfaceHostImplementation {
2658
2592
  }
2659
2593
  updateContext(context) {
2660
2594
  this.#assertActive();
2661
- if (this.bootstrap.uiProtocolVersion !== "plugin-ui-v7") {
2662
- throw new PluginBridgeError("PLUGIN_UI_PROTOCOL_UNSUPPORTED", "Plugin surface context requires plugin-ui-v7");
2663
- }
2664
2595
  const normalized = normalizePluginSurfaceContext(context);
2665
2596
  if (this.#surfaceContext && normalized.revision <= this.#surfaceContext.revision) {
2666
2597
  throw new PluginBridgeError("PLUGIN_INVALID_REQUEST", "Plugin surface context revision must increase monotonically");
@@ -2757,7 +2688,6 @@ class PluginSurfaceHostImplementation {
2757
2688
  this.#assetSessionID = undefined;
2758
2689
  this.#document = undefined;
2759
2690
  this.#assets.clear();
2760
- this.#streamCredentials.clear();
2761
2691
  if (this.#port) {
2762
2692
  try {
2763
2693
  this.#port.postMessage({ type: "redevplugin.bridge.lifecycle", event: { type: "dispose" } });
@@ -2824,20 +2754,16 @@ class PluginSurfaceHostImplementation {
2824
2754
  await this.#handleCall(data.request);
2825
2755
  return;
2826
2756
  }
2827
- if (isStreamReadMessage(data)) {
2828
- await this.#handleStreamRead(data.id, data.stream_handle);
2829
- return;
2830
- }
2831
- if (isStreamAcknowledgeMessage(data)) {
2832
- await this.#handleStreamAcknowledge(data.id, data.stream_handle, data.delivery_id);
2757
+ if (isExecutionCancelMessage(data)) {
2758
+ await this.#handleExecutionCancel(data);
2833
2759
  return;
2834
2760
  }
2835
- if (isOperationCancelMessage(data)) {
2836
- await this.#handleOperationCancel(data);
2761
+ if (isExecutionQueryMessage(data)) {
2762
+ await this.#handleExecutionQuery(data);
2837
2763
  return;
2838
2764
  }
2839
- if (isOperationSnapshotMessage(data)) {
2840
- await this.#handleOperationSnapshot(data);
2765
+ if (isExecutionEventsMessage(data)) {
2766
+ await this.#handleExecutionEvents(data);
2841
2767
  return;
2842
2768
  }
2843
2769
  if (isAssetReadMessage(data)) {
@@ -2939,152 +2865,57 @@ class PluginSurfaceHostImplementation {
2939
2865
  this.#postError(request.id, bridgeError.errorCode, bridgeError.message, bridgeError.details, bridgeError.mutationOutcome);
2940
2866
  }
2941
2867
  }
2942
- async #handleStreamRead(id, streamHandle) {
2943
- const credential = this.#streamCredentials.get(streamHandle);
2944
- if (!credential || credential.completed || credential.reading || credential.acknowledging) {
2945
- this.#postError(id, "PLUGIN_STREAM_TICKET_INVALID", "Plugin stream handle is invalid, completed, or busy");
2946
- return;
2947
- }
2948
- if (credential.expiresAtMs <= Date.now()) {
2949
- this.#postError(id, "PLUGIN_STREAM_TICKET_INVALID", "Plugin stream handle is expired");
2950
- return;
2951
- }
2952
- if (credential.pending) {
2953
- this.#postResponse(id, credential.pending.response);
2954
- return;
2955
- }
2956
- credential.reading = true;
2957
- const controller = this.#registerPendingRequest(id);
2958
- try {
2959
- const readPath = `/_redevplugin/api/plugins/surfaces/${encodeURIComponent(this.bootstrap.surfaceInstanceId)}/streams/read`;
2960
- const readBody = () => ({ stream_id: credential.streamID, stream_ticket: credential.streamTicket, read_id: credential.readID });
2961
- let result;
2962
- try {
2963
- result = await this.#postJSON(readPath, readBody, controller.signal);
2964
- }
2965
- catch (error) {
2966
- if (!retryableStreamReadTransportFailure(error) || controller.signal.aborted || this.#disposed)
2967
- throw error;
2968
- result = await this.#postJSON(readPath, readBody, controller.signal);
2969
- }
2970
- if (!isStreamReadResult(result, credential.streamID, credential.readID, credential.lastSequence)) {
2971
- throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin stream endpoint returned an invalid response");
2972
- }
2973
- const lastSequence = result.events.length > 0 ? result.events[result.events.length - 1].sequence : credential.lastSequence;
2974
- const events = result.events.map(publicPluginStreamEvent);
2975
- const response = result.done
2976
- ? { delivery_id: result.delivery_id, events, done: true, terminal_status: result.terminal_status, retry_after_ms: 0 }
2977
- : { delivery_id: result.delivery_id, events, done: false, retry_after_ms: events.length === 0 ? 25 : 0 };
2978
- if (result.delivery_id) {
2979
- credential.pending = {
2980
- deliveryID: result.delivery_id,
2981
- lastSequence,
2982
- done: result.done,
2983
- response,
2984
- };
2985
- }
2986
- credential.reading = false;
2987
- if (!controller.signal.aborted && !this.#disposed)
2988
- this.#postResponse(id, response);
2989
- }
2990
- catch (error) {
2991
- const bridgeError = toBridgeError(error, "PLUGIN_RUNTIME_UNAVAILABLE");
2992
- if (streamReadFailureInvalidatesCredential(bridgeError)) {
2993
- this.#streamCredentials.delete(streamHandle);
2994
- }
2995
- else {
2996
- credential.reading = false;
2997
- }
2998
- if (controller.signal.aborted || this.#disposed)
2999
- return;
3000
- this.#postError(id, bridgeError.errorCode, bridgeError.message);
3001
- }
3002
- finally {
3003
- this.#pendingRequestControllers.delete(id);
3004
- }
3005
- }
3006
- async #handleStreamAcknowledge(id, streamHandle, deliveryID) {
3007
- const credential = this.#streamCredentials.get(streamHandle);
3008
- if (!credential || credential.reading || credential.acknowledging) {
3009
- this.#postError(id, "PLUGIN_STREAM_DELIVERY_INVALID", "Plugin stream delivery is invalid or busy", undefined, "not_committed");
3010
- return;
3011
- }
3012
- if (credential.lastAcknowledgedDeliveryID === deliveryID) {
3013
- this.#postResponse(id, undefined);
3014
- return;
3015
- }
3016
- if (!credential.pending || credential.pending.deliveryID !== deliveryID) {
3017
- this.#postError(id, "PLUGIN_STREAM_DELIVERY_INVALID", "Plugin stream delivery does not match the pending batch", undefined, "not_committed");
3018
- return;
3019
- }
3020
- credential.acknowledging = true;
3021
- const controller = this.#registerPendingRequest(id);
2868
+ async #handleExecutionCancel(message) {
2869
+ const controller = this.#registerPendingRequest(message.id);
3022
2870
  try {
3023
- const result = await this.#postMutationJSON(`/_redevplugin/api/plugins/surfaces/${encodeURIComponent(this.bootstrap.surfaceInstanceId)}/streams/ack`, () => ({
3024
- stream_id: credential.streamID,
3025
- stream_ticket: credential.streamTicket,
3026
- delivery_id: deliveryID,
3027
- }), controller.signal);
3028
- if (!hasExactKeys(result, ["acknowledged"]) || result.acknowledged !== true) {
3029
- throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin stream acknowledgement endpoint returned an invalid response", undefined, undefined, "unknown");
3030
- }
3031
- const pending = credential.pending;
3032
- credential.lastAcknowledgedDeliveryID = deliveryID;
3033
- credential.lastSequence = pending.lastSequence;
3034
- credential.pending = undefined;
3035
- credential.readID = randomOpaqueHandle("read");
3036
- credential.completed = pending.done;
3037
- credential.acknowledging = false;
2871
+ await this.#postMutationJSON(`/_redevplugin/api/plugins/executions/${encodeURIComponent(message.execution_id)}/cancel`, { reason: message.reason }, controller.signal);
3038
2872
  if (!controller.signal.aborted && !this.#disposed)
3039
- this.#postResponse(id, undefined);
2873
+ this.#postResponse(message.id, undefined);
3040
2874
  }
3041
2875
  catch (error) {
3042
- credential.acknowledging = false;
3043
2876
  if (controller.signal.aborted || this.#disposed)
3044
2877
  return;
3045
- const bridgeError = toBridgeError(error, "PLUGIN_STREAM_DELIVERY_INVALID");
3046
- this.#postError(id, bridgeError.errorCode, bridgeError.message, bridgeError.details, bridgeError.mutationOutcome);
2878
+ const bridgeError = toBridgeError(error, "PLUGIN_EXECUTION_BLOCKED");
2879
+ this.#postError(message.id, bridgeError.errorCode, bridgeError.message, bridgeError.details, bridgeError.mutationOutcome);
3047
2880
  }
3048
2881
  finally {
3049
- this.#pendingRequestControllers.delete(id);
2882
+ this.#pendingRequestControllers.delete(message.id);
3050
2883
  }
3051
2884
  }
3052
- async #handleOperationCancel(message) {
2885
+ async #handleExecutionQuery(message) {
3053
2886
  const controller = this.#registerPendingRequest(message.id);
3054
2887
  try {
3055
- await this.#postMutationJSON(`/_redevplugin/api/plugins/surfaces/${encodeURIComponent(this.bootstrap.surfaceInstanceId)}/operations/cancel`, { operation_id: message.operation_id, bridge_channel_id: this.bridgeChannelId, reason: message.reason }, controller.signal);
3056
- this.#releaseOperationStreams(message.operation_id);
2888
+ const snapshot = await this.#postJSON(`/_redevplugin/api/plugins/executions/${encodeURIComponent(message.execution_id)}/query`, {}, controller.signal);
2889
+ if (!isPluginExecutionSnapshot(snapshot) || snapshot.execution_id !== message.execution_id) {
2890
+ throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin execution snapshot endpoint returned an invalid response");
2891
+ }
3057
2892
  if (!controller.signal.aborted && !this.#disposed)
3058
- this.#postResponse(message.id, undefined);
2893
+ this.#postResponse(message.id, snapshot);
3059
2894
  }
3060
2895
  catch (error) {
3061
2896
  if (controller.signal.aborted || this.#disposed)
3062
2897
  return;
3063
- const bridgeError = toBridgeError(error, "PLUGIN_OPERATION_BLOCKED");
2898
+ const bridgeError = toBridgeError(error, "PLUGIN_EXECUTION_NOT_FOUND");
3064
2899
  this.#postError(message.id, bridgeError.errorCode, bridgeError.message, bridgeError.details, bridgeError.mutationOutcome);
3065
2900
  }
3066
2901
  finally {
3067
2902
  this.#pendingRequestControllers.delete(message.id);
3068
2903
  }
3069
2904
  }
3070
- async #handleOperationSnapshot(message) {
3071
- if (this.bootstrap.uiProtocolVersion !== "plugin-ui-v6" && this.bootstrap.uiProtocolVersion !== "plugin-ui-v7") {
3072
- this.#postError(message.id, "PLUGIN_UI_PROTOCOL_UNSUPPORTED", "Plugin operation observation requires plugin-ui-v6 or plugin-ui-v7");
3073
- return;
3074
- }
2905
+ async #handleExecutionEvents(message) {
3075
2906
  const controller = this.#registerPendingRequest(message.id);
3076
2907
  try {
3077
- const snapshot = await this.#postJSON(`/_redevplugin/api/plugins/surfaces/${encodeURIComponent(this.bootstrap.surfaceInstanceId)}/operations/query`, { operation_id: message.operation_id, bridge_channel_id: this.bridgeChannelId }, controller.signal);
3078
- if (!isPluginOperationSnapshot(snapshot) || snapshot.operation_id !== message.operation_id) {
3079
- throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin operation snapshot endpoint returned an invalid response");
2908
+ const result = await this.#postJSON(`/_redevplugin/api/plugins/executions/${encodeURIComponent(message.execution_id)}/events/query`, { after_cursor: message.after_cursor }, controller.signal);
2909
+ if (!isPluginExecutionEventList(result, message.execution_id, message.after_cursor)) {
2910
+ throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin execution event endpoint returned an invalid response");
3080
2911
  }
3081
2912
  if (!controller.signal.aborted && !this.#disposed)
3082
- this.#postResponse(message.id, snapshot);
2913
+ this.#postResponse(message.id, result);
3083
2914
  }
3084
2915
  catch (error) {
3085
2916
  if (controller.signal.aborted || this.#disposed)
3086
2917
  return;
3087
- const bridgeError = toBridgeError(error, "PLUGIN_OPERATION_NOT_FOUND");
2918
+ const bridgeError = toBridgeError(error, "PLUGIN_EXECUTION_NOT_FOUND");
3088
2919
  this.#postError(message.id, bridgeError.errorCode, bridgeError.message, bridgeError.details, bridgeError.mutationOutcome);
3089
2920
  }
3090
2921
  finally {
@@ -3133,52 +2964,13 @@ class PluginSurfaceHostImplementation {
3133
2964
  return controller;
3134
2965
  }
3135
2966
  #publicMethodResult(result) {
3136
- const publicResult = {
2967
+ return removeUndefined({
3137
2968
  data: result.data,
3138
- operation_id: result.operation_id,
2969
+ execution_id: result.execution_id,
3139
2970
  confirmation_required: result.confirmation_required,
3140
2971
  confirmation_token_id: result.confirmation_token_id,
3141
2972
  request_hash: result.request_hash,
3142
- };
3143
- if (result.stream_id || result.stream_ticket || result.stream_ticket_id || result.stream_expires_at) {
3144
- if (!result.operation_id || !result.stream_id || !result.stream_ticket || !result.stream_ticket_id || !result.stream_expires_at) {
3145
- throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin RPC returned incomplete stream credentials");
3146
- }
3147
- const expiresAtMs = Date.parse(result.stream_expires_at);
3148
- if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) {
3149
- throw new PluginBridgeError("PLUGIN_STREAM_TICKET_INVALID", "Plugin RPC returned an expired stream ticket");
3150
- }
3151
- this.#pruneExpiredStreamCredentials();
3152
- if (this.#streamCredentials.size >= maxRetainedPluginStreamHandles) {
3153
- throw new PluginBridgeError("PLUGIN_JSON_LIMIT_EXCEEDED", "Plugin surface retained too many unread stream handles");
3154
- }
3155
- const handle = randomOpaqueHandle("stream");
3156
- this.#streamCredentials.set(handle, {
3157
- streamID: result.stream_id,
3158
- operationID: result.operation_id,
3159
- streamTicket: result.stream_ticket,
3160
- expiresAtMs,
3161
- lastSequence: 0,
3162
- readID: randomOpaqueHandle("read"),
3163
- reading: false,
3164
- acknowledging: false,
3165
- completed: false,
3166
- });
3167
- publicResult.stream_handle = handle;
3168
- }
3169
- return removeUndefined(publicResult);
3170
- }
3171
- #pruneExpiredStreamCredentials(now = Date.now()) {
3172
- for (const [handle, credential] of this.#streamCredentials) {
3173
- if (credential.expiresAtMs <= now)
3174
- this.#streamCredentials.delete(handle);
3175
- }
3176
- }
3177
- #releaseOperationStreams(operationID) {
3178
- for (const [handle, credential] of this.#streamCredentials) {
3179
- if (credential.operationID === operationID)
3180
- this.#streamCredentials.delete(handle);
3181
- }
2973
+ });
3182
2974
  }
3183
2975
  #callRPC(request, confirmationID, signal) {
3184
2976
  return this.#postMutationJSON("/_redevplugin/api/plugins/rpc", () => this.#rpcBody(request, confirmationID), signal);
@@ -4066,7 +3858,7 @@ function validateHostBootstrap(bootstrap) {
4066
3858
  throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin surface bootstrap is incomplete");
4067
3859
  }
4068
3860
  }
4069
- if (bootstrap.uiProtocolVersion !== "plugin-ui-v5" && bootstrap.uiProtocolVersion !== "plugin-ui-v6" && bootstrap.uiProtocolVersion !== "plugin-ui-v7") {
3861
+ if (bootstrap.uiProtocolVersion !== pluginUIProtocolVersion) {
4070
3862
  throw new PluginBridgeError("PLUGIN_CONTRACT_MISMATCH", "Plugin surface UI protocol is unsupported");
4071
3863
  }
4072
3864
  if (!Number.isSafeInteger(bootstrap.managementRevision) || bootstrap.managementRevision < 1 ||
@@ -4214,42 +4006,32 @@ function isBridgeCallMessage(value) {
4214
4006
  validMethod(value.request.method) &&
4215
4007
  validRPCParams(value.request.params);
4216
4008
  }
4217
- function isStreamReadMessage(value) {
4218
- return hasExactKeys(value, ["type", "id", "stream_handle"]) &&
4219
- value.type === "redevplugin.bridge.stream.read" &&
4220
- validBridgeRequestID(value.id, "stream") &&
4221
- validOpaqueHandle(value.stream_handle, "stream");
4222
- }
4223
- function isStreamAcknowledgeMessage(value) {
4224
- return hasExactKeys(value, ["type", "id", "stream_handle", "delivery_id"]) &&
4225
- value.type === "redevplugin.bridge.stream.ack" &&
4226
- validBridgeRequestID(value.id, "stream_ack") &&
4227
- validOpaqueHandle(value.stream_handle, "stream") &&
4228
- validDeliveryID(value.delivery_id);
4229
- }
4230
- function isOperationCancelMessage(value) {
4231
- return hasAllowedKeys(value, ["type", "id", "operation_id", "reason"]) &&
4232
- value.type === "redevplugin.bridge.operation.cancel" &&
4233
- validBridgeRequestID(value.id, "operation") &&
4234
- validOpaqueHandle(value.operation_id, "operation") &&
4009
+ function isExecutionCancelMessage(value) {
4010
+ return hasAllowedKeys(value, ["type", "id", "execution_id", "reason"]) &&
4011
+ value.type === "redevplugin.bridge.execution.cancel" &&
4012
+ validBridgeRequestID(value.id, "execution") &&
4013
+ validOpaqueHandle(value.execution_id, "execution") &&
4235
4014
  (value.reason === undefined || (typeof value.reason === "string" && value.reason.length <= 256));
4236
4015
  }
4237
- function isOperationSnapshotMessage(value) {
4238
- return hasExactKeys(value, ["type", "id", "operation_id"]) &&
4239
- value.type === "redevplugin.bridge.operation.snapshot" &&
4240
- validBridgeRequestID(value.id, "operation") &&
4241
- validOpaqueHandle(value.operation_id, "operation");
4016
+ function isExecutionQueryMessage(value) {
4017
+ return hasExactKeys(value, ["type", "id", "execution_id"]) && value.type === "redevplugin.bridge.execution.query" &&
4018
+ validBridgeRequestID(value.id, "execution") && validOpaqueHandle(value.execution_id, "execution");
4019
+ }
4020
+ function isExecutionEventsMessage(value) {
4021
+ return hasExactKeys(value, ["type", "id", "execution_id", "after_cursor"]) && value.type === "redevplugin.bridge.execution.events" &&
4022
+ validBridgeRequestID(value.id, "execution") && validOpaqueHandle(value.execution_id, "execution") &&
4023
+ Number.isSafeInteger(value.after_cursor) && Number(value.after_cursor) >= 0;
4242
4024
  }
4243
- function isPluginOperationSnapshot(value) {
4244
- if (!isRecord(value) || !validOpaqueHandle(value.operation_id, "operation") || typeof value.cancelable !== "boolean" ||
4245
- !Number.isInteger(value.retry_after_ms) || Number(value.retry_after_ms) < 500 || Number(value.retry_after_ms) > 10000 ||
4246
- !validDateTime(value.created_at) || !validDateTime(value.updated_at))
4025
+ function isPluginExecutionSnapshot(value) {
4026
+ if (!isRecord(value) || !validOpaqueHandle(value.execution_id, "execution") || typeof value.plugin_instance_id !== "string" ||
4027
+ (value.kind !== "operation" && value.kind !== "subscription") || typeof value.cancelable !== "boolean" ||
4028
+ !Number.isSafeInteger(value.cursor) || Number(value.cursor) < 0 || !validDateTime(value.created_at) || !validDateTime(value.updated_at))
4247
4029
  return false;
4248
- const common = ["operation_id", "status", "cancelable", "created_at", "updated_at", "retry_after_ms"];
4030
+ const common = ["execution_id", "plugin_instance_id", "kind", "status", "cursor", "cancelable", "created_at", "updated_at"];
4249
4031
  const withProgress = (keys) => hasAllowedKeys(value, [...keys, "progress"]) && (value.progress === undefined || validOperationProgress(value.progress));
4250
4032
  if (value.status === "running" || value.status === "cancel_requested")
4251
4033
  return withProgress(common);
4252
- if (["completed", "canceled", "orphaned_after_disable", "orphaned_after_uninstall"].includes(String(value.status))) {
4034
+ if (["completed", "canceled", "orphaned"].includes(String(value.status))) {
4253
4035
  return withProgress([...common, "terminal_at"]) && validDateTime(value.terminal_at);
4254
4036
  }
4255
4037
  return value.status === "failed" && withProgress([...common, "terminal_at", "failure_code"]) &&
@@ -4268,6 +4050,22 @@ function validOperationProgress(value) {
4268
4050
  function validDateTime(value) {
4269
4051
  return typeof value === "string" && value.length > 0 && Number.isFinite(Date.parse(value));
4270
4052
  }
4053
+ function isPluginExecutionEventList(value, executionID, afterCursor) {
4054
+ if (!hasExactKeys(value, ["execution_id", "events", "cursor"]) || value.execution_id !== executionID ||
4055
+ !Array.isArray(value.events) || !Number.isSafeInteger(value.cursor) || Number(value.cursor) < afterCursor)
4056
+ return false;
4057
+ let cursor = afterCursor;
4058
+ for (const event of value.events) {
4059
+ if (!isRecord(event) || !hasAllowedKeys(event, ["execution_id", "sequence", "kind", "payload", "error"]) ||
4060
+ event.execution_id !== executionID || !Number.isSafeInteger(event.sequence) || Number(event.sequence) <= cursor ||
4061
+ !["progress", "data", "diagnostic", "terminal"].includes(String(event.kind)) ||
4062
+ (event.payload !== undefined && !isRecord(event.payload)) ||
4063
+ (event.error !== undefined && (!hasExactKeys(event.error, ["code", "message"]) || typeof event.error.code !== "string" || typeof event.error.message !== "string")))
4064
+ return false;
4065
+ cursor = Number(event.sequence);
4066
+ }
4067
+ return Number(value.cursor) === cursor;
4068
+ }
4271
4069
  function isBridgeCancelMessage(value) {
4272
4070
  return hasExactKeys(value, ["type", "id"]) &&
4273
4071
  value.type === "redevplugin.bridge.cancel" &&
@@ -4539,48 +4337,6 @@ function isGatewayTokenResult(value) {
4539
4337
  typeof value.asset_session_id === "string" && value.asset_session_id.length > 0 &&
4540
4338
  typeof value.issued_at === "string" && typeof value.expires_at === "string";
4541
4339
  }
4542
- function isStreamReadResult(value, expectedStreamID, expectedReadID, previousSequence) {
4543
- if (!isRecord(value) || typeof value.done !== "boolean" || !Array.isArray(value.events))
4544
- return false;
4545
- const hasDelivery = value.events.length > 0 || value.done;
4546
- const expectedKeys = value.done
4547
- ? ["delivery_id", "done", "events", "read_id", "terminal_status"]
4548
- : hasDelivery
4549
- ? ["delivery_id", "done", "events", "read_id"]
4550
- : ["done", "events", "read_id"];
4551
- if (!hasExactKeys(value, expectedKeys))
4552
- return false;
4553
- if (value.read_id !== expectedReadID || (hasDelivery && !validDeliveryID(value.delivery_id)))
4554
- return false;
4555
- if (value.done) {
4556
- if (!validPluginStreamTerminalStatus(value.terminal_status))
4557
- return false;
4558
- }
4559
- let terminal = false;
4560
- for (const event of value.events) {
4561
- if (!isStreamEvent(event) || event.stream_id !== expectedStreamID || event.sequence <= previousSequence || terminal)
4562
- return false;
4563
- previousSequence = event.sequence;
4564
- terminal = event.kind === "end" || event.kind === "error";
4565
- }
4566
- return true;
4567
- }
4568
- function isStreamEvent(value) {
4569
- return hasAllowedKeys(value, ["stream_id", "sequence", "kind", "data", "error", "at"]) &&
4570
- typeof value.stream_id === "string" &&
4571
- Number.isSafeInteger(value.sequence) && Number(value.sequence) > 0 &&
4572
- typeof value.kind === "string" && value.kind.length > 0 &&
4573
- (value.data == null || typeof value.data === "string") &&
4574
- (value.error == null || typeof value.error === "string") &&
4575
- typeof value.at === "string";
4576
- }
4577
- function publicPluginStreamEvent(event) {
4578
- return removeUndefined({ sequence: event.sequence, kind: event.kind, data: event.data, error: event.error, at: event.at });
4579
- }
4580
- function validPluginStreamTerminalStatus(value) {
4581
- return value === "closed" || value === "canceled" || value === "failed" ||
4582
- value === "orphaned_after_disable" || value === "orphaned_after_uninstall";
4583
- }
4584
4340
  function validRPCParams(value) {
4585
4341
  if (value === undefined)
4586
4342
  return true;
@@ -4596,7 +4352,7 @@ const pluginMethodPattern = new RegExp("^[-A-Za-z0-9._:]{1,256}$");
4596
4352
  const pluginActionPattern = new RegExp("^[-A-Za-z0-9._:]{1,128}$");
4597
4353
  const pluginUIIdentifierPattern = new RegExp("^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$");
4598
4354
  const opaqueHandlePattern = new RegExp("^[-A-Za-z0-9_]{8,160}$");
4599
- const bridgeRequestIDPattern = /^(rpc|stream|stream_ack|render|operation|canvas|asset)_([1-9][0-9]{0,15})$/;
4355
+ const bridgeRequestIDPattern = /^(rpc|execution|render|canvas|asset)_([1-9][0-9]{0,15})$/;
4600
4356
  function validBridgeRequestID(value, expectedKind) {
4601
4357
  if (typeof value !== "string")
4602
4358
  return false;
@@ -4617,9 +4373,6 @@ function validUIIdentifier(value) {
4617
4373
  function validOpaqueHandle(value, prefix) {
4618
4374
  return typeof value === "string" && value.startsWith(`${prefix}_`) && opaqueHandlePattern.test(value);
4619
4375
  }
4620
- function validDeliveryID(value) {
4621
- return typeof value === "string" && /^delivery_[A-Za-z0-9_-]{8,128}$/.test(value);
4622
- }
4623
4376
  function validSHA256(value) {
4624
4377
  return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
4625
4378
  }
@@ -4720,25 +4473,6 @@ function requestCancelledError(options, posted) {
4720
4473
  return streamReadAbortedError();
4721
4474
  return new PluginBridgeError("PLUGIN_BRIDGE_CANCELLED", "Plugin bridge request was cancelled", undefined, undefined, mutationOutcome(options, posted));
4722
4475
  }
4723
- function abortableStreamRead(read, signal) {
4724
- if (!signal)
4725
- return read;
4726
- if (signal.aborted)
4727
- return Promise.reject(streamReadAbortedError());
4728
- return new Promise((resolve, reject) => {
4729
- let settled = false;
4730
- const finish = (callback, value) => {
4731
- if (settled)
4732
- return;
4733
- settled = true;
4734
- signal.removeEventListener("abort", onAbort);
4735
- callback(value);
4736
- };
4737
- const onAbort = () => finish(reject, streamReadAbortedError());
4738
- signal.addEventListener("abort", onAbort, { once: true });
4739
- read.then((value) => finish(resolve, value), (error) => finish(reject, error));
4740
- });
4741
- }
4742
4476
  function abortableConfirmationDecision(decision, signal) {
4743
4477
  if (signal.aborted) {
4744
4478
  return Promise.reject(new PluginBridgeError("PLUGIN_BRIDGE_DISPOSED", "Plugin confirmation was aborted"));
@@ -4877,12 +4611,3 @@ function toBridgeError(error, defaultCode) {
4877
4611
  return new PluginBridgeError(defaultCode, error.message);
4878
4612
  return new PluginBridgeError(defaultCode, String(error));
4879
4613
  }
4880
- function streamReadFailureInvalidatesCredential(error) {
4881
- if (!(error instanceof PluginBridgeError))
4882
- return true;
4883
- return streamCredentialInvalidatingErrorCodes.has(error.errorCode);
4884
- }
4885
- function retryableStreamReadTransportFailure(error) {
4886
- return error instanceof PluginTransportError ||
4887
- (error instanceof PluginBridgeError && error.errorCode === "PLUGIN_BRIDGE_TIMEOUT");
4888
- }