@peakflo/agent-harness 0.0.0-stage → 0.2.2-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +68 -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,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* THE MODEL-FACING CONVERSATION — the thing that was missing.
|
|
4
|
+
*
|
|
5
|
+
* We shipped a conversation product in which the conversation never reached the
|
|
6
|
+
* model. `SessionRunner` created a backend session, sent ONLY the new user
|
|
7
|
+
* text, and destroyed the session again, every turn. The UI transcript was
|
|
8
|
+
* complete and correct the whole time, which is exactly why nobody noticed.
|
|
9
|
+
*
|
|
10
|
+
* WHY A SECOND LOG, BESIDE THE TRANSCRIPT WE ALREADY HAVE. The transcript is a
|
|
11
|
+
* UI PROJECTION. It is shaped for rendering: ephemeral step markers are
|
|
12
|
+
* dropped, tool calls are flattened into a display payload, streaming deltas
|
|
13
|
+
* overwrite each other in place, and content is truncated when it is too large
|
|
14
|
+
* to store. Every one of those is right for a screen and lossy for a prompt.
|
|
15
|
+
* Replaying a projection back at a model is guessing at what the model was
|
|
16
|
+
* told. This log records what the model was ACTUALLY given and what it
|
|
17
|
+
* ACTUALLY said, so a replay is a replay rather than a reconstruction.
|
|
18
|
+
*
|
|
19
|
+
* TWO WAYS TO REMEMBER, AND THE ORDER MATTERS.
|
|
20
|
+
*
|
|
21
|
+
* 1. RESUME THE BACKEND SESSION. When the backend still holds the session —
|
|
22
|
+
* OpenCode does, and every adapter in this package implements
|
|
23
|
+
* `resumeSession` — the cheapest and most faithful thing is to keep
|
|
24
|
+
* talking to it. Nothing is reconstructed, tool results and reasoning stay
|
|
25
|
+
* exactly as the backend recorded them, and no tokens are re-sent.
|
|
26
|
+
*
|
|
27
|
+
* 2. REPLAY THE LOG INTO A FRESH SESSION. When the backend session is gone —
|
|
28
|
+
* a new sandbox, a redeployed runner, a crashed OpenCode — this log is the
|
|
29
|
+
* only memory left. It is a genuine downgrade: the model sees a
|
|
30
|
+
* transcript of the conversation rather than its own context. It is also
|
|
31
|
+
* the difference between a user repeating themselves and a user losing
|
|
32
|
+
* the thread entirely.
|
|
33
|
+
*
|
|
34
|
+
* `resumeFromRev` is what tells the runner which of those it is in.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.MAX_REPLAY_CHARS = void 0;
|
|
38
|
+
exports.trimHistory = trimHistory;
|
|
39
|
+
exports.buildReplayPrompt = buildReplayPrompt;
|
|
40
|
+
exports.assistantTextFrom = assistantTextFrom;
|
|
41
|
+
/**
|
|
42
|
+
* How much replayed history to put in front of a prompt, in characters.
|
|
43
|
+
*
|
|
44
|
+
* A CEILING, NOT A TARGET. Without one, a long-running session eventually
|
|
45
|
+
* builds a prompt bigger than the model's context window and every turn fails
|
|
46
|
+
* — and it fails at the WORST moment, once the user has invested a long
|
|
47
|
+
* conversation in it. Trimming from the FRONT keeps the most recent exchanges,
|
|
48
|
+
* which are the ones that carry the pronouns.
|
|
49
|
+
*
|
|
50
|
+
* This is a stop-gap and is deliberately labelled as one: the right answer is
|
|
51
|
+
* summarisation at a compaction checkpoint, which is S7's work. A crude ceiling
|
|
52
|
+
* that keeps a session alive beats an elegant plan that is not written yet.
|
|
53
|
+
*/
|
|
54
|
+
exports.MAX_REPLAY_CHARS = 24_000;
|
|
55
|
+
/**
|
|
56
|
+
* Rough allowance per message for the `Role: ` prefix and its newline.
|
|
57
|
+
*
|
|
58
|
+
* An estimate, and deliberately a slight over-estimate: the ceiling exists to
|
|
59
|
+
* keep a prompt inside a context window, and erring small costs a little
|
|
60
|
+
* history while erring large costs the turn.
|
|
61
|
+
*/
|
|
62
|
+
const PER_MESSAGE_OVERHEAD_CHARS = 16;
|
|
63
|
+
/** Says plainly that the model is not seeing the whole message. */
|
|
64
|
+
const TRUNCATION_NOTE = '[earlier part of this message omitted] ';
|
|
65
|
+
/**
|
|
66
|
+
* Keep the most recent messages that fit inside the ceiling.
|
|
67
|
+
*
|
|
68
|
+
* Walks BACKWARDS from the newest, so the tail is preserved and the head is
|
|
69
|
+
* what gets dropped.
|
|
70
|
+
*/
|
|
71
|
+
function trimHistory(history, maxChars = exports.MAX_REPLAY_CHARS) {
|
|
72
|
+
const kept = [];
|
|
73
|
+
let used = 0;
|
|
74
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
75
|
+
const message = history[i];
|
|
76
|
+
const cost = message.content.length + PER_MESSAGE_OVERHEAD_CHARS;
|
|
77
|
+
if (used + cost <= maxChars) {
|
|
78
|
+
kept.push(message);
|
|
79
|
+
used += cost;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
/*
|
|
83
|
+
* THE NEWEST MESSAGE IS TRUNCATED, NOT WAVED THROUGH.
|
|
84
|
+
*
|
|
85
|
+
* This function's first version broke out of the loop on `kept.length > 0`,
|
|
86
|
+
* which reads as "always keep at least one" and behaves as "the newest
|
|
87
|
+
* message is exempt from the limit". A single very large message therefore
|
|
88
|
+
* defeated the ceiling COMPLETELY — at the one moment the ceiling exists
|
|
89
|
+
* for, since an oversized message is exactly what pushes a prompt past the
|
|
90
|
+
* context window.
|
|
91
|
+
*
|
|
92
|
+
* Truncating from the FRONT of the message keeps its ending, which is
|
|
93
|
+
* where a tool result puts its conclusion and where a long answer puts its
|
|
94
|
+
* summary. The marker is left in the text so the model is not misled into
|
|
95
|
+
* thinking it has the whole thing.
|
|
96
|
+
*/
|
|
97
|
+
if (kept.length === 0) {
|
|
98
|
+
const room = maxChars - PER_MESSAGE_OVERHEAD_CHARS - TRUNCATION_NOTE.length;
|
|
99
|
+
if (room > 0) {
|
|
100
|
+
kept.push({
|
|
101
|
+
role: message.role,
|
|
102
|
+
content: TRUNCATION_NOTE + message.content.slice(-room)
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
return kept.reverse();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build the prompt for a fresh backend session that must be told what happened.
|
|
112
|
+
*
|
|
113
|
+
* THE MARKERS ARE LOAD-BEARING, and the reason is not cosmetic. The replayed
|
|
114
|
+
* history is presented as a QUOTED RECORD inside a single user message, not as
|
|
115
|
+
* a stack of alternating turns, because a fresh session has no message history
|
|
116
|
+
* to stack them into. Without a clear boundary the model cannot tell which part
|
|
117
|
+
* of the text is the conversation so far and which part is the question it is
|
|
118
|
+
* being asked now, and it answers the wrong one — usually by replying to the
|
|
119
|
+
* oldest message in the log.
|
|
120
|
+
*
|
|
121
|
+
* Returns the raw input unchanged when there is nothing to replay, so a first
|
|
122
|
+
* turn is byte-for-byte what it always was and cannot regress.
|
|
123
|
+
*/
|
|
124
|
+
function buildReplayPrompt(history, input) {
|
|
125
|
+
const trimmed = trimHistory(history);
|
|
126
|
+
if (trimmed.length === 0)
|
|
127
|
+
return input;
|
|
128
|
+
const lines = trimmed.map((message) => `${message.role === 'user' ? 'User' : 'Assistant'}: ${message.content}`);
|
|
129
|
+
return [
|
|
130
|
+
'The conversation so far, which you should treat as your own memory of it:',
|
|
131
|
+
'',
|
|
132
|
+
'<conversation>',
|
|
133
|
+
...lines,
|
|
134
|
+
'</conversation>',
|
|
135
|
+
'',
|
|
136
|
+
'Continue that conversation. Reply only to the new message below.',
|
|
137
|
+
'',
|
|
138
|
+
input
|
|
139
|
+
].join('\n');
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reduce a turn's projected parts to the assistant text the model produced.
|
|
143
|
+
*
|
|
144
|
+
* ONLY `text` PARTS, and only assistant ones. A tool call's payload is a record
|
|
145
|
+
* of what the agent DID, and replaying it as prose would tell the model it once
|
|
146
|
+
* said a blob of JSON. What belongs in a conversation log is what was said.
|
|
147
|
+
*/
|
|
148
|
+
function assistantTextFrom(parts) {
|
|
149
|
+
return parts
|
|
150
|
+
.filter((part) => part.role === 'assistant' &&
|
|
151
|
+
(part.partType === 'text' || part.partType === 'reasoning-text') &&
|
|
152
|
+
typeof part.content === 'string' &&
|
|
153
|
+
part.content.trim().length > 0)
|
|
154
|
+
.map((part) => part.content.trim())
|
|
155
|
+
.join('\n\n');
|
|
156
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LEASE RENEWAL FOR A SLOW, SILENT TURN — the fix for "a turn is requeued
|
|
3
|
+
* underneath itself".
|
|
4
|
+
*
|
|
5
|
+
* THE PROBLEM. Lease extension rode ENTIRELY on `/ingest` piggy-backing
|
|
6
|
+
* `leaseExpiresAt`: every successful ingest pushes the visibility timeout out by
|
|
7
|
+
* LEASE_TTL_MS (120 s). `renewLease` existed on the client but had ZERO call
|
|
8
|
+
* sites. So a turn that is slow AND produces no parts for over 120 s — one long
|
|
9
|
+
* tool call, one slow model, a big file read — has its lease expire while it is
|
|
10
|
+
* still working. The watchdog then requeues the session onto ANOTHER runner, and
|
|
11
|
+
* two runners run the same turn: two writers interleaving one transcript, double
|
|
12
|
+
* the model spend, and eventually MAX_REQUEUE_ATTEMPTS poisoning the turn.
|
|
13
|
+
*
|
|
14
|
+
* THE FIX, in two halves:
|
|
15
|
+
*
|
|
16
|
+
* 1. Renew on a TIMER for the whole duration of the turn, comfortably inside
|
|
17
|
+
* LEASE_TTL_MS, so a silent turn keeps its lease alive without waiting for
|
|
18
|
+
* output to ingest.
|
|
19
|
+
*
|
|
20
|
+
* 2. Handle `{ renewed: false }` by ABANDONING the turn. A lease that could
|
|
21
|
+
* not be renewed has already expired and been requeued elsewhere; another
|
|
22
|
+
* runner owns the session now. Continuing to spend model budget on work we
|
|
23
|
+
* no longer own is exactly the double-execution this fix exists to stop, so
|
|
24
|
+
* the turn's AbortController is fired and the runner stops.
|
|
25
|
+
*/
|
|
26
|
+
import type { LeasedGitHubCredential, SessionCommand } from '../types';
|
|
27
|
+
/** The default renewal cadence: half of LEASE_TTL_MS, so one lost renewal is survivable. */
|
|
28
|
+
export declare const DEFAULT_RENEW_INTERVAL_MS = 60000;
|
|
29
|
+
/**
|
|
30
|
+
* THE CADENCE WHILE A HUMAN IS BEING WAITED ON — and why this route, not the
|
|
31
|
+
* heartbeat.
|
|
32
|
+
*
|
|
33
|
+
* A permission prompt is only useful if it appears, and is answered, at
|
|
34
|
+
* conversational speed. Measured against the channels that existed before this
|
|
35
|
+
* change:
|
|
36
|
+
*
|
|
37
|
+
* heartbeat 30 s the ONLY carrier of sessionCommands
|
|
38
|
+
* lease long-poll 25 s but NOT polled at all during a turn — the loop
|
|
39
|
+
* sees `active.size >= maxConcurrentSessions` and
|
|
40
|
+
* sleeps 500 ms without calling the route
|
|
41
|
+
* lease renew 60 s per-lease, runs for the whole turn, carried
|
|
42
|
+
* no commands
|
|
43
|
+
*
|
|
44
|
+
* So the brief's premise — "commands ride back on BOTH the heartbeat and the
|
|
45
|
+
* lease poll, and the lease poll is far tighter" — does not hold in this build.
|
|
46
|
+
* `sessionCommands` is returned by exactly one route (handlers/runners.ts:391,
|
|
47
|
+
* the heartbeat), and the lease poll is not even called while the turn that
|
|
48
|
+
* needs the answer is running. Every approval would have cost up to 30 s.
|
|
49
|
+
*
|
|
50
|
+
* THE RENEW ROUTE IS THE RIGHT CARRIER, for three reasons that all point the
|
|
51
|
+
* same way:
|
|
52
|
+
* 1. It is the only control-plane call that already runs for the whole
|
|
53
|
+
* duration of a turn, which is exactly the window an approval lives in.
|
|
54
|
+
* 2. It is already per-lease and per-runner authenticated, so it needs no new
|
|
55
|
+
* auth path and no new route — `takePendingCommands` matches on the
|
|
56
|
+
* session's runner and can simply be called there too.
|
|
57
|
+
* 3. It is the same call that keeps the turn from being scavenged. One call
|
|
58
|
+
* proves the turn is alive AND collects the answer, so the two can never
|
|
59
|
+
* disagree about whether the turn still exists.
|
|
60
|
+
*
|
|
61
|
+
* Two seconds matches the runner's own backend poll interval, so the answer and
|
|
62
|
+
* the backend's reaction to it land in the same beat. It is expensive only
|
|
63
|
+
* while a human is genuinely being waited on, which is rare, bounded by the
|
|
64
|
+
* person, and precisely when the extra call is worth making.
|
|
65
|
+
*/
|
|
66
|
+
export declare const APPROVAL_RENEW_INTERVAL_MS = 2000;
|
|
67
|
+
export interface RenewedLease {
|
|
68
|
+
renewed: boolean;
|
|
69
|
+
reason?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Commands the control plane had queued for this runner.
|
|
72
|
+
*
|
|
73
|
+
* ADDITIVE AND OPTIONAL, so protocol v1 is untouched: a control plane that
|
|
74
|
+
* does not send them yields `undefined` and the harness behaves exactly as
|
|
75
|
+
* before, collecting commands on the heartbeat alone.
|
|
76
|
+
*/
|
|
77
|
+
sessionCommands?: SessionCommand[];
|
|
78
|
+
githubCredential?: LeasedGitHubCredential;
|
|
79
|
+
/**
|
|
80
|
+
* A replacement per-turn model token, present only when the request's
|
|
81
|
+
* `modelTokenExpiresAt` entered the refresh margin. See
|
|
82
|
+
* `SessionRunner.updateModelCredential` for how it reaches a running turn.
|
|
83
|
+
*/
|
|
84
|
+
modelToken?: string;
|
|
85
|
+
modelTokenExpiresAt?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface LeaseRenewer {
|
|
88
|
+
renewLease(leaseId: string, status?: undefined, githubTokenExpiresAt?: string, modelTokenExpiresAt?: string): Promise<RenewedLease>;
|
|
89
|
+
}
|
|
90
|
+
export interface LeaseRenewalOptions {
|
|
91
|
+
client: LeaseRenewer;
|
|
92
|
+
leaseId: string;
|
|
93
|
+
/**
|
|
94
|
+
* Fired when the lease can no longer be renewed. In the harness this is the
|
|
95
|
+
* turn's `AbortController.abort`, which stops the session runner and prevents
|
|
96
|
+
* any further ingest for a session another runner now owns.
|
|
97
|
+
*/
|
|
98
|
+
onLost: (reason: string) => void;
|
|
99
|
+
intervalMs?: number;
|
|
100
|
+
/**
|
|
101
|
+
* The cadence to use while `isUrgent()` is true. Defaults to
|
|
102
|
+
* `APPROVAL_RENEW_INTERVAL_MS`.
|
|
103
|
+
*/
|
|
104
|
+
urgentIntervalMs?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Commands that came back on a renewal. Called for every renewal that carries
|
|
107
|
+
* any, whatever the cadence — so an ABORT also arrives sooner than the
|
|
108
|
+
* heartbeat would have delivered it, which is a free improvement to a path
|
|
109
|
+
* that used to cost up to 30 s.
|
|
110
|
+
*/
|
|
111
|
+
onCommands?: (commands: SessionCommand[]) => void;
|
|
112
|
+
githubTokenExpiresAt?: string;
|
|
113
|
+
onGithubCredential?: (credential: LeasedGitHubCredential) => void;
|
|
114
|
+
/** See `RenewedLease.modelTokenExpiresAt`. */
|
|
115
|
+
modelTokenExpiresAt?: string;
|
|
116
|
+
onModelCredential?: (credential: {
|
|
117
|
+
token: string;
|
|
118
|
+
expiresAt: string;
|
|
119
|
+
}) => void;
|
|
120
|
+
/** Injected in tests so the timer does not cost real wall-clock. */
|
|
121
|
+
setIntervalImpl?: (fn: () => void, ms: number) => NodeJS.Timeout;
|
|
122
|
+
clearIntervalImpl?: (handle: NodeJS.Timeout) => void;
|
|
123
|
+
log?: (message: string, extra?: Record<string, unknown>) => void;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The handle a caller keeps for the life of the turn.
|
|
127
|
+
*
|
|
128
|
+
* `setUrgent` is EVENT-DRIVEN rather than a predicate the loop polls. The loop
|
|
129
|
+
* must not hold its own copy of a state the runner owns — it would drift, and
|
|
130
|
+
* the drift is silent in the expensive direction: the harness would keep
|
|
131
|
+
* renewing every two seconds long after the approval had been answered. The
|
|
132
|
+
* runner already knows the exact instant a prompt opens and closes, so it says
|
|
133
|
+
* so, and the cadence changes immediately instead of at the next tick.
|
|
134
|
+
*/
|
|
135
|
+
export interface LeaseRenewalHandle {
|
|
136
|
+
/** Stop renewing. MUST be called when the turn ends. */
|
|
137
|
+
stop(): void;
|
|
138
|
+
/**
|
|
139
|
+
* Switch between the ordinary cadence and the approval cadence. Idempotent:
|
|
140
|
+
* setting the cadence already in force does not re-arm the timer, so a
|
|
141
|
+
* chatty caller cannot postpone a renewal for ever by asking for the same
|
|
142
|
+
* thing repeatedly.
|
|
143
|
+
*/
|
|
144
|
+
setUrgent(urgent: boolean): void;
|
|
145
|
+
/** Replace the credential after the control plane rotates it. */
|
|
146
|
+
setCredential?(credential: string): void;
|
|
147
|
+
}
|
|
148
|
+
export interface IsolatedLeaseRenewalOptions extends Omit<LeaseRenewalOptions, 'client' | 'setIntervalImpl' | 'clearIntervalImpl'> {
|
|
149
|
+
url: string;
|
|
150
|
+
runnerCredential: string;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Renew a lease outside the adapter's event loop.
|
|
154
|
+
*
|
|
155
|
+
* The callbacks still run on the main thread, but the lease extension does
|
|
156
|
+
* not depend on them. Messages wait safely in the worker port until Pi yields.
|
|
157
|
+
*/
|
|
158
|
+
export declare function startIsolatedLeaseRenewal(options: IsolatedLeaseRenewalOptions): LeaseRenewalHandle;
|
|
159
|
+
/**
|
|
160
|
+
* Keep a lease alive for the duration of a turn. Returns a `stop` function the
|
|
161
|
+
* caller MUST call when the turn ends, so a completed turn does not keep
|
|
162
|
+
* renewing a lease that has already been released.
|
|
163
|
+
*
|
|
164
|
+
* A renewal that fails with a TRANSIENT error (a network blip, a 500) is logged
|
|
165
|
+
* and retried on the next tick — losing one renewal is why the cadence is half
|
|
166
|
+
* the TTL. Only a definitive `renewed: false`, or a FINAL HTTP status, means the
|
|
167
|
+
* lease is truly gone and the turn must be abandoned.
|
|
168
|
+
*/
|
|
169
|
+
export declare function startLeaseRenewal(options: LeaseRenewalOptions): LeaseRenewalHandle;
|