@indigoai-us/hq-cli 5.301.0 → 5.303.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
  3. package/assets/scaffold/core/scripts/token-usage-report.sh +72 -20
  4. package/dist/command-catalog.generated.d.ts +4 -1
  5. package/dist/command-catalog.generated.js +5 -1
  6. package/dist/commands/bot.js +1 -0
  7. package/dist/commands/core-checkpoint.js +1 -0
  8. package/dist/commands/core-handoff-fast.js +1 -0
  9. package/dist/commands/core-handoff.js +2 -0
  10. package/dist/commands/deploy-comments.js +5 -2
  11. package/dist/commands/integrations-connect.d.ts +3 -0
  12. package/dist/commands/integrations-connect.js +21 -2
  13. package/dist/commands/lanes-message.js +33 -3
  14. package/dist/commands/lanes-questions.d.ts +4 -2
  15. package/dist/commands/lanes-questions.js +123 -2
  16. package/dist/commands/lanes-resume.js +25 -4
  17. package/dist/commands/lanes-shared.d.ts +13 -1
  18. package/dist/commands/lanes-watch.js +5 -1
  19. package/dist/commands/lanes.js +91 -9
  20. package/dist/commands/monitor.js +17 -2
  21. package/dist/commands/report.js +5 -2
  22. package/dist/commands/rescue.d.ts +4 -0
  23. package/dist/commands/rescue.js +27 -17
  24. package/dist/lib/agent-kit/fallback.d.ts +1 -0
  25. package/dist/lib/agent-kit/fallback.js +1 -0
  26. package/dist/lib/bot/cli-update.d.ts +0 -8
  27. package/dist/lib/bot/cli-update.js +5 -5
  28. package/dist/lib/bot/daemon.d.ts +5 -1
  29. package/dist/lib/bot/daemon.js +4 -4
  30. package/dist/lib/bot/local-jobs.d.ts +1 -21
  31. package/dist/lib/bot/local-jobs.js +12 -12
  32. package/dist/lib/core-utils/soft-timeout.js +1 -1
  33. package/dist/lib/daemon/config.d.ts +0 -1
  34. package/dist/lib/daemon/config.js +1 -1
  35. package/dist/lib/deploy-apps.d.ts +13 -0
  36. package/dist/lib/deploy-apps.js +24 -0
  37. package/dist/lib/index-render/projects.js +82 -5
  38. package/dist/lib/lanes/delivery.d.ts +7 -3
  39. package/dist/lib/lanes/delivery.js +18 -8
  40. package/dist/lib/lanes/index.d.ts +1 -1
  41. package/dist/lib/lanes/keep-alive.d.ts +13 -3
  42. package/dist/lib/lanes/keep-alive.js +29 -5
  43. package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
  44. package/dist/lib/lanes/outpost-dispatch.js +12 -0
  45. package/dist/lib/lanes/reconcile.d.ts +1 -0
  46. package/dist/lib/lanes/reconcile.js +358 -38
  47. package/dist/lib/lanes/spawn.d.ts +1 -0
  48. package/dist/lib/lanes/spawn.js +20 -10
  49. package/dist/lib/lanes/store.js +24 -0
  50. package/dist/lib/lanes/story-ledger.d.ts +2 -0
  51. package/dist/lib/lanes/story-ledger.js +8 -0
  52. package/dist/lib/lanes/types.d.ts +26 -4
  53. package/dist/lib/lanes/types.js +33 -1
  54. package/dist/lib/lanes/watch.d.ts +1 -0
  55. package/dist/lib/lanes/watch.js +19 -6
  56. package/dist/lib/lanes/watcher.js +139 -9
  57. package/dist/lib/mesh/live/daemon/run.js +36 -2
  58. package/dist/lib/report-snapshot.d.ts +0 -2
  59. package/dist/lib/report-snapshot.js +2 -2
  60. package/dist/lib/search-index/background.d.ts +1 -0
  61. package/dist/lib/search-index/background.js +1 -0
  62. package/dist/utils/run-bundled-script.js +103 -1
  63. package/dist/utils/update-command-supervisor.cjs +1 -0
  64. package/dist/utils/version-gate.js +1 -1
  65. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.303.0] — 2026-10-01
6
+ - Fixed bundled shell scripts failing on Windows when Git Bash is installed but `bash` is not on `PATH`.
7
+ - The bundled token usage script now reads Claude sessions from `CLAUDE_CONFIG_DIR/projects` and the standard Claude project directories, including nested session folders (subagent transcripts are skipped), when no project directory is given.
8
+
9
+ ## [5.302.0] — 2026-10-01
10
+
11
+ - Generate company project indexes while preserving hand-written content unless `--accept` is used.
12
+
13
+ - Follow all hq-deploy app-list pages when resolving apps for comments and report publishing.
14
+ - Let a senior message or resume an `awaiting_input` lane when its pending question is gone or cannot be reached from the senior's host. Pending questions remain answer-first, including across registered Outpost routes.
15
+ - A senior can resume or send a fallback message to an `awaiting_input` lane when its pending questions are addressed to another audience. Once the resume is accepted, those questions are cancelled on their owner; unreachable owners are reported while the lane continues.
16
+ - When a blocker escalation targets a session without a registered monitor inbox, record the reason and deliver it through the lane's existing inbox instead of retrying the failed escalation every watcher pass.
17
+ - `hq integrations disconnect` can now remove company integrations that were created outside the app catalog.
18
+ - Lead envelopes can set `next_check_at` to defer automatic keep-alive resumes
19
+ until a scheduled check, with pending work continuing to resume immediately.
20
+ - Reconcile records deduplicated keep-alive skip reasons in each lane's watcher event log.
21
+ - Add a company-scoped reconcile gate that closes local interrupted worker lanes after 24 hours when all carried project stories are done or dropped and no pending work remains.
22
+ - Skip refreshing failed-dispatch lane mirrors that never reached their Outpost owner.
23
+ - Keep lane keep-alive, retry-context, and monitor history readers moving past torn JSONL records.
24
+ - Refresh primary mirror heartbeats from fresh running Outpost reports before dead-host reconciliation.
25
+ - Report answerable awaiting-input questions as the `hq lanes show` delivery path, and retry blocker-freshness fallback messages after a failed append.
26
+ - Let `hq integrations connect` send an API key in a custom HTTP header.
27
+ - Bound `hq rescue` GitHub release and tag lookups to 30 seconds with a clear timeout error.
28
+ - hq-cli now logs live-board refresh failures and recovery while continuing to retry.
29
+ - On Windows, background jobs started by `/handoff` and search indexing no longer open console windows.
30
+
5
31
  ## [5.301.0] — 2026-10-01
6
32
  - `hq meetings transcript` and `hq meetings notes` stop a stalled document download after two minutes and say so, instead of waiting forever.
7
33
 
@@ -11,12 +11,11 @@ set -euo pipefail
11
11
  HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
12
12
  cd "$HQ_ROOT"
13
13
 
14
+ COMPANIES_DIR="companies"
14
15
  PROJECTS_DIR="personal/projects"
15
16
  OUT="${PROJECTS_DIR}/INDEX.md"
16
17
  DATE=$(date -u +%Y-%m-%d)
17
18
 
18
- [[ -d "$PROJECTS_DIR" ]] || { echo "rebuild-projects-index: ${PROJECTS_DIR}/ missing, skipping" >&2; exit 0; }
19
-
20
19
  sanitize_cell() {
21
20
  printf '%s' "$1" | tr '\n' ' ' | sed -e 's/|/\\|/g' -e 's/ */ /g' -e 's/^ *//' -e 's/ *$//'
22
21
  }
@@ -29,17 +28,23 @@ trunc() {
29
28
  project_status() {
30
29
  local project="$1"
31
30
  local prd="$2"
31
+ local fallback="${3:-—}"
32
+ local expected_prd="${4:-}"
32
33
  local state="workspace/orchestrator/${project}/state.json"
33
34
  if [[ -f "$state" ]]; then
34
- local raw
35
- raw=$(jq -r '.status // "active"' "$state" 2>/dev/null || echo "active")
36
- case "$raw" in
37
- completed|complete) echo "complete" ;;
38
- in_progress|active|started) echo "active" ;;
39
- archived) echo "archived" ;;
40
- *) echo "$raw" ;;
41
- esac
42
- return
35
+ local state_prd
36
+ state_prd=$(jq -r '.prd_path // ""' "$state" 2>/dev/null || echo "")
37
+ if [[ -z "$expected_prd" || "$state_prd" == "$expected_prd" ]]; then
38
+ local raw
39
+ raw=$(jq -r '.status // "active"' "$state" 2>/dev/null || echo "active")
40
+ case "$raw" in
41
+ completed|complete) echo "complete" ;;
42
+ in_progress|active|started) echo "active" ;;
43
+ archived) echo "archived" ;;
44
+ *) echo "$raw" ;;
45
+ esac
46
+ return
47
+ fi
43
48
  fi
44
49
  if [[ -f "$prd" ]]; then
45
50
  local total passes
@@ -50,9 +55,91 @@ project_status() {
50
55
  return
51
56
  fi
52
57
  fi
53
- echo "—"
58
+ echo "$fallback"
59
+ }
60
+
61
+ sha256_file() {
62
+ if command -v sha256sum >/dev/null 2>&1; then
63
+ sha256sum "$1" | awk '{print $1}'
64
+ else
65
+ shasum -a 256 "$1" | awk '{print $1}'
66
+ fi
54
67
  }
55
68
 
69
+ stamp_generated() {
70
+ local input="$1" output="$2" normalized hash
71
+ normalized=$(mktemp)
72
+ sed 's/^> Auto-generated. Updated: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/> Auto-generated. Updated: <date>/' "$input" > "$normalized"
73
+ hash=$(sha256_file "$normalized")
74
+ rm -f "$normalized"
75
+ awk -v h="$hash" '{print} /^> Auto-generated. Updated: / {print "<!-- hq-generated-sha256: " h " -->"}' "$input" > "$output"
76
+ }
77
+
78
+ company_title() {
79
+ printf '%s\n' "$1" | awk -F'-' '{ for (i=1; i<=NF; i++) { $i = toupper(substr($i,1,1)) substr($i,2) } print }' OFS=' '
80
+ }
81
+
82
+ write_company_projects_index() {
83
+ local company="$1" company_root project_dir output tmp name prd description status
84
+ company_root="${COMPANIES_DIR}/${company}"
85
+ project_dir="${company_root}/projects"
86
+ output="${project_dir}/INDEX.md"
87
+ tmp=$(mktemp)
88
+ {
89
+ printf '# %s Projects\n\n' "$(company_title "$company")"
90
+ printf '> Auto-generated. Updated: %s\n\n' "$DATE"
91
+ echo '| Project | Status | Description |'
92
+ echo '|---------|--------|-------------|'
93
+ while IFS= read -r project; do
94
+ name=$(basename "$project")
95
+ [[ "$name" == _* ]] && continue
96
+ [[ "$name" == .* ]] && continue
97
+ prd="${project}/prd.json"
98
+ description=""
99
+ if [[ -f "$prd" ]]; then description=$(jq -r '.description // ""' "$prd" 2>/dev/null || echo ""); fi
100
+ status=$(project_status "$name" "$prd" "active" "$prd")
101
+ description=$(sanitize_cell "$description")
102
+ description=$(trunc "$description" 110)
103
+ [[ -z "$description" ]] && description="—"
104
+ printf '| `%s` | %s | %s |\n' "$name" "$status" "$description"
105
+ done < <(find "$project_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
106
+ } > "$tmp"
107
+ local existing=0 stored actual normalized
108
+ [[ -e "$output" ]] && existing=1
109
+ if [[ "$existing" == 1 ]]; then
110
+ if grep -q '<!-- hq:keep -->' "$output" || ! grep -q '^> Auto-generated. Updated: ' "$output"; then
111
+ echo "rebuild-projects-index: refused to overwrite ${output} (hand-written or kept)" >&2
112
+ rm -f "$tmp"
113
+ return 0
114
+ fi
115
+ stored=$(sed -n 's/^<!-- hq-generated-sha256: \([a-f0-9]*\) -->$/\1/p' "$output" | head -n 1)
116
+ if [[ -n "$stored" ]]; then
117
+ normalized=$(mktemp)
118
+ sed -e '/^<!-- hq-generated-sha256: [a-f0-9]* -->$/d' -e 's/^> Auto-generated. Updated: [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/> Auto-generated. Updated: <date>/' "$output" > "$normalized"
119
+ actual=$(sha256_file "$normalized")
120
+ rm -f "$normalized"
121
+ if [[ "$stored" != "$actual" ]]; then
122
+ echo "rebuild-projects-index: refused to overwrite ${output} (generated content was edited)" >&2
123
+ rm -f "$tmp"
124
+ return 0
125
+ fi
126
+ fi
127
+ fi
128
+ stamp_generated "$tmp" "$output"
129
+ rm -f "$tmp"
130
+ echo "rebuild-projects-index: wrote ${output}" >&2
131
+ }
132
+
133
+ while IFS= read -r dir; do
134
+ co=$(basename "$dir")
135
+ [[ "$co" == _* ]] && continue
136
+ [[ "$co" == .* ]] && continue
137
+ project_dir="${COMPANIES_DIR}/${co}/projects"
138
+ [[ -d "$project_dir" ]] && write_company_projects_index "$co"
139
+ done < <(find "$COMPANIES_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort)
140
+
141
+ [[ -d "$PROJECTS_DIR" ]] || { echo "rebuild-projects-index: ${PROJECTS_DIR}/ missing, skipping" >&2; exit 0; }
142
+
56
143
  {
57
144
  echo "# Projects"
58
145
  echo ""
@@ -12,7 +12,8 @@
12
12
 
13
13
  set -euo pipefail
14
14
 
15
- PROJECT_DIR="${CLAUDE_PROJECTS_DIR:-$HOME/.claude/projects/-Users-{your-name}-Documents-HQ}"
15
+ PROJECT_DIR=""
16
+ PROJECT_DIR_SET=0
16
17
  LAST_DAYS=7
17
18
  SINCE=""
18
19
  JSON_MODE=0
@@ -31,6 +32,7 @@ while [ $# -gt 0 ]; do
31
32
  exit 1
32
33
  fi
33
34
  PROJECT_DIR="$2"
35
+ PROJECT_DIR_SET=1
34
36
  shift 2
35
37
  ;;
36
38
  --compare-windows) shift ;; # flag, presence triggers compare mode via --before/--after
@@ -47,14 +49,47 @@ done
47
49
 
48
50
  command -v node >/dev/null 2>&1 || { echo "token-usage-report: node is required" >&2; exit 1; }
49
51
 
52
+ # Resolve roots in the same precedence order as the native command. Explicit
53
+ # project directories and environment overrides select one exact activity root;
54
+ # otherwise include standard, desktop-saved, and discovered Claude roots.
55
+ PROJECT_DIRS=("")
56
+ add_project_dir() {
57
+ local candidate="$1" existing
58
+ [ -d "$candidate" ] || return 0
59
+ for existing in "${PROJECT_DIRS[@]}"; do [ "$existing" = "$candidate" ] && return 0; done
60
+ PROJECT_DIRS+=("$candidate")
61
+ }
62
+ trim_env_path() {
63
+ local value="$1"
64
+ value="${value#"${value%%[![:space:]]*}"}"
65
+ value="${value%"${value##*[![:space:]]}"}"
66
+ printf '%s' "$value"
67
+ }
68
+ if [ "$PROJECT_DIR_SET" -eq 1 ]; then
69
+ if [ ! -d "$PROJECT_DIR" ]; then echo "Project dir not found: $PROJECT_DIR" >&2; exit 1; fi
70
+ add_project_dir "$PROJECT_DIR"
71
+ elif [ -n "$(trim_env_path "${CLAUDE_PROJECTS_DIR:-}")" ]; then
72
+ add_project_dir "$(trim_env_path "${CLAUDE_PROJECTS_DIR:-}")"
73
+ elif [ -n "$(trim_env_path "${CLAUDE_CONFIG_DIR:-}")" ]; then
74
+ add_project_dir "$(trim_env_path "${CLAUDE_CONFIG_DIR:-}")/projects"
75
+ else
76
+ add_project_dir "$HOME/.claude/projects"
77
+ SAVED_PROJECT_DIR="$(node -e 'try { const value = require(process.argv[1]).claudeProjectsDir; if (typeof value === "string" && value.trim()) process.stdout.write(value.trim()); } catch {}' "$HOME/.hq/menubar.json")"
78
+ [ -z "$SAVED_PROJECT_DIR" ] || add_project_dir "$SAVED_PROJECT_DIR"
79
+ for candidate in "$HOME"/.claude-*; do
80
+ [ -d "$candidate/projects" ] && add_project_dir "$candidate/projects"
81
+ done
82
+ fi
83
+ if [ "${#PROJECT_DIRS[@]}" -le 1 ]; then echo "Claude activity folders not found" >&2; exit 1; fi
84
+
50
85
  # --- Compare-windows mode (US-011) ---
51
86
  # Format: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD
52
87
  if [ -n "$COMPARE_BEFORE" ] && [ -n "$COMPARE_AFTER" ]; then
53
- node - "$PROJECT_DIR" "$COMPARE_BEFORE" "$COMPARE_AFTER" "$MIN_HOURS" "$JSON_MODE" <<'JS'
88
+ node - "$COMPARE_BEFORE" "$COMPARE_AFTER" "$MIN_HOURS" "$JSON_MODE" "${PROJECT_DIRS[@]:1}" <<'JS'
54
89
  const fs = require("fs");
55
90
  const path = require("path");
56
91
 
57
- const [proj, before, after, minHoursS, jsonModeS] = process.argv.slice(2);
92
+ const [before, after, minHoursS, jsonModeS, ...projectDirs] = process.argv.slice(2);
58
93
  const minHours = parseFloat(minHoursS);
59
94
  const jsonMode = parseInt(jsonModeS, 10) === 1;
60
95
 
@@ -63,13 +98,24 @@ const [bStart, bEnd] = parseRange(before);
63
98
  const [aStart, aEnd] = parseRange(after);
64
99
 
65
100
  const fmt = (n) => n.toLocaleString("en-US");
66
- const listJsonl = () => {
67
- try { return fs.readdirSync(proj).filter((f) => f.endsWith(".jsonl")).sort(); } catch (e) { return []; }
101
+ const listJsonl = (projectDir) => {
102
+ const found = [];
103
+ const visit = (current, relative) => {
104
+ let entries;
105
+ try { entries = fs.readdirSync(current, { withFileTypes: true }); } catch (e) { return; }
106
+ for (const entry of entries) {
107
+ const nextRelative = path.join(relative, entry.name);
108
+ if (entry.isDirectory() && entry.name !== "subagents") visit(path.join(current, entry.name), nextRelative);
109
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) found.push(nextRelative);
110
+ }
111
+ };
112
+ visit(projectDir, "");
113
+ return found.sort();
68
114
  };
69
115
 
70
116
  function collect(windowStart, windowEnd) {
71
117
  const rows = [];
72
- for (const name of listJsonl()) {
118
+ for (const proj of projectDirs) for (const name of listJsonl(proj)) {
73
119
  const f = path.join(proj, name);
74
120
  let cr = 0, firstTs = null, lastTs = null;
75
121
  const sid = name.replace(/\.jsonl$/, "");
@@ -130,16 +176,11 @@ JS
130
176
  exit 0
131
177
  fi
132
178
 
133
- if [ ! -d "$PROJECT_DIR" ]; then
134
- echo "Project dir not found: $PROJECT_DIR" >&2
135
- exit 1
136
- fi
137
-
138
- node - "$PROJECT_DIR" "$LAST_DAYS" "$SINCE" "$JSON_MODE" <<'JS'
179
+ node - "$LAST_DAYS" "$SINCE" "$JSON_MODE" "${PROJECT_DIRS[@]:1}" <<'JS'
139
180
  const fs = require("fs");
140
181
  const path = require("path");
141
182
 
142
- const [projectDir, lastDaysS, sinceS, jsonModeS] = process.argv.slice(2);
183
+ const [lastDaysS, sinceS, jsonModeS, ...projectDirs] = process.argv.slice(2);
143
184
  const lastDays = parseInt(lastDaysS, 10);
144
185
  const jsonMode = parseInt(jsonModeS, 10) === 1;
145
186
 
@@ -156,11 +197,20 @@ const fmt = (n) => n.toLocaleString("en-US");
156
197
  const dayTotals = {};
157
198
  const sessionTotals = {};
158
199
 
159
- let names;
160
- try { names = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).sort(); } catch (e) { names = []; }
161
-
162
- // Parent sessions only (top-level *.jsonl)
163
- for (const name of names) {
200
+ // Parent sessions only; listJsonl skips each session's subagents/ tree.
201
+ for (const projectDir of projectDirs) {
202
+ const listJsonl = (current, relative = "") => {
203
+ let entries;
204
+ try { entries = fs.readdirSync(current, { withFileTypes: true }); } catch (e) { return []; }
205
+ return entries.flatMap((entry) => {
206
+ const nextRelative = path.join(relative, entry.name);
207
+ if (entry.isDirectory() && entry.name !== "subagents") return listJsonl(path.join(current, entry.name), nextRelative);
208
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) return [nextRelative];
209
+ return [];
210
+ }).sort();
211
+ };
212
+ const names = listJsonl(projectDir);
213
+ for (const name of names) {
164
214
  const f = path.join(projectDir, name);
165
215
  let mtime;
166
216
  try { mtime = fs.statSync(f).mtime; } catch (e) { continue; }
@@ -170,6 +220,7 @@ for (const name of names) {
170
220
  let firstTs = null, lastTs = null, firstUser = "";
171
221
  let subCount = 0;
172
222
  const sid = name.replace(/\.jsonl$/, "");
223
+ const sessionId = path.basename(path.dirname(projectDir)) + "/" + sid;
173
224
  let content;
174
225
  try { content = fs.readFileSync(f, "utf8"); } catch (e) { continue; }
175
226
  for (const line of content.split(/\r?\n/)) {
@@ -196,15 +247,16 @@ for (const name of names) {
196
247
  if (!day) day = mt;
197
248
  if (day < cutoff) continue;
198
249
  if (!dayTotals[day]) dayTotals[day] = { sessions: new Set(), inp: 0, out: 0, cc: 0, cr: 0 };
199
- dayTotals[day].sessions.add(sid);
250
+ dayTotals[day].sessions.add(sessionId);
200
251
  dayTotals[day].inp += inp;
201
252
  dayTotals[day].out += out;
202
253
  dayTotals[day].cc += cc;
203
254
  dayTotals[day].cr += cr;
204
- sessionTotals[sid] = {
255
+ sessionTotals[sessionId] = {
205
256
  day: day, inp: inp, out: out, cc: cc, cr: cr,
206
257
  eff: Math.trunc(eff), subagents: subCount, first_user: firstUser.split("\n").join(" "),
207
258
  };
259
+ }
208
260
  }
209
261
 
210
262
  const sortedDays = Object.keys(dayTotals).sort();
@@ -3122,6 +3122,9 @@ export declare const COMMAND_CATALOG: readonly [{
3122
3122
  }, {
3123
3123
  readonly flags: "--token-stdin";
3124
3124
  readonly description: "Read the API key from stdin";
3125
+ }, {
3126
+ readonly flags: "--auth-header <name>";
3127
+ readonly description: "Send the API key in this HTTP header (implies --auth key)";
3125
3128
  }, {
3126
3129
  readonly flags: "--auth <mode>";
3127
3130
  readonly description: "Force the auth mode: none, key, or oauth (default: detect)";
@@ -8608,7 +8611,7 @@ export declare const COMMAND_CATALOG: readonly [{
8608
8611
  readonly subcommands: readonly [];
8609
8612
  }, {
8610
8613
  readonly name: "resume";
8611
- readonly description: "Resume an interrupted, done or blocked lane on its recorded provider session; recover failed lanes with the senior-only --from-failed option";
8614
+ readonly description: "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with the senior-only --from-failed option";
8612
8615
  readonly aliases: readonly [];
8613
8616
  readonly hidden: false;
8614
8617
  readonly usage: "[options] <lane-id>";
@@ -4013,6 +4013,10 @@ export const COMMAND_CATALOG = [
4013
4013
  "flags": "--token-stdin",
4014
4014
  "description": "Read the API key from stdin"
4015
4015
  },
4016
+ {
4017
+ "flags": "--auth-header <name>",
4018
+ "description": "Send the API key in this HTTP header (implies --auth key)"
4019
+ },
4016
4020
  {
4017
4021
  "flags": "--auth <mode>",
4018
4022
  "description": "Force the auth mode: none, key, or oauth (default: detect)"
@@ -11100,7 +11104,7 @@ export const COMMAND_CATALOG = [
11100
11104
  },
11101
11105
  {
11102
11106
  "name": "resume",
11103
- "description": "Resume an interrupted, done or blocked lane on its recorded provider session; recover failed lanes with the senior-only --from-failed option",
11107
+ "description": "Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with the senior-only --from-failed option",
11104
11108
  "aliases": [],
11105
11109
  "hidden": false,
11106
11110
  "usage": "[options] <lane-id>",
@@ -228,6 +228,7 @@ export function spawnDetachedRun(name, dir) {
228
228
  const fd = fs.openSync(botLogPath(dir), "a");
229
229
  const child = spawn(process.execPath, [entry, "bot", "run", name], {
230
230
  detached: true,
231
+ windowsHide: true,
231
232
  stdio: ["ignore", fd, fd],
232
233
  env: { ...process.env, HQ_NO_UPDATE_CHECK: "1" },
233
234
  });
@@ -1049,6 +1049,7 @@ function startSibling(liveRoot, input, threadPath, backend) {
1049
1049
  try {
1050
1050
  child = spawn(backend, args, {
1051
1051
  detached: true,
1052
+ windowsHide: true,
1052
1053
  cwd: liveRoot,
1053
1054
  stdio: ["ignore", logFd, logFd],
1054
1055
  env: siblingEnv(process.env, backend),
@@ -60,6 +60,7 @@ async function launchHandoffPost(args, root) {
60
60
  HQ_NATIVE_HANDOFF_POST_CHILD: "1",
61
61
  },
62
62
  detached: true,
63
+ windowsHide: true,
63
64
  stdio: "ignore",
64
65
  });
65
66
  child.once("error", (error) => {
@@ -802,6 +802,7 @@ function spawnBashDetached(script, args, cwd, root, logFile) {
802
802
  cwd,
803
803
  env: { ...process.env, HQ_ROOT: root },
804
804
  detached: true,
805
+ windowsHide: true,
805
806
  stdio: ["ignore", fd, fd],
806
807
  });
807
808
  child.once("error", (error) => appendLog(logFile, `${error.message}\n`));
@@ -841,6 +842,7 @@ async function launchHandoffPost(args, root) {
841
842
  cwd: root,
842
843
  env: { ...process.env, HQ_NATIVE_HANDOFF_POST_CHILD: "1" },
843
844
  detached: true,
845
+ windowsHide: true,
844
846
  stdio: "ignore",
845
847
  });
846
848
  child.once("error", (error) => resolve({ code: 127, stdout: "", stderr: `${error.message}\n` }));
@@ -1,4 +1,5 @@
1
1
  import { deployApiRequest } from "../lib/deploy-api.js";
2
+ import { listDeployApps } from "../lib/deploy-apps.js";
2
3
  import { DEFAULT_HQ_ROOT, ensureCognitoToken } from "../utils/cognito-session.js";
3
4
  import { readActiveCompanySlug } from "./skill.js";
4
5
  class DeployCommentsError extends Error {
@@ -50,8 +51,10 @@ async function deployJson(token, route, orgHeaders) {
50
51
  }
51
52
  }
52
53
  async function resolveAppId(token, appNameOrId, orgHeaders) {
53
- const result = await deployJson(token, "/api/apps", orgHeaders);
54
- const apps = Array.isArray(result.apps) ? result.apps : [];
54
+ const apps = await listDeployApps((cursor) => {
55
+ const route = cursor ? `/api/apps?cursor=${encodeURIComponent(cursor)}` : "/api/apps";
56
+ return deployJson(token, route, orgHeaders);
57
+ });
55
58
  const byId = apps.find((app) => app && typeof app.id === "string" && app.id === appNameOrId);
56
59
  const matches = byId
57
60
  ? [byId]
@@ -30,6 +30,7 @@ interface ConnectOptions {
30
30
  displayName?: string;
31
31
  token?: string;
32
32
  tokenStdin?: boolean;
33
+ authHeader?: string;
33
34
  auth?: string;
34
35
  browser?: boolean;
35
36
  timeout?: string;
@@ -48,6 +49,8 @@ interface ConnectOptions {
48
49
  * than fall through to a default.
49
50
  */
50
51
  export declare function assertAuthMode(value: string | undefined): void;
52
+ /** RFC 7230 token characters accepted for an HTTP field name. */
53
+ export declare function assertAuthHeader(value: string | undefined): void;
51
54
  /** Single-quote a value for a copy-pasteable shell command. */
52
55
  export declare function shellQuote(value: string): string;
53
56
  /** Everything the connect verbs need to name one app to hq-pro. */
@@ -185,6 +185,12 @@ export function assertAuthMode(value) {
185
185
  throw new IntegrationsCliError("--auth must be none, key, or oauth.", { expected: true });
186
186
  }
187
187
  }
188
+ /** RFC 7230 token characters accepted for an HTTP field name. */
189
+ export function assertAuthHeader(value) {
190
+ if (value !== undefined && !/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(value)) {
191
+ throw new IntegrationsCliError("--auth-header must be a non-empty HTTP header name using RFC 7230 token characters.", { expected: true });
192
+ }
193
+ }
188
194
  /** Single-quote a value for a copy-pasteable shell command. */
189
195
  export function shellQuote(value) {
190
196
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -904,7 +910,9 @@ function reportInstall(result, opts, expectedRevivedConnectionId) {
904
910
  */
905
911
  async function connectApp(token, companyUid, app, opts, expectedRevivedConnectionId) {
906
912
  const target = await resolveTarget(token, companyUid, app, opts, expectedRevivedConnectionId !== undefined);
907
- const authMode = opts.auth ?? target.authClass;
913
+ const authMode = opts.authHeader
914
+ ? "key"
915
+ : opts.auth ?? target.authClass;
908
916
  assertUserClientApplicable(opts, target, authMode);
909
917
  assertScopesApplicable(opts, target, authMode);
910
918
  if (authMode === "oauth") {
@@ -922,7 +930,13 @@ async function connectApp(token, companyUid, app, opts, expectedRevivedConnectio
922
930
  const result = await installIntegration(token, companyUid, {
923
931
  ...target.ref,
924
932
  ...(bearerToken
925
- ? { authMode: "bearer", bearerToken }
933
+ ? {
934
+ authMode: "bearer",
935
+ bearerToken,
936
+ ...(opts.authHeader
937
+ ? { authScheme: { placement: "header", header: opts.authHeader } }
938
+ : {}),
939
+ }
926
940
  : authMode === "none"
927
941
  ? { authMode: "none" }
928
942
  : {}),
@@ -1173,6 +1187,7 @@ export function registerConnectCommands(integrations) {
1173
1187
  .option("--display-name <name>", "Display name for a custom --mcp-url")
1174
1188
  .option("--token <key>", "API key (prefer --token-stdin: --token lands in shell history)")
1175
1189
  .option("--token-stdin", "Read the API key from stdin")
1190
+ .option("--auth-header <name>", "Send the API key in this HTTP header (implies --auth key)")
1176
1191
  .option("--auth <mode>", "Force the auth mode: none, key, or oauth (default: detect)")
1177
1192
  .option("--client-id <id>", "Client id of an OAuth app you registered with the provider yourself")
1178
1193
  .option("--client-secret-stdin", "Read that app's client secret from stdin")
@@ -1183,6 +1198,10 @@ export function registerConnectCommands(integrations) {
1183
1198
  .option("--json", "Machine-readable output")
1184
1199
  .action(async (app, opts) => {
1185
1200
  assertAuthMode(opts.auth);
1201
+ assertAuthHeader(opts.authHeader);
1202
+ if (opts.authHeader && opts.auth !== undefined && opts.auth !== "key") {
1203
+ throw new IntegrationsCliError("--auth-header can only be used with --auth key.", { expected: true });
1204
+ }
1186
1205
  const token = await ensureCognitoIdToken();
1187
1206
  const companyUid = await getCompanyUid(token, opts.company);
1188
1207
  if (await refuseIfAlreadyConnected(token, companyUid, app, opts))
@@ -247,6 +247,7 @@ export function createLaneMessageHandlers(deps) {
247
247
  }
248
248
  let lane = resolution.lane;
249
249
  if (resolution.remoteOnly || (lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane))) {
250
+ const fallbackAt = lane.state === "awaiting_input" ? ctx.now().toISOString() : undefined;
250
251
  const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
251
252
  const supersededMessageCallerId = callerLaneId === undefined || resolution.remoteOnly
252
253
  ? undefined
@@ -262,9 +263,14 @@ export function createLaneMessageHandlers(deps) {
262
263
  ...opts,
263
264
  ...(supersededMessageCallerId === undefined ? {} : { supersededMessageCallerId }),
264
265
  ...(supersededMessageSessionId === undefined ? {} : { supersededMessageSessionId }),
265
- }, resolution.remoteOnly
266
- ? { syncPrimary: false, expectedOwnerHost: resolution.ownerHost }
267
- : undefined);
266
+ }, {
267
+ ...(resolution.remoteOnly
268
+ ? { syncPrimary: false, expectedOwnerHost: resolution.ownerHost }
269
+ : {}),
270
+ ...(fallbackAt === undefined ? {} : {
271
+ afterFallbackResume: () => deps.cancelFallbackQuestionsForLane(ctx, lane, "message", fallbackAt),
272
+ }),
273
+ });
268
274
  return;
269
275
  }
270
276
  assertLaneRunsHereForOperatorMutation(ctx, lane, "message");
@@ -350,6 +356,19 @@ export function createLaneMessageHandlers(deps) {
350
356
  if (opts.account !== undefined && !keepCurrentAccount && !providerSessionPortable(lane.provider)) {
351
357
  throw new LanesError("provider_session_not_portable", sessionNotPortableMessage(lane.provider), { lane_id: laneId, provider: lane.provider });
352
358
  }
359
+ const pendingAnswerableQuestion = lane.state === "awaiting_input"
360
+ ? await deps.hasPendingQuestionForLane(ctx, lane)
361
+ : false;
362
+ if (pendingAnswerableQuestion) {
363
+ const message = deliveryRefusalReason(lane);
364
+ throw new LanesError("lane_not_accepting_messages", message, {
365
+ lane_id: laneId,
366
+ state: lane.state,
367
+ provider: lane.provider,
368
+ message,
369
+ });
370
+ }
371
+ const awaitingInputFallback = lane.state === "awaiting_input" && !pendingAnswerableQuestion;
353
372
  // One decision point for how a follow-up reaches this lane (delivery.ts).
354
373
  // `none` refuses before anything is written: no inbox append, no spawn.
355
374
  const mechanism = laneDeliveryMechanism(lane);
@@ -536,9 +555,13 @@ export function createLaneMessageHandlers(deps) {
536
555
  ...(keepCurrentAccount && opts.model !== undefined ? { cliModel: opts.model } : {}),
537
556
  });
538
557
  }
558
+ let questionCancellation = null;
539
559
  if (move?.queued) {
540
560
  // Parked at the target's cap: reconcile resumes the session there.
541
561
  markAccepted();
562
+ questionCancellation = awaitingInputFallback
563
+ ? await deps.cancelFallbackQuestionsForLane(ctx, lane, "message", at)
564
+ : null;
542
565
  }
543
566
  else {
544
567
  const dispatched = await dispatchLaneDelivery(lane, {
@@ -637,6 +660,9 @@ export function createLaneMessageHandlers(deps) {
637
660
  if (dispatched.mechanism === "resume")
638
661
  resumed = dispatched.value;
639
662
  markAccepted();
663
+ questionCancellation = awaitingInputFallback
664
+ ? await deps.cancelFallbackQuestionsForLane(ctx, lane, "message", at)
665
+ : null;
640
666
  }
641
667
  // Every accepted message moves the compaction reference forward.
642
668
  const next = casUpdateLane(hqRoot, laneId, (current) => ({
@@ -679,6 +705,10 @@ export function createLaneMessageHandlers(deps) {
679
705
  inbox_path: mechanism === "dropbox" ? dropboxInboxPath(hqRoot, laneId) : null,
680
706
  drain_hook: drainHook,
681
707
  warning,
708
+ ...(questionCancellation === null ? {} : {
709
+ cancelled_questions: questionCancellation.cancelled_question_ids,
710
+ question_cancellation_failures: questionCancellation.failures,
711
+ }),
682
712
  resumed_session_id: resumed?.last_resume?.session_id ?? null,
683
713
  compaction: resumed?.last_compaction && resumed.last_compaction.resume_at === resumed.last_resume?.at
684
714
  ? resumed.last_compaction
@@ -1,7 +1,9 @@
1
- import { type QuestionBody, type QuestionRecord } from "../lib/lanes/index.js";
2
- import type { Ctx, LaneQuestionsHandlerDeps } from "./lanes-shared.js";
1
+ import { type QuestionBody, type LaneRecord, type QuestionRecord } from "../lib/lanes/index.js";
2
+ import type { Ctx, FallbackQuestionCancellationResult, LaneQuestionsHandlerDeps } from "./lanes-shared.js";
3
3
  export declare function resolveAnswerChoice(choice: string, options: string[], freeform: boolean): string;
4
4
  export declare function createLaneQuestionsHandlers(deps: LaneQuestionsHandlerDeps): {
5
+ hasPendingQuestionForLane: (ctx: Ctx, lane: LaneRecord) => Promise<boolean>;
6
+ cancelFallbackQuestionsForLane: (ctx: Ctx, lane: LaneRecord, trigger: "message" | "resume", at: string) => Promise<FallbackQuestionCancellationResult>;
5
7
  runQuestionsList: (ctx: Ctx, opts: {
6
8
  allHosts?: boolean;
7
9
  for?: string;