@indigoai-us/hq-cli 5.325.0 → 5.327.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/bin/hq-auth-refresh.js +2 -3
- package/dist/command-catalog.generated.d.ts +19 -1
- package/dist/command-catalog.generated.js +21 -1
- package/dist/commands/anywhere-runtime-flag.d.ts +2 -2
- package/dist/commands/anywhere-runtime-flag.js +2 -2
- package/dist/commands/auth.js +3 -1
- package/dist/commands/core-detect-stale-review-base-fast.d.ts +2 -0
- package/dist/commands/core-detect-stale-review-base-fast.js +23 -0
- package/dist/commands/core-job-remote-launcher.js +2 -3
- package/dist/commands/core-register-project-native.js +1 -1
- package/dist/commands/core-repo-run-registry-fast.d.ts +2 -0
- package/dist/commands/core-repo-run-registry-fast.js +35 -0
- package/dist/commands/core-rrr-worktree-scope-fix-fast-flag.d.ts +7 -0
- package/dist/commands/core-rrr-worktree-scope-fix-fast-flag.js +25 -0
- package/dist/commands/core-session-journal-native.js +1 -1
- package/dist/commands/core-share-suggestion-state-fast.d.ts +2 -0
- package/dist/commands/core-share-suggestion-state-fast.js +25 -0
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/install-global.d.ts +10 -0
- package/dist/commands/install-global.js +96 -41
- package/dist/commands/logout.js +2 -0
- package/dist/commands/mcp-serve.d.ts +28 -0
- package/dist/commands/mcp-serve.js +165 -0
- package/dist/commands/mcp-status.js +14 -0
- package/dist/fast-core.js +3 -6
- package/dist/fast-scaffold.js +5 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +9 -0
- package/dist/lanes-drain-hook.js +2 -2
- package/dist/lib/anywhere-runtime-fast-flag.d.ts +3 -0
- package/dist/lib/anywhere-runtime-fast-flag.js +23 -0
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +3 -0
- package/dist/lib/daemon/mcp-endpoint.d.ts +8 -0
- package/dist/lib/daemon/mcp-endpoint.js +30 -0
- package/dist/lib/daemon/mcp-oauth.d.ts +24 -0
- package/dist/lib/daemon/mcp-oauth.js +298 -0
- package/dist/lib/daemon/ops/mcp.d.ts +53 -0
- package/dist/lib/daemon/ops/mcp.js +217 -0
- package/dist/lib/daemon/protocol.d.ts +2 -1
- package/dist/lib/daemon/protocol.js +3 -1
- package/dist/lib/daemon/server.d.ts +5 -0
- package/dist/lib/daemon/server.js +78 -11
- package/dist/lib/install/claude-targets.d.ts +11 -4
- package/dist/lib/install/claude-targets.js +21 -9
- package/dist/lib/install/codex-targets.d.ts +8 -4
- package/dist/lib/install/codex-targets.js +18 -9
- package/dist/lib/lanes/watcher-main.js +2 -4
- package/dist/lib/mcp/hosted-search-tools.d.ts +4 -0
- package/dist/lib/mcp/hosted-search-tools.js +28 -0
- package/dist/lib/mcp/hq-mcp-tools.generated.d.ts +3 -0
- package/dist/lib/mcp/hq-mcp-tools.generated.js +538 -0
- package/dist/lib/mcp/prompts.d.ts +30 -0
- package/dist/lib/mcp/prompts.js +48 -0
- package/dist/lib/mcp/tools.d.ts +22 -0
- package/dist/lib/mcp/tools.js +82 -0
- package/dist/lib/mcp/types.d.ts +6 -0
- package/dist/lib/mcp/types.js +2 -0
- package/dist/lib/process-flag-company-context-fast.d.ts +4 -0
- package/dist/lib/process-flag-company-context-fast.js +20 -0
- package/dist/lib/process-flag-company-context-light.d.ts +9 -0
- package/dist/lib/process-flag-company-context-light.js +81 -0
- package/dist/lib/process-flag-company-context.d.ts +2 -4
- package/dist/lib/process-flag-company-context.js +2 -46
- package/dist/utils/hq-roots-core.d.ts +130 -0
- package/dist/utils/hq-roots-core.js +354 -0
- package/dist/utils/hq-roots.d.ts +2 -129
- package/dist/utils/hq-roots.js +2 -353
- package/dist/utils/is-main-module.d.ts +3 -0
- package/dist/utils/is-main-module.js +19 -0
- package/dist/utils/package-root-diagnostics.d.ts +5 -36
- package/dist/utils/package-root-diagnostics.js +4 -83
- package/dist/utils/package-root-resolution-error.d.ts +32 -0
- package/dist/utils/package-root-resolution-error.js +79 -0
- package/dist/utils/staged-npm-install.js +2 -1
- package/package.json +3 -1
|
@@ -22,10 +22,12 @@ export const HQD_OPS = [
|
|
|
22
22
|
"memory.get",
|
|
23
23
|
"memory.put",
|
|
24
24
|
"mesh.presence",
|
|
25
|
+
"mcp.call",
|
|
25
26
|
"shutdown",
|
|
26
27
|
];
|
|
27
28
|
/** A request line larger than this is rejected and the connection closed. */
|
|
28
29
|
export const HQD_MAX_LINE_BYTES = 1024 * 1024;
|
|
30
|
+
export const HQD_DEFAULT_REQUEST_TIMEOUT_MS = 2_000;
|
|
29
31
|
/**
|
|
30
32
|
* Socket path: HQ_HQD_SOCKET, else <HQ_REGISTRY_DIR or ~/.hq>/hqd.sock.
|
|
31
33
|
* On Windows the socket is a per-user named pipe.
|
|
@@ -115,7 +117,7 @@ let nextClientId = 0;
|
|
|
115
117
|
*/
|
|
116
118
|
export function hqdRequest(op, args = {}, opts = {}) {
|
|
117
119
|
const socketPath = opts.socketPath ?? hqdSocketPath();
|
|
118
|
-
const timeoutMs = opts.timeoutMs ??
|
|
120
|
+
const timeoutMs = opts.timeoutMs ?? HQD_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
119
121
|
const id = `c${process.pid}-${(nextClientId += 1)}`;
|
|
120
122
|
return new Promise((resolve, reject) => {
|
|
121
123
|
const socket = net.createConnection(socketPath);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type ResolveResult } from "../registry/registry.js";
|
|
10
10
|
import { SessionRegistry } from "./session-registry.js";
|
|
11
|
+
import { HqMcpBridge } from "./ops/mcp.js";
|
|
11
12
|
export interface HqdServerOptions {
|
|
12
13
|
socketPath: string;
|
|
13
14
|
/** registry.json to resolve against (US-004). */
|
|
@@ -22,6 +23,10 @@ export interface HqdServerOptions {
|
|
|
22
23
|
env?: NodeJS.ProcessEnv;
|
|
23
24
|
/** Work-mesh root override for mesh.presence (tests). */
|
|
24
25
|
workMeshRoot?: string;
|
|
26
|
+
/** Test seam for the hq-anywhere-runtime default-off gate. */
|
|
27
|
+
mcpEnabled?: () => boolean | Promise<boolean>;
|
|
28
|
+
/** Test seam for the hosted Streamable HTTP MCP transport. */
|
|
29
|
+
mcpBridge?: HqMcpBridge;
|
|
25
30
|
/** Crash reporter for internal op failures (US-012); run.ts wires Sentry here. */
|
|
26
31
|
reportError?: (op: string, err: unknown) => void;
|
|
27
32
|
}
|
|
@@ -11,12 +11,15 @@ import * as net from "node:net";
|
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { CLI_VERSION } from "../../cli-version.js";
|
|
13
13
|
import { canonicalDir, readRegistry, registryFile, resolveCompanyForDir } from "../registry/registry.js";
|
|
14
|
-
import { createLineSplitter, encodeMessage, parseRequestLine } from "./protocol.js";
|
|
14
|
+
import { createLineSplitter, encodeMessage, HQD_DEFAULT_REQUEST_TIMEOUT_MS, HQD_MAX_LINE_BYTES, parseRequestLine } from "./protocol.js";
|
|
15
15
|
import { SessionRegistry } from "./session-registry.js";
|
|
16
16
|
import { journalAppend, JournalOpError } from "./ops/journal.js";
|
|
17
17
|
import { memoryGet, memoryPut, MemoryOpError } from "./ops/memory.js";
|
|
18
18
|
import { meshPresence, MeshOpError } from "./ops/mesh.js";
|
|
19
19
|
import { PolicyIndex, PolicyOpError, policyCheck } from "./ops/policy.js";
|
|
20
|
+
import { HqMcpBridge, jsonResult, McpOpError, runWorker } from "./ops/mcp.js";
|
|
21
|
+
import { HQ_MCP_TOOL_NAMES, HQD_NATIVE_TOOL_NAMES } from "../mcp/tools.js";
|
|
22
|
+
import { anywhereRuntimeEnabled, HQ_ANYWHERE_RUNTIME_FLAG } from "../../commands/anywhere-runtime-flag.js";
|
|
20
23
|
class OpError extends Error {
|
|
21
24
|
code;
|
|
22
25
|
constructor(code, message) {
|
|
@@ -87,19 +90,28 @@ export async function startHqdServer(opts) {
|
|
|
87
90
|
throw new OpError("no_hq_root", "hqd has no HQ root configured (set hqRoot in daemon config or HQ_ROOT)");
|
|
88
91
|
return opts.hqRoot;
|
|
89
92
|
};
|
|
90
|
-
|
|
93
|
+
const assertCompanyMatchesBinding = (explicit, bound) => {
|
|
94
|
+
if (explicit && bound && explicit !== bound) {
|
|
95
|
+
throw new OpError("company_mismatch", `explicit company ${explicit} conflicts with bound company ${bound}`);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/** Resolve a call's company while keeping any session or cwd binding authoritative. */
|
|
91
99
|
const companyFor = (args) => {
|
|
92
100
|
const explicit = stringArg(args, "company", false);
|
|
93
|
-
if (explicit)
|
|
94
|
-
return explicit;
|
|
95
101
|
const runtime = stringArg(args, "runtime", false);
|
|
96
102
|
const sessionId = stringArg(args, "sessionId", false);
|
|
97
103
|
const session = runtime && sessionId ? sessions.get(runtime, sessionId) : null;
|
|
98
|
-
if (session?.company)
|
|
99
|
-
return session.company;
|
|
100
104
|
const cwd = stringArg(args, "cwd", false);
|
|
101
|
-
|
|
105
|
+
const cwdCompany = cwd ? (resolve(cwd).company ?? undefined) : undefined;
|
|
106
|
+
if (session?.company && cwdCompany && session.company !== cwdCompany) {
|
|
107
|
+
throw new OpError("company_mismatch", `session company ${session.company} conflicts with cwd-bound company ${cwdCompany}`);
|
|
108
|
+
}
|
|
109
|
+
const boundCompany = session?.company ?? cwdCompany;
|
|
110
|
+
assertCompanyMatchesBinding(explicit, boundCompany);
|
|
111
|
+
return explicit ?? boundCompany;
|
|
102
112
|
};
|
|
113
|
+
let hqMcp = opts.mcpBridge;
|
|
114
|
+
const mcpEnabled = opts.mcpEnabled ?? (() => anywhereRuntimeEnabled(env));
|
|
103
115
|
if (!isPipe) {
|
|
104
116
|
fs.mkdirSync(path.dirname(socketPath), { recursive: true, mode: 0o700 });
|
|
105
117
|
if (fs.existsSync(socketPath)) {
|
|
@@ -108,7 +120,7 @@ export async function startHqdServer(opts) {
|
|
|
108
120
|
fs.unlinkSync(socketPath);
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
|
-
/** Synchronous ops answer in request order;
|
|
123
|
+
/** Synchronous ops answer in request order; policy.check and mcp.call may return a promise. */
|
|
112
124
|
const handle = (req) => {
|
|
113
125
|
const args = req.args ?? {};
|
|
114
126
|
switch (req.op) {
|
|
@@ -129,7 +141,9 @@ export async function startHqdServer(opts) {
|
|
|
129
141
|
const sessionId = stringArg(args, "sessionId");
|
|
130
142
|
const cwd = stringArg(args, "cwd");
|
|
131
143
|
const explicit = stringArg(args, "company", false);
|
|
132
|
-
const
|
|
144
|
+
const boundCompany = resolve(cwd).company;
|
|
145
|
+
assertCompanyMatchesBinding(explicit, boundCompany ?? undefined);
|
|
146
|
+
const company = explicit ?? boundCompany;
|
|
133
147
|
return sessions.open({ runtime, sessionId, cwd: canonicalDir(cwd), company });
|
|
134
148
|
}
|
|
135
149
|
case "session.get": {
|
|
@@ -197,6 +211,57 @@ export async function startHqdServer(opts) {
|
|
|
197
211
|
reason: stringArg(args, "reason", false),
|
|
198
212
|
}, { hqRoot: opts.hqRoot, workMeshRoot: opts.workMeshRoot, env, now: clock });
|
|
199
213
|
}
|
|
214
|
+
case "mcp.call": {
|
|
215
|
+
const name = stringArg(args, "name");
|
|
216
|
+
const raw = args.arguments ?? {};
|
|
217
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
218
|
+
throw new OpError("bad_args", "arguments must be an object");
|
|
219
|
+
// Preserve the tool cwd for its operation; caller cwd remains the tenant binding.
|
|
220
|
+
const toolArgs = { ...(args.cwd !== undefined ? { cwd: args.cwd } : {}), ...raw };
|
|
221
|
+
const companyArgs = { ...toolArgs, ...(args.cwd !== undefined ? { cwd: args.cwd } : {}) };
|
|
222
|
+
delete companyArgs.runtime;
|
|
223
|
+
delete companyArgs.sessionId;
|
|
224
|
+
const runtime = stringArg(args, "runtime", false);
|
|
225
|
+
const sessionId = stringArg(args, "sessionId", false);
|
|
226
|
+
if (runtime !== undefined)
|
|
227
|
+
companyArgs.runtime = runtime;
|
|
228
|
+
if (sessionId !== undefined)
|
|
229
|
+
companyArgs.sessionId = sessionId;
|
|
230
|
+
const call = (op, a) => handle({ id: req.id, op, args: a });
|
|
231
|
+
const asResult = (value) => (value instanceof Promise ? value.then(jsonResult) : jsonResult(value));
|
|
232
|
+
switch (name) {
|
|
233
|
+
case "hq_resolve_company":
|
|
234
|
+
return asResult(call("resolve", { cwd: toolArgs.cwd ?? process.cwd() }));
|
|
235
|
+
case "hq_policy_check":
|
|
236
|
+
return asResult(call("policy.check", companyArgs));
|
|
237
|
+
case "hq_journal_append":
|
|
238
|
+
return asResult(call("journal.append", companyArgs));
|
|
239
|
+
case "hq_run_worker": {
|
|
240
|
+
const tool = companyArgs;
|
|
241
|
+
return runWorker({
|
|
242
|
+
worker: stringArg(tool, "worker"),
|
|
243
|
+
skill: stringArg(tool, "skill", false),
|
|
244
|
+
args: typeof tool.args === "string" ? tool.args : undefined,
|
|
245
|
+
company: companyFor(tool),
|
|
246
|
+
}, requireRoot(), env, clock()).then(jsonResult);
|
|
247
|
+
}
|
|
248
|
+
default: {
|
|
249
|
+
if (HQD_NATIVE_TOOL_NAMES.has(name) || !HQ_MCP_TOOL_NAMES.has(name))
|
|
250
|
+
throw new OpError("unknown_tool", `unknown tool ${name}`);
|
|
251
|
+
companyFor(companyArgs);
|
|
252
|
+
const timeoutMs = typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs)
|
|
253
|
+
? Math.max(1, Math.min(args.timeoutMs, 120_000))
|
|
254
|
+
: HQD_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
255
|
+
return Promise.resolve(mcpEnabled()).then((enabled) => {
|
|
256
|
+
if (!enabled) {
|
|
257
|
+
throw new McpOpError("hq_mcp_unavailable", `Hosted hq-mcp forwarding is disabled by the ${HQ_ANYWHERE_RUNTIME_FLAG} flag.`);
|
|
258
|
+
}
|
|
259
|
+
hqMcp ??= new HqMcpBridge({ env });
|
|
260
|
+
return hqMcp.callTool(name, raw, timeoutMs);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
200
265
|
case "shutdown":
|
|
201
266
|
return { stopping: true };
|
|
202
267
|
default:
|
|
@@ -231,7 +296,8 @@ export async function startHqdServer(opts) {
|
|
|
231
296
|
err instanceof PolicyOpError ||
|
|
232
297
|
err instanceof JournalOpError ||
|
|
233
298
|
err instanceof MemoryOpError ||
|
|
234
|
-
err instanceof MeshOpError
|
|
299
|
+
err instanceof MeshOpError ||
|
|
300
|
+
err instanceof McpOpError
|
|
235
301
|
? { code: err.code, message: err.message }
|
|
236
302
|
: { code: "internal", message: err instanceof Error ? err.message : String(err) };
|
|
237
303
|
if (error.code === "internal") {
|
|
@@ -260,7 +326,7 @@ export async function startHqdServer(opts) {
|
|
|
260
326
|
}, () => {
|
|
261
327
|
respond(socket, { id: null, ok: false, error: { code: "too_large", message: "request line too large" } });
|
|
262
328
|
socket.end();
|
|
263
|
-
});
|
|
329
|
+
}, HQD_MAX_LINE_BYTES);
|
|
264
330
|
socket.on("data", push);
|
|
265
331
|
});
|
|
266
332
|
await new Promise((resolveListen, rejectListen) => {
|
|
@@ -287,6 +353,7 @@ export async function startHqdServer(opts) {
|
|
|
287
353
|
sessions,
|
|
288
354
|
close: () => (closing ??= new Promise((done) => {
|
|
289
355
|
policyIndex?.close();
|
|
356
|
+
hqMcp?.close();
|
|
290
357
|
server.close(() => done());
|
|
291
358
|
// Answered-but-idle clients would otherwise hold close() open.
|
|
292
359
|
for (const client of clients)
|
|
@@ -21,10 +21,14 @@ export interface ClaudeTargets {
|
|
|
21
21
|
}
|
|
22
22
|
export declare function claudeTargets(home: string): ClaudeTargets;
|
|
23
23
|
export declare function masterHookPath(hqRoot: string): string;
|
|
24
|
+
/** hqd hook shim (US-010): registered instead of master-hook.sh once hqd is installed. */
|
|
25
|
+
export declare function shimPath(hqRoot: string): string;
|
|
26
|
+
/** Which HQ hook script the user-scope install registers. */
|
|
27
|
+
export type HookTarget = 'master' | 'shim';
|
|
24
28
|
export declare function charterPath(hqRoot: string): string;
|
|
25
29
|
export declare function hqSkillsSource(hqRoot: string): string;
|
|
26
30
|
/** The exact hook command registered for one event (absolute path, quoted). */
|
|
27
|
-
export declare function hookCommand(hqRoot: string, event: string): string;
|
|
31
|
+
export declare function hookCommand(hqRoot: string, event: string, target?: HookTarget): string;
|
|
28
32
|
export declare function claudeMdBlock(hqRoot: string): string;
|
|
29
33
|
/** Append the block (replacing any prior HQ block). `null` input = file absent. */
|
|
30
34
|
export declare function addClaudeMdBlock(current: string | null, hqRoot: string): string;
|
|
@@ -32,11 +36,14 @@ export declare function addClaudeMdBlock(current: string | null, hqRoot: string)
|
|
|
32
36
|
export declare function removeClaudeMdBlock(current: string): string;
|
|
33
37
|
export declare function hasClaudeMdBlock(current: string | null): boolean;
|
|
34
38
|
type Doc = Record<string, unknown>;
|
|
35
|
-
/**
|
|
36
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Add one HQ hook group per event; idempotent. An HQ entry for the other
|
|
41
|
+
* target (master-hook.sh vs the hqd shim) is swapped out. Foreign hooks are untouched.
|
|
42
|
+
*/
|
|
43
|
+
export declare function addHqHooks(doc: Doc, hqRoot: string, target?: HookTarget): Doc;
|
|
37
44
|
/** Remove only HQ master-hook entries; drop containers this left empty. */
|
|
38
45
|
export declare function removeHqHooks(doc: Doc, hqRoot: string): Doc;
|
|
39
|
-
export declare function hasAllHqHooks(doc: Doc, hqRoot: string): boolean;
|
|
46
|
+
export declare function hasAllHqHooks(doc: Doc, hqRoot: string, target?: HookTarget): boolean;
|
|
40
47
|
/**
|
|
41
48
|
* Minimal line diff (LCS) with -/+ prefixes. Only changed lines and `context`
|
|
42
49
|
* unchanged lines around them are shown, so a dry run never echoes a whole
|
|
@@ -37,6 +37,10 @@ export function claudeTargets(home) {
|
|
|
37
37
|
export function masterHookPath(hqRoot) {
|
|
38
38
|
return path.join(hqRoot, '.claude', 'hooks', 'master-hook.sh');
|
|
39
39
|
}
|
|
40
|
+
/** hqd hook shim (US-010): registered instead of master-hook.sh once hqd is installed. */
|
|
41
|
+
export function shimPath(hqRoot) {
|
|
42
|
+
return path.join(hqRoot, 'core', 'scripts', 'hqd-hook-shim.sh');
|
|
43
|
+
}
|
|
40
44
|
export function charterPath(hqRoot) {
|
|
41
45
|
return path.join(hqRoot, '.claude', 'CLAUDE.md');
|
|
42
46
|
}
|
|
@@ -44,7 +48,9 @@ export function hqSkillsSource(hqRoot) {
|
|
|
44
48
|
return path.join(hqRoot, '.claude', 'skills');
|
|
45
49
|
}
|
|
46
50
|
/** The exact hook command registered for one event (absolute path, quoted). */
|
|
47
|
-
export function hookCommand(hqRoot, event) {
|
|
51
|
+
export function hookCommand(hqRoot, event, target = 'master') {
|
|
52
|
+
if (target === 'shim')
|
|
53
|
+
return `/bin/sh ${JSON.stringify(shimPath(hqRoot))} ${event} --runtime claude`;
|
|
48
54
|
return `bash ${JSON.stringify(masterHookPath(hqRoot))} ${event}`;
|
|
49
55
|
}
|
|
50
56
|
// ---------------------------------------------------------------------------
|
|
@@ -86,18 +92,24 @@ function isHqHookEntry(entry, hqRoot) {
|
|
|
86
92
|
if (!entry || typeof entry !== 'object')
|
|
87
93
|
return false;
|
|
88
94
|
const cmd = entry.command;
|
|
89
|
-
return typeof cmd === 'string' &&
|
|
95
|
+
return (typeof cmd === 'string' &&
|
|
96
|
+
(cmd.includes(JSON.stringify(masterHookPath(hqRoot))) || cmd.includes(JSON.stringify(shimPath(hqRoot)))));
|
|
90
97
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Add one HQ hook group per event; idempotent. An HQ entry for the other
|
|
100
|
+
* target (master-hook.sh vs the hqd shim) is swapped out. Foreign hooks are untouched.
|
|
101
|
+
*/
|
|
102
|
+
export function addHqHooks(doc, hqRoot, target = 'master') {
|
|
93
103
|
const hooks = { ...(doc.hooks ?? {}) };
|
|
94
104
|
let changed = false;
|
|
95
105
|
for (const event of GLOBAL_HOOK_EVENTS) {
|
|
96
|
-
const
|
|
97
|
-
|
|
106
|
+
const want = hookCommand(hqRoot, event, target);
|
|
107
|
+
let groups = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
108
|
+
const present = groups.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => h.command === want));
|
|
98
109
|
if (present)
|
|
99
110
|
continue;
|
|
100
|
-
groups
|
|
111
|
+
groups = removeHqHooks({ hooks: { [event]: groups } }, hqRoot).hooks?.[event] ?? [];
|
|
112
|
+
groups.push({ hooks: [{ type: 'command', command: want, timeout: 300 }] });
|
|
101
113
|
hooks[event] = groups;
|
|
102
114
|
changed = true;
|
|
103
115
|
}
|
|
@@ -141,8 +153,8 @@ export function removeHqHooks(doc, hqRoot) {
|
|
|
141
153
|
next.hooks = hooks;
|
|
142
154
|
return next;
|
|
143
155
|
}
|
|
144
|
-
export function hasAllHqHooks(doc, hqRoot) {
|
|
145
|
-
return addHqHooks(doc, hqRoot) === doc;
|
|
156
|
+
export function hasAllHqHooks(doc, hqRoot, target = 'master') {
|
|
157
|
+
return addHqHooks(doc, hqRoot, target) === doc;
|
|
146
158
|
}
|
|
147
159
|
// ---------------------------------------------------------------------------
|
|
148
160
|
// Dry-run diff.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type HookTarget } from './claude-targets.js';
|
|
1
2
|
/** Events the Codex hook adapter is registered for at user scope. */
|
|
2
3
|
export declare const CODEX_HOOK_EVENTS: readonly ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
|
|
3
4
|
export declare const AGENTS_MD_BEGIN = "<!-- hq-anywhere:begin (managed by `hq install --global --runtime codex`; remove with `hq uninstall --global --runtime codex`) -->";
|
|
@@ -24,18 +25,21 @@ export declare function codexCharterSource(hqRoot: string): string;
|
|
|
24
25
|
* Hook command for every event. The adapter reads the event from
|
|
25
26
|
* `hook_event_name` and falls back to its own $PWD when Codex sends no `cwd`.
|
|
26
27
|
*/
|
|
27
|
-
export declare function codexHookCommand(hqRoot: string): string;
|
|
28
|
+
export declare function codexHookCommand(hqRoot: string, target?: HookTarget): string;
|
|
28
29
|
export declare function agentsMdBlock(charter: string): string;
|
|
29
30
|
/** Append the block (replacing any prior HQ block). `null` input = file absent. */
|
|
30
31
|
export declare function addAgentsMdBlock(current: string | null, charter: string): string;
|
|
31
32
|
/** Strip the HQ block, plus the separator `addAgentsMdBlock` put before it. */
|
|
32
33
|
export declare function removeAgentsMdBlock(current: string): string;
|
|
33
34
|
type Doc = Record<string, unknown>;
|
|
34
|
-
/**
|
|
35
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Add one HQ hook group per event; idempotent. An HQ entry for the other
|
|
37
|
+
* target (adapter vs the hqd shim) is swapped out. Foreign hooks are untouched.
|
|
38
|
+
*/
|
|
39
|
+
export declare function addCodexHooks(doc: Doc, hqRoot: string, target?: HookTarget): Doc;
|
|
36
40
|
/** Remove only HQ adapter entries; drop containers this left empty. */
|
|
37
41
|
export declare function removeCodexHooks(doc: Doc, hqRoot: string): Doc;
|
|
38
|
-
export declare function hasAllCodexHooks(doc: Doc, hqRoot: string): boolean;
|
|
42
|
+
export declare function hasAllCodexHooks(doc: Doc, hqRoot: string, target?: HookTarget): boolean;
|
|
39
43
|
export type LegacySkillsMirror = {
|
|
40
44
|
kind: 'absent';
|
|
41
45
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as fs from 'fs';
|
|
13
13
|
import * as path from 'path';
|
|
14
|
+
import { shimPath } from './claude-targets.js';
|
|
14
15
|
/** Events the Codex hook adapter is registered for at user scope. */
|
|
15
16
|
export const CODEX_HOOK_EVENTS = [
|
|
16
17
|
'SessionStart',
|
|
@@ -50,7 +51,9 @@ export function codexCharterSource(hqRoot) {
|
|
|
50
51
|
* Hook command for every event. The adapter reads the event from
|
|
51
52
|
* `hook_event_name` and falls back to its own $PWD when Codex sends no `cwd`.
|
|
52
53
|
*/
|
|
53
|
-
export function codexHookCommand(hqRoot) {
|
|
54
|
+
export function codexHookCommand(hqRoot, target = 'master') {
|
|
55
|
+
if (target === 'shim')
|
|
56
|
+
return `/bin/sh ${JSON.stringify(shimPath(hqRoot))} --runtime codex`;
|
|
54
57
|
return `/bin/bash ${JSON.stringify(codexAdapterPath(hqRoot))}`;
|
|
55
58
|
}
|
|
56
59
|
// ---------------------------------------------------------------------------
|
|
@@ -88,18 +91,24 @@ function isAdapterEntry(entry, hqRoot) {
|
|
|
88
91
|
if (!entry || typeof entry !== 'object')
|
|
89
92
|
return false;
|
|
90
93
|
const cmd = entry.command;
|
|
91
|
-
return typeof cmd === 'string' &&
|
|
94
|
+
return (typeof cmd === 'string' &&
|
|
95
|
+
(cmd.includes(JSON.stringify(codexAdapterPath(hqRoot))) || cmd.includes(JSON.stringify(shimPath(hqRoot)))));
|
|
92
96
|
}
|
|
93
|
-
/**
|
|
94
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Add one HQ hook group per event; idempotent. An HQ entry for the other
|
|
99
|
+
* target (adapter vs the hqd shim) is swapped out. Foreign hooks are untouched.
|
|
100
|
+
*/
|
|
101
|
+
export function addCodexHooks(doc, hqRoot, target = 'master') {
|
|
95
102
|
const hooks = { ...(doc.hooks ?? {}) };
|
|
96
103
|
let changed = false;
|
|
104
|
+
const want = codexHookCommand(hqRoot, target);
|
|
97
105
|
for (const event of CODEX_HOOK_EVENTS) {
|
|
98
|
-
|
|
99
|
-
const present = groups.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) =>
|
|
106
|
+
let groups = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
|
|
107
|
+
const present = groups.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => h.command === want));
|
|
100
108
|
if (present)
|
|
101
109
|
continue;
|
|
102
|
-
groups
|
|
110
|
+
groups = removeCodexHooks({ hooks: { [event]: groups } }, hqRoot).hooks?.[event] ?? [];
|
|
111
|
+
groups.push({ hooks: [{ type: 'command', command: want, timeout: 300 }] });
|
|
103
112
|
hooks[event] = groups;
|
|
104
113
|
changed = true;
|
|
105
114
|
}
|
|
@@ -143,8 +152,8 @@ export function removeCodexHooks(doc, hqRoot) {
|
|
|
143
152
|
next.hooks = hooks;
|
|
144
153
|
return next;
|
|
145
154
|
}
|
|
146
|
-
export function hasAllCodexHooks(doc, hqRoot) {
|
|
147
|
-
return addCodexHooks(doc, hqRoot) === doc;
|
|
155
|
+
export function hasAllCodexHooks(doc, hqRoot, target = 'master') {
|
|
156
|
+
return addCodexHooks(doc, hqRoot, target) === doc;
|
|
148
157
|
}
|
|
149
158
|
function within(child, parent) {
|
|
150
159
|
const rel = path.relative(parent, child);
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* Requires HQ_ROOT in the environment.
|
|
7
7
|
*/
|
|
8
8
|
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
-
import { existsSync, realpathSync } from "node:fs";
|
|
10
9
|
import * as path from "node:path";
|
|
11
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { isMainModule } from "../../utils/is-main-module.js";
|
|
12
12
|
import { runWatcherLoop } from "./watcher.js";
|
|
13
13
|
import { spawnEnvelopeRepairViaCurrentCli, } from "./envelope-repair-dispatch.js";
|
|
14
14
|
import { LanesError } from "./errors.js";
|
|
@@ -100,9 +100,7 @@ function main() {
|
|
|
100
100
|
process.exit(1);
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
const currentModule = realpathSync(fileURLToPath(import.meta.url));
|
|
105
|
-
if (invokedEntryPath && existsSync(invokedEntryPath) && realpathSync(invokedEntryPath) === currentModule) {
|
|
103
|
+
if (isMainModule(import.meta.url)) {
|
|
106
104
|
main();
|
|
107
105
|
}
|
|
108
106
|
//# sourceMappingURL=watcher-main.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** OpenAI-compatible tools the cloud Lambda adds in place of hq_knowledge_search. */
|
|
2
|
+
export const HOSTED_SEARCH_FETCH_TOOLS = [
|
|
3
|
+
{
|
|
4
|
+
name: "search",
|
|
5
|
+
description: "Search the authenticated user's chat-visible HQ knowledge, files, projects, policies, and skills. Returns ranked document references across every company the user is a member of and their personal vault. Use the returned `id` with `fetch` to retrieve the full document body.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
query: { type: "string", minLength: 1, description: "Natural-language search query" },
|
|
10
|
+
},
|
|
11
|
+
required: ["query"],
|
|
12
|
+
additionalProperties: false,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "fetch",
|
|
17
|
+
description: "Retrieve the full body of a document returned by `search`. Enforces the same access scope as `search` — an id from a vault the caller no longer has access to is rejected. Skill bodies are returned as document text, not executable local tools.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
id: { type: "string", minLength: 1, description: "An `id` from a prior `search` result" },
|
|
22
|
+
},
|
|
23
|
+
required: ["id"],
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
//# sourceMappingURL=hosted-search-tools.js.map
|