@opengeni/runtime 0.14.1 → 0.14.15
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/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
- package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
- package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
- package/dist/chunk-GNEGANUF.js.map +1 -0
- package/dist/codex-tool-search.d.ts +100 -0
- package/dist/context-compaction.d.ts +236 -0
- package/dist/history-sanitizer.d.ts +320 -0
- package/dist/index.d.ts +186 -718
- package/dist/index.js +612 -126
- package/dist/index.js.map +1 -1
- package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
- package/dist/mcp-network.d.ts +32 -35
- package/dist/metrics.d.ts +33 -0
- package/dist/sandbox/capabilities.d.ts +11 -0
- package/dist/sandbox/channel-a.d.ts +226 -0
- package/dist/sandbox/display-stack.d.ts +99 -0
- package/dist/sandbox/errors.d.ts +16 -0
- package/dist/sandbox/exec-banner.d.ts +19 -0
- package/dist/sandbox/index.d.ts +190 -6
- package/dist/sandbox/index.js +20 -4
- package/dist/sandbox/op-correlation.d.ts +24 -0
- package/dist/sandbox/provider-errors.d.ts +32 -0
- package/dist/sandbox/provider-operation-gate.d.ts +12 -0
- package/dist/sandbox/providers/blaxel.d.ts +2 -0
- package/dist/sandbox/providers/cloudflare.d.ts +2 -0
- package/dist/sandbox/providers/daytona.d.ts +2 -0
- package/dist/sandbox/providers/docker.d.ts +2 -0
- package/dist/sandbox/providers/e2b.d.ts +2 -0
- package/dist/sandbox/providers/index.d.ts +11 -0
- package/dist/sandbox/providers/local.d.ts +2 -0
- package/dist/sandbox/providers/modal.d.ts +132 -0
- package/dist/sandbox/providers/none.d.ts +2 -0
- package/dist/sandbox/providers/runloop.d.ts +2 -0
- package/dist/sandbox/providers/selfhosted.d.ts +2 -0
- package/dist/sandbox/providers/types.d.ts +30 -0
- package/dist/sandbox/providers/vercel.d.ts +2 -0
- package/dist/sandbox/recording.d.ts +80 -0
- package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
- package/dist/sandbox/routing/routing-session.d.ts +401 -0
- package/dist/sandbox/run-credentials.d.ts +65 -0
- package/dist/sandbox/select.d.ts +120 -0
- package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
- package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
- package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
- package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
- package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
- package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
- package/dist/sandbox/selfhosted/session.d.ts +552 -0
- package/dist/sandbox/selfhosted/testing.d.ts +48 -0
- package/dist/sandbox/stream-port.d.ts +80 -0
- package/dist/sandbox/stream-token.d.ts +44 -0
- package/dist/sandbox/terminal-server.d.ts +67 -0
- package/dist/sandbox/toolspace-token.d.ts +23 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
- package/dist/sandbox/workspace-archive.d.ts +39 -0
- package/dist/sandbox-computer.d.ts +227 -0
- package/dist/screenshot-error-card.d.ts +1 -0
- package/dist/skill-library.d.ts +10 -12
- package/dist/usage-telemetry.d.ts +45 -0
- package/dist/workspace-skills.d.ts +31 -0
- package/package.json +7 -7
- package/src/context-compaction.ts +600 -6
- package/src/history-sanitizer.ts +10 -92
- package/src/index.ts +301 -62
- package/src/sandbox/channel-a.ts +267 -81
- package/src/sandbox/index.ts +56 -94
- package/src/sandbox/provider-operation-gate.ts +110 -0
- package/src/sandbox/providers/docker.ts +13 -1
- package/src/sandbox/providers/modal.ts +433 -3
- package/src/sandbox/routing/routing-session.ts +84 -8
- package/src/sandbox/selfhosted/session.ts +7 -6
- package/src/sandbox/turn-tool-cancellation.ts +47 -7
- package/src/sandbox/workspace-archive.ts +176 -73
- package/src/sandbox-computer.ts +25 -75
- package/dist/chunk-HHWIZHQM.js.map +0 -1
- package/dist/index-BraazrAO.d.ts +0 -2931
- /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { RunCredentialAuthNeeded, RunCredentialFile, RunCredentialRedaction, RunCredentialsResolution } from "@opengeni/contracts";
|
|
2
|
+
import type { ExecCommandArgs, SandboxClient, SandboxSessionLike } from "@openai/agents/sandbox";
|
|
3
|
+
export type RunCredentialExpectedScope = {
|
|
4
|
+
accountId: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
};
|
|
8
|
+
export type NormalizedRunCredentialMaterial = {
|
|
9
|
+
environment: Record<string, string>;
|
|
10
|
+
files: RunCredentialFile[];
|
|
11
|
+
fileEnvironment: Record<string, string>;
|
|
12
|
+
expiresAt: Date | null;
|
|
13
|
+
authNeeded: RunCredentialAuthNeeded[];
|
|
14
|
+
redactions: RunCredentialRedaction[];
|
|
15
|
+
};
|
|
16
|
+
export type RunCredentialCommandSession = Pick<SandboxSessionLike, "exec" | "execCommand">;
|
|
17
|
+
export type RunCredentialCommandRunner = (session: RunCredentialCommandSession, args: ExecCommandArgs) => Promise<unknown>;
|
|
18
|
+
export type RunCredentialSessionReady = (session: RunCredentialCommandSession) => Promise<void> | void;
|
|
19
|
+
export declare class RunCredentialValidationError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate the untrusted host-port response before any secret is written to a
|
|
24
|
+
* box. The host owns credential selection; OpenGeni owns scope checks, bounds,
|
|
25
|
+
* path safety, and the transport lifecycle.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeRunCredentialsResolution(resolution: Extract<RunCredentialsResolution, {
|
|
28
|
+
status: "not_applicable";
|
|
29
|
+
}>, expected: RunCredentialExpectedScope, now?: Date): null;
|
|
30
|
+
export declare function normalizeRunCredentialsResolution(resolution: Exclude<RunCredentialsResolution, {
|
|
31
|
+
status: "not_applicable";
|
|
32
|
+
}>, expected: RunCredentialExpectedScope, now?: Date): NormalizedRunCredentialMaterial;
|
|
33
|
+
export declare function normalizeRunCredentialsResolution(resolution: RunCredentialsResolution, expected: RunCredentialExpectedScope, now?: Date): NormalizedRunCredentialMaterial | null;
|
|
34
|
+
export declare function runCredentialRoot(sessionId: string): string;
|
|
35
|
+
export declare function runCredentialPointerFile(sessionId: string): string;
|
|
36
|
+
export type MaterializeRunCredentialsOptions = {
|
|
37
|
+
sessionId: string;
|
|
38
|
+
attemptId: string;
|
|
39
|
+
executionGeneration: number;
|
|
40
|
+
/** Initial provision after recovery may prune generations from older attempts. */
|
|
41
|
+
pruneOtherAttempts?: boolean;
|
|
42
|
+
/** An all-auth-needed replacement removes every prior readable generation. */
|
|
43
|
+
prunePreviousGenerations?: boolean;
|
|
44
|
+
/** Renewal keeps the active and immediately prior same-attempt generations. */
|
|
45
|
+
pruneSupersededGenerations?: boolean;
|
|
46
|
+
commandRunner?: RunCredentialCommandRunner;
|
|
47
|
+
};
|
|
48
|
+
/** Materialize one complete immutable generation, then atomically flip current. */
|
|
49
|
+
export declare function materializeRunCredentials(session: RunCredentialCommandSession, material: NormalizedRunCredentialMaterial, options: MaterializeRunCredentialsOptions): Promise<void>;
|
|
50
|
+
export declare function clearRunCredentials(session: RunCredentialCommandSession, sessionId: string, commandRunner?: RunCredentialCommandRunner): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove only generations owned by one attempt. A successor may already have
|
|
53
|
+
* atomically activated its own pointer after queue admission; this cleanup must
|
|
54
|
+
* never clear or overwrite that newer generation.
|
|
55
|
+
*/
|
|
56
|
+
export declare function clearRunCredentialsForAttempt(session: RunCredentialCommandSession, options: Pick<MaterializeRunCredentialsOptions, "sessionId" | "attemptId" | "executionGeneration">, commandRunner?: RunCredentialCommandRunner): Promise<void>;
|
|
57
|
+
/** Prefix one sandbox command so every new process observes the active generation. */
|
|
58
|
+
export declare function withRunCredentialEnvironment(cmd: string, sessionId: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Preserve the provider session identity/capabilities while decorating only
|
|
61
|
+
* command creation. Non-command methods stay bound to the original instance.
|
|
62
|
+
*/
|
|
63
|
+
export declare function withRunCredentialsSession<T extends object>(session: T, sessionId: string): T;
|
|
64
|
+
/** Decorate every client-created/resumed session after the host seeds it. */
|
|
65
|
+
export declare function withRunCredentialsClient(client: SandboxClient, sessionId: string, onSessionReady?: RunCredentialSessionReady): SandboxClient;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type CapabilityDescriptor, type SandboxBackend, type SandboxOs, type SessionCapabilities } from "@opengeni/contracts";
|
|
2
|
+
export interface NegotiationContext {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
backend: SandboxBackend;
|
|
5
|
+
os: SandboxOs;
|
|
6
|
+
/** Current lease liveness; cold means nothing is provisioned yet. */
|
|
7
|
+
liveness: "cold" | "warming" | "warm" | "draining";
|
|
8
|
+
/** The lease epoch echoed on viewer heartbeats (the split-brain fence). */
|
|
9
|
+
leaseEpoch: number;
|
|
10
|
+
/** Numeric/boolean archive completeness truth. Non-persistable targets use
|
|
11
|
+
* null/null/false; archive locations and provider details are never exposed. */
|
|
12
|
+
workspaceGeneration?: number | null;
|
|
13
|
+
archiveGeneration?: number | null;
|
|
14
|
+
archiveComplete?: boolean;
|
|
15
|
+
/** The deployment desktop toggle (settings.sandboxDesktopEnabled). */
|
|
16
|
+
desktopEnabled: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The HUMAN take-control toggle (settings.sandboxDesktopInteractive). When true
|
|
19
|
+
* (default) and the desktop cell is available, the negotiated DesktopStream.mode
|
|
20
|
+
* is "interactive" — the noVNC viewer can drive mouse+keyboard into :0 (the box's
|
|
21
|
+
* x11vnc runs without -viewonly). When false the cell reports mode "read-only"
|
|
22
|
+
* and the client disables the "Take control" affordance (a genuinely read-only
|
|
23
|
+
* deployment). Independent of `computerUseReadOnly`, which gates the AGENT
|
|
24
|
+
* driver, not the human viewer plane. Defaults to true so a caller that never
|
|
25
|
+
* threads it (e.g. headless tests) still gets the interactive plane when the
|
|
26
|
+
* desktop is available.
|
|
27
|
+
*/
|
|
28
|
+
desktopInteractive?: boolean;
|
|
29
|
+
/** The deployment computer-use toggle (settings.computerUseEnabled). The agent
|
|
30
|
+
* drives :0 via xdotool/scrot; availability tracks desktop. Defaults to true. */
|
|
31
|
+
computerUseEnabled?: boolean;
|
|
32
|
+
/** Whether the agent computer-use driver is gated to no-op input
|
|
33
|
+
* (settings.computerUseReadOnly). v1 default false (the agent clicks/types). */
|
|
34
|
+
computerUseReadOnly?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether a scoped-stream-token secret is resolvable (stream-token availability contract). When desktop
|
|
37
|
+
* is enabled but this is false (no streamTokenSecret AND no delegationSecret),
|
|
38
|
+
* the desktop plane GRACEFULLY DEGRADES to transport:null — the deployment
|
|
39
|
+
* boots, but the pixel plane cannot mint scoped tokens. Defaults to true so a
|
|
40
|
+
* caller that never threads it (e.g. headless tests) is unaffected.
|
|
41
|
+
*/
|
|
42
|
+
streamTokenSecretAvailable?: boolean;
|
|
43
|
+
/** Whether the calling principal has acknowledged the un-redacted desktop
|
|
44
|
+
* pixels (and, for a shared box, the shared-exposure disclosure). When the
|
|
45
|
+
* box is shared this must be the SHARED acknowledgment; a bare un-redacted ack
|
|
46
|
+
* does not satisfy a shared box. */
|
|
47
|
+
desktopAcknowledged?: boolean;
|
|
48
|
+
/** True when the box's group has >1 session: watching this desktop also shows
|
|
49
|
+
* the sibling sessions' agents on the one :0 framebuffer (addendum E.1). */
|
|
50
|
+
shared?: boolean;
|
|
51
|
+
/** The OTHER sessions whose agents may appear on the shared desktop — IDS
|
|
52
|
+
* ONLY, never their conversation/metadata (stress g). Empty for a solo box. */
|
|
53
|
+
sharedSessionIds?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* The minted pixel-plane endpoint (P4.2): the direct-to-provider WS URL + the
|
|
56
|
+
* scoped stream token + its expiry + the framebuffer geometry. Threaded by the
|
|
57
|
+
* API-direct handshake AFTER it has resumed the box, ensured the display stack,
|
|
58
|
+
* and resolved the provider tunnel. When ABSENT (the negotiation-only read, a
|
|
59
|
+
* cold lease, or a degraded desktop) the DesktopStream cell reports url/token/
|
|
60
|
+
* expiresAt as null — the capability is advertised, the live address is not yet
|
|
61
|
+
* minted (the caller POSTs to /viewers to mint it). Presence does NOT override
|
|
62
|
+
* the gates: a degraded/cold/unacked desktop still reports transport:null and
|
|
63
|
+
* the minted endpoint is dropped.
|
|
64
|
+
*/
|
|
65
|
+
desktopStream?: {
|
|
66
|
+
url: string;
|
|
67
|
+
token: string;
|
|
68
|
+
expiresAt: string;
|
|
69
|
+
resolution: [number, number];
|
|
70
|
+
};
|
|
71
|
+
/** The deployment terminal toggle (settings.sandboxTerminalEnabled). The REAL
|
|
72
|
+
* PTY (ttyd pty-ws) is gated on this + a real-PTY backend; when off the
|
|
73
|
+
* Terminal cell still advertises the read-only sse-events firehose. Defaults to
|
|
74
|
+
* true so a caller that never threads it is unaffected. */
|
|
75
|
+
terminalEnabled?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The minted terminal-plane endpoint (P5.t): the direct-to-provider ttyd
|
|
78
|
+
* PTY-over-websocket URL + the scoped stream token + its expiry. Threaded by the
|
|
79
|
+
* API-direct handshake AFTER it has resumed the box, ensured the terminal
|
|
80
|
+
* server, and resolved the provider tunnel (mintTerminalStream) — SYMMETRIC with
|
|
81
|
+
* `desktopStream`. When ABSENT (the negotiation-only read, a cold lease, or a
|
|
82
|
+
* degraded terminal) the Terminal cell reports url/token/expiresAt as null and
|
|
83
|
+
* falls back to transport "sse-events" (the read-only firehose) — the caller
|
|
84
|
+
* POSTs to /viewers to mint the live pty-ws address.
|
|
85
|
+
*/
|
|
86
|
+
terminalStream?: {
|
|
87
|
+
url: string;
|
|
88
|
+
token: string;
|
|
89
|
+
expiresAt: string;
|
|
90
|
+
};
|
|
91
|
+
/** Override the negotiation clock (tests). */
|
|
92
|
+
now?: Date;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the descriptor for a backend. Throws on an unknown backend rather than
|
|
96
|
+
* returning a half-formed default (the registry is the single source of truth).
|
|
97
|
+
*/
|
|
98
|
+
export declare function selectBackend(backend: SandboxBackend): CapabilityDescriptor;
|
|
99
|
+
/** True iff the descriptor lists the requested OS as supported. */
|
|
100
|
+
export declare function backendSupportsOs(descriptor: CapabilityDescriptor, os: SandboxOs): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* True iff the backend can serve the Channel-B desktop pixel plane at all — i.e.
|
|
103
|
+
* its static descriptor advertises DesktopStream as available. The gate the
|
|
104
|
+
* worker / API use before launching the display stack (so a headless-only
|
|
105
|
+
* backend like cloudflare/vercel/none never tries). This is the STATIC
|
|
106
|
+
* feasibility only; the runtime `sandboxDesktopEnabled` policy toggle and the
|
|
107
|
+
* stream-token-secret gate are layered on by the caller / negotiateCapabilities.
|
|
108
|
+
*
|
|
109
|
+
* Accepts EITHER the SandboxBackend enum value (e.g. "local") OR the SDK
|
|
110
|
+
* client backendId (e.g. "unix_local") — they diverge for the local backend —
|
|
111
|
+
* so a caller holding only `established.backendId` resolves correctly.
|
|
112
|
+
*/
|
|
113
|
+
export declare function desktopCapableBackend(backend: SandboxBackend | string): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Negotiate a coherent SessionCapabilities document for (backend, os). Every
|
|
116
|
+
* capability is reported with availability + a reason-when-unavailable; nothing
|
|
117
|
+
* is ever absent. The reason precedence is: os_unsupported (the OS axis can't be
|
|
118
|
+
* served at all) > the per-capability static feasibility > policy/liveness gates.
|
|
119
|
+
*/
|
|
120
|
+
export declare function negotiateCapabilities(ctx: NegotiationContext): SessionCapabilities;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { SandboxOs, SessionCapabilities } from "@opengeni/contracts";
|
|
2
|
+
import type { SelfhostedSession } from "./session";
|
|
3
|
+
/**
|
|
4
|
+
* The structural slice of the M2 `@opengeni/db` `EnrollmentRecord` the selfhosted
|
|
5
|
+
* negotiation reads. Defined STRUCTURALLY (not imported from `@opengeni/db`) so
|
|
6
|
+
* the agent-loop-free sandbox leaf does not couple to the DB package's graph —
|
|
7
|
+
* the API/worker pass an `EnrollmentRecord`, which satisfies this shape. The
|
|
8
|
+
* fields: `status` (active gates reachability), `exposure` +
|
|
9
|
+
* `allowScreenControl` (whole-machine + screen-control consent),`hasDisplay`
|
|
10
|
+
* (the display plane), `lastSeenAt` (the reconnecting-window disambiguator).
|
|
11
|
+
*/
|
|
12
|
+
export interface SelfhostedEnrollment {
|
|
13
|
+
status: string;
|
|
14
|
+
exposure: string;
|
|
15
|
+
allowScreenControl: boolean;
|
|
16
|
+
hasDisplay: boolean;
|
|
17
|
+
lastSeenAt: string | null;
|
|
18
|
+
/** An un-cleared clean going-offline marker (the machine announced a typed
|
|
19
|
+
* GoingOffline). When set, the derivation reads the machine OFFLINE immediately,
|
|
20
|
+
* regardless of a still-fresh `lastSeenAt`. NULL ⇒ no pending goodbye. */
|
|
21
|
+
wentOfflineAt: string | null;
|
|
22
|
+
/** The typed reason string of the pending goodbye (rides alongside
|
|
23
|
+
* `wentOfflineAt`; NULL when there is no un-cleared marker). */
|
|
24
|
+
wentOfflineReason: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** The derived liveness state of a selfhosted machine (the online/offline/
|
|
27
|
+
* reconnecting/consent/display matrix). */
|
|
28
|
+
export interface SelfhostedLivenessState {
|
|
29
|
+
/** The dominant machine state. */
|
|
30
|
+
state: "online" | "reconnecting" | "offline";
|
|
31
|
+
/** Whole-machine + screen-control consent acknowledged (gates desktop input). */
|
|
32
|
+
consented: boolean;
|
|
33
|
+
/** A display (real or Xvfb) is present (gates the desktop pixel plane). */
|
|
34
|
+
hasDisplay: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The window after `lastSeenAt` within which a missed liveness probe is read as a
|
|
38
|
+
* transient BLIP (`reconnecting`) rather than a hard `offline`. Mirrors the
|
|
39
|
+
* resiliency model (§10.6: reconnecting after 1 missed window, offline after
|
|
40
|
+
* ~30s). A probe miss with a lastSeenAt inside this window → reconnecting.
|
|
41
|
+
*/
|
|
42
|
+
export declare const SELFHOSTED_RECONNECT_WINDOW_MS = 30000;
|
|
43
|
+
/**
|
|
44
|
+
* Derive the selfhosted liveness state from the enrollment row + a liveness probe
|
|
45
|
+
* outcome. The probe is the authoritative "is the agent answering NOW" signal;
|
|
46
|
+
* `lastSeenAt` disambiguates a probe-miss into reconnecting (recent) vs offline
|
|
47
|
+
* (stale / never seen).
|
|
48
|
+
*
|
|
49
|
+
* - no enrollment / revoked → offline (the machine isn't enrolled).
|
|
50
|
+
* - un-cleared clean goodbye → offline (an announced GoingOffline beats
|
|
51
|
+
* last_seen aging AND a lingering probe).
|
|
52
|
+
* - probe responded → online.
|
|
53
|
+
* - probe missed, lastSeenAt recent → reconnecting (a transient blip).
|
|
54
|
+
* - probe missed, lastSeenAt stale → offline.
|
|
55
|
+
*/
|
|
56
|
+
export declare function selfhostedLiveness(input: {
|
|
57
|
+
enrollment: SelfhostedEnrollment | null;
|
|
58
|
+
/** The ControlRpc Ping outcome: true iff a responder answered. */
|
|
59
|
+
probeResponded: boolean;
|
|
60
|
+
/** Override the clock (tests). */
|
|
61
|
+
now?: Date;
|
|
62
|
+
}): SelfhostedLivenessState;
|
|
63
|
+
export interface SelfhostedNegotiationInput {
|
|
64
|
+
sessionId: string;
|
|
65
|
+
os?: SandboxOs;
|
|
66
|
+
leaseEpoch: number;
|
|
67
|
+
/** The M2 enrollment row for the machine (null → never enrolled → offline). */
|
|
68
|
+
enrollment: SelfhostedEnrollment | null;
|
|
69
|
+
/** A live liveness probe — typically `session.ping()`. When a session is
|
|
70
|
+
* provided this is called; otherwise pass `probeResponded` explicitly. */
|
|
71
|
+
session?: Pick<SelfhostedSession, "ping">;
|
|
72
|
+
/** Explicit probe outcome (when no session is given, e.g. a pure read). */
|
|
73
|
+
probeResponded?: boolean;
|
|
74
|
+
/** The deployment desktop/terminal/computer-use policy toggles (threaded
|
|
75
|
+
* through to the base negotiation). */
|
|
76
|
+
desktopEnabled?: boolean;
|
|
77
|
+
terminalEnabled?: boolean;
|
|
78
|
+
computerUseEnabled?: boolean;
|
|
79
|
+
/** Whether the calling principal acknowledged the un-redacted desktop. */
|
|
80
|
+
desktopAcknowledged?: boolean;
|
|
81
|
+
shared?: boolean;
|
|
82
|
+
sharedSessionIds?: string[];
|
|
83
|
+
/** Override the clock (tests). */
|
|
84
|
+
now?: Date;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Negotiate the full `SessionCapabilities` document for a selfhosted machine,
|
|
88
|
+
* with the online/offline/reconnecting/consent_required/display_unavailable cells
|
|
89
|
+
* correctly decided. Async because it issues the liveness probe.
|
|
90
|
+
*/
|
|
91
|
+
export declare function negotiateSelfhostedCapabilities(input: SelfhostedNegotiationInput): Promise<SessionCapabilities>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { AgentError, ControlRequest, ControlResponse, ErrorCode } from "@opengeni/agent-proto";
|
|
2
|
+
import type { CapabilityUnavailableReason } from "@opengeni/contracts";
|
|
3
|
+
export type SelfhostedUnavailableReason = Extract<CapabilityUnavailableReason, "agent_offline" | "agent_reconnecting" | "consent_required" | "display_unavailable">;
|
|
4
|
+
/**
|
|
5
|
+
* The selfhosted control-plane transport seam. ONE method: `request` — send a
|
|
6
|
+
* `ControlRequest` to the agent addressed by subject and await its
|
|
7
|
+
* `ControlResponse`. The subject is `subjectFor(workspaceId, agentId)`.
|
|
8
|
+
*
|
|
9
|
+
* The CONTRACT every implementor MUST honour (the M3 ruling): a
|
|
10
|
+
* no-responder / request-timeout is NOT an exception that means "not found" — it
|
|
11
|
+
* is surfaced as a `ControlResponse` carrying an `AgentError` with code
|
|
12
|
+
* `AGENT_OFFLINE` (no responder at all) or, when the caller can distinguish a
|
|
13
|
+
* transient blip, `TIMEOUT` (→ `agent_reconnecting`). The session maps these to
|
|
14
|
+
* the runtime error taxonomy; it NEVER lets agent-offline look like a provider
|
|
15
|
+
* NotFound (which would cold-create a rival box for a user's real machine).
|
|
16
|
+
*/
|
|
17
|
+
export interface ControlRpc {
|
|
18
|
+
request(subject: string, req: ControlRequest, opts: {
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
}): Promise<ControlResponse>;
|
|
21
|
+
}
|
|
22
|
+
/** The control-plane RPC subject for an enrolled agent — its subscription IS the
|
|
23
|
+
* registry (the binding two-plane decision). */
|
|
24
|
+
export declare function subjectFor(workspaceId: string, agentId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* The runtime-level error a `SelfhostedSession` op throws when the agent returns
|
|
27
|
+
* an `AgentError` (or no responder / timeout maps to one). It carries:
|
|
28
|
+
* - `code` — the wire `ErrorCode` (single-source-of-truth);
|
|
29
|
+
* - `reason` — the negotiated `CapabilityUnavailableReason` the capability /
|
|
30
|
+
* liveness surface uses (`agent_offline` / `agent_reconnecting`
|
|
31
|
+
* / `consent_required`), or null for op-level errors
|
|
32
|
+
* (OS/NOT_FOUND/UNSUPPORTED/STREAM/PROTOCOL) that are not a
|
|
33
|
+
* machine-liveness condition;
|
|
34
|
+
* - `retryable`— whether the caller should re-resolve + retry (DRAINING /
|
|
35
|
+
* FENCED / a reconnecting blip);
|
|
36
|
+
* - `notFound` — ALWAYS the provider-NotFound discriminator value: for
|
|
37
|
+
* selfhosted this is true ONLY for an OS-level NOT_FOUND of a
|
|
38
|
+
* path/ref (a real "the file does not exist"), and is FALSE for
|
|
39
|
+
* AGENT_OFFLINE (the machine isn't recreatable — never let the
|
|
40
|
+
* lease cold-create a rival). `isProviderSandboxNotFoundError`
|
|
41
|
+
* reads this.
|
|
42
|
+
*/
|
|
43
|
+
export declare class SelfhostedControlError extends Error {
|
|
44
|
+
readonly name = "SelfhostedControlError";
|
|
45
|
+
readonly code: ErrorCode;
|
|
46
|
+
readonly reason: SelfhostedUnavailableReason | null;
|
|
47
|
+
readonly retryable: boolean;
|
|
48
|
+
readonly fenced: boolean;
|
|
49
|
+
readonly draining: boolean;
|
|
50
|
+
readonly agentOffline: boolean;
|
|
51
|
+
readonly osNotFound: boolean;
|
|
52
|
+
/** The reply exceeded the transport's max payload (a distinguishing flag so the
|
|
53
|
+
* fault renderer can name the size wall + recovery instead of a generic OS error). */
|
|
54
|
+
readonly payloadTooLarge: boolean;
|
|
55
|
+
/** The transport synthesized this fault PRE-SEND — it KNOWS the request never
|
|
56
|
+
* reached the machine (no connection, or no responder on the subject), so the op
|
|
57
|
+
* provably did not execute. Only such a fault is safe to re-issue for ANY op kind
|
|
58
|
+
* (a post-send timeout is ambiguous — at-least-once — and must not be re-sent
|
|
59
|
+
* unless the op is read-only). Never set for an agent-returned error. */
|
|
60
|
+
readonly neverSent: boolean;
|
|
61
|
+
readonly detail: Record<string, string>;
|
|
62
|
+
constructor(input: {
|
|
63
|
+
message: string;
|
|
64
|
+
code: ErrorCode;
|
|
65
|
+
reason: SelfhostedUnavailableReason | null;
|
|
66
|
+
retryable: boolean;
|
|
67
|
+
fenced?: boolean;
|
|
68
|
+
draining?: boolean;
|
|
69
|
+
agentOffline?: boolean;
|
|
70
|
+
osNotFound?: boolean;
|
|
71
|
+
payloadTooLarge?: boolean;
|
|
72
|
+
neverSent?: boolean;
|
|
73
|
+
detail?: Record<string, string>;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Map an `AgentError` (from a `ControlResponse`) to the runtime
|
|
78
|
+
* `SelfhostedControlError`. THE load-bearing mapping (the M3 ruling):
|
|
79
|
+
* - AGENT_OFFLINE → reason `agent_offline`, agentOffline=true,
|
|
80
|
+
* osNotFound=FALSE (NEVER a provider NotFound).
|
|
81
|
+
* - TIMEOUT (a transient missed-window / no-responder blip the caller marked
|
|
82
|
+
* retryable) → reason `agent_reconnecting`.
|
|
83
|
+
* - CONSENT_REQUIRED → reason `consent_required`.
|
|
84
|
+
* - DRAINING → no capability reason; retryable (turn pauses + retries).
|
|
85
|
+
* - FENCED → no capability reason; retryable (the existing
|
|
86
|
+
* epoch-fence retry; the caller re-resolves + retries).
|
|
87
|
+
* - NOT_FOUND → an OS-level path/ref NotFound — osNotFound=true (a
|
|
88
|
+
* real "file does not exist"), no machine-liveness
|
|
89
|
+
* reason. (This is the ONLY NotFound; it is NOT the
|
|
90
|
+
* box-gone NotFound that licenses a cold restore.)
|
|
91
|
+
* - PAYLOAD_TOO_LARGE → the reply exceeded the transport's max payload;
|
|
92
|
+
* non-retryable, with actionable copy (the sizes + a
|
|
93
|
+
* redirect-to-file workaround).
|
|
94
|
+
* - OS / UNSUPPORTED / STREAM / PROTOCOL / UNSPECIFIED → op-level error, no
|
|
95
|
+
* reason, non-retryable.
|
|
96
|
+
*/
|
|
97
|
+
export declare function agentErrorToControlError(err: AgentError): SelfhostedControlError;
|
|
98
|
+
/**
|
|
99
|
+
* PAYLOAD_TOO_LARGE copy: state the actual sizes (from the agent's `detail` map —
|
|
100
|
+
* `encoded_bytes` / `max_payload`) and tell the caller how to get the data anyway:
|
|
101
|
+
* redirect the output to a file and read it back in ranges/chunks.
|
|
102
|
+
*/
|
|
103
|
+
export declare function payloadTooLargeMessage(detail: Record<string, string>): string;
|
|
104
|
+
/**
|
|
105
|
+
* DRAINING copy: the machine is at its concurrent-work capacity. `retries` is the
|
|
106
|
+
* number of times `SelfhostedSession.call` already re-tried before giving up (0 at
|
|
107
|
+
* the mapping layer, the final count when it surfaces after exhausting retries).
|
|
108
|
+
*/
|
|
109
|
+
export declare function drainingMessage(retries: number): string;
|
|
110
|
+
/** Rebuild a DRAINING error with the final retry count folded into its message —
|
|
111
|
+
* used by `SelfhostedSession.call` when the bounded DRAINING retries are exhausted. */
|
|
112
|
+
export declare function drainingExhaustedError(base: SelfhostedControlError, retries: number): SelfhostedControlError;
|
|
113
|
+
/**
|
|
114
|
+
* exec-deadline copy: the command was terminated at the exec time limit. Advise
|
|
115
|
+
* running long jobs in the background and polling their output rather than blocking
|
|
116
|
+
* the turn on one long command. Surfaced on a timed-out exec result's stderr.
|
|
117
|
+
*/
|
|
118
|
+
export declare function execDeadlineHint(seconds: number): string;
|
|
119
|
+
/** The reserved `detail` key the transport sets to "1" when it KNOWS a synthesized
|
|
120
|
+
* AGENT_OFFLINE fault occurred pre-send (no connection / no responder) — so the op
|
|
121
|
+
* provably never reached the machine. `agentErrorToControlError` reads it into
|
|
122
|
+
* `SelfhostedControlError.neverSent`. Reserved (never sent by the agent). */
|
|
123
|
+
export declare const NEVER_SENT_DETAIL_KEY = "_never_sent";
|
|
124
|
+
/** Build a synthesized AGENT_OFFLINE `AgentError` — the control plane uses this
|
|
125
|
+
* when no agent responds on the subject at all. `neverSent` marks that the request
|
|
126
|
+
* provably never reached the machine (no connection / no responder), which makes it
|
|
127
|
+
* safe to re-issue for ANY op kind; leave it false for an AMBIGUOUS transport error
|
|
128
|
+
* (a connection torn mid-request may have delivered the op). */
|
|
129
|
+
export declare function offlineAgentError(message?: string, neverSent?: boolean): AgentError;
|
|
130
|
+
/** Build a synthesized TIMEOUT `AgentError` — the control plane uses this when a
|
|
131
|
+
* responder existed but the request timed out (a transient blip → reconnecting). */
|
|
132
|
+
export declare function timeoutAgentError(message?: string): AgentError;
|
|
133
|
+
/**
|
|
134
|
+
* The minimal NATS request/reply surface `NatsControlRpc` needs. It mirrors the
|
|
135
|
+
* `nats` `NatsConnection.request` signature WITHOUT importing `nats` into the
|
|
136
|
+
* agent-loop-free runtime leaf: the API/worker injects the live connection (the
|
|
137
|
+
* SAME `@opengeni/events` bus connection). A factory may return `null` when NATS
|
|
138
|
+
* is not configured (boot must not require a live NATS) — `NatsControlRpc` then
|
|
139
|
+
* surfaces `agent_offline` for every request rather than throwing.
|
|
140
|
+
*/
|
|
141
|
+
export interface NatsRequestConnection {
|
|
142
|
+
request(subject: string, payload: Uint8Array, opts: {
|
|
143
|
+
timeout: number;
|
|
144
|
+
}): Promise<{
|
|
145
|
+
data: Uint8Array;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* A thin `ControlRpc` over a NATS request/reply connection. Constructed with a
|
|
150
|
+
* LAZY factory: the connection is resolved on first `request` (so boot never
|
|
151
|
+
* requires a live NATS). A null factory result, a no-responder error, or a
|
|
152
|
+
* request timeout each yield a `ControlResponse` carrying a synthesized
|
|
153
|
+
* `AgentError` (AGENT_OFFLINE / TIMEOUT) — NEVER a thrown transport error and
|
|
154
|
+
* NEVER a NotFound.
|
|
155
|
+
*
|
|
156
|
+
* The factory is async and memoized; it may itself dial the bus. M4 replaces the
|
|
157
|
+
* factory's body with the Accounts-scoped, hardened connection — this class's
|
|
158
|
+
* shape does not change.
|
|
159
|
+
*/
|
|
160
|
+
export declare class NatsControlRpc implements ControlRpc {
|
|
161
|
+
private readonly connect;
|
|
162
|
+
private connection;
|
|
163
|
+
private connecting;
|
|
164
|
+
constructor(connect: () => Promise<NatsRequestConnection | null>);
|
|
165
|
+
private resolveConnection;
|
|
166
|
+
request(subject: string, req: ControlRequest, opts: {
|
|
167
|
+
timeoutMs: number;
|
|
168
|
+
}): Promise<ControlResponse>;
|
|
169
|
+
}
|
|
170
|
+
/** A `ControlResponse` carrying a synthesized AGENT_OFFLINE error. `neverSent` marks
|
|
171
|
+
* a pre-send synthesis (no connection / no responder) — the op provably never
|
|
172
|
+
* reached the machine, so it is safe to re-issue for any op kind. */
|
|
173
|
+
export declare function offlineControlResponse(requestId: string, neverSent?: boolean): ControlResponse;
|
|
174
|
+
/** A `ControlResponse` carrying a synthesized TIMEOUT error (→ reconnecting). */
|
|
175
|
+
export declare function timeoutControlResponse(requestId: string): ControlResponse;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SelfhostedControlError } from "./control-rpc";
|
|
2
|
+
/**
|
|
3
|
+
* A stable, low-cardinality fault-class string for a `SelfhostedControlError` — the
|
|
4
|
+
* out-of-band twin of the in-band renderer's class taxonomy (single-sourced here).
|
|
5
|
+
* Used for the `machine.op.*` event class + the metrics label + the infra filter.
|
|
6
|
+
*/
|
|
7
|
+
export declare function selfhostedFaultClass(error: SelfhostedControlError): string;
|
|
8
|
+
/**
|
|
9
|
+
* The fault classes that count as an INFRASTRUCTURE fault (a `machine.op.failed`
|
|
10
|
+
* event). A semantic miss the model asked about — a missing path (`not_found`), a
|
|
11
|
+
* consent gate (`consent`), a nonzero exit (never a fault at all) — is an OUTCOME,
|
|
12
|
+
* not an infra fault, and is DELIBERATELY excluded; `fenced` is a routing/session
|
|
13
|
+
* condition, not a machine fault. (Doctrine two-planes / the team ruling.)
|
|
14
|
+
*/
|
|
15
|
+
export declare const SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES: ReadonlySet<string>;
|
|
16
|
+
/** The four mandatory field labels (exported so tests can assert their presence). */
|
|
17
|
+
export declare const FAULT_FIELD_WHAT_HAPPENED = "What happened:";
|
|
18
|
+
export declare const FAULT_FIELD_WHICH_LAYER = "Which layer:";
|
|
19
|
+
export declare const FAULT_FIELD_WHAT_PRESERVED = "What was preserved:";
|
|
20
|
+
export declare const FAULT_FIELD_WHAT_TO_TRY = "What to try:";
|
|
21
|
+
/**
|
|
22
|
+
* Render a `SelfhostedControlError` into the doctrine's four in-band fields with a
|
|
23
|
+
* correct retry verdict. The fault CLASS is read from the typed flags (never the
|
|
24
|
+
* message prose), so each class gets the right "which layer" and "what to try":
|
|
25
|
+
*
|
|
26
|
+
* - PAYLOAD_TOO_LARGE → the command ran but its reply was too big to return; the
|
|
27
|
+
* fix is to bound the output, not to retry.
|
|
28
|
+
* - DRAINING → pre-admission backpressure; nothing ran; safe to retry shortly.
|
|
29
|
+
* - CONSENT_REQUIRED → a machine-side consent gate; retrying cannot help.
|
|
30
|
+
* - AGENT_OFFLINE → the machine link is down. If the transport KNOWS the request
|
|
31
|
+
* never left (`neverSent`), nothing ran; otherwise the effect is ambiguous and
|
|
32
|
+
* the model must check before re-running. Either way "try again" is wrong until
|
|
33
|
+
* the machine reconnects.
|
|
34
|
+
* - agent_reconnecting (TIMEOUT) → a transient link blip AFTER send: ambiguous, so
|
|
35
|
+
* a state-changing command must be checked before re-running (at-least-once).
|
|
36
|
+
* - NOT_FOUND → a command/filesystem miss, not a link fault.
|
|
37
|
+
* - FENCED → the active machine session changed (a swap); re-run.
|
|
38
|
+
* - default (OS/UNSUPPORTED/STREAM/PROTOCOL) → the command/op failed on the machine.
|
|
39
|
+
*/
|
|
40
|
+
export declare function renderSelfhostedFault(error: SelfhostedControlError): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ErrorCode } from "@opengeni/agent-proto";
|
|
2
|
+
import type { SelfhostedUnavailableReason } from "./control-rpc";
|
|
3
|
+
/**
|
|
4
|
+
* One completed control-op observation. `outcome` is the terminal result AFTER any
|
|
5
|
+
* in-call retries: `ok` (the op succeeded) or `failed` (a terminal fault surfaced).
|
|
6
|
+
* `healed` marks a success that only landed after ≥1 retry — the doctrine's
|
|
7
|
+
* "healed and invisible in-band, always out-of-band" breadcrumb (drives
|
|
8
|
+
* `machine.op.recovered`). Every field is derivable at the `SelfhostedSession.call`
|
|
9
|
+
* exit points with no extra round-trip.
|
|
10
|
+
*/
|
|
11
|
+
export interface SelfhostedOpObservation {
|
|
12
|
+
/** The control-op kind (`ControlRequest["op"].$case`): exec / fsRead / ping / … */
|
|
13
|
+
op: string;
|
|
14
|
+
outcome: "ok" | "failed";
|
|
15
|
+
/** True when `outcome==="ok"` only after ≥1 in-call retry (draining/timeout/never-sent). */
|
|
16
|
+
healed: boolean;
|
|
17
|
+
/** Total in-call retries before this outcome (summed across the retry classes). */
|
|
18
|
+
retries: number;
|
|
19
|
+
/** Wall time from the first attempt to the terminal outcome, in ms. */
|
|
20
|
+
durationMs: number;
|
|
21
|
+
/** The typed wire code — present on `failed`. */
|
|
22
|
+
code?: ErrorCode;
|
|
23
|
+
/** The negotiated capability reason (agent_offline / agent_reconnecting / …), if any. */
|
|
24
|
+
reason?: SelfhostedUnavailableReason | null;
|
|
25
|
+
/** Whether a failed fault provably never reached the machine (a pre-send synthesis). */
|
|
26
|
+
neverSent?: boolean;
|
|
27
|
+
/** The enrolled machine (agent) id the op addressed — for machine-scoped fan-out. */
|
|
28
|
+
machineId?: string;
|
|
29
|
+
/** A stable fault-class string (`selfhostedFaultClass`): the fault class on a
|
|
30
|
+
* `failed` op, or the class of retries that a `healed` op recovered from. Absent
|
|
31
|
+
* on a clean success. Drives the `machine.op.*` event mapping + its infra filter. */
|
|
32
|
+
faultClass?: string;
|
|
33
|
+
/** The reply size in bytes when known — set on a PAYLOAD_TOO_LARGE fault (from the
|
|
34
|
+
* agent's `encoded_bytes` detail). The op-stream client fills the success-reply
|
|
35
|
+
* size once it owns the framed transport; today's request/reply does not surface it. */
|
|
36
|
+
replyBytes?: number;
|
|
37
|
+
}
|
|
38
|
+
/** A fire-and-forget observer of completed control ops. It MUST NOT throw (the
|
|
39
|
+
* session guards the call) and MUST NOT block the op — it is a telemetry tap. */
|
|
40
|
+
export type SelfhostedOpObserver = (observation: SelfhostedOpObservation) => void;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type ExecRequest, type ExecResponse } from "@opengeni/agent-proto";
|
|
2
|
+
import { type ControlRpc } from "./control-rpc";
|
|
3
|
+
import { type SelfhostedRetryClock } from "./retry-policy";
|
|
4
|
+
import { type OpStreamTransport } from "./op-transport";
|
|
5
|
+
/** Initial send-credit window (bytes) when the caller does not size it. Matches
|
|
6
|
+
* the wire default OpStart advertises (~4 MiB). */
|
|
7
|
+
export declare const OP_STREAM_DEFAULT_WINDOW_BYTES: number;
|
|
8
|
+
/** Cumulative-ack repetition cadence while an op is live (ruling M1: acks are
|
|
9
|
+
* best-effort and healed by repetition). */
|
|
10
|
+
export declare const OP_STREAM_ACK_INTERVAL_MS = 5000;
|
|
11
|
+
/** Frame-silence threshold before the client starts probing with OpQuery. The
|
|
12
|
+
* runner emits Progress every 5s while an op is alive and quiet, so 30s of
|
|
13
|
+
* TOTAL silence means frames are not reaching us (or the op is gone). */
|
|
14
|
+
export declare const OP_STREAM_SILENCE_TIMEOUT_MS = 30000;
|
|
15
|
+
/** How long a silent op is held open across a machine reconnect window before
|
|
16
|
+
* the client fails it typed (PROTOCOL liveness rule: hold ≤120s, then only a
|
|
17
|
+
* definitive answer fails the op). */
|
|
18
|
+
export declare const OP_STREAM_RECONNECT_HOLD_MS = 120000;
|
|
19
|
+
/**
|
|
20
|
+
* The durable-resume journal the worker adapts onto Temporal (design delta Δ1:
|
|
21
|
+
* the attach generation is `Info.currentAttemptScheduledTimestampMs` — strictly
|
|
22
|
+
* monotonic across worker-death redispatches, which `Info.attempt` is NOT under
|
|
23
|
+
* `maximumAttempts: 1`). `persistSettled` folds the op's settled frontier into
|
|
24
|
+
* the activity heartbeat details and MUST complete before the wire final ack
|
|
25
|
+
* (the durable-before-wire-ack gate); the default no-journal client (tests,
|
|
26
|
+
* non-activity callers) uses generation "1" and skips persistence.
|
|
27
|
+
*/
|
|
28
|
+
export interface OpStreamJournal {
|
|
29
|
+
/** The consumer's attach generation (u64 decimal string). */
|
|
30
|
+
attachGeneration(): string;
|
|
31
|
+
/** Durably record `opId` as settled at `exitSeq` BEFORE its final ack. */
|
|
32
|
+
persistSettled(opId: string, exitSeq: string): void | Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
export interface OpStreamExecClientDeps {
|
|
35
|
+
workspaceId: string;
|
|
36
|
+
agentId: string;
|
|
37
|
+
/** The lease/active epoch every ControlRequest is fenced under. */
|
|
38
|
+
epoch: number;
|
|
39
|
+
/** The rpc request/reply seam (OpStart/OpQuery/OpAttach ride here). */
|
|
40
|
+
controlRpc: ControlRpc;
|
|
41
|
+
/** The rpc subject (`agent.<ws>.<id>.rpc`). */
|
|
42
|
+
rpcSubject: string;
|
|
43
|
+
/** The frame/ack seam (subscribe + publish). */
|
|
44
|
+
transport: OpStreamTransport;
|
|
45
|
+
journal?: OpStreamJournal;
|
|
46
|
+
windowBytes?: number;
|
|
47
|
+
/** Control-op round-trip timeout for OpStart/OpQuery/OpAttach (the SHORT
|
|
48
|
+
* control budget, not the exec deadline). */
|
|
49
|
+
controlTimeoutMs: number;
|
|
50
|
+
/** The retry clock (sleep + jitter), injected for deterministic tests. */
|
|
51
|
+
retryClock: SelfhostedRetryClock;
|
|
52
|
+
ackIntervalMs?: number;
|
|
53
|
+
silenceTimeoutMs?: number;
|
|
54
|
+
reconnectHoldMs?: number;
|
|
55
|
+
}
|
|
56
|
+
/** A completed op-stream exec: the legacy-shaped response plus the healing
|
|
57
|
+
* telemetry the session folds into its op observation. */
|
|
58
|
+
export interface OpStreamExecOutcome {
|
|
59
|
+
response: ExecResponse;
|
|
60
|
+
/** Attach/query heals that occurred after streaming began (gap replays,
|
|
61
|
+
* silence probes that re-attached). >0 marks the op `healed`. */
|
|
62
|
+
heals: number;
|
|
63
|
+
/** OpStart-level retries (draining/blip) before the op was accepted. */
|
|
64
|
+
startRetries: number;
|
|
65
|
+
/** Total reassembled Data payload bytes (the observer's replyBytes). */
|
|
66
|
+
replyBytes: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The per-session op-stream exec client. One instance per `SelfhostedSession`;
|
|
70
|
+
* `exec()` runs one op end-to-end; `finalizeSettledOps()` is the turn-end hook
|
|
71
|
+
* that advances the acked frontier durably (journal) and then on the wire.
|
|
72
|
+
*/
|
|
73
|
+
export declare class OpStreamExecClient {
|
|
74
|
+
private readonly deps;
|
|
75
|
+
private readonly settled;
|
|
76
|
+
private readonly inFlight;
|
|
77
|
+
constructor(deps: OpStreamExecClientDeps);
|
|
78
|
+
private generation;
|
|
79
|
+
/**
|
|
80
|
+
* Cancel an exec by its durable op id. OpCancel is idempotent and also acts
|
|
81
|
+
* as a cancel-before-start tombstone, so calling this while OpStart is still
|
|
82
|
+
* racing is safe: the runner either kills the accepted process tree or
|
|
83
|
+
* refuses the later start without spawning it.
|
|
84
|
+
*/
|
|
85
|
+
cancel(opId: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Run one exec over the op stream. `opId` is the durable id (B1) — a re-issue
|
|
88
|
+
* with the same id attaches instead of re-running. `deadlineMs` is the exec
|
|
89
|
+
* process budget (relative); the runner's enforcement is authoritative.
|
|
90
|
+
* `wallMs` is the client-side give-up wall (deadline + reply grace).
|
|
91
|
+
*/
|
|
92
|
+
exec(opId: string, exec: ExecRequest, deadlineMs: number, wallMs: number): Promise<OpStreamExecOutcome>;
|
|
93
|
+
/**
|
|
94
|
+
* The turn-end durability hook (the hard gate's ordering): for every op whose
|
|
95
|
+
* result the turn has durably consumed — (1) already true when this runs —
|
|
96
|
+
* (2) persist the settled frontier to the journal, then (3) publish the wire
|
|
97
|
+
* final ack (`acked_seq = exit_seq, final = true`), which licenses the runner
|
|
98
|
+
* to GC the op. A kill between (2) and (3) is safe: the re-dispatched turn
|
|
99
|
+
* does not re-execute a durably-recorded call, and the runner's retention TTL
|
|
100
|
+
* reaps the never-final-acked op — no loss, bounded residue. Publish failures
|
|
101
|
+
* are swallowed for the same reason.
|
|
102
|
+
*/
|
|
103
|
+
finalizeSettledOps(): Promise<void>;
|
|
104
|
+
}
|