pi-harness-runtime 1.1.459 → 1.1.461
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/CHANGELOG.md +77 -0
- package/harness/pending-countdown.ts +348 -0
- package/harness/pending-wait.ts +69 -0
- package/index.ts +248 -48
- package/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/write-review/package.json +1 -1
package/index.ts
CHANGED
|
@@ -42,6 +42,11 @@ import {
|
|
|
42
42
|
relayShouldBeReported,
|
|
43
43
|
} from "./harness/startup-report.ts";
|
|
44
44
|
import { localSkillsReport, skillsReport } from "./harness/skills-startup.ts";
|
|
45
|
+
import {
|
|
46
|
+
PendingQuestionCountdown,
|
|
47
|
+
type CountdownView,
|
|
48
|
+
} from "./harness/pending-countdown.ts";
|
|
49
|
+
import { endPendingWait } from "./harness/pending-wait.ts";
|
|
45
50
|
import { isChannelDisabled, setChannelDisabled } from "./harness/notification-settings.ts";
|
|
46
51
|
// The Honcho contract lives with the Honcho integration. A type-only import
|
|
47
52
|
// is erased at runtime, so the startup keeps loading the module lazily.
|
|
@@ -292,6 +297,9 @@ const sharedState = {
|
|
|
292
297
|
validationLedger: null as unknown,
|
|
293
298
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
294
299
|
updateAutoContinueStatus: null as any,
|
|
300
|
+
// The live countdown of the pending question. The channels stop it when
|
|
301
|
+
// a human answer arrives.
|
|
302
|
+
pendingCountdown: null as PendingQuestionCountdown | null,
|
|
295
303
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
296
304
|
pqController: null as any,
|
|
297
305
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -335,6 +343,8 @@ function applyChannelApproval(
|
|
|
335
343
|
// cleared after a successful resolution.
|
|
336
344
|
sharedState.waitingForUserSince = null;
|
|
337
345
|
sharedState.updateAutoContinueStatus?.("IDLE");
|
|
346
|
+
// The countdown of the answered question is over with it.
|
|
347
|
+
sharedState.pendingCountdown?.stop();
|
|
338
348
|
|
|
339
349
|
if (result.resolution === "yes") {
|
|
340
350
|
(sharedState.todoContinuationController as any)?.resume?.();
|
|
@@ -932,6 +942,12 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
932
942
|
let lastUserMessage: Date | null = null;
|
|
933
943
|
let autoContinueTimer: NodeJS.Timeout | null = null;
|
|
934
944
|
let _inferenceInProgress = false; // Track for overlap prevention
|
|
945
|
+
// The timing of the wait. The countdown in the status row and the
|
|
946
|
+
// decision of the judge use the same constants, so the displayed
|
|
947
|
+
// deadline and the decision can never drift apart.
|
|
948
|
+
const AUTO_CONTINUE_INTERVAL_MS = 60 * 1000; // Fallback poll: every minute
|
|
949
|
+
const MIN_WAIT_MINUTES = 5; // Minimum wait before auto-continue
|
|
950
|
+
const MAX_WAIT_MINUTES = 30; // After this the work pauses instead of waiting
|
|
935
951
|
// A continuation was dispatched and its turn has not settled yet.
|
|
936
952
|
// The gate owns that state: the status stays "RUN" until the turn
|
|
937
953
|
// settles, and no timer may clear it in the meantime.
|
|
@@ -1023,6 +1039,15 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1023
1039
|
}
|
|
1024
1040
|
});
|
|
1025
1041
|
let _sessionId = Date.now().toString(36); // Track session for cancellation
|
|
1042
|
+
// The session of the Pi runtime. A countdown belongs to the session
|
|
1043
|
+
// that asked the question.
|
|
1044
|
+
let _piSessionId = "";
|
|
1045
|
+
// The session the running countdown belongs to. A countdown of a
|
|
1046
|
+
// session that is gone must not draw its status any more.
|
|
1047
|
+
let _countdownSessionId = "";
|
|
1048
|
+
// The request the row of the footer belongs to. A countdown that
|
|
1049
|
+
// stops must not clear the row of a newer question.
|
|
1050
|
+
let _countdownRowRequestId: string | null = null;
|
|
1026
1051
|
let _lastNotifiedQuestion = ""; // Track last question to prevent repeat notifications
|
|
1027
1052
|
let _completedNotifiedFor: string | null = null; // Track completion notification deduplication
|
|
1028
1053
|
|
|
@@ -1048,36 +1073,191 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1048
1073
|
ERROR: "Auto-continue unavailable",
|
|
1049
1074
|
} as const;
|
|
1050
1075
|
|
|
1076
|
+
// The live countdown of the pending question.
|
|
1077
|
+
//
|
|
1078
|
+
// It owns the deadline of the wait: it shows the remaining time in
|
|
1079
|
+
// the status row once per second and starts the decision of the
|
|
1080
|
+
// judge exactly at the deadline. The fallback poll only takes over
|
|
1081
|
+
// when there is no countdown, so a decision is never started twice.
|
|
1082
|
+
const pendingCountdown = new PendingQuestionCountdown({
|
|
1083
|
+
// The two policies of the wait belong to the countdown, so the
|
|
1084
|
+
// displayed deadline and the deadline of the judge cannot drift
|
|
1085
|
+
// apart.
|
|
1086
|
+
waitMinutes: MIN_WAIT_MINUTES,
|
|
1087
|
+
recheckIntervalMs: AUTO_CONTINUE_INTERVAL_MS,
|
|
1088
|
+
render: (view: CountdownView) => {
|
|
1089
|
+
_countdownRowRequestId = view.requestId;
|
|
1090
|
+
updateAutoContinueStatus(
|
|
1091
|
+
"WAITING",
|
|
1092
|
+
view.requestId.slice(0, 8),
|
|
1093
|
+
view.mode === "recheck"
|
|
1094
|
+
? `Jev will recheck in ${view.remaining}`
|
|
1095
|
+
: `Jev decision in ${view.remaining}`,
|
|
1096
|
+
);
|
|
1097
|
+
},
|
|
1098
|
+
onDeadline: (requestId: string) => {
|
|
1099
|
+
logToFile("[auto-continue]", "The decision deadline was reached:", requestId);
|
|
1100
|
+
checkAndAutoContinue("countdown")
|
|
1101
|
+
.then(() => {
|
|
1102
|
+
// The decision did not start - another one was still in
|
|
1103
|
+
// flight, or the wait was gone before it ran. The
|
|
1104
|
+
// deadline is not lost: the countdown is armed again
|
|
1105
|
+
// and the decision follows.
|
|
1106
|
+
if (pendingCountdown.fired && pendingCountdown.requestId === requestId) {
|
|
1107
|
+
pendingCountdown.startRecheck(requestId);
|
|
1108
|
+
}
|
|
1109
|
+
})
|
|
1110
|
+
.catch((error) => {
|
|
1111
|
+
logToFile("[auto-continue]", "The decision failed:", oneLineError(error));
|
|
1112
|
+
// The decision did not run: the countdown releases the
|
|
1113
|
+
// wait, so the fallback poll can take over again.
|
|
1114
|
+
pendingCountdown.stop();
|
|
1115
|
+
});
|
|
1116
|
+
},
|
|
1117
|
+
isPending: (requestId: string) => {
|
|
1118
|
+
// The wait must still belong to the request: a late tick of
|
|
1119
|
+
// an answered or replaced question must not draw its status
|
|
1120
|
+
// and must not trigger a decision.
|
|
1121
|
+
if (!waitingForUserSince) return false;
|
|
1122
|
+
if (_countdownSessionId !== _piSessionId) return false;
|
|
1123
|
+
const current = sharedState.pqController?.getCurrentQuestion?.();
|
|
1124
|
+
if (!current) return false;
|
|
1125
|
+
return (
|
|
1126
|
+
current.requestId === requestId &&
|
|
1127
|
+
(current.state === "pending" || current.state === "evaluating")
|
|
1128
|
+
);
|
|
1129
|
+
},
|
|
1130
|
+
onStop: (requestId: string | null) => {
|
|
1131
|
+
// The countdown is over: the row is cleared with it, so no
|
|
1132
|
+
// row claims a decision that is not due any more. Only the row
|
|
1133
|
+
// of that countdown is cleared: a row of another state - the
|
|
1134
|
+
// judge continues, for example - belongs to that state, and
|
|
1135
|
+
// the row of a newer question belongs to that question.
|
|
1136
|
+
if (_countdownRowRequestId && requestId && _countdownRowRequestId !== requestId) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
_countdownRowRequestId = null;
|
|
1140
|
+
if (_currentAutoContinueState.startsWith("[WAIT]")) {
|
|
1141
|
+
_currentAutoContinueState = "";
|
|
1142
|
+
_setAutoContinueStatus?.(undefined);
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
onError: (error: unknown) => {
|
|
1146
|
+
logToFile("[auto-continue]", "The countdown failed:", oneLineError(error));
|
|
1147
|
+
},
|
|
1148
|
+
});
|
|
1149
|
+
sharedState.pendingCountdown = pendingCountdown;
|
|
1150
|
+
|
|
1051
1151
|
// Wire shared state for callback handlers
|
|
1052
1152
|
sharedState.waitingForUserSince = waitingForUserSince;
|
|
1053
1153
|
sharedState.updateAutoContinueStatus = updateAutoContinueStatus;
|
|
1054
1154
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1155
|
+
/**
|
|
1156
|
+
* Start the countdown of a pending question.
|
|
1157
|
+
*
|
|
1158
|
+
* The deadline of the decision is derived from the wait that is
|
|
1159
|
+
* already running, so the displayed deadline and the decision of
|
|
1160
|
+
* the judge can never drift apart.
|
|
1161
|
+
*/
|
|
1162
|
+
function startPendingCountdown(requestId: string): void {
|
|
1163
|
+
_countdownSessionId = _piSessionId;
|
|
1164
|
+
const startedAt = waitingForUserSince?.getTime() ?? Date.now();
|
|
1165
|
+
pendingCountdown.start(requestId, startedAt);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* Start the countdown of the next attempt.
|
|
1170
|
+
*
|
|
1171
|
+
* The judge asked to wait longer: the countdown counts down to the
|
|
1172
|
+
* next attempt instead of to the deadline of the decision.
|
|
1173
|
+
*/
|
|
1174
|
+
function startRecheckCountdown(requestId: string): void {
|
|
1175
|
+
_countdownSessionId = _piSessionId;
|
|
1176
|
+
pendingCountdown.startRecheck(requestId);
|
|
1177
|
+
}
|
|
1058
1178
|
|
|
1179
|
+
// Update auto-continue status in TUI footer
|
|
1180
|
+
function updateAutoContinueStatus(
|
|
1181
|
+
state: keyof typeof AC_STATE_MESSAGES,
|
|
1182
|
+
requestId?: string,
|
|
1183
|
+
detail?: string,
|
|
1184
|
+
): void {
|
|
1059
1185
|
const message = AC_STATE_MESSAGES[state];
|
|
1060
1186
|
if (state === "IDLE") {
|
|
1061
|
-
//
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
// Only update if state changed (prevent flicker)
|
|
1070
|
-
if (statusText !== _currentAutoContinueState) {
|
|
1071
|
-
_setAutoContinueStatus(statusText);
|
|
1072
|
-
_currentAutoContinueState = statusText;
|
|
1187
|
+
// Only the row is cleared here: the countdown belongs to the
|
|
1188
|
+
// wait, not to the row. A settled continuation must not stop
|
|
1189
|
+
// the countdown of a question that the agent asked in the
|
|
1190
|
+
// same turn, and it must not blank its row either.
|
|
1191
|
+
if (!pendingCountdown.active) {
|
|
1192
|
+
_currentAutoContinueState = "";
|
|
1193
|
+
_setAutoContinueStatus?.(undefined);
|
|
1073
1194
|
}
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const parts = [`[${AC_STATE[state]}] ${message}`];
|
|
1199
|
+
if (requestId) parts.push(requestId);
|
|
1200
|
+
if (detail) parts.push(detail);
|
|
1201
|
+
const statusText = parts.join(" | ");
|
|
1202
|
+
|
|
1203
|
+
// Only update if state changed (prevent flicker). The state is
|
|
1204
|
+
// kept even without a UI binding: a session that starts without
|
|
1205
|
+
// a TUI shows the current state as soon as the UI is there.
|
|
1206
|
+
if (statusText !== _currentAutoContinueState) {
|
|
1207
|
+
_currentAutoContinueState = statusText;
|
|
1208
|
+
_setAutoContinueStatus?.(statusText);
|
|
1074
1209
|
}
|
|
1075
1210
|
}
|
|
1076
1211
|
|
|
1212
|
+
/**
|
|
1213
|
+
* End the wait for a pending question.
|
|
1214
|
+
*
|
|
1215
|
+
* The countdown, the wait state and the pending question belong
|
|
1216
|
+
* together: when one of them ends, the others end with it. A wait
|
|
1217
|
+
* that outlives its question would let the judge decide about a
|
|
1218
|
+
* question of a session that is gone.
|
|
1219
|
+
*/
|
|
1220
|
+
function endWait(reason: string): void {
|
|
1221
|
+
endPendingWait(reason, {
|
|
1222
|
+
currentQuestion: () => sharedState.pqController?.getCurrentQuestion?.(),
|
|
1223
|
+
expireQuestion: (requestId: string, why: string) =>
|
|
1224
|
+
sharedState.pqController?.expireByTimeout?.(requestId, why) === true,
|
|
1225
|
+
clearWait: () => {
|
|
1226
|
+
waitingForUserSince = null;
|
|
1227
|
+
sharedState.waitingForUserSince = null;
|
|
1228
|
+
_lastNotifiedQuestion = "";
|
|
1229
|
+
},
|
|
1230
|
+
stopCountdown: () => pendingCountdown.stop(),
|
|
1231
|
+
clearStatus: () => updateAutoContinueStatus("IDLE"),
|
|
1232
|
+
logTransition: (requestId: string, outcome: string, why: string) =>
|
|
1233
|
+
logQuestionTransition(requestId, outcome, why),
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1077
1237
|
// Capture UI context from session_start
|
|
1078
1238
|
pi.on("session_start", (_event, ctx) => {
|
|
1239
|
+
// The session of the runtime: the countdown of a question belongs
|
|
1240
|
+
// to the session that asked it.
|
|
1241
|
+
const sessionId = (
|
|
1242
|
+
ctx as { sessionManager?: { getSessionId?: () => string | undefined } }
|
|
1243
|
+
).sessionManager?.getSessionId?.();
|
|
1244
|
+
if (typeof sessionId === "string" && sessionId) {
|
|
1245
|
+
if (_piSessionId && _piSessionId !== sessionId) {
|
|
1246
|
+
// The session changed: the wait of the old session is
|
|
1247
|
+
// over with it. The question is closed and the wait
|
|
1248
|
+
// state is cleared, so the judge is not asked about a
|
|
1249
|
+
// question that nobody can answer any more.
|
|
1250
|
+
endWait("session-switched");
|
|
1251
|
+
}
|
|
1252
|
+
_piSessionId = sessionId;
|
|
1253
|
+
}
|
|
1079
1254
|
if (ctx.hasUI && ctx.ui) {
|
|
1080
1255
|
_setAutoContinueStatus = ctx.ui.setStatus.bind(ctx.ui, "harness-auto-continue");
|
|
1256
|
+
// A session without a TUI collected the state already: it is
|
|
1257
|
+
// shown as soon as the UI is there.
|
|
1258
|
+
if (_currentAutoContinueState) {
|
|
1259
|
+
_setAutoContinueStatus(_currentAutoContinueState);
|
|
1260
|
+
}
|
|
1081
1261
|
}
|
|
1082
1262
|
|
|
1083
1263
|
});
|
|
@@ -1090,34 +1270,21 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1090
1270
|
autoContinueTimer = null;
|
|
1091
1271
|
}
|
|
1092
1272
|
|
|
1093
|
-
// Clear UI binding and status
|
|
1094
|
-
if (_setAutoContinueStatus) {
|
|
1095
|
-
_setAutoContinueStatus(undefined);
|
|
1096
|
-
_setAutoContinueStatus = null;
|
|
1097
|
-
}
|
|
1098
|
-
_currentAutoContinueState = "";
|
|
1099
|
-
|
|
1100
1273
|
// A restart does not survive in memory: the wait state and the
|
|
1101
1274
|
// pending question are gone with the process. The question is
|
|
1102
1275
|
// therefore closed explicitly, so the buttons of that request
|
|
1103
1276
|
// do not stay open and look like a request that is still
|
|
1104
1277
|
// waiting.
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
logQuestionTransition(
|
|
1112
|
-
pending.requestId,
|
|
1113
|
-
expired ? "expired" : "closed",
|
|
1114
|
-
"session-ended",
|
|
1115
|
-
);
|
|
1278
|
+
endWait("session-ended");
|
|
1279
|
+
|
|
1280
|
+
// Clear UI binding and status
|
|
1281
|
+
if (_setAutoContinueStatus) {
|
|
1282
|
+
_setAutoContinueStatus(undefined);
|
|
1283
|
+
_setAutoContinueStatus = null;
|
|
1116
1284
|
}
|
|
1285
|
+
_currentAutoContinueState = "";
|
|
1117
1286
|
|
|
1118
1287
|
// Reset state
|
|
1119
|
-
waitingForUserSince = null;
|
|
1120
|
-
_lastNotifiedQuestion = "";
|
|
1121
1288
|
_inferenceInProgress = false;
|
|
1122
1289
|
});
|
|
1123
1290
|
|
|
@@ -1148,6 +1315,8 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1148
1315
|
waitingForUserSince = null;
|
|
1149
1316
|
sharedState.waitingForUserSince = null;
|
|
1150
1317
|
_lastNotifiedQuestion = "";
|
|
1318
|
+
// The question is answered: the countdown of the wait is over.
|
|
1319
|
+
pendingCountdown.stop();
|
|
1151
1320
|
updateAutoContinueStatus("IDLE");
|
|
1152
1321
|
// Dispatch answer to Pi
|
|
1153
1322
|
const message = answer === "yes" ? "yes" : "not yet";
|
|
@@ -1224,8 +1393,10 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1224
1393
|
// Use question.requestId as the jobId for callback matching
|
|
1225
1394
|
const questionJobId = question.requestId;
|
|
1226
1395
|
|
|
1227
|
-
//
|
|
1228
|
-
|
|
1396
|
+
// The status row counts down to the decision of the judge.
|
|
1397
|
+
// The countdown is bound to this request, so a newer
|
|
1398
|
+
// question replaces it.
|
|
1399
|
+
startPendingCountdown(questionJobId);
|
|
1229
1400
|
|
|
1230
1401
|
// Get task context for additional info
|
|
1231
1402
|
const taskContext = await getTaskContext().catch(() => ({ total: 0, completed: 0, remaining: [] as string[] }));
|
|
@@ -1388,6 +1559,9 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1388
1559
|
if (waitingForUserSince) {
|
|
1389
1560
|
waitingForUserSince = null;
|
|
1390
1561
|
_lastNotifiedQuestion = "";
|
|
1562
|
+
// The user answered in Pi: the countdown of the wait is
|
|
1563
|
+
// over with it.
|
|
1564
|
+
pendingCountdown.stop();
|
|
1391
1565
|
updateAutoContinueStatus("IDLE");
|
|
1392
1566
|
// Cancel pending question on user response
|
|
1393
1567
|
// (User answered via Pi instead of button)
|
|
@@ -1397,18 +1571,22 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1397
1571
|
});
|
|
1398
1572
|
|
|
1399
1573
|
// Periodic check for auto-continue decision
|
|
1400
|
-
const AUTO_CONTINUE_INTERVAL_MS = 60 * 1000; // Check every 1 minute
|
|
1401
|
-
const MIN_WAIT_MINUTES = 5; // Minimum wait before auto-continue
|
|
1402
|
-
const MAX_WAIT_MINUTES = 30; // After this the work pauses instead of waiting
|
|
1403
1574
|
|
|
1404
|
-
const checkAndAutoContinue = async () => {
|
|
1575
|
+
const checkAndAutoContinue = async (source: "countdown" | "poll" = "poll") => {
|
|
1405
1576
|
// Prevent overlap if inference is already in progress
|
|
1406
1577
|
if (_inferenceInProgress) {
|
|
1578
|
+
// A deadline that was blocked is armed again by the caller of
|
|
1579
|
+
// the countdown, so the decision is not lost.
|
|
1407
1580
|
return;
|
|
1408
1581
|
}
|
|
1409
1582
|
|
|
1410
1583
|
if (!waitingForUserSince) return;
|
|
1411
1584
|
|
|
1585
|
+
// The countdown owns the wait while it runs: the fallback poll
|
|
1586
|
+
// only takes over when there is no countdown, so the decision is
|
|
1587
|
+
// never started twice.
|
|
1588
|
+
if (source === "poll" && pendingCountdown.active) return;
|
|
1589
|
+
|
|
1412
1590
|
// Capture session ID at check time for post-inference validation
|
|
1413
1591
|
const checkSessionId = _sessionId;
|
|
1414
1592
|
// Use same hash algorithm as detection
|
|
@@ -1470,6 +1648,9 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1470
1648
|
if (taskContext.total === 0 && !hasPendingQuestion) {
|
|
1471
1649
|
logToFile("[auto-continue]", "No tasks and no pending questions, skipping auto-continue check");
|
|
1472
1650
|
_inferenceInProgress = false;
|
|
1651
|
+
// The countdown is over: the fallback poll takes over the
|
|
1652
|
+
// next attempt.
|
|
1653
|
+
pendingCountdown.stop();
|
|
1473
1654
|
return;
|
|
1474
1655
|
}
|
|
1475
1656
|
|
|
@@ -1624,15 +1805,20 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1624
1805
|
if (waitBelongsToRequest(questionContext?.requestId)) {
|
|
1625
1806
|
waitingForUserSince = null;
|
|
1626
1807
|
sharedState.waitingForUserSince = null;
|
|
1808
|
+
// The request expired: the countdown of the wait
|
|
1809
|
+
// is over with it.
|
|
1810
|
+
pendingCountdown.stop();
|
|
1627
1811
|
updateAutoContinueStatus("IDLE");
|
|
1628
1812
|
}
|
|
1629
1813
|
} else {
|
|
1630
|
-
// The request stays open: the
|
|
1631
|
-
//
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
questionContext
|
|
1635
|
-
|
|
1814
|
+
// The request stays open: the countdown counts down to
|
|
1815
|
+
// the next check, so the retry does not wait for the
|
|
1816
|
+
// fallback poll.
|
|
1817
|
+
if (questionContext?.requestId) {
|
|
1818
|
+
startRecheckCountdown(questionContext.requestId);
|
|
1819
|
+
} else {
|
|
1820
|
+
updateAutoContinueStatus("WAITING");
|
|
1821
|
+
}
|
|
1636
1822
|
}
|
|
1637
1823
|
} else {
|
|
1638
1824
|
// Close the question with the Jev decision. The buttons of
|
|
@@ -1669,6 +1855,9 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1669
1855
|
waitingForUserSince = null;
|
|
1670
1856
|
sharedState.waitingForUserSince = null;
|
|
1671
1857
|
_lastNotifiedQuestion = "";
|
|
1858
|
+
// The stale decision is discarded: the countdown
|
|
1859
|
+
// of the wait is over with it.
|
|
1860
|
+
pendingCountdown.stop();
|
|
1672
1861
|
updateAutoContinueStatus("IDLE");
|
|
1673
1862
|
} else {
|
|
1674
1863
|
logToFile(
|
|
@@ -1692,6 +1881,9 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1692
1881
|
waitingForUserSince = null;
|
|
1693
1882
|
sharedState.waitingForUserSince = null;
|
|
1694
1883
|
_lastNotifiedQuestion = "";
|
|
1884
|
+
// The work pauses for a human: the countdown of
|
|
1885
|
+
// the wait is over with it.
|
|
1886
|
+
pendingCountdown.stop();
|
|
1695
1887
|
updateAutoContinueStatus("IDLE");
|
|
1696
1888
|
}
|
|
1697
1889
|
return;
|
|
@@ -1725,6 +1917,8 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1725
1917
|
waitingForUserSince = null;
|
|
1726
1918
|
sharedState.waitingForUserSince = null;
|
|
1727
1919
|
_lastNotifiedQuestion = "";
|
|
1920
|
+
// The question is closed: the countdown of the wait is over.
|
|
1921
|
+
pendingCountdown.stop();
|
|
1728
1922
|
|
|
1729
1923
|
// The status stays "RUN" until the turn that the steer
|
|
1730
1924
|
// started has settled. A timer would clear it while the
|
|
@@ -1735,6 +1929,9 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
1735
1929
|
_inferenceInProgress = false;
|
|
1736
1930
|
} catch {
|
|
1737
1931
|
_inferenceInProgress = false;
|
|
1932
|
+
// The countdown is over: the fallback poll takes over the next
|
|
1933
|
+
// attempt.
|
|
1934
|
+
pendingCountdown.stop();
|
|
1738
1935
|
updateAutoContinueStatus("ERROR");
|
|
1739
1936
|
addHarnessMessage("Jev", "auto-continue evaluation failed", "err", [
|
|
1740
1937
|
"How to fix: Check logs at /tmp/pi-harness.log for API errors",
|
|
@@ -2108,7 +2305,10 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
|
|
|
2108
2305
|
});
|
|
2109
2306
|
|
|
2110
2307
|
// Start periodic check
|
|
2111
|
-
autoContinueTimer = setInterval(
|
|
2308
|
+
autoContinueTimer = setInterval(
|
|
2309
|
+
() => checkAndAutoContinue("poll"),
|
|
2310
|
+
AUTO_CONTINUE_INTERVAL_MS,
|
|
2311
|
+
);
|
|
2112
2312
|
if (jevKeyMissing) {
|
|
2113
2313
|
// Without a provider the health check cannot run. The missing-key
|
|
2114
2314
|
// message (with its fix commands) stays the visible one.
|
package/package.json
CHANGED