@indigoai-us/hq-cli 5.345.133 → 5.345.135
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 +17 -3
- package/dist/command-catalog.generated.d.ts +45 -0
- package/dist/command-catalog.generated.js +56 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/access.d.ts +2 -0
- package/dist/commands/access.js +58 -9
- 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-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/fingerprint.d.ts +10 -0
- package/dist/commands/fingerprint.js +191 -0
- package/dist/commands/lanes.js +9 -1
- 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/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/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/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/package.json +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the fingerprint for the current HQ session (`hq fingerprint self`).
|
|
3
|
+
*
|
|
4
|
+
* Sources, in order:
|
|
5
|
+
* - session id: the first valid value of HQ_SESSION_ID, CLAUDE_CODE_SESSION_ID,
|
|
6
|
+
* CLAUDE_SESSION_ID, CODEX_SESSION_ID, CODEX_THREAD_ID, GROK_SESSION_ID (same
|
|
7
|
+
* order as core/scripts/lib/session-id.sh). workspace/sessions/.current is
|
|
8
|
+
* never read: it names whichever session wrote last, not this one.
|
|
9
|
+
* - identity: `custom:entityUid` on the cached ID token, then the agent uid in
|
|
10
|
+
* the machine creds, then this session's fingerprint.yaml cache (only when
|
|
11
|
+
* it was written for the same token owner), then a person lookup (3s cap,
|
|
12
|
+
* skipped with --offline, without a session id, and for outpost and agent
|
|
13
|
+
* principals). A lookup is cached once it succeeds or the server says the
|
|
14
|
+
* caller has no person entity; transient failures retry next time.
|
|
15
|
+
* - machine: HQ_MACHINE_ID, then `<hqRoot>/.hq/machine-id`.
|
|
16
|
+
* - company: the session's meta.yaml company_slug, then HQ_COMPANY, then
|
|
17
|
+
* HQ_SPAWN_COMPANY. A value that is not a slug is replaced with `-`.
|
|
18
|
+
*
|
|
19
|
+
* The result is written to workspace/sessions/<sid>/fingerprint.yaml when a
|
|
20
|
+
* session id is known. registerSelfFingerprint (US-007) then records the
|
|
21
|
+
* fingerprint with hq-pro once per session and writes `registered_at`, or
|
|
22
|
+
* `registration_skipped` when hq-pro will never accept it (outpost hosts, a
|
|
23
|
+
* 403). It never prints and never throws; failures go to ~/.hq/fingerprint.log.
|
|
24
|
+
* registry-client is imported lazily, only on the registration path.
|
|
25
|
+
*
|
|
26
|
+
* The commit-msg hook runs `self --offline` on every commit, so this module
|
|
27
|
+
* keeps its static imports light: cognito-session, hq-cloud, and vault-api are
|
|
28
|
+
* loaded only on the paths that need them. The network-free core lives in
|
|
29
|
+
* self-core.ts so lane start and the handoff commits never reach them.
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from "node:fs";
|
|
32
|
+
import { finishFingerprint, IDENTITY_LOOKUP_TIMEOUT_MS, IDENTITY_UID_RE, identityContext, machineIdForFingerprint, readRegistrationState, recordRegistrationState, } from "./self-core.js";
|
|
33
|
+
export * from "./self-core.js";
|
|
34
|
+
async function defaultLoadIdToken() {
|
|
35
|
+
const { loadCachedTokens } = await import("../../utils/cognito-session.js");
|
|
36
|
+
return loadCachedTokens()?.idToken ?? null;
|
|
37
|
+
}
|
|
38
|
+
/** An unparseable creds file yields null, so it never marks the host as fleet. */
|
|
39
|
+
export async function defaultLoadMachineCreds() {
|
|
40
|
+
const { loadMachineCreds, machineCredsFilePath } = await import("@indigoai-us/hq-cloud");
|
|
41
|
+
if (!fs.existsSync(machineCredsFilePath()))
|
|
42
|
+
return null;
|
|
43
|
+
const creds = loadMachineCreds();
|
|
44
|
+
return creds ? { entityType: creds.entityType, entityUid: creds.entityUid } : null;
|
|
45
|
+
}
|
|
46
|
+
async function defaultResolveIdentity(idToken, signal) {
|
|
47
|
+
const { resolveCallerUid } = await import("../../utils/vault-api.js");
|
|
48
|
+
return resolveCallerUid(idToken, undefined, { signal });
|
|
49
|
+
}
|
|
50
|
+
/** Races the lookup against the timeout and aborts the request when time runs out. */
|
|
51
|
+
async function withTimeout(run, ms) {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
let timer;
|
|
54
|
+
const timeout = new Promise((_, reject) => {
|
|
55
|
+
timer = setTimeout(() => {
|
|
56
|
+
const err = new Error(`identity lookup timed out after ${ms}ms`);
|
|
57
|
+
controller.abort(err);
|
|
58
|
+
reject(err);
|
|
59
|
+
}, ms);
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
return await Promise.race([run(controller.signal), timeout]);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** vault-api's NoPersonEntityError, matched by name so vault-api stays lazily loaded. */
|
|
69
|
+
function isNoPersonEntityError(err) {
|
|
70
|
+
return err instanceof Error && err.name === "NoPersonEntityError";
|
|
71
|
+
}
|
|
72
|
+
export async function buildSelfFingerprint(deps) {
|
|
73
|
+
const warn = deps.warn ?? ((message) => process.stderr.write(`${message}\n`));
|
|
74
|
+
let idToken = null;
|
|
75
|
+
try {
|
|
76
|
+
idToken = await (deps.loadIdToken ?? defaultLoadIdToken)();
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
warn(`hq fingerprint: could not read the cached token: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
let creds = null;
|
|
82
|
+
try {
|
|
83
|
+
creds = await (deps.loadMachineCreds ?? defaultLoadMachineCreds)();
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
warn(`hq fingerprint: could not read machine creds: ${err.message}`);
|
|
87
|
+
}
|
|
88
|
+
const ctx = identityContext(deps, idToken, creds);
|
|
89
|
+
if (ctx.needsLookup && ctx.idToken) {
|
|
90
|
+
const resolveIdentity = deps.resolveIdentity ?? defaultResolveIdentity;
|
|
91
|
+
const token = ctx.idToken;
|
|
92
|
+
try {
|
|
93
|
+
const resolved = await withTimeout((signal) => resolveIdentity(token, signal), deps.lookupTimeoutMs ?? IDENTITY_LOOKUP_TIMEOUT_MS);
|
|
94
|
+
ctx.lookupDone = true;
|
|
95
|
+
if (IDENTITY_UID_RE.test(resolved.uid)) {
|
|
96
|
+
ctx.identityUid = resolved.uid;
|
|
97
|
+
ctx.source = "lookup";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// "No person entity" is a definitive answer; anything else may be transient.
|
|
102
|
+
if (isNoPersonEntityError(err))
|
|
103
|
+
ctx.lookupDone = true;
|
|
104
|
+
warn(`hq fingerprint: identity lookup failed: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return finishFingerprint(deps, ctx);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Records this session's fingerprint with hq-pro (POST /v1/fingerprints), at
|
|
111
|
+
* most once per session. Fire-and-forget: it never writes to stdout or stderr
|
|
112
|
+
* and never throws, so the printed fingerprint and the exit code stay the
|
|
113
|
+
* same whatever the registry does.
|
|
114
|
+
*
|
|
115
|
+
* hq-pro denies company-outpost machine tokens, so outpost hosts skip the call
|
|
116
|
+
* and record `registration_skipped: outpost`. A 403 is also final and is
|
|
117
|
+
* recorded the same way. A 404 means the registry is not deployed for this
|
|
118
|
+
* API, so it records `registration_skipped: unavailable` and the session stops
|
|
119
|
+
* calling. Any other failure is logged and retried next run.
|
|
120
|
+
*/
|
|
121
|
+
export async function registerSelfFingerprint(self, deps) {
|
|
122
|
+
const env = deps.env ?? process.env;
|
|
123
|
+
if (deps.register === false)
|
|
124
|
+
return { status: "skipped", reason: "--no-register" };
|
|
125
|
+
if (deps.offline || env.HQ_FINGERPRINT_OFFLINE === "1")
|
|
126
|
+
return { status: "skipped", reason: "offline" };
|
|
127
|
+
const { fingerprint, company, identityUid, sessionId, machineId, hostKind } = self;
|
|
128
|
+
if (!fingerprint || !company || company === "-" || !identityUid || !sessionId || !machineId) {
|
|
129
|
+
return { status: "skipped", reason: "incomplete fingerprint" };
|
|
130
|
+
}
|
|
131
|
+
// Cache read and write problems stay quiet here: the build already warned.
|
|
132
|
+
const quiet = () => { };
|
|
133
|
+
const state = readRegistrationState(deps.hqRoot, sessionId, fingerprint, quiet);
|
|
134
|
+
if (!state)
|
|
135
|
+
return { status: "skipped", reason: "no session cache" };
|
|
136
|
+
if (state.registeredAt)
|
|
137
|
+
return { status: "already-registered" };
|
|
138
|
+
if (state.skipped)
|
|
139
|
+
return { status: "skipped", reason: state.skipped };
|
|
140
|
+
try {
|
|
141
|
+
const registry = await import("./registry-client.js");
|
|
142
|
+
const log = (op, detail) => registry.appendFingerprintLog(op, detail, { dir: deps.logDir, now: deps.now });
|
|
143
|
+
if (hostKind === "outpost") {
|
|
144
|
+
log("register", `skipped outpost: hq-pro denies company-outpost machine tokens fingerprint=${fingerprint}`);
|
|
145
|
+
recordRegistrationState(deps.hqRoot, sessionId, fingerprint, { skipped: "outpost" }, quiet);
|
|
146
|
+
return { status: "skipped", reason: "outpost" };
|
|
147
|
+
}
|
|
148
|
+
let token = null;
|
|
149
|
+
try {
|
|
150
|
+
token = await (deps.loadIdToken ?? defaultLoadIdToken)();
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
log("register", `failed: could not read the cached token: ${err.name}`);
|
|
154
|
+
return { status: "failed", reason: "token unreadable" };
|
|
155
|
+
}
|
|
156
|
+
if (!token)
|
|
157
|
+
return { status: "skipped", reason: "not signed in" };
|
|
158
|
+
const result = await registry.registerFingerprint({
|
|
159
|
+
fingerprint,
|
|
160
|
+
companySlug: company,
|
|
161
|
+
identityUid,
|
|
162
|
+
machineId: machineIdForFingerprint(machineId),
|
|
163
|
+
hostKind,
|
|
164
|
+
sessionId,
|
|
165
|
+
}, token, { fetch: deps.fetch, timeoutMs: deps.timeoutMs });
|
|
166
|
+
if (result.ok) {
|
|
167
|
+
const at = (deps.now ?? (() => new Date()))().toISOString();
|
|
168
|
+
recordRegistrationState(deps.hqRoot, sessionId, fingerprint, { registeredAt: at }, quiet);
|
|
169
|
+
return { status: "registered" };
|
|
170
|
+
}
|
|
171
|
+
const detail = registry.describeFailure(result);
|
|
172
|
+
if (result.status === 403) {
|
|
173
|
+
// A denial will not change on retry; stop calling for this fingerprint.
|
|
174
|
+
log("register", `skipped forbidden: ${detail} fingerprint=${fingerprint}`);
|
|
175
|
+
recordRegistrationState(deps.hqRoot, sessionId, fingerprint, { skipped: "forbidden" }, quiet);
|
|
176
|
+
return { status: "skipped", reason: "forbidden" };
|
|
177
|
+
}
|
|
178
|
+
if (result.status === 404) {
|
|
179
|
+
// The registry route is not deployed here; stop calling for this session.
|
|
180
|
+
log("register", `skipped unavailable: ${detail} fingerprint=${fingerprint}`);
|
|
181
|
+
recordRegistrationState(deps.hqRoot, sessionId, fingerprint, { skipped: "unavailable" }, quiet);
|
|
182
|
+
return { status: "skipped", reason: "unavailable" };
|
|
183
|
+
}
|
|
184
|
+
log("register", `failed: ${detail} fingerprint=${fingerprint}`);
|
|
185
|
+
return { status: "failed", reason: detail };
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return { status: "failed", reason: err.message };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=self.js.map
|
|
@@ -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;
|
|
@@ -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
|
*
|
|
@@ -25,13 +25,14 @@ import * as os from "os";
|
|
|
25
25
|
import * as path from "path";
|
|
26
26
|
import * as yaml from "js-yaml";
|
|
27
27
|
import chalk from "chalk";
|
|
28
|
-
import {
|
|
28
|
+
import { isExpiring, refreshTokens, saveCachedTokens, browserLogin, detectHqCoreVersion, isMachineIdentity, getValidMachineTokens, CognitoRefreshError, accessTokenFingerprint, invalidateCachedTokensByFingerprint, } from "@indigoai-us/hq-cloud";
|
|
29
29
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
30
30
|
import { daemonDir } from "../lib/mesh/live/daemon/paths.js";
|
|
31
31
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
32
32
|
import { acquireLockFileExclusively } from "./exclusive-lock.js";
|
|
33
33
|
import { reportNoninteractivePersonRefreshRejection } from "./cognito-refresh-sentry.js";
|
|
34
|
-
import {
|
|
34
|
+
import { decodeJwtPayload, loadCachedTokens, personTokenCacheFile, readPersonTokenFile, readTokenFile, } from "./person-token-cache.js";
|
|
35
|
+
export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
35
36
|
// Re-export the cached-session primitives for callers that must deliberately
|
|
36
37
|
// avoid the refresh/login behavior in the higher-level helpers below.
|
|
37
38
|
export { isExpiring, isMachineIdentity };
|
|
@@ -294,17 +295,6 @@ function withAlignedMachineCredsEnv(env, fn) {
|
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
}
|
|
297
|
-
/**
|
|
298
|
-
* On-disk person Cognito cache path for `env` (`HQ_STATE_DIR` or `~/.hq`).
|
|
299
|
-
* Deliberately does not use hq-cloud's `loadCachedTokens()`: after a machine
|
|
300
|
-
* mint in this process, that helper returns in-memory machine tokens when the
|
|
301
|
-
* person file is absent, which would falsely block the fresh-box fallback.
|
|
302
|
-
*/
|
|
303
|
-
export function personTokenCacheFile(home = os.homedir(), env = process.env) {
|
|
304
|
-
const override = env.HQ_STATE_DIR?.trim();
|
|
305
|
-
const stateDir = override && override.length > 0 ? override : path.join(home, ".hq");
|
|
306
|
-
return path.join(stateDir, "cognito-tokens.json");
|
|
307
|
-
}
|
|
308
298
|
/**
|
|
309
299
|
* True when a usable person Cognito cache file exists under the effective
|
|
310
300
|
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
@@ -396,23 +386,6 @@ export function wantsMachineCognitoTokens(options = {}, env = process.env) {
|
|
|
396
386
|
export function resolveCognitoTokenSource(env = process.env) {
|
|
397
387
|
return wantsMachineCognitoTokens({}, env) ? "machine" : "person";
|
|
398
388
|
}
|
|
399
|
-
function decodeJwtPayload(token) {
|
|
400
|
-
if (!token)
|
|
401
|
-
return null;
|
|
402
|
-
const parts = token.split(".");
|
|
403
|
-
if (parts.length < 2 || !parts[1])
|
|
404
|
-
return null;
|
|
405
|
-
try {
|
|
406
|
-
const json = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
407
|
-
const parsed = JSON.parse(json);
|
|
408
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
409
|
-
return null;
|
|
410
|
-
return parsed;
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
389
|
/**
|
|
417
390
|
* Refresh wrote a different Cognito subject than the active cache.
|
|
418
391
|
* Callers must not fall through to browser login or machine mint — that
|
|
@@ -460,60 +433,6 @@ export function formatCognitoSubject(tokens) {
|
|
|
460
433
|
const fromAccess = cognitoSubjectFromToken(tokens.accessToken);
|
|
461
434
|
return `sub=${fromId.sub ?? fromAccess.sub ?? "-"} entityUid=${fromId.entityUid ?? fromAccess.entityUid ?? "-"}`;
|
|
462
435
|
}
|
|
463
|
-
function readTokenFile(file) {
|
|
464
|
-
try {
|
|
465
|
-
if (!fs.existsSync(file))
|
|
466
|
-
return null;
|
|
467
|
-
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
468
|
-
if (typeof raw.accessToken !== "string" || typeof raw.idToken !== "string")
|
|
469
|
-
return null;
|
|
470
|
-
return raw;
|
|
471
|
-
}
|
|
472
|
-
catch {
|
|
473
|
-
return null;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* The person session from the shared macOS Keychain item (see
|
|
478
|
-
* ./token-keychain.ts), or null. Not read when the token file is missing:
|
|
479
|
-
* no file means signed out. Machine-identity and invalidated tokens are
|
|
480
|
-
* refused, the same as the file loader.
|
|
481
|
-
*/
|
|
482
|
-
function personKeychainTokens(file, home, env, keychain = {}) {
|
|
483
|
-
if (!fs.existsSync(file))
|
|
484
|
-
return null;
|
|
485
|
-
const tokens = readKeychainTokens({ env, home, ...keychain });
|
|
486
|
-
if (!tokens)
|
|
487
|
-
return null;
|
|
488
|
-
const claims = decodeJwtPayload(tokens.idToken);
|
|
489
|
-
const entityType = claims?.["custom:entityType"];
|
|
490
|
-
if (typeof entityType === "string" && entityType.trim().length > 0)
|
|
491
|
-
return null;
|
|
492
|
-
if (fs.existsSync(`${file}.invalid.${accessTokenFingerprint(tokens.accessToken)}`))
|
|
493
|
-
return null;
|
|
494
|
-
return tokens;
|
|
495
|
-
}
|
|
496
|
-
/**
|
|
497
|
-
* Person session for `env`: the Keychain item and the token file, the newer
|
|
498
|
-
* by `expiresAt` (see ./token-keychain.ts). Does not consult hq-cloud's
|
|
499
|
-
* in-memory fallback, which can surface a different session when the file
|
|
500
|
-
* is missing or unreadable.
|
|
501
|
-
*/
|
|
502
|
-
export function readPersonTokenFile(home = os.homedir(), env = process.env, keychain = {}) {
|
|
503
|
-
const file = personTokenCacheFile(home, env);
|
|
504
|
-
return pickNewerSession(personKeychainTokens(file, home, env, keychain), readTokenFile(file));
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* The person's cached session: the shared Keychain item and hq-cloud's file
|
|
508
|
-
* loader, the newer by `expiresAt`. Writes still go only to the file in this
|
|
509
|
-
* step.
|
|
510
|
-
*/
|
|
511
|
-
export function loadCachedTokens(env = process.env, keychain = {}) {
|
|
512
|
-
const home = os.homedir();
|
|
513
|
-
const file = personTokenCacheFile(home, env);
|
|
514
|
-
const fromFile = loadCachedTokensFromFile();
|
|
515
|
-
return pickNewerSession(personKeychainTokens(file, home, env, keychain), fromFile);
|
|
516
|
-
}
|
|
517
436
|
function restoreTokenFile(file, before) {
|
|
518
437
|
if (before) {
|
|
519
438
|
const dir = path.dirname(file);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CognitoTokens } from "@indigoai-us/hq-cloud";
|
|
2
|
+
import { type KeychainReadOptions } from "./token-keychain.js";
|
|
3
|
+
/**
|
|
4
|
+
* On-disk person Cognito cache path for `env` (`HQ_STATE_DIR` or `~/.hq`).
|
|
5
|
+
* Deliberately does not use hq-cloud's `loadCachedTokens()`: after a machine
|
|
6
|
+
* mint in this process, that helper returns in-memory machine tokens when the
|
|
7
|
+
* person file is absent, which would falsely block the fresh-box fallback.
|
|
8
|
+
*/
|
|
9
|
+
export declare function personTokenCacheFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
10
|
+
export declare function decodeJwtPayload(token: string | undefined): Record<string, unknown> | null;
|
|
11
|
+
export declare function readTokenFile(file: string): CognitoTokens | null;
|
|
12
|
+
/**
|
|
13
|
+
* The person session from the shared macOS Keychain item (see
|
|
14
|
+
* ./token-keychain.ts), or null. Not read when the token file is missing:
|
|
15
|
+
* no file means signed out. Machine-identity and invalidated tokens are
|
|
16
|
+
* refused, the same as the file loader.
|
|
17
|
+
*/
|
|
18
|
+
export declare function personKeychainTokens(file: string, home: string, env: NodeJS.ProcessEnv, keychain?: KeychainReadOptions): CognitoTokens | null;
|
|
19
|
+
/**
|
|
20
|
+
* Person session for `env`: the Keychain item and the token file, the newer
|
|
21
|
+
* by `expiresAt` (see ./token-keychain.ts). Does not consult hq-cloud's
|
|
22
|
+
* in-memory fallback, which can surface a different session when the file
|
|
23
|
+
* is missing or unreadable.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readPersonTokenFile(home?: string, env?: NodeJS.ProcessEnv, keychain?: KeychainReadOptions): CognitoTokens | null;
|
|
26
|
+
/**
|
|
27
|
+
* The person's cached session: the shared Keychain item and hq-cloud's file
|
|
28
|
+
* loader, the newer by `expiresAt`. Writes still go only to the file in this
|
|
29
|
+
* step.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadCachedTokens(env?: NodeJS.ProcessEnv, keychain?: KeychainReadOptions): CognitoTokens | null;
|
|
32
|
+
//# sourceMappingURL=person-token-cache.d.ts.map
|