@jobshimo/browser-link 0.26.0 → 0.27.1
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/agent-instructions/content.js +53 -6
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/bridge/events.d.ts +1 -1
- package/dist/bridge/events.js +11 -1
- package/dist/bridge/events.js.map +1 -1
- package/dist/bridge/ws-bridge.d.ts +18 -0
- package/dist/bridge/ws-bridge.js +24 -0
- package/dist/bridge/ws-bridge.js.map +1 -1
- package/dist/cdp/detached-flows.d.ts +127 -0
- package/dist/cdp/detached-flows.js +272 -0
- package/dist/cdp/detached-flows.js.map +1 -0
- package/dist/cdp/support.js +2 -0
- package/dist/cdp/support.js.map +1 -1
- package/dist/cdp/transport.js +105 -20
- package/dist/cdp/transport.js.map +1 -1
- package/dist/extension/background.js +396 -37
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/flow-registry.d.ts +298 -13
- package/dist/extension/flow-registry.js +443 -37
- package/dist/extension/flow-registry.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/extension/popup.html +15 -0
- package/dist/extension/popup.js +6 -0
- package/dist/extension/popup.js.map +1 -1
- package/dist/messages.d.ts +9 -5
- package/dist/permissions.js +12 -0
- package/dist/permissions.js.map +1 -1
- package/dist/server.js +10 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +63 -2
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +13 -0
- package/dist/tools/browser-dispatch.js +149 -22
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-flight flow registry + per-tab flow history
|
|
3
|
-
* popup's Flows panel
|
|
2
|
+
* In-flight flow registry + per-tab flow history + detached-flow records —
|
|
3
|
+
* the state behind the popup's Flows panel, behind `deps.shouldCancel` (see
|
|
4
|
+
* `flow.ts`), and behind the `flow_status` wire tool that backs
|
|
5
|
+
* `browser.flow_status`.
|
|
4
6
|
*
|
|
5
7
|
* Extracted out of `background.ts` for the same reason `idle-policy.ts`,
|
|
6
8
|
* `reconnect-policy.ts` and `flow-recording-policy.ts` were: the service
|
|
7
9
|
* worker itself is untestable (it reaches for `chrome.*` at import time),
|
|
8
10
|
* while THIS is the part with rules worth pinning down — the lifecycle of a
|
|
9
11
|
* running flow, what a cancel does to an id nobody recognises, the 20-entry
|
|
10
|
-
* history cap, and above all WHAT A HISTORY ENTRY IS
|
|
12
|
+
* history cap, the detached ceiling, and above all WHAT A HISTORY ENTRY IS
|
|
13
|
+
* ALLOWED TO CONTAIN.
|
|
11
14
|
*
|
|
12
15
|
* PRIVACY CONTRACT (same rule as the persistent map, enforced by
|
|
13
16
|
* `toHistoryEntry` and asserted in the tests): a history entry holds UI
|
|
@@ -22,6 +25,18 @@
|
|
|
22
25
|
* `background.ts`, read by the popup through the same runtime-message
|
|
23
26
|
* channel) and therefore dies with the browser session BY DESIGN. This is
|
|
24
27
|
* an operator view, not an audit log.
|
|
28
|
+
*
|
|
29
|
+
* DETACHED-FLOW RECORDS ARE A DIFFERENT ANIMAL and the distinction matters.
|
|
30
|
+
* A record (`DetachedFlowRecord`, keyed `detached-flow:<flow_id>`) carries
|
|
31
|
+
* the flow's ACTION MANIFEST — the very `results[]` a synchronous flow
|
|
32
|
+
* returns to its caller — plus the failure message, so the agent that
|
|
33
|
+
* launched a detached run can still answer "which things did you just
|
|
34
|
+
* delete?" after the fact. That is page-derived data at rest in
|
|
35
|
+
* `chrome.storage.session`, deliberately, because a detached flow has no
|
|
36
|
+
* open tool call to return it through. It is NEVER shown in the popup, it
|
|
37
|
+
* is only ever handed back over the bridge to an agent that can already
|
|
38
|
+
* read the page, and it dies with the browser session like everything else
|
|
39
|
+
* here.
|
|
25
40
|
*/
|
|
26
41
|
import type { FlowProgress, FlowResult } from './flow.js';
|
|
27
42
|
/** How many completed flows are kept per tab. Small on purpose — this is a
|
|
@@ -32,7 +47,40 @@ export declare const MAX_FLOW_HISTORY_PER_TAB = 20;
|
|
|
32
47
|
* only ever knows the former, and a tab that reconnects under a new
|
|
33
48
|
* browser-link id is still the same tab to the human looking at it. */
|
|
34
49
|
export declare function flowHistoryKey(tabId: number): string;
|
|
35
|
-
|
|
50
|
+
/** Absolute wall-clock ceiling on a DETACHED flow, from the moment it is
|
|
51
|
+
* registered. Mirrors `MAX_DETACHED_FLOW_TIMEOUT_MS` in the server's
|
|
52
|
+
* `browser-dispatch.ts` (which rejects an over-ceiling flow up front) and
|
|
53
|
+
* `cdp/detached-flows.ts` (the cdp-direct runner's own copy); kept in sync
|
|
54
|
+
* manually, since the three packages share no module for it.
|
|
55
|
+
*
|
|
56
|
+
* A detached flow escapes `MAX_FLOW_TIMEOUT_MS` — nothing is parked on it,
|
|
57
|
+
* so the 60s "do not hold a tool call open longer than this" rule simply
|
|
58
|
+
* does not apply. What it must NOT escape is having an end: a flow that
|
|
59
|
+
* keeps acting with no agent attached and no upper bound is exactly the
|
|
60
|
+
* runaway this whole program exists to make impossible. Past the ceiling
|
|
61
|
+
* the flow self-cancels and reports `stopped_by: 'expired'` — a distinct
|
|
62
|
+
* outcome from a human pressing Stop, because "I ran out of time" and
|
|
63
|
+
* "someone stopped me" are different facts about the same partial work.
|
|
64
|
+
*
|
|
65
|
+
* Enforced as a DEADLINE checked at the same points as the cancel flag
|
|
66
|
+
* (see `shouldStop`), never as a timer: a timer would fire while the
|
|
67
|
+
* runner is mid-step and could not stop anything anyway, and a
|
|
68
|
+
* request-derived `setTimeout` duration is a resource-exhaustion finding
|
|
69
|
+
* waiting to happen. */
|
|
70
|
+
export declare const MAX_DETACHED_FLOW_MS: number;
|
|
71
|
+
export type FlowOutcome = 'completed' | 'cancelled' | 'failed' | 'expired';
|
|
72
|
+
/** Why a flow was told to stop. A fixed vocabulary — it reaches both the
|
|
73
|
+
* popup (as a history label) and the agent (as `stopped_by`), so it can
|
|
74
|
+
* never carry free text. */
|
|
75
|
+
export type FlowStopReason = 'cancelled' | 'expired' | 'worker-terminated';
|
|
76
|
+
/** Lifecycle state of a flow as `browser.flow_status` reports it.
|
|
77
|
+
* `'unknown'` is the answer for an id this worker has no record of — a
|
|
78
|
+
* flow that finished before the last service-worker restart, a synchronous
|
|
79
|
+
* flow that already returned, or an id that never existed. Deliberately a
|
|
80
|
+
* state rather than an error, for the same reason cancelling an unknown id
|
|
81
|
+
* is a no-op: the caller asked "what is this doing?", and "nothing I know
|
|
82
|
+
* of" is a truthful answer, not a failure. */
|
|
83
|
+
export type FlowState = 'running' | 'completed' | 'cancelled' | 'failed' | 'expired' | 'unknown';
|
|
36
84
|
/** One live flow. Mutable on purpose: `cancelled` is what the runner's
|
|
37
85
|
* `shouldCancel` reads on every step, and `progress` is overwritten in
|
|
38
86
|
* place by its `onProgress`. */
|
|
@@ -49,6 +97,18 @@ export interface FlowRegistryEntry {
|
|
|
49
97
|
steps: number;
|
|
50
98
|
cancelled: boolean;
|
|
51
99
|
progress: FlowProgress;
|
|
100
|
+
/** Started with `detach: true` — nothing is waiting on its result, so it
|
|
101
|
+
* keeps acting between agent turns and is the one thing here that
|
|
102
|
+
* outlives the call that launched it. */
|
|
103
|
+
detached: boolean;
|
|
104
|
+
/** Wall-clock instant past which a detached flow self-cancels as
|
|
105
|
+
* `'expired'`. Set only for detached flows (a synchronous one is already
|
|
106
|
+
* bounded by `MAX_FLOW_TIMEOUT_MS` on the server side). */
|
|
107
|
+
expiresAt?: number;
|
|
108
|
+
/** Set the moment the flow is told to stop, alongside `cancelled`. What
|
|
109
|
+
* turns an otherwise-identical `stopped_by: 'cancelled'` from the runner
|
|
110
|
+
* into `'expired'` in the record the agent reads. */
|
|
111
|
+
stopReason?: FlowStopReason;
|
|
52
112
|
}
|
|
53
113
|
/** A running flow as the popup renders it. Flat and pre-computed
|
|
54
114
|
* (`elapsedMs` rather than a raw clock) so the popup does no arithmetic on
|
|
@@ -66,6 +126,10 @@ export interface RunningFlowView {
|
|
|
66
126
|
* The button becomes "Stopping…" instead of vanishing, so the click is
|
|
67
127
|
* visibly acknowledged during the up-to-one-step gap. */
|
|
68
128
|
cancelling: boolean;
|
|
129
|
+
/** Rendered as a badge on the row. The human needs to know WHICH of the
|
|
130
|
+
* running flows is the one that will keep clicking after every agent has
|
|
131
|
+
* gone home — that is the whole reason the Stop button exists. */
|
|
132
|
+
detached: boolean;
|
|
69
133
|
}
|
|
70
134
|
/** A finished flow, as stored in `chrome.storage.session`. Every field is
|
|
71
135
|
* a number, an id or a fixed enum — see the PRIVACY CONTRACT above. */
|
|
@@ -87,16 +151,28 @@ export interface FlowHistoryEntry {
|
|
|
87
151
|
/** Step KIND of the failing step ('click', 'wait_for', …). A fixed
|
|
88
152
|
* vocabulary, never page-derived text. */
|
|
89
153
|
failedStepKind?: string;
|
|
154
|
+
/** The flow ran detached. Worth a badge in the panel: a detached entry is
|
|
155
|
+
* work the browser did with nobody watching. */
|
|
156
|
+
detached?: true;
|
|
157
|
+
/** Why it stopped, when that is not obvious from `outcome` alone — a
|
|
158
|
+
* `failed` entry reads very differently once you know the service worker
|
|
159
|
+
* was killed under it rather than a step going wrong. */
|
|
160
|
+
stoppedBy?: FlowStopReason;
|
|
90
161
|
}
|
|
91
162
|
export interface FlowRegistry {
|
|
92
|
-
/** Start tracking a flow
|
|
93
|
-
*
|
|
163
|
+
/** Start tracking a flow and hand back the live entry, so the caller can
|
|
164
|
+
* stamp late-arriving fields (the tab title) on it and close its
|
|
165
|
+
* `shouldCancel` / `onProgress` over the same object. */
|
|
94
166
|
register(input: {
|
|
95
167
|
flowId: string;
|
|
96
168
|
tabId: number;
|
|
97
169
|
steps: number;
|
|
98
170
|
title?: string;
|
|
99
|
-
|
|
171
|
+
detached?: boolean;
|
|
172
|
+
/** Wall-clock deadline for a detached flow. Ignored (and never set)
|
|
173
|
+
* for a synchronous one. */
|
|
174
|
+
expiresAt?: number;
|
|
175
|
+
}): FlowRegistryEntry;
|
|
100
176
|
get(flowId: string): FlowRegistryEntry | undefined;
|
|
101
177
|
/** Flip a running flow to cancelled. Returns whether this call is the
|
|
102
178
|
* one that flipped it — `false` for an unknown id, an id that already
|
|
@@ -104,11 +180,23 @@ export interface FlowRegistry {
|
|
|
104
180
|
* an agent and the flow's own completion race by nature, and every one
|
|
105
181
|
* of those races ends with the flow not running, which is what the
|
|
106
182
|
* caller asked for. */
|
|
107
|
-
cancel(flowId: string): boolean;
|
|
183
|
+
cancel(flowId: string, reason?: FlowStopReason): boolean;
|
|
108
184
|
/** Cancel every flow running against one Chrome tab — the tab closed,
|
|
109
185
|
* the debugger detached, or the bridge was disconnected. Returns how
|
|
110
186
|
* many were flipped. */
|
|
111
187
|
cancelForTab(tabId: number): number;
|
|
188
|
+
/** The single rule the whole "one detached flow per tab" contract rests
|
|
189
|
+
* on. Returns the entry so the rejection can NAME the flow already
|
|
190
|
+
* running — an error that just says "busy" leaves the caller with no way
|
|
191
|
+
* to find, inspect or stop the thing blocking it. */
|
|
192
|
+
detachedForTab(tabId: number): FlowRegistryEntry | undefined;
|
|
193
|
+
/** The one question the runner asks between steps: should I stop? True
|
|
194
|
+
* when the cancel flag is set OR a detached flow has outlived its
|
|
195
|
+
* deadline — and in that second case this call is also what RECORDS the
|
|
196
|
+
* reason, so the outcome comes out as `'expired'` rather than as an
|
|
197
|
+
* indistinguishable `'cancelled'`. Idempotent and cheap: it runs once
|
|
198
|
+
* per step. */
|
|
199
|
+
shouldStop(flowId: string, now: number): boolean;
|
|
112
200
|
/** Stop tracking a finished flow and hand back its final entry (or
|
|
113
201
|
* `undefined` if it was never registered). */
|
|
114
202
|
finish(flowId: string): FlowRegistryEntry | undefined;
|
|
@@ -119,14 +207,47 @@ export interface FlowRegistry {
|
|
|
119
207
|
}
|
|
120
208
|
export declare function createFlowRegistry(): FlowRegistry;
|
|
121
209
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
210
|
+
* What a finished flow amounted to, in fields that are the same whether
|
|
211
|
+
* the reader is the popup or an agent. Shared by `toHistoryEntry` and
|
|
212
|
+
* `finishedDetachedFlowRecord` precisely so the two can never disagree
|
|
213
|
+
* about the same run — a popup showing `cancelled` next to a
|
|
214
|
+
* `flow_status` reporting `expired` would be worse than either being
|
|
215
|
+
* wrong on its own.
|
|
216
|
+
*
|
|
217
|
+
* `error` is the one field with page-derived free text in it. It is
|
|
218
|
+
* produced here but consumed ONLY by the detached record (which the agent
|
|
219
|
+
* reads); `toHistoryEntry` drops it on the floor. See the privacy contract
|
|
220
|
+
* in the module header.
|
|
221
|
+
*/
|
|
222
|
+
export interface FlowOutcomeSummary {
|
|
223
|
+
outcome: FlowOutcome;
|
|
224
|
+
stepsCompleted: number;
|
|
225
|
+
iterationsCompleted?: number;
|
|
226
|
+
failedStep?: number;
|
|
227
|
+
failedStepKind?: string;
|
|
228
|
+
stoppedBy?: FlowStopReason;
|
|
229
|
+
error?: string;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Reduce a finished flow to its outcome. The ONE place a `FlowResult` is
|
|
233
|
+
* interpreted, so "what happened" has a single definition.
|
|
126
234
|
*
|
|
127
235
|
* `result === null` means the flow threw instead of returning (a detached
|
|
128
236
|
* debugger, a closed tab). That is a `failed` outcome with no step
|
|
129
237
|
* attribution, which is honest — the runner never got to report one.
|
|
238
|
+
*
|
|
239
|
+
* The runner only ever reports `stopped_by: 'cancelled'`: it flips when
|
|
240
|
+
* `shouldCancel` goes true and has no idea WHY it went true. The registry
|
|
241
|
+
* entry does (`stopReason`), which is what promotes a run that outlived
|
|
242
|
+
* its ceiling from `cancelled` to `expired` here.
|
|
243
|
+
*/
|
|
244
|
+
export declare function summarizeFlow(entry: FlowRegistryEntry, result: FlowResult | null): FlowOutcomeSummary;
|
|
245
|
+
/**
|
|
246
|
+
* Reduce a finished flow to a history entry — the ONE place a summary is
|
|
247
|
+
* allowed to turn into POPUP-visible stored state, and therefore the one
|
|
248
|
+
* place the privacy contract has to hold. Nothing is copied across by
|
|
249
|
+
* spread: every field is named, typed and numeric/enum, and the summary's
|
|
250
|
+
* `error` is deliberately not among them.
|
|
130
251
|
*/
|
|
131
252
|
export declare function toHistoryEntry(entry: FlowRegistryEntry, result: FlowResult | null, endedAt: number): FlowHistoryEntry;
|
|
132
253
|
/** Prepend a finished flow to a tab's history and enforce the cap. Newest
|
|
@@ -155,8 +276,172 @@ export declare function flowTabLabel(view: {
|
|
|
155
276
|
}): string;
|
|
156
277
|
/** The progress line for one running flow: "step 2/5" outside a repeat,
|
|
157
278
|
* "step 2/5 · iteration 12/50" inside one, "starting…" before the runner
|
|
158
|
-
* has reported its first step.
|
|
279
|
+
* has reported its first step. A detached flow is labelled as such: it is
|
|
280
|
+
* the one row where the human is the ONLY party still watching. */
|
|
159
281
|
export declare function describeFlowProgress(view: RunningFlowView): string;
|
|
160
282
|
/** The one-line summary for a finished flow: outcome, what it got through,
|
|
161
283
|
* and how long it took. Structure and counts only. */
|
|
162
284
|
export declare function describeFlowHistoryEntry(entry: FlowHistoryEntry): string;
|
|
285
|
+
/** `chrome.storage.session` key holding ONE detached flow's record. Keyed
|
|
286
|
+
* by `flow_id` (not by tab): the id is what `browser.flow_status` and
|
|
287
|
+
* `browser.flow_cancel` address, and it stays valid across a tab
|
|
288
|
+
* reconnecting under a new browser-link id. */
|
|
289
|
+
export declare function detachedFlowKey(flowId: string): string;
|
|
290
|
+
/** Inverse of `detachedFlowKey`, for the worker-start sweep over
|
|
291
|
+
* `chrome.storage.session.get(null)`. Returns null for every other key. */
|
|
292
|
+
export declare function flowIdFromDetachedFlowKey(key: string): string | null;
|
|
293
|
+
/** How many detached-flow records are kept at once, across every tab.
|
|
294
|
+
* Small on purpose: each terminal record carries a full action manifest,
|
|
295
|
+
* `chrome.storage.session` is a shared, quota-bounded store, and the
|
|
296
|
+
* realistic access pattern is "the agent that launched it polls it, reads
|
|
297
|
+
* the manifest once, and moves on". Oldest-first eviction. */
|
|
298
|
+
export declare const MAX_DETACHED_FLOW_RECORDS = 10;
|
|
299
|
+
/** Byte ceiling on ONE stored record's serialized manifest. A 500-iteration
|
|
300
|
+
* repeat over a 20-step body can produce a manifest far larger than
|
|
301
|
+
* anything a session store should hold, and losing the whole record to a
|
|
302
|
+
* quota rejection would be strictly worse than losing the manifest: the
|
|
303
|
+
* counts, the outcome and the failing step all still fit. Over the ceiling
|
|
304
|
+
* the manifest is dropped and `manifestTruncated` says so out loud, rather
|
|
305
|
+
* than a silently short array the agent would read as "that's all that
|
|
306
|
+
* happened". */
|
|
307
|
+
export declare const MAX_DETACHED_MANIFEST_BYTES = 128000;
|
|
308
|
+
/**
|
|
309
|
+
* The durable record of one detached flow — what `browser.flow_status`
|
|
310
|
+
* answers from once the flow is no longer in the live registry, and what
|
|
311
|
+
* makes MV3 service-worker termination detectable at all.
|
|
312
|
+
*
|
|
313
|
+
* Written twice per flow and no more: once at launch (`state: 'running'`)
|
|
314
|
+
* and once when it ends. There is deliberately NO per-step write — a
|
|
315
|
+
* 200-iteration flow would otherwise hammer `chrome.storage.session`
|
|
316
|
+
* hundreds of times to keep a progress counter warm that the live registry
|
|
317
|
+
* already holds for free.
|
|
318
|
+
*/
|
|
319
|
+
export interface DetachedFlowRecord {
|
|
320
|
+
flowId: string;
|
|
321
|
+
/** Chrome tab id. The server stamps the browser-link `tab_id` onto the
|
|
322
|
+
* status payload itself — the extension only ever knows this one. */
|
|
323
|
+
tabId: number;
|
|
324
|
+
state: Exclude<FlowState, 'unknown'>;
|
|
325
|
+
startedAt: number;
|
|
326
|
+
endedAt?: number;
|
|
327
|
+
/** Top-level step count the flow was launched with. */
|
|
328
|
+
steps: number;
|
|
329
|
+
stepsCompleted: number;
|
|
330
|
+
iterationsCompleted?: number;
|
|
331
|
+
stoppedBy?: FlowStopReason;
|
|
332
|
+
/** The flow failure message. Page-derived free text — agent-facing only,
|
|
333
|
+
* never rendered in the popup. */
|
|
334
|
+
error?: string;
|
|
335
|
+
/** The action manifest: `runFlow`'s `results[]`, verbatim. Absent while
|
|
336
|
+
* the flow is still running (the runner holds them until it returns) and
|
|
337
|
+
* when `manifestTruncated` is set. */
|
|
338
|
+
manifest?: unknown[];
|
|
339
|
+
manifestTruncated?: true;
|
|
340
|
+
/** The one-per-flow summary `bridge.event` has not reached the server
|
|
341
|
+
* yet — either the socket was closed when the flow ended, or the record
|
|
342
|
+
* was only reconciled at worker start, when no tab is connected. Flushed
|
|
343
|
+
* on that tab's next registration. */
|
|
344
|
+
eventPending?: true;
|
|
345
|
+
}
|
|
346
|
+
/** The record written the instant a detached flow is launched. Its whole
|
|
347
|
+
* job is to exist: a `running` record with no live registry entry behind it
|
|
348
|
+
* is the ONLY evidence that Chrome killed the service worker mid-flow. */
|
|
349
|
+
export declare function startedDetachedFlowRecord(entry: FlowRegistryEntry): DetachedFlowRecord;
|
|
350
|
+
/** The terminal record, manifest included. `endedAt` is passed in rather
|
|
351
|
+
* than read off the clock so the caller can use the same instant for the
|
|
352
|
+
* record, the history entry and the audit event. */
|
|
353
|
+
export declare function finishedDetachedFlowRecord(entry: FlowRegistryEntry, result: FlowResult | null, endedAt: number): DetachedFlowRecord;
|
|
354
|
+
/**
|
|
355
|
+
* Reconcile a persisted record against the fact that this service worker
|
|
356
|
+
* has no live registry entry for it — i.e. Chrome terminated the worker
|
|
357
|
+
* while the flow was mid-run.
|
|
358
|
+
*
|
|
359
|
+
* THE RULE, and it is not negotiable: mark it `failed` /
|
|
360
|
+
* `'worker-terminated'` and NEVER resume. A detached flow exists to do
|
|
361
|
+
* irreversible bulk work; a resume that could not know how far the dead run
|
|
362
|
+
* actually got would double-act on some prefix of it. A loud, honest
|
|
363
|
+
* failure is strictly better than a silent second pass, and a phantom
|
|
364
|
+
* `running` state — a flow that looks alive while nothing is executing it —
|
|
365
|
+
* is worse than both.
|
|
366
|
+
*
|
|
367
|
+
* The manifest of the dead run is genuinely unrecoverable: it lived in the
|
|
368
|
+
* runner's closure, and there was never a per-step write that could have
|
|
369
|
+
* saved it. `manifestTruncated` says so rather than presenting an empty
|
|
370
|
+
* array as "nothing happened".
|
|
371
|
+
*
|
|
372
|
+
* Returns `null` for a record that is already terminal — a second worker
|
|
373
|
+
* restart must not rewrite an outcome, nor re-fire its audit event.
|
|
374
|
+
*/
|
|
375
|
+
export declare function terminatedDetachedFlowRecord(record: DetachedFlowRecord, endedAt: number): DetachedFlowRecord | null;
|
|
376
|
+
/** Character ceiling on the error string of a MINIMAL record. The normal
|
|
377
|
+
* terminal record keeps the failure message whole — it is the answer to
|
|
378
|
+
* "why did it stop". This bound applies only where the full write already
|
|
379
|
+
* came back refused, and a flow error is page-derived free text with no
|
|
380
|
+
* upper bound of its own (a `find: multiple-matches` over a huge table
|
|
381
|
+
* quotes every candidate), so it is the one remaining field that could
|
|
382
|
+
* still be what breaches the quota. */
|
|
383
|
+
export declare const MAX_MINIMAL_RECORD_ERROR_CHARS = 1024;
|
|
384
|
+
/** The same terminal record without its manifest — what gets written when
|
|
385
|
+
* storage refuses the full one. The manifest is the only part large enough
|
|
386
|
+
* to be refused under normal conditions; state, `stoppedBy` and the
|
|
387
|
+
* counters are a few hundred bytes. Losing the manifest is a documented
|
|
388
|
+
* outcome; losing the write is not: the stale `running` record becomes
|
|
389
|
+
* `worker-terminated`. The error is trimmed to
|
|
390
|
+
* `MAX_MINIMAL_RECORD_ERROR_CHARS` with a visible ellipsis, so a
|
|
391
|
+
* pathological message cannot be what sinks the retry too. */
|
|
392
|
+
export declare function minimalDetachedFlowRecord(record: DetachedFlowRecord): DetachedFlowRecord;
|
|
393
|
+
/** The history entry for a flow the service worker was killed under. The
|
|
394
|
+
* live entry is gone, so this is reconstructed from the persisted record —
|
|
395
|
+
* counts and enums only, same privacy contract as every other entry. */
|
|
396
|
+
export declare function terminatedFlowHistoryEntry(record: DetachedFlowRecord): FlowHistoryEntry;
|
|
397
|
+
/** Narrow whatever `chrome.storage.session` hands back into a record.
|
|
398
|
+
* Same posture as `parseFlowHistory`: storage is not a typed channel, and a
|
|
399
|
+
* record written by an older extension version must be dropped rather than
|
|
400
|
+
* rendered as a half-`undefined` status payload. */
|
|
401
|
+
export declare function parseDetachedFlowRecord(raw: unknown): DetachedFlowRecord | null;
|
|
402
|
+
/** Which stored records to evict so `MAX_DETACHED_FLOW_RECORDS` holds,
|
|
403
|
+
* oldest-start first. RUNNING records are never evicted regardless of age:
|
|
404
|
+
* dropping one would erase the only evidence that a flow was in flight, and
|
|
405
|
+
* a worker restart would then have nothing to mark `worker-terminated`.
|
|
406
|
+
* Records still owing their summary event (`eventPending`) go LAST, reached
|
|
407
|
+
* only when nothing else is left: evicting one destroys an audit event no
|
|
408
|
+
* one has received, but a cap that can never be enforced would block every
|
|
409
|
+
* new record. The exemption lapses when the flush clears the flag. */
|
|
410
|
+
export declare function detachedFlowRecordsToEvict(records: readonly DetachedFlowRecord[]): DetachedFlowRecord[];
|
|
411
|
+
/** The wire shape `browser.flow_status` returns (the server additionally
|
|
412
|
+
* stamps `tab_id` onto it — the extension does not know the browser-link
|
|
413
|
+
* id). snake_case because this one crosses the bridge to an agent, unlike
|
|
414
|
+
* every other type in this module. */
|
|
415
|
+
export interface FlowStatusPayload {
|
|
416
|
+
flow_id: string;
|
|
417
|
+
state: FlowState;
|
|
418
|
+
detached: boolean;
|
|
419
|
+
started_at?: number;
|
|
420
|
+
ended_at?: number;
|
|
421
|
+
steps?: number;
|
|
422
|
+
steps_completed?: number;
|
|
423
|
+
iterations_completed?: number;
|
|
424
|
+
stopped_by?: FlowStopReason;
|
|
425
|
+
error?: string;
|
|
426
|
+
/** A stop has been requested and the runner has not reached its next
|
|
427
|
+
* check yet — the up-to-one-step gap. Distinguishes "asked to stop" from
|
|
428
|
+
* "stopped", which is exactly the ambiguity an agent polling right after
|
|
429
|
+
* `browser.flow_cancel` needs resolved. */
|
|
430
|
+
cancelling?: true;
|
|
431
|
+
/** When a detached flow will self-cancel as `expired`. */
|
|
432
|
+
expires_at?: number;
|
|
433
|
+
/** `runFlow`'s `results[]`. Present only in a terminal state: while a
|
|
434
|
+
* flow runs, the runner is still holding them. */
|
|
435
|
+
manifest?: unknown[];
|
|
436
|
+
/** The manifest existed but is not retrievable — over the storage
|
|
437
|
+
* ceiling, or lost with the service worker that was running the flow. */
|
|
438
|
+
manifest_truncated?: true;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Build the status payload for a flow id, from whichever source knows
|
|
442
|
+
* about it. The live registry entry wins for a running flow (it has
|
|
443
|
+
* up-to-the-step progress the twice-written record cannot), the persisted
|
|
444
|
+
* record answers for everything that already ended, and an id neither
|
|
445
|
+
* knows about comes back as `state: 'unknown'` rather than as an error.
|
|
446
|
+
*/
|
|
447
|
+
export declare function toFlowStatus(flowId: string, entry: FlowRegistryEntry | undefined, record: DetachedFlowRecord | undefined): FlowStatusPayload;
|