@indigoai-us/hq-cli 5.345.134 → 5.345.136
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 +48 -0
- package/assets/scaffold/core/scripts/worktree-gc.sh +98 -8
- package/dist/command-catalog.generated.d.ts +62 -6
- package/dist/command-catalog.generated.js +79 -7
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/core-bucket-c-handoff-knowledge-native.d.ts +3 -1
- package/dist/commands/core-bucket-c-handoff-knowledge-native.js +8 -4
- package/dist/commands/core-checkpoint-native.d.ts +10 -0
- package/dist/commands/core-checkpoint-native.js +60 -15
- package/dist/commands/core-handoff.d.ts +2 -0
- package/dist/commands/core-handoff.js +3 -1
- package/dist/commands/core-pipeline-workflow-runner-native.js +7 -5
- package/dist/commands/daemon.js +3 -1
- package/dist/commands/fingerprint.d.ts +10 -0
- package/dist/commands/fingerprint.js +191 -0
- package/dist/commands/integrations-core.d.ts +22 -0
- package/dist/commands/integrations-core.js +25 -0
- package/dist/commands/integrations-manage.d.ts +24 -1
- package/dist/commands/integrations-manage.js +92 -17
- package/dist/commands/integrations.d.ts +11 -0
- package/dist/commands/integrations.js +100 -19
- package/dist/commands/lanes-loop.d.ts +9 -0
- package/dist/commands/lanes-loop.js +9 -1
- package/dist/commands/lanes.js +78 -55
- package/dist/commands/meetings.d.ts +2 -0
- package/dist/commands/meetings.js +57 -14
- package/dist/commands/mesh.d.ts +19 -1
- package/dist/commands/mesh.js +158 -70
- package/dist/commands/reindex.js +80 -5
- package/dist/commands/storage-offload.d.ts +93 -28
- package/dist/commands/storage-offload.js +290 -34
- package/dist/commands/storage.js +60 -7
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2550 -1748
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/capacity/route.js +24 -0
- package/dist/lib/core-utils/session-company.d.ts +11 -0
- package/dist/lib/core-utils/session-company.js +34 -0
- package/dist/lib/core-utils/worktree-resume.js +44 -4
- package/dist/lib/core-utils/worktree-session-owner.d.ts +27 -0
- package/dist/lib/core-utils/worktree-session-owner.js +157 -0
- package/dist/lib/daemon/catalog.d.ts +2 -0
- package/dist/lib/daemon/catalog.js +3 -3
- package/dist/lib/daemon/hq-child-bootstrap-stage.d.ts +5 -0
- package/dist/lib/daemon/hq-child-bootstrap-stage.js +3 -11
- package/dist/lib/daemon/server.js +10 -1
- package/dist/lib/daemon/supervisor.js +6 -0
- package/dist/lib/daemon/sync-runner-bundle.d.ts +5 -0
- package/dist/lib/daemon/sync-runner-bundle.js +4 -12
- package/dist/lib/doctor/checks/claude-wiring.js +2 -1
- package/dist/lib/doctor/fix/apply.d.ts +20 -0
- package/dist/lib/doctor/fix/apply.js +63 -3
- package/dist/lib/doctor/fix/remediation.d.ts +1 -1
- package/dist/lib/doctor/fix/remediation.js +16 -0
- package/dist/lib/doctor/shared.d.ts +11 -1
- package/dist/lib/doctor/shared.js +16 -2
- package/dist/lib/fingerprint/fixtures.d.ts +19 -0
- package/dist/lib/fingerprint/fixtures.js +61 -0
- package/dist/lib/fingerprint/format.d.ts +50 -0
- package/dist/lib/fingerprint/format.js +105 -0
- package/dist/lib/fingerprint/offline.d.ts +16 -0
- package/dist/lib/fingerprint/offline.js +44 -0
- package/dist/lib/fingerprint/registry-client.d.ts +74 -0
- package/dist/lib/fingerprint/registry-client.js +141 -0
- package/dist/lib/fingerprint/resolve-local.d.ts +14 -0
- package/dist/lib/fingerprint/resolve-local.js +67 -0
- package/dist/lib/fingerprint/self-core.d.ts +139 -0
- package/dist/lib/fingerprint/self-core.js +371 -0
- package/dist/lib/fingerprint/self.d.ts +44 -0
- package/dist/lib/fingerprint/self.js +191 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/monitor.js +14 -2
- package/dist/lib/lanes/spawn.d.ts +19 -1
- package/dist/lib/lanes/spawn.js +50 -6
- package/dist/lib/lanes/story-ledger.d.ts +1 -0
- package/dist/lib/lanes/story-ledger.js +3 -1
- package/dist/lib/lanes/types.d.ts +12 -0
- package/dist/lib/lanes/types.js +12 -1
- package/dist/lib/mesh/live/daemon/install.d.ts +12 -0
- package/dist/lib/mesh/live/daemon/install.js +22 -0
- package/dist/lib/mesh/live/flush.js +106 -3
- package/dist/lib/mesh/live/session-events-client.d.ts +9 -0
- package/dist/lib/mesh/live/session-events-client.js +15 -0
- package/dist/lib/work-context/state.d.ts +10 -0
- package/dist/lib/work-context/state.js +5 -0
- package/dist/utils/cli-network-lifecycle.d.ts +2 -0
- package/dist/utils/cli-network-lifecycle.js +5 -1
- package/dist/utils/cognito-session.d.ts +2 -21
- package/dist/utils/cognito-session.js +3 -84
- package/dist/utils/fsync-directory.d.ts +17 -0
- package/dist/utils/fsync-directory.js +27 -0
- package/dist/utils/person-token-cache.d.ts +32 -0
- package/dist/utils/person-token-cache.js +95 -0
- package/dist/utils/vault-api.d.ts +21 -1
- package/dist/utils/vault-api.js +54 -5
- package/dist/utils/windows-hide-default.d.ts +42 -0
- package/dist/utils/windows-hide-default.js +98 -0
- package/package.json +2 -2
|
@@ -354,12 +354,24 @@ const WATCH_RUNTIME_EXECUTABLES = ["bun", "deno", "node", "nodejs", "ts-node", "
|
|
|
354
354
|
// on the raw line, then validate whatever precedes it as an hq invocation.
|
|
355
355
|
// A path form (absolute or ./, ../) is required so an unrelated leading
|
|
356
356
|
// command such as "vim /tmp/hq" is not absorbed into the path.
|
|
357
|
+
//
|
|
358
|
+
// Package managers that install the bin through a shell shim (pnpm, some
|
|
359
|
+
// volta/yarn setups) exec the runtime with the package entrypoint instead of
|
|
360
|
+
// the hq symlink, so the ps line reads
|
|
361
|
+
// "node .../node_modules/@indigoai-us/hq-cli/dist/index.js lanes watch ...".
|
|
362
|
+
// Accept that entrypoint path as an hq executable too.
|
|
363
|
+
const HQ_CLI_ENTRYPOINT_SUFFIX = "/hq-cli/dist/index.js";
|
|
357
364
|
function isHqExecutablePath(candidate) {
|
|
358
365
|
if (candidate === "hq")
|
|
359
366
|
return true;
|
|
360
|
-
if (!/^\.{0,2}\//u.test(candidate)
|
|
367
|
+
if (!/^\.{0,2}\//u.test(candidate))
|
|
368
|
+
return false;
|
|
369
|
+
const isHqBin = path.basename(candidate) === "hq";
|
|
370
|
+
const isHqEntrypoint = candidate
|
|
371
|
+
.replace(/\\/gu, "/")
|
|
372
|
+
.endsWith(HQ_CLI_ENTRYPOINT_SUFFIX);
|
|
373
|
+
if (!isHqBin && !isHqEntrypoint)
|
|
361
374
|
return false;
|
|
362
|
-
}
|
|
363
375
|
return !candidate.split(" ").some((piece) => piece.startsWith("-"));
|
|
364
376
|
}
|
|
365
377
|
function isHqInvocationPrefix(prefix) {
|
|
@@ -66,6 +66,8 @@ export type ProveDetachedFn = (pid: number) => DetachProof;
|
|
|
66
66
|
export type KillProcessFn = (pid: number, signal?: NodeJS.Signals) => void;
|
|
67
67
|
export interface SpawnLaneDeps {
|
|
68
68
|
env?: NodeJS.ProcessEnv;
|
|
69
|
+
/** Builds the lane's fingerprint from the worker env. Defaults to laneStartFingerprint. */
|
|
70
|
+
buildFingerprint?: (hqRoot: string, workerEnv: NodeJS.ProcessEnv) => string | null;
|
|
69
71
|
/** Test seam for the default-off quota account failover registry gate. */
|
|
70
72
|
quotaAccountFailoverEnabled?: boolean;
|
|
71
73
|
/** Return the selected retry transition without writing or starting a worker. */
|
|
@@ -255,7 +257,23 @@ export declare function buildProviderCommand(provider: LaneProvider, input: {
|
|
|
255
257
|
export declare function workerEnv(base: NodeJS.ProcessEnv, resolved: {
|
|
256
258
|
envVar: string;
|
|
257
259
|
home: string;
|
|
258
|
-
}, laneId: string, hqRoot: string, autoCompactTokens?: number, meshCompany?: LaneCompanyRef, workerPolicyDir?: string, registryWorker?: LaneRecord["registry_worker"], homedir?: () => string): NodeJS.ProcessEnv;
|
|
260
|
+
}, laneId: string, hqRoot: string, autoCompactTokens?: number, meshCompany?: LaneCompanyRef, workerPolicyDir?: string, registryWorker?: LaneRecord["registry_worker"], homedir?: () => string, session?: WorkerSessionEnv): NodeJS.ProcessEnv;
|
|
261
|
+
/** Session ids a worker must not inherit from the process that spawns it. */
|
|
262
|
+
export declare const WORKER_SESSION_ENV_VARS: readonly ["HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "GROK_SESSION_ID"];
|
|
263
|
+
/**
|
|
264
|
+
* The worker's fingerprint, built offline from the worker env at lane start.
|
|
265
|
+
* Null when it cannot be built (no identity and no session id, or an error);
|
|
266
|
+
* the lane still starts.
|
|
267
|
+
*/
|
|
268
|
+
export declare function laneStartFingerprint(hqRoot: string, env: NodeJS.ProcessEnv): string | null;
|
|
269
|
+
export interface WorkerSessionEnv {
|
|
270
|
+
/** The worker's own provider session id, when known before launch. */
|
|
271
|
+
workerSessionId?: string;
|
|
272
|
+
/** The senior's session id, when the senior is a session. */
|
|
273
|
+
parentSessionId?: string;
|
|
274
|
+
}
|
|
275
|
+
/** The session identity a lane turn exports to its worker (see `workerEnv`). */
|
|
276
|
+
export declare function laneWorkerSessionEnv(lane: Pick<LaneRecord, "senior">, workerSessionId: string | undefined): WorkerSessionEnv;
|
|
259
277
|
export declare const WORKER_POLICY_HOOK_CAPABILITY = "# hq-capability: worker-policy-dir v1";
|
|
260
278
|
/** Grok does not run HQ hooks; Claude and Codex use the installed capability marker. */
|
|
261
279
|
export declare function workerPolicyDeliveryMode(hqRoot: string, provider: LaneProvider): "hook" | "prompt";
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -42,6 +42,8 @@ import { MESSAGE_REPLAY_STATE_NAME } from "./message-replay.js";
|
|
|
42
42
|
import { markAnsweredQuestionsDelivered } from "./questions.js";
|
|
43
43
|
import { hasEarlierEligibleQueuedLane, laneAdmissionCapReason, withLaneAdmissionLock, } from "./admission.js";
|
|
44
44
|
import { resolveAdmissionHost } from "./admission-host.js";
|
|
45
|
+
import { offlineFingerprint } from "../fingerprint/offline.js";
|
|
46
|
+
import { SESSION_ID_ENV_VARS } from "../fingerprint/self-core.js";
|
|
45
47
|
import { checkLaneDiskSpace, laneDiskAccount } from "./disk-guard.js";
|
|
46
48
|
import { LANE_ENVELOPE_JSON_SCHEMA, ENVELOPE_INVALID_REASON, parseLaneProvider, } from "./types.js";
|
|
47
49
|
export const LANES_RUNS_REL = path.join("workspace", "lanes-runs");
|
|
@@ -1661,7 +1663,7 @@ export function buildProviderCommand(provider, input) {
|
|
|
1661
1663
|
args: appendFastModeFlags(parsed, appendEffortFlag(parsed, appendModelFlag(parsed, appendCodexWorkerPolicyDirFlag(parsed, appendProviderProfileFlags(parsed, args, input.profile), input.workerPolicyDir), input.model), input.effort), input.fastMode),
|
|
1662
1664
|
};
|
|
1663
1665
|
}
|
|
1664
|
-
export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, meshCompany, workerPolicyDir, registryWorker, homedir) {
|
|
1666
|
+
export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, meshCompany, workerPolicyDir, registryWorker, homedir, session) {
|
|
1665
1667
|
const accepted = Object.values(PROVIDER_HOME_ENV);
|
|
1666
1668
|
if (!accepted.includes(resolved.envVar)) {
|
|
1667
1669
|
throw new LanesError("unknown_provider_home_env", `Unknown provider home variable ${JSON.stringify(resolved.envVar)}. Accepted: ${accepted.join(", ")}`, { accepted, value: resolved.envVar });
|
|
@@ -1704,8 +1706,48 @@ export function workerEnv(base, resolved, laneId, hqRoot, autoCompactTokens, mes
|
|
|
1704
1706
|
else {
|
|
1705
1707
|
delete env.HQ_SPAWN_COMPANY;
|
|
1706
1708
|
}
|
|
1709
|
+
if (meshCompany?.slug) {
|
|
1710
|
+
env.HQ_COMPANY = meshCompany.slug;
|
|
1711
|
+
}
|
|
1712
|
+
else {
|
|
1713
|
+
delete env.HQ_COMPANY;
|
|
1714
|
+
}
|
|
1715
|
+
// Session identity precedence for the worker (the fingerprint builder and
|
|
1716
|
+
// the commit-msg hook read HQ_SESSION_ID first, then the harness ids):
|
|
1717
|
+
// 1. HQ_SESSION_ID is the worker's own session, the id `--resume` would
|
|
1718
|
+
// use. It is set only when that id is known before launch (a resume, or
|
|
1719
|
+
// a remote-control lane whose id hq-cli mints). A cold first turn leaves
|
|
1720
|
+
// it unset rather than guessing.
|
|
1721
|
+
// 2. The harness ids (CLAUDE_CODE_SESSION_ID and the rest) are set by the
|
|
1722
|
+
// worker's own CLI once it starts. Values inherited from the senior are
|
|
1723
|
+
// dropped so a worker never reports the senior's session as its own.
|
|
1724
|
+
// HQ_PARENT_SESSION_ID names the senior session when the senior is one.
|
|
1725
|
+
for (const name of WORKER_SESSION_ENV_VARS)
|
|
1726
|
+
delete env[name];
|
|
1727
|
+
delete env.HQ_PARENT_SESSION_ID;
|
|
1728
|
+
if (session?.workerSessionId)
|
|
1729
|
+
env.HQ_SESSION_ID = session.workerSessionId;
|
|
1730
|
+
if (session?.parentSessionId)
|
|
1731
|
+
env.HQ_PARENT_SESSION_ID = session.parentSessionId;
|
|
1707
1732
|
return env;
|
|
1708
1733
|
}
|
|
1734
|
+
/** Session ids a worker must not inherit from the process that spawns it. */
|
|
1735
|
+
export const WORKER_SESSION_ENV_VARS = SESSION_ID_ENV_VARS;
|
|
1736
|
+
/**
|
|
1737
|
+
* The worker's fingerprint, built offline from the worker env at lane start.
|
|
1738
|
+
* Null when it cannot be built (no identity and no session id, or an error);
|
|
1739
|
+
* the lane still starts.
|
|
1740
|
+
*/
|
|
1741
|
+
export function laneStartFingerprint(hqRoot, env) {
|
|
1742
|
+
return offlineFingerprint(hqRoot, env, "hq lanes");
|
|
1743
|
+
}
|
|
1744
|
+
/** The session identity a lane turn exports to its worker (see `workerEnv`). */
|
|
1745
|
+
export function laneWorkerSessionEnv(lane, workerSessionId) {
|
|
1746
|
+
return {
|
|
1747
|
+
...(workerSessionId ? { workerSessionId } : {}),
|
|
1748
|
+
...(lane.senior.kind === "session" && lane.senior.id ? { parentSessionId: lane.senior.id } : {}),
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1709
1751
|
export const WORKER_POLICY_HOOK_CAPABILITY = "# hq-capability: worker-policy-dir v1";
|
|
1710
1752
|
function installedWorkerPolicyHookSupportsDirectory(hqRoot) {
|
|
1711
1753
|
const hookPath = path.join(hqRoot, ".claude", "hooks", "inject-policy-on-trigger.sh");
|
|
@@ -2445,9 +2487,13 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2445
2487
|
const autoCompactTokens = lane.provider === "claude"
|
|
2446
2488
|
? providerAutoCompactTokens(config.providers.claude)
|
|
2447
2489
|
: undefined;
|
|
2448
|
-
const
|
|
2490
|
+
const remoteSessionId = existing.remote_control === true
|
|
2491
|
+
? plan.mode === "resume" ? plan.session_id : randomUUID()
|
|
2492
|
+
: undefined;
|
|
2493
|
+
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: remoteControlHome ?? resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir, lane.registry_worker, undefined, laneWorkerSessionEnv(existing, plan.mode === "resume" ? plan.session_id : remoteSessionId));
|
|
2449
2494
|
const laneTurnId = randomUUID();
|
|
2450
2495
|
childEnv.HQ_LANE_TURN_ID = laneTurnId;
|
|
2496
|
+
const fingerprint = (deps.buildFingerprint ?? laneStartFingerprint)(hqRoot, childEnv);
|
|
2451
2497
|
const model = resolveLaneModel({
|
|
2452
2498
|
cliModel: pinnedModel,
|
|
2453
2499
|
profileModel,
|
|
@@ -2507,9 +2553,6 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2507
2553
|
}),
|
|
2508
2554
|
}
|
|
2509
2555
|
: undefined;
|
|
2510
|
-
const remoteSessionId = existing.remote_control === true
|
|
2511
|
-
? plan.mode === "resume" ? plan.session_id : randomUUID()
|
|
2512
|
-
: undefined;
|
|
2513
2556
|
const providerCommand = buildProviderCommand(lane.provider, {
|
|
2514
2557
|
hqRoot,
|
|
2515
2558
|
briefPath,
|
|
@@ -2682,6 +2725,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2682
2725
|
pending_settings: undefined,
|
|
2683
2726
|
account: resolved.id,
|
|
2684
2727
|
provider_home_at_spawn: remoteControlHome ?? resolved.home,
|
|
2728
|
+
fingerprint,
|
|
2685
2729
|
working_directory_at_spawn: workerCwd,
|
|
2686
2730
|
...(model !== undefined ? { model } : {}),
|
|
2687
2731
|
...(model !== undefined
|
|
@@ -3094,7 +3138,7 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
3094
3138
|
const autoCompactTokens = lane.provider === "claude"
|
|
3095
3139
|
? providerAutoCompactTokens(config.providers.claude)
|
|
3096
3140
|
: undefined;
|
|
3097
|
-
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir, lane.registry_worker);
|
|
3141
|
+
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir, lane.registry_worker, undefined, laneWorkerSessionEnv(lane, mode === "resume" && sessionId !== null ? sessionId : undefined));
|
|
3098
3142
|
const accountCfg = config.providers[lane.provider].accounts[resolved.id];
|
|
3099
3143
|
const model = resolveLaneModel({
|
|
3100
3144
|
cliModel: pinnedModel,
|
|
@@ -812,6 +812,7 @@ export function buildLaneStatusSnapshot(input) {
|
|
|
812
812
|
no_story_reason: lane.no_story_reason ?? null,
|
|
813
813
|
state: lane.state,
|
|
814
814
|
queued_reason: lane.queued_reason ?? null,
|
|
815
|
+
fingerprint: lane.fingerprint ?? null,
|
|
815
816
|
}));
|
|
816
817
|
const projectPrs = [];
|
|
817
818
|
const blockers = [];
|
|
@@ -905,7 +906,8 @@ export function formatLaneStatusHuman(snapshot) {
|
|
|
905
906
|
const account = worker.account ? " account " + worker.account : "";
|
|
906
907
|
const queued = worker.queued_reason ? " queued: " + worker.queued_reason : "";
|
|
907
908
|
return " " + worker.lane_id + " " + worker.state + " " + storyLabel +
|
|
908
|
-
" " + worker.provider + model + effort + account + queued
|
|
909
|
+
" " + worker.provider + model + effort + account + queued +
|
|
910
|
+
"\n fingerprint: " + (worker.fingerprint ?? "-");
|
|
909
911
|
})),
|
|
910
912
|
"PRs (may include merged):",
|
|
911
913
|
...(snapshot.prs.length === 0
|
|
@@ -81,6 +81,10 @@ export declare const LANE_ENVELOPE_JSON_SCHEMA: {
|
|
|
81
81
|
readonly type: "string";
|
|
82
82
|
readonly minLength: 1;
|
|
83
83
|
};
|
|
84
|
+
readonly fingerprint: {
|
|
85
|
+
readonly type: readonly ["string", "null"];
|
|
86
|
+
readonly description: "Optional worker fingerprint (hq fingerprint self); null when it could not be built. Older readers ignore it.";
|
|
87
|
+
};
|
|
84
88
|
};
|
|
85
89
|
readonly allOf: readonly [{
|
|
86
90
|
readonly if: {
|
|
@@ -770,6 +774,12 @@ export interface LaneRecord {
|
|
|
770
774
|
*/
|
|
771
775
|
provider_session_id?: string;
|
|
772
776
|
provider_session_id_absent?: string;
|
|
777
|
+
/**
|
|
778
|
+
* The worker's fingerprint, built offline from the worker env when the
|
|
779
|
+
* lane's run starts (`hq fingerprint self --offline`). Null when it could
|
|
780
|
+
* not be built; absent on lanes started before fingerprints existed.
|
|
781
|
+
*/
|
|
782
|
+
fingerprint?: string | null;
|
|
773
783
|
/** Provider home used by the most recently spawned run, for transcript recovery. */
|
|
774
784
|
provider_home_at_spawn?: string;
|
|
775
785
|
/**
|
|
@@ -1002,6 +1012,8 @@ export interface LaneEnvelope {
|
|
|
1002
1012
|
blocked_reason?: string;
|
|
1003
1013
|
story_complete?: boolean;
|
|
1004
1014
|
story_complete_reason?: string;
|
|
1015
|
+
/** The worker's fingerprint, or null when it could not be built. Optional. */
|
|
1016
|
+
fingerprint?: string | null;
|
|
1005
1017
|
}
|
|
1006
1018
|
export interface AccountConfig {
|
|
1007
1019
|
home: string;
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -49,6 +49,10 @@ export const LANE_ENVELOPE_JSON_SCHEMA = {
|
|
|
49
49
|
blocked_reason: { type: "string" },
|
|
50
50
|
story_complete: { type: "boolean" },
|
|
51
51
|
story_complete_reason: { type: "string", minLength: 1 },
|
|
52
|
+
fingerprint: {
|
|
53
|
+
type: ["string", "null"],
|
|
54
|
+
description: "Optional worker fingerprint (hq fingerprint self); null when it could not be built. Older readers ignore it.",
|
|
55
|
+
},
|
|
52
56
|
},
|
|
53
57
|
allOf: [
|
|
54
58
|
{
|
|
@@ -243,7 +247,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
243
247
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
244
248
|
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command", "reconcile"], settings_history: ["command"], pending_settings: ["command", "spawn"], interrupted_at: ["command", "watcher", "reconcile", "spawn"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
245
249
|
quota_reset_at: ["watcher", "reconcile", "spawn", "command"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
|
|
246
|
-
provider_session_id: ["watcher", "command", "spawn"], provider_session_id_absent: ["watcher", "command"], provider_home_at_spawn: ["spawn"], working_directory_at_spawn: ["spawn"], last_resume: ["spawn"], resume_history: ["spawn", "watcher"], last_run_usage: ["watcher"], last_run_usage_at: ["watcher"], envelope_repair: ["watcher", "reconcile"], envelope_validation_errors: ["watcher"], last_progress_event_at: ["watcher"],
|
|
250
|
+
provider_session_id: ["watcher", "command", "spawn"], provider_session_id_absent: ["watcher", "command"], fingerprint: ["spawn"], provider_home_at_spawn: ["spawn"], working_directory_at_spawn: ["spawn"], last_resume: ["spawn"], resume_history: ["spawn", "watcher"], last_run_usage: ["watcher"], last_run_usage_at: ["watcher"], envelope_repair: ["watcher", "reconcile"], envelope_validation_errors: ["watcher"], last_progress_event_at: ["watcher"],
|
|
247
251
|
};
|
|
248
252
|
function isPlainObject(value) {
|
|
249
253
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -299,6 +303,7 @@ export function parseLaneEnvelope(raw) {
|
|
|
299
303
|
"blocked_reason",
|
|
300
304
|
"story_complete",
|
|
301
305
|
"story_complete_reason",
|
|
306
|
+
"fingerprint",
|
|
302
307
|
].includes(key));
|
|
303
308
|
if (extra.length > 0) {
|
|
304
309
|
throw new LaneSchemaError(`Lane envelope rejects unknown properties: ${extra.join(", ")}`, { extra });
|
|
@@ -382,6 +387,12 @@ export function parseLaneEnvelope(raw) {
|
|
|
382
387
|
};
|
|
383
388
|
if (nextCheckAt !== undefined)
|
|
384
389
|
envelope.next_check_at = nextCheckAt;
|
|
390
|
+
if (raw.fingerprint !== undefined) {
|
|
391
|
+
if (raw.fingerprint !== null && (typeof raw.fingerprint !== "string" || raw.fingerprint.trim().length === 0)) {
|
|
392
|
+
throw new LaneSchemaError("fingerprint must be a non-empty string or null", { field: "fingerprint" });
|
|
393
|
+
}
|
|
394
|
+
envelope.fingerprint = raw.fingerprint;
|
|
395
|
+
}
|
|
385
396
|
if (storyComplete !== undefined) {
|
|
386
397
|
envelope.story_complete = storyComplete;
|
|
387
398
|
envelope.story_complete_reason = storyCompleteReason;
|
|
@@ -121,6 +121,18 @@ export declare function detectPlatform(platform?: NodeJS.Platform): DaemonInstal
|
|
|
121
121
|
* binary when both exist.
|
|
122
122
|
*/
|
|
123
123
|
export declare function preferStableHqBinary(entry: string, platform?: DaemonInstallPlatform, existsSync?: typeof fs.existsSync): string;
|
|
124
|
+
/**
|
|
125
|
+
* A path that `node <path>` can run. preferStableHqBinary maps the package
|
|
126
|
+
* entry to the npm `hq.cmd` shim on Windows, which is right for a task that
|
|
127
|
+
* launches through cmd.exe but wrong for every caller that runs
|
|
128
|
+
* `node <hqBinary>`: node parses the batch file as JavaScript and exits with
|
|
129
|
+
* "SyntaxError: Invalid or unexpected token" on `@ECHO off`
|
|
130
|
+
* (feedback_a60339e8, feedback_516e4427). Map the shim back to the package's
|
|
131
|
+
* JS entry, which sits beside it in the same npm prefix and so stays valid
|
|
132
|
+
* across `npm i -g` updates. Anything that is not a Windows command shim is
|
|
133
|
+
* returned unchanged.
|
|
134
|
+
*/
|
|
135
|
+
export declare function nodeRunnableHqEntry(hqBinary: string, platform?: DaemonInstallPlatform | NodeJS.Platform, existsSync?: typeof fs.existsSync): string;
|
|
124
136
|
/**
|
|
125
137
|
* Absolute path of the CLI entry that is executing this process.
|
|
126
138
|
* realpath of argv[1] only — never a PATH lookup of `hq`.
|
|
@@ -299,6 +299,28 @@ export function preferStableHqBinary(entry, platform = detectPlatform(), existsS
|
|
|
299
299
|
}
|
|
300
300
|
return entry;
|
|
301
301
|
}
|
|
302
|
+
/** JS entry of the hq-cli package, relative to an npm global prefix. */
|
|
303
|
+
const HQ_CLI_PACKAGE_ENTRY = ["node_modules", "@indigoai-us", "hq-cli", "dist", "index.js"];
|
|
304
|
+
/**
|
|
305
|
+
* A path that `node <path>` can run. preferStableHqBinary maps the package
|
|
306
|
+
* entry to the npm `hq.cmd` shim on Windows, which is right for a task that
|
|
307
|
+
* launches through cmd.exe but wrong for every caller that runs
|
|
308
|
+
* `node <hqBinary>`: node parses the batch file as JavaScript and exits with
|
|
309
|
+
* "SyntaxError: Invalid or unexpected token" on `@ECHO off`
|
|
310
|
+
* (feedback_a60339e8, feedback_516e4427). Map the shim back to the package's
|
|
311
|
+
* JS entry, which sits beside it in the same npm prefix and so stays valid
|
|
312
|
+
* across `npm i -g` updates. Anything that is not a Windows command shim is
|
|
313
|
+
* returned unchanged.
|
|
314
|
+
*/
|
|
315
|
+
export function nodeRunnableHqEntry(hqBinary, platform = detectPlatform(), existsSync = fs.existsSync) {
|
|
316
|
+
if (platform !== "win32")
|
|
317
|
+
return hqBinary;
|
|
318
|
+
const ext = path.win32.extname(hqBinary).toLowerCase();
|
|
319
|
+
if (ext !== ".cmd" && ext !== ".bat" && ext !== ".ps1")
|
|
320
|
+
return hqBinary;
|
|
321
|
+
const entry = path.win32.join(path.win32.dirname(hqBinary), ...HQ_CLI_PACKAGE_ENTRY);
|
|
322
|
+
return existsSync(entry) ? entry : hqBinary;
|
|
323
|
+
}
|
|
302
324
|
/**
|
|
303
325
|
* Absolute path of the CLI entry that is executing this process.
|
|
304
326
|
* realpath of argv[1] only — never a PATH lookup of `hq`.
|
|
@@ -22,7 +22,7 @@ import { defaultSleep, FLUSH_MAX_ATTEMPTS, fullJitterDelayMs, } from "./backoff.
|
|
|
22
22
|
import { LOCAL_ONLY_FIELDS, stripLocalOnlyFields } from "./format-spool-line.js";
|
|
23
23
|
import { enrichSessionEndEvent } from "./report-summary.js";
|
|
24
24
|
import { createWorkLocationResolver, withWorkLocation, } from "./work-location.js";
|
|
25
|
-
import { SESSION_EVENTS_BATCH_MAX, WRITABLE_CONTEXT_STATUSES, parseSessionEventsBatchAck, } from "./session-events-client.js";
|
|
25
|
+
import { SESSION_EVENTS_BATCH_MAX, WRITABLE_CONTEXT_STATUSES, isSessionOwnerMismatch, parseSessionEventsBatchAck, SESSION_OWNER_MISMATCH, } from "./session-events-client.js";
|
|
26
26
|
import { appendDeadLetterLine, appendDroppedReceipt, appendHeldLinesIfAbsent, appendSpoolLine, claimHeld, claimSpool, countDistinctHeldEvents, countJsonlLines, recoverOrphanClaims, removeClaimFile, } from "./spool.js";
|
|
27
27
|
import { workMeshHeldPath, workMeshRoot } from "./paths.js";
|
|
28
28
|
/** Bound a replayed session backlog so one flush cannot burst hundreds of events. */
|
|
@@ -306,7 +306,7 @@ export async function flushSessionEvents(inputDeps) {
|
|
|
306
306
|
v: 1,
|
|
307
307
|
sessionId,
|
|
308
308
|
dropped: remainingItems.length,
|
|
309
|
-
reason:
|
|
309
|
+
reason: decision.reason,
|
|
310
310
|
at: now().toISOString(),
|
|
311
311
|
}, deps.workMeshRoot);
|
|
312
312
|
summary.dropped += remainingItems.length;
|
|
@@ -353,6 +353,22 @@ export async function flushSessionEvents(inputDeps) {
|
|
|
353
353
|
accountBatchSuccess(result.body, originalChunk, deps.workMeshRoot, now, summary, markDisposed);
|
|
354
354
|
continue;
|
|
355
355
|
}
|
|
356
|
+
if (!result.retryable && isSessionOwnerMismatch(result.status, result.body)) {
|
|
357
|
+
// Another actor already reports this session (feedback_5b4683c2).
|
|
358
|
+
// Not an error on this machine: drop the session's remaining events
|
|
359
|
+
// with a receipt, remember the answer, and skip it from now on.
|
|
360
|
+
const unsent = remainingItems.filter((item) => pendingRestore.has(item.event));
|
|
361
|
+
dropOwnerMismatchedSession({
|
|
362
|
+
sessionId,
|
|
363
|
+
companyUid: decision.companyUid,
|
|
364
|
+
items: unsent,
|
|
365
|
+
deps,
|
|
366
|
+
now,
|
|
367
|
+
summary,
|
|
368
|
+
markDisposed,
|
|
369
|
+
});
|
|
370
|
+
continue sessionLoop;
|
|
371
|
+
}
|
|
356
372
|
if (!result.retryable && result.status === 404) {
|
|
357
373
|
const rejectedProjectId = decision.projectId;
|
|
358
374
|
updateSessionState(sessionId, deps.workContextRoot, (current) => {
|
|
@@ -560,15 +576,34 @@ function accountBatchSuccess(body, originalChunk, meshRoot, now, summary, markDi
|
|
|
560
576
|
}
|
|
561
577
|
const rejectedIds = new Set();
|
|
562
578
|
const at = now().toISOString();
|
|
579
|
+
let ownerMismatchDropped = 0;
|
|
563
580
|
for (const row of ack.rejected) {
|
|
564
581
|
const item = byId.get(row.eventId);
|
|
565
582
|
if (!item)
|
|
566
583
|
continue;
|
|
584
|
+
if (row.code === SESSION_OWNER_MISMATCH) {
|
|
585
|
+
ownerMismatchDropped += 1;
|
|
586
|
+
summary.dropped += 1;
|
|
587
|
+
rejectedIds.add(row.eventId);
|
|
588
|
+
markDisposed(item.event);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
567
591
|
appendDeadLetterLine(deadLetterEnvelope(item.event, row.code, row.code, at, row.code), meshRoot);
|
|
568
592
|
summary.deadLettered += 1;
|
|
569
593
|
rejectedIds.add(row.eventId);
|
|
570
594
|
markDisposed(item.event);
|
|
571
595
|
}
|
|
596
|
+
if (ownerMismatchDropped > 0) {
|
|
597
|
+
const sessionId = sessionIdOf(originalChunk[0].event);
|
|
598
|
+
appendDroppedReceipt({
|
|
599
|
+
v: 1,
|
|
600
|
+
sessionId,
|
|
601
|
+
dropped: ownerMismatchDropped,
|
|
602
|
+
reason: "owner_mismatch",
|
|
603
|
+
code: SESSION_OWNER_MISMATCH,
|
|
604
|
+
at,
|
|
605
|
+
}, meshRoot);
|
|
606
|
+
}
|
|
572
607
|
const remaining = originalChunk.filter((item) => {
|
|
573
608
|
const id = eventIdOf(item.event);
|
|
574
609
|
return !id || !rejectedIds.has(id);
|
|
@@ -767,6 +802,26 @@ async function postPresenceLeaveIfNeeded(input) {
|
|
|
767
802
|
});
|
|
768
803
|
if (!result.ok) {
|
|
769
804
|
const at = input.now().toISOString();
|
|
805
|
+
if (!result.retryable && isSessionOwnerMismatch(result.status, result.body)) {
|
|
806
|
+
// The old company says another actor owns this session, so there is no
|
|
807
|
+
// presence of ours to leave. Drop the leave events, clear the pending
|
|
808
|
+
// leave, and let the remaining events continue to the new binding.
|
|
809
|
+
dropOwnerMismatchedSession({
|
|
810
|
+
sessionId: input.sessionId,
|
|
811
|
+
companyUid: leave.companyUid,
|
|
812
|
+
items: leaveItems,
|
|
813
|
+
deps: input.deps,
|
|
814
|
+
now: input.now,
|
|
815
|
+
summary: input.summary,
|
|
816
|
+
markDisposed: input.markDisposed,
|
|
817
|
+
clearPresenceLeave: true,
|
|
818
|
+
});
|
|
819
|
+
return {
|
|
820
|
+
state: readSessionState(input.sessionId, input.deps.workContextRoot) ?? input.state,
|
|
821
|
+
items: remaining,
|
|
822
|
+
abort: false,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
770
825
|
if (!result.retryable) {
|
|
771
826
|
for (const item of input.items) {
|
|
772
827
|
appendDeadLetterLine(deadLetterEnvelope(item.event, `HTTP_${result.status}`, result.status, at), input.deps.workMeshRoot);
|
|
@@ -843,12 +898,60 @@ function rebindUnboundSession(sessionId, items, state, deps, nowMs) {
|
|
|
843
898
|
}
|
|
844
899
|
return readSessionState(sessionId, deps.workContextRoot) ?? state;
|
|
845
900
|
}
|
|
901
|
+
const loggedOwnerMismatch = new Set();
|
|
902
|
+
/**
|
|
903
|
+
* Drop a session's events after the server answered 403
|
|
904
|
+
* SESSION_OWNER_MISMATCH: write one dropped receipt, mark the session state so
|
|
905
|
+
* later flushes skip it without posting, and log once per session. These
|
|
906
|
+
* events never reach dead-letter, so daemon health stays clean.
|
|
907
|
+
*/
|
|
908
|
+
function dropOwnerMismatchedSession(input) {
|
|
909
|
+
const at = input.now().toISOString();
|
|
910
|
+
if (input.items.length > 0) {
|
|
911
|
+
appendDroppedReceipt({
|
|
912
|
+
v: 1,
|
|
913
|
+
sessionId: input.sessionId,
|
|
914
|
+
dropped: input.items.length,
|
|
915
|
+
reason: "owner_mismatch",
|
|
916
|
+
code: SESSION_OWNER_MISMATCH,
|
|
917
|
+
companyUid: input.companyUid,
|
|
918
|
+
at,
|
|
919
|
+
}, input.deps.workMeshRoot);
|
|
920
|
+
}
|
|
921
|
+
input.summary.dropped += input.items.length;
|
|
922
|
+
for (const item of input.items)
|
|
923
|
+
input.markDisposed(item.event);
|
|
924
|
+
updateSessionState(input.sessionId, input.deps.workContextRoot, (current) => {
|
|
925
|
+
if (!current)
|
|
926
|
+
return current;
|
|
927
|
+
if (input.clearPresenceLeave) {
|
|
928
|
+
const { presenceLeave: _leave, ...rest } = current;
|
|
929
|
+
return { ...rest, updatedAt: at };
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
...current,
|
|
933
|
+
ownerMismatch: { companyUid: input.companyUid, at },
|
|
934
|
+
updatedAt: at,
|
|
935
|
+
};
|
|
936
|
+
});
|
|
937
|
+
const key = `${input.deps.workContextRoot}\0${input.sessionId}`;
|
|
938
|
+
if (!loggedOwnerMismatch.has(key)) {
|
|
939
|
+
loggedOwnerMismatch.add(key);
|
|
940
|
+
input.deps.log?.(`work-mesh: session ${input.sessionId} is reported by another actor (403 SESSION_OWNER_MISMATCH); dropped ${input.items.length} event(s), not dead-lettered`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
846
943
|
function classifySession(state) {
|
|
847
944
|
if (!state) {
|
|
848
945
|
return { action: "hold", heldReason: "STATE_ABSENT" };
|
|
849
946
|
}
|
|
850
947
|
if (state.contextStatus === "untracked") {
|
|
851
|
-
return { action: "drop" };
|
|
948
|
+
return { action: "drop", reason: "untracked" };
|
|
949
|
+
}
|
|
950
|
+
// The server already told us another actor owns this session in this
|
|
951
|
+
// company; posting again would only earn another 403.
|
|
952
|
+
if (state.ownerMismatch?.companyUid &&
|
|
953
|
+
state.companyUid === state.ownerMismatch.companyUid) {
|
|
954
|
+
return { action: "drop", reason: "owner_mismatch" };
|
|
852
955
|
}
|
|
853
956
|
if (state.contextStatus === "needs_company") {
|
|
854
957
|
return { action: "hold", heldReason: "NEEDS_COMPANY" };
|
|
@@ -41,6 +41,15 @@ export interface SessionEventsBatchAck {
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function parseSessionEventsBatchAck(body: unknown): SessionEventsBatchAck | null;
|
|
43
43
|
export type SessionEventsPoster = (body: SessionEventsBatchBody) => Promise<SessionEventsPostResult>;
|
|
44
|
+
/**
|
|
45
|
+
* Server code for a session that another actor in the same company already
|
|
46
|
+
* reports (for example a local bot's own headless turn, picked up by the
|
|
47
|
+
* person's daemon). The server will never accept this actor's events for that
|
|
48
|
+
* session, so the flush drops them with a receipt instead of dead-lettering.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SESSION_OWNER_MISMATCH = "SESSION_OWNER_MISMATCH";
|
|
51
|
+
/** True for the 403 SESSION_OWNER_MISMATCH answer (feedback_5b4683c2). */
|
|
52
|
+
export declare function isSessionOwnerMismatch(status: number, body: unknown): boolean;
|
|
44
53
|
export declare function classifySessionEventsResponse(status: number, networkError?: boolean): Pick<SessionEventsPostResult, "ok" | "retryable" | "networkError">;
|
|
45
54
|
/**
|
|
46
55
|
* Build a poster that uses vaultApiFetch + Cognito token.
|
|
@@ -65,6 +65,21 @@ export function parseSessionEventsBatchAck(body) {
|
|
|
65
65
|
}
|
|
66
66
|
return { accepted, duplicates, rejected };
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Server code for a session that another actor in the same company already
|
|
70
|
+
* reports (for example a local bot's own headless turn, picked up by the
|
|
71
|
+
* person's daemon). The server will never accept this actor's events for that
|
|
72
|
+
* session, so the flush drops them with a receipt instead of dead-lettering.
|
|
73
|
+
*/
|
|
74
|
+
export const SESSION_OWNER_MISMATCH = "SESSION_OWNER_MISMATCH";
|
|
75
|
+
/** True for the 403 SESSION_OWNER_MISMATCH answer (feedback_5b4683c2). */
|
|
76
|
+
export function isSessionOwnerMismatch(status, body) {
|
|
77
|
+
if (status !== 403)
|
|
78
|
+
return false;
|
|
79
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
80
|
+
return false;
|
|
81
|
+
return body.code === SESSION_OWNER_MISMATCH;
|
|
82
|
+
}
|
|
68
83
|
export function classifySessionEventsResponse(status, networkError = false) {
|
|
69
84
|
if (networkError || status === 0) {
|
|
70
85
|
return { ok: false, retryable: true, networkError: true };
|
|
@@ -117,6 +117,16 @@ export interface SessionStateFile {
|
|
|
117
117
|
taskId?: string;
|
|
118
118
|
bindingEpisodeId?: string;
|
|
119
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* LOCAL-ONLY. Set when the server answered 403 SESSION_OWNER_MISMATCH for
|
|
122
|
+
* this session in `companyUid`: another actor already reports it (a local
|
|
123
|
+
* bot's own turn, a checkpoint helper). While the session stays bound to
|
|
124
|
+
* that company, flush drops its events locally instead of posting them.
|
|
125
|
+
*/
|
|
126
|
+
ownerMismatch?: {
|
|
127
|
+
companyUid: string;
|
|
128
|
+
at: string;
|
|
129
|
+
};
|
|
120
130
|
updatedAt: string;
|
|
121
131
|
}
|
|
122
132
|
/**
|
|
@@ -215,6 +215,8 @@ function projectSessionState(state) {
|
|
|
215
215
|
out.projectHint = state.projectHint;
|
|
216
216
|
if (state.presenceLeave?.companyUid)
|
|
217
217
|
out.presenceLeave = state.presenceLeave;
|
|
218
|
+
if (state.ownerMismatch?.companyUid)
|
|
219
|
+
out.ownerMismatch = state.ownerMismatch;
|
|
218
220
|
return out;
|
|
219
221
|
}
|
|
220
222
|
/**
|
|
@@ -282,6 +284,9 @@ export function mergeLocalSessionFields(next, prior, options = {}) {
|
|
|
282
284
|
if (!out.presenceLeave && prior.presenceLeave) {
|
|
283
285
|
out.presenceLeave = prior.presenceLeave;
|
|
284
286
|
}
|
|
287
|
+
if (!out.ownerMismatch && prior.ownerMismatch) {
|
|
288
|
+
out.ownerMismatch = prior.ownerMismatch;
|
|
289
|
+
}
|
|
285
290
|
if (out.projectId)
|
|
286
291
|
out.projectHint = undefined;
|
|
287
292
|
const sameBinding = prior.companyUid === out.companyUid &&
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare function isMonitorFastPathCommand(argv: readonly string[]): boolean;
|
|
2
|
+
/** The commit-msg hook runs `hq fingerprint self --offline`; it must stay fast and offline. */
|
|
3
|
+
export declare function isOfflineFingerprintCommand(argv: readonly string[]): boolean;
|
|
2
4
|
/** Skip process-wide network lifecycle work for hooks and detached monitor supervisors. */
|
|
3
5
|
export declare function runGlobalNetworkLifecycle<T>(argv: readonly string[], operation: () => T): T | undefined;
|
|
4
6
|
//# sourceMappingURL=cli-network-lifecycle.d.ts.map
|
|
@@ -2,9 +2,13 @@ const MONITOR_HIDDEN_NETWORK_BYPASS = new Set(["drain", "wait", "enabled", "_run
|
|
|
2
2
|
export function isMonitorFastPathCommand(argv) {
|
|
3
3
|
return argv[2] === "monitor" && MONITOR_HIDDEN_NETWORK_BYPASS.has(argv[3] ?? "");
|
|
4
4
|
}
|
|
5
|
+
/** The commit-msg hook runs `hq fingerprint self --offline`; it must stay fast and offline. */
|
|
6
|
+
export function isOfflineFingerprintCommand(argv) {
|
|
7
|
+
return argv[2] === "fingerprint" && argv[3] === "self" && argv.slice(4).includes("--offline");
|
|
8
|
+
}
|
|
5
9
|
/** Skip process-wide network lifecycle work for hooks and detached monitor supervisors. */
|
|
6
10
|
export function runGlobalNetworkLifecycle(argv, operation) {
|
|
7
|
-
if (isMonitorFastPathCommand(argv))
|
|
11
|
+
if (isMonitorFastPathCommand(argv) || isOfflineFingerprintCommand(argv))
|
|
8
12
|
return undefined;
|
|
9
13
|
return operation();
|
|
10
14
|
}
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* HQ_VAULT_API_URL — vault-service API Gateway URL
|
|
20
20
|
*/
|
|
21
21
|
import { isExpiring, isMachineIdentity, type CognitoAuthConfig, type CognitoTokens, type ClientInfo, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
|
|
22
|
-
import {
|
|
22
|
+
import { loadCachedTokens, personTokenCacheFile, readPersonTokenFile } from "./person-token-cache.js";
|
|
23
|
+
export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
23
24
|
export { isExpiring, isMachineIdentity };
|
|
24
25
|
/** Person browser/login cache vs machine-creds USER_PASSWORD_AUTH mint. */
|
|
25
26
|
export type CognitoTokenSource = "person" | "machine";
|
|
@@ -118,13 +119,6 @@ export type EnsureCognitoTokenOptions = {
|
|
|
118
119
|
/** Cancels bounded, non-interactive person-session refreshes. */
|
|
119
120
|
signal?: AbortSignal;
|
|
120
121
|
};
|
|
121
|
-
/**
|
|
122
|
-
* On-disk person Cognito cache path for `env` (`HQ_STATE_DIR` or `~/.hq`).
|
|
123
|
-
* Deliberately does not use hq-cloud's `loadCachedTokens()`: after a machine
|
|
124
|
-
* mint in this process, that helper returns in-memory machine tokens when the
|
|
125
|
-
* person file is absent, which would falsely block the fresh-box fallback.
|
|
126
|
-
*/
|
|
127
|
-
export declare function personTokenCacheFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
128
122
|
/**
|
|
129
123
|
* True when a usable person Cognito cache file exists under the effective
|
|
130
124
|
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
@@ -186,19 +180,6 @@ export declare function formatCognitoSubject(tokens: {
|
|
|
186
180
|
idToken?: string;
|
|
187
181
|
accessToken?: string;
|
|
188
182
|
} | null | undefined): string;
|
|
189
|
-
/**
|
|
190
|
-
* Person session for `env`: the Keychain item and the token file, the newer
|
|
191
|
-
* by `expiresAt` (see ./token-keychain.ts). Does not consult hq-cloud's
|
|
192
|
-
* in-memory fallback, which can surface a different session when the file
|
|
193
|
-
* is missing or unreadable.
|
|
194
|
-
*/
|
|
195
|
-
export declare function readPersonTokenFile(home?: string, env?: NodeJS.ProcessEnv, keychain?: KeychainReadOptions): CognitoTokens | null;
|
|
196
|
-
/**
|
|
197
|
-
* The person's cached session: the shared Keychain item and hq-cloud's file
|
|
198
|
-
* loader, the newer by `expiresAt`. Writes still go only to the file in this
|
|
199
|
-
* step.
|
|
200
|
-
*/
|
|
201
|
-
export declare function loadCachedTokens(env?: NodeJS.ProcessEnv, keychain?: KeychainReadOptions): CognitoTokens | null;
|
|
202
183
|
/**
|
|
203
184
|
* Refresh the active person session and refuse a subject change.
|
|
204
185
|
*
|