cascade-ai 0.79.0 → 0.81.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.cts CHANGED
@@ -1020,7 +1020,7 @@ interface ThemeColors {
1020
1020
  t2Color: string;
1021
1021
  t3Color: string;
1022
1022
  }
1023
- type CascadeEventType = 'task:start' | 'task:complete' | 'task:error' | 'tier:status' | 'tier:result' | 'tier:root' | 'stream:token' | 'stream:done' | 'tool:approval-request' | 'tool:approval-response' | 'tool:execute' | 'tool:result' | 'tool:call' | 'cost:update' | 'session:save' | 'escalation' | 'peer:sync' | 'peer:message' | 'plan' | 'log' | 'run:cancelled' | 'budget:warning' | 'budget:exceeded' | 'permission:user-required' | 'mcp:approval-required' | 'plan:approval-required' | 'context:approval-required' | 'context:compacted';
1023
+ type CascadeEventType = 'task:start' | 'task:complete' | 'task:error' | 'tier:status' | 'tier:result' | 'tier:root' | 'stream:token' | 'stream:done' | 'tool:approval-request' | 'tool:approval-response' | 'tool:execute' | 'tool:result' | 'tool:call' | 'cost:update' | 'session:save' | 'escalation' | 'peer:sync' | 'peer:message' | 'plan' | 'log' | 'run:cancelled' | 'budget:warning' | 'budget:exceeded' | 'permission:user-required' | 'mcp:approval-required' | 'plan:approval-required' | 'context:approval-required' | 'context:approval-closed' | 'context:compacted';
1024
1024
  interface CascadeEvent<T = unknown> {
1025
1025
  type: CascadeEventType;
1026
1026
  taskId?: string;
@@ -3333,6 +3333,78 @@ declare class BrowserControlTool extends BaseTool {
3333
3333
  execute(input: Record<string, unknown>, options: ToolExecuteOptions): Promise<string>;
3334
3334
  }
3335
3335
 
3336
+ /** One thing the model needs to know before it can act. */
3337
+ interface ClarificationQuestion {
3338
+ /** Assigned here, never by the model: two questions with one id lose an answer. */
3339
+ id: string;
3340
+ prompt: string;
3341
+ /** `choice` picks one, `multi` picks any, `text` is free-form. */
3342
+ kind: 'choice' | 'multi' | 'text';
3343
+ /** Required for `choice` and `multi`; meaningless for `text`. */
3344
+ options?: string[];
3345
+ }
3346
+ interface ClarificationAnswer {
3347
+ id: string;
3348
+ /** One option, several, or typed text — matching the question's `kind`. */
3349
+ value: string | string[];
3350
+ }
3351
+ /**
3352
+ * Why a questionnaire came back empty.
3353
+ *
3354
+ * Distinguished because they mean different things to a reader of the
3355
+ * transcript: nobody was there to ask, the person was asked and did not
3356
+ * answer, or the run ended underneath the question.
3357
+ */
3358
+ type ClarificationOutcome = 'answered' | 'no-listener' | 'timeout' | 'aborted';
3359
+ interface ClarificationResult {
3360
+ outcome: ClarificationOutcome;
3361
+ answers: ClarificationAnswer[];
3362
+ }
3363
+ /** What the host does with a questionnaire. Supplied by `Cascade`. */
3364
+ type ClarificationAsker = (questions: ClarificationQuestion[], signal: AbortSignal | undefined) => Promise<ClarificationResult>;
3365
+ declare class AskUserTool extends BaseTool {
3366
+ readonly name = "ask_user";
3367
+ readonly description: string;
3368
+ readonly inputSchema: {
3369
+ type: string;
3370
+ properties: {
3371
+ questions: {
3372
+ type: string;
3373
+ minItems: number;
3374
+ maxItems: number;
3375
+ description: string;
3376
+ items: {
3377
+ type: string;
3378
+ properties: {
3379
+ prompt: {
3380
+ type: string;
3381
+ description: string;
3382
+ };
3383
+ kind: {
3384
+ type: string;
3385
+ enum: string[];
3386
+ description: string;
3387
+ };
3388
+ options: {
3389
+ type: string;
3390
+ items: {
3391
+ type: string;
3392
+ };
3393
+ description: string;
3394
+ };
3395
+ };
3396
+ required: string[];
3397
+ };
3398
+ };
3399
+ };
3400
+ required: string[];
3401
+ };
3402
+ private ask;
3403
+ constructor(ask: ClarificationAsker);
3404
+ isDangerous(): boolean;
3405
+ execute(input: Record<string, unknown>, options: ToolExecuteOptions): Promise<string>;
3406
+ }
3407
+
3336
3408
  /** One entry in the per-run orchestration decision trail (see /why). */
3337
3409
  interface DecisionLogEntry {
3338
3410
  at: string;
@@ -3427,6 +3499,77 @@ declare class Cascade extends EventEmitter {
3427
3499
  private pendingPlanApproval?;
3428
3500
  private requestPlanApproval;
3429
3501
  private pendingEscalations;
3502
+ /**
3503
+ * Questionnaires waiting on a person, keyed by request id.
3504
+ *
3505
+ * Exactly the shape `pendingEscalations` uses, and for the same reason: the
3506
+ * answer, the timeout and the abort all race, and one settle path guarded by
3507
+ * the map delete is what makes the first of them win.
3508
+ */
3509
+ private pendingClarifications;
3510
+ /**
3511
+ * Put a gate's opening question to its listeners, and NOTICE when one fails.
3512
+ *
3513
+ * `emit` cannot do this job. It calls listeners synchronously and discards
3514
+ * what they return, so an `async` listener — the ordinary shape for anything
3515
+ * that has to reach a socket or a UI — does not fail in a way `emit` reports:
3516
+ * it returns a REJECTED PROMISE that nobody is holding. The `try` around the
3517
+ * emit sees nothing, the gate is never told delivery failed, and it parks for
3518
+ * its full timeout while Node raises an unhandled rejection that can take the
3519
+ * process down.
3520
+ *
3521
+ * `onFailed` is called for either shape, and it is safe to call more than
3522
+ * once: both gates route it to `settle`, which is guarded by a map delete, so
3523
+ * the first failure wins and the rest are no-ops.
3524
+ *
3525
+ * PER LISTENER, not around the loop. `emit` stops dispatching at the first
3526
+ * throw, so one broken host used to cost a working one its notification —
3527
+ * the same reason `announceLiveView` delivers its two listeners separately.
3528
+ * A synchronous failure settles the gate once the whole snapshot has been
3529
+ * delivered; an asynchronous one settles it when the rejection arrives, which
3530
+ * is still far better than the window-long park it replaces.
3531
+ *
3532
+ * SETTLED AFTER THE LOOP, not inside it, and that is not tidiness. `onFailed`
3533
+ * settles the gate, and settling emits the gate's CLOSURE — so failing
3534
+ * mid-dispatch announced "this request is over" and then went on handing the
3535
+ * OPENING event to the listeners after it. A host later in the list would
3536
+ * draw a form whose only closure notification had already gone past, and it
3537
+ * would stand there forever. Deliver to everyone, then decide.
3538
+ *
3539
+ * `rawListeners`, NOT `listeners`. Bypassing `emit` means taking on what
3540
+ * `emit` was doing, and `once` is the part that bites: `listeners()` hands
3541
+ * back the UNWRAPPED callback, so calling it never runs the wrapper that
3542
+ * deregisters it. A host that asked to be told once would be told every
3543
+ * time — and told through a transport it had set up for one gate only.
3544
+ * `rawListeners()` returns the wrapper, which removes the registration and
3545
+ * passes the underlying listener's return value straight through, so the
3546
+ * promise below is still the one being observed. Verified against Node.
3547
+ *
3548
+ * `.call(this, …)`, which is the third thing `emit` was doing. EventEmitter
3549
+ * invokes listeners with the emitter as the receiver, so
3550
+ * `function () { this.resolveClarification(...) }` is a legitimate host — and
3551
+ * calling it bare gave it `this === undefined`, which throws, which this
3552
+ * would then have read as the host failing to receive the question and
3553
+ * settled the gate unanswered. A working listener turned into a delivery
3554
+ * failure by the thing delivering it.
3555
+ */
3556
+ private deliverGate;
3557
+ /**
3558
+ * Tell listeners something that is over, and let none of them make it worse.
3559
+ *
3560
+ * The closing half of the gate family — `clarification:closed`,
3561
+ * `clarification:timeout`, `escalation:closed`, `escalation:timeout`. Each
3562
+ * was already wrapped in a `try` for the synchronous case, and each had the
3563
+ * same hole `deliverGate` had before last round: `emit` discards what a
3564
+ * listener returns, so an `async` host fails by returning a rejected promise
3565
+ * nobody is holding and Node raises an unhandled rejection.
3566
+ *
3567
+ * There is nothing to settle here — the gate is already closed, which is the
3568
+ * whole message — so unlike `deliverGate` this reports to no one. It exists
3569
+ * so that the four informational emits cannot take the process down, and so
3570
+ * the rule is written once rather than four times.
3571
+ */
3572
+ private announceGateClosed;
3430
3573
  private requestEscalationDecision;
3431
3574
  /**
3432
3575
  * Resolve a pending escalation from a REPL / dashboard / cloud listener.
@@ -3452,6 +3595,27 @@ declare class Cascade extends EventEmitter {
3452
3595
  * reviewing and accepting that section's output.
3453
3596
  */
3454
3597
  private releasePendingEscalations;
3598
+ /**
3599
+ * Put a questionnaire to whoever is watching, if anybody is.
3600
+ *
3601
+ * Returns `no-listener` rather than waiting when there is nothing attached to
3602
+ * answer — a scheduled run, an API caller with no socket, an autonomous run
3603
+ * that has already said it wants no prompts. That check is the whole reason
3604
+ * this can be offered to the model at all: a tool that might hang a headless
3605
+ * run forever is a tool that cannot be registered.
3606
+ */
3607
+ askUser(questions: ClarificationQuestion[], signal?: AbortSignal): Promise<ClarificationResult>;
3608
+ /**
3609
+ * Answer a questionnaire from a REPL / dashboard / cloud listener.
3610
+ *
3611
+ * `requestId` comes back on the `clarification:required` event and is what
3612
+ * makes an answer land on the question that asked. Optional for a host with
3613
+ * only one in flight, matching `resolveEscalation`: without it the oldest
3614
+ * outstanding request is answered, which is correct whenever there is one.
3615
+ */
3616
+ resolveClarification(answers: ClarificationAnswer[], requestId?: string): void;
3617
+ /** Release every parked questionnaire — run teardown, so none outlive the run. */
3618
+ private releasePendingClarifications;
3455
3619
  /**
3456
3620
  * Resolve a pending boardroom plan approval from a REPL / dashboard listener.
3457
3621
  * An optional `note` re-plans and re-asks; an optional `editedPlan` is applied
@@ -3572,6 +3736,29 @@ declare class Cascade extends EventEmitter {
3572
3736
  * and not the other is silent on the surface that missed it.
3573
3737
  */
3574
3738
  private registerBrowserControl;
3739
+ /**
3740
+ * Declare that this host can put a questionnaire to a person and answer it.
3741
+ *
3742
+ * Explicit, and NOT done at construction, because registering a tool is a
3743
+ * promise the host has to keep. A host that registers `ask_user` without
3744
+ * listening for `clarification:required` gives the model a tool that always
3745
+ * answers "there is nobody watching this run" — while a person sits at the
3746
+ * terminal looking at it. That is worse than not offering the tool at all:
3747
+ * it costs a round trip and then tells the model something false.
3748
+ *
3749
+ * Only `cloud/server` bridges the protocol today. The CLI REPL and the
3750
+ * dashboard do not, so they do not call this, and their models are not shown
3751
+ * a capability nobody can serve. Wiring either of them is a matter of
3752
+ * listening for the event and calling `resolveClarification`, then calling
3753
+ * this.
3754
+ *
3755
+ * Still gated on the same conditions as the call itself, and `askUser`
3756
+ * re-checks them: `setUnattended` can land after this, so the tool can be
3757
+ * registered for a run that later turns out to have nobody on it. Then it
3758
+ * answers `no-listener` and the model proceeds on its own reading, which is
3759
+ * the same defence-in-depth `BrowserControlTool.revoke` provides.
3760
+ */
3761
+ enableClarification(): void;
3575
3762
  private registerMediaTools;
3576
3763
  /**
3577
3764
  * Register `generate_document` where there is a real workspace to write into.
@@ -6835,6 +7022,21 @@ interface RemoteBrowserSession {
6835
7022
  * than the one that owns the run.
6836
7023
  */
6837
7024
  liveViewUrl?: string;
7025
+ /**
7026
+ * How long the provider will keep this session before reaping it, in ms.
7027
+ *
7028
+ * Reported by the provider, never chosen by us: Steel's create API accepts
7029
+ * no timeout field (verified against the API source — see steel.ts), so this
7030
+ * ceiling is the provider's and the only thing we can do about it is know
7031
+ * what it is. Absent where a provider says nothing, which includes a bare
7032
+ * CDP endpoint: that browser is somebody else's and has no session at all.
7033
+ *
7034
+ * Worth surfacing because a session that outlives its usefulness dies at
7035
+ * exactly this number, and a leak is otherwise indistinguishable from a
7036
+ * deliberate shutdown — two field sessions ended at precisely 0:05:00 with
7037
+ * nothing anywhere recording that five minutes was the limit.
7038
+ */
7039
+ expiresInMs?: number;
6838
7040
  }
6839
7041
  /**
6840
7042
  * Somewhere to get a browser from.
@@ -6889,6 +7091,88 @@ interface BrowserViewInfo {
6889
7091
  /** Where to watch it, when the provider can stream one. */
6890
7092
  liveViewUrl?: string;
6891
7093
  }
7094
+ /**
7095
+ * One rendered frame of a run's browser, on its way to whoever is watching.
7096
+ *
7097
+ * A JPEG rather than a video stream, and deliberately: CDP's screencast is
7098
+ * adaptive — Chrome sends a frame when the page actually changes, not on a
7099
+ * clock — so an idle page costs nothing, and there is no media pipeline to run
7100
+ * inside the deployment. It is worse than WebRTC for video-heavy pages and
7101
+ * better than it for everything else this feature is pointed at.
7102
+ */
7103
+ interface BrowserFrame {
7104
+ /** base64 JPEG, as CDP hands it over. Never decoded on the way through. */
7105
+ data: string;
7106
+ /** The remote viewport, so a canvas can size itself without guessing. */
7107
+ width: number;
7108
+ height: number;
7109
+ /**
7110
+ * Which watch this frame belongs to. See `watchGen`.
7111
+ *
7112
+ * Travels with the picture so the receipt the client sends back names the
7113
+ * watch it actually saw, rather than whichever one is current by the time it
7114
+ * arrives.
7115
+ */
7116
+ generation: number;
7117
+ }
7118
+ /** Who is driving a run's browser, and whether it is being pictured. */
7119
+ interface BrowserControlState {
7120
+ /** True while a person holds it and the agent is being refused. */
7121
+ human: boolean;
7122
+ /** Whether frames are being produced right now. See `setCapture`. */
7123
+ capturing: boolean;
7124
+ /**
7125
+ * Whether THIS watch has confirmed receiving a picture. See `seenGen`.
7126
+ *
7127
+ * Pushed rather than inferred, for the same reason ownership is: the client
7128
+ * cannot work it out while the picture is off, because it is the frames that
7129
+ * would tell it. A client left to guess would guess from what it remembers of
7130
+ * a previous watch, which is exactly the state this distinguishes.
7131
+ */
7132
+ confirmed: boolean;
7133
+ }
7134
+ /**
7135
+ * One thing a person did to the page while they held it.
7136
+ *
7137
+ * Coordinates are NORMALISED — 0..1 across the frame — rather than pixels, and
7138
+ * that is a boundary decision rather than a convenience. The picture the user
7139
+ * clicked is a JPEG scaled twice on its way to them: once by CDP into
7140
+ * `maxWidth`/`maxHeight`, once by the panel into whatever width the layout
7141
+ * gave it. Only this side knows the viewport those pixels came from, so only
7142
+ * this side can turn a click back into a page coordinate. A client sending
7143
+ * pixels would have to reconstruct both scalings and would silently miss by a
7144
+ * few pixels whenever it got one wrong — which, on a page of small controls, is
7145
+ * a click on the wrong thing.
7146
+ *
7147
+ * Typing is `text` rather than a stream of key events because `Input.insertText`
7148
+ * puts the whole string in at once: no IME to emulate, no per-character key
7149
+ * codes to get wrong for anything but a US layout, and no chance of a password
7150
+ * arriving one keystroke per socket message. `key` covers the keys that are
7151
+ * commands rather than characters, from a fixed list — see `KEYS`.
7152
+ */
7153
+ type BrowserInput = {
7154
+ kind: 'move';
7155
+ x: number;
7156
+ y: number;
7157
+ } | {
7158
+ kind: 'click';
7159
+ x: number;
7160
+ y: number;
7161
+ button?: 'left' | 'right' | 'middle';
7162
+ clicks?: number;
7163
+ } | {
7164
+ kind: 'scroll';
7165
+ x: number;
7166
+ y: number;
7167
+ deltaY: number;
7168
+ } | {
7169
+ kind: 'text';
7170
+ text: string;
7171
+ } | {
7172
+ kind: 'key';
7173
+ key: string;
7174
+ modifiers?: Array<'Control' | 'Meta' | 'Shift'>;
7175
+ };
6892
7176
  interface RemoteBrowserControllerOptions {
6893
7177
  provider: RemoteBrowserProvider;
6894
7178
  /**
@@ -6896,8 +7180,51 @@ interface RemoteBrowserControllerOptions {
6896
7180
  * a wave of workers each opening their own is a cost nobody chose.
6897
7181
  */
6898
7182
  maxSessions?: number;
6899
- /** Told when a run's live view becomes available, so the owner can watch. */
6900
- onLiveView?: (runId: string, liveViewUrl: string | undefined) => void;
7183
+ /**
7184
+ * Told when a run's live view becomes available, so the owner can watch.
7185
+ *
7186
+ * `void | Promise<void>`, like `onReleaseFailed` below and for the same
7187
+ * reason: an async sink is the ordinary shape, TypeScript accepts one
7188
+ * wherever a `void` return is declared, and a signature promising `void`
7189
+ * tells an embedder their rejection is somebody's problem when it is
7190
+ * nobody's. `announceLiveView` observes it.
7191
+ */
7192
+ onLiveView?: (runId: string, liveViewUrl: string | undefined) => void | Promise<void>;
7193
+ /**
7194
+ * Told when a provider session could not be handed back.
7195
+ *
7196
+ * Release is best-effort by design — a run that has finished must not be
7197
+ * reported as failed because the provider was briefly unreachable — but
7198
+ * best-effort is not the same as unobservable. A release that silently did
7199
+ * not happen leaves a billed browser running until the provider's own
7200
+ * timeout collects it, and without this there is no way to tell that from a
7201
+ * clean handback.
7202
+ */
7203
+ /**
7204
+ * `void | Promise<void>`: an async sink is the ordinary shape for telemetry,
7205
+ * and a signature promising `void` tells an embedder their rejection is
7206
+ * somebody's problem when it is nobody's. `reportReleaseFailure` observes it.
7207
+ */
7208
+ onReleaseFailed?: (runId: string, sessionId: string, err: unknown, expiresInMs?: number) => void | Promise<void>;
7209
+ /**
7210
+ * Something that must finish before this controller may open anything.
7211
+ *
7212
+ * For ROTATION. A settings change builds a new controller and disposes the
7213
+ * old one, and disposal is not instant: it detaches CDP sessions and hands
7214
+ * provider sessions back over the network. Installing the replacement
7215
+ * immediately meant the first action after a rotation could call
7216
+ * `createSession` while the previous controller was still releasing — two
7217
+ * controllers holding sessions against one provider's cap, which the pool
7218
+ * here cannot see because it counts only its own. On a provider that
7219
+ * enforces its own limit that action simply fails, and it is the first one
7220
+ * after an operator changed a setting, which is the worst moment for it.
7221
+ *
7222
+ * Awaited once, before the pool slot is reserved, and never awaited again.
7223
+ * A rejection is ignored on purpose: the old controller failing to tidy up
7224
+ * is not a reason to refuse the new one work, and `dispose` already reports
7225
+ * its own trouble.
7226
+ */
7227
+ ready?: Promise<unknown>;
6901
7228
  }
6902
7229
  declare class RemoteBrowserController {
6903
7230
  private provider;
@@ -6911,8 +7238,47 @@ declare class RemoteBrowserController {
6911
7238
  * bearer capability for a browser somebody else is driving.
6912
7239
  */
6913
7240
  private liveViewListeners;
7241
+ /**
7242
+ * Told when a run's browser changes hands, or stops being pictured.
7243
+ *
7244
+ * Needed because a takeover can end without anybody asking. The hold lapses
7245
+ * on its own after `HUMAN_IDLE_MS`, and a client left believing it still has
7246
+ * control would keep sending input into a lease it no longer holds — every
7247
+ * event refused, with nothing on screen saying why.
7248
+ */
7249
+ private controlListeners;
7250
+ /** What each run's listener was last told, so unchanged states stay quiet. */
7251
+ private controlAnnounced;
6914
7252
  /** An embedder that wants every run's live view, told which run each is. */
6915
7253
  private onLiveViewAll;
7254
+ /** Told when a provider session could not be handed back. See `disposeRun`. */
7255
+ private onReleaseFailed;
7256
+ /**
7257
+ * Tell the observer a session leaked, without letting it become the leak.
7258
+ *
7259
+ * Guarded, because this runs inside a `.catch` on the release: an observer
7260
+ * that throws — a logging or telemetry sink that is momentarily unavailable
7261
+ * is the ordinary case — would turn its own failure into a fresh rejection
7262
+ * from `disposeRun`, so `endRun` would reject before `forgetRun` and the
7263
+ * fire-and-forget `stopRun` path would raise an unhandled rejection.
7264
+ *
7265
+ * That is precisely the guarantee this whole change claims to preserve: a
7266
+ * release that fails must never fail the run. Reporting it cannot be the
7267
+ * thing that breaks it.
7268
+ *
7269
+ * TWO WAYS TO FAIL, not one. The `try` catches a synchronous throw; an
7270
+ * `async` sink — which is what a telemetry or logging callback usually is —
7271
+ * fails by returning a REJECTED PROMISE, which no `try` around a call that is
7272
+ * never awaited can see. Node then raises an unhandled rejection and can take
7273
+ * the process down: the observer would not merely have failed to report the
7274
+ * leak, it would have killed the run, which is the one outcome this function
7275
+ * exists to prevent, reached through the door the guard did not cover.
7276
+ *
7277
+ * Not awaited, though. The caller is a `.catch` on the release and is not
7278
+ * going to wait for a telemetry round trip; attaching a handler is what makes
7279
+ * the rejection observed, which is the whole requirement.
7280
+ */
7281
+ private reportReleaseFailure;
6916
7282
  private runs;
6917
7283
  /**
6918
7284
  * Runs that have a pool slot but no browser yet.
@@ -6967,12 +7333,46 @@ declare class RemoteBrowserController {
6967
7333
  */
6968
7334
  private leases;
6969
7335
  constructor(options: RemoteBrowserControllerOptions);
7336
+ /**
7337
+ * The predecessor's teardown, until it has been waited for once.
7338
+ *
7339
+ * Cleared as soon as it settles so the wait is not re-entered on every open
7340
+ * — and so the promise itself is not retained for the life of the process.
7341
+ */
7342
+ private ready;
7343
+ /**
7344
+ * This controller has been retired and will never open anything again.
7345
+ *
7346
+ * Stronger than "its runs were ended", and it has to be. A caller parked on
7347
+ * the `ready` gate above is invisible to `dispose()` — it holds no run and
7348
+ * has reserved no slot, because it has not got that far — so a SECOND
7349
+ * settings change, arriving while the first retirement is still settling,
7350
+ * found nothing to wait for and let the newest controller start immediately.
7351
+ * The parked caller then woke up on a controller nobody holds a reference to
7352
+ * and allocated a session against the superseded configuration, with the
7353
+ * newest controller free to allocate its own.
7354
+ *
7355
+ * Set synchronously at the top of `dispose`, so anything already waiting is
7356
+ * excluded the moment retirement begins rather than when it finishes.
7357
+ */
7358
+ private retired;
6970
7359
  private leaseFor;
6971
7360
  /** The controller to hand to `Cascade.setBrowserController`. */
6972
7361
  get controller(): (action: BrowserAction, context: BrowserActionContext) => Promise<BrowserActionOutcome>;
6973
7362
  /** Watch one run's browser. The URL never goes to any other run's listener. */
6974
- onLiveViewFor(runKey: string, listener: (info: BrowserViewInfo) => void): void;
7363
+ onLiveViewFor(runKey: string, listener: (info: BrowserViewInfo) => void | Promise<void>): void;
6975
7364
  offLiveViewFor(runKey: string): void;
7365
+ onControlFor(runKey: string, listener: (state: BrowserControlState) => void | Promise<void>): void;
7366
+ offControlFor(runKey: string): void;
7367
+ /**
7368
+ * Say who has the browser, but only when the answer has changed.
7369
+ *
7370
+ * The lease reports every ownership change, and during ordinary work that is
7371
+ * one per action as workers take it and give it back. None of those are
7372
+ * interesting to a viewer — the agent had it before and has it now — and
7373
+ * emitting them would put a socket message on every click the agent makes.
7374
+ */
7375
+ private announceControl;
6976
7376
  /**
6977
7377
  * Tell a run's listener what its browser situation is.
6978
7378
  *
@@ -6995,6 +7395,184 @@ declare class RemoteBrowserController {
6995
7395
  dispose(): Promise<void>;
6996
7396
  /** A worker finished; it will never ask for the browser again. */
6997
7397
  actorEnded(actorId: string): void;
7398
+ /**
7399
+ * Start streaming this run's page to whoever is watching it.
7400
+ *
7401
+ * Nothing streams until someone asks. That is the whole reason this is a
7402
+ * method rather than something `open()` does: an unwatched run would
7403
+ * otherwise pay to encode frames nobody sees, on every run, forever — and on
7404
+ * a metered provider it would pay for the bandwidth too.
7405
+ *
7406
+ * Frames are ACKED after they are handed on, never before. Chrome sends the
7407
+ * next frame only once the previous is acknowledged, so this bounds it to one
7408
+ * outstanding frame against OUR OWN handling. It does not reach further than
7409
+ * that, and an earlier version of this comment claimed it did: `onFrame` ends
7410
+ * at a socket emit, which hands the JPEG to a transport rather than to a
7411
+ * viewer, so the ack says nothing about what the person actually received.
7412
+ * Bounding the rest of the path is the transport's job — see `emitFrame`,
7413
+ * where frames go out lossy so a slow connection drops pictures instead of
7414
+ * banking stale ones. Dropping the ack stops the stream dead, which is why it
7415
+ * is best-effort but unconditional.
7416
+ *
7417
+ * Returns false when there is nothing to watch — the run has no browser, or
7418
+ * it is already being watched. Callers use that to avoid promising a viewer
7419
+ * a stream that will never arrive.
7420
+ */
7421
+ startWatching(runId: string, onFrame: (frame: BrowserFrame) => void | Promise<void>): Promise<boolean>;
7422
+ /**
7423
+ * Run one watch or unwatch after every one already asked for.
7424
+ *
7425
+ * The chain is kept non-rejecting on purpose: a failed attach must not poison
7426
+ * every later request for this run, which is what a bare `.then` chain would
7427
+ * do. The caller still sees its own failure, because that is the promise
7428
+ * returned rather than the one stored.
7429
+ */
7430
+ private queueWatch;
7431
+ /**
7432
+ * Attach at most one physical CDP screencast session at a time.
7433
+ *
7434
+ * `startWatching()` already serializes watcher lifecycle through `watchQueue`,
7435
+ * but the agent's detached-hidden restore cannot join that queue: it holds the
7436
+ * action slot, while an earlier unwatch may be waiting for exactly that slot.
7437
+ * Sharing only the physical attach closes the two-session race without making
7438
+ * watcher teardown and agent mutation wait on each other in a cycle.
7439
+ *
7440
+ * A real watcher claims `onFrame` immediately, before it waits for an attach
7441
+ * already started by the agent. The lower-level attach never writes the sink,
7442
+ * so finishing an older temporary attempt cannot overwrite a newer viewer.
7443
+ */
7444
+ private attachScreencast;
7445
+ /** Open one new CDP session and start the stream when it is safe to do so. */
7446
+ private attachScreencastFresh;
7447
+ /** Nobody is watching any more: stop paying to render frames. */
7448
+ stopWatching(runId: string): Promise<void>;
7449
+ /**
7450
+ * Suspend or resume the picture without giving up control.
7451
+ *
7452
+ * The credential case, and the only reason this is separate from watching. A
7453
+ * person who has taken the page over to sign in is about to type a password
7454
+ * into a field whose contents are echoed as dots — but a password manager's
7455
+ * dropdown, a "show password" toggle, a one-time code in plain text and the
7456
+ * confirmation page afterwards are not, and all of them would otherwise be
7457
+ * encoded into frames and sent across the network.
7458
+ *
7459
+ * What this does NOT do is hide anything from the agent: it stops the
7460
+ * picture, not the page. Nothing about a suspended capture would stop a
7461
+ * `extract_text` from reading the same screen — it is the lease that stops
7462
+ * that, by refusing the agent for as long as the person holds it.
7463
+ *
7464
+ * Answers whether frames are flowing afterwards, which is what a UI needs to
7465
+ * show and is not always what was asked for: a run with nobody watching has
7466
+ * no session to suspend.
7467
+ */
7468
+ setCapture(runId: string, on: boolean): Promise<boolean>;
7469
+ /**
7470
+ * The capture transition itself, with no lease bookkeeping.
7471
+ *
7472
+ * Separate because the agent's restore in `act()` already holds the action
7473
+ * slot — taking it again there would deadlock against itself, and the whole
7474
+ * point of that call site is that it happens INSIDE the agent's turn.
7475
+ *
7476
+ * `confirmWithFrame` is used only by the agent's visibility gate. There, a
7477
+ * successful CDP command is not enough: the externally visible state remains
7478
+ * paused until a frame arrives, and the frame handler is the only code that
7479
+ * promotes it to capturing. This makes a restore timeout sticky rather than
7480
+ * a one-action refusal.
7481
+ */
7482
+ private applyCapture;
7483
+ /**
7484
+ * Wait, briefly, for Chrome to produce a frame.
7485
+ *
7486
+ * `Page.startScreencast` resolves when the command is accepted, which is not
7487
+ * when a picture exists. The agent's visibility gate — "the page must be
7488
+ * VISIBLE before anything changes it" — was therefore satisfied by a request
7489
+ * rather than by a repaint, and the agent could click while the panel was
7490
+ * still showing the page from before it was hidden, or nothing at all.
7491
+ *
7492
+ * A frame rather than a client receipt, deliberately. A receipt would prove
7493
+ * somebody SAW it, which is stronger — and would make the agent's progress
7494
+ * depend on a viewer that may not exist: this path runs after a handback or a
7495
+ * lapse, when there may be nobody watching at all, and an agent that stalls
7496
+ * because a tab was closed is a worse failure than the one being fixed. What
7497
+ * this does guarantee is ours to guarantee: the stream is producing again.
7498
+ *
7499
+ * Bounded, and the bound rests on the property rounds 14 and 15 already rely
7500
+ * on — a screencast that has just been started emits its first frame at once,
7501
+ * whatever the page is doing. If one does not arrive, the picture genuinely
7502
+ * has not come back, and the caller refuses on the same trade it already
7503
+ * makes when the restore itself fails.
7504
+ */
7505
+ private nextFrame;
7506
+ /** Whether a person is driving this run's browser right now. */
7507
+ humanHolds(runId: string): boolean;
7508
+ /**
7509
+ * Hand this run's browser to the person watching it.
7510
+ *
7511
+ * The wait is the substance. An agent action already inside Playwright cannot
7512
+ * be interrupted — `click` waits for its element to become actionable and
7513
+ * then clicks, whatever anyone decided in the meantime — so a takeover that
7514
+ * returned immediately would put the person on a page an action was still
7515
+ * about to change. Bounded by the action ceiling, because that is the longest
7516
+ * an action can legitimately take; past it something is wedged, and saying so
7517
+ * is better than a spinner that never resolves.
7518
+ *
7519
+ * Idempotent for someone who already holds it: a second click of Take control
7520
+ * just says they are still there.
7521
+ */
7522
+ takeOver(runId: string): Promise<{
7523
+ ok: boolean;
7524
+ detail?: string;
7525
+ }>;
7526
+ /** The person is done. Answers whether they were holding it at all. */
7527
+ /**
7528
+ * The viewer confirms it received a picture of this run's page.
7529
+ *
7530
+ * Reliable, one per watch, and the only thing that opens Hide. Checked
7531
+ * against the CURRENT watch: a receipt that names an older one is from a
7532
+ * viewer that has since been replaced, and vouching for its successor is the
7533
+ * reconnect hole this counter exists to close.
7534
+ */
7535
+ frameSeen(runId: string, generation: number): void;
7536
+ handBack(runId: string): boolean;
7537
+ /**
7538
+ * One thing the person did to the page.
7539
+ *
7540
+ * Authorised per event, not per session. A takeover is not a channel that
7541
+ * stays open once opened: between two clicks the run can be stopped, the hold
7542
+ * can lapse, the page can close, and each of those has to stop the NEXT
7543
+ * event rather than be noticed at the end. So every event re-checks that this
7544
+ * run is live and that the lease is still the person's, and every accepted
7545
+ * event says so by extending the hold.
7546
+ */
7547
+ input(runId: string, event: BrowserInput): Promise<{
7548
+ ok: boolean;
7549
+ detail?: string;
7550
+ }>;
7551
+ /**
7552
+ * The page's own coordinate space, measured rather than inferred.
7553
+ *
7554
+ * Cached, because a click should not cost a round trip, and invalidated by
7555
+ * the frame metadata changing shape — which arrives free with every frame. A
7556
+ * failed measurement falls back to the last good one rather than to a guess:
7557
+ * a stale viewport is off by a resize, an invented one is off by anything.
7558
+ */
7559
+ private pageViewport;
7560
+ /**
7561
+ * Turn one authorised event into CDP calls. Coordinates arrive normalised.
7562
+ *
7563
+ * A switch with a default that REFUSES, rather than a chain of ifs ending in
7564
+ * the click branch. This is a mutation boundary reached from a socket, so the
7565
+ * question is not what a well-formed event does but what a malformed one
7566
+ * does: the chain answered "clicks", which meant a version-skewed client
7567
+ * sending `{ kind: 'drag', x, y }` pressed the mouse on a real page, and a
7568
+ * kind with no coordinates at all clamped to 0 and clicked the top-left
7569
+ * corner. Unknown input has to fail closed.
7570
+ *
7571
+ * The `never` in the default is the other half: adding a member to
7572
+ * `BrowserInput` without handling it here stops compiling, so the next kind
7573
+ * cannot silently inherit whatever the last branch happened to be.
7574
+ */
7575
+ private dispatch;
6998
7576
  /** The user stopped this run. Refuses further actions and clears its queue. */
6999
7577
  stopRun(runId: string): void;
7000
7578
  /** A run ended: release its browser rather than pay for an idle session. */
@@ -7032,6 +7610,25 @@ declare class RemoteBrowserController {
7032
7610
  private open;
7033
7611
  /** The part that may fail, with the pool slot already reserved. */
7034
7612
  private openReserved;
7613
+ /** Count main-frame navigation only while this page is the run's active page. */
7614
+ private trackPageNavigation;
7615
+ /**
7616
+ * Follow a page opened by the action that just finished.
7617
+ *
7618
+ * A popup/new tab is part of the same user gesture (OAuth is the common
7619
+ * example). Keeping `held.page` and the screencast on its opener makes the
7620
+ * new page both invisible and impossible to drive. This runs while the SAME
7621
+ * action slot is still held, after the opening click/key has fully landed, so
7622
+ * no later human/agent event can slip between detecting the popup and moving
7623
+ * the view to it.
7624
+ */
7625
+ private followOpenedPage;
7626
+ /**
7627
+ * Make one live page the run's controlled/viewed page. Used in both
7628
+ * directions: into a popup opened by an action, and back to another surviving
7629
+ * page when that popup closes itself after OAuth/login completion.
7630
+ */
7631
+ private adoptPage;
7035
7632
  private perform;
7036
7633
  }
7037
7634
 
@@ -7067,6 +7664,73 @@ declare class GenericCdpProvider implements RemoteBrowserProvider {
7067
7664
  */
7068
7665
  declare function isCdpEndpoint(url: string): boolean;
7069
7666
 
7667
+ /**
7668
+ * Whether this configuration will end up talking to Steel's hosted API.
7669
+ *
7670
+ * Lives HERE, next to the default it has to agree with, rather than in the
7671
+ * caller that needs the answer. The hosted endpoint requires a credential and a
7672
+ * self-hosted one usually does not, so something has to decide which a given
7673
+ * config is — and a server-side copy of that decision is a second definition of
7674
+ * "hosted" free to drift from the one that actually picks the URL.
7675
+ *
7676
+ * Several configs reach the hosted API and they do not look alike: no url at
7677
+ * all, an empty one (`opts.url || DEFAULT_BASE` takes the default for both),
7678
+ * the hosted host typed out in full, and the fully-qualified spelling with a
7679
+ * terminal dot. Asking about the RESOLVED destination answers all of them at
7680
+ * once, which asking about the presence of `url` cannot.
7681
+ *
7682
+ * Compared by host key, so a trailing slash, a port, a path, a different scheme,
7683
+ * a different case or a root dot cannot disguise it. A URL this cannot parse is
7684
+ * not the hosted API — and is refused earlier anyway, for being unparseable.
7685
+ */
7686
+ declare function isHostedSteel(url?: string): boolean;
7687
+ /**
7688
+ * Whether this can serve as the API base — asked of the URL ACTUALLY BUILT.
7689
+ *
7690
+ * Six rounds of review found six ways a base could look fine and not work:
7691
+ * a wrong host, a terminal dot, a query string, userinfo, a path prefix on the
7692
+ * hosted service, doubled trailing slashes. Each was answered by inspecting one
7693
+ * more component, and a seventh arrived that no component exposes — a bare `?`
7694
+ * or `#` leaves `search` and `hash` EMPTY while `href` keeps the delimiter, so
7695
+ * `${base}/v1/sessions` becomes `...?/v1/sessions` and the POST lands on `/`.
7696
+ *
7697
+ * So this stopped adding conditions and started asking the only question that
7698
+ * was ever the real one: build the request `call()` will build, and see whether
7699
+ * it addresses the endpoint. A spelling nobody has thought of yet fails this by
7700
+ * construction rather than by being enumerated.
7701
+ */
7702
+ declare function isUsableSteelBase(url: string): boolean;
7703
+ /**
7704
+ * Whether this credential can actually be SENT.
7705
+ *
7706
+ * The env schema trims, which removes surrounding whitespace and nothing else,
7707
+ * so a key pasted out of a wrapped terminal or a here-doc can carry an embedded
7708
+ * newline. It is truthy, so the hosted gate reads the deployment as
7709
+ * authenticated and `/api/config` advertises Browser mode — and then `fetch`
7710
+ * refuses the header before a byte is sent, leaving a control that cannot make
7711
+ * one call. The third member of the category `fetch` refuses before sending,
7712
+ * after userinfo and blocked ports.
7713
+ *
7714
+ * CONSTRUCTING A HEADER IS NOT SENDING ONE, and the first version of this asked
7715
+ * the wrong one of those two questions. It built a `Headers` and trusted the
7716
+ * result — but `Headers` (and `Request`) accept DEL and every C0 control except
7717
+ * CR, LF and NUL, while the SEND path refuses them, so a key containing `\x7f`
7718
+ * was advertised as usable and then died as `invalid steel-api-key header`
7719
+ * before DNS. The comment here stated that as verified fact; it had verified
7720
+ * the constructor, which is a different thing.
7721
+ *
7722
+ * So this asks the send path's own rule — RFC 9110's field value: horizontal
7723
+ * tab, printable ASCII, or a high byte — because no constructor asks it.
7724
+ * Confirmed by probing every byte from 0x00 to 0xff through `fetch` itself:
7725
+ * refused are 0x00-0x08, 0x0a-0x1f and 0x7f, and this predicate agrees with it
7726
+ * on all 256. The test pins that set rather than describing it.
7727
+ *
7728
+ * A scan rather than a regex: the equivalent character class is linear, but
7729
+ * this file already carries two loops written to avoid CodeQL's backtracking
7730
+ * finding, and one idiom is easier to keep true than two. Code points above
7731
+ * 0xff fail here and at `fetch`, which refuses them as un-ByteString-able.
7732
+ */
7733
+ declare function isUsableSteelKey(apiKey: string): boolean;
7070
7734
  declare class SteelProvider implements RemoteBrowserProvider {
7071
7735
  readonly name = "Steel";
7072
7736
  readonly isolatesSessions = true;
@@ -7089,7 +7753,7 @@ declare class SteelProvider implements RemoteBrowserProvider {
7089
7753
  * the capability was switched off is neither. Reporting all three as "busy"
7090
7754
  * had a model politely retrying something that would never be granted.
7091
7755
  */
7092
- type LeaseResult = 'granted' | 'busy' | 'cancelled' | 'off';
7756
+ type LeaseResult = 'granted' | 'busy' | 'cancelled' | 'off' | 'human';
7093
7757
  interface BrowserLeaseOptions {
7094
7758
  /**
7095
7759
  * Whether a run has been stopped. The lease skips revoked runs when handing
@@ -7118,10 +7782,65 @@ declare class BrowserLease {
7118
7782
  * took the slot may release it.
7119
7783
  */
7120
7784
  private action;
7785
+ /** Whether the current action slot represents activity by its holder. */
7786
+ private actionCountsAsHumanActivity;
7787
+ /**
7788
+ * The human action that has already consumed its one idle-deadline grace.
7789
+ * A single wedged CDP request must not look like fresh activity every two
7790
+ * minutes forever. The first deadline may yield to an event that began before
7791
+ * it; a second deadline for that SAME event becomes a pending lapse instead.
7792
+ */
7793
+ private idleGraceAction;
7794
+ /** Release the human hold as soon as this overlong action finally unwinds. */
7795
+ private idleLapseAfterAction;
7796
+ /**
7797
+ * Everyone waiting for that slot, IN THE ORDER THEY ASKED.
7798
+ *
7799
+ * A queue rather than a poll, and the ordering is the whole reason for it.
7800
+ * `acquireActionSlot` used to spin on a 25ms timer, which decides nothing
7801
+ * about who goes first: each waiter checks on its own phase, so the one that
7802
+ * asked SECOND can find the slot free a full poll ahead of the one that asked
7803
+ * first. The person types one event per keypress — `{ kind: 'text', text }`
7804
+ * carries a single character — so that reordering is not a theoretical
7805
+ * unfairness, it is "passowrd" appearing in the box.
7806
+ *
7807
+ * The slot is handed straight from `endAction` to the head of this line while
7808
+ * still synchronous, so nothing can slip in between the two, and a waiter
7809
+ * that reaches the front never has to be told the slot is already taken.
7810
+ */
7811
+ private actionQueue;
7812
+ /** Lapses a human hold nobody is using. See `HUMAN_IDLE_MS`. */
7813
+ private humanTimer;
7814
+ /**
7815
+ * The person ASKED for the hold to end and it could not end yet.
7816
+ *
7817
+ * Kept as a request rather than only a retry timer, because the retry shares
7818
+ * `humanTimer` with the idle deadline: an authorised event settling after the
7819
+ * request would otherwise call `touch()`, re-arm that timer for a full idle
7820
+ * interval, and quietly cancel a handback the person had already asked for.
7821
+ * "Give it back" is not undone by activity that was already under way when
7822
+ * they said it.
7823
+ *
7824
+ * Explicit requests ONLY. An idle expiry must never set this: a deadline that
7825
+ * lands on the person's own in-flight event is evidence they were THERE, and
7826
+ * recording it here made `touch()` bail afterwards, so the hold dropped the
7827
+ * instant a fresh interaction finished. See `lapse()`.
7828
+ */
7829
+ private handBackWanted;
7121
7830
  private isRevoked;
7122
7831
  private onChange;
7123
7832
  constructor(options?: BrowserLeaseOptions);
7124
7833
  get holderActor(): string | null;
7834
+ /** Whether a person, rather than the agent, is driving the page right now. */
7835
+ get heldByHuman(): boolean;
7836
+ /**
7837
+ * Whether THIS actor is the one holding the browser right now.
7838
+ *
7839
+ * For asking again after a wait. `acquire` is re-entrant for the holder, so
7840
+ * "I was granted the lease" is a fact about the moment it was granted and
7841
+ * says nothing about the moment the caller finally gets to act.
7842
+ */
7843
+ heldBy(actorId: string): boolean;
7125
7844
  get holderSession(): string | null;
7126
7845
  get queueDepth(): number;
7127
7846
  get actionInFlight(): boolean;
@@ -7160,6 +7879,57 @@ declare class BrowserLease {
7160
7879
  * refusal path runs a Stop may already have handed the lease on.
7161
7880
  */
7162
7881
  releaseIfHeldBy(actorId: string): void;
7882
+ /**
7883
+ * Give the browser to the person watching it.
7884
+ *
7885
+ * Callers must have waited the in-flight action out first — see
7886
+ * `acquireActionSlot`. This does not interrupt one, because it cannot: a
7887
+ * Playwright call already inside the page finishes whatever it started, and
7888
+ * taking the lease off it would only mean the person and a still-running
7889
+ * `fill` were both touching the same form. Waiting is what makes "you have
7890
+ * control" true when the UI says it.
7891
+ *
7892
+ * Every queued worker is turned away rather than left in line, for the same
7893
+ * reason `acquire` refuses: they were planned against a page the person is
7894
+ * about to change.
7895
+ */
7896
+ takeOver(sessionId: string): void;
7897
+ /**
7898
+ * The person is still there — anything they did counts.
7899
+ *
7900
+ * Called per authorised input rather than on a heartbeat, so the hold is
7901
+ * extended by USE and not merely by a tab being open. A left-open panel on a
7902
+ * forgotten laptop stops holding a billed session two minutes later.
7903
+ */
7904
+ touch(): void;
7905
+ /** (Re)start the idle interval. The only place the deadline is set. */
7906
+ private armIdle;
7907
+ /**
7908
+ * The idle deadline came round.
7909
+ *
7910
+ * Both ways a hold ends obey the same rule — never while one of the person's
7911
+ * own events is still landing — but they resolve that clash in OPPOSITE
7912
+ * directions, and routing the lapse through `handBack()` collapsed them into
7913
+ * one. An event in flight BEGAN before this deadline, which makes it evidence
7914
+ * the person was there; deferring the lapse and then releasing the moment
7915
+ * that event lands ends the hold immediately after a fresh interaction, which
7916
+ * is the precise opposite of what an idle deadline measures. So an
7917
+ * interrupted lapse yields: the interval starts again, and the next silence
7918
+ * decides.
7919
+ *
7920
+ * `handBack()` keeps the deferral instead, because "Give it back" is a
7921
+ * decision and activity already under way when it was made does not withdraw
7922
+ * it. Same rule, different answer, which is why they are different methods.
7923
+ */
7924
+ private lapse;
7925
+ /**
7926
+ * The person hands the browser back, or their hold lapses.
7927
+ *
7928
+ * Answers whether there was a human hold to end, so a caller can tell a real
7929
+ * handback from a duplicate click or a lapse that already happened.
7930
+ */
7931
+ handBack(): boolean;
7932
+ private clearHumanIdle;
7163
7933
  /** Drop every queued waiter belonging to a run, and the lease if it holds it. */
7164
7934
  dropRun(sessionId: string): void;
7165
7935
  /** Turn everyone away — the capability itself has gone. */
@@ -7169,19 +7939,67 @@ declare class BrowserLease {
7169
7939
  /** Arm the deadlock ceiling. Not a release schedule — see the constant. */
7170
7940
  armCeiling(): void;
7171
7941
  clearCeiling(): void;
7172
- /** Take the per-action slot, or null when one is already running. */
7173
- beginAction(): symbol | null;
7174
- /** Release the slot, but only for the action that took it. */
7942
+ /**
7943
+ * Take the per-action slot, or null when one is already running.
7944
+ *
7945
+ * PRIVATE, so the queue is the only way in. A caller that could take the slot
7946
+ * without joining the line would be exactly the jump-the-queue the ordering
7947
+ * is there to prevent.
7948
+ */
7949
+ private beginAction;
7950
+ /**
7951
+ * Release the slot, but only for the action that took it.
7952
+ *
7953
+ * Hands it to the head of the line rather than merely clearing it, and does
7954
+ * so SYNCHRONOUSLY: the waiter's `beginAction` runs before this call returns,
7955
+ * so there is no window in which the slot is free and somebody who asked
7956
+ * later can take it.
7957
+ */
7175
7958
  endAction(token: symbol): boolean;
7176
7959
  /**
7177
- * Wait, briefly, for an outgoing action to unwind.
7960
+ * Take the slot only if it is free RIGHT NOW, never joining the line.
7961
+ *
7962
+ * For input that is a SAMPLE rather than a decision — pointer movement. A
7963
+ * click or a keystroke is a thing the person meant to do and has to happen,
7964
+ * so it waits its turn; a pointer position is one of many on the way
7965
+ * somewhere, and one that cannot be applied now is worth less than the one
7966
+ * behind it. Queueing them is actively harmful: on an endpoint slower than
7967
+ * the client's sampling interval the backlog grows, and the click behind it
7968
+ * can age out against `acquireActionSlot`'s bound and be refused — movement
7969
+ * costing the person the action they actually intended.
7970
+ *
7971
+ * The queue length is checked as well as the slot, so a sample cannot step
7972
+ * over a click that is already waiting.
7973
+ */
7974
+ tryActionSlot(): symbol | null;
7975
+ /**
7976
+ * Take the per-action slot, waiting in line for it if somebody has it.
7178
7977
  *
7179
7978
  * A release can hand the browser on while the previous holder's abort is
7180
7979
  * still propagating through an awaited navigation. That is a handoff, not a
7181
- * conflict, so it is waited out rather than refused — but bounded, because a
7182
- * wedged action must not pin the queue.
7183
- */
7184
- awaitActionSlot(): Promise<boolean>;
7980
+ * conflict, so it is normally bounded: a wedged action must not pin an agent
7981
+ * or a takeover forever. Human discrete input is the exception and passes
7982
+ * `null`: once a click, key or committed text has been accepted into this
7983
+ * ordered queue, expiring it merely because earlier accepted input took more
7984
+ * than two seconds silently truncates what the person typed.
7985
+ *
7986
+ * Waiting and TAKING are one step, which they deliberately were not before:
7987
+ * the wait used to answer "the slot looks free" and leave the caller to call
7988
+ * `beginAction` afterwards. Two callers woken by the same release both saw it
7989
+ * free, and the loser was told "another action is already running" — a
7990
+ * refusal for having asked first. Granting it here means whoever reaches the
7991
+ * front of the line gets it, and everybody else is still waiting rather than
7992
+ * refused.
7993
+ *
7994
+ * `null` as a RESULT means a bounded wait ran out OR a caller-supplied queue
7995
+ * cap refused a new waiter. `null` as `withinMs` means an ACCEPTED operation
7996
+ * is intentionally unbounded by time and will leave the queue only when its
7997
+ * turn arrives; it does not require accepting infinitely many operations.
7998
+ * `countsAsHumanActivity=false` is for
7999
+ * lifecycle barriers: they still serialize, but an idle deadline must not
8000
+ * mistake framework cleanup for evidence that the person is present.
8001
+ */
8002
+ acquireActionSlot(withinMs?: number | null, countsAsHumanActivity?: boolean, maxQueued?: number | null): Promise<symbol | null>;
7185
8003
  /**
7186
8004
  * Queue one actor, resolving when it gets the browser or is turned away.
7187
8005
  *
@@ -8316,4 +9134,4 @@ declare function gatherSyncBundle(config: CascadeConfig): SyncBundle;
8316
9134
  /** Merge a pulled bundle into a config, returning the updated config to persist. */
8317
9135
  declare function applySyncBundle(bundle: SyncBundle, config: CascadeConfig, cleanup?: RetiredProviderCleanup): CascadeConfig;
8318
9136
 
8319
- 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, 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 };
9137
+ export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, type AcceptanceOptions, type AcceptanceProbe, type AcceptanceResult, type AcceptanceVerdict, type ApprovalRequest, type ApprovalResponse, AskUserTool, 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, type ClarificationAnswer, type ClarificationOutcome, type ClarificationQuestion, type ClarificationResult, 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, isHostedSteel, isMcpToolName, isUsableSteelBase, isUsableSteelKey, 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 };