@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
package/dist/commands/lanes.js
CHANGED
|
@@ -31,18 +31,17 @@ import { registerLaneReviewCommand } from "./lanes-review.js";
|
|
|
31
31
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
32
32
|
import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
|
|
33
33
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
34
|
-
import { startOutpostLaneSnapshotRefresh } from "../lib/lanes/outpost-snapshot-refresh.js";
|
|
35
34
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
36
35
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
37
36
|
import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
|
|
38
|
-
import {
|
|
37
|
+
import { OutpostSnapshotInvocationMemo } from "../lib/lanes/outpost-snapshot-memo.js";
|
|
39
38
|
import { createOutpostHireGrantContext, parseOutpostHireGrantContext, selectOutpostHireGrantHierarchy, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
|
|
40
39
|
import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
|
|
41
40
|
import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
|
|
42
41
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
43
42
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
44
43
|
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, REMOTE_LANE_READ_ENV, outpostRejectedEffortOption, parseOutpostPickupPayload, PICKUP_WAIT_MS, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
45
|
-
import { outpostHireSummaryCacheKey,
|
|
44
|
+
import { outpostHireSummaryCacheKey, OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS, readOutpostHireSummaryCache, readOutpostHireSummaryCacheIncludingStale, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
|
|
46
45
|
import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
|
|
47
46
|
import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
|
|
48
47
|
import { parseUserYaml } from "../utils/user-yaml-error.js";
|
|
@@ -69,7 +68,7 @@ import { LanesError } from "../lib/lanes/errors.js";
|
|
|
69
68
|
import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
|
|
70
69
|
import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
|
|
71
70
|
import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
|
|
72
|
-
import {
|
|
71
|
+
import { resolveManagerStatusTarget, } from "../lib/lanes/manager-status-gate.js";
|
|
73
72
|
import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "../lib/lanes/blocker-freshness.js";
|
|
74
73
|
import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
|
|
75
74
|
import { ensureSessionHome } from "../lib/lanes/session-relink.js";
|
|
@@ -85,7 +84,6 @@ const REMOTE_MIRROR_REFRESH_BATCH_SIZE = 100;
|
|
|
85
84
|
const REMOTE_LANE_NOT_FOUND_GRACE_MS = 15 * 60_000;
|
|
86
85
|
/** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
|
|
87
86
|
export { emitLaneDecisionEvents };
|
|
88
|
-
export { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG };
|
|
89
87
|
function withoutProviderHome(value) {
|
|
90
88
|
if (Array.isArray(value))
|
|
91
89
|
return value.map(withoutProviderHome);
|
|
@@ -301,8 +299,6 @@ function resolveDeps(deps = {}) {
|
|
|
301
299
|
})),
|
|
302
300
|
fetchText: deps.fetchText ?? defaultFetchText,
|
|
303
301
|
lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
|
|
304
|
-
outpostSnapshotCacheEnabled: deps.outpostSnapshotCacheEnabled ?? (() => readLanesRegistryFlag(LANE_OUTPOST_SNAPSHOT_CACHE_FLAG, { env })),
|
|
305
|
-
startOutpostLaneSnapshotRefresh: deps.startOutpostLaneSnapshotRefresh ?? ((root) => startOutpostLaneSnapshotRefresh(root, env)),
|
|
306
302
|
storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
|
|
307
303
|
isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
|
|
308
304
|
withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
|
|
@@ -514,7 +510,7 @@ async function readRemoteHireGrantSenior(ctx, hqRoot, seniorLaneId) {
|
|
|
514
510
|
laneIds: [seniorLaneId],
|
|
515
511
|
snapshot: true,
|
|
516
512
|
strictSnapshot: true,
|
|
517
|
-
}, {}, {
|
|
513
|
+
}, {}, { bypassMemo: true });
|
|
518
514
|
}
|
|
519
515
|
catch (error) {
|
|
520
516
|
throw unavailable(isLanesError(error) ? error.error : "outpost_lane_list_unavailable");
|
|
@@ -1053,7 +1049,7 @@ function laneHostFromCtx(ctx, expectedId) {
|
|
|
1053
1049
|
return { kind: "local", id: hostname };
|
|
1054
1050
|
}
|
|
1055
1051
|
async function readOutpostCapacity(ctx, outpostId) {
|
|
1056
|
-
return
|
|
1052
|
+
return readOutpostCapacityUncached(ctx, outpostId);
|
|
1057
1053
|
}
|
|
1058
1054
|
async function readOutpostCapacityUncached(ctx, outpostId) {
|
|
1059
1055
|
let result;
|
|
@@ -1089,39 +1085,6 @@ async function readOutpostCapacityUncached(ctx, outpostId) {
|
|
|
1089
1085
|
}
|
|
1090
1086
|
return snapshot;
|
|
1091
1087
|
}
|
|
1092
|
-
async function readOutpostSnapshotWithCache(ctx, key, isValue, fetch) {
|
|
1093
|
-
let enabled = false;
|
|
1094
|
-
try {
|
|
1095
|
-
enabled = await ctx.outpostSnapshotCacheEnabled();
|
|
1096
|
-
}
|
|
1097
|
-
catch (err) {
|
|
1098
|
-
process.stderr.write(`lanes snapshot cache flag read failed; using uncached Outpost read: ${dispatchErrorMessage(err)}\n`);
|
|
1099
|
-
}
|
|
1100
|
-
if (!enabled)
|
|
1101
|
-
return fetch();
|
|
1102
|
-
return getOrFetchOutpostSnapshot({ hqRoot: ctx.hqRoot(), key, isValue, fetch });
|
|
1103
|
-
}
|
|
1104
|
-
function isLaneRecordArray(value) {
|
|
1105
|
-
if (!Array.isArray(value))
|
|
1106
|
-
return false;
|
|
1107
|
-
try {
|
|
1108
|
-
for (const lane of value)
|
|
1109
|
-
parseLaneRecord(lane);
|
|
1110
|
-
return true;
|
|
1111
|
-
}
|
|
1112
|
-
catch {
|
|
1113
|
-
return false;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
function isOutpostCapacitySnapshot(value, outpostId) {
|
|
1117
|
-
try {
|
|
1118
|
-
const snapshot = parseHostCapacitySnapshot(value);
|
|
1119
|
-
return snapshot.host.kind === "outpost" && snapshot.host.id === outpostId;
|
|
1120
|
-
}
|
|
1121
|
-
catch {
|
|
1122
|
-
return false;
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
1088
|
async function loadHostCapacitySnapshots(ctx, hqRoot, config = loadLanesConfig(hqRoot), localLanes = loadAllLanes(hqRoot)) {
|
|
1126
1089
|
const localHost = laneHostFromCtx(ctx);
|
|
1127
1090
|
let outpostIds;
|
|
@@ -1280,7 +1243,7 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
|
|
|
1280
1243
|
}
|
|
1281
1244
|
async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispatchOptions = {}, options = {}) {
|
|
1282
1245
|
const query = normalizedLaneListQuery(requestedQuery);
|
|
1283
|
-
if (options.
|
|
1246
|
+
if (options.bypassMemo === true) {
|
|
1284
1247
|
return readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions);
|
|
1285
1248
|
}
|
|
1286
1249
|
let invocationMemo = outpostLaneListMemo.get(ctx);
|
|
@@ -1288,10 +1251,10 @@ async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}, dispa
|
|
|
1288
1251
|
invocationMemo = new OutpostSnapshotInvocationMemo();
|
|
1289
1252
|
outpostLaneListMemo.set(ctx, invocationMemo);
|
|
1290
1253
|
}
|
|
1291
|
-
return invocationMemo.getOrCreate(outpostId, query, () =>
|
|
1254
|
+
return invocationMemo.getOrCreate(outpostId, query, () => readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOptions));
|
|
1292
1255
|
}
|
|
1293
|
-
/** Refresh
|
|
1294
|
-
async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
|
|
1256
|
+
/** Refresh this host's selected Outpost mirrors in one bounded owner query per Outpost. */
|
|
1257
|
+
async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, options = {}) {
|
|
1295
1258
|
const result = {
|
|
1296
1259
|
updated: new Map(),
|
|
1297
1260
|
unverifiedRemote: new Map(),
|
|
@@ -1316,11 +1279,21 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
|
|
|
1316
1279
|
.filter((lane) => lane.host.kind === "outpost" &&
|
|
1317
1280
|
(host.outpostId === null || lane.host.id !== host.outpostId) &&
|
|
1318
1281
|
originMatchesThisHost(lane) &&
|
|
1319
|
-
(parentLaneIds === undefined || (lane.senior.kind === "lane" &&
|
|
1282
|
+
((parentLaneIds === undefined || (lane.senior.kind === "lane" &&
|
|
1320
1283
|
lane.senior.id !== undefined &&
|
|
1321
|
-
parentLaneIds.has(lane.senior.id))) &&
|
|
1322
|
-
!isTerminalLaneState(lane.state))
|
|
1323
|
-
.sort((a, b) =>
|
|
1284
|
+
parentLaneIds.has(lane.senior.id))) || options.laneIds?.has(lane.lane_id) === true) &&
|
|
1285
|
+
(options.includeTerminalMirrors === true || !isTerminalLaneState(lane.state)))
|
|
1286
|
+
.sort((a, b) => {
|
|
1287
|
+
const terminalOrder = Number(isTerminalLaneState(a.state)) - Number(isTerminalLaneState(b.state));
|
|
1288
|
+
if (terminalOrder !== 0)
|
|
1289
|
+
return terminalOrder;
|
|
1290
|
+
if (isTerminalLaneState(a.state)) {
|
|
1291
|
+
const updatedAtOrder = Date.parse(b.updated_at) - Date.parse(a.updated_at);
|
|
1292
|
+
if (updatedAtOrder !== 0)
|
|
1293
|
+
return updatedAtOrder;
|
|
1294
|
+
}
|
|
1295
|
+
return a.lane_id.localeCompare(b.lane_id);
|
|
1296
|
+
});
|
|
1324
1297
|
if (candidates.length === 0)
|
|
1325
1298
|
return result;
|
|
1326
1299
|
const enabledByCompany = new Map();
|
|
@@ -1406,7 +1379,13 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
|
|
|
1406
1379
|
: undefined;
|
|
1407
1380
|
const before = readLane(hqRoot, mirror.lane_id);
|
|
1408
1381
|
if (before.host.kind !== "outpost" || before.host.id !== outpostId ||
|
|
1409
|
-
!originMatchesThisHost(before) ||
|
|
1382
|
+
!originMatchesThisHost(before) ||
|
|
1383
|
+
(!options.includeTerminalMirrors && isTerminalLaneState(before.state))) {
|
|
1384
|
+
result.updated.set(mirror.lane_id, before);
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
if (owner.version < before.version) {
|
|
1388
|
+
result.unverifiedRemote.set(mirror.lane_id, "owner_snapshot_behind_mirror");
|
|
1410
1389
|
result.updated.set(mirror.lane_id, before);
|
|
1411
1390
|
continue;
|
|
1412
1391
|
}
|
|
@@ -1424,10 +1403,16 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
|
|
|
1424
1403
|
result.updated.set(mirror.lane_id, before);
|
|
1425
1404
|
continue;
|
|
1426
1405
|
}
|
|
1406
|
+
let ownerSnapshotLostVersionRace = false;
|
|
1427
1407
|
const updated = retryUpdateLane(hqRoot, mirror.lane_id, "reconcile", (current) => {
|
|
1428
1408
|
if (current.host.kind !== "outpost" || current.host.id !== outpostId ||
|
|
1429
|
-
!originMatchesThisHost(current) ||
|
|
1409
|
+
!originMatchesThisHost(current) ||
|
|
1410
|
+
(!options.includeTerminalMirrors && isTerminalLaneState(current.state)))
|
|
1411
|
+
return current;
|
|
1412
|
+
if (owner.version < current.version) {
|
|
1413
|
+
ownerSnapshotLostVersionRace = true;
|
|
1430
1414
|
return current;
|
|
1415
|
+
}
|
|
1431
1416
|
return {
|
|
1432
1417
|
...current,
|
|
1433
1418
|
state: owner.state,
|
|
@@ -1445,6 +1430,9 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
|
|
|
1445
1430
|
} : {}),
|
|
1446
1431
|
};
|
|
1447
1432
|
});
|
|
1433
|
+
if (ownerSnapshotLostVersionRace) {
|
|
1434
|
+
result.unverifiedRemote.set(mirror.lane_id, "owner_snapshot_behind_mirror");
|
|
1435
|
+
}
|
|
1448
1436
|
result.updated.set(mirror.lane_id, updated);
|
|
1449
1437
|
}
|
|
1450
1438
|
}
|
|
@@ -1524,6 +1512,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1524
1512
|
const hostOutpostIdSet = new Set(hostOutpostIds);
|
|
1525
1513
|
const matches = [];
|
|
1526
1514
|
const failedOutpostIds = [];
|
|
1515
|
+
const probedOutpostIds = [];
|
|
1527
1516
|
const lookupOutpostIds = requestedOutpostId !== undefined
|
|
1528
1517
|
? [requestedOutpostId]
|
|
1529
1518
|
: [
|
|
@@ -1537,6 +1526,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1537
1526
|
hierarchy: [laneId],
|
|
1538
1527
|
snapshot: true,
|
|
1539
1528
|
});
|
|
1529
|
+
probedOutpostIds.push(outpostId);
|
|
1540
1530
|
}
|
|
1541
1531
|
catch (err) {
|
|
1542
1532
|
if (requestedOutpostId !== undefined)
|
|
@@ -1555,7 +1545,10 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1555
1545
|
}
|
|
1556
1546
|
}
|
|
1557
1547
|
if (matches.length === 0) {
|
|
1558
|
-
throw commandLaneNotFound(laneId, options.outpostHint === true,
|
|
1548
|
+
throw commandLaneNotFound(laneId, options.outpostHint === true, {
|
|
1549
|
+
...(failedOutpostIds.length > 0 ? { failed_outpost_ids: failedOutpostIds } : {}),
|
|
1550
|
+
...(probedOutpostIds.length > 0 ? { probed_outpost_ids: probedOutpostIds } : {}),
|
|
1551
|
+
});
|
|
1559
1552
|
}
|
|
1560
1553
|
if (matches.length !== 1) {
|
|
1561
1554
|
throw new LanesError("lane_outpost_ambiguous", `More than one registered Outpost reported lane ${laneId} with the same recorded host identity`, { lane_id: laneId, outpost_ids: matches.map((match) => match.outpostId) });
|
|
@@ -3502,7 +3495,6 @@ async function runCreate(ctx, opts) {
|
|
|
3502
3495
|
if (admission.action === "start") {
|
|
3503
3496
|
started = spawnLaneWorker(hqRoot, lane, {
|
|
3504
3497
|
env: ctx.env,
|
|
3505
|
-
managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env }),
|
|
3506
3498
|
modelDeps: {
|
|
3507
3499
|
readFileSync: ctx.readFileSync,
|
|
3508
3500
|
probe: ctx.probeProvider,
|
|
@@ -4276,6 +4268,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4276
4268
|
outpost_id: outpostId,
|
|
4277
4269
|
outpost_command_id: dispatched.commandId,
|
|
4278
4270
|
outpost_transport: "outposts_exec",
|
|
4271
|
+
delivered_to: { host: outpostId, lane_id: ownerLane.lane_id, mechanism: "outposts_exec" },
|
|
4279
4272
|
});
|
|
4280
4273
|
return;
|
|
4281
4274
|
}
|
|
@@ -4306,6 +4299,9 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4306
4299
|
outpost_id: outpostId,
|
|
4307
4300
|
outpost_command_id: dispatched.commandId,
|
|
4308
4301
|
outpost_transport: "outposts_exec",
|
|
4302
|
+
...(action === "message"
|
|
4303
|
+
? { delivered_to: { host: outpostId, lane_id: lane.lane_id, mechanism: "outposts_exec" } }
|
|
4304
|
+
: {}),
|
|
4309
4305
|
});
|
|
4310
4306
|
}
|
|
4311
4307
|
function dispatchedOwnerLane(ctx, laneId, outpostId, rawLane, behavior) {
|
|
@@ -4581,7 +4577,6 @@ async function runPickup(ctx, opts) {
|
|
|
4581
4577
|
try {
|
|
4582
4578
|
return spawnLaneWorker(hqRoot, current, {
|
|
4583
4579
|
env: ctx.env,
|
|
4584
|
-
managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, current, { env: ctx.env }),
|
|
4585
4580
|
modelDeps: {
|
|
4586
4581
|
readFileSync: ctx.readFileSync,
|
|
4587
4582
|
probe: ctx.probeProvider,
|
|
@@ -4626,7 +4621,7 @@ async function runPickup(ctx, opts) {
|
|
|
4626
4621
|
lane: started,
|
|
4627
4622
|
});
|
|
4628
4623
|
}
|
|
4629
|
-
async function runList(ctx, opts
|
|
4624
|
+
async function runList(ctx, opts) {
|
|
4630
4625
|
if (opts.json && opts.human) {
|
|
4631
4626
|
throw new LanesError("conflicting_output_format", "Use either --json or --human for hq lanes list", { json: true, human: true });
|
|
4632
4627
|
}
|
|
@@ -4666,15 +4661,14 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4666
4661
|
const hierarchyIds = opts.hierarchy === undefined
|
|
4667
4662
|
? undefined
|
|
4668
4663
|
: new Set(opts.hierarchy.flatMap((laneId) => selectOutpostHireGrantHierarchy(laneId, laneSource).map((lane) => lane.lane_id)));
|
|
4669
|
-
|
|
4670
|
-
.filter((lane) => {
|
|
4664
|
+
const matchesLaneFilters = (lane, includeState) => {
|
|
4671
4665
|
if (requestedLaneIds.length > 0 && !requestedIds.has(lane.lane_id))
|
|
4672
4666
|
return false;
|
|
4673
|
-
if (opts.active && isTerminalLaneState(lane.state))
|
|
4667
|
+
if (includeState && opts.active && isTerminalLaneState(lane.state))
|
|
4674
4668
|
return false;
|
|
4675
4669
|
if (hierarchyIds && !hierarchyIds.has(lane.lane_id))
|
|
4676
4670
|
return false;
|
|
4677
|
-
if (state && lane.state !== state)
|
|
4671
|
+
if (includeState && state && lane.state !== state)
|
|
4678
4672
|
return false;
|
|
4679
4673
|
const carriesFilter = laneStories(lane).some((story) => (!opts.project || story.project_id === opts.project) &&
|
|
4680
4674
|
(!opts.story || story.story_id === opts.story));
|
|
@@ -4689,8 +4683,11 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4689
4683
|
if (senior && seniorKey(lane.senior) !== seniorKey(senior))
|
|
4690
4684
|
return false;
|
|
4691
4685
|
return true;
|
|
4692
|
-
}
|
|
4686
|
+
};
|
|
4687
|
+
const selectLanes = () => laneSource
|
|
4688
|
+
.filter((lane) => matchesLaneFilters(lane, true))
|
|
4693
4689
|
.sort(compareLaneQueueOrder);
|
|
4690
|
+
let selectedLanes = selectLanes();
|
|
4694
4691
|
if (opts.snapshot) {
|
|
4695
4692
|
printJson({
|
|
4696
4693
|
ok: true,
|
|
@@ -4707,15 +4704,20 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4707
4704
|
const remoteRead = ctx.env[REMOTE_LANE_READ_ENV] === "1";
|
|
4708
4705
|
const remoteRefresh = remoteRead
|
|
4709
4706
|
? { updated: new Map(), unverifiedRemote: new Map(), missingOnOwner: new Map() }
|
|
4710
|
-
: await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(
|
|
4711
|
-
.filter((lane) =>
|
|
4712
|
-
.map((lane) => lane.lane_id))
|
|
4707
|
+
: await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(laneSource
|
|
4708
|
+
.filter((lane) => matchesLaneFilters(lane, false))
|
|
4709
|
+
.map((lane) => lane.lane_id)), {
|
|
4710
|
+
includeTerminalMirrors: true,
|
|
4711
|
+
laneIds: new Set(laneSource
|
|
4712
|
+
.filter((lane) => matchesLaneFilters(lane, false))
|
|
4713
|
+
.map((lane) => lane.lane_id)),
|
|
4714
|
+
});
|
|
4713
4715
|
for (const [laneId, updated] of remoteRefresh.updated) {
|
|
4714
4716
|
const index = laneSource.findIndex((lane) => lane.lane_id === laneId);
|
|
4715
4717
|
if (index >= 0)
|
|
4716
4718
|
laneSource[index] = updated;
|
|
4717
4719
|
}
|
|
4718
|
-
selectedLanes =
|
|
4720
|
+
selectedLanes = selectLanes();
|
|
4719
4721
|
const now = ctx.now();
|
|
4720
4722
|
const host = reconcileHostFromCtx(ctx);
|
|
4721
4723
|
const allLanes = laneSource;
|
|
@@ -4736,33 +4738,7 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4736
4738
|
ownerEligibleLaneIds.add(lane.lane_id);
|
|
4737
4739
|
}
|
|
4738
4740
|
const ownerLanes = selectedLanes.filter((lane) => ownerEligibleLaneIds.has(lane.lane_id));
|
|
4739
|
-
const refreshRemote = ctx.env.HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT === "1";
|
|
4740
|
-
let snapshotCacheEnabled = fastRouteFlags?.snapshotCacheEnabled ?? false;
|
|
4741
|
-
if (fastRouteFlags === undefined &&
|
|
4742
|
-
(refreshRemote || (!opts.fresh && ownerLanes.length > 0))) {
|
|
4743
|
-
try {
|
|
4744
|
-
snapshotCacheEnabled = await ctx.outpostSnapshotCacheEnabled();
|
|
4745
|
-
}
|
|
4746
|
-
catch (err) {
|
|
4747
|
-
process.stderr.write(`lanes snapshot cache flag read failed; using wait-for-remote list: ${dispatchErrorMessage(err)}\n`);
|
|
4748
|
-
}
|
|
4749
|
-
}
|
|
4750
|
-
if (refreshRemote) {
|
|
4751
|
-
if (snapshotCacheEnabled && ownerLanes.length > 0) {
|
|
4752
|
-
const outcomes = await batchOwnerLaneHireFields(ctx, ownerLanes, allLanes, { forceRefresh: true });
|
|
4753
|
-
const failures = [...outcomes.entries()].filter(([, outcome]) => "error" in outcome);
|
|
4754
|
-
if (failures.length > 0) {
|
|
4755
|
-
const firstOutcome = failures[0][1];
|
|
4756
|
-
const firstError = "error" in firstOutcome ? firstOutcome.error : undefined;
|
|
4757
|
-
process.stderr.write(`lanes snapshot refresh failed for ${failures.length} lane(s): ${firstError?.error ?? "outpost_lane_hire_summary_missing"}: ${firstError?.message ?? "owner summary outcome was missing"}\n`);
|
|
4758
|
-
throw firstError ?? new LanesError("outpost_lane_hire_summary_missing", "Background Outpost lane summary refresh returned a missing outcome", { lane_ids: failures.map(([laneId]) => laneId) });
|
|
4759
|
-
}
|
|
4760
|
-
}
|
|
4761
|
-
return;
|
|
4762
|
-
}
|
|
4763
4741
|
const hireByLane = new Map();
|
|
4764
|
-
const remoteSnapshotMetadata = new Map();
|
|
4765
|
-
let remoteLanesLoading = false;
|
|
4766
4742
|
for (const lane of selectedLanes) {
|
|
4767
4743
|
if (!ownerEligibleLaneIds.has(lane.lane_id)) {
|
|
4768
4744
|
hireByLane.set(lane.lane_id, {
|
|
@@ -4772,47 +4748,7 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4772
4748
|
continue;
|
|
4773
4749
|
}
|
|
4774
4750
|
}
|
|
4775
|
-
if (
|
|
4776
|
-
const nowMs = ctx.now().getTime();
|
|
4777
|
-
const cached = readOutpostHireSummaryCacheIncludingStale(hqRoot, nowMs);
|
|
4778
|
-
let refreshNeeded = false;
|
|
4779
|
-
for (const lane of ownerLanes) {
|
|
4780
|
-
if (lane.host.kind !== "outpost")
|
|
4781
|
-
continue;
|
|
4782
|
-
const summary = cached.get(outpostHireSummaryCacheKey(lane.host.id, lane.lane_id));
|
|
4783
|
-
if (!summary) {
|
|
4784
|
-
hireByLane.set(lane.lane_id, {
|
|
4785
|
-
...laneHireFields(lane, allLanes, aliveChildrenByParent),
|
|
4786
|
-
hire_source: "local",
|
|
4787
|
-
});
|
|
4788
|
-
remoteSnapshotMetadata.set(lane.lane_id, { ageSeconds: null, stale: true, loading: true });
|
|
4789
|
-
refreshNeeded = true;
|
|
4790
|
-
remoteLanesLoading = true;
|
|
4791
|
-
continue;
|
|
4792
|
-
}
|
|
4793
|
-
const ageMs = Math.max(0, nowMs - Date.parse(summary.cached_at));
|
|
4794
|
-
const stale = ageMs >= OUTPOST_SNAPSHOT_CACHE_TTL_MS;
|
|
4795
|
-
hireByLane.set(lane.lane_id, {
|
|
4796
|
-
hire_grant: summary.hire_grant,
|
|
4797
|
-
hire_alive_children: summary.hire_alive_children,
|
|
4798
|
-
hire: summary.hire,
|
|
4799
|
-
hire_summary: summary.hire_summary,
|
|
4800
|
-
hire_source: "owner",
|
|
4801
|
-
});
|
|
4802
|
-
remoteSnapshotMetadata.set(lane.lane_id, {
|
|
4803
|
-
ageSeconds: Math.floor(ageMs / 1_000),
|
|
4804
|
-
stale,
|
|
4805
|
-
loading: stale,
|
|
4806
|
-
});
|
|
4807
|
-
if (stale) {
|
|
4808
|
-
refreshNeeded = true;
|
|
4809
|
-
remoteLanesLoading = true;
|
|
4810
|
-
}
|
|
4811
|
-
}
|
|
4812
|
-
if (refreshNeeded)
|
|
4813
|
-
ctx.startOutpostLaneSnapshotRefresh(hqRoot);
|
|
4814
|
-
}
|
|
4815
|
-
else if (ownerLanes.length > 0) {
|
|
4751
|
+
if (ownerLanes.length > 0) {
|
|
4816
4752
|
const ownerSummaries = await batchOwnerLaneHireFields(ctx, ownerLanes, allLanes);
|
|
4817
4753
|
for (const lane of ownerLanes) {
|
|
4818
4754
|
const outcome = ownerSummaries.get(lane.lane_id);
|
|
@@ -4846,15 +4782,6 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4846
4782
|
...(remoteQueued
|
|
4847
4783
|
? { state: "remote", remote_state: lane.state, queued_reason: undefined, queued_details: undefined }
|
|
4848
4784
|
: {}),
|
|
4849
|
-
...(remoteSnapshotMetadata.has(lane.lane_id)
|
|
4850
|
-
? {
|
|
4851
|
-
remote_snapshot_age_s: remoteSnapshotMetadata.get(lane.lane_id).ageSeconds,
|
|
4852
|
-
remote_snapshot_stale: remoteSnapshotMetadata.get(lane.lane_id).stale,
|
|
4853
|
-
...(remoteSnapshotMetadata.get(lane.lane_id).loading
|
|
4854
|
-
? { remote_snapshot_loading: true }
|
|
4855
|
-
: {}),
|
|
4856
|
-
}
|
|
4857
|
-
: {}),
|
|
4858
4785
|
overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, now),
|
|
4859
4786
|
stalled: Boolean(lane.stalled),
|
|
4860
4787
|
// Listing is observational. Reconcile owns durable liveness transitions.
|
|
@@ -4878,8 +4805,7 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4878
4805
|
};
|
|
4879
4806
|
});
|
|
4880
4807
|
const placementCompany = company ?? processLanePlacementCompany(ctx);
|
|
4881
|
-
const placementEnabled = !remoteRead && (
|
|
4882
|
-
await readLanePlacementEnabled(ctx, placementCompany));
|
|
4808
|
+
const placementEnabled = !remoteRead && await readLanePlacementEnabled(ctx, placementCompany);
|
|
4883
4809
|
const host_capacity = placementEnabled
|
|
4884
4810
|
? (await loadHostCapacitySnapshots(ctx, hqRoot, undefined, allLanes)).entries
|
|
4885
4811
|
: undefined;
|
|
@@ -4890,43 +4816,24 @@ async function runList(ctx, opts, fastRouteFlags) {
|
|
|
4890
4816
|
...(remoteRefresh.unverifiedRemote.size > 0
|
|
4891
4817
|
? { unverified_remote_lanes: [...remoteRefresh.unverifiedRemote.keys()] }
|
|
4892
4818
|
: {}),
|
|
4893
|
-
...(remoteLanesLoading ? { remote_lanes_loading: true } : {}),
|
|
4894
4819
|
...(host_capacity !== undefined ? { host_capacity } : {}),
|
|
4895
4820
|
};
|
|
4896
4821
|
if (opts.human)
|
|
4897
|
-
console.log(formatLaneListHuman(lanes
|
|
4822
|
+
console.log(formatLaneListHuman(lanes));
|
|
4898
4823
|
else
|
|
4899
4824
|
printJson(result);
|
|
4900
4825
|
}
|
|
4901
|
-
|
|
4902
|
-
export async function runFastCachedLanesListFromEntrypoint() {
|
|
4903
|
-
try {
|
|
4904
|
-
await runList(resolveDeps(), { json: true }, {
|
|
4905
|
-
snapshotCacheEnabled: true,
|
|
4906
|
-
lanePlacementEnabled: true,
|
|
4907
|
-
});
|
|
4908
|
-
}
|
|
4909
|
-
catch (err) {
|
|
4910
|
-
failJson(err);
|
|
4911
|
-
}
|
|
4912
|
-
}
|
|
4913
|
-
function formatLaneListHuman(lanes, remoteLanesLoading = false) {
|
|
4826
|
+
function formatLaneListHuman(lanes) {
|
|
4914
4827
|
const rows = lanes.map((lane) => {
|
|
4915
4828
|
const ids = lane.story_ids;
|
|
4916
4829
|
const storyLabel = ids.length > 0
|
|
4917
4830
|
? "stories: " + ids.join(", ")
|
|
4918
4831
|
: "no story: " + (lane.no_story_reason ?? "reason not recorded");
|
|
4919
|
-
const snapshotNote = lane.remote_snapshot_stale && lane.remote_snapshot_age_s !== null &&
|
|
4920
|
-
lane.remote_snapshot_age_s !== undefined
|
|
4921
|
-
? ` remote snapshot stale (${lane.remote_snapshot_age_s}s)`
|
|
4922
|
-
: "";
|
|
4923
4832
|
return lane.lane_id + " " + lane.state + " " + lane.provider + " " + lane.project_id +
|
|
4924
|
-
" " + storyLabel
|
|
4833
|
+
" " + storyLabel;
|
|
4925
4834
|
});
|
|
4926
4835
|
if (rows.length === 0)
|
|
4927
4836
|
rows.push("No lanes.");
|
|
4928
|
-
if (remoteLanesLoading)
|
|
4929
|
-
rows.push("Remote lanes loading.");
|
|
4930
4837
|
return rows.join("\n");
|
|
4931
4838
|
}
|
|
4932
4839
|
async function runStatus(ctx, opts) {
|
|
@@ -5121,8 +5028,17 @@ async function runShow(ctx, laneId) {
|
|
|
5121
5028
|
const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
|
|
5122
5029
|
? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete }, storedLane.keep_alive_standing === true && hasPendingKeepAliveWork(hqRoot, storedLane))
|
|
5123
5030
|
: null;
|
|
5124
|
-
|
|
5125
|
-
|
|
5031
|
+
let managerStatusTarget;
|
|
5032
|
+
let managerStatusGateError;
|
|
5033
|
+
try {
|
|
5034
|
+
resolveLaneCompany(hqRoot, storedLane);
|
|
5035
|
+
managerStatusTarget = resolveManagerStatusTarget(hqRoot, storedLane);
|
|
5036
|
+
}
|
|
5037
|
+
catch (err) {
|
|
5038
|
+
managerStatusGateError = err instanceof LanesError ? err.error : "manager_status_gate_unavailable";
|
|
5039
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5040
|
+
process.stderr.write(`hq lanes: manager status gate disabled for ${storedLane.lane_id}: ${managerStatusGateError}: ${message}\n`);
|
|
5041
|
+
}
|
|
5126
5042
|
const blockerFreshness = await resolveBlockerFreshnessForLane(hqRoot, storedLane, {
|
|
5127
5043
|
env: ctx.env,
|
|
5128
5044
|
now: ctx.now().getTime(),
|
|
@@ -5146,10 +5062,12 @@ async function runShow(ctx, laneId) {
|
|
|
5146
5062
|
})
|
|
5147
5063
|
.slice(0, 20)
|
|
5148
5064
|
.map(({ repo, pr, head_sha, recorded_at }) => ({ repo, pr, head_sha, recorded_at }));
|
|
5065
|
+
const laneForShow = { ...lane };
|
|
5066
|
+
delete laneForShow.resume_history;
|
|
5149
5067
|
printJson({
|
|
5150
5068
|
ok: true,
|
|
5151
5069
|
action: "show",
|
|
5152
|
-
lane,
|
|
5070
|
+
lane: laneForShow,
|
|
5153
5071
|
...(laneIsSenior ? { reviews: recentReviews, reviews_skipped: reviewListing.skipped } : {}),
|
|
5154
5072
|
...hire,
|
|
5155
5073
|
overdue: Boolean(lane.overdue) || isOverdue(hqRoot, lane.lane_id, ctx.now()),
|
|
@@ -5163,6 +5081,9 @@ async function runShow(ctx, laneId) {
|
|
|
5163
5081
|
envelope_repair_mode: lane.envelope_repair?.mode ?? null,
|
|
5164
5082
|
envelope_validation_errors: lane.envelope_validation_errors ?? [],
|
|
5165
5083
|
last_resume: lane.last_resume ?? null,
|
|
5084
|
+
// Public JSON key for the bounded start audit; `last_resume` remains unchanged.
|
|
5085
|
+
resume_history: lane.resume_history ?? [],
|
|
5086
|
+
keep_alive_history: lane.keep_alive_history ?? [],
|
|
5166
5087
|
account: lane.account ?? null,
|
|
5167
5088
|
account_history: lane.account_history ?? [],
|
|
5168
5089
|
failed_resume_history: lane.failed_resume_history ?? [],
|
|
@@ -5186,15 +5107,16 @@ async function runShow(ctx, laneId) {
|
|
|
5186
5107
|
(storedLane.keep_alive === true ? null : "not_opted_in"),
|
|
5187
5108
|
consecutive_auto_resumes: lane.keep_alive_resume_count ?? 0,
|
|
5188
5109
|
last_auto_resume_at: lane.keep_alive_last_resume_at ?? null,
|
|
5110
|
+
keep_alive_history: lane.keep_alive_history ?? [],
|
|
5189
5111
|
next_eligible_at: lane.keep_alive_next_eligible_at ?? null,
|
|
5190
5112
|
exhausted_at: lane.keep_alive_exhausted_at ?? null,
|
|
5191
5113
|
},
|
|
5192
|
-
manager_status_gate:
|
|
5114
|
+
manager_status_gate: managerStatusGateError !== undefined
|
|
5193
5115
|
? {
|
|
5194
5116
|
status_path: null,
|
|
5195
5117
|
skipped: null,
|
|
5196
5118
|
consecutive_misses: storedLane.status_gate?.consecutive_misses ?? 0,
|
|
5197
|
-
error:
|
|
5119
|
+
error: managerStatusGateError,
|
|
5198
5120
|
}
|
|
5199
5121
|
: managerStatusTarget?.manager
|
|
5200
5122
|
? {
|
|
@@ -5910,7 +5832,6 @@ async function runAdmit(ctx, laneId, opts) {
|
|
|
5910
5832
|
}
|
|
5911
5833
|
const started = spawnLaneWorker(hqRoot, lane, {
|
|
5912
5834
|
env: ctx.env,
|
|
5913
|
-
managerStatusGateEnabled: await managerStatusGateEnabledForLane(hqRoot, lane, { env: ctx.env }),
|
|
5914
5835
|
modelDeps: {
|
|
5915
5836
|
readFileSync: ctx.readFileSync,
|
|
5916
5837
|
probe: ctx.probeProvider,
|
|
@@ -6274,7 +6195,12 @@ async function prepareAccountMove(ctx, lane, input) {
|
|
|
6274
6195
|
}),
|
|
6275
6196
|
pending_resume: input.deferTurnStart
|
|
6276
6197
|
? undefined
|
|
6277
|
-
: {
|
|
6198
|
+
: {
|
|
6199
|
+
session_id: sessionId,
|
|
6200
|
+
at,
|
|
6201
|
+
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
6202
|
+
...input.pending,
|
|
6203
|
+
},
|
|
6278
6204
|
};
|
|
6279
6205
|
applyResumeProfileAndEffort(current, next, {
|
|
6280
6206
|
...(input.workerProfile !== undefined ? { profile: input.workerProfile } : {}),
|
|
@@ -6917,6 +6843,89 @@ function isDefiniteOutpostLaneNotFound(dispatched, laneId, ownerError) {
|
|
|
6917
6843
|
ownerError?.error === "lane_not_found" &&
|
|
6918
6844
|
ownerError.details.lane_id === laneId;
|
|
6919
6845
|
}
|
|
6846
|
+
async function dispatchPrimaryLaneMessage(ctx, laneId, options) {
|
|
6847
|
+
const outpostId = "primary";
|
|
6848
|
+
const waitMs = outpostSingleLaneWaitMs(ctx.env);
|
|
6849
|
+
let dispatched;
|
|
6850
|
+
try {
|
|
6851
|
+
dispatched = await ctx.dispatchOutpostExec({
|
|
6852
|
+
outpostId,
|
|
6853
|
+
command: buildOutpostLaneCommand({
|
|
6854
|
+
action: "message",
|
|
6855
|
+
laneId,
|
|
6856
|
+
text: options.text,
|
|
6857
|
+
project: options.project,
|
|
6858
|
+
story: options.story,
|
|
6859
|
+
account: options.account,
|
|
6860
|
+
model: options.model,
|
|
6861
|
+
effort: options.effort,
|
|
6862
|
+
worker: options.worker,
|
|
6863
|
+
compact: options.compact,
|
|
6864
|
+
callerLaneId: ctx.env.HQ_LANE_ID?.trim() || undefined,
|
|
6865
|
+
callerSessionId: sessionIdFromEnv(ctx.env),
|
|
6866
|
+
}),
|
|
6867
|
+
timeoutSeconds: Math.ceil(waitMs / 1000),
|
|
6868
|
+
waitMs,
|
|
6869
|
+
});
|
|
6870
|
+
}
|
|
6871
|
+
catch (err) {
|
|
6872
|
+
const message = dispatchErrorMessage(err);
|
|
6873
|
+
throw new LanesError("outpost_dispatch_failed", `Could not dispatch message for lane ${laneId} to primary: ${message}`, { lane_id: laneId, outpost_id: outpostId, wait_ms: waitMs, wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV });
|
|
6874
|
+
}
|
|
6875
|
+
const confirmedCommandId = dispatched.commandId?.trim();
|
|
6876
|
+
const ownerError = ownerErrorFromOutput(dispatched.stdout);
|
|
6877
|
+
if (isDefiniteOutpostLaneNotFound(dispatched, laneId, ownerError)) {
|
|
6878
|
+
throw new LanesError("lane_not_found", ownerError.message, {
|
|
6879
|
+
...ownerError.details,
|
|
6880
|
+
lane_id: laneId,
|
|
6881
|
+
outpost_id: outpostId,
|
|
6882
|
+
command_id: confirmedCommandId,
|
|
6883
|
+
});
|
|
6884
|
+
}
|
|
6885
|
+
if (dispatched.done === true &&
|
|
6886
|
+
Boolean(confirmedCommandId) &&
|
|
6887
|
+
dispatched.status === "Failed" &&
|
|
6888
|
+
dispatched.exitCode !== null &&
|
|
6889
|
+
dispatched.exitCode !== 0 &&
|
|
6890
|
+
ownerError) {
|
|
6891
|
+
throw new LanesError(ownerError.error, ownerError.message, {
|
|
6892
|
+
...ownerError.details,
|
|
6893
|
+
lane_id: laneId,
|
|
6894
|
+
outpost_id: outpostId,
|
|
6895
|
+
command_id: confirmedCommandId,
|
|
6896
|
+
});
|
|
6897
|
+
}
|
|
6898
|
+
if (dispatched.done !== true ||
|
|
6899
|
+
dispatched.status !== "Success" ||
|
|
6900
|
+
dispatched.exitCode !== 0 ||
|
|
6901
|
+
!confirmedCommandId) {
|
|
6902
|
+
throw new LanesError("outpost_delivery_unconfirmed", `Primary did not confirm message delivery for lane ${laneId}`, {
|
|
6903
|
+
lane_id: laneId,
|
|
6904
|
+
outpost_id: outpostId,
|
|
6905
|
+
command_id: confirmedCommandId ?? null,
|
|
6906
|
+
status: dispatched.status,
|
|
6907
|
+
done: dispatched.done ?? false,
|
|
6908
|
+
exit_code: dispatched.exitCode,
|
|
6909
|
+
});
|
|
6910
|
+
}
|
|
6911
|
+
const remote = parseRemoteJson(dispatched.stdout, "Primary lane message");
|
|
6912
|
+
if (remote === null ||
|
|
6913
|
+
remote.ok !== true ||
|
|
6914
|
+
remote.action !== "message" ||
|
|
6915
|
+
remote.lane_id !== laneId ||
|
|
6916
|
+
remote.accepted !== true) {
|
|
6917
|
+
throw new LanesError("outpost_delivery_unconfirmed", `Primary completed exec without a confirmed message result for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, command_id: confirmedCommandId });
|
|
6918
|
+
}
|
|
6919
|
+
if (!options.silent) {
|
|
6920
|
+
printJson({
|
|
6921
|
+
...remote,
|
|
6922
|
+
outpost_id: outpostId,
|
|
6923
|
+
outpost_command_id: confirmedCommandId,
|
|
6924
|
+
outpost_transport: "outposts_exec",
|
|
6925
|
+
delivered_to: { host: outpostId, lane_id: laneId, mechanism: "outposts_exec" },
|
|
6926
|
+
});
|
|
6927
|
+
}
|
|
6928
|
+
}
|
|
6920
6929
|
async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false) {
|
|
6921
6930
|
if (lane.host.kind !== "outpost") {
|
|
6922
6931
|
throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to dispatch stop to`, { lane_id: lane.lane_id });
|
|
@@ -7591,6 +7600,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7591
7600
|
? { model: automatic.preservedModel, model_source: "preserved" }
|
|
7592
7601
|
: {}),
|
|
7593
7602
|
},
|
|
7603
|
+
accountSelection: selection,
|
|
7594
7604
|
};
|
|
7595
7605
|
}
|
|
7596
7606
|
let latest;
|
|
@@ -7608,7 +7618,10 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7608
7618
|
latest.last_resume?.session_id !== lane.last_resume?.session_id)
|
|
7609
7619
|
return { lane: latest };
|
|
7610
7620
|
if (selection.account === lane.account) {
|
|
7611
|
-
return {
|
|
7621
|
+
return {
|
|
7622
|
+
lane: persistClaudeAccountSelection(hqRoot, recoveredLane, selection),
|
|
7623
|
+
accountSelection: selection,
|
|
7624
|
+
};
|
|
7612
7625
|
}
|
|
7613
7626
|
try {
|
|
7614
7627
|
const moved = await prepareAccountMove(ctx, recoveredLane, {
|
|
@@ -7622,7 +7635,7 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7622
7635
|
admissionLocksHeld: true,
|
|
7623
7636
|
accountSelection: selection,
|
|
7624
7637
|
});
|
|
7625
|
-
return { lane: moved.lane };
|
|
7638
|
+
return { lane: moved.lane, accountSelection: selection };
|
|
7626
7639
|
}
|
|
7627
7640
|
catch (err) {
|
|
7628
7641
|
let latest;
|
|
@@ -7830,6 +7843,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7830
7843
|
assessLaneAdmission,
|
|
7831
7844
|
casUpdateLane,
|
|
7832
7845
|
dispatchOutpostLaneCommand,
|
|
7846
|
+
dispatchPrimaryLaneMessage,
|
|
7833
7847
|
drainHookStatusFor,
|
|
7834
7848
|
laneIsHostedHere,
|
|
7835
7849
|
laneWithRecoveredProviderSession,
|
|
@@ -8166,6 +8180,15 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8166
8180
|
.action(wrap(async (laneId, storyId, opts) => {
|
|
8167
8181
|
await storyHandlers.runStoryStart(ctx, laneId, storyId, opts);
|
|
8168
8182
|
}));
|
|
8183
|
+
stories
|
|
8184
|
+
.command("review")
|
|
8185
|
+
.description("Move a carried story to review")
|
|
8186
|
+
.argument("<lane-id>", "Lane id")
|
|
8187
|
+
.argument("<story-id>", "Story id")
|
|
8188
|
+
.option("--project <slug>", "Project id; defaults to the lane's current project")
|
|
8189
|
+
.action(wrap(async (laneId, storyId, opts) => {
|
|
8190
|
+
await storyHandlers.runStoryReview(ctx, laneId, storyId, opts);
|
|
8191
|
+
}));
|
|
8169
8192
|
stories
|
|
8170
8193
|
.command("block")
|
|
8171
8194
|
.description("Mark one carried story blocked")
|