@indigoai-us/hq-cli 5.275.0 → 5.277.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -0
- package/README.md +12 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +4 -4
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/dist/command-catalog.generated.d.ts +239 -1
- package/dist/command-catalog.generated.js +302 -1
- package/dist/commands/core-checkpoint.d.ts +4 -2
- package/dist/commands/core-checkpoint.js +34 -3
- package/dist/commands/core-command-catalog.d.ts +4 -2
- package/dist/commands/core-command-catalog.js +5 -2
- package/dist/commands/core-delegate.d.ts +6 -0
- package/dist/commands/core-delegate.js +987 -0
- package/dist/commands/core-ontology.js +3 -3
- package/dist/commands/core.js +11 -1
- package/dist/commands/daemon.d.ts +4 -0
- package/dist/commands/daemon.js +175 -2
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +11 -4
- package/dist/commands/feedback.d.ts +6 -0
- package/dist/commands/feedback.js +86 -37
- package/dist/commands/lanes-accounts.js +44 -2
- package/dist/commands/lanes-message.js +69 -12
- package/dist/commands/lanes-resume.js +2 -0
- package/dist/commands/lanes-shared.d.ts +3 -0
- package/dist/commands/lanes-shared.js +12 -2
- package/dist/commands/lanes.d.ts +5 -1
- package/dist/commands/lanes.js +402 -49
- package/dist/commands/run-feature-telemetry.d.ts +3 -0
- package/dist/commands/run-feature-telemetry.js +6 -0
- package/dist/commands/run.js +3 -2
- package/dist/commands/secrets.d.ts +16 -1
- package/dist/commands/secrets.js +80 -45
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/commands/whoami.d.ts +4 -6
- package/dist/commands/whoami.js +39 -8
- package/dist/fast-core.d.ts +6 -7
- package/dist/fast-core.js +22 -8
- package/dist/fast-scaffold.d.ts +33 -7
- package/dist/fast-scaffold.js +104 -8
- package/dist/index.js +32 -1
- package/dist/lanes-drain-hook.js +154 -7
- package/dist/lib/agent-kit/run/inbox.js +3 -2
- package/dist/lib/billing/plan-lock.d.ts +3 -1
- package/dist/lib/billing/plan-lock.js +7 -1
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
- package/dist/lib/checkpoint-ontology/apply.js +60 -3
- package/dist/lib/checkpoint-ontology/company.d.ts +8 -4
- package/dist/lib/checkpoint-ontology/company.js +15 -7
- package/dist/lib/checkpoint-ontology/plan.d.ts +36 -0
- package/dist/lib/checkpoint-ontology/plan.js +66 -4
- package/dist/lib/checkpoint-ontology/queued-touches.d.ts +32 -0
- package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
- package/dist/lib/checkpoint-ontology/tree.js +34 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
- package/dist/lib/core-utils/secret-patterns.js +33 -0
- package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
- package/dist/lib/core-utils/timeout-guard-native.js +40 -0
- package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
- package/dist/lib/core-utils/timeout-guard.js +28 -13
- package/dist/lib/daemon/catalog.d.ts +18 -2
- package/dist/lib/daemon/catalog.js +18 -2
- package/dist/lib/daemon/config.d.ts +6 -0
- package/dist/lib/daemon/config.js +15 -2
- package/dist/lib/daemon/requests.d.ts +4 -0
- package/dist/lib/daemon/requests.js +2 -0
- package/dist/lib/daemon/run.js +94 -8
- package/dist/lib/daemon/supervisor.d.ts +8 -0
- package/dist/lib/daemon/supervisor.js +101 -1
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.d.ts +12 -7
- package/dist/lib/flag-registry.js +66 -75
- package/dist/lib/flag-snapshot-file.d.ts +20 -0
- package/dist/lib/flag-snapshot-file.js +91 -0
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +46 -8
- package/dist/lib/lanes/config.js +15 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +11 -2
- package/dist/lib/lanes/reconcile.js +20 -4
- package/dist/lib/lanes/spawn.js +17 -10
- package/dist/lib/lanes/store.js +45 -1
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -2
- package/dist/lib/process-flag-company-context.d.ts +8 -0
- package/dist/lib/process-flag-company-context.js +67 -0
- package/dist/main.js +3 -1
- package/dist/native-fast-route.d.ts +3 -0
- package/dist/native-fast-route.js +39 -0
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/dist/utils/cli-telemetry.d.ts +26 -2
- package/dist/utils/cli-telemetry.js +178 -3
- package/dist/utils/hq-root-resolver.d.ts +13 -0
- package/dist/utils/hq-root-resolver.js +103 -0
- package/dist/utils/scaffold-fast-import-guard.js +6 -1
- package/dist/utils/vault-api.d.ts +8 -0
- package/dist/utils/vault-api.js +13 -1
- package/package.json +2 -2
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* keep a short-lived, offline CLI alive until the client's network deadline.
|
|
7
7
|
*/
|
|
8
8
|
import { type WorkerOptions } from "node:worker_threads";
|
|
9
|
-
import { type FlagClient, type FlagLookupOptions, type FlagSnapshot } from "@indigoai-us/hq-flags-client";
|
|
9
|
+
import { createFlagClient, type FlagClient, type FlagLookupOptions, type FlagSnapshot } from "@indigoai-us/hq-flags-client";
|
|
10
|
+
export { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
11
|
+
export type { ProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
10
12
|
export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
|
|
11
13
|
/** The synchronous capability that command gate call sites need. */
|
|
12
14
|
export type FlagReader = Pick<FlagClient, "isEnabled">;
|
|
@@ -22,12 +24,13 @@ export interface FlagRegistryDependencies {
|
|
|
22
24
|
workerSource?: string;
|
|
23
25
|
captureWorkerFailure?: (error: Error, tags: Record<string, string>) => void;
|
|
24
26
|
}
|
|
25
|
-
export interface
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
export interface CompanyFlagGateDependencies {
|
|
28
|
+
endpoint?: string;
|
|
29
|
+
requestTimeoutMs?: number;
|
|
30
|
+
createClient?: typeof createFlagClient;
|
|
28
31
|
}
|
|
29
|
-
/**
|
|
30
|
-
export declare function
|
|
32
|
+
/** Create the same snapshot-backed reader used by process gates. */
|
|
33
|
+
export declare function createProcessSnapshotReader(companyIdentifiers: string[], getSnapshot?: () => FlagSnapshot | null, env?: NodeJS.ProcessEnv): FlagReader;
|
|
31
34
|
/**
|
|
32
35
|
* Begin one registry refresh without awaiting it or retaining the CLI process.
|
|
33
36
|
*
|
|
@@ -43,7 +46,9 @@ export declare function kickFlagRegistryReadiness(dependencies?: FlagRegistryDep
|
|
|
43
46
|
* `isEnabled` itself is synchronous and snapshot-only; any reader failure
|
|
44
47
|
* falls through to the exact legacy predicate owned by the caller.
|
|
45
48
|
*/
|
|
46
|
-
export declare function resolveFlagGate(reader: FlagReader | null | undefined, flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean): boolean;
|
|
49
|
+
export declare function resolveFlagGate(reader: FlagReader | null | undefined, flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, env?: NodeJS.ProcessEnv): boolean;
|
|
50
|
+
/** Resolve one explicit company's flag without relying on process-wide binding. */
|
|
51
|
+
export declare function resolveCompanyFlagGate(companyUid: string, idToken: string, flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, dependencies?: CompanyFlagGateDependencies): Promise<boolean>;
|
|
47
52
|
/** Resolve a process gate against the shared snapshot reader, when configured. */
|
|
48
53
|
export declare function resolveProcessFlagGate(flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean): boolean;
|
|
49
54
|
/**
|
|
@@ -7,99 +7,43 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Worker } from "node:worker_threads";
|
|
9
9
|
import * as Sentry from "@sentry/node";
|
|
10
|
-
import { isFlagSnapshot, readLocalOverride, resolveFlag, } from "@indigoai-us/hq-flags-client";
|
|
11
|
-
import { readMachineCompanyBinding } from "./work-context/company.js";
|
|
12
|
-
import { readSessionState } from "./work-context/state.js";
|
|
13
|
-
import { workContextRoot } from "./work-context/paths.js";
|
|
14
|
-
import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
|
|
15
|
-
import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
10
|
+
import { createFlagClient, isFlagSnapshot, readLocalOverride, resolveFlag, } from "@indigoai-us/hq-flags-client";
|
|
16
11
|
import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
|
|
17
12
|
import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
|
|
13
|
+
import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
14
|
+
import { writeFlagSnapshotFile } from "./flag-snapshot-file.js";
|
|
15
|
+
export { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
18
16
|
export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
|
|
19
17
|
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
20
|
-
const COMPANY_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
21
|
-
let heldSnapshot = null;
|
|
22
|
-
let processReader = null;
|
|
23
|
-
function processLocalOverride(flagKey) {
|
|
24
|
-
return readLocalOverride(flagKey, process.env);
|
|
25
|
-
}
|
|
26
|
-
function localOverrideOrFallback(flagKey, legacyFallback) {
|
|
27
|
-
return processLocalOverride(flagKey) ?? legacyFallback();
|
|
28
|
-
}
|
|
29
18
|
function normalizedCompanyUid(value) {
|
|
30
19
|
const candidate = value?.trim() ?? "";
|
|
31
20
|
return COMPANY_UID_RE.test(candidate) ? candidate : undefined;
|
|
32
21
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Resolve only an explicit machine/session company binding. Device defaults
|
|
39
|
-
* and cwd/repository heuristics do not scope process-wide feature flags.
|
|
40
|
-
*/
|
|
41
|
-
function processCompanyContext(env = process.env) {
|
|
42
|
-
const explicitUid = normalizedCompanyUid(env.HQ_COMPANY_UID);
|
|
43
|
-
const explicitSlug = normalizedCompanySlug(env.HQ_COMPANY_SLUG);
|
|
44
|
-
if (explicitUid || explicitSlug) {
|
|
45
|
-
return { companyUid: explicitUid, companySlug: explicitSlug };
|
|
46
|
-
}
|
|
47
|
-
const machine = readMachineCompanyBinding(env);
|
|
48
|
-
const spawnValue = env.HQ_SPAWN_COMPANY?.trim();
|
|
49
|
-
const spawnSlug = normalizedCompanySlug(spawnValue);
|
|
50
|
-
const machineContext = spawnValue && COMPANY_UID_RE.test(spawnValue)
|
|
51
|
-
? { companyUid: spawnValue }
|
|
52
|
-
: spawnSlug
|
|
53
|
-
? { companySlug: spawnSlug }
|
|
54
|
-
: {
|
|
55
|
-
companyUid: normalizedCompanyUid(machine?.uid),
|
|
56
|
-
companySlug: normalizedCompanySlug(machine?.slug),
|
|
57
|
-
};
|
|
58
|
-
if (machineContext.companyUid || machineContext.companySlug)
|
|
59
|
-
return machineContext;
|
|
60
|
-
const sessionId = env.HQ_SESSION_ID?.trim();
|
|
61
|
-
if (!sessionId)
|
|
62
|
-
return {};
|
|
63
|
-
const session = readSessionState(sessionId, workContextRoot(undefined, env));
|
|
64
|
-
if (session?.contextStatus !== "bound" || session.companyConfidence === "device_default") {
|
|
65
|
-
return {};
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
companyUid: normalizedCompanyUid(session.companyUid),
|
|
69
|
-
companySlug: normalizedCompanySlug(session.companySlug),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function companyUidForContext(context, env = process.env) {
|
|
73
|
-
if (context.companyUid)
|
|
74
|
-
return context.companyUid;
|
|
75
|
-
if (!context.companySlug)
|
|
76
|
-
return undefined;
|
|
77
|
-
const hqRoot = resolveDefaultHqRoot({ env });
|
|
78
|
-
const manifestUid = readCompaniesManifestMap(hqRoot)?.[context.companySlug]?.cloud_uid;
|
|
79
|
-
return typeof manifestUid === "string" ? normalizedCompanyUid(manifestUid) : undefined;
|
|
22
|
+
let heldSnapshot = null;
|
|
23
|
+
let processReader = null;
|
|
24
|
+
function processLocalOverride(flagKey, env = process.env) {
|
|
25
|
+
return readLocalOverride(flagKey, env);
|
|
80
26
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const context = processCompanyContext(env);
|
|
84
|
-
const companyUid = companyUidForContext(context, env);
|
|
85
|
-
return {
|
|
86
|
-
...(companyUid ? { companyUid } : {}),
|
|
87
|
-
...(context.companySlug ? { companySlug: context.companySlug } : {}),
|
|
88
|
-
};
|
|
27
|
+
function localOverrideOrFallback(flagKey, legacyFallback, env = process.env) {
|
|
28
|
+
return processLocalOverride(flagKey, env) ?? legacyFallback();
|
|
89
29
|
}
|
|
90
|
-
|
|
30
|
+
/** Create the same snapshot-backed reader used by process gates. */
|
|
31
|
+
export function createProcessSnapshotReader(companyIdentifiers, getSnapshot = () => heldSnapshot, env = process.env) {
|
|
91
32
|
return {
|
|
92
33
|
isEnabled(flagKey, lookup = {}) {
|
|
93
34
|
return resolveFlag({
|
|
94
35
|
flagKey,
|
|
95
|
-
env
|
|
96
|
-
snapshot:
|
|
36
|
+
env,
|
|
37
|
+
snapshot: getSnapshot(),
|
|
97
38
|
companyIdentifiers,
|
|
98
39
|
lookup,
|
|
99
40
|
}).value;
|
|
100
41
|
},
|
|
101
42
|
};
|
|
102
43
|
}
|
|
44
|
+
function processSnapshotReader(companyIdentifiers) {
|
|
45
|
+
return createProcessSnapshotReader(companyIdentifiers);
|
|
46
|
+
}
|
|
103
47
|
/**
|
|
104
48
|
* Begin one registry refresh without awaiting it or retaining the CLI process.
|
|
105
49
|
*
|
|
@@ -150,6 +94,19 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
|
|
|
150
94
|
worker.on("message", (value) => {
|
|
151
95
|
if (isFlagSnapshot(value)) {
|
|
152
96
|
heldSnapshot = value;
|
|
97
|
+
if (companyUid) {
|
|
98
|
+
try {
|
|
99
|
+
writeFlagSnapshotFile(companyUid, value.flags);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
Sentry.captureException(error, {
|
|
103
|
+
tags: {
|
|
104
|
+
component: "flag-snapshot-cache-write",
|
|
105
|
+
company_uid_present: "true",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
153
110
|
}
|
|
154
111
|
});
|
|
155
112
|
// A failed background refresh must stay invisible to an offline CLI.
|
|
@@ -169,15 +126,49 @@ export function kickFlagRegistryReadiness(dependencies = {}) {
|
|
|
169
126
|
* `isEnabled` itself is synchronous and snapshot-only; any reader failure
|
|
170
127
|
* falls through to the exact legacy predicate owned by the caller.
|
|
171
128
|
*/
|
|
172
|
-
export function resolveFlagGate(reader, flagKey, lookup, legacyFallback) {
|
|
129
|
+
export function resolveFlagGate(reader, flagKey, lookup, legacyFallback, env = process.env) {
|
|
173
130
|
if (!reader)
|
|
174
|
-
return localOverrideOrFallback(flagKey, legacyFallback);
|
|
131
|
+
return localOverrideOrFallback(flagKey, legacyFallback, env);
|
|
175
132
|
try {
|
|
176
133
|
return reader.isEnabled(flagKey, lookup);
|
|
177
134
|
}
|
|
178
135
|
catch {
|
|
136
|
+
return localOverrideOrFallback(flagKey, legacyFallback, env);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Resolve one explicit company's flag without relying on process-wide binding. */
|
|
140
|
+
export async function resolveCompanyFlagGate(companyUid, idToken, flagKey, lookup, legacyFallback, dependencies = {}) {
|
|
141
|
+
const localOverride = processLocalOverride(flagKey);
|
|
142
|
+
if (localOverride !== null)
|
|
143
|
+
return localOverride;
|
|
144
|
+
const targetCompanyUid = normalizedCompanyUid(companyUid);
|
|
145
|
+
if (!targetCompanyUid || idToken.trim() === "") {
|
|
179
146
|
return localOverrideOrFallback(flagKey, legacyFallback);
|
|
180
147
|
}
|
|
148
|
+
let client;
|
|
149
|
+
try {
|
|
150
|
+
client = (dependencies.createClient ?? createFlagClient)({
|
|
151
|
+
endpoint: resolveFlagRegistryEndpoint(dependencies.endpoint, process.env),
|
|
152
|
+
getToken: () => idToken,
|
|
153
|
+
companyUid: targetCompanyUid,
|
|
154
|
+
refreshIntervalMs: 0,
|
|
155
|
+
requestTimeoutMs: dependencies.requestTimeoutMs ?? 1_200,
|
|
156
|
+
onError: (error) => {
|
|
157
|
+
const errorClass = error instanceof Error ? error.name : "unknown";
|
|
158
|
+
console.error(`hq: Could not resolve a company flag (${errorClass}); using its fallback.`);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
await client.ready();
|
|
162
|
+
return resolveFlagGate(client, flagKey, lookup, legacyFallback);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const errorClass = error instanceof Error ? error.name : "unknown";
|
|
166
|
+
console.error(`hq: Could not resolve a company flag (${errorClass}); using its fallback.`);
|
|
167
|
+
return localOverrideOrFallback(flagKey, legacyFallback);
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
client?.close();
|
|
171
|
+
}
|
|
181
172
|
}
|
|
182
173
|
/** Resolve a process gate against the shared snapshot reader, when configured. */
|
|
183
174
|
export function resolveProcessFlagGate(flagKey, lookup, legacyFallback) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Private, synchronous, offline snapshot used by the native CLI fast route. */
|
|
2
|
+
export declare const FLAG_SNAPSHOT_MAX_AGE_MS: number;
|
|
3
|
+
export interface PersistedFlagSnapshot {
|
|
4
|
+
company_uid: string;
|
|
5
|
+
fetched_at: string;
|
|
6
|
+
flags: Record<string, boolean>;
|
|
7
|
+
}
|
|
8
|
+
export declare function flagSnapshotPath(companyUid: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
9
|
+
/** Write just the validated flag decisions and company scope, never auth data. */
|
|
10
|
+
export declare function writeFlagSnapshotFile(companyUid: string, flags: Readonly<Record<string, unknown>>, options?: {
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
fetchedAt?: string;
|
|
13
|
+
}): string;
|
|
14
|
+
/** Return null for every miss, malformed file, company mismatch, or stale file. */
|
|
15
|
+
export declare function readFlagSnapshotFile(companyUid: string, options?: {
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
now?: number;
|
|
18
|
+
maxAgeMs?: number;
|
|
19
|
+
}): PersistedFlagSnapshot | null;
|
|
20
|
+
//# sourceMappingURL=flag-snapshot-file.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Private, synchronous, offline snapshot used by the native CLI fast route. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
export const FLAG_SNAPSHOT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
7
|
+
export function flagSnapshotPath(companyUid, env = process.env) {
|
|
8
|
+
if (!COMPANY_UID_RE.test(companyUid))
|
|
9
|
+
return undefined;
|
|
10
|
+
const cacheHome = env.XDG_CACHE_HOME?.trim() || path.join(os.homedir(), ".cache");
|
|
11
|
+
return path.join(cacheHome, "hq", "flags", `snapshot-${companyUid}.json`);
|
|
12
|
+
}
|
|
13
|
+
/** Write just the validated flag decisions and company scope, never auth data. */
|
|
14
|
+
export function writeFlagSnapshotFile(companyUid, flags, options = {}) {
|
|
15
|
+
const file = flagSnapshotPath(companyUid, options.env);
|
|
16
|
+
if (!file)
|
|
17
|
+
throw new Error("Cannot persist a flag snapshot without a valid company uid");
|
|
18
|
+
const normalizedFlags = Object.fromEntries(Object.entries(flags).filter((entry) => Boolean(entry[0].trim()) && typeof entry[1] === "boolean"));
|
|
19
|
+
const snapshot = {
|
|
20
|
+
company_uid: companyUid,
|
|
21
|
+
fetched_at: options.fetchedAt ?? new Date().toISOString(),
|
|
22
|
+
flags: normalizedFlags,
|
|
23
|
+
};
|
|
24
|
+
if (!Number.isFinite(Date.parse(snapshot.fetched_at))) {
|
|
25
|
+
throw new Error("Cannot persist a flag snapshot with an invalid fetched_at timestamp");
|
|
26
|
+
}
|
|
27
|
+
const directory = path.dirname(file);
|
|
28
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
29
|
+
fs.chmodSync(directory, 0o700);
|
|
30
|
+
const temporary = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
31
|
+
let fd;
|
|
32
|
+
try {
|
|
33
|
+
fd = fs.openSync(temporary, "wx", 0o600);
|
|
34
|
+
fs.writeFileSync(fd, `${JSON.stringify(snapshot)}\n`, "utf8");
|
|
35
|
+
fs.fsyncSync(fd);
|
|
36
|
+
fs.closeSync(fd);
|
|
37
|
+
fd = undefined;
|
|
38
|
+
fs.renameSync(temporary, file);
|
|
39
|
+
fs.chmodSync(file, 0o600);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (fd !== undefined)
|
|
43
|
+
fs.closeSync(fd);
|
|
44
|
+
try {
|
|
45
|
+
fs.unlinkSync(temporary);
|
|
46
|
+
}
|
|
47
|
+
catch (cleanupError) {
|
|
48
|
+
if (cleanupError.code !== "ENOENT")
|
|
49
|
+
throw cleanupError;
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
return file;
|
|
54
|
+
}
|
|
55
|
+
/** Return null for every miss, malformed file, company mismatch, or stale file. */
|
|
56
|
+
export function readFlagSnapshotFile(companyUid, options = {}) {
|
|
57
|
+
const file = flagSnapshotPath(companyUid, options.env);
|
|
58
|
+
if (!file)
|
|
59
|
+
return null;
|
|
60
|
+
let parsed;
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
68
|
+
return null;
|
|
69
|
+
const value = parsed;
|
|
70
|
+
if (Object.keys(value).sort().join(",") !== "company_uid,fetched_at,flags" ||
|
|
71
|
+
value.company_uid !== companyUid ||
|
|
72
|
+
typeof value.fetched_at !== "string" ||
|
|
73
|
+
!value.flags || typeof value.flags !== "object" || Array.isArray(value.flags))
|
|
74
|
+
return null; // Expected on missing, malformed, unreadable, or cross-company cache data.
|
|
75
|
+
const fetchedAt = Date.parse(value.fetched_at);
|
|
76
|
+
const age = (options.now ?? Date.now()) - fetchedAt;
|
|
77
|
+
if (!Number.isFinite(fetchedAt) || age < 0 || age > (options.maxAgeMs ?? FLAG_SNAPSHOT_MAX_AGE_MS)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const flags = value.flags;
|
|
81
|
+
for (const enabled of Object.values(flags)) {
|
|
82
|
+
if (typeof enabled !== "boolean")
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
company_uid: companyUid,
|
|
87
|
+
fetched_at: value.fetched_at,
|
|
88
|
+
flags: flags,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=flag-snapshot-file.js.map
|
|
@@ -3,6 +3,7 @@ import type { HostCapacityAccount, HostCapacitySnapshot } from "./host-capacity.
|
|
|
3
3
|
import type { LaneHost, LaneRecord, LanesConfig } from "./types.js";
|
|
4
4
|
export declare const BALANCE_LIVE_STATES: readonly ["running", "queued", "awaiting_input"];
|
|
5
5
|
export declare const BALANCE_MOVABLE_STATES: readonly ["interrupted", "done", "awaiting_input"];
|
|
6
|
+
export declare const CLAUDE_RECENT_USAGE_WINDOW_MS: number;
|
|
6
7
|
export interface BalanceHostSnapshot {
|
|
7
8
|
capacity: HostCapacitySnapshot;
|
|
8
9
|
lanes: readonly LaneRecord[];
|
|
@@ -39,11 +40,12 @@ export interface ClaudeAccountLoad {
|
|
|
39
40
|
live_lanes: number;
|
|
40
41
|
counts_complete: boolean;
|
|
41
42
|
usage: NonNullable<HostCapacityAccount["usage"]> | null;
|
|
43
|
+
recent_usage_at: string | null;
|
|
42
44
|
hosts: AccountHostLoad[];
|
|
43
45
|
}
|
|
44
46
|
export interface ClaudeAccountSelection {
|
|
45
47
|
account: string;
|
|
46
|
-
signal: "usage" | "lane_count" | "explicit";
|
|
48
|
+
signal: "usage" | "recent_usage" | "lane_count" | "explicit";
|
|
47
49
|
reason: "least_loaded" | "explicit";
|
|
48
50
|
target_host: LaneHost;
|
|
49
51
|
considered: ClaudeAccountLoad[];
|
|
@@ -72,6 +74,9 @@ export declare function selectClaudeAccount(input: {
|
|
|
72
74
|
model?: string;
|
|
73
75
|
/** Keep the recorded account eligible for its already-running CLI model. */
|
|
74
76
|
recordedModelAccount?: string;
|
|
77
|
+
/** Recent completed-run tiebreak; callers pass the default-off hq-flags result. */
|
|
78
|
+
recentUsageEnabled?: boolean;
|
|
79
|
+
now?: Date;
|
|
75
80
|
explicitAccount?: string;
|
|
76
81
|
unavailableHosts?: readonly BalanceHostGap[];
|
|
77
82
|
}): ClaudeAccountSelection;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LanesError } from "./errors.js";
|
|
2
2
|
export const BALANCE_LIVE_STATES = ["running", "queued", "awaiting_input"];
|
|
3
3
|
export const BALANCE_MOVABLE_STATES = ["interrupted", "done", "awaiting_input"];
|
|
4
|
+
export const CLAUDE_RECENT_USAGE_WINDOW_MS = 5 * 60 * 60 * 1000;
|
|
4
5
|
function sameHost(a, b) {
|
|
5
6
|
return a.kind === b.kind && a.id === b.id;
|
|
6
7
|
}
|
|
@@ -74,10 +75,31 @@ function usageScore(usage) {
|
|
|
74
75
|
return null;
|
|
75
76
|
return Math.max(usage.five_hour_percent, usage.weekly_percent);
|
|
76
77
|
}
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
function recentUsageAt(hosts, account, now) {
|
|
79
|
+
if (hosts.some((host) => !host.laneListAvailable))
|
|
80
|
+
return null;
|
|
81
|
+
const nowMs = now.getTime();
|
|
82
|
+
const cutoff = nowMs - CLAUDE_RECENT_USAGE_WINDOW_MS;
|
|
83
|
+
let latestAt = null;
|
|
84
|
+
let latestMs = cutoff - 1;
|
|
85
|
+
for (const host of hosts) {
|
|
86
|
+
for (const lane of host.lanes) {
|
|
87
|
+
if (lane.provider !== "claude" || lane.account !== account ||
|
|
88
|
+
lane.last_run_usage === undefined || lane.last_run_usage_at === undefined)
|
|
89
|
+
continue;
|
|
90
|
+
const atMs = Date.parse(lane.last_run_usage_at);
|
|
91
|
+
if (!Number.isFinite(atMs) || atMs < cutoff || atMs > nowMs || atMs <= latestMs)
|
|
92
|
+
continue;
|
|
93
|
+
latestMs = atMs;
|
|
94
|
+
latestAt = lane.last_run_usage_at;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return latestAt;
|
|
98
|
+
}
|
|
99
|
+
function selectionSignal(loads, recentUsageEnabled, laneSnapshotsComplete) {
|
|
100
|
+
if (loads.length > 0 && loads.every((load) => usageScore(load.usage) !== null))
|
|
101
|
+
return "usage";
|
|
102
|
+
return recentUsageEnabled && laneSnapshotsComplete ? "recent_usage" : "lane_count";
|
|
81
103
|
}
|
|
82
104
|
function targetIneligibilityReason(accountId, globalEnabled, allowedAccounts, target) {
|
|
83
105
|
if (!globalEnabled)
|
|
@@ -120,6 +142,7 @@ function buildLoads(input) {
|
|
|
120
142
|
live_lanes: knownCounts.reduce((sum, count) => sum + count, 0),
|
|
121
143
|
counts_complete: hosts.every((host) => host.live_lanes !== null && host.count_source !== "capacity_partial"),
|
|
122
144
|
usage: input.target.capacity.providers.claude.accounts[account]?.usage ?? null,
|
|
145
|
+
recent_usage_at: recentUsageAt(input.hosts, account, input.now),
|
|
123
146
|
hosts,
|
|
124
147
|
};
|
|
125
148
|
});
|
|
@@ -133,7 +156,8 @@ export function selectClaudeAccount(input) {
|
|
|
133
156
|
unavailable_hosts: input.unavailableHosts ?? [],
|
|
134
157
|
});
|
|
135
158
|
}
|
|
136
|
-
const
|
|
159
|
+
const now = input.now ?? new Date();
|
|
160
|
+
const { loads, skipped } = buildLoads({ ...input, target, now });
|
|
137
161
|
if (input.explicitAccount !== undefined) {
|
|
138
162
|
const selected = loads.find((load) => load.account === input.explicitAccount);
|
|
139
163
|
if (!selected || !selected.eligible) {
|
|
@@ -173,14 +197,28 @@ export function selectClaudeAccount(input) {
|
|
|
173
197
|
unavailable_hosts: input.unavailableHosts ?? [],
|
|
174
198
|
});
|
|
175
199
|
}
|
|
176
|
-
const signal = selectionSignal(eligible);
|
|
200
|
+
const signal = selectionSignal(eligible, input.recentUsageEnabled === true, input.hosts.every((host) => host.laneListAvailable) && (input.unavailableHosts?.length ?? 0) === 0);
|
|
177
201
|
eligible.sort((a, b) => {
|
|
178
202
|
if (signal === "usage") {
|
|
179
203
|
const byUsage = usageScore(a.usage) - usageScore(b.usage);
|
|
180
204
|
if (byUsage !== 0)
|
|
181
205
|
return byUsage;
|
|
182
206
|
}
|
|
183
|
-
|
|
207
|
+
const byLiveLanes = a.live_lanes - b.live_lanes;
|
|
208
|
+
if (byLiveLanes !== 0)
|
|
209
|
+
return byLiveLanes;
|
|
210
|
+
if (signal === "recent_usage") {
|
|
211
|
+
if (a.recent_usage_at === null && b.recent_usage_at !== null)
|
|
212
|
+
return -1;
|
|
213
|
+
if (a.recent_usage_at !== null && b.recent_usage_at === null)
|
|
214
|
+
return 1;
|
|
215
|
+
if (a.recent_usage_at !== null && b.recent_usage_at !== null) {
|
|
216
|
+
const byRecentUsage = Date.parse(a.recent_usage_at) - Date.parse(b.recent_usage_at);
|
|
217
|
+
if (byRecentUsage !== 0)
|
|
218
|
+
return byRecentUsage;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return a.account.localeCompare(b.account);
|
|
184
222
|
});
|
|
185
223
|
return {
|
|
186
224
|
account: eligible[0].account,
|
|
@@ -196,7 +234,7 @@ export function accountLoadsForReport(input) {
|
|
|
196
234
|
const first = input.hosts[0];
|
|
197
235
|
if (!first)
|
|
198
236
|
return [];
|
|
199
|
-
return buildLoads({ hosts: input.hosts, target: first, config: input.config }).loads.map((load) => ({
|
|
237
|
+
return buildLoads({ hosts: input.hosts, target: first, config: input.config, now: new Date() }).loads.map((load) => ({
|
|
200
238
|
...load,
|
|
201
239
|
eligible: false,
|
|
202
240
|
ineligible_reason: undefined,
|
package/dist/lib/lanes/config.js
CHANGED
|
@@ -252,6 +252,21 @@ function parsePreferredAccounts(provider, value, accounts) {
|
|
|
252
252
|
return [...preferred];
|
|
253
253
|
}
|
|
254
254
|
export function assertUniqueAccountHomes(provider, accounts) {
|
|
255
|
+
// An override must not let one account reuse another account's shared default home.
|
|
256
|
+
const declaredHomes = new Map();
|
|
257
|
+
for (const [id, account] of Object.entries(accounts)) {
|
|
258
|
+
const homes = new Set([
|
|
259
|
+
account.home,
|
|
260
|
+
...Object.values(account.hosts ?? {}).map(({ home }) => home),
|
|
261
|
+
]);
|
|
262
|
+
for (const home of homes) {
|
|
263
|
+
const prior = declaredHomes.get(home);
|
|
264
|
+
if (prior !== undefined && prior !== id) {
|
|
265
|
+
throw new LanesConfigError("duplicate_account_home", `providers.${provider} accounts ${JSON.stringify(prior)} and ${JSON.stringify(id)} declare the same home ${home}`, { provider, accounts: [prior, id], home });
|
|
266
|
+
}
|
|
267
|
+
declaredHomes.set(home, id);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
255
270
|
const hostnames = new Set(Object.values(accounts).flatMap((account) => Object.keys(account.hosts ?? {})));
|
|
256
271
|
const check = (hostname) => {
|
|
257
272
|
const seen = new Map();
|
|
@@ -18,6 +18,8 @@ export interface OutpostHireGrantContext {
|
|
|
18
18
|
superseded_message_caller_id?: string;
|
|
19
19
|
superseded_message_session_id?: string;
|
|
20
20
|
}
|
|
21
|
+
/** Select the owner records needed to reproduce the primary's grant hierarchy. */
|
|
22
|
+
export declare function selectOutpostHireGrantHierarchy(targetLaneId: string, lanes: readonly LaneRecord[]): LaneRecord[];
|
|
21
23
|
/** Encode only the owner-relevant hierarchy from the primary's complete store. */
|
|
22
24
|
export declare function createOutpostHireGrantContext(target: LaneRecord, lanes: readonly LaneRecord[], supersededMessageCallerId?: string, supersededMessageSessionId?: string): string;
|
|
23
25
|
/** Decode and validate an owner grant context, binding it to the target lane. */
|
|
@@ -15,6 +15,47 @@ function copySenior(senior) {
|
|
|
15
15
|
? { kind: senior.kind }
|
|
16
16
|
: { kind: senior.kind, id: senior.id };
|
|
17
17
|
}
|
|
18
|
+
// Terminal relatives are included so the owner's final state overwrites a stale
|
|
19
|
+
// primary copy; the context builder counts only alive children afterwards.
|
|
20
|
+
function directChildren(lanes, parentLaneId) {
|
|
21
|
+
return lanes.filter((lane) => lane.senior.kind === "lane" && lane.senior.id === parentLaneId);
|
|
22
|
+
}
|
|
23
|
+
/** Select the owner records needed to reproduce the primary's grant hierarchy. */
|
|
24
|
+
export function selectOutpostHireGrantHierarchy(targetLaneId, lanes) {
|
|
25
|
+
const current = lanes.find((lane) => lane.lane_id === targetLaneId);
|
|
26
|
+
if (!current)
|
|
27
|
+
return [];
|
|
28
|
+
const relevant = new Set([current.lane_id]);
|
|
29
|
+
if (current.senior.kind === "lane" && current.senior.id) {
|
|
30
|
+
relevant.add(current.senior.id);
|
|
31
|
+
for (const sibling of directChildren(lanes, current.senior.id)) {
|
|
32
|
+
relevant.add(sibling.lane_id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const child of directChildren(lanes, current.lane_id)) {
|
|
36
|
+
relevant.add(child.lane_id);
|
|
37
|
+
}
|
|
38
|
+
const companyUid = current.company?.uid;
|
|
39
|
+
const projectId = current.project_id;
|
|
40
|
+
if (companyUid && projectId) {
|
|
41
|
+
// A superseded-message caller must be checked against the same project and
|
|
42
|
+
// company rows that the complete hierarchy builder sees.
|
|
43
|
+
for (const candidate of lanes) {
|
|
44
|
+
if (candidate.company?.uid === companyUid && candidate.project_id === projectId) {
|
|
45
|
+
relevant.add(candidate.lane_id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (current.senior.kind === "session" && companyUid) {
|
|
50
|
+
for (const candidate of lanes) {
|
|
51
|
+
if (candidate.company?.uid === companyUid && ((current.worker_profile !== undefined && candidate.worker_profile === current.worker_profile) ||
|
|
52
|
+
(projectId !== undefined && candidate.project_id === projectId && (candidate.hire_grant ?? 0) > 0))) {
|
|
53
|
+
relevant.add(candidate.lane_id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return lanes.filter((lane) => relevant.has(lane.lane_id));
|
|
58
|
+
}
|
|
18
59
|
function snapshot(lane) {
|
|
19
60
|
return {
|
|
20
61
|
lane_id: lane.lane_id,
|
|
@@ -10,7 +10,7 @@ export type { AdmissionCapReason, AdmissionHost } from "./admission.js";
|
|
|
10
10
|
export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
|
|
11
11
|
export type { HostCapacityAccount, HostCapacityProvider, HostCapacitySnapshot, HostPlacementChoice, } from "./host-capacity.js";
|
|
12
12
|
export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STORY_STATUSES, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
|
|
13
|
-
export type { AccountConfig, AttributionHistoryEntry, EnvelopeDecision, EnvelopeQuestion, HostKind, LaneCompanyRef, LaneAccountSelection, LaneAccountSelectionConsidered, LaneAccountSelectionHostCount, LaneEnvelope, LaneHost, LaneProvider, LanePlacementPolicy, LaneRecord, LaneStory, LaneStoryStatus, PendingResume, AccountHistoryEntry, LaneState, LanesConfig, LastEnvelopeRef, LivenessState, LivenessWindows, ProviderConfig, QuestionAnswer, QuestionBodyRef, QuestionRecord, QuestionStatus, QueuedReason, RaisedVia, RoutingHistoryEntry, RoutingReason, SeniorHistoryEntry, SeniorKind, SeniorRef, WorkerProfileRepinHistoryEntry, StoryStatus, TerminalLaneState, ThrottleConfig, } from "./types.js";
|
|
13
|
+
export type { AccountConfig, AttributionHistoryEntry, EnvelopeDecision, EnvelopeQuestion, HostKind, LaneCompanyRef, LaneAccountSelection, LaneAccountSelectionConsidered, LaneAccountSelectionHostCount, LaneEnvelope, LaneHost, LaneProvider, LanePlacementPolicy, LaneRecord, LaneStory, LaneStoryStatus, PendingResume, AccountHistoryEntry, LaneState, LanesConfig, LastEnvelopeRef, LivenessState, LivenessWindows, ProviderConfig, QuestionAnswer, QuestionBodyRef, QuestionRecord, QuestionStatus, QueuedReason, RaisedVia, RoutingHistoryEntry, RoutingReason, SeniorHistoryEntry, SeniorKind, SeniorRef, WorkerProfileRepinHistoryEntry, LaneSettingsHistoryEntry, StoryStatus, TerminalLaneState, ThrottleConfig, } from "./types.js";
|
|
14
14
|
export { ACTIVE_LANE_STORY_STATUSES, activeLaneStories, laneStories, laneStory, storyKey, storyStatusForEnvelope, storyWarning, withCurrentStory, withLaneStories, } from "./stories.js";
|
|
15
15
|
export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
|
|
16
16
|
export type { ConfigLoadDeps, ConfigWriteDeps } from "./config.js";
|
|
@@ -4,6 +4,9 @@ export declare const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
|
4
4
|
/** Missing or unreadable registry values retain the legacy supersession behavior. */
|
|
5
5
|
export declare const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
|
|
6
6
|
export declare const LANE_SUPERSESSION_LINEAGE_AWARE_DEFAULT_ENABLED = false;
|
|
7
|
+
/** Missing or unreadable registry values keep rejecting superseded messages. */
|
|
8
|
+
export declare const LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG = "lanes.running-superseded-message-delivery";
|
|
9
|
+
export declare const LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_DEFAULT_ENABLED = false;
|
|
7
10
|
export declare const LEAD_LANE_KEEP_ALIVE_MAX_RESUMES = 24;
|
|
8
11
|
export declare const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS: number;
|
|
9
12
|
export declare const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS: number;
|
|
@@ -8,6 +8,9 @@ export const LEAD_LANE_KEEP_ALIVE_DEFAULT_ENABLED = false;
|
|
|
8
8
|
/** Missing or unreadable registry values retain the legacy supersession behavior. */
|
|
9
9
|
export const LANE_SUPERSESSION_LINEAGE_AWARE_FLAG = "lanes.supersession-lineage-aware";
|
|
10
10
|
export const LANE_SUPERSESSION_LINEAGE_AWARE_DEFAULT_ENABLED = false;
|
|
11
|
+
/** Missing or unreadable registry values keep rejecting superseded messages. */
|
|
12
|
+
export const LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG = "lanes.running-superseded-message-delivery";
|
|
13
|
+
export const LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_DEFAULT_ENABLED = false;
|
|
11
14
|
export const LEAD_LANE_KEEP_ALIVE_MAX_RESUMES = 24;
|
|
12
15
|
export const LEAD_LANE_KEEP_ALIVE_BASE_BACKOFF_MS = 5 * 60 * 1000;
|
|
13
16
|
export const LEAD_LANE_KEEP_ALIVE_MAX_BACKOFF_MS = 60 * 60 * 1000;
|
|
@@ -82,6 +82,10 @@ export declare function buildOutpostCapacityCommand(): string;
|
|
|
82
82
|
/** Read lane records owned by an Outpost, using its configured HQ binary when set. */
|
|
83
83
|
export declare function buildOutpostLaneListCommand(input?: {
|
|
84
84
|
hqBinary?: string;
|
|
85
|
+
laneIds?: readonly string[];
|
|
86
|
+
active?: boolean;
|
|
87
|
+
hierarchy?: string;
|
|
88
|
+
snapshot?: boolean;
|
|
85
89
|
}): string;
|
|
86
90
|
/** Read questions on one Outpost, scoped to a company when supplied. */
|
|
87
91
|
export declare function buildOutpostQuestionListCommand(input?: {
|
|
@@ -67,12 +67,21 @@ export function buildOutpostCapacityCommand() {
|
|
|
67
67
|
}
|
|
68
68
|
/** Read lane records owned by an Outpost, using its configured HQ binary when set. */
|
|
69
69
|
export function buildOutpostLaneListCommand(input = {}) {
|
|
70
|
-
|
|
70
|
+
const args = [
|
|
71
71
|
input.hqBinary?.trim() || "hq",
|
|
72
72
|
"lanes",
|
|
73
73
|
"list",
|
|
74
74
|
"--json",
|
|
75
|
-
]
|
|
75
|
+
];
|
|
76
|
+
for (const laneId of input.laneIds ?? [])
|
|
77
|
+
args.push("--lane", laneId);
|
|
78
|
+
if (input.active)
|
|
79
|
+
args.push("--active");
|
|
80
|
+
if (input.hierarchy !== undefined)
|
|
81
|
+
args.push("--hierarchy", input.hierarchy);
|
|
82
|
+
if (input.snapshot)
|
|
83
|
+
args.push("--snapshot");
|
|
84
|
+
return withRemoteHqDir(joinCommandParts(args));
|
|
76
85
|
}
|
|
77
86
|
/** Read questions on one Outpost, scoped to a company when supplied. */
|
|
78
87
|
export function buildOutpostQuestionListCommand(input = {}) {
|