@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.
@@ -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.6",
13
- version: "0.4.6",
14
- artifact: "hq-fleet-local-0.4.6.zip",
15
- artifactSha256: "24e48887dd4450ad63eda11e61f78363b0cc53b3fc87e3fd21bfe97e4a430865",
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: "5cc06b30a724419c22f4ab116db1ae745158d33c4aa46c7be04d29639172af1d",
19
- signatureSha256: "b7a6c771dc674423b56db36f58b5a2a2a49eb2e27b33a41206fcc228a11d1def",
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 = [...new Set([...credentialProviders, provider.credentialProvider])];
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
- provider: { id: "custom:personal-chatgpt", model: "gpt-5.6-sol" },
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}[all,messaging,slack,anthropic]`,
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 });
@@ -26,5 +26,6 @@ export * from "./lock.js";
26
26
  export * from "./display-name.js";
27
27
  export * from "./decisions.js";
28
28
  export * from "./fleet-local.js";
29
+ export * from "./fleet-default-flag.js";
29
30
  export * from "./fleet-python.js";
30
31
  //# sourceMappingURL=index.d.ts.map
@@ -26,5 +26,6 @@ export * from "./lock.js";
26
26
  export * from "./display-name.js";
27
27
  export * from "./decisions.js";
28
28
  export * from "./fleet-local.js";
29
+ export * from "./fleet-default-flag.js";
29
30
  export * from "./fleet-python.js";
30
31
  //# sourceMappingURL=index.js.map
@@ -41,7 +41,7 @@ export function buildBotLocalConfig(config) {
41
41
  const companies = effectiveBotCompanies(config);
42
42
  const local = {
43
43
  v: 1,
44
- ...(config.engine ? {} : { runtime: config.runtime }),
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 wakeSubscriber = wanted("sync-core") && !overrides.disable.includes("mesh");
88
- return SERVICE_NAMES.filter((s) => wanted(s) || (s === "mesh" && wakeSubscriber));
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.wakeSequence;
51
+ const { wakeSequence: sequence, head } = entry;
51
52
  if (Number.isSafeInteger(sequence) && Number(sequence) >= 0) {
52
- vaults[vault] = { wakeSequence: Number(sequence) };
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
- writeWakeState(opts.meshRoot, basename, {
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
- }, durable);
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: (syncWakeVaults[envelope.vault]?.wakeSequence ?? 0) + 1,
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.20";
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.20";
7
- export const SYNC_CORE_COMPANY_FULL_RELEASE = "v0.1.20";
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)];
@@ -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
@@ -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.176",
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.176",
118
- "@indigoai-us/hq-hook-win32-arm64": "5.345.176",
119
- "@indigoai-us/hq-hook-darwin-x64": "5.345.176",
120
- "@indigoai-us/hq-hook-darwin-arm64": "5.345.176",
121
- "@indigoai-us/hq-hook-linux-x64": "5.345.176",
122
- "@indigoai-us/hq-hook-linux-arm64": "5.345.176"
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
  }