@indigoai-us/hq-cli 5.345.160 → 5.345.161
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 +24 -0
- package/assets/scaffold/core/scripts/session-journal.sh +116 -24
- package/dist/command-catalog.generated.d.ts +218 -5
- package/dist/command-catalog.generated.js +269 -5
- package/dist/commands/auth.js +6 -5
- package/dist/commands/core-session-helper-runtime.d.ts +1 -0
- package/dist/commands/core-session-journal-native.d.ts +1 -0
- package/dist/commands/core-session-journal-native.js +158 -34
- package/dist/commands/feedback.d.ts +12 -0
- package/dist/commands/feedback.js +52 -0
- package/dist/commands/files-browse.d.ts +8 -0
- package/dist/commands/files-browse.js +83 -1
- package/dist/commands/install-global.d.ts +2 -0
- package/dist/commands/install-global.js +24 -1
- package/dist/commands/integrations-connect.js +1 -0
- package/dist/commands/integrations-manage.d.ts +2 -0
- package/dist/commands/integrations-manage.js +6 -4
- package/dist/commands/lanes-link.js +5 -3
- package/dist/commands/lanes-session-start.d.ts +12 -0
- package/dist/commands/lanes-session-start.js +93 -12
- package/dist/commands/meetings.d.ts +11 -0
- package/dist/commands/meetings.js +41 -11
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2486 -1794
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +21 -0
- package/dist/lanes-session-start-argv.d.ts +7 -0
- package/dist/lanes-session-start-argv.js +9 -0
- package/dist/lib/bot/log.d.ts +3 -2
- package/dist/lib/bot/progress.d.ts +23 -1
- package/dist/lib/bot/progress.js +43 -5
- package/dist/lib/bot/run.js +2 -0
- package/dist/lib/bot/runtime/claude.d.ts +12 -0
- package/dist/lib/bot/runtime/claude.js +20 -0
- package/dist/lib/bot/runtime/index.d.ts +10 -1
- package/dist/lib/bot/runtime/index.js +14 -2
- package/dist/lib/bot/runtime/types.d.ts +8 -0
- package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
- package/dist/lib/doctor/checks/ps1-shims.js +82 -0
- package/dist/lib/doctor/fix/apply.d.ts +13 -1
- package/dist/lib/doctor/fix/apply.js +38 -3
- package/dist/lib/doctor/fix/remediation.d.ts +9 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/doctor/registry.js +2 -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/lanes/links.d.ts +2 -1
- package/dist/lib/lanes/links.js +235 -17
- package/dist/lib/lanes/registry-flag.d.ts +24 -0
- package/dist/lib/lanes/registry-flag.js +72 -6
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +8 -8
- package/dist/lib/windows/ps1-shims.d.ts +60 -0
- package/dist/lib/windows/ps1-shims.js +152 -0
- package/dist/utils/self-update.d.ts +2 -0
- package/dist/utils/self-update.js +8 -0
- package/dist/utils/staged-npm-install.js +13 -2
- package/dist/utils/token-keychain.d.ts +11 -8
- package/dist/utils/token-keychain.js +36 -15
- package/package.json +8 -8
- package/scripts/install-hook-engine.mjs +12 -0
|
@@ -4,8 +4,17 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
7
|
-
import {
|
|
7
|
+
import { readLanesRegistryFlagOffline, readLanesRegistryFlagResult } from "../lib/lanes/registry-flag.js";
|
|
8
8
|
import { LANE_CONDUCT_SESSION_START_FLAG, LANE_ORPHAN_SWEEP_CONTEXT_FLAG, LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG } from "../lib/lanes/flag-keys.js";
|
|
9
|
+
const SESSION_START_FLAGS = [
|
|
10
|
+
LANE_CONDUCT_SESSION_START_FLAG,
|
|
11
|
+
LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG,
|
|
12
|
+
LANE_ORPHAN_SWEEP_CONTEXT_FLAG,
|
|
13
|
+
];
|
|
14
|
+
/** Offline flag values older than this trigger one background registry read. */
|
|
15
|
+
export const SESSION_START_FLAG_REFRESH_AFTER_MS = 10 * 60 * 1000;
|
|
16
|
+
const SESSION_START_FLAG_REFRESH_LEASE_MS = 60 * 1000;
|
|
17
|
+
const SESSION_START_FLAG_REFRESH_LEASE = "lanes-session-start-flag-refresh.lock";
|
|
9
18
|
const CODEX_DEFAULT_KEYS = ["conduct_engine", "conduct_child_model", "conduct_child_effort", "conduct_default_source"];
|
|
10
19
|
function readSessionMetaValue(text, key) {
|
|
11
20
|
const line = text.split(/\r?\n/).find((entry) => new RegExp(`^\\s*${key}:`).test(entry));
|
|
@@ -247,6 +256,58 @@ export function startSessionStartOrphanSweep(root, env = process.env, spawnDetac
|
|
|
247
256
|
// SessionStart's orphan report is advisory and must never block startup.
|
|
248
257
|
}
|
|
249
258
|
}
|
|
259
|
+
function flagRefreshLeaseDir(env) {
|
|
260
|
+
const stateDir = env.HQ_STATE_DIR?.trim();
|
|
261
|
+
if (stateDir)
|
|
262
|
+
return stateDir;
|
|
263
|
+
const home = env.HOME?.trim() || env.USERPROFILE?.trim();
|
|
264
|
+
return home ? path.join(home, ".hq") : null;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Start a detached `hq lanes session-start --refresh-flags` so the registry
|
|
268
|
+
* read never runs inside the SessionStart hook. A short lease keeps a burst of
|
|
269
|
+
* session starts to one refresh. An env without a state dir never spawns.
|
|
270
|
+
*/
|
|
271
|
+
export function startSessionStartFlagRefresh(env, spawnDetached = spawn, now = Date.now()) {
|
|
272
|
+
const dir = flagRefreshLeaseDir(env);
|
|
273
|
+
const cli = process.argv[1];
|
|
274
|
+
if (!dir || !cli)
|
|
275
|
+
return false;
|
|
276
|
+
try {
|
|
277
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
278
|
+
const lease = path.join(dir, SESSION_START_FLAG_REFRESH_LEASE);
|
|
279
|
+
try {
|
|
280
|
+
if (now - fs.statSync(lease).mtimeMs < SESSION_START_FLAG_REFRESH_LEASE_MS)
|
|
281
|
+
return false;
|
|
282
|
+
fs.rmSync(lease, { force: true });
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (error.code !== "ENOENT")
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
fs.closeSync(fs.openSync(lease, "wx", 0o600));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
const child = spawnDetached(process.execPath, [cli, "lanes", "session-start", "--refresh-flags"], {
|
|
295
|
+
detached: true,
|
|
296
|
+
stdio: "ignore",
|
|
297
|
+
env: { ...env, HQ_NO_UPDATE_CHECK: "1" },
|
|
298
|
+
});
|
|
299
|
+
child.on("error", () => { });
|
|
300
|
+
child.unref();
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Read the session-start flags from the registry, refreshing the offline cache. */
|
|
308
|
+
export async function refreshSessionStartFlags(env = process.env) {
|
|
309
|
+
await Promise.all(SESSION_START_FLAGS.map((flag) => readLanesRegistryFlagResult(flag, { env, defaultValue: true, reportFailure: () => undefined }).catch(() => undefined)));
|
|
310
|
+
}
|
|
250
311
|
/** SessionStart-compatible, fail-open conduct bootstrap. */
|
|
251
312
|
export async function runLaneSessionStart(options = {}) {
|
|
252
313
|
const env = options.env ?? process.env;
|
|
@@ -278,19 +339,33 @@ export async function runLaneSessionStart(options = {}) {
|
|
|
278
339
|
if (/(^|,)auto-conduct(,|$)/.test(disabledHooks))
|
|
279
340
|
return;
|
|
280
341
|
const root = (options.resolveRoot ?? ((e, cwd) => resolveLanesHqRoot({ env: e, cwd })))(env, options.cwd);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
342
|
+
// Flags come from offline state only. A missing or old value fails open and
|
|
343
|
+
// schedules one background registry read for the next session start.
|
|
344
|
+
let refreshNeeded = false;
|
|
345
|
+
const readFlag = (flag) => {
|
|
346
|
+
let read = null;
|
|
347
|
+
try {
|
|
348
|
+
read = readLanesRegistryFlagOffline(flag, { env, defaultValue: true });
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
read = null;
|
|
352
|
+
}
|
|
353
|
+
if (!read || (read.source !== "local-override" && read.ageMs > SESSION_START_FLAG_REFRESH_AFTER_MS))
|
|
354
|
+
refreshNeeded = true;
|
|
355
|
+
return read?.value ?? true;
|
|
356
|
+
};
|
|
357
|
+
const flagEnabled = readFlag(LANE_CONDUCT_SESSION_START_FLAG);
|
|
358
|
+
const rootScoped = readFlag(LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG);
|
|
359
|
+
const contextEnabled = readFlag(LANE_ORPHAN_SWEEP_CONTEXT_FLAG);
|
|
360
|
+
if (refreshNeeded) {
|
|
361
|
+
try {
|
|
362
|
+
(options.refreshFlags ?? ((e) => { startSessionStartFlagRefresh(e); }))(env);
|
|
363
|
+
}
|
|
364
|
+
catch { /* advisory */ }
|
|
365
|
+
}
|
|
289
366
|
if (!flagEnabled)
|
|
290
367
|
return;
|
|
291
|
-
const rootScoped = await readLanesRegistryFlagFailOpen(LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG, { env, defaultValue: true });
|
|
292
368
|
(options.startSweep ?? ((r, e) => startSessionStartOrphanSweep(r, e, spawn, rootScoped)))(root, env);
|
|
293
|
-
const contextEnabled = await readLanesRegistryFlagFailOpen(LANE_ORPHAN_SWEEP_CONTEXT_FLAG, { env, defaultValue: true });
|
|
294
369
|
if (contextEnabled) {
|
|
295
370
|
const count = readOrphanCount(root, env, rootScoped, (error) => debug(`hq lanes session-start: orphan report read failed (${error instanceof Error ? error.name : "Error"})`));
|
|
296
371
|
if (count > 0)
|
|
@@ -324,6 +399,12 @@ export async function runLaneSessionStart(options = {}) {
|
|
|
324
399
|
export function registerLaneSessionStartCommand(lanes) {
|
|
325
400
|
lanes.command("session-start", { hidden: true })
|
|
326
401
|
.description("Internal SessionStart bootstrap")
|
|
327
|
-
.
|
|
402
|
+
.option("--refresh-flags", "Refresh the offline session-start flag values from the registry")
|
|
403
|
+
.action(async (opts) => {
|
|
404
|
+
if (opts.refreshFlags)
|
|
405
|
+
await refreshSessionStartFlags();
|
|
406
|
+
else
|
|
407
|
+
await runLaneSessionStart();
|
|
408
|
+
});
|
|
328
409
|
}
|
|
329
410
|
//# sourceMappingURL=lanes-session-start.js.map
|
|
@@ -9,5 +9,16 @@ export declare const SHORT_ID_MAX_PAGES = 50;
|
|
|
9
9
|
* names no plan: the flat plans are closed.
|
|
10
10
|
*/
|
|
11
11
|
export declare function meetingPaymentRequiredLines(body: Record<string, unknown>, resolvePlan?: () => Promise<PricingPlan | null>): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* The company a `hq meetings` call is about: the explicit `--company` value,
|
|
14
|
+
* otherwise the company an explicitly named HQ session is bound to (the session's company
|
|
15
|
+
* lock, from `scope-capability.json` or `meta.yaml`). A session bound to
|
|
16
|
+
* Personal, an unbound session, or an unreadable lock gives undefined so the
|
|
17
|
+
* server's own default applies. Before this, every subcommand sent no
|
|
18
|
+
* company at all without `--company`, so the list, get, transcript and
|
|
19
|
+
* search views silently showed only the caller's personal recordings.
|
|
20
|
+
* Feedback 04771691-fcbc-4847-899a-6c966c561b52.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sessionCompanySlug(env?: NodeJS.ProcessEnv): string | undefined;
|
|
12
23
|
export declare function registerMeetingsCommand(program: Command): void;
|
|
13
24
|
//# sourceMappingURL=meetings.d.ts.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
4
|
+
import { readSessionCompanyLock } from "../lib/session-company-lock.js";
|
|
5
|
+
import { sessionIdFromEnv } from "../lib/core-utils/session-id.js";
|
|
4
6
|
import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
|
|
5
7
|
import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
|
|
6
8
|
import { mapWithConcurrency } from "./sync-manifest.js";
|
|
@@ -434,11 +436,39 @@ function printMeetingTable(meetings) {
|
|
|
434
436
|
].join(" "));
|
|
435
437
|
}
|
|
436
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* The company a `hq meetings` call is about: the explicit `--company` value,
|
|
441
|
+
* otherwise the company an explicitly named HQ session is bound to (the session's company
|
|
442
|
+
* lock, from `scope-capability.json` or `meta.yaml`). A session bound to
|
|
443
|
+
* Personal, an unbound session, or an unreadable lock gives undefined so the
|
|
444
|
+
* server's own default applies. Before this, every subcommand sent no
|
|
445
|
+
* company at all without `--company`, so the list, get, transcript and
|
|
446
|
+
* search views silently showed only the caller's personal recordings.
|
|
447
|
+
* Feedback 04771691-fcbc-4847-899a-6c966c561b52.
|
|
448
|
+
*/
|
|
449
|
+
export function sessionCompanySlug(env = process.env) {
|
|
450
|
+
// The shared reader also supports the last-writer-wins .current pointer.
|
|
451
|
+
// It cannot identify a plain terminal or cron caller's session.
|
|
452
|
+
if (!sessionIdFromEnv(env))
|
|
453
|
+
return undefined;
|
|
454
|
+
let lock;
|
|
455
|
+
try {
|
|
456
|
+
lock = readSessionCompanyLock(env);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
// Fall through to the server default, but say why so an unbound call is
|
|
460
|
+
// never mistaken for a working company scope.
|
|
461
|
+
console.error(chalk.dim(` (session company not applied: ${err instanceof Error ? err.message : String(err)})`));
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
const primary = lock?.companies[0];
|
|
465
|
+
return primary && primary !== "personal" ? primary : undefined;
|
|
466
|
+
}
|
|
437
467
|
export function registerMeetingsCommand(program) {
|
|
438
468
|
const meetings = program
|
|
439
469
|
.command("meetings")
|
|
440
470
|
.description("View and search meeting recordings, transcripts, and notes")
|
|
441
|
-
.option("--company <slug>", "Company slug (
|
|
471
|
+
.option("--company <slug>", "Company slug (defaults to the company this HQ session is bound to)")
|
|
442
472
|
.option("--json", "Output raw JSON instead of formatted text");
|
|
443
473
|
// ── hq meetings list ──────────────────────────────────────────────
|
|
444
474
|
meetings
|
|
@@ -450,7 +480,7 @@ export function registerMeetingsCommand(program) {
|
|
|
450
480
|
try {
|
|
451
481
|
const token = await ensureCognitoToken();
|
|
452
482
|
const query = {};
|
|
453
|
-
const companySlug = meetings.opts().company;
|
|
483
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
454
484
|
if (opts.limit)
|
|
455
485
|
query.limit = opts.limit;
|
|
456
486
|
if (opts.next)
|
|
@@ -488,9 +518,9 @@ export function registerMeetingsCommand(program) {
|
|
|
488
518
|
.action(async (recallBotId, opts) => {
|
|
489
519
|
try {
|
|
490
520
|
const token = await ensureCognitoToken();
|
|
491
|
-
const companySlug = meetings.opts().company;
|
|
521
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
492
522
|
if (!companySlug)
|
|
493
|
-
throw new Error("--company <slug|uid> is required for live transcripts");
|
|
523
|
+
throw new Error("--company <slug|uid> is required for live transcripts (this session is not bound to a company)");
|
|
494
524
|
const companyId = companySlug.startsWith("cmp_") ? companySlug : await getCompanyUid(token, companySlug);
|
|
495
525
|
if (!recallBotId) {
|
|
496
526
|
if (opts.follow || opts.since)
|
|
@@ -610,9 +640,9 @@ export function registerMeetingsCommand(program) {
|
|
|
610
640
|
.description("Import a normalized historical transcript JSON file")
|
|
611
641
|
.action(async (file) => {
|
|
612
642
|
try {
|
|
613
|
-
const companySlug = meetings.opts().company;
|
|
643
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
614
644
|
if (!companySlug) {
|
|
615
|
-
throw new Error("--company <slug> is required for historical imports");
|
|
645
|
+
throw new Error("--company <slug> is required for historical imports (this session is not bound to a company)");
|
|
616
646
|
}
|
|
617
647
|
const bytes = await readFile(file);
|
|
618
648
|
if (bytes.byteLength > 5 * 1024 * 1024) {
|
|
@@ -666,7 +696,7 @@ export function registerMeetingsCommand(program) {
|
|
|
666
696
|
.action(async (meetingUrl) => {
|
|
667
697
|
try {
|
|
668
698
|
const token = await ensureCognitoToken();
|
|
669
|
-
const companySlug = meetings.opts().company;
|
|
699
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
670
700
|
// Always name a company. With no companyId the backend files the bot
|
|
671
701
|
// under Personal, which can never record, so an entitled user would
|
|
672
702
|
// get the payment-required refusal. Without --company this uses the
|
|
@@ -707,7 +737,7 @@ export function registerMeetingsCommand(program) {
|
|
|
707
737
|
try {
|
|
708
738
|
const token = await ensureCognitoToken();
|
|
709
739
|
const query = {};
|
|
710
|
-
const companySlug = meetings.opts().company;
|
|
740
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
711
741
|
if (companySlug)
|
|
712
742
|
query.companyId = await getCompanyUid(token, companySlug);
|
|
713
743
|
const meetingId = await resolveShortId(token, rawId, query);
|
|
@@ -832,7 +862,7 @@ export function registerMeetingsCommand(program) {
|
|
|
832
862
|
try {
|
|
833
863
|
const token = await ensureCognitoToken();
|
|
834
864
|
const params = { q: query };
|
|
835
|
-
const companySlug = meetings.opts().company;
|
|
865
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
836
866
|
if (companySlug)
|
|
837
867
|
params.companyId = await getCompanyUid(token, companySlug);
|
|
838
868
|
const res = await vaultApiFetch({
|
|
@@ -863,7 +893,7 @@ export function registerMeetingsCommand(program) {
|
|
|
863
893
|
try {
|
|
864
894
|
const token = await ensureCognitoToken();
|
|
865
895
|
const query = {};
|
|
866
|
-
const companySlug = meetings.opts().company;
|
|
896
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
867
897
|
if (companySlug)
|
|
868
898
|
query.companyId = await getCompanyUid(token, companySlug);
|
|
869
899
|
const meetingId = await resolveShortId(token, rawId, query);
|
|
@@ -932,7 +962,7 @@ export function registerMeetingsCommand(program) {
|
|
|
932
962
|
try {
|
|
933
963
|
const token = await ensureCognitoToken();
|
|
934
964
|
const query = {};
|
|
935
|
-
const companySlug = meetings.opts().company;
|
|
965
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
936
966
|
if (companySlug)
|
|
937
967
|
query.companyId = await getCompanyUid(token, companySlug);
|
|
938
968
|
const meetingId = await resolveShortId(token, rawId, query);
|