@indigoai-us/hq-cli 5.222.0 → 5.224.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 +24 -1
- package/dist/command-catalog.generated.d.ts +24 -0
- package/dist/command-catalog.generated.js +31 -0
- package/dist/commands/lanes-import.d.ts +15 -0
- package/dist/commands/lanes-import.js +1213 -0
- package/dist/commands/lanes.js +164 -50
- package/dist/lib/daemon/hq-cloud.d.ts +2 -0
- package/dist/lib/daemon/hq-cloud.js +7 -1
- package/dist/lib/daemon/run.js +8 -0
- package/dist/lib/lanes/reconcile.js +1 -1
- package/dist/lib/lanes/spawn.js +62 -11
- package/dist/lib/lanes/types.d.ts +3 -3
- package/dist/lib/lanes/watch.d.ts +18 -1
- package/dist/lib/lanes/watch.js +122 -2
- package/package.json +1 -1
package/dist/commands/lanes.js
CHANGED
|
@@ -15,6 +15,7 @@ import { Option } from "commander";
|
|
|
15
15
|
import { registerOutpostPrunerCommand } from "./outpost-pruner.js";
|
|
16
16
|
import { registerLaneChecksCommand } from "./lanes-checks.js";
|
|
17
17
|
import { registerLaneReportCommands } from "./lanes-report.js";
|
|
18
|
+
import { registerLaneTrackerImportCommand } from "./lanes-import.js";
|
|
18
19
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
19
20
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
20
21
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
@@ -42,7 +43,7 @@ import { effectiveAccountHome } from "../lib/lanes/account-home.js";
|
|
|
42
43
|
import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
|
|
43
44
|
import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
|
|
44
45
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
45
|
-
import { keepAliveExclusion, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
46
|
+
import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
46
47
|
import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
47
48
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
48
49
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
@@ -1262,10 +1263,75 @@ function resolveMonitorEngine(requested, env) {
|
|
|
1262
1263
|
}
|
|
1263
1264
|
async function runWatch(ctx, laneIds, opts) {
|
|
1264
1265
|
const rawInterval = opts.intervalMs === undefined ? undefined : Number(opts.intervalMs);
|
|
1265
|
-
|
|
1266
|
+
const hqRoot = ctx.hqRoot();
|
|
1267
|
+
await runLaneWatch(hqRoot, laneIds, {
|
|
1266
1268
|
intervalMs: rawInterval,
|
|
1267
1269
|
sleep: ctx.sleep,
|
|
1270
|
+
keepAliveEnabled: (lane) => readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
|
|
1271
|
+
company: lane.company,
|
|
1272
|
+
env: ctx.env,
|
|
1273
|
+
}),
|
|
1274
|
+
readAccountStatuses: async (lanes) => {
|
|
1275
|
+
if (lanes.length === 0)
|
|
1276
|
+
return [];
|
|
1277
|
+
const config = loadLanesConfig(hqRoot);
|
|
1278
|
+
const currentHost = laneHostFromCtx(ctx);
|
|
1279
|
+
const currentHostname = ctx.hostname().trim();
|
|
1280
|
+
const isCurrentHost = (host) => (host.kind === currentHost.kind && host.id === currentHost.id) ||
|
|
1281
|
+
(host.kind === "local" && host.id === currentHostname);
|
|
1282
|
+
const remoteOutpostIds = [...new Set(lanes
|
|
1283
|
+
.filter((lane) => lane.host.kind === "outpost" && !isCurrentHost(lane.host))
|
|
1284
|
+
.map((lane) => lane.host.id))];
|
|
1285
|
+
const remoteOutposts = await Promise.all(remoteOutpostIds.map(async (id) => [id, await readOutpostCapacity(ctx, id)]));
|
|
1286
|
+
const outpostSnapshots = new Map(remoteOutposts);
|
|
1287
|
+
const remoteLocalLanes = lanes.filter((lane) => lane.host.kind === "local" && !isCurrentHost(lane.host));
|
|
1288
|
+
const remoteLocalHosts = remoteLocalLanes.length > 0
|
|
1289
|
+
? await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config)
|
|
1290
|
+
: undefined;
|
|
1291
|
+
const localStatusCache = new Map();
|
|
1292
|
+
return lanes.map((lane) => {
|
|
1293
|
+
if (config.providers[lane.provider].accounts[lane.account] === undefined) {
|
|
1294
|
+
throw new LanesError("watch_account_unconfigured", `Lane ${lane.lane_id} records unconfigured ${lane.provider} account ${lane.account}`, { lane_id: lane.lane_id, provider: lane.provider, account: lane.account, host: lane.host });
|
|
1295
|
+
}
|
|
1296
|
+
let loggedIn;
|
|
1297
|
+
if (isCurrentHost(lane.host)) {
|
|
1298
|
+
const key = JSON.stringify([lane.provider, lane.account, currentHost.kind, currentHost.id]);
|
|
1299
|
+
if (!localStatusCache.has(key)) {
|
|
1300
|
+
localStatusCache.set(key, accountStatus(lane.provider, lane.account, config, {
|
|
1301
|
+
existsSync: ctx.existsSync,
|
|
1302
|
+
probe: ctx.probeProvider,
|
|
1303
|
+
env: ctx.env,
|
|
1304
|
+
hostname: currentHostname,
|
|
1305
|
+
}).logged_in);
|
|
1306
|
+
}
|
|
1307
|
+
loggedIn = localStatusCache.get(key);
|
|
1308
|
+
}
|
|
1309
|
+
else if (lane.host.kind === "outpost") {
|
|
1310
|
+
loggedIn = outpostSnapshots.get(lane.host.id)?.providers[lane.provider].accounts[lane.account]?.logged_in;
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
const snapshot = remoteLocalHosts?.snapshots.find((candidate) => candidate.capacity.host.kind === lane.host.kind &&
|
|
1314
|
+
candidate.capacity.host.id === lane.host.id);
|
|
1315
|
+
if (!snapshot) {
|
|
1316
|
+
const gap = remoteLocalHosts?.unavailableHosts.find((candidate) => candidate.host.kind === lane.host.kind && candidate.host.id === lane.host.id);
|
|
1317
|
+
throw new LanesError("watch_account_host_unavailable", `Cannot check ${lane.provider} account ${lane.account} on ${lane.host.kind}:${lane.host.id}${gap ? `: ${gap.reason}` : ""}`, { lane_id: lane.lane_id, provider: lane.provider, account: lane.account, host: lane.host });
|
|
1318
|
+
}
|
|
1319
|
+
loggedIn = snapshot.capacity.providers[lane.provider].accounts[lane.account]?.logged_in;
|
|
1320
|
+
}
|
|
1321
|
+
if (loggedIn === undefined) {
|
|
1322
|
+
throw new LanesError("watch_account_unconfigured", `Lane ${lane.lane_id} records unconfigured ${lane.provider} account ${lane.account} on ${lane.host.kind}:${lane.host.id}`, { lane_id: lane.lane_id, provider: lane.provider, account: lane.account, host: lane.host });
|
|
1323
|
+
}
|
|
1324
|
+
return {
|
|
1325
|
+
lane_id: lane.lane_id,
|
|
1326
|
+
provider: lane.provider,
|
|
1327
|
+
account: lane.account,
|
|
1328
|
+
host: lane.host,
|
|
1329
|
+
logged_in: loggedIn,
|
|
1330
|
+
};
|
|
1331
|
+
});
|
|
1332
|
+
},
|
|
1268
1333
|
writeLine: (line) => console.log(opts.json ? JSON.stringify({ event: line }) : line),
|
|
1334
|
+
writeAccountEvent: (event) => console.log(opts.json ? JSON.stringify(event) : `account_logged_out provider=${event.provider} account=${event.account} host=${event.host.kind}:${event.host.id} lane_ids=${event.lane_ids.join(",")}`),
|
|
1269
1335
|
writeError: (line) => process.stderr.write(`${line}\n`),
|
|
1270
1336
|
});
|
|
1271
1337
|
return 0;
|
|
@@ -5002,7 +5068,8 @@ function resolveResumeProfile(ctx, lane, workerId) {
|
|
|
5002
5068
|
function validateResumeProfileModel(ctx, lane, profile) {
|
|
5003
5069
|
if (profile.meta.provider !== lane.provider ||
|
|
5004
5070
|
profile.meta.model === undefined ||
|
|
5005
|
-
lane.model_source === "cli"
|
|
5071
|
+
lane.model_source === "cli" ||
|
|
5072
|
+
lane.model_source === "preserved") {
|
|
5006
5073
|
return;
|
|
5007
5074
|
}
|
|
5008
5075
|
const hqRoot = ctx.hqRoot();
|
|
@@ -5036,7 +5103,7 @@ function applyResumeProfileAndEffort(current, next, input) {
|
|
|
5036
5103
|
: undefined;
|
|
5037
5104
|
next.keep_alive_blocked_reason = undefined;
|
|
5038
5105
|
}
|
|
5039
|
-
if (next.model_source !== "cli") {
|
|
5106
|
+
if (next.model_source !== "cli" && next.model_source !== "preserved") {
|
|
5040
5107
|
if (profile.meta.provider === current.provider && profile.meta.model !== undefined) {
|
|
5041
5108
|
next.model = profile.meta.model;
|
|
5042
5109
|
next.model_source = "profile";
|
|
@@ -5130,7 +5197,7 @@ function resolveResumeEffort(ctx, lane, input) {
|
|
|
5130
5197
|
? undefined
|
|
5131
5198
|
: providerConfig.accounts[accountId];
|
|
5132
5199
|
const profileDefaultsApply = input.profile?.profile.meta.provider === lane.provider;
|
|
5133
|
-
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
|
|
5200
|
+
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
5134
5201
|
? lane.model
|
|
5135
5202
|
: undefined;
|
|
5136
5203
|
const model = resolveLaneModel({
|
|
@@ -5225,11 +5292,11 @@ async function prepareAccountMove(ctx, lane, input) {
|
|
|
5225
5292
|
? loadWorkerProfile(hqRoot, lane.worker_profile)
|
|
5226
5293
|
: undefined);
|
|
5227
5294
|
const profileDefaultsApply = profile !== undefined && profile.meta.provider === lane.provider;
|
|
5228
|
-
const pinnedModel = input.workerProfile !== undefined
|
|
5229
|
-
? lane.model_source === "cli" ? lane.model : undefined
|
|
5230
|
-
: lane.model_source === undefined || lane.model_source === "cli"
|
|
5295
|
+
const pinnedModel = input.preservedModel ?? (input.workerProfile !== undefined
|
|
5296
|
+
? lane.model_source === "cli" || lane.model_source === "preserved" ? lane.model : undefined
|
|
5297
|
+
: lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
5231
5298
|
? lane.model
|
|
5232
|
-
: undefined;
|
|
5299
|
+
: undefined);
|
|
5233
5300
|
const profileModel = profileDefaultsApply ? profile?.meta.model : undefined;
|
|
5234
5301
|
if (input.cliModel === undefined && profileDefaultsApply && profileModel !== undefined && pinnedModel === undefined) {
|
|
5235
5302
|
validateProfileLaneModel(lane.provider, profileModel, targetHome, profile.id, {
|
|
@@ -5313,12 +5380,14 @@ async function prepareAccountMove(ctx, lane, input) {
|
|
|
5313
5380
|
provider_session_id_absent: undefined,
|
|
5314
5381
|
...(input.cliModel !== undefined
|
|
5315
5382
|
? { model: input.cliModel, model_source: "cli" }
|
|
5316
|
-
:
|
|
5383
|
+
: input.preservedModel !== undefined
|
|
5384
|
+
? { model: input.preservedModel, model_source: "preserved" }
|
|
5385
|
+
: {}),
|
|
5317
5386
|
account_history: appendCappedHistory(current.account_history ?? [], {
|
|
5318
5387
|
from,
|
|
5319
5388
|
to: target,
|
|
5320
5389
|
at,
|
|
5321
|
-
reason: SESSION_MOVE_REASON,
|
|
5390
|
+
reason: input.accountHistoryReason ?? SESSION_MOVE_REASON,
|
|
5322
5391
|
}),
|
|
5323
5392
|
pending_resume: input.deferTurnStart
|
|
5324
5393
|
? undefined
|
|
@@ -5345,31 +5414,39 @@ async function prepareAccountMove(ctx, lane, input) {
|
|
|
5345
5414
|
return { lane: moved, from, to: target, copy, queued };
|
|
5346
5415
|
}, ctx.sleep);
|
|
5347
5416
|
}
|
|
5348
|
-
async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile) {
|
|
5417
|
+
async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, options = {}) {
|
|
5349
5418
|
if (lane.provider !== "claude")
|
|
5350
5419
|
return undefined;
|
|
5351
|
-
|
|
5420
|
+
const balanceEnabled = await ctx.laneClaudeAccountBalanceEnabled(lane.company);
|
|
5421
|
+
const hqRoot = ctx.hqRoot();
|
|
5422
|
+
const config = loadLanesConfig(hqRoot);
|
|
5423
|
+
const currentConfig = lane.account === undefined
|
|
5424
|
+
? undefined
|
|
5425
|
+
: config.providers.claude.accounts[lane.account];
|
|
5426
|
+
const currentLogin = lane.account === undefined || currentConfig === undefined
|
|
5427
|
+
? false
|
|
5428
|
+
: accountStatus("claude", lane.account, config, {
|
|
5429
|
+
existsSync: ctx.existsSync,
|
|
5430
|
+
hostname: ctx.hostname(),
|
|
5431
|
+
probe: ctx.probeProvider,
|
|
5432
|
+
env: ctx.env,
|
|
5433
|
+
}).logged_in;
|
|
5434
|
+
const accountNeedsRepair = lane.account !== undefined &&
|
|
5435
|
+
(currentConfig?.enabled !== true || currentLogin === false);
|
|
5436
|
+
const repairUnavailableAccount = options.repairUnavailableAccount === true && accountNeedsRepair;
|
|
5437
|
+
if (!balanceEnabled && !(options.repairUnavailableAccount && accountNeedsRepair)) {
|
|
5352
5438
|
if (lane.account === undefined)
|
|
5353
5439
|
return undefined;
|
|
5354
|
-
const config = loadLanesConfig(ctx.hqRoot());
|
|
5355
|
-
const currentConfig = config.providers.claude.accounts[lane.account];
|
|
5356
5440
|
if (currentConfig === undefined || !currentConfig.enabled) {
|
|
5357
5441
|
throw new AccountResolutionError(currentConfig === undefined ? "unknown_account" : "account_disabled", currentConfig === undefined
|
|
5358
5442
|
? `Lane ${lane.lane_id} records account ${JSON.stringify(lane.account)} which is not configured for claude`
|
|
5359
5443
|
: `Account ${JSON.stringify(lane.account)} is unavailable for claude`, { provider: "claude", account: lane.account });
|
|
5360
5444
|
}
|
|
5361
|
-
if (
|
|
5362
|
-
existsSync: ctx.existsSync,
|
|
5363
|
-
hostname: ctx.hostname(),
|
|
5364
|
-
probe: ctx.probeProvider,
|
|
5365
|
-
env: ctx.env,
|
|
5366
|
-
}).logged_in === false) {
|
|
5445
|
+
if (currentLogin === false) {
|
|
5367
5446
|
throw new LanesError("account_not_logged_in", `Account ${lane.account} for claude is not logged in`, { provider: "claude", account: lane.account, lane_id: lane.lane_id });
|
|
5368
5447
|
}
|
|
5369
5448
|
return undefined;
|
|
5370
5449
|
}
|
|
5371
|
-
const hqRoot = ctx.hqRoot();
|
|
5372
|
-
const config = loadLanesConfig(hqRoot);
|
|
5373
5450
|
const profile = workerProfile?.profile ?? (lane.worker_profile !== undefined
|
|
5374
5451
|
? loadWorkerProfile(hqRoot, lane.worker_profile)
|
|
5375
5452
|
: undefined);
|
|
@@ -5380,33 +5457,45 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile) {
|
|
|
5380
5457
|
const allowedAccounts = profilePin !== undefined && lane.admission_accounts !== undefined
|
|
5381
5458
|
? profilePin.filter((account) => lane.admission_accounts.includes(account))
|
|
5382
5459
|
: profilePin ?? lane.admission_accounts;
|
|
5383
|
-
const currentConfig = lane.account === undefined
|
|
5384
|
-
? undefined
|
|
5385
|
-
: config.providers.claude.accounts[lane.account];
|
|
5386
|
-
const currentLogin = lane.account === undefined || currentConfig === undefined
|
|
5387
|
-
? false
|
|
5388
|
-
: accountStatus("claude", lane.account, config, {
|
|
5389
|
-
existsSync: ctx.existsSync,
|
|
5390
|
-
hostname: ctx.hostname(),
|
|
5391
|
-
probe: ctx.probeProvider,
|
|
5392
|
-
env: ctx.env,
|
|
5393
|
-
}).logged_in;
|
|
5394
5460
|
const pinnedModel = workerProfile !== undefined
|
|
5395
|
-
? lane.model_source === "cli" ? lane.model : undefined
|
|
5396
|
-
: lane.model_source === undefined || lane.model_source === "cli"
|
|
5461
|
+
? lane.model_source === "cli" || lane.model_source === "preserved" ? lane.model : undefined
|
|
5462
|
+
: lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
5397
5463
|
? lane.model
|
|
5398
5464
|
: undefined;
|
|
5465
|
+
const preservedModel = repairUnavailableAccount && lane.model_source === "account"
|
|
5466
|
+
? lane.model ?? currentConfig?.model
|
|
5467
|
+
: undefined;
|
|
5399
5468
|
const model = resolveLaneModel({
|
|
5400
|
-
cliModel: pinnedModel,
|
|
5469
|
+
cliModel: preservedModel ?? pinnedModel,
|
|
5401
5470
|
profileModel: profileDefaultsApply ? profile?.meta.model : undefined,
|
|
5402
5471
|
accountModel: lane.account === undefined ? undefined : config.providers.claude.accounts[lane.account]?.model,
|
|
5403
5472
|
providerModel: config.providers.claude.model,
|
|
5404
5473
|
});
|
|
5405
|
-
const accountNeedsRepair = currentConfig?.enabled !== true || currentLogin === false;
|
|
5406
5474
|
const mayRebalance = BALANCE_MOVABLE_STATES.includes(lane.state);
|
|
5407
5475
|
if (!accountNeedsRepair && !mayRebalance)
|
|
5408
5476
|
return undefined;
|
|
5409
|
-
const balanceHosts =
|
|
5477
|
+
const balanceHosts = balanceEnabled
|
|
5478
|
+
? await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config)
|
|
5479
|
+
: (() => {
|
|
5480
|
+
const lanes = loadAllLanes(hqRoot).filter((candidate) => candidate.host.kind === lane.host.kind && candidate.host.id === lane.host.id);
|
|
5481
|
+
return {
|
|
5482
|
+
snapshots: [{
|
|
5483
|
+
capacity: buildHostCapacitySnapshot({
|
|
5484
|
+
host: lane.host,
|
|
5485
|
+
lanes,
|
|
5486
|
+
config,
|
|
5487
|
+
existsSync: ctx.existsSync,
|
|
5488
|
+
probe: ctx.probeProvider,
|
|
5489
|
+
env: ctx.env,
|
|
5490
|
+
hostname: ctx.hostname(),
|
|
5491
|
+
now: ctx.now(),
|
|
5492
|
+
}),
|
|
5493
|
+
lanes,
|
|
5494
|
+
laneListAvailable: true,
|
|
5495
|
+
}],
|
|
5496
|
+
unavailableHosts: [],
|
|
5497
|
+
};
|
|
5498
|
+
})();
|
|
5410
5499
|
const targetHost = balanceHosts.snapshots.find((host) => host.capacity.host.kind === lane.host.kind && host.capacity.host.id === lane.host.id);
|
|
5411
5500
|
const currentTargetAccount = lane.account === undefined
|
|
5412
5501
|
? undefined
|
|
@@ -5423,11 +5512,15 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile) {
|
|
|
5423
5512
|
hosts: balanceHosts.snapshots,
|
|
5424
5513
|
targetHost: lane.host,
|
|
5425
5514
|
config,
|
|
5426
|
-
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
5515
|
+
...(!repairUnavailableAccount && allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
5427
5516
|
...(model !== undefined ? { model } : {}),
|
|
5428
5517
|
unavailableHosts: balanceHosts.unavailableHosts,
|
|
5429
5518
|
});
|
|
5430
|
-
return {
|
|
5519
|
+
return {
|
|
5520
|
+
selection: { ...selected, selected_at: ctx.now().toISOString() },
|
|
5521
|
+
...(repairUnavailableAccount ? { historyReason: "recorded_account_logged_out" } : {}),
|
|
5522
|
+
...(preservedModel !== undefined ? { preservedModel } : {}),
|
|
5523
|
+
};
|
|
5431
5524
|
}
|
|
5432
5525
|
function accountRequestKeepsCurrentLane(lane, rawAccount) {
|
|
5433
5526
|
const raw = rawAccount.trim();
|
|
@@ -5525,7 +5618,7 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
5525
5618
|
const resumeEffort = resolveResumeEffort(ctx, lane, {
|
|
5526
5619
|
cliEffort,
|
|
5527
5620
|
profile: resumeProfile,
|
|
5528
|
-
account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.account,
|
|
5621
|
+
account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.selection.account,
|
|
5529
5622
|
model: opts.model,
|
|
5530
5623
|
});
|
|
5531
5624
|
const messageId = mintMessageId();
|
|
@@ -5585,12 +5678,14 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
5585
5678
|
}
|
|
5586
5679
|
}
|
|
5587
5680
|
else if (mechanism === "resume") {
|
|
5588
|
-
const
|
|
5589
|
-
if (
|
|
5681
|
+
const automatic = automaticAccountSelection;
|
|
5682
|
+
if (automatic !== undefined) {
|
|
5683
|
+
const selection = automatic.selection;
|
|
5590
5684
|
if (selection.account !== lane.account) {
|
|
5591
5685
|
move = await prepareAccountMove(ctx, lane, {
|
|
5592
5686
|
account: selection.account,
|
|
5593
5687
|
cliModel: undefined,
|
|
5688
|
+
...(automatic.historyReason !== undefined ? { accountHistoryReason: automatic.historyReason } : {}),
|
|
5594
5689
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
5595
5690
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
5596
5691
|
pending: {
|
|
@@ -6063,12 +6158,12 @@ async function runResume(ctx, laneId, opts) {
|
|
|
6063
6158
|
validateResumeProfileModel(ctx, lane, resumeProfile.profile);
|
|
6064
6159
|
}
|
|
6065
6160
|
const automaticAccountSelection = opts.account === undefined
|
|
6066
|
-
? await selectAutomaticClaudeResumeAccount(ctx, lane, resumeProfile)
|
|
6161
|
+
? await selectAutomaticClaudeResumeAccount(ctx, lane, resumeProfile, { repairUnavailableAccount: true })
|
|
6067
6162
|
: undefined;
|
|
6068
6163
|
const resumeEffort = resolveResumeEffort(ctx, lane, {
|
|
6069
6164
|
cliEffort,
|
|
6070
6165
|
profile: resumeProfile,
|
|
6071
|
-
account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.account,
|
|
6166
|
+
account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.selection.account,
|
|
6072
6167
|
model: opts.model,
|
|
6073
6168
|
});
|
|
6074
6169
|
let move = null;
|
|
@@ -6089,10 +6184,17 @@ async function runResume(ctx, laneId, opts) {
|
|
|
6089
6184
|
lane = move.lane;
|
|
6090
6185
|
}
|
|
6091
6186
|
else if (automaticAccountSelection !== undefined) {
|
|
6092
|
-
|
|
6187
|
+
const selection = automaticAccountSelection.selection;
|
|
6188
|
+
if (selection.account !== lane.account) {
|
|
6093
6189
|
move = await prepareAccountMove(ctx, lane, {
|
|
6094
|
-
account:
|
|
6190
|
+
account: selection.account,
|
|
6095
6191
|
cliModel: undefined,
|
|
6192
|
+
...(automaticAccountSelection.historyReason !== undefined
|
|
6193
|
+
? { accountHistoryReason: automaticAccountSelection.historyReason }
|
|
6194
|
+
: {}),
|
|
6195
|
+
...(automaticAccountSelection.preservedModel !== undefined
|
|
6196
|
+
? { preservedModel: automaticAccountSelection.preservedModel }
|
|
6197
|
+
: {}),
|
|
6096
6198
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
6097
6199
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
6098
6200
|
pending: {
|
|
@@ -6102,12 +6204,12 @@ async function runResume(ctx, laneId, opts) {
|
|
|
6102
6204
|
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
6103
6205
|
compact: opts.compact !== false,
|
|
6104
6206
|
},
|
|
6105
|
-
accountSelection:
|
|
6207
|
+
accountSelection: selection,
|
|
6106
6208
|
});
|
|
6107
6209
|
lane = move.lane;
|
|
6108
6210
|
}
|
|
6109
6211
|
else {
|
|
6110
|
-
lane = persistClaudeAccountSelection(hqRoot, lane,
|
|
6212
|
+
lane = persistClaudeAccountSelection(hqRoot, lane, selection);
|
|
6111
6213
|
}
|
|
6112
6214
|
}
|
|
6113
6215
|
lane = recordResumeSetup(ctx, lane, {
|
|
@@ -8412,6 +8514,11 @@ async function runReconcile(ctx, opts) {
|
|
|
8412
8514
|
dryRun: Boolean(opts.dryRun),
|
|
8413
8515
|
spawnLaneDeps: {
|
|
8414
8516
|
env: ctx.env,
|
|
8517
|
+
modelDeps: {
|
|
8518
|
+
readFileSync: ctx.readFileSync,
|
|
8519
|
+
probe: ctx.probeProvider,
|
|
8520
|
+
env: ctx.env,
|
|
8521
|
+
},
|
|
8415
8522
|
existsSync: ctx.existsSync,
|
|
8416
8523
|
spawnProcess: ctx.spawnProcess,
|
|
8417
8524
|
proveDetached: ctx.proveDetached,
|
|
@@ -8505,6 +8612,13 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8505
8612
|
now: ctx.now,
|
|
8506
8613
|
actor: ctx.trackerActor,
|
|
8507
8614
|
}, wrap);
|
|
8615
|
+
registerLaneTrackerImportCommand(lanes, {
|
|
8616
|
+
hqRoot: ctx.hqRoot,
|
|
8617
|
+
resolveCompany: ctx.resolveCompany,
|
|
8618
|
+
trackerEnabled: ctx.laneTrackerEnabled,
|
|
8619
|
+
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
8620
|
+
now: ctx.now,
|
|
8621
|
+
}, wrap);
|
|
8508
8622
|
registerLaneChecksCommand(lanes, {
|
|
8509
8623
|
hqRoot: ctx.hqRoot,
|
|
8510
8624
|
resolveCompany: ctx.resolveCompany,
|
|
@@ -14,6 +14,8 @@ export interface HqCloudCopy {
|
|
|
14
14
|
}
|
|
15
15
|
export interface PickedHqCloud extends HqCloudCopy {
|
|
16
16
|
source: "bundled" | "managed";
|
|
17
|
+
/** Set when a version is pinned but that version is not the installed managed copy. */
|
|
18
|
+
pinnedVersionMissing?: string;
|
|
17
19
|
}
|
|
18
20
|
export declare function managedHqCloudDir(daemonRoot: string): string;
|
|
19
21
|
/** The package folder inside the updater's install prefix. */
|
|
@@ -44,8 +44,14 @@ export function pickHqCloud(bundled, managed, usable = () => true, pinnedVersion
|
|
|
44
44
|
if (managed && managed.version === pinnedVersion && b && m && m[0] === b[0] && usable(managed)) {
|
|
45
45
|
return { ...managed, source: "managed" };
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
// The pinned version is not installed yet (the updater installs it). Keep
|
|
48
|
+
// running the newest usable copy meanwhile instead of dropping to an older
|
|
49
|
+
// bundled one, and report the missing pin.
|
|
50
|
+
return { ...pickNewest(bundled, managed, b, m, usable), pinnedVersionMissing: pinnedVersion };
|
|
48
51
|
}
|
|
52
|
+
return pickNewest(bundled, managed, b, m, usable);
|
|
53
|
+
}
|
|
54
|
+
function pickNewest(bundled, managed, b, m, usable) {
|
|
49
55
|
if (managed && b && m && m[0] === b[0] && newer(m, b) && usable(managed))
|
|
50
56
|
return { ...managed, source: "managed" };
|
|
51
57
|
return { ...bundled, source: "bundled" };
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -243,6 +243,7 @@ export async function runDaemon(opts) {
|
|
|
243
243
|
}
|
|
244
244
|
return legacyCache.units;
|
|
245
245
|
};
|
|
246
|
+
let warnedMissingPin = null;
|
|
246
247
|
const plan = () => {
|
|
247
248
|
const config = readDaemonConfig(paths.configPath);
|
|
248
249
|
const legacy = legacyUnits();
|
|
@@ -250,6 +251,13 @@ export async function runDaemon(opts) {
|
|
|
250
251
|
const hqRoot = configured && fs.existsSync(configured) ? configured : undefined;
|
|
251
252
|
// Re-picked every reconcile, so a newer hq-cloud from the updater is used as soon as it lands.
|
|
252
253
|
const hqCloud = currentHqCloud(paths.root);
|
|
254
|
+
const missingPin = hqCloud?.pinnedVersionMissing ?? null;
|
|
255
|
+
if (missingPin !== warnedMissingPin) {
|
|
256
|
+
warnedMissingPin = missingPin;
|
|
257
|
+
if (missingPin && hqCloud) {
|
|
258
|
+
log("warn", `hq-cloud is pinned to ${missingPin}, which is not installed; running ${hqCloud.version} (${hqCloud.source}) until the updater installs it`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
253
261
|
const runners = hqCloud ? hqCloudRunners(hqCloud.root) : { syncRunner: null, heartbeat: null, sessionHost: null };
|
|
254
262
|
return planUnits(enabledServices(machineType, config), {
|
|
255
263
|
machineType,
|
|
@@ -329,7 +329,7 @@ function availableBalancedAccount(hqRoot, lane, occupying, config, host, profile
|
|
|
329
329
|
if (laneAdmissionCapReason(occupying, lane, config, host, profileMaxConcurrent, nowMs) !== "account_cap") {
|
|
330
330
|
return undefined;
|
|
331
331
|
}
|
|
332
|
-
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
|
|
332
|
+
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
333
333
|
? lane.model
|
|
334
334
|
: lane.model_source === "profile"
|
|
335
335
|
? lane.model
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -14,6 +14,8 @@ import * as path from "node:path";
|
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBalancedAccount, } from "./accounts.js";
|
|
16
16
|
import { effectiveAccountHome } from "./account-home.js";
|
|
17
|
+
import { buildHostCapacitySnapshot } from "./host-capacity.js";
|
|
18
|
+
import { selectClaudeAccount } from "./claude-account-balance.js";
|
|
17
19
|
import { COMPACTION_PROMPT, compactionAfterUsage, compactionDue, compactLaneSession, } from "./compaction.js";
|
|
18
20
|
import { installDrainHook } from "./hooks-install.js";
|
|
19
21
|
import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } from "./config.js";
|
|
@@ -159,7 +161,7 @@ function spawnModelFailure(provider, accountId, config, pinnedModel, profileMode
|
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
function selectSpawnAccount(input) {
|
|
162
|
-
const { hqRoot, lane, config, existsSync, accountDeps, modelDeps, pinnedModel, profileModel, currentModelValidated, hostname } = input;
|
|
164
|
+
const { hqRoot, lane, config, existsSync, accountDeps, modelDeps, pinnedModel, profileModel, currentModelValidated, hostname, repairRecordedAccount, recordedModel, now } = input;
|
|
163
165
|
const context = {
|
|
164
166
|
hqRoot,
|
|
165
167
|
existsSync,
|
|
@@ -187,7 +189,7 @@ function selectSpawnAccount(input) {
|
|
|
187
189
|
const operatorMovePending = lane.pending_resume !== undefined &&
|
|
188
190
|
lane.account_history?.at(-1)?.reason === "operator_move";
|
|
189
191
|
if (current === undefined || !current.enabled) {
|
|
190
|
-
reason = "account_unavailable";
|
|
192
|
+
reason = repairRecordedAccount ? "recorded_account_logged_out" : "account_unavailable";
|
|
191
193
|
refusal = new AccountResolutionError(current === undefined ? "unknown_account" : "account_disabled", `Account ${JSON.stringify(lane.account)} is unavailable for ${lane.provider}`, { provider: lane.provider, account: lane.account });
|
|
192
194
|
}
|
|
193
195
|
else {
|
|
@@ -198,7 +200,7 @@ function selectSpawnAccount(input) {
|
|
|
198
200
|
...(accountDeps?.env ? { env: accountDeps.env } : {}),
|
|
199
201
|
});
|
|
200
202
|
if (isLoggedOut(status)) {
|
|
201
|
-
reason = "account_unavailable";
|
|
203
|
+
reason = repairRecordedAccount ? "recorded_account_logged_out" : "account_unavailable";
|
|
202
204
|
refusal = new LanesError("account_not_logged_in", `Account ${lane.account} for ${lane.provider} is not logged in`, { provider: lane.provider, account: lane.account });
|
|
203
205
|
}
|
|
204
206
|
else {
|
|
@@ -242,6 +244,33 @@ function selectSpawnAccount(input) {
|
|
|
242
244
|
account: lane.account,
|
|
243
245
|
});
|
|
244
246
|
}
|
|
247
|
+
if (reason === "recorded_account_logged_out" && lane.provider === "claude") {
|
|
248
|
+
const lanes = listLaneIds(hqRoot)
|
|
249
|
+
.map((laneId) => readLane(hqRoot, laneId))
|
|
250
|
+
.filter((record) => record.host.kind === lane.host.kind && record.host.id === lane.host.id);
|
|
251
|
+
const capacity = buildHostCapacitySnapshot({
|
|
252
|
+
host: lane.host,
|
|
253
|
+
lanes,
|
|
254
|
+
config,
|
|
255
|
+
existsSync,
|
|
256
|
+
probe: accountDeps?.probe,
|
|
257
|
+
env: accountDeps?.env,
|
|
258
|
+
hostname,
|
|
259
|
+
now,
|
|
260
|
+
});
|
|
261
|
+
const selection = selectClaudeAccount({
|
|
262
|
+
hosts: [{ capacity, lanes, laneListAvailable: true }],
|
|
263
|
+
targetHost: lane.host,
|
|
264
|
+
config,
|
|
265
|
+
...((recordedModel ?? pinnedModel ?? profileModel) !== undefined
|
|
266
|
+
? { model: recordedModel ?? pinnedModel ?? profileModel }
|
|
267
|
+
: {}),
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
account: resolveAccount(lane.provider, selection.account, config, context),
|
|
271
|
+
changeReason: "recorded_account_logged_out",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
245
274
|
const allowedAccounts = Object.entries(config.providers[lane.provider].accounts)
|
|
246
275
|
.filter(([id, account]) => id !== lane.account && account.enabled)
|
|
247
276
|
.filter(([id]) => accountStatus(lane.provider, id, config, {
|
|
@@ -254,12 +283,15 @@ function selectSpawnAccount(input) {
|
|
|
254
283
|
.map(([id]) => id);
|
|
255
284
|
try {
|
|
256
285
|
return {
|
|
257
|
-
account: selectBalancedAccount(lane.provider, config, {
|
|
286
|
+
account: selectBalancedAccount(lane.provider, config, {
|
|
287
|
+
...context,
|
|
288
|
+
allowedAccounts,
|
|
289
|
+
}),
|
|
258
290
|
changeReason: reason,
|
|
259
291
|
};
|
|
260
292
|
}
|
|
261
293
|
catch (err) {
|
|
262
|
-
if (reason === "account_unavailable") {
|
|
294
|
+
if (reason === "account_unavailable" || reason === "recorded_account_logged_out") {
|
|
263
295
|
if (refusal instanceof Error)
|
|
264
296
|
throw refusal;
|
|
265
297
|
throw new LanesError("account_not_logged_in", `Account ${lane.account} for ${lane.provider} is not logged in or available, and no other logged-in account can serve this lane`, { provider: lane.provider, account: lane.account });
|
|
@@ -274,7 +306,8 @@ function previouslyUnavailableAccounts(lane) {
|
|
|
274
306
|
for (const entry of [...(lane.account_history ?? [])].reverse()) {
|
|
275
307
|
if (entry.reason === "first_assignment" || entry.reason === "operator_move")
|
|
276
308
|
break;
|
|
277
|
-
if (entry.reason === "account_unavailable"
|
|
309
|
+
if ((entry.reason === "account_unavailable" || entry.reason === "recorded_account_logged_out") &&
|
|
310
|
+
entry.from !== null)
|
|
278
311
|
excluded.add(entry.from);
|
|
279
312
|
}
|
|
280
313
|
return excluded;
|
|
@@ -364,7 +397,9 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
|
|
|
364
397
|
};
|
|
365
398
|
const profile = lane.worker_profile ? loadWorkerProfile(hqRoot, lane.worker_profile) : undefined;
|
|
366
399
|
const profileModel = profile?.meta.provider === lane.provider ? profile.meta.model : undefined;
|
|
367
|
-
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
|
|
400
|
+
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
401
|
+
? lane.model
|
|
402
|
+
: undefined;
|
|
368
403
|
const unavailableAccounts = previouslyUnavailableAccounts(lane);
|
|
369
404
|
const allowedAccounts = Object.entries(config.providers[lane.provider].accounts)
|
|
370
405
|
.filter(([id, account]) => id !== lane.account && !unavailableAccounts.has(id) && account.enabled)
|
|
@@ -1649,7 +1684,12 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1649
1684
|
: undefined;
|
|
1650
1685
|
const profileChanged = profile !== undefined && existing.worker_profile_hash !== profile.contentHash;
|
|
1651
1686
|
const config = loadLanesConfig(hqRoot);
|
|
1652
|
-
const
|
|
1687
|
+
const repairRecordedAccount = plan.mode === "resume" &&
|
|
1688
|
+
(trigger.kind === "resume" || pendingResume?.trigger === "resume");
|
|
1689
|
+
const recordedModel = repairRecordedAccount && existing.model_source === "account"
|
|
1690
|
+
? existing.model
|
|
1691
|
+
: undefined;
|
|
1692
|
+
let pinnedModel = existing.model_source === undefined || existing.model_source === "cli" || existing.model_source === "preserved"
|
|
1653
1693
|
? existing.model
|
|
1654
1694
|
: undefined;
|
|
1655
1695
|
const accountDeps = {
|
|
@@ -1669,6 +1709,9 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1669
1709
|
profileModel,
|
|
1670
1710
|
currentModelValidated: deps.currentModelValidated === true,
|
|
1671
1711
|
hostname,
|
|
1712
|
+
repairRecordedAccount,
|
|
1713
|
+
...(recordedModel !== undefined ? { recordedModel } : {}),
|
|
1714
|
+
now: (deps.now ?? (() => new Date()))(),
|
|
1672
1715
|
});
|
|
1673
1716
|
}
|
|
1674
1717
|
catch (err) {
|
|
@@ -1683,6 +1726,9 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1683
1726
|
process.stderr.write(`hq lanes: account bind failed for ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1684
1727
|
throw err;
|
|
1685
1728
|
}
|
|
1729
|
+
const preservedAccountModel = selection.changeReason === "recorded_account_logged_out" ? recordedModel : undefined;
|
|
1730
|
+
if (preservedAccountModel !== undefined)
|
|
1731
|
+
pinnedModel = preservedAccountModel;
|
|
1686
1732
|
const resolved = selection.account;
|
|
1687
1733
|
const status = accountStatus(lane.provider, resolved.id, config, { existsSync, hostname });
|
|
1688
1734
|
if (isLoggedOut(status)) {
|
|
@@ -1738,6 +1784,9 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1738
1784
|
return {
|
|
1739
1785
|
...current,
|
|
1740
1786
|
account: resolved.id,
|
|
1787
|
+
...(preservedAccountModel !== undefined
|
|
1788
|
+
? { model: preservedAccountModel, model_source: "preserved" }
|
|
1789
|
+
: {}),
|
|
1741
1790
|
account_history: appendCappedHistory(current.account_history ?? [], entry),
|
|
1742
1791
|
};
|
|
1743
1792
|
});
|
|
@@ -1934,7 +1983,9 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
1934
1983
|
...(model !== undefined
|
|
1935
1984
|
? {
|
|
1936
1985
|
model_source: pinnedModel !== undefined
|
|
1937
|
-
? "
|
|
1986
|
+
? preservedAccountModel !== undefined || existing.model_source === "preserved"
|
|
1987
|
+
? "preserved"
|
|
1988
|
+
: "cli"
|
|
1938
1989
|
: profileModel !== undefined
|
|
1939
1990
|
? "profile"
|
|
1940
1991
|
: accountCfg?.model !== undefined
|
|
@@ -2195,7 +2246,7 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2195
2246
|
ignoredFields: ignoredProfileFields,
|
|
2196
2247
|
}
|
|
2197
2248
|
: undefined;
|
|
2198
|
-
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
|
|
2249
|
+
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
2199
2250
|
? lane.model
|
|
2200
2251
|
: undefined;
|
|
2201
2252
|
const resolved = resolveAccount(lane.provider, lane.account, config, {
|
|
@@ -2307,7 +2358,7 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
2307
2358
|
if (model !== undefined) {
|
|
2308
2359
|
next.model = model;
|
|
2309
2360
|
next.model_source = pinnedModel !== undefined
|
|
2310
|
-
? "cli"
|
|
2361
|
+
? lane.model_source === "preserved" ? "preserved" : "cli"
|
|
2311
2362
|
: profileModel !== undefined
|
|
2312
2363
|
? "profile"
|
|
2313
2364
|
: accountCfg?.model !== undefined
|
|
@@ -412,8 +412,8 @@ export interface LaneRecord {
|
|
|
412
412
|
last_envelope?: LastEnvelopeRef;
|
|
413
413
|
/** Effective model id recorded at create/spawn. Omitted when the provider chooses. */
|
|
414
414
|
model?: string;
|
|
415
|
-
/** Where the effective model came from;
|
|
416
|
-
model_source?: "cli" | "profile" | "account" | "provider" | "none";
|
|
415
|
+
/** Where the effective model came from; repaired models stay pinned across the account move. */
|
|
416
|
+
model_source?: "cli" | "profile" | "account" | "provider" | "preserved" | "none";
|
|
417
417
|
/** Effective effort level passed to the provider at the last spawn (effort.ts). */
|
|
418
418
|
effort?: string;
|
|
419
419
|
/** Where `effort` came from. `cli` pins it across later spawns. */
|
|
@@ -523,7 +523,7 @@ export interface AccountHistoryEntry {
|
|
|
523
523
|
from: string | null;
|
|
524
524
|
to: string;
|
|
525
525
|
at: string;
|
|
526
|
-
reason: "first_assignment" | "model_change" | "usage_limit" | "account_unavailable" | "operator_move";
|
|
526
|
+
reason: "first_assignment" | "model_change" | "usage_limit" | "account_unavailable" | "recorded_account_logged_out" | "operator_move";
|
|
527
527
|
}
|
|
528
528
|
export interface PendingResume {
|
|
529
529
|
/** Fresh answered-question launches need no previous session id. */
|