@peakflo/agent-harness 0.2.4 → 0.2.5-rc.2
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/adapters/coding-agent-adapter.d.ts +6 -0
- package/dist/adapters/pi-adapter.d.ts +3 -0
- package/dist/adapters/pi-adapter.js +137 -4
- package/dist/capabilities.d.ts +2 -0
- package/dist/capabilities.js +27 -2
- package/dist/contract.d.ts +4 -1
- package/dist/contract.js +4 -1
- package/dist/control-plane.d.ts +6 -0
- package/dist/control-plane.js +29 -0
- package/dist/harness.d.ts +14 -4
- package/dist/harness.js +156 -22
- package/dist/runtime/cold-start-steps.d.ts +11 -0
- package/dist/runtime/cold-start-steps.js +57 -0
- package/dist/runtime/session-runner.d.ts +23 -1
- package/dist/runtime/session-runner.js +241 -12
- package/dist/types.d.ts +9 -1
- package/package.json +1 -1
package/dist/harness.js
CHANGED
|
@@ -21,6 +21,7 @@ const sqlite_store_1 = require("./store/sqlite-store");
|
|
|
21
21
|
const version_1 = require("./version");
|
|
22
22
|
/** Backoff after a lease poll fails, so an unreachable control plane is not hammered. */
|
|
23
23
|
const LEASE_ERROR_BACKOFF_MS = 5_000;
|
|
24
|
+
const LEASE_MIN_EMPTY_POLL_MS = 250;
|
|
24
25
|
/**
|
|
25
26
|
* How often the model catalogue is re-probed once the harness is running.
|
|
26
27
|
*
|
|
@@ -150,10 +151,15 @@ class Harness {
|
|
|
150
151
|
releaseGitHubGuard = null;
|
|
151
152
|
/** sessionId -> the controller that aborts its turn. */
|
|
152
153
|
active = new Map();
|
|
154
|
+
activeLeaseIds = new Map();
|
|
153
155
|
/** Renewal workers that must receive a credential rotation in flight. */
|
|
154
156
|
activeRenewals = new Set();
|
|
155
157
|
/** commandIds already applied. `SessionCommand` is idempotent by contract. */
|
|
156
158
|
appliedCommands = new Set();
|
|
159
|
+
/** Prune lease-bound command IDs when their session turn ends. */
|
|
160
|
+
appliedCommandsBySession = new Map();
|
|
161
|
+
/** A steer can arrive on renewal while a leased runner is still starting. */
|
|
162
|
+
deferredSteers = new Map();
|
|
157
163
|
/** Session-end cleanup waits for an active turn to save its dirty work. */
|
|
158
164
|
pendingWorkspaceReleases = new Set();
|
|
159
165
|
/**
|
|
@@ -338,6 +344,7 @@ class Harness {
|
|
|
338
344
|
}
|
|
339
345
|
this.stopping = true;
|
|
340
346
|
this.status = 'draining';
|
|
347
|
+
this.deferredSteers.clear();
|
|
341
348
|
if (this.heartbeatTimer) {
|
|
342
349
|
clearTimeout(this.heartbeatTimer);
|
|
343
350
|
this.heartbeatTimer = null;
|
|
@@ -506,9 +513,7 @@ class Harness {
|
|
|
506
513
|
}
|
|
507
514
|
this.observeHarnessPin(response.harnessVersion);
|
|
508
515
|
this.observeSandboxToolsPin(response.sandboxToolsVersion);
|
|
509
|
-
|
|
510
|
-
this.applyCommand(command);
|
|
511
|
-
}
|
|
516
|
+
this.applyCommandsSafely(response.sessionCommands ?? []);
|
|
512
517
|
log('Reused stored runner credential', {
|
|
513
518
|
runnerId: stored.runnerId,
|
|
514
519
|
tenantId: stored.tenantId
|
|
@@ -782,9 +787,7 @@ class Harness {
|
|
|
782
787
|
}
|
|
783
788
|
this.observeHarnessPin(response.harnessVersion);
|
|
784
789
|
this.observeSandboxToolsPin(response.sandboxToolsVersion);
|
|
785
|
-
|
|
786
|
-
this.applyCommand(command);
|
|
787
|
-
}
|
|
790
|
+
this.applyCommandsSafely(response.sessionCommands ?? []);
|
|
788
791
|
if (response.action === 'drain') {
|
|
789
792
|
this.status = 'draining';
|
|
790
793
|
log('Control plane asked the harness to drain');
|
|
@@ -822,21 +825,85 @@ class Harness {
|
|
|
822
825
|
}
|
|
823
826
|
this.scheduleHeartbeat();
|
|
824
827
|
}
|
|
828
|
+
/** A bad command must not stop the others or fail a transcript ingest. */
|
|
829
|
+
applyCommandsSafely(commands) {
|
|
830
|
+
for (const command of commands) {
|
|
831
|
+
try {
|
|
832
|
+
this.applyCommand(command);
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
// Commands are a side channel. An application error must not reject
|
|
836
|
+
// the ingest of model output or skip the commands after this one.
|
|
837
|
+
this.forgetAppliedCommand(command);
|
|
838
|
+
log('Session command failed', {
|
|
839
|
+
sessionId: command.sessionId,
|
|
840
|
+
commandId: command.commandId,
|
|
841
|
+
command: command.command,
|
|
842
|
+
error: error instanceof Error ? error.message : String(error)
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
forgetAppliedCommand(command) {
|
|
848
|
+
this.appliedCommands.delete(command.commandId);
|
|
849
|
+
const ids = this.appliedCommandsBySession.get(command.sessionId);
|
|
850
|
+
ids?.delete(command.commandId);
|
|
851
|
+
if (ids?.size === 0)
|
|
852
|
+
this.appliedCommandsBySession.delete(command.sessionId);
|
|
853
|
+
}
|
|
854
|
+
clearAppliedCommandsForSession(sessionId) {
|
|
855
|
+
for (const id of this.appliedCommandsBySession.get(sessionId) ?? []) {
|
|
856
|
+
this.appliedCommands.delete(id);
|
|
857
|
+
}
|
|
858
|
+
this.appliedCommandsBySession.delete(sessionId);
|
|
859
|
+
}
|
|
825
860
|
/**
|
|
826
|
-
* Apply a command that rode back on a heartbeat.
|
|
861
|
+
* Apply a command that rode back on a heartbeat or ingest response.
|
|
827
862
|
*
|
|
828
|
-
* Idempotent by `commandId`, because the
|
|
829
|
-
*
|
|
830
|
-
* know, since it cannot dial in and ask.
|
|
863
|
+
* Idempotent by `commandId`, because the control plane may repeat a command
|
|
864
|
+
* until it observes the effect; it cannot dial the harness to ask.
|
|
831
865
|
*/
|
|
832
866
|
applyCommand(command) {
|
|
833
867
|
if (this.appliedCommands.has(command.commandId)) {
|
|
834
868
|
return;
|
|
835
869
|
}
|
|
836
870
|
this.appliedCommands.add(command.commandId);
|
|
871
|
+
const sessionCommands = this.appliedCommandsBySession.get(command.sessionId) ?? new Set();
|
|
872
|
+
sessionCommands.add(command.commandId);
|
|
873
|
+
this.appliedCommandsBySession.set(command.sessionId, sessionCommands);
|
|
874
|
+
if (command.command === 'steer') {
|
|
875
|
+
const runner = this.runners.get(command.sessionId);
|
|
876
|
+
if (!runner || !command.turnId || !command.leaseId || !command.input) {
|
|
877
|
+
if (!runner && command.turnId && command.leaseId && command.input) {
|
|
878
|
+
this.deferredSteers.set(command.commandId, command);
|
|
879
|
+
}
|
|
880
|
+
this.forgetAppliedCommand(command);
|
|
881
|
+
// The durable turn remains queued until Pi confirms consumption.
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (this.activeLeaseIds.get(command.sessionId) !== command.leaseId) {
|
|
885
|
+
this.forgetAppliedCommand(command);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
runner.steerMessage({
|
|
889
|
+
sessionId: command.sessionId,
|
|
890
|
+
turnId: command.turnId,
|
|
891
|
+
input: command.input,
|
|
892
|
+
acknowledge: async (consumed) => (await this.client.acknowledgeSteer(command.sessionId, command.leaseId, command.turnId, consumed)).delivery
|
|
893
|
+
});
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
837
896
|
if (command.command === 'abort') {
|
|
838
897
|
const controller = this.active.get(command.sessionId);
|
|
839
|
-
|
|
898
|
+
const activeLeaseId = this.activeLeaseIds.get(command.sessionId);
|
|
899
|
+
if (!controller || (command.optionId && !activeLeaseId)) {
|
|
900
|
+
// A command can arrive between lease claim and local runner setup.
|
|
901
|
+
// Leave its id open so the control plane's safe abort redelivery can
|
|
902
|
+
// apply it once this exact lease is active.
|
|
903
|
+
this.forgetAppliedCommand(command);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
if (!command.optionId || activeLeaseId === command.optionId) {
|
|
840
907
|
log('Aborting session by control-plane command', {
|
|
841
908
|
sessionId: command.sessionId
|
|
842
909
|
});
|
|
@@ -854,7 +921,7 @@ class Harness {
|
|
|
854
921
|
});
|
|
855
922
|
}
|
|
856
923
|
else {
|
|
857
|
-
void this.releaseSessionWorkspace(command.sessionId);
|
|
924
|
+
void this.releaseSessionWorkspace(command.sessionId).finally(() => this.forgetAppliedCommand(command));
|
|
858
925
|
}
|
|
859
926
|
return;
|
|
860
927
|
}
|
|
@@ -895,7 +962,7 @@ class Harness {
|
|
|
895
962
|
* all that can be said here; what this buys is that a redelivery would
|
|
896
963
|
* be attempted rather than discarded as a duplicate.
|
|
897
964
|
*/
|
|
898
|
-
this.
|
|
965
|
+
this.forgetAppliedCommand(command);
|
|
899
966
|
log('Answer arrived for a session this runner is no longer running', {
|
|
900
967
|
sessionId: command.sessionId,
|
|
901
968
|
commandId: command.commandId
|
|
@@ -963,6 +1030,16 @@ class Harness {
|
|
|
963
1030
|
});
|
|
964
1031
|
}
|
|
965
1032
|
}
|
|
1033
|
+
/** Deliver commands received while the leased runner was still starting. */
|
|
1034
|
+
flushDeferredSteers(sessionId, leaseId) {
|
|
1035
|
+
for (const [commandId, command] of this.deferredSteers) {
|
|
1036
|
+
if (command.sessionId !== sessionId)
|
|
1037
|
+
continue;
|
|
1038
|
+
this.deferredSteers.delete(commandId);
|
|
1039
|
+
if (command.leaseId === leaseId)
|
|
1040
|
+
this.applyCommandsSafely([command]);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
966
1043
|
async releaseSessionWorkspace(sessionId) {
|
|
967
1044
|
try {
|
|
968
1045
|
const released = await this.workspace.release(sessionId, { force: true });
|
|
@@ -992,7 +1069,7 @@ class Harness {
|
|
|
992
1069
|
* backend.
|
|
993
1070
|
*/
|
|
994
1071
|
recoverFailedAnswer(runner, command, reason) {
|
|
995
|
-
this.
|
|
1072
|
+
this.forgetAppliedCommand(command);
|
|
996
1073
|
void runner.reportApprovalFailure(command.sessionId, reason).catch(() => {
|
|
997
1074
|
// If even that cannot be delivered, the log at the call site is all
|
|
998
1075
|
// there is. Never let reporting a failure become a second failure.
|
|
@@ -1031,6 +1108,7 @@ class Harness {
|
|
|
1031
1108
|
continue;
|
|
1032
1109
|
}
|
|
1033
1110
|
let lease = null;
|
|
1111
|
+
const pollStartedAt = Date.now();
|
|
1034
1112
|
try {
|
|
1035
1113
|
lease = await this.client.lease(this.leaseWaitSeconds);
|
|
1036
1114
|
}
|
|
@@ -1050,12 +1128,54 @@ class Harness {
|
|
|
1050
1128
|
continue;
|
|
1051
1129
|
}
|
|
1052
1130
|
if (!lease) {
|
|
1053
|
-
//
|
|
1054
|
-
//
|
|
1131
|
+
// The server normally long-polls. A 204 returned immediately during
|
|
1132
|
+
// a fence or by a faulty proxy must not make this process spin.
|
|
1133
|
+
const waitLeft = LEASE_MIN_EMPTY_POLL_MS - (Date.now() - pollStartedAt);
|
|
1134
|
+
if (waitLeft > 0)
|
|
1135
|
+
await sleep(waitLeft);
|
|
1055
1136
|
continue;
|
|
1056
1137
|
}
|
|
1138
|
+
const leaseReceived = JSON.stringify({
|
|
1139
|
+
event: 'cold_start_step',
|
|
1140
|
+
sessionId: lease.session.sessionId,
|
|
1141
|
+
turnId: lease.session.turnId,
|
|
1142
|
+
step: 'lease_poll_to_pickup',
|
|
1143
|
+
startedAt: new Date(pollStartedAt).toISOString(),
|
|
1144
|
+
endedAt: new Date().toISOString(),
|
|
1145
|
+
durationMs: Date.now() - pollStartedAt
|
|
1146
|
+
});
|
|
1147
|
+
log(leaseReceived);
|
|
1148
|
+
void this.client.reportColdStart(leaseReceived).catch(() => undefined);
|
|
1057
1149
|
this.observeHarnessPin(lease.harnessVersion);
|
|
1058
1150
|
this.observeSandboxToolsPin(lease.sandboxToolsVersion);
|
|
1151
|
+
// Protocol 3 uses a short visibility window until the runner confirms
|
|
1152
|
+
// receipt. A dropped GET response is then reclaimed within nine seconds.
|
|
1153
|
+
// Do not start work unless the acknowledgement extends this lease.
|
|
1154
|
+
let leaseAcknowledged = false;
|
|
1155
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1156
|
+
try {
|
|
1157
|
+
const acknowledged = await this.client.acknowledgeLease(lease.leaseId);
|
|
1158
|
+
if (!acknowledged.renewed) {
|
|
1159
|
+
log('Lease acknowledgement refused', { leaseId: lease.leaseId });
|
|
1160
|
+
break;
|
|
1161
|
+
}
|
|
1162
|
+
leaseAcknowledged = true;
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
log('Lease acknowledgement failed', {
|
|
1167
|
+
leaseId: lease.leaseId,
|
|
1168
|
+
attempt: attempt + 1,
|
|
1169
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1170
|
+
});
|
|
1171
|
+
if (attempt < 2)
|
|
1172
|
+
await sleep(500);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
if (!leaseAcknowledged) {
|
|
1176
|
+
await sleep(LEASE_ERROR_BACKOFF_MS);
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1059
1179
|
void this.runLeasedTurn(lease);
|
|
1060
1180
|
}
|
|
1061
1181
|
await this.stop();
|
|
@@ -1186,15 +1306,14 @@ class Harness {
|
|
|
1186
1306
|
*/
|
|
1187
1307
|
const renewalCallbacks = {
|
|
1188
1308
|
leaseId: lease.leaseId,
|
|
1309
|
+
// A running turn does not poll the lease endpoint. Ingest returns
|
|
1310
|
+
// commands at each output boundary; a silent tool relies on heartbeat
|
|
1311
|
+
// or this normal renewal interval, which can take up to a minute.
|
|
1189
1312
|
intervalMs: this.deps.leaseRenewIntervalMs,
|
|
1190
1313
|
// While a permission prompt is open, renew — and therefore collect
|
|
1191
1314
|
// commands — every couple of seconds instead of every minute.
|
|
1192
1315
|
urgentIntervalMs: this.deps.approvalRenewIntervalMs,
|
|
1193
|
-
onCommands: (commands) =>
|
|
1194
|
-
for (const command of commands) {
|
|
1195
|
-
this.applyCommand(command);
|
|
1196
|
-
}
|
|
1197
|
-
},
|
|
1316
|
+
onCommands: (commands) => this.applyCommandsSafely(commands),
|
|
1198
1317
|
githubTokenExpiresAt: lease.githubCredential?.expiresAt,
|
|
1199
1318
|
onGithubCredential: (credential) => {
|
|
1200
1319
|
githubWorkspace.setCredential?.(sessionId, credential);
|
|
@@ -1306,6 +1425,9 @@ class Harness {
|
|
|
1306
1425
|
secrets: this.secrets,
|
|
1307
1426
|
events: this.events,
|
|
1308
1427
|
telemetry: this.telemetry,
|
|
1428
|
+
coldStartLog: (line) => {
|
|
1429
|
+
void this.client.reportColdStart(line).catch(() => undefined);
|
|
1430
|
+
},
|
|
1309
1431
|
notifier: this.notifier,
|
|
1310
1432
|
artifacts: this.artifacts,
|
|
1311
1433
|
conversation: this.conversation,
|
|
@@ -1348,7 +1470,9 @@ class Harness {
|
|
|
1348
1470
|
final,
|
|
1349
1471
|
modelMessages
|
|
1350
1472
|
})
|
|
1351
|
-
.then(() =>
|
|
1473
|
+
.then((response) => {
|
|
1474
|
+
this.applyCommandsSafely(response.sessionCommands ?? []);
|
|
1475
|
+
})
|
|
1352
1476
|
});
|
|
1353
1477
|
runnerRef = runner;
|
|
1354
1478
|
// Registered BEFORE the turn starts. A permission can be raised on the
|
|
@@ -1357,6 +1481,8 @@ class Harness {
|
|
|
1357
1481
|
// running" — which is indistinguishable, in the log, from a real
|
|
1358
1482
|
// mis-routing.
|
|
1359
1483
|
this.runners.set(sessionId, runner);
|
|
1484
|
+
this.activeLeaseIds.set(sessionId, lease.leaseId);
|
|
1485
|
+
this.flushDeferredSteers(sessionId, lease.leaseId);
|
|
1360
1486
|
try {
|
|
1361
1487
|
const result = await runner.run(lease, controller.signal);
|
|
1362
1488
|
log('Turn finished', {
|
|
@@ -1399,6 +1525,14 @@ class Harness {
|
|
|
1399
1525
|
// that arrives during the commit must defer, never delete under Git.
|
|
1400
1526
|
this.active.delete(sessionId);
|
|
1401
1527
|
this.runners.delete(sessionId);
|
|
1528
|
+
this.activeLeaseIds.delete(sessionId);
|
|
1529
|
+
// The control plane expires lease-bound commands at turn end. Keep IDs
|
|
1530
|
+
// only while their lease can redeliver them; a warm runner serves many turns.
|
|
1531
|
+
this.clearAppliedCommandsForSession(sessionId);
|
|
1532
|
+
for (const [commandId, command] of this.deferredSteers) {
|
|
1533
|
+
if (command.sessionId === sessionId)
|
|
1534
|
+
this.deferredSteers.delete(commandId);
|
|
1535
|
+
}
|
|
1402
1536
|
// Revoke ONLY this session's tokens. A process-wide revoke here blanked
|
|
1403
1537
|
// the credential of every concurrent turn still running.
|
|
1404
1538
|
this.tokens.revokeTargets(mcpTargetUrls);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Timing for one leased turn. Values in this log contain no credentials. */
|
|
2
|
+
export declare class ColdStartSteps {
|
|
3
|
+
private readonly sessionId;
|
|
4
|
+
private readonly turnId;
|
|
5
|
+
private readonly log;
|
|
6
|
+
private readonly startedAt;
|
|
7
|
+
private readonly steps;
|
|
8
|
+
constructor(sessionId: string, turnId: string, log: (line: string) => void);
|
|
9
|
+
begin(step: string): () => void;
|
|
10
|
+
summary(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ColdStartSteps = void 0;
|
|
4
|
+
/** Timing for one leased turn. Values in this log contain no credentials. */
|
|
5
|
+
class ColdStartSteps {
|
|
6
|
+
sessionId;
|
|
7
|
+
turnId;
|
|
8
|
+
log;
|
|
9
|
+
startedAt = Date.now();
|
|
10
|
+
steps = {};
|
|
11
|
+
constructor(sessionId, turnId, log) {
|
|
12
|
+
this.sessionId = sessionId;
|
|
13
|
+
this.turnId = turnId;
|
|
14
|
+
this.log = log;
|
|
15
|
+
}
|
|
16
|
+
begin(step) {
|
|
17
|
+
const startedAt = Date.now();
|
|
18
|
+
let ended = false;
|
|
19
|
+
return () => {
|
|
20
|
+
if (ended)
|
|
21
|
+
return;
|
|
22
|
+
ended = true;
|
|
23
|
+
const endedAt = Date.now();
|
|
24
|
+
const durationMs = Math.max(0, endedAt - startedAt);
|
|
25
|
+
this.steps[step] = durationMs;
|
|
26
|
+
try {
|
|
27
|
+
this.log(JSON.stringify({
|
|
28
|
+
event: 'cold_start_step',
|
|
29
|
+
sessionId: this.sessionId,
|
|
30
|
+
turnId: this.turnId,
|
|
31
|
+
step,
|
|
32
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
33
|
+
endedAt: new Date(endedAt).toISOString(),
|
|
34
|
+
durationMs
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// A diagnostic sink must never stop an agent turn.
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
summary() {
|
|
43
|
+
try {
|
|
44
|
+
this.log(JSON.stringify({
|
|
45
|
+
event: 'cold_start_summary',
|
|
46
|
+
sessionId: this.sessionId,
|
|
47
|
+
turnId: this.turnId,
|
|
48
|
+
durationMs: Math.max(0, Date.now() - this.startedAt),
|
|
49
|
+
steps: this.steps
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// A diagnostic sink must never stop an agent turn.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.ColdStartSteps = ColdStartSteps;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import type { CodingAgentAdapter } from '../adapters';
|
|
22
22
|
import { SessionModelGateway } from '../adapters/model-gateway';
|
|
23
23
|
import { ArtifactInspector, ConversationStore, EventSink, ModelMessageRecord, Notifier, SecretProvider, Telemetry, TranscriptStore, WorkspaceProvider } from '../ports';
|
|
24
|
-
import { LeaseResponse, ModelMessageInput, TranscriptPartInput, TurnMetadata } from '../types';
|
|
24
|
+
import { LeaseResponse, LeasedTurnInput, ModelMessageInput, TranscriptPartInput, TurnMetadata } from '../types';
|
|
25
25
|
import { KnowledgeToolHost, type KnowledgeControl } from './knowledge-tools';
|
|
26
26
|
import { McpAuthProxy } from './mcp-auth-proxy';
|
|
27
27
|
export interface SessionRunnerDeps {
|
|
@@ -40,6 +40,8 @@ export interface SessionRunnerDeps {
|
|
|
40
40
|
secrets: SecretProvider;
|
|
41
41
|
events: EventSink;
|
|
42
42
|
telemetry: Telemetry;
|
|
43
|
+
/** Send a structured timing event to the control plane without waiting. */
|
|
44
|
+
coldStartLog?: (line: string) => void;
|
|
43
45
|
notifier: Notifier;
|
|
44
46
|
artifacts: ArtifactInspector;
|
|
45
47
|
/**
|
|
@@ -258,6 +260,25 @@ export declare class SessionRunner {
|
|
|
258
260
|
* three are what `answerPendingApproval` needs and cannot otherwise reach.
|
|
259
261
|
*/
|
|
260
262
|
private turnContext;
|
|
263
|
+
private readonly pendingSteers;
|
|
264
|
+
private readonly deferredSteers;
|
|
265
|
+
private steerDispatchChain;
|
|
266
|
+
private nextSteerOrder;
|
|
267
|
+
private steeredPrompts;
|
|
268
|
+
private readonly steerEchoes;
|
|
269
|
+
private steerTurnGeneration;
|
|
270
|
+
private steerAcknowledgementsClosed;
|
|
271
|
+
private waitForSteerAcknowledgements;
|
|
272
|
+
/** A command can arrive on heartbeat or renewal while run() is in drain(). */
|
|
273
|
+
steerMessage(command: {
|
|
274
|
+
sessionId: string;
|
|
275
|
+
turnId: string;
|
|
276
|
+
input: LeasedTurnInput;
|
|
277
|
+
acknowledge: (consumed: boolean) => Promise<'steered' | 'queued'>;
|
|
278
|
+
}): void;
|
|
279
|
+
private rejectLateSteer;
|
|
280
|
+
private forgetSteerEcho;
|
|
281
|
+
private consumesSteerEcho;
|
|
261
282
|
/** The approval currently shown to the user, if any. */
|
|
262
283
|
private openApproval;
|
|
263
284
|
/** The transcript identity of an ordinary agent question, if one is open. */
|
|
@@ -323,6 +344,7 @@ export declare class SessionRunner {
|
|
|
323
344
|
* from the echoed text must not slip past the prefix check on that alone.
|
|
324
345
|
*/
|
|
325
346
|
private isEchoOfTurnPrompt;
|
|
347
|
+
private isExactSteerEcho;
|
|
326
348
|
/**
|
|
327
349
|
* Poll the adapter until the turn goes quiet or the signal aborts.
|
|
328
350
|
*
|