@indigoai-us/hq-cli 5.178.0 → 5.179.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.179.0] — 2026-09-24
6
+
5
7
  ## [5.178.0] — 2026-09-24
6
8
 
7
9
  ### Fixed
8
10
 
11
+ - Work Mesh transcript-watch still tracks hook-covered sessions (interactive Claude/Codex) and enqueues `session_end` after 10 minutes of quiet. Hooks keep owning start/turn events; the quiet `session_end` is the channel work-report trigger those sessions were missing. Resume (transcript mtime or a later hook event) resets the timer. A SessionEnd hook within 60s of that emit is not treated as resume, so `/exit` does not double-fire.
9
12
  - Reloading a macOS LaunchAgent (`hq mesh daemon install`, including `--if-stale` and the post-update restart, `hq daemon install`, `hq agent kit` services, and bot agents) now waits for `launchctl bootout` to finish before bootstrapping. Before, bootstrap could race the old job's teardown, fail with "Bootstrap failed: 5: Input/output error", and leave the service unloaded with nothing running. Bootstrap now polls `launchctl print` for up to 10 seconds, retries EIO with backoff (0.5s to 8s), and on final failure either restarts the still-loaded previous job or reports that the service is NOT loaded with the `launchctl bootstrap` command to fix it.
10
13
 
11
14
  ## [5.177.0] — 2026-09-24
@@ -52,7 +55,6 @@
52
55
  - Bot LaunchAgents and systemd units, the Work Mesh daemon unit, the `hq daemon` unit, and the lanes and agent-kit units now name node by its upgrade-stable path (`/opt/homebrew/bin/node` or `/opt/homebrew/opt/<formula>/bin/node`, or nvm's `current` link) instead of the versioned `/opt/homebrew/Cellar/node/<version>/bin/node`. Before, `brew upgrade node` deleted that path and launchd could no longer start the bots. The stable path is used only when it resolves to the same node binary; otherwise the unit keeps the resolved path as before.
53
56
  - Installed bot LaunchAgents that still name a versioned or missing node are repaired: `hq bot start` rewrites and reloads the agent, a bot started by launchd rewrites its own agent and reloads it from a detached shell before it takes any message, and `hq doctor` reports it (`bots.<name>.daemon-node`), with `hq doctor --fix` rewriting only the plist file and not reloading launchd. `hq bot list` and `hq bot status` flag a missing node binary. `hq mesh daemon install --if-stale` also reinstalls a same-version unit whose node path is missing or versioned.
54
57
 
55
-
56
58
  ## [5.172.0] — 2026-09-24
57
59
 
58
60
  ### Changed
@@ -89,7 +91,6 @@
89
91
 
90
92
  - Switching the HQ session company (including `hq-session.sh set company_slug`) clears the Work Mesh project and task, posts `session_end` to the previous company, and holds a personal target as `needs_company`.
91
93
 
92
-
93
94
  ## [5.169.0] — 2026-09-24
94
95
 
95
96
  ### Fixed
@@ -24,6 +24,14 @@
24
24
  * Network: harness claude-desktop | codex, source "transcript" on every event.
25
25
  * Local state keeps transcriptMarker for channel-card / Atlas before network sync;
26
26
  * localHarness is no longer written (harness carries claude-desktop on the wire).
27
+ *
28
+ * Hook-covered sessions (durable hook-written state, or a hook event within
29
+ * TRANSCRIPT_HOOK_COVER_MS) do not get synthesized session_start / turn_end —
30
+ * hooks own those. The watcher still tracks last activity (transcript mtime or
31
+ * hook events) and enqueues session_end after TRANSCRIPT_SESSION_END_MS of
32
+ * quiet so the channel work report has a trigger (US-050b). Resume resets the
33
+ * timer. A hook event within TRANSCRIPT_HOOK_COVER_MS of our own emit is the
34
+ * SessionEnd hook catching up and is not treated as resume.
27
35
  */
28
36
  import * as fs from "node:fs";
29
37
  import type { MembershipValidationResult } from "../../../work-context/config.js";
@@ -260,6 +268,14 @@ export declare class TranscriptWatcher {
260
268
  /** Record that a hook path emitted an event for sessionId (daemon flush). */
261
269
  noteHookEvent(sessionId: string, atMs?: number): void;
262
270
  tick(): Promise<TranscriptWatchTickResult>;
271
+ /**
272
+ * Hook-covered sessions already emit start/turns. Still track last activity
273
+ * and enqueue session_end after TRANSCRIPT_SESSION_END_MS of quiet so the
274
+ * channel work report has a trigger (US-050b). Resume (mtime or a later hook
275
+ * event) resets the timer. A hook event shortly after our own emit is the
276
+ * SessionEnd hook catching up — do not treat it as resume.
277
+ */
278
+ private tickHookCoveredQuietEnd;
263
279
  /**
264
280
  * Heavy ticks double the poll interval up to MAX; a quiet tick resets to base.
265
281
  */
@@ -24,6 +24,14 @@
24
24
  * Network: harness claude-desktop | codex, source "transcript" on every event.
25
25
  * Local state keeps transcriptMarker for channel-card / Atlas before network sync;
26
26
  * localHarness is no longer written (harness carries claude-desktop on the wire).
27
+ *
28
+ * Hook-covered sessions (durable hook-written state, or a hook event within
29
+ * TRANSCRIPT_HOOK_COVER_MS) do not get synthesized session_start / turn_end —
30
+ * hooks own those. The watcher still tracks last activity (transcript mtime or
31
+ * hook events) and enqueues session_end after TRANSCRIPT_SESSION_END_MS of
32
+ * quiet so the channel work report has a trigger (US-050b). Resume resets the
33
+ * timer. A hook event within TRANSCRIPT_HOOK_COVER_MS of our own emit is the
34
+ * SessionEnd hook catching up and is not treated as resume.
27
35
  */
28
36
  import * as fs from "node:fs";
29
37
  import * as os from "node:os";
@@ -632,8 +640,7 @@ export class TranscriptWatcher {
632
640
  lastHookEventAt: this.deps.lastHookEventAt,
633
641
  readState: this.readState,
634
642
  })) {
635
- result.skippedHookCovered += 1;
636
- this.tracked.delete(d.path);
643
+ this.tickHookCoveredQuietEnd(d, nowMs, result);
637
644
  continue;
638
645
  }
639
646
  let track = this.tracked.get(d.path);
@@ -719,6 +726,63 @@ export class TranscriptWatcher {
719
726
  this.tickInFlight = false;
720
727
  }
721
728
  }
729
+ /**
730
+ * Hook-covered sessions already emit start/turns. Still track last activity
731
+ * and enqueue session_end after TRANSCRIPT_SESSION_END_MS of quiet so the
732
+ * channel work report has a trigger (US-050b). Resume (mtime or a later hook
733
+ * event) resets the timer. A hook event shortly after our own emit is the
734
+ * SessionEnd hook catching up — do not treat it as resume.
735
+ */
736
+ tickHookCoveredQuietEnd(d, nowMs, result) {
737
+ result.skippedHookCovered += 1;
738
+ const hookAt = this.deps.lastHookEventAt?.get(d.sessionId);
739
+ const lastActivity = Math.max(d.mtimeMs, hookAt ?? 0);
740
+ let track = this.tracked.get(d.path);
741
+ if (!track) {
742
+ if (nowMs - lastActivity > TRANSCRIPT_FRESH_MS)
743
+ return;
744
+ track = {
745
+ path: d.path,
746
+ sessionId: d.sessionId,
747
+ kind: d.kind,
748
+ cwd: d.cwd,
749
+ harness: d.harness,
750
+ lastMtimeMs: d.mtimeMs,
751
+ lastChangeAtMs: lastActivity,
752
+ started: true,
753
+ ended: false,
754
+ pendingTurn: false,
755
+ seq: 0,
756
+ startRetryAttempts: 0,
757
+ nextStartRetryAtMs: 0,
758
+ };
759
+ this.tracked.set(d.path, track);
760
+ }
761
+ if (d.mtimeMs > track.lastMtimeMs) {
762
+ track.lastMtimeMs = d.mtimeMs;
763
+ track.lastChangeAtMs = nowMs;
764
+ if (track.ended)
765
+ track.ended = false;
766
+ }
767
+ if (hookAt !== undefined) {
768
+ const resumeFromHook = !track.ended ||
769
+ track.endedAtMs === undefined ||
770
+ hookAt - track.endedAtMs >= TRANSCRIPT_HOOK_COVER_MS;
771
+ if (resumeFromHook && hookAt > track.lastChangeAtMs) {
772
+ track.lastChangeAtMs = hookAt;
773
+ if (track.ended)
774
+ track.ended = false;
775
+ }
776
+ }
777
+ if (track.started &&
778
+ !track.ended &&
779
+ nowMs - track.lastChangeAtMs >= TRANSCRIPT_SESSION_END_MS) {
780
+ this.emitKind(track, "session_end", nowMs);
781
+ track.ended = true;
782
+ track.endedAtMs = nowMs;
783
+ result.ended += 1;
784
+ }
785
+ }
722
786
  /**
723
787
  * Heavy ticks double the poll interval up to MAX; a quiet tick resets to base.
724
788
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.178.0",
3
+ "version": "5.179.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {