@indigoai-us/hq-cli 5.345.106 → 5.345.107
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/assets/scaffold/core/scripts/hq-task.mjs +259 -0
- package/assets/scaffold/core/scripts/hq-task.sh +31 -0
- package/assets/scaffold/core/scripts/pipeline-worker-table.sh +285 -0
- package/dist/command-catalog.generated.d.ts +135 -5
- package/dist/command-catalog.generated.js +161 -5
- package/dist/commands/auth.js +42 -0
- package/dist/commands/core-hq-task-fast.d.ts +4 -0
- package/dist/commands/core-hq-task-fast.js +11 -0
- package/dist/commands/core-hq-task-native.d.ts +20 -0
- package/dist/commands/core-hq-task-native.js +275 -0
- package/dist/commands/core-native-bucket-c-flag.d.ts +3 -0
- package/dist/commands/core-native-bucket-c-flag.js +6 -0
- package/dist/commands/core-native-family-flag.d.ts +0 -2
- package/dist/commands/core-native-family-flag.js +0 -4
- package/dist/commands/core-native-pipeline-flag.d.ts +3 -0
- package/dist/commands/core-native-pipeline-flag.js +6 -0
- package/dist/commands/core-ontology-native.js +24 -30
- package/dist/commands/core-pipeline-workers-command.d.ts +3 -0
- package/dist/commands/core-pipeline-workers-command.js +37 -0
- package/dist/commands/core-pipeline-workers-native.d.ts +13 -0
- package/dist/commands/core-pipeline-workers-native.js +361 -0
- package/dist/commands/core.js +12 -10
- package/dist/commands/lanes.js +32 -7
- package/dist/commands/outposts.d.ts +5 -0
- package/dist/commands/outposts.js +5 -0
- package/dist/fast-scaffold.d.ts +1 -1
- package/dist/fast-scaffold.js +13 -5
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +56937 -64550
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/daemon/remote-control-pools.d.ts +2 -2
- 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/reconcile.js +4 -0
- package/dist/lib/lanes/remote-control-lane.d.ts +33 -0
- package/dist/lib/lanes/remote-control-lane.js +276 -0
- package/dist/lib/lanes/spawn.d.ts +5 -0
- package/dist/lib/lanes/spawn.js +65 -14
- package/dist/lib/lanes/store.js +7 -0
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.js +8 -1
- package/dist/utils/cognito-session.d.ts +12 -4
- package/dist/utils/cognito-session.js +42 -9
- package/dist/utils/token-keychain.d.ts +96 -0
- package/dist/utils/token-keychain.js +218 -0
- package/package.json +2 -2
- package/assets/scaffold/core/scripts/ontology-audience-grant.sh +0 -81
- package/assets/scaffold/core/scripts/ontology-brief.mjs +0 -93
- package/assets/scaffold/core/scripts/ontology-candidate.sh +0 -100
- package/assets/scaffold/core/scripts/ontology-garden.mjs +0 -327
- package/assets/scaffold/core/scripts/ontology-source-worklist.sh +0 -226
- package/assets/scaffold/core/scripts/source-yaml-validate.sh +0 -36
|
@@ -1673,6 +1673,7 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1673
1673
|
if (!deps.dryRun &&
|
|
1674
1674
|
!providerRejection &&
|
|
1675
1675
|
!quota &&
|
|
1676
|
+
classified.remote_control !== true &&
|
|
1676
1677
|
repairCandidate &&
|
|
1677
1678
|
repairCandidate.envelope === null &&
|
|
1678
1679
|
(!repairCandidate.missing || envelopeRepairMode(classified.provider) === "resume")) {
|
|
@@ -1715,6 +1716,9 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1715
1716
|
if (!repairCandidate.missing)
|
|
1716
1717
|
invalidEnvelope = { errors: repair.errors };
|
|
1717
1718
|
}
|
|
1719
|
+
if (classified.remote_control === true && repairCandidate?.envelope === null) {
|
|
1720
|
+
invalidEnvelope = { errors: ["Remote Control turn did not write a valid lane envelope"] };
|
|
1721
|
+
}
|
|
1718
1722
|
let repairedClassification = invalidEnvelope
|
|
1719
1723
|
? classifyLaneExit({ envelope: null, hasPartialOutput: false, invalidEnvelope })
|
|
1720
1724
|
: classification;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LanesConfig } from "./types.js";
|
|
2
|
+
export interface RemoteControlLaneLaunch {
|
|
3
|
+
accountHome: string;
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
caPath: string;
|
|
7
|
+
}
|
|
8
|
+
/** Keep every credential and network route tied to the selected pool. */
|
|
9
|
+
export declare function configureRemoteControlChildEnv(env: NodeJS.ProcessEnv, launch: RemoteControlLaneLaunch): void;
|
|
10
|
+
/** Keep account balancing inside the daemon pools that can actually forward. */
|
|
11
|
+
export declare function remoteControlSelectionConfig(config: LanesConfig, env: NodeJS.ProcessEnv): LanesConfig;
|
|
12
|
+
/** Pool readiness replaces the inference account's local login for this turn. */
|
|
13
|
+
export declare function remoteControlAccountsForReadyPools(config: LanesConfig, ready: ReadonlySet<string>, hostname: string): LanesConfig;
|
|
14
|
+
/** Session files stay in account a when the inference pool changes. */
|
|
15
|
+
export declare function remoteControlSessionHome(config: LanesConfig, hostname: string): string;
|
|
16
|
+
export declare function prepareRemoteControlLane(input: {
|
|
17
|
+
config: LanesConfig;
|
|
18
|
+
hostname: string;
|
|
19
|
+
env: NodeJS.ProcessEnv;
|
|
20
|
+
accountId: string;
|
|
21
|
+
laneId: string;
|
|
22
|
+
envelopePath: string;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
claudeArgs: string[];
|
|
25
|
+
}): RemoteControlLaneLaunch;
|
|
26
|
+
declare function tmux(socket: string, args: string[]): {
|
|
27
|
+
status: number | null;
|
|
28
|
+
stdout: string;
|
|
29
|
+
stderr: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function remoteControlFailureText(socket: string, session: string, capture?: typeof tmux): string;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=remote-control-lane.d.ts.map
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Claude lane opened as its own Remote Control terminal. The login and
|
|
3
|
+
* transcript stay in account a; a private hosts mount forces all Anthropic API
|
|
4
|
+
* requests through the selected daemon pool's token forwarder.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import * as os from "node:os";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { stripVTControlCharacters } from "node:util";
|
|
13
|
+
import { daemonPaths, ensurePrivateDir } from "../daemon/paths.js";
|
|
14
|
+
import { isOwnedPoolForwarder, poolHostsFile, readRemoteControlPoolsConfig } from "../daemon/remote-control-pools.js";
|
|
15
|
+
import { accountStatus } from "./accounts.js";
|
|
16
|
+
import { effectiveAccountHome } from "./account-home.js";
|
|
17
|
+
import { LanesError } from "./errors.js";
|
|
18
|
+
import { defaultProbe } from "./models.js";
|
|
19
|
+
import { encodeClaudeProjectDir } from "./move-account.js";
|
|
20
|
+
/** Keep every credential and network route tied to the selected pool. */
|
|
21
|
+
export function configureRemoteControlChildEnv(env, launch) {
|
|
22
|
+
env.CLAUDE_CONFIG_DIR = launch.accountHome;
|
|
23
|
+
env.NODE_EXTRA_CA_CERTS = launch.caPath;
|
|
24
|
+
env.IS_SANDBOX = "1";
|
|
25
|
+
for (const key of Object.keys(env)) {
|
|
26
|
+
if (/_proxy$/i.test(key) || ["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"].includes(key)) {
|
|
27
|
+
delete env[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Keep account balancing inside the daemon pools that can actually forward. */
|
|
32
|
+
export function remoteControlSelectionConfig(config, env) {
|
|
33
|
+
if (process.platform !== "linux") {
|
|
34
|
+
throw new LanesError("remote_control_linux_required", "Claude Remote Control account pools require Linux", {});
|
|
35
|
+
}
|
|
36
|
+
const home = os.homedir();
|
|
37
|
+
const paths = daemonPaths(home, env);
|
|
38
|
+
const pools = readRemoteControlPoolsConfig(paths.remoteControlPoolsPath, Object.keys(config.providers.claude.accounts), home);
|
|
39
|
+
const ready = new Set(pools.frontendCaPath && fs.existsSync(pools.frontendCaPath)
|
|
40
|
+
? pools.pools
|
|
41
|
+
.filter((pool) => ownedForwarder(path.join(paths.root, "remote-control-pools", "forwarder", `${pool.accountId}.json`), pool.accountId))
|
|
42
|
+
.map((pool) => pool.accountId)
|
|
43
|
+
: []);
|
|
44
|
+
if (ready.size === 0) {
|
|
45
|
+
throw new LanesError("remote_control_pool_unavailable", "No Claude Remote Control inference pool is running in the local HQ daemon", {});
|
|
46
|
+
}
|
|
47
|
+
return remoteControlAccountsForReadyPools(config, ready, os.hostname());
|
|
48
|
+
}
|
|
49
|
+
/** Pool readiness replaces the inference account's local login for this turn. */
|
|
50
|
+
export function remoteControlAccountsForReadyPools(config, ready, hostname) {
|
|
51
|
+
const loginHome = remoteControlSessionHome(config, hostname);
|
|
52
|
+
// The forwarder owns the inference credential. Claude itself always uses
|
|
53
|
+
// account a's home, so selection must not require credentials in B/C/D.
|
|
54
|
+
const accounts = Object.fromEntries(Object.entries(config.providers.claude.accounts).map(([id, account]) => [
|
|
55
|
+
id,
|
|
56
|
+
id !== "a" && ready.has(id)
|
|
57
|
+
? { ...account, home: loginHome, hosts: undefined }
|
|
58
|
+
: {
|
|
59
|
+
...account,
|
|
60
|
+
enabled: false,
|
|
61
|
+
disabled_reason: id === "a"
|
|
62
|
+
? "Account a is reserved for Remote Control login"
|
|
63
|
+
: "The local daemon inference pool is unavailable",
|
|
64
|
+
},
|
|
65
|
+
]));
|
|
66
|
+
return {
|
|
67
|
+
...config,
|
|
68
|
+
providers: {
|
|
69
|
+
...config.providers,
|
|
70
|
+
claude: { ...config.providers.claude, accounts },
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Session files stay in account a when the inference pool changes. */
|
|
75
|
+
export function remoteControlSessionHome(config, hostname) {
|
|
76
|
+
const loginAccount = config.providers.claude.accounts.a;
|
|
77
|
+
if (!loginAccount) {
|
|
78
|
+
throw new LanesError("remote_control_login_missing", "Claude account a must be configured for Remote Control login", {});
|
|
79
|
+
}
|
|
80
|
+
return effectiveAccountHome(loginAccount, hostname).home;
|
|
81
|
+
}
|
|
82
|
+
function ownedForwarder(readyFile, accountId) {
|
|
83
|
+
return isOwnedPoolForwarder(readyFile, accountId, (pid) => {
|
|
84
|
+
try {
|
|
85
|
+
process.kill(pid, 0);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}, (pid) => {
|
|
92
|
+
try {
|
|
93
|
+
return fs.readFileSync(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function prepareRemoteControlLane(input) {
|
|
101
|
+
if (process.platform !== "linux") {
|
|
102
|
+
throw new LanesError("remote_control_linux_required", "Claude Remote Control account pools require Linux", {});
|
|
103
|
+
}
|
|
104
|
+
if (input.accountId === "a") {
|
|
105
|
+
throw new LanesError("remote_control_token_account_required", "Account a is reserved for Remote Control login; select b or a later account for tokens", {});
|
|
106
|
+
}
|
|
107
|
+
const accountA = input.config.providers.claude.accounts.a;
|
|
108
|
+
if (!accountA) {
|
|
109
|
+
throw new LanesError("remote_control_login_missing", "Claude account a must be configured for Remote Control login", {});
|
|
110
|
+
}
|
|
111
|
+
const status = accountStatus("claude", "a", input.config, {
|
|
112
|
+
hostname: input.hostname, existsSync: fs.existsSync, probe: defaultProbe, env: input.env,
|
|
113
|
+
});
|
|
114
|
+
if (!status.logged_in) {
|
|
115
|
+
throw new LanesError("remote_control_login_missing", "Claude account a must be logged in for Remote Control", {});
|
|
116
|
+
}
|
|
117
|
+
const home = os.homedir();
|
|
118
|
+
const paths = daemonPaths(home, input.env);
|
|
119
|
+
const pools = readRemoteControlPoolsConfig(paths.remoteControlPoolsPath, Object.keys(input.config.providers.claude.accounts), home);
|
|
120
|
+
const pool = pools.pools.find((entry) => entry.accountId === input.accountId);
|
|
121
|
+
if (!pool || !pools.frontendCaPath || !fs.existsSync(pools.frontendCaPath)) {
|
|
122
|
+
throw new LanesError("remote_control_pool_missing", `The daemon has no ready Remote Control pool for Claude account ${input.accountId}`, { account: input.accountId });
|
|
123
|
+
}
|
|
124
|
+
const readyFile = path.join(paths.root, "remote-control-pools", "forwarder", `${input.accountId}.json`);
|
|
125
|
+
if (!ownedForwarder(readyFile, input.accountId)) {
|
|
126
|
+
throw new LanesError("remote_control_pool_unavailable", `The daemon inference forwarder for Claude account ${input.accountId} is not running`, { account: input.accountId });
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
accountHome: effectiveAccountHome(accountA, input.hostname).home,
|
|
130
|
+
command: process.execPath,
|
|
131
|
+
args: [
|
|
132
|
+
fileURLToPath(import.meta.url),
|
|
133
|
+
"run",
|
|
134
|
+
paths.root,
|
|
135
|
+
input.laneId,
|
|
136
|
+
input.accountId,
|
|
137
|
+
pool.frontendAddress,
|
|
138
|
+
input.envelopePath,
|
|
139
|
+
input.sessionId,
|
|
140
|
+
...input.claudeArgs,
|
|
141
|
+
],
|
|
142
|
+
caPath: pools.frontendCaPath,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function quote(value) {
|
|
146
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
147
|
+
}
|
|
148
|
+
function tmux(socket, args) {
|
|
149
|
+
const result = spawnSync("tmux", ["-L", socket, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
150
|
+
return { status: result.status, stdout: result.stdout || "", stderr: result.stderr || result.error?.message || "" };
|
|
151
|
+
}
|
|
152
|
+
export function remoteControlFailureText(socket, session, capture = tmux) {
|
|
153
|
+
const pane = capture(socket, ["capture-pane", "-p", "-S", "-2000", "-t", session]).stdout.trimEnd();
|
|
154
|
+
const visible = stripVTControlCharacters(pane);
|
|
155
|
+
const failureLine = visible.split("\n").map((line) => line.trim()).reverse().find((line) => /weekly limit|usage limit|rate limit|quota|usage credits|hit your .+ limit|reached your limit|not logged in|please run \/login|authentication failed|invalid api key/i.test(line));
|
|
156
|
+
if (!failureLine)
|
|
157
|
+
return pane;
|
|
158
|
+
const quota = /weekly limit|usage limit|rate limit|quota|usage credits|hit your .+ limit|reached your limit/i.test(failureLine);
|
|
159
|
+
const result = JSON.stringify({ type: "result", is_error: true, api_error_status: quota ? 429 : 401, result: failureLine });
|
|
160
|
+
return `${pane}\n${result}`;
|
|
161
|
+
}
|
|
162
|
+
async function runInside(args) {
|
|
163
|
+
const [daemonRoot, laneId, accountId, _address, envelopePath, sessionId, ...claudeArgs] = args;
|
|
164
|
+
if (!daemonRoot || !laneId || !accountId || !envelopePath || !sessionId || claudeArgs.length === 0)
|
|
165
|
+
return 2;
|
|
166
|
+
const digest = createHash("sha256").update(laneId).digest("hex").slice(0, 18);
|
|
167
|
+
const socket = `hq-lane-${digest}`;
|
|
168
|
+
const session = "lane";
|
|
169
|
+
const exitDir = path.join(daemonRoot, "remote-control-pools", "lanes");
|
|
170
|
+
ensurePrivateDir(exitDir);
|
|
171
|
+
const exitFile = path.join(exitDir, `${digest}.${process.pid}.exit`);
|
|
172
|
+
const readyFile = path.join(daemonRoot, "remote-control-pools", "forwarder", `${accountId}.json`);
|
|
173
|
+
const launcher = fileURLToPath(new URL("../../commands/core-job-remote-launcher.js", import.meta.url));
|
|
174
|
+
const command = [process.execPath, launcher, exitFile, ...claudeArgs].map(quote).join(" ");
|
|
175
|
+
tmux(socket, ["kill-session", "-t", session]);
|
|
176
|
+
const started = tmux(socket, ["new-session", "-d", "-s", session, "-c", process.cwd(), command]);
|
|
177
|
+
if (started.status !== 0) {
|
|
178
|
+
process.stderr.write(`hq lanes: could not start Remote Control terminal: ${started.stderr}\n`);
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
const retained = tmux(socket, ["set-option", "-t", session, "remain-on-exit", "on"]);
|
|
182
|
+
if (retained.status !== 0) {
|
|
183
|
+
tmux(socket, ["kill-session", "-t", session]);
|
|
184
|
+
process.stderr.write(`hq lanes: could not retain Remote Control terminal output: ${retained.stderr}\n`);
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
let stopped = false;
|
|
188
|
+
const stop = () => { stopped = true; tmux(socket, ["kill-session", "-t", session]); };
|
|
189
|
+
process.once("SIGTERM", stop);
|
|
190
|
+
process.once("SIGINT", stop);
|
|
191
|
+
try {
|
|
192
|
+
while (!stopped) {
|
|
193
|
+
if (!ownedForwarder(readyFile, accountId)) {
|
|
194
|
+
process.stderr.write(`hq lanes: daemon inference forwarder for account ${accountId} stopped\n`);
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
if (fs.existsSync(envelopePath)) {
|
|
198
|
+
// The id was passed to Claude; verify the provider really persisted it
|
|
199
|
+
// before reporting it to the lane watcher as a resumable session.
|
|
200
|
+
const transcript = path.join(process.env.CLAUDE_CONFIG_DIR || "", "projects", encodeClaudeProjectDir(process.cwd()), `${sessionId}.jsonl`);
|
|
201
|
+
for (let attempt = 0; attempt < 20 && !fs.existsSync(transcript); attempt++) {
|
|
202
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
203
|
+
}
|
|
204
|
+
if (!fs.existsSync(transcript)) {
|
|
205
|
+
process.stderr.write("hq lanes: Remote Control wrote an envelope but its session transcript is missing\n");
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
process.stdout.write(`${JSON.stringify({ type: "result", session_id: sessionId })}\n`);
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
if (fs.existsSync(exitFile) || tmux(socket, ["has-session", "-t", session]).status !== 0) {
|
|
212
|
+
// The launcher retains the pane briefly after Claude exits. Relay its
|
|
213
|
+
// text so quota and auth rejection classifiers see the provider error.
|
|
214
|
+
const paneText = remoteControlFailureText(socket, session);
|
|
215
|
+
if (paneText.trim())
|
|
216
|
+
process.stderr.write(`${paneText}\n`);
|
|
217
|
+
process.stderr.write("hq lanes: Remote Control terminal exited before the lane envelope was written\n");
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
221
|
+
}
|
|
222
|
+
return 143;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
tmux(socket, ["kill-session", "-t", session]);
|
|
226
|
+
process.off("SIGTERM", stop);
|
|
227
|
+
process.off("SIGINT", stop);
|
|
228
|
+
fs.rmSync(exitFile, { force: true });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function runOuter(args) {
|
|
232
|
+
const [daemonRoot, laneId, accountId, address] = args;
|
|
233
|
+
if (!daemonRoot || !laneId || !accountId || !address)
|
|
234
|
+
return 2;
|
|
235
|
+
const overlayDir = path.join(daemonRoot, "remote-control-pools", "hosts", "lanes");
|
|
236
|
+
ensurePrivateDir(overlayDir);
|
|
237
|
+
const overlay = path.join(overlayDir, `${process.pid}.hosts`);
|
|
238
|
+
fs.writeFileSync(overlay, poolHostsFile(fs.readFileSync("/etc/hosts", "utf8"), {
|
|
239
|
+
frontendAddress: address,
|
|
240
|
+
}), { mode: 0o600 });
|
|
241
|
+
const script = 'set -eu\nmount --make-rprivate /\nmount --bind "$1" /etc/hosts\nshift\nexec "$@"';
|
|
242
|
+
try {
|
|
243
|
+
return await new Promise((resolve) => {
|
|
244
|
+
const child = spawn("unshare", [
|
|
245
|
+
"--map-root-user", "--mount", "--fork", "--kill-child=TERM",
|
|
246
|
+
"sh", "-c", script, "hq-lane-rc", overlay,
|
|
247
|
+
process.execPath, fileURLToPath(import.meta.url), "inside", ...args,
|
|
248
|
+
], { cwd: process.cwd(), env: process.env, stdio: "inherit" });
|
|
249
|
+
let settled = false;
|
|
250
|
+
const forward = () => child.kill("SIGTERM");
|
|
251
|
+
const finish = (code) => {
|
|
252
|
+
if (settled)
|
|
253
|
+
return;
|
|
254
|
+
settled = true;
|
|
255
|
+
process.off("SIGTERM", forward);
|
|
256
|
+
process.off("SIGINT", forward);
|
|
257
|
+
resolve(code);
|
|
258
|
+
};
|
|
259
|
+
process.once("SIGTERM", forward);
|
|
260
|
+
process.once("SIGINT", forward);
|
|
261
|
+
child.once("error", (error) => {
|
|
262
|
+
process.stderr.write(`hq lanes: Remote Control namespace could not start: ${error.message}\n`);
|
|
263
|
+
finish(127);
|
|
264
|
+
});
|
|
265
|
+
child.once("exit", (code) => finish(code ?? 1));
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
fs.rmSync(overlay, { force: true });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
273
|
+
const [mode, ...args] = process.argv.slice(2);
|
|
274
|
+
process.exitCode = mode === "inside" ? await runInside(args) : mode === "run" ? await runOuter(args) : 2;
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=remote-control-lane.js.map
|
|
@@ -245,6 +245,11 @@ export declare function buildProviderCommand(provider: LaneProvider, input: {
|
|
|
245
245
|
* exclusive with `resume`.
|
|
246
246
|
*/
|
|
247
247
|
freshTurnPrompt?: string;
|
|
248
|
+
/** Interactive Claude Remote Control turn with a known provider session id. */
|
|
249
|
+
remoteControl?: {
|
|
250
|
+
sessionId: string;
|
|
251
|
+
name: string;
|
|
252
|
+
};
|
|
248
253
|
}): ProviderLaunch;
|
|
249
254
|
export declare function workerEnv(base: NodeJS.ProcessEnv, resolved: {
|
|
250
255
|
envVar: string;
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -12,6 +12,7 @@ import { randomUUID } from "node:crypto";
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as path from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { configureRemoteControlChildEnv, prepareRemoteControlLane, remoteControlSelectionConfig } from "./remote-control-lane.js";
|
|
15
16
|
import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBalancedAccount, } from "./accounts.js";
|
|
16
17
|
import { effectiveAccountHome } from "./account-home.js";
|
|
17
18
|
import { buildHostCapacitySnapshot } from "./host-capacity.js";
|
|
@@ -694,7 +695,7 @@ export async function retryLaneOnAlternateAccount(hqRoot, lane, reason, writer =
|
|
|
694
695
|
lane.provider_session_id.trim().length > 0
|
|
695
696
|
? lane.provider_session_id
|
|
696
697
|
: undefined;
|
|
697
|
-
if (source && sessionId) {
|
|
698
|
+
if (source && sessionId && lane.remote_control !== true) {
|
|
698
699
|
copySessionFiles(planSessionCopy(lane.provider, {
|
|
699
700
|
sourceHome: lane.provider_home_at_spawn ?? effectiveAccountHome(source, hostname).home,
|
|
700
701
|
targetHome: target.home,
|
|
@@ -1546,7 +1547,28 @@ export function buildProviderCommand(provider, input) {
|
|
|
1546
1547
|
prompt += ` ${input.extraPrompt}`;
|
|
1547
1548
|
}
|
|
1548
1549
|
let args;
|
|
1549
|
-
if (input.
|
|
1550
|
+
if (input.remoteControl && parsed !== "claude") {
|
|
1551
|
+
throw new LanesError("remote_control_requires_claude", "Remote Control requires Claude", { provider: parsed });
|
|
1552
|
+
}
|
|
1553
|
+
if (input.remoteControl && input.resume && input.remoteControl.sessionId !== input.resume.session_id) {
|
|
1554
|
+
throw new LanesError("remote_control_session_mismatch", "Remote Control resume id does not match the recorded session", {});
|
|
1555
|
+
}
|
|
1556
|
+
if (input.remoteControl) {
|
|
1557
|
+
args = [
|
|
1558
|
+
"--remote-control",
|
|
1559
|
+
input.remoteControl.name,
|
|
1560
|
+
...(input.resume
|
|
1561
|
+
? ["--resume", input.resume.session_id]
|
|
1562
|
+
: ["--session-id", input.remoteControl.sessionId]),
|
|
1563
|
+
"--permission-mode",
|
|
1564
|
+
"bypassPermissions",
|
|
1565
|
+
"--dangerously-skip-permissions",
|
|
1566
|
+
input.resume?.prompt ?? prompt,
|
|
1567
|
+
"--disallowedTools",
|
|
1568
|
+
"AskUserQuestion",
|
|
1569
|
+
];
|
|
1570
|
+
}
|
|
1571
|
+
else if (input.resume && parsed === "codex") {
|
|
1550
1572
|
// `codex exec resume` (0.155.1) takes `[SESSION_ID] [PROMPT]` and has
|
|
1551
1573
|
// neither `-C` nor `--color`; the session keeps its recorded cwd. `--json`
|
|
1552
1574
|
// makes the resumed run re-emit `thread.started` with the same id.
|
|
@@ -2182,9 +2204,15 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2182
2204
|
}
|
|
2183
2205
|
: undefined;
|
|
2184
2206
|
const profileChanged = profile !== undefined && existing.worker_profile_hash !== profile.contentHash;
|
|
2185
|
-
const
|
|
2207
|
+
const loadedConfig = loadLanesConfig(hqRoot);
|
|
2208
|
+
const config = existing.remote_control === true
|
|
2209
|
+
? remoteControlSelectionConfig(loadedConfig, env)
|
|
2210
|
+
: loadedConfig;
|
|
2186
2211
|
const repairRecordedAccount = plan.mode === "resume" &&
|
|
2187
2212
|
(trigger.kind === "resume" || pendingResume?.trigger === "resume" || pendingResume?.trigger === "watcher");
|
|
2213
|
+
if (existing.remote_control === true && config.providers.claude.accounts.a === undefined) {
|
|
2214
|
+
throw new LanesError("remote_control_login_missing", "Claude account a must be configured for Remote Control login", {});
|
|
2215
|
+
}
|
|
2188
2216
|
const recordedModel = repairRecordedAccount && existing.model_source === "account"
|
|
2189
2217
|
? existing.model
|
|
2190
2218
|
: undefined;
|
|
@@ -2300,7 +2328,8 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2300
2328
|
const source = existing.account
|
|
2301
2329
|
? config.providers[existing.provider].accounts[existing.account]
|
|
2302
2330
|
: undefined;
|
|
2303
|
-
if (
|
|
2331
|
+
if (existing.remote_control !== true &&
|
|
2332
|
+
source &&
|
|
2304
2333
|
existing.provider_session_id &&
|
|
2305
2334
|
providerSupportsSessionResume(existing.provider)) {
|
|
2306
2335
|
copySessionFiles(planSessionCopy(existing.provider, {
|
|
@@ -2312,11 +2341,14 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2312
2341
|
}
|
|
2313
2342
|
}
|
|
2314
2343
|
if (plan.mode === "resume" && existing.provider !== "grok") {
|
|
2344
|
+
const targetHome = existing.remote_control === true
|
|
2345
|
+
? effectiveAccountHome(config.providers.claude.accounts.a, hostname).home
|
|
2346
|
+
: resolved.home;
|
|
2315
2347
|
ensureSessionHome({
|
|
2316
2348
|
provider: existing.provider,
|
|
2317
2349
|
sessionId: plan.session_id,
|
|
2318
2350
|
cwd: hqRoot,
|
|
2319
|
-
targetHome
|
|
2351
|
+
targetHome,
|
|
2320
2352
|
registeredHomes: Object.values(config.providers[existing.provider].accounts)
|
|
2321
2353
|
.map((account) => effectiveAccountHome(account, hostname).home),
|
|
2322
2354
|
providerHomeAtSpawn: existing.provider_home_at_spawn,
|
|
@@ -2347,8 +2379,11 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2347
2379
|
};
|
|
2348
2380
|
});
|
|
2349
2381
|
}
|
|
2382
|
+
const remoteControlHome = existing.remote_control === true
|
|
2383
|
+
? effectiveAccountHome(config.providers.claude.accounts.a, hostname).home
|
|
2384
|
+
: undefined;
|
|
2350
2385
|
try {
|
|
2351
|
-
installDrainHook(lane.provider, resolved.home);
|
|
2386
|
+
installDrainHook(lane.provider, remoteControlHome ?? resolved.home);
|
|
2352
2387
|
}
|
|
2353
2388
|
catch (err) {
|
|
2354
2389
|
process.stderr.write(`hq lanes: drain hook install failed for ${resolved.home}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -2369,7 +2404,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2369
2404
|
const autoCompactTokens = lane.provider === "claude"
|
|
2370
2405
|
? providerAutoCompactTokens(config.providers.claude)
|
|
2371
2406
|
: undefined;
|
|
2372
|
-
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);
|
|
2407
|
+
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);
|
|
2373
2408
|
const laneTurnId = randomUUID();
|
|
2374
2409
|
childEnv.HQ_LANE_TURN_ID = laneTurnId;
|
|
2375
2410
|
const model = resolveLaneModel({
|
|
@@ -2431,7 +2466,10 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2431
2466
|
}),
|
|
2432
2467
|
}
|
|
2433
2468
|
: undefined;
|
|
2434
|
-
const
|
|
2469
|
+
const remoteSessionId = existing.remote_control === true
|
|
2470
|
+
? plan.mode === "resume" ? plan.session_id : randomUUID()
|
|
2471
|
+
: undefined;
|
|
2472
|
+
const providerCommand = buildProviderCommand(lane.provider, {
|
|
2435
2473
|
hqRoot,
|
|
2436
2474
|
briefPath,
|
|
2437
2475
|
envelopePath,
|
|
@@ -2443,19 +2481,32 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2443
2481
|
profile: profileLaunch,
|
|
2444
2482
|
workerPolicyDir,
|
|
2445
2483
|
resume,
|
|
2484
|
+
...(remoteSessionId ? { remoteControl: { sessionId: remoteSessionId, name: `hq/${lane.lane_id}` } } : {}),
|
|
2446
2485
|
});
|
|
2486
|
+
const remoteLaunch = remoteSessionId
|
|
2487
|
+
? prepareRemoteControlLane({
|
|
2488
|
+
config, hostname, env, accountId: resolved.id, laneId: lane.lane_id,
|
|
2489
|
+
envelopePath, sessionId: remoteSessionId, claudeArgs: providerCommand.args,
|
|
2490
|
+
})
|
|
2491
|
+
: undefined;
|
|
2492
|
+
if (remoteLaunch) {
|
|
2493
|
+
configureRemoteControlChildEnv(childEnv, remoteLaunch);
|
|
2494
|
+
}
|
|
2495
|
+
const command = remoteLaunch ?? providerCommand;
|
|
2447
2496
|
// A systemd PATH mismatch is an environment defect, not a lane failure.
|
|
2448
2497
|
// Keep the lane eligible for a later reconcile after the installer repairs it.
|
|
2449
|
-
|
|
2498
|
+
const missingCommand = [providerCommand.command, ...(remoteLaunch ? ["unshare", "mount", "tmux"] : [])]
|
|
2499
|
+
.find((candidate) => !providerCommandOnPath(candidate, childEnv.PATH));
|
|
2500
|
+
if (missingCommand) {
|
|
2450
2501
|
const searchedPath = childEnv.PATH ?? "";
|
|
2451
2502
|
// Only admission may re-queue. A resume (answered questions, operator
|
|
2452
2503
|
// message) must keep the recorded state so the next reconcile does not
|
|
2453
2504
|
// infer `admit` and start a fresh run in place of the recorded session;
|
|
2454
2505
|
// the caller keeps the message text and sees the named error.
|
|
2455
2506
|
if (trigger.kind !== "admit") {
|
|
2456
|
-
throw new LanesError("provider_binary_missing", `
|
|
2507
|
+
throw new LanesError("provider_binary_missing", `Required binary ${missingCommand} for ${lane.provider} is not executable on PATH ${JSON.stringify(searchedPath)}; lane ${lane.lane_id} keeps state ${existing.state}`, {
|
|
2457
2508
|
provider: lane.provider,
|
|
2458
|
-
command:
|
|
2509
|
+
command: missingCommand,
|
|
2459
2510
|
searched_path: searchedPath,
|
|
2460
2511
|
lane_id: lane.lane_id,
|
|
2461
2512
|
state: existing.state,
|
|
@@ -2470,7 +2521,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2470
2521
|
terminal_reason: undefined,
|
|
2471
2522
|
terminal_details: {
|
|
2472
2523
|
provider: lane.provider,
|
|
2473
|
-
command:
|
|
2524
|
+
command: missingCommand,
|
|
2474
2525
|
searched_path: searchedPath,
|
|
2475
2526
|
},
|
|
2476
2527
|
}));
|
|
@@ -2479,7 +2530,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2479
2530
|
// lane is never `running` without a worker while `/compact` works.
|
|
2480
2531
|
let compaction;
|
|
2481
2532
|
const compactWanted = pendingResume?.compact ?? launch.compact;
|
|
2482
|
-
if (resume && compactWanted !== false) {
|
|
2533
|
+
if (resume && compactWanted !== false && existing.remote_control !== true) {
|
|
2483
2534
|
const nowFn = deps.now ?? (() => new Date());
|
|
2484
2535
|
const decision = compactionDue(existing, nowFn().getTime(), providerCompactAfterMs(config.providers[lane.provider]));
|
|
2485
2536
|
if (decision.due) {
|
|
@@ -2589,7 +2640,7 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2589
2640
|
pending_resume: undefined,
|
|
2590
2641
|
pending_settings: undefined,
|
|
2591
2642
|
account: resolved.id,
|
|
2592
|
-
provider_home_at_spawn: resolved.home,
|
|
2643
|
+
provider_home_at_spawn: remoteControlHome ?? resolved.home,
|
|
2593
2644
|
...(model !== undefined ? { model } : {}),
|
|
2594
2645
|
...(model !== undefined
|
|
2595
2646
|
? {
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -426,6 +426,13 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
426
426
|
const schema_version = requireString(raw.schema_version, "schema_version");
|
|
427
427
|
parseMajorSchema(schema_version, "hq-lane", 1);
|
|
428
428
|
requireString(raw.lane_id, "lane_id");
|
|
429
|
+
if (raw.remote_control !== undefined && (raw.remote_control !== true || raw.provider !== "claude")) {
|
|
430
|
+
throw new LaneSchemaError("remote_control requires a Claude lane", {
|
|
431
|
+
path: filePath,
|
|
432
|
+
field: "remote_control",
|
|
433
|
+
value: raw.remote_control,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
429
436
|
if (raw.written_by_version !== undefined) {
|
|
430
437
|
const writtenByVersion = requireString(raw.written_by_version, "written_by_version");
|
|
431
438
|
if (!semver.valid(writtenByVersion)) {
|
|
@@ -612,6 +612,8 @@ export interface LaneRecord {
|
|
|
612
612
|
/** How policy files were delivered on the most recent profile spawn. */
|
|
613
613
|
worker_policy_delivery?: "hook" | "prompt";
|
|
614
614
|
provider: LaneProvider;
|
|
615
|
+
/** Open each Claude turn as a named Remote Control session using account a's login. */
|
|
616
|
+
remote_control?: boolean;
|
|
615
617
|
/** Whether provider came from an explicit create override or the worker profile/default. */
|
|
616
618
|
provider_source?: "cli" | "profile" | "default";
|
|
617
619
|
account?: string;
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -235,7 +235,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
235
235
|
pickup_validation_pending_since: ["creator", "command", "spawn", "reconcile"],
|
|
236
236
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
237
237
|
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile", "spawn"], keep_alive_history: ["reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile", "spawn"], keep_alive_next_eligible_at: ["command", "reconcile", "spawn"], keep_alive_exhausted_at: ["command", "reconcile", "spawn"], loop_mode: ["creator"],
|
|
238
|
-
provider: ["creator", "command"], provider_source: ["creator", "command"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_source: ["creator", "command"], profile_settings_pinned: ["command"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
238
|
+
provider: ["creator", "command"], remote_control: ["creator"], provider_source: ["creator", "command"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_source: ["creator", "command"], profile_settings_pinned: ["command"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
239
239
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command", "watcher", "reconcile"], story_switch_pending: ["command", "watcher", "reconcile"], story_switch_unacknowledged: ["command", "watcher", "reconcile"],
|
|
240
240
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
241
241
|
last_message_at: ["creator", "command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher", "reconcile"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
@@ -1564,12 +1564,14 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1564
1564
|
let questionIndexes;
|
|
1565
1565
|
let recoveredAsk = false;
|
|
1566
1566
|
const repairCandidate = latestFile ?? missingExpected;
|
|
1567
|
+
const remoteControlLane = readLaneSoft(hqRoot, laneId)?.remote_control === true;
|
|
1567
1568
|
if (!processAlive &&
|
|
1568
1569
|
!envelope &&
|
|
1569
1570
|
!providerRejection &&
|
|
1570
1571
|
!quota &&
|
|
1571
1572
|
repairCandidate &&
|
|
1572
|
-
repairCandidate.envelope === null
|
|
1573
|
+
repairCandidate.envelope === null &&
|
|
1574
|
+
!remoteControlLane) {
|
|
1573
1575
|
const lane = repairCandidate.missing ? readLaneSoft(hqRoot, laneId) : null;
|
|
1574
1576
|
const missingRepairAllowed = !repairCandidate.missing ||
|
|
1575
1577
|
(lane !== null && envelopeRepairMode(lane.provider) === "resume");
|
|
@@ -1648,6 +1650,11 @@ async function runWatcherLoopOwned(hqRoot, laneId, deps) {
|
|
|
1648
1650
|
}
|
|
1649
1651
|
}
|
|
1650
1652
|
}
|
|
1653
|
+
if (!processAlive && !envelope && remoteControlLane && repairCandidate?.envelope === null) {
|
|
1654
|
+
// A headless repair would resume from account a outside the pool's
|
|
1655
|
+
// namespace and could charge that account. Fail the lane instead.
|
|
1656
|
+
invalidEnvelope = { errors: ["Remote Control turn did not write a valid lane envelope"] };
|
|
1657
|
+
}
|
|
1651
1658
|
if (processAlive === false &&
|
|
1652
1659
|
envelope &&
|
|
1653
1660
|
latestFile !== null &&
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* HQ_COGNITO_CALLBACK_PORT — Loopback OAuth callback port
|
|
19
19
|
* HQ_VAULT_API_URL — vault-service API Gateway URL
|
|
20
20
|
*/
|
|
21
|
-
import {
|
|
22
|
-
|
|
21
|
+
import { isExpiring, isMachineIdentity, type CognitoAuthConfig, type CognitoTokens, type ClientInfo, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
|
|
22
|
+
import { type KeychainReadOptions } from "./token-keychain.js";
|
|
23
|
+
export { isExpiring, isMachineIdentity };
|
|
23
24
|
/** Person browser/login cache vs machine-creds USER_PASSWORD_AUTH mint. */
|
|
24
25
|
export type CognitoTokenSource = "person" | "machine";
|
|
25
26
|
/** Actor kind derived from the active token / machine-creds signal. */
|
|
@@ -186,11 +187,18 @@ export declare function formatCognitoSubject(tokens: {
|
|
|
186
187
|
accessToken?: string;
|
|
187
188
|
} | null | undefined): string;
|
|
188
189
|
/**
|
|
189
|
-
* Person
|
|
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
|
|
190
192
|
* in-memory fallback, which can surface a different session when the file
|
|
191
193
|
* is missing or unreadable.
|
|
192
194
|
*/
|
|
193
|
-
export declare function readPersonTokenFile(home?: string, env?: NodeJS.ProcessEnv): CognitoTokens | null;
|
|
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;
|
|
194
202
|
/**
|
|
195
203
|
* Refresh the active person session and refuse a subject change.
|
|
196
204
|
*
|