@indigoai-us/hq-cli 5.345.155 → 5.345.156
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 +58 -0
- package/dist/command-catalog.generated.d.ts +5 -1
- package/dist/command-catalog.generated.js +6 -1
- package/dist/commands/agent-inbox.js +2 -2
- package/dist/commands/core.js +1 -1
- package/dist/commands/files-browse.d.ts +16 -0
- package/dist/commands/files-browse.js +52 -1
- package/dist/commands/integrations-core.d.ts +13 -1
- package/dist/commands/integrations-core.js +49 -4
- package/dist/commands/integrations.js +4 -2
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +72 -3
- package/dist/lib/agent-kit/inbox-state.d.ts +7 -0
- package/dist/lib/agent-kit/inbox-state.js +5 -1
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/doctor/checks/sync-health.d.ts +9 -0
- package/dist/lib/doctor/checks/sync-health.js +66 -1
- package/dist/lib/doctor/company-resolution-state.d.ts +30 -0
- package/dist/lib/doctor/company-resolution-state.js +89 -0
- package/dist/lib/doctor/fix/apply.d.ts +36 -6
- package/dist/lib/doctor/fix/apply.js +128 -12
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/hook-engine/resolve.js +6 -2
- package/dist/lib/index-render/company-knowledge.d.ts +1 -1
- package/dist/lib/index-render/company-knowledge.js +3 -2
- package/dist/lib/index-render/index.js +44 -3
- package/dist/lib/index-render/projects.d.ts +1 -1
- package/dist/lib/index-render/projects.js +4 -2
- package/dist/sentry-before-send.js +1 -0
- package/dist/utils/access-denied-hint.js +6 -0
- package/dist/utils/client-health.d.ts +34 -0
- package/dist/utils/client-health.js +96 -3
- package/package.json +7 -7
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { CompanyIndexUserError, renderCompanies } from "./companies.js";
|
|
2
4
|
import { renderCompanyKnowledge } from "./company-knowledge.js";
|
|
3
5
|
import { renderOrchestrator } from "./orchestrator.js";
|
|
4
6
|
import { renderProjects } from "./projects.js";
|
|
@@ -8,12 +10,51 @@ import { renderSocialDrafts } from "./social-drafts.js";
|
|
|
8
10
|
import { renderThreads } from "./threads.js";
|
|
9
11
|
import { renderWorkers } from "./workers.js";
|
|
10
12
|
const allOrder = ["threads", "orchestrator", "companies", "projects", "company-knowledge", "public-knowledge", "workers", "reports", "social-drafts"];
|
|
13
|
+
function companyScope(target, context, args) {
|
|
14
|
+
let company;
|
|
15
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
16
|
+
let value = args[i];
|
|
17
|
+
if (value === "--company")
|
|
18
|
+
value = args[++i] ?? "";
|
|
19
|
+
else if (value.startsWith("--company="))
|
|
20
|
+
value = value.slice("--company=".length);
|
|
21
|
+
if (!value || value.startsWith("-"))
|
|
22
|
+
throw new CompanyIndexUserError(`rebuild-index ${target}: expected a company slug; unknown or incomplete option`);
|
|
23
|
+
if (company !== undefined)
|
|
24
|
+
throw new CompanyIndexUserError(`rebuild-index ${target}: supply only one company slug`);
|
|
25
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value))
|
|
26
|
+
throw new CompanyIndexUserError(`rebuild-index ${target}: invalid company slug`);
|
|
27
|
+
company = value;
|
|
28
|
+
}
|
|
29
|
+
if (company !== undefined) {
|
|
30
|
+
const directory = path.join(context.root, "companies", company);
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.lstatSync(directory).isDirectory())
|
|
33
|
+
throw new CompanyIndexUserError(`rebuild-index ${target}: company must be a real directory`);
|
|
34
|
+
const contentDirectory = path.join(directory, target === "projects" ? "projects" : "knowledge");
|
|
35
|
+
if (fs.existsSync(contentDirectory) && !fs.lstatSync(contentDirectory).isDirectory())
|
|
36
|
+
throw new CompanyIndexUserError(`rebuild-index ${target}: content must be a real directory`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code === "ENOENT")
|
|
40
|
+
throw new CompanyIndexUserError(`rebuild-index ${target}: no company directory named "${company}"`);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return company;
|
|
45
|
+
}
|
|
11
46
|
export function renderIndexTarget(target, context, args = []) {
|
|
47
|
+
if (target === "company-knowledge" || target === "projects") {
|
|
48
|
+
if (args.some((arg) => arg === "--help" || arg === "-h")) {
|
|
49
|
+
(context.output ?? ((message) => process.stdout.write(message)))(`Usage: hq core rebuild-index ${target} [<company> | --company <company>] [--accept]\n\nWith a company, rebuild only that company's index. Without a company, retain the all-company rebuild. Help performs no writes.\n`);
|
|
50
|
+
return { written: [] };
|
|
51
|
+
}
|
|
52
|
+
const company = companyScope(target, context, args);
|
|
53
|
+
return target === "projects" ? renderProjects(context, company) : renderCompanyKnowledge(context, company);
|
|
54
|
+
}
|
|
12
55
|
switch (target) {
|
|
13
56
|
case "companies": return renderCompanies(context, args);
|
|
14
|
-
case "company-knowledge": return renderCompanyKnowledge(context);
|
|
15
57
|
case "orchestrator": return renderOrchestrator(context);
|
|
16
|
-
case "projects": return renderProjects(context);
|
|
17
58
|
case "public-knowledge": return renderPublicKnowledge(context);
|
|
18
59
|
case "reports": return renderReports(context);
|
|
19
60
|
case "social-drafts": return renderSocialDrafts(context);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { RenderContext, RenderResult } from "./shared.js";
|
|
2
|
-
export declare function renderProjects(context: RenderContext): RenderResult;
|
|
2
|
+
export declare function renderProjects(context: RenderContext, selectedCompany?: string): RenderResult;
|
|
3
3
|
//# sourceMappingURL=projects.d.ts.map
|
|
@@ -64,9 +64,9 @@ function renderCompanyProjects(context, company, projectRoot) {
|
|
|
64
64
|
log(context, `rebuild-projects-index: wrote ${output}`);
|
|
65
65
|
return written;
|
|
66
66
|
}
|
|
67
|
-
export function renderProjects(context) {
|
|
67
|
+
export function renderProjects(context, selectedCompany) {
|
|
68
68
|
const written = [];
|
|
69
|
-
const companyDirectories = immediateEntries(context.root, "companies", "dir");
|
|
69
|
+
const companyDirectories = selectedCompany === undefined ? immediateEntries(context.root, "companies", "dir") : [at(context.root, `companies/${selectedCompany}`)];
|
|
70
70
|
for (const companyDirectory of companyDirectories) {
|
|
71
71
|
const company = basename(companyDirectory);
|
|
72
72
|
if (company.startsWith("_") || isHidden(company))
|
|
@@ -76,6 +76,8 @@ export function renderProjects(context) {
|
|
|
76
76
|
written.push(...renderCompanyProjects(context, company, projectRoot));
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
if (selectedCompany !== undefined)
|
|
80
|
+
return { written };
|
|
79
81
|
const base = "personal/projects";
|
|
80
82
|
if (!exists(context.root, base)) {
|
|
81
83
|
log(context, "rebuild-projects-index: personal/projects/ missing, skipping");
|
|
@@ -31,6 +31,12 @@ function errMessage(err) {
|
|
|
31
31
|
export function isAccessDeniedError(err) {
|
|
32
32
|
const rec = errRecord(err);
|
|
33
33
|
if (rec) {
|
|
34
|
+
// A presigned storage download that failed AFTER the API authorized the
|
|
35
|
+
// key (createCompanyPresignClient). The ACL already passed, so pointing
|
|
36
|
+
// the caller at `hq access` would loop. Checked before the message regex
|
|
37
|
+
// because the message names the HTTP status.
|
|
38
|
+
if (rec.code === "FILES_STORAGE_DOWNLOAD_FAILED")
|
|
39
|
+
return false;
|
|
34
40
|
if (rec.status === 403)
|
|
35
41
|
return true;
|
|
36
42
|
const meta = rec.$metadata;
|
|
@@ -64,6 +64,17 @@ export declare const CLIENT_HEALTH_OBSERVATION_FILE = "cli-client-health.observa
|
|
|
64
64
|
* always re-observe — they have opened the store anyway, so it costs nothing.
|
|
65
65
|
*/
|
|
66
66
|
export declare const JOURNAL_OBSERVATION_TTL_MS: number;
|
|
67
|
+
/**
|
|
68
|
+
* V8 old-space cap, in MB, for the child process that enumerates journals on
|
|
69
|
+
* behalf of a plain invocation. `listJournals()` materializes every area of
|
|
70
|
+
* the local sync state store; on a large store that took a short-lived
|
|
71
|
+
* command such as `hq secrets exec` to 2.5-4 GB RSS (feedback_d70e9d63). The
|
|
72
|
+
* child either finishes inside this cap or dies, and the command never holds
|
|
73
|
+
* that memory itself.
|
|
74
|
+
*/
|
|
75
|
+
export declare const JOURNAL_OBSERVER_HEAP_MB = 512;
|
|
76
|
+
/** Wall-clock bound for the isolated journal observer. */
|
|
77
|
+
export declare const JOURNAL_OBSERVER_TIMEOUT_MS = 20000;
|
|
67
78
|
export interface CliClientHealthState {
|
|
68
79
|
/** Stable random installation identity — regenerated only if invalid. */
|
|
69
80
|
installationId: string;
|
|
@@ -120,6 +131,24 @@ interface JournalLike {
|
|
|
120
131
|
* contract ISO-UTC. Null when nothing has ever synced.
|
|
121
132
|
*/
|
|
122
133
|
export declare function latestJournalSyncTime(journals: readonly JournalLike[]): string | null;
|
|
134
|
+
/**
|
|
135
|
+
* The isolated observer could not produce a value (heap cap, timeout, crash or
|
|
136
|
+
* unreadable output). The caller stamps the observation so the next attempt
|
|
137
|
+
* waits a full TTL instead of re-running a read already known to fail here.
|
|
138
|
+
*/
|
|
139
|
+
export declare class JournalObservationUnavailableError extends Error {
|
|
140
|
+
constructor(message: string);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Run `listJournals()` in a heap-capped child process and return only the
|
|
144
|
+
* `lastSync` values. Used for plain invocations so the full store
|
|
145
|
+
* materialization never lands in the invoking command's own memory.
|
|
146
|
+
*/
|
|
147
|
+
export declare function observeJournalsIsolated(opts?: {
|
|
148
|
+
engineUrl?: string;
|
|
149
|
+
heapMb?: number;
|
|
150
|
+
timeoutMs?: number;
|
|
151
|
+
}): JournalLike[];
|
|
123
152
|
/**
|
|
124
153
|
* True when this event kind must pay for a fresh `listJournals()` read.
|
|
125
154
|
*
|
|
@@ -167,6 +196,11 @@ export interface ClientHealthDeps {
|
|
|
167
196
|
expiring?: typeof isExpiring;
|
|
168
197
|
versions?: () => VersionInfo;
|
|
169
198
|
journals?: () => readonly JournalLike[];
|
|
199
|
+
/**
|
|
200
|
+
* Journal source for plain invocations when `journals` is not set. Defaults
|
|
201
|
+
* to {@link observeJournalsIsolated}; sync events read in-process.
|
|
202
|
+
*/
|
|
203
|
+
isolatedJournals?: () => readonly JournalLike[];
|
|
170
204
|
post?: HeartbeatPoster;
|
|
171
205
|
timeoutMs?: number;
|
|
172
206
|
/** US-016: overridable for tests; defaults to {@link collectLocalFilesOverview}. */
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
import * as fs from "node:fs";
|
|
34
34
|
import * as path from "node:path";
|
|
35
35
|
import { randomBytes } from "node:crypto";
|
|
36
|
+
import { spawnSync } from "node:child_process";
|
|
37
|
+
import { createRequire } from "node:module";
|
|
38
|
+
import { pathToFileURL } from "node:url";
|
|
36
39
|
import { getStateDir, listJournals } from "@indigoai-us/hq-cloud";
|
|
37
40
|
import { isExpiring, isMachineIdentity, loadCachedTokens, } from "./cognito-session.js";
|
|
38
41
|
import { vaultApiFetch } from "./vault-api.js";
|
|
@@ -73,6 +76,17 @@ export const CLIENT_HEALTH_OBSERVATION_FILE = "cli-client-health.observation.jso
|
|
|
73
76
|
* always re-observe — they have opened the store anyway, so it costs nothing.
|
|
74
77
|
*/
|
|
75
78
|
export const JOURNAL_OBSERVATION_TTL_MS = 15 * 60_000;
|
|
79
|
+
/**
|
|
80
|
+
* V8 old-space cap, in MB, for the child process that enumerates journals on
|
|
81
|
+
* behalf of a plain invocation. `listJournals()` materializes every area of
|
|
82
|
+
* the local sync state store; on a large store that took a short-lived
|
|
83
|
+
* command such as `hq secrets exec` to 2.5-4 GB RSS (feedback_d70e9d63). The
|
|
84
|
+
* child either finishes inside this cap or dies, and the command never holds
|
|
85
|
+
* that memory itself.
|
|
86
|
+
*/
|
|
87
|
+
export const JOURNAL_OBSERVER_HEAP_MB = 512;
|
|
88
|
+
/** Wall-clock bound for the isolated journal observer. */
|
|
89
|
+
export const JOURNAL_OBSERVER_TIMEOUT_MS = 20_000;
|
|
76
90
|
/** An observation lock older than this is treated as abandoned. */
|
|
77
91
|
const JOURNAL_OBSERVATION_LOCK_STALE_MS = 5 * 60_000;
|
|
78
92
|
/**
|
|
@@ -234,6 +248,66 @@ export function latestJournalSyncTime(journals) {
|
|
|
234
248
|
}
|
|
235
249
|
return latest === null ? null : new Date(latest).toISOString();
|
|
236
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* The isolated observer could not produce a value (heap cap, timeout, crash or
|
|
253
|
+
* unreadable output). The caller stamps the observation so the next attempt
|
|
254
|
+
* waits a full TTL instead of re-running a read already known to fail here.
|
|
255
|
+
*/
|
|
256
|
+
export class JournalObservationUnavailableError extends Error {
|
|
257
|
+
constructor(message) {
|
|
258
|
+
super(message);
|
|
259
|
+
this.name = "JournalObservationUnavailableError";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const JOURNAL_OBSERVER_SOURCE = [
|
|
263
|
+
"const engine = await import(process.argv[1]);",
|
|
264
|
+
"const out = [];",
|
|
265
|
+
"for (const entry of engine.listJournals()) {",
|
|
266
|
+
" const value = entry && entry.journal ? entry.journal.lastSync : undefined;",
|
|
267
|
+
" if (typeof value === 'string') out.push(value);",
|
|
268
|
+
"}",
|
|
269
|
+
"process.stdout.write(JSON.stringify(out));",
|
|
270
|
+
].join("\n");
|
|
271
|
+
function defaultJournalEngineUrl() {
|
|
272
|
+
return pathToFileURL(createRequire(import.meta.url).resolve("@indigoai-us/hq-cloud")).href;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Run `listJournals()` in a heap-capped child process and return only the
|
|
276
|
+
* `lastSync` values. Used for plain invocations so the full store
|
|
277
|
+
* materialization never lands in the invoking command's own memory.
|
|
278
|
+
*/
|
|
279
|
+
export function observeJournalsIsolated(opts = {}) {
|
|
280
|
+
const engineUrl = opts.engineUrl ?? defaultJournalEngineUrl();
|
|
281
|
+
const result = spawnSync(process.execPath, [
|
|
282
|
+
`--max-old-space-size=${opts.heapMb ?? JOURNAL_OBSERVER_HEAP_MB}`,
|
|
283
|
+
"--input-type=module",
|
|
284
|
+
"-e",
|
|
285
|
+
JOURNAL_OBSERVER_SOURCE,
|
|
286
|
+
engineUrl,
|
|
287
|
+
], {
|
|
288
|
+
encoding: "utf8",
|
|
289
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
290
|
+
timeout: opts.timeoutMs ?? JOURNAL_OBSERVER_TIMEOUT_MS,
|
|
291
|
+
maxBuffer: 1024 * 1024,
|
|
292
|
+
windowsHide: true,
|
|
293
|
+
});
|
|
294
|
+
if (result.error || result.status !== 0 || result.signal) {
|
|
295
|
+
throw new JournalObservationUnavailableError(`journal observer failed (status ${String(result.status)}, signal ${String(result.signal)})`);
|
|
296
|
+
}
|
|
297
|
+
let values;
|
|
298
|
+
try {
|
|
299
|
+
values = JSON.parse(result.stdout);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
throw new JournalObservationUnavailableError("journal observer output was not JSON");
|
|
303
|
+
}
|
|
304
|
+
if (!Array.isArray(values)) {
|
|
305
|
+
throw new JournalObservationUnavailableError("journal observer output was not a list");
|
|
306
|
+
}
|
|
307
|
+
return values
|
|
308
|
+
.filter((value) => typeof value === "string")
|
|
309
|
+
.map((lastSync) => ({ journal: { lastSync } }));
|
|
310
|
+
}
|
|
237
311
|
/**
|
|
238
312
|
* True when this event kind must pay for a fresh `listJournals()` read.
|
|
239
313
|
*
|
|
@@ -527,7 +601,14 @@ async function contributeClientHealthUnbounded(kind, deps, failureReason) {
|
|
|
527
601
|
}
|
|
528
602
|
else {
|
|
529
603
|
try {
|
|
530
|
-
|
|
604
|
+
// A plain invocation (any command, `hq secrets exec` included) reads
|
|
605
|
+
// the store in a heap-capped child. Sync commands have the store open
|
|
606
|
+
// already and read in-process.
|
|
607
|
+
const source = deps.journals ??
|
|
608
|
+
(kind === "invocation"
|
|
609
|
+
? (deps.isolatedJournals ?? observeJournalsIsolated)
|
|
610
|
+
: listJournals);
|
|
611
|
+
const observed = latestJournalSyncTime(source());
|
|
531
612
|
if (observed !== null) {
|
|
532
613
|
journalLastSyncAt = observed;
|
|
533
614
|
observation.lastSyncAt = observed;
|
|
@@ -543,8 +624,20 @@ async function contributeClientHealthUnbounded(kind, deps, failureReason) {
|
|
|
543
624
|
observation.observedAt = now.toISOString();
|
|
544
625
|
persistJournalObservation(stateDir, observation);
|
|
545
626
|
}
|
|
546
|
-
catch {
|
|
547
|
-
// Journal enumeration is best effort.
|
|
627
|
+
catch (error) {
|
|
628
|
+
// Journal enumeration is best effort. When the isolated observer hit
|
|
629
|
+
// its heap or time cap, keep the previous value but stamp the attempt
|
|
630
|
+
// so the next invocation waits a full TTL instead of retrying a read
|
|
631
|
+
// already known to fail on this machine.
|
|
632
|
+
if (error instanceof JournalObservationUnavailableError) {
|
|
633
|
+
observation.observedAt = now.toISOString();
|
|
634
|
+
try {
|
|
635
|
+
persistJournalObservation(stateDir, observation);
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
// Best effort.
|
|
639
|
+
}
|
|
640
|
+
}
|
|
548
641
|
}
|
|
549
642
|
finally {
|
|
550
643
|
release();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.156",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -114,11 +114,11 @@
|
|
|
114
114
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
115
115
|
},
|
|
116
116
|
"optionalDependencies": {
|
|
117
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
118
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
119
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
117
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.156",
|
|
118
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.156",
|
|
119
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.156",
|
|
120
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.156",
|
|
121
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.156",
|
|
122
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.156"
|
|
123
123
|
}
|
|
124
124
|
}
|