@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/assets/scaffold/core/scripts/session-journal.sh +116 -24
  3. package/dist/command-catalog.generated.d.ts +218 -5
  4. package/dist/command-catalog.generated.js +269 -5
  5. package/dist/commands/auth.js +6 -5
  6. package/dist/commands/core-session-helper-runtime.d.ts +1 -0
  7. package/dist/commands/core-session-journal-native.d.ts +1 -0
  8. package/dist/commands/core-session-journal-native.js +158 -34
  9. package/dist/commands/feedback.d.ts +12 -0
  10. package/dist/commands/feedback.js +52 -0
  11. package/dist/commands/files-browse.d.ts +8 -0
  12. package/dist/commands/files-browse.js +83 -1
  13. package/dist/commands/install-global.d.ts +2 -0
  14. package/dist/commands/install-global.js +24 -1
  15. package/dist/commands/integrations-connect.js +1 -0
  16. package/dist/commands/integrations-manage.d.ts +2 -0
  17. package/dist/commands/integrations-manage.js +6 -4
  18. package/dist/commands/lanes-link.js +5 -3
  19. package/dist/commands/lanes-session-start.d.ts +12 -0
  20. package/dist/commands/lanes-session-start.js +93 -12
  21. package/dist/commands/meetings.d.ts +11 -0
  22. package/dist/commands/meetings.js +41 -11
  23. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2486 -1794
  24. package/dist/hq-cloud-sync-runner/package.json +1 -1
  25. package/dist/index.js +21 -0
  26. package/dist/lanes-session-start-argv.d.ts +7 -0
  27. package/dist/lanes-session-start-argv.js +9 -0
  28. package/dist/lib/bot/log.d.ts +3 -2
  29. package/dist/lib/bot/progress.d.ts +23 -1
  30. package/dist/lib/bot/progress.js +43 -5
  31. package/dist/lib/bot/run.js +2 -0
  32. package/dist/lib/bot/runtime/claude.d.ts +12 -0
  33. package/dist/lib/bot/runtime/claude.js +20 -0
  34. package/dist/lib/bot/runtime/index.d.ts +10 -1
  35. package/dist/lib/bot/runtime/index.js +14 -2
  36. package/dist/lib/bot/runtime/types.d.ts +8 -0
  37. package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
  38. package/dist/lib/doctor/checks/ps1-shims.js +82 -0
  39. package/dist/lib/doctor/fix/apply.d.ts +13 -1
  40. package/dist/lib/doctor/fix/apply.js +38 -3
  41. package/dist/lib/doctor/fix/remediation.d.ts +9 -1
  42. package/dist/lib/doctor/fix/remediation.js +22 -0
  43. package/dist/lib/doctor/registry.js +2 -0
  44. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  45. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  46. package/dist/lib/lanes/links.d.ts +2 -1
  47. package/dist/lib/lanes/links.js +235 -17
  48. package/dist/lib/lanes/registry-flag.d.ts +24 -0
  49. package/dist/lib/lanes/registry-flag.js +72 -6
  50. package/dist/lib/lanes/store.d.ts +4 -2
  51. package/dist/lib/lanes/store.js +8 -8
  52. package/dist/lib/windows/ps1-shims.d.ts +60 -0
  53. package/dist/lib/windows/ps1-shims.js +152 -0
  54. package/dist/utils/self-update.d.ts +2 -0
  55. package/dist/utils/self-update.js +8 -0
  56. package/dist/utils/staged-npm-install.js +13 -2
  57. package/dist/utils/token-keychain.d.ts +11 -8
  58. package/dist/utils/token-keychain.js +36 -15
  59. package/package.json +8 -8
  60. 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 { readLanesRegistryFlagFailOpen } from "../lib/lanes/registry-flag.js";
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
- const flagEnabled = await readLanesRegistryFlagFailOpen(LANE_CONDUCT_SESSION_START_FLAG, {
282
- env,
283
- defaultValue: true,
284
- reportFailure: (error) => {
285
- const kind = error instanceof Error ? error.name : "Error";
286
- debug(`hq lanes session-start: flag read failed (${kind})`);
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
- .action(async () => runLaneSessionStart());
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 (for multi-company users)")
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);