@indigoai-us/hq-cli 5.345.135 → 5.345.136

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 (63) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/assets/scaffold/core/scripts/worktree-gc.sh +98 -8
  3. package/dist/command-catalog.generated.d.ts +17 -6
  4. package/dist/command-catalog.generated.js +23 -7
  5. package/dist/commands/core-checkpoint-native.d.ts +10 -0
  6. package/dist/commands/core-checkpoint-native.js +60 -15
  7. package/dist/commands/daemon.js +3 -1
  8. package/dist/commands/integrations-core.d.ts +22 -0
  9. package/dist/commands/integrations-core.js +25 -0
  10. package/dist/commands/integrations-manage.d.ts +24 -1
  11. package/dist/commands/integrations-manage.js +92 -17
  12. package/dist/commands/integrations.d.ts +11 -0
  13. package/dist/commands/integrations.js +100 -19
  14. package/dist/commands/lanes-loop.d.ts +9 -0
  15. package/dist/commands/lanes-loop.js +9 -1
  16. package/dist/commands/lanes.js +69 -54
  17. package/dist/commands/meetings.d.ts +2 -0
  18. package/dist/commands/meetings.js +57 -14
  19. package/dist/commands/mesh.d.ts +19 -1
  20. package/dist/commands/mesh.js +158 -70
  21. package/dist/commands/reindex.js +80 -5
  22. package/dist/commands/storage-offload.d.ts +93 -28
  23. package/dist/commands/storage-offload.js +290 -34
  24. package/dist/commands/storage.js +60 -7
  25. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2550 -1748
  26. package/dist/hq-cloud-sync-runner/package.json +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/lib/capacity/route.js +24 -0
  29. package/dist/lib/core-utils/session-company.d.ts +11 -0
  30. package/dist/lib/core-utils/session-company.js +34 -0
  31. package/dist/lib/core-utils/worktree-resume.js +44 -4
  32. package/dist/lib/core-utils/worktree-session-owner.d.ts +27 -0
  33. package/dist/lib/core-utils/worktree-session-owner.js +157 -0
  34. package/dist/lib/daemon/catalog.d.ts +2 -0
  35. package/dist/lib/daemon/catalog.js +3 -3
  36. package/dist/lib/daemon/hq-child-bootstrap-stage.d.ts +5 -0
  37. package/dist/lib/daemon/hq-child-bootstrap-stage.js +3 -11
  38. package/dist/lib/daemon/server.js +10 -1
  39. package/dist/lib/daemon/supervisor.js +6 -0
  40. package/dist/lib/daemon/sync-runner-bundle.d.ts +5 -0
  41. package/dist/lib/daemon/sync-runner-bundle.js +4 -12
  42. package/dist/lib/doctor/checks/claude-wiring.js +2 -1
  43. package/dist/lib/doctor/fix/apply.d.ts +20 -0
  44. package/dist/lib/doctor/fix/apply.js +63 -3
  45. package/dist/lib/doctor/fix/remediation.d.ts +1 -1
  46. package/dist/lib/doctor/fix/remediation.js +16 -0
  47. package/dist/lib/doctor/shared.d.ts +11 -1
  48. package/dist/lib/doctor/shared.js +16 -2
  49. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  50. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  51. package/dist/lib/lanes/monitor.js +14 -2
  52. package/dist/lib/mesh/live/daemon/install.d.ts +12 -0
  53. package/dist/lib/mesh/live/daemon/install.js +22 -0
  54. package/dist/lib/mesh/live/flush.js +106 -3
  55. package/dist/lib/mesh/live/session-events-client.d.ts +9 -0
  56. package/dist/lib/mesh/live/session-events-client.js +15 -0
  57. package/dist/lib/work-context/state.d.ts +10 -0
  58. package/dist/lib/work-context/state.js +5 -0
  59. package/dist/utils/fsync-directory.d.ts +17 -0
  60. package/dist/utils/fsync-directory.js +27 -0
  61. package/dist/utils/windows-hide-default.d.ts +42 -0
  62. package/dist/utils/windows-hide-default.js +98 -0
  63. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.136] — 2026-10-15
6
+ - The work-mesh daemon no longer dead-letters events for sessions another actor already reports (HTTP 403 `SESSION_OWNER_MISMATCH`, for example a local bot's own turns or checkpoint helper sessions). It drops them with a receipt in `dropped-receipts.jsonl`, stops posting that session while it stays bound to the same company, and `hq doctor` no longer marks the daemon unhealthy for them.
7
+
8
+ - `hq storage offload --current [paths...]` now offloads idle working files. A file under `companies/<slug>/` is replaced with a placeholder that points at the object HQ sync already stored in that company's vault; nothing is uploaded, and you must be an active member of the company. A company file sync has not stored yet (new, edited since the last sync, or changed in the vault) is skipped, and the run lists those files with "run hq sync first". Any other file points at its personal-vault copy when sync stored this exact version, and is uploaded to your personal vault otherwise. `--idle-days 0` includes files edited today, and `--dry-run` lists each file with its size, target vault, sync state and action.
9
+ - Current-file offload turns on only when the installed sync engine exports `CLOUD_PLACEHOLDER_HOLDS_ANY_SIBLING`, so sync never deletes or downloads over a `.hqcloud` placeholder. With an older `@indigoai-us/hq-cloud` it reports "update HQ sync" and changes nothing.
10
+ - `hq storage offload` without `--current` now handles only old versions in history and no longer reports on current files.
11
+ - Windows: the `ai.getindigo.hq-daemon` Scheduled Task now runs the hq-cli JS entry under node instead of the npm `hq.cmd` shim, so the daemon starts at logon. Re-run `hq daemon install` to rewrite an existing task.
12
+ - Windows: the daemon now stages its hq child bootstrap and the packaged sync runner. Directory fsync, which Windows refuses with EPERM, is skipped on win32, so the updater, search-index, lanes-reconcile and housekeeping services no longer fall back to running `node hq.cmd` and crashing.
13
+ - Windows: `hq doctor` no longer reports hook scripts and the Codex and Grok adapters as not executable. Windows has no execute bit, so the check now passes any present script there.
14
+ - Windows: an `hq` started in the background (no terminal, as the desktop app, sync runner and hooks start it) now starts its child processes with hidden console windows, so they no longer flash on screen every minute or two.
15
+ - `hq core checkpoint --backend auto` finds `claude.exe` and `grok.exe` on Windows, and after it finds no allowed backend it stops re-probing for six hours, or until PATH changes, instead of spawning version probes and printing "no allowed backend" on every checkpoint.
16
+ - `hq mesh story` no longer stops at `Unknown story` when the story is in the project's local prd.json but not yet on the Board. It registers the project from that PRD (the same merge `hq mesh project ensure` does) and retries once. `--no-ensure` prints the ensure command instead.
17
+ - `hq doctor --fix` now repairs a bot startup agent that points at a missing hq binary (`bots.<name>.daemon-binary`), the same way `hq bot daemon install <name>` does, so hq-checkup stops reporting safety checks off after the fix. It only points a bot at an installed hq, never at a worktree build.
18
+ - hqd: when a policy check, journal append or presence call names no company and the cwd is not linked to one, hqd now uses the session's own bind in its `meta.yaml` (`company_slug`). A company bot at the HQ root can read its own company's files again, and the scope guard still limits it to that one company. The bind is ignored unless that company folder exists, and it never overrides an explicit company or a cwd link.
19
+ - `hq integrations call` accepts tool arguments from a file with `--args-file <path>` or from stdin with `--args -`, so JSON that contains apostrophes or parentheses no longer has to pass through shell quoting. `--args` and `--args-file` together is an error, and invalid JSON in a file names the file.
20
+ - `hq integrations audit` pages through the full activity feed instead of stopping at the latest 50 events, so `--limit` above 50 returns that many rows. A new `--since <ISO timestamp>` stops at older events, and `--provider` keeps paging until the limit is met. Against an older server it shows the latest 50 events and says so.
21
+ - `hq meetings transcript`, `notes`, `get` and `set-company` resolve every short id `hq meetings list` prints. The lookup now follows the meeting list's pages instead of reading only the first one, which the server caps at 100 meetings and can end early when it runs short of time.
22
+ - `hq integrations approve` no longer reports "the call ran" when the approved call failed at the provider. It prints the provider's error and exits 1, prints "the call ran" only for a resumed or completed call, and shows any other status as is. `--json` output is unchanged apart from the exit code.
23
+ - `hq lanes monitor-check` now counts a live `hq lanes watch` process when the CLI
24
+ is installed through pnpm, where the process line runs node on the hq-cli
25
+ package entrypoint instead of an `hq` bin path.
26
+ - `hq lanes admit` on a queued loop lane now starts the lane's loop controller, the
27
+ same way background admission does, instead of running the brief once with no
28
+ controller. Later phase envelopes no longer sit unprocessed.
29
+ - An interrupt that HQ issues itself no longer records `operator_interrupt`. A loop
30
+ lane interrupted to recover a dead loop controller now shows
31
+ `loop_controller_recovery`; a stall restart shows `loop_stall_restart` and a
32
+ phase deadline shows `loop_phase_deadline`. Every interrupt now adds a stop-history
33
+ entry; an operator interrupt without `--reason` records the calling session or lane.
34
+ - `hq lanes cancel <lane-id>` is now an alias of `hq lanes stop`.
35
+ - `hq lanes create --placement local` no longer fails with `lane_placement_disabled`
36
+ when Outpost load balancing is off. Local placement is the default there, so the
37
+ flag is accepted as a no-op. Other placements still need the gate.
38
+ - `--account auto` and `hq capacity route` no longer tell you to log in to an account
39
+ that simply has no usage reading yet. With fresh capacity data it is skipped as
40
+ "no usage reading for this account yet" (fix: `hq capacity doctor`); with the
41
+ collector off or stale data it is ranked with unknown usage.
42
+ - `hq reindex` no longer removes a worktree under `workspace/worktrees/<repo>/<name>` that a session is still using. It keeps a worktree when a live process has its cwd inside it, when a Claude Code or Codex transcript from the last 7 days recorded a cwd inside it, when it has uncommitted or untracked changes, or when its branch has no upstream or commits not on a remote. It prints `reindex: kept stale worktree <path> (<reason>)` for each one and no longer passes `--force` to `git worktree remove`.
43
+ - `hq core worktree-gc` applies the same session-ownership skips (`live-session`, `recent-session`) as `core/scripts/worktree-gc.sh`, and counts a recent commit or checkout in the worktree as activity.
44
+ - Keep running Claude Remote Control account pools alive when a daemon restart temporarily cannot verify their HQ Secrets. A confirmed missing secret still stops its pool.
45
+
5
46
  ## [5.345.135] — 2026-10-15
6
47
  - `hq fingerprint self` prints the current session's HQ fingerprint, and `hq fingerprint <value>` resolves a fingerprint to its local session record. The commit-msg hook calls `hq fingerprint self --json --offline`, which stays offline and always exits 0.
7
48
  - Worker lanes get their own `HQ_SESSION_ID`, `HQ_LANE_ID`, `HQ_PARENT_SESSION_ID` and `HQ_COMPANY` with every inherited session id removed; lane records carry a `fingerprint` field shown by `lanes status --json` and `lanes show`; handoff commits add an `HQ-Fingerprint` trailer when a value exists. The handoff lookup is read-only and the offline builder never loads network modules (US-005).
@@ -20,8 +20,14 @@
20
20
  # is already merged into the HQ root's
21
21
  # local `main`. Fetch is best-effort; a
22
22
  # failed fetch makes the worktree UNSAFE.
23
- # 4. Not in use — no active session references its path,
24
- # and it was not touched in the last 2h.
23
+ # 4. Not in use — no live process has its cwd inside it,
24
+ # no Claude Code / Codex session transcript
25
+ # written in the last 7 days (env
26
+ # HQ_WORKTREE_SESSION_WINDOW_HOURS) recorded
27
+ # a cwd inside it, no session / lock file
28
+ # references its path, and neither the
29
+ # directory nor its HEAD / HEAD reflog
30
+ # changed in the last 2h.
25
31
  #
26
32
  # Removal mechanics are the gentle git verbs only — never `rm -rf`, never
27
33
  # `--force`, never `branch -D`:
@@ -64,7 +70,7 @@ while [[ $# -gt 0 ]]; do
64
70
  --gated) GATED=1; shift ;;
65
71
  --json) JSON=1; shift ;;
66
72
  -h|--help)
67
- sed -n '2,45p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
73
+ sed -n '2,51p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
68
74
  exit 0 ;;
69
75
  *) echo "worktree-gc: unknown arg: $1" >&2; exit 2 ;;
70
76
  esac
@@ -126,6 +132,76 @@ session_references() {
126
132
  return 1
127
133
  }
128
134
 
135
+ # ---------------------------- session liveness ------------------------------
136
+ # A worktree is owned by a live session when a running process has its cwd
137
+ # inside it (lsof on macOS, /proc on Linux), or when a Claude Code / Codex
138
+ # session transcript written within the session window recorded a cwd inside
139
+ # it. Agent shells do not keep a process parked in the worktree between tool
140
+ # calls, so the transcript cwd is what covers an idle but open session. Both
141
+ # lists are gathered once per run. With no lsof and no /proc the process probe
142
+ # yields nothing; the transcript, reference, and activity guards still apply.
143
+ SESSION_WINDOW_HOURS="${HQ_WORKTREE_SESSION_WINDOW_HOURS:-168}"
144
+ case "$SESSION_WINDOW_HOURS" in ''|*[!0-9]*) SESSION_WINDOW_HOURS=168 ;; esac
145
+ LIVE_PROC_CWDS=""
146
+ SESSION_CWDS=""
147
+ collect_session_cwds() {
148
+ local _p d dirs
149
+ LIVE_PROC_CWDS="$(mktemp "${TMPDIR:-/tmp}/hq-wt-proc.XXXXXX" 2>/dev/null)" || LIVE_PROC_CWDS=""
150
+ SESSION_CWDS="$(mktemp "${TMPDIR:-/tmp}/hq-wt-sess.XXXXXX" 2>/dev/null)" || SESSION_CWDS=""
151
+ if [ -n "$LIVE_PROC_CWDS" ]; then
152
+ if [ -e /proc/self/cwd ]; then
153
+ for _p in /proc/[0-9]*; do readlink "$_p/cwd" 2>/dev/null || true; done > "$LIVE_PROC_CWDS"
154
+ elif command -v lsof >/dev/null 2>&1; then
155
+ { lsof -w -n -P -d cwd -Fn 2>/dev/null || true; } | sed -n 's/^n//p' > "$LIVE_PROC_CWDS"
156
+ fi
157
+ fi
158
+ if [ -n "$SESSION_CWDS" ]; then
159
+ dirs="${HQ_SESSION_TRANSCRIPT_DIRS:-$HOME/.claude/projects:$HOME/.codex/sessions}"
160
+ printf '%s\n' "$dirs" | tr ':' '\n' | while IFS= read -r d; do
161
+ [ -n "$d" ] && [ -d "$d" ] || continue
162
+ { find "$d" -type f -name '*.jsonl' -mmin "-$((SESSION_WINDOW_HOURS * 60))" -print0 2>/dev/null \
163
+ | xargs -0 grep -h -o '"cwd": *"[^"]*"' /dev/null 2>/dev/null || true; } \
164
+ | sed 's/^"cwd": *"//; s/"$//'
165
+ done | sort -u > "$SESSION_CWDS"
166
+ fi
167
+ }
168
+
169
+ # path_in_list <list-file> <path> [<alt-path>] — true when a listed cwd is the
170
+ # path or lies inside it.
171
+ path_in_list() {
172
+ local f="$1" a="${2:-}" b="${3:-}"
173
+ [ -n "$f" ] && [ -s "$f" ] || return 1
174
+ awk -v a="$a" -v b="$b" '
175
+ function hit(p, w) { return w != "" && (p == w || index(p, w "/") == 1) }
176
+ hit($0, a) || hit($0, b) { found = 1; exit }
177
+ END { exit found ? 0 : 1 }' "$f"
178
+ }
179
+
180
+ # session_owner_reason <wt> [<alt>] — prints live-session or recent-session and
181
+ # succeeds when a session owns the worktree.
182
+ session_owner_reason() {
183
+ if path_in_list "$LIVE_PROC_CWDS" "$1" "${2:-}"; then echo "live-session"; return 0; fi
184
+ if path_in_list "$SESSION_CWDS" "$1" "${2:-}"; then echo "recent-session"; return 0; fi
185
+ return 1
186
+ }
187
+
188
+ # worktree_activity_epoch <wt> — newest mtime of the worktree directory and its
189
+ # per-worktree HEAD and HEAD reflog (commits, checkouts, resets). The index is
190
+ # left out because `git status` may rewrite it on its own.
191
+ worktree_activity_epoch() {
192
+ local wt="$1" gd newest f m
193
+ newest=$(path_mtime "$wt")
194
+ gd=$(git -C "$wt" rev-parse --path-format=absolute --git-dir 2>/dev/null || echo "")
195
+ if [[ -n "$gd" ]]; then
196
+ for f in "$gd/HEAD" "$gd/logs/HEAD"; do
197
+ [[ -e "$f" ]] || continue
198
+ m=$(path_mtime "$f")
199
+ if (( m > newest )); then newest=$m; fi
200
+ done
201
+ fi
202
+ echo "$newest"
203
+ }
204
+
129
205
  # Optional timeout wrapper for network fetches (macOS has no `timeout`).
130
206
  _TIMEOUT_BIN=""
131
207
  if command -v timeout >/dev/null 2>&1; then _TIMEOUT_BIN="timeout"
@@ -160,7 +236,7 @@ SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
160
236
  # --------------------------- results / counters -----------------------------
161
237
  REC_NDJSON="$(mktemp 2>/dev/null || echo "/tmp/worktree-gc-rec.$$")"
162
238
  : > "$REC_NDJSON"
163
- cleanup_rec() { rm -f "$REC_NDJSON"; }
239
+ cleanup_rec() { rm -f "$REC_NDJSON" "${LIVE_PROC_CWDS:-}" "${SESSION_CWDS:-}"; }
164
240
  trap cleanup_rec EXIT
165
241
 
166
242
  examined=0
@@ -169,6 +245,9 @@ would=0
169
245
  reclaimed_kb=0
170
246
  declare -i skip_total=0
171
247
 
248
+ if [[ "${#CANDIDATES[@]}" -gt 0 ]]; then collect_session_cwds; fi
249
+ HQ_ROOT_PHYS="$(cd "$HQ_ROOT" && pwd -P)"
250
+
172
251
  # wt_git <git-args...> — run git against the current worktree's main repo, adding
173
252
  # the HQ_ALLOW_HQ_ROOT_GIT escape hatch only for HQ-root-class worktrees (whose
174
253
  # mutations touch the HQ root object store and are otherwise hook-blocked).
@@ -225,8 +304,19 @@ for wt in "${CANDIDATES[@]:-}"; do
225
304
  branch=$(git -C "$wt" symbolic-ref --short HEAD 2>/dev/null || echo "")
226
305
  head_sha=$(git -C "$wt" rev-parse HEAD 2>/dev/null || echo "")
227
306
 
307
+ # ---- guard 4a: owned by a live session ----
308
+ # Checked before anything else inspects the tree. wt is the physical path;
309
+ # the spelling under the given HQ_ROOT is matched too.
310
+ wt_alt="$wt"
311
+ [[ "$wt" == "$HQ_ROOT_PHYS"/* ]] && wt_alt="$HQ_ROOT/${wt#"$HQ_ROOT_PHYS"/}"
312
+ if owner=$(session_owner_reason "$wt" "$wt_alt"); then
313
+ skip "$wt" "$main_repo" "$branch" "$is_hq_root" "" "" "$owner"; continue
314
+ fi
315
+
228
316
  # ---- guard 1: clean working tree ----
229
- porcelain=$(git -C "$wt" status --porcelain 2>/dev/null || echo "__ERR__")
317
+ # --no-optional-locks keeps the scan from rewriting the index. Untracked files
318
+ # count as dirty even when status.showUntrackedFiles=no.
319
+ porcelain=$(git --no-optional-locks -C "$wt" status --porcelain --untracked-files=all 2>/dev/null || echo "__ERR__")
230
320
  if [[ "$porcelain" == "__ERR__" ]]; then
231
321
  skip "$wt" "$main_repo" "$branch" "$is_hq_root" "" "" "git-error"; continue
232
322
  fi
@@ -234,13 +324,13 @@ for wt in "${CANDIDATES[@]:-}"; do
234
324
  skip "$wt" "$main_repo" "$branch" "$is_hq_root" "" "" "dirty"; continue
235
325
  fi
236
326
 
237
- # ---- guard 4a: recent activity (in-use heuristic) ----
327
+ # ---- guard 4b: recent activity (in-use heuristic) ----
238
328
  wt_mtime=$(path_mtime "$wt")
239
- if (( wt_mtime > RECENT_EPOCH )); then
329
+ if (( $(worktree_activity_epoch "$wt") > RECENT_EPOCH )); then
240
330
  skip "$wt" "$main_repo" "$branch" "$is_hq_root" "" "" "recent-activity"; continue
241
331
  fi
242
332
 
243
- # ---- guard 4b: active-session reference ----
333
+ # ---- guard 4c: active-session reference ----
244
334
  if session_references "$wt"; then
245
335
  skip "$wt" "$main_repo" "$branch" "$is_hq_root" "" "" "active-session"; continue
246
336
  fi
@@ -3245,7 +3245,7 @@ export declare const COMMAND_CATALOG: readonly [{
3245
3245
  readonly subcommands: readonly [];
3246
3246
  }, {
3247
3247
  readonly name: "offload";
3248
- readonly description: "Move big files to your personal HQ cloud storage and leave links behind (old versions in history; idle current files)";
3248
+ readonly description: "Move big files to HQ cloud storage and leave links behind (old versions in history; with --current, idle working files to the vault that owns them)";
3249
3249
  readonly aliases: readonly [];
3250
3250
  readonly hidden: false;
3251
3251
  readonly helpOption: true;
@@ -3267,8 +3267,8 @@ export declare const COMMAND_CATALOG: readonly [{
3267
3267
  readonly description: "Only old versions in local autosave history";
3268
3268
  readonly hidden: false;
3269
3269
  }, {
3270
- readonly flags: "--current";
3271
- readonly description: "Only idle files in the working tree";
3270
+ readonly flags: "--current [paths...]";
3271
+ readonly description: "Idle working files instead of history; optional HQ paths limit it to those files or folders";
3272
3272
  readonly hidden: false;
3273
3273
  }, {
3274
3274
  readonly flags: "--dry-run";
@@ -4813,9 +4813,12 @@ export declare const COMMAND_CATALOG: readonly [{
4813
4813
  readonly hidden: false;
4814
4814
  }, {
4815
4815
  readonly flags: "--args <json>";
4816
- readonly description: "Tool arguments as JSON";
4816
+ readonly description: "Tool arguments as a JSON object (default {}); use - to read the JSON from stdin";
4817
+ readonly hidden: false;
4818
+ }, {
4819
+ readonly flags: "--args-file <path>";
4820
+ readonly description: "Read tool arguments from a JSON file (use - for stdin); avoids shell quoting of apostrophes and parentheses";
4817
4821
  readonly hidden: false;
4818
- readonly defaultValue: "{}";
4819
4822
  }, {
4820
4823
  readonly flags: "--idempotency-key <key>";
4821
4824
  readonly description: "Stable key so retries execute at most once (default: generated)";
@@ -5492,6 +5495,10 @@ export declare const COMMAND_CATALOG: readonly [{
5492
5495
  readonly description: "How many events to show";
5493
5496
  readonly hidden: false;
5494
5497
  readonly defaultValue: "20";
5498
+ }, {
5499
+ readonly flags: "--since <iso>";
5500
+ readonly description: "Only events at or after this ISO timestamp, e.g. 2026-10-01T00:00:00Z";
5501
+ readonly hidden: false;
5495
5502
  }, {
5496
5503
  readonly flags: "--json";
5497
5504
  readonly description: "Machine-readable output";
@@ -10581,6 +10588,10 @@ export declare const COMMAND_CATALOG: readonly [{
10581
10588
  readonly flags: "--force";
10582
10589
  readonly description: "Override another holder's assignment";
10583
10590
  readonly hidden: false;
10591
+ }, {
10592
+ readonly flags: "--no-ensure";
10593
+ readonly description: "Do not register the project from its local prd.json when the story is not on the Board";
10594
+ readonly hidden: false;
10584
10595
  }, {
10585
10596
  readonly flags: "--company <slug|uid>";
10586
10597
  readonly description: "Company slug or cloud uid";
@@ -14985,7 +14996,7 @@ export declare const COMMAND_CATALOG: readonly [{
14985
14996
  }, {
14986
14997
  readonly name: "stop";
14987
14998
  readonly description: "Cancel a queued lane or kill a running one (terminal; use interrupt to keep it resumable)";
14988
- readonly aliases: readonly [];
14999
+ readonly aliases: readonly ["cancel"];
14989
15000
  readonly hidden: false;
14990
15001
  readonly helpOption: true;
14991
15002
  readonly helpCommand: false;
@@ -3912,7 +3912,7 @@ export const COMMAND_CATALOG = [
3912
3912
  },
3913
3913
  {
3914
3914
  "name": "offload",
3915
- "description": "Move big files to your personal HQ cloud storage and leave links behind (old versions in history; idle current files)",
3915
+ "description": "Move big files to HQ cloud storage and leave links behind (old versions in history; with --current, idle working files to the vault that owns them)",
3916
3916
  "aliases": [],
3917
3917
  "hidden": false,
3918
3918
  "helpOption": true,
@@ -3938,8 +3938,8 @@ export const COMMAND_CATALOG = [
3938
3938
  "hidden": false
3939
3939
  },
3940
3940
  {
3941
- "flags": "--current",
3942
- "description": "Only idle files in the working tree",
3941
+ "flags": "--current [paths...]",
3942
+ "description": "Idle working files instead of history; optional HQ paths limit it to those files or folders",
3943
3943
  "hidden": false
3944
3944
  },
3945
3945
  {
@@ -5820,9 +5820,13 @@ export const COMMAND_CATALOG = [
5820
5820
  },
5821
5821
  {
5822
5822
  "flags": "--args <json>",
5823
- "description": "Tool arguments as JSON",
5824
- "hidden": false,
5825
- "defaultValue": "{}"
5823
+ "description": "Tool arguments as a JSON object (default {}); use - to read the JSON from stdin",
5824
+ "hidden": false
5825
+ },
5826
+ {
5827
+ "flags": "--args-file <path>",
5828
+ "description": "Read tool arguments from a JSON file (use - for stdin); avoids shell quoting of apostrophes and parentheses",
5829
+ "hidden": false
5826
5830
  },
5827
5831
  {
5828
5832
  "flags": "--idempotency-key <key>",
@@ -6667,6 +6671,11 @@ export const COMMAND_CATALOG = [
6667
6671
  "hidden": false,
6668
6672
  "defaultValue": "20"
6669
6673
  },
6674
+ {
6675
+ "flags": "--since <iso>",
6676
+ "description": "Only events at or after this ISO timestamp, e.g. 2026-10-01T00:00:00Z",
6677
+ "hidden": false
6678
+ },
6670
6679
  {
6671
6680
  "flags": "--json",
6672
6681
  "description": "Machine-readable output",
@@ -12821,6 +12830,11 @@ export const COMMAND_CATALOG = [
12821
12830
  "description": "Override another holder's assignment",
12822
12831
  "hidden": false
12823
12832
  },
12833
+ {
12834
+ "flags": "--no-ensure",
12835
+ "description": "Do not register the project from its local prd.json when the story is not on the Board",
12836
+ "hidden": false
12837
+ },
12824
12838
  {
12825
12839
  "flags": "--company <slug|uid>",
12826
12840
  "description": "Company slug or cloud uid",
@@ -18231,7 +18245,9 @@ export const COMMAND_CATALOG = [
18231
18245
  {
18232
18246
  "name": "stop",
18233
18247
  "description": "Cancel a queued lane or kill a running one (terminal; use interrupt to keep it resumable)",
18234
- "aliases": [],
18248
+ "aliases": [
18249
+ "cancel"
18250
+ ],
18235
18251
  "hidden": false,
18236
18252
  "helpOption": true,
18237
18253
  "helpCommand": false,
@@ -13,6 +13,14 @@ import { Command } from "commander";
13
13
  * transient failure (a DNS blip, a brief outage) must not pin the choice.
14
14
  */
15
15
  export declare const CRASHED_RUN_MAX_AGE_MS: number;
16
+ /**
17
+ * After `--backend auto` finds no allowed backend, later checkpoints skip the
18
+ * PATH scan and `--version` probes for this long, unless PATH changes. Each
19
+ * probe is a child process, and on Windows each one could open a console
20
+ * window; a Stop hook that checkpoints every turn re-ran them every minute or
21
+ * two, all day, to reach the same answer (feedback_351a40d5).
22
+ */
23
+ export declare const NO_BACKEND_RETRY_MS: number;
16
24
  /**
17
25
  * Wall-clock cap on one sibling run. The supervisor kills the sibling and its
18
26
  * descendants at this point. It stays under the lock TTL so a capped run
@@ -89,6 +97,8 @@ export declare function autoBackendPreference(preferGrok?: boolean): SpawnableBa
89
97
  * registry; a flag change takes effect from the checkpoint after the next plan.
90
98
  */
91
99
  export declare function companyPrefersGrok(liveRoot: string, companySlug: string | null | undefined): Promise<boolean>;
100
+ /** True while a recent "no allowed backend" result still holds for this PATH. */
101
+ export declare function noBackendRecentlyFound(liveRoot: string, now?: number): boolean;
92
102
  export declare function siblingArgs(backend: SpawnableBackend, prompt: string): string[];
93
103
  /**
94
104
  * Supervisor for a detached sibling on POSIX. It forks the backend, records
@@ -8,11 +8,12 @@
8
8
  */
9
9
  import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
- import { randomUUID } from "node:crypto";
11
+ import { createHash, randomUUID } from "node:crypto";
12
12
  import { execFileSync, spawn } from "node:child_process";
13
13
  import { homedir } from "node:os";
14
14
  import { toHqStateWriteError } from "../utils/hq-state-write-error.js";
15
15
  import { checkpointStampWriteErrorFor, isCheckpointStampWriteError, } from "../utils/checkpoint-stamp-write-error.js";
16
+ import { isExecutableOnPath } from "../utils/resolve-executable.js";
16
17
  import { describeCoreCommand } from "./core-command-catalog.js";
17
18
  const DEFAULT_TRIGGER = "stop-gate";
18
19
  const BACKENDS = new Set(["auto", "claude", "codex", "grok", "none"]);
@@ -73,6 +74,14 @@ const BACKEND_SETTING = "HQ_CHECKPOINT_BACKEND";
73
74
  export const CRASHED_RUN_MAX_AGE_MS = 6 * 60 * 60 * 1000;
74
75
  /** A backend that cannot answer `--version` this fast is treated as broken. */
75
76
  const BACKEND_PROBE_TIMEOUT_MS = 10_000;
77
+ /**
78
+ * After `--backend auto` finds no allowed backend, later checkpoints skip the
79
+ * PATH scan and `--version` probes for this long, unless PATH changes. Each
80
+ * probe is a child process, and on Windows each one could open a console
81
+ * window; a Stop hook that checkpoints every turn re-ran them every minute or
82
+ * two, all day, to reach the same answer (feedback_351a40d5).
83
+ */
84
+ export const NO_BACKEND_RETRY_MS = 6 * 60 * 60 * 1000;
76
85
  /** A sibling still holding the lock after this long is treated as abandoned. */
77
86
  const SIBLING_LOCK_TTL_MS = 60 * 60 * 1000;
78
87
  /**
@@ -392,6 +401,7 @@ function readGitState(repoDir) {
392
401
  const read = (args) => execFileSync("git", ["-C", repoDir, ...args], {
393
402
  encoding: "utf8",
394
403
  stdio: ["ignore", "pipe", "ignore"],
404
+ windowsHide: true,
395
405
  }).trim();
396
406
  if (read(["rev-parse", "--is-inside-work-tree"]) !== "true")
397
407
  return null;
@@ -415,7 +425,7 @@ function readGitState(repoDir) {
415
425
  }
416
426
  function commitsSince(repoDir, initialCommit) {
417
427
  try {
418
- const output = execFileSync("git", ["-C", repoDir, "log", "--no-decorate", "--pretty=format:%h: %s", `${initialCommit}..HEAD`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
428
+ const output = execFileSync("git", ["-C", repoDir, "log", "--no-decorate", "--pretty=format:%h: %s", `${initialCommit}..HEAD`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
419
429
  return output ? output.split("\n") : [];
420
430
  }
421
431
  catch {
@@ -590,17 +600,8 @@ function backendOnPath(name) {
590
600
  const pathValue = process.env.PATH;
591
601
  if (!pathValue)
592
602
  return false;
593
- return pathValue.split(path.delimiter).some((directory) => {
594
- if (!directory)
595
- return false;
596
- try {
597
- fs.accessSync(path.join(directory, name), fs.constants.X_OK);
598
- return true;
599
- }
600
- catch {
601
- return false;
602
- }
603
- });
603
+ // PATHEXT-aware on Windows, where the binary is claude.exe, not claude.
604
+ return isExecutableOnPath(name, pathValue);
604
605
  }
605
606
  /**
606
607
  * An installed binary is not necessarily a working one: a partial install, a
@@ -613,6 +614,7 @@ function backendResponds(name) {
613
614
  execFileSync(name, ["--version"], {
614
615
  timeout: BACKEND_PROBE_TIMEOUT_MS,
615
616
  stdio: "ignore",
617
+ windowsHide: true,
616
618
  });
617
619
  return true;
618
620
  }
@@ -835,7 +837,43 @@ function backendSetting(liveRoot) {
835
837
  const lookup = settingsEnvValue(liveRoot, BACKEND_SETTING);
836
838
  return lookup.kind === "value" ? lookup.value.trim().toLowerCase() || undefined : undefined;
837
839
  }
838
- async function resolveBackend(requested, liveRoot, company) {
840
+ function noBackendMarkerPath(liveRoot) {
841
+ return path.join(liveRoot, "workspace", "orchestrator", "hook-state", "checkpoint-no-backend.json");
842
+ }
843
+ function pathFingerprint() {
844
+ return createHash("sha256").update(process.env.PATH ?? "").digest("hex").slice(0, 16);
845
+ }
846
+ /** True while a recent "no allowed backend" result still holds for this PATH. */
847
+ export function noBackendRecentlyFound(liveRoot, now = Date.now()) {
848
+ try {
849
+ const marker = JSON.parse(fs.readFileSync(noBackendMarkerPath(liveRoot), "utf8"));
850
+ if (typeof marker.at !== "number" || marker.path !== pathFingerprint())
851
+ return false;
852
+ return now - marker.at >= 0 && now - marker.at < NO_BACKEND_RETRY_MS;
853
+ }
854
+ catch {
855
+ return false;
856
+ }
857
+ }
858
+ function recordNoBackend(liveRoot, now = Date.now()) {
859
+ try {
860
+ const marker = noBackendMarkerPath(liveRoot);
861
+ fs.mkdirSync(path.dirname(marker), { recursive: true });
862
+ fs.writeFileSync(marker, `${JSON.stringify({ at: now, path: pathFingerprint() })}\n`);
863
+ }
864
+ catch {
865
+ // Best effort: without the marker the next checkpoint probes again.
866
+ }
867
+ }
868
+ function clearNoBackend(liveRoot) {
869
+ try {
870
+ fs.rmSync(noBackendMarkerPath(liveRoot), { force: true });
871
+ }
872
+ catch {
873
+ // Best effort.
874
+ }
875
+ }
876
+ async function resolveBackend(requested, liveRoot, company, options = {}) {
839
877
  let value = assertKnownBackend(requested);
840
878
  if (value === "auto") {
841
879
  const setting = backendSetting(liveRoot);
@@ -849,12 +887,19 @@ async function resolveBackend(requested, liveRoot, company) {
849
887
  // An explicitly named backend is honoured as given; only `auto` shops around.
850
888
  if (value !== "auto")
851
889
  return value;
890
+ // A recent identical miss: skip the probes rather than spawn them again.
891
+ if (noBackendRecentlyFound(liveRoot))
892
+ return "none";
852
893
  const available = autoBackendPreference(await companyPrefersGrok(liveRoot, company)).filter((name) => backendOnPath(name) && backendResponds(name));
853
894
  if (available.length === 0) {
854
895
  printError("checkpoint: sibling skipped: no allowed backend available (claude, grok); " +
855
896
  `codex runs only with --backend codex or ${BACKEND_SETTING}=codex`);
897
+ if (!options.dryRun)
898
+ recordNoBackend(liveRoot);
856
899
  return "none";
857
900
  }
901
+ if (!options.dryRun)
902
+ clearNoBackend(liveRoot);
858
903
  const crashed = crashedBackends(path.join(liveRoot, "workspace", "checkpoints", "sibling"));
859
904
  // If every candidate failed last time, still try the preferred one: a stale
860
905
  // failure is a weaker signal than running no maintenance sibling at all.
@@ -1255,7 +1300,7 @@ async function runCheckpoint(options, command, group, preResolvedHqRoot) {
1255
1300
  const siblingOptOut = options.agent === false || !siblingEnabledBySetting(liveRoot);
1256
1301
  // Validated even when nothing will spawn, so `--backend bogus` is still an error.
1257
1302
  const requestedBackend = assertKnownBackend(options.backend);
1258
- const backend = siblingOptOut ? "none" : await resolveBackend(requestedBackend, liveRoot, input.company);
1303
+ const backend = siblingOptOut ? "none" : await resolveBackend(requestedBackend, liveRoot, input.company, { dryRun: options.dryRun });
1259
1304
  const now = new Date();
1260
1305
  const threadId = `T-${formatTimestamp(now)}-auto-${summarySlug(input.summary)}`;
1261
1306
  const threadPath = path.join(liveRoot, "workspace", "threads", `${threadId}.json`);
@@ -112,7 +112,9 @@ async function loadDefaults(over) {
112
112
  err: over.err ?? ((l) => console.error(l)),
113
113
  run,
114
114
  nodeBinary: over.nodeBinary ?? mesh.resolveAbsoluteNodeBinary(),
115
- hqBinary: over.hqBinary ?? mesh.resolveHqBinary(),
115
+ // `node <hqBinary>` runs the task and every child service, so on Windows
116
+ // this must be the package's JS entry rather than the npm hq.cmd shim.
117
+ hqBinary: over.hqBinary ?? mesh.nodeRunnableHqEntry(mesh.resolveHqBinary(), platform),
116
118
  uid: over.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0),
117
119
  isRoot: over.isRoot ?? (typeof process.getuid === "function" && process.getuid() === 0),
118
120
  fileExists: over.fileExists ?? fs.existsSync,
@@ -224,6 +224,28 @@ export declare function toolPrefixForProvider(provider: string): string;
224
224
  * projection over it.
225
225
  */
226
226
  export declare function fetchAdminSurface(token: string, companyUid: string): Promise<AdminSurface>;
227
+ /** One page of the company's integration audit feed. */
228
+ export interface AuditPage {
229
+ /**
230
+ * True when the server answered the paged audit view. False means an older
231
+ * server ignored `view=audit` and returned the full admin surface, whose
232
+ * audit feed is capped at the latest 50 events and has no cursor.
233
+ */
234
+ paged: boolean;
235
+ audit: AdminAuditEvent[];
236
+ nextCursor?: string;
237
+ }
238
+ /** Most rows the paged audit view returns per request. */
239
+ export declare const AUDIT_PAGE_MAX = 100;
240
+ /**
241
+ * Read one page of the audit feed via the admin route's paged view
242
+ * (`view=audit&limit=&cursor=`), newest first. Falls back to the surface's
243
+ * capped feed when the server does not know the view.
244
+ */
245
+ export declare function fetchAuditPage(token: string, companyUid: string, opts: {
246
+ limit: number;
247
+ cursor?: string;
248
+ }): Promise<AuditPage>;
227
249
  export declare function fetchConnections(token: string, companyUid: string): Promise<AdminConnection[]>;
228
250
  /**
229
251
  * One app the caller can use right now through the HQ gateway. Field names
@@ -352,6 +352,31 @@ export async function fetchAdminSurface(token, companyUid) {
352
352
  audit: data.audit ?? [],
353
353
  };
354
354
  }
355
+ /** Most rows the paged audit view returns per request. */
356
+ export const AUDIT_PAGE_MAX = 100;
357
+ /**
358
+ * Read one page of the audit feed via the admin route's paged view
359
+ * (`view=audit&limit=&cursor=`), newest first. Falls back to the surface's
360
+ * capped feed when the server does not know the view.
361
+ */
362
+ export async function fetchAuditPage(token, companyUid, opts) {
363
+ const limit = Math.max(1, Math.min(AUDIT_PAGE_MAX, Math.floor(opts.limit)));
364
+ const query = { companyUid, view: "audit", limit: String(limit) };
365
+ if (opts.cursor)
366
+ query.cursor = opts.cursor;
367
+ const res = await vaultApiFetch({ token, path: "/v1/integrations/admin", query });
368
+ if (!res.ok)
369
+ await raiseForResponse(res, "Failed to read integration activity");
370
+ const data = (await res.json());
371
+ const audit = Array.isArray(data.audit) ? data.audit : [];
372
+ if (data.view !== "audit")
373
+ return { paged: false, audit };
374
+ return {
375
+ paged: true,
376
+ audit,
377
+ ...(typeof data.nextCursor === "string" && data.nextCursor ? { nextCursor: data.nextCursor } : {}),
378
+ };
379
+ }
355
380
  export async function fetchConnections(token, companyUid) {
356
381
  return (await fetchAdminSurface(token, companyUid)).connections;
357
382
  }
@@ -13,7 +13,7 @@
13
13
  * and layered under the connection's write policy.
14
14
  */
15
15
  import { Command } from "commander";
16
- import { type GranteeType, type Permission, type WriteAllowlistGrant } from "./integrations-core.js";
16
+ import { type AdminAuditEvent, type GranteeType, type Permission, type WriteAllowlistGrant } from "./integrations-core.js";
17
17
  interface Principal {
18
18
  granteeType: GranteeType;
19
19
  granteeId?: string;
@@ -46,5 +46,28 @@ export declare function mergeGrant(current: WriteAllowlistGrant[], toolName: str
46
46
  * dropped entirely rather than persisted as an empty rule.
47
47
  */
48
48
  export declare function removeGrant(current: WriteAllowlistGrant[], toolName: string, principal: Principal): WriteAllowlistGrant[] | null;
49
+ /** Upper bound on audit pages one `hq integrations audit` run reads. */
50
+ export declare const AUDIT_MAX_PAGES = 200;
51
+ export interface CollectedAudit {
52
+ rows: AdminAuditEvent[];
53
+ /** False when the server only returned its capped surface feed. */
54
+ paged: boolean;
55
+ /** Rows in the surface feed (fallback path only), before filtering. */
56
+ feedSize: number;
57
+ /** More matching rows may exist past what was collected. */
58
+ more: boolean;
59
+ pageCapHit: boolean;
60
+ }
61
+ /**
62
+ * Page the audit feed (newest first) until `limit` rows match, the cursor runs
63
+ * out, a row is older than `sinceMs`, or AUDIT_MAX_PAGES pages were read.
64
+ * `provider` is matched client-side on the bare slug, so the qualified
65
+ * `factory:linear` form copied from JSON output matches bare stored rows.
66
+ */
67
+ export declare function collectAuditRows(token: string, companyUid: string, opts: {
68
+ limit: number;
69
+ provider?: string;
70
+ sinceMs?: number;
71
+ }): Promise<CollectedAudit>;
49
72
  export {};
50
73
  //# sourceMappingURL=integrations-manage.d.ts.map