@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,92 @@
1
+ /**
2
+ * Streaming JSONL reader shared by adapters.
3
+ *
4
+ * Sessions run to hundreds of megabytes in aggregate, so we stream line by line
5
+ * and never hold a whole file as one string. Malformed lines are skipped rather
6
+ * than thrown: a truncated final line is normal for a session that is still open.
7
+ */
8
+ import { createReadStream } from "node:fs";
9
+ import { createInterface } from "node:readline";
10
+ export async function* readJsonl(filePath) {
11
+ const stream = createReadStream(filePath, { encoding: "utf8" });
12
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
13
+ try {
14
+ for await (const line of lines) {
15
+ if (line.trim() === "")
16
+ continue;
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(line);
20
+ }
21
+ catch {
22
+ continue; // Truncated or corrupt line; a live session's tail often is.
23
+ }
24
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
25
+ yield parsed;
26
+ }
27
+ }
28
+ }
29
+ finally {
30
+ lines.close();
31
+ stream.close();
32
+ }
33
+ }
34
+ /** Keep only the last `n` items pushed. Used for message tails. */
35
+ export class RingBuffer {
36
+ capacity;
37
+ items = [];
38
+ constructor(capacity) {
39
+ this.capacity = capacity;
40
+ }
41
+ push(item) {
42
+ this.items.push(item);
43
+ if (this.items.length > this.capacity)
44
+ this.items.shift();
45
+ }
46
+ toArray() {
47
+ return [...this.items];
48
+ }
49
+ get last() {
50
+ return this.items[this.items.length - 1];
51
+ }
52
+ }
53
+ /**
54
+ * Keep an evenly-spaced sample of an unbounded stream, in bounded memory.
55
+ *
56
+ * `RingBuffer` above answers "how did this end?". This answers "what shape was
57
+ * it?" — and crucially it never drops the FIRST item, which is the developer's
58
+ * original ask. A summarizer that never sees that writes the same thing the
59
+ * stale harness title already says.
60
+ *
61
+ * Every `stride`-th item is a candidate; when the buffer fills we drop every
62
+ * other one and double the stride. Stride is therefore always a power of two,
63
+ * and the retained set lands between `ceil((capacity + 1) / 2)` and `capacity`
64
+ * — evenly spaced, not exactly `capacity` long. A few waypoints is all the
65
+ * prompt needs.
66
+ */
67
+ export class DecimatingSampler {
68
+ capacity;
69
+ items = [];
70
+ stride = 1;
71
+ seen = 0;
72
+ constructor(capacity) {
73
+ this.capacity = capacity;
74
+ if (capacity < 1)
75
+ throw new RangeError(`DecimatingSampler needs capacity >= 1, got ${capacity}`);
76
+ }
77
+ push(item) {
78
+ const index = this.seen;
79
+ this.seen += 1;
80
+ if (index % this.stride !== 0)
81
+ return;
82
+ this.items.push(item);
83
+ if (this.items.length > this.capacity) {
84
+ this.items = this.items.filter((_, i) => i % 2 === 0);
85
+ this.stride *= 2;
86
+ }
87
+ }
88
+ toArray() {
89
+ return [...this.items];
90
+ }
91
+ }
92
+ //# sourceMappingURL=jsonl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../src/adapters/jsonl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The adapter registry.
3
+ *
4
+ * Adding a harness means adding one file and one line here. If you are that
5
+ * contributor, read docs/adding-a-harness.md — it is short.
6
+ */
7
+ import type { HarnessAdapter } from "./types.js";
8
+ export declare function allAdapters(): HarnessAdapter[];
9
+ /** Adapters whose harness actually stores sessions on this machine. */
10
+ export declare function availableAdapters(): Promise<HarnessAdapter[]>;
11
+ export declare function adapterById(id: string): HarnessAdapter | undefined;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The adapter registry.
3
+ *
4
+ * Adding a harness means adding one file and one line here. If you are that
5
+ * contributor, read docs/adding-a-harness.md — it is short.
6
+ */
7
+ import { ClaudeCodeAdapter } from "./claude-code.js";
8
+ import { CodexAdapter } from "./codex.js";
9
+ export function allAdapters() {
10
+ return [new ClaudeCodeAdapter(), new CodexAdapter()];
11
+ }
12
+ /** Adapters whose harness actually stores sessions on this machine. */
13
+ export async function availableAdapters() {
14
+ const adapters = allAdapters();
15
+ const available = await Promise.all(adapters.map((a) => a.isAvailable()));
16
+ return adapters.filter((_, i) => available[i] === true);
17
+ }
18
+ export function adapterById(id) {
19
+ return allAdapters().find((a) => a.id === id);
20
+ }
21
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/adapters/registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG1C,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,IAAI,iBAAiB,EAAE,EAAE,IAAI,YAAY,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC1E,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { SessionRecord, SessionRef, HarnessId } from "../core/types.js";
2
+ /**
3
+ * The one seam that makes gmux harness-agnostic.
4
+ *
5
+ * To support a new agent harness, implement this interface in a single file
6
+ * under `src/adapters/` and register it in `registry.ts`. Nothing else in the
7
+ * codebase changes. See docs/adding-a-harness.md.
8
+ */
9
+ export interface HarnessAdapter {
10
+ /** Stable id, e.g. "claude-code". Appears in `--json` and in `--harness`. */
11
+ readonly id: HarnessId;
12
+ /** Name shown to humans, e.g. "Claude Code". */
13
+ readonly displayName: string;
14
+ /**
15
+ * Distinctive `pane_current_command` names that mean "this harness". Used only
16
+ * to *prefer* a harness when resolving a pane; matching is a hint, never a
17
+ * gate, because a node-based harness often shows as "node". List only names
18
+ * that unambiguously identify this harness — omit "node".
19
+ */
20
+ readonly processNames: readonly string[];
21
+ /** The binary `gmux run` launches for this harness, e.g. "claude" or "codex". */
22
+ readonly launchCommand: string;
23
+ /**
24
+ * True when this harness stores sessions on this machine. Adapters whose
25
+ * harness isn't installed are skipped silently rather than erroring.
26
+ */
27
+ isAvailable(): Promise<boolean>;
28
+ /** Every session file this harness has written. Cheap: stats only, no parsing. */
29
+ listSessions(): Promise<SessionRef[]>;
30
+ /** Parse one session file into hard facts. No model calls, no network. */
31
+ parseSession(ref: SessionRef): Promise<SessionRecord>;
32
+ /**
33
+ * The command that drops the user back into this session.
34
+ * Returned as argv rather than a string so nothing has to be shell-escaped.
35
+ */
36
+ resumeCommand(record: SessionRecord): ResumeCommand;
37
+ }
38
+ export interface ResumeCommand {
39
+ command: string;
40
+ args: string[];
41
+ /** Directory to run it in — normally the session's original cwd. */
42
+ cwd: string;
43
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/adapters/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The daemon-driven border painter: given a `WorkspaceSnapshot`, writes every
3
+ * pane's `@gm_label` from the model, zero sensing. `gmux daemon run` calls this
4
+ * on every model "change" event; `snapshotLabel` (tmux-label.ts) does the
5
+ * actual glyph/text formatting.
6
+ */
7
+ import type { WorkspaceSnapshot } from "../core/gmux-types.js";
8
+ export declare function paintFromSnapshot(snapshot: WorkspaceSnapshot, setLabel: (paneId: string, text: string) => Promise<void>): Promise<void>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The daemon-driven border painter: given a `WorkspaceSnapshot`, writes every
3
+ * pane's `@gm_label` from the model, zero sensing. `gmux daemon run` calls this
4
+ * on every model "change" event; `snapshotLabel` (tmux-label.ts) does the
5
+ * actual glyph/text formatting.
6
+ */
7
+ import { snapshotLabel } from "./tmux-label.js";
8
+ export async function paintFromSnapshot(snapshot, setLabel) {
9
+ await Promise.all(snapshot.panes.map((e) => setLabel(e.identity.paneId, snapshotLabel(e))));
10
+ }
11
+ //# sourceMappingURL=border-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"border-client.js","sourceRoot":"","sources":["../../src/cli/border-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAA2B,EAC3B,QAAyD;IAEzD,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9F,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `gmux __ask-cancel` — what esc does mid-answer.
3
+ *
4
+ * **It kills the process GROUP, and that is the whole point of this command.**
5
+ * Measured, and it is where the prototype actually broke: `kill -TERM <worker>`
6
+ * leaves the provider ORPHANED AND ALIVE — still running, still billing, still
7
+ * writing into a transcript nobody is reading — while the picker cheerfully
8
+ * flips back to browse mode. `kill -TERM -- -<worker>` reaps the tree, which is
9
+ * why the worker is spawned `detached` and is therefore its own group leader.
10
+ *
11
+ * **The canceller writes the `aborted` record, not the worker.** A worker wedged
12
+ * badly enough to need a SIGKILL is a worker that cannot write anything, and a
13
+ * pane stuck on "answering" forever is a worse failure than the one we started
14
+ * with. Whoever knows the kill happened owns the record.
15
+ */
16
+ import type { Command } from "commander";
17
+ /** The hidden command fzf's `esc` binding runs in ask mode. */
18
+ export declare const ASK_CANCEL_COMMAND = "__ask-cancel";
19
+ export interface AskCancelOptions {
20
+ transcript: string;
21
+ port?: string;
22
+ now?: Date;
23
+ /** Injected in tests, so no test signals a real process group. */
24
+ kill?: (pid: number, signal: NodeJS.Signals) => void;
25
+ graceMs?: number;
26
+ }
27
+ export type AskCancelStatus = "cancelled" | "idle";
28
+ /**
29
+ * Stop whatever turn is in flight, if any.
30
+ *
31
+ * Never throws. esc is also how you leave ask mode, so a cancel with nothing to
32
+ * cancel is the ordinary case and must be silent and cheap.
33
+ */
34
+ export declare function cancelAskTurn(options: AskCancelOptions): Promise<AskCancelStatus>;
35
+ export declare function registerAskCancel(program: Command): void;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `gmux __ask-cancel` — what esc does mid-answer.
3
+ *
4
+ * **It kills the process GROUP, and that is the whole point of this command.**
5
+ * Measured, and it is where the prototype actually broke: `kill -TERM <worker>`
6
+ * leaves the provider ORPHANED AND ALIVE — still running, still billing, still
7
+ * writing into a transcript nobody is reading — while the picker cheerfully
8
+ * flips back to browse mode. `kill -TERM -- -<worker>` reaps the tree, which is
9
+ * why the worker is spawned `detached` and is therefore its own group leader.
10
+ *
11
+ * **The canceller writes the `aborted` record, not the worker.** A worker wedged
12
+ * badly enough to need a SIGKILL is a worker that cannot write anything, and a
13
+ * pane stuck on "answering" forever is a worse failure than the one we started
14
+ * with. Whoever knows the kill happened owns the record.
15
+ */
16
+ import { ASK_KILL_GRACE_MS, appendAskEvent, closeAskTranscript, inFlightSeq, openAskTranscript, readAskLock, readAskTranscript, releaseAskLock, } from "../../services/ask-transcript.js";
17
+ import { isLockStale } from "../../services/auto-summarize.js";
18
+ import { refreshNotifier } from "./__ask-refresh.js";
19
+ /** The hidden command fzf's `esc` binding runs in ask mode. */
20
+ export const ASK_CANCEL_COMMAND = "__ask-cancel";
21
+ function groupKill(pid, signal) {
22
+ // Negative pid: the group, not the process. The worker is a group leader
23
+ // because it was spawned detached; a plain pid here would orphan the provider.
24
+ process.kill(-pid, signal);
25
+ }
26
+ function alive(pid) {
27
+ try {
28
+ process.kill(pid, 0);
29
+ return true;
30
+ }
31
+ catch (error) {
32
+ return error.code === "EPERM";
33
+ }
34
+ }
35
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
36
+ /**
37
+ * Stop whatever turn is in flight, if any.
38
+ *
39
+ * Never throws. esc is also how you leave ask mode, so a cancel with nothing to
40
+ * cancel is the ordinary case and must be silent and cheap.
41
+ */
42
+ export async function cancelAskTurn(options) {
43
+ const notify = refreshNotifier(options.port);
44
+ const now = options.now ?? new Date();
45
+ const kill = options.kill ?? groupKill;
46
+ const lock = await readAskLock(options.transcript);
47
+ // A stale lock's pid is either dead or recycled into some innocent process —
48
+ // liveness ANDed with age is what stops us signalling a stranger.
49
+ const live = lock !== null && lock.pid > 0 && !isLockStale(lock, now);
50
+ if (live && lock) {
51
+ try {
52
+ kill(lock.pid, "SIGTERM");
53
+ await sleep(options.graceMs ?? ASK_KILL_GRACE_MS);
54
+ if (alive(lock.pid))
55
+ kill(lock.pid, "SIGKILL");
56
+ }
57
+ catch {
58
+ // ESRCH: it finished on its own between our read and our signal. The
59
+ // `aborted` record below is still the honest thing to write — the user
60
+ // pressed esc, and a race must not resurrect the answer in the pane.
61
+ }
62
+ }
63
+ const { events } = await readAskTranscript(options.transcript);
64
+ const seq = inFlightSeq(events);
65
+ if (seq === null) {
66
+ await releaseAskLock(options.transcript);
67
+ notify();
68
+ return "idle";
69
+ }
70
+ const fd = openAskTranscript(options.transcript);
71
+ try {
72
+ appendAskEvent(fd, { t: "aborted", seq, at: now.toISOString() });
73
+ }
74
+ finally {
75
+ closeAskTranscript(fd);
76
+ }
77
+ await releaseAskLock(options.transcript);
78
+ notify();
79
+ return "cancelled";
80
+ }
81
+ export function registerAskCancel(program) {
82
+ program
83
+ .command(ASK_CANCEL_COMMAND, { hidden: true })
84
+ .description("internal: cancel the in-flight answer (run by the picker's esc binding)")
85
+ .requiredOption("--transcript <path>", "the picker's chat transcript")
86
+ .option("--port <port>", "fzf's listen port, for the repaint")
87
+ .action(async (options) => {
88
+ await cancelAskTurn(options);
89
+ });
90
+ }
91
+ //# sourceMappingURL=__ask-cancel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__ask-cancel.js","sourceRoot":"","sources":["../../../src/cli/commands/__ask-cancel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Telling fzf to repaint — the whole of what the ask commands know about fzf.
3
+ *
4
+ * `POST /` with the body `refresh-preview` to `$FZF_PORT`, authenticated with
5
+ * `$FZF_API_KEY`. Plain `fetch` (global in Node 20), not `curl`: an undeclared
6
+ * dependency for one HTTP request.
7
+ *
8
+ * **Failure is swallowed, and that is not laziness.** fzf may have exited while
9
+ * the answer was in flight, and a POST to a closed port must not take a worker
10
+ * down before it has finished writing the transcript.
11
+ *
12
+ * **The key comes from the environment and goes nowhere else.** fzf exports it
13
+ * to its children, `__ask-send` passes it to the worker by inheritance, and it
14
+ * must never appear in an argv, a binding string or the transcript's `meta`
15
+ * record — argv is world-readable (`ps -ww -o args=`), another process's
16
+ * environment is not. The port takes the opposite route, on the argv, precisely
17
+ * because it is not a secret.
18
+ *
19
+ * Honest about what this buys: the key raises the bar against a different-user
20
+ * or non-local attacker, and it is what stops an unauthenticated POST executing
21
+ * commands as us. It does NOT stop a same-uid local attacker, who can read fzf's
22
+ * environment. The only boundary there is a unix socket, whose fzf floor
23
+ * (0.66.0) is out of reach. Known and accepted.
24
+ */
25
+ /** A refresh POST as data, so the policy is testable without a socket. */
26
+ export interface RefreshRequest {
27
+ url: string;
28
+ method: "POST";
29
+ headers: Record<string, string>;
30
+ body: "refresh-preview";
31
+ }
32
+ export declare function refreshRequest(port: number, apiKey: string): RefreshRequest;
33
+ /**
34
+ * The notifier for this process, or a no-op.
35
+ *
36
+ * No port or no key means no POST at all: there is no unauthenticated path here
37
+ * to accidentally take. A missing port is also the normal case for `gmux __ask-*`
38
+ * driven straight from a shell, where there is no pane to repaint.
39
+ */
40
+ export declare function refreshNotifier(port: string | undefined, env?: NodeJS.ProcessEnv): () => void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Telling fzf to repaint — the whole of what the ask commands know about fzf.
3
+ *
4
+ * `POST /` with the body `refresh-preview` to `$FZF_PORT`, authenticated with
5
+ * `$FZF_API_KEY`. Plain `fetch` (global in Node 20), not `curl`: an undeclared
6
+ * dependency for one HTTP request.
7
+ *
8
+ * **Failure is swallowed, and that is not laziness.** fzf may have exited while
9
+ * the answer was in flight, and a POST to a closed port must not take a worker
10
+ * down before it has finished writing the transcript.
11
+ *
12
+ * **The key comes from the environment and goes nowhere else.** fzf exports it
13
+ * to its children, `__ask-send` passes it to the worker by inheritance, and it
14
+ * must never appear in an argv, a binding string or the transcript's `meta`
15
+ * record — argv is world-readable (`ps -ww -o args=`), another process's
16
+ * environment is not. The port takes the opposite route, on the argv, precisely
17
+ * because it is not a secret.
18
+ *
19
+ * Honest about what this buys: the key raises the bar against a different-user
20
+ * or non-local attacker, and it is what stops an unauthenticated POST executing
21
+ * commands as us. It does NOT stop a same-uid local attacker, who can read fzf's
22
+ * environment. The only boundary there is a unix socket, whose fzf floor
23
+ * (0.66.0) is out of reach. Known and accepted.
24
+ */
25
+ export function refreshRequest(port, apiKey) {
26
+ return {
27
+ url: `http://127.0.0.1:${port}`,
28
+ method: "POST",
29
+ headers: { "x-api-key": apiKey },
30
+ body: "refresh-preview",
31
+ };
32
+ }
33
+ /**
34
+ * The notifier for this process, or a no-op.
35
+ *
36
+ * No port or no key means no POST at all: there is no unauthenticated path here
37
+ * to accidentally take. A missing port is also the normal case for `gmux __ask-*`
38
+ * driven straight from a shell, where there is no pane to repaint.
39
+ */
40
+ export function refreshNotifier(port, env = process.env) {
41
+ const parsed = Number.parseInt(port ?? "", 10);
42
+ const apiKey = env["FZF_API_KEY"];
43
+ if (!Number.isFinite(parsed) || parsed <= 0 || !apiKey)
44
+ return () => { };
45
+ const request = refreshRequest(parsed, apiKey);
46
+ return () => {
47
+ void fetch(request.url, {
48
+ method: request.method,
49
+ headers: request.headers,
50
+ body: request.body,
51
+ }).catch(() => { });
52
+ };
53
+ }
54
+ //# sourceMappingURL=__ask-refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__ask-refresh.js","sourceRoot":"","sources":["../../../src/cli/commands/__ask-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAUH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAc;IACzD,OAAO;QACL,GAAG,EAAE,oBAAoB,IAAI,EAAE;QAC/B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;QAChC,IAAI,EAAE,iBAAiB;KACxB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAwB,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC5F,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAExE,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,OAAO,GAAG,EAAE;QACV,KAAK,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * `gmux __ask-run` — the detached worker that answers one question.
3
+ *
4
+ * It is spawned by `__ask-send`, holds the lock its sender took, writes into the
5
+ * transcript and exits. Its stdio is `ignore`d and fzf owns the terminal, so it
6
+ * says nothing to anyone: the transcript is its only output, errors included.
7
+ *
8
+ * **It rebuilds the conversation from the file.** `gmux ask`'s REPL kept its turns
9
+ * in a closure; a fan of one-shot workers has no closure to keep them in. That
10
+ * difference is the whole design, and none of it reaches `buildAskPrompt`, which
11
+ * still takes a plain `AskTurn[]`.
12
+ *
13
+ * **The heartbeat is why the pane is not a frozen rectangle.** The provider
14
+ * buffers — one write at the end, ~9–20s later — so without a timer nothing
15
+ * repaints for the length of the think and the picker looks wedged. The worker
16
+ * only makes the pane re-render; the pane does its own `thinking… Ns`
17
+ * arithmetic, so the transcript never holds a number that is stale the instant
18
+ * it is written.
19
+ */
20
+ import type { Command } from "commander";
21
+ import { type LsOptions } from "./ls.js";
22
+ /** The hidden command `__ask-send` forks. Not a thing a person runs. */
23
+ export declare const ASK_RUN_COMMAND = "__ask-run";
24
+ export interface AskRunOptions extends LsOptions {
25
+ transcript: string;
26
+ seq: string;
27
+ port?: string;
28
+ /** Injected in tests. Defaults to the fzf POST, or to nothing without a port. */
29
+ notify?: () => void;
30
+ /** Injected in tests. In production this is aborted by SIGTERM. */
31
+ signal?: AbortSignal;
32
+ }
33
+ export type AskRunStatus = "answered" | "aborted" | "no-question" | "no-provider";
34
+ /**
35
+ * Answer the question at `seq`.
36
+ *
37
+ * Never throws: a worker that dies loudly dies into `/dev/null`. Whatever
38
+ * happens, the turn ends in the transcript with an `end` or an `error` — except
39
+ * on cancellation, where the record is the canceller's to write, so that a
40
+ * wedged worker cannot leave the pane stuck on "answering".
41
+ */
42
+ export declare function runAskTurn(options: AskRunOptions): Promise<AskRunStatus>;
43
+ export declare function registerAskRun(program: Command): void;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `gmux __ask-run` — the detached worker that answers one question.
3
+ *
4
+ * It is spawned by `__ask-send`, holds the lock its sender took, writes into the
5
+ * transcript and exits. Its stdio is `ignore`d and fzf owns the terminal, so it
6
+ * says nothing to anyone: the transcript is its only output, errors included.
7
+ *
8
+ * **It rebuilds the conversation from the file.** `gmux ask`'s REPL kept its turns
9
+ * in a closure; a fan of one-shot workers has no closure to keep them in. That
10
+ * difference is the whole design, and none of it reaches `buildAskPrompt`, which
11
+ * still takes a plain `AskTurn[]`.
12
+ *
13
+ * **The heartbeat is why the pane is not a frozen rectangle.** The provider
14
+ * buffers — one write at the end, ~9–20s later — so without a timer nothing
15
+ * repaints for the length of the think and the picker looks wedged. The worker
16
+ * only makes the pane re-render; the pane does its own `thinking… Ns`
17
+ * arithmetic, so the transcript never holds a number that is stale the instant
18
+ * it is written.
19
+ */
20
+ import { NoProviderError } from "../../core/errors.js";
21
+ import { ASK_SESSION_LIMIT, buildAskContext, buildAskPrompt } from "../../services/ask.js";
22
+ import { ASK_HEARTBEAT_MS, appendAskEvent, closeAskTranscript, foldCompletedTurns, openAskTranscript, readAskTranscript, releaseAskLock, streamAnswer, } from "../../services/ask-transcript.js";
23
+ import { readConfig, resolveAskCommand } from "../../services/config.js";
24
+ import { loadViews } from "../../services/views.js";
25
+ import { refreshNotifier } from "./__ask-refresh.js";
26
+ import { toFilters } from "./ls.js";
27
+ /** The hidden command `__ask-send` forks. Not a thing a person runs. */
28
+ export const ASK_RUN_COMMAND = "__ask-run";
29
+ /**
30
+ * Answer the question at `seq`.
31
+ *
32
+ * Never throws: a worker that dies loudly dies into `/dev/null`. Whatever
33
+ * happens, the turn ends in the transcript with an `end` or an `error` — except
34
+ * on cancellation, where the record is the canceller's to write, so that a
35
+ * wedged worker cannot leave the pane stuck on "answering".
36
+ */
37
+ export async function runAskTurn(options) {
38
+ const seq = Number.parseInt(options.seq, 10);
39
+ const notify = options.notify ?? refreshNotifier(options.port);
40
+ const { events } = await readAskTranscript(options.transcript);
41
+ const question = events.find((event) => event.t === "question" && event.seq === seq);
42
+ const settled = events.some((event) => (event.t === "end" || event.t === "aborted" || event.t === "error") && event.seq === seq);
43
+ if (!question || question.t !== "question" || settled) {
44
+ // Nothing to answer: a stale respawn, or a turn that was cancelled before we
45
+ // started. Drop the lock so the next enter is not locked out by a ghost.
46
+ await releaseAskLock(options.transcript);
47
+ return "no-question";
48
+ }
49
+ const argv = resolveAskCommand(await readConfig());
50
+ const fd = openAskTranscript(options.transcript);
51
+ const controller = new AbortController();
52
+ const signal = options.signal ?? controller.signal;
53
+ // Cancellation arrives as a signal to our process GROUP: `__ask-cancel` kills
54
+ // the group because killing us alone would orphan the provider, which would
55
+ // keep running, keep billing and keep nobody informed. Aborting propagates
56
+ // that down to `runProviderCommand`'s child.
57
+ const onSignal = () => controller.abort();
58
+ process.on("SIGTERM", onSignal);
59
+ process.on("SIGINT", onSignal);
60
+ // The only timer in the design, and it lives exactly as long as the request.
61
+ const heartbeat = setInterval(notify, ASK_HEARTBEAT_MS);
62
+ try {
63
+ if (!argv) {
64
+ const failed = new NoProviderError("`gmux ask`");
65
+ appendAskEvent(fd, {
66
+ t: "error",
67
+ seq,
68
+ at: new Date().toISOString(),
69
+ message: failed.fix ? `${failed.message}\n${failed.fix}` : failed.message,
70
+ });
71
+ notify();
72
+ return "no-provider";
73
+ }
74
+ const limit = Number(options.limit) || ASK_SESSION_LIMIT;
75
+ const views = await loadViews(toFilters(options, ASK_SESSION_LIMIT));
76
+ const context = buildAskContext(views, question.focus, limit);
77
+ // Completed turns only. The question we are about to answer has no `end`
78
+ // record, so it cannot replay itself into its own prompt.
79
+ const turns = foldCompletedTurns(events);
80
+ await streamAnswer({
81
+ // The provider argv is exactly what bare `gmux ask` uses, `-p` and all —
82
+ // which is what keeps this call's own session flagged automated, and so
83
+ // out of the picker that started it.
84
+ argv,
85
+ prompt: buildAskPrompt(context, turns, question.text),
86
+ transcriptFd: fd,
87
+ seq,
88
+ notify,
89
+ signal,
90
+ });
91
+ return signal.aborted ? "aborted" : "answered";
92
+ }
93
+ catch {
94
+ return "aborted";
95
+ }
96
+ finally {
97
+ clearInterval(heartbeat);
98
+ process.off("SIGTERM", onSignal);
99
+ process.off("SIGINT", onSignal);
100
+ closeAskTranscript(fd);
101
+ // On cancellation the lock is the canceller's to remove — it may already
102
+ // have handed the transcript to a newer turn, and releasing that turn's lock
103
+ // would let a third writer in.
104
+ if (!signal.aborted)
105
+ await releaseAskLock(options.transcript);
106
+ }
107
+ }
108
+ export function registerAskRun(program) {
109
+ program
110
+ .command(ASK_RUN_COMMAND, { hidden: true })
111
+ .description("internal: answer one question into a transcript (run detached by gmux itself)")
112
+ .requiredOption("--transcript <path>", "the picker's chat transcript")
113
+ .requiredOption("--seq <n>", "which question to answer")
114
+ .option("--port <port>", "fzf's listen port, for the repaint")
115
+ .option("--harness <id>", "only this harness")
116
+ .option("-p, --project <name>", "only sessions whose project matches")
117
+ .option("-b, --branch <name>", "only sessions whose git branch matches")
118
+ .option("-s, --since <when>", "only sessions newer than this")
119
+ .option("-n, --limit <count>", "how many recent sessions to consider")
120
+ .option("--include-sidechains", "include subagent transcripts")
121
+ .option("--include-automated", "include non-interactive runs")
122
+ .action(async (options) => {
123
+ await runAskTurn(options);
124
+ });
125
+ }
126
+ //# sourceMappingURL=__ask-run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__ask-run.js","sourceRoot":"","sources":["../../../src/cli/commands/__ask-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,YAAY,GACb,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAkB,MAAM,SAAS,CAAC;AAEpD,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAG,WAAW,CAAC;AAc3C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAsB;IACrD,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,UAAU,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IACrF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CACzB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CACpG,CAAC;IACF,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,KAAK,UAAU,IAAI,OAAO,EAAE,CAAC;QACtD,6EAA6E;QAC7E,yEAAyE;QACzE,MAAM,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzC,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,MAAM,IAAI,GAAG,iBAAiB,CAAC,MAAM,UAAU,EAAE,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAEjD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC;IACnD,8EAA8E;IAC9E,4EAA4E;IAC5E,2EAA2E;IAC3E,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,GAAS,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE/B,6EAA6E;IAC7E,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAExD,IAAI,CAAC;QACH,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,YAAY,CAAC,CAAC;YACjD,cAAc,CAAC,EAAE,EAAE;gBACjB,CAAC,EAAE,OAAO;gBACV,GAAG;gBACH,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAC5B,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO;aAC1E,CAAC,CAAC;YACH,MAAM,EAAE,CAAC;YACT,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,iBAAiB,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACrE,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC9D,yEAAyE;QACzE,0DAA0D;QAC1D,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAEzC,MAAM,YAAY,CAAC;YACjB,yEAAyE;YACzE,wEAAwE;YACxE,qCAAqC;YACrC,IAAI;YACJ,MAAM,EAAE,cAAc,CAAC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC;YACrD,YAAY,EAAE,EAAE;YAChB,GAAG;YACH,MAAM;YACN,MAAM;SACP,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,SAAS,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChC,kBAAkB,CAAC,EAAE,CAAC,CAAC;QACvB,yEAAyE;QACzE,6EAA6E;QAC7E,+BAA+B;QAC/B,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,OAAO;SACJ,OAAO,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SAC1C,WAAW,CAAC,+EAA+E,CAAC;SAC5F,cAAc,CAAC,qBAAqB,EAAE,8BAA8B,CAAC;SACrE,cAAc,CAAC,WAAW,EAAE,0BAA0B,CAAC;SACvD,MAAM,CAAC,eAAe,EAAE,oCAAoC,CAAC;SAC7D,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;SAC7C,MAAM,CAAC,sBAAsB,EAAE,qCAAqC,CAAC;SACrE,MAAM,CAAC,qBAAqB,EAAE,wCAAwC,CAAC;SACvE,MAAM,CAAC,oBAAoB,EAAE,+BAA+B,CAAC;SAC7D,MAAM,CAAC,qBAAqB,EAAE,sCAAsC,CAAC;SACrE,MAAM,CAAC,sBAAsB,EAAE,8BAA8B,CAAC;SAC9D,MAAM,CAAC,qBAAqB,EAAE,8BAA8B,CAAC;SAC7D,MAAM,CAAC,KAAK,EAAE,OAAsB,EAAE,EAAE;QACvC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACP,CAAC"}