@indigoai-us/hq-cli 5.294.0 → 5.296.0
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 +33 -0
- package/dist/command-catalog.generated.d.ts +70 -9
- package/dist/command-catalog.generated.js +88 -12
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +76 -1
- package/dist/commands/core-job-native.d.ts +7 -0
- package/dist/commands/core-job-native.js +20 -0
- package/dist/commands/core-job-probe-native.d.ts +45 -0
- package/dist/commands/core-job-probe-native.js +564 -0
- package/dist/commands/core-job-remote-launcher.d.ts +7 -0
- package/dist/commands/core-job-remote-launcher.js +51 -0
- package/dist/commands/core-job-run-native.d.ts +47 -0
- package/dist/commands/core-job-run-native.js +1215 -0
- package/dist/commands/core.js +1 -1
- package/dist/commands/doctor.js +0 -74
- package/dist/commands/lanes-message.js +24 -5
- package/dist/commands/lanes-resume.js +1 -3
- package/dist/commands/lanes-shared.d.ts +1 -2
- package/dist/commands/lanes-stories.d.ts +1 -0
- package/dist/commands/lanes-stories.js +40 -1
- package/dist/commands/lanes.d.ts +1 -9
- package/dist/commands/lanes.js +197 -174
- package/dist/commands/run.js +5 -0
- package/dist/fast-scaffold.d.ts +0 -5
- package/dist/fast-scaffold.js +0 -10
- package/dist/index.js +1 -10
- package/dist/lib/core-utils/sentry-report.d.ts +0 -19
- package/dist/lib/core-utils/sentry-report.js +8 -61
- package/dist/lib/doctor/checks/claude-wiring.js +9 -0
- package/dist/lib/lanes/flag-keys.d.ts +2 -0
- package/dist/lib/lanes/flag-keys.js +2 -0
- package/dist/lib/lanes/manager-status-gate.d.ts +0 -17
- package/dist/lib/lanes/manager-status-gate.js +2 -43
- package/dist/lib/lanes/mesh-events.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -0
- package/dist/lib/lanes/outpost-dispatch.js +7 -1
- package/dist/lib/lanes/outpost-hire-cache.d.ts +0 -1
- package/dist/lib/lanes/outpost-hire-cache.js +0 -1
- package/dist/lib/lanes/outpost-snapshot-memo.d.ts +6 -0
- package/dist/lib/lanes/outpost-snapshot-memo.js +14 -0
- package/dist/lib/lanes/reconcile.d.ts +2 -3
- package/dist/lib/lanes/reconcile.js +67 -57
- package/dist/lib/lanes/spawn.d.ts +0 -2
- package/dist/lib/lanes/spawn.js +57 -5
- package/dist/lib/lanes/store.js +54 -0
- package/dist/lib/lanes/stories.d.ts +7 -1
- package/dist/lib/lanes/stories.js +16 -0
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.d.ts +4 -3
- package/dist/lib/lanes/watcher.js +122 -56
- package/dist/lib/mesh/live/daemon/run.js +18 -1
- package/dist/run/env-graph-guard.d.ts +2 -2
- package/dist/run/env-graph-guard.js +5 -2
- package/dist/run/hq-plugin.js +6 -5
- package/dist/utils/cognito-session.d.ts +6 -0
- package/dist/utils/cognito-session.js +9 -0
- package/dist/utils/self-update.js +31 -0
- package/dist/utils/version-gate.js +22 -3
- package/package.json +2 -2
- package/dist/fast-lanes-list-route.d.ts +0 -8
- package/dist/fast-lanes-list-route.js +0 -32
- package/dist/fast-lanes-list.d.ts +0 -3
- package/dist/fast-lanes-list.js +0 -224
- package/dist/lib/doctor/client-health-sentry-flag.d.ts +0 -23
- package/dist/lib/doctor/client-health-sentry-flag.js +0 -99
- package/dist/lib/doctor/client-health-sentry.d.ts +0 -71
- package/dist/lib/doctor/client-health-sentry.js +0 -286
- package/dist/lib/lanes/outpost-snapshot-cache.d.ts +0 -26
- package/dist/lib/lanes/outpost-snapshot-cache.js +0 -215
- package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +0 -21
- package/dist/lib/lanes/outpost-snapshot-refresh.js +0 -329
|
@@ -31,8 +31,7 @@ import { applyDefaultQuotaHold, readQuotaSignal, recordAccountQuota } from "./qu
|
|
|
31
31
|
import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
32
32
|
import { SILENCE_MS, appendWatcherEvent, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, watcherEventsPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
|
|
33
33
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshCompany, } from "./mesh-events.js";
|
|
34
|
-
import { activeLaneStories, laneStories,
|
|
35
|
-
import { resolveManagerStatusGateForLane } from "./manager-status-gate.js";
|
|
34
|
+
import { activeLaneStories, laneStories, terminalEnvelopeStoryStatus } from "./stories.js";
|
|
36
35
|
import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
|
|
37
36
|
import { PROVIDER_REJECTED_REASON, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
38
37
|
/** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
|
|
@@ -48,43 +47,12 @@ export const PICKUP_VALIDATION_MAX_AGE_MS = 15 * 60 * 1000;
|
|
|
48
47
|
export const PICKUP_VALIDATION_TIMEOUT_REASON = "pickup_validation_timeout";
|
|
49
48
|
export const RECONCILE_UNIT_NAME = "hq-lanes-reconcile.service";
|
|
50
49
|
export const RECONCILE_TIMER_NAME = "hq-lanes-reconcile.timer";
|
|
51
|
-
function spawnDepsForHost(deps
|
|
50
|
+
function spawnDepsForHost(deps) {
|
|
52
51
|
return {
|
|
53
52
|
...(deps.spawnLaneDeps ?? {}),
|
|
53
|
+
env: deps.spawnLaneDeps?.env ?? deps.env ?? process.env,
|
|
54
54
|
hostname: deps.host.hostname,
|
|
55
55
|
admissionHost: deps.host,
|
|
56
|
-
...(lane !== undefined && deps.managerStatusGateByLane?.has(lane.lane_id)
|
|
57
|
-
? { managerStatusGateEnabled: deps.managerStatusGateByLane.get(lane.lane_id) === true }
|
|
58
|
-
: {}),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
async function prepareManagerStatusGateStarts(root, lanes, deps, summary) {
|
|
62
|
-
const enabledByLane = new Map();
|
|
63
|
-
let gateFailureRecorded = false;
|
|
64
|
-
if (!deps.dryRun) {
|
|
65
|
-
await Promise.all(lanes.filter((lane) => laneRunsHere(lane, deps.host)).map(async (lane) => {
|
|
66
|
-
const resolution = await resolveManagerStatusGateForLane(root, lane, {
|
|
67
|
-
env: deps.env,
|
|
68
|
-
readFlag: deps.readRegistryFlag,
|
|
69
|
-
onError: (err) => {
|
|
70
|
-
if (gateFailureRecorded)
|
|
71
|
-
return;
|
|
72
|
-
gateFailureRecorded = true;
|
|
73
|
-
const code = err instanceof LanesError ? err.error : "manager_status_gate_unavailable";
|
|
74
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
-
summary.skipped.push({
|
|
76
|
-
kind: "lane",
|
|
77
|
-
id: lane.lane_id,
|
|
78
|
-
why: `manager status gate disabled for this reconcile pass: ${code}: ${message}`,
|
|
79
|
-
});
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
enabledByLane.set(lane.lane_id, resolution.enabled);
|
|
83
|
-
}));
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
...deps,
|
|
87
|
-
managerStatusGateByLane: enabledByLane,
|
|
88
56
|
};
|
|
89
57
|
}
|
|
90
58
|
function logSkip(skip) {
|
|
@@ -854,7 +822,7 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
854
822
|
if (deps.afterLoad)
|
|
855
823
|
await deps.afterLoad();
|
|
856
824
|
await runRunawayToolGuardPass(hqRoot, lanes, config, deps, now, summary);
|
|
857
|
-
const startDeps =
|
|
825
|
+
const startDeps = deps;
|
|
858
826
|
markOverdueLanes(hqRoot, lanes, summary, deps, now);
|
|
859
827
|
await heartbeatAndDeadHosts(hqRoot, lanes, summary, startDeps, now);
|
|
860
828
|
for (const question of questions) {
|
|
@@ -1229,7 +1197,7 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1229
1197
|
classified.account) {
|
|
1230
1198
|
try {
|
|
1231
1199
|
const recovered = await retryLaneOnAlternateAccount(hqRoot, classified, accountRetryReason, "reconcile", {
|
|
1232
|
-
...spawnDepsForHost(deps
|
|
1200
|
+
...spawnDepsForHost(deps),
|
|
1233
1201
|
});
|
|
1234
1202
|
summary.dead_host.push({
|
|
1235
1203
|
id: lane.lane_id,
|
|
@@ -1303,24 +1271,22 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1303
1271
|
}
|
|
1304
1272
|
if (terminalEnvelope && latest) {
|
|
1305
1273
|
const envelope = terminalEnvelope;
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
?
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
}
|
|
1323
|
-
: story);
|
|
1274
|
+
const storyStatus = terminalEnvelopeStoryStatus(current, envelope.decision, envelope.story_status);
|
|
1275
|
+
if (storyStatus !== undefined) {
|
|
1276
|
+
next.story_status = envelope.story_status;
|
|
1277
|
+
next.story_complete = envelope.story_complete;
|
|
1278
|
+
next.story_complete_reason = envelope.story_complete_reason;
|
|
1279
|
+
next.stories = laneStories(current).map((story) => story.project_id === current.project_id && story.story_id === current.story_id
|
|
1280
|
+
? {
|
|
1281
|
+
...story,
|
|
1282
|
+
status: storyStatus,
|
|
1283
|
+
updated_at: nowIso,
|
|
1284
|
+
...(envelope.decision === "blocked"
|
|
1285
|
+
? { blocked_reason: envelope.blocked_reason ?? "" }
|
|
1286
|
+
: { blocked_reason: undefined }),
|
|
1287
|
+
}
|
|
1288
|
+
: story);
|
|
1289
|
+
}
|
|
1324
1290
|
if (approvalGate?.violation) {
|
|
1325
1291
|
next.last_envelope = {
|
|
1326
1292
|
ref: latest.ref,
|
|
@@ -1512,7 +1478,7 @@ async function admitQueued(hqRoot, lanes, summary, deps, now) {
|
|
|
1512
1478
|
nowMs,
|
|
1513
1479
|
});
|
|
1514
1480
|
const spawnFn = deps.spawn ??
|
|
1515
|
-
((root, lane) => spawnLaneWorker(root, lane, spawnDepsForHost(deps
|
|
1481
|
+
((root, lane) => spawnLaneWorker(root, lane, spawnDepsForHost(deps)));
|
|
1516
1482
|
const running = lanes.filter((lane) => (lane.state === "running" ||
|
|
1517
1483
|
hasFreshAdmissionReservation(lane, now.getTime()) ||
|
|
1518
1484
|
hasActivePendingResume(lane, now.getTime())) &&
|
|
@@ -1699,6 +1665,21 @@ function recordKeepAliveEvent(hqRoot, laneId, summary, event) {
|
|
|
1699
1665
|
});
|
|
1700
1666
|
}
|
|
1701
1667
|
}
|
|
1668
|
+
function keepAliveAccountSelectionEvidence(selection) {
|
|
1669
|
+
if (selection === undefined)
|
|
1670
|
+
return undefined;
|
|
1671
|
+
return {
|
|
1672
|
+
account: selection.account,
|
|
1673
|
+
selected_at: selection.selected_at,
|
|
1674
|
+
reason: selection.reason,
|
|
1675
|
+
considered: selection.considered.map((row) => ({
|
|
1676
|
+
account: row.account,
|
|
1677
|
+
live_lanes: row.live_lanes,
|
|
1678
|
+
eligible: row.eligible,
|
|
1679
|
+
reason: row.ineligible_reason ?? null,
|
|
1680
|
+
})),
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1702
1683
|
function keepAliveSkipGuidance(laneId, reason) {
|
|
1703
1684
|
switch (reason) {
|
|
1704
1685
|
case "missing_opt_in_time":
|
|
@@ -1892,10 +1873,12 @@ async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, no
|
|
|
1892
1873
|
return;
|
|
1893
1874
|
}
|
|
1894
1875
|
let current = latest;
|
|
1876
|
+
let accountSelection;
|
|
1895
1877
|
if (current.provider === "claude" && deps.prepareKeepAliveClaudeResume !== undefined) {
|
|
1896
1878
|
try {
|
|
1897
1879
|
const prepared = await deps.prepareKeepAliveClaudeResume(current);
|
|
1898
1880
|
current = prepared.lane;
|
|
1881
|
+
accountSelection = prepared.accountSelection;
|
|
1899
1882
|
}
|
|
1900
1883
|
catch (error) {
|
|
1901
1884
|
markDirty(summary, { kind: "lane", id: lane.lane_id, why: `blocked keep-alive account preparation failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
@@ -1917,8 +1900,16 @@ async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, no
|
|
|
1917
1900
|
const queued = casLaneIf(hqRoot, latest.lane_id, (latestRecord) => latestRecord.version === expectedVersion && latestRecord.state === "blocked" && latestRecord.last_envelope?.decision === "blocked" && latestRecord.last_envelope.at === terminalAt, (current) => ({
|
|
1918
1901
|
...result.lane,
|
|
1919
1902
|
version: current.version,
|
|
1903
|
+
pending_resume: result.lane.pending_resume === undefined
|
|
1904
|
+
? undefined
|
|
1905
|
+
: { ...result.lane.pending_resume, keep_alive: true },
|
|
1920
1906
|
keep_alive_blocked_resume_streak: priorResumes + 1,
|
|
1921
1907
|
keep_alive_resume_count: (current.keep_alive_resume_count ?? 0) + 1,
|
|
1908
|
+
keep_alive_history: appendCappedHistory(current.keep_alive_history ?? [], {
|
|
1909
|
+
at: now.toISOString(),
|
|
1910
|
+
resumed_from: "blocked",
|
|
1911
|
+
account: current.account ?? null,
|
|
1912
|
+
}),
|
|
1922
1913
|
keep_alive_last_resume_at: now.toISOString(),
|
|
1923
1914
|
status_gate_history: appendCappedHistory(current.status_gate_history ?? [], {
|
|
1924
1915
|
event: "keep_alive_blocked_resumed",
|
|
@@ -1935,6 +1926,9 @@ async function resumeBlockedKeepAlive(hqRoot, lane, questions, summary, deps, no
|
|
|
1935
1926
|
at: now.toISOString(),
|
|
1936
1927
|
attempt: (lane.keep_alive_resume_count ?? 0) + 1,
|
|
1937
1928
|
resumed_from: "blocked",
|
|
1929
|
+
...(accountSelection !== undefined
|
|
1930
|
+
? { account_selection: keepAliveAccountSelectionEvidence(accountSelection) }
|
|
1931
|
+
: {}),
|
|
1938
1932
|
});
|
|
1939
1933
|
}
|
|
1940
1934
|
}
|
|
@@ -2390,6 +2384,7 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
2390
2384
|
writeAnsweredQuestions(hqRoot, current, questions);
|
|
2391
2385
|
}
|
|
2392
2386
|
let accountSelectionFailure;
|
|
2387
|
+
let accountSelection;
|
|
2393
2388
|
if (current.provider === "claude" && deps.prepareKeepAliveClaudeResume !== undefined) {
|
|
2394
2389
|
const optInBeforePreparation = current.keep_alive_opted_in_at;
|
|
2395
2390
|
let preparedLane;
|
|
@@ -2397,6 +2392,7 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
2397
2392
|
const prepared = await deps.prepareKeepAliveClaudeResume(current);
|
|
2398
2393
|
preparedLane = prepared.lane;
|
|
2399
2394
|
accountSelectionFailure = prepared.selectionFailure;
|
|
2395
|
+
accountSelection = prepared.accountSelection;
|
|
2400
2396
|
}
|
|
2401
2397
|
catch (err) {
|
|
2402
2398
|
// Account balancing is best-effort for keep-alive. A failure to read
|
|
@@ -2514,12 +2510,18 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
2514
2510
|
pending_resume: {
|
|
2515
2511
|
session_id: currentSessionId,
|
|
2516
2512
|
trigger: carriesAnsweredQuestions ? "answered_questions" : "resume",
|
|
2513
|
+
keep_alive: true,
|
|
2517
2514
|
resumed_from: resumedFrom,
|
|
2518
2515
|
text: resumeText,
|
|
2519
2516
|
compact: true,
|
|
2520
2517
|
at: nowIso,
|
|
2521
2518
|
},
|
|
2522
2519
|
keep_alive_resume_count: attempt,
|
|
2520
|
+
keep_alive_history: appendCappedHistory(latestLane.keep_alive_history ?? [], {
|
|
2521
|
+
at: nowIso,
|
|
2522
|
+
resumed_from: resumedFrom,
|
|
2523
|
+
account: latestLane.account ?? null,
|
|
2524
|
+
}),
|
|
2523
2525
|
keep_alive_last_resume_at: nowIso,
|
|
2524
2526
|
keep_alive_next_eligible_at: nextEligibleAt,
|
|
2525
2527
|
keep_alive_exhausted_at: undefined,
|
|
@@ -2573,6 +2575,9 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
2573
2575
|
attempt,
|
|
2574
2576
|
next_eligible_at: nextEligibleAt,
|
|
2575
2577
|
resumed_from: resumedFrom,
|
|
2578
|
+
...(accountSelection !== undefined
|
|
2579
|
+
? { account_selection: keepAliveAccountSelectionEvidence(accountSelection) }
|
|
2580
|
+
: {}),
|
|
2576
2581
|
});
|
|
2577
2582
|
}
|
|
2578
2583
|
}
|
|
@@ -2604,7 +2609,7 @@ async function resumeAwaiting(hqRoot, lanes, questions, summary, deps, onlyLaneI
|
|
|
2604
2609
|
const throttle = await decideAdmission(hqRoot, config.throttle, throttleDeps, deps.cpuBusyFraction, deps.cpuObservation);
|
|
2605
2610
|
throttleDecision = throttle.decision;
|
|
2606
2611
|
}
|
|
2607
|
-
const spawnFn = deps.spawn ?? ((root, lane) => spawnLaneWorker(root, lane, spawnDepsForHost(deps
|
|
2612
|
+
const spawnFn = deps.spawn ?? ((root, lane) => spawnLaneWorker(root, lane, spawnDepsForHost(deps)));
|
|
2608
2613
|
const nowMs = deps.now().getTime();
|
|
2609
2614
|
const fallbackAnswerAt = new Date(nowMs).toISOString();
|
|
2610
2615
|
const answerAtByLane = new Map();
|
|
@@ -2676,6 +2681,10 @@ async function resumeAwaiting(hqRoot, lanes, questions, summary, deps, onlyLaneI
|
|
|
2676
2681
|
if (!deps.dryRun)
|
|
2677
2682
|
writeAnsweredQuestions(hqRoot, current, questions);
|
|
2678
2683
|
if (queueReason !== undefined) {
|
|
2684
|
+
const answerEnv = deps.spawnLaneDeps?.env ?? deps.env ?? process.env;
|
|
2685
|
+
const answerCaller = typeof answerEnv.HQ_LANE_ID === "string"
|
|
2686
|
+
? answerEnv.HQ_LANE_ID.trim() || "user"
|
|
2687
|
+
: "user";
|
|
2679
2688
|
const queued = casLaneIf(hqRoot, current.lane_id, (latest) => latest.state === "awaiting_input", (latest) => ({
|
|
2680
2689
|
...latest,
|
|
2681
2690
|
state: "queued",
|
|
@@ -2687,6 +2696,7 @@ async function resumeAwaiting(hqRoot, lanes, questions, summary, deps, onlyLaneI
|
|
|
2687
2696
|
? { session_id: latest.provider_session_id }
|
|
2688
2697
|
: {}),
|
|
2689
2698
|
trigger: "answered_questions",
|
|
2699
|
+
caller: answerCaller,
|
|
2690
2700
|
at: answeredAt,
|
|
2691
2701
|
},
|
|
2692
2702
|
}), deps.dryRun);
|
|
@@ -88,8 +88,6 @@ export interface SpawnLaneDeps {
|
|
|
88
88
|
runCompaction?: RunCompactionFn;
|
|
89
89
|
/** Finds a session transcript under an account home. Injected in tests. */
|
|
90
90
|
locateTranscript?: (home: string, sessionId: string) => string | null;
|
|
91
|
-
/** Pre-read by the async command/reconcile boundary before this sync spawn. */
|
|
92
|
-
managerStatusGateEnabled?: boolean;
|
|
93
91
|
}
|
|
94
92
|
export interface ProviderLaunch {
|
|
95
93
|
command: string;
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -354,12 +354,13 @@ function archivedResumeMessageText(hqRoot, laneId, messageId) {
|
|
|
354
354
|
}
|
|
355
355
|
function pendingResumeForRetry(hqRoot, lane, sessionId, at) {
|
|
356
356
|
const last = lane.last_resume;
|
|
357
|
+
const keepAlive = last?.keep_alive === true ? { keep_alive: true } : {};
|
|
357
358
|
if (last === undefined || last.session_id !== sessionId) {
|
|
358
|
-
return { session_id: sessionId, trigger: "resume", resumed_from: "interrupted", at };
|
|
359
|
+
return { session_id: sessionId, trigger: "resume", resumed_from: "interrupted", at, ...keepAlive };
|
|
359
360
|
}
|
|
360
361
|
const compact = last.compact === undefined ? {} : { compact: last.compact };
|
|
361
362
|
if (last.trigger === "answered_questions") {
|
|
362
|
-
return { session_id: sessionId, trigger: "answered_questions", at, ...compact };
|
|
363
|
+
return { session_id: sessionId, trigger: "answered_questions", at, ...compact, ...keepAlive };
|
|
363
364
|
}
|
|
364
365
|
if (last.trigger === "message") {
|
|
365
366
|
if (last.message_id === undefined) {
|
|
@@ -376,6 +377,7 @@ function pendingResumeForRetry(hqRoot, lane, sessionId, at) {
|
|
|
376
377
|
message_id: last.message_id,
|
|
377
378
|
at,
|
|
378
379
|
...compact,
|
|
380
|
+
...keepAlive,
|
|
379
381
|
};
|
|
380
382
|
}
|
|
381
383
|
return {
|
|
@@ -386,6 +388,7 @@ function pendingResumeForRetry(hqRoot, lane, sessionId, at) {
|
|
|
386
388
|
...(last.message_id !== undefined ? { message_id: last.message_id } : {}),
|
|
387
389
|
at,
|
|
388
390
|
...compact,
|
|
391
|
+
...keepAlive,
|
|
389
392
|
};
|
|
390
393
|
}
|
|
391
394
|
/** Change account only after a provider/runtime account failure, then re-admit. */
|
|
@@ -1112,6 +1115,8 @@ export function envelopeRepairPrompt(input) {
|
|
|
1112
1115
|
`Missing expected envelope file: ${input.envelopePath}`,
|
|
1113
1116
|
"The previous envelope is stale and must not be reused or copied into this run's result.",
|
|
1114
1117
|
"Printing an envelope in the conversation or writing it to last-message.txt is not enough; write the JSON object directly to the expected file.",
|
|
1118
|
+
"If the task is unfinished, write decision: done, story_complete: false, and set story_status and story_complete_reason to describe the work that remains. The repair turn itself is never a reason for blocked.",
|
|
1119
|
+
"This repair turn only writes the missing envelope; do not resume the task.",
|
|
1115
1120
|
"Schema:",
|
|
1116
1121
|
JSON.stringify(LANE_ENVELOPE_JSON_SCHEMA, null, 2),
|
|
1117
1122
|
`Write the corrected JSON object to ${input.envelopePath}, overwriting it. ` +
|
|
@@ -2000,10 +2005,30 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2000
2005
|
// Claim running before spawning. Two callers that both observe queued must
|
|
2001
2006
|
// not both start processes; the CAS loser returns without spawn.
|
|
2002
2007
|
const nowIso = (deps.now ?? (() => new Date()))().toISOString();
|
|
2003
|
-
const
|
|
2004
|
-
?
|
|
2005
|
-
:
|
|
2008
|
+
const resumeHistoryTrigger = pendingResume?.keep_alive === true
|
|
2009
|
+
? "keep_alive"
|
|
2010
|
+
: pendingResume?.trigger === "message" || trigger.kind === "message"
|
|
2011
|
+
? "message"
|
|
2012
|
+
: pendingResume?.trigger === "resume" || trigger.kind === "resume"
|
|
2013
|
+
? "resume_cli"
|
|
2014
|
+
: "reconcile";
|
|
2015
|
+
const resumeHistoryCaller = pendingResume?.caller ||
|
|
2016
|
+
(typeof env.HQ_LANE_ID === "string" ? env.HQ_LANE_ID.trim() : "") ||
|
|
2017
|
+
(trigger.kind === "answered_questions" || trigger.kind === "resume" || trigger.kind === "message"
|
|
2018
|
+
? "user"
|
|
2019
|
+
: undefined);
|
|
2020
|
+
let statusGateHeartbeat;
|
|
2021
|
+
try {
|
|
2022
|
+
statusGateHeartbeat = managerStatusGateHeartbeat(hqRoot, existing, laneTurnId, nowIso);
|
|
2023
|
+
}
|
|
2024
|
+
catch (err) {
|
|
2025
|
+
const code = err instanceof LanesError ? err.error : "manager_status_gate_unavailable";
|
|
2026
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2027
|
+
process.stderr.write(`hq lanes: manager status gate disabled for ${lane.lane_id}: ${code}: ${message}\n`);
|
|
2028
|
+
statusGateHeartbeat = undefined;
|
|
2029
|
+
}
|
|
2006
2030
|
let claimed = false;
|
|
2031
|
+
let priorStartRecorded = false;
|
|
2007
2032
|
const claimedRecord = retryUpdateLane(hqRoot, lane.lane_id, "spawn", (current) => {
|
|
2008
2033
|
claimed = false;
|
|
2009
2034
|
if (current.state === "running" && current.account === resolved.id) {
|
|
@@ -2015,6 +2040,12 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2015
2040
|
if (!claimableStatesFor(trigger, current).includes(current.state)) {
|
|
2016
2041
|
throw new LanesError("spawn_state_mismatch", `Cannot mark ${current.lane_id} running from ${current.state}`, { lane_id: current.lane_id, state: current.state, trigger: trigger.kind });
|
|
2017
2042
|
}
|
|
2043
|
+
// The host report is written in the claim CAS before exec proves that a
|
|
2044
|
+
// worker started. Use the prior spawn manifest's turn id (written only
|
|
2045
|
+
// after detachment is proven), with last_resume for legacy manifests.
|
|
2046
|
+
priorStartRecorded = previousSpawn.turn_id !== undefined ||
|
|
2047
|
+
current.last_resume !== undefined ||
|
|
2048
|
+
(current.resume_history?.length ?? 0) > 0;
|
|
2018
2049
|
if (failedResumeClaim &&
|
|
2019
2050
|
(failedResumeExpectedSenior === undefined ||
|
|
2020
2051
|
!sameSeniorRef(current.senior, failedResumeExpectedSenior))) {
|
|
@@ -2102,6 +2133,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2102
2133
|
at: nowIso,
|
|
2103
2134
|
account: resolved.id,
|
|
2104
2135
|
trigger: pendingResume.trigger,
|
|
2136
|
+
...(pendingResume.keep_alive === true ? { keep_alive: true } : {}),
|
|
2105
2137
|
...(pendingResume.message_id !== undefined ? { message_id: pendingResume.message_id } : {}),
|
|
2106
2138
|
...(pendingResume.monitor_event === true ? { monitor_event: true } : {}),
|
|
2107
2139
|
...(pendingResume.resumed_from !== undefined ? { resumed_from: pendingResume.resumed_from } : {}),
|
|
@@ -2247,6 +2279,16 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2247
2279
|
}
|
|
2248
2280
|
throw err;
|
|
2249
2281
|
}
|
|
2282
|
+
if (priorStartRecorded) {
|
|
2283
|
+
retryUpdateLane(hqRoot, lane.lane_id, "spawn", (current) => ({
|
|
2284
|
+
...current,
|
|
2285
|
+
resume_history: appendCappedHistory(current.resume_history ?? [], {
|
|
2286
|
+
at: nowIso,
|
|
2287
|
+
trigger: resumeHistoryTrigger,
|
|
2288
|
+
...(resumeHistoryCaller ? { caller: resumeHistoryCaller } : {}),
|
|
2289
|
+
}),
|
|
2290
|
+
}));
|
|
2291
|
+
}
|
|
2250
2292
|
return laneLaunchResult(readLane(hqRoot, lane.lane_id));
|
|
2251
2293
|
}
|
|
2252
2294
|
/**
|
|
@@ -2550,6 +2592,16 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2550
2592
|
killQuietly(killProcess, workerPid, "worker");
|
|
2551
2593
|
throw err;
|
|
2552
2594
|
}
|
|
2595
|
+
const startedAt = (deps.now ?? (() => new Date()))().toISOString();
|
|
2596
|
+
const callerLaneId = typeof env.HQ_LANE_ID === "string" ? env.HQ_LANE_ID.trim() : "";
|
|
2597
|
+
retryUpdateLane(hqRoot, lane.lane_id, "spawn", (current) => ({
|
|
2598
|
+
...current,
|
|
2599
|
+
resume_history: appendCappedHistory(current.resume_history ?? [], {
|
|
2600
|
+
at: startedAt,
|
|
2601
|
+
trigger: "envelope_repair",
|
|
2602
|
+
...(callerLaneId ? { caller: callerLaneId } : {}),
|
|
2603
|
+
}),
|
|
2604
|
+
}));
|
|
2553
2605
|
return workerPid;
|
|
2554
2606
|
}
|
|
2555
2607
|
export { PROVIDER_HOME_ENV, PROVIDER_COMMAND };
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -751,6 +751,55 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
751
751
|
path: filePath,
|
|
752
752
|
});
|
|
753
753
|
}
|
|
754
|
+
if (isPlainObject(raw.pending_resume) &&
|
|
755
|
+
raw.pending_resume.caller !== undefined) {
|
|
756
|
+
requireString(raw.pending_resume.caller, "pending_resume.caller");
|
|
757
|
+
}
|
|
758
|
+
if (raw.keep_alive_history !== undefined) {
|
|
759
|
+
if (!Array.isArray(raw.keep_alive_history) || raw.keep_alive_history.length > HISTORY_CAP) {
|
|
760
|
+
throw new LaneSchemaError(`keep_alive_history must be an array capped at ${HISTORY_CAP} entries`, {
|
|
761
|
+
path: filePath,
|
|
762
|
+
field: "keep_alive_history",
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
for (const [index, value] of raw.keep_alive_history.entries()) {
|
|
766
|
+
const field = `keep_alive_history[${index}]`;
|
|
767
|
+
if (!isPlainObject(value) ||
|
|
768
|
+
Object.keys(value).some((key) => !["at", "resumed_from", "account"].includes(key)) ||
|
|
769
|
+
(value.resumed_from !== "done" && value.resumed_from !== "interrupted" && value.resumed_from !== "blocked") ||
|
|
770
|
+
(value.account !== null && typeof value.account !== "string")) {
|
|
771
|
+
throw new LaneSchemaError(`${field} must be a keep-alive resume history entry`, {
|
|
772
|
+
path: filePath,
|
|
773
|
+
field,
|
|
774
|
+
value,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
requireTimestamp(value.at, `${field}.at`, filePath);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (raw.resume_history !== undefined) {
|
|
781
|
+
if (!Array.isArray(raw.resume_history) || raw.resume_history.length > HISTORY_CAP) {
|
|
782
|
+
throw new LaneSchemaError(`resume_history must be an array capped at ${HISTORY_CAP} entries`, {
|
|
783
|
+
path: filePath,
|
|
784
|
+
field: "resume_history",
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
for (const [index, value] of raw.resume_history.entries()) {
|
|
788
|
+
const field = `resume_history[${index}]`;
|
|
789
|
+
if (!isPlainObject(value) ||
|
|
790
|
+
Object.keys(value).some((key) => !["at", "trigger", "caller"].includes(key))) {
|
|
791
|
+
throw new LaneSchemaError(`${field} must be a resume history entry`, {
|
|
792
|
+
path: filePath,
|
|
793
|
+
field,
|
|
794
|
+
value,
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
requireTimestamp(value.at, `${field}.at`, filePath);
|
|
798
|
+
requireString(value.trigger, `${field}.trigger`);
|
|
799
|
+
if (value.caller !== undefined)
|
|
800
|
+
requireString(value.caller, `${field}.caller`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
754
803
|
if (raw.stop_history !== undefined) {
|
|
755
804
|
if (!Array.isArray(raw.stop_history)) {
|
|
756
805
|
throw new LaneSchemaError("stop_history must be an array when present", {
|
|
@@ -1141,6 +1190,11 @@ export function mergeLaneRecords(live, sidecars) {
|
|
|
1141
1190
|
failed_resume_history: unionHistory(all.map((record) => record.failed_resume_history ?? []), entryIdentity, base.failed_resume_history ?? []),
|
|
1142
1191
|
}
|
|
1143
1192
|
: {}),
|
|
1193
|
+
...(all.some((record) => record.resume_history !== undefined)
|
|
1194
|
+
? {
|
|
1195
|
+
resume_history: unionHistory(all.map((record) => record.resume_history ?? []), entryIdentity, base.resume_history ?? []),
|
|
1196
|
+
}
|
|
1197
|
+
: {}),
|
|
1144
1198
|
...(all.some((record) => record.settings_history !== undefined)
|
|
1145
1199
|
? {
|
|
1146
1200
|
settings_history: unionHistory(all.map((record) => record.settings_history ?? []), entryIdentity, base.settings_history ?? []),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LaneRecord, LaneStory } from "./types.js";
|
|
1
|
+
import type { LaneRecord, LaneStory, LaneStoryStatus } from "./types.js";
|
|
2
2
|
/** Board-facing states for the stories a lane currently carries. */
|
|
3
3
|
export declare const ACTIVE_LANE_STORY_STATUSES: readonly ["in_progress", "blocked", "review"];
|
|
4
4
|
export declare function storyKey(projectId: string, storyId: string): string;
|
|
@@ -14,4 +14,10 @@ export declare function storyWarning(lane: LaneRecord): string | undefined;
|
|
|
14
14
|
export declare function withCurrentStory(lane: LaneRecord, projectId: string, storyId: string): LaneRecord;
|
|
15
15
|
export declare function withLaneStories(lane: LaneRecord, stories: LaneStory[]): LaneRecord;
|
|
16
16
|
export declare function storyStatusForEnvelope(status: string): "in_progress" | "review";
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the story status requested by a terminal envelope. Already-final
|
|
19
|
+
* carried stories are immutable through the envelope path; senior verification
|
|
20
|
+
* and explicit story commands own those transitions.
|
|
21
|
+
*/
|
|
22
|
+
export declare function terminalEnvelopeStoryStatus(lane: LaneRecord, decision: "done" | "blocked" | "ask", envelopeStatus?: string): LaneStoryStatus | undefined;
|
|
17
23
|
//# sourceMappingURL=stories.d.ts.map
|
|
@@ -73,4 +73,20 @@ export function storyStatusForEnvelope(status) {
|
|
|
73
73
|
return "review";
|
|
74
74
|
return "in_progress";
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the story status requested by a terminal envelope. Already-final
|
|
78
|
+
* carried stories are immutable through the envelope path; senior verification
|
|
79
|
+
* and explicit story commands own those transitions.
|
|
80
|
+
*/
|
|
81
|
+
export function terminalEnvelopeStoryStatus(lane, decision, envelopeStatus = "in_progress") {
|
|
82
|
+
const primary = laneStory(lane, lane.project_id, lane.story_id);
|
|
83
|
+
if (primary && (String(primary.status) === "done" || String(primary.status) === "dropped")) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
if (decision === "blocked")
|
|
87
|
+
return "blocked";
|
|
88
|
+
if (decision === "done")
|
|
89
|
+
return "review";
|
|
90
|
+
return storyStatusForEnvelope(envelopeStatus);
|
|
91
|
+
}
|
|
76
92
|
//# sourceMappingURL=stories.js.map
|
|
@@ -407,6 +407,19 @@ export interface KeepAliveBlockedHistoryEntry {
|
|
|
407
407
|
resume_skipped_reason?: "open_question" | "blocked_grace";
|
|
408
408
|
resume_queued?: boolean;
|
|
409
409
|
}
|
|
410
|
+
export interface KeepAliveResumeHistoryEntry {
|
|
411
|
+
at: string;
|
|
412
|
+
resumed_from: "done" | "interrupted" | "blocked";
|
|
413
|
+
account: string | null;
|
|
414
|
+
}
|
|
415
|
+
/** Bounded audit entry for every lane worker start after its first start. */
|
|
416
|
+
export interface LaneResumeHistoryEntry {
|
|
417
|
+
at: string;
|
|
418
|
+
/** Stable trigger class; path-specific starts may use their path name. */
|
|
419
|
+
trigger: string;
|
|
420
|
+
/** HQ lane id for an agent caller, or `user` for an explicit human CLI action. */
|
|
421
|
+
caller?: string;
|
|
422
|
+
}
|
|
410
423
|
export interface KrFreshnessHistoryEntry {
|
|
411
424
|
event: "kr_stalled";
|
|
412
425
|
turn_id: string;
|
|
@@ -418,7 +431,13 @@ export interface KrFreshnessHistoryEntry {
|
|
|
418
431
|
resume_queued?: boolean;
|
|
419
432
|
resume_skipped_reason?: "open_question" | "blocked" | "miss_limit" | "no_provider_session";
|
|
420
433
|
}
|
|
421
|
-
export
|
|
434
|
+
export interface EnvelopeRepairContinueHistoryEntry {
|
|
435
|
+
event: "envelope_repair_continued";
|
|
436
|
+
at: string;
|
|
437
|
+
envelope_ref: string;
|
|
438
|
+
session_id: string;
|
|
439
|
+
}
|
|
440
|
+
export type LaneGateHistoryEntry = ManagerStatusGateHistoryEntry | BlockerFreshnessHistoryEntry | KrFreshnessHistoryEntry | EnvelopeRepairContinueHistoryEntry | KeepAliveBlockedHistoryEntry;
|
|
422
441
|
export interface LaneStory {
|
|
423
442
|
project_id: string;
|
|
424
443
|
story_id: string;
|
|
@@ -491,6 +510,8 @@ export interface LaneRecord {
|
|
|
491
510
|
keep_alive_blocked_reason?: "not_opted_in" | "missing_opt_in_time" | "envelope_before_opt_in" | "interruption_before_opt_in" | "missing_interruption_time" | "retired" | "superseded" | "retired_list_invalid" | "terminal_state";
|
|
492
511
|
/** Consecutive automatic keep-alive resumes since the last human reset. */
|
|
493
512
|
keep_alive_resume_count?: number;
|
|
513
|
+
/** Bounded audit trail of automatic keep-alive resumes; human resets never clear it. */
|
|
514
|
+
keep_alive_history?: KeepAliveResumeHistoryEntry[];
|
|
494
515
|
/** Blocked keep-alive resumes in the current blocked episode. */
|
|
495
516
|
keep_alive_blocked_resume_streak?: number;
|
|
496
517
|
/** Time the most recent automatic keep-alive resume was queued. */
|
|
@@ -646,6 +667,8 @@ export interface LaneRecord {
|
|
|
646
667
|
provider_home_at_spawn?: string;
|
|
647
668
|
/** Set at spawn when this run continued a recorded session instead of starting fresh. */
|
|
648
669
|
last_resume?: LaneResumeRecord;
|
|
670
|
+
/** Last 50 lane worker starts after the initial start, including actor provenance. */
|
|
671
|
+
resume_history?: LaneResumeHistoryEntry[];
|
|
649
672
|
/** Cache counters from the last exited run's result. Nothing else from the payload. */
|
|
650
673
|
last_run_usage?: LaneRunUsage;
|
|
651
674
|
/** Completion time paired with last_run_usage; written by the watcher. */
|
|
@@ -700,6 +723,8 @@ export interface PendingResume {
|
|
|
700
723
|
/** Fresh answered-question launches need no previous session id. */
|
|
701
724
|
session_id?: string;
|
|
702
725
|
trigger: "answered_questions" | "message" | "resume";
|
|
726
|
+
/** True when this queued resume was initiated automatically by keep-alive. */
|
|
727
|
+
keep_alive?: true;
|
|
703
728
|
resumed_from?: "interrupted" | "done" | "blocked" | "failed";
|
|
704
729
|
from_failed?: true;
|
|
705
730
|
/** Senior that authorized a failed-lane resume, rechecked at the claim CAS. */
|
|
@@ -708,6 +733,8 @@ export interface PendingResume {
|
|
|
708
733
|
message_id?: string;
|
|
709
734
|
/** The text is already framed as untrusted monitor data, not an operator instruction. */
|
|
710
735
|
monitor_event?: true;
|
|
736
|
+
/** Caller identity captured when a deferred start was requested, when known. */
|
|
737
|
+
caller?: string;
|
|
711
738
|
/** False when the command passed --no-compact; the deferred spawn honours it. */
|
|
712
739
|
compact?: boolean;
|
|
713
740
|
at: string;
|
|
@@ -727,6 +754,8 @@ export interface LaneResumeRecord {
|
|
|
727
754
|
/** Account the resumed run was bound to. */
|
|
728
755
|
account?: string;
|
|
729
756
|
trigger: "answered_questions" | "message" | "resume";
|
|
757
|
+
/** True when this was an automatic keep-alive resume. */
|
|
758
|
+
keep_alive?: true;
|
|
730
759
|
message_id?: string;
|
|
731
760
|
monitor_event?: true;
|
|
732
761
|
/**
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -225,7 +225,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
225
225
|
pickup_validation_pending: ["creator", "command", "spawn", "reconcile"],
|
|
226
226
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
227
227
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
228
|
-
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
228
|
+
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_history: ["reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
229
229
|
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
230
230
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command"],
|
|
231
231
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
@@ -233,7 +233,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
233
233
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
234
234
|
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command", "reconcile"], settings_history: ["command"], pending_settings: ["command", "spawn"], interrupted_at: ["command", "watcher", "reconcile"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
235
235
|
quota_reset_at: ["watcher", "reconcile"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
|
|
236
|
-
provider_session_id: ["watcher", "command", "spawn"], provider_session_id_absent: ["watcher", "command"], provider_home_at_spawn: ["spawn"], last_resume: ["spawn"], last_run_usage: ["watcher"], last_run_usage_at: ["watcher"], envelope_repair: ["watcher", "reconcile"], envelope_validation_errors: ["watcher"], last_progress_event_at: ["watcher"],
|
|
236
|
+
provider_session_id: ["watcher", "command", "spawn"], provider_session_id_absent: ["watcher", "command"], provider_home_at_spawn: ["spawn"], last_resume: ["spawn"], resume_history: ["spawn", "watcher"], last_run_usage: ["watcher"], last_run_usage_at: ["watcher"], envelope_repair: ["watcher", "reconcile"], envelope_validation_errors: ["watcher"], last_progress_event_at: ["watcher"],
|
|
237
237
|
};
|
|
238
238
|
function isPlainObject(value) {
|
|
239
239
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -41,6 +41,7 @@ import { type EnvelopeRepairSpawnInput, type SpawnLaneDeps } from "./spawn.js";
|
|
|
41
41
|
/** Spec §5.7: no signal for 20 minutes while the process is still alive. */
|
|
42
42
|
export declare const SILENCE_MS: number;
|
|
43
43
|
export declare const WATCHER_POLL_MS = 5000;
|
|
44
|
+
export declare const ENVELOPE_REPAIR_CONTINUE_TEXT = "continue the task; the previous turn ended without an envelope.";
|
|
44
45
|
/** Synced last_host_report_at cadence. Reconcile dead-host TTL is 20 minutes. */
|
|
45
46
|
export declare const WATCHER_HEARTBEAT_MS: number;
|
|
46
47
|
/**
|
|
@@ -49,7 +50,7 @@ export declare const WATCHER_HEARTBEAT_MS: number;
|
|
|
49
50
|
* once the file would exceed this, the oldest complete lines are dropped.
|
|
50
51
|
*/
|
|
51
52
|
export declare const WATCHER_OUT_MAX_BYTES: number;
|
|
52
|
-
export declare const WATCHER_DECISION_EVENTS: readonly ["liveness_transition", "silence_deadline", "envelope_observed", "envelope_invalid", "envelope_repair", "classification", "terminal_write", "message_recovery", "message_recovery_failed", "message_undeliverable", "mesh_event", "mesh_event_failed"];
|
|
53
|
+
export declare const WATCHER_DECISION_EVENTS: readonly ["liveness_transition", "silence_deadline", "envelope_observed", "envelope_invalid", "envelope_repair", "envelope_repair_continue", "classification", "terminal_write", "message_recovery", "message_recovery_failed", "message_undeliverable", "mesh_event", "mesh_event_failed"];
|
|
53
54
|
/** Terminal reason when the envelope failed to parse after the repair turn (spec §4). */
|
|
54
55
|
export { ENVELOPE_INVALID_REASON };
|
|
55
56
|
export type WatcherDecisionEvent = (typeof WATCHER_DECISION_EVENTS)[number];
|
|
@@ -232,11 +233,11 @@ export interface WatcherLoopDeps {
|
|
|
232
233
|
/** Deliver a lane-senior escalation through the normal lane-message router. */
|
|
233
234
|
deliverLaneSeniorMessage?: (seniorLaneId: string, text: string) => Promise<void>;
|
|
234
235
|
/** Test seam; production reads the company-scoped hq-flags row. */
|
|
235
|
-
readManagerStatusGateFlag?: typeof readLanesRegistryFlag;
|
|
236
|
-
/** Test seam; production reads the company-scoped hq-flags row. */
|
|
237
236
|
readBlockerFreshnessFlag?: typeof readLanesRegistryFlag;
|
|
238
237
|
/** Test seam; production reads the company-scoped hq-flags row. */
|
|
239
238
|
readKrFreshnessFlag?: typeof readLanesRegistryFlag;
|
|
239
|
+
/** Test seam; production reads the company-scoped hq-flags row. */
|
|
240
|
+
readEnvelopeRepairContinueFlag?: typeof readLanesRegistryFlag;
|
|
240
241
|
}
|
|
241
242
|
/**
|
|
242
243
|
* Wrap one mesh call. A failed emit is logged as `mesh_event_failed` with
|