@gigaflow/gmux 0.15.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 (229) hide show
  1. package/CHANGELOG.md +654 -0
  2. package/LICENSE +21 -0
  3. package/README.md +351 -0
  4. package/dist/adapters/claude-code.d.ts +52 -0
  5. package/dist/adapters/claude-code.js +315 -0
  6. package/dist/adapters/claude-code.js.map +1 -0
  7. package/dist/adapters/codex.d.ts +39 -0
  8. package/dist/adapters/codex.js +233 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/jsonl.d.ts +40 -0
  11. package/dist/adapters/jsonl.js +92 -0
  12. package/dist/adapters/jsonl.js.map +1 -0
  13. package/dist/adapters/registry.d.ts +11 -0
  14. package/dist/adapters/registry.js +21 -0
  15. package/dist/adapters/registry.js.map +1 -0
  16. package/dist/adapters/types.d.ts +43 -0
  17. package/dist/adapters/types.js +2 -0
  18. package/dist/adapters/types.js.map +1 -0
  19. package/dist/cli/border-client.d.ts +8 -0
  20. package/dist/cli/border-client.js +11 -0
  21. package/dist/cli/border-client.js.map +1 -0
  22. package/dist/cli/commands/__ask-cancel.d.ts +35 -0
  23. package/dist/cli/commands/__ask-cancel.js +91 -0
  24. package/dist/cli/commands/__ask-cancel.js.map +1 -0
  25. package/dist/cli/commands/__ask-refresh.d.ts +40 -0
  26. package/dist/cli/commands/__ask-refresh.js +54 -0
  27. package/dist/cli/commands/__ask-refresh.js.map +1 -0
  28. package/dist/cli/commands/__ask-run.d.ts +43 -0
  29. package/dist/cli/commands/__ask-run.js +126 -0
  30. package/dist/cli/commands/__ask-run.js.map +1 -0
  31. package/dist/cli/commands/__ask-send.d.ts +50 -0
  32. package/dist/cli/commands/__ask-send.js +138 -0
  33. package/dist/cli/commands/__ask-send.js.map +1 -0
  34. package/dist/cli/commands/__preview-card.d.ts +31 -0
  35. package/dist/cli/commands/__preview-card.js +72 -0
  36. package/dist/cli/commands/__preview-card.js.map +1 -0
  37. package/dist/cli/commands/ask.d.ts +50 -0
  38. package/dist/cli/commands/ask.js +240 -0
  39. package/dist/cli/commands/ask.js.map +1 -0
  40. package/dist/cli/commands/auto.d.ts +12 -0
  41. package/dist/cli/commands/auto.js +28 -0
  42. package/dist/cli/commands/auto.js.map +1 -0
  43. package/dist/cli/commands/cockpit.d.ts +14 -0
  44. package/dist/cli/commands/cockpit.js +52 -0
  45. package/dist/cli/commands/cockpit.js.map +1 -0
  46. package/dist/cli/commands/daemon.d.ts +75 -0
  47. package/dist/cli/commands/daemon.js +258 -0
  48. package/dist/cli/commands/daemon.js.map +1 -0
  49. package/dist/cli/commands/doctor.d.ts +6 -0
  50. package/dist/cli/commands/doctor.js +159 -0
  51. package/dist/cli/commands/doctor.js.map +1 -0
  52. package/dist/cli/commands/grep.d.ts +2 -0
  53. package/dist/cli/commands/grep.js +52 -0
  54. package/dist/cli/commands/grep.js.map +1 -0
  55. package/dist/cli/commands/index-cmd.d.ts +2 -0
  56. package/dist/cli/commands/index-cmd.js +29 -0
  57. package/dist/cli/commands/index-cmd.js.map +1 -0
  58. package/dist/cli/commands/ls.d.ts +30 -0
  59. package/dist/cli/commands/ls.js +95 -0
  60. package/dist/cli/commands/ls.js.map +1 -0
  61. package/dist/cli/commands/overlay.d.ts +26 -0
  62. package/dist/cli/commands/overlay.js +216 -0
  63. package/dist/cli/commands/overlay.js.map +1 -0
  64. package/dist/cli/commands/pick.d.ts +59 -0
  65. package/dist/cli/commands/pick.js +254 -0
  66. package/dist/cli/commands/pick.js.map +1 -0
  67. package/dist/cli/commands/picker-rows.d.ts +17 -0
  68. package/dist/cli/commands/picker-rows.js +47 -0
  69. package/dist/cli/commands/picker-rows.js.map +1 -0
  70. package/dist/cli/commands/resume.d.ts +25 -0
  71. package/dist/cli/commands/resume.js +88 -0
  72. package/dist/cli/commands/resume.js.map +1 -0
  73. package/dist/cli/commands/run.d.ts +13 -0
  74. package/dist/cli/commands/run.js +78 -0
  75. package/dist/cli/commands/run.js.map +1 -0
  76. package/dist/cli/commands/setup.d.ts +37 -0
  77. package/dist/cli/commands/setup.js +200 -0
  78. package/dist/cli/commands/setup.js.map +1 -0
  79. package/dist/cli/commands/show.d.ts +2 -0
  80. package/dist/cli/commands/show.js +24 -0
  81. package/dist/cli/commands/show.js.map +1 -0
  82. package/dist/cli/commands/summarize.d.ts +2 -0
  83. package/dist/cli/commands/summarize.js +59 -0
  84. package/dist/cli/commands/summarize.js.map +1 -0
  85. package/dist/cli/commands/tmux.d.ts +11 -0
  86. package/dist/cli/commands/tmux.js +90 -0
  87. package/dist/cli/commands/tmux.js.map +1 -0
  88. package/dist/cli/commands/watch.d.ts +2 -0
  89. package/dist/cli/commands/watch.js +76 -0
  90. package/dist/cli/commands/watch.js.map +1 -0
  91. package/dist/cli/format.d.ts +74 -0
  92. package/dist/cli/format.js +242 -0
  93. package/dist/cli/format.js.map +1 -0
  94. package/dist/cli/gmux-render.d.ts +11 -0
  95. package/dist/cli/gmux-render.js +60 -0
  96. package/dist/cli/gmux-render.js.map +1 -0
  97. package/dist/cli/main.d.ts +8 -0
  98. package/dist/cli/main.js +145 -0
  99. package/dist/cli/main.js.map +1 -0
  100. package/dist/cli/overlay-ask.d.ts +13 -0
  101. package/dist/cli/overlay-ask.js +32 -0
  102. package/dist/cli/overlay-ask.js.map +1 -0
  103. package/dist/cli/overlay.d.ts +26 -0
  104. package/dist/cli/overlay.js +149 -0
  105. package/dist/cli/overlay.js.map +1 -0
  106. package/dist/cli/picker.d.ts +308 -0
  107. package/dist/cli/picker.js +642 -0
  108. package/dist/cli/picker.js.map +1 -0
  109. package/dist/cli/preview.d.ts +87 -0
  110. package/dist/cli/preview.js +292 -0
  111. package/dist/cli/preview.js.map +1 -0
  112. package/dist/cli/tmux-label.d.ts +50 -0
  113. package/dist/cli/tmux-label.js +146 -0
  114. package/dist/cli/tmux-label.js.map +1 -0
  115. package/dist/core/errors.d.ts +54 -0
  116. package/dist/core/errors.js +91 -0
  117. package/dist/core/errors.js.map +1 -0
  118. package/dist/core/fingerprint.d.ts +21 -0
  119. package/dist/core/fingerprint.js +59 -0
  120. package/dist/core/fingerprint.js.map +1 -0
  121. package/dist/core/gmux-types.d.ts +102 -0
  122. package/dist/core/gmux-types.js +13 -0
  123. package/dist/core/gmux-types.js.map +1 -0
  124. package/dist/core/pane-state.d.ts +5 -0
  125. package/dist/core/pane-state.js +15 -0
  126. package/dist/core/pane-state.js.map +1 -0
  127. package/dist/core/paths.d.ts +68 -0
  128. package/dist/core/paths.js +106 -0
  129. package/dist/core/paths.js.map +1 -0
  130. package/dist/core/proc-tree.d.ts +9 -0
  131. package/dist/core/proc-tree.js +33 -0
  132. package/dist/core/proc-tree.js.map +1 -0
  133. package/dist/core/text.d.ts +34 -0
  134. package/dist/core/text.js +126 -0
  135. package/dist/core/text.js.map +1 -0
  136. package/dist/core/types.d.ts +322 -0
  137. package/dist/core/types.js +10 -0
  138. package/dist/core/types.js.map +1 -0
  139. package/dist/services/ask-transcript.d.ts +256 -0
  140. package/dist/services/ask-transcript.js +544 -0
  141. package/dist/services/ask-transcript.js.map +1 -0
  142. package/dist/services/ask.d.ts +54 -0
  143. package/dist/services/ask.js +161 -0
  144. package/dist/services/ask.js.map +1 -0
  145. package/dist/services/auto-summarize.d.ts +188 -0
  146. package/dist/services/auto-summarize.js +415 -0
  147. package/dist/services/auto-summarize.js.map +1 -0
  148. package/dist/services/concurrency.d.ts +2 -0
  149. package/dist/services/concurrency.js +16 -0
  150. package/dist/services/concurrency.js.map +1 -0
  151. package/dist/services/config.d.ts +131 -0
  152. package/dist/services/config.js +325 -0
  153. package/dist/services/config.js.map +1 -0
  154. package/dist/services/daemon-client.d.ts +9 -0
  155. package/dist/services/daemon-client.js +34 -0
  156. package/dist/services/daemon-client.js.map +1 -0
  157. package/dist/services/daemon-socket.d.ts +30 -0
  158. package/dist/services/daemon-socket.js +76 -0
  159. package/dist/services/daemon-socket.js.map +1 -0
  160. package/dist/services/daemon.d.ts +43 -0
  161. package/dist/services/daemon.js +106 -0
  162. package/dist/services/daemon.js.map +1 -0
  163. package/dist/services/distill.d.ts +39 -0
  164. package/dist/services/distill.js +93 -0
  165. package/dist/services/distill.js.map +1 -0
  166. package/dist/services/guardian.d.ts +26 -0
  167. package/dist/services/guardian.js +71 -0
  168. package/dist/services/guardian.js.map +1 -0
  169. package/dist/services/index-store.d.ts +36 -0
  170. package/dist/services/index-store.js +125 -0
  171. package/dist/services/index-store.js.map +1 -0
  172. package/dist/services/log-rotation.d.ts +3 -0
  173. package/dist/services/log-rotation.js +28 -0
  174. package/dist/services/log-rotation.js.map +1 -0
  175. package/dist/services/pane-links.d.ts +10 -0
  176. package/dist/services/pane-links.js +47 -0
  177. package/dist/services/pane-links.js.map +1 -0
  178. package/dist/services/pane-process.d.ts +95 -0
  179. package/dist/services/pane-process.js +183 -0
  180. package/dist/services/pane-process.js.map +1 -0
  181. package/dist/services/pane-registry.d.ts +24 -0
  182. package/dist/services/pane-registry.js +40 -0
  183. package/dist/services/pane-registry.js.map +1 -0
  184. package/dist/services/provider-process.d.ts +37 -0
  185. package/dist/services/provider-process.js +105 -0
  186. package/dist/services/provider-process.js.map +1 -0
  187. package/dist/services/providers.d.ts +76 -0
  188. package/dist/services/providers.js +104 -0
  189. package/dist/services/providers.js.map +1 -0
  190. package/dist/services/resolve.d.ts +3 -0
  191. package/dist/services/resolve.js +18 -0
  192. package/dist/services/resolve.js.map +1 -0
  193. package/dist/services/resources.d.ts +17 -0
  194. package/dist/services/resources.js +113 -0
  195. package/dist/services/resources.js.map +1 -0
  196. package/dist/services/search.d.ts +29 -0
  197. package/dist/services/search.js +135 -0
  198. package/dist/services/search.js.map +1 -0
  199. package/dist/services/semantic-gate.d.ts +13 -0
  200. package/dist/services/semantic-gate.js +23 -0
  201. package/dist/services/semantic-gate.js.map +1 -0
  202. package/dist/services/semantic.d.ts +113 -0
  203. package/dist/services/semantic.js +216 -0
  204. package/dist/services/semantic.js.map +1 -0
  205. package/dist/services/sensors.d.ts +92 -0
  206. package/dist/services/sensors.js +246 -0
  207. package/dist/services/sensors.js.map +1 -0
  208. package/dist/services/summarize.d.ts +84 -0
  209. package/dist/services/summarize.js +230 -0
  210. package/dist/services/summarize.js.map +1 -0
  211. package/dist/services/tmux-gateway.d.ts +17 -0
  212. package/dist/services/tmux-gateway.js +10 -0
  213. package/dist/services/tmux-gateway.js.map +1 -0
  214. package/dist/services/tmux-resolve.d.ts +54 -0
  215. package/dist/services/tmux-resolve.js +0 -0
  216. package/dist/services/tmux-resolve.js.map +1 -0
  217. package/dist/services/tmux.d.ts +32 -0
  218. package/dist/services/tmux.js +107 -0
  219. package/dist/services/tmux.js.map +1 -0
  220. package/dist/services/views.d.ts +7 -0
  221. package/dist/services/views.js +18 -0
  222. package/dist/services/views.js.map +1 -0
  223. package/dist/services/watch.d.ts +36 -0
  224. package/dist/services/watch.js +98 -0
  225. package/dist/services/watch.js.map +1 -0
  226. package/dist/services/workspace.d.ts +19 -0
  227. package/dist/services/workspace.js +88 -0
  228. package/dist/services/workspace.js.map +1 -0
  229. package/package.json +64 -0
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Auto-summarize: keep the top of `gmux ls` written, without ever making you wait.
3
+ *
4
+ * A summary costs a model call (~8s). Ten of them, inline, would turn a 60ms
5
+ * `gmux ls` into a minute of staring at a cursor — so the foreground command never
6
+ * runs one. Instead it decides *what* would need summarizing, hands that job to
7
+ * a DETACHED child process, prints a one-line notice to stderr and exits. The
8
+ * summaries land in the cache and appear on the next run.
9
+ *
10
+ * Three things this module exists to prevent:
11
+ *
12
+ * 1. **Blocking.** The child is spawned `detached` with `stdio: "ignore"` and
13
+ * unref'd, so it outlives its parent and the parent's event loop drains
14
+ * immediately. Nothing is awaited.
15
+ *
16
+ * 2. **A stampede.** Five `gmux ls` in a row must not start five summarizers doing
17
+ * identical work. A lock file in the cache dir is created with O_EXCL before
18
+ * spawning; a lock whose owner is dead — or older than LOCK_STALE_MS — is
19
+ * taken over. A short cooldown stops us re-deciding on every invocation.
20
+ *
21
+ * 3. **A feedback loop.** The provider is `claude -p`, which itself writes a new
22
+ * Claude Code session to disk. If those were eligible targets, gmux
23
+ * would summarize its own summarizer forever. `automated` and `sidechain`
24
+ * sessions are therefore excluded from the target set — see
25
+ * `autoSummarizeCandidates`, which is where that guarantee lives.
26
+ */
27
+ import { spawn } from "node:child_process";
28
+ import { mkdir, open, readFile, rm, writeFile } from "node:fs/promises";
29
+ import { join } from "node:path";
30
+ import { cacheDir } from "../core/paths.js";
31
+ import { autoSummarizeAllowed, isChildProcess, readConfig } from "./config.js";
32
+ import { defaultSummaryProvider, readSummary, shouldRefresh, summarizeBatch, } from "./summarize.js";
33
+ import { loadRecords } from "./views.js";
34
+ /**
35
+ * How many recent sessions the background pass keeps summarized.
36
+ *
37
+ * This MUST be at least as large as `gmux ls`'s default limit, or the bottom of
38
+ * the default view is permanently `○` and the feature looks broken — which is
39
+ * exactly what happened when this was 10 and `gmux ls` showed 20.
40
+ *
41
+ * Override with `GMUX_AUTO_SUMMARIZE=<n>`.
42
+ */
43
+ export const AUTO_SUMMARIZE_LIMIT = 20;
44
+ /**
45
+ * Hard ceiling on one background pass.
46
+ *
47
+ * `gmux ls -n 500` should not fire 500 model calls. Anything beyond this is left
48
+ * for the next run, and we say so rather than truncating silently.
49
+ */
50
+ export const MAX_PER_PASS = 50;
51
+ /** A lock older than this belongs to a process that died without cleaning up. */
52
+ export const LOCK_STALE_MS = 10 * 60_000;
53
+ /** Don't re-decide on every invocation; `gmux ls` in a loop should cost nothing. */
54
+ export const COOLDOWN_MS = 60_000;
55
+ /** The hidden CLI command the detached worker runs. It never auto-summarizes itself. */
56
+ export const AUTO_SUMMARIZE_COMMAND = "__auto-summarize";
57
+ export function lockPath() {
58
+ return join(cacheDir(), "auto-summarize.lock");
59
+ }
60
+ export function statePath() {
61
+ return join(cacheDir(), "auto-summarize.state.json");
62
+ }
63
+ /** The sessions the running worker is writing right now. Drives the `◐` marker. */
64
+ export function queuePath() {
65
+ return join(cacheDir(), "auto-summarize.queue.json");
66
+ }
67
+ /** Where a failing background pass leaves its evidence. */
68
+ export function logPath() {
69
+ return join(cacheDir(), "auto-summarize.log");
70
+ }
71
+ /**
72
+ * Off via `GMUX_AUTO_SUMMARIZE=0`.
73
+ *
74
+ * Background model calls spend tokens. A user who has not opted into that must
75
+ * be able to say no once, in their shell profile, and be done with it.
76
+ */
77
+ export function autoSummarizeEnabled(env = process.env) {
78
+ const raw = env["GMUX_AUTO_SUMMARIZE"];
79
+ if (raw == null || raw.trim() === "")
80
+ return true;
81
+ return !["0", "false", "off", "no"].includes(raw.trim().toLowerCase());
82
+ }
83
+ /**
84
+ * Every reason the background pass might be off, in one place.
85
+ *
86
+ * Three independent "no"s, and any of them is final:
87
+ *
88
+ * - the env var — a shell-profile opt-out
89
+ * - the config — what the user answered in `gmux setup`
90
+ * - GMUX_CHILD — we are a `gmux` spawned by our own provider
91
+ *
92
+ * The last is the one that isn't obvious. `gmux ask`'s provider may run `gmux grep`,
93
+ * whose postAction hook would start a summarize pass — a model call spawned by a
94
+ * model call. The lock would absorb most of the damage; this makes it a
95
+ * non-question.
96
+ */
97
+ export async function autoSummarizeAllowedNow(env = process.env) {
98
+ if (!autoSummarizeEnabled(env))
99
+ return false;
100
+ if (isChildProcess(env))
101
+ return false;
102
+ return autoSummarizeAllowed(await readConfig());
103
+ }
104
+ function processAlive(pid) {
105
+ if (pid <= 0)
106
+ return true; // Acquired-but-not-yet-spawned: trust the timestamp.
107
+ try {
108
+ process.kill(pid, 0);
109
+ return true;
110
+ }
111
+ catch (error) {
112
+ // EPERM means the process exists but belongs to someone else — still alive.
113
+ return error.code === "EPERM";
114
+ }
115
+ }
116
+ /** A lock is stale when its owner is gone, or when it is simply too old. */
117
+ export function isLockStale(lock, now = new Date(), staleMs = LOCK_STALE_MS) {
118
+ const started = Date.parse(lock.startedAt);
119
+ if (Number.isNaN(started))
120
+ return true;
121
+ if (now.getTime() - started > staleMs)
122
+ return true;
123
+ return !processAlive(lock.pid);
124
+ }
125
+ export async function readLock() {
126
+ try {
127
+ const parsed = JSON.parse(await readFile(lockPath(), "utf8"));
128
+ if (typeof parsed?.startedAt !== "string")
129
+ return null;
130
+ return { pid: Number(parsed.pid) || 0, startedAt: parsed.startedAt };
131
+ }
132
+ catch {
133
+ return null; // No lock, or an unreadable one: treat as not held.
134
+ }
135
+ }
136
+ async function writeLock(lock) {
137
+ await writeFile(lockPath(), JSON.stringify(lock), "utf8");
138
+ }
139
+ async function createLockExclusive(lock) {
140
+ let handle;
141
+ try {
142
+ handle = await open(lockPath(), "wx");
143
+ }
144
+ catch {
145
+ return false; // EEXIST: someone else got there first.
146
+ }
147
+ try {
148
+ await handle.writeFile(JSON.stringify(lock), "utf8");
149
+ return true;
150
+ }
151
+ finally {
152
+ await handle.close();
153
+ }
154
+ }
155
+ /**
156
+ * Take the lock, or report that someone else holds it.
157
+ *
158
+ * The create is O_EXCL, so two `gmux` processes racing cannot both win. A stale
159
+ * lock is removed and re-created rather than overwritten, so the takeover is
160
+ * itself a race whose loser backs off instead of double-spawning.
161
+ */
162
+ export async function acquireLock(now = new Date()) {
163
+ await mkdir(cacheDir(), { recursive: true });
164
+ const lock = { pid: 0, startedAt: now.toISOString() };
165
+ if (await createLockExclusive(lock))
166
+ return true;
167
+ const existing = await readLock();
168
+ if (existing && !isLockStale(existing, now))
169
+ return false;
170
+ await rm(lockPath(), { force: true });
171
+ return createLockExclusive(lock);
172
+ }
173
+ export async function releaseLock() {
174
+ await rm(lockPath(), { force: true });
175
+ }
176
+ /** Record that we just decided; suppresses the next COOLDOWN_MS worth of checks. */
177
+ export async function noteCheck(now = new Date()) {
178
+ await mkdir(cacheDir(), { recursive: true });
179
+ await writeFile(statePath(), JSON.stringify({ lastCheckAt: now.toISOString() }), "utf8");
180
+ }
181
+ export async function inCooldown(now = new Date(), cooldownMs = COOLDOWN_MS) {
182
+ try {
183
+ const parsed = JSON.parse(await readFile(statePath(), "utf8"));
184
+ const last = Date.parse(parsed.lastCheckAt ?? "");
185
+ if (Number.isNaN(last))
186
+ return false;
187
+ return now.getTime() - last < cooldownMs;
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ }
193
+ /** Publish what the worker is about to write, so the list can mark those rows `◐`. */
194
+ export async function writeQueue(ids, now = new Date()) {
195
+ await mkdir(cacheDir(), { recursive: true });
196
+ const queue = { ids: [...ids], startedAt: now.toISOString() };
197
+ await writeFile(queuePath(), JSON.stringify(queue), "utf8");
198
+ }
199
+ export async function readQueue() {
200
+ try {
201
+ const parsed = JSON.parse(await readFile(queuePath(), "utf8"));
202
+ return Array.isArray(parsed?.ids) ? parsed : null;
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ export async function clearQueue() {
209
+ await rm(queuePath(), { force: true });
210
+ }
211
+ /**
212
+ * Sessions being summarized *right now*, for the `◐` marker.
213
+ *
214
+ * Guarded by the lock: a queue left behind by a crashed worker would otherwise
215
+ * mark rows as in-progress forever. No live lock, no in-progress rows.
216
+ */
217
+ export async function inProgressIds(now = new Date()) {
218
+ const lock = await readLock();
219
+ if (!lock || isLockStale(lock, now))
220
+ return new Set();
221
+ const queue = await readQueue();
222
+ return new Set(queue?.ids ?? []);
223
+ }
224
+ /**
225
+ * The sessions the background pass is allowed to touch.
226
+ *
227
+ * THE FEEDBACK-LOOP GUARD. `claude -p` — our own summary provider — writes a
228
+ * Claude Code session for every summary it produces, flagged `isAutomated`.
229
+ * Summarizing those would generate more of them, forever, at real token cost.
230
+ * Sidechains are excluded for the same reason plus a better one: nobody resumes
231
+ * a subagent transcript.
232
+ *
233
+ * Pure. This is the function the anti-loop test pins.
234
+ */
235
+ export function autoSummarizeCandidates(records, limit = AUTO_SUMMARIZE_LIMIT) {
236
+ return [...records]
237
+ .filter((record) => !record.isAutomated && !record.isSidechain)
238
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
239
+ .slice(0, limit);
240
+ }
241
+ /**
242
+ * Of the recent, eligible sessions, the ones whose summary is missing or stale.
243
+ *
244
+ * Reads the summary cache but calls no model, so tests exercise it for real.
245
+ */
246
+ export async function selectAutoSummarizeTargets(records, limit = AUTO_SUMMARIZE_LIMIT) {
247
+ const candidates = autoSummarizeCandidates(records, limit);
248
+ const needed = await Promise.all(candidates.map(async (record) => shouldRefresh(await readSummary(record), record)));
249
+ return candidates.filter((_, index) => needed[index] === true);
250
+ }
251
+ /**
252
+ * Decide whether to kick off a background summarize pass, and if so, do it.
253
+ *
254
+ * Never throws and never blocks on a model: every failure path is a silent skip.
255
+ * A read command must not die because `claude` is missing or a cache file is odd.
256
+ */
257
+ export async function maybeAutoSummarize(options = {}) {
258
+ try {
259
+ return await decide(options);
260
+ }
261
+ catch {
262
+ return { status: "spawn-failed", targets: 0, targetIds: [] };
263
+ }
264
+ }
265
+ async function decide(options) {
266
+ const now = options.now ?? new Date();
267
+ const none = (status) => ({
268
+ status,
269
+ targets: 0,
270
+ targetIds: [],
271
+ });
272
+ if (options.enabled === false || !(await autoSummarizeAllowedNow()))
273
+ return none("disabled");
274
+ // Cheapest checks first: two small file reads keep a repeated `gmux ls` free.
275
+ if (options.force !== true && (await inCooldown(now)))
276
+ return none("cooling-down");
277
+ const held = await readLock();
278
+ if (held && !isLockStale(held, now))
279
+ return none("locked");
280
+ // A missing model is not an error — it just means no summaries today. Nor is
281
+ // a null provider: that is `gmux setup`'s "none" being honored.
282
+ const provider = options.provider ?? (await defaultSummaryProvider());
283
+ if (!provider || !(await provider.isAvailable()))
284
+ return none("no-provider");
285
+ // The window follows what was displayed. With nothing passed (e.g. `gmux show`),
286
+ // fall back to the default recent window.
287
+ const records = options.records ?? (await loadRecords({ limit: AUTO_SUMMARIZE_LIMIT }));
288
+ const limit = Math.max(AUTO_SUMMARIZE_LIMIT, records.length);
289
+ const all = await selectAutoSummarizeTargets(records, limit);
290
+ if (all.length === 0) {
291
+ await noteCheck(now);
292
+ return none("nothing-to-do");
293
+ }
294
+ // Bounded, and never silently: a dropped tail gets picked up next run.
295
+ const targets = all.slice(0, MAX_PER_PASS);
296
+ const deferred = all.length - targets.length;
297
+ if (!(await acquireLock(now)))
298
+ return none("locked");
299
+ await noteCheck(now);
300
+ await writeQueue(targets.map((r) => r.sessionId), now);
301
+ let pid;
302
+ try {
303
+ pid = (options.spawnWorker ?? spawnWorker)();
304
+ }
305
+ catch {
306
+ pid = undefined;
307
+ }
308
+ if (pid === undefined) {
309
+ await releaseLock();
310
+ await clearQueue();
311
+ return none("spawn-failed");
312
+ }
313
+ await writeLock({ pid, startedAt: now.toISOString() });
314
+ const plural = targets.length === 1 ? "" : "s";
315
+ const tail = deferred > 0 ? ` (${deferred} more will follow on the next run)` : "";
316
+ options.notify?.(`summarizing ${targets.length} session${plural} in the background${tail} — marked \u25d0 below`);
317
+ return { status: "spawned", targets: targets.length, targetIds: targets.map((r) => r.sessionId) };
318
+ }
319
+ /**
320
+ * Re-run this same CLI, detached, on the hidden worker command.
321
+ *
322
+ * `detached` + `stdio: "ignore"` + `unref()` is the combination that lets the
323
+ * parent exit at once: detached gives the child its own process group so it
324
+ * survives the shell reaping the foreground job, ignoring stdio stops it holding
325
+ * the terminal (or a pipe) open, and unref drops it from the parent's event loop.
326
+ *
327
+ * `execArgv` is forwarded so this works under `tsx` (`npm run dev`) as well as
328
+ * from the built `dist/cli/main.js`.
329
+ */
330
+ function spawnWorker() {
331
+ const entry = process.argv[1];
332
+ if (!entry)
333
+ return undefined;
334
+ const child = spawn(process.execPath, [...process.execArgv, entry, AUTO_SUMMARIZE_COMMAND], {
335
+ detached: true,
336
+ stdio: "ignore",
337
+ // Belt and braces: the worker must never auto-summarize, or it would spawn a
338
+ // grandchild for every summary it writes.
339
+ env: { ...process.env, GMUX_AUTO_SUMMARIZE: "0" },
340
+ });
341
+ child.unref();
342
+ return child.pid;
343
+ }
344
+ /**
345
+ * The worker body. Runs in the detached child, holding the lock its parent took.
346
+ *
347
+ * Re-selects its own targets rather than trusting a list handed down an argv: by
348
+ * the time it starts, the parent's view of the store is already stale.
349
+ */
350
+ export async function runAutoSummarize(provider) {
351
+ try {
352
+ // Work the queue its parent published, so `gmux ls -n 50` really does get all
353
+ // fifty — not just whatever the default window happens to be.
354
+ const queue = await readQueue();
355
+ const wanted = new Set(queue?.ids ?? []);
356
+ // Look up the queued ids across the WHOLE store, with no limit.
357
+ //
358
+ // Do not "load the N most recent and filter" — the queue holds human
359
+ // sessions, while the most recent N of everything is mostly subagent
360
+ // transcripts, so the filter matches nothing and the pass silently writes
361
+ // zero summaries. (It did exactly that, once.)
362
+ const records = await loadRecords(wanted.size > 0 ? { includeSidechains: true, includeAutomated: true } : { limit: AUTO_SUMMARIZE_LIMIT });
363
+ const targets = wanted.size > 0
364
+ ? // Re-filter through the guard: a queued id can never smuggle an
365
+ // automated session past the feedback-loop check.
366
+ autoSummarizeCandidates(records.filter((r) => wanted.has(r.sessionId)), wanted.size)
367
+ : await selectAutoSummarizeTargets(records);
368
+ if (targets.length === 0)
369
+ return { generated: 0, skipped: 0, failed: [] };
370
+ const result = await summarizeBatch(targets, provider);
371
+ if (result.failed.length > 0)
372
+ await logFailures(result);
373
+ return result;
374
+ }
375
+ catch (error) {
376
+ await logLine(`pass failed: ${error.message}`);
377
+ return { generated: 0, skipped: 0, failed: [] };
378
+ }
379
+ finally {
380
+ await clearQueue();
381
+ await releaseLock();
382
+ }
383
+ }
384
+ /**
385
+ * Leave evidence when a background pass fails.
386
+ *
387
+ * The worker's stdio is `ignore`d, so without this a broken provider is utterly
388
+ * silent: summaries just never appear and there is nothing to look at. `gmux doctor`
389
+ * surfaces the last line of this file.
390
+ */
391
+ async function logLine(message) {
392
+ try {
393
+ await mkdir(cacheDir(), { recursive: true });
394
+ await writeFile(logPath(), `${new Date().toISOString()} ${message}\n`, { flag: "a" });
395
+ }
396
+ catch {
397
+ // Logging must never be the thing that breaks the worker.
398
+ }
399
+ }
400
+ async function logFailures(result) {
401
+ for (const failure of result.failed.slice(0, 5)) {
402
+ await logLine(`${failure.sessionId.slice(0, 8)}: ${failure.reason}`);
403
+ }
404
+ }
405
+ /** The most recent background failure, if any — surfaced by `gmux doctor`. */
406
+ export async function lastAutoSummarizeError() {
407
+ try {
408
+ const lines = (await readFile(logPath(), "utf8")).trim().split("\n").filter(Boolean);
409
+ return lines.at(-1) ?? null;
410
+ }
411
+ catch {
412
+ return null;
413
+ }
414
+ }
415
+ //# sourceMappingURL=auto-summarize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-summarize.js","sourceRoot":"","sources":["../../src/services/auto-summarize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** Bounded-parallelism map. Keeps a cold index build from opening 1,100 files at once. */
2
+ export declare function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
@@ -0,0 +1,16 @@
1
+ /** Bounded-parallelism map. Keeps a cold index build from opening 1,100 files at once. */
2
+ export async function mapLimit(items, limit, fn) {
3
+ const results = new Array(items.length);
4
+ let cursor = 0;
5
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
6
+ for (;;) {
7
+ const index = cursor++;
8
+ if (index >= items.length)
9
+ return;
10
+ results[index] = await fn(items[index], index);
11
+ }
12
+ });
13
+ await Promise.all(workers);
14
+ return results;
15
+ }
16
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/services/concurrency.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,KAAmB,EACnB,KAAa,EACb,EAA0C;IAE1C,MAAM,OAAO,GAAG,IAAI,KAAK,CAAI,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QAC/E,SAAS,CAAC;YACR,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;YACvB,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM;gBAAE,OAAO;YAClC,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,KAAK,CAAE,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Config: the one thing gmux remembers because a human said so.
3
+ *
4
+ * Everything else in `~/.cache/gmux` is derived and disposable. This
5
+ * isn't, which is why it lives under the config dir instead — see core/paths.ts.
6
+ *
7
+ * Two rules run through this file:
8
+ *
9
+ * 1. **A bad config is not an error.** Unreadable, malformed, or written by a
10
+ * future version — all are treated as *absent*. `gmux ls` must not die because
11
+ * a JSON file got truncated; it should fall back to the behavior of someone
12
+ * who never ran `gmux setup`, which is a behavior that works.
13
+ * 2. **The env var wins.** `GMUX_SUMMARY_CMD` sat on top before config
14
+ * existed, and every script, test and CI job that sets it must keep working
15
+ * without being told about a new file.
16
+ */
17
+ import { type GmuxConfig } from "../core/gmux-types.js";
18
+ import { type GmConfig } from "../core/types.js";
19
+ import { type ProviderSpec } from "./providers.js";
20
+ /** The last-resort provider, unchanged from before config existed. */
21
+ export declare const FALLBACK_COMMAND: readonly string[];
22
+ /** The env var that outranks everything here. */
23
+ export declare const SUMMARY_CMD_ENV = "GMUX_SUMMARY_CMD";
24
+ /**
25
+ * Set on any `gmux` we spawn ourselves.
26
+ *
27
+ * THE RECURSION GUARD. `gmux ask` runs a provider that may run `gmux grep`, and
28
+ * `gmux grep` would otherwise fire the postAction hook and spawn a summarizer —
29
+ * from inside a process that a summarizer-adjacent call started. The lock would
30
+ * absorb most of it; this stops it being a question in the first place.
31
+ */
32
+ export declare const CHILD_ENV = "GMUX_CHILD";
33
+ export declare function isChildProcess(env?: NodeJS.ProcessEnv): boolean;
34
+ /** The env a spawned provider gets: ours, plus the marker. */
35
+ export declare function childEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
36
+ export declare function defaultConfig(): GmConfig;
37
+ /** Parse a config blob. Returns null for anything we cannot trust. */
38
+ export declare function parseConfig(raw: string): GmConfig | null;
39
+ /**
40
+ * Fill in whatever a stored `gmux` block leaves out, from `DEFAULT_GMUX_CONFIG`.
41
+ *
42
+ * Never fails on a missing or partial block — older configs have none at all,
43
+ * and `parseGmuxBlock` above only ever keeps well-typed fields, so what
44
+ * reaches here is trustworthy as far as it goes.
45
+ */
46
+ export declare function resolveGmuxConfig(config: GmConfig | null): GmuxConfig;
47
+ /** The config on disk, or null when there isn't a usable one. */
48
+ export declare function readConfig(): Promise<GmConfig | null>;
49
+ /**
50
+ * True when a config file exists, however broken.
51
+ *
52
+ * Distinct from `readConfig() !== null` on purpose: the first-run wizard keys
53
+ * off *existence*, so a corrupt file doesn't re-launch the wizard on every
54
+ * single run. It falls back to defaults quietly instead, and `gmux doctor` says so.
55
+ */
56
+ export declare function configExists(): Promise<boolean>;
57
+ /** Write config through a temp file and a rename, so a kill can't truncate it. */
58
+ export declare function writeConfig(config: GmConfig): Promise<void>;
59
+ /**
60
+ * Split a command string into argv, honoring quotes and backslash escapes.
61
+ *
62
+ * Splitting on whitespace alone was wrong in a way that fails quietly: a
63
+ * perfectly reasonable `llm -m "model name"` became four arguments with literal
64
+ * quote characters embedded — `["llm","-m","\"model","name\""]` — and gmux then
65
+ * spawned a command the user never typed. The wizard invites free-form input, so
66
+ * "don't type spaces in your arguments" is not a rule we get to have.
67
+ *
68
+ * This is not a shell. It handles the quoting a person actually uses in an
69
+ * answer to a prompt — single quotes, double quotes, backslash escapes — and
70
+ * deliberately does NOT do expansion, substitution, globbing or operators. The
71
+ * argv goes to `spawn` without a shell, so there is nothing for those to mean,
72
+ * and pretending otherwise would invite an injection surface where none exists.
73
+ *
74
+ * An unterminated quote closes at end of input rather than throwing: the user is
75
+ * standing at a prompt, and taking their obvious intent beats an error message
76
+ * about lexing.
77
+ */
78
+ export declare function parseCommand(input: string): string[];
79
+ /**
80
+ * The command to use for summaries, or null for "make no model calls".
81
+ *
82
+ * Pure, and takes `detected` rather than probing PATH itself, so the precedence
83
+ * rule can be tested without a machine that happens to have Claude installed.
84
+ *
85
+ * Highest wins:
86
+ * 1. GMUX_SUMMARY_CMD
87
+ * 2. config.provider.command
88
+ * 3. first detected provider
89
+ * 4. `claude -p`
90
+ *
91
+ * Rules 3 and 4 are the pre-config behavior, preserved exactly: someone with no
92
+ * config file sees no change at all.
93
+ */
94
+ export declare function resolveSummaryCommand(config: GmConfig | null, env?: NodeJS.ProcessEnv, detected?: ProviderSpec | null): string[] | null;
95
+ /**
96
+ * The command to use for `gmux ask`, or null for "make no model calls".
97
+ *
98
+ * Same precedence, but the catalog's `askArgv` is used where we recognise the
99
+ * provider — that's the variant permitted to run `gmux grep`.
100
+ */
101
+ export declare function resolveAskCommand(config: GmConfig | null, env?: NodeJS.ProcessEnv, detected?: ProviderSpec | null): string[] | null;
102
+ /**
103
+ * Whether background summaries are allowed.
104
+ *
105
+ * `GMUX_AUTO_SUMMARIZE=0` is checked by the caller and still wins. This
106
+ * adds the config answer: a user who said "no" in the wizard has said it once
107
+ * and for good, and must not be asked again by a token spend they declined.
108
+ *
109
+ * No config means yes — the pre-config default.
110
+ */
111
+ export declare function autoSummarizeAllowed(config: GmConfig | null): boolean;
112
+ /**
113
+ * Whether a bare `gmux` should drop into the setup wizard.
114
+ *
115
+ * Pure, and takes the world as data, because the interesting cases are exactly
116
+ * the ones you cannot reproduce in a test terminal. Same reason `fzfArgs` is
117
+ * split from the spawn.
118
+ *
119
+ * The TTY and `--json` gates are not politeness — they are non-negotiable #4.
120
+ * `gmux ls --json` is an interface agents call; a version of it that can block on
121
+ * a human prompt is a broken interface, whatever it prints.
122
+ */
123
+ export interface SetupGate {
124
+ hasConfig: boolean;
125
+ /** Both stdin and stdout. A wizard needs to ask AND to be seen. */
126
+ isTty: boolean;
127
+ isJson: boolean;
128
+ /** The command commander resolved, e.g. "pick", "ls", "__picker-rows". */
129
+ commandName: string;
130
+ }
131
+ export declare function shouldRunSetupWizard(gate: SetupGate): boolean;