@indigoai-us/hq-cli 5.307.0 → 5.309.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 +11 -0
- package/assets/scaffold/core/scripts/resolve-company.sh +199 -0
- package/dist/command-catalog.generated.d.ts +13 -0
- package/dist/command-catalog.generated.js +16 -0
- package/dist/commands/core-resolve-company-command.d.ts +4 -0
- package/dist/commands/core-resolve-company-command.js +42 -0
- package/dist/commands/core-resolve-company-native.d.ts +28 -0
- package/dist/commands/core-resolve-company-native.js +408 -0
- package/dist/commands/core.js +3 -0
- package/dist/commands/lanes-checks.d.ts +7 -1
- package/dist/commands/lanes-checks.js +68 -18
- package/dist/commands/lanes-report.js +5 -2
- package/dist/fast-scaffold.js +12 -0
- package/dist/lib/doctor/checks/codex-wiring.d.ts +0 -4
- package/dist/lib/doctor/checks/codex-wiring.js +2 -2
- package/dist/lib/doctor/checks/runtime-health.d.ts +0 -17
- package/dist/lib/doctor/checks/runtime-health.js +4 -4
- package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +0 -1
- package/dist/lib/doctor/checks/scaffold-cli-floor.js +1 -1
- package/dist/lib/doctor/compat.d.ts +0 -16
- package/dist/lib/doctor/compat.js +3 -3
- package/dist/lib/doctor/platform.d.ts +0 -26
- package/dist/lib/doctor/platform.js +5 -5
- package/dist/lib/lanes/autopilot.js +122 -102
- package/dist/lib/onboarding/errors.d.ts +2 -1
- package/dist/lib/onboarding/errors.js +1 -1
- package/dist/lib/onboarding/index.d.ts +2 -6
- package/dist/lib/onboarding/index.js +2 -5
- package/dist/lib/search-index/embed-lock.d.ts +0 -7
- package/dist/lib/search-index/embed-lock.js +1 -1
- package/dist/lib/search-index/load-gate.d.ts +0 -8
- package/dist/lib/search-index/load-gate.js +3 -3
- package/dist/lib/search-index/max-doc-bytes.d.ts +2 -3
- package/dist/lib/search-index/max-doc-bytes.js +1 -1
- package/package.json +1 -1
|
@@ -41,10 +41,6 @@
|
|
|
41
41
|
*/
|
|
42
42
|
import { type AllowedDivergenceLoad } from "../allowed-divergence.js";
|
|
43
43
|
import type { CheckContext, CheckResult } from "../types.js";
|
|
44
|
-
/** Basename of the single-adapter script the current architecture routes to. */
|
|
45
|
-
export declare const CODEX_ADAPTER_BASENAME = "hq-codex-hook-adapter.sh";
|
|
46
|
-
/** Library the adapter sources to dispatch `.claude/settings.json` live. */
|
|
47
|
-
export declare const CODEX_DISPATCH_LIB_RELPATH: string;
|
|
48
44
|
/**
|
|
49
45
|
* Lifecycle events the shipped `.codex/config.toml` routes through the adapter.
|
|
50
46
|
* A missing event means the Claude hooks for that event never run under Codex.
|
|
@@ -44,9 +44,9 @@ import * as path from "node:path";
|
|
|
44
44
|
import { parse as parseToml } from "smol-toml";
|
|
45
45
|
import { ALLOWED_DIVERGENCE_RELPATH, loadAllowedDivergence, } from "../allowed-divergence.js";
|
|
46
46
|
/** Basename of the single-adapter script the current architecture routes to. */
|
|
47
|
-
|
|
47
|
+
const CODEX_ADAPTER_BASENAME = "hq-codex-hook-adapter.sh";
|
|
48
48
|
/** Library the adapter sources to dispatch `.claude/settings.json` live. */
|
|
49
|
-
|
|
49
|
+
const CODEX_DISPATCH_LIB_RELPATH = path.join("core", "scripts", "lib", "hook-adapter-core.sh");
|
|
50
50
|
/**
|
|
51
51
|
* Lifecycle events the shipped `.codex/config.toml` routes through the adapter.
|
|
52
52
|
* A missing event means the Claude hooks for that event never run under Codex.
|
|
@@ -28,16 +28,6 @@
|
|
|
28
28
|
import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
|
|
29
29
|
/** The id of the AI-runtime-health family. */
|
|
30
30
|
export declare const RUNTIMES_FAMILY_ID = "runtimes";
|
|
31
|
-
/** Common id prefix for every result this family emits. */
|
|
32
|
-
export declare const RUNTIMES_PREFIX = "runtimes";
|
|
33
|
-
/**
|
|
34
|
-
* The deterministic one-line probe prompt. Health is proven by a round-trip
|
|
35
|
-
* (exit 0 plus non-empty output), not by exact-matching the reply, so a model
|
|
36
|
-
* that answers with anything at all still passes.
|
|
37
|
-
*/
|
|
38
|
-
export declare const PROBE_PROMPT = "Reply with exactly: OK";
|
|
39
|
-
/** How long each live probe may run before it is killed and marked UNKNOWN. */
|
|
40
|
-
export declare const DEFAULT_PROBE_TIMEOUT_MS = 120000;
|
|
41
31
|
/** One AI runtime the doctor knows how to find and probe. */
|
|
42
32
|
export interface RuntimeSpec {
|
|
43
33
|
/** Stable key used in check ids, e.g. `runtimes.codex.binary`. */
|
|
@@ -94,11 +84,4 @@ export interface RuntimeHealthDeps {
|
|
|
94
84
|
export declare function checkRuntimeHealth(context: CheckContext, deps?: RuntimeHealthDeps): Promise<CheckResult[]>;
|
|
95
85
|
/** The registered family object. */
|
|
96
86
|
export declare const runtimeHealthFamily: CheckFamily;
|
|
97
|
-
/**
|
|
98
|
-
* Resolve an executable name against PATH with a pure filesystem scan — no
|
|
99
|
-
* process is spawned, keeping the doctor's default run a function of on-disk
|
|
100
|
-
* shape. First PATH entry containing an executable regular file wins, which is
|
|
101
|
-
* exactly the copy a shell would run.
|
|
102
|
-
*/
|
|
103
|
-
export declare function resolveOnPath(binary: string): string | null;
|
|
104
87
|
//# sourceMappingURL=runtime-health.d.ts.map
|
|
@@ -33,15 +33,15 @@ import { planCommandSpawn } from "../../../utils/windows-spawn.js";
|
|
|
33
33
|
/** The id of the AI-runtime-health family. */
|
|
34
34
|
export const RUNTIMES_FAMILY_ID = "runtimes";
|
|
35
35
|
/** Common id prefix for every result this family emits. */
|
|
36
|
-
|
|
36
|
+
const RUNTIMES_PREFIX = "runtimes";
|
|
37
37
|
/**
|
|
38
38
|
* The deterministic one-line probe prompt. Health is proven by a round-trip
|
|
39
39
|
* (exit 0 plus non-empty output), not by exact-matching the reply, so a model
|
|
40
40
|
* that answers with anything at all still passes.
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
const PROBE_PROMPT = "Reply with exactly: OK";
|
|
43
43
|
/** How long each live probe may run before it is killed and marked UNKNOWN. */
|
|
44
|
-
|
|
44
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 120_000;
|
|
45
45
|
/**
|
|
46
46
|
* The three runtimes HQ wires hooks for. Codex runs its probe under its own
|
|
47
47
|
* read-only sandbox and outside-a-repo mode so the probe can never write; the
|
|
@@ -243,7 +243,7 @@ export const runtimeHealthFamily = {
|
|
|
243
243
|
* shape. First PATH entry containing an executable regular file wins, which is
|
|
244
244
|
* exactly the copy a shell would run.
|
|
245
245
|
*/
|
|
246
|
-
|
|
246
|
+
function resolveOnPath(binary) {
|
|
247
247
|
const pathVar = process.env.PATH ?? "";
|
|
248
248
|
for (const dir of pathVar.split(path.delimiter)) {
|
|
249
249
|
if (!dir)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
|
|
2
2
|
export declare const SCAFFOLD_CLI_FLOOR_FAMILY_ID = "scaffold-cli-floor";
|
|
3
|
-
export declare const SCAFFOLD_CLI_FLOOR_FAMILY_TITLE = "Scaffold CLI compatibility";
|
|
4
3
|
export declare const SCAFFOLD_CLI_FLOOR_CHECK_ID = "scaffold-cli-floor.requires-hq-cli";
|
|
5
4
|
export declare const scaffoldCliFloorFamily: CheckFamily;
|
|
6
5
|
export declare function checkScaffoldCliFloor(context: CheckContext): CheckResult[];
|
|
@@ -4,7 +4,7 @@ import * as yaml from "js-yaml";
|
|
|
4
4
|
import { CLI_VERSION } from "../../../cli-version.js";
|
|
5
5
|
import { compareScaffoldCliFloor } from "../../scaffold-cli-floor.js";
|
|
6
6
|
export const SCAFFOLD_CLI_FLOOR_FAMILY_ID = "scaffold-cli-floor";
|
|
7
|
-
|
|
7
|
+
const SCAFFOLD_CLI_FLOOR_FAMILY_TITLE = "Scaffold CLI compatibility";
|
|
8
8
|
export const SCAFFOLD_CLI_FLOOR_CHECK_ID = "scaffold-cli-floor.requires-hq-cli";
|
|
9
9
|
export const scaffoldCliFloorFamily = {
|
|
10
10
|
id: SCAFFOLD_CLI_FLOOR_FAMILY_ID,
|
|
@@ -31,22 +31,6 @@ import type { DoctorJsonDocument } from "./json-output.js";
|
|
|
31
31
|
export declare const CHECK_HQ_HOOKS_PASS_EXIT = 0;
|
|
32
32
|
/** Exit code check-hq-hooks.sh returns on a health failure. Preserved. */
|
|
33
33
|
export declare const CHECK_HQ_HOOKS_FAIL_EXIT = 2;
|
|
34
|
-
/** Exit code check-hq-hooks.sh returns on a usage error. Preserved. */
|
|
35
|
-
export declare const CHECK_HQ_HOOKS_USAGE_EXIT = 64;
|
|
36
|
-
/** The runtime-probe result id: whether hooks actually fired (the ledger). */
|
|
37
|
-
export declare const RUNTIME_ENFORCEMENT_CHECK_ID = "hooks.runtime.enforcement";
|
|
38
|
-
/**
|
|
39
|
-
* The `hq doctor` check ids that correspond to check-hq-hooks.sh's *settings
|
|
40
|
-
* load* scope — the subset of the hooks family the wrapper is allowed to fail
|
|
41
|
-
* on. Deliberately narrow: it covers a missing/invalid settings file, an
|
|
42
|
-
* unquoted `$CLAUDE_PROJECT_DIR` that word-splits on a spaced root, and a hook
|
|
43
|
-
* command that runs a script which is not on disk — and nothing else. The many
|
|
44
|
-
* richer doctor findings (executable bits, orphaned scripts, three-profile
|
|
45
|
-
* membership, Codex/Grok parity, fixture coverage) are intentionally OUT of
|
|
46
|
-
* scope, so the wrapper stays byte-for-byte compatible with callers that only
|
|
47
|
-
* ever cared whether the hook configuration could load.
|
|
48
|
-
*/
|
|
49
|
-
export declare const CHECK_HQ_HOOKS_SETTINGS_SCOPE: readonly string[];
|
|
50
34
|
/**
|
|
51
35
|
* Whether hook dispatch was observed this session. Mirrors the wrapper's
|
|
52
36
|
* `HQ runtime enforcement: …` line. `NOT CHECKED` is the default when
|
|
@@ -31,9 +31,9 @@ export const CHECK_HQ_HOOKS_PASS_EXIT = 0;
|
|
|
31
31
|
/** Exit code check-hq-hooks.sh returns on a health failure. Preserved. */
|
|
32
32
|
export const CHECK_HQ_HOOKS_FAIL_EXIT = 2;
|
|
33
33
|
/** Exit code check-hq-hooks.sh returns on a usage error. Preserved. */
|
|
34
|
-
|
|
34
|
+
const CHECK_HQ_HOOKS_USAGE_EXIT = 64;
|
|
35
35
|
/** The runtime-probe result id: whether hooks actually fired (the ledger). */
|
|
36
|
-
|
|
36
|
+
const RUNTIME_ENFORCEMENT_CHECK_ID = "hooks.runtime.enforcement";
|
|
37
37
|
/**
|
|
38
38
|
* The `hq doctor` check ids that correspond to check-hq-hooks.sh's *settings
|
|
39
39
|
* load* scope — the subset of the hooks family the wrapper is allowed to fail
|
|
@@ -45,7 +45,7 @@ export const RUNTIME_ENFORCEMENT_CHECK_ID = "hooks.runtime.enforcement";
|
|
|
45
45
|
* scope, so the wrapper stays byte-for-byte compatible with callers that only
|
|
46
46
|
* ever cared whether the hook configuration could load.
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
const CHECK_HQ_HOOKS_SETTINGS_SCOPE = [
|
|
49
49
|
"hooks.settings-present",
|
|
50
50
|
"hooks.settings-required-command-hooks",
|
|
51
51
|
"hooks.settings-valid-json",
|
|
@@ -126,24 +126,8 @@ export declare function claudeCodeFlagEvidence(env: NodeJS.ProcessEnv): Platform
|
|
|
126
126
|
* `com.googlecode.iterm2`), so this fires only under a real desktop host.
|
|
127
127
|
*/
|
|
128
128
|
export declare function bundleIdEvidence(env: NodeJS.ProcessEnv): PlatformEvidence | null;
|
|
129
|
-
/**
|
|
130
|
-
* Codex runtime environment variables, kept in parity with the canonical shell
|
|
131
|
-
* detector `core/scripts/lib/detect-codex.sh`. Presence of any is a strong Codex
|
|
132
|
-
* signal. One evidence entry is produced per variable found so a single Codex
|
|
133
|
-
* signal can be tested in isolation.
|
|
134
|
-
*/
|
|
135
|
-
export declare const CODEX_ENV_VARS: readonly ["CODEX_SANDBOX", "CODEX_SESSION_ID", "CODEX_EXECUTION_ID", "CODEX_AGENT_ID", "CODEX_THREAD_ID", "CODEX_SHELL", "CODEX_CI", "CODEX_INTERNAL_ORIGINATOR_OVERRIDE", "OPENAI_CODEX"];
|
|
136
129
|
/** Every Codex environment signal present in `env`, one entry per variable. */
|
|
137
130
|
export declare function codexEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidence[];
|
|
138
|
-
/**
|
|
139
|
-
* Grok runtime environment variables. Restricted to variables the Grok host
|
|
140
|
-
* exports into the session (a session id, its workspace root) — HQ's own Grok
|
|
141
|
-
* *launcher* variables (`GROK_BIN`, `GROK_CONFIG`, `XAI_API_KEY`, …) are
|
|
142
|
-
* excluded on purpose: those can be set inside a Claude or Codex session that is
|
|
143
|
-
* merely preparing to launch Grok, and treating them as "we are running under
|
|
144
|
-
* Grok" would be a false positive.
|
|
145
|
-
*/
|
|
146
|
-
export declare const GROK_ENV_VARS: readonly ["GROK_SESSION_ID", "GROK_WORKSPACE_ROOT"];
|
|
147
131
|
/** Every Grok environment signal present in `env`, one entry per variable. */
|
|
148
132
|
export declare function grokEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidence[];
|
|
149
133
|
/**
|
|
@@ -157,10 +141,6 @@ export declare function grokEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidenc
|
|
|
157
141
|
* on its environment signals instead, and this function does not guess it.
|
|
158
142
|
*/
|
|
159
143
|
export declare function ancestryEvidence(ancestors: AncestorProcess[]): PlatformEvidence[];
|
|
160
|
-
/** The one command this module ever launches to read process ancestry. */
|
|
161
|
-
export declare const PS_COMMAND = "ps";
|
|
162
|
-
/** `ps` arguments: every process as `pid ppid comm`, header-suppressed. */
|
|
163
|
-
export declare const PS_ARGS: readonly ["-Ao", "pid=,ppid=,comm="];
|
|
164
144
|
/**
|
|
165
145
|
* Low-level process-table provider: runs a command and returns its stdout, or
|
|
166
146
|
* `null` on failure. This is the single seam through which any process is
|
|
@@ -186,12 +166,6 @@ export interface ReadAncestryOptions {
|
|
|
186
166
|
* environment signals rather than throwing.
|
|
187
167
|
*/
|
|
188
168
|
export declare function readProcessAncestry(options?: ReadAncestryOptions): AncestorProcess[];
|
|
189
|
-
/**
|
|
190
|
-
* Collect the environment signals, in a stable order: entrypoint, the Claude
|
|
191
|
-
* flag, bundle id, Codex vars, then Grok vars. Process ancestry is collected
|
|
192
|
-
* separately (and lazily) by {@link detectPlatform}.
|
|
193
|
-
*/
|
|
194
|
-
export declare function collectEnvEvidence(env: NodeJS.ProcessEnv): PlatformEvidence[];
|
|
195
169
|
/**
|
|
196
170
|
* Collect every signal from an environment and an explicit ancestor chain, in a
|
|
197
171
|
* stable order: environment signals first, then process ancestry. Exposed for
|
|
@@ -150,7 +150,7 @@ function looksLikeClaudeDesktopBundle(id) {
|
|
|
150
150
|
* signal. One evidence entry is produced per variable found so a single Codex
|
|
151
151
|
* signal can be tested in isolation.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
const CODEX_ENV_VARS = [
|
|
154
154
|
"CODEX_SANDBOX",
|
|
155
155
|
"CODEX_SESSION_ID",
|
|
156
156
|
"CODEX_EXECUTION_ID",
|
|
@@ -185,7 +185,7 @@ export function codexEnvEvidence(env) {
|
|
|
185
185
|
* merely preparing to launch Grok, and treating them as "we are running under
|
|
186
186
|
* Grok" would be a false positive.
|
|
187
187
|
*/
|
|
188
|
-
|
|
188
|
+
const GROK_ENV_VARS = ["GROK_SESSION_ID", "GROK_WORKSPACE_ROOT"];
|
|
189
189
|
/** Every Grok environment signal present in `env`, one entry per variable. */
|
|
190
190
|
export function grokEnvEvidence(env) {
|
|
191
191
|
const found = [];
|
|
@@ -249,9 +249,9 @@ const DEFAULT_MAX_HOPS = 20;
|
|
|
249
249
|
/** Wall-clock bound for the single `ps` snapshot, keeping startup fast. */
|
|
250
250
|
const PS_TIMEOUT_MS = 250;
|
|
251
251
|
/** The one command this module ever launches to read process ancestry. */
|
|
252
|
-
|
|
252
|
+
const PS_COMMAND = "ps";
|
|
253
253
|
/** `ps` arguments: every process as `pid ppid comm`, header-suppressed. */
|
|
254
|
-
|
|
254
|
+
const PS_ARGS = ["-Ao", "pid=,ppid=,comm="];
|
|
255
255
|
/**
|
|
256
256
|
* Read this process's ancestor chain (nearest parent first) by taking one `ps`
|
|
257
257
|
* snapshot of the process table and walking parent pointers upward. A single
|
|
@@ -330,7 +330,7 @@ function defaultPsSpawner(command, args) {
|
|
|
330
330
|
* flag, bundle id, Codex vars, then Grok vars. Process ancestry is collected
|
|
331
331
|
* separately (and lazily) by {@link detectPlatform}.
|
|
332
332
|
*/
|
|
333
|
-
|
|
333
|
+
function collectEnvEvidence(env) {
|
|
334
334
|
const evidence = [];
|
|
335
335
|
const entrypoint = entrypointEvidence(env);
|
|
336
336
|
if (entrypoint)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { laneRunsOnHost } from "./admission.js";
|
|
3
4
|
import { appendDropboxMessage } from "./dropbox.js";
|
|
4
5
|
import { LanesError } from "./errors.js";
|
|
5
6
|
import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
@@ -406,7 +407,7 @@ function recordOperatorEscalation(hqRoot, record) {
|
|
|
406
407
|
async function runLeadAutopilotPassUnlocked(input) {
|
|
407
408
|
const { hqRoot, host, now } = input;
|
|
408
409
|
const result = { ok: true, host, evaluated: 0, sent: [], started: [], escalated: [], skipped: [] };
|
|
409
|
-
const leads = parseRegistry(hqRoot)
|
|
410
|
+
const leads = parseRegistry(hqRoot);
|
|
410
411
|
const lanes = readAllLanes(hqRoot);
|
|
411
412
|
const byId = new Map(lanes.map((lane) => [lane.lane_id, lane]));
|
|
412
413
|
const stateFile = path.join(hqRoot, "workspace", "lane-briefs", "autopilot-state.json");
|
|
@@ -422,131 +423,150 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
422
423
|
continue;
|
|
423
424
|
}
|
|
424
425
|
const registeredLocalHost = registered.host === "local" || registered.host === input.hostname;
|
|
425
|
-
|
|
426
|
-
|
|
426
|
+
const hostMatchesRegistry = registeredLocalHost
|
|
427
|
+
? lead.host.kind === "local"
|
|
428
|
+
: lead.host.kind === "outpost" && lead.host.id === registered.host && input.outpostId === registered.host;
|
|
429
|
+
if (!hostMatchesRegistry ||
|
|
430
|
+
!laneRunsOnHost(lead, { hostname: input.hostname, outpostId: input.outpostId })) {
|
|
427
431
|
result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_host_mismatch" });
|
|
428
432
|
continue;
|
|
429
433
|
}
|
|
430
|
-
const enabled = await (input.readFlag ?? readLanesRegistryFlag)(LEAD_AUTOPILOT_FLAG, { company: lead.company });
|
|
431
|
-
if (!enabled) {
|
|
432
|
-
result.skipped.push({ lead_lane: lead.lane_id, reason: "flag_off" });
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
const status = readStatus(hqRoot, registered.status, lead.company.slug);
|
|
436
|
-
const statusUpdatedAt = typeof status.updated_at === "string" && Number.isFinite(Date.parse(status.updated_at))
|
|
437
|
-
? new Date(Date.parse(status.updated_at)).toISOString()
|
|
438
|
-
: undefined;
|
|
439
|
-
result.evaluated += 1;
|
|
440
|
-
const stories = projectStories(hqRoot, registered.prd, lead.company.slug, lead.project_id);
|
|
441
|
-
const children = lanes.filter((lane) => lane.senior.kind === "lane" && lane.senior.id === lead.lane_id);
|
|
442
|
-
const planned = planLeadAutopilotWakes({
|
|
443
|
-
lead,
|
|
444
|
-
children,
|
|
445
|
-
stories,
|
|
446
|
-
now,
|
|
447
|
-
managerStatus: status,
|
|
448
|
-
keepAliveResumed: input.keepAliveResumedLeads?.has(lead.lane_id) === true,
|
|
449
|
-
});
|
|
450
434
|
let stateChanged = false;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
|
|
435
|
+
try {
|
|
436
|
+
const enabled = await (input.readFlag ?? readLanesRegistryFlag)(LEAD_AUTOPILOT_FLAG, { company: lead.company });
|
|
437
|
+
if (!enabled) {
|
|
438
|
+
result.skipped.push({ lead_lane: lead.lane_id, reason: "flag_off" });
|
|
439
|
+
continue;
|
|
456
440
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
state.sent[key]
|
|
441
|
+
const status = readStatus(hqRoot, registered.status, lead.company.slug);
|
|
442
|
+
const statusUpdatedAt = typeof status.updated_at === "string" && Number.isFinite(Date.parse(status.updated_at))
|
|
443
|
+
? new Date(Date.parse(status.updated_at)).toISOString()
|
|
444
|
+
: undefined;
|
|
445
|
+
const stories = projectStories(hqRoot, registered.prd, lead.company.slug, lead.project_id);
|
|
446
|
+
const children = lanes.filter((lane) => lane.senior.kind === "lane" && lane.senior.id === lead.lane_id);
|
|
447
|
+
const planned = planLeadAutopilotWakes({
|
|
448
|
+
lead,
|
|
449
|
+
children,
|
|
450
|
+
stories,
|
|
451
|
+
now,
|
|
452
|
+
managerStatus: status,
|
|
453
|
+
keepAliveResumed: input.keepAliveResumedLeads?.has(lead.lane_id) === true,
|
|
454
|
+
});
|
|
455
|
+
const activeKeys = new Set(planned.map((wake) => `${lead.lane_id}:${wake.condition}`));
|
|
456
|
+
for (const key of Object.keys(state.sent)) {
|
|
457
|
+
if (key.startsWith(`${lead.lane_id}:`) && !activeKeys.has(key)) {
|
|
458
|
+
delete state.sent[key];
|
|
475
459
|
stateChanged = true;
|
|
476
460
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
461
|
+
}
|
|
462
|
+
for (const wake of planned) {
|
|
463
|
+
const key = `${lead.lane_id}:${wake.condition}`;
|
|
464
|
+
const prior = state.sent[key];
|
|
465
|
+
if (prior?.fingerprint === wake.fingerprint) {
|
|
466
|
+
if (input.dryRun)
|
|
467
|
+
continue;
|
|
468
|
+
let current = prior;
|
|
469
|
+
const lastWakeAt = Date.parse(prior.last_wake_at ?? prior.at);
|
|
470
|
+
if (!Number.isFinite(lastWakeAt)) {
|
|
471
|
+
throw new LanesError("lead_autopilot_state_invalid", `Invalid wake time for ${lead.lane_id}`, { lead_lane: lead.lane_id, story_id: wake.story_id });
|
|
472
|
+
}
|
|
473
|
+
if (statusUpdatedAt === undefined || prior.status_updated_at === undefined) {
|
|
474
|
+
throw new LanesError("lead_autopilot_status_activity_unverifiable", `Cannot verify whether lead ${lead.lane_id} acted after its autopilot wake`, { lead_lane: lead.lane_id, story_id: wake.story_id });
|
|
475
|
+
}
|
|
476
|
+
if (!prior.acted && Date.parse(statusUpdatedAt) > Date.parse(prior.status_updated_at)) {
|
|
477
|
+
current = { ...prior, acted: true };
|
|
478
|
+
state.sent[key] = current;
|
|
479
|
+
stateChanged = true;
|
|
481
480
|
}
|
|
482
|
-
if (
|
|
483
|
-
const
|
|
484
|
-
if (!
|
|
485
|
-
throw new LanesError("
|
|
481
|
+
if (wake.condition === "fill_grant" && input.startWorker && !current.acted && !current.auto_started) {
|
|
482
|
+
const firstWakeAt = Date.parse(current.at);
|
|
483
|
+
if (!Number.isFinite(firstWakeAt)) {
|
|
484
|
+
throw new LanesError("lead_autopilot_state_invalid", `Invalid fill-grant wake time for ${lead.lane_id}`, { lead_lane: lead.lane_id, story_id: wake.story_id });
|
|
486
485
|
}
|
|
487
|
-
if (
|
|
488
|
-
|
|
486
|
+
if (now.getTime() - firstWakeAt >= LEAD_AUTOPILOT_FILL_DELAY_MS) {
|
|
487
|
+
const story = stories.find((candidate) => candidate.id === wake.story_id && candidate.status === "todo");
|
|
488
|
+
if (!story?.briefPath) {
|
|
489
|
+
throw new LanesError("lead_autopilot_story_brief_missing", `Cannot auto-start story ${wake.story_id}: its project record has no valid brief path`, { lead_lane: lead.lane_id, story_id: wake.story_id, project_id: lead.project_id });
|
|
490
|
+
}
|
|
491
|
+
if (lead.state !== "running" && lead.state !== "queued") {
|
|
492
|
+
current = { ...current, acted: true };
|
|
493
|
+
state.sent[key] = current;
|
|
494
|
+
stateChanged = true;
|
|
495
|
+
result.skipped.push({ lead_lane: lead.lane_id, reason: "auto_start_lead_not_active" });
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
const started = await input.startWorker({ lead, story });
|
|
499
|
+
current = { ...current, auto_started: true, worker_lane: started.lane_id };
|
|
500
|
+
state.sent[key] = current;
|
|
501
|
+
stateChanged = true;
|
|
502
|
+
result.started.push({ lead_lane: lead.lane_id, story_id: story.id, worker_lane: started.lane_id });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!current.acted && !current.auto_started && current.escalated_at === undefined && now.getTime() - lastWakeAt >= LEAD_AUTOPILOT_FILL_DELAY_MS) {
|
|
507
|
+
const wakeCount = current.wake_count ?? 1;
|
|
508
|
+
if (wakeCount < 2) {
|
|
509
|
+
appendDropboxMessage(hqRoot, lead.lane_id, {
|
|
510
|
+
at: now.toISOString(),
|
|
511
|
+
message_id: `lead-autopilot:${wake.fingerprint}:wake-2`,
|
|
512
|
+
kind: "system_notice",
|
|
513
|
+
text: wake.prompt,
|
|
514
|
+
});
|
|
515
|
+
current = { ...current, wake_count: 2, last_wake_at: now.toISOString() };
|
|
489
516
|
state.sent[key] = current;
|
|
490
517
|
stateChanged = true;
|
|
491
|
-
result.
|
|
518
|
+
result.sent.push(wake);
|
|
492
519
|
}
|
|
493
520
|
else {
|
|
494
|
-
const
|
|
495
|
-
|
|
521
|
+
const escalation = { team: registered.team, lead_lane: lead.lane_id, condition: wake.condition, story_id: wake.story_id, at: now.toISOString(), fingerprint: wake.fingerprint };
|
|
522
|
+
recordOperatorEscalation(hqRoot, escalation);
|
|
523
|
+
current = { ...current, escalated_at: now.toISOString() };
|
|
496
524
|
state.sent[key] = current;
|
|
497
525
|
stateChanged = true;
|
|
498
|
-
result.
|
|
526
|
+
result.escalated.push(escalation);
|
|
499
527
|
}
|
|
500
528
|
}
|
|
529
|
+
continue;
|
|
501
530
|
}
|
|
502
|
-
if (!
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const escalation = { team: registered.team, lead_lane: lead.lane_id, condition: wake.condition, story_id: wake.story_id, at: now.toISOString(), fingerprint: wake.fingerprint };
|
|
518
|
-
recordOperatorEscalation(hqRoot, escalation);
|
|
519
|
-
current = { ...current, escalated_at: now.toISOString() };
|
|
520
|
-
state.sent[key] = current;
|
|
521
|
-
stateChanged = true;
|
|
522
|
-
result.escalated.push(escalation);
|
|
523
|
-
}
|
|
531
|
+
if (!input.dryRun) {
|
|
532
|
+
appendDropboxMessage(hqRoot, lead.lane_id, {
|
|
533
|
+
at: now.toISOString(),
|
|
534
|
+
message_id: `lead-autopilot:${wake.fingerprint}`,
|
|
535
|
+
kind: "system_notice",
|
|
536
|
+
text: wake.prompt,
|
|
537
|
+
});
|
|
538
|
+
state.sent[key] = {
|
|
539
|
+
fingerprint: wake.fingerprint,
|
|
540
|
+
at: now.toISOString(),
|
|
541
|
+
...(statusUpdatedAt ? { status_updated_at: statusUpdatedAt } : {}),
|
|
542
|
+
wake_count: 1,
|
|
543
|
+
last_wake_at: now.toISOString(),
|
|
544
|
+
};
|
|
545
|
+
stateChanged = true;
|
|
524
546
|
}
|
|
525
|
-
|
|
547
|
+
result.sent.push(wake);
|
|
526
548
|
}
|
|
527
|
-
if (!input.dryRun)
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
...(statusUpdatedAt ? { status_updated_at: statusUpdatedAt } : {}),
|
|
538
|
-
wake_count: 1,
|
|
539
|
-
last_wake_at: now.toISOString(),
|
|
540
|
-
};
|
|
541
|
-
stateChanged = true;
|
|
542
|
-
}
|
|
543
|
-
result.sent.push(wake);
|
|
549
|
+
if (!input.dryRun && stateChanged)
|
|
550
|
+
writeState(stateFile, state);
|
|
551
|
+
result.evaluated += 1;
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
if (!input.dryRun && stateChanged)
|
|
555
|
+
writeState(stateFile, state);
|
|
556
|
+
if (!(error instanceof LanesError) || LEAD_AUTOPILOT_PASS_FATAL_ERRORS.has(error.error))
|
|
557
|
+
throw error;
|
|
558
|
+
result.skipped.push({ lead_lane: lead.lane_id, reason: error.error });
|
|
544
559
|
}
|
|
545
|
-
if (!input.dryRun && stateChanged)
|
|
546
|
-
writeState(stateFile, state);
|
|
547
560
|
}
|
|
548
561
|
return result;
|
|
549
562
|
}
|
|
563
|
+
// Tenant-boundary and path-safety failures stop the whole pass; other per-lead
|
|
564
|
+
// data errors skip only that lead.
|
|
565
|
+
const LEAD_AUTOPILOT_PASS_FATAL_ERRORS = new Set([
|
|
566
|
+
"lead_autopilot_tenant_mismatch",
|
|
567
|
+
"lead_autopilot_status_path_invalid",
|
|
568
|
+
"lead_autopilot_escalation_record_invalid",
|
|
569
|
+
]);
|
|
550
570
|
const LEAD_AUTOPILOT_STALE_LOCK_MAX_AGE_MS = 60 * 60_000;
|
|
551
571
|
function processStartIdentity(pid) {
|
|
552
572
|
try {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Each failure class maps to a distinct step in the onboarding flow,
|
|
5
5
|
* making it easy for the installer to show targeted recovery hints.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
declare class OnboardingError extends Error {
|
|
8
8
|
readonly step: string;
|
|
9
9
|
readonly cause?: Error | undefined;
|
|
10
10
|
constructor(message: string, step: string, cause?: Error | undefined);
|
|
@@ -30,4 +30,5 @@ export declare class InviteAcceptError extends OnboardingError {
|
|
|
30
30
|
export declare class StsVerifyError extends OnboardingError {
|
|
31
31
|
constructor(message: string, cause?: Error);
|
|
32
32
|
}
|
|
33
|
+
export {};
|
|
33
34
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Each failure class maps to a distinct step in the onboarding flow,
|
|
5
5
|
* making it easy for the installer to show targeted recovery hints.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
class OnboardingError extends Error {
|
|
8
8
|
step;
|
|
9
9
|
cause;
|
|
10
10
|
constructor(message, step, cause) {
|
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
* invite/accept into create-company and join-company flows with
|
|
8
8
|
* checkpoint/resume.
|
|
9
9
|
*/
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export { OnboardingError, PersonCreationError, CompanyCreationError, ProvisioningError, MembershipBootstrapError, FirstSyncError, InviteAcceptError, StsVerifyError, } from "./errors.js";
|
|
13
|
-
export type { OnboardingInput, CreateCompanyInput, JoinCompanyInput, OnboardingConfig, OnboardingResult, OnboardingProgress, OnboardingStep, StepStatus, ProgressCallback, OnboardingCheckpoint, DesktopInstallerContract, HqConfig, } from "./types.js";
|
|
10
|
+
export { readCheckpoint } from "./checkpoint.js";
|
|
11
|
+
export type { OnboardingCheckpoint } from "./types.js";
|
|
14
12
|
export { runOnboardCli } from "./cli/onboard.js";
|
|
15
|
-
export type { OnboardCliOptions, OnboardCliResult } from "./cli/onboard.js";
|
|
16
|
-
export { formatProgress, formatSummary, formatError, validateSlug, validateEmail } from "./cli/prompts.js";
|
|
17
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -7,10 +7,7 @@
|
|
|
7
7
|
* invite/accept into create-company and join-company flows with
|
|
8
8
|
* checkpoint/resume.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
export { readCheckpoint
|
|
12
|
-
export { OnboardingError, PersonCreationError, CompanyCreationError, ProvisioningError, MembershipBootstrapError, FirstSyncError, InviteAcceptError, StsVerifyError, } from "./errors.js";
|
|
13
|
-
// CLI
|
|
10
|
+
// The CLI command and identity guard are the only consumers of this barrel.
|
|
11
|
+
export { readCheckpoint } from "./checkpoint.js";
|
|
14
12
|
export { runOnboardCli } from "./cli/onboard.js";
|
|
15
|
-
export { formatProgress, formatSummary, formatError, validateSlug, validateEmail } from "./cli/prompts.js";
|
|
16
13
|
//# sourceMappingURL=index.js.map
|
|
@@ -11,13 +11,6 @@ export type EmbedLockProcessEvents = Pick<NodeJS.Process, 'once'> & {
|
|
|
11
11
|
removeListener?: NodeJS.Process['removeListener'];
|
|
12
12
|
};
|
|
13
13
|
export type EmbedLockRunResult = 'ran' | 'skipped' | 'busy' | 'unavailable';
|
|
14
|
-
/**
|
|
15
|
-
* Serializes QMD writes started through HQ-managed hq-cli paths. A bare `qmd
|
|
16
|
-
* update` / `qmd embed` (or another tool that does not call this module) cannot
|
|
17
|
-
* participate, because qmd is a third-party package and does not know about
|
|
18
|
-
* HQ's lock protocol.
|
|
19
|
-
*/
|
|
20
|
-
export declare const EMBED_LOCK_NAME = "qmd-reindex-bg.lock";
|
|
21
14
|
export declare function defaultEmbedLockDependencies(env?: NodeJS.ProcessEnv): EmbedLockDependencies;
|
|
22
15
|
export declare function lockRoot(home: string): string;
|
|
23
16
|
export declare function lockPath(home: string): string;
|
|
@@ -6,7 +6,7 @@ import * as path from 'node:path';
|
|
|
6
6
|
* participate, because qmd is a third-party package and does not know about
|
|
7
7
|
* HQ's lock protocol.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
const EMBED_LOCK_NAME = 'qmd-reindex-bg.lock';
|
|
10
10
|
export function defaultEmbedLockDependencies(env = process.env) {
|
|
11
11
|
return {
|
|
12
12
|
env,
|
|
@@ -22,10 +22,6 @@ export type LoadGateDependencies = {
|
|
|
22
22
|
freemem: () => number;
|
|
23
23
|
totalmem: () => number;
|
|
24
24
|
};
|
|
25
|
-
type CpuCounters = {
|
|
26
|
-
total: number;
|
|
27
|
-
idle: number;
|
|
28
|
-
};
|
|
29
25
|
export type EmbedLoadDecision = {
|
|
30
26
|
action: 'embed';
|
|
31
27
|
forced: false;
|
|
@@ -44,11 +40,8 @@ export declare class LoadGateConfigurationError extends Error {
|
|
|
44
40
|
constructor(value: string);
|
|
45
41
|
}
|
|
46
42
|
export declare function defaultLoadGateDependencies(env?: NodeJS.ProcessEnv): LoadGateDependencies;
|
|
47
|
-
export declare function parseProcStatAggregate(contents: string): CpuCounters | undefined;
|
|
48
|
-
export declare function cpuBusyPercent(before: CpuCounters, after: CpuCounters): number | undefined;
|
|
49
43
|
/** Sample aggregate CPU busy time over 200 ms, falling back only without /proc/stat. */
|
|
50
44
|
export declare function measureCpuPercent(dependencies: LoadGateDependencies): number;
|
|
51
|
-
export declare function memAvailableFraction(contents: string): number | undefined;
|
|
52
45
|
/** Prefer Linux MemAvailable, because MemFree alone treats reclaimable cache as used memory. */
|
|
53
46
|
export declare function measureMemoryPercent(dependencies: LoadGateDependencies): number;
|
|
54
47
|
export declare function parseLoadThreshold(env: NodeJS.ProcessEnv): number | undefined;
|
|
@@ -61,5 +54,4 @@ export declare function loadGateStatePath(home: string): string;
|
|
|
61
54
|
export declare function checkEmbedLoad(home: string, dependencies: LoadGateDependencies): EmbedLoadDecision;
|
|
62
55
|
/** Reset the starvation counter only after qmd embed has actually returned successfully. */
|
|
63
56
|
export declare function recordSuccessfulEmbed(home: string, dependencies: LoadGateDependencies): void;
|
|
64
|
-
export {};
|
|
65
57
|
//# sourceMappingURL=load-gate.d.ts.map
|