@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,544 @@
1
+ /**
2
+ * The ask thread, on disk.
3
+ *
4
+ * `gmux ask`'s REPL keeps its turns in a closure and replays them, because the
5
+ * providers we call are one-shot. The picker's chat cannot: every question is
6
+ * answered by a fresh detached worker that exits when it is done, so there is no
7
+ * closure left to hold anything. **The transcript file IS the turn array**, and
8
+ * `foldCompletedTurns` is what turns it back into one — `buildAskPrompt` never
9
+ * learns the difference.
10
+ *
11
+ * An append-only JSONL *event* log, not a document, and three things force that:
12
+ *
13
+ * 1. **A question must land before its answer exists.** The echo of what you
14
+ * typed appears the instant you press enter, ~9–20s before there is an answer
15
+ * to pair it with. A turn record cannot be written until both halves exist.
16
+ * 2. **Two writers.** `__ask-send` appends `meta`/`question`, the worker appends
17
+ * `chunk`/`end`. `O_APPEND` makes the offset update atomic, so ordering holds
18
+ * for whole records — and for nothing else.
19
+ * 3. **The reader is unsynchronized.** The preview re-runs on every cursor move
20
+ * and takes no lock, so it must be able to parse a file that is being
21
+ * appended to right now.
22
+ *
23
+ * Which is why **gmux must never truncate-and-rewrite a transcript**: a reader
24
+ * would catch the file mid-rewrite and the pane would flash empty, or show the
25
+ * previous answer. Appends are whole-line and ordered, so the only line a reader
26
+ * can ever see torn is the last one — `parseTranscript` drops it and the next
27
+ * refresh gets it. A dropped tail costs one repaint; a lock on the read path
28
+ * would cost the whole format.
29
+ */
30
+ import { spawn } from "node:child_process";
31
+ import { randomBytes } from "node:crypto";
32
+ import { closeSync, mkdirSync, openSync, writeSync } from "node:fs";
33
+ import { open, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
34
+ import { basename, dirname } from "node:path";
35
+ import { AskProviderError } from "../core/errors.js";
36
+ import { askTranscriptDir, askTranscriptPath } from "../core/paths.js";
37
+ import { ASK_TIMEOUT_MS } from "./ask.js";
38
+ import { isLockStale, LOCK_STALE_MS } from "./auto-summarize.js";
39
+ import { childEnv } from "./config.js";
40
+ import { runProviderCommand } from "./provider-process.js";
41
+ /**
42
+ * How many prior turns a worker replays into the prompt.
43
+ *
44
+ * Every turn re-sends every earlier answer, so tokens grow quadratically and the
45
+ * 300s timeout starts to bite exactly when the thread gets useful. A bare slice
46
+ * of the most recent turns is the bound.
47
+ *
48
+ * Bounded at the fold, deliberately: `buildAskPrompt` is shared with bare
49
+ * `gmux ask`, which this design does not change.
50
+ */
51
+ export const ASK_MAX_REPLAY_TURNS = 8;
52
+ /**
53
+ * The in-flight heartbeat. The only timer in the design.
54
+ *
55
+ * The provider buffers — a 40-line request came back as one 111-byte write at
56
+ * +3.32s — so nothing else fires for the whole think, and a pane that is silent
57
+ * and static for 20s is indistinguishable from a wedged one. This is what makes
58
+ * `thinking… 14s` tick. It exists only while a request is in flight; that is the
59
+ * whole of the exception to "no idle polling".
60
+ */
61
+ export const ASK_HEARTBEAT_MS = 1_000;
62
+ /**
63
+ * Trailing-edge throttle on chunk-driven refreshes.
64
+ *
65
+ * Against `claude -p` this fires once, with the answer. It is throttled anyway
66
+ * because `onChunk` is a tee: a provider that trickles would otherwise spawn one
67
+ * preview process per chunk. fzf does not save us — measured on 0.74, 100
68
+ * back-to-back `refresh-preview` POSTs ran a fast preview command 100 times. It
69
+ * bounds concurrency, not spawn rate.
70
+ */
71
+ export const REFRESH_INTERVAL_MS = 150;
72
+ /** How long a cancelled worker's group gets to die politely before SIGKILL. */
73
+ export const ASK_KILL_GRACE_MS = 250;
74
+ /** `<pid>-<rand8>`: the pid reaps, the random half keeps two pickers apart. */
75
+ export function newAskRunId(pid = process.pid) {
76
+ return `${pid}-${randomBytes(4).toString("hex")}`;
77
+ }
78
+ /** The transcript for a fresh run. Allocates a path and creates nothing. */
79
+ export function newAskTranscriptPath() {
80
+ return askTranscriptPath(newAskRunId());
81
+ }
82
+ /** The run id a transcript path carries. Derived, so it is never stored twice. */
83
+ export function askRunIdOf(transcript) {
84
+ return basename(transcript, ".jsonl");
85
+ }
86
+ /**
87
+ * Open a transcript for appending, creating it and its directory if needed.
88
+ *
89
+ * The fd is the caller's to close, and it is held for the life of the turn:
90
+ * `openSync`/`writeSync` rather than a stream because `createWriteStream` can
91
+ * defer its flush past the refresh POST, and with one paint per answer that is
92
+ * not a frame of lag — it is the whole answer missing until something else
93
+ * happens to repaint. **The bytes are on disk before the POST goes out.**
94
+ */
95
+ export function openAskTranscript(transcript) {
96
+ mkdirSync(dirname(transcript), { recursive: true });
97
+ return openSync(transcript, "a");
98
+ }
99
+ /** One record, one whole-line append. Synchronous — see `openAskTranscript`. */
100
+ export function appendAskEvent(fd, event) {
101
+ writeSync(fd, `${JSON.stringify(event)}\n`);
102
+ }
103
+ function isAskEvent(value) {
104
+ if (!value || typeof value !== "object")
105
+ return false;
106
+ const t = value.t;
107
+ return t === "meta" || t === "question" || t === "chunk" || t === "end" || t === "aborted" || t === "error";
108
+ }
109
+ /**
110
+ * Parse a transcript's text.
111
+ *
112
+ * Takes no lock and cannot fail. A half-written last line is the normal case,
113
+ * not the error case — the reader is a preview re-running on a keystroke while
114
+ * a worker appends a multi-KB answer. Whole-line appends mean only the last line
115
+ * can ever be torn, so everything before it is still true.
116
+ */
117
+ export function parseTranscript(text) {
118
+ const lines = text.split("\n");
119
+ const events = [];
120
+ let torn = false;
121
+ for (const [index, line] of lines.entries()) {
122
+ if (line.trim() === "")
123
+ continue;
124
+ let parsed;
125
+ try {
126
+ parsed = JSON.parse(line);
127
+ }
128
+ catch {
129
+ // Only the tail can be torn. A broken line anywhere else is a file we
130
+ // don't understand, and skipping it beats guessing at it.
131
+ if (index === lines.length - 1)
132
+ torn = true;
133
+ continue;
134
+ }
135
+ if (isAskEvent(parsed))
136
+ events.push(parsed);
137
+ }
138
+ return { events, torn };
139
+ }
140
+ /**
141
+ * Read a transcript. A missing file is the empty state, not an error — it is
142
+ * how "no conversation yet" costs no flag and no branch.
143
+ */
144
+ export async function readAskTranscript(transcript) {
145
+ try {
146
+ return parseTranscript(await readFile(transcript, "utf8"));
147
+ }
148
+ catch {
149
+ return { events: [], torn: false };
150
+ }
151
+ }
152
+ /** The seq the next question takes. Questions are the only thing that allocates one. */
153
+ export function nextSeq(events) {
154
+ let max = 0;
155
+ for (const event of events) {
156
+ if (event.t === "question" && event.seq > max)
157
+ max = event.seq;
158
+ }
159
+ return max + 1;
160
+ }
161
+ /**
162
+ * The turn being answered right now, if any — a question with no `end`,
163
+ * `aborted` or `error` against it. What `__ask-cancel` aborts, and what the pane
164
+ * renders as `thinking… Ns`.
165
+ */
166
+ export function inFlightSeq(events) {
167
+ const settled = new Set();
168
+ let latest = null;
169
+ for (const event of events) {
170
+ if (event.t === "question")
171
+ latest = latest === null ? event.seq : Math.max(latest, event.seq);
172
+ if (event.t === "end" || event.t === "aborted" || event.t === "error")
173
+ settled.add(event.seq);
174
+ }
175
+ return latest !== null && !settled.has(latest) ? latest : null;
176
+ }
177
+ /**
178
+ * The conversation so far, as `buildAskPrompt` wants it.
179
+ *
180
+ * Two rules carry the weight:
181
+ *
182
+ * - **Keyed by `seq`, never file order.** Chunks are contiguous in practice
183
+ * because the lock guarantees one writer, but folding by position would make
184
+ * this function's correctness depend on a lock in another module.
185
+ * - **A turn enters history only with an `end` record.** In-flight, aborted and
186
+ * errored turns are excluded, so an aborted question is dropped from the
187
+ * model's view entirely (the human still sees it in the pane). A half-written
188
+ * answer promoted to history is shown to the model as its own completed
189
+ * statement of fact, and it will build on a sentence that stops mid-clause.
190
+ * "The answer so far" and "the conversation so far" are different things.
191
+ */
192
+ export function foldCompletedTurns(events, options = {}) {
193
+ const questions = new Map();
194
+ const chunks = new Map();
195
+ const ended = new Set();
196
+ for (const event of events) {
197
+ switch (event.t) {
198
+ case "question":
199
+ questions.set(event.seq, event.text);
200
+ break;
201
+ case "chunk": {
202
+ const existing = chunks.get(event.seq);
203
+ if (existing)
204
+ existing.push(event.text);
205
+ else
206
+ chunks.set(event.seq, [event.text]);
207
+ break;
208
+ }
209
+ case "end":
210
+ ended.add(event.seq);
211
+ break;
212
+ default:
213
+ break;
214
+ }
215
+ }
216
+ const turns = [];
217
+ for (const seq of [...ended].sort((a, b) => a - b)) {
218
+ const question = questions.get(seq);
219
+ const answer = (chunks.get(seq) ?? []).join("").trim();
220
+ if (question === undefined || answer === "")
221
+ continue;
222
+ turns.push({ question, answer });
223
+ }
224
+ const maxTurns = options.maxTurns ?? ASK_MAX_REPLAY_TURNS;
225
+ return maxTurns >= 0 && turns.length > maxTurns ? turns.slice(-maxTurns) : turns;
226
+ }
227
+ /* ---------------------------------------------------------------- the lock */
228
+ /**
229
+ * `transform` returns immediately, so nothing stops you pressing enter twice.
230
+ *
231
+ * Two workers interleaving chunks of different seqs is the one thing that makes
232
+ * the fold ambiguous. With the lock the second enter appends nothing and
233
+ * refreshes a pane that already says "still answering". A key that visibly does
234
+ * nothing for a stated reason is fine; a key that silently corrupts the thread
235
+ * is not.
236
+ */
237
+ export function askLockPath(transcript) {
238
+ return `${transcript}.lock`;
239
+ }
240
+ /**
241
+ * Where ctrl-o parks the browse query while ask mode owns fzf's query line.
242
+ *
243
+ * A sibling of the transcript rather than a state directory: it inherits the
244
+ * `<pid>-<rand8>` uniqueness for free, so two concurrent pickers cannot collide,
245
+ * and it falls into the cleanup and sweep paths that already exist with no new
246
+ * concept to name.
247
+ *
248
+ * The picker spells this suffix a second time, because it may not import a
249
+ * service and the path is baked into a shell binding. A drift between the two is
250
+ * SILENT — esc restores an empty query and the browse filter is gone — so
251
+ * tests/picker.test.ts pins them equal.
252
+ */
253
+ export function askBrowseQueryPath(transcript) {
254
+ return `${transcript}.browseq`;
255
+ }
256
+ export async function readAskLock(transcript) {
257
+ try {
258
+ const parsed = JSON.parse(await readFile(askLockPath(transcript), "utf8"));
259
+ if (typeof parsed?.startedAt !== "string")
260
+ return null;
261
+ return { pid: Number(parsed.pid) || 0, startedAt: parsed.startedAt };
262
+ }
263
+ catch {
264
+ return null; // No lock, or an unreadable one: treat as not held.
265
+ }
266
+ }
267
+ async function createAskLockExclusive(transcript, lock) {
268
+ let handle;
269
+ try {
270
+ handle = await open(askLockPath(transcript), "wx");
271
+ }
272
+ catch {
273
+ return false; // EEXIST: the other enter got there first.
274
+ }
275
+ try {
276
+ await handle.writeFile(JSON.stringify(lock), "utf8");
277
+ return true;
278
+ }
279
+ finally {
280
+ await handle.close();
281
+ }
282
+ }
283
+ /**
284
+ * Take the turn lock, or report that a turn is already in flight.
285
+ *
286
+ * `pid: 0` until the worker exists — the same acquired-but-not-yet-spawned shape
287
+ * `auto-summarize` uses, and the reason `isLockStale` ANDs liveness with age
288
+ * rather than trusting a pid on its own. That AND is also the pid-reuse guard:
289
+ * a recycled pid can only look alive, never young.
290
+ */
291
+ export async function acquireAskLock(transcript, now = new Date()) {
292
+ mkdirSync(dirname(transcript), { recursive: true });
293
+ const lock = { pid: 0, startedAt: now.toISOString() };
294
+ if (await createAskLockExclusive(transcript, lock))
295
+ return true;
296
+ const existing = await readAskLock(transcript);
297
+ if (existing && !isLockStale(existing, now))
298
+ return false;
299
+ await rm(askLockPath(transcript), { force: true });
300
+ return createAskLockExclusive(transcript, lock);
301
+ }
302
+ /** Record the worker that now owns the turn. `__ask-cancel` kills this pid's group. */
303
+ export async function writeAskLockPid(transcript, pid, now = new Date()) {
304
+ await writeFile(askLockPath(transcript), JSON.stringify({ pid, startedAt: now.toISOString() }), "utf8");
305
+ }
306
+ export async function releaseAskLock(transcript) {
307
+ await rm(askLockPath(transcript), { force: true });
308
+ }
309
+ /* --------------------------------------------------------------- the sweep */
310
+ function processAlive(pid) {
311
+ if (pid <= 0)
312
+ return false;
313
+ try {
314
+ process.kill(pid, 0);
315
+ return true;
316
+ }
317
+ catch (error) {
318
+ // EPERM means the process exists but belongs to someone else — still alive.
319
+ return error.code === "EPERM";
320
+ }
321
+ }
322
+ /**
323
+ * Every run with a file in the directory, however far its picker got.
324
+ *
325
+ * Keyed on the RUN, not on the transcript: `ctrl-o` writes `.browseq` before a
326
+ * question exists, so a picker killed between `ctrl-o` and `enter` leaves a
327
+ * `.browseq` with no `.jsonl` beside it. Keying the sweep on `.jsonl` — the file
328
+ * that happens to be reaped first — made that one unreapable forever, and a
329
+ * cache that only ever grows is the bug the sweep exists to prevent.
330
+ */
331
+ function askRunIds(entries) {
332
+ const ids = new Set();
333
+ for (const entry of entries) {
334
+ const base = entry.replace(/\.(browseq|lock)$/, "");
335
+ if (base.endsWith(".jsonl"))
336
+ ids.add(basename(base, ".jsonl"));
337
+ }
338
+ return [...ids];
339
+ }
340
+ /** The newest mtime among a run's files, or null when none of them exist. */
341
+ async function newestMtime(paths) {
342
+ let newest = null;
343
+ for (const path of paths) {
344
+ try {
345
+ const at = (await stat(path)).mtime.getTime();
346
+ if (newest === null || at > newest)
347
+ newest = at;
348
+ }
349
+ catch {
350
+ // Not every member of a run exists — that is the normal case, not an error.
351
+ }
352
+ }
353
+ return newest;
354
+ }
355
+ /**
356
+ * Reap the files left behind by pickers that died without cleaning up.
357
+ *
358
+ * Normal exit is `pick.ts`'s `onClose`; this is the `kill -9` path. **A run
359
+ * whose owning pid is alive is never removed** — that is another picker's live
360
+ * thread, and deleting it would empty a pane someone is reading.
361
+ *
362
+ * The spec also says "or the mtime is older than LOCK_STALE_MS", on the grounds
363
+ * that a live worker's file cannot be 10 minutes idle. That reasoning does not
364
+ * survive the filename: the pid is the *picker's*, not the worker's, and a
365
+ * picker left open for an hour after one question is entirely ordinary. So age
366
+ * alone reaps only a run we cannot attribute to a pid at all.
367
+ *
368
+ * Never throws, and never able to fail the picker: an orphan is a few KB of
369
+ * disposable cache, so the cost of not reaping is nil and the cost of a sweep
370
+ * that throws is a picker that will not open.
371
+ */
372
+ export async function sweepAskTranscripts(now = new Date(), staleMs = LOCK_STALE_MS) {
373
+ const removed = [];
374
+ let entries;
375
+ try {
376
+ entries = await readdir(askTranscriptDir());
377
+ }
378
+ catch {
379
+ return removed; // No directory: nobody has ever asked. Nothing to sweep.
380
+ }
381
+ for (const runId of askRunIds(entries)) {
382
+ const transcript = askTranscriptPath(runId);
383
+ const members = [transcript, askLockPath(transcript), askBrowseQueryPath(transcript)];
384
+ try {
385
+ const pid = Number.parseInt(runId.split("-")[0] ?? "", 10);
386
+ if (Number.isFinite(pid) && processAlive(pid))
387
+ continue;
388
+ if (!Number.isFinite(pid)) {
389
+ const mtime = await newestMtime(members);
390
+ if (mtime === null || now.getTime() - mtime <= staleMs)
391
+ continue;
392
+ }
393
+ for (const member of members)
394
+ await rm(member, { force: true });
395
+ removed.push(transcript);
396
+ }
397
+ catch {
398
+ // One unreadable entry must not cost the others their sweep.
399
+ }
400
+ }
401
+ return removed;
402
+ }
403
+ /* --------------------------------------------------------- the worker spawn */
404
+ /**
405
+ * How `__ask-send` forks the worker. Pure, so the decision is testable without
406
+ * spawning anything — the same seam `maybeAutoSummarize`'s `spawnWorker` has.
407
+ *
408
+ * `detached` is not a nicety twice over: it lets the sender exit at once, and it
409
+ * makes the worker a process-group leader, which is the only thing that lets
410
+ * `__ask-cancel` reap the provider underneath it. `spawn` does not kill its
411
+ * child on POSIX, so a worker that merely exits leaves `claude -p` running and
412
+ * billing. (`setsid` does not exist on macOS — gmux's own platform — so Node's
413
+ * `detached: true` is the portable equivalent; it calls `setsid(2)`.)
414
+ *
415
+ * `childEnv()`, not `process.env`: the worker is a `gmux` that is about to make a
416
+ * model call, which is the definition of a child — its provider may shell back
417
+ * into `gmux grep`, and that nested `gmux` must not start a summarize pass. The fzf
418
+ * api key rides here by inheritance and nowhere else; it must never reach an
419
+ * argv, where `ps` would hand it to any user on the box.
420
+ */
421
+ export function askChildSpawnOptions(env = process.env) {
422
+ return {
423
+ detached: true,
424
+ stdio: "ignore",
425
+ // Belt and braces on top of GMUX_CHILD: the worker must never
426
+ // summarize, or every answer would fork a summarize pass from inside fzf.
427
+ env: { ...childEnv(env), GMUX_AUTO_SUMMARIZE: "0" },
428
+ };
429
+ }
430
+ /**
431
+ * Re-enter this same CLI, detached, on a hidden command.
432
+ *
433
+ * `execArgv` is forwarded for the reason picker.ts already documents once: under
434
+ * `npm run dev` the entry is `src/cli/main.ts` and `execArgv` carries tsx's
435
+ * loader flags. Drop them and the command is `node src/cli/main.ts`, which Node
436
+ * 20 cannot run — so in development every answer silently never arrives, with
437
+ * `stdio: "ignore"` swallowing the evidence.
438
+ */
439
+ export function spawnAskWorker(args) {
440
+ const entry = process.argv[1];
441
+ if (!entry)
442
+ return undefined;
443
+ const child = spawn(process.execPath, [...process.execArgv, entry, ...args], askChildSpawnOptions());
444
+ child.unref();
445
+ return child.pid;
446
+ }
447
+ /** The throttle policy, as a function of state, so it is testable without a socket. */
448
+ export function shouldRefresh(state, now) {
449
+ if (state.final)
450
+ return true;
451
+ if (!state.pending)
452
+ return false;
453
+ if (state.lastAt === null)
454
+ return true;
455
+ return now - state.lastAt >= REFRESH_INTERVAL_MS;
456
+ }
457
+ /**
458
+ * Run one turn: call the provider, append what it says, tell fzf to repaint.
459
+ *
460
+ * **Write, then notify.** Notify-then-write renders the previous chunk, forever
461
+ * one behind — and with a provider that buffers, "one behind" means an empty
462
+ * pane until something else repaints it.
463
+ *
464
+ * **No `env` is passed to `runProviderCommand`, and that is load-bearing.** It
465
+ * defaults to `childEnv()`, which is what stops the provider's `gmux grep` from
466
+ * starting a summarize pass. A well-meant `env: { ...process.env, FZF_PORT }`
467
+ * here would silently drop `GMUX_CHILD` and reopen the loop — which is why
468
+ * the port travels on the worker's argv instead, where the trap cannot be set.
469
+ *
470
+ * **The canceller owns the `aborted` record, not us.** On abort we write nothing
471
+ * and return: a wedged worker must not be able to leave the pane stuck on
472
+ * "answering", and only the process that knows the kill happened can be sure.
473
+ */
474
+ export async function streamAnswer(options) {
475
+ const { transcriptFd: fd, seq, notify, signal } = options;
476
+ const state = { lastAt: null, pending: false, final: false };
477
+ let trailing = null;
478
+ const flush = () => {
479
+ state.pending = false;
480
+ state.lastAt = Date.now();
481
+ notify();
482
+ };
483
+ const onChunk = (text) => {
484
+ appendAskEvent(fd, { t: "chunk", seq, text });
485
+ state.pending = true;
486
+ if (shouldRefresh(state, Date.now())) {
487
+ flush();
488
+ return;
489
+ }
490
+ // Trailing edge: the last chunk of a burst must still land, and the final
491
+ // refresh below covers the case where the burst ends the turn.
492
+ if (!trailing) {
493
+ trailing = setTimeout(() => {
494
+ trailing = null;
495
+ if (state.pending)
496
+ flush();
497
+ }, REFRESH_INTERVAL_MS);
498
+ }
499
+ };
500
+ try {
501
+ const output = await runProviderCommand(options.argv, options.prompt, {
502
+ timeoutMs: options.timeoutMs ?? ASK_TIMEOUT_MS,
503
+ onChunk,
504
+ signal,
505
+ });
506
+ if (signal.aborted)
507
+ return;
508
+ // The resolved string is every byte `onChunk` was handed, in order, so the
509
+ // chunks on disk are already the whole answer — this only judges it.
510
+ if (output.trim() === "")
511
+ throw new Error("reply was empty");
512
+ appendAskEvent(fd, { t: "end", seq, at: new Date().toISOString() });
513
+ }
514
+ catch (error) {
515
+ if (signal.aborted)
516
+ return;
517
+ // fzf owns the terminal, so this error cannot be printed at anyone. It goes
518
+ // where the answer would have gone, carrying its fix — non-negotiable #5
519
+ // does not stop applying because the surface is a pane.
520
+ const failed = new AskProviderError(options.argv.join(" "), error.message);
521
+ appendAskEvent(fd, {
522
+ t: "error",
523
+ seq,
524
+ at: new Date().toISOString(),
525
+ message: failed.fix ? `${failed.message}\n${failed.fix}` : failed.message,
526
+ });
527
+ }
528
+ finally {
529
+ if (trailing)
530
+ clearTimeout(trailing);
531
+ if (!signal.aborted) {
532
+ // One final unthrottled refresh, so the answer cannot be stranded behind
533
+ // the throttle or the cancelled heartbeat.
534
+ state.final = true;
535
+ if (shouldRefresh(state, Date.now()))
536
+ flush();
537
+ }
538
+ }
539
+ }
540
+ /** Close a transcript fd. Always from a `finally` — the fd lives for the turn. */
541
+ export function closeAskTranscript(fd) {
542
+ closeSync(fd);
543
+ }
544
+ //# sourceMappingURL=ask-transcript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-transcript.js","sourceRoot":"","sources":["../../src/services/ask-transcript.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Ask: questions about the sessions you already have, answered from the
3
+ * summaries you already paid for.
4
+ *
5
+ * The design decision worth knowing before you change anything here:
6
+ *
7
+ * **The tool loop is the harness's, not ours.** The prompt tells the model it
8
+ * may run `gmux grep '<query>' --json`, and the provider is invoked with exactly
9
+ * that permission (see providers.ts `askArgv`). We parse no tool calls and speak
10
+ * no vendor protocol — the abstraction stays "a CLI that reads a prompt and
11
+ * writes text", which is the only reason gmux depends on no vendor SDK.
12
+ *
13
+ * The cost is that we don't meter how many greps the model runs. Accepted: grep
14
+ * is read-only and cheap, and the provider's own turn limit bounds it.
15
+ *
16
+ * The context block is bounded the way `distill()` is bounded — a few KB
17
+ * regardless of how many sessions you have — because a REPL re-sends it every
18
+ * turn.
19
+ */
20
+ import type { AskContext, AskProvider, AskTurn, SessionView } from "../core/types.js";
21
+ /**
22
+ * Ask gets longer than a summary does: the model may run several greps before
23
+ * it answers, and each is a subprocess of its own.
24
+ */
25
+ export declare const ASK_TIMEOUT_MS = 300000;
26
+ /** How many sessions go into the block. Matches the picker's default window. */
27
+ export declare const ASK_SESSION_LIMIT = 20;
28
+ export declare function buildAskContext(views: readonly SessionView[], focusId?: string | null, limit?: number): AskContext;
29
+ /** Short id: what a human types and what the model should quote back. */
30
+ export declare function shortId(sessionId: string): string;
31
+ /**
32
+ * The prompt for one turn.
33
+ *
34
+ * `turns` is the conversation so far. We re-send it because `claude -p` is
35
+ * one-shot and `--resume` is Claude-specific — carrying state in memory and
36
+ * replaying it is what keeps this working for any provider. Bounded, because
37
+ * the context block is.
38
+ */
39
+ export declare function buildAskPrompt(context: AskContext, turns: readonly AskTurn[], question: string, now?: Date): string;
40
+ export declare class CliAskProvider implements AskProvider {
41
+ readonly name: string;
42
+ private readonly argv;
43
+ constructor(argv: string[]);
44
+ isAvailable(): Promise<boolean>;
45
+ ask(prompt: string): Promise<string>;
46
+ }
47
+ /**
48
+ * The ask provider for the current config, or null when the user has said no to
49
+ * model calls.
50
+ *
51
+ * Null is an answer, not a failure — `provider: null` in config means exactly
52
+ * this, and the caller renders it as "run `gmux setup`" rather than as an error.
53
+ */
54
+ export declare function defaultAskProvider(): Promise<CliAskProvider | null>;