@indigoai-us/hq-cli 5.126.0 → 5.128.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 (78) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
  3. package/assets/lanes-hooks/hq-lanes-drain.mjs +254 -0
  4. package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
  5. package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
  6. package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
  7. package/dist/command-catalog.generated.d.ts +245 -11
  8. package/dist/command-catalog.generated.js +323 -11
  9. package/dist/command-registration-plan.d.ts +1 -0
  10. package/dist/command-registration-plan.js +11 -2
  11. package/dist/commands/agents.d.ts +12 -0
  12. package/dist/commands/agents.js +46 -2
  13. package/dist/commands/lanes.d.ts +55 -16
  14. package/dist/commands/lanes.js +1187 -250
  15. package/dist/commands/mesh.js +254 -19
  16. package/dist/lib/core-utils/sentry-report.d.ts +2 -0
  17. package/dist/lib/core-utils/sentry-report.js +47 -7
  18. package/dist/lib/lanes/accounts.d.ts +67 -7
  19. package/dist/lib/lanes/accounts.js +233 -43
  20. package/dist/lib/lanes/compaction.d.ts +96 -0
  21. package/dist/lib/lanes/compaction.js +197 -0
  22. package/dist/lib/lanes/config.d.ts +15 -0
  23. package/dist/lib/lanes/config.js +65 -8
  24. package/dist/lib/lanes/delivery.d.ts +151 -0
  25. package/dist/lib/lanes/delivery.js +354 -0
  26. package/dist/lib/lanes/dropbox.d.ts +96 -0
  27. package/dist/lib/lanes/dropbox.js +408 -0
  28. package/dist/lib/lanes/effort.d.ts +50 -0
  29. package/dist/lib/lanes/effort.js +109 -0
  30. package/dist/lib/lanes/errors.d.ts +2 -2
  31. package/dist/lib/lanes/hooks-install.d.ts +81 -0
  32. package/dist/lib/lanes/hooks-install.js +326 -0
  33. package/dist/lib/lanes/hq-root.d.ts +19 -0
  34. package/dist/lib/lanes/hq-root.js +28 -0
  35. package/dist/lib/lanes/index.d.ts +35 -5
  36. package/dist/lib/lanes/index.js +19 -4
  37. package/dist/lib/lanes/liveness.d.ts +7 -1
  38. package/dist/lib/lanes/liveness.js +64 -5
  39. package/dist/lib/lanes/machine-identity.d.ts +34 -0
  40. package/dist/lib/lanes/machine-identity.js +92 -0
  41. package/dist/lib/lanes/mesh-events.d.ts +109 -0
  42. package/dist/lib/lanes/mesh-events.js +275 -0
  43. package/dist/lib/lanes/outpost-dispatch.d.ts +65 -0
  44. package/dist/lib/lanes/outpost-dispatch.js +90 -0
  45. package/dist/lib/lanes/questions.d.ts +75 -0
  46. package/dist/lib/lanes/questions.js +243 -0
  47. package/dist/lib/lanes/quota.d.ts +95 -0
  48. package/dist/lib/lanes/quota.js +320 -0
  49. package/dist/lib/lanes/reconcile-install.d.ts +69 -0
  50. package/dist/lib/lanes/reconcile-install.js +221 -0
  51. package/dist/lib/lanes/reconcile.d.ts +102 -0
  52. package/dist/lib/lanes/reconcile.js +985 -0
  53. package/dist/lib/lanes/registry.d.ts +18 -5
  54. package/dist/lib/lanes/registry.js +293 -19
  55. package/dist/lib/lanes/spawn.d.ts +274 -0
  56. package/dist/lib/lanes/spawn.js +1363 -0
  57. package/dist/lib/lanes/store.d.ts +7 -3
  58. package/dist/lib/lanes/store.js +150 -32
  59. package/dist/lib/lanes/types.d.ts +346 -4
  60. package/dist/lib/lanes/types.js +138 -5
  61. package/dist/lib/lanes/watcher-main.d.ts +9 -0
  62. package/dist/lib/lanes/watcher-main.js +22 -0
  63. package/dist/lib/lanes/watcher.d.ts +216 -0
  64. package/dist/lib/lanes/watcher.js +859 -0
  65. package/dist/lib/mesh/api.d.ts +71 -7
  66. package/dist/lib/mesh/api.js +167 -28
  67. package/dist/lib/mesh/live/backfill-held.d.ts +18 -7
  68. package/dist/lib/mesh/live/backfill-held.js +66 -21
  69. package/dist/lib/mesh/live/daemon/run.d.ts +2 -0
  70. package/dist/lib/mesh/live/daemon/run.js +111 -54
  71. package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
  72. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +3 -0
  73. package/dist/lib/mesh/live/daemon/transcript-watch.js +31 -8
  74. package/dist/lib/scan-packages/index.d.ts +2 -0
  75. package/dist/lib/scan-packages/index.js +1 -1
  76. package/dist/lib/work-context/state.d.ts +12 -0
  77. package/dist/lib/work-context/state.js +131 -3
  78. package/package.json +3 -1
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Per-lane watcher (spec §5.7).
3
+ *
4
+ * Armed at spawn, detached from the caller. Emits on progress, terminal, and
5
+ * silence (20 minutes with the process still alive). Terminal classification
6
+ * follows spec §5: an envelope decides done/ask/blocked; a process that exits
7
+ * with partial output and no envelope is `interrupted`; silence is never
8
+ * success.
9
+ *
10
+ * Progress has two sources. Output files (worker.out, worker.err,
11
+ * last-message.txt) cover codex and grok, which stream as they work. They do
12
+ * not cover Claude: `claude -p --output-format json` writes nothing until it
13
+ * exits, so a healthy Claude lane used to hit the silence deadline at exactly
14
+ * 20 minutes every time, and a hung one looked identical. The second source
15
+ * is logs/tool-activity.json, which the drain hook rewrites on every
16
+ * PreToolUse call (dropbox.ts). A worker that is working calls tools; a
17
+ * worker that has not called a tool or written output for the whole window
18
+ * is `stalled`, and the watcher says so on the lane record.
19
+ *
20
+ * What this cannot tell apart: a worker hung inside one tool call from a
21
+ * worker running one legitimately long tool call (a 25-minute build); a
22
+ * worker looping on tool calls without making progress from a working one;
23
+ * a model stuck mid-generation with no tool call from one that is writing a
24
+ * long answer. It also depends on the drain hook being registered in the
25
+ * provider home (hooks-install.ts): with no hook there is no touch, and the
26
+ * lane reads as stalled every window, which is the pre-existing behaviour.
27
+ * Distinguishing a hung tool from a slow one needs the provider to report
28
+ * per-call state, which none of the three CLIs expose today. `stalled` is
29
+ * therefore a flag for the senior to look at, never a kill.
30
+ *
31
+ * Queue admission of the next lane when a slot frees is owned by the
32
+ * periodic reconcile pass and is out of scope here.
33
+ */
34
+ import { type LaneEnvelope, type LaneRecord, type LaneState } from "./types.js";
35
+ import { type QuotaSignal } from "./quota.js";
36
+ import { type LaneMesh } from "./mesh-events.js";
37
+ import { type EnvelopeRepairSpawnInput } from "./spawn.js";
38
+ /** Spec §5.7: no signal for 20 minutes while the process is still alive. */
39
+ export declare const SILENCE_MS: number;
40
+ export declare const WATCHER_POLL_MS = 5000;
41
+ /** Synced last_host_report_at cadence. Reconcile dead-host TTL is 20 minutes. */
42
+ export declare const WATCHER_HEARTBEAT_MS: number;
43
+ /**
44
+ * Cap on logs/watcher.out. A decision line is typically under 300 bytes, so
45
+ * 256 KiB holds hundreds of transitions. A stuck lane cannot fill the disk:
46
+ * once the file would exceed this, the oldest complete lines are dropped.
47
+ */
48
+ export declare const WATCHER_OUT_MAX_BYTES: number;
49
+ export declare const WATCHER_DECISION_EVENTS: readonly ["liveness_transition", "silence_deadline", "envelope_observed", "envelope_invalid", "envelope_repair", "classification", "terminal_write", "mesh_event", "mesh_event_failed"];
50
+ /** Terminal reason when the envelope failed to parse after the repair turn (spec §4). */
51
+ export declare const ENVELOPE_INVALID_REASON: "envelope_invalid";
52
+ export type WatcherDecisionEvent = (typeof WATCHER_DECISION_EVENTS)[number];
53
+ export declare const WATCHER_SIGNAL_KINDS: readonly ["progress", "terminal", "silence"];
54
+ export type WatcherSignalKind = (typeof WATCHER_SIGNAL_KINDS)[number];
55
+ export interface TerminalClassification {
56
+ state: Extract<LaneState, "done" | "awaiting_input" | "blocked" | "interrupted" | "failed">;
57
+ reason?: string;
58
+ }
59
+ export interface WatcherSignal {
60
+ kind: WatcherSignalKind;
61
+ at: string;
62
+ classification?: TerminalClassification;
63
+ }
64
+ export interface WatcherTickInput {
65
+ nowMs: number;
66
+ processAlive: boolean;
67
+ progressed: boolean;
68
+ lastSignalMs: number;
69
+ envelope: LaneEnvelope | null;
70
+ hasPartialOutput: boolean;
71
+ silenceMs?: number;
72
+ quota?: QuotaSignal | null;
73
+ invalidEnvelope?: {
74
+ errors: string[];
75
+ } | null;
76
+ }
77
+ export interface WatcherTickResult {
78
+ emit?: WatcherSignal;
79
+ nextLastSignalMs: number;
80
+ done: boolean;
81
+ }
82
+ export declare function parseWatcherSignalKind(value: unknown): WatcherSignalKind;
83
+ export declare function classifyLaneExit(input: {
84
+ envelope: LaneEnvelope | null;
85
+ hasPartialOutput: boolean;
86
+ /**
87
+ * Set when the worker's own result is a 429 or a usage-limit error.
88
+ * Distinct from a crash: the reason is account_quota_exhausted or
89
+ * account_model_unavailable, not exited_with_partial_output.
90
+ */
91
+ quota?: QuotaSignal | null;
92
+ /**
93
+ * Set when the run's envelope file exists but does not parse and no
94
+ * repair turn remains. Failed, regardless of partial output: the worker
95
+ * did finish, it just did not honour the contract.
96
+ */
97
+ invalidEnvelope?: {
98
+ errors: string[];
99
+ } | null;
100
+ }): TerminalClassification;
101
+ export declare function tickWatcher(input: WatcherTickInput): WatcherTickResult;
102
+ export declare function watcherEventsPath(hqRoot: string, laneId: string): string;
103
+ export declare function watcherOutLogPath(hqRoot: string, laneId: string): string;
104
+ export declare function appendCappedLog(filePath: string, line: string, maxBytes?: number): void;
105
+ export declare function writeWatcherDecision(filePath: string, record: Record<string, unknown>, maxBytes?: number): void;
106
+ export declare function watcherLastSignalPath(hqRoot: string, laneId: string): string;
107
+ export declare function appendWatcherSignal(hqRoot: string, laneId: string, signal: WatcherSignal): void;
108
+ /**
109
+ * Slot-free notification. The periodic reconcile pass admits the next queued
110
+ * lane; this watcher must not do it. Intentionally empty.
111
+ */
112
+ export declare function notifySlotFreed(_input: {
113
+ hqRoot: string;
114
+ provider: LaneRecord["provider"];
115
+ account?: string;
116
+ }): void;
117
+ export interface ApplyTerminalOpts {
118
+ envelope?: LaneEnvelope | null;
119
+ envelopeRef?: string;
120
+ at?: string;
121
+ quota?: QuotaSignal | null;
122
+ }
123
+ export declare function stalledReason(silenceMs: number): string;
124
+ /**
125
+ * The consumer of the silence signal. A silence emit on a running lane sets
126
+ * `stalled` on the record so `hq lanes list` and `show` surface it; the next
127
+ * progress emit clears it. Nothing here changes `state`.
128
+ */
129
+ export declare function markLaneStalled(hqRoot: string, laneId: string, at: string, reason: string): LaneRecord;
130
+ export declare function clearLaneStalled(hqRoot: string, laneId: string): LaneRecord;
131
+ export declare function applyTerminalToLane(hqRoot: string, laneId: string, classification: TerminalClassification, opts?: ApplyTerminalOpts): LaneRecord;
132
+ /** Terminal write outcome for emitters: only the CAS winner may publish mesh effects. */
133
+ export declare function applyTerminalToLaneWithResult(hqRoot: string, laneId: string, classification: TerminalClassification, opts?: ApplyTerminalOpts): {
134
+ record: LaneRecord;
135
+ changed: boolean;
136
+ };
137
+ export declare function isPidAlive(pid: number): boolean;
138
+ export interface LatestEnvelope {
139
+ envelope: LaneEnvelope;
140
+ ref: string;
141
+ }
142
+ /** The newest envelope file of this run, parsed or not. */
143
+ export interface LatestEnvelopeFile {
144
+ ref: string;
145
+ /** Absolute path of the file. */
146
+ path: string;
147
+ text: string;
148
+ envelope: LaneEnvelope | null;
149
+ /** Parser message when `envelope` is null. */
150
+ error: string | null;
151
+ }
152
+ export declare function readLatestEnvelope(hqRoot: string, laneId: string, minName?: string): LaneEnvelope | null;
153
+ /** Valid-only view over `readLatestEnvelopeFile`; an unparseable file reads as absent. */
154
+ export declare function readLatestEnvelopeRecord(hqRoot: string, laneId: string, minName?: string): LatestEnvelope | null;
155
+ /**
156
+ * `minName` is this run's envelope slot (basename from spawn.json). Envelopes
157
+ * that sort before it belong to an earlier run of the same lane and must not
158
+ * classify this exit: a resumed run that dies before writing its own envelope
159
+ * would otherwise be marked done again from the previous one.
160
+ */
161
+ export declare function readLatestEnvelopeFile(hqRoot: string, laneId: string, minName?: string): LatestEnvelopeFile | null;
162
+ export interface WatcherLoopDeps {
163
+ sleep?: (ms: number) => Promise<void>;
164
+ now?: () => number;
165
+ isAlive?: (pid: number) => boolean;
166
+ readStarttime?: (pid: number) => number | null;
167
+ pollMs?: number;
168
+ silenceMs?: number;
169
+ watcherOutPath?: string;
170
+ watcherOutMaxBytes?: number;
171
+ /**
172
+ * Mesh seam (spec §8). Default: `createLaneMesh({ interactive: false })`.
173
+ * `null` disables emission entirely (tests that are not about mesh).
174
+ */
175
+ mesh?: LaneMesh | null;
176
+ /** Override for `providers.<provider>.progress_event_min_interval_ms`. */
177
+ progressEventMinIntervalMs?: number;
178
+ /** Bound a hung mesh request so watcher liveness polling continues. */
179
+ meshRequestTimeoutMs?: number;
180
+ /** Spec §4 repair spawn. Default: `spawnEnvelopeRepair`. */
181
+ spawnRepair?: (hqRoot: string, lane: LaneRecord, input: EnvelopeRepairSpawnInput) => number;
182
+ }
183
+ /**
184
+ * Wrap one mesh call. A failed emit is logged as `mesh_event_failed` with
185
+ * the event name and the error message, and never propagates: the lane's
186
+ * state is decided by the envelope and the process, not by the network.
187
+ */
188
+ export declare function emitMeshSoft(event: string, logDecision: (record: Record<string, unknown>) => void, at: string, fn: (signal: AbortSignal) => Promise<void>, timeoutMs?: number): Promise<boolean>;
189
+ /** Question ids an ask envelope raised, as `raiseEnvelopeQuestions` mints them. */
190
+ export declare function envelopeQuestionIds(laneId: string, envelopeRef: string, envelope: LaneEnvelope): string[];
191
+ export declare function runWatcherLoop(hqRoot: string, laneId: string, deps?: WatcherLoopDeps): Promise<void>;
192
+ export type EnvelopeRepairDecision = {
193
+ action: "spawned";
194
+ } | {
195
+ action: "give_up";
196
+ errors: string[];
197
+ };
198
+ /**
199
+ * Spec §4 repair decision for an exited worker whose envelope file does not
200
+ * parse. First failure on a resumable provider with a recorded session id:
201
+ * record `envelope_repair` on the lane and spawn the repair turn. A provider
202
+ * with no session resume gets the same single attempt as a fresh turn. Anything
203
+ * else (already attempted, no session id on a resumable provider, spawn
204
+ * failure): give up with every error collected, and the caller classifies
205
+ * the lane `failed` with reason `envelope_invalid`.
206
+ */
207
+ export declare function decideEnvelopeRepair(hqRoot: string, laneId: string, latest: LatestEnvelopeFile, nowMs: number, spawnRepair: NonNullable<WatcherLoopDeps["spawnRepair"]>, logDecision: (record: Record<string, unknown>) => void): EnvelopeRepairDecision;
208
+ /** Exported so tests can prove the watcher reads the live record, not spawn env. */
209
+ export declare function laneIdentityFromRecord(hqRoot: string, laneId: string): {
210
+ lane_id: string;
211
+ company: LaneRecord["company"];
212
+ project_id: string;
213
+ story_id: string;
214
+ senior: LaneRecord["senior"];
215
+ };
216
+ //# sourceMappingURL=watcher.d.ts.map