@indigoai-us/hq-cli 5.130.0 → 5.132.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 +155 -1
- package/contracts/preflight/v1/fixtures.json +87 -0
- package/contracts/preflight/v1/schema.json +58 -0
- package/dist/command-catalog.generated.d.ts +39 -0
- package/dist/command-catalog.generated.js +50 -0
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +1 -1
- package/dist/commands/files.js +137 -9
- package/dist/commands/lanes.d.ts +13 -2
- package/dist/commands/lanes.js +394 -42
- package/dist/commands/mesh.js +66 -13
- package/dist/commands/update.js +1 -1
- package/dist/lib/bot/paths.d.ts +7 -5
- package/dist/lib/bot/paths.js +8 -1
- package/dist/lib/bot/prompt.d.ts +0 -2
- package/dist/lib/bot/prompt.js +5 -11
- package/dist/lib/bot/room-policy.d.ts +1 -24
- package/dist/lib/bot/room-policy.js +2 -35
- package/dist/lib/bot/run.d.ts +7 -8
- package/dist/lib/bot/run.js +21 -33
- package/dist/lib/bot/session.d.ts +1 -1
- package/dist/lib/bot/session.js +1 -1
- package/dist/lib/lanes/accounts.d.ts +6 -0
- package/dist/lib/lanes/accounts.js +2 -1
- package/dist/lib/lanes/config.d.ts +4 -0
- package/dist/lib/lanes/config.js +9 -0
- package/dist/lib/lanes/index.d.ts +10 -3
- package/dist/lib/lanes/index.js +5 -1
- package/dist/lib/lanes/mesh-events.js +4 -0
- package/dist/lib/lanes/models.d.ts +78 -0
- package/dist/lib/lanes/models.js +205 -0
- package/dist/lib/lanes/move-account.d.ts +56 -0
- package/dist/lib/lanes/move-account.js +193 -0
- package/dist/lib/lanes/provider-rejection.d.ts +86 -0
- package/dist/lib/lanes/provider-rejection.js +258 -0
- package/dist/lib/lanes/quota.d.ts +17 -0
- package/dist/lib/lanes/quota.js +24 -0
- package/dist/lib/lanes/reconcile-install.d.ts +24 -3
- package/dist/lib/lanes/reconcile-install.js +48 -12
- package/dist/lib/lanes/reconcile.js +41 -11
- package/dist/lib/lanes/spawn.js +26 -5
- package/dist/lib/lanes/store.js +10 -0
- package/dist/lib/lanes/types.d.ts +49 -2
- package/dist/lib/lanes/types.js +4 -3
- package/dist/lib/lanes/watcher.d.ts +27 -0
- package/dist/lib/lanes/watcher.js +68 -5
- package/dist/lib/mesh/api.d.ts +2 -0
- package/dist/lib/mesh/api.js +15 -2
- package/dist/lib/mesh/live/backfill-held.d.ts +4 -3
- package/dist/lib/mesh/live/backfill-held.js +37 -14
- package/dist/lib/mesh/live/daemon/doctor.d.ts +4 -0
- package/dist/lib/mesh/live/daemon/doctor.js +15 -4
- package/dist/lib/mesh/live/daemon/install.d.ts +5 -0
- package/dist/lib/mesh/live/daemon/install.js +84 -1
- package/dist/lib/mesh/live/daemon/run.d.ts +5 -0
- package/dist/lib/mesh/live/daemon/run.js +17 -4
- package/dist/lib/mesh/live/emit-client.d.ts +2 -0
- package/dist/lib/mesh/live/emit-client.js +15 -8
- package/dist/lib/mesh/live/emit-evidence.d.ts +19 -0
- package/dist/lib/mesh/live/emit-evidence.js +20 -2
- package/dist/lib/mesh/live/emit.d.ts +9 -0
- package/dist/lib/mesh/live/emit.js +28 -2
- package/dist/lib/mesh/live/flush.js +9 -3
- package/dist/lib/search-index/index.js +22 -3
- package/dist/lib/work-context/company.d.ts +16 -0
- package/dist/lib/work-context/company.js +46 -14
- package/dist/lib/work-context/preflight-contract.d.ts +28 -0
- package/dist/lib/work-context/preflight-contract.js +85 -0
- package/dist/main.js +11 -1
- package/dist/utils/cognito-session.d.ts +39 -0
- package/dist/utils/cognito-session.js +119 -3
- package/dist/utils/environmental-error.d.ts +18 -0
- package/dist/utils/environmental-error.js +240 -7
- package/dist/utils/expected-cli-error.d.ts +2 -0
- package/dist/utils/expected-cli-error.js +4 -0
- package/dist/utils/self-update.js +4 -1
- package/dist/utils/vault-api.d.ts +1 -0
- package/dist/utils/vault-api.js +11 -3
- package/package.json +3 -2
package/dist/commands/files.js
CHANGED
|
@@ -8,6 +8,13 @@ import { GROUP_ID_PATTERN, EMAIL_PATTERN, normalizeFilePrefix } from "./_pattern
|
|
|
8
8
|
import { looksLikeCompanyUid } from "../utils/vault-api.js";
|
|
9
9
|
import { AuthError } from "../utils/auth-error.js";
|
|
10
10
|
import { formatAccessDenied, offerAccessLadder, } from "../utils/access-denied-hint.js";
|
|
11
|
+
import { fetchChannels, matchChannelsByName } from "./dm.js";
|
|
12
|
+
import { resolveCompanySlug, resolvePersonWithRosterFallback, } from "./people.js";
|
|
13
|
+
import { findHqRoot } from "../utils/manifest.js";
|
|
14
|
+
const PERSON_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
|
|
15
|
+
function collectMentions(value, previous) {
|
|
16
|
+
return [...previous, value];
|
|
17
|
+
}
|
|
11
18
|
// ---------------------------------------------------------------------------
|
|
12
19
|
// Pure helpers (exported for unit tests)
|
|
13
20
|
// ---------------------------------------------------------------------------
|
|
@@ -129,8 +136,23 @@ export function registerFilesCommand(program) {
|
|
|
129
136
|
.option("--full", "Grant access to the ENTIRE vault (the '*' wildcard prefix) — no need to quote a glob. Requires --with; defaults to write permission.")
|
|
130
137
|
.option("--expires <duration>", "Token expiry duration for share-session URL (e.g. 15m, 1h, 24h). Default 15m. Max 24h.")
|
|
131
138
|
.option("--no-open", "Print the share-session URL but do not launch the browser")
|
|
139
|
+
.option("--channel <name>", "Post the share record to this named channel instead of a DM")
|
|
140
|
+
.option("--mention <person>", "Mention a person on the channel share record (repeatable)", collectMentions, [])
|
|
132
141
|
.action(async (paths, opts) => {
|
|
133
142
|
try {
|
|
143
|
+
const mentions = opts.mention ?? [];
|
|
144
|
+
if (mentions.length > 0 && opts.channel === undefined) {
|
|
145
|
+
console.error(chalk.red("--mention requires --channel <name>."));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
const companySlug = files.opts().company;
|
|
149
|
+
const channelTarget = opts.channel !== undefined
|
|
150
|
+
? await resolveShareChannelTarget({
|
|
151
|
+
channelName: opts.channel,
|
|
152
|
+
mentions,
|
|
153
|
+
companySlug,
|
|
154
|
+
})
|
|
155
|
+
: undefined;
|
|
134
156
|
// Full-vault grant: a glob-safe affordance for "give this person the
|
|
135
157
|
// whole vault" so admins never have to quote a `*` (an unquoted glob
|
|
136
158
|
// expands to local filenames and instantly fails the one-prefix
|
|
@@ -150,8 +172,9 @@ export function registerFilesCommand(program) {
|
|
|
150
172
|
prefix: "*",
|
|
151
173
|
principal: opts.with,
|
|
152
174
|
permission: opts.permission ?? "write",
|
|
153
|
-
companySlug
|
|
175
|
+
companySlug,
|
|
154
176
|
fullVault: true,
|
|
177
|
+
...channelTarget,
|
|
155
178
|
});
|
|
156
179
|
return;
|
|
157
180
|
}
|
|
@@ -159,9 +182,9 @@ export function registerFilesCommand(program) {
|
|
|
159
182
|
console.error(chalk.red("usage: hq files share <paths...> [--with <principal>]"));
|
|
160
183
|
process.exit(1);
|
|
161
184
|
}
|
|
162
|
-
// Fork: --with present → existing direct-grant path.
|
|
163
|
-
//
|
|
164
|
-
//
|
|
185
|
+
// Fork: --with present → existing direct-grant path. --channel without
|
|
186
|
+
// --with grants each --mention as a person. No --with and no --channel
|
|
187
|
+
// → browser-launch share-session path.
|
|
165
188
|
if (opts.with !== undefined) {
|
|
166
189
|
if (paths.length !== 1) {
|
|
167
190
|
console.error(chalk.red("Direct grant (--with) takes exactly one prefix. Pass multiple paths only when minting a share-session URL."));
|
|
@@ -171,15 +194,42 @@ export function registerFilesCommand(program) {
|
|
|
171
194
|
prefix: paths[0],
|
|
172
195
|
principal: opts.with,
|
|
173
196
|
permission: opts.permission,
|
|
174
|
-
companySlug
|
|
197
|
+
companySlug,
|
|
198
|
+
...channelTarget,
|
|
175
199
|
});
|
|
176
200
|
return;
|
|
177
201
|
}
|
|
202
|
+
if (channelTarget) {
|
|
203
|
+
if (channelTarget.mentionPersonUids.length === 0) {
|
|
204
|
+
console.error(chalk.red("--channel requires --with <principal> or --mention <person>."));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
if (paths.length !== 1) {
|
|
208
|
+
console.error(chalk.red("Channel share takes exactly one prefix. Pass multiple paths only when minting a share-session URL."));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
const permission = opts.permission ?? "read";
|
|
212
|
+
for (const [index, uid] of channelTarget.mentionPersonUids.entries()) {
|
|
213
|
+
await runDirectGrant({
|
|
214
|
+
prefix: paths[0],
|
|
215
|
+
principal: uid,
|
|
216
|
+
permission,
|
|
217
|
+
companySlug,
|
|
218
|
+
...(index === 0
|
|
219
|
+
? {
|
|
220
|
+
channelId: channelTarget.channelId,
|
|
221
|
+
mentionPersonUids: channelTarget.mentionPersonUids,
|
|
222
|
+
}
|
|
223
|
+
: {}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
178
228
|
await runShareSession({
|
|
179
229
|
paths,
|
|
180
230
|
expires: opts.expires,
|
|
181
231
|
launchBrowser: opts.open,
|
|
182
|
-
companySlug
|
|
232
|
+
companySlug,
|
|
183
233
|
});
|
|
184
234
|
}
|
|
185
235
|
catch (err) {
|
|
@@ -511,6 +561,64 @@ export function registerFilesCommand(program) {
|
|
|
511
561
|
// files-browse.ts) onto the same group without re-creating it.
|
|
512
562
|
return files;
|
|
513
563
|
}
|
|
564
|
+
async function resolveShareChannelTarget(params) {
|
|
565
|
+
const token = (await resolveVaultCredential()).token;
|
|
566
|
+
const channels = await fetchChannels(token);
|
|
567
|
+
const matches = matchChannelsByName(channels, params.channelName);
|
|
568
|
+
if (matches.length === 0) {
|
|
569
|
+
console.error(chalk.red(`No channel named '${params.channelName}' — run \`hq channels\` to see your channels.`));
|
|
570
|
+
process.exit(1);
|
|
571
|
+
}
|
|
572
|
+
if (matches.length > 1) {
|
|
573
|
+
const scopes = matches
|
|
574
|
+
.map((c) => c.scope ?? "unknown")
|
|
575
|
+
.join(", ");
|
|
576
|
+
console.error(chalk.red(`'${params.channelName}' matches ${matches.length} channels (${scopes}) — this is ambiguous.`));
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
const channelId = matches[0].channelId;
|
|
580
|
+
const mentionPersonUids = [];
|
|
581
|
+
for (const mention of params.mentions) {
|
|
582
|
+
mentionPersonUids.push(await resolveMentionPersonUid(mention, params.companySlug));
|
|
583
|
+
}
|
|
584
|
+
return { channelId, mentionPersonUids };
|
|
585
|
+
}
|
|
586
|
+
async function resolveMentionPersonUid(mention, companySlug) {
|
|
587
|
+
const trimmed = mention.trim();
|
|
588
|
+
if (PERSON_UID_PATTERN.test(trimmed))
|
|
589
|
+
return trimmed;
|
|
590
|
+
const hqRoot = findHqRoot();
|
|
591
|
+
const slug = resolveCompanySlug(hqRoot, companySlug);
|
|
592
|
+
const result = await resolvePersonWithRosterFallback({
|
|
593
|
+
hqRoot,
|
|
594
|
+
slug,
|
|
595
|
+
name: trimmed,
|
|
596
|
+
});
|
|
597
|
+
if (result.status === "found" && result.person.personUid) {
|
|
598
|
+
return result.person.personUid;
|
|
599
|
+
}
|
|
600
|
+
if (result.status === "ambiguous") {
|
|
601
|
+
const names = result.matches.map((p) => p.name).join(", ");
|
|
602
|
+
console.error(chalk.red(`'${trimmed}' matches multiple people (${names}) — use a person uid or email.`));
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
const membership = await resolvePersonWithRosterFallback({
|
|
606
|
+
hqRoot,
|
|
607
|
+
slug,
|
|
608
|
+
name: trimmed,
|
|
609
|
+
opts: { membershipOnly: true },
|
|
610
|
+
});
|
|
611
|
+
if (membership.status === "found" && membership.person.personUid) {
|
|
612
|
+
return membership.person.personUid;
|
|
613
|
+
}
|
|
614
|
+
if (membership.status === "ambiguous") {
|
|
615
|
+
const names = membership.matches.map((p) => p.name).join(", ");
|
|
616
|
+
console.error(chalk.red(`'${trimmed}' matches multiple people (${names}) — use a person uid or email.`));
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
console.error(chalk.red(`Could not resolve --mention '${trimmed}' to a person.`));
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
514
622
|
async function runDirectGrant(params) {
|
|
515
623
|
const canonicalPrefix = normalizeCompanyFilePrefix(params.prefix, {
|
|
516
624
|
mutating: true,
|
|
@@ -535,11 +643,18 @@ async function runDirectGrant(params) {
|
|
|
535
643
|
const isAll = principal === "@all";
|
|
536
644
|
const isEmail = !isAll && EMAIL_PATTERN.test(principal);
|
|
537
645
|
const isGroup = !isAll && GROUP_ID_PATTERN.test(principal);
|
|
538
|
-
|
|
539
|
-
|
|
646
|
+
const isPerson = !isAll && !isEmail && !isGroup && PERSON_UID_PATTERN.test(principal);
|
|
647
|
+
if (!isAll && !isEmail && !isGroup && !isPerson) {
|
|
648
|
+
console.error(chalk.red(`Invalid principal '${principal}': must be '@all', an email address, a person uid, or a group id matching grp_<alphanumeric>`));
|
|
540
649
|
process.exit(1);
|
|
541
650
|
}
|
|
542
|
-
const granteeType = isAll
|
|
651
|
+
const granteeType = isAll
|
|
652
|
+
? "company-wide"
|
|
653
|
+
: isEmail
|
|
654
|
+
? "email"
|
|
655
|
+
: isPerson
|
|
656
|
+
? "person"
|
|
657
|
+
: "group";
|
|
543
658
|
const granteeId = isAll
|
|
544
659
|
? ""
|
|
545
660
|
: isEmail
|
|
@@ -558,6 +673,10 @@ async function runDirectGrant(params) {
|
|
|
558
673
|
granteeType,
|
|
559
674
|
granteeId,
|
|
560
675
|
permission: params.permission,
|
|
676
|
+
...(params.channelId ? { channelId: params.channelId } : {}),
|
|
677
|
+
...(params.mentionPersonUids && params.mentionPersonUids.length > 0
|
|
678
|
+
? { mentionPersonUids: params.mentionPersonUids }
|
|
679
|
+
: {}),
|
|
561
680
|
},
|
|
562
681
|
});
|
|
563
682
|
// No ACL row exists yet for this prefix. Auto-create one with this
|
|
@@ -572,6 +691,10 @@ async function runDirectGrant(params) {
|
|
|
572
691
|
body: {
|
|
573
692
|
prefix: canonicalPrefix,
|
|
574
693
|
entries: [{ granteeType, granteeId, permission: params.permission }],
|
|
694
|
+
...(params.channelId ? { channelId: params.channelId } : {}),
|
|
695
|
+
...(params.mentionPersonUids && params.mentionPersonUids.length > 0
|
|
696
|
+
? { mentionPersonUids: params.mentionPersonUids }
|
|
697
|
+
: {}),
|
|
575
698
|
},
|
|
576
699
|
});
|
|
577
700
|
autoCreated = res.ok;
|
|
@@ -610,6 +733,11 @@ async function runDirectGrant(params) {
|
|
|
610
733
|
else {
|
|
611
734
|
console.log(chalk.green(`${verb} ${params.permission} on ${printedPrefix} to ${principalLabel}`));
|
|
612
735
|
}
|
|
736
|
+
if (typeof data.record?.error === "string" &&
|
|
737
|
+
typeof data.record.status === "number" &&
|
|
738
|
+
data.record.status >= 400) {
|
|
739
|
+
console.log(data.record.error);
|
|
740
|
+
}
|
|
613
741
|
}
|
|
614
742
|
async function runShareSession(params) {
|
|
615
743
|
// Validate every path before minting a session; an anchored path must never
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.
|
|
|
10
10
|
import { type LaneCompanyRef, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
|
|
11
11
|
import { questionBodyAbsPath } from "../lib/lanes/questions.js";
|
|
12
12
|
import { type RunCompactionFn } from "../lib/lanes/compaction.js";
|
|
13
|
+
import { type KnownModelsDeps, type ProbeCommand } from "../lib/lanes/models.js";
|
|
13
14
|
import { spawnLaneWorker, type KillProcessFn, type ProveDetachedFn, type SpawnProcessFn } from "../lib/lanes/spawn.js";
|
|
14
15
|
export { spawnLaneWorker };
|
|
15
16
|
export { questionBodyAbsPath };
|
|
@@ -62,6 +63,8 @@ export interface LanesCommandDeps {
|
|
|
62
63
|
presenceConnected?: boolean;
|
|
63
64
|
systemdRoot?: string;
|
|
64
65
|
runSystemctl?: RunCommand;
|
|
66
|
+
/** Provider CLI probe for --model validation; tests inject a fake. */
|
|
67
|
+
probeProvider?: ProbeCommand;
|
|
65
68
|
resolveMachineIdentity?: () => MachineIdentity;
|
|
66
69
|
stageOutpostPayload?: (outpostId: string, bytes: Uint8Array) => Promise<{
|
|
67
70
|
getUrl: string;
|
|
@@ -100,14 +103,22 @@ export declare function composeWorkerBrief(worker: ResolvedLaneWorker, taskBrief
|
|
|
100
103
|
*/
|
|
101
104
|
export declare function composeGeneralBrief(taskBrief: string): string;
|
|
102
105
|
export declare function parseMaxRuntimeSeconds(raw: unknown, workerId: string): number | undefined;
|
|
103
|
-
export declare const PICKUP_TERMINAL_REASONS: readonly ["account_not_logged_in", "unknown_account", "account_disabled"];
|
|
106
|
+
export declare const PICKUP_TERMINAL_REASONS: readonly ["account_not_logged_in", "unknown_account", "account_disabled", "model_unsupported"];
|
|
104
107
|
export declare const DISPATCH_TERMINAL_REASON: "outpost_dispatch_failed";
|
|
105
108
|
export type PickupTerminalReason = (typeof PICKUP_TERMINAL_REASONS)[number];
|
|
109
|
+
/**
|
|
110
|
+
* The CAS updater a pickup failure applies. A lane that is already terminal
|
|
111
|
+
* for any reason (a cancel that landed after the caller's last read, an
|
|
112
|
+
* earlier pickup failure) is returned unchanged: the guard lives inside the
|
|
113
|
+
* same compare-and-swap that writes the failure, so no read-then-write gap
|
|
114
|
+
* can overwrite another writer's terminal decision.
|
|
115
|
+
*/
|
|
116
|
+
export declare function pickupFailureUpdater(reasonRaw: string, details: Record<string, unknown>): (current: LaneRecord) => LaneRecord;
|
|
106
117
|
/**
|
|
107
118
|
* Outpost-side account bind (spec §5.2). Writes a named terminal reason,
|
|
108
119
|
* never a plain `failed`.
|
|
109
120
|
*/
|
|
110
|
-
export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string): LaneRecord;
|
|
121
|
+
export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string, modelDeps?: KnownModelsDeps): LaneRecord;
|
|
111
122
|
export declare function cancelPendingQuestionsForLane(hqRoot: string, laneId: string, nowIso: string, operatorReason?: string): QuestionRecord[];
|
|
112
123
|
export declare function stopCommittedState(state: LaneRecord["state"]): "cancelled" | "killed";
|
|
113
124
|
export declare function registerLanesCommand(parent: Command, deps?: LanesCommandDeps): Command;
|