@indigoai-us/hq-cli 5.345.58 → 5.345.60

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/command-catalog.generated.d.ts +8 -0
  3. package/dist/command-catalog.generated.js +10 -0
  4. package/dist/commands/agent-kit.js +20 -10
  5. package/dist/commands/billing.d.ts +13 -0
  6. package/dist/commands/billing.js +31 -0
  7. package/dist/commands/db-dump.js +2 -2
  8. package/dist/commands/db-remote-deps.js +6 -0
  9. package/dist/commands/lanes-loop.js +3 -1
  10. package/dist/commands/lanes-questions.js +16 -0
  11. package/dist/commands/lanes-session-start.js +19 -3
  12. package/dist/commands/lanes-shared.d.ts +1 -0
  13. package/dist/commands/lanes.d.ts +3 -1
  14. package/dist/commands/lanes.js +52 -2
  15. package/dist/lib/agent-kit/inbox-poll-backoff-flag.d.ts +19 -0
  16. package/dist/lib/agent-kit/inbox-poll-backoff-flag.js +70 -0
  17. package/dist/lib/agent-kit/run/inbox.d.ts +7 -3
  18. package/dist/lib/agent-kit/run/inbox.js +45 -6
  19. package/dist/lib/agent-kit/run/stop-signals.d.ts +14 -0
  20. package/dist/lib/agent-kit/run/stop-signals.js +26 -0
  21. package/dist/lib/agent-kit/services.d.ts +2 -2
  22. package/dist/lib/agent-kit/services.js +5 -3
  23. package/dist/lib/anywhere-runtime-flag-key.d.ts +1 -0
  24. package/dist/lib/anywhere-runtime-flag-key.js +1 -0
  25. package/dist/lib/anywhere-runtime-revocation-marker.d.ts +4 -0
  26. package/dist/lib/anywhere-runtime-revocation-marker.js +26 -0
  27. package/dist/lib/daemon/run.d.ts +9 -0
  28. package/dist/lib/daemon/run.js +42 -5
  29. package/dist/lib/daemon/server.d.ts +11 -0
  30. package/dist/lib/daemon/server.js +58 -0
  31. package/dist/lib/db/control-plane.d.ts +12 -1
  32. package/dist/lib/db/usage-format.d.ts +2 -1
  33. package/dist/lib/db/usage-format.js +19 -0
  34. package/dist/lib/install/provider-wrappers.js +13 -0
  35. package/dist/lib/lanes/loop.d.ts +23 -1
  36. package/dist/lib/lanes/loop.js +134 -19
  37. package/dist/lib/lanes/questions.d.ts +5 -0
  38. package/dist/lib/lanes/reconcile.js +3 -0
  39. package/dist/lib/lanes/story-ledger.js +30 -20
  40. package/dist/lib/lanes/types.js +1 -1
  41. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ import { EventEmitter } from "node:events";
2
+ export declare const KIT_STOP_GRACE_MS = 10000;
3
+ interface KitStopControllerOptions {
4
+ emitter?: Pick<EventEmitter, "once" | "removeListener">;
5
+ forceExit?: (code: number) => void;
6
+ timeoutMs?: number;
7
+ }
8
+ /** Abort service work on stop, with a deadline if an awaited operation stalls. */
9
+ export declare function createKitStopController(options?: KitStopControllerOptions): {
10
+ signal: AbortSignal;
11
+ dispose(): void;
12
+ };
13
+ export {};
14
+ //# sourceMappingURL=stop-signals.d.ts.map
@@ -0,0 +1,26 @@
1
+ export const KIT_STOP_GRACE_MS = 10_000;
2
+ /** Abort service work on stop, with a deadline if an awaited operation stalls. */
3
+ export function createKitStopController(options = {}) {
4
+ const emitter = options.emitter ?? process;
5
+ const forceExit = options.forceExit ?? ((code) => process.exit(code));
6
+ const controller = new AbortController();
7
+ let deadline;
8
+ const stop = () => {
9
+ if (controller.signal.aborted)
10
+ return;
11
+ controller.abort();
12
+ deadline = setTimeout(() => forceExit(0), options.timeoutMs ?? KIT_STOP_GRACE_MS);
13
+ };
14
+ emitter.once("SIGTERM", stop);
15
+ emitter.once("SIGINT", stop);
16
+ return {
17
+ signal: controller.signal,
18
+ dispose() {
19
+ if (deadline)
20
+ clearTimeout(deadline);
21
+ emitter.removeListener("SIGTERM", stop);
22
+ emitter.removeListener("SIGINT", stop);
23
+ },
24
+ };
25
+ }
26
+ //# sourceMappingURL=stop-signals.js.map
@@ -14,8 +14,8 @@ export declare function isKitService(value: string): value is KitService;
14
14
  * file a loud error instead of a browser-login hang; HQ_AGENT_DIR keeps a
15
15
  * relocated kit consistent across restarts.
16
16
  */
17
- export declare function kitServiceEnv(paths: AgentKitPaths): Record<string, string>;
18
- export declare function kitServiceSpec(service: KitService, paths: AgentKitPaths): ServiceSpec;
17
+ export declare function kitServiceEnv(paths: AgentKitPaths, env?: NodeJS.ProcessEnv): Record<string, string>;
18
+ export declare function kitServiceSpec(service: KitService, paths: AgentKitPaths, env?: NodeJS.ProcessEnv): ServiceSpec;
19
19
  /**
20
20
  * Services to install. `sync` is included only when the kit is configured to
21
21
  * mirror the vault to disk; by default a bot reads files on demand through the
@@ -20,14 +20,16 @@ const DESCRIPTIONS = {
20
20
  * file a loud error instead of a browser-login hang; HQ_AGENT_DIR keeps a
21
21
  * relocated kit consistent across restarts.
22
22
  */
23
- export function kitServiceEnv(paths) {
23
+ export function kitServiceEnv(paths, env = process.env) {
24
+ const flagsApiUrl = env.HQ_FLAGS_API_URL;
24
25
  return {
25
26
  HQ_AGENT_DIR: paths.agentDir,
26
27
  HQ_MACHINE_CREDS_FILE: paths.machineCredsPath,
27
28
  HQ_REQUIRE_MACHINE_IDENTITY: "1",
29
+ ...(flagsApiUrl?.trim() ? { HQ_FLAGS_API_URL: flagsApiUrl } : {}),
28
30
  };
29
31
  }
30
- export function kitServiceSpec(service, paths) {
32
+ export function kitServiceSpec(service, paths, env = process.env) {
31
33
  return {
32
34
  name: service,
33
35
  label: `${KIT_LABEL_PREFIX}.${service}`,
@@ -35,7 +37,7 @@ export function kitServiceSpec(service, paths) {
35
37
  args: ["agent", "kit", "run", service],
36
38
  logPath: serviceLogPath(paths, service),
37
39
  workingDir: paths.agentDir,
38
- env: kitServiceEnv(paths),
40
+ env: kitServiceEnv(paths, env),
39
41
  restartSec: 10,
40
42
  };
41
43
  }
@@ -1,5 +1,6 @@
1
1
  /** hq-flags key for the staged HQ Anywhere runtime. The registry default is on. */
2
2
  export declare const HQ_ANYWHERE_RUNTIME_FLAG = "hq-anywhere-runtime";
3
+ export declare const HQ_ANYWHERE_RUNTIME_REVOCATION_MARKER = "hq-anywhere-runtime.revoked";
3
4
  /** The HQ Anywhere environment kill switch stays compatible with managed provider wrappers. */
4
5
  export declare function hqAnywhereRuntimeLocallyDisabled(env?: NodeJS.ProcessEnv): boolean;
5
6
  //# sourceMappingURL=anywhere-runtime-flag-key.d.ts.map
@@ -1,5 +1,6 @@
1
1
  /** hq-flags key for the staged HQ Anywhere runtime. The registry default is on. */
2
2
  export const HQ_ANYWHERE_RUNTIME_FLAG = "hq-anywhere-runtime";
3
+ export const HQ_ANYWHERE_RUNTIME_REVOCATION_MARKER = "hq-anywhere-runtime.revoked";
3
4
  /** The HQ Anywhere environment kill switch stays compatible with managed provider wrappers. */
4
5
  export function hqAnywhereRuntimeLocallyDisabled(env = process.env) {
5
6
  const value = env.HQ_FLAG_HQ_ANYWHERE_RUNTIME;
@@ -0,0 +1,4 @@
1
+ export declare function anywhereRuntimeRevocationMarkerPath(home?: string, env?: NodeJS.ProcessEnv): string;
2
+ /** Keep installed wrappers aligned with the daemon's refreshed access decision. */
3
+ export declare function updateAnywhereRuntimeRevocationMarker(enabled: boolean, home?: string, env?: NodeJS.ProcessEnv): void;
4
+ //# sourceMappingURL=anywhere-runtime-revocation-marker.d.ts.map
@@ -0,0 +1,26 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { HQ_ANYWHERE_RUNTIME_REVOCATION_MARKER } from "./anywhere-runtime-flag-key.js";
5
+ import { registryDir } from "./registry/registry.js";
6
+ export function anywhereRuntimeRevocationMarkerPath(home = os.homedir(), env = process.env) {
7
+ return path.join(registryDir(home, env), HQ_ANYWHERE_RUNTIME_REVOCATION_MARKER);
8
+ }
9
+ /** Keep installed wrappers aligned with the daemon's refreshed access decision. */
10
+ export function updateAnywhereRuntimeRevocationMarker(enabled, home = os.homedir(), env = process.env) {
11
+ const marker = anywhereRuntimeRevocationMarkerPath(home, env);
12
+ if (!enabled) {
13
+ fs.mkdirSync(path.dirname(marker), { recursive: true, mode: 0o700 });
14
+ fs.writeFileSync(marker, "revoked\n", { mode: 0o600 });
15
+ fs.chmodSync(marker, 0o600);
16
+ return;
17
+ }
18
+ try {
19
+ fs.unlinkSync(marker);
20
+ }
21
+ catch (error) {
22
+ if (error.code !== "ENOENT")
23
+ throw error;
24
+ }
25
+ }
26
+ //# sourceMappingURL=anywhere-runtime-revocation-marker.js.map
@@ -29,6 +29,15 @@ export interface DaemonLockConflictOutcome {
29
29
  export declare function daemonLockConflictOutcome(lock: DaemonLockResult, anywhereEnabled: boolean): DaemonLockConflictOutcome | null;
30
30
  /** hqd is available on every platform while Anywhere is enabled; its listener fails closed if setup is unsafe. */
31
31
  export declare function shouldStartHqd(anywhereEnabled: boolean): boolean;
32
+ /**
33
+ * Resolve the supervisor's runtime-access decision and reflect it in the local
34
+ * markers: the wrapper revocation marker and the hook shims' hqd enabled marker.
35
+ * Turning on publishes the enabled marker before lifting revocation, and turning
36
+ * off revokes before withdrawing it, so no hook ever sees revocation lifted while
37
+ * the shim would stay inert. If the enabled marker cannot be published,
38
+ * revocation stays in place.
39
+ */
40
+ export declare function refreshHqdRuntimeDecision(readDecision: () => Promise<boolean>, home: string, env: NodeJS.ProcessEnv, onMarkerError?: (error: unknown) => void, syncEnabledMarker?: (enabled: boolean) => void): Promise<boolean>;
32
41
  /** Route an updater request through the daemon's existing graceful handoff. */
33
42
  export declare function handoffForPackageUpdateRequest(requested: boolean, restartDaemon: () => void): void;
34
43
  export interface RunDaemonOptions {
@@ -30,11 +30,12 @@ import { retireLegacyMeshUnits } from "./migrate.js";
30
30
  import { daemonDir as meshDaemonDir, daemonPidPath as meshDaemonPidPath } from "../mesh/live/daemon/paths.js";
31
31
  import { readPidLock } from "../mesh/live/daemon/pid-lock.js";
32
32
  import { registryDir, registryFile } from "../registry/registry.js";
33
+ import { updateAnywhereRuntimeRevocationMarker } from "../anywhere-runtime-revocation-marker.js";
33
34
  import { daemonRootRefusal } from "./install.js";
34
35
  import { detectMachineType } from "./machine-type.js";
35
36
  import { appendPrivate, daemonPaths, ensurePrivateDir, openPrivateAppend } from "./paths.js";
36
37
  import { hqdSocketPath } from "./protocol.js";
37
- import { startHqdServer } from "./server.js";
38
+ import { startHqdServer, syncHqdEnabledMarker } from "./server.js";
38
39
  import { startHqdRuntimeListener } from "./anywhere-listener.js";
39
40
  import { heartbeatFile, startHeartbeat } from "./heartbeat.js";
40
41
  import { createProtocolTail, ensureProtocolFifo, openProtocolWriter } from "./protocol-tail.js";
@@ -93,6 +94,41 @@ export function daemonLockConflictOutcome(lock, anywhereEnabled) {
93
94
  export function shouldStartHqd(anywhereEnabled) {
94
95
  return anywhereEnabled;
95
96
  }
97
+ /**
98
+ * Resolve the supervisor's runtime-access decision and reflect it in the local
99
+ * markers: the wrapper revocation marker and the hook shims' hqd enabled marker.
100
+ * Turning on publishes the enabled marker before lifting revocation, and turning
101
+ * off revokes before withdrawing it, so no hook ever sees revocation lifted while
102
+ * the shim would stay inert. If the enabled marker cannot be published,
103
+ * revocation stays in place.
104
+ */
105
+ export async function refreshHqdRuntimeDecision(readDecision, home, env, onMarkerError = () => { }, syncEnabledMarker = () => { }) {
106
+ const enabled = await readDecision();
107
+ if (enabled) {
108
+ try {
109
+ syncEnabledMarker(true);
110
+ }
111
+ catch (error) {
112
+ onMarkerError(error);
113
+ return enabled;
114
+ }
115
+ }
116
+ try {
117
+ updateAnywhereRuntimeRevocationMarker(enabled, home, env);
118
+ }
119
+ catch (error) {
120
+ onMarkerError(error);
121
+ }
122
+ if (!enabled) {
123
+ try {
124
+ syncEnabledMarker(false);
125
+ }
126
+ catch (error) {
127
+ onMarkerError(error);
128
+ }
129
+ }
130
+ return enabled;
131
+ }
96
132
  /** Route an updater request through the daemon's existing graceful handoff. */
97
133
  export function handoffForPackageUpdateRequest(requested, restartDaemon) {
98
134
  if (requested)
@@ -897,13 +933,13 @@ export async function runDaemon(opts) {
897
933
  onError: (name) => log("warn", `hqd person setting read failed (${name}); keeping the last read value for up to ${HQD_PERSON_SETTING_GRACE_MS / 60_000} minutes`),
898
934
  }), { graceMs: HQD_PERSON_SETTING_GRACE_MS });
899
935
  const hqdListener = await startHqdRuntimeListener({
900
- isEnabled: async () => shouldStartHqd(await anywhereRuntimeAllowed(env, {
936
+ isEnabled: async () => refreshHqdRuntimeDecision(async () => shouldStartHqd(await anywhereRuntimeAllowed(env, {
901
937
  refreshFlag: async () => {
902
938
  const { refreshFlagRegistrySnapshot } = await import("../flag-registry.js");
903
939
  await refreshFlagRegistrySnapshot(5_000);
904
940
  },
905
941
  readSetting: readPersonSetting,
906
- })),
942
+ })), home, env, (error) => log("warn", `hqd runtime marker update failed: ${error.message ?? String(error)}`), (enabled) => syncHqdEnabledMarker(hqdSocketPath(home, env), enabled)),
907
943
  startServer: async () => {
908
944
  const startConfig = readDaemonConfig(paths.configPath);
909
945
  return startHqdServer({
@@ -959,8 +995,9 @@ export async function runDaemon(opts) {
959
995
  releaseDaemonLock(paths.lockPath);
960
996
  resolve(exitCode);
961
997
  // Pipes, sockets and unrelated timers can outlive a clean shutdown.
962
- // Keep restart and signal exits bounded just like the handoff path.
963
- setTimeout(() => process.exit(exitCode), 5_000).unref();
998
+ // Services and the lock are already stopped; unrelated handles should
999
+ // not hold a restart or signal exit open for another service interval.
1000
+ setTimeout(() => process.exit(exitCode), 1_000).unref();
964
1001
  });
965
1002
  };
966
1003
  /** Exit for a restart but leave the services running for the next daemon to adopt. */
@@ -68,5 +68,16 @@ export declare function createResolver(registryPath: string, options?: {
68
68
  readRegistry?: typeof readRegistry;
69
69
  readCompaniesManifest?: typeof readCompaniesManifestMap;
70
70
  }): (cwd: string) => ResolveResult;
71
+ /** Marker beside the hqd socket; hook shims fail closed only where it (or the socket) exists. */
72
+ export declare const HQD_ENABLED_MARKER = "hqd.enabled";
73
+ /**
74
+ * Keep `<socket dir>/hqd.enabled` in step with the hqd gate. The POSIX hook
75
+ * shim reads it to tell "hqd should answer" from "hqd is not set up here".
76
+ * The directory gets the same trust checks as the socket itself, and the
77
+ * marker is opened without following symlinks or blocking on a FIFO, so a
78
+ * pre-planted entry is refused instead of written through. Throws when the
79
+ * marker cannot be published safely. Windows named pipes have no marker.
80
+ */
81
+ export declare function syncHqdEnabledMarker(socketPath: string, enabled: boolean): void;
71
82
  export declare function startHqdServer(opts: HqdServerOptions): Promise<HqdServer>;
72
83
  //# sourceMappingURL=server.d.ts.map
@@ -23,6 +23,7 @@ import { defaultMcpToken, HqMcpBridge, jsonResult, McpOpError, runWorker } from
23
23
  import { authenticateMachineCredentials, machineTokenExpiresAt, MachineLoginError, parseStdinMachineCredentials } from "./machine-login.js";
24
24
  import { HQ_MCP_TOOL_NAMES, HQD_NATIVE_TOOL_NAMES } from "../mcp/tools.js";
25
25
  import { HQ_ANYWHERE_RUNTIME_FLAG } from "../../commands/anywhere-runtime-flag.js";
26
+ import { updateAnywhereRuntimeRevocationMarker } from "../anywhere-runtime-revocation-marker.js";
26
27
  import { readHqAnywherePersonSetting } from "../anywhere-person-setting.js";
27
28
  import { createBoundedDaemonFlagMonitor } from "./flag-monitor.js";
28
29
  import { readDaemonFlag } from "./flag.js";
@@ -304,6 +305,57 @@ function prepareSocketDirectory(directory, lstat, uid) {
304
305
  throw new Error(`${directory}: newly created socket directory mode is not 0700 after chmod`);
305
306
  }
306
307
  }
308
+ /** Marker beside the hqd socket; hook shims fail closed only where it (or the socket) exists. */
309
+ export const HQD_ENABLED_MARKER = "hqd.enabled";
310
+ /**
311
+ * Keep `<socket dir>/hqd.enabled` in step with the hqd gate. The POSIX hook
312
+ * shim reads it to tell "hqd should answer" from "hqd is not set up here".
313
+ * The directory gets the same trust checks as the socket itself, and the
314
+ * marker is opened without following symlinks or blocking on a FIFO, so a
315
+ * pre-planted entry is refused instead of written through. Throws when the
316
+ * marker cannot be published safely. Windows named pipes have no marker.
317
+ */
318
+ export function syncHqdEnabledMarker(socketPath, enabled) {
319
+ if (socketPath.startsWith("\\\\.\\pipe\\"))
320
+ return;
321
+ const directory = path.dirname(path.resolve(socketPath));
322
+ const marker = path.join(directory, HQD_ENABLED_MARKER);
323
+ const lstat = (candidate) => fs.lstatSync(candidate);
324
+ const uid = process.getuid?.();
325
+ if (uid === undefined)
326
+ throw new Error(`${directory}: cannot verify current uid for the hqd enabled marker`);
327
+ if (!enabled) {
328
+ assertTrustedSocketPathComponents(directory, lstat, uid);
329
+ try {
330
+ fs.unlinkSync(marker);
331
+ }
332
+ catch (err) {
333
+ if (err.code !== "ENOENT")
334
+ throw err;
335
+ }
336
+ return;
337
+ }
338
+ prepareSocketDirectory(directory, lstat, uid);
339
+ const { O_WRONLY, O_CREAT, O_NOFOLLOW, O_NONBLOCK } = fs.constants;
340
+ let fd;
341
+ try {
342
+ fd = fs.openSync(marker, O_WRONLY | O_CREAT | O_NOFOLLOW | O_NONBLOCK, 0o600);
343
+ }
344
+ catch (err) {
345
+ const detail = err instanceof Error ? err.message : String(err);
346
+ throw new Error(`${marker}: cannot open hqd enabled marker safely: ${detail}`);
347
+ }
348
+ try {
349
+ const stat = fs.fstatSync(fd);
350
+ if (!stat.isFile())
351
+ throw new Error(`${marker}: existing path is not a regular file`);
352
+ if (stat.uid !== uid)
353
+ throw new Error(`${marker}: existing marker is not owned by the current uid`);
354
+ }
355
+ finally {
356
+ fs.closeSync(fd);
357
+ }
358
+ }
307
359
  function verifyUserOnlySocket(socketPath, lstat, uid) {
308
360
  assertTrustedSocketPathComponents(path.dirname(socketPath), lstat, uid);
309
361
  const before = socketLstatOrMissing(socketPath, lstat);
@@ -416,6 +468,12 @@ export async function startHqdServer(opts) {
416
468
  return personSetting === true;
417
469
  },
418
470
  onChange: (enabled) => {
471
+ try {
472
+ updateAnywhereRuntimeRevocationMarker(enabled, env.HOME?.trim() || undefined, env);
473
+ }
474
+ catch (error) {
475
+ log("warn", `hqd runtime revocation marker update failed (${error instanceof Error ? error.name : "UnknownError"})`);
476
+ }
419
477
  if (!enabled) {
420
478
  flagRevocationGeneration += 1;
421
479
  ephemeralMachineToken = undefined;
@@ -71,7 +71,18 @@ export interface RemoteUsageResponse {
71
71
  apps?: RemoteAppUsage[];
72
72
  }
73
73
  /** Answer for one app: its own numbers, or the company numbers when it shares the company cluster. */
74
- export type RemoteAppUsageResponse = (RemoteAppUsage & {
74
+ export interface RemoteRemovedAppUsage {
75
+ companyUid: string;
76
+ appId: string;
77
+ mode: "app-cluster" | "company-cluster";
78
+ status: "detached" | "purged";
79
+ removedAt: string;
80
+ retainUntil: string | null;
81
+ purgedAt: string | null;
82
+ billed: false;
83
+ exempt: boolean;
84
+ }
85
+ export type RemoteAppUsageResponse = RemoteRemovedAppUsage | (RemoteAppUsage & {
75
86
  companyUid: string;
76
87
  mode: "app-cluster";
77
88
  status?: string;
@@ -1,7 +1,8 @@
1
- import type { RemoteAppUsage, RemoteAppUsageResponse, RemoteUsageResponse } from "./control-plane.js";
1
+ import type { RemoteAppUsage, RemoteAppUsageResponse, RemoteRemovedAppUsage, RemoteUsageResponse } from "./control-plane.js";
2
2
  export declare function formatBytes(bytes: number): string;
3
3
  export declare function formatUsage(usage: RemoteUsageResponse): string;
4
4
  export declare function formatAppUsage(usage: RemoteAppUsage): string;
5
+ export declare function formatRemovedAppUsage(usage: RemoteRemovedAppUsage): string;
5
6
  export declare function formatAppUsageResponse(response: RemoteAppUsageResponse): string;
6
7
  export declare function formatCompanyApps(apps: RemoteAppUsage[]): string;
7
8
  //# sourceMappingURL=usage-format.d.ts.map
@@ -80,7 +80,26 @@ export function formatAppUsage(usage) {
80
80
  : `Last measured ${usage.measuredAt}. Numbers can lag by a few minutes.`);
81
81
  return lines.join("\n");
82
82
  }
83
+ function day(iso) {
84
+ return iso.slice(0, 10);
85
+ }
86
+ export function formatRemovedAppUsage(usage) {
87
+ const lines = [`The database for app ${usage.appId} was removed on ${day(usage.removedAt)}.`];
88
+ if (usage.purgedAt) {
89
+ lines.push(`Its data was deleted for good on ${day(usage.purgedAt)}.`);
90
+ }
91
+ else if (usage.retainUntil) {
92
+ lines.push(`Its data is kept until ${day(usage.retainUntil)}, then deleted for good.`);
93
+ }
94
+ lines.push("It is not billed.");
95
+ return lines.join("\n");
96
+ }
97
+ function isRemoved(response) {
98
+ return "status" in response && (response.status === "detached" || response.status === "purged");
99
+ }
83
100
  export function formatAppUsageResponse(response) {
101
+ if (isRemoved(response))
102
+ return formatRemovedAppUsage(response);
84
103
  if (response.mode === "company-cluster") {
85
104
  return [
86
105
  `App ${response.appId} uses the shared company database, so these are the company numbers.`,
@@ -1,6 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
+ import { HQ_ANYWHERE_RUNTIME_REVOCATION_MARKER } from '../anywhere-runtime-flag-key.js';
4
5
  const marker = '# hq-anywhere provider sign-in wrapper';
5
6
  const PROVIDER_PATH_RC_FILES = ['.bashrc', '.bash_profile', '.zshrc', '.zprofile', '.profile'];
6
7
  const wrapperHeader = (provider) => `#!/bin/sh\n${marker}: ${provider}\n`;
@@ -76,6 +77,18 @@ function wrapperSource(provider) {
76
77
  ` ${provider === 'claude' ? '/login|auth' : 'login'}) exec "$real" "$@" ;;`,
77
78
  'esac',
78
79
  '',
80
+ '# The local marker mirrors registryDir: trim the override and use ~/.hq when empty.',
81
+ 'registry_dir=${HQ_REGISTRY_DIR:-}',
82
+ "registry_dir=$(printf '%s' \"$registry_dir\" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')",
83
+ 'if [ -z "$registry_dir" ]; then',
84
+ ' registry_dir=${HOME:-}',
85
+ ' [ -n "$registry_dir" ] && registry_dir="$registry_dir/.hq"',
86
+ 'fi',
87
+ 'if [ -n "$registry_dir" ]; then',
88
+ ' case "$registry_dir" in /*) ;; *) registry_dir="$PWD/$registry_dir" ;; esac',
89
+ ` if [ -e "$registry_dir/${HQ_ANYWHERE_RUNTIME_REVOCATION_MARKER}" ]; then exec "$real" "$@"; fi`,
90
+ 'fi',
91
+ '',
79
92
  'log_dir="$HOME/.hq/logs"',
80
93
  'mkdir -p "$log_dir" 2>/dev/null || :',
81
94
  'log_file="$log_dir/provider-auth.log"',
@@ -60,6 +60,13 @@ export interface LoopResult {
60
60
  session_id?: string;
61
61
  resumed?: boolean;
62
62
  fresh_fallback?: boolean;
63
+ question_id?: string;
64
+ decision?: {
65
+ question_id: string;
66
+ choice: string;
67
+ notes?: string;
68
+ at: string;
69
+ };
63
70
  }
64
71
  export interface Section8Handoff {
65
72
  schema: typeof PHASE_HANDOFF_SCHEMA;
@@ -103,6 +110,7 @@ export interface LoopRunnerOptions {
103
110
  envelopeId: string;
104
111
  attempt: number;
105
112
  error: string;
113
+ questionId: string;
106
114
  }): Promise<void> | void;
107
115
  defaultDeadlineSeconds?: number;
108
116
  now?: () => Date;
@@ -120,7 +128,6 @@ interface StoredEnvelope {
120
128
  envelope_id: string;
121
129
  queued_at: string;
122
130
  deadline_budget_seconds?: number;
123
- deadline_at_ms?: number;
124
131
  envelope: LoopEnvelope;
125
132
  }
126
133
  /** Validate both hq-cli phase envelopes and the conduct protocol §8 shape. */
@@ -148,6 +155,21 @@ export declare function interruptLoopQueue(runDir: string): {
148
155
  withdrawn: string[];
149
156
  already_picked_up: string[];
150
157
  };
158
+ export declare function interruptLoopQueueForStory(runDir: string, storyId: string, phase?: string): {
159
+ withdrawn: string[];
160
+ already_picked_up: string[];
161
+ };
162
+ export declare function applyLoopStallAnswer(input: {
163
+ runDir: string;
164
+ questionId: string;
165
+ choice: string;
166
+ notes?: string;
167
+ at: string;
168
+ }): {
169
+ lane_id: string;
170
+ envelope_id: string;
171
+ action: "requeued" | "parked";
172
+ } | undefined;
151
173
  export declare function loopStatus(runDir: string, laneId: string): LoopStatus;
152
174
  export declare function setLoopControllerPid(runDir: string, laneId: string, pid: number | undefined): void;
153
175
  export declare function clearLoopControllerPid(runDir: string, laneId: string, pid: number): void;