@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.
- package/CHANGELOG.md +17 -0
- package/dist/command-catalog.generated.d.ts +8 -0
- package/dist/command-catalog.generated.js +10 -0
- package/dist/commands/agent-kit.js +20 -10
- package/dist/commands/billing.d.ts +13 -0
- package/dist/commands/billing.js +31 -0
- package/dist/commands/db-dump.js +2 -2
- package/dist/commands/db-remote-deps.js +6 -0
- package/dist/commands/lanes-loop.js +3 -1
- package/dist/commands/lanes-questions.js +16 -0
- package/dist/commands/lanes-session-start.js +19 -3
- package/dist/commands/lanes-shared.d.ts +1 -0
- package/dist/commands/lanes.d.ts +3 -1
- package/dist/commands/lanes.js +52 -2
- package/dist/lib/agent-kit/inbox-poll-backoff-flag.d.ts +19 -0
- package/dist/lib/agent-kit/inbox-poll-backoff-flag.js +70 -0
- package/dist/lib/agent-kit/run/inbox.d.ts +7 -3
- package/dist/lib/agent-kit/run/inbox.js +45 -6
- package/dist/lib/agent-kit/run/stop-signals.d.ts +14 -0
- package/dist/lib/agent-kit/run/stop-signals.js +26 -0
- package/dist/lib/agent-kit/services.d.ts +2 -2
- package/dist/lib/agent-kit/services.js +5 -3
- package/dist/lib/anywhere-runtime-flag-key.d.ts +1 -0
- package/dist/lib/anywhere-runtime-flag-key.js +1 -0
- package/dist/lib/anywhere-runtime-revocation-marker.d.ts +4 -0
- package/dist/lib/anywhere-runtime-revocation-marker.js +26 -0
- package/dist/lib/daemon/run.d.ts +9 -0
- package/dist/lib/daemon/run.js +42 -5
- package/dist/lib/daemon/server.d.ts +11 -0
- package/dist/lib/daemon/server.js +58 -0
- package/dist/lib/db/control-plane.d.ts +12 -1
- package/dist/lib/db/usage-format.d.ts +2 -1
- package/dist/lib/db/usage-format.js +19 -0
- package/dist/lib/install/provider-wrappers.js +13 -0
- package/dist/lib/lanes/loop.d.ts +23 -1
- package/dist/lib/lanes/loop.js +134 -19
- package/dist/lib/lanes/questions.d.ts +5 -0
- package/dist/lib/lanes/reconcile.js +3 -0
- package/dist/lib/lanes/story-ledger.js +30 -20
- package/dist/lib/lanes/types.js +1 -1
- 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
|
package/dist/lib/daemon/run.d.ts
CHANGED
|
@@ -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 {
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -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
|
-
//
|
|
963
|
-
|
|
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
|
|
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"',
|
package/dist/lib/lanes/loop.d.ts
CHANGED
|
@@ -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;
|