@indigoai-us/hq-cli 5.304.0 → 5.306.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 +16 -0
- package/dist/command-catalog.generated.d.ts +3 -3
- package/dist/command-catalog.generated.js +5 -3
- package/dist/command-registration-plan.js +8 -0
- package/dist/commands/daemon.js +24 -23
- package/dist/commands/lanes.js +13 -0
- package/dist/commands/mcp-status.js +1 -0
- package/dist/commands/members.js +0 -1
- package/dist/commands/people.d.ts +3 -0
- package/dist/commands/people.js +27 -3
- package/dist/index.js +6 -1
- package/dist/lazy-commands.js +5 -2
- package/dist/lib/aws-sdk-node-warning-output.d.ts +12 -0
- package/dist/lib/aws-sdk-node-warning-output.js +22 -0
- package/dist/lib/company-errors-sdk-warning-flag.d.ts +5 -0
- package/dist/lib/company-errors-sdk-warning-flag.js +8 -0
- package/dist/lib/daemon/catalog.js +6 -2
- package/dist/lib/daemon/remote-control.d.ts +19 -8
- package/dist/lib/daemon/remote-control.js +62 -12
- package/dist/lib/daemon/supervisor.d.ts +2 -0
- package/dist/lib/daemon/supervisor.js +11 -0
- package/dist/lib/doctor/checks/work-context.js +2 -2
- package/dist/lib/lanes/keep-alive.js +13 -2
- package/dist/lib/mesh/live/daemon/doctor.js +2 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -2
- package/dist/lib/mesh/live/flush.js +49 -29
- package/dist/lib/mesh/live/spool.d.ts +6 -0
- package/dist/lib/mesh/live/spool.js +203 -4
- package/dist/lib/work-context/bind-best.js +13 -1
- package/dist/main.js +16 -3
- package/dist/startup-registration.js +9 -3
- package/dist/utils/install-tree-torn.d.ts +13 -6
- package/dist/utils/install-tree-torn.js +42 -16
- package/dist/utils/self-update.d.ts +4 -0
- package/dist/utils/self-update.js +46 -9
- package/dist/utils/sentry-fingerprint.js +1 -0
- package/dist/utils/version-check.d.ts +7 -0
- package/dist/utils/version-check.js +30 -1
- package/dist/utils/version-gate.d.ts +28 -10
- package/dist/utils/version-gate.js +159 -29
- package/dist/utils/version-report.d.ts +2 -1
- package/dist/utils/version-report.js +8 -1
- package/package.json +1 -1
- package/dist/lib/work-context/index.d.ts +0 -26
- package/dist/lib/work-context/index.js +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.306.0] — 2026-10-01
|
|
6
|
+
- End long install-tree waits early when a named retired npm sibling's deep activity scan is incomplete, report the recovery as unverified, and give safe wait-and-repair guidance.
|
|
7
|
+
|
|
8
|
+
## [5.305.0] — 2026-10-01
|
|
9
|
+
|
|
10
|
+
- Work Mesh project discovery skips projects whose matching local lifecycle state is archived or retired.
|
|
11
|
+
|
|
12
|
+
- `hq lanes show` now treats a newer terminal primary mirror as authoritative when an Outpost owner snapshot is behind, avoiding a misleading refresh warning while preserving warnings for nonterminal conflicts.
|
|
13
|
+
- Self-updates detect an unwritable npm global prefix before install, show the command to repair that install, and report the handled permission state as a rate-limited warning.
|
|
14
|
+
- Keep Work Mesh held events unique by event ID across retries, including interrupted overflow recovery.
|
|
15
|
+
- Added `hq mcp list` as an alias for the existing MCP status command. `hq version` can show cached CLI update guidance on stderr when the default-off `cli.version-update-nudge` flag is enabled. Bare `hq --version` prints only the installed CLI semver and starts no flag or network work.
|
|
16
|
+
- Keep-alive no longer lets a newer terminal lane or a same-profile lane under a distinct lane senior supersede a running lane.
|
|
17
|
+
- With `cli.company-errors-sdk-warning` enabled, `hq people` uses shared active-company guidance when local metadata is missing, and files commands show the AWS SDK Node-version notice only with verbose diagnostics.
|
|
18
|
+
- Remove the unrelated secret-sharing hint from active member listings.
|
|
19
|
+
|
|
5
20
|
## [5.304.0] — 2026-10-01
|
|
6
21
|
- `hq lanes show` skips owner refresh only when a failed Outpost dispatch explicitly recorded `pickup_persisted: false` and no owner heartbeat; it reports `remote_refresh: "skipped_undispatched"` in that case.
|
|
7
22
|
|
|
@@ -46,6 +61,7 @@
|
|
|
46
61
|
|
|
47
62
|
## [5.300.0] — 2026-09-30
|
|
48
63
|
- `hq files unshare --with agt_...` removes a grant made to an agent. Before, agent uids were rejected as invalid principals.
|
|
64
|
+
- `hq daemon` no longer drops remote-control sessions when claude or codex is reinstalled at the same version or when hq-cli is released. The daemon's plan leaves out the tool's path (the host finds it when it starts), the host restarts a session only for a new tool version, and a tool that looks missing for one plan read does not pause the session. On outpost primary, a long-running Claude session's auto-updater reinstalled claude every few minutes and each reinstall restarted remote-control two or three times.
|
|
49
65
|
- With the default-off hq-flags gate `files.share-empty-prefix-warning`, `hq files share` warns when a granted prefix has no files. A failed or timed-out list check does not block the grant.
|
|
50
66
|
- Recognize pnpm v11+ global installs and handle Windows drive-path tar archives without waiting for the asynchronous feature-flag snapshot.
|
|
51
67
|
- Self-update failure reports now include the npm error code and a sanitized reason.
|
|
@@ -3622,7 +3622,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3622
3622
|
readonly subcommands: readonly [{
|
|
3623
3623
|
readonly name: "status";
|
|
3624
3624
|
readonly description: "Show installed MCP packs/servers and their health in BOTH Claude and Codex (PARTIAL/DRIFT flagged; secrets redacted)";
|
|
3625
|
-
readonly aliases: readonly [];
|
|
3625
|
+
readonly aliases: readonly ["list"];
|
|
3626
3626
|
readonly hidden: false;
|
|
3627
3627
|
readonly usage: "[options]";
|
|
3628
3628
|
readonly arguments: readonly [];
|
|
@@ -9664,7 +9664,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9664
9664
|
readonly arguments: readonly [];
|
|
9665
9665
|
readonly options: readonly [{
|
|
9666
9666
|
readonly flags: "--claude <path>";
|
|
9667
|
-
readonly description: "claude binary";
|
|
9667
|
+
readonly description: "claude binary (default: the one installed for this user)";
|
|
9668
9668
|
}, {
|
|
9669
9669
|
readonly flags: "--name <name>";
|
|
9670
9670
|
readonly description: "remote-control session name";
|
|
@@ -9685,7 +9685,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
9685
9685
|
readonly description: "the box's Codex runner script";
|
|
9686
9686
|
}, {
|
|
9687
9687
|
readonly flags: "--codex <path>";
|
|
9688
|
-
readonly description: "codex binary, watched for updates";
|
|
9688
|
+
readonly description: "codex binary, watched for updates (default: the one installed for this user)";
|
|
9689
9689
|
}];
|
|
9690
9690
|
readonly subcommands: readonly [];
|
|
9691
9691
|
}];
|
|
@@ -4674,7 +4674,9 @@ export const COMMAND_CATALOG = [
|
|
|
4674
4674
|
{
|
|
4675
4675
|
"name": "status",
|
|
4676
4676
|
"description": "Show installed MCP packs/servers and their health in BOTH Claude and Codex (PARTIAL/DRIFT flagged; secrets redacted)",
|
|
4677
|
-
"aliases": [
|
|
4677
|
+
"aliases": [
|
|
4678
|
+
"list"
|
|
4679
|
+
],
|
|
4678
4680
|
"hidden": false,
|
|
4679
4681
|
"usage": "[options]",
|
|
4680
4682
|
"arguments": [],
|
|
@@ -12455,7 +12457,7 @@ export const COMMAND_CATALOG = [
|
|
|
12455
12457
|
"options": [
|
|
12456
12458
|
{
|
|
12457
12459
|
"flags": "--claude <path>",
|
|
12458
|
-
"description": "claude binary"
|
|
12460
|
+
"description": "claude binary (default: the one installed for this user)"
|
|
12459
12461
|
},
|
|
12460
12462
|
{
|
|
12461
12463
|
"flags": "--name <name>",
|
|
@@ -12482,7 +12484,7 @@ export const COMMAND_CATALOG = [
|
|
|
12482
12484
|
},
|
|
12483
12485
|
{
|
|
12484
12486
|
"flags": "--codex <path>",
|
|
12485
|
-
"description": "codex binary, watched for updates"
|
|
12487
|
+
"description": "codex binary, watched for updates (default: the one installed for this user)"
|
|
12486
12488
|
}
|
|
12487
12489
|
],
|
|
12488
12490
|
"subcommands": []
|
|
@@ -85,6 +85,7 @@ export const REGISTRATION_PLAN = [
|
|
|
85
85
|
];
|
|
86
86
|
async function executePlan(program, steps) {
|
|
87
87
|
const parents = new Map([["program", program]]);
|
|
88
|
+
let filesRuntimeConfigured = false;
|
|
88
89
|
for (const step of steps) {
|
|
89
90
|
if (step.type === "group") {
|
|
90
91
|
const group = program.command(step.name, step.hidden ? { hidden: true } : {});
|
|
@@ -94,6 +95,13 @@ async function executePlan(program, steps) {
|
|
|
94
95
|
const parent = parents.get(step.parent);
|
|
95
96
|
if (!parent)
|
|
96
97
|
throw new Error(`Missing command parent '${step.parent}' for '${step.exportName}'`);
|
|
98
|
+
if (step.root === "files" && !filesRuntimeConfigured) {
|
|
99
|
+
// The SDK emits its Node support notice during module evaluation. Set
|
|
100
|
+
// its narrow suppression switch before loading any files command module.
|
|
101
|
+
const { configureAwsSdkNodeVersionWarningOutput } = await import("./lib/aws-sdk-node-warning-output.js");
|
|
102
|
+
configureAwsSdkNodeVersionWarningOutput();
|
|
103
|
+
filesRuntimeConfigured = true;
|
|
104
|
+
}
|
|
97
105
|
const imported = await import(step.module);
|
|
98
106
|
const register = imported[step.exportName];
|
|
99
107
|
if (!register)
|
package/dist/commands/daemon.js
CHANGED
|
@@ -627,25 +627,9 @@ async function runServiceSelfUpdate(d) {
|
|
|
627
627
|
if (!result.ok)
|
|
628
628
|
process.exitCode = 1;
|
|
629
629
|
}
|
|
630
|
-
/** Size, mtime and real path of a binary: changes whenever a new version is installed over it. */
|
|
631
|
-
function binaryFingerprint(bin) {
|
|
632
|
-
try {
|
|
633
|
-
const real = fs.realpathSync(bin);
|
|
634
|
-
const st = fs.statSync(real);
|
|
635
|
-
return `${real}:${st.size}:${st.mtimeMs}`;
|
|
636
|
-
}
|
|
637
|
-
catch {
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
630
|
async function runTmuxService(d, opts) {
|
|
642
|
-
const {
|
|
631
|
+
const { runRemoteControlHost, toolFingerprint } = await import("../lib/daemon/remote-control.js");
|
|
643
632
|
const { findOnPath } = await import("../lib/daemon/run.js");
|
|
644
|
-
const { resolveRunningInstall } = await import("../utils/version-gate.js");
|
|
645
|
-
// The daemon keeps this host running across a CLI update, so a new hq-cli
|
|
646
|
-
// install also counts as an update and restarts the session once it is quiet.
|
|
647
|
-
const cliRoot = resolveRunningInstall().packageRoot;
|
|
648
|
-
const watched = cliRoot ? [opts.watch, path.join(cliRoot, "package.json")] : [opts.watch];
|
|
649
633
|
const hqRoot = d.env.HQ_ROOT?.trim();
|
|
650
634
|
if (!hqRoot)
|
|
651
635
|
throw new DaemonCommandError(`${opts.label} needs HQ_ROOT (the daemon sets it from the saved HQ folder)`);
|
|
@@ -663,7 +647,9 @@ async function runTmuxService(d, opts) {
|
|
|
663
647
|
cwd: hqRoot,
|
|
664
648
|
argv: opts.argv,
|
|
665
649
|
label: opts.label,
|
|
666
|
-
|
|
650
|
+
// Only a new version of the tool restarts the session: not a same-version
|
|
651
|
+
// reinstall, and not an hq-cli release (the host's own code rarely matters).
|
|
652
|
+
binaryFingerprint: () => toolFingerprint(opts.watch),
|
|
667
653
|
now: () => Date.now(),
|
|
668
654
|
run: d.run,
|
|
669
655
|
pollMs: 5_000,
|
|
@@ -683,22 +669,37 @@ async function runTmuxService(d, opts) {
|
|
|
683
669
|
process.off("SIGINT", stop);
|
|
684
670
|
}
|
|
685
671
|
}
|
|
672
|
+
/** The claude or codex a remote-control host runs: --claude/--codex, else the one installed now. */
|
|
673
|
+
async function hostTool(d, name, given) {
|
|
674
|
+
const [{ serviceTool }, { findUserBinary }] = await Promise.all([
|
|
675
|
+
import("../lib/daemon/remote-control.js"),
|
|
676
|
+
import("../lib/daemon/run.js"),
|
|
677
|
+
]);
|
|
678
|
+
try {
|
|
679
|
+
return serviceTool(name, given, (tool) => findUserBinary(tool, d.env, d.home));
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
throw new DaemonCommandError(err.message);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
686
685
|
async function runServiceRemoteControl(d, opts) {
|
|
687
686
|
const { TMUX_SESSION, remoteControlArgs } = await import("../lib/daemon/remote-control.js");
|
|
687
|
+
const claude = await hostTool(d, "claude", opts.claude);
|
|
688
688
|
await runTmuxService(d, {
|
|
689
689
|
session: TMUX_SESSION,
|
|
690
|
-
argv: [
|
|
690
|
+
argv: [claude, ...remoteControlArgs({ name: opts.name, permissionMode: opts.permissionMode })],
|
|
691
691
|
label: "claude remote-control",
|
|
692
|
-
watch:
|
|
692
|
+
watch: claude,
|
|
693
693
|
});
|
|
694
694
|
}
|
|
695
695
|
async function runServiceCodexRemoteControl(d, opts) {
|
|
696
696
|
const { CODEX_TMUX_SESSION } = await import("../lib/daemon/remote-control.js");
|
|
697
|
+
const codex = await hostTool(d, "codex", opts.codex);
|
|
697
698
|
await runTmuxService(d, {
|
|
698
699
|
session: CODEX_TMUX_SESSION,
|
|
699
700
|
argv: ["bash", opts.script],
|
|
700
701
|
label: "codex remote-control",
|
|
701
|
-
watch:
|
|
702
|
+
watch: codex,
|
|
702
703
|
});
|
|
703
704
|
}
|
|
704
705
|
export function registerDaemonCommand(program, over = {}) {
|
|
@@ -839,7 +840,7 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
839
840
|
service
|
|
840
841
|
.command("remote-control")
|
|
841
842
|
.description("Keep `claude remote-control` running in a tmux session in the HQ folder")
|
|
842
|
-
.
|
|
843
|
+
.option("--claude <path>", "claude binary (default: the one installed for this user)")
|
|
843
844
|
.requiredOption("--name <name>", "remote-control session name")
|
|
844
845
|
.option("--permission-mode <mode>", "claude --permission-mode")
|
|
845
846
|
.action(wrap((d, opts) => runServiceRemoteControl(d, opts)));
|
|
@@ -847,7 +848,7 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
847
848
|
.command("codex-remote-control")
|
|
848
849
|
.description("Keep the box's Codex remote-control runner going in a tmux session, restarting it after a Codex update")
|
|
849
850
|
.requiredOption("--script <path>", "the box's Codex runner script")
|
|
850
|
-
.
|
|
851
|
+
.option("--codex <path>", "codex binary, watched for updates (default: the one installed for this user)")
|
|
851
852
|
.action(wrap((d, opts) => runServiceCodexRemoteControl(d, opts)));
|
|
852
853
|
return daemon;
|
|
853
854
|
}
|
package/dist/commands/lanes.js
CHANGED
|
@@ -5065,6 +5065,19 @@ async function refreshOutpostLaneCopiesForShow(ctx, lane, localLanes) {
|
|
|
5065
5065
|
return syncLaneRecordFromOwner(ctx.hqRoot(), laneId, outpostId, ownerLane, undefined, { allowNonTerminal: true });
|
|
5066
5066
|
}
|
|
5067
5067
|
catch (err) {
|
|
5068
|
+
if (isLanesError(err) && err.error === "outpost_lane_sync_conflict") {
|
|
5069
|
+
const ownerVersion = err.details.owner_version;
|
|
5070
|
+
const primaryVersion = err.details.primary_version;
|
|
5071
|
+
if (typeof ownerVersion === "number" &&
|
|
5072
|
+
typeof primaryVersion === "number" &&
|
|
5073
|
+
ownerVersion < primaryVersion) {
|
|
5074
|
+
const currentPrimary = readLane(ctx.hqRoot(), laneId);
|
|
5075
|
+
if (currentPrimary.version === primaryVersion &&
|
|
5076
|
+
isTerminalLaneState(currentPrimary.state)) {
|
|
5077
|
+
return currentPrimary;
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5068
5081
|
const detail = isLanesError(err)
|
|
5069
5082
|
? `${err.error}: ${err.message}`
|
|
5070
5083
|
: `outpost_lane_sync_unavailable: ${dispatchErrorMessage(err)}`;
|
|
@@ -374,6 +374,7 @@ export function registerMcpCommand(program) {
|
|
|
374
374
|
.description('MCP pack observability (read-only) across Claude + Codex');
|
|
375
375
|
mcp
|
|
376
376
|
.command('status')
|
|
377
|
+
.alias('list')
|
|
377
378
|
.description('Show installed MCP packs/servers and their health in BOTH Claude and Codex (PARTIAL/DRIFT flagged; secrets redacted)')
|
|
378
379
|
.option('--json', 'Emit the structured report as JSON (for CI / scripting)')
|
|
379
380
|
.action((opts) => {
|
package/dist/commands/members.js
CHANGED
|
@@ -672,7 +672,6 @@ export function registerMembersCommand(program) {
|
|
|
672
672
|
const name = m.personName ?? m.personSlug ?? "";
|
|
673
673
|
console.log([email.padEnd(emailW), m.role.padEnd(roleW), name].join(" "));
|
|
674
674
|
}
|
|
675
|
-
console.log(chalk.gray("Share a secret with a member: hq secrets share <path> --with <email>"));
|
|
676
675
|
}
|
|
677
676
|
catch (err) {
|
|
678
677
|
if (err instanceof InviteHttpError) {
|
|
@@ -17,6 +17,9 @@ import { resolveNameToEmail, type PersonRecord } from "../utils/people.js";
|
|
|
17
17
|
export type FetchPeopleRoster = (hqRoot: string, companySlug: string) => Promise<PersonRecord[]>;
|
|
18
18
|
interface PeopleCommandDeps {
|
|
19
19
|
fetchRoster?: FetchPeopleRoster;
|
|
20
|
+
/** Test seam for the shared active-membership ambiguity resolver. */
|
|
21
|
+
resolveMissingManifestMemberships?: () => Promise<void>;
|
|
22
|
+
companySelectionGuidanceEnabled?: () => boolean;
|
|
20
23
|
}
|
|
21
24
|
interface PeopleLookupOpts {
|
|
22
25
|
localOnly?: boolean;
|
package/dist/commands/people.js
CHANGED
|
@@ -19,6 +19,7 @@ import { findHqRoot } from "../utils/manifest.js";
|
|
|
19
19
|
import { manifestPath } from "./cloud-provision.js";
|
|
20
20
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
21
21
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
22
|
+
import { companyErrorsSdkWarningEnabled } from "../lib/company-errors-sdk-warning-flag.js";
|
|
22
23
|
import { listActiveMembers } from "./members.js";
|
|
23
24
|
import { assertSafeCompanySlug, listCompanyPeople, searchPeople, resolveNameToEmail, companyPeopleDir, } from "../utils/people.js";
|
|
24
25
|
function slugify(value) {
|
|
@@ -108,6 +109,29 @@ export function resolveCompanySlug(hqRoot, explicit) {
|
|
|
108
109
|
throw new Error("Multiple companies found — re-run with --company <slug> to pick one:\n" +
|
|
109
110
|
slugs.map((s) => ` --company ${s}`).join("\n"));
|
|
110
111
|
}
|
|
112
|
+
async function resolvePeopleCompanySlug(hqRoot, explicit, localOnly, deps) {
|
|
113
|
+
try {
|
|
114
|
+
return resolveCompanySlug(hqRoot, explicit);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
const ambiguous = message.includes("companies/manifest.yaml not found") ||
|
|
119
|
+
message.startsWith("Multiple companies found");
|
|
120
|
+
const enabled = (deps.companySelectionGuidanceEnabled ?? companyErrorsSdkWarningEnabled)();
|
|
121
|
+
if (!explicit && !localOnly && enabled && ambiguous) {
|
|
122
|
+
const resolveMemberships = deps.resolveMissingManifestMemberships ??
|
|
123
|
+
(async () => {
|
|
124
|
+
const token = await ensureCognitoToken();
|
|
125
|
+
await getCompanyUid(token, undefined);
|
|
126
|
+
});
|
|
127
|
+
// The shared resolver throws members-style guidance for multiple active
|
|
128
|
+
// companies. A single membership cannot provide a local slug without a
|
|
129
|
+
// manifest, so preserve the existing missing-manifest error in that case.
|
|
130
|
+
await resolveMemberships();
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
111
135
|
/** Resolve the HQ root: explicit override (tests) → cwd walk-up. */
|
|
112
136
|
function resolveHqRoot(opts) {
|
|
113
137
|
return opts.hqRoot ?? findHqRoot();
|
|
@@ -192,7 +216,7 @@ export function registerPeopleCommand(program, deps = {}) {
|
|
|
192
216
|
try {
|
|
193
217
|
const scope = people.opts();
|
|
194
218
|
const hqRoot = resolveHqRoot(scope);
|
|
195
|
-
const slug =
|
|
219
|
+
const slug = await resolvePeopleCompanySlug(hqRoot, scope.company, opts.localOnly, deps);
|
|
196
220
|
let records = listCompanyPeople(hqRoot, slug);
|
|
197
221
|
if (!opts.localOnly) {
|
|
198
222
|
const roster = await tryFetchRoster(deps.fetchRoster ?? fetchMembershipRoster, hqRoot, slug);
|
|
@@ -223,7 +247,7 @@ export function registerPeopleCommand(program, deps = {}) {
|
|
|
223
247
|
try {
|
|
224
248
|
const scope = people.opts();
|
|
225
249
|
const hqRoot = resolveHqRoot(scope);
|
|
226
|
-
const slug =
|
|
250
|
+
const slug = await resolvePeopleCompanySlug(hqRoot, scope.company, opts.localOnly, deps);
|
|
227
251
|
const matches = await searchPeopleWithRosterFallback({
|
|
228
252
|
hqRoot,
|
|
229
253
|
slug,
|
|
@@ -255,7 +279,7 @@ export function registerPeopleCommand(program, deps = {}) {
|
|
|
255
279
|
try {
|
|
256
280
|
const scope = people.opts();
|
|
257
281
|
const hqRoot = resolveHqRoot(scope);
|
|
258
|
-
const slug =
|
|
282
|
+
const slug = await resolvePeopleCompanySlug(hqRoot, scope.company, opts.localOnly, deps);
|
|
259
283
|
const result = await resolvePersonWithRosterFallback({
|
|
260
284
|
hqRoot,
|
|
261
285
|
slug,
|
package/dist/index.js
CHANGED
|
@@ -22,9 +22,14 @@ if (isVersionRequest(process.argv)) {
|
|
|
22
22
|
// compare). `hq version` and `--json` print the labelled HQ/CLI report.
|
|
23
23
|
// Lazy so this path still skips the command graph.
|
|
24
24
|
if (wantsLabelledVersionReport(process.argv)) {
|
|
25
|
-
void import("./
|
|
25
|
+
void import("./lib/flag-registry.js").then(({ kickFlagRegistryReadiness }) => {
|
|
26
|
+
kickFlagRegistryReadiness();
|
|
27
|
+
return import("./utils/version-report.js").then(({ runVersionCommand }) => runVersionCommand(process.argv));
|
|
28
|
+
});
|
|
26
29
|
}
|
|
27
30
|
else {
|
|
31
|
+
// Keep the benchmarked updater fast route limited to the installed semver.
|
|
32
|
+
// Registry checks and flag resolution belong to the regular command path.
|
|
28
33
|
void import("./cli-version.js").then(({ CLI_VERSION }) => {
|
|
29
34
|
process.stdout.write(`${CLI_VERSION}\n`);
|
|
30
35
|
});
|
package/dist/lazy-commands.js
CHANGED
|
@@ -55,8 +55,11 @@ function commandForToken(token) {
|
|
|
55
55
|
* preserve the complete help and unknown-command recovery surfaces.
|
|
56
56
|
*/
|
|
57
57
|
export function findLazyCommand(argv) {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
let commandIndex = 2;
|
|
59
|
+
while (argv[commandIndex] === "--verbose")
|
|
60
|
+
commandIndex += 1;
|
|
61
|
+
const first = argv[commandIndex];
|
|
62
|
+
const token = first === "help" ? argv[commandIndex + 1] : first;
|
|
60
63
|
if (typeof token !== "string" || token.startsWith("-"))
|
|
61
64
|
return null;
|
|
62
65
|
const command = commandForToken(token);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const AWS_SDK_NODE_VERSION_WARNING_ENV = "AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED";
|
|
2
|
+
export interface AwsSdkWarningOutputOptions {
|
|
3
|
+
argv?: readonly string[];
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Configure the AWS SDK's documented, warning-specific switch before loading
|
|
9
|
+
* the files command and its SDK clients. No other SDK warnings are affected.
|
|
10
|
+
*/
|
|
11
|
+
export declare function configureAwsSdkNodeVersionWarningOutput(options?: AwsSdkWarningOutputOptions): void;
|
|
12
|
+
//# sourceMappingURL=aws-sdk-node-warning-output.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { companyErrorsSdkWarningEnabled } from "./company-errors-sdk-warning-flag.js";
|
|
2
|
+
export const AWS_SDK_NODE_VERSION_WARNING_ENV = "AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED";
|
|
3
|
+
/**
|
|
4
|
+
* Configure the AWS SDK's documented, warning-specific switch before loading
|
|
5
|
+
* the files command and its SDK clients. No other SDK warnings are affected.
|
|
6
|
+
*/
|
|
7
|
+
export function configureAwsSdkNodeVersionWarningOutput(options = {}) {
|
|
8
|
+
const env = options.env ?? process.env;
|
|
9
|
+
const argv = options.argv ?? process.argv;
|
|
10
|
+
const enabled = options.enabled ?? companyErrorsSdkWarningEnabled();
|
|
11
|
+
if (!enabled)
|
|
12
|
+
return;
|
|
13
|
+
const verbose = argv.includes("--verbose");
|
|
14
|
+
const debug = /^(1|true|yes)$/i.test(env.HQ_DEBUG?.trim() ?? "");
|
|
15
|
+
if (verbose || debug) {
|
|
16
|
+
delete env[AWS_SDK_NODE_VERSION_WARNING_ENV];
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
env[AWS_SDK_NODE_VERSION_WARNING_ENV] = "true";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=aws-sdk-node-warning-output.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const COMPANY_ERRORS_SDK_WARNING_FLAG = "cli.company-errors-sdk-warning";
|
|
2
|
+
export declare const COMPANY_ERRORS_SDK_WARNING_FALLBACK = false;
|
|
3
|
+
/** One default-off rollout gate for the two US-163 CLI compatibility fixes. */
|
|
4
|
+
export declare function companyErrorsSdkWarningEnabled(): boolean;
|
|
5
|
+
//# sourceMappingURL=company-errors-sdk-warning-flag.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { resolveProcessFlagGate } from "./flag-registry.js";
|
|
2
|
+
export const COMPANY_ERRORS_SDK_WARNING_FLAG = "cli.company-errors-sdk-warning";
|
|
3
|
+
export const COMPANY_ERRORS_SDK_WARNING_FALLBACK = false;
|
|
4
|
+
/** One default-off rollout gate for the two US-163 CLI compatibility fixes. */
|
|
5
|
+
export function companyErrorsSdkWarningEnabled() {
|
|
6
|
+
return resolveProcessFlagGate(COMPANY_ERRORS_SDK_WARNING_FLAG, {}, () => COMPANY_ERRORS_SDK_WARNING_FALLBACK);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=company-errors-sdk-warning-flag.js.map
|
|
@@ -283,7 +283,9 @@ function planService(service, ctx) {
|
|
|
283
283
|
base(service, {
|
|
284
284
|
command: {
|
|
285
285
|
type: "hq",
|
|
286
|
-
|
|
286
|
+
// No codex path: the host finds codex itself, so a reinstall that
|
|
287
|
+
// briefly moves the link cannot change the plan and restart it.
|
|
288
|
+
args: ["daemon", "service", "codex-remote-control", "--script", script ?? ""],
|
|
287
289
|
},
|
|
288
290
|
// Restarting the host ends the tmux session people are using.
|
|
289
291
|
keepAcrossCliUpdates: true,
|
|
@@ -303,7 +305,9 @@ function planService(service, ctx) {
|
|
|
303
305
|
// Outposts keep the runner's session name and permission mode.
|
|
304
306
|
const outpost = ctx.machineType === "outpost";
|
|
305
307
|
const name = ctx.remoteControlName ?? (outpost ? "outpost-1" : ctx.hostName);
|
|
306
|
-
|
|
308
|
+
// No claude path: the host finds claude itself, so a reinstall that
|
|
309
|
+
// briefly moves the link cannot change the plan and restart the session.
|
|
310
|
+
const args = ["daemon", "service", "remote-control", "--name", name];
|
|
307
311
|
if (outpost)
|
|
308
312
|
args.push("--permission-mode", "bypassPermissions");
|
|
309
313
|
return [
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
* - the session ends -> the host exits 1 and the daemon restarts it with
|
|
11
11
|
* backoff (replacing the runner's `while true` loop and its watchdogs);
|
|
12
12
|
* - the daemon stops the host -> the host kills the session and exits 0;
|
|
13
|
-
* - the tool
|
|
14
|
-
* once the session has been quiet for 10 minutes, or after
|
|
15
|
-
* latest, the host kills the session and exits 0, and the
|
|
16
|
-
* again on the new version.
|
|
13
|
+
* - a new version of the tool is installed (the updater, or the tool's own
|
|
14
|
+
* auto-updater) -> once the session has been quiet for 10 minutes, or after
|
|
15
|
+
* 2 hours at the latest, the host kills the session and exits 0, and the
|
|
16
|
+
* daemon starts it again on the new version. Reinstalling the same version
|
|
17
|
+
* (a long-running Claude session's auto-updater does that every few
|
|
18
|
+
* minutes) and hq-cli releases leave the session alone.
|
|
17
19
|
*
|
|
18
20
|
* The tmux server is a private socket (`-L hq-daemon`), so it never touches a
|
|
19
21
|
* user's own tmux sessions.
|
|
@@ -32,11 +34,20 @@ export declare function remoteControlArgs(opts: {
|
|
|
32
34
|
*/
|
|
33
35
|
export declare function runnerSessionName(script: string): string | null;
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
* Which version of a tool is installed: the npm package that installs the
|
|
38
|
+
* binary (`name@version`), else the binary's real path and size. A native
|
|
39
|
+
* install's link points at a versioned file, so its real path is the version.
|
|
40
|
+
* The mtime is left out on purpose: reinstalling the same version rewrites
|
|
41
|
+
* the file, and that must not restart the session. Null while the tool
|
|
42
|
+
* cannot be read.
|
|
38
43
|
*/
|
|
39
|
-
export declare function
|
|
44
|
+
export declare function toolFingerprint(bin: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* The tool a host runs: the path it was given, else the one found now. The
|
|
47
|
+
* daemon's plan leaves the path out, so a reinstall that briefly removes the
|
|
48
|
+
* tool's link cannot change the plan and restart the session.
|
|
49
|
+
*/
|
|
50
|
+
export declare function serviceTool(name: string, given: string | undefined, find: (name: string) => string | null): string;
|
|
40
51
|
/** A tool updated on disk restarts its session once quiet this long... */
|
|
41
52
|
export declare const IDLE_BEFORE_RESTART_MS: number;
|
|
42
53
|
/** ...or after this long, however busy the session is. */
|
|
@@ -10,14 +10,18 @@
|
|
|
10
10
|
* - the session ends -> the host exits 1 and the daemon restarts it with
|
|
11
11
|
* backoff (replacing the runner's `while true` loop and its watchdogs);
|
|
12
12
|
* - the daemon stops the host -> the host kills the session and exits 0;
|
|
13
|
-
* - the tool
|
|
14
|
-
* once the session has been quiet for 10 minutes, or after
|
|
15
|
-
* latest, the host kills the session and exits 0, and the
|
|
16
|
-
* again on the new version.
|
|
13
|
+
* - a new version of the tool is installed (the updater, or the tool's own
|
|
14
|
+
* auto-updater) -> once the session has been quiet for 10 minutes, or after
|
|
15
|
+
* 2 hours at the latest, the host kills the session and exits 0, and the
|
|
16
|
+
* daemon starts it again on the new version. Reinstalling the same version
|
|
17
|
+
* (a long-running Claude session's auto-updater does that every few
|
|
18
|
+
* minutes) and hq-cli releases leave the session alone.
|
|
17
19
|
*
|
|
18
20
|
* The tmux server is a private socket (`-L hq-daemon`), so it never touches a
|
|
19
21
|
* user's own tmux sessions.
|
|
20
22
|
*/
|
|
23
|
+
import * as fs from "node:fs";
|
|
24
|
+
import * as path from "node:path";
|
|
21
25
|
export const TMUX_SOCKET = "hq-daemon";
|
|
22
26
|
export const TMUX_SESSION = "remote-control";
|
|
23
27
|
export const CODEX_TMUX_SESSION = "codex-remote-control";
|
|
@@ -35,17 +39,63 @@ export function runnerSessionName(script) {
|
|
|
35
39
|
const m = /remote-control\b[^\n]*?--name[= ]+"?([^\s"]+)"?/.exec(script);
|
|
36
40
|
return m ? m[1] : null;
|
|
37
41
|
}
|
|
42
|
+
/** How far above the binary to look for the package that installs it (bin/claude.exe sits one level down). */
|
|
43
|
+
const PACKAGE_SEARCH_DEPTH = 3;
|
|
38
44
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
45
|
+
* Which version of a tool is installed: the npm package that installs the
|
|
46
|
+
* binary (`name@version`), else the binary's real path and size. A native
|
|
47
|
+
* install's link points at a versioned file, so its real path is the version.
|
|
48
|
+
* The mtime is left out on purpose: reinstalling the same version rewrites
|
|
49
|
+
* the file, and that must not restart the session. Null while the tool
|
|
50
|
+
* cannot be read.
|
|
42
51
|
*/
|
|
43
|
-
export function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
export function toolFingerprint(bin) {
|
|
53
|
+
let real;
|
|
54
|
+
let size;
|
|
55
|
+
try {
|
|
56
|
+
real = fs.realpathSync(bin);
|
|
57
|
+
size = fs.statSync(real).size;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
let dir = path.dirname(real);
|
|
63
|
+
for (let i = 0; i < PACKAGE_SEARCH_DEPTH; i++) {
|
|
64
|
+
const version = packageVersionFor(dir, real);
|
|
65
|
+
if (version)
|
|
66
|
+
return version;
|
|
67
|
+
const parent = path.dirname(dir);
|
|
68
|
+
if (parent === dir)
|
|
69
|
+
break;
|
|
70
|
+
dir = parent;
|
|
71
|
+
}
|
|
72
|
+
return `${real}:${size}`;
|
|
73
|
+
}
|
|
74
|
+
/** `name@version` when `<dir>/package.json` declares `real` as one of its bins. */
|
|
75
|
+
function packageVersionFor(dir, real) {
|
|
76
|
+
let pkg;
|
|
77
|
+
try {
|
|
78
|
+
pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
47
81
|
return null;
|
|
48
|
-
|
|
82
|
+
}
|
|
83
|
+
if (typeof pkg.name !== "string" || typeof pkg.version !== "string")
|
|
84
|
+
return null;
|
|
85
|
+
const bins = typeof pkg.bin === "string" ? [pkg.bin] : pkg.bin && typeof pkg.bin === "object" ? Object.values(pkg.bin) : [];
|
|
86
|
+
const installs = bins.some((b) => typeof b === "string" && path.resolve(dir, b) === real);
|
|
87
|
+
return installs ? `${pkg.name}@${pkg.version}` : null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The tool a host runs: the path it was given, else the one found now. The
|
|
91
|
+
* daemon's plan leaves the path out, so a reinstall that briefly removes the
|
|
92
|
+
* tool's link cannot change the plan and restart the session.
|
|
93
|
+
*/
|
|
94
|
+
export function serviceTool(name, given, find) {
|
|
95
|
+
const tool = given?.trim() || find(name);
|
|
96
|
+
if (!tool)
|
|
97
|
+
throw new Error(`the ${name} CLI is not installed`);
|
|
98
|
+
return tool;
|
|
49
99
|
}
|
|
50
100
|
/** A tool updated on disk restarts its session once quiet this long... */
|
|
51
101
|
export const IDLE_BEFORE_RESTART_MS = 10 * 60_000;
|
|
@@ -123,6 +123,8 @@ export interface Supervisor {
|
|
|
123
123
|
/** Stop supervising without signalling any child; returns the long-running units left running. */
|
|
124
124
|
release(): ReleasedUnit[];
|
|
125
125
|
}
|
|
126
|
+
/** Plan reads in a row that must find a running remote-control host blocked before it is paused. */
|
|
127
|
+
export declare const BLOCKED_READS_BEFORE_PAUSE = 2;
|
|
126
128
|
export declare function backoffMs(attempt: number): number;
|
|
127
129
|
/**
|
|
128
130
|
* What a unit's process was started from; a unit whose key changes is
|
|
@@ -33,6 +33,8 @@ const MESH_OWNER_LOG_INTERVAL_MS = 10 * 60_000;
|
|
|
33
33
|
export const HANDOFF_STAGGER_MS = 15_000;
|
|
34
34
|
/** How long a draining unit may take to finish its work before it gets SIGTERM. */
|
|
35
35
|
export const HANDOFF_DRAIN_MAX_MS = 15 * 60_000;
|
|
36
|
+
/** Plan reads in a row that must find a running remote-control host blocked before it is paused. */
|
|
37
|
+
export const BLOCKED_READS_BEFORE_PAUSE = 2;
|
|
36
38
|
export function backoffMs(attempt) {
|
|
37
39
|
return Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** Math.max(0, attempt));
|
|
38
40
|
}
|
|
@@ -505,6 +507,15 @@ export function createSupervisor(deps) {
|
|
|
505
507
|
continue;
|
|
506
508
|
}
|
|
507
509
|
next.set(plan.id, existing);
|
|
510
|
+
// A remote-control host is what people's sessions run in. Its tool can
|
|
511
|
+
// look missing for one plan read while it is being reinstalled, so it is
|
|
512
|
+
// only paused once the tool is missing on two reads in a row.
|
|
513
|
+
if (plan.blocked && existing.plan.blocked === undefined && existing.child && plan.keepAcrossCliUpdates) {
|
|
514
|
+
existing.blockedReads = (existing.blockedReads ?? 0) + 1;
|
|
515
|
+
if (existing.blockedReads < BLOCKED_READS_BEFORE_PAUSE)
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
existing.blockedReads = 0;
|
|
508
519
|
const adopted = existing.adopted === true;
|
|
509
520
|
existing.adopted = false;
|
|
510
521
|
const key = planKey(plan, deps.cliVersion);
|
|
@@ -10,7 +10,7 @@ import { readDeviceConfig, } from "../../work-context/config.js";
|
|
|
10
10
|
import { outboxStats, listOutboxOperations } from "../../work-context/outbox.js";
|
|
11
11
|
import { outboxQuarantineStatusEnabled } from "../../work-context/outbox-quarantine-status-flag.js";
|
|
12
12
|
import { workContextRoot, workContextSessionsDir } from "../../work-context/paths.js";
|
|
13
|
-
import { countJsonlLines, deadLetterNonEmpty, } from "../../mesh/live/spool.js";
|
|
13
|
+
import { countDistinctHeldEvents, countJsonlLines, deadLetterNonEmpty, } from "../../mesh/live/spool.js";
|
|
14
14
|
import { workMeshDeadLetterPath, workMeshHeldPath, workMeshRoot, workMeshSpoolPath, } from "../../mesh/live/paths.js";
|
|
15
15
|
import { collectDaemonDoctor, formatDaemonDoctor, } from "../../mesh/live/daemon/doctor.js";
|
|
16
16
|
export const WORK_CONTEXT_FAMILY_ID = "work-context";
|
|
@@ -139,7 +139,7 @@ function runChecks(deps) {
|
|
|
139
139
|
const heldPath = workMeshHeldPath(meshRoot);
|
|
140
140
|
const deadLetterPath = workMeshDeadLetterPath(meshRoot);
|
|
141
141
|
const spoolDepth = countJsonlLines(spoolPath);
|
|
142
|
-
const heldDepth =
|
|
142
|
+
const heldDepth = countDistinctHeldEvents(heldPath);
|
|
143
143
|
results.push({
|
|
144
144
|
status: "PASS",
|
|
145
145
|
checkId: "work-context.spool-depth",
|