cascade-ai 0.78.0 → 0.80.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/index.d.ts CHANGED
@@ -880,6 +880,17 @@ interface ToolsConfig {
880
880
  * `src/tools/browser-control.ts`.
881
881
  */
882
882
  agentBrowserControl?: boolean;
883
+ /**
884
+ * Where a hosted run gets a browser. Absent means the capability does not
885
+ * exist on that surface. See `src/config/schema.ts` and
886
+ * `src/browser/remote/provider.ts`.
887
+ */
888
+ remoteBrowser?: {
889
+ provider?: 'cdp' | 'steel';
890
+ url?: string;
891
+ apiKey?: string;
892
+ maxSessions?: number;
893
+ };
883
894
  mcpServers?: McpServerConfig$1[];
884
895
  /**
885
896
  * Names of MCP servers (matching McpServerConfig.name) that the user has
@@ -3535,6 +3546,32 @@ declare class Cascade extends EventEmitter {
3535
3546
  * run — it is simply not there.
3536
3547
  */
3537
3548
  setBrowserController(controller: BrowserController, release?: BrowserActorRelease): void;
3549
+ /**
3550
+ * Wire a REMOTE browser — one the host connects to rather than one the user
3551
+ * is signed into.
3552
+ *
3553
+ * A separate entry point because the enablement is a different question, and
3554
+ * conflating them made the hosted capability dead on arrival: this went out
3555
+ * calling `setBrowserController`, which gates on `agentBrowserControl`, and
3556
+ * that flag means "drive the browser I am signed into on this machine". It
3557
+ * defaults to false and has no meaning on a server, so the hosted tool was
3558
+ * never registered at all — the feature looked shipped and did nothing.
3559
+ *
3560
+ * Configuring a provider IS the opt-in here. It takes a deliberate act with
3561
+ * an endpoint and usually a paid account, and the browser it reaches is a
3562
+ * throwaway signed into nothing — a smaller consent than handing over the
3563
+ * session the user is already authenticated in, and one the desktop flag
3564
+ * cannot stand in for.
3565
+ */
3566
+ setRemoteBrowserController(controller: BrowserController, release?: BrowserActorRelease): void;
3567
+ /**
3568
+ * The gates every surface shares, with only enablement differing.
3569
+ *
3570
+ * One place, because the two callers above are exactly the sibling-writer
3571
+ * shape that has already drifted twice in this feature: a rule added to one
3572
+ * and not the other is silent on the surface that missed it.
3573
+ */
3574
+ private registerBrowserControl;
3538
3575
  private registerMediaTools;
3539
3576
  /**
3540
3577
  * Register `generate_document` where there is a real workspace to write into.
@@ -4677,6 +4714,41 @@ declare const CascadeConfigSchema: z.ZodObject<{
4677
4714
  * stop control for the run that is acting.
4678
4715
  */
4679
4716
  agentBrowserControl: z.ZodDefault<z.ZodBoolean>;
4717
+ /**
4718
+ * Where a HOSTED run gets a browser, since it has none of its own.
4719
+ *
4720
+ * The desktop drives the Chromium the user is signed into and can watch. A
4721
+ * server has neither, and running one inside the container is wrong twice
4722
+ * over — page JavaScript issues requests no URL check ever sees, from inside
4723
+ * the trust boundary, and a session costs 200-400 MB. So this points at a
4724
+ * browser somebody else runs.
4725
+ *
4726
+ * Absent by default: no provider, no capability. There is nothing to turn
4727
+ * off because nothing is registered until an endpoint is supplied.
4728
+ */
4729
+ remoteBrowser: z.ZodOptional<z.ZodObject<{
4730
+ /** `cdp` for an endpoint you run; `steel` for a Steel deployment. */
4731
+ provider: z.ZodOptional<z.ZodEnum<["cdp", "steel"]>>;
4732
+ /** The websocket endpoint for `cdp`, or the API base for `steel`. */
4733
+ url: z.ZodOptional<z.ZodString>;
4734
+ apiKey: z.ZodOptional<z.ZodString>;
4735
+ /**
4736
+ * Concurrent sessions. One by default and deliberately so: every session
4737
+ * is billed, and a wave of workers each opening their own is a cost the
4738
+ * user did not choose. Raising it is a decision, not a default.
4739
+ */
4740
+ maxSessions: z.ZodDefault<z.ZodNumber>;
4741
+ }, "strip", z.ZodTypeAny, {
4742
+ maxSessions: number;
4743
+ url?: string | undefined;
4744
+ provider?: "cdp" | "steel" | undefined;
4745
+ apiKey?: string | undefined;
4746
+ }, {
4747
+ url?: string | undefined;
4748
+ provider?: "cdp" | "steel" | undefined;
4749
+ apiKey?: string | undefined;
4750
+ maxSessions?: number | undefined;
4751
+ }>>;
4680
4752
  mcpServers: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
4681
4753
  name: z.ZodString;
4682
4754
  command: z.ZodOptional<z.ZodString>;
@@ -4790,6 +4862,12 @@ declare const CascadeConfigSchema: z.ZodObject<{
4790
4862
  env?: Record<string, string> | undefined;
4791
4863
  oauthStore?: string | undefined;
4792
4864
  }[] | undefined;
4865
+ remoteBrowser?: {
4866
+ maxSessions: number;
4867
+ url?: string | undefined;
4868
+ provider?: "cdp" | "steel" | undefined;
4869
+ apiKey?: string | undefined;
4870
+ } | undefined;
4793
4871
  mcpTrusted?: string[] | undefined;
4794
4872
  webSearch?: {
4795
4873
  maxResults: number;
@@ -4815,6 +4893,12 @@ declare const CascadeConfigSchema: z.ZodObject<{
4815
4893
  requireApprovalFor?: string[] | undefined;
4816
4894
  browserEnabled?: boolean | undefined;
4817
4895
  agentBrowserControl?: boolean | undefined;
4896
+ remoteBrowser?: {
4897
+ url?: string | undefined;
4898
+ provider?: "cdp" | "steel" | undefined;
4899
+ apiKey?: string | undefined;
4900
+ maxSessions?: number | undefined;
4901
+ } | undefined;
4818
4902
  mcpTrusted?: string[] | undefined;
4819
4903
  webSearch?: {
4820
4904
  searxngUrl?: string | undefined;
@@ -5409,6 +5493,12 @@ declare const CascadeConfigSchema: z.ZodObject<{
5409
5493
  env?: Record<string, string> | undefined;
5410
5494
  oauthStore?: string | undefined;
5411
5495
  }[] | undefined;
5496
+ remoteBrowser?: {
5497
+ maxSessions: number;
5498
+ url?: string | undefined;
5499
+ provider?: "cdp" | "steel" | undefined;
5500
+ apiKey?: string | undefined;
5501
+ } | undefined;
5412
5502
  mcpTrusted?: string[] | undefined;
5413
5503
  webSearch?: {
5414
5504
  maxResults: number;
@@ -5588,6 +5678,12 @@ declare const CascadeConfigSchema: z.ZodObject<{
5588
5678
  requireApprovalFor?: string[] | undefined;
5589
5679
  browserEnabled?: boolean | undefined;
5590
5680
  agentBrowserControl?: boolean | undefined;
5681
+ remoteBrowser?: {
5682
+ url?: string | undefined;
5683
+ provider?: "cdp" | "steel" | undefined;
5684
+ apiKey?: string | undefined;
5685
+ maxSessions?: number | undefined;
5686
+ } | undefined;
5591
5687
  mcpTrusted?: string[] | undefined;
5592
5688
  webSearch?: {
5593
5689
  searxngUrl?: string | undefined;
@@ -5905,6 +6001,12 @@ interface SettingsPayload {
5905
6001
  braveApiKey?: string;
5906
6002
  tavilyApiKey?: string;
5907
6003
  };
6004
+ remoteBrowser?: {
6005
+ provider?: string;
6006
+ url?: string;
6007
+ apiKey?: string;
6008
+ maxSessions?: number;
6009
+ };
5908
6010
  advanced?: Record<string, unknown>;
5909
6011
  }
5910
6012
  /**
@@ -5968,6 +6070,12 @@ interface SettingsSnapshot {
5968
6070
  hasBraveKey: boolean;
5969
6071
  hasTavilyKey: boolean;
5970
6072
  };
6073
+ remoteBrowser: {
6074
+ provider?: string;
6075
+ url?: string;
6076
+ hasApiKey: boolean;
6077
+ maxSessions?: number;
6078
+ };
5971
6079
  advanced: Record<string, unknown>;
5972
6080
  }
5973
6081
  declare function settingsSnapshot(config: CascadeConfig): SettingsSnapshot;
@@ -6706,6 +6814,881 @@ declare function mcpServerPrefix(serverName: string): string;
6706
6814
  */
6707
6815
  declare function removeMcpServerDenials(disabledTools: string[] | undefined, serverName: string): string[] | undefined;
6708
6816
 
6817
+ /** A live browser session somebody else is hosting. */
6818
+ interface RemoteBrowserSession {
6819
+ /** The provider's id for this session, needed to end it. */
6820
+ id: string;
6821
+ /**
6822
+ * CDP endpoint to drive, for Playwright's `connectOverCDP`.
6823
+ *
6824
+ * Carries the provider's credentials in most cases (a token in the query
6825
+ * string), so it is as sensitive as the API key and must never be logged.
6826
+ */
6827
+ cdpUrl: string;
6828
+ /**
6829
+ * A page showing the live session, embeddable in an iframe.
6830
+ *
6831
+ * A CAPABILITY URL: providers issue it deliberately without a token so it can
6832
+ * be handed to a viewer, which means anyone holding it can watch and — where
6833
+ * the provider supports input passthrough — drive the browser. It must never
6834
+ * be persisted in run history, written to a log, or sent to any client other
6835
+ * than the one that owns the run.
6836
+ */
6837
+ liveViewUrl?: string;
6838
+ }
6839
+ /**
6840
+ * Somewhere to get a browser from.
6841
+ *
6842
+ * Deliberately tiny. Everything a session DOES goes over CDP and is identical
6843
+ * whoever hosts it; only obtaining and releasing one differs, so those are the
6844
+ * only two things a provider has to implement. That keeps a new adapter to a
6845
+ * few dozen lines and keeps provider-specific behaviour from leaking into the
6846
+ * driver.
6847
+ */
6848
+ interface RemoteBrowserProvider {
6849
+ /** A short name for messages the model and the user see. */
6850
+ readonly name: string;
6851
+ /**
6852
+ * Whether `createSession` yields a browser of its own.
6853
+ *
6854
+ * False for a bare CDP endpoint, and the consequence is not cosmetic: that
6855
+ * endpoint IS one browser, so two "sessions" against it are the same browser
6856
+ * and, taking `contexts()[0].pages()[0]`, the same PAGE. Two runs would then
6857
+ * type into each other's forms — across users on a shared deployment.
6858
+ *
6859
+ * A provider that cannot isolate is therefore capped at one concurrent run
6860
+ * whatever the configured limit says. Raising a limit must not silently buy
6861
+ * concurrency the provider cannot actually deliver.
6862
+ */
6863
+ readonly isolatesSessions: boolean;
6864
+ createSession(signal?: AbortSignal): Promise<RemoteBrowserSession>;
6865
+ endSession(id: string): Promise<void>;
6866
+ }
6867
+ /** How the host was told to obtain browsers. */
6868
+ interface RemoteBrowserConfig {
6869
+ /** `cdp` for an endpoint you run; `steel` for a Steel deployment. */
6870
+ provider?: 'cdp' | 'steel';
6871
+ /**
6872
+ * For `cdp`, the websocket endpoint to connect to.
6873
+ * For `steel`, the API base — defaults to Steel's hosted API.
6874
+ */
6875
+ url?: string;
6876
+ apiKey?: string;
6877
+ /**
6878
+ * Concurrent sessions allowed. One by default, and deliberately so: every
6879
+ * session is billed, and a wave of workers each opening their own is a cost
6880
+ * the user did not choose. Raising it is a decision, not a default.
6881
+ */
6882
+ maxSessions?: number;
6883
+ }
6884
+
6885
+ /** What a run's watcher is told about its browser. */
6886
+ interface BrowserViewInfo {
6887
+ /** A browser is attached to this run. Stop is meaningful while true. */
6888
+ active: boolean;
6889
+ /** Where to watch it, when the provider can stream one. */
6890
+ liveViewUrl?: string;
6891
+ }
6892
+ /**
6893
+ * One rendered frame of a run's browser, on its way to whoever is watching.
6894
+ *
6895
+ * A JPEG rather than a video stream, and deliberately: CDP's screencast is
6896
+ * adaptive — Chrome sends a frame when the page actually changes, not on a
6897
+ * clock — so an idle page costs nothing, and there is no media pipeline to run
6898
+ * inside the deployment. It is worse than WebRTC for video-heavy pages and
6899
+ * better than it for everything else this feature is pointed at.
6900
+ */
6901
+ interface BrowserFrame {
6902
+ /** base64 JPEG, as CDP hands it over. Never decoded on the way through. */
6903
+ data: string;
6904
+ /** The remote viewport, so a canvas can size itself without guessing. */
6905
+ width: number;
6906
+ height: number;
6907
+ /**
6908
+ * Which watch this frame belongs to. See `watchGen`.
6909
+ *
6910
+ * Travels with the picture so the receipt the client sends back names the
6911
+ * watch it actually saw, rather than whichever one is current by the time it
6912
+ * arrives.
6913
+ */
6914
+ generation: number;
6915
+ }
6916
+ /** Who is driving a run's browser, and whether it is being pictured. */
6917
+ interface BrowserControlState {
6918
+ /** True while a person holds it and the agent is being refused. */
6919
+ human: boolean;
6920
+ /** Whether frames are being produced right now. See `setCapture`. */
6921
+ capturing: boolean;
6922
+ /**
6923
+ * Whether THIS watch has confirmed receiving a picture. See `seenGen`.
6924
+ *
6925
+ * Pushed rather than inferred, for the same reason ownership is: the client
6926
+ * cannot work it out while the picture is off, because it is the frames that
6927
+ * would tell it. A client left to guess would guess from what it remembers of
6928
+ * a previous watch, which is exactly the state this distinguishes.
6929
+ */
6930
+ confirmed: boolean;
6931
+ }
6932
+ /**
6933
+ * One thing a person did to the page while they held it.
6934
+ *
6935
+ * Coordinates are NORMALISED — 0..1 across the frame — rather than pixels, and
6936
+ * that is a boundary decision rather than a convenience. The picture the user
6937
+ * clicked is a JPEG scaled twice on its way to them: once by CDP into
6938
+ * `maxWidth`/`maxHeight`, once by the panel into whatever width the layout
6939
+ * gave it. Only this side knows the viewport those pixels came from, so only
6940
+ * this side can turn a click back into a page coordinate. A client sending
6941
+ * pixels would have to reconstruct both scalings and would silently miss by a
6942
+ * few pixels whenever it got one wrong — which, on a page of small controls, is
6943
+ * a click on the wrong thing.
6944
+ *
6945
+ * Typing is `text` rather than a stream of key events because `Input.insertText`
6946
+ * puts the whole string in at once: no IME to emulate, no per-character key
6947
+ * codes to get wrong for anything but a US layout, and no chance of a password
6948
+ * arriving one keystroke per socket message. `key` covers the keys that are
6949
+ * commands rather than characters, from a fixed list — see `KEYS`.
6950
+ */
6951
+ type BrowserInput = {
6952
+ kind: 'move';
6953
+ x: number;
6954
+ y: number;
6955
+ } | {
6956
+ kind: 'click';
6957
+ x: number;
6958
+ y: number;
6959
+ button?: 'left' | 'right' | 'middle';
6960
+ clicks?: number;
6961
+ } | {
6962
+ kind: 'scroll';
6963
+ x: number;
6964
+ y: number;
6965
+ deltaY: number;
6966
+ } | {
6967
+ kind: 'text';
6968
+ text: string;
6969
+ } | {
6970
+ kind: 'key';
6971
+ key: string;
6972
+ modifiers?: Array<'Control' | 'Meta' | 'Shift'>;
6973
+ };
6974
+ interface RemoteBrowserControllerOptions {
6975
+ provider: RemoteBrowserProvider;
6976
+ /**
6977
+ * Concurrent browser sessions. One by default: every session is billed, and
6978
+ * a wave of workers each opening their own is a cost nobody chose.
6979
+ */
6980
+ maxSessions?: number;
6981
+ /** Told when a run's live view becomes available, so the owner can watch. */
6982
+ onLiveView?: (runId: string, liveViewUrl: string | undefined) => void;
6983
+ /**
6984
+ * Something that must finish before this controller may open anything.
6985
+ *
6986
+ * For ROTATION. A settings change builds a new controller and disposes the
6987
+ * old one, and disposal is not instant: it detaches CDP sessions and hands
6988
+ * provider sessions back over the network. Installing the replacement
6989
+ * immediately meant the first action after a rotation could call
6990
+ * `createSession` while the previous controller was still releasing — two
6991
+ * controllers holding sessions against one provider's cap, which the pool
6992
+ * here cannot see because it counts only its own. On a provider that
6993
+ * enforces its own limit that action simply fails, and it is the first one
6994
+ * after an operator changed a setting, which is the worst moment for it.
6995
+ *
6996
+ * Awaited once, before the pool slot is reserved, and never awaited again.
6997
+ * A rejection is ignored on purpose: the old controller failing to tidy up
6998
+ * is not a reason to refuse the new one work, and `dispose` already reports
6999
+ * its own trouble.
7000
+ */
7001
+ ready?: Promise<unknown>;
7002
+ }
7003
+ declare class RemoteBrowserController {
7004
+ private provider;
7005
+ private maxSessions;
7006
+ /**
7007
+ * Live-view listeners, one per run.
7008
+ *
7009
+ * A single callback was fine while each run had its own controller. Now the
7010
+ * controller is shared across a deployment, so one callback would send run
7011
+ * A's live-view URL to whichever run registered last — and that URL is a
7012
+ * bearer capability for a browser somebody else is driving.
7013
+ */
7014
+ private liveViewListeners;
7015
+ /**
7016
+ * Told when a run's browser changes hands, or stops being pictured.
7017
+ *
7018
+ * Needed because a takeover can end without anybody asking. The hold lapses
7019
+ * on its own after `HUMAN_IDLE_MS`, and a client left believing it still has
7020
+ * control would keep sending input into a lease it no longer holds — every
7021
+ * event refused, with nothing on screen saying why.
7022
+ */
7023
+ private controlListeners;
7024
+ /** What each run's listener was last told, so unchanged states stay quiet. */
7025
+ private controlAnnounced;
7026
+ /** An embedder that wants every run's live view, told which run each is. */
7027
+ private onLiveViewAll;
7028
+ private runs;
7029
+ /**
7030
+ * Runs that have a pool slot but no browser yet.
7031
+ *
7032
+ * Counted alongside `runs` against the cap. Without it the limit was a
7033
+ * check-then-act across four awaits, and simultaneous first uses both won.
7034
+ */
7035
+ private opening;
7036
+ /**
7037
+ * A run's stop signal, created when its slot is reserved.
7038
+ *
7039
+ * Before this, Stop looked only in `runs` — so during the first `open()`,
7040
+ * while the run existed only in `opening` and four awaits were in flight, it
7041
+ * found nothing and returned. The open then completed, built a FRESH
7042
+ * unaborted controller, and the click the user had stopped went ahead. The
7043
+ * signal has to exist before the browser does.
7044
+ */
7045
+ private aborts;
7046
+ /**
7047
+ * Runs whose provider session is being released right now.
7048
+ *
7049
+ * `stopRun`, `endRun`, `releaseIfIdle`, and the stale-page branch of `open`
7050
+ * all delete a run from `runs` synchronously and only THEN await
7051
+ * `disposeRun` settling — the browser disconnect, and `provider.endSession`,
7052
+ * which for Steel is a real HTTP `/release` call. Between that delete and
7053
+ * the call actually finishing, the admission check in `open()` — which
7054
+ * counts only `runs` and `opening` — saw the slot as free, so a second run
7055
+ * could pass admission and call `createSession` before the first run's
7056
+ * session was truly released. At `maxSessions: 1` that is two live, BILLED
7057
+ * provider sessions at once, exactly what the pool exists to prevent.
7058
+ * Counting this set alongside the other two closes the window.
7059
+ *
7060
+ * Keyed by run id, not a counter: every one of those four call sites checks
7061
+ * the run is still IN `runs` before deleting it and starting teardown, and
7062
+ * that delete is synchronous, so at most one teardown can ever be in flight
7063
+ * for a given run id at a time — a second caller finds the entry already
7064
+ * gone and never starts a competing one. The entry is removed only once its
7065
+ * own teardown settles, so a later, genuine re-open of the same run id
7066
+ * never collides with one still in flight.
7067
+ */
7068
+ private closing;
7069
+ /** Runs the user has stopped, by run id — same meaning as on the desktop. */
7070
+ private revoked;
7071
+ /**
7072
+ * One lease PER RUN, because there is one browser per run.
7073
+ *
7074
+ * The desktop has a single lease because it has a single browser. Here each
7075
+ * run gets its own session, so a global lease would make run B queue behind
7076
+ * run A for a browser it was never going to use — and with no queue timeout,
7077
+ * queue for as long as A lasts. What limits runs against each other is the
7078
+ * session POOL, not the lease.
7079
+ */
7080
+ private leases;
7081
+ constructor(options: RemoteBrowserControllerOptions);
7082
+ /**
7083
+ * The predecessor's teardown, until it has been waited for once.
7084
+ *
7085
+ * Cleared as soon as it settles so the wait is not re-entered on every open
7086
+ * — and so the promise itself is not retained for the life of the process.
7087
+ */
7088
+ private ready;
7089
+ /**
7090
+ * This controller has been retired and will never open anything again.
7091
+ *
7092
+ * Stronger than "its runs were ended", and it has to be. A caller parked on
7093
+ * the `ready` gate above is invisible to `dispose()` — it holds no run and
7094
+ * has reserved no slot, because it has not got that far — so a SECOND
7095
+ * settings change, arriving while the first retirement is still settling,
7096
+ * found nothing to wait for and let the newest controller start immediately.
7097
+ * The parked caller then woke up on a controller nobody holds a reference to
7098
+ * and allocated a session against the superseded configuration, with the
7099
+ * newest controller free to allocate its own.
7100
+ *
7101
+ * Set synchronously at the top of `dispose`, so anything already waiting is
7102
+ * excluded the moment retirement begins rather than when it finishes.
7103
+ */
7104
+ private retired;
7105
+ private leaseFor;
7106
+ /** The controller to hand to `Cascade.setBrowserController`. */
7107
+ get controller(): (action: BrowserAction, context: BrowserActionContext) => Promise<BrowserActionOutcome>;
7108
+ /** Watch one run's browser. The URL never goes to any other run's listener. */
7109
+ onLiveViewFor(runKey: string, listener: (info: BrowserViewInfo) => void): void;
7110
+ offLiveViewFor(runKey: string): void;
7111
+ onControlFor(runKey: string, listener: (state: BrowserControlState) => void): void;
7112
+ offControlFor(runKey: string): void;
7113
+ /**
7114
+ * Say who has the browser, but only when the answer has changed.
7115
+ *
7116
+ * The lease reports every ownership change, and during ordinary work that is
7117
+ * one per action as workers take it and give it back. None of those are
7118
+ * interesting to a viewer — the agent had it before and has it now — and
7119
+ * emitting them would put a socket message on every click the agent makes.
7120
+ */
7121
+ private announceControl;
7122
+ /**
7123
+ * Tell a run's listener what its browser situation is.
7124
+ *
7125
+ * `active` is separate from the URL and both are needed: a provider with no
7126
+ * live view is attached-but-unwatchable, and a finished run is not attached
7127
+ * at all. Both have no URL, so a listener given only the URL cannot tell them
7128
+ * apart — and would drop the Stop control for the first as if it were the
7129
+ * second.
7130
+ */
7131
+ private announceLiveView;
7132
+ /**
7133
+ * Hand back a session the run turned out not to want.
7134
+ *
7135
+ * A browser opened for an action that is then refused would otherwise stay
7136
+ * allocated and billed until the whole run ended, for a run that has just
7137
+ * been stopped.
7138
+ */
7139
+ private releaseIfIdle;
7140
+ /** Release every run's session. For when the deployment's config changes. */
7141
+ dispose(): Promise<void>;
7142
+ /** A worker finished; it will never ask for the browser again. */
7143
+ actorEnded(actorId: string): void;
7144
+ /**
7145
+ * Start streaming this run's page to whoever is watching it.
7146
+ *
7147
+ * Nothing streams until someone asks. That is the whole reason this is a
7148
+ * method rather than something `open()` does: an unwatched run would
7149
+ * otherwise pay to encode frames nobody sees, on every run, forever — and on
7150
+ * a metered provider it would pay for the bandwidth too.
7151
+ *
7152
+ * Frames are ACKED after they are handed on, never before. Chrome sends the
7153
+ * next frame only once the previous is acknowledged, so this bounds it to one
7154
+ * outstanding frame against OUR OWN handling. It does not reach further than
7155
+ * that, and an earlier version of this comment claimed it did: `onFrame` ends
7156
+ * at a socket emit, which hands the JPEG to a transport rather than to a
7157
+ * viewer, so the ack says nothing about what the person actually received.
7158
+ * Bounding the rest of the path is the transport's job — see `emitFrame`,
7159
+ * where frames go out lossy so a slow connection drops pictures instead of
7160
+ * banking stale ones. Dropping the ack stops the stream dead, which is why it
7161
+ * is best-effort but unconditional.
7162
+ *
7163
+ * Returns false when there is nothing to watch — the run has no browser, or
7164
+ * it is already being watched. Callers use that to avoid promising a viewer
7165
+ * a stream that will never arrive.
7166
+ */
7167
+ startWatching(runId: string, onFrame: (frame: BrowserFrame) => void): Promise<boolean>;
7168
+ /**
7169
+ * Run one watch or unwatch after every one already asked for.
7170
+ *
7171
+ * The chain is kept non-rejecting on purpose: a failed attach must not poison
7172
+ * every later request for this run, which is what a bare `.then` chain would
7173
+ * do. The caller still sees its own failure, because that is the promise
7174
+ * returned rather than the one stored.
7175
+ */
7176
+ private queueWatch;
7177
+ /**
7178
+ * Attach at most one physical CDP screencast session at a time.
7179
+ *
7180
+ * `startWatching()` already serializes watcher lifecycle through `watchQueue`,
7181
+ * but the agent's detached-hidden restore cannot join that queue: it holds the
7182
+ * action slot, while an earlier unwatch may be waiting for exactly that slot.
7183
+ * Sharing only the physical attach closes the two-session race without making
7184
+ * watcher teardown and agent mutation wait on each other in a cycle.
7185
+ *
7186
+ * A real watcher claims `onFrame` immediately, before it waits for an attach
7187
+ * already started by the agent. The lower-level attach never writes the sink,
7188
+ * so finishing an older temporary attempt cannot overwrite a newer viewer.
7189
+ */
7190
+ private attachScreencast;
7191
+ /** Open one new CDP session and start the stream when it is safe to do so. */
7192
+ private attachScreencastFresh;
7193
+ /** Nobody is watching any more: stop paying to render frames. */
7194
+ stopWatching(runId: string): Promise<void>;
7195
+ /**
7196
+ * Suspend or resume the picture without giving up control.
7197
+ *
7198
+ * The credential case, and the only reason this is separate from watching. A
7199
+ * person who has taken the page over to sign in is about to type a password
7200
+ * into a field whose contents are echoed as dots — but a password manager's
7201
+ * dropdown, a "show password" toggle, a one-time code in plain text and the
7202
+ * confirmation page afterwards are not, and all of them would otherwise be
7203
+ * encoded into frames and sent across the network.
7204
+ *
7205
+ * What this does NOT do is hide anything from the agent: it stops the
7206
+ * picture, not the page. Nothing about a suspended capture would stop a
7207
+ * `extract_text` from reading the same screen — it is the lease that stops
7208
+ * that, by refusing the agent for as long as the person holds it.
7209
+ *
7210
+ * Answers whether frames are flowing afterwards, which is what a UI needs to
7211
+ * show and is not always what was asked for: a run with nobody watching has
7212
+ * no session to suspend.
7213
+ */
7214
+ setCapture(runId: string, on: boolean): Promise<boolean>;
7215
+ /**
7216
+ * The capture transition itself, with no lease bookkeeping.
7217
+ *
7218
+ * Separate because the agent's restore in `act()` already holds the action
7219
+ * slot — taking it again there would deadlock against itself, and the whole
7220
+ * point of that call site is that it happens INSIDE the agent's turn.
7221
+ *
7222
+ * `confirmWithFrame` is used only by the agent's visibility gate. There, a
7223
+ * successful CDP command is not enough: the externally visible state remains
7224
+ * paused until a frame arrives, and the frame handler is the only code that
7225
+ * promotes it to capturing. This makes a restore timeout sticky rather than
7226
+ * a one-action refusal.
7227
+ */
7228
+ private applyCapture;
7229
+ /**
7230
+ * Wait, briefly, for Chrome to produce a frame.
7231
+ *
7232
+ * `Page.startScreencast` resolves when the command is accepted, which is not
7233
+ * when a picture exists. The agent's visibility gate — "the page must be
7234
+ * VISIBLE before anything changes it" — was therefore satisfied by a request
7235
+ * rather than by a repaint, and the agent could click while the panel was
7236
+ * still showing the page from before it was hidden, or nothing at all.
7237
+ *
7238
+ * A frame rather than a client receipt, deliberately. A receipt would prove
7239
+ * somebody SAW it, which is stronger — and would make the agent's progress
7240
+ * depend on a viewer that may not exist: this path runs after a handback or a
7241
+ * lapse, when there may be nobody watching at all, and an agent that stalls
7242
+ * because a tab was closed is a worse failure than the one being fixed. What
7243
+ * this does guarantee is ours to guarantee: the stream is producing again.
7244
+ *
7245
+ * Bounded, and the bound rests on the property rounds 14 and 15 already rely
7246
+ * on — a screencast that has just been started emits its first frame at once,
7247
+ * whatever the page is doing. If one does not arrive, the picture genuinely
7248
+ * has not come back, and the caller refuses on the same trade it already
7249
+ * makes when the restore itself fails.
7250
+ */
7251
+ private nextFrame;
7252
+ /** Whether a person is driving this run's browser right now. */
7253
+ humanHolds(runId: string): boolean;
7254
+ /**
7255
+ * Hand this run's browser to the person watching it.
7256
+ *
7257
+ * The wait is the substance. An agent action already inside Playwright cannot
7258
+ * be interrupted — `click` waits for its element to become actionable and
7259
+ * then clicks, whatever anyone decided in the meantime — so a takeover that
7260
+ * returned immediately would put the person on a page an action was still
7261
+ * about to change. Bounded by the action ceiling, because that is the longest
7262
+ * an action can legitimately take; past it something is wedged, and saying so
7263
+ * is better than a spinner that never resolves.
7264
+ *
7265
+ * Idempotent for someone who already holds it: a second click of Take control
7266
+ * just says they are still there.
7267
+ */
7268
+ takeOver(runId: string): Promise<{
7269
+ ok: boolean;
7270
+ detail?: string;
7271
+ }>;
7272
+ /** The person is done. Answers whether they were holding it at all. */
7273
+ /**
7274
+ * The viewer confirms it received a picture of this run's page.
7275
+ *
7276
+ * Reliable, one per watch, and the only thing that opens Hide. Checked
7277
+ * against the CURRENT watch: a receipt that names an older one is from a
7278
+ * viewer that has since been replaced, and vouching for its successor is the
7279
+ * reconnect hole this counter exists to close.
7280
+ */
7281
+ frameSeen(runId: string, generation: number): void;
7282
+ handBack(runId: string): boolean;
7283
+ /**
7284
+ * One thing the person did to the page.
7285
+ *
7286
+ * Authorised per event, not per session. A takeover is not a channel that
7287
+ * stays open once opened: between two clicks the run can be stopped, the hold
7288
+ * can lapse, the page can close, and each of those has to stop the NEXT
7289
+ * event rather than be noticed at the end. So every event re-checks that this
7290
+ * run is live and that the lease is still the person's, and every accepted
7291
+ * event says so by extending the hold.
7292
+ */
7293
+ input(runId: string, event: BrowserInput): Promise<{
7294
+ ok: boolean;
7295
+ detail?: string;
7296
+ }>;
7297
+ /**
7298
+ * The page's own coordinate space, measured rather than inferred.
7299
+ *
7300
+ * Cached, because a click should not cost a round trip, and invalidated by
7301
+ * the frame metadata changing shape — which arrives free with every frame. A
7302
+ * failed measurement falls back to the last good one rather than to a guess:
7303
+ * a stale viewport is off by a resize, an invented one is off by anything.
7304
+ */
7305
+ private pageViewport;
7306
+ /**
7307
+ * Turn one authorised event into CDP calls. Coordinates arrive normalised.
7308
+ *
7309
+ * A switch with a default that REFUSES, rather than a chain of ifs ending in
7310
+ * the click branch. This is a mutation boundary reached from a socket, so the
7311
+ * question is not what a well-formed event does but what a malformed one
7312
+ * does: the chain answered "clicks", which meant a version-skewed client
7313
+ * sending `{ kind: 'drag', x, y }` pressed the mouse on a real page, and a
7314
+ * kind with no coordinates at all clamped to 0 and clicked the top-left
7315
+ * corner. Unknown input has to fail closed.
7316
+ *
7317
+ * The `never` in the default is the other half: adding a member to
7318
+ * `BrowserInput` without handling it here stops compiling, so the next kind
7319
+ * cannot silently inherit whatever the last branch happened to be.
7320
+ */
7321
+ private dispatch;
7322
+ /** The user stopped this run. Refuses further actions and clears its queue. */
7323
+ stopRun(runId: string): void;
7324
+ /** A run ended: release its browser rather than pay for an idle session. */
7325
+ endRun(runId: string): Promise<void>;
7326
+ /** Forget a finished run entirely, so nothing accumulates per run. */
7327
+ private forgetRun;
7328
+ /**
7329
+ * Let go of one run's browser.
7330
+ *
7331
+ * Best-effort throughout: a run is already over by the time this runs, and a
7332
+ * provider that is briefly unreachable must not turn that into a failure the
7333
+ * user sees. The provider's own idle timeout collects anything left.
7334
+ */
7335
+ private disposeRun;
7336
+ /**
7337
+ * Run `disposeRun`, with the run id counted in `closing` until it settles.
7338
+ *
7339
+ * The caller adds the id to `closing` itself, synchronously, in the same
7340
+ * breath as deleting the run from `runs` — see the field comment. This just
7341
+ * guarantees the removal happens, success or failure, so a disposal that
7342
+ * throws cannot leave the slot counted against the pool forever.
7343
+ */
7344
+ private teardown;
7345
+ private act;
7346
+ /**
7347
+ * The run's browser, opened on first use.
7348
+ *
7349
+ * The ONE place the session cap is enforced. An earlier version also checked
7350
+ * it before taking the lease, on the reasoning that a run which cannot get a
7351
+ * session would otherwise queue forever — true when every run shared one
7352
+ * lease, and false once each run got its own, because a second run no longer
7353
+ * queues behind the first at all. Its own revert-check proved it dead: taking
7354
+ * it out changed no test. Two copies of a cap invite them to disagree.
7355
+ */
7356
+ private open;
7357
+ /** The part that may fail, with the pool slot already reserved. */
7358
+ private openReserved;
7359
+ /** Count main-frame navigation only while this page is the run's active page. */
7360
+ private trackPageNavigation;
7361
+ /**
7362
+ * Follow a page opened by the action that just finished.
7363
+ *
7364
+ * A popup/new tab is part of the same user gesture (OAuth is the common
7365
+ * example). Keeping `held.page` and the screencast on its opener makes the
7366
+ * new page both invisible and impossible to drive. This runs while the SAME
7367
+ * action slot is still held, after the opening click/key has fully landed, so
7368
+ * no later human/agent event can slip between detecting the popup and moving
7369
+ * the view to it.
7370
+ */
7371
+ private followOpenedPage;
7372
+ /**
7373
+ * Make one live page the run's controlled/viewed page. Used in both
7374
+ * directions: into a popup opened by an action, and back to another surviving
7375
+ * page when that popup closes itself after OAuth/login completion.
7376
+ */
7377
+ private adoptPage;
7378
+ private perform;
7379
+ }
7380
+
7381
+ declare class GenericCdpProvider implements RemoteBrowserProvider {
7382
+ readonly name = "CDP endpoint";
7383
+ readonly isolatesSessions = false;
7384
+ private endpoint;
7385
+ constructor(endpoint: string);
7386
+ /**
7387
+ * There is no session to create — the endpoint is the session.
7388
+ *
7389
+ * `id` is a constant rather than a generated one because there is nothing to
7390
+ * look up later: `endSession` has nothing to call. Inventing a unique id
7391
+ * would imply a lifecycle this provider does not have.
7392
+ */
7393
+ createSession(): Promise<RemoteBrowserSession>;
7394
+ /**
7395
+ * Deliberately nothing.
7396
+ *
7397
+ * The endpoint belongs to whoever runs it and outlives any single run, so
7398
+ * "ending" it is not ours to do — closing it would take the browser away from
7399
+ * every other user of a shared endpoint. The driver still closes its own
7400
+ * connection and its pages; this is only about the browser's lifetime.
7401
+ */
7402
+ endSession(): Promise<void>;
7403
+ }
7404
+ /**
7405
+ * Whether a URL is a plausible CDP endpoint.
7406
+ *
7407
+ * Checked so a misconfiguration is a clear message at startup rather than an
7408
+ * obscure Playwright failure on the first action, and so an `http://` URL
7409
+ * pasted from a provider's docs is named as the mistake it is.
7410
+ */
7411
+ declare function isCdpEndpoint(url: string): boolean;
7412
+
7413
+ declare class SteelProvider implements RemoteBrowserProvider {
7414
+ readonly name = "Steel";
7415
+ readonly isolatesSessions = true;
7416
+ private base;
7417
+ private apiKey;
7418
+ constructor(opts?: {
7419
+ url?: string;
7420
+ apiKey?: string;
7421
+ });
7422
+ createSession(signal?: AbortSignal): Promise<RemoteBrowserSession>;
7423
+ endSession(id: string): Promise<void>;
7424
+ private call;
7425
+ }
7426
+
7427
+ /**
7428
+ * Why an actor did not get the browser.
7429
+ *
7430
+ * A boolean was not enough: "the queue is full, try again" and "your run was
7431
+ * stopped" are different things to tell a model, and a waiter dropped because
7432
+ * the capability was switched off is neither. Reporting all three as "busy"
7433
+ * had a model politely retrying something that would never be granted.
7434
+ */
7435
+ type LeaseResult = 'granted' | 'busy' | 'cancelled' | 'off' | 'human';
7436
+ interface BrowserLeaseOptions {
7437
+ /**
7438
+ * Whether a run has been stopped. The lease skips revoked runs when handing
7439
+ * on, so a stopped run is not dealt the browser only to refuse it a line
7440
+ * later while the actor behind it waits out the whole queue.
7441
+ */
7442
+ isRevoked?: (sessionId: string) => boolean;
7443
+ /** Called whenever ownership or queue depth changes, so a UI can follow. */
7444
+ onChange?: () => void;
7445
+ }
7446
+ declare class BrowserLease {
7447
+ private actor;
7448
+ private session;
7449
+ private ceilingTimer;
7450
+ private waiting;
7451
+ private lifecycleWired;
7452
+ /**
7453
+ * The action holding the browser, if any.
7454
+ *
7455
+ * Needed alongside the lease for a reason the lease cannot cover: the lease
7456
+ * is RE-ENTRANT for its holder — that is what makes a sequence a sequence —
7457
+ * so nothing in it stops ONE actor running two actions at once. A retry
7458
+ * racing the call it retried is exactly that shape.
7459
+ *
7460
+ * A symbol because it identifies the individual call: only the action that
7461
+ * took the slot may release it.
7462
+ */
7463
+ private action;
7464
+ /** Whether the current action slot represents activity by its holder. */
7465
+ private actionCountsAsHumanActivity;
7466
+ /**
7467
+ * The human action that has already consumed its one idle-deadline grace.
7468
+ * A single wedged CDP request must not look like fresh activity every two
7469
+ * minutes forever. The first deadline may yield to an event that began before
7470
+ * it; a second deadline for that SAME event becomes a pending lapse instead.
7471
+ */
7472
+ private idleGraceAction;
7473
+ /** Release the human hold as soon as this overlong action finally unwinds. */
7474
+ private idleLapseAfterAction;
7475
+ /**
7476
+ * Everyone waiting for that slot, IN THE ORDER THEY ASKED.
7477
+ *
7478
+ * A queue rather than a poll, and the ordering is the whole reason for it.
7479
+ * `acquireActionSlot` used to spin on a 25ms timer, which decides nothing
7480
+ * about who goes first: each waiter checks on its own phase, so the one that
7481
+ * asked SECOND can find the slot free a full poll ahead of the one that asked
7482
+ * first. The person types one event per keypress — `{ kind: 'text', text }`
7483
+ * carries a single character — so that reordering is not a theoretical
7484
+ * unfairness, it is "passowrd" appearing in the box.
7485
+ *
7486
+ * The slot is handed straight from `endAction` to the head of this line while
7487
+ * still synchronous, so nothing can slip in between the two, and a waiter
7488
+ * that reaches the front never has to be told the slot is already taken.
7489
+ */
7490
+ private actionQueue;
7491
+ /** Lapses a human hold nobody is using. See `HUMAN_IDLE_MS`. */
7492
+ private humanTimer;
7493
+ /**
7494
+ * The person ASKED for the hold to end and it could not end yet.
7495
+ *
7496
+ * Kept as a request rather than only a retry timer, because the retry shares
7497
+ * `humanTimer` with the idle deadline: an authorised event settling after the
7498
+ * request would otherwise call `touch()`, re-arm that timer for a full idle
7499
+ * interval, and quietly cancel a handback the person had already asked for.
7500
+ * "Give it back" is not undone by activity that was already under way when
7501
+ * they said it.
7502
+ *
7503
+ * Explicit requests ONLY. An idle expiry must never set this: a deadline that
7504
+ * lands on the person's own in-flight event is evidence they were THERE, and
7505
+ * recording it here made `touch()` bail afterwards, so the hold dropped the
7506
+ * instant a fresh interaction finished. See `lapse()`.
7507
+ */
7508
+ private handBackWanted;
7509
+ private isRevoked;
7510
+ private onChange;
7511
+ constructor(options?: BrowserLeaseOptions);
7512
+ get holderActor(): string | null;
7513
+ /** Whether a person, rather than the agent, is driving the page right now. */
7514
+ get heldByHuman(): boolean;
7515
+ /**
7516
+ * Whether THIS actor is the one holding the browser right now.
7517
+ *
7518
+ * For asking again after a wait. `acquire` is re-entrant for the holder, so
7519
+ * "I was granted the lease" is a fact about the moment it was granted and
7520
+ * says nothing about the moment the caller finally gets to act.
7521
+ */
7522
+ heldBy(actorId: string): boolean;
7523
+ get holderSession(): string | null;
7524
+ get queueDepth(): number;
7525
+ get actionInFlight(): boolean;
7526
+ /**
7527
+ * Declare that this host reports worker terminal states.
7528
+ *
7529
+ * When it does, timers stop being ownership boundaries entirely. When it does
7530
+ * not — an embedder that wired a controller but not the release — the
7531
+ * fallbacks stay, because something has to break a deadlock.
7532
+ */
7533
+ setLifecycleReleaseWired(on: boolean): void;
7534
+ /**
7535
+ * Take the browser for this actor, waiting in line if someone else holds it.
7536
+ *
7537
+ * Re-entrant for the actor that already holds it: that is the sequence case —
7538
+ * the same worker coming back for its next step must not queue behind itself.
7539
+ */
7540
+ acquire(actorId: string, sessionId: string, signal?: AbortSignal): Promise<LeaseResult>;
7541
+ /**
7542
+ * One worker has finished its whole sequence, so its hold ends.
7543
+ *
7544
+ * THIS is the semantic release. It comes from the worker's own terminal path,
7545
+ * so it means "this actor will never ask again" — which a timer can never
7546
+ * mean about a worker that is merely thinking.
7547
+ *
7548
+ * Idempotent, and safe for an actor that never touched the browser.
7549
+ */
7550
+ actorEnded(actorId: string): void;
7551
+ /**
7552
+ * Give the browser up when about to return without using it.
7553
+ *
7554
+ * Two guards, both load-bearing. Never while an action is in flight, because
7555
+ * the lease is RE-ENTRANT: the action still running may be this same actor's
7556
+ * previous step, and taking the lease off it hands the page to a queued
7557
+ * worker mid-sequence. And only while still the holder, because by the time a
7558
+ * refusal path runs a Stop may already have handed the lease on.
7559
+ */
7560
+ releaseIfHeldBy(actorId: string): void;
7561
+ /**
7562
+ * Give the browser to the person watching it.
7563
+ *
7564
+ * Callers must have waited the in-flight action out first — see
7565
+ * `acquireActionSlot`. This does not interrupt one, because it cannot: a
7566
+ * Playwright call already inside the page finishes whatever it started, and
7567
+ * taking the lease off it would only mean the person and a still-running
7568
+ * `fill` were both touching the same form. Waiting is what makes "you have
7569
+ * control" true when the UI says it.
7570
+ *
7571
+ * Every queued worker is turned away rather than left in line, for the same
7572
+ * reason `acquire` refuses: they were planned against a page the person is
7573
+ * about to change.
7574
+ */
7575
+ takeOver(sessionId: string): void;
7576
+ /**
7577
+ * The person is still there — anything they did counts.
7578
+ *
7579
+ * Called per authorised input rather than on a heartbeat, so the hold is
7580
+ * extended by USE and not merely by a tab being open. A left-open panel on a
7581
+ * forgotten laptop stops holding a billed session two minutes later.
7582
+ */
7583
+ touch(): void;
7584
+ /** (Re)start the idle interval. The only place the deadline is set. */
7585
+ private armIdle;
7586
+ /**
7587
+ * The idle deadline came round.
7588
+ *
7589
+ * Both ways a hold ends obey the same rule — never while one of the person's
7590
+ * own events is still landing — but they resolve that clash in OPPOSITE
7591
+ * directions, and routing the lapse through `handBack()` collapsed them into
7592
+ * one. An event in flight BEGAN before this deadline, which makes it evidence
7593
+ * the person was there; deferring the lapse and then releasing the moment
7594
+ * that event lands ends the hold immediately after a fresh interaction, which
7595
+ * is the precise opposite of what an idle deadline measures. So an
7596
+ * interrupted lapse yields: the interval starts again, and the next silence
7597
+ * decides.
7598
+ *
7599
+ * `handBack()` keeps the deferral instead, because "Give it back" is a
7600
+ * decision and activity already under way when it was made does not withdraw
7601
+ * it. Same rule, different answer, which is why they are different methods.
7602
+ */
7603
+ private lapse;
7604
+ /**
7605
+ * The person hands the browser back, or their hold lapses.
7606
+ *
7607
+ * Answers whether there was a human hold to end, so a caller can tell a real
7608
+ * handback from a duplicate click or a lapse that already happened.
7609
+ */
7610
+ handBack(): boolean;
7611
+ private clearHumanIdle;
7612
+ /** Drop every queued waiter belonging to a run, and the lease if it holds it. */
7613
+ dropRun(sessionId: string): void;
7614
+ /** Turn everyone away — the capability itself has gone. */
7615
+ dropAll(): void;
7616
+ /** Hand the browser to the next actor in line, if any. */
7617
+ release(): void;
7618
+ /** Arm the deadlock ceiling. Not a release schedule — see the constant. */
7619
+ armCeiling(): void;
7620
+ clearCeiling(): void;
7621
+ /**
7622
+ * Take the per-action slot, or null when one is already running.
7623
+ *
7624
+ * PRIVATE, so the queue is the only way in. A caller that could take the slot
7625
+ * without joining the line would be exactly the jump-the-queue the ordering
7626
+ * is there to prevent.
7627
+ */
7628
+ private beginAction;
7629
+ /**
7630
+ * Release the slot, but only for the action that took it.
7631
+ *
7632
+ * Hands it to the head of the line rather than merely clearing it, and does
7633
+ * so SYNCHRONOUSLY: the waiter's `beginAction` runs before this call returns,
7634
+ * so there is no window in which the slot is free and somebody who asked
7635
+ * later can take it.
7636
+ */
7637
+ endAction(token: symbol): boolean;
7638
+ /**
7639
+ * Take the slot only if it is free RIGHT NOW, never joining the line.
7640
+ *
7641
+ * For input that is a SAMPLE rather than a decision — pointer movement. A
7642
+ * click or a keystroke is a thing the person meant to do and has to happen,
7643
+ * so it waits its turn; a pointer position is one of many on the way
7644
+ * somewhere, and one that cannot be applied now is worth less than the one
7645
+ * behind it. Queueing them is actively harmful: on an endpoint slower than
7646
+ * the client's sampling interval the backlog grows, and the click behind it
7647
+ * can age out against `acquireActionSlot`'s bound and be refused — movement
7648
+ * costing the person the action they actually intended.
7649
+ *
7650
+ * The queue length is checked as well as the slot, so a sample cannot step
7651
+ * over a click that is already waiting.
7652
+ */
7653
+ tryActionSlot(): symbol | null;
7654
+ /**
7655
+ * Take the per-action slot, waiting in line for it if somebody has it.
7656
+ *
7657
+ * A release can hand the browser on while the previous holder's abort is
7658
+ * still propagating through an awaited navigation. That is a handoff, not a
7659
+ * conflict, so it is normally bounded: a wedged action must not pin an agent
7660
+ * or a takeover forever. Human discrete input is the exception and passes
7661
+ * `null`: once a click, key or committed text has been accepted into this
7662
+ * ordered queue, expiring it merely because earlier accepted input took more
7663
+ * than two seconds silently truncates what the person typed.
7664
+ *
7665
+ * Waiting and TAKING are one step, which they deliberately were not before:
7666
+ * the wait used to answer "the slot looks free" and leave the caller to call
7667
+ * `beginAction` afterwards. Two callers woken by the same release both saw it
7668
+ * free, and the loser was told "another action is already running" — a
7669
+ * refusal for having asked first. Granting it here means whoever reaches the
7670
+ * front of the line gets it, and everybody else is still waiting rather than
7671
+ * refused.
7672
+ *
7673
+ * `null` as a RESULT means a bounded wait ran out OR a caller-supplied queue
7674
+ * cap refused a new waiter. `null` as `withinMs` means an ACCEPTED operation
7675
+ * is intentionally unbounded by time and will leave the queue only when its
7676
+ * turn arrives; it does not require accepting infinitely many operations.
7677
+ * `countsAsHumanActivity=false` is for
7678
+ * lifecycle barriers: they still serialize, but an idle deadline must not
7679
+ * mistake framework cleanup for evidence that the person is present.
7680
+ */
7681
+ acquireActionSlot(withinMs?: number | null, countsAsHumanActivity?: boolean, maxQueued?: number | null): Promise<symbol | null>;
7682
+ /**
7683
+ * Queue one actor, resolving when it gets the browser or is turned away.
7684
+ *
7685
+ * `settle` is wrapped so it runs at most once and always takes the waiter out
7686
+ * of the line: the timeout, an abort and a handoff all race, and settling
7687
+ * twice would silently leave a dead entry holding a queue slot.
7688
+ */
7689
+ private enqueue;
7690
+ }
7691
+
6709
7692
  /** A unit of indexed text. `sourceId` groups chunks from one document/file. */
6710
7693
  interface Chunk {
6711
7694
  id: string;
@@ -7830,4 +8813,4 @@ declare function gatherSyncBundle(config: CascadeConfig): SyncBundle;
7830
8813
  /** Merge a pulled bundle into a config, returning the updated config to persist. */
7831
8814
  declare function applySyncBundle(bundle: SyncBundle, config: CascadeConfig, cleanup?: RetiredProviderCleanup): CascadeConfig;
7832
8815
 
7833
- export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, type AcceptanceOptions, type AcceptanceProbe, type AcceptanceResult, type AcceptanceVerdict, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type AzureDeploymentInput, type BenchmarksConfig, type Block, type BrowserAction, type BrowserActionOutcome, BrowserControlTool, type BrowserController, type BudgetConfig, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, CascadeConfigSchema, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, type ChartSeries, type ChartSpec, type Chunk, type ChunkOptions, CloudClient, type CloudConversation, type CloudMessage, type CloudSession, type CloudSessionStore, type CloudTurnInput, type CloudUser, type CodeChunkOptions, type CodeChunker, CodeSearchTool, type CompileTaskGraphOptions, type CompiledTaskGraph, type CompleteFn, type CompletedNode, ConfigManager, type ConversationMessage, type CredentialDisposition, type CredentialEndpoint, type CredentialWriteOutcome, type CurrentPageProvider, type CurrentPageSnapshot, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, type DashboardConfig, DashboardServer, DependencyScheduler, type DeviceStart, type DiscoveredMcpTool, type DistilledFact, type ChartKind as DocumentChartKind, type DocumentFormat, type Embedder, type EncryptedBlob, type EscalationDecision, type EscalationPayload, type ExtendedContextConfig, type FileManifest, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, type GenerateOptions, type GenerateResult, type GraphFactSource, GraphRetriever, type GraphSearchOptions, GraphSearchTool, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, type ImageByteLoader, type ImageInfo, type ImageRef, type InlineRun, Keystore, LLMReranker, LM_STUDIO_BASE_URL, type LegacyThemeName, type LoadedImage, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, type ManifestDiff, McpClient, type McpDiscoveryResult, McpOAuthProvider, type McpOAuthState, type McpOAuthStore, type McpServerConfig$1 as McpServerConfig, type McpServerRename, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, type NativeProvider, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, type OpenAIEmbedderOptions, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PermissionTrailEntry, type PlanReviewConfig, type ProviderConfig, type ProviderType, RESUME_SCHEMA_VERSION, type RankedFact, type RefreshResult, type RenderOptions, type Reranker, type ResumeCheckpoint, type ResumeReason, ResumeStore, type ResumeStoreOptions, type RetrievalContext, type RetrievalMode, type RetrievalPlan, Retriever, type RetrieverSearchOptions, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type SchedulerExecutionMode, type SchedulerHooks, type SchedulerOptions, type SchedulerResult, type ScoredChunk, type SearchOptions, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type SettingsCommitResult, type SettingsCredentialResult, type SettingsPayload, type SettingsSnapshot, type Slide, SqliteVectorStore, type StatusUpdate, type StoredMessage, type StreamChunk, type SyncBundle, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, type TaskGraph, type TaskGraphInputNode, type TaskGraphIssue, type TaskGraphIssueCode, type TaskGraphNode, type TaskGraphNodeKind, TaskGraphValidationError, type TaskGraphValidationMode, TaskScheduler, type TaskType, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type VectorStore, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, type WorkspaceFileReader, WorkspaceIndex, type WorkspaceIndexOptions, applyEndpointEdit, applyProviderApiKey, applyProviderCredential, applySettingsCredentials, applySettingsPayload, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText, commitSettings, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, credentialDispositionForEdit, credentialEndpointIdentity, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, endpointFromSettingsPayload, evaluateAcceptance, explainRefusal, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasDefaultEndpoint, hasProviderCredential, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, normalizeEndpoint, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, priorAzureRow, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sameAzureEndpoint, sameCredentialEndpoint, sameEndpoint, sessionWorthRemembering, settingsSnapshot, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
8816
+ export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, type AcceptanceOptions, type AcceptanceProbe, type AcceptanceResult, type AcceptanceVerdict, type ApprovalRequest, type ApprovalResponse, type AuditEntry, AuditLogger, type AzureDeploymentInput, type BenchmarksConfig, type Block, type BrowserAction, type BrowserActionOutcome, type BrowserControlState, BrowserControlTool, type BrowserController, type BrowserFrame, type BrowserInput, BrowserLease, type BudgetConfig, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, type CascadeConfig, CascadeConfigSchema, type CascadeEvent, type CascadeEventType, CascadeIgnore, type CascadeMessage, CascadeRouter, type CascadeRunOptions, type CascadeRunResult, type CascadeThemeName, CascadeToolError, type ChartSeries, type ChartSpec, type Chunk, type ChunkOptions, CloudClient, type CloudConversation, type CloudMessage, type CloudSession, type CloudSessionStore, type CloudTurnInput, type CloudUser, type CodeChunkOptions, type CodeChunker, CodeSearchTool, type CompileTaskGraphOptions, type CompiledTaskGraph, type CompleteFn, type CompletedNode, ConfigManager, type ConversationMessage, type CredentialDisposition, type CredentialEndpoint, type CredentialWriteOutcome, type CurrentPageProvider, type CurrentPageSnapshot, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, type DashboardConfig, DashboardServer, DependencyScheduler, type DeviceStart, type DiscoveredMcpTool, type DistilledFact, type ChartKind as DocumentChartKind, type DocumentFormat, type Embedder, type EncryptedBlob, type EscalationDecision, type EscalationPayload, type ExtendedContextConfig, type FileManifest, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, type GenerateOptions, type GenerateResult, GenericCdpProvider, type GraphFactSource, GraphRetriever, type GraphSearchOptions, GraphSearchTool, type HookDefinition, type HooksConfig, HooksRunner, type Identity, type ImageAttachment, type ImageByteLoader, type ImageInfo, type ImageRef, type InlineRun, Keystore, LLMReranker, LM_STUDIO_BASE_URL, type LegacyThemeName, type LoadedImage, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, type ManifestDiff, McpClient, type McpDiscoveryResult, McpOAuthProvider, type McpOAuthState, type McpOAuthStore, type McpServerConfig$1 as McpServerConfig, type McpServerRename, type MemoryConfig, MemoryStore, type Message, type MessageContent, type MessagePayload, type MessageStatus, type MessageType, type ModelInfo, type ModelOverrides, type NativeProvider, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, type OpenAIEmbedderOptions, PROVIDER_DISPLAY_NAMES, type PeerMessage, type PeerMessageEvent, type PeerSyncPayload, type PeerSyncType, type PermissionDecision, type PermissionDecisionPayload, type PermissionRequest, type PermissionTrailEntry, type PlanReviewConfig, type ProviderConfig, type ProviderType, RESUME_SCHEMA_VERSION, type RankedFact, type RefreshResult, type RemoteBrowserConfig, RemoteBrowserController, type RemoteBrowserProvider, type RemoteBrowserSession, type RenderOptions, type Reranker, type ResumeCheckpoint, type ResumeReason, ResumeStore, type ResumeStoreOptions, type RetrievalContext, type RetrievalMode, type RetrievalPlan, Retriever, type RetrieverSearchOptions, type RuntimeNode, type RuntimeNodeLog, type RuntimeRefreshPayload, type RuntimeScope, type RuntimeSession, type RuntimeSnapshotPayload, type ScheduledTask, type SchedulerExecutionMode, type SchedulerHooks, type SchedulerOptions, type SchedulerResult, type ScoredChunk, type SearchOptions, type Session, type SessionCheckpoint, type SessionMetadata, type SessionSubscriptionPayload, type SettingsCommitResult, type SettingsCredentialResult, type SettingsPayload, type SettingsSnapshot, type Slide, SqliteVectorStore, type StatusUpdate, SteelProvider, type StoredMessage, type StreamChunk, type SyncBundle, T1Administrator, type T1ToT2Assignment, T1_MODEL_PRIORITY, T2Manager, type T2Result, type T2ToT3Assignment, T2_MODEL_PRIORITY, type T3Result, type T3ResultPayload, type T3SubtaskSpec, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, type TaskComplexity, type TaskGraph, type TaskGraphInputNode, type TaskGraphIssue, type TaskGraphIssueCode, type TaskGraphNode, type TaskGraphNodeKind, TaskGraphValidationError, type TaskGraphValidationMode, TaskScheduler, type TaskType, Telemetry, type TelemetryConfig, type Theme, type ThemeColors, type ThemeName, type TierConfig, type TierLimits, type TierRole, type TierStatus, type TokenUsage, type ToolCall, type ToolDefinition, type ToolExecuteOptions, ToolRegistry, type ToolResult, type ToolsConfig, VISION_MODEL_PRIORITY, type VectorStore, type WebSearchConfig, type WebhookConfig, type WorkspaceConfig, type WorkspaceFileReader, WorkspaceIndex, type WorkspaceIndexOptions, applyEndpointEdit, applyProviderApiKey, applyProviderCredential, applySettingsCredentials, applySettingsPayload, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText, commitSettings, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, credentialDispositionForEdit, credentialEndpointIdentity, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, endpointFromSettingsPayload, evaluateAcceptance, explainRefusal, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasDefaultEndpoint, hasProviderCredential, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isCdpEndpoint, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, normalizeEndpoint, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, priorAzureRow, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sameAzureEndpoint, sameCredentialEndpoint, sameEndpoint, sessionWorthRemembering, settingsSnapshot, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };