@indigoai-us/hq-cli 5.306.0 → 5.308.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 +10 -0
- package/assets/scaffold/core/scripts/audit-log.sh +384 -0
- package/assets/scaffold/core/scripts/git-pack-extension-check.sh +164 -0
- package/assets/scaffold/core/scripts/hq-pin.sh +118 -0
- package/assets/scaffold/core/scripts/lib/portable.sh +211 -0
- package/assets/scaffold/core/scripts/refresh-vault-access.sh +188 -0
- package/assets/scaffold/core/scripts/resolve-company.sh +199 -0
- package/dist/command-catalog.generated.d.ts +65 -0
- package/dist/command-catalog.generated.js +80 -0
- package/dist/commands/core-audit-log-native.d.ts +9 -0
- package/dist/commands/core-audit-log-native.js +257 -0
- package/dist/commands/core-git-pack-extension-check-native.d.ts +22 -0
- package/dist/commands/core-git-pack-extension-check-native.js +190 -0
- package/dist/commands/core-native-utilities-flag.d.ts +4 -0
- package/dist/commands/core-native-utilities-flag.js +7 -0
- package/dist/commands/core-native-utilities.d.ts +28 -0
- package/dist/commands/core-native-utilities.js +52 -0
- package/dist/commands/core-pin-native.d.ts +14 -0
- package/dist/commands/core-pin-native.js +236 -0
- package/dist/commands/core-refresh-vault-access-native.d.ts +20 -0
- package/dist/commands/core-refresh-vault-access-native.js +229 -0
- package/dist/commands/core-resolve-company-command.d.ts +4 -0
- package/dist/commands/core-resolve-company-command.js +42 -0
- package/dist/commands/core-resolve-company-native.d.ts +28 -0
- package/dist/commands/core-resolve-company-native.js +408 -0
- package/dist/commands/core.js +18 -0
- package/dist/commands/dm.js +6 -1
- package/dist/commands/lanes.js +43 -1
- package/dist/fast-scaffold.js +12 -0
- package/dist/lib/agent-kit/fallback.d.ts +0 -7
- package/dist/lib/agent-kit/fallback.js +4 -4
- package/dist/lib/agent-kit/inbox-state.d.ts +0 -2
- package/dist/lib/agent-kit/inbox-state.js +2 -2
- package/dist/lib/agent-kit/kit-config.d.ts +0 -5
- package/dist/lib/agent-kit/kit-config.js +5 -5
- package/dist/lib/agent-kit/log.d.ts +0 -1
- package/dist/lib/agent-kit/log.js +1 -1
- package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +3 -12
- package/dist/lib/agent-kit/mcp/jsonrpc.js +4 -4
- package/dist/lib/agent-kit/mcp/tools.d.ts +0 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -2
- package/dist/lib/agent-kit/services.d.ts +0 -1
- package/dist/lib/agent-kit/services.js +1 -1
- package/dist/lib/checkpoint-ontology/apply.d.ts +0 -49
- package/dist/lib/checkpoint-ontology/apply.js +5 -5
- package/dist/lib/checkpoint-ontology/identity.d.ts +2 -1
- package/dist/lib/checkpoint-ontology/identity.js +1 -1
- package/dist/lib/checkpoint-ontology/multi-writer.d.ts +4 -9
- package/dist/lib/checkpoint-ontology/multi-writer.js +6 -6
- package/dist/lib/checkpoint-ontology/plan.d.ts +4 -17
- package/dist/lib/checkpoint-ontology/plan.js +5 -5
- package/dist/lib/checkpoint-ontology/queued-touches.d.ts +0 -3
- package/dist/lib/checkpoint-ontology/queued-touches.js +3 -3
- package/dist/lib/db/control-plane.js +6 -0
- package/dist/lib/deploy-apps.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/** Native port of resolve-company.sh. This module stays on the fast import path. */
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
export const RESOLVE_COMPANY_ASSET = "core/scripts/resolve-company.sh";
|
|
6
|
+
export const RESOLVE_COMPANY_HELP = [
|
|
7
|
+
"hq-core: public",
|
|
8
|
+
"resolve-company.sh — resolve the active company for skills and routing.",
|
|
9
|
+
"",
|
|
10
|
+
"Resolution order (first hit wins): explicit manifest slug in the prompt,",
|
|
11
|
+
"session company_slug, enabled device default, then none. Prompt matching is",
|
|
12
|
+
"whole-token only; free text never guesses a company.",
|
|
13
|
+
"",
|
|
14
|
+
"Output: {\"company\":\"<slug>\",\"source\":\"prompt|session|device_default|none\"}",
|
|
15
|
+
"Always exits 0 and never writes state.",
|
|
16
|
+
"",
|
|
17
|
+
"set -uo pipefail",
|
|
18
|
+
"",
|
|
19
|
+
"ROOT=\"\"",
|
|
20
|
+
].join("\n") + "\n";
|
|
21
|
+
const EMPTY_ENV = {};
|
|
22
|
+
const keepSlug = (slug) => slug !== "" && slug !== "_template" && slug !== "companies" && slug !== "unaffiliated_repos";
|
|
23
|
+
function errorCode(error) {
|
|
24
|
+
return error && typeof error === "object" && "code" in error && typeof error.code === "string"
|
|
25
|
+
? error.code
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
function readFile(filePath, fileSystem) {
|
|
29
|
+
try {
|
|
30
|
+
return fileSystem.readFileSync(filePath, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const code = errorCode(error);
|
|
34
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "ELOOP")
|
|
35
|
+
return undefined;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function readManifest(filePath, fileSystem) {
|
|
40
|
+
try {
|
|
41
|
+
return fileSystem.readFileSync(filePath, "utf8");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
const code = errorCode(error);
|
|
45
|
+
if (code === "EACCES") {
|
|
46
|
+
return {
|
|
47
|
+
code: 0,
|
|
48
|
+
stdout: Buffer.from('{"company":"","source":"none"}\n'),
|
|
49
|
+
stderr: Buffer.from("awk: fatal: cannot open file `" + filePath + "' for reading: Permission denied\n"),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP")
|
|
53
|
+
return output("", "none");
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isFile(filePath, fileSystem) {
|
|
58
|
+
try {
|
|
59
|
+
return fileSystem.statSync(filePath).isFile();
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const code = errorCode(error);
|
|
63
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "ELOOP")
|
|
64
|
+
return false;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function parseManifestSlugs(text) {
|
|
69
|
+
const slugs = new Set();
|
|
70
|
+
let wrapped = false;
|
|
71
|
+
for (const rawLine of text.split("\n")) {
|
|
72
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
73
|
+
if (/^companies:[ \t]*$/.test(line)) {
|
|
74
|
+
wrapped = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (wrapped && /^[^ \t][^:]*:[ \t]*$/.test(line))
|
|
78
|
+
wrapped = false;
|
|
79
|
+
if (wrapped) {
|
|
80
|
+
const match = line.match(/^ {2}([A-Za-z][A-Za-z0-9_-]*):/);
|
|
81
|
+
if (match && keepSlug(match[1]))
|
|
82
|
+
slugs.add(match[1]);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const match = line.match(/^([A-Za-z][A-Za-z0-9_-]*):/);
|
|
86
|
+
if (match && keepSlug(match[1]))
|
|
87
|
+
slugs.add(match[1]);
|
|
88
|
+
}
|
|
89
|
+
return [...slugs];
|
|
90
|
+
}
|
|
91
|
+
function promptSlug(prompt, slugs) {
|
|
92
|
+
if (!prompt)
|
|
93
|
+
return "";
|
|
94
|
+
const asciiLower = (value) => value.replace(/[A-Z]/g, (letter) => String.fromCharCode(letter.charCodeAt(0) + 32));
|
|
95
|
+
const normalized = asciiLower(prompt).replace(/[^a-z0-9_-]/g, " ");
|
|
96
|
+
const positions = new Map();
|
|
97
|
+
normalized.split(/[ \t]+/).forEach((token, index) => {
|
|
98
|
+
if (token && !positions.has(token))
|
|
99
|
+
positions.set(token, index + 1);
|
|
100
|
+
});
|
|
101
|
+
const matches = new Map();
|
|
102
|
+
for (const slug of slugs) {
|
|
103
|
+
const key = asciiLower(slug);
|
|
104
|
+
if (!positions.has(key))
|
|
105
|
+
continue;
|
|
106
|
+
matches.set(key, [...(matches.get(key) ?? []), slug]);
|
|
107
|
+
}
|
|
108
|
+
let bestSlug = "";
|
|
109
|
+
let bestLength = 0;
|
|
110
|
+
let bestPosition = 0;
|
|
111
|
+
for (const [key, candidates] of matches) {
|
|
112
|
+
if (candidates.length !== 1)
|
|
113
|
+
continue;
|
|
114
|
+
const slug = candidates[0];
|
|
115
|
+
const length = slug.length;
|
|
116
|
+
const position = positions.get(key);
|
|
117
|
+
if (length > bestLength || (length === bestLength && position < bestPosition)) {
|
|
118
|
+
bestSlug = slug;
|
|
119
|
+
bestLength = length;
|
|
120
|
+
bestPosition = position;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return bestSlug;
|
|
124
|
+
}
|
|
125
|
+
function validSessionId(value) {
|
|
126
|
+
return value !== "" && value !== "." && value !== ".." && /^[A-Za-z0-9._-]+$/.test(value);
|
|
127
|
+
}
|
|
128
|
+
function sessionId(root, env, fileSystem) {
|
|
129
|
+
for (const name of ["HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID", "GROK_SESSION_ID"]) {
|
|
130
|
+
const value = (env[name] ?? "").replace(/[ \t\n\v\f\r]/g, "");
|
|
131
|
+
if (validSessionId(value))
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
const current = readFile(path.join(root, "workspace/sessions/.current"), fileSystem)?.replace(/[ \t\n\v\f\r]/g, "") ?? "";
|
|
135
|
+
return validSessionId(current) ? current : "";
|
|
136
|
+
}
|
|
137
|
+
function sessionCompany(root, env, fileSystem) {
|
|
138
|
+
const helper = path.join(root, "core/scripts/hq-session.sh");
|
|
139
|
+
try {
|
|
140
|
+
fileSystem.accessSync(helper, fs.constants.X_OK);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
const code = errorCode(error);
|
|
144
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "ELOOP")
|
|
145
|
+
return "";
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
// A directory may be executable according to access(2), but `bash directory`
|
|
149
|
+
// fails without output, just as hq-session.sh does here.
|
|
150
|
+
if (!isFile(helper, fileSystem))
|
|
151
|
+
return "";
|
|
152
|
+
const id = sessionId(root, env, fileSystem);
|
|
153
|
+
if (!id)
|
|
154
|
+
return "";
|
|
155
|
+
const meta = readFile(path.join(root, "workspace/sessions", id, "meta.yaml"), fileSystem);
|
|
156
|
+
if (meta === undefined)
|
|
157
|
+
return "";
|
|
158
|
+
let value = "";
|
|
159
|
+
for (const line of meta.split("\n")) {
|
|
160
|
+
const firstField = line.replace(/^[ \t]+/, "").split(/[ \t]+/, 1)[0];
|
|
161
|
+
if (firstField !== "company_slug:")
|
|
162
|
+
continue;
|
|
163
|
+
value = line.replace(/^[^:]+:[ \t]*/, "").replace(/^"|"$/g, "");
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
// The shell's command substitution strips trailing newlines, then tr removes
|
|
167
|
+
// all remaining whitespace and double quotes from the helper's output.
|
|
168
|
+
return value.replace(/[ \t\n\v\f\r"]/g, "");
|
|
169
|
+
}
|
|
170
|
+
function fleetIdentityPresent(rootPrefix, env, fileSystem) {
|
|
171
|
+
if (env.HQ_AGENT_WORKDIR || env.HQ_AGENT_COMPANY_DIR || env.HQ_AGENT_IDENTITY_FILE)
|
|
172
|
+
return true;
|
|
173
|
+
const paths = [
|
|
174
|
+
"/etc/hq-agent/identity.json",
|
|
175
|
+
"/etc/hq-agent/machine-creds.json",
|
|
176
|
+
rootPrefix ? `${rootPrefix.replace(/\/$/, "")}/var/lib/hq-agent/identity.json` : "/var/lib/hq-agent/identity.json",
|
|
177
|
+
"/var/lib/hq-agent/machine-creds.json",
|
|
178
|
+
];
|
|
179
|
+
return paths.some((filePath) => isFile(filePath, fileSystem));
|
|
180
|
+
}
|
|
181
|
+
function parseJson(text) {
|
|
182
|
+
try {
|
|
183
|
+
return JSON.parse(text);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function record(value) {
|
|
190
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
191
|
+
? value
|
|
192
|
+
: undefined;
|
|
193
|
+
}
|
|
194
|
+
function normalizeShellValue(value) {
|
|
195
|
+
return value.replace(/[ \t\n\v\f\r"]/g, "");
|
|
196
|
+
}
|
|
197
|
+
function defaultSlugFromJson(text, knownSlugs) {
|
|
198
|
+
const root = record(parseJson(text));
|
|
199
|
+
if (!root)
|
|
200
|
+
return "";
|
|
201
|
+
const candidate = root.defaultCompany;
|
|
202
|
+
const preference = candidate === null || candidate === undefined || candidate === false
|
|
203
|
+
? root
|
|
204
|
+
: record(candidate);
|
|
205
|
+
if (!preference)
|
|
206
|
+
return "";
|
|
207
|
+
if (preference.enabled !== true || (preference.source ?? "") === "disabled" || (preference.needsChoice ?? false) !== false)
|
|
208
|
+
return "";
|
|
209
|
+
if (typeof preference.slug !== "string")
|
|
210
|
+
return "";
|
|
211
|
+
const slug = normalizeShellValue(preference.slug);
|
|
212
|
+
return knownSlugs.has(slug) ? slug : "";
|
|
213
|
+
}
|
|
214
|
+
function validPreflight(text, slug, session, operation) {
|
|
215
|
+
const value = record(parseJson(text));
|
|
216
|
+
return Boolean(value && value.contractVersion === 1 && value.sessionId === session &&
|
|
217
|
+
value.clientOperationId === operation &&
|
|
218
|
+
typeof value.kind === "string" && ["queued", "needs_project", "needs_task", "bound"].includes(value.kind) &&
|
|
219
|
+
typeof value.classification === "string" && ["needs_project", "needs_task", "bound"].includes(value.classification) &&
|
|
220
|
+
typeof value.delivery === "string" && ["queued", "acked"].includes(value.delivery) && value.lifecycle === "open" &&
|
|
221
|
+
value.companySlug === slug &&
|
|
222
|
+
typeof value.companyUid === "string" && value.companyUid.length > 0);
|
|
223
|
+
}
|
|
224
|
+
function commandOnPath(name, env) {
|
|
225
|
+
const pathValue = env.PATH ?? "";
|
|
226
|
+
for (const directory of pathValue.split(path.delimiter)) {
|
|
227
|
+
const candidate = path.join(directory || ".", name);
|
|
228
|
+
try {
|
|
229
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// `command -v` simply advances to the next PATH entry.
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
function runHq(args, env, timeoutMs, cwd) {
|
|
239
|
+
return new Promise((resolve) => {
|
|
240
|
+
let settled = false;
|
|
241
|
+
let stdout = "";
|
|
242
|
+
let child;
|
|
243
|
+
let timeoutKill;
|
|
244
|
+
let timedOut = false;
|
|
245
|
+
const signalGroup = (signal) => {
|
|
246
|
+
if (!child?.pid)
|
|
247
|
+
return;
|
|
248
|
+
try {
|
|
249
|
+
process.kill(-child.pid, signal);
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
if (errorCode(error) !== "ESRCH")
|
|
253
|
+
finish({ code: 1, stdout });
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const finish = (result) => {
|
|
257
|
+
if (settled)
|
|
258
|
+
return;
|
|
259
|
+
settled = true;
|
|
260
|
+
clearTimeout(timer);
|
|
261
|
+
if (timeoutKill)
|
|
262
|
+
clearTimeout(timeoutKill);
|
|
263
|
+
resolve(result);
|
|
264
|
+
};
|
|
265
|
+
const timer = setTimeout(() => {
|
|
266
|
+
timedOut = true;
|
|
267
|
+
if (child?.pid) {
|
|
268
|
+
signalGroup("SIGTERM");
|
|
269
|
+
// The shell wrapper returns shortly after its 2 second deadline. Do
|
|
270
|
+
// not wait for inherited stdio handles after killing the process group.
|
|
271
|
+
timeoutKill = setTimeout(() => {
|
|
272
|
+
signalGroup("SIGKILL");
|
|
273
|
+
finish({ code: 124, stdout });
|
|
274
|
+
}, 250);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
finish({ code: 124, stdout });
|
|
278
|
+
}
|
|
279
|
+
}, timeoutMs);
|
|
280
|
+
try {
|
|
281
|
+
child = spawn("hq", args, { cwd, detached: true, stdio: ["ignore", "pipe", "ignore"], env });
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
finish({ code: 127, stdout });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
child.stdout?.setEncoding("utf8");
|
|
288
|
+
child.stdout?.on("data", (chunk) => { stdout += chunk; });
|
|
289
|
+
child.once("error", () => finish({ code: 127, stdout }));
|
|
290
|
+
child.once("exit", (code, signal) => {
|
|
291
|
+
signalGroup("SIGTERM");
|
|
292
|
+
finish({ code: timedOut ? 124 : typeof code === "number" ? code : signal ? 128 : 1, stdout });
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function output(company, source) {
|
|
297
|
+
return { code: 0, stdout: Buffer.from(`{"company":"${company}","source":"${source}"}\n`), stderr: Buffer.alloc(0) };
|
|
298
|
+
}
|
|
299
|
+
function parseArgs(args) {
|
|
300
|
+
let root = "";
|
|
301
|
+
let prompt = "";
|
|
302
|
+
let havePrompt = false;
|
|
303
|
+
for (let index = 0; index < args.length;) {
|
|
304
|
+
const arg = args[index];
|
|
305
|
+
if (arg === "--root") {
|
|
306
|
+
root = args[index + 1] ?? "";
|
|
307
|
+
index += Math.min(2, args.length - index);
|
|
308
|
+
}
|
|
309
|
+
else if (arg === "--prompt") {
|
|
310
|
+
prompt = args[index + 1] ?? "";
|
|
311
|
+
havePrompt = true;
|
|
312
|
+
index += Math.min(2, args.length - index);
|
|
313
|
+
}
|
|
314
|
+
else if (arg === "--help" || arg === "-h") {
|
|
315
|
+
return { root, prompt, havePrompt, help: true };
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
index += 1;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { root, prompt, havePrompt, help: false };
|
|
322
|
+
}
|
|
323
|
+
async function readStdin() {
|
|
324
|
+
const chunks = [];
|
|
325
|
+
for await (const chunk of process.stdin)
|
|
326
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
327
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
328
|
+
}
|
|
329
|
+
export async function resolveCompany(args, dependencies) {
|
|
330
|
+
const env = dependencies.env ?? process.env;
|
|
331
|
+
const fileSystem = dependencies.fs ?? fs;
|
|
332
|
+
const parsed = parseArgs(args);
|
|
333
|
+
if (parsed.help)
|
|
334
|
+
return { code: 0, stdout: Buffer.from(RESOLVE_COMPANY_HELP), stderr: Buffer.alloc(0) };
|
|
335
|
+
let prompt = parsed.prompt;
|
|
336
|
+
if (!parsed.havePrompt && dependencies.stdinIsTty !== true) {
|
|
337
|
+
prompt = dependencies.stdin ?? await readStdin();
|
|
338
|
+
}
|
|
339
|
+
const root = parsed.root || dependencies.hqRoot || env.HQ_ROOT || env.CLAUDE_PROJECT_DIR || dependencies.cwd;
|
|
340
|
+
const manifest = path.join(root, "companies/manifest.yaml");
|
|
341
|
+
if (!isFile(manifest, fileSystem))
|
|
342
|
+
return output("", "none");
|
|
343
|
+
const manifestResult = readManifest(manifest, fileSystem);
|
|
344
|
+
if (typeof manifestResult !== "string")
|
|
345
|
+
return manifestResult;
|
|
346
|
+
const manifestText = manifestResult;
|
|
347
|
+
const slugs = parseManifestSlugs(manifestText);
|
|
348
|
+
if (slugs.length === 0)
|
|
349
|
+
return output("", "none");
|
|
350
|
+
const knownSlugs = new Set(slugs);
|
|
351
|
+
const promptMatch = promptSlug(prompt, slugs);
|
|
352
|
+
if (promptMatch)
|
|
353
|
+
return output(promptMatch, "prompt");
|
|
354
|
+
const sessionMatch = sessionCompany(root, env, fileSystem);
|
|
355
|
+
if (sessionMatch && knownSlugs.has(sessionMatch))
|
|
356
|
+
return output(sessionMatch, "session");
|
|
357
|
+
if (fleetIdentityPresent(env.HQ_AGENT_ROOT_PREFIX ?? "", env, fileSystem))
|
|
358
|
+
return output("", "none");
|
|
359
|
+
// jq is intentionally no longer needed. The native route is always hosted by
|
|
360
|
+
// Node; the old shell route required `node`, `hq`, and `jq` on PATH.
|
|
361
|
+
if (!commandOnPath("hq", env))
|
|
362
|
+
return output("", "none");
|
|
363
|
+
const hqEnv = { ...EMPTY_ENV, ...env };
|
|
364
|
+
if (dependencies.hqRoot) {
|
|
365
|
+
hqEnv.HQ_ROOT = dependencies.hqRoot;
|
|
366
|
+
hqEnv.CLAUDE_PROJECT_DIR = dependencies.hqRoot;
|
|
367
|
+
}
|
|
368
|
+
const invoke = dependencies.spawnHq ?? runHq;
|
|
369
|
+
const defaultResponse = await invoke(["mesh", "context", "default", "get", "--json"], hqEnv, 2_000, root);
|
|
370
|
+
const defaultSlug = defaultSlugFromJson(defaultResponse.stdout, knownSlugs);
|
|
371
|
+
if (!defaultSlug)
|
|
372
|
+
return output("", "none");
|
|
373
|
+
const pid = dependencies.pid ?? process.pid;
|
|
374
|
+
const random = dependencies.random ?? Math.random;
|
|
375
|
+
const session = `resolve-default-${pid}-${Math.floor(random() * 32768)}`;
|
|
376
|
+
const operation = `resolve-default-op-${pid}-${Math.floor(random() * 32768)}`;
|
|
377
|
+
const observation = JSON.stringify({
|
|
378
|
+
contractVersion: 1,
|
|
379
|
+
clientOperationId: operation,
|
|
380
|
+
identity: { sessionId: session, harness: "hq-core", adapterVersion: "1" },
|
|
381
|
+
cwd: root,
|
|
382
|
+
hqRoot: root,
|
|
383
|
+
}).replaceAll("\u007f", "\\u007f");
|
|
384
|
+
const preflight = await invoke([
|
|
385
|
+
"mesh", "context", "reconcile", "--observation-json", observation, "--machine", "--offline",
|
|
386
|
+
], hqEnv, 2_000, root);
|
|
387
|
+
if (preflight.code === 0 && validPreflight(preflight.stdout, defaultSlug, session, operation)) {
|
|
388
|
+
return output(defaultSlug, "device_default");
|
|
389
|
+
}
|
|
390
|
+
return output("", "none");
|
|
391
|
+
}
|
|
392
|
+
export async function runResolveCompanyFast(args, stdin, cwd, hqRoot, _commandName, _hqRootArg, stdinReader = readStdin) {
|
|
393
|
+
const parsed = parseArgs(args);
|
|
394
|
+
const actualStdin = parsed.havePrompt || process.stdin.isTTY === true
|
|
395
|
+
? stdin
|
|
396
|
+
: stdin || await stdinReader();
|
|
397
|
+
const result = await resolveCompany(args, {
|
|
398
|
+
cwd,
|
|
399
|
+
hqRoot,
|
|
400
|
+
stdin: actualStdin,
|
|
401
|
+
stdinIsTty: process.stdin.isTTY === true,
|
|
402
|
+
});
|
|
403
|
+
process.stdout.write(result.stdout);
|
|
404
|
+
if (result.stderr.length)
|
|
405
|
+
process.stderr.write(result.stderr);
|
|
406
|
+
return result.code;
|
|
407
|
+
}
|
|
408
|
+
//# sourceMappingURL=core-resolve-company-native.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -37,7 +37,12 @@ import { registerCoreOntologyCommand } from "./core-ontology.js";
|
|
|
37
37
|
import { registerCoreKnowledgePrefsCommand } from "./core-knowledge-prefs.js";
|
|
38
38
|
import { registerCorePolicyCommand } from "./core-policy.js";
|
|
39
39
|
import { registerCoreJobsNativeCommand } from "./core-job-native.js";
|
|
40
|
+
import { registerCoreResolveCompanyNativeCommand } from "./core-resolve-company-command.js";
|
|
40
41
|
import { registerCoreCheckpointGateSettingCommand } from "./core-checkpoint-gate-setting.js";
|
|
42
|
+
import { registerCoreAuditLogNative } from "./core-audit-log-native.js";
|
|
43
|
+
import { registerCoreGitPackExtensionCheckNative } from "./core-git-pack-extension-check-native.js";
|
|
44
|
+
import { registerCorePinNative } from "./core-pin-native.js";
|
|
45
|
+
import { registerCoreRefreshVaultAccessNative } from "./core-refresh-vault-access-native.js";
|
|
41
46
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
42
47
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
|
43
48
|
import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
|
|
@@ -214,6 +219,7 @@ export const SCAFFOLD_COMMANDS = [
|
|
|
214
219
|
* direct `hq core` command.
|
|
215
220
|
*/
|
|
216
221
|
export const SCAFFOLD_ONLY_ASSETS = [
|
|
222
|
+
{ asset: "core/scripts/resolve-company.sh", root: "live", summary: "Rollback companion for native resolve-company" },
|
|
217
223
|
{ asset: "core/scripts/jobs-schedule-parse.sh", root: "live", summary: "Rollback companion for native jobs schedule-parse" },
|
|
218
224
|
{ asset: "core/scripts/jobs-validate.sh", root: "live", summary: "Rollback companion for native jobs validate" },
|
|
219
225
|
{ asset: "core/scripts/outpost-jobs-reconcile.sh", root: "live", summary: "Rollback companion for native jobs reconcile" },
|
|
@@ -271,6 +277,13 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
271
277
|
{ asset: "core/scripts/handoff-post.sh", root: "live", summary: "Rollback shell for native handoff post" },
|
|
272
278
|
{ asset: "core/scripts/handoff-open-steps.sh", root: "live", summary: "Rollback shell for native handoff open-steps" },
|
|
273
279
|
{ asset: "core/scripts/qmd-reindex-bg.sh", root: "live", summary: "Rollback handoff reindex forwarder" },
|
|
280
|
+
// Wave 5 native utility rollback companions, sourced verbatim from staging
|
|
281
|
+
// because these assets were not present in the hq-cli bundle at launch.
|
|
282
|
+
{ asset: "core/scripts/audit-log.sh", root: "live", summary: "Rollback shell for native audit-log" },
|
|
283
|
+
{ asset: "core/scripts/git-pack-extension-check.sh", root: "cwd", summary: "Rollback shell for native git-pack-extension-check" },
|
|
284
|
+
{ asset: "core/scripts/hq-pin.sh", root: "live", summary: "Rollback shell for native pin" },
|
|
285
|
+
{ asset: "core/scripts/refresh-vault-access.sh", root: "cwd", summary: "Rollback shell for native refresh-vault-access" },
|
|
286
|
+
{ asset: "core/scripts/lib/portable.sh", root: "live", summary: "Shared portable helpers sourced by the audit-log rollback shell" },
|
|
274
287
|
...NATIVE_UTILITY_COMMANDS.map(({ asset, root, summary }) => ({
|
|
275
288
|
asset,
|
|
276
289
|
root,
|
|
@@ -354,6 +367,11 @@ export function registerCoreCommands(program) {
|
|
|
354
367
|
registerCoreKnowledgePrefsCommand(core);
|
|
355
368
|
registerCorePolicyCommand(core);
|
|
356
369
|
registerCoreJobsNativeCommand(core);
|
|
370
|
+
registerCoreResolveCompanyNativeCommand(core);
|
|
371
|
+
registerCoreAuditLogNative(core);
|
|
372
|
+
registerCoreGitPackExtensionCheckNative(core);
|
|
373
|
+
registerCorePinNative(core);
|
|
374
|
+
registerCoreRefreshVaultAccessNative(core);
|
|
357
375
|
// Wave 2 handoff/worktree commands retain their byte-identical shell assets
|
|
358
376
|
// as a default-off rollback path for one release. Keep the gate helper small
|
|
359
377
|
// so sibling Wave 2 work can reuse it without editing this command table.
|
package/dist/commands/dm.js
CHANGED
|
@@ -407,7 +407,12 @@ async function runConnectionAction(action, identifier) {
|
|
|
407
407
|
const token = await ensureCognitoToken();
|
|
408
408
|
// Resolve the pairKey from the caller's pending requests when possible so
|
|
409
409
|
// accept/decline/block work against a friendly email or personUid.
|
|
410
|
-
const requests = await fetchRequests(token).catch(() =>
|
|
410
|
+
const requests = await fetchRequests(token).catch((err) => {
|
|
411
|
+
console.error("hq dm: pending connection request lookup failed", {
|
|
412
|
+
errorName: err instanceof Error ? err.name : typeof err,
|
|
413
|
+
});
|
|
414
|
+
return [];
|
|
415
|
+
});
|
|
411
416
|
const matched = matchRequest(requests, identifier);
|
|
412
417
|
const body = buildConnectionActionBody(identifier, matched);
|
|
413
418
|
const res = await vaultApiFetch({
|
package/dist/commands/lanes.js
CHANGED
|
@@ -184,8 +184,12 @@ async function defaultDispatchOutpostExec(input) {
|
|
|
184
184
|
throw new LanesError("outpost_dispatch_failed", `Outpost exec failed: ${dispatchErrorMessage(err)}`, { outpost_id: input.outpostId, message: dispatchErrorMessage(err) });
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
+
// Pickup payloads are small; 30 seconds leaves room for normal network variance.
|
|
188
|
+
const OUTPOST_PICKUP_PAYLOAD_TIMEOUT_MS = 30_000;
|
|
187
189
|
async function defaultFetchText(url) {
|
|
188
|
-
const res = await fetch(url
|
|
190
|
+
const res = await fetch(url, {
|
|
191
|
+
signal: AbortSignal.timeout(OUTPOST_PICKUP_PAYLOAD_TIMEOUT_MS),
|
|
192
|
+
});
|
|
189
193
|
if (!res.ok) {
|
|
190
194
|
throw new LanesError("outpost_payload_invalid", `Failed to download pickup payload: HTTP ${res.status}`, { status: res.status });
|
|
191
195
|
}
|
|
@@ -1006,6 +1010,8 @@ const OUTPOST_HIRE_SUMMARY_TIMEOUT_SECONDS = 20;
|
|
|
1006
1010
|
const OUTPOST_HIRE_SUMMARY_WAIT_MS = 15_000;
|
|
1007
1011
|
const OUTPOST_SINGLE_LANE_WAIT_ENV = "HQ_LANES_OUTPOST_SINGLE_LANE_WAIT_MS";
|
|
1008
1012
|
const OUTPOST_SINGLE_LANE_WAIT_MAX_MS = 600_000;
|
|
1013
|
+
const LEGACY_PICKUP_SETTLEMENT_MARGIN_MS = 60_000;
|
|
1014
|
+
const LEGACY_PICKUP_SETTLEMENT_MIN_AGE_MS = OUTPOST_SINGLE_LANE_WAIT_MAX_MS + LEGACY_PICKUP_SETTLEMENT_MARGIN_MS;
|
|
1009
1015
|
function outpostSingleLaneWaitMs(env) {
|
|
1010
1016
|
const raw = env[OUTPOST_SINGLE_LANE_WAIT_ENV]?.trim() || String(PICKUP_WAIT_MS);
|
|
1011
1017
|
const value = /^\d+$/.test(raw) ? Number(raw) : Number.NaN;
|
|
@@ -1363,6 +1369,25 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1363
1369
|
!ownerIsHostedByOutpost ||
|
|
1364
1370
|
(owner.company?.uid ?? null) !== (mirror.company?.uid ?? null) ||
|
|
1365
1371
|
(owner.company?.slug ?? null) !== (mirror.company?.slug ?? null)) {
|
|
1372
|
+
if (matches.length === 0 && isLegacyFailedDispatchWithoutPickupEvidence(mirror, nowMs)) {
|
|
1373
|
+
const settled = retryUpdateLane(hqRoot, mirror.lane_id, "reconcile", (current) => {
|
|
1374
|
+
if (!isLegacyFailedDispatchWithoutPickupEvidence(current, nowMs) ||
|
|
1375
|
+
current.host.id !== outpostId) {
|
|
1376
|
+
return current;
|
|
1377
|
+
}
|
|
1378
|
+
return {
|
|
1379
|
+
...current,
|
|
1380
|
+
terminal_details: {
|
|
1381
|
+
...(current.terminal_details ?? {}),
|
|
1382
|
+
pickup_persisted: false,
|
|
1383
|
+
},
|
|
1384
|
+
};
|
|
1385
|
+
});
|
|
1386
|
+
if (isUndispatchedOutpostMirror(settled)) {
|
|
1387
|
+
result.updated.set(mirror.lane_id, settled);
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1366
1391
|
if (matches.length === 0)
|
|
1367
1392
|
result.missingOnOwner.set(mirror.lane_id, mirror);
|
|
1368
1393
|
const detail = matches.length === 0
|
|
@@ -2530,6 +2555,23 @@ export function isUndispatchedOutpostMirror(lane) {
|
|
|
2530
2555
|
lane.terminal_details?.pickup_persisted === false &&
|
|
2531
2556
|
lane.last_host_report_at == null;
|
|
2532
2557
|
}
|
|
2558
|
+
function isLegacyFailedDispatchWithoutPickupEvidence(lane, nowMs) {
|
|
2559
|
+
const terminalDetails = lane.terminal_details;
|
|
2560
|
+
const updatedAtMs = Date.parse(lane.updated_at);
|
|
2561
|
+
const completedFailure = (terminalDetails?.status === "Failed" ||
|
|
2562
|
+
terminalDetails?.status === "Cancelled" ||
|
|
2563
|
+
terminalDetails?.status === "TimedOut") &&
|
|
2564
|
+
typeof terminalDetails.exit_code === "number" &&
|
|
2565
|
+
Number.isSafeInteger(terminalDetails.exit_code);
|
|
2566
|
+
return lane.host.kind === "outpost" &&
|
|
2567
|
+
isTerminalLaneState(lane.state) &&
|
|
2568
|
+
lane.terminal_reason === DISPATCH_TERMINAL_REASON &&
|
|
2569
|
+
lane.last_host_report_at == null &&
|
|
2570
|
+
lane.terminal_details?.pickup_persisted === undefined &&
|
|
2571
|
+
completedFailure &&
|
|
2572
|
+
Number.isFinite(updatedAtMs) &&
|
|
2573
|
+
nowMs - updatedAtMs > LEGACY_PICKUP_SETTLEMENT_MIN_AGE_MS;
|
|
2574
|
+
}
|
|
2533
2575
|
function parsePickupTerminalReason(value) {
|
|
2534
2576
|
if (PICKUP_TERMINAL_REASONS.includes(value)) {
|
|
2535
2577
|
return value;
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -144,6 +144,18 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
144
144
|
shellCwd: "caller",
|
|
145
145
|
shellHqRoot: name === "reconcile" ? "reconcile-default" : "none",
|
|
146
146
|
})),
|
|
147
|
+
{
|
|
148
|
+
name: "resolve-company",
|
|
149
|
+
kind: "native",
|
|
150
|
+
modulePath: "./commands/core-resolve-company-native.js",
|
|
151
|
+
exportName: "runResolveCompanyFast",
|
|
152
|
+
flagKey: "hq-core-native-fast-route",
|
|
153
|
+
familyFlagKey: "core-native-utilities",
|
|
154
|
+
asset: "core/scripts/resolve-company.sh",
|
|
155
|
+
root: "live",
|
|
156
|
+
shellCwd: "live",
|
|
157
|
+
shellHqRoot: "live",
|
|
158
|
+
},
|
|
147
159
|
...["resolve", "pickup", "repo", "secrets", "transfer"].map((name) => ({
|
|
148
160
|
name: `delegate ${name}`,
|
|
149
161
|
kind: "native",
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ServiceHostPaths, ServiceSetResult } from "../service-manager/index.js";
|
|
11
11
|
import type { AgentKitPaths } from "./paths.js";
|
|
12
|
-
export declare const KIT_PID_NAME = "kit.pid";
|
|
13
|
-
export declare const KIT_FALLBACK_LOG_NAME = "kit.log";
|
|
14
12
|
export declare function kitPidPath(paths: Pick<AgentKitPaths, "agentDir">): string;
|
|
15
|
-
export declare function kitFallbackLogPath(paths: Pick<AgentKitPaths, "logsDir">): string;
|
|
16
13
|
/** True when an activation error means the service manager itself is unusable. */
|
|
17
14
|
export declare function isServiceManagerUnavailable(error: string | undefined): boolean;
|
|
18
15
|
/**
|
|
@@ -46,10 +43,6 @@ export interface DetachedSpawn {
|
|
|
46
43
|
unref(): void;
|
|
47
44
|
};
|
|
48
45
|
}
|
|
49
|
-
export declare function fallbackCommand(host: ServiceHostPaths): {
|
|
50
|
-
cmd: string;
|
|
51
|
-
args: string[];
|
|
52
|
-
};
|
|
53
46
|
/** crontab line that restarts the fallback supervisor after a reboot. */
|
|
54
47
|
export declare function rebootCrontabLine(paths: Pick<AgentKitPaths, "agentDir" | "logsDir">, host: ServiceHostPaths): string;
|
|
55
48
|
export declare function startFallback(paths: AgentKitPaths, host: ServiceHostPaths, deps?: {
|
|
@@ -12,12 +12,12 @@ import * as fs from "node:fs";
|
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { kitServiceEnv } from "./services.js";
|
|
14
14
|
import { writePrivateFile } from "./private-file.js";
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const KIT_PID_NAME = "kit.pid";
|
|
16
|
+
const KIT_FALLBACK_LOG_NAME = "kit.log";
|
|
17
17
|
export function kitPidPath(paths) {
|
|
18
18
|
return path.join(paths.agentDir, KIT_PID_NAME);
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
function kitFallbackLogPath(paths) {
|
|
21
21
|
return path.join(paths.logsDir, KIT_FALLBACK_LOG_NAME);
|
|
22
22
|
}
|
|
23
23
|
const UNAVAILABLE_PATTERNS = [
|
|
@@ -93,7 +93,7 @@ export function stopFallback(paths, kill = defaultKill) {
|
|
|
93
93
|
fs.rmSync(kitPidPath(paths), { force: true });
|
|
94
94
|
return { pid, killed };
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
function fallbackCommand(host) {
|
|
97
97
|
return { cmd: host.nodeBinary, args: [host.hqBinary, "agent", "kit", "run", "all"] };
|
|
98
98
|
}
|
|
99
99
|
/** crontab line that restarts the fallback supervisor after a reboot. */
|
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
* always drain exactly what it has not handled yet and never reply twice.
|
|
10
10
|
*/
|
|
11
11
|
import type { AgentKitPaths } from "./paths.js";
|
|
12
|
-
export declare const DONE_IDS_NAME = "done-ids.json";
|
|
13
|
-
export declare const MAX_DONE_IDS = 5000;
|
|
14
12
|
export interface MirroredInboxEntry {
|
|
15
13
|
id: string;
|
|
16
14
|
mirroredAt?: string;
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
import * as fs from "node:fs";
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { writePrivateFile } from "./private-file.js";
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const DONE_IDS_NAME = "done-ids.json";
|
|
15
|
+
const MAX_DONE_IDS = 5000;
|
|
16
16
|
const INBOX_JSONL = "inbox.jsonl";
|
|
17
17
|
export function readDoneIds(paths) {
|
|
18
18
|
try {
|
|
@@ -5,10 +5,6 @@
|
|
|
5
5
|
* contract-shaped credential file and nothing else.
|
|
6
6
|
*/
|
|
7
7
|
import { type AgentKitPaths, type KitComponent } from "./paths.js";
|
|
8
|
-
export declare const DEFAULT_SYNC_INTERVAL_MS: number;
|
|
9
|
-
export declare const DEFAULT_INBOX_POLL_MS = 15000;
|
|
10
|
-
export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 60000;
|
|
11
|
-
export declare const DEFAULT_MESH_REFRESH_MS: number;
|
|
12
8
|
export interface KitConfig {
|
|
13
9
|
/** Local HQ tree the company vault syncs into (companies/<slug>/ …). */
|
|
14
10
|
hqRoot: string;
|
|
@@ -53,7 +49,6 @@ export interface KitConfig {
|
|
|
53
49
|
* unhealthy, so reporting it either way would be a lie.
|
|
54
50
|
*/
|
|
55
51
|
export declare function enabledKitComponents(config: Pick<KitConfig, "syncFiles">): readonly KitComponent[];
|
|
56
|
-
export declare function defaultHqRoot(paths: Pick<AgentKitPaths, "agentDir">): string;
|
|
57
52
|
export declare function defaultKitConfig(paths: Pick<AgentKitPaths, "agentDir">, cliVersion: string, now?: () => Date): KitConfig;
|
|
58
53
|
export declare function writeKitConfig(paths: Pick<AgentKitPaths, "kitConfigPath">, config: KitConfig): void;
|
|
59
54
|
/** Read kit.json, falling back to defaults for any missing/invalid field. */
|