@indigoai-us/hq-cli 5.345.176 → 5.345.178
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 +34 -0
- package/dist/command-catalog.generated.d.ts +8 -0
- package/dist/command-catalog.generated.js +10 -0
- package/dist/commands/bot.d.ts +5 -0
- package/dist/commands/bot.js +186 -21
- package/dist/commands/cloud.d.ts +49 -0
- package/dist/commands/cloud.js +155 -15
- package/dist/commands/dm-attach.js +15 -3
- package/dist/commands/library-add.d.ts +9 -0
- package/dist/commands/library-add.js +19 -2
- package/dist/commands/run.js +9 -1
- package/dist/lib/bot/config.d.ts +2 -0
- package/dist/lib/bot/fleet-default-flag.d.ts +31 -0
- package/dist/lib/bot/fleet-default-flag.js +127 -0
- package/dist/lib/bot/fleet-local.d.ts +29 -0
- package/dist/lib/bot/fleet-local.js +85 -10
- package/dist/lib/bot/index.d.ts +1 -0
- package/dist/lib/bot/index.js +1 -0
- package/dist/lib/bot/local-config.js +1 -1
- package/dist/lib/daemon/catalog.d.ts +2 -0
- package/dist/lib/daemon/catalog.js +7 -2
- package/dist/lib/daemon/config.d.ts +2 -1
- package/dist/lib/daemon/config.js +4 -3
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -0
- package/dist/lib/mesh/live/daemon/work-subscribe.js +26 -6
- package/dist/lib/sync-core/release.d.ts +1 -1
- package/dist/lib/sync-core/release.js +2 -2
- package/dist/run/hq-plugin.d.ts +3 -0
- package/dist/run/hq-plugin.js +18 -0
- package/package.json +7 -7
|
@@ -40,6 +40,7 @@ export interface FleetLocalProfile {
|
|
|
40
40
|
baseUrl?: string;
|
|
41
41
|
transport?: string;
|
|
42
42
|
};
|
|
43
|
+
ownerCompanies?: string[];
|
|
43
44
|
hqRoot: string;
|
|
44
45
|
profileDir: string;
|
|
45
46
|
runtime: {
|
|
@@ -69,7 +70,27 @@ export interface FleetLocalProfileInput {
|
|
|
69
70
|
installation: FleetLocalInstallation;
|
|
70
71
|
/** The bot's standing HQ instructions; written as the runtime's SOUL.md persona. */
|
|
71
72
|
systemPrompt?: string;
|
|
73
|
+
/** Which model login the bot answers with; ChatGPT when omitted. */
|
|
74
|
+
provider?: FleetLocalProfile["provider"];
|
|
75
|
+
/** The hello and first task the runtime sends and runs once, on its first start. */
|
|
76
|
+
firstStart?: {
|
|
77
|
+
intro?: string;
|
|
78
|
+
kickoff?: string;
|
|
79
|
+
};
|
|
80
|
+
/** Companies the owner's own turns may use; other senders get none of them. */
|
|
81
|
+
ownerCompanies?: string[];
|
|
72
82
|
}
|
|
83
|
+
/** The model the HQ Fleet Runtime asks for when a Claude bot names none. */
|
|
84
|
+
export declare const FLEET_CLAUDE_DEFAULT_MODEL = "claude-opus-5-5";
|
|
85
|
+
/**
|
|
86
|
+
* The runtime provider for a bot created from a model CLI choice. Claude uses
|
|
87
|
+
* the runtime's native Anthropic provider, which reads the owner's Claude Code
|
|
88
|
+
* login, so it needs no sign-in of its own; ChatGPT and Grok sign in with
|
|
89
|
+
* `hq bot auth`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function fleetProviderForRuntime(runtime: string, model?: string): FleetLocalProfile["provider"];
|
|
92
|
+
/** True when a bot with this provider still needs `hq bot auth` before it can answer. */
|
|
93
|
+
export declare function fleetProviderNeedsSignIn(provider: FleetLocalProfile["provider"]): boolean;
|
|
73
94
|
type FleetLocalExec = (executable: string, args: string[], options: Record<string, unknown>) => Buffer | string;
|
|
74
95
|
export interface FleetLocalBootstrapDeps {
|
|
75
96
|
fleetHome?: string;
|
|
@@ -91,6 +112,7 @@ export interface FleetLocalControlDeps {
|
|
|
91
112
|
run?: FleetLocalExec;
|
|
92
113
|
}
|
|
93
114
|
export interface FleetLocalAuthInput {
|
|
115
|
+
installService?: boolean;
|
|
94
116
|
agentUid: string;
|
|
95
117
|
provider: string;
|
|
96
118
|
model?: string;
|
|
@@ -128,6 +150,11 @@ export declare function fleetLocalPromotionRuntime(agentUid: string, deps?: Flee
|
|
|
128
150
|
export declare function removeFleetLocalBot(agentUid: string, deps?: FleetLocalControlDeps): FleetLocalRemovalResult;
|
|
129
151
|
export declare function authenticateFleetLocalBot(input: FleetLocalAuthInput, deps?: FleetLocalControlDeps): FleetLocalAuthResult;
|
|
130
152
|
export declare function writeFleetLocalProfile(input: FleetLocalProfileInput, deps?: Pick<FleetLocalBootstrapDeps, "fleetHome" | "platform">): string;
|
|
153
|
+
/** The runtime's inbox plugin sends the intro and runs the kickoff once, then stamps this file. */
|
|
154
|
+
export declare function writeFleetLocalFirstStart(profileDir: string, firstStart: {
|
|
155
|
+
intro?: string;
|
|
156
|
+
kickoff?: string;
|
|
157
|
+
}): string;
|
|
131
158
|
export declare function writeFleetLocalSoul(agentUid: string, systemPrompt: string, deps?: Pick<FleetLocalControlDeps, "fleetHome">): string;
|
|
132
159
|
/**
|
|
133
160
|
* The pinned runtime refuses a persona file that trips its context-file
|
|
@@ -137,6 +164,8 @@ export declare function writeFleetLocalSoul(agentUid: string, systemPrompt: stri
|
|
|
137
164
|
export declare function scanFleetLocalInstructions(agentUid: string, text: string, deps?: FleetLocalControlDeps): string[];
|
|
138
165
|
/** Checks the instructions with the runtime's scanner, then makes them the bot's persona. */
|
|
139
166
|
export declare function refreshFleetLocalInstructions(agentUid: string, systemPrompt: string, deps?: FleetLocalControlDeps): string;
|
|
167
|
+
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
168
|
+
export declare function importFleetLocalMachineCredential(agentUid: string, machineCredentials: string, deps?: FleetLocalControlDeps): void;
|
|
140
169
|
export declare function bootstrapFleetLocalBot(input: FleetLocalProfileInput & {
|
|
141
170
|
machineCredentials: string;
|
|
142
171
|
}, deps?: FleetLocalBootstrapDeps): Promise<FleetLocalBootstrapResult>;
|
|
@@ -7,16 +7,42 @@ import { HQ_FLEET_RUNTIME_ENGINE_ID, } from "./config.js";
|
|
|
7
7
|
import { ensureFleetPython, fleetUvEnv } from "./fleet-python.js";
|
|
8
8
|
import { ensureCognitoToken } from "../../utils/cognito-session.js";
|
|
9
9
|
import { vaultApiFetch } from "../../utils/vault-api.js";
|
|
10
|
+
/** The model the HQ Fleet Runtime asks for when a Claude bot names none. */
|
|
11
|
+
export const FLEET_CLAUDE_DEFAULT_MODEL = "claude-opus-5-5";
|
|
12
|
+
/**
|
|
13
|
+
* The runtime provider for a bot created from a model CLI choice. Claude uses
|
|
14
|
+
* the runtime's native Anthropic provider, which reads the owner's Claude Code
|
|
15
|
+
* login, so it needs no sign-in of its own; ChatGPT and Grok sign in with
|
|
16
|
+
* `hq bot auth`.
|
|
17
|
+
*/
|
|
18
|
+
export function fleetProviderForRuntime(runtime, model) {
|
|
19
|
+
if (runtime === "claude") {
|
|
20
|
+
const requested = model?.trim() ?? "";
|
|
21
|
+
return { id: "anthropic", model: /^claude-[a-z0-9.-]+$/.test(requested) ? requested : FLEET_CLAUDE_DEFAULT_MODEL };
|
|
22
|
+
}
|
|
23
|
+
const preset = providerProfiles[runtime === "grok" ? "grok" : "chatgpt"];
|
|
24
|
+
return {
|
|
25
|
+
id: preset.provider,
|
|
26
|
+
model: model?.trim() || preset.model,
|
|
27
|
+
credentialProvider: preset.runtimeProvider,
|
|
28
|
+
baseUrl: preset.baseUrl,
|
|
29
|
+
transport: preset.transport,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** True when a bot with this provider still needs `hq bot auth` before it can answer. */
|
|
33
|
+
export function fleetProviderNeedsSignIn(provider) {
|
|
34
|
+
return provider.id !== "anthropic";
|
|
35
|
+
}
|
|
10
36
|
export const HQ_FLEET_LOCAL_RELEASE = {
|
|
11
37
|
repository: "indigoai-us/hq-fleet-local",
|
|
12
|
-
tag: "v0.4.
|
|
13
|
-
version: "0.4.
|
|
14
|
-
artifact: "hq-fleet-local-0.4.
|
|
15
|
-
artifactSha256: "
|
|
38
|
+
tag: "v0.4.12",
|
|
39
|
+
version: "0.4.12",
|
|
40
|
+
artifact: "hq-fleet-local-0.4.12.zip",
|
|
41
|
+
artifactSha256: "2a07e79b1740e8ef68b19078b73b10cdf9377065a2fd1f53287b7e353099c577",
|
|
16
42
|
runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
|
|
17
43
|
runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
|
|
18
|
-
manifestSha256: "
|
|
19
|
-
signatureSha256: "
|
|
44
|
+
manifestSha256: "cdac590bdcebea6629de0c164c42c7baf30c025a299ff5829380989dd22a9ff1",
|
|
45
|
+
signatureSha256: "f07911dd3187393cd325fc8f88ddaa6a7568825aee6d8748a992cea822d5d9b1",
|
|
20
46
|
runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
|
|
21
47
|
};
|
|
22
48
|
export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
@@ -417,7 +443,9 @@ function replaceProfileProvider(profilePath, agentUid, provider) {
|
|
|
417
443
|
const credentialProviders = Array.isArray(profile.credentialProviders)
|
|
418
444
|
? profile.credentialProviders.filter((value) => typeof value === "string")
|
|
419
445
|
: [];
|
|
420
|
-
profile.credentialProviders = [
|
|
446
|
+
profile.credentialProviders = [
|
|
447
|
+
...new Set([...credentialProviders, ...(provider.credentialProvider ? [provider.credentialProvider] : [])]),
|
|
448
|
+
];
|
|
421
449
|
const temporary = `${profilePath}.${process.pid}.${crypto.randomUUID()}`;
|
|
422
450
|
fs.writeFileSync(temporary, `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
|
|
423
451
|
fs.renameSync(temporary, profilePath);
|
|
@@ -498,6 +526,16 @@ export function authenticateFleetLocalBot(input, deps = {}) {
|
|
|
498
526
|
if (!/^[a-z0-9][a-z0-9._:-]*$/.test(requested)) {
|
|
499
527
|
throw expectedError("HQ Fleet provider identifier is invalid.");
|
|
500
528
|
}
|
|
529
|
+
if ((requested === "claude" || requested === "anthropic") && !input.apiKey) {
|
|
530
|
+
const files = fleetLocalExecutables(input.agentUid, deps);
|
|
531
|
+
const run = deps.run ?? execFileSync;
|
|
532
|
+
const native = fleetProviderForRuntime("claude", input.model);
|
|
533
|
+
replaceProfileProvider(files.profile, input.agentUid, native);
|
|
534
|
+
runJson(run, files.helper, ["render-profile", "--spec", files.profile, "--mode", "update"], { stdio: ["ignore", "pipe", "pipe"] }, "profile update");
|
|
535
|
+
runJson(run, files.helper, ["doctor-profile", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "profile validation");
|
|
536
|
+
const restarted = controlFleetLocalBot(input.agentUid, input.installService ? "install" : "restart", deps).ok === true;
|
|
537
|
+
return { provider: requested, runtimeProvider: "anthropic", model: native.model, authenticated: true, restarted };
|
|
538
|
+
}
|
|
501
539
|
const known = providerProfiles[requested];
|
|
502
540
|
const provider = known ?? {
|
|
503
541
|
provider: `custom:personal-${requested}`,
|
|
@@ -558,7 +596,7 @@ export function authenticateFleetLocalBot(input, deps = {}) {
|
|
|
558
596
|
});
|
|
559
597
|
runJson(run, files.helper, ["render-profile", "--spec", files.profile, "--mode", "update"], { stdio: ["ignore", "pipe", "pipe"] }, "profile update");
|
|
560
598
|
runJson(run, files.helper, ["doctor-profile", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "profile validation");
|
|
561
|
-
const restarted = controlFleetLocalBot(input.agentUid, "restart", deps).ok === true;
|
|
599
|
+
const restarted = controlFleetLocalBot(input.agentUid, input.installService ? "install" : "restart", deps).ok === true;
|
|
562
600
|
return {
|
|
563
601
|
provider: requested,
|
|
564
602
|
runtimeProvider: provider.runtimeProvider,
|
|
@@ -583,7 +621,8 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
583
621
|
schemaVersion: 1,
|
|
584
622
|
botId: input.agentUid,
|
|
585
623
|
ownerUid: input.ownerUid,
|
|
586
|
-
|
|
624
|
+
...(input.ownerCompanies?.length ? { ownerCompanies: [...new Set(input.ownerCompanies)].sort() } : {}),
|
|
625
|
+
provider: input.provider ?? { id: "custom:personal-chatgpt", model: "gpt-5.6-sol" },
|
|
587
626
|
hqRoot: path.resolve(input.hqRoot),
|
|
588
627
|
profileDir: path.resolve(dir),
|
|
589
628
|
runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
|
|
@@ -599,6 +638,24 @@ export function writeFleetLocalProfile(input, deps = {}) {
|
|
|
599
638
|
fs.renameSync(temporary, destination);
|
|
600
639
|
if (input.systemPrompt !== undefined)
|
|
601
640
|
writeFleetLocalSoul(input.agentUid, input.systemPrompt, { fleetHome: root });
|
|
641
|
+
if (input.firstStart && (input.firstStart.intro || input.firstStart.kickoff)) {
|
|
642
|
+
writeFleetLocalFirstStart(dir, input.firstStart);
|
|
643
|
+
}
|
|
644
|
+
return destination;
|
|
645
|
+
}
|
|
646
|
+
/** The runtime's inbox plugin sends the intro and runs the kickoff once, then stamps this file. */
|
|
647
|
+
export function writeFleetLocalFirstStart(profileDir, firstStart) {
|
|
648
|
+
const destination = path.join(profileDir, "first-start.json");
|
|
649
|
+
if (fs.existsSync(destination))
|
|
650
|
+
return destination;
|
|
651
|
+
const temporary = path.join(profileDir, `.first-start-${process.pid}-${crypto.randomUUID()}`);
|
|
652
|
+
const record = {
|
|
653
|
+
schemaVersion: 1,
|
|
654
|
+
...(firstStart.intro ? { intro: firstStart.intro } : {}),
|
|
655
|
+
...(firstStart.kickoff ? { kickoff: firstStart.kickoff } : {}),
|
|
656
|
+
};
|
|
657
|
+
fs.writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
658
|
+
fs.renameSync(temporary, destination);
|
|
602
659
|
return destination;
|
|
603
660
|
}
|
|
604
661
|
export function writeFleetLocalSoul(agentUid, systemPrompt, deps = {}) {
|
|
@@ -669,6 +726,24 @@ function checkedRun(run, executable, args, options, label) {
|
|
|
669
726
|
throw expectedError(`HQ Fleet Local ${label} failed.`);
|
|
670
727
|
}
|
|
671
728
|
}
|
|
729
|
+
/** Moves a fresh machine credential into the bot's secure store and deletes the file. */
|
|
730
|
+
export function importFleetLocalMachineCredential(agentUid, machineCredentials, deps = {}) {
|
|
731
|
+
const run = deps.run ?? execFileSync;
|
|
732
|
+
const files = fleetLocalExecutables(agentUid, deps);
|
|
733
|
+
const machineValue = fs.readFileSync(machineCredentials, "utf8").trim();
|
|
734
|
+
if (!machineValue) {
|
|
735
|
+
throw expectedError(`HQ Fleet Local machine credential file is empty: ${machineCredentials}`);
|
|
736
|
+
}
|
|
737
|
+
checkedRun(run, files.helper, [
|
|
738
|
+
"machine-credential",
|
|
739
|
+
"import",
|
|
740
|
+
"--profile",
|
|
741
|
+
files.profileDir,
|
|
742
|
+
"--bot-id",
|
|
743
|
+
agentUid,
|
|
744
|
+
], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
|
|
745
|
+
fs.rmSync(machineCredentials, { force: true });
|
|
746
|
+
}
|
|
672
747
|
export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
673
748
|
const run = deps.run ?? execFileSync;
|
|
674
749
|
const platform = deps.platform ?? process.platform;
|
|
@@ -716,7 +791,7 @@ export async function bootstrapFleetLocalBot(input, deps = {}) {
|
|
|
716
791
|
"--python",
|
|
717
792
|
runtimePython,
|
|
718
793
|
"--editable",
|
|
719
|
-
`${runtimeSource}[
|
|
794
|
+
`${runtimeSource}[mcp,anthropic]`,
|
|
720
795
|
distributionSource,
|
|
721
796
|
], { stdio: ["ignore", 2, 2], env: uvEnv }, "package installation");
|
|
722
797
|
fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: input.installation.engine }, null, 2)}\n`, { mode: 0o600 });
|
package/dist/lib/bot/index.d.ts
CHANGED
package/dist/lib/bot/index.js
CHANGED
|
@@ -41,7 +41,7 @@ export function buildBotLocalConfig(config) {
|
|
|
41
41
|
const companies = effectiveBotCompanies(config);
|
|
42
42
|
const local = {
|
|
43
43
|
v: 1,
|
|
44
|
-
|
|
44
|
+
runtime: config.runtime,
|
|
45
45
|
...(config.engine ? { engine: config.engine } : {}),
|
|
46
46
|
workerSource: config.workerSource ?? "scaffold",
|
|
47
47
|
...(config.workerId ? { workerId: config.workerId } : {}),
|
|
@@ -21,6 +21,8 @@ export declare function isServiceName(value: unknown): value is ServiceName;
|
|
|
21
21
|
export declare const SERVICE_DESCRIPTIONS: Record<ServiceName, string>;
|
|
22
22
|
/** The services each machine type runs without being asked. */
|
|
23
23
|
export declare const DEFAULT_SERVICES: Record<MachineType, readonly ServiceName[]>;
|
|
24
|
+
/** Services another enabled service turns on; only an explicit disable keeps them off. */
|
|
25
|
+
export declare const IMPLIED_SERVICES: Partial<Record<ServiceName, ServiceName>>;
|
|
24
26
|
/** Defaults plus this host's overrides, in catalog order. Unknown names are ignored. */
|
|
25
27
|
export declare function enabledServices(machineType: MachineType, overrides: {
|
|
26
28
|
enable: readonly string[];
|
|
@@ -80,12 +80,17 @@ export const DEFAULT_SERVICES = {
|
|
|
80
80
|
"agent-box": ["mesh", "heartbeat", "inbox", "search-index", "updater", "housekeeping"],
|
|
81
81
|
"agent-kit": ["heartbeat", "inbox", "updater", "housekeeping"],
|
|
82
82
|
};
|
|
83
|
+
/** Services another enabled service turns on; only an explicit disable keeps them off. */
|
|
84
|
+
export const IMPLIED_SERVICES = { mesh: "sync-core" };
|
|
83
85
|
/** Defaults plus this host's overrides, in catalog order. Unknown names are ignored. */
|
|
84
86
|
export function enabledServices(machineType, overrides) {
|
|
85
87
|
const defaults = DEFAULT_SERVICES[machineType];
|
|
86
88
|
const wanted = (s) => (defaults.includes(s) || overrides.enable.includes(s)) && !overrides.disable.includes(s);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
+
const implied = (s) => {
|
|
90
|
+
const by = IMPLIED_SERVICES[s];
|
|
91
|
+
return by !== undefined && wanted(by) && !overrides.disable.includes(s);
|
|
92
|
+
};
|
|
93
|
+
return SERVICE_NAMES.filter((s) => wanted(s) || implied(s));
|
|
89
94
|
}
|
|
90
95
|
export const SYNC_CORE_RECONCILE_UNIT = "sync-core";
|
|
91
96
|
export const SYNC_CORE_RECONCILE_INTERVAL_SEC = 600;
|
|
@@ -29,7 +29,8 @@ export declare function readDaemonConfig(file: string): DaemonConfig;
|
|
|
29
29
|
export declare function writeDaemonConfig(file: string, config: DaemonConfig): void;
|
|
30
30
|
/**
|
|
31
31
|
* Turn services on or off for this host, relative to the machine type's
|
|
32
|
-
* defaults: only differences from the defaults are stored.
|
|
32
|
+
* defaults: only differences from the defaults are stored. A disable is also
|
|
33
|
+
* stored for a service another service can turn on, so it stays off.
|
|
33
34
|
*/
|
|
34
35
|
export declare function applyServiceToggle(config: DaemonConfig, action: "enable" | "disable", services: readonly string[], defaults: readonly ServiceName[]): DaemonConfig;
|
|
35
36
|
//# sourceMappingURL=config.d.ts.map
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { valid as validSemver } from "semver";
|
|
10
|
-
import { isServiceName } from "./catalog.js";
|
|
10
|
+
import { IMPLIED_SERVICES, isServiceName } from "./catalog.js";
|
|
11
11
|
import { isMachineType } from "./machine-type.js";
|
|
12
12
|
import { ensurePrivateDir } from "./paths.js";
|
|
13
13
|
export function effectiveSyncOwner(config, machineType) {
|
|
@@ -81,7 +81,8 @@ export function writeDaemonConfig(file, config) {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Turn services on or off for this host, relative to the machine type's
|
|
84
|
-
* defaults: only differences from the defaults are stored.
|
|
84
|
+
* defaults: only differences from the defaults are stored. A disable is also
|
|
85
|
+
* stored for a service another service can turn on, so it stays off.
|
|
85
86
|
*/
|
|
86
87
|
export function applyServiceToggle(config, action, services, defaults) {
|
|
87
88
|
for (const s of services) {
|
|
@@ -99,7 +100,7 @@ export function applyServiceToggle(config, action, services, defaults) {
|
|
|
99
100
|
}
|
|
100
101
|
else {
|
|
101
102
|
enable.delete(s);
|
|
102
|
-
if (isDefault)
|
|
103
|
+
if (isDefault || (isServiceName(s) && IMPLIED_SERVICES[s] !== undefined))
|
|
103
104
|
disable.add(s);
|
|
104
105
|
}
|
|
105
106
|
}
|
|
@@ -17,6 +17,7 @@ export interface WorkCacheSubscriber {
|
|
|
17
17
|
export { AGENT_INBOX_PUSH_STATE_BASENAME, SYNC_WAKE_STATE_BASENAME } from "../paths.js";
|
|
18
18
|
export type SyncWakeVaults = Record<string, {
|
|
19
19
|
wakeSequence: number;
|
|
20
|
+
head?: number;
|
|
20
21
|
}>;
|
|
21
22
|
export declare function startWorkCacheSubscriber(opts: {
|
|
22
23
|
meshRoot: string;
|
|
@@ -15,6 +15,7 @@ const INBOX_PUSH_HEARTBEAT_MS = 5_000;
|
|
|
15
15
|
const SYNC_WAKE_KEYS = new Set(["v", "type", "vault", "head", "rootId"]);
|
|
16
16
|
const SYNC_WAKE_VAULT = /^(?:prs|cmp|agt)_[0-9A-Za-z]+$/;
|
|
17
17
|
const SYNC_WAKE_ROOT = /^[0-9a-f]{64}$/i;
|
|
18
|
+
const SYNC_WAKE_MAX_BYTES = 4_096;
|
|
18
19
|
function parseSyncWakeEnvelope(payload) {
|
|
19
20
|
let parsed;
|
|
20
21
|
try {
|
|
@@ -38,7 +39,7 @@ function parseSyncWakeEnvelope(payload) {
|
|
|
38
39
|
Number(message.head) > 0 &&
|
|
39
40
|
(message.rootId === null ||
|
|
40
41
|
(typeof message.rootId === "string" && SYNC_WAKE_ROOT.test(message.rootId)));
|
|
41
|
-
return valid ? { vault: message.vault } : null;
|
|
42
|
+
return valid ? { vault: message.vault, head: Number(message.head) } : null;
|
|
42
43
|
}
|
|
43
44
|
function readVaultSequences(raw) {
|
|
44
45
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
@@ -47,9 +48,11 @@ function readVaultSequences(raw) {
|
|
|
47
48
|
for (const [vault, entry] of Object.entries(raw)) {
|
|
48
49
|
if (!SYNC_WAKE_VAULT.test(vault) || !entry || typeof entry !== "object")
|
|
49
50
|
continue;
|
|
50
|
-
const sequence = entry
|
|
51
|
+
const { wakeSequence: sequence, head } = entry;
|
|
51
52
|
if (Number.isSafeInteger(sequence) && Number(sequence) >= 0) {
|
|
52
|
-
vaults[vault] =
|
|
53
|
+
vaults[vault] = Number.isSafeInteger(head) && Number(head) >= 0
|
|
54
|
+
? { wakeSequence: Number(sequence), head: Number(head) }
|
|
55
|
+
: { wakeSequence: Number(sequence) };
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
return vaults;
|
|
@@ -85,6 +88,9 @@ function readWakeSequence(meshRoot, basename, label, onInfo) {
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
}
|
|
91
|
+
function withoutHeads(vaults) {
|
|
92
|
+
return Object.fromEntries(Object.entries(vaults).map(([vault, entry]) => [vault, { wakeSequence: entry.wakeSequence }]));
|
|
93
|
+
}
|
|
88
94
|
function writeWakeState(meshRoot, basename, state, durable) {
|
|
89
95
|
fs.mkdirSync(meshRoot, { recursive: true, mode: 0o700 });
|
|
90
96
|
const rootInfo = fs.lstatSync(meshRoot);
|
|
@@ -162,15 +168,27 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
162
168
|
timers.clearTimeout(heartbeatTimer);
|
|
163
169
|
heartbeatTimer = null;
|
|
164
170
|
};
|
|
171
|
+
let syncWakeHeadsOmitted = false;
|
|
165
172
|
const persistState = (basename, label, connected, wakeSequence, identity, durable = true, vaults) => {
|
|
166
173
|
try {
|
|
167
|
-
|
|
174
|
+
const state = {
|
|
168
175
|
connected,
|
|
169
176
|
heartbeatAtMs: now().getTime(),
|
|
170
177
|
wakeSequence,
|
|
171
178
|
...(connected && actorUid && identity ? { [identity]: actorUid } : {}),
|
|
172
179
|
...(vaults ? { vaults } : {}),
|
|
173
|
-
}
|
|
180
|
+
};
|
|
181
|
+
if (vaults) {
|
|
182
|
+
const oversized = Buffer.byteLength(`${JSON.stringify(state)}\n`) > SYNC_WAKE_MAX_BYTES;
|
|
183
|
+
if (oversized) {
|
|
184
|
+
state.vaults = withoutHeads(vaults);
|
|
185
|
+
if (!syncWakeHeadsOmitted) {
|
|
186
|
+
opts.onInfo?.(`${label} state over ${SYNC_WAKE_MAX_BYTES} bytes; vault heads omitted`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
syncWakeHeadsOmitted = oversized;
|
|
190
|
+
}
|
|
191
|
+
writeWakeState(opts.meshRoot, basename, state, durable);
|
|
174
192
|
}
|
|
175
193
|
catch (error) {
|
|
176
194
|
const name = error instanceof Error ? error.name : "UnknownError";
|
|
@@ -318,8 +336,10 @@ export async function startWorkCacheSubscriber(opts) {
|
|
|
318
336
|
return;
|
|
319
337
|
}
|
|
320
338
|
syncWakeSequence += 1;
|
|
339
|
+
const previous = syncWakeVaults[envelope.vault];
|
|
321
340
|
syncWakeVaults[envelope.vault] = {
|
|
322
|
-
wakeSequence: (
|
|
341
|
+
wakeSequence: (previous?.wakeSequence ?? 0) + 1,
|
|
342
|
+
head: Math.max(previous?.head ?? 0, envelope.head),
|
|
323
343
|
};
|
|
324
344
|
persistSyncState(true);
|
|
325
345
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SYNC_CORE_RELEASE = "v0.1.
|
|
1
|
+
export declare const SYNC_CORE_RELEASE = "v0.1.21";
|
|
2
2
|
export declare const SYNC_CORE_COMPANY_FULL_RELEASE: string | null;
|
|
3
3
|
export declare function compareSyncCoreReleases(a: string, b: string): number;
|
|
4
4
|
export declare function companyFullModeAllowed(release: string): boolean;
|
|
@@ -3,8 +3,8 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
-
export const SYNC_CORE_RELEASE = "v0.1.
|
|
7
|
-
export const SYNC_CORE_COMPANY_FULL_RELEASE = "v0.1.
|
|
6
|
+
export const SYNC_CORE_RELEASE = "v0.1.21";
|
|
7
|
+
export const SYNC_CORE_COMPANY_FULL_RELEASE = "v0.1.21";
|
|
8
8
|
export function compareSyncCoreReleases(a, b) {
|
|
9
9
|
const parse = (release) => release.replace(/^v/, "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
10
10
|
const [x, y] = [parse(a), parse(b)];
|
package/dist/run/hq-plugin.d.ts
CHANGED
|
@@ -18,6 +18,9 @@ export interface PluginState {
|
|
|
18
18
|
}>;
|
|
19
19
|
loadedSecretsByName: Map<string, string>;
|
|
20
20
|
}
|
|
21
|
+
/** True when the varlock item has an `# @optional` decorator. */
|
|
22
|
+
export declare function itemIsOptional(item: unknown): boolean;
|
|
23
|
+
export declare function isSkippableOptionalSecretError(code: string | undefined): boolean;
|
|
21
24
|
export declare function installHqPlugin(graph: any, opts: InstallHqPluginOpts): PluginState;
|
|
22
25
|
export declare function prewarmHqSecrets(graph: any, opts: InstallHqPluginOpts, state: PluginState): Promise<void>;
|
|
23
26
|
//# sourceMappingURL=hq-plugin.d.ts.map
|
package/dist/run/hq-plugin.js
CHANGED
|
@@ -16,6 +16,15 @@ function normalizeCacheTtlMs(secret) {
|
|
|
16
16
|
? secret.cacheTtlMs
|
|
17
17
|
: 0;
|
|
18
18
|
}
|
|
19
|
+
const SKIPPABLE_OPTIONAL_CODES = new Set(['forbidden', 'not_found', 'not_returned']);
|
|
20
|
+
/** True when the varlock item has an `# @optional` decorator. */
|
|
21
|
+
export function itemIsOptional(item) {
|
|
22
|
+
const decorators = item?.effectiveDecorators;
|
|
23
|
+
return Boolean(decorators && Object.prototype.hasOwnProperty.call(decorators, 'optional'));
|
|
24
|
+
}
|
|
25
|
+
export function isSkippableOptionalSecretError(code) {
|
|
26
|
+
return Boolean(code && SKIPPABLE_OPTIONAL_CODES.has(code));
|
|
27
|
+
}
|
|
19
28
|
export function installHqPlugin(graph /* EnvGraph */, opts) {
|
|
20
29
|
const pluginState = {
|
|
21
30
|
schemaCompanySlug: null,
|
|
@@ -63,6 +72,10 @@ export function installHqPlugin(graph /* EnvGraph */, opts) {
|
|
|
63
72
|
}
|
|
64
73
|
const err = pluginState.errorsByName.get(secretName);
|
|
65
74
|
if (err) {
|
|
75
|
+
// `# @optional`: missing ACL or a missing secret leaves the var unset.
|
|
76
|
+
if (itemIsOptional(this._ownerItem) && isSkippableOptionalSecretError(err.code)) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
66
79
|
if (err.code === 'forbidden') {
|
|
67
80
|
throw new ResolutionError(`No read permission for secret "${secretName}" — ask an admin to share it via \`hq secrets share ${secretName} --with <you> --permission read\``);
|
|
68
81
|
}
|
|
@@ -85,13 +98,18 @@ export function installHqPlugin(graph /* EnvGraph */, opts) {
|
|
|
85
98
|
if (inMemory != null) {
|
|
86
99
|
return inMemory;
|
|
87
100
|
}
|
|
101
|
+
if (itemIsOptional(this._ownerItem)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
88
104
|
throw new ResolutionError(`Secret "${secretName}" was not returned by vault after server authorization`);
|
|
89
105
|
},
|
|
90
106
|
};
|
|
91
107
|
// Captured during process(parent); used by resolve() to fall back to the var key.
|
|
92
108
|
_ownerKey;
|
|
109
|
+
_ownerItem;
|
|
93
110
|
process(parent) {
|
|
94
111
|
super.process(parent);
|
|
112
|
+
this._ownerItem = parent;
|
|
95
113
|
if (parent != null && typeof parent.key === 'string') {
|
|
96
114
|
this._ownerKey = parent.key;
|
|
97
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.178",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
118
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
119
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
117
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.178",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.178",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.178",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.178",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.178",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.178"
|
|
123
123
|
}
|
|
124
124
|
}
|