@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 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/services/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Distillation: turn a session record into the small bundle of evidence a model
3
+ * needs to say where the work landed.
4
+ *
5
+ * This is the heart of the tool. Two properties matter:
6
+ *
7
+ * 1. **The arc, not one endpoint.** We send where the work started, a few
8
+ * waypoints through it, and how it ended. Head-only is the bug we exist to
9
+ * fix — that just restates the stale `aiTitle`. But tail-only overcorrects:
10
+ * a status with no subject ("timestamp check still red") is unreadable next
11
+ * to twenty others. Never head-only; never the head speaking alone.
12
+ *
13
+ * 2. **Small.** The distilled input is a couple of KB regardless of whether the
14
+ * session was 20 messages or 2,000, so summarizing is cheap and bounded.
15
+ */
16
+ import type { SessionRecord, SummaryInput } from "../core/types.js";
17
+ /**
18
+ * Bump when `buildPrompt` changes what it asks for.
19
+ *
20
+ * The summary cache is keyed on the hash below, which covers session content
21
+ * only — so without this, editing the prompt would change nothing for anything
22
+ * already summarized: those sessions keep their old summaries until their
23
+ * transcripts happen to change, which for a finished session is never.
24
+ *
25
+ * Bumping marks every cached summary stale at once, and they regenerate through
26
+ * the normal background pass. No cache wipe, no migration.
27
+ *
28
+ * 2: headlines tightened to a short scannable clause (was "max 80 chars",
29
+ * which overflowed the 72-char row and read as truncated).
30
+ * 3: summaries describe the arc, not just the tail — the prompt gained the
31
+ * original ask, and the output gained `overview`. The headline changed
32
+ * meaning: it now says what the work IS, not what state it is in.
33
+ * 4: added `summary` — a paragraph-or-two drilldown between `overview` and the
34
+ * status fields, for reorienting on a session without opening it.
35
+ */
36
+ export declare const PROMPT_VERSION = 4;
37
+ export declare function distill(record: SessionRecord): SummaryInput;
38
+ /** The instruction given to whichever model provider is configured. */
39
+ export declare function buildPrompt(input: SummaryInput): string;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Distillation: turn a session record into the small bundle of evidence a model
3
+ * needs to say where the work landed.
4
+ *
5
+ * This is the heart of the tool. Two properties matter:
6
+ *
7
+ * 1. **The arc, not one endpoint.** We send where the work started, a few
8
+ * waypoints through it, and how it ended. Head-only is the bug we exist to
9
+ * fix — that just restates the stale `aiTitle`. But tail-only overcorrects:
10
+ * a status with no subject ("timestamp check still red") is unreadable next
11
+ * to twenty others. Never head-only; never the head speaking alone.
12
+ *
13
+ * 2. **Small.** The distilled input is a couple of KB regardless of whether the
14
+ * session was 20 messages or 2,000, so summarizing is cheap and bounded.
15
+ */
16
+ import { hash } from "../core/text.js";
17
+ /**
18
+ * Bump when `buildPrompt` changes what it asks for.
19
+ *
20
+ * The summary cache is keyed on the hash below, which covers session content
21
+ * only — so without this, editing the prompt would change nothing for anything
22
+ * already summarized: those sessions keep their old summaries until their
23
+ * transcripts happen to change, which for a finished session is never.
24
+ *
25
+ * Bumping marks every cached summary stale at once, and they regenerate through
26
+ * the normal background pass. No cache wipe, no migration.
27
+ *
28
+ * 2: headlines tightened to a short scannable clause (was "max 80 chars",
29
+ * which overflowed the 72-char row and read as truncated).
30
+ * 3: summaries describe the arc, not just the tail — the prompt gained the
31
+ * original ask, and the output gained `overview`. The headline changed
32
+ * meaning: it now says what the work IS, not what state it is in.
33
+ * 4: added `summary` — a paragraph-or-two drilldown between `overview` and the
34
+ * status fields, for reorienting on a session without opening it.
35
+ */
36
+ export const PROMPT_VERSION = 4;
37
+ export function distill(record) {
38
+ const input = {
39
+ promptVersion: PROMPT_VERSION,
40
+ harness: record.harness,
41
+ sessionId: record.sessionId,
42
+ project: record.project,
43
+ gitBranch: record.gitBranch,
44
+ title: record.title,
45
+ recentUserPrompts: record.recentUserPrompts,
46
+ arcPrompts: record.arcPrompts,
47
+ lastAssistantText: record.lastAssistantText,
48
+ // Cap the file list: a refactor touching 200 files should not dominate the prompt.
49
+ filesTouched: record.filesTouched.slice(0, 25),
50
+ lastToolFailure: record.lastToolFailure,
51
+ endedMidTask: record.endedMidTask,
52
+ };
53
+ return { ...input, hash: hash(JSON.stringify(input)) };
54
+ }
55
+ /** The instruction given to whichever model provider is configured. */
56
+ export function buildPrompt(input) {
57
+ const lines = [];
58
+ lines.push("You are summarizing a coding-agent session so a developer can decide, at a glance, whether to pick it back up.", "", "You are shown the ARC of the session: where it started, waypoints through the middle, and how it ended. Describe where the work ACTUALLY LANDED — and what it is fundamentally about, which may not be what it set out to do.", "", "## Session", `harness: ${input.harness}`);
59
+ if (input.project)
60
+ lines.push(`project: ${input.project}`);
61
+ if (input.gitBranch)
62
+ lines.push(`branch: ${input.gitBranch}`);
63
+ if (input.title)
64
+ lines.push(`title recorded at session START (may be stale): ${input.title}`);
65
+ lines.push(`ended mid-task: ${input.endedMidTask ? "yes" : "no"}`);
66
+ if (input.filesTouched.length > 0) {
67
+ lines.push("", "## Files the agent changed", ...input.filesTouched.map((f) => `- ${f}`));
68
+ }
69
+ // Only what the tail does not already carry. On a short session the sampler
70
+ // and the tail window hold the same turns, and printing both would make the
71
+ // model read the duplication as emphasis.
72
+ const tail = new Set(input.recentUserPrompts);
73
+ const [anchor, ...waypoints] = input.arcPrompts;
74
+ const freshWaypoints = waypoints.filter((p) => !tail.has(p));
75
+ if (anchor !== undefined && !tail.has(anchor)) {
76
+ lines.push("", "## The original ask (how this session opened)", anchor);
77
+ }
78
+ if (freshWaypoints.length > 0) {
79
+ lines.push("", "## How the work moved (sampled across the session, oldest first)", ...freshWaypoints.map((p) => `- ${p}`));
80
+ }
81
+ if (input.recentUserPrompts.length > 0) {
82
+ lines.push("", "## The developer's most recent instructions (oldest first — the LAST one matters most)", ...input.recentUserPrompts.map((p) => `- ${p}`));
83
+ }
84
+ if (input.lastAssistantText) {
85
+ lines.push("", "## The agent's final message", input.lastAssistantText);
86
+ }
87
+ if (input.lastToolFailure) {
88
+ lines.push("", "## The last failing command", input.lastToolFailure);
89
+ }
90
+ lines.push("", "## Output", "Reply with ONLY a JSON object, no code fence, no commentary:", "{", ' "headline": "what this work IS, one clause, under 60 chars, no trailing period",', ' "overview": "2-3 sentences: what this session is fundamentally about, including how the goal shifted if it did",', ' "summary": "a paragraph or two: the real drilldown. Trace how the work evolved — the original goal, the turns it took, decisions and dead ends — and land on where it stands now. Enough that a developer who has not seen this session can reorient. Longer sessions warrant more; short ones stay short.",', ' "landed": "1-2 sentences: the MOST RECENT work done",', ' "open": "1-2 sentences: what is unresolved, blocked, or broken. \'Nothing outstanding.\' if genuinely finished",', ' "nextStep": "one concrete next action a developer would take"', "}", "", "These three describe the same work at widening zoom — never contradicting, each adding detail:", " headline — a scannable clause, read at a glance next to twenty others;", " overview — 2-3 sentences of what it is about;", " summary — a paragraph or two that actually reorients you.", "Write a clause, not a sentence:", ' good: "Migrating webhook retries to the new queue backend"', ' bad: "The retry logic has been partially applied, but the signature verification test is still failing."', "", "`landed` is the LATEST work, not a recap of the whole session — that is what `overview` is for.", "", "Be specific and factual. Name the files, tests, and errors involved. Never speculate beyond the evidence above.");
91
+ return lines.join("\n");
92
+ }
93
+ //# sourceMappingURL=distill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distill.js","sourceRoot":"","sources":["../../src/services/distill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { GuardianPolicy, HostPressure, PaneEntry } from "../core/gmux-types.js";
2
+ export interface GuardianDecision {
3
+ action: "broadcast" | "notify" | "log-only" | "none";
4
+ culpritPaneId: string | null;
5
+ culpritLabel: string;
6
+ message: string;
7
+ }
8
+ /**
9
+ * Pure policy state machine: pressure + policy + clock in, a decision out.
10
+ * The ONE component that acts (types into agents via the caller), so it
11
+ * carries no I/O of its own — decide() is a pure function of its inputs
12
+ * plus the instance's own fire-history state (firedAt/belowSinceFire).
13
+ */
14
+ export declare class Guardian {
15
+ private readonly opts;
16
+ private firedAt;
17
+ private belowSinceFire;
18
+ constructor(opts: {
19
+ policy: GuardianPolicy;
20
+ threshold: number;
21
+ cooldownSeconds: number;
22
+ });
23
+ decide(host: HostPressure, panes: PaneEntry[], now: number): GuardianDecision;
24
+ private fire;
25
+ private none;
26
+ }
@@ -0,0 +1,71 @@
1
+ import { basename } from "node:path";
2
+ /**
3
+ * Pure policy state machine: pressure + policy + clock in, a decision out.
4
+ * The ONE component that acts (types into agents via the caller), so it
5
+ * carries no I/O of its own — decide() is a pure function of its inputs
6
+ * plus the instance's own fire-history state (firedAt/belowSinceFire).
7
+ */
8
+ export class Guardian {
9
+ opts;
10
+ firedAt = null;
11
+ belowSinceFire = false;
12
+ constructor(opts) {
13
+ this.opts = opts;
14
+ }
15
+ decide(host, panes, now) {
16
+ const over = host.usedRatio >= this.opts.threshold;
17
+ if (!over) {
18
+ if (this.firedAt !== null)
19
+ this.belowSinceFire = true;
20
+ return this.none();
21
+ }
22
+ // In cooldown? Stay quiet unless pressure dropped and re-crossed since the
23
+ // last fire (hysteresis), or the cooldown window has fully elapsed.
24
+ if (this.firedAt !== null) {
25
+ const elapsed = (now - this.firedAt) / 1000;
26
+ const mayRefire = this.belowSinceFire || elapsed >= this.opts.cooldownSeconds;
27
+ if (!mayRefire)
28
+ return this.none();
29
+ }
30
+ const { culprit, label } = topConsumer(panes, host);
31
+ const agents = panes.filter((p) => p.identity.harness && p.identity.sessionId);
32
+ const pct = Math.round(host.usedRatio * 100);
33
+ const message = `host memory ${pct}% — top consumer: ${label}; checkpoint your work and pause non-essential tasks.`;
34
+ if (this.opts.policy === "off") {
35
+ return { action: "log-only", culpritPaneId: culprit, culpritLabel: label, message };
36
+ }
37
+ if (this.opts.policy === "notify") {
38
+ return this.fire("notify", culprit, label, message, now);
39
+ }
40
+ // policy === "auto"
41
+ if (agents.length === 0) {
42
+ return { action: "log-only", culpritPaneId: culprit, culpritLabel: label, message };
43
+ }
44
+ return this.fire("broadcast", culprit, label, message, now);
45
+ }
46
+ fire(action, culprit, label, message, now) {
47
+ this.firedAt = now;
48
+ this.belowSinceFire = false;
49
+ return { action, culpritPaneId: culprit, culpritLabel: label, message };
50
+ }
51
+ none() {
52
+ return { action: "none", culpritPaneId: null, culpritLabel: "", message: "" };
53
+ }
54
+ }
55
+ function topConsumer(panes, host) {
56
+ let best = null;
57
+ for (const p of panes) {
58
+ if (p.resources && (!best || p.resources.perPaneRss > best.resources.perPaneRss))
59
+ best = p;
60
+ }
61
+ const bestRss = best?.resources?.perPaneRss ?? 0;
62
+ if (!best || host.unattributed > bestRss) {
63
+ return { culprit: null, label: "a source outside tracked panes" };
64
+ }
65
+ const name = best.identity.cwd ? basename(best.identity.cwd) : best.identity.command;
66
+ // Binary units (matches the cockpit's formatBytes) so the same pane shows
67
+ // the same GB figure in both the cockpit memory column and this message.
68
+ const gb = (bestRss / 1024 ** 3).toFixed(1);
69
+ return { culprit: best.identity.paneId, label: `window \`${name}\` (${gb} GB)` };
70
+ }
71
+ //# sourceMappingURL=guardian.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardian.js","sourceRoot":"","sources":["../../src/services/guardian.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The session index.
3
+ *
4
+ * Parsing 1,000+ sessions on every invocation would make `gmux` unusable, so we
5
+ * cache parsed records keyed on each file's (mtime, size). A file whose stats
6
+ * are unchanged is served from cache; anything else is re-parsed. That keeps a
7
+ * warm `gmux ls` in the millisecond range while staying correct as sessions grow.
8
+ */
9
+ import type { ListFilters, SessionRecord, SessionRef } from "../core/types.js";
10
+ export interface RefreshResult {
11
+ records: SessionRecord[];
12
+ parsed: number;
13
+ cached: number;
14
+ }
15
+ /** Every session file every installed harness knows about. */
16
+ export declare function discover(): Promise<SessionRef[]>;
17
+ /**
18
+ * Bring the index up to date and return every known session record.
19
+ *
20
+ * `force` re-parses everything, for when a parser bug is fixed and the cached
21
+ * records are wrong rather than stale.
22
+ */
23
+ export declare function refreshIndex(options?: {
24
+ force?: boolean;
25
+ }): Promise<RefreshResult>;
26
+ /**
27
+ * Records straight from the on-disk cache — no directory discovery, no re-stat.
28
+ *
29
+ * A fast, slightly-stale read for latency-sensitive callers (the overlay's first
30
+ * paint): it skips the thousands-of-files scan `refreshIndex` does, at the cost
31
+ * of missing sessions created since the last refresh. The caller upgrades to
32
+ * `refreshIndex` in the background.
33
+ */
34
+ export declare function cachedRecords(): Promise<SessionRecord[]>;
35
+ /** Apply the shared `ls`/picker filters. Pure — exported for tests. */
36
+ export declare function filterRecords(records: readonly SessionRecord[], filters: ListFilters): SessionRecord[];
@@ -0,0 +1,125 @@
1
+ /**
2
+ * The session index.
3
+ *
4
+ * Parsing 1,000+ sessions on every invocation would make `gmux` unusable, so we
5
+ * cache parsed records keyed on each file's (mtime, size). A file whose stats
6
+ * are unchanged is served from cache; anything else is re-parsed. That keeps a
7
+ * warm `gmux ls` in the millisecond range while staying correct as sessions grow.
8
+ */
9
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
10
+ import { dirname } from "node:path";
11
+ import { indexPath } from "../core/paths.js";
12
+ import { availableAdapters, adapterById } from "../adapters/registry.js";
13
+ import { mapLimit } from "./concurrency.js";
14
+ const PARSE_CONCURRENCY = 16;
15
+ /** Bump whenever SessionRecord changes shape, so stale caches are discarded. */
16
+ const INDEX_VERSION = 3;
17
+ async function loadIndexFile() {
18
+ try {
19
+ const raw = await readFile(indexPath(), "utf8");
20
+ const parsed = JSON.parse(raw);
21
+ if (parsed.version !== INDEX_VERSION || !Array.isArray(parsed.entries))
22
+ return new Map();
23
+ return new Map(parsed.entries.map((e) => [e.record.filePath, e]));
24
+ }
25
+ catch {
26
+ return new Map(); // Missing or corrupt cache is not an error; rebuild it.
27
+ }
28
+ }
29
+ async function saveIndexFile(entries) {
30
+ const path = indexPath();
31
+ await mkdir(dirname(path), { recursive: true });
32
+ const payload = { version: INDEX_VERSION, entries: [...entries.values()] };
33
+ // Write-then-rename: a killed `gmux` must never leave a half-written index.
34
+ const temp = `${path}.${process.pid}.tmp`;
35
+ await writeFile(temp, JSON.stringify(payload), "utf8");
36
+ await rename(temp, path);
37
+ }
38
+ /** Every session file every installed harness knows about. */
39
+ export async function discover() {
40
+ const adapters = await availableAdapters();
41
+ const lists = await Promise.all(adapters.map((a) => a.listSessions()));
42
+ return lists.flat();
43
+ }
44
+ /**
45
+ * Bring the index up to date and return every known session record.
46
+ *
47
+ * `force` re-parses everything, for when a parser bug is fixed and the cached
48
+ * records are wrong rather than stale.
49
+ */
50
+ export async function refreshIndex(options = {}) {
51
+ const cache = options.force ? new Map() : await loadIndexFile();
52
+ const refs = await discover();
53
+ let parsed = 0;
54
+ let cached = 0;
55
+ const records = await mapLimit(refs, PARSE_CONCURRENCY, async (ref) => {
56
+ const hit = cache.get(ref.filePath);
57
+ if (hit && hit.mtimeMs === ref.mtimeMs && hit.size === ref.size) {
58
+ cached += 1;
59
+ return hit.record;
60
+ }
61
+ const adapter = adapterById(ref.harness);
62
+ if (!adapter)
63
+ return null;
64
+ try {
65
+ const record = await adapter.parseSession(ref);
66
+ parsed += 1;
67
+ return record;
68
+ }
69
+ catch {
70
+ return null; // One unreadable session must not sink the whole index.
71
+ }
72
+ });
73
+ const next = new Map();
74
+ for (let i = 0; i < refs.length; i++) {
75
+ const record = records[i];
76
+ const ref = refs[i];
77
+ if (!record)
78
+ continue;
79
+ next.set(ref.filePath, { mtimeMs: ref.mtimeMs, size: ref.size, record });
80
+ }
81
+ await saveIndexFile(next);
82
+ const all = [...next.values()].map((e) => e.record);
83
+ all.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
84
+ return { records: all, parsed, cached };
85
+ }
86
+ /**
87
+ * Records straight from the on-disk cache — no directory discovery, no re-stat.
88
+ *
89
+ * A fast, slightly-stale read for latency-sensitive callers (the overlay's first
90
+ * paint): it skips the thousands-of-files scan `refreshIndex` does, at the cost
91
+ * of missing sessions created since the last refresh. The caller upgrades to
92
+ * `refreshIndex` in the background.
93
+ */
94
+ export async function cachedRecords() {
95
+ const records = [...(await loadIndexFile()).values()].map((e) => e.record);
96
+ records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
97
+ return records;
98
+ }
99
+ /** Apply the shared `ls`/picker filters. Pure — exported for tests. */
100
+ export function filterRecords(records, filters) {
101
+ let out = [...records];
102
+ if (!filters.includeSidechains)
103
+ out = out.filter((r) => !r.isSidechain);
104
+ if (!filters.includeAutomated)
105
+ out = out.filter((r) => !r.isAutomated);
106
+ if (filters.harness)
107
+ out = out.filter((r) => r.harness === filters.harness);
108
+ if (filters.project) {
109
+ const needle = filters.project.toLowerCase();
110
+ out = out.filter((r) => (r.project ?? "").toLowerCase().includes(needle));
111
+ }
112
+ if (filters.branch) {
113
+ const needle = filters.branch.toLowerCase();
114
+ out = out.filter((r) => (r.gitBranch ?? "").toLowerCase().includes(needle));
115
+ }
116
+ if (filters.since) {
117
+ const cutoff = filters.since;
118
+ out = out.filter((r) => r.updatedAt >= cutoff);
119
+ }
120
+ out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
121
+ if (filters.limit != null && filters.limit > 0)
122
+ out = out.slice(0, filters.limit);
123
+ return out;
124
+ }
125
+ //# sourceMappingURL=index-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-store.js","sourceRoot":"","sources":["../../src/services/index-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const MAX_PANE_LOG_BYTES: number;
2
+ export declare function rotateIfLarge(logPath: string, maxBytes: number): Promise<boolean>;
3
+ export declare function pruneLog(logPath: string): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { rm, stat, open } from "node:fs/promises";
2
+ export const MAX_PANE_LOG_BYTES = 5 * 1024 * 1024;
3
+ export async function rotateIfLarge(logPath, maxBytes) {
4
+ let size;
5
+ try {
6
+ size = (await stat(logPath)).size;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ if (size <= maxBytes)
12
+ return false;
13
+ const fh = await open(logPath, "r+");
14
+ try {
15
+ const keep = Buffer.alloc(maxBytes);
16
+ await fh.read(keep, 0, maxBytes, size - maxBytes);
17
+ await fh.truncate(0);
18
+ await fh.write(keep, 0, maxBytes, 0);
19
+ }
20
+ finally {
21
+ await fh.close();
22
+ }
23
+ return true;
24
+ }
25
+ export async function pruneLog(logPath) {
26
+ await rm(logPath, { force: true });
27
+ }
28
+ //# sourceMappingURL=log-rotation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-rotation.js","sourceRoot":"","sources":["../../src/services/log-rotation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAe,EACf,QAAgB;IAEhB,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,IAAI,QAAQ;QAAE,OAAO,KAAK,CAAC;IAEnC,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAG,QAAQ,CAAC,CAAC;QAClD,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe;IAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The pane→session links `gmux run` records, so the overlay maps a live pane to
3
+ * the exact session it launched rather than guessing. Cache, disposable, pruned
4
+ * to the live pane set on every overlay render.
5
+ */
6
+ import type { PaneLink } from "../core/types.js";
7
+ export declare function readPaneLinks(): Promise<PaneLink[]>;
8
+ export declare function writePaneLink(link: PaneLink): Promise<void>;
9
+ export declare function prunePaneLinks(livePaneIds: Iterable<string>): Promise<PaneLink[]>;
10
+ export declare function linkForPane(links: readonly PaneLink[], paneId: string): PaneLink | null;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The pane→session links `gmux run` records, so the overlay maps a live pane to
3
+ * the exact session it launched rather than guessing. Cache, disposable, pruned
4
+ * to the live pane set on every overlay render.
5
+ */
6
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
7
+ import { dirname } from "node:path";
8
+ import { paneLinksPath } from "../core/paths.js";
9
+ function isPaneLink(value) {
10
+ const link = value;
11
+ return (!!link &&
12
+ typeof link.paneId === "string" &&
13
+ typeof link.harness === "string" &&
14
+ typeof link.sessionId === "string");
15
+ }
16
+ export async function readPaneLinks() {
17
+ try {
18
+ const parsed = JSON.parse(await readFile(paneLinksPath(), "utf8"));
19
+ return Array.isArray(parsed) ? parsed.filter(isPaneLink) : [];
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ }
25
+ async function persist(links) {
26
+ const path = paneLinksPath();
27
+ await mkdir(dirname(path), { recursive: true });
28
+ // Write-then-rename: a killed `gmux run` must never leave a half-written pane-links.json.
29
+ const temp = `${path}.${process.pid}.tmp`;
30
+ await writeFile(temp, JSON.stringify(links), "utf8");
31
+ await rename(temp, path);
32
+ }
33
+ export async function writePaneLink(link) {
34
+ const links = (await readPaneLinks()).filter((l) => l.paneId !== link.paneId);
35
+ links.push(link);
36
+ await persist(links);
37
+ }
38
+ export async function prunePaneLinks(livePaneIds) {
39
+ const live = new Set(livePaneIds);
40
+ const kept = (await readPaneLinks()).filter((l) => live.has(l.paneId));
41
+ await persist(kept);
42
+ return kept;
43
+ }
44
+ export function linkForPane(links, paneId) {
45
+ return links.find((l) => l.paneId === paneId) ?? null;
46
+ }
47
+ //# sourceMappingURL=pane-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pane-links.js","sourceRoot":"","sources":["../../src/services/pane-links.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,SAAS,UAAU,CAAC,KAAc;IAChC,MAAM,IAAI,GAAG,KAAiB,CAAC;IAC/B,OAAO,CACL,CAAC,CAAC,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;QAC/B,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;QAChC,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CACnC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,aAAa,EAAE,EAAE,MAAM,CAAC,CAAY,CAAC;QAC9E,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,KAA0B;IAC/C,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,0FAA0F;IAC1F,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IAC1C,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;IACrD,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAc;IAChD,MAAM,KAAK,GAAG,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,WAA6B;IAChE,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACvE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAA0B,EAAE,MAAc;IACpE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC;AACxD,CAAC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Read which session a tmux pane is running from the pane's own process tree.
3
+ *
4
+ * The agent process carries the session id in its command line — `codex resume
5
+ * <id>`, `claude --resume <id>` — and both ids are exactly gmux's session
6
+ * id. That is a far stronger signal than the pane's working directory, which is
7
+ * the *shell's* cwd (usually `~`), not the agent's. Where the argv has no id (a
8
+ * fresh session), the agent process's real cwd stands in.
9
+ *
10
+ * The parsing here is pure and tested; `panePid`/`descendants`/`processCwd` are
11
+ * thin shells over `tmux`/`pgrep`/`ps`/`lsof`, and every one of them degrades to
12
+ * a null rather than throwing — a resolve must never fail because a process
13
+ * vanished mid-walk.
14
+ */
15
+ import type { HarnessId } from "../core/types.js";
16
+ export interface AgentProcess {
17
+ pid: number;
18
+ command: string;
19
+ }
20
+ export interface AgentSession {
21
+ harness: HarnessId;
22
+ sessionId: string;
23
+ }
24
+ /**
25
+ * The session id on an agent's command line, or null.
26
+ *
27
+ * Codex writes `codex resume <uuid>`; Claude Code writes `claude --resume <uuid>`
28
+ * (or `-r`). The id is the standard 8-4-4-4-12 hex form; anything else is not a
29
+ * session id we can trust, so it reads as null and the caller falls back to cwd.
30
+ */
31
+ export declare function parseAgentSession(command: string): AgentSession | null;
32
+ /** The harness a command names (claude / codex), even without a session id. */
33
+ export declare function harnessFromCommand(command: string): HarnessId | null;
34
+ /**
35
+ * The harness process among a pane's descendants, or null.
36
+ *
37
+ * Prefer a process whose argv already yields a session id — that is definitive.
38
+ * Otherwise the first process that looks like a harness invocation, for its cwd.
39
+ * An MCP-server child (`node ./mcp/server.mjs`) names no harness, so it is never
40
+ * chosen over the agent that spawned it.
41
+ */
42
+ export declare function pickAgentProcess(procs: readonly AgentProcess[]): AgentProcess | null;
43
+ interface SnapshotEntry {
44
+ ppid: number;
45
+ command: string;
46
+ /** Seconds since the process started (`ps etime`). Older process → larger. Null if unparsed. */
47
+ elapsedSeconds: number | null;
48
+ }
49
+ /** The whole process table, pid → {ppid, command, elapsedSeconds}. One `ps` — the parser is pure. */
50
+ export type ProcessSnapshot = Map<number, SnapshotEntry>;
51
+ /**
52
+ * Parse a `ps` `etime` field — `[[DD-]HH:]MM:SS` — into seconds.
53
+ *
54
+ * We use `etime`, not `etimes`: BSD `ps` on macOS has no `etimes` keyword (it
55
+ * silently drops the column), while `etime` is portable across macOS and Linux.
56
+ * Only relative order matters to the caller, so absolute wall-clock is not needed.
57
+ */
58
+ export declare function parseElapsedSeconds(etime: string): number | null;
59
+ export declare function parseProcessSnapshot(output: string): ProcessSnapshot;
60
+ /**
61
+ * One snapshot of every process, taken once and walked in memory — instead of a
62
+ * `pgrep` per node down a deep agent tree, which spawned dozens of processes and
63
+ * dominated resolve latency.
64
+ */
65
+ export declare function processSnapshot(): Promise<ProcessSnapshot>;
66
+ /** Every descendant of `rootPid` in the snapshot (pure breadth-first, cycle-safe). */
67
+ export declare function descendantsOf(rootPid: number, snapshot: ProcessSnapshot): AgentProcess[];
68
+ /** A process's real working directory: /proc on Linux, lsof on macOS. Null if unknown. */
69
+ export declare function processCwd(pid: number): Promise<string | null>;
70
+ export interface PaneProcessHint {
71
+ /** Session id read straight off the agent's argv — exact when present. */
72
+ argvSession: AgentSession | null;
73
+ /** Which harness the agent is (from its command line) — so a fresh session
74
+ * resolves to that harness, never another. Null when no agent was found;
75
+ * absent on hints from callers that don't inspect the process. */
76
+ agentHarness?: HarnessId | null;
77
+ /** The agent process's real cwd, for a fresh session with no id on the line. */
78
+ agentCwd: string | null;
79
+ /**
80
+ * Seconds since the agent process started (`ps etime`). Older process → larger.
81
+ *
82
+ * This is the discriminator between fresh same-cwd panes: with no session id on
83
+ * the command line, the process's start ORDER matches the sessions' start order,
84
+ * so a group of panes pairs to its sessions by this rather than by array order.
85
+ * Absent on hints from callers that don't inspect the process.
86
+ */
87
+ agentElapsedSeconds?: number | null;
88
+ }
89
+ /**
90
+ * What the pane's running agent tells us about its session, from a shared process
91
+ * snapshot. Never throws — every failure (no agent, no permission) degrades to an
92
+ * empty hint, and the resolver falls back to its cwd heuristics.
93
+ */
94
+ export declare function paneProcessHint(panePid: number, snapshot: ProcessSnapshot): Promise<PaneProcessHint>;
95
+ export {};