@indigoai-us/hq-cli 5.345.155 → 5.345.159
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 +289 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
- package/assets/scaffold/core/scripts/worktree.sh +4 -0
- package/dist/command-catalog.generated.d.ts +536 -7
- package/dist/command-catalog.generated.js +676 -24
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +4 -0
- package/dist/commands/_patterns.d.ts +17 -0
- package/dist/commands/_patterns.js +22 -0
- package/dist/commands/agent-inbox.js +2 -2
- package/dist/commands/agent-kit.js +8 -6
- package/dist/commands/bot.js +7 -4
- package/dist/commands/channel-suggest.d.ts +56 -0
- package/dist/commands/channel-suggest.js +170 -0
- package/dist/commands/channels.js +7 -1
- package/dist/commands/cloud.d.ts +45 -0
- package/dist/commands/cloud.js +150 -10
- package/dist/commands/company.js +4 -3
- package/dist/commands/core-checkpoint-native.d.ts +18 -0
- package/dist/commands/core-checkpoint-native.js +67 -2
- package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
- package/dist/commands/core-pipeline-driver-native.js +131 -5
- package/dist/commands/core.js +13 -1
- package/dist/commands/crm.js +83 -6
- package/dist/commands/deploy-access.d.ts +33 -0
- package/dist/commands/deploy-access.js +322 -0
- package/dist/commands/dm-attach.d.ts +106 -0
- package/dist/commands/dm-attach.js +184 -0
- package/dist/commands/dm.d.ts +37 -0
- package/dist/commands/dm.js +172 -10
- package/dist/commands/feedback-show.d.ts +40 -0
- package/dist/commands/feedback-show.js +261 -0
- package/dist/commands/feedback.d.ts +21 -0
- package/dist/commands/feedback.js +152 -2
- package/dist/commands/files-browse.d.ts +16 -0
- package/dist/commands/files-browse.js +52 -1
- package/dist/commands/files.d.ts +24 -0
- package/dist/commands/files.js +52 -1
- package/dist/commands/integrations-connect.js +7 -3
- 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/lanes-accounts.js +16 -1
- package/dist/commands/lanes-loop.js +14 -4
- package/dist/commands/lanes-message.js +6 -4
- package/dist/commands/lanes-resume.js +36 -13
- package/dist/commands/lanes-shared.d.ts +5 -0
- package/dist/commands/lanes-shared.js +13 -3
- package/dist/commands/lanes-watch.js +1 -0
- package/dist/commands/lanes.d.ts +20 -1
- package/dist/commands/lanes.js +201 -51
- package/dist/commands/mesh.js +149 -1
- package/dist/commands/monitor.d.ts +1 -0
- package/dist/commands/monitor.js +4 -1
- package/dist/commands/repo.d.ts +41 -0
- package/dist/commands/repo.js +331 -0
- package/dist/commands/scan.d.ts +3 -1
- package/dist/commands/scan.js +7 -3
- package/dist/commands/secrets.d.ts +37 -0
- package/dist/commands/secrets.js +185 -16
- package/dist/commands/signals.js +17 -5
- package/dist/commands/staff/listings.d.ts +41 -0
- package/dist/commands/staff/listings.js +160 -0
- package/dist/commands/staff/reset-test-account.d.ts +3 -2
- package/dist/commands/staff/reset-test-account.js +4 -3
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2629 -1036
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- 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/agent-kit/run/inbox.js +2 -1
- package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
- package/dist/lib/agent-kit/wake-secrets.js +32 -0
- package/dist/lib/agent-kit/wake.d.ts +4 -0
- package/dist/lib/agent-kit/wake.js +49 -3
- package/dist/lib/bot/inflight.d.ts +7 -1
- package/dist/lib/bot/inflight.js +10 -2
- package/dist/lib/bot/prompt.js +3 -0
- package/dist/lib/bot/run.js +72 -20
- package/dist/lib/bot/session.d.ts +14 -0
- package/dist/lib/bot/session.js +70 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
- package/dist/lib/capacity/lane-login.d.ts +6 -0
- package/dist/lib/capacity/lane-login.js +5 -0
- package/dist/lib/core-utils/worktree.js +2 -0
- package/dist/lib/daemon/exit-cause.d.ts +43 -0
- package/dist/lib/daemon/exit-cause.js +88 -0
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +6 -1
- package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
- package/dist/lib/daemon/paths.d.ts +2 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/run.js +13 -0
- package/dist/lib/daemon/supervisor.d.ts +54 -1
- package/dist/lib/daemon/supervisor.js +297 -21
- package/dist/lib/daemon/unit-holds.d.ts +22 -0
- package/dist/lib/daemon/unit-holds.js +86 -0
- 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/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- 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 +4 -5
- 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 +5 -5
- package/dist/lib/lanes/account-company.d.ts +28 -0
- package/dist/lib/lanes/account-company.js +53 -0
- package/dist/lib/lanes/accounts.d.ts +22 -3
- package/dist/lib/lanes/accounts.js +38 -2
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +6 -1
- package/dist/lib/lanes/claude-credential.d.ts +78 -0
- package/dist/lib/lanes/claude-credential.js +163 -0
- package/dist/lib/lanes/compaction.d.ts +2 -0
- package/dist/lib/lanes/compaction.js +9 -0
- package/dist/lib/lanes/config.js +6 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
- package/dist/lib/lanes/hooks-install.js +2 -1
- package/dist/lib/lanes/host-capacity.d.ts +2 -0
- package/dist/lib/lanes/host-capacity.js +1 -0
- package/dist/lib/lanes/keep-alive.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.js +3 -1
- package/dist/lib/lanes/links.js +10 -4
- package/dist/lib/lanes/loop.js +16 -1
- package/dist/lib/lanes/node-binary.d.ts +17 -0
- package/dist/lib/lanes/node-binary.js +30 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/reconcile.js +4 -0
- package/dist/lib/lanes/remote-control-lane.js +2 -0
- package/dist/lib/lanes/spawn.d.ts +3 -0
- package/dist/lib/lanes/spawn.js +119 -9
- package/dist/lib/lanes/store.d.ts +6 -0
- package/dist/lib/lanes/store.js +107 -18
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/watch.js +28 -5
- package/dist/lib/lanes/watcher-main.js +4 -3
- package/dist/lib/lanes/watcher.d.ts +4 -0
- package/dist/lib/lanes/watcher.js +110 -21
- package/dist/lib/mesh/api.d.ts +32 -0
- package/dist/lib/mesh/api.js +75 -0
- package/dist/lib/secret-scan/detect.js +9 -0
- package/dist/lib/secret-scan/repo-config.d.ts +41 -0
- package/dist/lib/secret-scan/repo-config.js +237 -0
- package/dist/lib/secret-scan/rules.d.ts +7 -1
- package/dist/lib/secret-scan/rules.js +3 -1
- package/dist/lib/secret-scan/staged.d.ts +5 -0
- package/dist/lib/secret-scan/staged.js +19 -2
- package/dist/lib/secret-scan/types.d.ts +2 -0
- package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
- package/dist/lib/sync/server-owned-local-only.js +75 -0
- 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/dist/utils/package-use-lease.js +27 -9
- package/dist/utils/version-gate.d.ts +7 -0
- package/dist/utils/version-gate.js +66 -0
- package/package.json +8 -8
|
@@ -4,7 +4,15 @@ import * as path from "node:path";
|
|
|
4
4
|
import { inflateRawSync } from "node:zlib";
|
|
5
5
|
import { isPipelineDriverProcess } from "./core-pipeline-driver-process.js";
|
|
6
6
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
|
-
|
|
7
|
+
// Longest time a routed phase may wait in its lane's queue before the driver
|
|
8
|
+
// gives up on it (PC_QUEUE_MAX_MIN minutes, default 240: twice the default
|
|
9
|
+
// PC_DEADLINE_MIN). Queue time does not count toward the deadline or the stall
|
|
10
|
+
// window, so this ceiling is what ends a run whose lane is never admitted.
|
|
11
|
+
export function queueMaxSeconds(env = process.env) {
|
|
12
|
+
const minutes = Number.parseInt(String(env.PC_QUEUE_MAX_MIN ?? ''), 10);
|
|
13
|
+
return (Number.isFinite(minutes) && minutes > 0 ? minutes : 240) * 60;
|
|
14
|
+
}
|
|
15
|
+
export function scanDriverState(stateDir, maxFails, hqBin, run = spawnSync, queueMax = queueMaxSeconds()) {
|
|
8
16
|
const S = stateDir, MAX = maxFails, HQ = hqBin;
|
|
9
17
|
const TERMINAL = ['passed', 'failed', 'blocked'];
|
|
10
18
|
const EARLY = 'engine_exited_early';
|
|
@@ -78,6 +86,59 @@ export function scanDriverState(stateDir, maxFails, hqBin, run = spawnSync) {
|
|
|
78
86
|
return { reason: 'loop_stalled', phase: ph, story_id: sid };
|
|
79
87
|
return state === 'failed' ? { reason: state, phase: ph, story_id: sid } : null;
|
|
80
88
|
}
|
|
89
|
+
// The deadline bounds work time, not queue time (ported from hq-core-staging
|
|
90
|
+
// #1357). An envelope still waiting in its loop lane's queue (the lane is queued
|
|
91
|
+
// for admission behind the host throttle, or busy on another envelope) is not
|
|
92
|
+
// past its deadline yet. With no lane row or no loop status the deadline applies.
|
|
93
|
+
// An admitted lane counts as holding the envelope only while its queue is not
|
|
94
|
+
// empty: a lane with queue_depth 0 that is not working on this envelope has lost
|
|
95
|
+
// or dropped it, and the deadline applies.
|
|
96
|
+
// Known limit: while the lane is busy on an envelope from another run, this phase
|
|
97
|
+
// waits on that envelope's own watchdog; PC_QUEUE_MAX_MIN still bounds the wait.
|
|
98
|
+
function waitingInQueue(worker, envId) {
|
|
99
|
+
if (typeof envId !== 'string' || !envId)
|
|
100
|
+
return false;
|
|
101
|
+
const row = laneRow(worker);
|
|
102
|
+
if (isObj(row) && get(row, 'state', null) === 'queued')
|
|
103
|
+
return true;
|
|
104
|
+
const loop = get(row, 'loop', null);
|
|
105
|
+
if (!isObj(loop) || !Object.prototype.hasOwnProperty.call(loop, 'active_envelope_id'))
|
|
106
|
+
return false;
|
|
107
|
+
if (get(loop, 'active_envelope_id', null) === envId)
|
|
108
|
+
return false;
|
|
109
|
+
const depth = get(loop, 'queue_depth', 0);
|
|
110
|
+
return isNum(depth) && depth > 0;
|
|
111
|
+
}
|
|
112
|
+
const queuedMark = (sid, ph) => path.join(S, 'driver', `queued-${sid}-${ph}`);
|
|
113
|
+
function clearQueuedMark(sid, ph) {
|
|
114
|
+
try {
|
|
115
|
+
fs.rmSync(queuedMark(sid, ph), { force: true });
|
|
116
|
+
}
|
|
117
|
+
catch (e) { /* best effort */ }
|
|
118
|
+
}
|
|
119
|
+
// The deadline to enforce, or null while the envelope still waits. The envelope
|
|
120
|
+
// deadline is stamped at routing, so the time the envelope was last seen waiting
|
|
121
|
+
// is added back: a phase gets its full budget once the lane picks it up.
|
|
122
|
+
// driver/queued-<sid>-<phase> holds that time (epoch seconds).
|
|
123
|
+
function workDeadline(sid, ph, dl, since, waiting) {
|
|
124
|
+
const mark = queuedMark(sid, ph);
|
|
125
|
+
if (waiting) {
|
|
126
|
+
try {
|
|
127
|
+
fs.writeFileSync(mark, String(Math.trunc(now())) + '\n');
|
|
128
|
+
}
|
|
129
|
+
catch (e) { /* best effort */ }
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
let seen = NaN;
|
|
133
|
+
try {
|
|
134
|
+
seen = parseInt(fs.readFileSync(mark, 'utf8'), 10);
|
|
135
|
+
}
|
|
136
|
+
catch (e) { /* never queued */ }
|
|
137
|
+
const base = epoch(dl);
|
|
138
|
+
if (!isNum(since) || !Number.isFinite(seen) || seen <= since || base === null)
|
|
139
|
+
return dl;
|
|
140
|
+
return new Date((base + (seen - Math.trunc(since))) * 1000).toISOString().slice(0, 19) + 'Z';
|
|
141
|
+
}
|
|
81
142
|
function mtime(p) {
|
|
82
143
|
try {
|
|
83
144
|
return fs.statSync(p).mtimeMs / 1000;
|
|
@@ -146,6 +207,7 @@ export function scanDriverState(stateDir, maxFails, hqBin, run = spawnSync) {
|
|
|
146
207
|
const hstat = mine ? get(hd, 'status', null) : null;
|
|
147
208
|
let ev = null;
|
|
148
209
|
if (mine && TERMINAL.includes(hstat) && get(hd, 'exit_reason', null) !== EARLY) {
|
|
210
|
+
clearQueuedMark(sid, ph);
|
|
149
211
|
out.push(['ACCEPT', sid, ph, h]);
|
|
150
212
|
}
|
|
151
213
|
else if (mine && get(hd, 'exit_reason', null) === EARLY) {
|
|
@@ -153,6 +215,7 @@ export function scanDriverState(stateDir, maxFails, hqBin, run = spawnSync) {
|
|
|
153
215
|
if (!isNum(secs))
|
|
154
216
|
secs = since !== null ? Math.trunc(now() - since) : 0;
|
|
155
217
|
const why = collapse(str(truthy(hd.notes) ? hd.notes : truthy(hd.summary) ? hd.summary : 'no reason given'));
|
|
218
|
+
clearQueuedMark(sid, ph);
|
|
156
219
|
out.push(['EARLY', sid, ph, String(Math.trunc(secs)), why]);
|
|
157
220
|
}
|
|
158
221
|
else if ((!mine || !TERMINAL.includes(hstat)) && (ev = phaseExit(worker, sid, ph, since)) !== null) {
|
|
@@ -171,8 +234,15 @@ export function scanDriverState(stateDir, maxFails, hqBin, run = spawnSync) {
|
|
|
171
234
|
else {
|
|
172
235
|
const env = load(path.join(S, 'envelopes', `${sid}-${ph}.json`));
|
|
173
236
|
const dl = get(env, 'deadline', null);
|
|
174
|
-
|
|
175
|
-
|
|
237
|
+
const waiting = waitingInQueue(worker, get(env, 'id', null));
|
|
238
|
+
if (waiting && isNum(since) && now() - since >= queueMax) {
|
|
239
|
+
out.push(['QUEUED_TOO_LONG', sid, ph, String(Math.trunc(now() - since)), String(get(laneRow(worker), 'lane_id', worker))]);
|
|
240
|
+
}
|
|
241
|
+
else if (typeof dl === 'string') {
|
|
242
|
+
const due = workDeadline(sid, ph, dl, since, waiting);
|
|
243
|
+
if (due !== null && isoNow() > due)
|
|
244
|
+
out.push(['DEADLINE', sid, ph, due]);
|
|
245
|
+
}
|
|
176
246
|
if (since !== null)
|
|
177
247
|
out.push(['INFLIGHT', sid, ph, String(Math.max(0, Math.trunc(now() - since)))]);
|
|
178
248
|
}
|
|
@@ -293,7 +363,8 @@ export function checkDriverStall(stateDir, stallWindow, hqBin, run = spawnSync)
|
|
|
293
363
|
byWorker.set(worker, r);
|
|
294
364
|
for (const [sid, worker, s] of inflight) {
|
|
295
365
|
const r = byWorker.get(worker);
|
|
296
|
-
|
|
366
|
+
// a lane queued for admission (host throttle, queue position) is waiting, not stalled
|
|
367
|
+
if (r && get(r, 'state', null) !== 'queued' && get(r.loop, 'queue_depth', 0) > 0 && !truthy(get(r.loop, 'active_envelope_id', null))) {
|
|
297
368
|
cond = true;
|
|
298
369
|
stories.push(sid);
|
|
299
370
|
lanes.push(get(r, 'lane_id', worker));
|
|
@@ -480,6 +551,45 @@ export async function sleep(ms, signal) {
|
|
|
480
551
|
signal?.addEventListener("abort", finish, { once: true });
|
|
481
552
|
});
|
|
482
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Move a handoff that failed validation to a numbered sibling
|
|
556
|
+
* (handoffs/<story>-<phase>.invalid.<n>.json) so the worker's evidence is never
|
|
557
|
+
* lost, and write a failed handoff in its place that names the validation
|
|
558
|
+
* error and the kept file. Returns both paths.
|
|
559
|
+
*/
|
|
560
|
+
export function quarantineInvalidHandoff(state, storyId, phase, handoffPath, reason) {
|
|
561
|
+
const dir = path.dirname(handoffPath);
|
|
562
|
+
let n = 1;
|
|
563
|
+
let keptPath = path.join(dir, `${storyId}-${phase}.invalid.${n}.json`);
|
|
564
|
+
while (fs.existsSync(keptPath)) {
|
|
565
|
+
n += 1;
|
|
566
|
+
keptPath = path.join(dir, `${storyId}-${phase}.invalid.${n}.json`);
|
|
567
|
+
}
|
|
568
|
+
fs.renameSync(handoffPath, keptPath);
|
|
569
|
+
const story = readJson(path.join(state, "stories", `${storyId}.json`));
|
|
570
|
+
const storyRecord = story && typeof story === "object" ? story : {};
|
|
571
|
+
const current = Number.isInteger(storyRecord.current) ? storyRecord.current : 0;
|
|
572
|
+
const worker = storyRecord.phases?.[current]?.worker ?? "None";
|
|
573
|
+
const loaded = readJson(keptPath);
|
|
574
|
+
const workerId = loaded && typeof loaded === "object" && typeof loaded.worker_id === "string" && loaded.worker_id ? loaded.worker_id : String(worker);
|
|
575
|
+
const record = {
|
|
576
|
+
schema: "hq-phase-handoff/v1",
|
|
577
|
+
story_id: storyId,
|
|
578
|
+
phase,
|
|
579
|
+
worker_id: workerId,
|
|
580
|
+
status: "failed",
|
|
581
|
+
exit_reason: "invalid_handoff",
|
|
582
|
+
summary: `invalid handoff: ${reason}`,
|
|
583
|
+
files_changed: [],
|
|
584
|
+
commits: [],
|
|
585
|
+
back_pressure: { tests: "skip", lint: "skip", typecheck: "skip", build: "skip" },
|
|
586
|
+
context_for_next: `The worker handoff failed validation (${reason}). It is kept unchanged at ${keptPath}.`,
|
|
587
|
+
invalid_handoff_path: keptPath,
|
|
588
|
+
notes: reason,
|
|
589
|
+
};
|
|
590
|
+
fs.writeFileSync(handoffPath, `${JSON.stringify(record)}\n`);
|
|
591
|
+
return { keptPath, handoffPath };
|
|
592
|
+
}
|
|
483
593
|
function writeEarlyHandoff(state, storyId, phase, seconds, reason) {
|
|
484
594
|
const story = readJson(path.join(state, "stories", `${storyId}.json`));
|
|
485
595
|
const storyRecord = story && typeof story === "object" ? story : {};
|
|
@@ -697,7 +807,21 @@ export async function runPipelineDriverNative(args, hqRoot = "") {
|
|
|
697
807
|
writeDriverLog(logFile, `ACCEPTING ${a} ${b} ${c}`);
|
|
698
808
|
const result = runPc(["accept", "--state", state, "--story", a, "--handoff", c]);
|
|
699
809
|
writeDriverLog(logFile, `ACCEPT ${a} ${b} rc=${result.code}: ${result.stdout.trim()}`);
|
|
700
|
-
|
|
810
|
+
const invalid = /invalid handoff: (.*)/.exec(result.stderr);
|
|
811
|
+
if (result.code !== 0 && invalid) {
|
|
812
|
+
// A worker wrote a handoff that fails validation. Keep it on disk
|
|
813
|
+
// beside the handoff, record the phase as failed with the reason,
|
|
814
|
+
// and keep driving: the fail cap bounds the retries.
|
|
815
|
+
const reason = invalid[1].trim();
|
|
816
|
+
const kept = quarantineInvalidHandoff(state, a, b, c, reason);
|
|
817
|
+
writeDriverLog(logFile, `INVALID_HANDOFF ${a} ${b}: ${reason}; worker handoff kept at ${kept.keptPath}`);
|
|
818
|
+
const failed = runPc(["accept", "--state", state, "--story", a, "--handoff", kept.handoffPath]);
|
|
819
|
+
writeDriverLog(logFile, `ACCEPT ${a} ${b} rc=${failed.code}: ${failed.stdout.trim()}`);
|
|
820
|
+
if (!(failed.code === 1 && failed.stdout.startsWith("PHASE_FAILED"))) {
|
|
821
|
+
return finish(23, `accept invalid handoff ${a} ${b}: unexpected (rc=${failed.code}): ${failed.stdout} ${failed.stderr}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
else if (!((result.code === 0 && (/^(NEXT|RECHECK)/.test(result.stdout))) || (result.code === 1 && /^PHASE_/.test(result.stdout)) || (result.code === 10 && /^PHASE_BLOCKED/.test(result.stdout)))) {
|
|
701
825
|
return finish(23, `accept ${a} ${b}: unexpected (rc=${result.code}): ${result.stdout} ${result.stderr}`);
|
|
702
826
|
}
|
|
703
827
|
acted = 1;
|
|
@@ -717,6 +841,8 @@ export async function runPipelineDriverNative(args, hqRoot = "") {
|
|
|
717
841
|
}
|
|
718
842
|
else if (action === "DEADLINE")
|
|
719
843
|
return finish(22, `phase ${a}/${b} passed its deadline ${c} with no handoff`);
|
|
844
|
+
else if (action === "QUEUED_TOO_LONG")
|
|
845
|
+
return finish(22, `phase ${a}/${b} waited ${c}s in the queue of lane ${d} without being picked up (limit PC_QUEUE_MAX_MIN=${queueMaxSeconds() / 60} min); check lane admission (hq lanes list), then restart the driver`);
|
|
720
846
|
else if (action === "HELD")
|
|
721
847
|
held = `${a} ${b}`;
|
|
722
848
|
else if (action === "BLOCKED")
|
package/dist/commands/core.js
CHANGED
|
@@ -652,6 +652,12 @@ export function registerCoreCommands(program) {
|
|
|
652
652
|
.allowExcessArguments()
|
|
653
653
|
.helpOption(false)
|
|
654
654
|
.action((target, args = [], _opts, cmd) => {
|
|
655
|
+
// Help must never write. `--help`/`-h` in the target slot prints the
|
|
656
|
+
// command usage instead of being treated as an unknown target.
|
|
657
|
+
if (target === "--help" || target === "-h") {
|
|
658
|
+
process.stdout.write(`Usage: hq core rebuild-index <target> [args...]\n\nRegenerate one index. Targets: ${validTargets.join(", ")}.\nRun \`hq core rebuild-index <target> --help\` for target options. Help performs no writes.\n`);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
655
661
|
const entry = REBUILD_INDEX_TARGETS.find((candidate) => candidate.target === target);
|
|
656
662
|
if (!entry) {
|
|
657
663
|
throw expectedUserError(`Unknown rebuild-index target "${target}". Valid targets: ${validTargets.join(", ")}.`);
|
|
@@ -663,10 +669,16 @@ export function registerCoreCommands(program) {
|
|
|
663
669
|
const rendererArgs = passthrough.filter((arg) => arg !== "--accept");
|
|
664
670
|
const isCompaniesTarget = entry.renderer === "companies";
|
|
665
671
|
const wantsHelp = rendererArgs.some((arg) => arg === "--help" || arg === "-h");
|
|
666
|
-
if (
|
|
672
|
+
if (wantsHelp && ["companies", "company-knowledge", "projects"].includes(entry.renderer)) {
|
|
667
673
|
renderIndexTarget(entry.renderer, { root: "", output: (message) => process.stdout.write(message) }, rendererArgs);
|
|
668
674
|
return;
|
|
669
675
|
}
|
|
676
|
+
if (wantsHelp) {
|
|
677
|
+
// Every other renderer has no option grammar of its own; print usage
|
|
678
|
+
// and exit before resolving a root or rendering anything.
|
|
679
|
+
process.stdout.write(`Usage: hq core rebuild-index ${entry.target} [--accept]\n\n${entry.summary}. Help performs no writes.\n`);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
670
682
|
const requestsCompanyScope = isCompaniesTarget && rendererArgs.some((arg) => arg === "--company" || arg.startsWith("--company="));
|
|
671
683
|
if (requestsCompanyScope && !coreCompanyIndexScopeEnabled()) {
|
|
672
684
|
throw expectedUserError("rebuild-index companies --company is disabled; enable cli.rebuild-index-company-scope for a canary.");
|
package/dist/commands/crm.js
CHANGED
|
@@ -2,6 +2,23 @@ import chalk from "chalk";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
4
4
|
import { vaultApiFetch, getEntityUid } from "../utils/vault-api.js";
|
|
5
|
+
import { readSessionCompanyLock } from "../lib/session-company-lock.js";
|
|
6
|
+
import { sessionIdFromEnv } from "../lib/core-utils/session-id.js";
|
|
7
|
+
import { MULTI_COMPANY_SESSION_LOCK_FLAG } from "../lib/cli-flag-keys.js";
|
|
8
|
+
import { fastFlagEnabledDefaultOn } from "../native-fast-route.js";
|
|
9
|
+
async function resolveCrmCompany(requestedCompany) {
|
|
10
|
+
const lock = sessionIdFromEnv(process.env)
|
|
11
|
+
&& await fastFlagEnabledDefaultOn(MULTI_COMPANY_SESSION_LOCK_FLAG)
|
|
12
|
+
? readSessionCompanyLock(process.env)
|
|
13
|
+
: undefined;
|
|
14
|
+
const company = requestedCompany ?? lock?.companies[0];
|
|
15
|
+
if (lock && company && !lock.companies.includes(company)) {
|
|
16
|
+
throw new Error(`Company '${company}' is outside this session's locked company set.`);
|
|
17
|
+
}
|
|
18
|
+
if (!company)
|
|
19
|
+
throw new Error("--company <slug> is required.");
|
|
20
|
+
return company;
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* `hq crm entity upsert` — agent-facing controlled entity upsert
|
|
7
24
|
* (hq-native-crm US-002). Wraps `POST /crm/entities`, which fronts the ontology
|
|
@@ -19,6 +36,17 @@ const CRM_ENTITY_TYPES = [
|
|
|
19
36
|
"invoice",
|
|
20
37
|
"company",
|
|
21
38
|
];
|
|
39
|
+
/** Entity types the read route accepts as a `list --type` filter. */
|
|
40
|
+
const CRM_READ_ENTITY_TYPES = [
|
|
41
|
+
"person",
|
|
42
|
+
"project",
|
|
43
|
+
"company",
|
|
44
|
+
"concept",
|
|
45
|
+
"contact",
|
|
46
|
+
"deal",
|
|
47
|
+
"contract",
|
|
48
|
+
"invoice",
|
|
49
|
+
];
|
|
22
50
|
const CRM_SETTINGS_PRECHECK_TIMEOUT_MS = 3_000;
|
|
23
51
|
const CRM_DISABLED_HINT_PREFIX = "CRM is not enabled for this company. Enable it first: ";
|
|
24
52
|
function printCrmDisabledHint(companySlug) {
|
|
@@ -129,11 +157,64 @@ function buildEntityFromFlags(opts) {
|
|
|
129
157
|
export function registerCrmCommand(program) {
|
|
130
158
|
const crm = program
|
|
131
159
|
.command("crm")
|
|
132
|
-
.description("
|
|
160
|
+
.description("Read and upsert canonical CRM entities in a company vault")
|
|
133
161
|
.option("--company <slug>", "Company slug (resolves to companyUid)");
|
|
134
162
|
const entity = crm
|
|
135
163
|
.command("entity")
|
|
136
164
|
.description("CRM entity operations");
|
|
165
|
+
async function readEntities(path, query) {
|
|
166
|
+
try {
|
|
167
|
+
const companySlug = await resolveCrmCompany(crm.opts().company);
|
|
168
|
+
const token = await ensureCognitoToken();
|
|
169
|
+
const companyUid = await getEntityUid(token, { companySlug });
|
|
170
|
+
const res = await vaultApiFetch({ token, path, query: { ...query, companyUid }, signal: AbortSignal.timeout(25_000) });
|
|
171
|
+
const text = await res.text();
|
|
172
|
+
let result;
|
|
173
|
+
try {
|
|
174
|
+
result = text ? JSON.parse(text) : undefined;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
if (res.ok)
|
|
178
|
+
throw new Error(`CRM read returned a non-JSON response (HTTP ${res.status})`);
|
|
179
|
+
result = undefined;
|
|
180
|
+
}
|
|
181
|
+
if (!res.ok) {
|
|
182
|
+
if (res.status === 403 && isRecord(result) && result.code === "CRM_DISABLED")
|
|
183
|
+
printCrmDisabledHint(companySlug);
|
|
184
|
+
throw new Error(isRecord(result) && typeof result.error === "string" ? result.error : `CRM read failed (HTTP ${res.status})`);
|
|
185
|
+
}
|
|
186
|
+
console.log(JSON.stringify(result, null, 2));
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
console.error(chalk.red(error instanceof Error ? error.message : String(error)));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
entity.command("list")
|
|
194
|
+
.description("List one CRM entity page as JSON; continue with nextCursor until null")
|
|
195
|
+
.option("--type <type>", `Filter by entity type: ${CRM_READ_ENTITY_TYPES.join(" | ")}`)
|
|
196
|
+
.option("--stage <stage>", "Exact pipeline stage")
|
|
197
|
+
.option("--name <text>", "Case-insensitive canonical name filter")
|
|
198
|
+
.option("--limit <number>", "Object keys examined per page (1–100)", "50")
|
|
199
|
+
.option("--cursor <cursor>", "Continue a prior page with the same company and filters")
|
|
200
|
+
.action(async (opts) => {
|
|
201
|
+
if (!/^\d+$/.test(opts.limit) || Number(opts.limit) < 1 || Number(opts.limit) > 100) {
|
|
202
|
+
console.error(chalk.red("Error: --limit must be an integer from 1 to 100."));
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
if (opts.type !== undefined && !CRM_READ_ENTITY_TYPES.includes(opts.type)) {
|
|
206
|
+
console.error(chalk.red(`Error: --type must be one of ${CRM_READ_ENTITY_TYPES.join(", ")}.`));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
const query = Object.fromEntries(Object.entries(opts).filter((entry) => typeof entry[1] === "string"));
|
|
210
|
+
await readEntities("/crm/entities", query);
|
|
211
|
+
});
|
|
212
|
+
entity.command("get <id>")
|
|
213
|
+
.description("Read a canonical CRM entity as JSON")
|
|
214
|
+
.option("--key <path>", "Source key returned by list, for direct lookup in large vaults")
|
|
215
|
+
.action(async (id, opts) => {
|
|
216
|
+
await readEntities(`/crm/entities/${encodeURIComponent(id)}`, opts.key ? { key: opts.key } : {});
|
|
217
|
+
});
|
|
137
218
|
entity
|
|
138
219
|
.command("upsert")
|
|
139
220
|
.description("Create or update a canonical CRM entity (POST /crm/entities). " +
|
|
@@ -148,11 +229,7 @@ export function registerCrmCommand(program) {
|
|
|
148
229
|
.option("--json <file|->", "Path to a JSON file (or '-' for stdin) with a full entity object or array")
|
|
149
230
|
.action(async (opts) => {
|
|
150
231
|
try {
|
|
151
|
-
const companySlug = crm.opts().company;
|
|
152
|
-
if (!companySlug) {
|
|
153
|
-
console.error(chalk.red("Error: --company <slug> is required."));
|
|
154
|
-
process.exit(1);
|
|
155
|
-
}
|
|
232
|
+
const companySlug = await resolveCrmCompany(crm.opts().company);
|
|
156
233
|
// Resolve the entity payload: --json is the alternative to flags.
|
|
157
234
|
let entities;
|
|
158
235
|
if (opts.json) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `hq deploy access` reads and changes a deploy app's access policy through
|
|
4
|
+
* hq-deploy's GET/PUT /api/apps/:id/access-policy (the same API the HQ console
|
|
5
|
+
* access modal uses). Feedback bcd64202-071a-4d01-9005-3329a13ca4ae.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEPLOY_ACCESS_MODES: readonly ["company", "selected", "public"];
|
|
8
|
+
export type DeployAccessMode = (typeof DEPLOY_ACCESS_MODES)[number];
|
|
9
|
+
interface Grantee {
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AccessPolicyView {
|
|
13
|
+
appId: string | null;
|
|
14
|
+
mode: string | null;
|
|
15
|
+
companyUid: string | null;
|
|
16
|
+
users: Grantee[];
|
|
17
|
+
groups: Grantee[];
|
|
18
|
+
policyVersion: number | null;
|
|
19
|
+
updatedAt: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Terminal hooks for the approval prompt. Tests replace these; production reads
|
|
23
|
+
* the real TTY state and asks on stdin.
|
|
24
|
+
*/
|
|
25
|
+
export declare const deployAccessPrompt: {
|
|
26
|
+
isInteractive: () => boolean;
|
|
27
|
+
ask: (question: string) => Promise<string>;
|
|
28
|
+
};
|
|
29
|
+
export declare function parseMode(raw: string): DeployAccessMode;
|
|
30
|
+
export declare function toView(body: Record<string, unknown>): AccessPolicyView;
|
|
31
|
+
export declare function registerDeployAccessCommand(deploy: Command): void;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=deploy-access.d.ts.map
|