@peakflo/agent-harness 0.0.0-stage → 0.2.1-rc.3
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/LICENSE +21 -0
- package/README.md +38 -2
- package/dist/adapters/acp-adapter.d.ts +177 -0
- package/dist/adapters/acp-adapter.js +1732 -0
- package/dist/adapters/claude-code-adapter.d.ts +171 -0
- package/dist/adapters/claude-code-adapter.js +1918 -0
- package/dist/adapters/codex-adapter.d.ts +67 -0
- package/dist/adapters/codex-adapter.js +681 -0
- package/dist/adapters/codex-app-server-adapter.d.ts +105 -0
- package/dist/adapters/codex-app-server-adapter.js +1309 -0
- package/dist/adapters/coding-agent-adapter.d.ts +289 -0
- package/dist/adapters/coding-agent-adapter.js +31 -0
- package/dist/adapters/index.d.ts +17 -0
- package/dist/adapters/index.js +35 -0
- package/dist/adapters/model-gateway.d.ts +84 -0
- package/dist/adapters/model-gateway.js +270 -0
- package/dist/adapters/opencode-adapter.d.ts +403 -0
- package/dist/adapters/opencode-adapter.js +2710 -0
- package/dist/adapters/opencode-config.d.ts +41 -0
- package/dist/adapters/opencode-config.js +147 -0
- package/dist/adapters/opencode-server-policy.d.ts +82 -0
- package/dist/adapters/opencode-server-policy.js +148 -0
- package/dist/adapters/pi-adapter.d.ts +168 -0
- package/dist/adapters/pi-adapter.js +1468 -0
- package/dist/capabilities.d.ts +58 -0
- package/dist/capabilities.js +161 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +231 -0
- package/dist/contract.d.ts +273 -0
- package/dist/contract.js +357 -0
- package/dist/control-plane.d.ts +178 -0
- package/dist/control-plane.js +431 -0
- package/dist/credential-store.d.ts +110 -0
- package/dist/credential-store.js +181 -0
- package/dist/enrol/browser-enrol.d.ts +85 -0
- package/dist/enrol/browser-enrol.js +387 -0
- package/dist/github-command-guards.d.ts +20 -0
- package/dist/github-command-guards.js +330 -0
- package/dist/github-proxy-certificates.d.ts +25 -0
- package/dist/github-proxy-certificates.js +145 -0
- package/dist/github-proxy.d.ts +37 -0
- package/dist/github-proxy.js +476 -0
- package/dist/github-workspace.d.ts +55 -0
- package/dist/github-workspace.js +537 -0
- package/dist/harness.d.ts +333 -0
- package/dist/harness.js +1414 -0
- package/dist/health.d.ts +45 -0
- package/dist/health.js +116 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +123 -0
- package/dist/model-probe.d.ts +96 -0
- package/dist/model-probe.js +195 -0
- package/dist/options.d.ts +15 -0
- package/dist/options.js +212 -0
- package/dist/ports-default.d.ts +212 -0
- package/dist/ports-default.js +333 -0
- package/dist/ports.d.ts +436 -0
- package/dist/ports.js +27 -0
- package/dist/runtime/attachments.d.ts +92 -0
- package/dist/runtime/attachments.js +377 -0
- package/dist/runtime/child-stream-guards.d.ts +45 -0
- package/dist/runtime/child-stream-guards.js +111 -0
- package/dist/runtime/conversation.d.ts +87 -0
- package/dist/runtime/conversation.js +156 -0
- package/dist/runtime/lease-renewal.d.ts +169 -0
- package/dist/runtime/lease-renewal.js +433 -0
- package/dist/runtime/mcp-auth-proxy.d.ts +82 -0
- package/dist/runtime/mcp-auth-proxy.js +300 -0
- package/dist/runtime/permission-mode.d.ts +116 -0
- package/dist/runtime/permission-mode.js +146 -0
- package/dist/runtime/projection.d.ts +71 -0
- package/dist/runtime/projection.js +211 -0
- package/dist/runtime/session-runner.d.ts +520 -0
- package/dist/runtime/session-runner.js +2122 -0
- package/dist/shared/reasoning-effort.d.ts +11 -0
- package/dist/shared/reasoning-effort.js +20 -0
- package/dist/store/conversation-store.d.ts +100 -0
- package/dist/store/conversation-store.js +305 -0
- package/dist/store/sqlite-store.d.ts +120 -0
- package/dist/store/sqlite-store.js +346 -0
- package/dist/types.d.ts +952 -0
- package/dist/types.js +55 -0
- package/dist/version.d.ts +32 -0
- package/dist/version.js +77 -0
- package/package.json +53 -4
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import type { CodingAgentAdapter } from './adapters';
|
|
2
|
+
import { SessionModelGateway } from './adapters/model-gateway';
|
|
3
|
+
import { ControlPlaneClient } from './control-plane';
|
|
4
|
+
import { CredentialStore } from './credential-store';
|
|
5
|
+
import { ArtifactInspector, EventSink, Notifier, SecretProvider, Telemetry, ConversationStore, TranscriptStore, WorkspaceProvider } from './ports';
|
|
6
|
+
import { HarnessOptions, HealthReport } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* The control plane refused this harness's PROTOCOL or VERSION. FINAL.
|
|
9
|
+
*
|
|
10
|
+
* A distinct class rather than a message match, so a caller cannot break the
|
|
11
|
+
* gate by rewording an error string. The process must EXIT — not merely throw —
|
|
12
|
+
* because the health socket keeps the Node event loop alive for ever, and a
|
|
13
|
+
* harness that only threw would hang while the gate quietly never took effect.
|
|
14
|
+
* Phase 0 shipped exactly that bug.
|
|
15
|
+
*/
|
|
16
|
+
export declare class VersionRefusedError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** The sandbox supervisor, not the harness, owns package installation. */
|
|
20
|
+
export declare class UpgradeRequestedError extends Error {
|
|
21
|
+
readonly harnessVersion: string;
|
|
22
|
+
constructor(harnessVersion: string);
|
|
23
|
+
}
|
|
24
|
+
export interface HarnessUpgradeRequest {
|
|
25
|
+
harnessVersion: string;
|
|
26
|
+
/**
|
|
27
|
+
* Present when a TOOLS-only pin is what actually woke the idle drain.
|
|
28
|
+
* Advisory for the supervisor's logging — pf-bootstrap's recursed main()
|
|
29
|
+
* re-dials /harness/bootstrap regardless and resyncs both layers off that
|
|
30
|
+
* fresh response, not off this field.
|
|
31
|
+
*/
|
|
32
|
+
sandboxToolsVersion?: string;
|
|
33
|
+
runnerId: string;
|
|
34
|
+
tenantId: string | null;
|
|
35
|
+
runnerCredential: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The per-turn ports an adapter is built against.
|
|
39
|
+
*
|
|
40
|
+
* Passed to `getAdapter` rather than read from a module singleton, because the
|
|
41
|
+
* credential inside changes every turn and an adapter constructed once must see
|
|
42
|
+
* the CURRENT one.
|
|
43
|
+
*/
|
|
44
|
+
export interface AdapterPorts {
|
|
45
|
+
/** Holds the model-gateway credential minted for the running turn. */
|
|
46
|
+
modelGateway: SessionModelGateway;
|
|
47
|
+
}
|
|
48
|
+
export interface HarnessDeps {
|
|
49
|
+
/**
|
|
50
|
+
* Resolves a backend name from a lease to a working adapter.
|
|
51
|
+
*
|
|
52
|
+
* OPTIONAL, so a host with no backend installed still boots, registers and
|
|
53
|
+
* reports its capabilities. That matters: a runner whose `pi` install
|
|
54
|
+
* failed should appear in the fleet as present-but-unusable, not as a host
|
|
55
|
+
* that never came back at all.
|
|
56
|
+
*/
|
|
57
|
+
getAdapter?: (backend: string, ports: AdapterPorts) => CodingAgentAdapter;
|
|
58
|
+
store?: TranscriptStore;
|
|
59
|
+
/** Injected in tests; defaults to a sqlite file beside the workspace root. */
|
|
60
|
+
conversation?: ConversationStore;
|
|
61
|
+
workspace?: WorkspaceProvider;
|
|
62
|
+
secrets?: SecretProvider;
|
|
63
|
+
events?: EventSink;
|
|
64
|
+
telemetry?: Telemetry;
|
|
65
|
+
notifier?: Notifier;
|
|
66
|
+
artifacts?: ArtifactInspector;
|
|
67
|
+
/**
|
|
68
|
+
* Where the runner credential is persisted between runs.
|
|
69
|
+
*
|
|
70
|
+
* Injected in tests; in production it is derived from the runtime — a
|
|
71
|
+
* durable file store for `self-hosted`/`desktop`, a no-op for `sandbox`.
|
|
72
|
+
*/
|
|
73
|
+
credentialStore?: CredentialStore;
|
|
74
|
+
/**
|
|
75
|
+
* How often the lease is renewed during a turn. Defaults to
|
|
76
|
+
* `DEFAULT_RENEW_INTERVAL_MS` (half of LEASE_TTL_MS). Injected in tests so a
|
|
77
|
+
* renewal can be driven in milliseconds rather than a minute.
|
|
78
|
+
*/
|
|
79
|
+
leaseRenewIntervalMs?: number;
|
|
80
|
+
/** Pass the live runner identity to the sandbox supervisor over IPC. */
|
|
81
|
+
onUpgradeRequested?: (request: HarnessUpgradeRequest) => void;
|
|
82
|
+
/**
|
|
83
|
+
* How often the lease is renewed WHILE A PERMISSION PROMPT IS OUTSTANDING.
|
|
84
|
+
* Defaults to `APPROVAL_RENEW_INTERVAL_MS` (2 s).
|
|
85
|
+
*
|
|
86
|
+
* The renewal call is also the channel the user's answer comes back on, so
|
|
87
|
+
* this is the approval latency — see the long note on that constant for why
|
|
88
|
+
* the renew route carries it rather than the 30 s heartbeat.
|
|
89
|
+
*/
|
|
90
|
+
approvalRenewIntervalMs?: number;
|
|
91
|
+
/** Injected in tests. */
|
|
92
|
+
clientFactory?: (url: string) => ControlPlaneClient;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* THE HARNESS.
|
|
96
|
+
*
|
|
97
|
+
* ONE PROGRAM, THREE HOSTING MODES. A Daytona sandbox, a customer VM and the
|
|
98
|
+
* 20x desktop run this identical loop; nothing below asks which it is. Where a
|
|
99
|
+
* host genuinely differs it supplies a PORT, and the ports are capabilities
|
|
100
|
+
* ("can you notify a human?"), never identities ("are you the desktop?").
|
|
101
|
+
*
|
|
102
|
+
* THE LOOP, and why it has this shape:
|
|
103
|
+
*
|
|
104
|
+
* register -> heartbeat (30s, forever) + lease (25s poll, forever)
|
|
105
|
+
*
|
|
106
|
+
* Both are OUTBOUND. Nothing dials in, there is no listening port reachable
|
|
107
|
+
* from the network, and that single property is what lets a customer VM behind
|
|
108
|
+
* NAT work with no tunnel and no firewall change. It is also why an abort or a
|
|
109
|
+
* user's answer to a question rides back on `sessionCommands` in the HEARTBEAT
|
|
110
|
+
* response rather than arriving on a socket.
|
|
111
|
+
*
|
|
112
|
+
* Long-poll rather than a WebSocket because Cloud Run bills a request slot for
|
|
113
|
+
* the WHOLE life of a connection and caps it at 3600s. One persistent socket per
|
|
114
|
+
* runner would be one billed slot per runner, permanently.
|
|
115
|
+
*/
|
|
116
|
+
export declare class Harness {
|
|
117
|
+
private readonly options;
|
|
118
|
+
private readonly deps;
|
|
119
|
+
private readonly client;
|
|
120
|
+
private readonly health;
|
|
121
|
+
private readonly proxy;
|
|
122
|
+
private readonly store;
|
|
123
|
+
/** The model-facing conversation log. See `ports.ts#ConversationStore`. */
|
|
124
|
+
private readonly conversation;
|
|
125
|
+
/** False when the log is in memory only, so a restart forgets. Reported. */
|
|
126
|
+
private readonly conversationDurable;
|
|
127
|
+
private readonly storeDurable;
|
|
128
|
+
private readonly storeReason;
|
|
129
|
+
private readonly workspace;
|
|
130
|
+
private readonly secrets;
|
|
131
|
+
private readonly events;
|
|
132
|
+
private readonly telemetry;
|
|
133
|
+
private readonly notifier;
|
|
134
|
+
private readonly artifacts;
|
|
135
|
+
private readonly tokens;
|
|
136
|
+
private readonly startedAt;
|
|
137
|
+
private status;
|
|
138
|
+
private tenantId;
|
|
139
|
+
private runnerId;
|
|
140
|
+
private capabilities;
|
|
141
|
+
/**
|
|
142
|
+
* The cached model catalogue, probed at start-up and on a LONG interval.
|
|
143
|
+
*
|
|
144
|
+
* Probed ONCE, not per heartbeat: Pi discovery starts a short-lived RPC
|
|
145
|
+
* process, and doing that every 30 seconds would be a regression. The cache
|
|
146
|
+
* is the flat `models` + per-backend list
|
|
147
|
+
* that every capability report after start reuses.
|
|
148
|
+
*/
|
|
149
|
+
private modelCatalog;
|
|
150
|
+
/** Epoch ms of the last successful (or attempted) model probe. */
|
|
151
|
+
private modelProbeAt;
|
|
152
|
+
/**
|
|
153
|
+
* Empty gateway for the model probe. The probe builds a FRESH adapter and
|
|
154
|
+
* must not read a live turn's credential; a null config is "no gateway",
|
|
155
|
+
* which makes the probe list the host's own local models — and the control
|
|
156
|
+
* plane separately supplies the gateway list.
|
|
157
|
+
*/
|
|
158
|
+
private readonly modelProbeGateway;
|
|
159
|
+
private heartbeatIntervalMs;
|
|
160
|
+
private leaseWaitSeconds;
|
|
161
|
+
private heartbeatTimer;
|
|
162
|
+
/** Fires when the runner credential should be rotated. Durable runtimes re-persist the new one. */
|
|
163
|
+
private rotateTimer;
|
|
164
|
+
private readonly credentialStore;
|
|
165
|
+
/** True when this run reused a persisted credential rather than enrolling. */
|
|
166
|
+
private reusedStoredCredential;
|
|
167
|
+
private lastHeartbeatAt;
|
|
168
|
+
private lastHeartbeatError;
|
|
169
|
+
private consecutiveHeartbeatFailures;
|
|
170
|
+
private stopping;
|
|
171
|
+
/** Releases this Harness's reference to the process-wide generic wrappers. */
|
|
172
|
+
private releaseGitHubGuard;
|
|
173
|
+
/** sessionId -> the controller that aborts its turn. */
|
|
174
|
+
private readonly active;
|
|
175
|
+
/** Renewal workers that must receive a credential rotation in flight. */
|
|
176
|
+
private readonly activeRenewals;
|
|
177
|
+
/** commandIds already applied. `SessionCommand` is idempotent by contract. */
|
|
178
|
+
private readonly appliedCommands;
|
|
179
|
+
/** Session-end cleanup waits for an active turn to save its dirty work. */
|
|
180
|
+
private readonly pendingWorkspaceReleases;
|
|
181
|
+
/**
|
|
182
|
+
* sessionId -> the runner executing its turn.
|
|
183
|
+
*
|
|
184
|
+
* `active` above holds only an AbortController, which is all an `abort`
|
|
185
|
+
* command needs. An `answer` needs the RUNNER: the adapter and the backend
|
|
186
|
+
* session id live inside the turn, and the answer has to reach them from the
|
|
187
|
+
* command handler's stack. This map is what makes wire 3 possible.
|
|
188
|
+
*/
|
|
189
|
+
private readonly runners;
|
|
190
|
+
private leaseLoop;
|
|
191
|
+
/** Set when the control plane refuses this build. Ends the process. */
|
|
192
|
+
private fatal;
|
|
193
|
+
/** A changed pin waits here until every live lease has completed. */
|
|
194
|
+
private pendingHarnessVersion;
|
|
195
|
+
/** Same idea, for the INDEPENDENT sandbox-tools pin (gh, node deps, ...). */
|
|
196
|
+
private pendingSandboxToolsVersion;
|
|
197
|
+
constructor(options: HarnessOptions, deps?: HarnessDeps);
|
|
198
|
+
/** The actual bound health port. Used by tests to prove the socket closed. */
|
|
199
|
+
getHealthPort(): number | null;
|
|
200
|
+
snapshot(): HealthReport;
|
|
201
|
+
start(): Promise<void>;
|
|
202
|
+
/**
|
|
203
|
+
* Block until the harness stops for a reason worth reporting.
|
|
204
|
+
*
|
|
205
|
+
* Resolves on a clean drain; rejects when the control plane refused this
|
|
206
|
+
* build, so the CLI can exit non-zero and the host is recycled onto a
|
|
207
|
+
* supported version.
|
|
208
|
+
*/
|
|
209
|
+
wait(): Promise<void>;
|
|
210
|
+
stop(options?: {
|
|
211
|
+
preserveRegistration?: boolean;
|
|
212
|
+
}): Promise<void>;
|
|
213
|
+
private register;
|
|
214
|
+
/**
|
|
215
|
+
* Try to authenticate with a persisted credential. Returns true on success.
|
|
216
|
+
*
|
|
217
|
+
* The stored credential is VALIDATED by an actual authenticated call — a
|
|
218
|
+
* heartbeat, the lightest one — before the harness commits to it. That is what
|
|
219
|
+
* makes the fallback safe: a stored credential that the control plane has
|
|
220
|
+
* revoked (a `401`/`403`) is dropped here and enrolment is retried, so a stale
|
|
221
|
+
* credential cannot wedge the runner. A TRANSIENT failure is NOT treated as a
|
|
222
|
+
* rejection — re-enrolling on a network blip would burn an already-spent token
|
|
223
|
+
* — so the harness keeps the credential and lets the normal loop retry.
|
|
224
|
+
*/
|
|
225
|
+
private tryStoredCredential;
|
|
226
|
+
/**
|
|
227
|
+
* Persist the runner credential for durable runtimes.
|
|
228
|
+
*
|
|
229
|
+
* A no-op for `sandbox` (its store discards everything) and for a response
|
|
230
|
+
* that carried no credential. This is what a crash on the NEXT run reuses.
|
|
231
|
+
*/
|
|
232
|
+
private persistCredential;
|
|
233
|
+
/**
|
|
234
|
+
* Schedule a rotation at `rotateAfter`, comfortably before the credential
|
|
235
|
+
* expires. The control plane owns the cadence; if it names neither a
|
|
236
|
+
* `rotateAfter` nor an expiry, no rotation is scheduled and the credential
|
|
237
|
+
* simply lives out its TTL.
|
|
238
|
+
*/
|
|
239
|
+
private scheduleRotation;
|
|
240
|
+
/**
|
|
241
|
+
* Rotate the runner credential and re-persist it.
|
|
242
|
+
*
|
|
243
|
+
* A credential cannot authenticate its own replacement once expired, so
|
|
244
|
+
* rotation is scheduled AHEAD of expiry and never used to climb out of a 401.
|
|
245
|
+
* A failed rotation keeps the current credential and retries later; a final
|
|
246
|
+
* status stops trying, because the credential is gone for good and the loop
|
|
247
|
+
* will surface that.
|
|
248
|
+
*/
|
|
249
|
+
private rotateCredential;
|
|
250
|
+
/**
|
|
251
|
+
* Re-probe the model catalogue.
|
|
252
|
+
*
|
|
253
|
+
* Runs ONCE at start-up and on a long interval after that — never per
|
|
254
|
+
* heartbeat, because the probe starts a Pi discovery process.
|
|
255
|
+
* `force` re-probes regardless of age (used at start-up). Never rejects.
|
|
256
|
+
*/
|
|
257
|
+
private refreshModelCatalog;
|
|
258
|
+
/**
|
|
259
|
+
* Attach the cached model catalogue to a capability report.
|
|
260
|
+
*
|
|
261
|
+
* Mutates `capabilities` in place: the flat list on `models`, and each
|
|
262
|
+
* installed backend's own list on `backends[].models`. Absent (rather than
|
|
263
|
+
* an empty-but-meaningful list) when the catalogue is empty.
|
|
264
|
+
*/
|
|
265
|
+
private applyModelCatalog;
|
|
266
|
+
/**
|
|
267
|
+
* Whether the model catalogue needs a (rare) re-probe.
|
|
268
|
+
*
|
|
269
|
+
* Interval-only, and the interval doubles as a cooldown: `refreshModelCatalog`
|
|
270
|
+
* stamps `modelProbeAt` even when the probe itself fails, so a host whose
|
|
271
|
+
* models cannot be enumerated does not keep starting a process on every
|
|
272
|
+
* heartbeat trying. Start-up force bypasses this.
|
|
273
|
+
*
|
|
274
|
+
* NEVER WHILE A TURN IS RUNNING. Pi discovery and a live turn share the Pi
|
|
275
|
+
* agent directory, including models.json; avoiding concurrent discovery
|
|
276
|
+
* keeps provider configuration changes out of an active model call.
|
|
277
|
+
* Deferring costs nothing: the catalogue only
|
|
278
|
+
* changes when the host's model config does, and the next idle heartbeat
|
|
279
|
+
* picks it up.
|
|
280
|
+
*/
|
|
281
|
+
private modelsNeedRefresh;
|
|
282
|
+
private scheduleHeartbeat;
|
|
283
|
+
private sendHeartbeat;
|
|
284
|
+
/**
|
|
285
|
+
* Apply a command that rode back on a heartbeat.
|
|
286
|
+
*
|
|
287
|
+
* Idempotent by `commandId`, because the contract lets the control plane
|
|
288
|
+
* repeat a command until it observes the effect — it has no other way to
|
|
289
|
+
* know, since it cannot dial in and ask.
|
|
290
|
+
*/
|
|
291
|
+
private applyCommand;
|
|
292
|
+
private releaseSessionWorkspace;
|
|
293
|
+
/**
|
|
294
|
+
* A user's answer did not reach the backend. Do the two things that keep it
|
|
295
|
+
* from vanishing.
|
|
296
|
+
*
|
|
297
|
+
* 1. FORGET THE commandId, so a redelivery is retried rather than discarded
|
|
298
|
+
* as a duplicate. On its own this fixes nothing — the control plane stamps
|
|
299
|
+
* `delivered_at` at hand-off and does not resend today — so it is a latch
|
|
300
|
+
* for a retry that does not yet exist.
|
|
301
|
+
* 2. TELL THE USER, in the transcript. This is the half that actually
|
|
302
|
+
* reaches them: without it the question card stays open, the turn keeps
|
|
303
|
+
* waiting (approval time is deliberately outside the turn deadline, so
|
|
304
|
+
* nothing else ends it), and a click is indistinguishable from a slow
|
|
305
|
+
* backend.
|
|
306
|
+
*/
|
|
307
|
+
private recoverFailedAnswer;
|
|
308
|
+
private runLeaseLoop;
|
|
309
|
+
/**
|
|
310
|
+
* Record a changed pin. Only managed sandboxes have the baked supervisor
|
|
311
|
+
* that can service the distinguished exit code.
|
|
312
|
+
*/
|
|
313
|
+
private observeHarnessPin;
|
|
314
|
+
/**
|
|
315
|
+
* Same shape as observeHarnessPin(), for the INDEPENDENT sandbox-tools pin.
|
|
316
|
+
* A host that never reported an installed tools version (self-hosted,
|
|
317
|
+
* desktop, or a sandbox that predates this layer) has nothing to compare
|
|
318
|
+
* against and is left alone rather than treated as "version none, always
|
|
319
|
+
* behind" — that would drain and exit-75 every such host on every lease.
|
|
320
|
+
*/
|
|
321
|
+
private observeSandboxToolsPin;
|
|
322
|
+
/** Stop once. A failed install is then the supervisor's terminal decision. */
|
|
323
|
+
private requestUpgrade;
|
|
324
|
+
/**
|
|
325
|
+
* Run one leased turn.
|
|
326
|
+
*
|
|
327
|
+
* NOT awaited by the loop, so a runner allowed more than one concurrent
|
|
328
|
+
* session actually gets one. Every failure is contained here: a turn that
|
|
329
|
+
* throws must not end the lease loop, or one bad session would take the whole
|
|
330
|
+
* runner offline.
|
|
331
|
+
*/
|
|
332
|
+
private runLeasedTurn;
|
|
333
|
+
}
|