@hwip/cli 0.0.1 → 0.1.1

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 (245) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +11 -0
  3. package/README.md +1983 -0
  4. package/dist/agent-list.d.ts +76 -0
  5. package/dist/agent-list.js +102 -0
  6. package/dist/agent-read.d.ts +105 -0
  7. package/dist/agent-read.js +309 -0
  8. package/dist/agent-rules-fetch.d.ts +51 -0
  9. package/dist/agent-rules-fetch.js +275 -0
  10. package/dist/agent-rules.default.d.ts +30 -0
  11. package/dist/agent-rules.default.js +305 -0
  12. package/dist/agent-session.d.ts +23 -0
  13. package/dist/agent-session.js +44 -0
  14. package/dist/agent-state.d.ts +84 -0
  15. package/dist/agent-state.js +188 -0
  16. package/dist/agent-target.d.ts +60 -0
  17. package/dist/agent-target.js +81 -0
  18. package/dist/agents.d.ts +31 -0
  19. package/dist/agents.js +75 -0
  20. package/dist/api.d.ts +28 -0
  21. package/dist/api.js +76 -0
  22. package/dist/args.d.ts +74 -0
  23. package/dist/args.js +254 -0
  24. package/dist/ask-context-attach.d.ts +73 -0
  25. package/dist/ask-context-attach.js +151 -0
  26. package/dist/ask-context-screen.d.ts +21 -0
  27. package/dist/ask-context-screen.js +49 -0
  28. package/dist/ask-context-switch.d.ts +31 -0
  29. package/dist/ask-context-switch.js +74 -0
  30. package/dist/ask-context.d.ts +84 -0
  31. package/dist/ask-context.js +172 -0
  32. package/dist/ask-pending.d.ts +47 -0
  33. package/dist/ask-pending.js +221 -0
  34. package/dist/ask-seal.d.ts +120 -0
  35. package/dist/ask-seal.js +179 -0
  36. package/dist/ask.d.ts +107 -0
  37. package/dist/ask.js +551 -0
  38. package/dist/blocked-prompt-seal.d.ts +39 -0
  39. package/dist/blocked-prompt-seal.js +92 -0
  40. package/dist/blocked-prompt.d.ts +62 -0
  41. package/dist/blocked-prompt.js +518 -0
  42. package/dist/channel.d.ts +67 -0
  43. package/dist/channel.js +136 -0
  44. package/dist/check-update.d.ts +28 -0
  45. package/dist/check-update.js +153 -0
  46. package/dist/cli.d.ts +3 -0
  47. package/dist/cli.js +813 -0
  48. package/dist/codex-config.d.ts +24 -0
  49. package/dist/codex-config.js +169 -0
  50. package/dist/codex-transcript.d.ts +59 -0
  51. package/dist/codex-transcript.js +373 -0
  52. package/dist/command-refusal.d.ts +56 -0
  53. package/dist/command-refusal.js +127 -0
  54. package/dist/command-scan.d.ts +46 -0
  55. package/dist/command-scan.js +154 -0
  56. package/dist/command-seal.d.ts +57 -0
  57. package/dist/command-seal.js +88 -0
  58. package/dist/command-seq.d.ts +30 -0
  59. package/dist/command-seq.js +150 -0
  60. package/dist/config.d.ts +156 -0
  61. package/dist/config.js +392 -0
  62. package/dist/core-sections.d.ts +18 -0
  63. package/dist/core-sections.js +28 -0
  64. package/dist/crypto.d.ts +278 -0
  65. package/dist/crypto.js +698 -0
  66. package/dist/desktop-notify.d.ts +33 -0
  67. package/dist/desktop-notify.js +49 -0
  68. package/dist/device-directory.d.ts +36 -0
  69. package/dist/device-directory.js +97 -0
  70. package/dist/device-presence.d.ts +17 -0
  71. package/dist/device-presence.js +62 -0
  72. package/dist/downloads.d.ts +29 -0
  73. package/dist/downloads.js +106 -0
  74. package/dist/errors.d.ts +12 -0
  75. package/dist/errors.js +28 -0
  76. package/dist/file-crypto.d.ts +94 -0
  77. package/dist/file-crypto.js +278 -0
  78. package/dist/file-view-budget.d.ts +15 -0
  79. package/dist/file-view-budget.js +34 -0
  80. package/dist/file-view-switch.d.ts +28 -0
  81. package/dist/file-view-switch.js +122 -0
  82. package/dist/file-view.d.ts +108 -0
  83. package/dist/file-view.js +327 -0
  84. package/dist/gate.d.ts +141 -0
  85. package/dist/gate.js +336 -0
  86. package/dist/gemini-transcript.d.ts +32 -0
  87. package/dist/gemini-transcript.js +189 -0
  88. package/dist/http-failure.d.ts +98 -0
  89. package/dist/http-failure.js +349 -0
  90. package/dist/hwip-core.generated.d.ts +7 -0
  91. package/dist/hwip-core.generated.js +14 -0
  92. package/dist/hwip-hook.d.ts +77 -0
  93. package/dist/hwip-hook.js +340 -0
  94. package/dist/index.d.ts +7 -0
  95. package/dist/index.js +14 -0
  96. package/dist/input-sequencer.d.ts +65 -0
  97. package/dist/input-sequencer.js +142 -0
  98. package/dist/insecure-url.d.ts +16 -0
  99. package/dist/insecure-url.js +76 -0
  100. package/dist/installer.d.ts +96 -0
  101. package/dist/installer.js +850 -0
  102. package/dist/inventory-offload.d.ts +49 -0
  103. package/dist/inventory-offload.js +209 -0
  104. package/dist/inventory-worker.d.ts +2 -0
  105. package/dist/inventory-worker.js +27 -0
  106. package/dist/jsonc-doc.d.ts +106 -0
  107. package/dist/jsonc-doc.js +354 -0
  108. package/dist/keep-awake.d.ts +32 -0
  109. package/dist/keep-awake.js +46 -0
  110. package/dist/key-changes.d.ts +48 -0
  111. package/dist/key-changes.js +140 -0
  112. package/dist/lan-auth.d.ts +140 -0
  113. package/dist/lan-auth.js +217 -0
  114. package/dist/lan-endpoint.d.ts +41 -0
  115. package/dist/lan-endpoint.js +115 -0
  116. package/dist/lan-receiver.d.ts +122 -0
  117. package/dist/lan-receiver.js +510 -0
  118. package/dist/lan-sender.d.ts +76 -0
  119. package/dist/lan-sender.js +184 -0
  120. package/dist/lan-transfer.d.ts +36 -0
  121. package/dist/lan-transfer.js +54 -0
  122. package/dist/listener-device-id.d.ts +20 -0
  123. package/dist/listener-device-id.js +133 -0
  124. package/dist/listener-log.d.ts +4 -0
  125. package/dist/listener-log.js +23 -0
  126. package/dist/listener-process.d.ts +200 -0
  127. package/dist/listener-process.js +470 -0
  128. package/dist/listener-service.d.ts +225 -0
  129. package/dist/listener-service.js +682 -0
  130. package/dist/listener-worktree-cleanup.d.ts +27 -0
  131. package/dist/listener-worktree-cleanup.js +62 -0
  132. package/dist/listener.d.ts +1325 -0
  133. package/dist/listener.js +5681 -0
  134. package/dist/login-address.d.ts +25 -0
  135. package/dist/login-address.js +97 -0
  136. package/dist/login-page.d.ts +29 -0
  137. package/dist/login-page.js +93 -0
  138. package/dist/login.d.ts +98 -0
  139. package/dist/login.js +388 -0
  140. package/dist/mcp-command.d.ts +45 -0
  141. package/dist/mcp-command.js +50 -0
  142. package/dist/mcp.d.ts +19 -0
  143. package/dist/mcp.js +29 -0
  144. package/dist/node-support.d.ts +19 -0
  145. package/dist/node-support.js +38 -0
  146. package/dist/notify-failure.d.ts +57 -0
  147. package/dist/notify-failure.js +150 -0
  148. package/dist/opencode-transcript.d.ts +139 -0
  149. package/dist/opencode-transcript.js +390 -0
  150. package/dist/optional-features.d.ts +6 -0
  151. package/dist/optional-features.js +19 -0
  152. package/dist/pane-fit.d.ts +40 -0
  153. package/dist/pane-fit.js +119 -0
  154. package/dist/peer-key.d.ts +33 -0
  155. package/dist/peer-key.js +34 -0
  156. package/dist/pi-transcript.d.ts +26 -0
  157. package/dist/pi-transcript.js +255 -0
  158. package/dist/presence.d.ts +14 -0
  159. package/dist/presence.js +124 -0
  160. package/dist/private-files.d.ts +51 -0
  161. package/dist/private-files.js +218 -0
  162. package/dist/project-switch.d.ts +19 -0
  163. package/dist/project-switch.js +60 -0
  164. package/dist/redact.d.ts +10 -0
  165. package/dist/redact.js +21 -0
  166. package/dist/remote-agents.d.ts +410 -0
  167. package/dist/remote-agents.js +1218 -0
  168. package/dist/remote-hook.d.ts +12 -0
  169. package/dist/remote-hook.js +196 -0
  170. package/dist/replace-file.d.ts +50 -0
  171. package/dist/replace-file.js +328 -0
  172. package/dist/schedule-command.d.ts +35 -0
  173. package/dist/schedule-command.js +260 -0
  174. package/dist/schedule-launch.d.ts +15 -0
  175. package/dist/schedule-launch.js +24 -0
  176. package/dist/schedule-ready.d.ts +44 -0
  177. package/dist/schedule-ready.js +71 -0
  178. package/dist/schedule-remote.d.ts +77 -0
  179. package/dist/schedule-remote.js +167 -0
  180. package/dist/schedule-runner.d.ts +89 -0
  181. package/dist/schedule-runner.js +452 -0
  182. package/dist/schedule.d.ts +149 -0
  183. package/dist/schedule.js +428 -0
  184. package/dist/secret-mask.d.ts +29 -0
  185. package/dist/secret-mask.js +245 -0
  186. package/dist/self-register.d.ts +45 -0
  187. package/dist/self-register.js +67 -0
  188. package/dist/send.d.ts +56 -0
  189. package/dist/send.js +104 -0
  190. package/dist/session-diff.d.ts +161 -0
  191. package/dist/session-diff.js +445 -0
  192. package/dist/session-registry.d.ts +143 -0
  193. package/dist/session-registry.js +216 -0
  194. package/dist/sessions-report.d.ts +59 -0
  195. package/dist/sessions-report.js +99 -0
  196. package/dist/shell-audit.d.ts +34 -0
  197. package/dist/shell-audit.js +47 -0
  198. package/dist/subagent-transcripts.d.ts +167 -0
  199. package/dist/subagent-transcripts.js +437 -0
  200. package/dist/subscriber-trust.d.ts +128 -0
  201. package/dist/subscriber-trust.js +397 -0
  202. package/dist/templates.d.ts +105 -0
  203. package/dist/templates.js +871 -0
  204. package/dist/tmux-install.d.ts +44 -0
  205. package/dist/tmux-install.js +65 -0
  206. package/dist/tmux-liveness.d.ts +45 -0
  207. package/dist/tmux-liveness.js +71 -0
  208. package/dist/tmux-pane.d.ts +55 -0
  209. package/dist/tmux-pane.js +126 -0
  210. package/dist/tmux-socket.d.ts +20 -0
  211. package/dist/tmux-socket.js +292 -0
  212. package/dist/todo-fold.d.ts +31 -0
  213. package/dist/todo-fold.js +102 -0
  214. package/dist/transcript-tail.d.ts +312 -0
  215. package/dist/transcript-tail.js +718 -0
  216. package/dist/trust-command.d.ts +49 -0
  217. package/dist/trust-command.js +200 -0
  218. package/dist/trust-lock.d.ts +26 -0
  219. package/dist/trust-lock.js +94 -0
  220. package/dist/trust-pins.d.ts +37 -0
  221. package/dist/trust-pins.js +82 -0
  222. package/dist/turn-recorder-wire.d.ts +70 -0
  223. package/dist/turn-recorder-wire.js +165 -0
  224. package/dist/turn-recorder.d.ts +190 -0
  225. package/dist/turn-recorder.js +695 -0
  226. package/dist/turn-ring.d.ts +93 -0
  227. package/dist/turn-ring.js +418 -0
  228. package/dist/turn-watch.d.ts +177 -0
  229. package/dist/turn-watch.js +630 -0
  230. package/dist/types.d.ts +63 -0
  231. package/dist/types.js +2 -0
  232. package/dist/uninstall.d.ts +55 -0
  233. package/dist/uninstall.js +507 -0
  234. package/dist/usage.d.ts +17 -0
  235. package/dist/usage.js +421 -0
  236. package/dist/verify.d.ts +47 -0
  237. package/dist/verify.js +396 -0
  238. package/dist/worktree.d.ts +144 -0
  239. package/dist/worktree.js +310 -0
  240. package/dist/wrapper.d.ts +254 -0
  241. package/dist/wrapper.js +704 -0
  242. package/dist/ws-url.d.ts +109 -0
  243. package/dist/ws-url.js +202 -0
  244. package/package.json +78 -10
  245. package/bin/hwip.js +0 -3
@@ -0,0 +1,49 @@
1
+ import type { CollectResult } from './listener.js';
2
+ export interface InventoryReply {
3
+ id: number;
4
+ result?: CollectResult;
5
+ error?: string;
6
+ }
7
+ export interface InventoryOffload {
8
+ /** Resolve one inventory sweep. Rejects when a working worker fails mid-sweep. */
9
+ collect: () => Promise<CollectResult>;
10
+ /** Stop the worker. Safe to call more than once. */
11
+ close: () => void;
12
+ }
13
+ /** Longer than any sane sweep (measured ~3s at 15 sessions), shorter than the
14
+ * run of cycles a stuck worker would otherwise cost: the caller gates sweeps
15
+ * one-at-a-time, so this bounds how long a hang can hold the inventory. */
16
+ export declare const COLLECT_TIMEOUT_MS = 20000;
17
+ /**
18
+ * Consecutive timed-out sweeps before the offload stops using workers.
19
+ *
20
+ * A sweep that overruns is terminated, and the next one starts a fresh worker
21
+ * from nothing — so on a host where a cold sweep cannot finish inside the
22
+ * timeout, retrying forever means the daemon never reports at all. Reporting
23
+ * slowly beats not reporting, and in-thread is exactly "slowly". Three in a row
24
+ * rather than one, so a single slow moment (a load spike, a machine waking) does
25
+ * not cost the offload straight away; the streak resets on any answered sweep.
26
+ */
27
+ export declare const MAX_CONSECUTIVE_TIMEOUTS = 3;
28
+ /**
29
+ * How long timed-out sweeps run in-thread before a worker is tried again.
30
+ *
31
+ * This used to be forever, which inverted the whole point of the offload: a
32
+ * load spike long enough to overrun three sweeps left the daemon running the
33
+ * blocking sweep on its own event loop for the rest of its life. Measured
34
+ * 2026-09-18 on this Mac — after the switch, a `setInterval(5_000)` fired at a
35
+ * median of 28.5 s, the WebSocket read loop went with it, and the heartbeat
36
+ * killed a healthy socket every 1-3 minutes.
37
+ *
38
+ * The load that overruns a sweep passes; the give-up must pass with it. Five
39
+ * minutes is long enough that a busy stretch is not spent respawning workers
40
+ * into the same wall, short enough that the degraded state is measured in
41
+ * minutes rather than days.
42
+ */
43
+ export declare const IN_THREAD_COOLDOWN_MS: number;
44
+ export declare const startInventoryOffload: (inThread: () => CollectResult, log: (msg: string) => void, workerPath?: string,
45
+ /** Overridable so a test can exercise the timeout path without waiting 20s. */
46
+ timeoutMs?: number,
47
+ /** Overridable for the same reason as `timeoutMs`. */
48
+ cooldownMs?: number) => InventoryOffload;
49
+ //# sourceMappingURL=inventory-offload.d.ts.map
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startInventoryOffload = exports.IN_THREAD_COOLDOWN_MS = exports.MAX_CONSECUTIVE_TIMEOUTS = exports.COLLECT_TIMEOUT_MS = void 0;
4
+ /**
5
+ * Runs the tmux session inventory on a worker thread so the daemon's event
6
+ * loop stays free while `tmux` child processes are waited on.
7
+ *
8
+ * Why a thread and not async spawns: the inventory is a chain of synchronous
9
+ * `spawnSync` calls — one `list-sessions` plus `display-message` and
10
+ * `capture-pane` per session — and with 15 sessions a single sweep was measured
11
+ * at 3s on the main thread (57% of daemon wall time, measured 2026-08-26).
12
+ * Every one of those seconds the WebSocket was neither read nor written, so
13
+ * stream subscribes arrived late, frames left late, pongs were missed and leases
14
+ * lapsed. Moving the same synchronous code onto a worker keeps every tmux call
15
+ * exactly as it was — including the tests that stub `spawnSync` — and only
16
+ * changes which thread waits.
17
+ *
18
+ * The worker is optional. A worker that fails before it has ever answered
19
+ * (missing file, module error, immediate exit) is given up on: pending and
20
+ * future sweeps run in-thread as before, so a broken worker degrades to the old
21
+ * behaviour instead of an empty session list. A worker that dies after having
22
+ * worked is treated as transient — that sweep fails (the caller skips the
23
+ * cycle) and the next `collect()` starts a fresh one.
24
+ *
25
+ * A worker we killed ourselves is neither. `terminate()` (on timeout, on close)
26
+ * exits with code 1, indistinguishable from a crash, so the exit code cannot be
27
+ * read — what separates the two is whether that worker still owed an answer,
28
+ * which is why `Live` carries its own `owed` ids rather than sharing `pending`.
29
+ * Getting this wrong is expensive in one direction only: a timeout misread as
30
+ * "died before ever answering" is permanent, and it fires exactly when the host
31
+ * is slow enough for a sweep to overrun, which is when the offload matters most.
32
+ */
33
+ const path_1 = require("path");
34
+ const worker_threads_1 = require("worker_threads");
35
+ /** Longer than any sane sweep (measured ~3s at 15 sessions), shorter than the
36
+ * run of cycles a stuck worker would otherwise cost: the caller gates sweeps
37
+ * one-at-a-time, so this bounds how long a hang can hold the inventory. */
38
+ exports.COLLECT_TIMEOUT_MS = 20_000;
39
+ /**
40
+ * Consecutive timed-out sweeps before the offload stops using workers.
41
+ *
42
+ * A sweep that overruns is terminated, and the next one starts a fresh worker
43
+ * from nothing — so on a host where a cold sweep cannot finish inside the
44
+ * timeout, retrying forever means the daemon never reports at all. Reporting
45
+ * slowly beats not reporting, and in-thread is exactly "slowly". Three in a row
46
+ * rather than one, so a single slow moment (a load spike, a machine waking) does
47
+ * not cost the offload straight away; the streak resets on any answered sweep.
48
+ */
49
+ exports.MAX_CONSECUTIVE_TIMEOUTS = 3;
50
+ /**
51
+ * How long timed-out sweeps run in-thread before a worker is tried again.
52
+ *
53
+ * This used to be forever, which inverted the whole point of the offload: a
54
+ * load spike long enough to overrun three sweeps left the daemon running the
55
+ * blocking sweep on its own event loop for the rest of its life. Measured
56
+ * 2026-09-18 on this Mac — after the switch, a `setInterval(5_000)` fired at a
57
+ * median of 28.5 s, the WebSocket read loop went with it, and the heartbeat
58
+ * killed a healthy socket every 1-3 minutes.
59
+ *
60
+ * The load that overruns a sweep passes; the give-up must pass with it. Five
61
+ * minutes is long enough that a busy stretch is not spent respawning workers
62
+ * into the same wall, short enough that the degraded state is measured in
63
+ * minutes rather than days.
64
+ */
65
+ exports.IN_THREAD_COOLDOWN_MS = 5 * 60_000;
66
+ const startInventoryOffload = (inThread, log, workerPath = (0, path_1.join)(__dirname, 'inventory-worker.js'),
67
+ /** Overridable so a test can exercise the timeout path without waiting 20s. */
68
+ timeoutMs = exports.COLLECT_TIMEOUT_MS,
69
+ /** Overridable for the same reason as `timeoutMs`. */
70
+ cooldownMs = exports.IN_THREAD_COOLDOWN_MS) => {
71
+ let worker = null;
72
+ let closed = false;
73
+ /** Set once a worker has answered; a failure before that is not transient. */
74
+ let everReplied = false;
75
+ /** Set when workers are given up on for good — a worker that never worked. */
76
+ let inThreadOnly = false;
77
+ /** While `Date.now()` is under this, timed-out sweeps run in-thread instead. */
78
+ let inThreadUntil = 0;
79
+ let nextId = 0;
80
+ /** Timed-out sweeps since the last answered one. */
81
+ let consecutiveTimeouts = 0;
82
+ const pending = new Map();
83
+ const settleAll = (outcome) => {
84
+ for (const [id, p] of pending) {
85
+ clearTimeout(p.timer);
86
+ pending.delete(id);
87
+ outcome(p);
88
+ }
89
+ };
90
+ const onFailure = (what) => {
91
+ if (closed)
92
+ return settleAll((p) => p.reject(new Error('inventory offload closed')));
93
+ if (everReplied)
94
+ return settleAll((p) => p.reject(new Error(what)));
95
+ inThreadOnly = true;
96
+ log(`! ${what} before its first sweep — running the inventory in-thread from now on`);
97
+ settleAll((p) => {
98
+ try {
99
+ p.resolve(inThread());
100
+ }
101
+ catch (err) {
102
+ p.reject(err instanceof Error ? err : new Error(String(err)));
103
+ }
104
+ });
105
+ };
106
+ const spawn = () => {
107
+ const w = new worker_threads_1.Worker(workerPath);
108
+ // Never keep the process alive on the worker's account: the daemon's
109
+ // own socket and timers decide when it exits.
110
+ w.unref();
111
+ const live = { w, owed: new Set() };
112
+ w.on('message', (msg) => {
113
+ live.owed.delete(msg.id);
114
+ const p = pending.get(msg.id);
115
+ if (!p)
116
+ return;
117
+ pending.delete(msg.id);
118
+ clearTimeout(p.timer);
119
+ everReplied = true;
120
+ consecutiveTimeouts = 0;
121
+ if (msg.error !== undefined)
122
+ p.reject(new Error(msg.error));
123
+ else
124
+ p.resolve(msg.result);
125
+ });
126
+ w.on('error', (err) => {
127
+ if (worker === live)
128
+ worker = null;
129
+ // Reported here, so the exit that follows is this same death and not
130
+ // a second one. An id leaves `owed` the moment its sweep is settled,
131
+ // whatever settled it — that is the invariant the exit handler reads.
132
+ live.owed.clear();
133
+ onFailure(`inventory worker error (${err instanceof Error ? err.message : String(err)})`);
134
+ });
135
+ w.on('exit', (code) => {
136
+ if (worker === live)
137
+ worker = null;
138
+ // Did THIS worker still owe an answer? `terminate()` exits with code
139
+ // 1 exactly like a crash, so the code says nothing; what separates a
140
+ // death we caused from one we did not is whether the sweep it was
141
+ // holding had already been settled. Asking the shared `pending` map
142
+ // instead judged a terminated worker against the NEXT worker's sweep
143
+ // — the timeout path nulls `worker`, so the following poll spawns a
144
+ // replacement and registers its id a second before this event lands.
145
+ // A routine timeout then read as "died before ever answering", which
146
+ // is permanent: `everReplied` is false, so the offload never
147
+ // recovers for the life of the daemon.
148
+ if (live.owed.size > 0)
149
+ onFailure(`inventory worker exited (${code})`);
150
+ });
151
+ return live;
152
+ };
153
+ const collect = () => {
154
+ if (closed || inThreadOnly)
155
+ return Promise.resolve(inThread());
156
+ if (inThreadUntil > 0) {
157
+ if (Date.now() < inThreadUntil)
158
+ return Promise.resolve(inThread());
159
+ inThreadUntil = 0;
160
+ // One strike, not three. The first degrade spends three sweeps
161
+ // finding out the host is too slow, and each of those costs a whole
162
+ // `timeoutMs` with no inventory at all (the caller skips the cycle
163
+ // and gates the next sweep behind this one). Paying that again on
164
+ // every cooldown expiry, on a host that is still loaded, is the
165
+ // "not reporting" this module exists to avoid.
166
+ consecutiveTimeouts = exports.MAX_CONSECUTIVE_TIMEOUTS - 1;
167
+ log('inventory: trying the worker again');
168
+ }
169
+ worker ??= spawn();
170
+ const live = worker;
171
+ const id = ++nextId;
172
+ return new Promise((resolve, reject) => {
173
+ const timer = setTimeout(() => {
174
+ pending.delete(id);
175
+ // Settled here, so the terminate below is a death we caused and
176
+ // this worker's exit must not be reported as a failure.
177
+ live.owed.delete(id);
178
+ consecutiveTimeouts += 1;
179
+ if (consecutiveTimeouts >= exports.MAX_CONSECUTIVE_TIMEOUTS && !inThreadOnly && inThreadUntil === 0) {
180
+ inThreadUntil = Date.now() + cooldownMs;
181
+ const held = cooldownMs >= 60_000 ? `${Math.round(cooldownMs / 60_000)} min` : `${Math.round(cooldownMs / 1_000)}s`;
182
+ log(`! inventory sweep timed out ${consecutiveTimeouts}× in a row — running the inventory in-thread for ${held}`);
183
+ }
184
+ reject(new Error(`inventory sweep exceeded ${timeoutMs / 1000}s`));
185
+ // A stuck worker is not coming back; the next call spawns a fresh one.
186
+ if (worker === live)
187
+ worker = null;
188
+ void live.w.terminate();
189
+ }, timeoutMs);
190
+ timer.unref();
191
+ pending.set(id, { resolve, reject, timer });
192
+ live.owed.add(id);
193
+ live.w.postMessage(id);
194
+ });
195
+ };
196
+ const close = () => {
197
+ closed = true;
198
+ settleAll((p) => p.reject(new Error('inventory offload closed')));
199
+ const live = worker;
200
+ worker = null;
201
+ if (!live)
202
+ return;
203
+ // Settled above; clear what it owed for the same reason the timeout does.
204
+ live.owed.clear();
205
+ void live.w.terminate();
206
+ };
207
+ return { collect, close };
208
+ };
209
+ exports.startInventoryOffload = startInventoryOffload;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inventory-worker.d.ts.map
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Worker-thread entry for the tmux session inventory (see inventory-offload.ts).
5
+ * One sweep per request; the sweep itself is the unchanged synchronous
6
+ * `collectSessionsVerbose` — it only runs on this thread now.
7
+ */
8
+ const worker_threads_1 = require("worker_threads");
9
+ const agent_rules_fetch_js_1 = require("./agent-rules-fetch.js");
10
+ const listener_js_1 = require("./listener.js");
11
+ const port = worker_threads_1.parentPort;
12
+ if (!port)
13
+ throw new Error('inventory-worker must be started as a worker thread');
14
+ port.on('message', (id) => {
15
+ let reply;
16
+ try {
17
+ // Own module instance: pick up the OTA rules the main thread cached, else
18
+ // every non-claude session reports `unknown` (see syncManifestFromCache).
19
+ // Inside the try: a throw here must become an error reply, not a timeout.
20
+ (0, agent_rules_fetch_js_1.syncManifestFromCache)();
21
+ reply = { id, result: (0, listener_js_1.collectSessionsVerbose)() };
22
+ }
23
+ catch (err) {
24
+ reply = { id, error: err instanceof Error ? err.message : String(err) };
25
+ }
26
+ port.postMessage(reply);
27
+ });
@@ -0,0 +1,106 @@
1
+ import { type JSONPath } from 'jsonc-parser';
2
+ /**
3
+ * An agent's settings file read as JSONC — comments and trailing commas allowed,
4
+ * the way the agents themselves read them — and changed by small edits to its
5
+ * text, so what the user wrote around hwip's entries (comments, their ordering,
6
+ * their indentation) stays exactly as it was. `jsonc-parser` (Microsoft, the
7
+ * parser VS Code uses for its own settings) does the parsing and the edits;
8
+ * this file only fixes the conventions hwip needs on top of it.
9
+ *
10
+ * Plain JSON is a subset, so a file without comments is read and edited the
11
+ * same way. A file with a syntax error is not read at all (`JsoncSyntaxError`):
12
+ * editing around a broken part could write something worse.
13
+ *
14
+ * Every edit is checked: the text after it must read back as exactly the value
15
+ * the edit was meant to produce. The library gets that wrong in places — removing
16
+ * an item of an array whose last two items share a line joins them into one or
17
+ * breaks the brackets — and a settings file that agents read must never be left
18
+ * like that. A file with
19
+ * no comments is then written whole from the intended value (nothing of the
20
+ * user's is lost); one with comments is left unchanged and the edit throws
21
+ * (`JsoncEditError`).
22
+ */
23
+ export declare class JsoncSyntaxError extends Error {
24
+ constructor();
25
+ }
26
+ /** Valid JSONC, but the value is a list, a string, `null` — not a settings object. */
27
+ export declare class JsoncNotObjectError extends Error {
28
+ constructor();
29
+ }
30
+ /** An edit the library could not apply correctly to a file whose comments a rewrite would lose. */
31
+ export declare class JsoncEditError extends Error {
32
+ constructor();
33
+ }
34
+ /** The value of a JSONC text, or `undefined` when it has a syntax error. An empty text is `{}`. */
35
+ export declare const parseJsonc: (text: string) => unknown;
36
+ export declare class JsoncDoc {
37
+ private readonly bom;
38
+ private body;
39
+ private readonly formatting;
40
+ /** What the file held when it was read (null: there was no file), for `replaceFile` to check before it writes. */
41
+ readonly expected: string | null;
42
+ /**
43
+ * `text` is a settings file's content; throws `JsoncSyntaxError`, or `JsoncNotObjectError` when it is valid but not an object.
44
+ * `existed` is false for a file that was not there (`text` is then empty).
45
+ */
46
+ constructor(text: string, existed?: boolean);
47
+ /** The text without an entry that shares its line (a compact array or object), or null when a comment sits in the way. */
48
+ private withoutInline;
49
+ /** Would removing the entry at `path` take a comment with it (one inside the entry, or at the end of its last line)? */
50
+ removalTakesComment(path: JSONPath): boolean;
51
+ /** Does the text hold a comment? (Not one inside a string: the scanner knows tokens.) */
52
+ hasComments(): boolean;
53
+ /** Every comment in `text` with where it starts, in order. */
54
+ private static commentsIn;
55
+ /**
56
+ * Did `next` lose a comment of the current text that was not inside `allowed`
57
+ * (a range of the current text: what the edit may take with it)? The library
58
+ * drops comments around a removed entry it has no right to; a copy of the same
59
+ * text elsewhere does not stand in for a lost one.
60
+ */
61
+ private losesComment;
62
+ /** Take `next` as the new text if it reads back as `expected` and loses no comment but those in `allowed`; else see the class comment. */
63
+ private commit;
64
+ /** The current value, read again after every edit. */
65
+ get data(): Record<string, unknown>;
66
+ /** Set `path` to `value`, creating what is missing on the way. */
67
+ set(path: JSONPath, value: unknown): void;
68
+ /** Remove the property or array item at `path`. A comment right next to it goes with it. */
69
+ remove(path: JSONPath): void;
70
+ /**
71
+ * What removing the entry at `path` may take with it: the entry itself and the
72
+ * comment at the end of its last line — not one above it, nor one after the
73
+ * closing bracket of its container, which are not this entry's.
74
+ */
75
+ private removalZone;
76
+ /**
77
+ * The text without the lines of the entry at `path`, when it stands on lines of
78
+ * its own: its own comment and comma go with it, and so does the comma before it
79
+ * when it was the last one. A comment on another line, or on the line of the
80
+ * entry before, stays — the library would take the comment next to the entry
81
+ * along. An entry that shares its line with others goes through `withoutInline`.
82
+ */
83
+ private withoutLines;
84
+ /** Add `value` at the end of the array at `path`, after the comment on the last item's line. */
85
+ append(path: JSONPath, value: unknown): void;
86
+ /**
87
+ * The library's insert can leave a comment of the user's on the line of the new
88
+ * entry, and an uninstall would take it away with the entry (it is the entry's
89
+ * "own" comment by position). A new entry must have no comment in its removal
90
+ * zone; otherwise fail now, naming the file, and leave the text alone.
91
+ */
92
+ private refuseAdoptedComment;
93
+ /** `value` as JSON text in the file's own indentation unit. */
94
+ private render;
95
+ /**
96
+ * The text with `childText` put after the last child of an object or array, on a
97
+ * line of its own at the last child's indentation; null for an empty container
98
+ * or one whose last child shares its line with something else (the caller falls
99
+ * back). The library's insert would re-indent the siblings and move a comment at
100
+ * the end of the last child's line onto the new child.
101
+ */
102
+ private insertLast;
103
+ /** The file's new text: its byte order mark kept, one line ending at the end. */
104
+ text(): string;
105
+ }
106
+ //# sourceMappingURL=jsonc-doc.d.ts.map