@indigoai-us/hq-cli 5.289.0 → 5.291.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 +61 -0
- package/assets/scaffold/core/scripts/handoff-finalize.sh +670 -0
- package/assets/scaffold/core/scripts/handoff-open-steps.sh +81 -0
- package/assets/scaffold/core/scripts/handoff-post.sh +187 -0
- package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
- package/assets/scaffold/core/scripts/hq-session.sh +182 -21
- package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
- package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
- package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +40 -0
- package/dist/command-catalog.generated.d.ts +110 -0
- package/dist/command-catalog.generated.js +136 -0
- package/dist/commands/cloud.d.ts +26 -14
- package/dist/commands/cloud.js +207 -56
- package/dist/commands/core-handoff.d.ts +22 -0
- package/dist/commands/core-handoff.js +1240 -0
- package/dist/commands/core-jobs-native.d.ts +8 -0
- package/dist/commands/core-jobs-native.js +624 -0
- package/dist/commands/core-native-jobs-flag.d.ts +4 -0
- package/dist/commands/core-native-jobs-flag.js +7 -0
- package/dist/commands/core-ontology.d.ts +6 -1
- package/dist/commands/core-ontology.js +42 -7
- package/dist/commands/core-policy.js +24 -5
- package/dist/commands/core.js +15 -1
- package/dist/commands/integrations-core.js +7 -0
- package/dist/commands/lanes.js +111 -35
- package/dist/commands/members.js +5 -0
- package/dist/commands/mesh.js +46 -25
- package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
- package/dist/commands/personal-targeted-push-flag.js +7 -0
- package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
- package/dist/commands/personal-targeted-push-paths.js +67 -0
- package/dist/commands/secrets.js +9 -0
- package/dist/commands/skill.d.ts +3 -0
- package/dist/commands/skill.js +28 -0
- package/dist/fast-core.js +1 -0
- package/dist/lib/billing/free-month.d.ts +18 -0
- package/dist/lib/billing/free-month.js +31 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
- package/dist/lib/billing/plan-limit-refusals.js +187 -0
- package/dist/lib/billing/plan-lock.d.ts +2 -6
- package/dist/lib/billing/plan-lock.js +5 -16
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +21 -0
- package/dist/lib/checkpoint-ontology/entity-file.js +100 -0
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +34 -2
- package/dist/lib/checkpoint-ontology/merge-copies.js +541 -27
- package/dist/lib/core-utils/secret-patterns.d.ts +3 -1
- package/dist/lib/core-utils/secret-patterns.js +3 -2
- 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/lanes/autopilot.d.ts +69 -0
- package/dist/lib/lanes/autopilot.js +418 -0
- package/dist/lib/lanes/mesh-events.js +2 -0
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +18 -9
- package/dist/lib/mesh/api.js +4 -0
- package/dist/lib/mesh/live/daemon/state.d.ts +7 -1
- package/dist/lib/mesh/live/daemon/state.js +34 -2
- package/dist/main.js +23 -12
- package/dist/utils/plan-gate-error.d.ts +24 -0
- package/dist/utils/plan-gate-error.js +219 -9
- package/dist/utils/self-update.d.ts +3 -0
- package/dist/utils/self-update.js +232 -1
- package/package.json +2 -2
|
@@ -17,7 +17,7 @@ import { applyOntologyRun, ApplyError } from "../lib/checkpoint-ontology/apply.j
|
|
|
17
17
|
import { resolveOntologyCompany, WriteBoundsError } from "../lib/checkpoint-ontology/company.js";
|
|
18
18
|
import { findDuplicateGroups, summarizeMergeBack, } from "../lib/checkpoint-ontology/multi-writer.js";
|
|
19
19
|
import { buildEntityIndex } from "../lib/checkpoint-ontology/tree.js";
|
|
20
|
-
import { MERGE_COPIES_FLAG, MERGE_VARIANTS_FLAG, mergeCopies, } from "../lib/checkpoint-ontology/merge-copies.js";
|
|
20
|
+
import { MERGE_COPIES_FLAG, MERGE_REWRITE_REFS_FLAG, MERGE_VARIANTS_FLAG, mergeCopies, } from "../lib/checkpoint-ontology/merge-copies.js";
|
|
21
21
|
import { callDeleteEndpoint } from "./files.js";
|
|
22
22
|
import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
|
|
23
23
|
import { registerCoreOntologyNativeCommands } from "./core-ontology-native.js";
|
|
@@ -146,11 +146,12 @@ function mergeCopiesSummary(report) {
|
|
|
146
146
|
const held = report.plans.filter((plan) => plan.held).length;
|
|
147
147
|
const lines = [
|
|
148
148
|
`ontology merge-copies (${report.mode.replace("_", " ")}) in ${report.company} (${report.pair_kind}): ${report.plans.length} of ${report.copies_found} copies, ${held} held, ${report.skipped.length} skipped`,
|
|
149
|
-
` merged ${report.merged.length}, relinked ${report.relinked.length}, deleted ${report.deleted.length}, not ready ${report.not_ready.length}, errors ${report.errors.length}`,
|
|
149
|
+
` merged ${report.merged.length}, relinked ${report.relinked.length}${report.rewritten !== undefined ? `, rewritten ${report.rewritten.length}` : ""}, deleted ${report.deleted.length}, not ready ${report.not_ready.length}, errors ${report.errors.length}`,
|
|
150
150
|
` duplicate groups ${report.duplicate_groups_before} -> ${report.duplicate_groups_after}; broken entity links ${report.broken_links_before} -> ${report.broken_links_after}`,
|
|
151
151
|
];
|
|
152
|
-
for (const plan of report.plans.filter((entry) => entry.held))
|
|
153
|
-
lines.push(` held ${plan.copy}: ${plan.held}`);
|
|
152
|
+
for (const plan of report.plans.filter((entry) => entry.held)) {
|
|
153
|
+
lines.push(` held ${plan.copy}: ${plan.held}${plan.held === "external_id_conflict" ? ` (${plan.detail})` : ""}`);
|
|
154
|
+
}
|
|
154
155
|
for (const error of report.errors)
|
|
155
156
|
lines.push(` error ${error.path}: ${error.error}`);
|
|
156
157
|
for (const entry of report.only_unmatched ?? [])
|
|
@@ -176,7 +177,36 @@ export function readListedPairs(file) {
|
|
|
176
177
|
})) {
|
|
177
178
|
throw new OntologyUsageError("ontology: --only must be a non-empty JSON array of { copy, original } paths");
|
|
178
179
|
}
|
|
179
|
-
return parsed.map((entry) =>
|
|
180
|
+
return parsed.map((entry) => {
|
|
181
|
+
if (entry.resolve_external_ids === undefined)
|
|
182
|
+
return { copy: entry.copy, original: entry.original };
|
|
183
|
+
return { copy: entry.copy, original: entry.original, resolve_external_ids: readResolution(entry.copy, entry.resolve_external_ids) };
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** `{ provider: { primary, namespace, object_type? } }`, every value a non-empty string. */
|
|
187
|
+
function readResolution(copy, raw) {
|
|
188
|
+
const usage = `ontology: --only entry ${copy}: resolve_external_ids must map each provider to { primary, namespace, object_type? } strings`;
|
|
189
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw) || Object.keys(raw).length === 0)
|
|
190
|
+
throw new OntologyUsageError(usage);
|
|
191
|
+
const out = {};
|
|
192
|
+
for (const [provider, value] of Object.entries(raw)) {
|
|
193
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
194
|
+
throw new OntologyUsageError(usage);
|
|
195
|
+
const record = value;
|
|
196
|
+
const nonEmpty = (field) => typeof field === "string" && field.length > 0;
|
|
197
|
+
if (!Object.keys(record).every((key) => key === "primary" || key === "namespace" || key === "object_type") ||
|
|
198
|
+
!nonEmpty(record.primary) ||
|
|
199
|
+
!nonEmpty(record.namespace) ||
|
|
200
|
+
(record.object_type !== undefined && !nonEmpty(record.object_type))) {
|
|
201
|
+
throw new OntologyUsageError(usage);
|
|
202
|
+
}
|
|
203
|
+
out[provider] = {
|
|
204
|
+
primary: record.primary,
|
|
205
|
+
namespace: record.namespace,
|
|
206
|
+
...(record.object_type !== undefined ? { object_type: record.object_type } : {}),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
180
210
|
}
|
|
181
211
|
/**
|
|
182
212
|
* `hq core ontology merge-copies`: merge `<slug> <n>.md` copies into their
|
|
@@ -185,7 +215,10 @@ export function readListedPairs(file) {
|
|
|
185
215
|
* `checkpoint.ontology.merge-copies` and `checkpoint.ontology.multi-writer`.
|
|
186
216
|
* With `--variants`, merges two-file duplicate groups whose names differ only
|
|
187
217
|
* in case, spacing or punctuation instead, gated by
|
|
188
|
-
* `checkpoint.ontology.merge-variants`.
|
|
218
|
+
* `checkpoint.ontology.merge-variants`. `--rewrite-refs` (flag
|
|
219
|
+
* `checkpoint.ontology.merge-rewrite-refs`) also re-points signal refs,
|
|
220
|
+
* invoice and bill links, source and knowledge links, and carries external
|
|
221
|
+
* ids and web domains.
|
|
189
222
|
*/
|
|
190
223
|
export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
191
224
|
if (options.apply && options.delete) {
|
|
@@ -200,7 +233,7 @@ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
|
200
233
|
throw new OntologyUsageError(`ontology: company rejected: ${resolution.reason}`);
|
|
201
234
|
const company = resolution.company;
|
|
202
235
|
const readFlag = deps.readFlag ?? registryFlagReader;
|
|
203
|
-
for (const flag of [options.variants ? MERGE_VARIANTS_FLAG : MERGE_COPIES_FLAG, MULTI_WRITER_FLAG]) {
|
|
236
|
+
for (const flag of [options.variants ? MERGE_VARIANTS_FLAG : MERGE_COPIES_FLAG, MULTI_WRITER_FLAG, ...(options.rewriteRefs ? [MERGE_REWRITE_REFS_FLAG] : [])]) {
|
|
204
237
|
if (!(await readFlag(flag, company))) {
|
|
205
238
|
return options.json ? JSON.stringify({ company: company.slug, skipped: `${flag} off` }) : `ontology merge-copies: skipped (${flag} is off)`;
|
|
206
239
|
}
|
|
@@ -220,6 +253,7 @@ export async function runOntologyMergeCopies(hqRoot, slug, options, deps = {}) {
|
|
|
220
253
|
...(options.variants ? { pairKind: "variants" } : {}),
|
|
221
254
|
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
222
255
|
...(only ? { only } : {}),
|
|
256
|
+
...(options.rewriteRefs ? { rewriteRefs: true } : {}),
|
|
223
257
|
...(deleteVaultKey ? { deleteVaultKey } : {}),
|
|
224
258
|
});
|
|
225
259
|
return options.json ? JSON.stringify(report, null, 2) : mergeCopiesSummary(report);
|
|
@@ -274,6 +308,7 @@ export function registerCoreOntologyCommand(core) {
|
|
|
274
308
|
.option("--variants", "merge two-file duplicate groups whose names differ only in case, spacing or punctuation (checkpoint.ontology.merge-variants)")
|
|
275
309
|
.option("--limit <n>", "handle at most this many copies", (value) => Number(value))
|
|
276
310
|
.option("--only <file>", "handle only the copy -> original pairs listed in this JSON file")
|
|
311
|
+
.option("--rewrite-refs", "also re-point signal refs, invoice, source and knowledge links to the survivor and carry external ids and web domains (checkpoint.ontology.merge-rewrite-refs)")
|
|
277
312
|
.option("--json", "print JSON")
|
|
278
313
|
.option("--hq-root <path>", "HQ installation to operate on")
|
|
279
314
|
.action(async (options) => {
|
|
@@ -205,6 +205,28 @@ function extractRulePaths(file) {
|
|
|
205
205
|
}
|
|
206
206
|
return [...paths].sort();
|
|
207
207
|
}
|
|
208
|
+
function shellReferenceStem(reference) {
|
|
209
|
+
const lastDot = reference.lastIndexOf('.');
|
|
210
|
+
return lastDot === -1 ? reference : reference.slice(0, lastDot);
|
|
211
|
+
}
|
|
212
|
+
function shellLsExtensionGlobHasMatch(root, stem) {
|
|
213
|
+
const patternPath = path.join(root, `${stem}.*`);
|
|
214
|
+
const directory = path.dirname(patternPath);
|
|
215
|
+
const basenamePattern = path.basename(patternPath);
|
|
216
|
+
const literalPrefix = basenamePattern.slice(0, -1);
|
|
217
|
+
let entries;
|
|
218
|
+
try {
|
|
219
|
+
entries = fs.readdirSync(directory);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (expectedFsMiss(error))
|
|
223
|
+
return false;
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
// Bash 5.2 enables globskipdots by default, so `.*` only matches when the
|
|
227
|
+
// directory has an actual hidden entry. readdir omits `.` and `..`.
|
|
228
|
+
return entries.some((entry) => entry.startsWith(literalPrefix));
|
|
229
|
+
}
|
|
208
230
|
function refSummary(file, root) {
|
|
209
231
|
const refs = extractRulePaths(file);
|
|
210
232
|
const judged = refs.filter((reference) => {
|
|
@@ -228,11 +250,8 @@ function refSummary(file, root) {
|
|
|
228
250
|
}
|
|
229
251
|
if (fs.existsSync(path.join(root, reference)))
|
|
230
252
|
continue;
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const parent = path.join(root, path.posix.dirname(stem));
|
|
234
|
-
const basename = path.posix.basename(stem);
|
|
235
|
-
const hasSibling = listFiles(parent).some((name) => name.startsWith(`${basename}.`));
|
|
253
|
+
const stem = shellReferenceStem(reference);
|
|
254
|
+
const hasSibling = shellLsExtensionGlobHasMatch(root, stem);
|
|
236
255
|
if (hasSibling)
|
|
237
256
|
renamed.push(reference);
|
|
238
257
|
else
|
package/dist/commands/core.js
CHANGED
|
@@ -31,9 +31,11 @@
|
|
|
31
31
|
import { Option } from "commander";
|
|
32
32
|
import { registerCoreCheckpointCommand } from "./core-checkpoint.js";
|
|
33
33
|
import { registerCoreDelegateCommand } from "./core-delegate.js";
|
|
34
|
+
import { registerCoreHandoffCommand } from "./core-handoff.js";
|
|
34
35
|
import { registerCoreOntologyCommand } from "./core-ontology.js";
|
|
35
36
|
import { registerCoreKnowledgePrefsCommand } from "./core-knowledge-prefs.js";
|
|
36
37
|
import { registerCorePolicyCommand } from "./core-policy.js";
|
|
38
|
+
import { registerCoreJobsNativeCommand } from "./core-jobs-native.js";
|
|
37
39
|
import { registerCoreCheckpointGateSettingCommand } from "./core-checkpoint-gate-setting.js";
|
|
38
40
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
39
41
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
|
@@ -210,6 +212,8 @@ export const SCAFFOLD_COMMANDS = [
|
|
|
210
212
|
* direct `hq core` command.
|
|
211
213
|
*/
|
|
212
214
|
export const SCAFFOLD_ONLY_ASSETS = [
|
|
215
|
+
{ asset: "core/scripts/jobs-schedule-parse.sh", root: "live", summary: "Rollback companion for native jobs schedule-parse" },
|
|
216
|
+
{ asset: "core/scripts/jobs-validate.sh", root: "live", summary: "Rollback companion for native jobs validate" },
|
|
213
217
|
// Shared shell libs sourced by the two relocated scripts (and hooks). Never
|
|
214
218
|
// dispatched on their own (so no `hq core` command), but they MUST ship in the
|
|
215
219
|
// tarball, or the bundled hq-session / checkpoint-stop-gate die at `source`
|
|
@@ -241,7 +245,7 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
241
245
|
{ asset: "core/scripts/ontology-source-worklist.sh", root: "live", summary: "Rollback companion for native ontology source worklist" },
|
|
242
246
|
{ asset: "core/scripts/source-yaml-validate.sh", root: "cwd", summary: "Rollback companion for native source YAML validation" },
|
|
243
247
|
// Shared by native knowledge-prefs and the ontology candidate shell fallback.
|
|
244
|
-
{ asset: "core/scripts/knowledge-prefs.sh", root: "live", summary: "
|
|
248
|
+
{ asset: "core/scripts/knowledge-prefs.sh", root: "live", summary: "Sibling dependency for ontology candidate rollback companion" },
|
|
245
249
|
// Rollback companions for the default-off native knowledge and policy routes.
|
|
246
250
|
// policy-retire also runs the scaffold validator in-process when native, but
|
|
247
251
|
// its bundled shell fallback needs both helper assets at these exact paths.
|
|
@@ -250,6 +254,11 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
250
254
|
{ asset: "core/scripts/read-policy-frontmatter.sh", root: "cwd", summary: "Rollback companion for native policy frontmatter" },
|
|
251
255
|
{ asset: "core/scripts/eval-trigger.sh", root: "live", summary: "Trigger grammar helper used by the policy validator companion" },
|
|
252
256
|
{ asset: ".claude/hooks/validate-policy-frontmatter.sh", root: "live", summary: "Policy validator companion used by policy-retire rollback" },
|
|
257
|
+
// Wave 2a handoff shells remain bundled as the default-off rollback path.
|
|
258
|
+
{ asset: "core/scripts/handoff-finalize.sh", root: "live", summary: "Rollback shell for native handoff finalize" },
|
|
259
|
+
{ asset: "core/scripts/handoff-post.sh", root: "live", summary: "Rollback shell for native handoff post" },
|
|
260
|
+
{ asset: "core/scripts/handoff-open-steps.sh", root: "live", summary: "Rollback shell for native handoff open-steps" },
|
|
261
|
+
{ asset: "core/scripts/qmd-reindex-bg.sh", root: "live", summary: "Rollback handoff reindex forwarder" },
|
|
253
262
|
...NATIVE_UTILITY_COMMANDS.map(({ asset, root, summary }) => ({
|
|
254
263
|
asset,
|
|
255
264
|
root,
|
|
@@ -302,6 +311,9 @@ function runEntry(entry, scope, args) {
|
|
|
302
311
|
args,
|
|
303
312
|
cwd,
|
|
304
313
|
hqRoot,
|
|
314
|
+
env: entry.asset === "core/scripts/hq-session.sh"
|
|
315
|
+
? { HQ_HQ_SESSION_NO_CLI: "1" }
|
|
316
|
+
: undefined,
|
|
305
317
|
});
|
|
306
318
|
// Propagate verbatim. Several of these scripts use their exit code as their
|
|
307
319
|
// interface (drift detected, lint failed), so remapping it would break the
|
|
@@ -326,8 +338,10 @@ export function registerCoreCommands(program) {
|
|
|
326
338
|
registerCoreOntologyCommand(core);
|
|
327
339
|
registerCoreCheckpointGateSettingCommand(core);
|
|
328
340
|
registerCoreDelegateCommand(core);
|
|
341
|
+
registerCoreHandoffCommand(core);
|
|
329
342
|
registerCoreKnowledgePrefsCommand(core);
|
|
330
343
|
registerCorePolicyCommand(core);
|
|
344
|
+
registerCoreJobsNativeCommand(core);
|
|
331
345
|
// Wave 2 handoff/worktree commands retain their byte-identical shell assets
|
|
332
346
|
// as a default-off rollback path for one release. Keep the gate helper small
|
|
333
347
|
// so sibling Wave 2 work can reuse it without editing this command table.
|
|
@@ -12,6 +12,7 @@ import { randomUUID } from "node:crypto";
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
14
14
|
import { AuthError } from "../utils/auth-error.js";
|
|
15
|
+
import { planGateErrorFromPayload } from "../utils/plan-gate-error.js";
|
|
15
16
|
import { redactErrorText } from "../utils/redact-error-text.js";
|
|
16
17
|
import { redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
|
|
17
18
|
import { connectionHealthNotice } from "../lib/integrations/health.js";
|
|
@@ -195,6 +196,12 @@ export async function raiseForResponse(res, fallback) {
|
|
|
195
196
|
raiseIfUnauthorized(res);
|
|
196
197
|
raiseIfUpstreamUnavailable(res);
|
|
197
198
|
const body = (await res.json().catch(() => ({})));
|
|
199
|
+
// A plan-limit 402 is normally decoded by vaultApiFetch before it gets here.
|
|
200
|
+
// Decode it again so no integration route can print the wire token
|
|
201
|
+
// `plan_limit_reached` as its whole message; main.ts renders the typed gate.
|
|
202
|
+
const planGate = planGateErrorFromPayload(res.status, body);
|
|
203
|
+
if (planGate)
|
|
204
|
+
throw planGate;
|
|
198
205
|
// hq-pro mints this error text, so it is untrusted input to a message we
|
|
199
206
|
// print verbatim — scrub credentials out before it can reach a terminal or a
|
|
200
207
|
// Sentry crash report (same reasoning as the gateway path below).
|
package/dist/commands/lanes.js
CHANGED
|
@@ -32,6 +32,7 @@ import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
|
32
32
|
import { startOutpostLaneSnapshotRefresh } from "../lib/lanes/outpost-snapshot-refresh.js";
|
|
33
33
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
34
34
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
35
|
+
import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
|
|
35
36
|
import { getOrFetchOutpostSnapshot, OUTPOST_SNAPSHOT_CACHE_TTL_MS, outpostLaneListSnapshotKey, OutpostSnapshotInvocationMemo, } from "../lib/lanes/outpost-snapshot-cache.js";
|
|
36
37
|
import { createOutpostHireGrantContext, parseOutpostHireGrantContext, selectOutpostHireGrantHierarchy, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
|
|
37
38
|
import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
|
|
@@ -2369,6 +2370,8 @@ async function emitLaneStartedForStories(ctx, company, lane, threadId, knownAbse
|
|
|
2369
2370
|
failures.push(err);
|
|
2370
2371
|
continue;
|
|
2371
2372
|
}
|
|
2373
|
+
if (!story.project_id?.trim() || !story.story_id?.trim())
|
|
2374
|
+
continue;
|
|
2372
2375
|
if (knownAbsentStories.has(storyBoardStoryKey(story.project_id, story.story_id)))
|
|
2373
2376
|
continue;
|
|
2374
2377
|
try {
|
|
@@ -3453,40 +3456,42 @@ async function runCreate(ctx, opts) {
|
|
|
3453
3456
|
: undefined,
|
|
3454
3457
|
});
|
|
3455
3458
|
}
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3459
|
+
if (!opts.quiet)
|
|
3460
|
+
printJson({
|
|
3461
|
+
ok: true,
|
|
3462
|
+
action: "create",
|
|
3463
|
+
lane_id: started.lane_id,
|
|
3464
|
+
state: started.state,
|
|
3465
|
+
queued_reason: started.queued_reason ?? null,
|
|
3466
|
+
priority: started.priority,
|
|
3467
|
+
provider: started.provider,
|
|
3468
|
+
account: started.account ?? null,
|
|
3469
|
+
model: started.model ?? null,
|
|
3470
|
+
effort: started.effort ?? null,
|
|
3471
|
+
effort_source: started.effort_source ?? null,
|
|
3472
|
+
host: started.host,
|
|
3473
|
+
...(autoPlacementSkippedHosts.length > 0
|
|
3474
|
+
? { skipped_hosts: autoPlacementSkippedHosts }
|
|
3475
|
+
: {}),
|
|
3476
|
+
company: started.company,
|
|
3477
|
+
project_id: started.project_id,
|
|
3478
|
+
story_id: started.story_id,
|
|
3479
|
+
stories: laneStories(started),
|
|
3480
|
+
story_warning: combinedStoryWarning(started, createStoryWarnings),
|
|
3481
|
+
thread_id: started.thread_id ?? null,
|
|
3482
|
+
senior: started.senior,
|
|
3483
|
+
depth: started.depth,
|
|
3484
|
+
timeout_secs: timeoutSecs ?? null,
|
|
3485
|
+
...(validation ? { validation } : {}),
|
|
3486
|
+
...(started.outpost_command_id
|
|
3487
|
+
? {
|
|
3488
|
+
outpost_command_id: started
|
|
3489
|
+
.outpost_command_id,
|
|
3490
|
+
}
|
|
3491
|
+
: {}),
|
|
3492
|
+
lane: started,
|
|
3493
|
+
});
|
|
3494
|
+
return started;
|
|
3490
3495
|
}
|
|
3491
3496
|
async function dispatchCreatedOutpostLane(ctx, input) {
|
|
3492
3497
|
const { hqRoot, lane, outpostId, briefText, timeout, preserveQueuedOnFailure = false, } = input;
|
|
@@ -6464,7 +6469,7 @@ async function emitStoryTransition(ctx, lane, projectId, storyId, event, status,
|
|
|
6464
6469
|
...payload,
|
|
6465
6470
|
lane_status: lane.state,
|
|
6466
6471
|
});
|
|
6467
|
-
if (!skipStoryStatusPatch) {
|
|
6472
|
+
if (!skipStoryStatusPatch && projectId?.trim() && storyId?.trim()) {
|
|
6468
6473
|
await ctx.mesh.patchStoryStatus(company, projectId, storyId, status === "blocked" ? "in_progress" : status);
|
|
6469
6474
|
}
|
|
6470
6475
|
}
|
|
@@ -7824,8 +7829,44 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7824
7829
|
summary.ok = false;
|
|
7825
7830
|
}
|
|
7826
7831
|
const terminalizedRemoteIds = new Set(remoteLost.map(({ id }) => id));
|
|
7832
|
+
let autopilot;
|
|
7833
|
+
try {
|
|
7834
|
+
const host = reconcileHostFromCtx(ctx);
|
|
7835
|
+
autopilot = await runLeadAutopilotPass({
|
|
7836
|
+
hqRoot,
|
|
7837
|
+
host: host.outpostId ?? "local",
|
|
7838
|
+
hostname: host.hostname,
|
|
7839
|
+
outpostId: host.outpostId,
|
|
7840
|
+
now: ctx.now(),
|
|
7841
|
+
dryRun: Boolean(opts.dryRun),
|
|
7842
|
+
readFlag: readLanesRegistryFlag,
|
|
7843
|
+
keepAliveResumedLeads: new Set(summary.resumed.filter((entry) => entry.reason === "keep_alive").map((entry) => entry.id)),
|
|
7844
|
+
startWorker: async ({ lead, story }) => {
|
|
7845
|
+
const workerCtx = { ...ctx, env: { ...ctx.env, HQ_LANE_ID: lead.lane_id } };
|
|
7846
|
+
const started = await runCreate(workerCtx, {
|
|
7847
|
+
company: lead.company?.slug,
|
|
7848
|
+
project: lead.project_id,
|
|
7849
|
+
story: story.id,
|
|
7850
|
+
briefFile: story.briefPath,
|
|
7851
|
+
provider: "codex",
|
|
7852
|
+
model: "gpt-6-luna",
|
|
7853
|
+
effort: "high",
|
|
7854
|
+
senior: `lane:${lead.lane_id}`,
|
|
7855
|
+
quiet: true,
|
|
7856
|
+
});
|
|
7857
|
+
return { lane_id: started.lane_id };
|
|
7858
|
+
},
|
|
7859
|
+
});
|
|
7860
|
+
}
|
|
7861
|
+
catch (error) {
|
|
7862
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7863
|
+
summary.ok = false;
|
|
7864
|
+
summary.errors.push({ code: "lead_autopilot_failed", message });
|
|
7865
|
+
process.stderr.write(`hq lanes autopilot: ${message}\n`);
|
|
7866
|
+
}
|
|
7827
7867
|
printJson({
|
|
7828
7868
|
...summary,
|
|
7869
|
+
...(autopilot ? { autopilot } : {}),
|
|
7829
7870
|
...(remoteLost.length > 0 ? { remote_lost: remoteLost } : {}),
|
|
7830
7871
|
...(mirrorRefresh.unverifiedRemote.size > 0
|
|
7831
7872
|
? {
|
|
@@ -8590,6 +8631,41 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8590
8631
|
.action(wrap(async (opts) => {
|
|
8591
8632
|
await runPickup(ctx, opts);
|
|
8592
8633
|
}));
|
|
8634
|
+
lanes
|
|
8635
|
+
.command("autopilot")
|
|
8636
|
+
.description("Check registered team leads and wake them for ready work")
|
|
8637
|
+
.command("run")
|
|
8638
|
+
.description("Run one lead autopilot check pass")
|
|
8639
|
+
.option("--dry-run", "Report proposed wakes without writing them")
|
|
8640
|
+
.option("--json", "Emit JSON on stdout (default)")
|
|
8641
|
+
.action(wrap(async (opts) => {
|
|
8642
|
+
const host = reconcileHostFromCtx(ctx);
|
|
8643
|
+
const result = await runLeadAutopilotPass({
|
|
8644
|
+
hqRoot: ctx.hqRoot(),
|
|
8645
|
+
host: host.outpostId ?? "local",
|
|
8646
|
+
hostname: host.hostname,
|
|
8647
|
+
outpostId: host.outpostId,
|
|
8648
|
+
now: ctx.now(),
|
|
8649
|
+
dryRun: Boolean(opts.dryRun),
|
|
8650
|
+
readFlag: readLanesRegistryFlag,
|
|
8651
|
+
startWorker: async ({ lead, story }) => {
|
|
8652
|
+
const workerCtx = { ...ctx, env: { ...ctx.env, HQ_LANE_ID: lead.lane_id } };
|
|
8653
|
+
const started = await runCreate(workerCtx, {
|
|
8654
|
+
company: lead.company?.slug,
|
|
8655
|
+
project: lead.project_id,
|
|
8656
|
+
story: story.id,
|
|
8657
|
+
briefFile: story.briefPath,
|
|
8658
|
+
provider: "codex",
|
|
8659
|
+
model: "gpt-6-luna",
|
|
8660
|
+
effort: "high",
|
|
8661
|
+
senior: `lane:${lead.lane_id}`,
|
|
8662
|
+
quiet: true,
|
|
8663
|
+
});
|
|
8664
|
+
return { lane_id: started.lane_id };
|
|
8665
|
+
},
|
|
8666
|
+
});
|
|
8667
|
+
printJson(result);
|
|
8668
|
+
}));
|
|
8593
8669
|
const reconcile = lanes
|
|
8594
8670
|
.command("reconcile", { hidden: true })
|
|
8595
8671
|
.description("Run one machine-owned reconcile pass and exit")
|
package/dist/commands/members.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
|
|
3
3
|
import { gateApiKeyCapabilities } from "../utils/api-key-command-gate.js";
|
|
4
4
|
import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
|
|
5
|
+
import { isPlanGateError } from "../utils/plan-gate-error.js";
|
|
5
6
|
const EMAIL_PATTERN = /^[^\s]+@[^\s]+$/;
|
|
6
7
|
const PERSON_UID_PATTERN = /^prs_[A-Za-z0-9_-]+$/;
|
|
7
8
|
const AGENT_UID_PATTERN = /^agt_[A-Za-z0-9_-]+$/i;
|
|
@@ -558,6 +559,10 @@ export function registerMembersCommand(program) {
|
|
|
558
559
|
}
|
|
559
560
|
}
|
|
560
561
|
catch (err) {
|
|
562
|
+
// A seat-limit refusal goes to main.ts's shared plan-gate renderer:
|
|
563
|
+
// reason, usage, fix options and upgrade link, then a non-zero exit.
|
|
564
|
+
if (isPlanGateError(err))
|
|
565
|
+
throw err;
|
|
561
566
|
// A 409 may refer to either an active membership or an existing
|
|
562
567
|
// pending invite. Probe both views so email targets get actionable
|
|
563
568
|
// guidance while preserving the terminal exit-0 behavior for bulk
|
package/dist/commands/mesh.js
CHANGED
|
@@ -11,7 +11,7 @@ import { readFileSync } from "node:fs";
|
|
|
11
11
|
import { findHqRoot } from "../utils/manifest.js";
|
|
12
12
|
import { CLI_VERSION } from "../cli-version.js";
|
|
13
13
|
import chalk from "chalk";
|
|
14
|
-
import {
|
|
14
|
+
import { ensureCognitoToken, isExpiring, isMachineIdentity, loadCachedTokens, } from "../utils/cognito-session.js";
|
|
15
15
|
import * as readline from "node:readline/promises";
|
|
16
16
|
import { STORY_STATUSES, appendThreadEvent, ensureProjectThread, eventPayload, getProjectView, listActiveMembershipCompanies, listActiveThreads, patchStory, putProjectView, registerMeshProject, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
17
17
|
import { closeSupersededProjectThread, ensureMissingBoardStories, ensureProjectBoard, boardStoryStatus, formatStoryStatusChanges, mergeProjectStories, prdSyncEventsThatChangeBoard, } from "../lib/mesh/project-board.js";
|
|
@@ -26,7 +26,9 @@ import { CLI_KIND_TO_SCHEMA, resolveEnqueueSessionId, } from "../lib/mesh/live/i
|
|
|
26
26
|
import { flushSessionEvents } from "../lib/mesh/live/flush.js";
|
|
27
27
|
import { backfillHeldSessions } from "../lib/mesh/live/backfill-held.js";
|
|
28
28
|
import { createSessionEventsPoster, resolveVaultApiBase, } from "../lib/mesh/live/session-events-client.js";
|
|
29
|
-
const
|
|
29
|
+
const ONLINE_MEMBERSHIP_VALIDATION_TIMEOUT_MS = 5_000;
|
|
30
|
+
// Leave 500 ms of headroom inside SessionStart's 2 s process-group limit.
|
|
31
|
+
const OFFLINE_MEMBERSHIP_VALIDATION_TIMEOUT_MS = 1_500;
|
|
30
32
|
import { buildEmitEvidence, machineEmitDisposition } from "../lib/mesh/live/emit-evidence.js";
|
|
31
33
|
import { appendHeldLine } from "../lib/mesh/live/spool.js";
|
|
32
34
|
import { createEmitPoster, buildMeshEmitEvent } from "../lib/mesh/live/emit-client.js";
|
|
@@ -860,29 +862,45 @@ async function runContextReconcile(opts) {
|
|
|
860
862
|
let offline = Boolean(opts.offline);
|
|
861
863
|
let token;
|
|
862
864
|
try {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
865
|
+
if (offline) {
|
|
866
|
+
if (isMachineIdentity()) {
|
|
867
|
+
token = await ensureCognitoToken({ tokenSource: "machine", interactive: false });
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
const cached = loadCachedTokens();
|
|
871
|
+
if (cached && !isExpiring(cached, 120)) {
|
|
872
|
+
token = cached.accessToken;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
token = await requireToken();
|
|
878
|
+
}
|
|
879
|
+
if (token) {
|
|
880
|
+
const sessionToken = token;
|
|
881
|
+
const membershipValidationTimeoutMs = offline
|
|
882
|
+
? OFFLINE_MEMBERSHIP_VALIDATION_TIMEOUT_MS
|
|
883
|
+
: ONLINE_MEMBERSHIP_VALIDATION_TIMEOUT_MS;
|
|
884
|
+
validateMembership = async (candidate) => {
|
|
885
|
+
const membership = await new Promise((resolve, reject) => {
|
|
886
|
+
const timer = setTimeout(() => reject(new Error("membership validation timed out")), membershipValidationTimeoutMs);
|
|
887
|
+
void resolveActiveMembershipCompany(sessionToken, candidate).then((value) => {
|
|
888
|
+
clearTimeout(timer);
|
|
889
|
+
resolve(value);
|
|
890
|
+
}, (error) => {
|
|
891
|
+
clearTimeout(timer);
|
|
892
|
+
reject(error);
|
|
893
|
+
});
|
|
876
894
|
});
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
895
|
+
if (!membership || typeof membership.companyUid !== "string" || !membership.companyUid) {
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
return { uid: membership.companyUid };
|
|
899
|
+
};
|
|
900
|
+
if (!offline) {
|
|
901
|
+
deliver = createWorkSessionDeliverer({ token: sessionToken });
|
|
902
|
+
fetchCandidates = createCandidatesFetcher({ token: sessionToken });
|
|
880
903
|
}
|
|
881
|
-
return { uid: membership.companyUid };
|
|
882
|
-
};
|
|
883
|
-
if (!offline) {
|
|
884
|
-
deliver = createWorkSessionDeliverer({ token: sessionToken });
|
|
885
|
-
fetchCandidates = createCandidatesFetcher({ token: sessionToken });
|
|
886
904
|
}
|
|
887
905
|
}
|
|
888
906
|
catch {
|
|
@@ -900,7 +918,10 @@ async function runContextReconcile(opts) {
|
|
|
900
918
|
log: (message) => console.error(chalk.dim(message)),
|
|
901
919
|
});
|
|
902
920
|
if (activation.status === "needs_choice") {
|
|
903
|
-
const interactive = !
|
|
921
|
+
const interactive = !offline &&
|
|
922
|
+
!opts.machine &&
|
|
923
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY) &&
|
|
924
|
+
!isMachineIdentity();
|
|
904
925
|
if (interactive) {
|
|
905
926
|
const choices = activation.memberships
|
|
906
927
|
.map((membership, index) => `${index + 1}. ${membership.slug}`)
|
|
@@ -920,7 +941,7 @@ async function runContextReconcile(opts) {
|
|
|
920
941
|
rl.close();
|
|
921
942
|
}
|
|
922
943
|
}
|
|
923
|
-
else if (!opts.machine) {
|
|
944
|
+
else if (!offline && !opts.machine) {
|
|
924
945
|
console.error(chalk.yellow("Multiple active memberships. Run `hq mesh context default set <slug>` to choose this device's default company."));
|
|
925
946
|
}
|
|
926
947
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const CLI_PERSONAL_TARGETED_PUSH_PATHS_FLAG = "cli.personal-targeted-push-paths";
|
|
2
|
+
/** The established full-scope behavior remains in force until an explicit hq-flags enablement. */
|
|
3
|
+
export declare function personalTargetedPushPathsEnabled(): boolean;
|
|
4
|
+
//# sourceMappingURL=personal-targeted-push-flag.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
2
|
+
export const CLI_PERSONAL_TARGETED_PUSH_PATHS_FLAG = "cli.personal-targeted-push-paths";
|
|
3
|
+
/** The established full-scope behavior remains in force until an explicit hq-flags enablement. */
|
|
4
|
+
export function personalTargetedPushPathsEnabled() {
|
|
5
|
+
return resolveProcessFlagGate(CLI_PERSONAL_TARGETED_PUSH_PATHS_FLAG, { fallback: false }, () => false);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=personal-targeted-push-flag.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Resolve explicit --personal paths against the same roots as a full personal push. */
|
|
2
|
+
export declare function resolveExplicitPersonalPushPaths(hqRoot: string, requestedPaths: readonly string[], cwd?: string): string[];
|
|
3
|
+
//# sourceMappingURL=personal-targeted-push-paths.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { computePersonalVaultPaths, matchPersonalVaultExclusion, } from "@indigoai-us/hq-cloud";
|
|
4
|
+
function isWithin(parent, candidate) {
|
|
5
|
+
const relative = path.relative(parent, candidate);
|
|
6
|
+
return relative === "" ||
|
|
7
|
+
(relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
8
|
+
}
|
|
9
|
+
function rejectPath(input, reason) {
|
|
10
|
+
throw new Error(`--personal cannot push path "${input}": ${reason}`);
|
|
11
|
+
}
|
|
12
|
+
/** Resolve explicit --personal paths against the same roots as a full personal push. */
|
|
13
|
+
export function resolveExplicitPersonalPushPaths(hqRoot, requestedPaths, cwd = process.cwd()) {
|
|
14
|
+
let realRoot;
|
|
15
|
+
try {
|
|
16
|
+
realRoot = fs.realpathSync.native(hqRoot);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : "unavailable";
|
|
20
|
+
throw new Error(`--personal cannot resolve HQ root "${hqRoot}" (${code})`);
|
|
21
|
+
}
|
|
22
|
+
const allowedRoots = computePersonalVaultPaths(realRoot)
|
|
23
|
+
.map((allowedPath) => {
|
|
24
|
+
try {
|
|
25
|
+
return fs.realpathSync.native(allowedPath);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : "unavailable";
|
|
29
|
+
throw new Error(`--personal personal-vault scope changed at "${allowedPath}" (${code})`);
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
.filter((allowedPath) => isWithin(realRoot, allowedPath));
|
|
33
|
+
const resolved = [];
|
|
34
|
+
for (const input of requestedPaths) {
|
|
35
|
+
const normalizedInput = path.resolve(cwd, input);
|
|
36
|
+
let realPath;
|
|
37
|
+
try {
|
|
38
|
+
realPath = fs.realpathSync.native(normalizedInput);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : "unavailable";
|
|
42
|
+
rejectPath(input, `path cannot be resolved (${code})`);
|
|
43
|
+
}
|
|
44
|
+
if (!isWithin(realRoot, realPath)) {
|
|
45
|
+
rejectPath(input, "resolved path escapes the HQ root");
|
|
46
|
+
}
|
|
47
|
+
let isDirectory;
|
|
48
|
+
try {
|
|
49
|
+
isDirectory = fs.statSync(realPath).isDirectory();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const code = error instanceof Error && "code" in error ? String(error.code) : "unavailable";
|
|
53
|
+
rejectPath(input, `path cannot be inspected (${code})`);
|
|
54
|
+
}
|
|
55
|
+
const relativePath = path.relative(realRoot, realPath).split(path.sep).join("/");
|
|
56
|
+
const exclusion = matchPersonalVaultExclusion(relativePath, isDirectory);
|
|
57
|
+
if (exclusion) {
|
|
58
|
+
rejectPath(input, `excluded by personal-vault rule ${exclusion.id}`);
|
|
59
|
+
}
|
|
60
|
+
if (!allowedRoots.some((allowedRoot) => isWithin(allowedRoot, realPath))) {
|
|
61
|
+
rejectPath(input, "resolved path is outside the personal-vault paths");
|
|
62
|
+
}
|
|
63
|
+
resolved.push(realPath);
|
|
64
|
+
}
|
|
65
|
+
return resolved;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=personal-targeted-push-paths.js.map
|