@indigoai-us/hq-cli 5.310.0 → 5.311.1
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 +20 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +1 -1
- package/dist/command-catalog.generated.d.ts +2 -2
- package/dist/command-catalog.generated.js +2 -2
- package/dist/commands/channels.d.ts +1 -9
- package/dist/commands/channels.js +2 -2
- package/dist/commands/company-transfer.d.ts +4 -28
- package/dist/commands/company-transfer.js +5 -49
- package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +713 -0
- package/dist/commands/core-checkpoint.d.ts +0 -24
- package/dist/commands/core-checkpoint.js +4 -4
- package/dist/commands/core-command-catalog.js +1 -1
- package/dist/commands/core-delegate-w1b.js +111 -14
- package/dist/commands/core-delegate.d.ts +9 -0
- package/dist/commands/core-delegate.js +44 -23
- package/dist/commands/core-hq-session-native.d.ts +39 -0
- package/dist/commands/core-hq-session-native.js +579 -0
- package/dist/commands/core-jobs-native.js +1 -4
- package/dist/commands/core-native-session-flag.d.ts +4 -0
- package/dist/commands/core-native-session-flag.js +7 -0
- package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
- package/dist/commands/core-native-worker-skills-flag.js +7 -0
- package/dist/commands/core-worker-lint-native.d.ts +4 -0
- package/dist/commands/core-worker-lint-native.js +160 -0
- package/dist/commands/core-worker-share-native.d.ts +5 -0
- package/dist/commands/core-worker-share-native.js +260 -0
- package/dist/commands/core-worker-skills-command.d.ts +8 -0
- package/dist/commands/core-worker-skills-command.js +33 -0
- package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
- package/dist/commands/core-worker-skills-runtime.js +86 -0
- package/dist/commands/core.js +4 -2
- package/dist/commands/dm.d.ts +3 -7
- package/dist/commands/dm.js +3 -3
- package/dist/commands/integrations-core.d.ts +2 -0
- package/dist/commands/integrations-core.js +4 -0
- package/dist/commands/integrations-import.d.ts +1 -1
- package/dist/commands/integrations-import.js +34 -19
- package/dist/commands/integrations.js +2 -2
- package/dist/commands/lanes-accounts.js +3 -1
- package/dist/commands/lanes-message.js +84 -10
- package/dist/commands/lanes-shared.d.ts +4 -1
- package/dist/commands/lanes.d.ts +4 -1
- package/dist/commands/lanes.js +48 -7
- package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
- package/dist/commands/personal-connector-settings-flag.js +7 -0
- package/dist/commands/report.d.ts +1 -26
- package/dist/commands/report.js +6 -6
- package/dist/fast-core.js +15 -2
- package/dist/fast-scaffold.js +25 -2
- package/dist/index.js +8 -0
- package/dist/lib/bot/continuity.d.ts +2 -1
- package/dist/lib/bot/job-completions.d.ts +0 -2
- package/dist/lib/bot/job-completions.js +2 -2
- package/dist/lib/bot/local-config.d.ts +0 -10
- package/dist/lib/bot/local-config.js +3 -9
- package/dist/lib/bot/owner-context.d.ts +2 -6
- package/dist/lib/bot/owner-context.js +3 -3
- package/dist/lib/bot/setup-explain.d.ts +0 -4
- package/dist/lib/bot/setup-explain.js +2 -2
- package/dist/lib/daemon/catalog.d.ts +3 -2
- package/dist/lib/daemon/hq-cloud.d.ts +2 -2
- package/dist/lib/daemon/hq-cloud.js +1 -1
- package/dist/lib/daemon/machine-type.d.ts +3 -4
- package/dist/lib/daemon/machine-type.js +2 -2
- package/dist/lib/daemon/remote-control.d.ts +0 -1
- package/dist/lib/daemon/remote-control.js +1 -1
- package/dist/lib/daemon/supervisor.d.ts +4 -9
- package/dist/lib/daemon/supervisor.js +5 -5
- package/dist/lib/lanes/admission-host.d.ts +3 -0
- package/dist/lib/lanes/admission-host.js +4 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
- package/dist/lib/lanes/autopilot-registry.js +192 -0
- package/dist/lib/lanes/autopilot.d.ts +2 -0
- package/dist/lib/lanes/autopilot.js +18 -5
- package/dist/lib/lanes/claude-balance.d.ts +6 -0
- package/dist/lib/lanes/claude-balance.js +17 -2
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/reconcile.js +14 -6
- package/dist/lib/lanes/spawn.js +2 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +2 -17
- package/dist/lib/mesh/live/daemon/credentials.js +9 -9
- package/dist/lib/mesh/live/daemon/index.d.ts +5 -5
- package/dist/lib/mesh/live/daemon/index.js +2 -2
- package/dist/lib/mesh/live/daemon/state.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +4 -11
- package/dist/lib/mesh/live/daemon/transcript-watch.js +5 -5
- package/dist/lib/work-context/company.d.ts +2 -25
- package/dist/lib/work-context/company.js +3 -3
- package/dist/lib/work-context/config.d.ts +3 -8
- package/dist/lib/work-context/config.js +2 -13
- package/dist/lib/work-context/state.d.ts +4 -13
- package/dist/lib/work-context/state.js +2 -30
- package/dist/utils/run-bundled-script.d.ts +6 -0
- package/dist/utils/run-bundled-script.js +14 -3
- package/package.json +1 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { joinCommandParts, withRemoteHqDir } from "@indigoai-us/hq-cloud/outposts";
|
|
5
|
+
import { isControllerAliasOutpostId } from "./admission-host.js";
|
|
6
|
+
import { LanesError } from "./errors.js";
|
|
7
|
+
export const OUTPOST_LEAD_REGISTRY_SCHEMA = "hq-lanes-outpost-lead-registry.v1";
|
|
8
|
+
export const OUTPOST_LEAD_REGISTRY_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
export const OUTPOST_LEAD_REGISTRY_REFRESH_MS = 6 * 60 * 60 * 1000;
|
|
10
|
+
export const LEAD_REGISTRY_REL = path.join("workspace", "lane-briefs", "juniors.json");
|
|
11
|
+
export const OUTPOST_LEAD_REGISTRY_STATE_REL = path.join("workspace", "lane-briefs", "autopilot-outpost-registry-state.json");
|
|
12
|
+
export const LEAD_AUTOPILOT_FLAG = "lanes.lead-autopilot";
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function parseSourceRegistry(file) {
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
const stat = fs.lstatSync(file);
|
|
20
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
21
|
+
throw new Error("expected a regular file");
|
|
22
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new LanesError("lead_autopilot_registry_unreadable", `Cannot read primary lead registry ${file}: ${error instanceof Error ? error.message : String(error)}`, { path: file });
|
|
26
|
+
}
|
|
27
|
+
// A retired-only registry has no leads, matching the autopilot reader.
|
|
28
|
+
if (isRecord(parsed) && parsed.juniors === undefined && Array.isArray(parsed.retired))
|
|
29
|
+
return [];
|
|
30
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.juniors)) {
|
|
31
|
+
throw new LanesError("lead_autopilot_registry_invalid", `Invalid primary lead registry ${file}: juniors must be an array`, { path: file });
|
|
32
|
+
}
|
|
33
|
+
return parsed.juniors.map((row, index) => {
|
|
34
|
+
if (!isRecord(row) ||
|
|
35
|
+
["team", "lane", "host", "status", "prd"].some((key) => typeof row[key] !== "string" || !row[key].trim())) {
|
|
36
|
+
throw new LanesError("lead_autopilot_registry_invalid", `Invalid primary lead registry ${file}: juniors[${index}] must name team, lane, host, status, and prd`, { path: file, index });
|
|
37
|
+
}
|
|
38
|
+
return row;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function buildOutpostLeadRegistry(input) {
|
|
42
|
+
if (!input.outpostId.trim() || !Number.isFinite(Date.parse(input.generatedAt))) {
|
|
43
|
+
throw new LanesError("lead_autopilot_registry_invalid", "Outpost registry target and generation time must be valid", { outpost_id: input.outpostId });
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
schema_version: OUTPOST_LEAD_REGISTRY_SCHEMA,
|
|
47
|
+
outpost_id: input.outpostId,
|
|
48
|
+
generated_at: new Date(input.generatedAt).toISOString(),
|
|
49
|
+
juniors: input.rows.filter((row) => row.host === input.outpostId),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Stage a host-filtered registry on the Outpost using an atomic local rename. */
|
|
53
|
+
export function buildOutpostLeadRegistryInstallCommand(registry) {
|
|
54
|
+
const encoded = Buffer.from(`${JSON.stringify(registry)}\n`, "utf8").toString("base64");
|
|
55
|
+
const script = [
|
|
56
|
+
'const fs=require("node:fs"),path=require("node:path");',
|
|
57
|
+
'const root=process.cwd(),dir=path.join(root,"workspace","lane-briefs"),file=path.join(dir,"juniors.json");',
|
|
58
|
+
"fs.mkdirSync(dir,{recursive:true});const realRoot=fs.realpathSync(root),realDir=fs.realpathSync(dir);",
|
|
59
|
+
'if(realDir!==realRoot&&!realDir.startsWith(realRoot+path.sep))throw Error("registry directory escapes HQ root");',
|
|
60
|
+
'try{const s=fs.lstatSync(file);if(!s.isFile()||s.isSymbolicLink())throw Error("registry destination must be a regular file")}catch(e){if(e.code!=="ENOENT")throw e}',
|
|
61
|
+
`const bytes=Buffer.from("${encoded}","base64"),tmp=file+"."+process.pid+".tmp";`,
|
|
62
|
+
'fs.writeFileSync(tmp,bytes,{mode:0o600,flag:"wx"});try{fs.renameSync(tmp,file)}catch(e){try{fs.unlinkSync(tmp)}catch{}throw e}',
|
|
63
|
+
].join("");
|
|
64
|
+
return withRemoteHqDir(joinCommandParts(["node", "-e", script]));
|
|
65
|
+
}
|
|
66
|
+
function companySlug(row) {
|
|
67
|
+
const match = /^companies\/([a-z0-9][a-z0-9._-]*)\//i.exec(row.prd);
|
|
68
|
+
return match?.[1] ?? null;
|
|
69
|
+
}
|
|
70
|
+
function readDeliveryState(file) {
|
|
71
|
+
try {
|
|
72
|
+
const stat = fs.lstatSync(file);
|
|
73
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
74
|
+
throw new Error("expected regular state file");
|
|
75
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
76
|
+
if (!isRecord(value) ||
|
|
77
|
+
value.schema_version !== "hq-lanes-outpost-registry-delivery-state.v1" ||
|
|
78
|
+
!isRecord(value.delivered)) {
|
|
79
|
+
throw new Error("invalid delivery state shape");
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code === "ENOENT") {
|
|
85
|
+
return { schema_version: "hq-lanes-outpost-registry-delivery-state.v1", delivered: {} };
|
|
86
|
+
}
|
|
87
|
+
throw new LanesError("lead_autopilot_registry_state_invalid", `Cannot read registry delivery state: ${error instanceof Error ? error.message : String(error)}`, { path: file });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function writeDeliveryState(file, state) {
|
|
91
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
92
|
+
const temp = `${file}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
|
|
93
|
+
try {
|
|
94
|
+
fs.writeFileSync(temp, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
95
|
+
fs.renameSync(temp, file);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
try {
|
|
99
|
+
fs.unlinkSync(temp);
|
|
100
|
+
}
|
|
101
|
+
catch (cleanupError) {
|
|
102
|
+
if (cleanupError.code !== "ENOENT")
|
|
103
|
+
throw cleanupError;
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function syncOutpostLeadRegistries(input) {
|
|
109
|
+
const sourceFile = path.join(input.hqRoot, LEAD_REGISTRY_REL);
|
|
110
|
+
// Most HQ installs have no lead registry. Nothing to deliver is not a reconcile failure.
|
|
111
|
+
if (!fs.existsSync(sourceFile))
|
|
112
|
+
return [];
|
|
113
|
+
const sourceRows = parseSourceRegistry(sourceFile);
|
|
114
|
+
const eligible = [];
|
|
115
|
+
const skipped = [];
|
|
116
|
+
const enabledByCompany = new Map();
|
|
117
|
+
for (const row of sourceRows) {
|
|
118
|
+
const slug = companySlug(row);
|
|
119
|
+
if (!slug) {
|
|
120
|
+
skipped.push({ lane: row.lane, reason: "company_unresolved" });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
let enabled = enabledByCompany.get(slug);
|
|
124
|
+
if (enabled === undefined) {
|
|
125
|
+
const company = await input.resolveCompany(slug);
|
|
126
|
+
enabled = await input.readFlag(LEAD_AUTOPILOT_FLAG, { company });
|
|
127
|
+
enabledByCompany.set(slug, enabled);
|
|
128
|
+
}
|
|
129
|
+
if (!enabled) {
|
|
130
|
+
skipped.push({ lane: row.lane, reason: "flag_off" });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
eligible.push(row);
|
|
134
|
+
}
|
|
135
|
+
const stateFile = path.join(input.hqRoot, OUTPOST_LEAD_REGISTRY_STATE_REL);
|
|
136
|
+
const state = readDeliveryState(stateFile);
|
|
137
|
+
const results = [];
|
|
138
|
+
const targets = [...new Set(input.outpostIds.map((id) => id.trim()))]
|
|
139
|
+
.filter((id) => id && !isControllerAliasOutpostId(id, input.localHost))
|
|
140
|
+
.sort();
|
|
141
|
+
for (const outpostId of targets) {
|
|
142
|
+
const registry = buildOutpostLeadRegistry({ rows: eligible, outpostId, generatedAt: input.generatedAt });
|
|
143
|
+
// Hash only the rows. A new generated_at alone does not trigger a remote write.
|
|
144
|
+
const hash = crypto.createHash("sha256").update(JSON.stringify(registry.juniors)).digest("hex");
|
|
145
|
+
const prior = state.delivered[outpostId];
|
|
146
|
+
const age = prior ? Date.parse(input.generatedAt) - Date.parse(prior.at) : Infinity;
|
|
147
|
+
const shouldDispatch = !prior ||
|
|
148
|
+
prior.hash !== hash ||
|
|
149
|
+
!Number.isFinite(age) ||
|
|
150
|
+
age < 0 ||
|
|
151
|
+
age >= OUTPOST_LEAD_REGISTRY_REFRESH_MS;
|
|
152
|
+
if (shouldDispatch) {
|
|
153
|
+
const result = await input.dispatch({
|
|
154
|
+
outpostId,
|
|
155
|
+
command: buildOutpostLeadRegistryInstallCommand(registry),
|
|
156
|
+
timeoutSeconds: 120,
|
|
157
|
+
waitMs: 120_000,
|
|
158
|
+
});
|
|
159
|
+
if (result.done !== true || result.exitCode !== 0) {
|
|
160
|
+
throw new LanesError("lead_autopilot_registry_sync_failed", `Could not sync lead registry to Outpost ${outpostId}`, { outpost_id: outpostId, exit_code: result.exitCode, done: result.done === true });
|
|
161
|
+
}
|
|
162
|
+
state.delivered[outpostId] = { hash, at: new Date(input.generatedAt).toISOString() };
|
|
163
|
+
writeDeliveryState(stateFile, state);
|
|
164
|
+
}
|
|
165
|
+
results.push({
|
|
166
|
+
outpost_id: outpostId,
|
|
167
|
+
row_count: registry.juniors.length,
|
|
168
|
+
skipped: skipped.filter(({ lane }) => sourceRows.some((row) => row.lane === lane && row.host === outpostId)),
|
|
169
|
+
dispatched: shouldDispatch,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
export function validateOutpostLeadRegistry(input) {
|
|
175
|
+
const value = input.registry;
|
|
176
|
+
if (!isRecord(value) ||
|
|
177
|
+
value.schema_version !== OUTPOST_LEAD_REGISTRY_SCHEMA ||
|
|
178
|
+
value.outpost_id !== input.outpostId ||
|
|
179
|
+
typeof value.generated_at !== "string" ||
|
|
180
|
+
!Array.isArray(value.juniors)) {
|
|
181
|
+
throw new LanesError("lead_autopilot_registry_invalid", `Outpost ${input.outpostId} has an invalid lead registry`, { outpost_id: input.outpostId });
|
|
182
|
+
}
|
|
183
|
+
const age = input.now.getTime() - Date.parse(value.generated_at);
|
|
184
|
+
if (!Number.isFinite(age) || age < 0 || age > OUTPOST_LEAD_REGISTRY_MAX_AGE_MS) {
|
|
185
|
+
throw new LanesError("lead_autopilot_registry_stale", `Outpost ${input.outpostId} lead registry is stale`, { outpost_id: input.outpostId, generated_at: value.generated_at });
|
|
186
|
+
}
|
|
187
|
+
if (value.juniors.some((row) => !isRecord(row) || row.host !== input.outpostId)) {
|
|
188
|
+
throw new LanesError("lead_autopilot_registry_invalid", `Outpost ${input.outpostId} registry contains rows for another host`, { outpost_id: input.outpostId });
|
|
189
|
+
}
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=autopilot-registry.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { laneRunsOnHost } from "./admission.js";
|
|
4
|
+
import { isControllerAliasOutpostId } from "./admission-host.js";
|
|
5
|
+
import { LEAD_REGISTRY_REL, validateOutpostLeadRegistry } from "./autopilot-registry.js";
|
|
4
6
|
import { appendDropboxMessage } from "./dropbox.js";
|
|
5
7
|
import { LanesError } from "./errors.js";
|
|
6
8
|
import { resolveLaneCompany } from "./lane-company.js";
|
|
@@ -37,21 +39,28 @@ function readJsonFile(filePath, code) {
|
|
|
37
39
|
throw new LanesError(code, `Cannot parse ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { path: filePath });
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
function parseRegistry(hqRoot) {
|
|
41
|
-
const file = path.join(hqRoot,
|
|
42
|
+
function parseRegistry(hqRoot, outpostId, now) {
|
|
43
|
+
const file = path.join(hqRoot, LEAD_REGISTRY_REL);
|
|
42
44
|
let stat;
|
|
43
45
|
try {
|
|
44
46
|
stat = fs.lstatSync(file);
|
|
45
47
|
}
|
|
46
48
|
catch (error) {
|
|
47
|
-
if (error.code === "ENOENT")
|
|
49
|
+
if (error.code === "ENOENT") {
|
|
50
|
+
// An Outpost without a delivered registry reports it; it does not fail reconcile.
|
|
51
|
+
// The primary delivers nothing when it has no registry or every company is flagged off.
|
|
52
|
+
if (outpostId !== null && !isControllerAliasOutpostId(outpostId, { kind: "outpost", id: outpostId }))
|
|
53
|
+
return null;
|
|
48
54
|
return [];
|
|
55
|
+
}
|
|
49
56
|
throw new LanesError("lead_autopilot_registry_invalid", `Cannot inspect ${file}: ${error instanceof Error ? error.message : String(error)}`, { path: file });
|
|
50
57
|
}
|
|
51
58
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
52
59
|
throw new LanesError("lead_autopilot_registry_invalid", `Invalid lead registry ${file}: expected a regular file`, { path: file });
|
|
53
60
|
}
|
|
54
61
|
const raw = readJsonFile(file, "lead_autopilot_registry_invalid");
|
|
62
|
+
if (outpostId !== null && !isControllerAliasOutpostId(outpostId, { kind: "outpost", id: outpostId }))
|
|
63
|
+
return validateOutpostLeadRegistry({ registry: raw, outpostId, now }).juniors;
|
|
55
64
|
if (isRecord(raw) && raw.juniors === undefined && Array.isArray(raw.retired))
|
|
56
65
|
return [];
|
|
57
66
|
if (!isRecord(raw) || !Array.isArray(raw.juniors)) {
|
|
@@ -407,8 +416,12 @@ function recordOperatorEscalation(hqRoot, record) {
|
|
|
407
416
|
}
|
|
408
417
|
async function runLeadAutopilotPassUnlocked(input) {
|
|
409
418
|
const { hqRoot, host, now } = input;
|
|
419
|
+
const effectiveOutpostId = input.outpostId !== null && isControllerAliasOutpostId(input.outpostId, { kind: "outpost", id: input.outpostId }) ? null : input.outpostId;
|
|
410
420
|
const result = { ok: true, host, evaluated: 0, sent: [], started: [], escalated: [], skipped: [] };
|
|
411
|
-
const
|
|
421
|
+
const registered = parseRegistry(hqRoot, input.outpostId, now);
|
|
422
|
+
if (registered === null)
|
|
423
|
+
result.registry = "missing";
|
|
424
|
+
const leads = registered ?? [];
|
|
412
425
|
const lanes = readAllLanes(hqRoot);
|
|
413
426
|
const byId = new Map(lanes.map((lane) => [lane.lane_id, lane]));
|
|
414
427
|
const stateFile = path.join(hqRoot, "workspace", "lane-briefs", "autopilot-state.json");
|
|
@@ -429,7 +442,7 @@ async function runLeadAutopilotPassUnlocked(input) {
|
|
|
429
442
|
? lead.host.kind === "local"
|
|
430
443
|
: lead.host.kind === "outpost" && lead.host.id === registered.host && input.outpostId === registered.host;
|
|
431
444
|
if (!hostMatchesRegistry ||
|
|
432
|
-
!laneRunsOnHost(lead, { hostname: input.hostname, outpostId:
|
|
445
|
+
!laneRunsOnHost(lead, { hostname: input.hostname, outpostId: effectiveOutpostId })) {
|
|
433
446
|
result.skipped.push({ lead_lane: lead.lane_id, reason: "lead_host_mismatch" });
|
|
434
447
|
continue;
|
|
435
448
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AuthState } from "./accounts.js";
|
|
2
2
|
import type { HostCapacityAccount, HostCapacitySnapshot } from "./host-capacity.js";
|
|
3
3
|
import type { LaneHost, LaneRecord, LanesConfig } from "./types.js";
|
|
4
|
+
import { type QuotaState } from "./quota.js";
|
|
4
5
|
export declare const BALANCE_LIVE_STATES: readonly ["running", "queued", "awaiting_input"];
|
|
5
6
|
export declare const BALANCE_MOVABLE_STATES: readonly ["interrupted", "done", "awaiting_input"];
|
|
6
7
|
export declare const CLAUDE_RECENT_USAGE_WINDOW_MS: number;
|
|
@@ -37,6 +38,9 @@ export interface ClaudeAccountLoad {
|
|
|
37
38
|
target_logged_in: boolean | null;
|
|
38
39
|
eligible: boolean;
|
|
39
40
|
ineligible_reason?: string;
|
|
41
|
+
quota_reset_at?: string;
|
|
42
|
+
quota_marker?: string;
|
|
43
|
+
quota_model?: string;
|
|
40
44
|
live_lanes: number;
|
|
41
45
|
counts_complete: boolean;
|
|
42
46
|
usage: NonNullable<HostCapacityAccount["usage"]> | null;
|
|
@@ -74,6 +78,7 @@ export declare function selectClaudeAccount(input: {
|
|
|
74
78
|
model?: string;
|
|
75
79
|
/** Keep the recorded account eligible for its already-running CLI model. */
|
|
76
80
|
recordedModelAccount?: string;
|
|
81
|
+
quotaState?: QuotaState;
|
|
77
82
|
/** Recent completed-run tiebreak; callers pass the default-off hq-flags result. */
|
|
78
83
|
recentUsageEnabled?: boolean;
|
|
79
84
|
now?: Date;
|
|
@@ -83,6 +88,7 @@ export declare function selectClaudeAccount(input: {
|
|
|
83
88
|
export declare function accountLoadsForReport(input: {
|
|
84
89
|
hosts: readonly BalanceHostSnapshot[];
|
|
85
90
|
config: LanesConfig;
|
|
91
|
+
quotaState?: QuotaState;
|
|
86
92
|
}): ClaudeAccountLoad[];
|
|
87
93
|
export declare function planClaudeBalanceMoves(input: {
|
|
88
94
|
hosts: readonly BalanceHostSnapshot[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LanesError } from "./errors.js";
|
|
2
|
+
import { quotaBlockFor } from "./quota.js";
|
|
2
3
|
export const BALANCE_LIVE_STATES = ["running", "queued", "awaiting_input"];
|
|
3
4
|
export const BALANCE_MOVABLE_STATES = ["interrupted", "done", "awaiting_input"];
|
|
4
5
|
export const CLAUDE_RECENT_USAGE_WINDOW_MS = 5 * 60 * 60 * 1000;
|
|
@@ -126,10 +127,16 @@ function buildLoads(input) {
|
|
|
126
127
|
const hosts = input.hosts.map((host) => accountHostLoad(host, account));
|
|
127
128
|
const targetLoad = hosts.find((host) => sameHost(host.host, targetHost));
|
|
128
129
|
const configuredModel = configAccounts[account]?.model;
|
|
130
|
+
const quotaModel = input.model ?? configuredModel ?? input.config.providers.claude.model;
|
|
131
|
+
const quotaBlock = input.quotaState === undefined
|
|
132
|
+
? undefined
|
|
133
|
+
: quotaBlockFor(input.quotaState, "claude", account, quotaModel, input.now);
|
|
129
134
|
const ineligible = targetIneligibilityReason(account, configAccounts[account]?.enabled === true, input.allowedAccounts, targetLoad) ?? (account !== input.recordedModelAccount &&
|
|
130
135
|
input.model !== undefined && configuredModel !== undefined && configuredModel !== input.model
|
|
131
136
|
? "model_mismatch"
|
|
132
|
-
:
|
|
137
|
+
: quotaBlock?.blocked === true
|
|
138
|
+
? "quota_held"
|
|
139
|
+
: undefined);
|
|
133
140
|
if (ineligible !== undefined)
|
|
134
141
|
skipped.push({ account, host: targetHost, reason: ineligible });
|
|
135
142
|
const knownCounts = hosts.flatMap((host) => host.live_lanes === null ? [] : [host.live_lanes]);
|
|
@@ -139,6 +146,11 @@ function buildLoads(input) {
|
|
|
139
146
|
target_logged_in: targetLoad.logged_in,
|
|
140
147
|
eligible: ineligible === undefined,
|
|
141
148
|
...(ineligible !== undefined ? { ineligible_reason: ineligible } : {}),
|
|
149
|
+
...(quotaBlock?.blocked ? {
|
|
150
|
+
...(quotaBlock.quota_reset_at !== undefined ? { quota_reset_at: quotaBlock.quota_reset_at } : {}),
|
|
151
|
+
...(quotaBlock.marker !== undefined ? { quota_marker: quotaBlock.marker } : {}),
|
|
152
|
+
...(quotaBlock.model !== undefined ? { quota_model: quotaBlock.model } : {}),
|
|
153
|
+
} : {}),
|
|
142
154
|
live_lanes: knownCounts.reduce((sum, count) => sum + count, 0),
|
|
143
155
|
counts_complete: hosts.every((host) => host.live_lanes !== null && host.count_source !== "capacity_partial"),
|
|
144
156
|
usage: input.target.capacity.providers.claude.accounts[account]?.usage ?? null,
|
|
@@ -193,6 +205,9 @@ export function selectClaudeAccount(input) {
|
|
|
193
205
|
account_reasons: loads.map((load) => ({
|
|
194
206
|
account: load.account,
|
|
195
207
|
reason: load.ineligible_reason ?? "unavailable",
|
|
208
|
+
...(load.quota_reset_at !== undefined ? { quota_reset_at: load.quota_reset_at } : {}),
|
|
209
|
+
...(load.quota_marker !== undefined ? { marker: load.quota_marker } : {}),
|
|
210
|
+
...(load.quota_model !== undefined ? { model: load.quota_model } : {}),
|
|
196
211
|
})),
|
|
197
212
|
unavailable_hosts: input.unavailableHosts ?? [],
|
|
198
213
|
});
|
|
@@ -234,7 +249,7 @@ export function accountLoadsForReport(input) {
|
|
|
234
249
|
const first = input.hosts[0];
|
|
235
250
|
if (!first)
|
|
236
251
|
return [];
|
|
237
|
-
return buildLoads({ hosts: input.hosts, target: first, config: input.config, now: new Date() }).loads.map((load) => ({
|
|
252
|
+
return buildLoads({ hosts: input.hosts, target: first, config: input.config, quotaState: input.quotaState, now: new Date() }).loads.map((load) => ({
|
|
238
253
|
...load,
|
|
239
254
|
eligible: false,
|
|
240
255
|
ineligible_reason: undefined,
|
|
@@ -6,6 +6,9 @@ export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
|
|
|
6
6
|
export declare const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS: number;
|
|
7
7
|
export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
|
|
8
8
|
export declare const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
|
|
9
|
+
export declare const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG = "lanes.keep-alive-immediate-first-resume";
|
|
10
|
+
export declare const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_DEFAULT_ENABLED = false;
|
|
11
|
+
export declare const LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS = 30000;
|
|
9
12
|
export declare const LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS: number;
|
|
10
13
|
/** Missing or unreadable registry values retain the legacy supersession behavior. */
|
|
11
14
|
export declare const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
|
|
@@ -11,6 +11,9 @@ export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_DEFAULT_ENABLED = false;
|
|
|
11
11
|
export const LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_MAX_DELAY_MS = 24 * 60 * 60 * 1000;
|
|
12
12
|
export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG = "lanes.keep-alive-resume-blocked";
|
|
13
13
|
export const LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_DEFAULT_ENABLED = false;
|
|
14
|
+
export const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG = "lanes.keep-alive-immediate-first-resume";
|
|
15
|
+
export const LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_DEFAULT_ENABLED = false;
|
|
16
|
+
export const LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS = 30_000;
|
|
14
17
|
export const LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS = 15 * 60 * 1000;
|
|
15
18
|
/** Missing or unreadable registry values retain the legacy supersession behavior. */
|
|
16
19
|
export const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
|
|
@@ -25,7 +25,7 @@ import { readLanesRegistryFlag } from "./registry-flag.js";
|
|
|
25
25
|
import { resolveLaneCompany } from "./lane-company.js";
|
|
26
26
|
import { appendDropboxMessage } from "./dropbox.js";
|
|
27
27
|
import { DEFAULT_RUNAWAY_SEARCH_GUARD_CONFIG, listHostProcesses, runRunawayToolGuard, RUNAWAY_TOOL_GUARD_FLAG, } from "./runaway-tool-guard.js";
|
|
28
|
-
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
28
|
+
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_GRACE_MS, LEAD_LANE_KEEP_ALIVE_BLOCKED_RESUME_TEXT, queueLaneTerminalResume, LEAD_LANE_KEEP_ALIVE_INTERRUPTED_RESUME_TEXT, LEAD_LANE_KEEP_ALIVE_MAX_RESUMES, LEAD_LANE_KEEP_ALIVE_RESUME_TEXT, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, loadRetiredLaneIds, leadLaneKeepAliveBackoffMs, undeliveredAnsweredQuestions, } from "./keep-alive.js";
|
|
29
29
|
import { providerSupportsSessionResume } from "./delivery.js";
|
|
30
30
|
import { applyDefaultQuotaHold, clearAccountQuotaAfterSuccessfulTurnBestEffort, readQuotaSignal, recordAccountQuota, } from "./quota.js";
|
|
31
31
|
import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
@@ -1119,6 +1119,7 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
1119
1119
|
const keepAliveFlagByCompany = new Map();
|
|
1120
1120
|
const nextCheckAtFlagByCompany = new Map();
|
|
1121
1121
|
const blockedResumeFlagByCompany = new Map();
|
|
1122
|
+
const immediateFirstResumeFlagByCompany = new Map();
|
|
1122
1123
|
const supersessionLineageAwareByCompany = new Map();
|
|
1123
1124
|
await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
|
|
1124
1125
|
const options = {
|
|
@@ -1129,7 +1130,7 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
1129
1130
|
lane.keep_alive === true &&
|
|
1130
1131
|
laneRunsHere(lane, startDeps.host) &&
|
|
1131
1132
|
keepAliveCompanyKey(resolveLaneCompany(hqRoot, lane).company) === key);
|
|
1132
|
-
const [keepAliveEnabled, nextCheckAtEnabled, lineageAware, blockedResumeEnabled] = await Promise.all([
|
|
1133
|
+
const [keepAliveEnabled, nextCheckAtEnabled, lineageAware, blockedResumeEnabled, immediateFirstResumeEnabled] = await Promise.all([
|
|
1133
1134
|
readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, options),
|
|
1134
1135
|
nextCheckAtFlagCompanyKeys.has(key)
|
|
1135
1136
|
? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, options)
|
|
@@ -1138,10 +1139,12 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
1138
1139
|
hasBlockedKeepAliveLane
|
|
1139
1140
|
? readRegistryFlag(LEAD_LANE_KEEP_ALIVE_RESUME_BLOCKED_FLAG, options)
|
|
1140
1141
|
: Promise.resolve(false),
|
|
1142
|
+
readRegistryFlag(LEAD_LANE_KEEP_ALIVE_IMMEDIATE_FIRST_RESUME_FLAG, options),
|
|
1141
1143
|
]);
|
|
1142
1144
|
keepAliveFlagByCompany.set(key, keepAliveEnabled);
|
|
1143
1145
|
nextCheckAtFlagByCompany.set(key, nextCheckAtEnabled);
|
|
1144
1146
|
blockedResumeFlagByCompany.set(key, blockedResumeEnabled);
|
|
1147
|
+
immediateFirstResumeFlagByCompany.set(key, immediateFirstResumeEnabled);
|
|
1145
1148
|
supersessionLineageAwareByCompany.set(key, lineageAware);
|
|
1146
1149
|
}));
|
|
1147
1150
|
const admissionThrottleDeps = startDeps.throttleDeps ?? defaultThrottleDeps(startDeps.env ?? process.env);
|
|
@@ -1162,7 +1165,7 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
1162
1165
|
const freshLanes = loadLaneRecords(hqRoot, summary);
|
|
1163
1166
|
const freshQuestions = loadQuestionRecords(hqRoot, summary);
|
|
1164
1167
|
await closeInterruptedDoneStoryWorkers(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, storyCloseEnabledByCompany);
|
|
1165
|
-
await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany);
|
|
1168
|
+
await resumeKeepAlive(hqRoot, freshLanes, freshQuestions, summary, admissionDeps, now, keepAliveFlagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany, immediateFirstResumeFlagByCompany);
|
|
1166
1169
|
const drainLanes = loadLaneRecords(hqRoot, summary);
|
|
1167
1170
|
await admitQueued(hqRoot, drainLanes, summary, admissionDeps, now);
|
|
1168
1171
|
await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, admissionDeps);
|
|
@@ -2241,7 +2244,7 @@ function appendBlockedKeepAliveHistory(hqRoot, lane, terminalAt, at, reason, dep
|
|
|
2241
2244
|
}),
|
|
2242
2245
|
}), false);
|
|
2243
2246
|
}
|
|
2244
|
-
async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany) {
|
|
2247
|
+
async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, flagByCompany, supersessionLineageAwareByCompany, blockedResumeFlagByCompany, nextCheckAtFlagByCompany, immediateFirstResumeFlagByCompany) {
|
|
2245
2248
|
const nowMs = now.getTime();
|
|
2246
2249
|
const nowIso = now.toISOString();
|
|
2247
2250
|
const config = loadLanesConfig(hqRoot);
|
|
@@ -2634,7 +2637,12 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
2634
2637
|
const terminalAtMs = initialState === "done"
|
|
2635
2638
|
? Math.min(Date.parse(current.updated_at), currentEligibilityAtMs)
|
|
2636
2639
|
: currentEligibilityAtMs;
|
|
2637
|
-
const
|
|
2640
|
+
const immediateFirstResumeEnabled = initialState === "done" &&
|
|
2641
|
+
immediateFirstResumeFlagByCompany.get(keepAliveCompanyKey(companyForLane(current))) === true;
|
|
2642
|
+
const attemptDelayMs = immediateFirstResumeEnabled && attempt === 1
|
|
2643
|
+
? LEAD_LANE_KEEP_ALIVE_FIRST_RESUME_SETTLE_MS
|
|
2644
|
+
: leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? Math.max(1, attempt - 1) : attempt);
|
|
2645
|
+
const attemptEligibleMs = terminalAtMs + attemptDelayMs;
|
|
2638
2646
|
const storedNextMs = resetByHuman
|
|
2639
2647
|
? Number.NaN
|
|
2640
2648
|
: Date.parse(current.keep_alive_next_eligible_at ?? "");
|
|
@@ -2653,7 +2661,7 @@ async function resumeKeepAlive(hqRoot, lanes, questions, summary, deps, now, fla
|
|
|
2653
2661
|
recordKeepAliveSkipEvent(hqRoot, skipLane, "backoff", skipLane.version, summary, deps, new Date(nextEligibleMs).toISOString());
|
|
2654
2662
|
continue;
|
|
2655
2663
|
}
|
|
2656
|
-
const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(attempt + 1)).toISOString();
|
|
2664
|
+
const nextEligibleAt = new Date(nowMs + leadLaneKeepAliveBackoffMs(immediateFirstResumeEnabled ? attempt : attempt + 1)).toISOString();
|
|
2657
2665
|
const queuedCount = loadLaneRecords(hqRoot, summary).filter((queuedLane) => queuedLane.state === "queued" &&
|
|
2658
2666
|
queuedLane.provider === current.provider &&
|
|
2659
2667
|
laneRunsHere(queuedLane, deps.host) &&
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -16,6 +16,7 @@ import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBa
|
|
|
16
16
|
import { effectiveAccountHome } from "./account-home.js";
|
|
17
17
|
import { buildHostCapacitySnapshot } from "./host-capacity.js";
|
|
18
18
|
import { selectClaudeAccount } from "./claude-balance.js";
|
|
19
|
+
import { loadQuotaState } from "./quota.js";
|
|
19
20
|
import { COMPACTION_PROMPT, compactionAfterUsage, compactionDue, compactLaneSession, } from "./compaction.js";
|
|
20
21
|
import { installDrainHook } from "./hooks-install.js";
|
|
21
22
|
import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } from "./config.js";
|
|
@@ -269,6 +270,7 @@ function selectSpawnAccount(input) {
|
|
|
269
270
|
hosts: [{ capacity, lanes, laneListAvailable: true }],
|
|
270
271
|
targetHost: lane.host,
|
|
271
272
|
config,
|
|
273
|
+
quotaState: loadQuotaState(hqRoot),
|
|
272
274
|
...((recordedModel ?? pinnedModel ?? profileModel) !== undefined
|
|
273
275
|
? { model: recordedModel ?? pinnedModel ?? profileModel }
|
|
274
276
|
: {}),
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
* Never logs or persists credential material.
|
|
4
4
|
*/
|
|
5
5
|
import type { IotCredentials } from "./presign.js";
|
|
6
|
-
export declare const REALTIME_CREDENTIALS_PATH = "/v1/realtime/credentials";
|
|
7
6
|
export declare const MQTT_KEEPALIVE_SECONDS = 15;
|
|
8
|
-
/** Renew at 80% of credential lifetime. */
|
|
9
|
-
export declare const CREDENTIAL_RENEWAL_FRACTION = 0.8;
|
|
10
|
-
export declare const MIN_RENEWAL_DELAY_MS = 5000;
|
|
11
|
-
/** Long retry when the server refuses credentials (disabled / unsupported). */
|
|
12
|
-
export declare const DEFAULT_REFUSED_RETRY_MS: number;
|
|
13
|
-
export declare const REFUSED_RETRY_ENV = "HQ_MESH_DAEMON_REFUSED_RETRY_MS";
|
|
14
7
|
/**
|
|
15
8
|
* Node's setTimeout treats delays above this as ~1ms (signed 32-bit overflow).
|
|
16
9
|
* Always schedule long waits via {@link scheduleBoundedTimeout}.
|
|
@@ -18,10 +11,6 @@ export declare const REFUSED_RETRY_ENV = "HQ_MESH_DAEMON_REFUSED_RETRY_MS";
|
|
|
18
11
|
export declare const MAX_TIMER_DELAY_MS = 2147483647;
|
|
19
12
|
/** Cap any single retry so a bogus Retry-After cannot park the daemon. */
|
|
20
13
|
export declare const MAX_RETRY_DELAY_MS: number;
|
|
21
|
-
/** Contract-3 vend accepts at most this many preferred company uids. */
|
|
22
|
-
export declare const PREFERRED_COMPANY_LIMIT = 5;
|
|
23
|
-
/** Session files older than this do not count as companies in use. */
|
|
24
|
-
export declare const PREFERRED_SESSION_WINDOW_MS: number;
|
|
25
14
|
/** Preferred-set changes re-vend at most this often. */
|
|
26
15
|
export declare const PREFERRED_REVEND_MIN_MS = 60000;
|
|
27
16
|
export type CredentialVendFailureKind = "refused" | "transient";
|
|
@@ -44,14 +33,13 @@ export declare class CredentialVendError extends Error {
|
|
|
44
33
|
});
|
|
45
34
|
}
|
|
46
35
|
export declare function parseRetryAfterMs(value: string | null | undefined, nowMs?: number): number | undefined;
|
|
47
|
-
export declare function extractVendErrorCode(body: unknown): string | undefined;
|
|
48
36
|
/** True for server "not now" responses that should use the long retry. */
|
|
49
37
|
export declare function isCredentialVendRefused(status: number, code: string | undefined): boolean;
|
|
50
38
|
export declare function classifyCredentialVendFailure(status: number, body: unknown, retryAfterHeader?: string | null, nowMs?: number): CredentialVendError;
|
|
51
39
|
export declare function defaultRefusedRetryMs(env?: NodeJS.ProcessEnv): number;
|
|
52
40
|
/** ±20% jitter around the refused-retry base interval. */
|
|
53
41
|
export declare function refusedRetryDelayMs(baseMs: number, random: () => number): number;
|
|
54
|
-
|
|
42
|
+
interface PresenceCompany {
|
|
55
43
|
companyUid: string;
|
|
56
44
|
presenceTopic: string;
|
|
57
45
|
}
|
|
@@ -124,10 +112,6 @@ export declare function shouldRevendPreferredCompanies(input: {
|
|
|
124
112
|
nowMs: number;
|
|
125
113
|
minIntervalMs?: number;
|
|
126
114
|
}): boolean;
|
|
127
|
-
export declare function contract3VendBody(preferredCompanyUids: readonly string[] | undefined): {
|
|
128
|
-
contractVersion: 3;
|
|
129
|
-
preferredCompanyUids?: string[];
|
|
130
|
-
};
|
|
131
115
|
/** Plain doctor line for one company the vend left out. */
|
|
132
116
|
export declare function formatDroppedPresenceCompany(company: DroppedPresenceCompany): string;
|
|
133
117
|
export type CredentialVendPostResult = {
|
|
@@ -200,4 +184,5 @@ export declare function createPersonalRealtimeFetcher(opts: {
|
|
|
200
184
|
baseUrl?: string;
|
|
201
185
|
post?: (path: string, body: unknown) => Promise<CredentialVendPostResult>;
|
|
202
186
|
}): PersonalCredentialsFetcher;
|
|
187
|
+
export {};
|
|
203
188
|
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -9,14 +9,14 @@ import { vaultApiFetch } from "../../../../utils/vault-api.js";
|
|
|
9
9
|
import { readAgentIdentityCompanyUid } from "../../../work-context/company.js";
|
|
10
10
|
import { readDeviceConfig } from "../../../work-context/config.js";
|
|
11
11
|
import { workContextSessionsDir } from "../../../work-context/paths.js";
|
|
12
|
-
|
|
12
|
+
const REALTIME_CREDENTIALS_PATH = "/v1/realtime/credentials";
|
|
13
13
|
export const MQTT_KEEPALIVE_SECONDS = 15;
|
|
14
14
|
/** Renew at 80% of credential lifetime. */
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const CREDENTIAL_RENEWAL_FRACTION = 0.8;
|
|
16
|
+
const MIN_RENEWAL_DELAY_MS = 5_000;
|
|
17
17
|
/** Long retry when the server refuses credentials (disabled / unsupported). */
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const DEFAULT_REFUSED_RETRY_MS = 10 * 60_000;
|
|
19
|
+
const REFUSED_RETRY_ENV = "HQ_MESH_DAEMON_REFUSED_RETRY_MS";
|
|
20
20
|
/**
|
|
21
21
|
* Node's setTimeout treats delays above this as ~1ms (signed 32-bit overflow).
|
|
22
22
|
* Always schedule long waits via {@link scheduleBoundedTimeout}.
|
|
@@ -25,9 +25,9 @@ export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
|
25
25
|
/** Cap any single retry so a bogus Retry-After cannot park the daemon. */
|
|
26
26
|
export const MAX_RETRY_DELAY_MS = 24 * 60 * 60 * 1000;
|
|
27
27
|
/** Contract-3 vend accepts at most this many preferred company uids. */
|
|
28
|
-
|
|
28
|
+
const PREFERRED_COMPANY_LIMIT = 5;
|
|
29
29
|
/** Session files older than this do not count as companies in use. */
|
|
30
|
-
|
|
30
|
+
const PREFERRED_SESSION_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
31
31
|
/** Preferred-set changes re-vend at most this often. */
|
|
32
32
|
export const PREFERRED_REVEND_MIN_MS = 60_000;
|
|
33
33
|
/**
|
|
@@ -66,7 +66,7 @@ export function parseRetryAfterMs(value, nowMs = Date.now()) {
|
|
|
66
66
|
}
|
|
67
67
|
return undefined;
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
function extractVendErrorCode(body) {
|
|
70
70
|
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
71
71
|
return undefined;
|
|
72
72
|
const code = body.code;
|
|
@@ -370,7 +370,7 @@ export function shouldRevendPreferredCompanies(input) {
|
|
|
370
370
|
const min = input.minIntervalMs ?? PREFERRED_REVEND_MIN_MS;
|
|
371
371
|
return input.nowMs - input.lastVendMs >= min;
|
|
372
372
|
}
|
|
373
|
-
|
|
373
|
+
function contract3VendBody(preferredCompanyUids) {
|
|
374
374
|
const body = {
|
|
375
375
|
contractVersion: 3,
|
|
376
376
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, WINDOWS_TASK_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
|
|
2
2
|
export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
|
|
3
3
|
export type { PidLockDeps, PidLockRecord, PidLockResult } from "./pid-lock.js";
|
|
4
|
-
export {
|
|
5
|
-
export type { BoundedTimeoutHandle, Contract3Bundle, CredentialRenewalErrorInfo, CredentialVendFailureKind, CredentialVendPostResult, CredentialsFetcher,
|
|
4
|
+
export { MAX_RETRY_DELAY_MS, MAX_TIMER_DELAY_MS, MQTT_KEEPALIVE_SECONDS, CredentialRenewalManager, CredentialVendError, classifyCredentialVendFailure, clampRetryDelayMs, createContract3Fetcher, defaultRefusedRetryMs, isCredentialVendRefused, normalizeContract3Bundle, parseRetryAfterMs, realTimerHost, refusedRetryDelayMs, renewalDelayMs, scheduleBoundedTimeout, } from "./credentials.js";
|
|
5
|
+
export type { BoundedTimeoutHandle, Contract3Bundle, CredentialRenewalErrorInfo, CredentialVendFailureKind, CredentialVendPostResult, CredentialsFetcher, TimerHost, } from "./credentials.js";
|
|
6
6
|
export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
|
|
7
7
|
export type { IotCredentials } from "./presign.js";
|
|
8
8
|
export { CLOSE_LOG_ALWAYS_COUNT, CLOSE_LOG_INTERVAL_MS, CONNECT_STABLE_GRACE_MS, PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
|
|
9
9
|
export type { MeshMqttClientLike, MqttConnectFn, MqttConnectionState, PresenceClientOptions, PresencePayload, PresenceRefusal, } from "./presence.js";
|
|
10
10
|
export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
|
|
11
|
-
export type { DaemonStateFile
|
|
11
|
+
export type { DaemonStateFile } from "./state.js";
|
|
12
12
|
export { appendDaemonLog, daemonLogLine, ensureDaemonLog, resolveDaemonAssetDir, rotateDaemonLogIfNeeded, } from "./log.js";
|
|
13
13
|
export { BOARD_REFRESH_INTERVAL_MS, createVaultBoardReader, formatBoardMarkdown, refreshBoundSessionBoards, writeBoardMarkdown, } from "./board-refresh.js";
|
|
14
14
|
export type { BoardReader, BoardRefreshDeps, BoardStorySnapshot } from "./board-refresh.js";
|
|
@@ -22,6 +22,6 @@ export type { DaemonDoctorDeps, DaemonDoctorReport } from "./doctor.js";
|
|
|
22
22
|
export { scopeBundleToAgentIdentity, } from "./credentials.js";
|
|
23
23
|
export { FLUSH_INTERVAL_MS, DAEMON_VERSION_DRIFT_EXIT_CODE, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
|
|
24
24
|
export type { DaemonHandle, DaemonRunDeps } from "./run.js";
|
|
25
|
-
export {
|
|
26
|
-
export type { DiscoveredTranscript,
|
|
25
|
+
export { TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_HOOK_QUIET_ADOPT_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_BACKOFF_DURATION_MS, TRANSCRIPT_WATCH_BACKOFF_ENTRIES, TRANSCRIPT_WATCH_INTERVAL_MAX_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TRANSCRIPT_WATCH_REVALIDATE_MS, TranscriptWatcher, createTranscriptDiscoverCache, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, discoverTranscriptsWithMeta, isTranscriptHookCovered, nextTranscriptWatchIntervalMs, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, sortDiscoveries, } from "./transcript-watch.js";
|
|
26
|
+
export type { DiscoveredTranscript, DiscoverTranscriptsResult, TranscriptDiscoverCache, TranscriptFs, TranscriptWatchDeps, TranscriptWatchTickResult, } from "./transcript-watch.js";
|
|
27
27
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { DAEMON_DIRNAME, DAEMON_LOG_MAX_BYTES, DAEMON_LOG_NAME, DAEMON_PID_NAME, DAEMON_STATE_NAME, LAUNCHD_LABEL, SYSTEMD_UNIT_NAME, WINDOWS_TASK_NAME, daemonDir, daemonLogPath, daemonPidPath, daemonStatePath, sessionBoardPath, } from "./paths.js";
|
|
2
2
|
export { acquirePidLock, defaultPidLockDeps, parsePidLock, pidLockStatus, readPidLock, releasePidLock, resolveDaemonDir, } from "./pid-lock.js";
|
|
3
|
-
export {
|
|
3
|
+
export { MAX_RETRY_DELAY_MS, MAX_TIMER_DELAY_MS, MQTT_KEEPALIVE_SECONDS, CredentialRenewalManager, CredentialVendError, classifyCredentialVendFailure, clampRetryDelayMs, createContract3Fetcher, defaultRefusedRetryMs, isCredentialVendRefused, normalizeContract3Bundle, parseRetryAfterMs, realTimerHost, refusedRetryDelayMs, renewalDelayMs, scheduleBoundedTimeout, } from "./credentials.js";
|
|
4
4
|
export { amzDateOf, hex, presignIotWssUrl, rfc3986Encode } from "./presign.js";
|
|
5
5
|
export { CLOSE_LOG_ALWAYS_COUNT, CLOSE_LOG_INTERVAL_MS, CONNECT_STABLE_GRACE_MS, PresenceClient, backoffDelayMs, buildPresencePayload, defaultMqttConnect, isOwnPresenceTopic, } from "./presence.js";
|
|
6
6
|
export { defaultDaemonState, patchDaemonState, readDaemonState, writeDaemonState, } from "./state.js";
|
|
@@ -11,5 +11,5 @@ export { daemonConfigPath, findAncestorHqRoot, isInstallableHqRoot, readDaemonCo
|
|
|
11
11
|
export { UNHEALTHY_SPOOL_AGE_MS, collectDaemonDoctor, formatCliIdentityLines, formatCliMismatchLine, formatDaemonDoctor, listHqBinariesOnPath, } from "./doctor.js";
|
|
12
12
|
export { scopeBundleToAgentIdentity, } from "./credentials.js";
|
|
13
13
|
export { FLUSH_INTERVAL_MS, DAEMON_VERSION_DRIFT_EXIT_CODE, SPOOL_DEBOUNCE_MS, runMeshDaemon, } from "./run.js";
|
|
14
|
-
export {
|
|
14
|
+
export { TRANSCRIPT_EVENT_SOURCE, TRANSCRIPT_HOOK_COVER_MS, TRANSCRIPT_MARKER, TRANSCRIPT_SESSION_END_MS, TRANSCRIPT_HOOK_QUIET_ADOPT_MS, TRANSCRIPT_TURN_QUIET_MS, TRANSCRIPT_WATCH_BACKOFF_DURATION_MS, TRANSCRIPT_WATCH_BACKOFF_ENTRIES, TRANSCRIPT_WATCH_INTERVAL_MAX_MS, TRANSCRIPT_WATCH_INTERVAL_MS, TRANSCRIPT_WATCH_REVALIDATE_MS, TranscriptWatcher, createTranscriptDiscoverCache, decodeClaudeProjectDirName, defaultTranscriptFs, discoverTranscripts, discoverTranscriptsWithMeta, isTranscriptHookCovered, nextTranscriptWatchIntervalMs, noteHookSessionsFromSpoolFile, resolveTranscriptRegistration, sessionIdFromClaudeTranscriptPath, sessionIdFromCodexTranscriptPath, sortDiscoveries, } from "./transcript-watch.js";
|
|
15
15
|
//# sourceMappingURL=index.js.map
|