@indigoai-us/hq-cli 5.345.146 → 5.345.148
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 +45 -0
- package/README.md +14 -0
- package/assets/bot-workers/setup/worker.yaml +11 -0
- package/dist/command-catalog.generated.d.ts +326 -4
- package/dist/command-catalog.generated.js +403 -4
- package/dist/commands/agent-inbox.js +2 -1
- package/dist/commands/auth.js +4 -2
- package/dist/commands/bot.js +1 -1
- package/dist/commands/cloud.js +31 -1
- package/dist/commands/core-refresh-vault-access-native.d.ts +17 -0
- package/dist/commands/core-refresh-vault-access-native.js +214 -19
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
- package/dist/commands/core.js +6 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.js +37 -3
- package/dist/commands/dm.js +9 -2
- package/dist/commands/doctor.d.ts +11 -1
- package/dist/commands/doctor.js +56 -1
- package/dist/commands/files.js +10 -6
- package/dist/commands/install-global.d.ts +7 -0
- package/dist/commands/install-global.js +129 -35
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +4 -4
- package/dist/commands/lanes.js +380 -25
- package/dist/commands/login.js +4 -3
- package/dist/commands/pack-install.d.ts +2 -2
- package/dist/commands/pack-install.js +133 -35
- package/dist/commands/sync-manifest.d.ts +2 -9
- package/dist/commands/sync-manifest.js +4 -15
- package/dist/hook-engine-path.d.ts +4 -0
- package/dist/hook-engine-path.js +36 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2784 -2360
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +6 -0
- package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
- package/dist/lib/agent-kit/inbox-redact.js +361 -0
- package/dist/lib/agent-kit/inbox-state.js +11 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +36 -7
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +9 -0
- package/dist/lib/bot/daemon.js +3 -0
- package/dist/lib/bot/runtime/index.js +3 -1
- package/dist/lib/company-skill-wrapper.js +6 -4
- package/dist/lib/daemon/install.d.ts +47 -1
- package/dist/lib/daemon/install.js +181 -15
- package/dist/lib/daemon/lock.d.ts +32 -0
- package/dist/lib/daemon/lock.js +55 -2
- package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +3 -0
- package/dist/lib/doctor/checks/anywhere.js +23 -8
- package/dist/lib/doctor/checks/cli-path.d.ts +11 -0
- package/dist/lib/doctor/checks/cli-path.js +45 -0
- package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
- package/dist/lib/doctor/checks/hook-engine.js +16 -0
- package/dist/lib/doctor/checks/release-links.d.ts +23 -0
- package/dist/lib/doctor/checks/release-links.js +152 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
- package/dist/lib/doctor/checks/sync-health.js +38 -2
- package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
- package/dist/lib/doctor/checks/temp-homes.js +54 -0
- package/dist/lib/doctor/fix/apply.d.ts +90 -1
- package/dist/lib/doctor/fix/apply.js +176 -10
- package/dist/lib/doctor/fix/json-output.d.ts +4 -1
- package/dist/lib/doctor/fix/json-output.js +6 -0
- package/dist/lib/doctor/fix/remediation.d.ts +7 -1
- package/dist/lib/doctor/fix/remediation.js +23 -1
- package/dist/lib/doctor/registry.js +13 -0
- package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
- package/dist/lib/doctor/temp-home-sweeper.js +418 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.js +9 -1
- package/dist/lib/flag-snapshot-file.js +8 -3
- package/dist/lib/hook-engine/resolve.d.ts +31 -0
- package/dist/lib/hook-engine/resolve.js +134 -0
- package/dist/lib/lanes/admission-host.d.ts +1 -0
- package/dist/lib/lanes/admission-host.js +8 -1
- package/dist/lib/lanes/admission.d.ts +4 -0
- package/dist/lib/lanes/admission.js +6 -2
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.js +4 -1
- package/dist/lib/lanes/local-host-identity.d.ts +40 -0
- package/dist/lib/lanes/local-host-identity.js +314 -0
- package/dist/lib/lanes/reaper.d.ts +1 -1
- package/dist/lib/lanes/reaper.js +7 -0
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +16 -8
- package/dist/lib/lanes/spawn.d.ts +20 -0
- package/dist/lib/lanes/spawn.js +48 -2
- package/dist/lib/lanes/status-fields.js +6 -1
- package/dist/lib/lanes/store.js +8 -1
- package/dist/lib/lanes/throttle.d.ts +49 -6
- package/dist/lib/lanes/throttle.js +154 -17
- package/dist/lib/lanes/types.d.ts +22 -1
- package/dist/lib/lanes/types.js +27 -3
- package/dist/lib/lanes/watcher.js +27 -2
- package/dist/lib/lanes/worker-continuity.d.ts +2 -0
- package/dist/lib/lanes/worker-continuity.js +18 -11
- package/dist/lib/manifest-disabled.d.ts +13 -0
- package/dist/lib/manifest-disabled.js +19 -0
- package/dist/lib/service-manager/launchd.d.ts +1 -0
- package/dist/lib/service-manager/launchd.js +1 -1
- package/dist/lib/windows/user-path.d.ts +48 -0
- package/dist/lib/windows/user-path.js +114 -0
- package/dist/lib/work-context/auto-bind.d.ts +1 -1
- package/dist/lib/work-context/auto-bind.js +22 -3
- package/dist/lib/work-context/lane-bind.d.ts +31 -0
- package/dist/lib/work-context/lane-bind.js +101 -0
- package/dist/lib/work-context/organize.js +1 -0
- package/dist/lib/work-context/prd-sync.js +7 -0
- package/dist/lib/work-context/project.d.ts +36 -10
- package/dist/lib/work-context/project.js +130 -41
- package/dist/lib/work-context/reconcile.d.ts +6 -0
- package/dist/lib/work-context/reconcile.js +25 -5
- package/dist/lib/work-context/state.d.ts +6 -0
- package/dist/lib/work-context/state.js +29 -1
- package/dist/lib/workers-registry/index.d.ts +6 -0
- package/dist/lib/workers-registry/index.js +8 -0
- package/dist/utils/copy-tree.d.ts +61 -0
- package/dist/utils/copy-tree.js +249 -0
- package/dist/utils/environmental-error.js +7 -3
- package/dist/utils/headless.d.ts +2 -0
- package/dist/utils/headless.js +8 -0
- package/dist/utils/login-browser.d.ts +4 -0
- package/dist/utils/login-browser.js +122 -0
- package/dist/utils/npm-invocation.d.ts +27 -0
- package/dist/utils/npm-invocation.js +54 -0
- package/dist/utils/safe-link.d.ts +50 -0
- package/dist/utils/safe-link.js +300 -0
- package/dist/utils/team-upgrade.js +1 -7
- package/package.json +17 -5
- package/scripts/hook-engine-manifest.mjs +22 -0
- package/scripts/install-hook-engine.mjs +10 -0
|
@@ -17,6 +17,14 @@ export declare const STARVATION_MAX_DEFERRALS = 10;
|
|
|
17
17
|
export declare const STARVATION_MAX_MS: number;
|
|
18
18
|
export declare const PROC_MEMINFO = "/proc/meminfo";
|
|
19
19
|
export declare const VM_STAT_BIN = "/usr/bin/vm_stat";
|
|
20
|
+
export declare const SYSCTL_BIN = "/usr/sbin/sysctl";
|
|
21
|
+
/**
|
|
22
|
+
* The sysctls behind macOS `memory_pressure`: its "System-wide memory free
|
|
23
|
+
* percentage" is kern.memorystatus_level, and its pressure level is
|
|
24
|
+
* kern.memorystatus_vm_pressure_level (1 normal, 2 warn, 4 critical).
|
|
25
|
+
* hw.memsize turns the percentage into MiB for min_available_mem_mb.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MEMORY_PRESSURE_SYSCTLS: readonly ["kern.memorystatus_level", "kern.memorystatus_vm_pressure_level", "hw.memsize"];
|
|
20
28
|
export declare const LANES_CPU_BUSY_OVERRIDE = "HQ_LANES_CPU_BUSY_OVERRIDE";
|
|
21
29
|
export declare const LANES_CPU_CHECK = "HQ_LANES_CPU_CHECK";
|
|
22
30
|
export interface CpuTimes {
|
|
@@ -38,7 +46,8 @@ export interface ThrottleDeps {
|
|
|
38
46
|
freemem: () => number;
|
|
39
47
|
platform: () => NodeJS.Platform;
|
|
40
48
|
/**
|
|
41
|
-
* Darwin only: read
|
|
49
|
+
* Darwin only: read the memory_pressure sysctls (vm_stat as a fallback) when
|
|
50
|
+
* /proc/meminfo is absent. Unused on Linux.
|
|
42
51
|
*/
|
|
43
52
|
execFileSync: (file: string, args: readonly string[], options: {
|
|
44
53
|
encoding: "utf8";
|
|
@@ -82,16 +91,45 @@ export declare function recordCpuBusyObservation(state: ThrottleState, observati
|
|
|
82
91
|
export declare function parseMemAvailableKb(contents: string): number | null;
|
|
83
92
|
/**
|
|
84
93
|
* Available memory in MiB from darwin `vm_stat`: free + inactive + speculative
|
|
85
|
-
* pages.
|
|
94
|
+
* pages. Only a fallback when the memory_pressure sysctls cannot be read: it
|
|
95
|
+
* leaves out purgeable and compressible memory, so it reads lower than
|
|
96
|
+
* `memory_pressure` on a Mac that is not under pressure.
|
|
86
97
|
*/
|
|
87
98
|
export declare function parseVmStatAvailableMb(contents: string): number | null;
|
|
99
|
+
export type MemoryPressureLevel = "normal" | "warn" | "critical";
|
|
100
|
+
export type MemorySignalSource = "memory_pressure" | "vm_stat" | "meminfo" | "freemem";
|
|
101
|
+
/** One memory reading and where it came from. */
|
|
102
|
+
export interface MemorySignal {
|
|
103
|
+
source: MemorySignalSource;
|
|
104
|
+
available_mb: number;
|
|
105
|
+
/** memory_pressure only: "System-wide memory free percentage". */
|
|
106
|
+
free_percent?: number;
|
|
107
|
+
/** memory_pressure only: kern.memorystatus_vm_pressure_level. */
|
|
108
|
+
pressure_level?: MemoryPressureLevel;
|
|
109
|
+
}
|
|
110
|
+
/** The memory gate's reason for holding admission, shown to the user. */
|
|
111
|
+
export interface MemoryHold extends MemorySignal {
|
|
112
|
+
threshold_mb: number;
|
|
113
|
+
message: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Parse `sysctl -n kern.memorystatus_level kern.memorystatus_vm_pressure_level
|
|
117
|
+
* hw.memsize`: one integer per line, in that order.
|
|
118
|
+
*/
|
|
119
|
+
export declare function parseMemoryPressureSysctl(contents: string): MemorySignal | null;
|
|
88
120
|
/**
|
|
89
|
-
*
|
|
90
|
-
* /proc/meminfo,
|
|
91
|
-
*
|
|
92
|
-
*
|
|
121
|
+
* Current memory reading for the admission gate. Prefers kernel MemAvailable.
|
|
122
|
+
* On darwin without /proc/meminfo, reads the same values `memory_pressure`
|
|
123
|
+
* prints (free percentage and pressure level), falls back to vm_stat, and never
|
|
124
|
+
* uses os.freemem(). Elsewhere, falls back to os.freemem() only where
|
|
125
|
+
* MemAvailable is absent. Returning null skips the memory gate instead of
|
|
126
|
+
* holding every lane.
|
|
93
127
|
*/
|
|
128
|
+
export declare function readMemorySignal(deps?: ThrottleDeps): MemorySignal | null;
|
|
129
|
+
/** Available memory in MiB from readMemorySignal; null skips the memory gate. */
|
|
94
130
|
export declare function readAvailableMemMb(deps?: ThrottleDeps): number | null;
|
|
131
|
+
/** Plain explanation of a memory hold: which signal, its value, and the threshold. */
|
|
132
|
+
export declare function formatMemoryHoldMessage(signal: MemorySignal, thresholdMb: number): string;
|
|
95
133
|
export interface ThrottleState {
|
|
96
134
|
consecutiveDeferrals: number;
|
|
97
135
|
lastAdmissionAt: string | null;
|
|
@@ -100,6 +138,8 @@ export interface ThrottleState {
|
|
|
100
138
|
last_busy?: number | null;
|
|
101
139
|
busy_source?: CpuBusySource;
|
|
102
140
|
sampled_at?: string;
|
|
141
|
+
/** Set while the memory gate holds admission; cleared on the next admit or CPU hold. */
|
|
142
|
+
memory_hold?: MemoryHold;
|
|
103
143
|
}
|
|
104
144
|
export type ThrottleAdmitReason = "ok" | "starvation_bound" | "unreadable" | "cpu_check_disabled";
|
|
105
145
|
export type ThrottleDeferReason = "cpu" | "memory";
|
|
@@ -111,12 +151,15 @@ export type ThrottleDecision = {
|
|
|
111
151
|
reason: ThrottleDeferReason;
|
|
112
152
|
busy?: number;
|
|
113
153
|
availableMemMb?: number;
|
|
154
|
+
memory_hold?: MemoryHold;
|
|
114
155
|
};
|
|
115
156
|
export declare function emptyThrottleState(): ThrottleState;
|
|
116
157
|
export declare function starvationBoundReached(state: ThrottleState, nowMs: number): boolean;
|
|
117
158
|
export declare function evaluateThrottle(input: {
|
|
118
159
|
busy: number | null;
|
|
119
160
|
availableMemMb: number | null;
|
|
161
|
+
/** Where availableMemMb came from. Without it the reading is reported as MemAvailable. */
|
|
162
|
+
memory?: MemorySignal | null;
|
|
120
163
|
cpuCheckEnabled: boolean;
|
|
121
164
|
state: ThrottleState | "unreadable";
|
|
122
165
|
throttle: ThrottleConfig;
|
|
@@ -22,6 +22,18 @@ export const STARVATION_MAX_MS = 30 * 60 * 1000;
|
|
|
22
22
|
export const PROC_MEMINFO = "/proc/meminfo";
|
|
23
23
|
export const VM_STAT_BIN = "/usr/bin/vm_stat";
|
|
24
24
|
const VM_STAT_TIMEOUT_MS = 2_000;
|
|
25
|
+
export const SYSCTL_BIN = "/usr/sbin/sysctl";
|
|
26
|
+
/**
|
|
27
|
+
* The sysctls behind macOS `memory_pressure`: its "System-wide memory free
|
|
28
|
+
* percentage" is kern.memorystatus_level, and its pressure level is
|
|
29
|
+
* kern.memorystatus_vm_pressure_level (1 normal, 2 warn, 4 critical).
|
|
30
|
+
* hw.memsize turns the percentage into MiB for min_available_mem_mb.
|
|
31
|
+
*/
|
|
32
|
+
export const MEMORY_PRESSURE_SYSCTLS = [
|
|
33
|
+
"kern.memorystatus_level",
|
|
34
|
+
"kern.memorystatus_vm_pressure_level",
|
|
35
|
+
"hw.memsize",
|
|
36
|
+
];
|
|
25
37
|
export const LANES_CPU_BUSY_OVERRIDE = "HQ_LANES_CPU_BUSY_OVERRIDE";
|
|
26
38
|
export const LANES_CPU_CHECK = "HQ_LANES_CPU_CHECK";
|
|
27
39
|
function defaultSleep(ms) {
|
|
@@ -200,7 +212,9 @@ function parseVmStatPages(contents, label) {
|
|
|
200
212
|
}
|
|
201
213
|
/**
|
|
202
214
|
* Available memory in MiB from darwin `vm_stat`: free + inactive + speculative
|
|
203
|
-
* pages.
|
|
215
|
+
* pages. Only a fallback when the memory_pressure sysctls cannot be read: it
|
|
216
|
+
* leaves out purgeable and compressible memory, so it reads lower than
|
|
217
|
+
* `memory_pressure` on a Mac that is not under pressure.
|
|
204
218
|
*/
|
|
205
219
|
export function parseVmStatAvailableMb(contents) {
|
|
206
220
|
const pageSizeMatch = /page size of (\d+) bytes/i.exec(contents);
|
|
@@ -214,7 +228,53 @@ export function parseVmStatAvailableMb(contents) {
|
|
|
214
228
|
return null;
|
|
215
229
|
return ((free + inactive + speculative) * pageSize) / (1024 * 1024);
|
|
216
230
|
}
|
|
217
|
-
|
|
231
|
+
const PRESSURE_LEVELS = {
|
|
232
|
+
1: "normal",
|
|
233
|
+
2: "warn",
|
|
234
|
+
4: "critical",
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Parse `sysctl -n kern.memorystatus_level kern.memorystatus_vm_pressure_level
|
|
238
|
+
* hw.memsize`: one integer per line, in that order.
|
|
239
|
+
*/
|
|
240
|
+
export function parseMemoryPressureSysctl(contents) {
|
|
241
|
+
const lines = contents.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
242
|
+
if (lines.length !== MEMORY_PRESSURE_SYSCTLS.length)
|
|
243
|
+
return null;
|
|
244
|
+
if (!lines.every((line) => /^\d+$/.test(line)))
|
|
245
|
+
return null;
|
|
246
|
+
const [freePercent, level, memsize] = lines.map(Number);
|
|
247
|
+
const pressureLevel = PRESSURE_LEVELS[level];
|
|
248
|
+
if (!Number.isSafeInteger(freePercent) || freePercent < 0 || freePercent > 100 ||
|
|
249
|
+
pressureLevel === undefined ||
|
|
250
|
+
!Number.isSafeInteger(memsize) || memsize <= 0) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
source: "memory_pressure",
|
|
255
|
+
available_mb: (memsize * (freePercent / 100)) / (1024 * 1024),
|
|
256
|
+
free_percent: freePercent,
|
|
257
|
+
pressure_level: pressureLevel,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function readDarwinMemoryPressure(deps) {
|
|
261
|
+
try {
|
|
262
|
+
const contents = deps.execFileSync(SYSCTL_BIN, ["-n", ...MEMORY_PRESSURE_SYSCTLS], {
|
|
263
|
+
encoding: "utf8",
|
|
264
|
+
timeout: VM_STAT_TIMEOUT_MS,
|
|
265
|
+
});
|
|
266
|
+
const signal = parseMemoryPressureSysctl(contents);
|
|
267
|
+
if (signal !== null)
|
|
268
|
+
return signal;
|
|
269
|
+
process.stderr.write("lanes throttle: darwin memory_pressure sysctls were unreadable; falling back to vm_stat\n");
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
process.stderr.write(`lanes throttle: darwin memory_pressure sysctls failed: ${err instanceof Error ? err.message : String(err)}; falling back to vm_stat\n`);
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function readDarwinVmStat(deps) {
|
|
218
278
|
try {
|
|
219
279
|
const contents = deps.execFileSync(VM_STAT_BIN, [], {
|
|
220
280
|
encoding: "utf8",
|
|
@@ -222,7 +282,7 @@ function readDarwinAvailableMemMb(deps) {
|
|
|
222
282
|
});
|
|
223
283
|
const mb = parseVmStatAvailableMb(contents);
|
|
224
284
|
if (mb !== null)
|
|
225
|
-
return mb;
|
|
285
|
+
return { source: "vm_stat", available_mb: mb };
|
|
226
286
|
process.stderr.write("lanes throttle: darwin vm_stat output was unreadable; skipping the memory gate\n");
|
|
227
287
|
return null;
|
|
228
288
|
}
|
|
@@ -236,7 +296,7 @@ function readFreememMb(deps) {
|
|
|
236
296
|
const bytes = deps.freemem();
|
|
237
297
|
if (!Number.isFinite(bytes) || bytes < 0)
|
|
238
298
|
return null;
|
|
239
|
-
return bytes / (1024 * 1024);
|
|
299
|
+
return { source: "freemem", available_mb: bytes / (1024 * 1024) };
|
|
240
300
|
}
|
|
241
301
|
catch (err) {
|
|
242
302
|
process.stderr.write(`lanes throttle: freemem fallback failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -244,17 +304,19 @@ function readFreememMb(deps) {
|
|
|
244
304
|
}
|
|
245
305
|
}
|
|
246
306
|
/**
|
|
247
|
-
*
|
|
248
|
-
* /proc/meminfo,
|
|
249
|
-
*
|
|
250
|
-
*
|
|
307
|
+
* Current memory reading for the admission gate. Prefers kernel MemAvailable.
|
|
308
|
+
* On darwin without /proc/meminfo, reads the same values `memory_pressure`
|
|
309
|
+
* prints (free percentage and pressure level), falls back to vm_stat, and never
|
|
310
|
+
* uses os.freemem(). Elsewhere, falls back to os.freemem() only where
|
|
311
|
+
* MemAvailable is absent. Returning null skips the memory gate instead of
|
|
312
|
+
* holding every lane.
|
|
251
313
|
*/
|
|
252
|
-
export function
|
|
314
|
+
export function readMemorySignal(deps = defaultThrottleDeps()) {
|
|
253
315
|
try {
|
|
254
316
|
const contents = deps.readFileSync(PROC_MEMINFO, "utf8");
|
|
255
317
|
const kb = parseMemAvailableKb(contents);
|
|
256
318
|
if (kb !== null)
|
|
257
|
-
return kb / 1024;
|
|
319
|
+
return { source: "meminfo", available_mb: kb / 1024 };
|
|
258
320
|
// /proc/meminfo exists but has no MemAvailable (unusual). Fall back.
|
|
259
321
|
process.stderr.write("lanes throttle: MemAvailable missing from /proc/meminfo; falling back to os.freemem()\n");
|
|
260
322
|
}
|
|
@@ -264,11 +326,35 @@ export function readAvailableMemMb(deps = defaultThrottleDeps()) {
|
|
|
264
326
|
process.stderr.write(`lanes throttle: failed to read ${PROC_MEMINFO}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
265
327
|
return null;
|
|
266
328
|
}
|
|
267
|
-
if (deps.platform() === "darwin")
|
|
268
|
-
return
|
|
329
|
+
if (deps.platform() === "darwin") {
|
|
330
|
+
return readDarwinMemoryPressure(deps) ?? readDarwinVmStat(deps);
|
|
331
|
+
}
|
|
269
332
|
}
|
|
270
333
|
return readFreememMb(deps);
|
|
271
334
|
}
|
|
335
|
+
/** Available memory in MiB from readMemorySignal; null skips the memory gate. */
|
|
336
|
+
export function readAvailableMemMb(deps = defaultThrottleDeps()) {
|
|
337
|
+
return readMemorySignal(deps)?.available_mb ?? null;
|
|
338
|
+
}
|
|
339
|
+
function formatMb(mb) {
|
|
340
|
+
return `${Math.round(mb)} MB`;
|
|
341
|
+
}
|
|
342
|
+
/** Plain explanation of a memory hold: which signal, its value, and the threshold. */
|
|
343
|
+
export function formatMemoryHoldMessage(signal, thresholdMb) {
|
|
344
|
+
if (signal.source === "memory_pressure") {
|
|
345
|
+
const reading = `memory_pressure reports ${signal.free_percent}% free (about ${formatMb(signal.available_mb)}), pressure level ${signal.pressure_level}`;
|
|
346
|
+
if (signal.pressure_level !== "normal") {
|
|
347
|
+
return `Holding new lanes: ${reading}. Lanes wait while the level is warn or critical (throttle.min_available_mem_mb is ${thresholdMb}).`;
|
|
348
|
+
}
|
|
349
|
+
return `Holding new lanes: ${reading}, below throttle.min_available_mem_mb ${thresholdMb}.`;
|
|
350
|
+
}
|
|
351
|
+
const label = signal.source === "vm_stat"
|
|
352
|
+
? "vm_stat free+inactive+speculative"
|
|
353
|
+
: signal.source === "meminfo"
|
|
354
|
+
? "/proc/meminfo MemAvailable"
|
|
355
|
+
: "os.freemem()";
|
|
356
|
+
return `Holding new lanes: ${label} is ${formatMb(signal.available_mb)}, below throttle.min_available_mem_mb ${thresholdMb}.`;
|
|
357
|
+
}
|
|
272
358
|
export function emptyThrottleState() {
|
|
273
359
|
return {
|
|
274
360
|
consecutiveDeferrals: 0,
|
|
@@ -303,9 +389,17 @@ export function evaluateThrottle(input) {
|
|
|
303
389
|
cpuBlocks = input.busy >= pause;
|
|
304
390
|
}
|
|
305
391
|
}
|
|
392
|
+
// On darwin the signal is memory_pressure's: a warn or critical level holds
|
|
393
|
+
// even above the threshold, and a normal level holds only below it.
|
|
306
394
|
let memBlocks = false;
|
|
395
|
+
let memorySignal;
|
|
307
396
|
if (minMem !== undefined && input.availableMemMb !== null) {
|
|
308
|
-
|
|
397
|
+
memorySignal = input.memory && input.memory.available_mb === input.availableMemMb
|
|
398
|
+
? input.memory
|
|
399
|
+
: { source: "meminfo", available_mb: input.availableMemMb };
|
|
400
|
+
memBlocks = input.availableMemMb < minMem ||
|
|
401
|
+
memorySignal.pressure_level === "warn" ||
|
|
402
|
+
memorySignal.pressure_level === "critical";
|
|
309
403
|
}
|
|
310
404
|
if (!cpuBlocks && !memBlocks) {
|
|
311
405
|
return {
|
|
@@ -323,27 +417,67 @@ export function evaluateThrottle(input) {
|
|
|
323
417
|
admit: false,
|
|
324
418
|
reason: "memory",
|
|
325
419
|
availableMemMb: input.availableMemMb ?? undefined,
|
|
420
|
+
memory_hold: {
|
|
421
|
+
...memorySignal,
|
|
422
|
+
threshold_mb: minMem,
|
|
423
|
+
message: formatMemoryHoldMessage(memorySignal, minMem),
|
|
424
|
+
},
|
|
326
425
|
};
|
|
327
426
|
}
|
|
328
427
|
export function applyThrottleDecision(state, decision, nowMs) {
|
|
428
|
+
const { memory_hold: _previousHold, ...rest } = state;
|
|
329
429
|
if (decision.admit) {
|
|
330
430
|
return {
|
|
331
|
-
...
|
|
431
|
+
...rest,
|
|
332
432
|
consecutiveDeferrals: 0,
|
|
333
433
|
lastAdmissionAt: new Date(nowMs).toISOString(),
|
|
334
434
|
cpuPaused: false,
|
|
335
435
|
};
|
|
336
436
|
}
|
|
337
437
|
return {
|
|
338
|
-
...
|
|
438
|
+
...rest,
|
|
339
439
|
consecutiveDeferrals: state.consecutiveDeferrals + 1,
|
|
340
440
|
lastAdmissionAt: state.lastAdmissionAt,
|
|
341
441
|
cpuPaused: decision.reason === "cpu" ? true : state.cpuPaused,
|
|
442
|
+
...(decision.memory_hold !== undefined ? { memory_hold: decision.memory_hold } : {}),
|
|
342
443
|
};
|
|
343
444
|
}
|
|
344
445
|
export function throttleStatePath(hqRoot) {
|
|
345
446
|
return path.join(hqRoot, "workspace", "lanes-runs", "throttle-state.json");
|
|
346
447
|
}
|
|
448
|
+
const MEMORY_SIGNAL_SOURCES = [
|
|
449
|
+
"memory_pressure",
|
|
450
|
+
"vm_stat",
|
|
451
|
+
"meminfo",
|
|
452
|
+
"freemem",
|
|
453
|
+
];
|
|
454
|
+
/** A stored memory_hold is informational; a malformed one is dropped, not fatal. */
|
|
455
|
+
function parseMemoryHold(value) {
|
|
456
|
+
if (value === undefined || value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
const hold = value;
|
|
460
|
+
const finiteNonNegative = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
461
|
+
if (!MEMORY_SIGNAL_SOURCES.includes(hold.source) ||
|
|
462
|
+
!finiteNonNegative(hold.available_mb) ||
|
|
463
|
+
!finiteNonNegative(hold.threshold_mb) ||
|
|
464
|
+
typeof hold.message !== "string" || hold.message.trim() === "" ||
|
|
465
|
+
(hold.free_percent !== undefined && !finiteNonNegative(hold.free_percent)) ||
|
|
466
|
+
(hold.pressure_level !== undefined &&
|
|
467
|
+
!["normal", "warn", "critical"].includes(String(hold.pressure_level)))) {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
source: hold.source,
|
|
472
|
+
available_mb: hold.available_mb,
|
|
473
|
+
...(hold.free_percent !== undefined ? { free_percent: hold.free_percent } : {}),
|
|
474
|
+
...(hold.pressure_level !== undefined
|
|
475
|
+
? { pressure_level: hold.pressure_level }
|
|
476
|
+
: {}),
|
|
477
|
+
threshold_mb: hold.threshold_mb,
|
|
478
|
+
message: hold.message,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
347
481
|
export function readThrottleState(filePath, deps = defaultThrottleDeps()) {
|
|
348
482
|
if (!deps.existsSync(filePath))
|
|
349
483
|
return "missing";
|
|
@@ -410,10 +544,12 @@ export function readThrottleState(filePath, deps = defaultThrottleDeps()) {
|
|
|
410
544
|
process.stderr.write(`lanes throttle: state ${filePath} has invalid sampled_at\n`);
|
|
411
545
|
return "unreadable";
|
|
412
546
|
}
|
|
547
|
+
const memoryHold = parseMemoryHold(obj.memory_hold);
|
|
413
548
|
return {
|
|
414
549
|
consecutiveDeferrals: obj.consecutiveDeferrals,
|
|
415
550
|
lastAdmissionAt: obj.lastAdmissionAt,
|
|
416
551
|
cpuPaused: obj.cpuPaused,
|
|
552
|
+
...(memoryHold !== undefined ? { memory_hold: memoryHold } : {}),
|
|
417
553
|
...(cpuSnapshot !== undefined ? { cpu_snapshot: cpuSnapshot } : {}),
|
|
418
554
|
...(obj.last_busy !== undefined ? { last_busy: obj.last_busy } : {}),
|
|
419
555
|
...(obj.busy_source !== undefined ? { busy_source: obj.busy_source } : {}),
|
|
@@ -465,11 +601,12 @@ export async function decideAdmission(hqRoot, throttle = {}, deps = defaultThrot
|
|
|
465
601
|
const busy = cpuBusyFraction === undefined
|
|
466
602
|
? observation?.busy ?? null
|
|
467
603
|
: cpuBusyFraction;
|
|
468
|
-
const
|
|
604
|
+
const memory = readMemorySignal(deps);
|
|
469
605
|
const nowMs = deps.now();
|
|
470
606
|
const decision = evaluateThrottle({
|
|
471
607
|
busy,
|
|
472
|
-
availableMemMb,
|
|
608
|
+
availableMemMb: memory?.available_mb ?? null,
|
|
609
|
+
memory,
|
|
473
610
|
cpuCheckEnabled,
|
|
474
611
|
state,
|
|
475
612
|
throttle,
|
|
@@ -181,6 +181,17 @@ export declare const TERMINAL_LANE_STATES: readonly ["done", "failed", "interrup
|
|
|
181
181
|
export type TerminalLaneState = (typeof TERMINAL_LANE_STATES)[number];
|
|
182
182
|
export declare const LANE_PROVIDERS: readonly ["claude", "codex", "grok"];
|
|
183
183
|
export type LaneProvider = (typeof LANE_PROVIDERS)[number];
|
|
184
|
+
/**
|
|
185
|
+
* Who runs a lane's work. A process lane (the default, recorded by absence)
|
|
186
|
+
* is a provider process HQ spawned and watches. A subagent lane is a record
|
|
187
|
+
* only: an in-session Claude subagent does the work and reports through
|
|
188
|
+
* `hq lanes progress`, so admission, watchers, reapers and keep-alive skip it.
|
|
189
|
+
*/
|
|
190
|
+
export declare const LANE_RUNNERS: readonly ["process", "subagent"];
|
|
191
|
+
export type LaneRunner = (typeof LANE_RUNNERS)[number];
|
|
192
|
+
export declare function parseLaneRunner(value: unknown): LaneRunner;
|
|
193
|
+
/** True when an in-session subagent, not an HQ-spawned process, runs this lane. */
|
|
194
|
+
export declare function isSubagentLane(lane: Pick<LaneRecord, "runner">): boolean;
|
|
184
195
|
export declare const QUEUED_REASONS: readonly ["cap", "provider_cap", "account_cap", "profile_cap", "queue_position", "throttle", "provider_binary_missing", "account_retry", "lead_reserve", "worker_profile_changed", "low_disk", "admit_failed"];
|
|
185
196
|
export type QueuedReason = (typeof QUEUED_REASONS)[number];
|
|
186
197
|
type QueuedDetails = {
|
|
@@ -616,6 +627,8 @@ export interface LaneRecord {
|
|
|
616
627
|
/** How policy files were delivered on the most recent profile spawn. */
|
|
617
628
|
worker_policy_delivery?: "hook" | "prompt";
|
|
618
629
|
provider: LaneProvider;
|
|
630
|
+
/** `subagent` when an in-session subagent runs the work; absent for HQ-spawned process lanes. */
|
|
631
|
+
runner?: "subagent";
|
|
619
632
|
/** Open each Claude turn as a named Remote Control session using account a's login. */
|
|
620
633
|
remote_control?: boolean;
|
|
621
634
|
/** Human-readable Remote Control name, fixed when the lane is created. Older lanes may lack it. */
|
|
@@ -1121,6 +1134,14 @@ export interface LanesConfig {
|
|
|
1121
1134
|
}
|
|
1122
1135
|
export declare function isIsoDateTime(value: string): boolean;
|
|
1123
1136
|
export declare function parseMajorSchema(schemaVersion: string, expectedPrefix: string, expectedMajor: number): void;
|
|
1124
|
-
export
|
|
1137
|
+
export interface ParseLaneEnvelopeOptions {
|
|
1138
|
+
/**
|
|
1139
|
+
* False for a lane started with --no-story. Such a lane has no board story
|
|
1140
|
+
* for story_status to describe, so a story_complete / story_status mismatch
|
|
1141
|
+
* is repaired from story_complete instead of failing the envelope.
|
|
1142
|
+
*/
|
|
1143
|
+
storyBound?: boolean;
|
|
1144
|
+
}
|
|
1145
|
+
export declare function parseLaneEnvelope(raw: unknown, options?: ParseLaneEnvelopeOptions): LaneEnvelope;
|
|
1125
1146
|
export {};
|
|
1126
1147
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -106,6 +106,23 @@ export const TERMINAL_LANE_STATES = [
|
|
|
106
106
|
"killed",
|
|
107
107
|
];
|
|
108
108
|
export const LANE_PROVIDERS = ["claude", "codex", "grok"];
|
|
109
|
+
/**
|
|
110
|
+
* Who runs a lane's work. A process lane (the default, recorded by absence)
|
|
111
|
+
* is a provider process HQ spawned and watches. A subagent lane is a record
|
|
112
|
+
* only: an in-session Claude subagent does the work and reports through
|
|
113
|
+
* `hq lanes progress`, so admission, watchers, reapers and keep-alive skip it.
|
|
114
|
+
*/
|
|
115
|
+
export const LANE_RUNNERS = ["process", "subagent"];
|
|
116
|
+
export function parseLaneRunner(value) {
|
|
117
|
+
if (typeof value === "string" && LANE_RUNNERS.includes(value)) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
throw new LanesError("unknown_lane_runner", `Unknown lane runner ${JSON.stringify(value)}. Accepted: ${LANE_RUNNERS.join(", ")}`, { accepted: [...LANE_RUNNERS], value });
|
|
121
|
+
}
|
|
122
|
+
/** True when an in-session subagent, not an HQ-spawned process, runs this lane. */
|
|
123
|
+
export function isSubagentLane(lane) {
|
|
124
|
+
return lane.runner === "subagent";
|
|
125
|
+
}
|
|
109
126
|
export const QUEUED_REASONS = [
|
|
110
127
|
"cap",
|
|
111
128
|
"provider_cap",
|
|
@@ -240,7 +257,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
240
257
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
241
258
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
242
259
|
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile", "spawn"], keep_alive_history: ["reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile", "spawn"], keep_alive_next_eligible_at: ["command", "reconcile", "spawn"], keep_alive_exhausted_at: ["command", "reconcile", "spawn"], loop_mode: ["creator"],
|
|
243
|
-
provider: ["creator", "command"], remote_control: ["creator"], remote_control_title: ["creator"], provider_source: ["creator", "command"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_source: ["creator", "command"], profile_settings_pinned: ["command"], account_selection: ["creator", "command"], account_pick: ["creator"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
260
|
+
provider: ["creator", "command"], runner: ["creator"], remote_control: ["creator"], remote_control_title: ["creator"], provider_source: ["creator", "command"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_source: ["creator", "command"], profile_settings_pinned: ["command"], account_selection: ["creator", "command"], account_pick: ["creator"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
244
261
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command", "watcher", "reconcile"], story_switch_pending: ["command", "watcher", "reconcile"], story_switch_unacknowledged: ["command", "watcher", "reconcile"],
|
|
245
262
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
246
263
|
last_message_at: ["creator", "command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher", "reconcile"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
@@ -288,7 +305,8 @@ export function parseMajorSchema(schemaVersion, expectedPrefix, expectedMajor) {
|
|
|
288
305
|
throw new LaneSchemaError(`Unsupported ${expectedPrefix} major version ${major} (found ${schemaVersion})`, { schema_version: schemaVersion, major });
|
|
289
306
|
}
|
|
290
307
|
}
|
|
291
|
-
export function parseLaneEnvelope(raw) {
|
|
308
|
+
export function parseLaneEnvelope(raw, options = {}) {
|
|
309
|
+
const storyBound = options.storyBound !== false;
|
|
292
310
|
if (!isPlainObject(raw)) {
|
|
293
311
|
throw new LaneSchemaError("Lane envelope must be a JSON object");
|
|
294
312
|
}
|
|
@@ -345,7 +363,7 @@ export function parseLaneEnvelope(raw) {
|
|
|
345
363
|
else if (raw.blocked_reason !== undefined) {
|
|
346
364
|
throw new LaneSchemaError('blocked_reason is present exactly when decision is "blocked"');
|
|
347
365
|
}
|
|
348
|
-
|
|
366
|
+
let storyStatus = raw.story_status;
|
|
349
367
|
let storyComplete;
|
|
350
368
|
let storyCompleteReason;
|
|
351
369
|
if (raw.story_complete !== undefined) {
|
|
@@ -358,6 +376,12 @@ export function parseLaneEnvelope(raw) {
|
|
|
358
376
|
raw.story_complete_reason.trim().length === 0) {
|
|
359
377
|
throw new LaneSchemaError("story_complete_reason is required when story_complete is present", { field: "story_complete_reason" });
|
|
360
378
|
}
|
|
379
|
+
if (!storyBound && raw.story_complete === true && storyStatus !== "done") {
|
|
380
|
+
storyStatus = "done";
|
|
381
|
+
}
|
|
382
|
+
else if (!storyBound && raw.story_complete === false && storyStatus === "done") {
|
|
383
|
+
storyStatus = "in_progress";
|
|
384
|
+
}
|
|
361
385
|
if (raw.story_complete === true && storyStatus !== "done") {
|
|
362
386
|
throw new LaneSchemaError("story_complete true requires story_status done", {
|
|
363
387
|
error: "story_complete_status_mismatch",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import * as fs from "node:fs";
|
|
35
35
|
import * as path from "node:path";
|
|
36
|
-
import { LanesError } from "./errors.js";
|
|
36
|
+
import { LaneSchemaError, LanesError } from "./errors.js";
|
|
37
37
|
import { compareEnvelopeNames } from "./envelope-sequence.js";
|
|
38
38
|
import { questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
39
39
|
import { isTerminalLaneState, ENVELOPE_INVALID_REASON, parseLaneEnvelope, STORY_STATUSES, LANE_PROVIDERS, } from "./types.js";
|
|
@@ -738,7 +738,7 @@ export function readLatestEnvelopeFile(hqRoot, laneId, minName) {
|
|
|
738
738
|
ref,
|
|
739
739
|
path: filePath,
|
|
740
740
|
text,
|
|
741
|
-
envelope:
|
|
741
|
+
envelope: parseLaneEnvelopeForLane(hqRoot, laneId, JSON.parse(text)),
|
|
742
742
|
error: null,
|
|
743
743
|
};
|
|
744
744
|
}
|
|
@@ -752,6 +752,31 @@ export function readLatestEnvelopeFile(hqRoot, laneId, minName) {
|
|
|
752
752
|
};
|
|
753
753
|
}
|
|
754
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Parse strictly first. Only a story_complete / story_status mismatch reads the
|
|
757
|
+
* lane record: a --no-story lane (no_story_reason set) has no board story, so
|
|
758
|
+
* that mismatch is repaired instead of failing the lane. An unreadable lane
|
|
759
|
+
* record keeps the strict error.
|
|
760
|
+
*/
|
|
761
|
+
function parseLaneEnvelopeForLane(hqRoot, laneId, raw) {
|
|
762
|
+
try {
|
|
763
|
+
return parseLaneEnvelope(raw);
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
if (!(err instanceof LaneSchemaError) || err.details.error !== "story_complete_status_mismatch")
|
|
767
|
+
throw err;
|
|
768
|
+
let noStory = false;
|
|
769
|
+
try {
|
|
770
|
+
noStory = readLane(hqRoot, laneId).no_story_reason !== undefined;
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
throw err;
|
|
774
|
+
}
|
|
775
|
+
if (!noStory)
|
|
776
|
+
throw err;
|
|
777
|
+
return parseLaneEnvelope(raw, { storyBound: false });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
755
780
|
/** A descriptor for this run's manifest slot when no envelope file was written. */
|
|
756
781
|
export function missingExpectedEnvelopeFile(hqRoot, laneId, envelopePath) {
|
|
757
782
|
if (!envelopePath || !path.isAbsolute(envelopePath))
|
|
@@ -5,6 +5,8 @@ export interface WorkerContinuityLookup {
|
|
|
5
5
|
provider: LaneProvider;
|
|
6
6
|
company: LaneRecord["company"];
|
|
7
7
|
freshThread?: boolean;
|
|
8
|
+
/** When set, a live lane on another machine names that machine and how to free the worker. */
|
|
9
|
+
runsHere?: (lane: LaneRecord) => boolean;
|
|
8
10
|
}
|
|
9
11
|
export interface WorkerContinuityResult {
|
|
10
12
|
previous?: LaneRecord;
|
|
@@ -14,6 +14,22 @@ function sameCompany(left, right) {
|
|
|
14
14
|
function compareNewest(left, right) {
|
|
15
15
|
return right.created_at.localeCompare(left.created_at) || right.lane_id.localeCompare(left.lane_id);
|
|
16
16
|
}
|
|
17
|
+
function workerBusyError(lookup, lane, lead, extra = {}) {
|
|
18
|
+
const remote = lookup.runsHere !== undefined && lookup.runsHere(lane) === false;
|
|
19
|
+
const fix = `hq lanes stop ${lane.lane_id}`;
|
|
20
|
+
const message = remote
|
|
21
|
+
? `${lead} on ${lane.host.kind}:${lane.host.id} (state ${lane.state}). That lane is on another machine. Free the worker on that machine with \`${fix}\`.`
|
|
22
|
+
: `${lead} (state ${lane.state}). Free the worker with \`${fix}\`.`;
|
|
23
|
+
return new LanesError("worker_lane_busy", message, {
|
|
24
|
+
worker: lookup.worker,
|
|
25
|
+
lane_id: lane.lane_id,
|
|
26
|
+
state: lane.state,
|
|
27
|
+
host: lane.host,
|
|
28
|
+
remote,
|
|
29
|
+
fix_command: fix,
|
|
30
|
+
...extra,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
17
33
|
export function sameLaneHost(left, right) {
|
|
18
34
|
return left.kind === right.kind && left.id === right.id;
|
|
19
35
|
}
|
|
@@ -25,11 +41,7 @@ export function findWorkerContinuityLane(lanes, lookup) {
|
|
|
25
41
|
.sort(compareNewest);
|
|
26
42
|
const live = scoped.find((lane) => !isTerminalLaneState(lane.state));
|
|
27
43
|
if (live) {
|
|
28
|
-
throw
|
|
29
|
-
worker: lookup.worker,
|
|
30
|
-
lane_id: live.lane_id,
|
|
31
|
-
state: live.state,
|
|
32
|
-
});
|
|
44
|
+
throw workerBusyError(lookup, live, `Worker ${lookup.worker} already has live lane ${live.lane_id}`);
|
|
33
45
|
}
|
|
34
46
|
const previous = scoped[0];
|
|
35
47
|
if (!previous)
|
|
@@ -45,12 +57,7 @@ export function findWorkerContinuityLane(lanes, lookup) {
|
|
|
45
57
|
(lane.state === "queued" || lane.state === "running"))
|
|
46
58
|
.sort(compareNewest)[0];
|
|
47
59
|
if (liveDescendant) {
|
|
48
|
-
throw
|
|
49
|
-
worker: lookup.worker,
|
|
50
|
-
lane_id: liveDescendant.lane_id,
|
|
51
|
-
state: liveDescendant.state,
|
|
52
|
-
provider_session_id: previousSessionId,
|
|
53
|
-
});
|
|
60
|
+
throw workerBusyError(lookup, liveDescendant, `Provider thread lineage is active in lane ${liveDescendant.lane_id}`, { provider_session_id: previousSessionId });
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
// Only clean, resumable terminal outcomes are safe for automatic reuse.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kill switch hq-cloud's manifest pass honours. Restated locally (rather
|
|
3
|
+
* than imported) for two reasons: it lives in the same unreleased module as
|
|
4
|
+
* the manifest exports, and — more importantly — we must be able to read it
|
|
5
|
+
* BEFORE deciding to do any work at all.
|
|
6
|
+
*
|
|
7
|
+
* Kept in its own leaf module so `hq doctor` can read the switch without
|
|
8
|
+
* loading `hq sync manifest` and its imports.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MANIFEST_DISABLED_ENV = "HQ_SYNC_MANIFEST_DISABLED";
|
|
11
|
+
/** True when the kill switch is set to anything other than empty/0/false. */
|
|
12
|
+
export declare function manifestUploadsDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
13
|
+
//# sourceMappingURL=manifest-disabled.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kill switch hq-cloud's manifest pass honours. Restated locally (rather
|
|
3
|
+
* than imported) for two reasons: it lives in the same unreleased module as
|
|
4
|
+
* the manifest exports, and — more importantly — we must be able to read it
|
|
5
|
+
* BEFORE deciding to do any work at all.
|
|
6
|
+
*
|
|
7
|
+
* Kept in its own leaf module so `hq doctor` can read the switch without
|
|
8
|
+
* loading `hq sync manifest` and its imports.
|
|
9
|
+
*/
|
|
10
|
+
export const MANIFEST_DISABLED_ENV = "HQ_SYNC_MANIFEST_DISABLED";
|
|
11
|
+
/** True when the kill switch is set to anything other than empty/0/false. */
|
|
12
|
+
export function manifestUploadsDisabled(env = process.env) {
|
|
13
|
+
const raw = env[MANIFEST_DISABLED_ENV];
|
|
14
|
+
if (raw === undefined)
|
|
15
|
+
return false;
|
|
16
|
+
const value = raw.trim().toLowerCase();
|
|
17
|
+
return value !== "" && value !== "0" && value !== "false";
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=manifest-disabled.js.map
|
|
@@ -62,6 +62,7 @@ export declare const LAUNCHD_BOOTSTRAP_BACKOFF_MS: readonly number[];
|
|
|
62
62
|
/** Bootstrap attempts after bootout (first try + EIO retries). */
|
|
63
63
|
export declare const LAUNCHD_BOOTSTRAP_ATTEMPTS: number;
|
|
64
64
|
export declare const defaultRunCommand: RunCommand;
|
|
65
|
+
export declare function blockingSleep(ms: number): void;
|
|
65
66
|
export declare function launchdServiceTarget(plistPath: string, uid?: number): string;
|
|
66
67
|
/** ProgramArguments from a LaunchAgent plist. */
|
|
67
68
|
export declare function launchdPlistProgramArguments(plist: string): string[];
|
|
@@ -177,7 +177,7 @@ export const defaultRunCommand = (cmd, args) => {
|
|
|
177
177
|
stderr: r.error ? r.error.message : (typeof r.stderr === "string" ? r.stderr : ""),
|
|
178
178
|
};
|
|
179
179
|
};
|
|
180
|
-
function blockingSleep(ms) {
|
|
180
|
+
export function blockingSleep(ms) {
|
|
181
181
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
182
182
|
}
|
|
183
183
|
function guiDomain(uid = os.userInfo().uid) {
|