@indigoai-us/hq-cli 5.345.200 → 5.345.201
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 +14 -0
- package/dist/commands/bot.d.ts +47 -0
- package/dist/commands/bot.js +200 -20
- package/dist/commands/cloud.d.ts +39 -0
- package/dist/commands/cloud.js +173 -82
- package/dist/commands/deploy-domains.js +8 -4
- package/dist/lib/bot/api.d.ts +8 -0
- package/dist/lib/bot/api.js +9 -0
- package/dist/lib/bot/fleet-local.d.ts +28 -0
- package/dist/lib/bot/fleet-local.js +75 -6
- package/dist/lib/bot/jobs/source-report.d.ts +31 -0
- package/dist/lib/bot/jobs/source-report.js +276 -0
- package/dist/lib/bot/legacy-convert.d.ts +107 -4
- package/dist/lib/bot/legacy-convert.js +159 -13
- package/dist/lib/bot/legacy-provider-login.d.ts +34 -0
- package/dist/lib/bot/legacy-provider-login.js +144 -0
- package/dist/lib/bot/run.js +20 -1
- package/dist/lib/doctor/checks/bots.d.ts +4 -0
- package/dist/lib/doctor/checks/bots.js +33 -0
- package/dist/lib/lanes/store.js +7 -0
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/types.js +1 -0
- package/dist/lib/lanes/watcher.d.ts +0 -33
- package/dist/lib/lanes/watcher.js +7 -0
- package/dist/utils/resident-package-preload.js +57 -4
- package/package.json +7 -7
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is the provider sign-in a legacy Codex or Grok bot needs readable on this
|
|
3
|
+
* computer, the way HQ Fleet Local imports it (`hq-fleet-local auth login`)?
|
|
4
|
+
*
|
|
5
|
+
* - Codex: the Codex CLI login file `<CODEX_HOME>/auth.json` (else
|
|
6
|
+
* ~/.codex/auth.json) with a refreshable, unexpired token. A bot with its
|
|
7
|
+
* own Codex login (hq bot login) or a capacity account imports from that
|
|
8
|
+
* home instead, so the account it answered with comes along.
|
|
9
|
+
* - Grok: the runtime only copies another Fleet bot's Grok login (its own
|
|
10
|
+
* device-code sign-in needs a browser), so the probe asks the helper of
|
|
11
|
+
* every other Fleet bot on this computer whether it holds one.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here reads, prints or copies a credential value: the Codex check
|
|
14
|
+
* decodes only the token's expiry claim, and the Grok check asks the helper
|
|
15
|
+
* for a yes or no.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as os from "node:os";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { fleetLocalProviderAuthStatus, listFleetLocalProfiles } from "./fleet-local.js";
|
|
21
|
+
import { botOwnAuthHome, botRuntimeAuth, capacityAccountHome } from "./runtime-auth.js";
|
|
22
|
+
export function codexCliAuthPath(home) {
|
|
23
|
+
return path.join(home, "auth.json");
|
|
24
|
+
}
|
|
25
|
+
/** The `exp` claim of a JWT, in seconds, or null when the token has none that reads. */
|
|
26
|
+
export function jwtExpiry(token) {
|
|
27
|
+
const parts = token.split(".");
|
|
28
|
+
if (parts.length < 2)
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
32
|
+
return typeof claims.exp === "number" && Number.isFinite(claims.exp) ? claims.exp : null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Mirrors the runtime's `_import_codex_cli_tokens`: a refreshable, unexpired login in `<home>/auth.json`. */
|
|
39
|
+
export function codexCliLoginReadable(home, now = Date.now()) {
|
|
40
|
+
const file = codexCliAuthPath(home);
|
|
41
|
+
if (!fs.existsSync(file))
|
|
42
|
+
return { readable: false, detail: `no Codex CLI login at ${file}` };
|
|
43
|
+
let tokens;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(fs.readFileSync(file, "utf8").replace(/^\uFEFF/, ""));
|
|
46
|
+
tokens = parsed.tokens && typeof parsed.tokens === "object" ? parsed.tokens : undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { readable: false, detail: `${file} could not be read` };
|
|
50
|
+
}
|
|
51
|
+
const access = typeof tokens?.access_token === "string" ? tokens.access_token.trim() : "";
|
|
52
|
+
const refresh = typeof tokens?.refresh_token === "string" ? tokens.refresh_token.trim() : "";
|
|
53
|
+
if (!access || !refresh)
|
|
54
|
+
return { readable: false, detail: `${file} holds no refreshable Codex login` };
|
|
55
|
+
const exp = jwtExpiry(access);
|
|
56
|
+
if (exp !== null && exp * 1000 <= now)
|
|
57
|
+
return { readable: false, detail: `the Codex login at ${file} has expired` };
|
|
58
|
+
return { readable: true, detail: `Codex CLI login at ${file}` };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Where a legacy bot's Codex login lives: its own folder, a capacity account,
|
|
62
|
+
* or the owner's (CODEX_HOME, else ~/.codex). `env` is set when the import
|
|
63
|
+
* must look somewhere other than the owner's default.
|
|
64
|
+
*/
|
|
65
|
+
export function legacyCodexHome(dir, config, env = process.env, homedir = os.homedir()) {
|
|
66
|
+
const auth = botRuntimeAuth(config, "codex");
|
|
67
|
+
if (!auth) {
|
|
68
|
+
if (config.runtimeAuth?.codex !== undefined)
|
|
69
|
+
return { error: `${config.name}'s saved codex login setting is not readable` };
|
|
70
|
+
return { home: env.CODEX_HOME?.trim() || path.join(homedir, ".codex") };
|
|
71
|
+
}
|
|
72
|
+
if (auth.mode === "own") {
|
|
73
|
+
const home = botOwnAuthHome(dir, "codex");
|
|
74
|
+
return { home, env: { CODEX_HOME: home } };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const home = capacityAccountHome(config.hqRoot, "codex", auth.account);
|
|
78
|
+
return { home, env: { CODEX_HOME: home } };
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** The helper asks the keychain per profile, so one list of bots shares a scan this long. */
|
|
85
|
+
export const GROK_HOLDER_SCAN_TTL_MS = 30_000;
|
|
86
|
+
let grokHolderScan = null;
|
|
87
|
+
export function resetGrokHolderScan() {
|
|
88
|
+
grokHolderScan = null;
|
|
89
|
+
}
|
|
90
|
+
/** Every Fleet bot here with a Grok OAuth login, scanned at most once per TTL in this process (live deps are never cached). */
|
|
91
|
+
function grokHolders(deps, now) {
|
|
92
|
+
const live = !deps.profiles && !deps.status;
|
|
93
|
+
if (live && grokHolderScan && now - grokHolderScan.at < GROK_HOLDER_SCAN_TTL_MS)
|
|
94
|
+
return grokHolderScan;
|
|
95
|
+
const profiles = (deps.profiles ?? listFleetLocalProfiles)();
|
|
96
|
+
const status = deps.status ?? ((agentUid) => fleetLocalProviderAuthStatus(agentUid, "xai-oauth"));
|
|
97
|
+
const holders = [];
|
|
98
|
+
const unanswered = [];
|
|
99
|
+
for (const profile of profiles) {
|
|
100
|
+
const found = status(profile.agentUid);
|
|
101
|
+
if (found === null)
|
|
102
|
+
unanswered.push(profile.agentUid);
|
|
103
|
+
else if (found.authenticated && found.authType === "oauth")
|
|
104
|
+
holders.push(profile.agentUid);
|
|
105
|
+
}
|
|
106
|
+
const scan = { at: now, holders, uids: profiles.map((profile) => profile.agentUid), unanswered };
|
|
107
|
+
// A scan that found nothing or got no answer may be a passing failure: ask again next time.
|
|
108
|
+
if (live && profiles.length > 0 && unanswered.length < profiles.length)
|
|
109
|
+
grokHolderScan = scan;
|
|
110
|
+
return scan;
|
|
111
|
+
}
|
|
112
|
+
/** Mirrors the helper's `_existing_machine_oauth`: another Fleet bot here holds a Grok OAuth login. */
|
|
113
|
+
export function grokLoginReadableHere(exclude, deps = {}, now = Date.now()) {
|
|
114
|
+
const scan = grokHolders(deps, now);
|
|
115
|
+
const other = (agentUid) => agentUid.toLowerCase() !== exclude.toLowerCase();
|
|
116
|
+
const holder = scan.holders.find(other);
|
|
117
|
+
if (holder)
|
|
118
|
+
return { readable: true, detail: `the Grok sign-in of HQ Fleet bot ${holder} on this computer` };
|
|
119
|
+
const others = scan.uids.filter(other);
|
|
120
|
+
const unanswered = scan.unanswered.filter(other);
|
|
121
|
+
if (others.length && unanswered.length === others.length) {
|
|
122
|
+
return { readable: false, detail: `the HQ Fleet helper could not say whether any of the ${others.length} other Fleet bot(s) on this computer is signed in to Grok` };
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
readable: false,
|
|
126
|
+
detail: others.length ? `none of the ${others.length} other HQ Fleet bot(s) on this computer is signed in to Grok` : "no other HQ Fleet bot on this computer is signed in to Grok",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** The live probe for a bot: Codex from its login home, Grok from the other Fleet bots here. */
|
|
130
|
+
export function legacyProviderLoginProbe(dir, config) {
|
|
131
|
+
return (runtime) => {
|
|
132
|
+
if (runtime === "grok") {
|
|
133
|
+
if (config.runtimeAuth?.grok !== undefined)
|
|
134
|
+
return { readable: false, detail: "it uses its own Grok login, which HQ Fleet Local cannot import" };
|
|
135
|
+
return grokLoginReadableHere(config.agentUid);
|
|
136
|
+
}
|
|
137
|
+
const home = legacyCodexHome(dir, config);
|
|
138
|
+
if ("error" in home)
|
|
139
|
+
return { readable: false, detail: home.error };
|
|
140
|
+
const login = codexCliLoginReadable(home.home);
|
|
141
|
+
return home.env && login.readable ? { ...login, env: home.env } : login;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=legacy-provider-login.js.map
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createSourceReporter, ensureSourceReportJobs, SOURCE_REPORT_JOB_KIND } from "./jobs/source-report.js";
|
|
1
2
|
import { ensureSignalToLaneJobs, runSignalToLane, SIGNAL_TO_LANE_JOB_KIND } from "./jobs/signal-to-lane.js";
|
|
2
3
|
/**
|
|
3
4
|
* The resident bot process: `hq bot run <name>` (local-bots US-003/US-004).
|
|
@@ -424,7 +425,13 @@ export async function runBot(deps) {
|
|
|
424
425
|
const meetingRider = createMeetingRider(defaultMeetingRiderDeps(config, dir, (message) => log("info", message)));
|
|
425
426
|
const meetingDetectDeps = defaultMeetingDetectDeps(config, dir, (message) => log("info", message));
|
|
426
427
|
const meetingDetect = createMeetingDetect(meetingDetectDeps);
|
|
428
|
+
const sourceReporter = createSourceReporter({ config, api, now, botsDir: path.dirname(dir) });
|
|
427
429
|
const eventHandlers = Object.create(builtinEventHandlers);
|
|
430
|
+
eventHandlers[SOURCE_REPORT_JOB_KIND] = async ({ item }) => {
|
|
431
|
+
const context = typeof item.context === "object" ? item.context : undefined;
|
|
432
|
+
const event = context?.event;
|
|
433
|
+
return sourceReporter.handle(event?.event, context?.payload, context?.company);
|
|
434
|
+
};
|
|
428
435
|
eventHandlers[MEETING_RIDER_JOB_KIND] = async ({ item }) => {
|
|
429
436
|
if (!isExecutiveBot(config))
|
|
430
437
|
return { status: "skipped-precondition", output: "Not the Executive" };
|
|
@@ -2060,7 +2067,7 @@ export async function runBot(deps) {
|
|
|
2060
2067
|
if (eventHandler) {
|
|
2061
2068
|
// Recoverable built-ins persist intent before acknowledging the event.
|
|
2062
2069
|
// Signal lane creation is idempotent, so a pre-ack crash can replay.
|
|
2063
|
-
const durableEvent = typeof job.context === "object" && (job.context?.jobKind === MEETING_RIDER_JOB_KIND || job.context?.jobKind === SIGNAL_TO_LANE_JOB_KIND);
|
|
2070
|
+
const durableEvent = typeof job.context === "object" && (job.context?.jobKind === SOURCE_REPORT_JOB_KIND || job.context?.jobKind === MEETING_RIDER_JOB_KIND || job.context?.jobKind === SIGNAL_TO_LANE_JOB_KIND);
|
|
2064
2071
|
if (!durableEvent)
|
|
2065
2072
|
markProcessed(dir, id, now().toISOString());
|
|
2066
2073
|
let completion;
|
|
@@ -2884,6 +2891,12 @@ export async function runBot(deps) {
|
|
|
2884
2891
|
// up below: end here, before the kickoff can send a "needs a sign-in" DM.
|
|
2885
2892
|
if (stopping)
|
|
2886
2893
|
return await stoppedDuringStartup();
|
|
2894
|
+
try {
|
|
2895
|
+
ensureSourceReportJobs(dir, config);
|
|
2896
|
+
}
|
|
2897
|
+
catch {
|
|
2898
|
+
log("warn", "Source report registration failed");
|
|
2899
|
+
}
|
|
2887
2900
|
try {
|
|
2888
2901
|
ensureSignalToLaneJobs(dir, config);
|
|
2889
2902
|
}
|
|
@@ -3016,6 +3029,12 @@ export async function runBot(deps) {
|
|
|
3016
3029
|
// A failed signals run's automatic retry, once due (no model turn).
|
|
3017
3030
|
enqueueDueSignalsRetries();
|
|
3018
3031
|
if (isExecutiveBot(config)) {
|
|
3032
|
+
try {
|
|
3033
|
+
await sourceReporter.recover();
|
|
3034
|
+
}
|
|
3035
|
+
catch {
|
|
3036
|
+
log("warn", "Source report recovery deferred");
|
|
3037
|
+
}
|
|
3019
3038
|
try {
|
|
3020
3039
|
meetingDetect.tick();
|
|
3021
3040
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type BotConfig } from "../../bot/config.js";
|
|
1
2
|
import { type ClaudeLoginCheck } from "../../bot/claude-login.js";
|
|
2
3
|
import { type BotStatusFile } from "../../bot/status.js";
|
|
4
|
+
import { type LegacyMoveState } from "../../bot/legacy-convert.js";
|
|
3
5
|
import { type NodeBinaryIo } from "../../node-binary.js";
|
|
4
6
|
import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
|
|
5
7
|
export declare const BOTS_FAMILY_ID = "bots";
|
|
@@ -11,6 +13,8 @@ export interface BotsDoctorDeps {
|
|
|
11
13
|
nodeIo?: NodeBinaryIo;
|
|
12
14
|
/** The Claude Code login read the way the HQ Fleet Runtime reads it (tests). */
|
|
13
15
|
claudeLogin?: () => ClaudeLoginCheck;
|
|
16
|
+
/** Whether and why a legacy bot moves to the HQ Fleet Runtime at its next start (tests). */
|
|
17
|
+
legacyMove?: (dir: string, config: BotConfig) => LegacyMoveState;
|
|
14
18
|
}
|
|
15
19
|
/** The last reported failure, unless a later turn worked. */
|
|
16
20
|
export declare function unresolvedLastFailure(status: BotStatusFile | null): BotStatusFile["lastFailure"] | null;
|
|
@@ -10,10 +10,39 @@ import { botsRoot, botDir, launchdLabel } from "../../bot/paths.js";
|
|
|
10
10
|
import { HQ_FLEET_RUNTIME_ENGINE_ID, readBotConfig } from "../../bot/config.js";
|
|
11
11
|
import { checkClaudeCodeLogin, claudeLoginFix, describeClaudeLoginSources, } from "../../bot/claude-login.js";
|
|
12
12
|
import { isPidAlive, readBotStatus } from "../../bot/status.js";
|
|
13
|
+
import { legacyMoveState } from "../../bot/legacy-convert.js";
|
|
14
|
+
import { legacyProviderLoginProbe } from "../../bot/legacy-provider-login.js";
|
|
15
|
+
import { runtimeDisplayName } from "../../bot/runtime-sign-in.js";
|
|
13
16
|
import { nodeBinaryProblem } from "../../node-binary.js";
|
|
14
17
|
import { launchdPlistProgramArguments, parseLaunchdCommand } from "../../service-manager/launchd.js";
|
|
15
18
|
export const BOTS_FAMILY_ID = "bots";
|
|
16
19
|
export const BOTS_FAMILY_TITLE = "Local bots";
|
|
20
|
+
function isLegacyBot(config) {
|
|
21
|
+
return config.engine?.id !== HQ_FLEET_RUNTIME_ENGINE_ID;
|
|
22
|
+
}
|
|
23
|
+
/** A bot still on its legacy runner: one WARN when it moves at its next start, one FAIL when it is held. */
|
|
24
|
+
function legacyRuntimeCheck(name, dir, config, state) {
|
|
25
|
+
const tool = runtimeDisplayName(config.runtime);
|
|
26
|
+
const failure = state.lastFailure;
|
|
27
|
+
const lastMove = failure ? ` Its last move failed at ${failure.at}${failure.step ? ` (could not ${failure.step})` : ""}${failure.error ? `: ${failure.error}` : ""}.` : "";
|
|
28
|
+
if (state.decision.convert) {
|
|
29
|
+
return {
|
|
30
|
+
status: "WARN",
|
|
31
|
+
checkId: `bots.${name}.legacy-runtime`,
|
|
32
|
+
target: dir,
|
|
33
|
+
message: `${name} still runs on its older ${tool} runner and moves to the HQ Fleet Runtime at its next start.${lastMove}`,
|
|
34
|
+
remediation: `hq bot start ${name}`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
// The env opt-out is the owner's choice (and read from this shell, not the bot's); a sign-in hold is a bot that cannot work.
|
|
39
|
+
status: state.decision.reason === "kept-by-env" ? "WARN" : "FAIL",
|
|
40
|
+
checkId: `bots.${name}.legacy-runtime`,
|
|
41
|
+
target: dir,
|
|
42
|
+
message: `${name} still runs on its older ${tool} runner (${state.decision.reason}): ${state.notice?.cause ?? state.decision.detail}${lastMove}`,
|
|
43
|
+
remediation: `${state.notice?.fix ?? ""} Details: hq bot status ${name}`.trim(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
17
46
|
const DEFAULT_DEPS = {
|
|
18
47
|
home: () => os.homedir(),
|
|
19
48
|
root: (home) => botsRoot(home ?? os.homedir()),
|
|
@@ -89,6 +118,10 @@ function runChecks(deps) {
|
|
|
89
118
|
continue;
|
|
90
119
|
const status = readBotStatus(dir);
|
|
91
120
|
const alive = !!status && status.state === "running" && isPidAlive(status.pid);
|
|
121
|
+
if (isLegacyBot(config)) {
|
|
122
|
+
const state = (deps.legacyMove ?? ((d, c) => legacyMoveState(d, c, legacyProviderLoginProbe(d, c))))(dir, config);
|
|
123
|
+
results.push(legacyRuntimeCheck(name, dir, config, state));
|
|
124
|
+
}
|
|
92
125
|
if (isClaudeFleetBot(config)) {
|
|
93
126
|
claudeLogin ??= (deps.claudeLogin ?? (() => checkClaudeCodeLogin({ home })))();
|
|
94
127
|
if (claudeLogin.signedIn) {
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -495,6 +495,9 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
495
495
|
const schema_version = requireString(raw.schema_version, "schema_version");
|
|
496
496
|
parseMajorSchema(schema_version, "hq-lane", 1);
|
|
497
497
|
requireString(raw.lane_id, "lane_id");
|
|
498
|
+
if (raw.source_report_pending !== undefined && typeof raw.source_report_pending !== "boolean") {
|
|
499
|
+
throw new LaneSchemaError("source_report_pending must be boolean", { field: "source_report_pending" });
|
|
500
|
+
}
|
|
498
501
|
if (raw.signal !== undefined) {
|
|
499
502
|
if (!raw.signal || typeof raw.signal !== "object" || Array.isArray(raw.signal)) {
|
|
500
503
|
throw new LaneSchemaError("signal must be an object", { field: "signal" });
|
|
@@ -1818,6 +1821,10 @@ export function updateLane(hqRoot, laneId, expectedVersion, writer, updater, dep
|
|
|
1818
1821
|
}
|
|
1819
1822
|
}
|
|
1820
1823
|
if (!isTerminalLaneState(current.state) && isTerminalLaneState(updated.state)) {
|
|
1824
|
+
// Couple the report-event intent to the terminal commit. The resident
|
|
1825
|
+
// Executive can publish it even if the watcher dies before appending.
|
|
1826
|
+
if (updated.signal && updated.company)
|
|
1827
|
+
updated.source_report_pending = true;
|
|
1821
1828
|
const releasedGrant = updated.hire_grant ?? current.hire_grant ?? 0;
|
|
1822
1829
|
return withoutStaleQueuedDetails({
|
|
1823
1830
|
...updated,
|
|
@@ -654,6 +654,8 @@ export interface LaneSignal {
|
|
|
654
654
|
export interface LaneRecord {
|
|
655
655
|
signal?: LaneSignal;
|
|
656
656
|
signal_link?: string;
|
|
657
|
+
/** Event outbox bit, persisted atomically with a signaled lane's terminal transition. */
|
|
658
|
+
source_report_pending?: boolean;
|
|
657
659
|
schema_version: typeof LANE_SCHEMA_VERSION | string;
|
|
658
660
|
lane_id: string;
|
|
659
661
|
/** CLI release that most recently wrote this lane record. Absent on legacy rows. */
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -255,6 +255,7 @@ export const LANE_WRITERS = ["creator", "command", "watcher", "reconcile", "spaw
|
|
|
255
255
|
/** Field ownership at the durable-record mutation boundary (spec §10.1). */
|
|
256
256
|
export const LANE_FIELD_WRITERS = {
|
|
257
257
|
signal: ["creator"], signal_link: ["creator"],
|
|
258
|
+
source_report_pending: ["watcher"],
|
|
258
259
|
schema_version: ["creator"], lane_id: ["creator"], written_by_version: [], owner_written_by_version: [], company: ["creator", "command"], company_history: ["command"],
|
|
259
260
|
project_id: ["command"], story_id: ["command"], no_story_reason: ["creator", "command"], focus_overrides: ["command"], ledger_sync_error: ["story-ledger"], pending_question_ids: ["story-ledger"], stories: ["creator", "command", "watcher", "reconcile", "spawn"], thread_id: ["creator", "command", "spawn"], mesh_company: ["creator", "command"],
|
|
260
261
|
depth: ["command"], senior: ["command"], senior_history: ["command"],
|
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-lane watcher (spec §5.7).
|
|
3
|
-
*
|
|
4
|
-
* Armed at spawn, detached from the caller. Emits on progress, terminal, and
|
|
5
|
-
* silence (20 minutes with the process still alive). Terminal classification
|
|
6
|
-
* follows spec §5: an envelope decides done/ask/blocked; a process that exits
|
|
7
|
-
* with partial output and no envelope is `interrupted`; silence is never
|
|
8
|
-
* success.
|
|
9
|
-
*
|
|
10
|
-
* Progress has two sources. Output files (worker.out, worker.err,
|
|
11
|
-
* last-message.txt) cover codex and grok, which stream as they work. They do
|
|
12
|
-
* not cover Claude: `claude -p --output-format json` writes nothing until it
|
|
13
|
-
* exits, so a healthy Claude lane used to hit the silence deadline at exactly
|
|
14
|
-
* 20 minutes every time, and a hung one looked identical. The second source
|
|
15
|
-
* is logs/tool-activity.json, which the drain hook rewrites on every
|
|
16
|
-
* PreToolUse call (dropbox.ts). A worker that is working calls tools; a
|
|
17
|
-
* worker that has not called a tool or written output for the whole window
|
|
18
|
-
* is `stalled`, and the watcher says so on the lane record.
|
|
19
|
-
*
|
|
20
|
-
* What this cannot tell apart: a worker hung inside one tool call from a
|
|
21
|
-
* worker running one legitimately long tool call (a 25-minute build); a
|
|
22
|
-
* worker looping on tool calls without making progress from a working one;
|
|
23
|
-
* a model stuck mid-generation with no tool call from one that is writing a
|
|
24
|
-
* long answer. It also depends on the drain hook being registered in the
|
|
25
|
-
* provider home (hooks-install.ts): with no hook there is no touch, and the
|
|
26
|
-
* lane reads as stalled every window, which is the pre-existing behaviour.
|
|
27
|
-
* Distinguishing a hung tool from a slow one needs the provider to report
|
|
28
|
-
* per-call state, which none of the three CLIs expose today. `stalled` is
|
|
29
|
-
* therefore a flag for the senior to look at, never a kill.
|
|
30
|
-
*
|
|
31
|
-
* Queue admission of the next lane when a slot frees is owned by the
|
|
32
|
-
* periodic reconcile pass and is out of scope here.
|
|
33
|
-
*/
|
|
34
1
|
import { ENVELOPE_INVALID_REASON, type LaneEnvelope, type LaneCompanyRef, type LaneRecord, type LaneState } from "./types.js";
|
|
35
2
|
import { type QuotaSignal } from "./quota.js";
|
|
36
3
|
import { type ProviderRejection } from "./provider-rejection.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { emitExecutiveLaneFinished } from "../bot/jobs/source-report.js";
|
|
1
2
|
/**
|
|
2
3
|
* Per-lane watcher (spec §5.7).
|
|
3
4
|
*
|
|
@@ -561,6 +562,12 @@ function applyTerminalToLaneWithResult(hqRoot, laneId, classification, opts = {}
|
|
|
561
562
|
updated.model) {
|
|
562
563
|
clearAccountQuotaAfterSuccessfulTurnBestEffort(hqRoot, updated.provider, updated.account, updated.model, new Date(at));
|
|
563
564
|
}
|
|
565
|
+
try {
|
|
566
|
+
emitExecutiveLaneFinished(hqRoot, updated);
|
|
567
|
+
}
|
|
568
|
+
catch (error) {
|
|
569
|
+
process.stderr.write(`hq lanes watcher: source report event failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
570
|
+
}
|
|
564
571
|
notifySlotFreed({
|
|
565
572
|
hqRoot,
|
|
566
573
|
provider: updated.provider,
|
|
@@ -69,15 +69,22 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
69
69
|
"../lib/first-sync-next-step.js",
|
|
70
70
|
"@indigoai-us/hq-cloud",
|
|
71
71
|
"../lib/lanes/reconcile.js",
|
|
72
|
+
"../utils/local-files-overview.js",
|
|
73
|
+
"../utils/vault-api.js",
|
|
74
|
+
"../commands/files-browse.js",
|
|
75
|
+
"@aws-sdk/client-s3",
|
|
76
|
+
"../commands/access.js",
|
|
77
|
+
"../commands/sources.js",
|
|
78
|
+
"../lib/hot-channels/adapters/index.js",
|
|
72
79
|
],
|
|
73
80
|
"daemon-logs-follow": [],
|
|
74
81
|
"mesh-daemon": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
75
|
-
"lanes-watch": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
76
|
-
"monitor-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
77
|
-
"monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
82
|
+
"lanes-watch": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js", "../utils/local-files-overview.js", "../lib/flag-registry.js", "../utils/vault-api.js", "../commands/files-browse.js", "@aws-sdk/client-s3", "../commands/access.js", "../commands/sources.js", "../lib/hot-channels/adapters/index.js", "../commands/lanes.js"],
|
|
83
|
+
"monitor-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js", "../utils/local-files-overview.js", "../lib/flag-registry.js", "../utils/vault-api.js", "../commands/files-browse.js", "@aws-sdk/client-s3", "../commands/access.js", "../commands/sources.js", "../lib/hot-channels/adapters/index.js", "../commands/lanes.js"],
|
|
84
|
+
"monitor-run": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js", "../utils/local-files-overview.js", "../lib/flag-registry.js", "../utils/vault-api.js", "../commands/files-browse.js", "@aws-sdk/client-s3", "../commands/access.js", "../commands/sources.js", "../lib/hot-channels/adapters/index.js"],
|
|
78
85
|
"agents-message-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
79
86
|
"integrations-oauth-wait": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
80
|
-
"outpost-wait": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js"],
|
|
87
|
+
"outpost-wait": ["../commands/lanes.js", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud", "../lib/lanes/reconcile.js", "../utils/local-files-overview.js", "../lib/flag-registry.js", "../utils/vault-api.js", "../commands/files-browse.js", "@aws-sdk/client-s3", "../commands/access.js", "../commands/sources.js", "../lib/hot-channels/adapters/index.js"],
|
|
81
88
|
"secrets-sandbox": [],
|
|
82
89
|
"pkg-update": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../utils/cognito-session.js", "@indigoai-us/hq-cloud"],
|
|
83
90
|
"pack-install": [
|
|
@@ -252,6 +259,14 @@ const LOADERS = {
|
|
|
252
259
|
() => import("../lib/first-sync-next-step.js"),
|
|
253
260
|
() => import("@indigoai-us/hq-cloud"),
|
|
254
261
|
() => import("../lib/lanes/reconcile.js"),
|
|
262
|
+
// Source reports use the signals engine after a resident wait.
|
|
263
|
+
() => import("../utils/local-files-overview.js"),
|
|
264
|
+
() => import("../utils/vault-api.js"),
|
|
265
|
+
() => import("../commands/files-browse.js"),
|
|
266
|
+
() => import("@aws-sdk/client-s3"),
|
|
267
|
+
() => import("../commands/access.js"),
|
|
268
|
+
() => import("../commands/sources.js"),
|
|
269
|
+
() => import("../lib/hot-channels/adapters/index.js"),
|
|
255
270
|
],
|
|
256
271
|
"daemon-logs-follow": [],
|
|
257
272
|
"mesh-daemon": [
|
|
@@ -283,6 +298,16 @@ const LOADERS = {
|
|
|
283
298
|
() => import("@indigoai-us/hq-cloud"),
|
|
284
299
|
// The watcher admits queued lanes in-process after a lane exits.
|
|
285
300
|
() => import("../lib/lanes/reconcile.js"),
|
|
301
|
+
// Source reports use the signals engine after a resident wait.
|
|
302
|
+
() => import("../utils/local-files-overview.js"),
|
|
303
|
+
() => import("../lib/flag-registry.js"),
|
|
304
|
+
() => import("../utils/vault-api.js"),
|
|
305
|
+
() => import("../commands/files-browse.js"),
|
|
306
|
+
() => import("@aws-sdk/client-s3"),
|
|
307
|
+
() => import("../commands/access.js"),
|
|
308
|
+
() => import("../commands/sources.js"),
|
|
309
|
+
() => import("../lib/hot-channels/adapters/index.js"),
|
|
310
|
+
() => import("../commands/lanes.js"),
|
|
286
311
|
],
|
|
287
312
|
"monitor-wait": [
|
|
288
313
|
async () => {
|
|
@@ -299,6 +324,16 @@ const LOADERS = {
|
|
|
299
324
|
() => import("@indigoai-us/hq-cloud"),
|
|
300
325
|
// The watcher admits queued lanes in-process after a lane exits.
|
|
301
326
|
() => import("../lib/lanes/reconcile.js"),
|
|
327
|
+
// Source reports use the signals engine after a resident wait.
|
|
328
|
+
() => import("../utils/local-files-overview.js"),
|
|
329
|
+
() => import("../lib/flag-registry.js"),
|
|
330
|
+
() => import("../utils/vault-api.js"),
|
|
331
|
+
() => import("../commands/files-browse.js"),
|
|
332
|
+
() => import("@aws-sdk/client-s3"),
|
|
333
|
+
() => import("../commands/access.js"),
|
|
334
|
+
() => import("../commands/sources.js"),
|
|
335
|
+
() => import("../lib/hot-channels/adapters/index.js"),
|
|
336
|
+
() => import("../commands/lanes.js"),
|
|
302
337
|
],
|
|
303
338
|
"monitor-run": [
|
|
304
339
|
() => import("../commands/lanes.js"),
|
|
@@ -316,6 +351,15 @@ const LOADERS = {
|
|
|
316
351
|
() => import("@indigoai-us/hq-cloud"),
|
|
317
352
|
// The watcher admits queued lanes in-process after a lane exits.
|
|
318
353
|
() => import("../lib/lanes/reconcile.js"),
|
|
354
|
+
// Source reports use the signals engine after a resident wait.
|
|
355
|
+
() => import("../utils/local-files-overview.js"),
|
|
356
|
+
() => import("../lib/flag-registry.js"),
|
|
357
|
+
() => import("../utils/vault-api.js"),
|
|
358
|
+
() => import("../commands/files-browse.js"),
|
|
359
|
+
() => import("@aws-sdk/client-s3"),
|
|
360
|
+
() => import("../commands/access.js"),
|
|
361
|
+
() => import("../commands/sources.js"),
|
|
362
|
+
() => import("../lib/hot-channels/adapters/index.js"),
|
|
319
363
|
],
|
|
320
364
|
"agents-message-wait": [
|
|
321
365
|
async () => {
|
|
@@ -361,6 +405,15 @@ const LOADERS = {
|
|
|
361
405
|
() => import("@indigoai-us/hq-cloud"),
|
|
362
406
|
// The watcher admits queued lanes in-process after a lane exits.
|
|
363
407
|
() => import("../lib/lanes/reconcile.js"),
|
|
408
|
+
// Source reports use the signals engine after a resident wait.
|
|
409
|
+
() => import("../utils/local-files-overview.js"),
|
|
410
|
+
() => import("../lib/flag-registry.js"),
|
|
411
|
+
() => import("../utils/vault-api.js"),
|
|
412
|
+
() => import("../commands/files-browse.js"),
|
|
413
|
+
() => import("@aws-sdk/client-s3"),
|
|
414
|
+
() => import("../commands/access.js"),
|
|
415
|
+
() => import("../commands/sources.js"),
|
|
416
|
+
() => import("../lib/hot-channels/adapters/index.js"),
|
|
364
417
|
],
|
|
365
418
|
"secrets-sandbox": [],
|
|
366
419
|
"pkg-update": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.201",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
117
117
|
},
|
|
118
118
|
"optionalDependencies": {
|
|
119
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
123
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
124
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
119
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.201",
|
|
120
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.201",
|
|
121
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.201",
|
|
122
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.201",
|
|
123
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.201",
|
|
124
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.201"
|
|
125
125
|
}
|
|
126
126
|
}
|