@indigoai-us/hq-cli 5.203.0 → 5.205.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 +27 -0
- package/dist/command-catalog.generated.d.ts +33 -0
- package/dist/command-catalog.generated.js +43 -0
- package/dist/commands/lanes.d.ts +3 -0
- package/dist/commands/lanes.js +253 -50
- package/dist/lib/lanes/account-home.d.ts +8 -0
- package/dist/lib/lanes/account-home.js +10 -0
- package/dist/lib/lanes/accounts.d.ts +4 -1
- package/dist/lib/lanes/accounts.js +15 -10
- package/dist/lib/lanes/admission.js +6 -0
- package/dist/lib/lanes/claude-account-balance.d.ts +3 -0
- package/dist/lib/lanes/claude-account-balance.js +19 -4
- package/dist/lib/lanes/config.d.ts +2 -1
- package/dist/lib/lanes/config.js +55 -13
- package/dist/lib/lanes/hire-grants.d.ts +4 -0
- package/dist/lib/lanes/hire-grants.js +62 -0
- package/dist/lib/lanes/host-capacity.d.ts +4 -0
- package/dist/lib/lanes/host-capacity.js +8 -1
- package/dist/lib/lanes/index.d.ts +3 -1
- package/dist/lib/lanes/index.js +2 -1
- package/dist/lib/lanes/keep-alive.d.ts +8 -0
- package/dist/lib/lanes/keep-alive.js +14 -0
- package/dist/lib/lanes/liveness.d.ts +1 -0
- package/dist/lib/lanes/liveness.js +3 -1
- package/dist/lib/lanes/outpost-dispatch.d.ts +1 -0
- package/dist/lib/lanes/outpost-dispatch.js +2 -0
- package/dist/lib/lanes/profile.d.ts +1 -0
- package/dist/lib/lanes/profile.js +8 -0
- package/dist/lib/lanes/provider-rejection.d.ts +1 -1
- package/dist/lib/lanes/provider-rejection.js +15 -5
- package/dist/lib/lanes/reconcile.d.ts +3 -0
- package/dist/lib/lanes/reconcile.js +293 -13
- package/dist/lib/lanes/spawn.d.ts +1 -0
- package/dist/lib/lanes/spawn.js +30 -14
- package/dist/lib/lanes/store.js +25 -0
- package/dist/lib/lanes/types.d.ts +15 -0
- package/dist/lib/lanes/types.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AccountConfig } from "./types.js";
|
|
2
|
+
export type EffectiveAccountHome = {
|
|
3
|
+
home: string;
|
|
4
|
+
source: "host" | "default";
|
|
5
|
+
};
|
|
6
|
+
/** Resolve the account home for this host; an empty host identity fails back to the default. */
|
|
7
|
+
export declare function effectiveAccountHome(account: AccountConfig, hostname?: string): EffectiveAccountHome;
|
|
8
|
+
//# sourceMappingURL=account-home.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
/** Resolve the account home for this host; an empty host identity fails back to the default. */
|
|
3
|
+
export function effectiveAccountHome(account, hostname = os.hostname()) {
|
|
4
|
+
const host = hostname.trim();
|
|
5
|
+
const hostHome = host ? account.hosts?.[host]?.home : undefined;
|
|
6
|
+
return hostHome === undefined
|
|
7
|
+
? { home: account.home, source: "default" }
|
|
8
|
+
: { home: hostHome, source: "host" };
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=account-home.js.map
|
|
@@ -19,7 +19,7 @@ export declare const CLAUDE_CREDENTIALS_NAME = ".credentials.json";
|
|
|
19
19
|
* file here at all.
|
|
20
20
|
*/
|
|
21
21
|
export declare const PROVIDER_CREDENTIAL_FILES: Record<LaneProvider, readonly string[]>;
|
|
22
|
-
export type AuthState = "file" | "status" | "absent" | "unknown";
|
|
22
|
+
export type AuthState = "file" | "status" | "absent" | "unknown" | "home_missing";
|
|
23
23
|
export interface AccountStatusProbeResult {
|
|
24
24
|
status: number | null;
|
|
25
25
|
stdout: string;
|
|
@@ -27,12 +27,14 @@ export interface AccountStatusProbeResult {
|
|
|
27
27
|
}
|
|
28
28
|
export interface AccountDeps {
|
|
29
29
|
existsSync: (p: string) => boolean;
|
|
30
|
+
hostname?: string;
|
|
30
31
|
/** Provider status output is parsed for a boolean only; it is never printed. */
|
|
31
32
|
probe?: (command: string, args: string[], env: NodeJS.ProcessEnv) => AccountStatusProbeResult;
|
|
32
33
|
env?: NodeJS.ProcessEnv;
|
|
33
34
|
}
|
|
34
35
|
export interface AccountResolveContext {
|
|
35
36
|
hqRoot?: string;
|
|
37
|
+
hostname?: string;
|
|
36
38
|
existsSync?: (p: string) => boolean;
|
|
37
39
|
accountDeps?: Omit<AccountDeps, "existsSync">;
|
|
38
40
|
loadLanes?: (hqRoot: string) => LaneRecord[];
|
|
@@ -61,6 +63,7 @@ export interface AccountStatus {
|
|
|
61
63
|
provider: LaneProvider;
|
|
62
64
|
id: string;
|
|
63
65
|
home: string;
|
|
66
|
+
home_source?: "host" | "default";
|
|
64
67
|
enabled: boolean;
|
|
65
68
|
disabled_reason?: string;
|
|
66
69
|
/** Present in personal/lanes/config.json. */
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* module never reads, prints, copies, or hashes credential files.
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
|
+
import { effectiveAccountHome } from "./account-home.js";
|
|
11
12
|
import * as path from "node:path";
|
|
12
13
|
import { AccountResolutionError } from "./errors.js";
|
|
13
14
|
import { isTerminalLaneState } from "./types.js";
|
|
@@ -179,11 +180,11 @@ function throwIfQuotaBlocked(block, provider, accountId) {
|
|
|
179
180
|
quota_reset_at: block.quota_reset_at ?? null,
|
|
180
181
|
});
|
|
181
182
|
}
|
|
182
|
-
function boundAccount(provider, id, account) {
|
|
183
|
+
function boundAccount(provider, id, account, hostname) {
|
|
183
184
|
return {
|
|
184
185
|
provider,
|
|
185
186
|
id,
|
|
186
|
-
home: account.home,
|
|
187
|
+
home: effectiveAccountHome(account, hostname).home,
|
|
187
188
|
enabled: true,
|
|
188
189
|
envVar: PROVIDER_HOME_ENV[provider],
|
|
189
190
|
};
|
|
@@ -209,6 +210,7 @@ export function selectBalancedAccount(provider, config, ctx = {}) {
|
|
|
209
210
|
existsSync,
|
|
210
211
|
...(ctx.accountDeps?.probe ? { probe: ctx.accountDeps.probe } : {}),
|
|
211
212
|
...(ctx.accountDeps?.env ? { env: ctx.accountDeps.env } : {}),
|
|
213
|
+
...(ctx.hostname !== undefined ? { hostname: ctx.hostname } : {}),
|
|
212
214
|
});
|
|
213
215
|
if (status.logged_in === true)
|
|
214
216
|
proven.push(id);
|
|
@@ -251,7 +253,7 @@ export function selectBalancedAccount(provider, config, ctx = {}) {
|
|
|
251
253
|
}))
|
|
252
254
|
.sort(compareEligible);
|
|
253
255
|
const chosen = preferred ?? ranked[0].id;
|
|
254
|
-
return boundAccount(provider, chosen, providerCfg.accounts[chosen]);
|
|
256
|
+
return boundAccount(provider, chosen, providerCfg.accounts[chosen], ctx.hostname);
|
|
255
257
|
}
|
|
256
258
|
export function resolveAccount(provider, requestedId, config, ctx = {}) {
|
|
257
259
|
const providerCfg = config.providers[provider];
|
|
@@ -276,7 +278,7 @@ export function resolveAccount(provider, requestedId, config, ctx = {}) {
|
|
|
276
278
|
const block = quotaBlockFor(loadQuotaState(ctx.hqRoot), provider, requestedId, modelForAccount(provider, requestedId, config, ctx), now);
|
|
277
279
|
throwIfQuotaBlocked(block, provider, requestedId);
|
|
278
280
|
}
|
|
279
|
-
return boundAccount(provider, requestedId, account);
|
|
281
|
+
return boundAccount(provider, requestedId, account, ctx.hostname);
|
|
280
282
|
}
|
|
281
283
|
return selectBalancedAccount(provider, config, ctx);
|
|
282
284
|
}
|
|
@@ -299,12 +301,14 @@ export function accountStatus(provider, accountId, config, deps = defaultAccount
|
|
|
299
301
|
envVar,
|
|
300
302
|
};
|
|
301
303
|
}
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
const effective = effectiveAccountHome(account, deps.hostname);
|
|
305
|
+
const homeExists = deps.existsSync(effective.home);
|
|
306
|
+
const present = homeExists && hasCredentialFile(provider, effective.home, deps.existsSync);
|
|
307
|
+
let auth_state = homeExists ? authStateFor(provider, present) : "home_missing";
|
|
308
|
+
let logged_in = homeExists ? loggedInFromAuthState(auth_state) : false;
|
|
309
|
+
if (homeExists && !present && provider === "claude" && deps.probe !== undefined) {
|
|
306
310
|
try {
|
|
307
|
-
const result = deps.probe("claude", ["auth", "status"], { ...(deps.env ?? process.env), CLAUDE_CONFIG_DIR:
|
|
311
|
+
const result = deps.probe("claude", ["auth", "status"], { ...(deps.env ?? process.env), CLAUDE_CONFIG_DIR: effective.home });
|
|
308
312
|
const providerStatus = claudeLoginStatus(result);
|
|
309
313
|
if (providerStatus !== null) {
|
|
310
314
|
auth_state = "status";
|
|
@@ -318,7 +322,8 @@ export function accountStatus(provider, accountId, config, deps = defaultAccount
|
|
|
318
322
|
const status = {
|
|
319
323
|
provider,
|
|
320
324
|
id: accountId,
|
|
321
|
-
home:
|
|
325
|
+
home: effective.home,
|
|
326
|
+
home_source: effective.source,
|
|
322
327
|
enabled: account.enabled,
|
|
323
328
|
configured: true,
|
|
324
329
|
logged_in,
|
|
@@ -137,6 +137,12 @@ export function hasActivePendingResume(lane, nowMs) {
|
|
|
137
137
|
return false;
|
|
138
138
|
if (lane.state === "queued" && pending.trigger === "answered_questions")
|
|
139
139
|
return false;
|
|
140
|
+
if (lane.state === "queued" &&
|
|
141
|
+
lane.keep_alive === true &&
|
|
142
|
+
pending.trigger === "resume" &&
|
|
143
|
+
pending.resumed_from === "done" &&
|
|
144
|
+
lane.keep_alive_last_resume_at === pending.at)
|
|
145
|
+
return false;
|
|
140
146
|
const reservedAt = Date.parse(pending.at);
|
|
141
147
|
const ageMs = nowMs - reservedAt;
|
|
142
148
|
return Number.isFinite(reservedAt) && ageMs >= 0 && ageMs < ADMISSION_RESERVATION_GRACE_MS;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AuthState } from "./accounts.js";
|
|
1
2
|
import type { HostCapacityAccount, HostCapacitySnapshot } from "./host-capacity.js";
|
|
2
3
|
import type { LaneHost, LaneRecord, LanesConfig } from "./types.js";
|
|
3
4
|
export declare const CLAUDE_ACCOUNT_BALANCE_FLAG = "lanes.claude-account-balance";
|
|
@@ -20,6 +21,8 @@ export interface AccountHostLoad {
|
|
|
20
21
|
host: LaneHost;
|
|
21
22
|
enabled: boolean;
|
|
22
23
|
logged_in: boolean | null;
|
|
24
|
+
account_in_snapshot: boolean;
|
|
25
|
+
auth_state?: AuthState;
|
|
23
26
|
live_lanes: number | null;
|
|
24
27
|
lane_counts: {
|
|
25
28
|
running: number | null;
|
|
@@ -21,7 +21,8 @@ function accountHostLoad(host, accountId) {
|
|
|
21
21
|
return {
|
|
22
22
|
host: host.capacity.host,
|
|
23
23
|
enabled: false,
|
|
24
|
-
logged_in:
|
|
24
|
+
logged_in: null,
|
|
25
|
+
account_in_snapshot: false,
|
|
25
26
|
live_lanes: host.laneListAvailable ? countLaneStates(host.lanes, accountId).live : null,
|
|
26
27
|
lane_counts: host.laneListAvailable
|
|
27
28
|
? countLaneStates(host.lanes, accountId)
|
|
@@ -35,6 +36,8 @@ function accountHostLoad(host, accountId) {
|
|
|
35
36
|
host: host.capacity.host,
|
|
36
37
|
enabled: hostCapacity.enabled,
|
|
37
38
|
logged_in: hostCapacity.logged_in,
|
|
39
|
+
account_in_snapshot: true,
|
|
40
|
+
...(hostCapacity.auth_state !== undefined ? { auth_state: hostCapacity.auth_state } : {}),
|
|
38
41
|
live_lanes: counts.live,
|
|
39
42
|
lane_counts: { running: counts.running, queued: counts.queued, awaiting_input: counts.awaiting_input },
|
|
40
43
|
count_source: "lane_list",
|
|
@@ -45,6 +48,8 @@ function accountHostLoad(host, accountId) {
|
|
|
45
48
|
host: host.capacity.host,
|
|
46
49
|
enabled: hostCapacity.enabled,
|
|
47
50
|
logged_in: hostCapacity.logged_in,
|
|
51
|
+
account_in_snapshot: true,
|
|
52
|
+
...(hostCapacity.auth_state !== undefined ? { auth_state: hostCapacity.auth_state } : {}),
|
|
48
53
|
live_lanes: hostCapacity.lane_counts.live,
|
|
49
54
|
lane_counts: {
|
|
50
55
|
running: hostCapacity.lane_counts.running,
|
|
@@ -58,6 +63,8 @@ function accountHostLoad(host, accountId) {
|
|
|
58
63
|
host: host.capacity.host,
|
|
59
64
|
enabled: hostCapacity.enabled,
|
|
60
65
|
logged_in: hostCapacity.logged_in,
|
|
66
|
+
account_in_snapshot: true,
|
|
67
|
+
...(hostCapacity.auth_state !== undefined ? { auth_state: hostCapacity.auth_state } : {}),
|
|
61
68
|
live_lanes: hostCapacity.running + hostCapacity.queued,
|
|
62
69
|
lane_counts: { running: hostCapacity.running, queued: hostCapacity.queued, awaiting_input: null },
|
|
63
70
|
count_source: "capacity_partial",
|
|
@@ -74,10 +81,16 @@ function selectionSignal(loads) {
|
|
|
74
81
|
: "lane_count";
|
|
75
82
|
}
|
|
76
83
|
function targetIneligibilityReason(accountId, globalEnabled, allowedAccounts, target) {
|
|
77
|
-
if (!globalEnabled
|
|
84
|
+
if (!globalEnabled)
|
|
78
85
|
return "disabled";
|
|
79
86
|
if (allowedAccounts !== undefined && !allowedAccounts.includes(accountId))
|
|
80
87
|
return "not_allowed_by_lane";
|
|
88
|
+
if (!target.account_in_snapshot)
|
|
89
|
+
return "not_in_snapshot";
|
|
90
|
+
if (!target.enabled)
|
|
91
|
+
return "disabled";
|
|
92
|
+
if (target.auth_state === "home_missing")
|
|
93
|
+
return "home_missing";
|
|
81
94
|
if (target.logged_in === false)
|
|
82
95
|
return "not_logged_in";
|
|
83
96
|
if (target.logged_in === null)
|
|
@@ -203,6 +216,9 @@ export function planClaudeBalanceMoves(input) {
|
|
|
203
216
|
const hostSnapshot = input.hosts.find((row) => sameHost(row.capacity.host, host));
|
|
204
217
|
if (!hostSnapshot)
|
|
205
218
|
continue;
|
|
219
|
+
const sourceStatus = hostSnapshot.capacity.providers.claude.accounts[source];
|
|
220
|
+
if (sourceStatus === undefined)
|
|
221
|
+
continue;
|
|
206
222
|
const candidates = Object.keys(input.config.providers.claude.accounts)
|
|
207
223
|
.filter((account) => account !== source && input.config.providers.claude.accounts[account]?.enabled === true)
|
|
208
224
|
.map((account) => ({
|
|
@@ -212,8 +228,7 @@ export function planClaudeBalanceMoves(input) {
|
|
|
212
228
|
}))
|
|
213
229
|
.filter((candidate) => candidate.targetStatus?.enabled === true && candidate.targetStatus.logged_in === true)
|
|
214
230
|
.sort((a, b) => a.load - b.load || a.account.localeCompare(b.account));
|
|
215
|
-
const
|
|
216
|
-
const sourceUnavailable = sourceStatus?.enabled !== true || sourceStatus.logged_in !== true;
|
|
231
|
+
const sourceUnavailable = sourceStatus.enabled !== true || sourceStatus.logged_in !== true;
|
|
217
232
|
const target = candidates[0];
|
|
218
233
|
const laneIsLive = isBalanceLive(lane);
|
|
219
234
|
if (!target || (!sourceUnavailable && (!laneIsLive || target.load >= sourceLoad)))
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* that resolve to the same home are refused (identity-keyed-cap rule).
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
-
import { type LaneProvider, type LanesConfig, type LivenessWindows, type ProviderConfig } from "./types.js";
|
|
10
|
+
import { type AccountConfig, type LaneProvider, type LanesConfig, type LivenessWindows, type ProviderConfig } from "./types.js";
|
|
11
11
|
export declare const LANES_CONFIG_REL: string;
|
|
12
12
|
export declare const DEFAULT_MAX_QUEUED = 3;
|
|
13
13
|
export declare const DEFAULT_CPU_BUSY_PAUSE = 0.85;
|
|
@@ -55,6 +55,7 @@ export declare function expandHome(home: string, homedir: string): string;
|
|
|
55
55
|
* not-yet-created spellings of the same directory still collide.
|
|
56
56
|
*/
|
|
57
57
|
export declare function canonicalizeHome(home: string, deps?: Pick<ConfigLoadDeps, "homedir" | "realpathSync" | "existsSync">): string;
|
|
58
|
+
export declare function assertUniqueAccountHomes(provider: LaneProvider, accounts: Record<string, AccountConfig>): void;
|
|
58
59
|
export declare function documentedDefaultConfig(deps?: ConfigLoadDeps): LanesConfig;
|
|
59
60
|
export declare function parseLanesConfig(raw: unknown, deps?: ConfigLoadDeps): LanesConfig;
|
|
60
61
|
export declare function lanesConfigPath(hqRoot: string): string;
|
package/dist/lib/lanes/config.js
CHANGED
|
@@ -110,6 +110,17 @@ export function canonicalizeHome(home, deps = defaultDeps()) {
|
|
|
110
110
|
}
|
|
111
111
|
return expanded;
|
|
112
112
|
}
|
|
113
|
+
function canonicalizeHostHome(hostname, home, deps) {
|
|
114
|
+
if (hostname === os.hostname())
|
|
115
|
+
return canonicalizeHome(home, deps);
|
|
116
|
+
if (home === "~" || home.startsWith("~/")) {
|
|
117
|
+
return home === "~" ? home : `~/${path.posix.normalize(home.slice(2))}`;
|
|
118
|
+
}
|
|
119
|
+
if (!path.isAbsolute(home)) {
|
|
120
|
+
throw new LanesConfigError("invalid_config", `Host-scoped home for remote host ${JSON.stringify(hostname)} must be absolute or start with ~/`, { hostname, home });
|
|
121
|
+
}
|
|
122
|
+
return path.normalize(home);
|
|
123
|
+
}
|
|
113
124
|
function isPlainObject(value) {
|
|
114
125
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
115
126
|
}
|
|
@@ -143,6 +154,27 @@ function parseAccount(provider, id, raw, deps) {
|
|
|
143
154
|
home: canonicalizeHome(raw.home, deps),
|
|
144
155
|
enabled: raw.enabled,
|
|
145
156
|
};
|
|
157
|
+
if (raw.hosts !== undefined) {
|
|
158
|
+
const field = `providers.${provider}.accounts.${id}.hosts`;
|
|
159
|
+
if (!isPlainObject(raw.hosts)) {
|
|
160
|
+
throw new LanesConfigError("invalid_config", `${field} must be an object keyed by non-empty hostnames`, { provider, account: id, field });
|
|
161
|
+
}
|
|
162
|
+
const hosts = Object.create(null);
|
|
163
|
+
for (const [hostname, value] of Object.entries(raw.hosts)) {
|
|
164
|
+
const hostField = `${field}.${hostname}`;
|
|
165
|
+
if (hostname.trim().length === 0 || hostname.trim() !== hostname) {
|
|
166
|
+
throw new LanesConfigError("invalid_config", `${field} hostnames must be non-empty and have no surrounding whitespace`, { provider, account: id, field, hostname });
|
|
167
|
+
}
|
|
168
|
+
if (!isPlainObject(value) || Object.keys(value).some((key) => key !== "home")) {
|
|
169
|
+
throw new LanesConfigError("invalid_config", `${hostField} must be an object containing only a home field`, { provider, account: id, field: hostField });
|
|
170
|
+
}
|
|
171
|
+
if (typeof value.home !== "string" || value.home.trim().length === 0) {
|
|
172
|
+
throw new LanesConfigError("invalid_config", `${hostField}.home must be a non-empty string`, { provider, account: id, field: `${hostField}.home` });
|
|
173
|
+
}
|
|
174
|
+
hosts[hostname] = { home: canonicalizeHostHome(hostname, value.home, deps) };
|
|
175
|
+
}
|
|
176
|
+
account.hosts = hosts;
|
|
177
|
+
}
|
|
146
178
|
if (raw.disabled_reason !== undefined) {
|
|
147
179
|
if (typeof raw.disabled_reason !== "string") {
|
|
148
180
|
throw new LanesConfigError("invalid_config", `providers.${provider}.accounts.${id}.disabled_reason must be a string`, { provider, account: id });
|
|
@@ -198,19 +230,29 @@ function parsePreferredAccounts(provider, value, accounts) {
|
|
|
198
230
|
}
|
|
199
231
|
return [...preferred];
|
|
200
232
|
}
|
|
201
|
-
function
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
export function assertUniqueAccountHomes(provider, accounts) {
|
|
234
|
+
const hostnames = new Set(Object.values(accounts).flatMap((account) => Object.keys(account.hosts ?? {})));
|
|
235
|
+
const check = (hostname) => {
|
|
236
|
+
const seen = new Map();
|
|
237
|
+
for (const [id, account] of Object.entries(accounts)) {
|
|
238
|
+
const home = hostname === undefined
|
|
239
|
+
? account.home
|
|
240
|
+
: account.hosts?.[hostname]?.home ?? account.home;
|
|
241
|
+
const prior = seen.get(home);
|
|
242
|
+
if (prior !== undefined) {
|
|
243
|
+
throw new LanesConfigError("duplicate_account_home", `providers.${provider} accounts ${JSON.stringify(prior)} and ${JSON.stringify(id)} resolve to the same home ${home}${hostname === undefined ? "" : ` on host ${JSON.stringify(hostname)}`}`, {
|
|
244
|
+
provider,
|
|
245
|
+
accounts: [prior, id],
|
|
246
|
+
home,
|
|
247
|
+
...(hostname !== undefined ? { hostname } : {}),
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
seen.set(home, id);
|
|
211
251
|
}
|
|
212
|
-
|
|
213
|
-
|
|
252
|
+
};
|
|
253
|
+
check();
|
|
254
|
+
for (const hostname of hostnames)
|
|
255
|
+
check(hostname);
|
|
214
256
|
}
|
|
215
257
|
function defaultAccountMap(provider, deps) {
|
|
216
258
|
return {
|
|
@@ -240,7 +282,7 @@ function parseProvider(provider, raw, deps) {
|
|
|
240
282
|
accounts[id] = parseAccount(provider, id, value, deps);
|
|
241
283
|
}
|
|
242
284
|
}
|
|
243
|
-
|
|
285
|
+
assertUniqueAccountHomes(provider, accounts);
|
|
244
286
|
// default_account is accepted so existing files parse, and ignored for
|
|
245
287
|
// selection. An omitted field is not an error.
|
|
246
288
|
let defaultAccount;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type LaneRecord } from "./types.js";
|
|
2
|
+
import { LanesError } from "./errors.js";
|
|
2
3
|
export declare const LANE_HIRE_GRANTS_FLAG = "lanes.hire-grants";
|
|
3
4
|
export type LaneHireGrantRow = Pick<LaneRecord, "lane_id" | "state" | "senior" | "hire_grant">;
|
|
4
5
|
export declare function aliveDirectChildren<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string): T[];
|
|
@@ -9,6 +10,9 @@ export interface HireGrantAdmissionSnapshot {
|
|
|
9
10
|
parentGrant: number;
|
|
10
11
|
availableChildGrant: number;
|
|
11
12
|
}
|
|
13
|
+
/** Shared refusal logic for resuming a terminal lane under an active senior. */
|
|
14
|
+
export declare function terminalChildResumeHireGrantError(current: LaneRecord, snapshot: HireGrantAdmissionSnapshot): LanesError | null;
|
|
15
|
+
export declare function hireGrantCapacityError(parent: LaneRecord, children: readonly LaneRecord[]): LanesError;
|
|
12
16
|
/** Keep the occupancy snapshot and the resulting lane write in one parent-lock scope. */
|
|
13
17
|
export declare function withHireGrantAdmission<T>(hqRoot: string, parentLaneId: string, fn: (snapshot: HireGrantAdmissionSnapshot) => T): T;
|
|
14
18
|
export interface HireGrantBackfillRow {
|
|
@@ -10,6 +10,68 @@ export function aliveDirectChildren(lanes, parentLaneId) {
|
|
|
10
10
|
export function assignedChildHireGrants(children, exceptLaneId) {
|
|
11
11
|
return children.reduce((sum, child) => child.lane_id === exceptLaneId ? sum : sum + (child.hire_grant ?? 0), 0);
|
|
12
12
|
}
|
|
13
|
+
/** Shared refusal logic for resuming a terminal lane under an active senior. */
|
|
14
|
+
export function terminalChildResumeHireGrantError(current, snapshot) {
|
|
15
|
+
if (!isTerminalLaneState(current.state))
|
|
16
|
+
return null;
|
|
17
|
+
const { parent, children, parentGrant, availableChildGrant } = snapshot;
|
|
18
|
+
if (isTerminalLaneState(parent.state)) {
|
|
19
|
+
return new LanesError("hire_grant_senior_inactive", `Senior lane ${parent.lane_id} is ${parent.state} and cannot resume child lanes`, { senior_lane_id: parent.lane_id, state: parent.state });
|
|
20
|
+
}
|
|
21
|
+
if (children.length >= parentGrant)
|
|
22
|
+
return hireGrantCapacityError(parent, children);
|
|
23
|
+
const requestedGrant = current.hire_grant ?? 0;
|
|
24
|
+
if (requestedGrant > availableChildGrant) {
|
|
25
|
+
return new LanesError("hire_grant_carve_exceeded", `Senior lane ${parent.lane_id} has ${availableChildGrant} unassigned hire grant remaining, but terminal child ${current.lane_id} needs ${requestedGrant} to resume. Ask its senior for more: write an ask envelope with the desired total grant and why; the senior runs hq lanes grant ${parent.lane_id} --hire <n>.`, {
|
|
26
|
+
senior_lane_id: parent.lane_id,
|
|
27
|
+
parent_grant: parentGrant,
|
|
28
|
+
alive_children: children.map((child) => child.lane_id),
|
|
29
|
+
assigned_child_grants: assignedChildHireGrants(children),
|
|
30
|
+
requested_hire_grant: requestedGrant,
|
|
31
|
+
available_hire_grant: availableChildGrant,
|
|
32
|
+
ask_guidance: {
|
|
33
|
+
envelope: "decision=ask; include the desired total grant and the reason",
|
|
34
|
+
senior_command: `hq lanes grant ${parent.lane_id} --hire <n>`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
export function hireGrantCapacityError(parent, children) {
|
|
41
|
+
const grant = parent.hire_grant ?? 0;
|
|
42
|
+
const grantor = (parent.senior.kind === "lane" || parent.senior.kind === "session") &&
|
|
43
|
+
parent.senior.id
|
|
44
|
+
? { kind: parent.senior.kind, id: parent.senior.id }
|
|
45
|
+
: null;
|
|
46
|
+
const grantorReason = grantor
|
|
47
|
+
? undefined
|
|
48
|
+
: parent.senior.kind === "user"
|
|
49
|
+
? "senior_is_user"
|
|
50
|
+
: "parent_senior_missing_id";
|
|
51
|
+
const capacity = children.length === grant
|
|
52
|
+
? `is at its hire grant (${children.length} of ${grant} alive children)`
|
|
53
|
+
: `has exhausted its hire grant (${children.length} alive children; grant ${grant})`;
|
|
54
|
+
const grantCommand = `hq lanes grant ${parent.lane_id} --hire <n>`;
|
|
55
|
+
const authorityGuidance = grantor
|
|
56
|
+
? `Its senior ${grantor.kind} ${grantor.id} can raise it with: ${grantCommand}`
|
|
57
|
+
: parent.senior.kind === "user"
|
|
58
|
+
? `Its senior is the user and can raise it with: ${grantCommand}`
|
|
59
|
+
: `No senior is recorded, so an operator must resolve ownership before running: ${grantCommand}`;
|
|
60
|
+
const message = `Lane ${parent.lane_id} ${capacity}. ${authorityGuidance}`;
|
|
61
|
+
return new LanesError("hire_grant_exhausted", message, {
|
|
62
|
+
senior_lane_id: parent.lane_id,
|
|
63
|
+
parent_grant: grant,
|
|
64
|
+
alive_children: children.map((child) => child.lane_id),
|
|
65
|
+
alive_child_count: children.length,
|
|
66
|
+
grantor,
|
|
67
|
+
...(grantorReason ? { grantor_reason: grantorReason } : {}),
|
|
68
|
+
message,
|
|
69
|
+
ask_guidance: {
|
|
70
|
+
envelope: "decision=ask; include the desired total grant and the reason",
|
|
71
|
+
senior_command: grantCommand,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
13
75
|
/** Keep the occupancy snapshot and the resulting lane write in one parent-lock scope. */
|
|
14
76
|
export function withHireGrantAdmission(hqRoot, parentLaneId, fn) {
|
|
15
77
|
return withLaneHireLocks(hqRoot, [parentLaneId], () => {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { type AuthState } from "./accounts.js";
|
|
1
2
|
import type { LaneHost, LaneProvider, LaneRecord, LanesConfig } from "./types.js";
|
|
2
3
|
export interface HostCapacityAccount {
|
|
3
4
|
enabled: boolean;
|
|
4
5
|
logged_in: boolean | null;
|
|
6
|
+
/** Absent on snapshots from older CLIs. */
|
|
7
|
+
auth_state?: AuthState;
|
|
5
8
|
max_running: number;
|
|
6
9
|
running: number;
|
|
7
10
|
queued: number;
|
|
@@ -49,6 +52,7 @@ export declare function buildHostCapacitySnapshot(input: {
|
|
|
49
52
|
stderr: string;
|
|
50
53
|
};
|
|
51
54
|
env?: NodeJS.ProcessEnv;
|
|
55
|
+
hostname?: string;
|
|
52
56
|
now: Date;
|
|
53
57
|
}): HostCapacitySnapshot;
|
|
54
58
|
/** Reject malformed remote snapshots before they can influence host selection. */
|
|
@@ -23,12 +23,14 @@ export function buildHostCapacitySnapshot(input) {
|
|
|
23
23
|
existsSync: input.existsSync,
|
|
24
24
|
...(input.probe !== undefined ? { probe: input.probe } : {}),
|
|
25
25
|
...(input.env !== undefined ? { env: input.env } : {}),
|
|
26
|
+
...(input.hostname !== undefined ? { hostname: input.hostname } : {}),
|
|
26
27
|
});
|
|
27
28
|
const accountLanes = providerLanes.filter((lane) => lane.account === id);
|
|
28
29
|
const liveLanes = accountLanes.filter((lane) => lane.state === "running" || lane.state === "queued" || lane.state === "awaiting_input");
|
|
29
30
|
accounts[id] = {
|
|
30
31
|
enabled: account.enabled,
|
|
31
32
|
logged_in: status.logged_in,
|
|
33
|
+
auth_state: status.auth_state,
|
|
32
34
|
max_running: accountMaxRunning(providerConfig, id),
|
|
33
35
|
running: running.filter((lane) => lane.account === id).length,
|
|
34
36
|
queued: queued.filter((lane) => lane.account === id).length,
|
|
@@ -99,7 +101,9 @@ export function parseHostCapacitySnapshot(raw) {
|
|
|
99
101
|
const account = objectValue(accountValue);
|
|
100
102
|
if (id.trim() === "" || account === undefined ||
|
|
101
103
|
typeof account.enabled !== "boolean" ||
|
|
102
|
-
!(account.logged_in === null || typeof account.logged_in === "boolean")
|
|
104
|
+
!(account.logged_in === null || typeof account.logged_in === "boolean") ||
|
|
105
|
+
(account.auth_state !== undefined &&
|
|
106
|
+
!["file", "status", "absent", "unknown", "home_missing"].includes(account.auth_state))) {
|
|
103
107
|
throw new LanesError("host_capacity_invalid", `Malformed ${provider} account capacity`, {
|
|
104
108
|
provider,
|
|
105
109
|
account: id,
|
|
@@ -108,6 +112,9 @@ export function parseHostCapacitySnapshot(raw) {
|
|
|
108
112
|
accounts[id] = {
|
|
109
113
|
enabled: account.enabled,
|
|
110
114
|
logged_in: account.logged_in,
|
|
115
|
+
...(account.auth_state !== undefined
|
|
116
|
+
? { auth_state: account.auth_state }
|
|
117
|
+
: {}),
|
|
111
118
|
max_running: requireCount(account.max_running, `${provider}.${id}.max_running`, true),
|
|
112
119
|
running: requireCount(account.running, `${provider}.${id}.running`),
|
|
113
120
|
queued: requireCount(account.queued, `${provider}.${id}.queued`),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
|
|
5
5
|
export { MAX_LANE_DEPTH } from "./constants.js";
|
|
6
|
-
export { assignedChildHireGrants, aliveDirectChildren, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, withHireGrantAdmission } from "./hire-grants.js";
|
|
6
|
+
export { assignedChildHireGrants, aliveDirectChildren, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
|
|
7
7
|
export type { HireGrantBackfillRow } from "./hire-grants.js";
|
|
8
8
|
export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, hasEarlierEligibleQueuedLane, laneOccupiesRunningSlot, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, withLaneAdmissionLock, } from "./admission.js";
|
|
9
9
|
export type { AdmissionCapReason, AdmissionHost } from "./admission.js";
|
|
@@ -38,6 +38,8 @@ export { LANE_MESH_EVENTS, WIRE_EVENT_KINDS, createLaneMesh, emitLaneDecisionEve
|
|
|
38
38
|
export type { CreateLaneMeshOptions, LaneMesh, LaneMeshEvent, WireEventKind, WireThreadEvent, } from "./mesh-events.js";
|
|
39
39
|
export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
|
|
40
40
|
export type { CpuTimes, ThrottleAdmitReason, ThrottleDecision, ThrottleDeferReason, ThrottleDeps, ThrottleState, } from "./throttle.js";
|
|
41
|
+
export { effectiveAccountHome, } from "./account-home.js";
|
|
42
|
+
export type { EffectiveAccountHome, } from "./account-home.js";
|
|
41
43
|
export { AUTH_JSON_NAME, CLAUDE_CREDENTIALS_NAME, PROVIDER_CREDENTIAL_FILES, PROVIDER_HOME_ENV, accountEnv, accountStatus, authJsonPath, authStateFor, credentialPaths, hasCredentialFile, isCredentialProven, isLoggedOut, isOccupyingLaneState, lastStartedAt, listAccountStatuses, occupyingLaneCount, resolveAccount, runningLaneCount, selectBalancedAccount, } from "./accounts.js";
|
|
42
44
|
export type { AccountResolveContext, AccountStatus, AuthState, ResolvedAccount, } from "./accounts.js";
|
|
43
45
|
export { LIVE_WITHIN_MS, LIVENESS_EVENT_KINDS, SILENT_LOST_MS, TURN_START_LOST_MS, buildLivenessIndexFromSpool, classifySessionLiveness, defaultLivenessDeps, defaultSpoolPath, ingestSpoolLine, listSessionLiveness, livenessIndexPath, loadLivenessIndex, readDaemonPresenceConnected, readLivenessIndex, rebuildLivenessIndex, sessionLiveness, } from "./liveness.js";
|
package/dist/lib/lanes/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
|
|
5
5
|
export { MAX_LANE_DEPTH } from "./constants.js";
|
|
6
|
-
export { assignedChildHireGrants, aliveDirectChildren, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, withHireGrantAdmission } from "./hire-grants.js";
|
|
6
|
+
export { assignedChildHireGrants, aliveDirectChildren, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
|
|
7
7
|
export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, hasEarlierEligibleQueuedLane, laneOccupiesRunningSlot, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, withLaneAdmissionLock, } from "./admission.js";
|
|
8
8
|
export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
|
|
9
9
|
export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STORY_STATUSES, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
|
|
@@ -21,6 +21,7 @@ export { SILENCE_MS, WATCHER_DECISION_EVENTS, WATCHER_OUT_MAX_BYTES, WATCHER_POL
|
|
|
21
21
|
export { ENVELOPE_INVALID_REASON, decideEnvelopeRepair, emitMeshSoft, envelopeQuestionIds, readLatestEnvelopeFile, readLatestEnvelopeRecord, } from "./watcher.js";
|
|
22
22
|
export { LANE_MESH_EVENTS, WIRE_EVENT_KINDS, createLaneMesh, emitLaneDecisionEvents, emitLaneProgressEvent, laneCanEmit, parseLaneMeshEvent, progressEventDue, toWireThreadEvent, } from "./mesh-events.js";
|
|
23
23
|
export { CPU_SAMPLE_MS, LANES_CPU_BUSY_OVERRIDE, LANES_CPU_CHECK, PROC_MEMINFO, STARVATION_MAX_DEFERRALS, STARVATION_MAX_MS, applyThrottleDecision, busyFractionFromDeltas, cpuTimesSnapshot, decideAdmission, defaultThrottleDeps, emptyThrottleState, evaluateThrottle, isCpuCheckEnabled, parseMemAvailableKb, readAvailableMemMb, readThrottleState, resolveCpuBusyFraction, sampleCpuBusyFraction, starvationBoundReached, throttleStatePath, writeThrottleState, } from "./throttle.js";
|
|
24
|
+
export { effectiveAccountHome, } from "./account-home.js";
|
|
24
25
|
export { AUTH_JSON_NAME, CLAUDE_CREDENTIALS_NAME, PROVIDER_CREDENTIAL_FILES, PROVIDER_HOME_ENV, accountEnv, accountStatus, authJsonPath, authStateFor, credentialPaths, hasCredentialFile, isCredentialProven, isLoggedOut, isOccupyingLaneState, lastStartedAt, listAccountStatuses, occupyingLaneCount, resolveAccount, runningLaneCount, selectBalancedAccount, } from "./accounts.js";
|
|
25
26
|
export { LIVE_WITHIN_MS, LIVENESS_EVENT_KINDS, SILENT_LOST_MS, TURN_START_LOST_MS, buildLivenessIndexFromSpool, classifySessionLiveness, defaultLivenessDeps, defaultSpoolPath, ingestSpoolLine, listSessionLiveness, livenessIndexPath, loadLivenessIndex, readDaemonPresenceConnected, readLivenessIndex, rebuildLivenessIndex, sessionLiveness, } from "./liveness.js";
|
|
26
27
|
export { DEAD_HOST_MS, MAX_AGED_ADVANCES, QUESTION_AGE_MS, RECONCILE_TIMER_NAME, RECONCILE_UNIT_NAME, wakeAnsweredLaneNow, agedAdvanceCount, currentHopStartedAtMs, isDeadlinePassed, laneRunsHere, readTimeoutDeadline, reconcileHeartbeatPath, resolveQuestionAudience, runReconcilePass, shouldAdvanceAudienceEnded, shouldAgeQuestion, timeoutPath, } from "./reconcile.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
|
|
2
|
+
export declare const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
3
|
+
export declare const LEAD_LANE_KEEP_ALIVE_MAX_RESUMES = 24;
|
|
4
|
+
export declare const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS: number;
|
|
5
|
+
export declare const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS: number;
|
|
6
|
+
export declare const LEAD_LANE_KEEP_ALIVE_RESUME_TEXT = "Automatic keep-alive resume: your last turn ended with decision done and story_complete false. Continue the open work from where you stopped. End with done only when story_complete is true; if you are waiting, use hq lanes ask or stay in the loop.";
|
|
7
|
+
export declare function leadLaneKeepAliveBackoffMs(attempt: number): number;
|
|
8
|
+
//# sourceMappingURL=keep-alive.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const LEAD_LANE_KEEP_ALIVE_FLAG = "lanes.lead-keep-alive";
|
|
2
|
+
export const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
3
|
+
export const LEAD_LANE_KEEP_ALIVE_MAX_RESUMES = 24;
|
|
4
|
+
export const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS = 5 * 60 * 1000;
|
|
5
|
+
export const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS = 60 * 60 * 1000;
|
|
6
|
+
export const LEAD_LANE_KEEP_ALIVE_RESUME_TEXT = "Automatic keep-alive resume: your last turn ended with decision done and story_complete false. Continue the open work from where you stopped. End with done only when story_complete is true; if you are waiting, use hq lanes ask or stay in the loop.";
|
|
7
|
+
export function leadLaneKeepAliveBackoffMs(attempt) {
|
|
8
|
+
if (!Number.isSafeInteger(attempt) || attempt < 1) {
|
|
9
|
+
throw new RangeError("keep-alive attempt must be a positive safe integer");
|
|
10
|
+
}
|
|
11
|
+
const exponential = LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS * 2 ** (attempt - 1);
|
|
12
|
+
return Math.min(exponential, LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=keep-alive.js.map
|
|
@@ -44,6 +44,7 @@ export interface LivenessDeps {
|
|
|
44
44
|
readdirSync: (dir: string, options: {
|
|
45
45
|
withFileTypes: true;
|
|
46
46
|
}) => fs.Dirent[];
|
|
47
|
+
hostname?: () => string;
|
|
47
48
|
}
|
|
48
49
|
export declare function defaultLivenessDeps(): LivenessDeps;
|
|
49
50
|
export declare function defaultSpoolPath(deps?: LivenessDeps): string;
|
|
@@ -14,6 +14,7 @@ import * as path from "node:path";
|
|
|
14
14
|
import { workMeshSpoolPath, workMeshRoot } from "../mesh/live/paths.js";
|
|
15
15
|
import { daemonDir, daemonStatePath } from "../mesh/live/daemon/paths.js";
|
|
16
16
|
import { LanesError } from "./errors.js";
|
|
17
|
+
import { effectiveAccountHome } from "./account-home.js";
|
|
17
18
|
import { DEFAULT_LIVE_WITHIN_MS, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, loadLanesConfig, } from "./config.js";
|
|
18
19
|
import { lanesStoreRoot } from "./store.js";
|
|
19
20
|
export const LIVE_WITHIN_MS = DEFAULT_LIVE_WITHIN_MS;
|
|
@@ -39,6 +40,7 @@ export function defaultLivenessDeps() {
|
|
|
39
40
|
statSync: (file) => fs.statSync(file),
|
|
40
41
|
lstatSync: (file) => fs.lstatSync(file),
|
|
41
42
|
readdirSync: fs.readdirSync.bind(fs),
|
|
43
|
+
hostname: () => os.hostname(),
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
export function defaultSpoolPath(deps = defaultLivenessDeps()) {
|
|
@@ -373,7 +375,7 @@ export function sessionLiveness(hqRoot, sessionId, opts = {}) {
|
|
|
373
375
|
const config = loadLanesConfig(hqRoot);
|
|
374
376
|
const windows = opts.windows ?? config.liveness;
|
|
375
377
|
if (!entry) {
|
|
376
|
-
return unindexedClaudeSessionLiveness(sessionId, opts.nowMs ?? deps.now(), windows, deps, Object.values(config.providers.claude.accounts).map((account) => account.home));
|
|
378
|
+
return unindexedClaudeSessionLiveness(sessionId, opts.nowMs ?? deps.now(), windows, deps, Object.values(config.providers.claude.accounts).map((account) => effectiveAccountHome(account, deps.hostname?.()).home));
|
|
377
379
|
}
|
|
378
380
|
const presenceConnected = opts.presenceConnected ?? readDaemonPresenceConnected(deps);
|
|
379
381
|
return classifySessionLiveness(entry, opts.nowMs ?? deps.now(), presenceConnected, windows);
|
|
@@ -154,6 +154,8 @@ export function buildOutpostLaneCommand(input) {
|
|
|
154
154
|
if (value !== undefined)
|
|
155
155
|
args.push(flag, value);
|
|
156
156
|
}
|
|
157
|
+
if (input.action === "resume" && input.keepAlive === true)
|
|
158
|
+
args.push("--keep-alive");
|
|
157
159
|
if (input.compact === false)
|
|
158
160
|
args.push("--no-compact");
|
|
159
161
|
return withRemoteHqDir(joinCommandParts(args));
|