tmux-ide 2.9.3 → 2.9.5

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 (85) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +120 -139
  3. package/bin/cli.js +4847 -3585
  4. package/package.json +16 -9
  5. package/packages/contracts/src/session-runtime.ts +2 -0
  6. package/packages/contracts/src/terminal-delivery.ts +4 -0
  7. package/packages/core/src/terminal-compact-capability.ts +31 -1
  8. package/packages/core/src/terminal-delivery.ts +234 -32
  9. package/packages/core/src/terminal-replica-hash-cache.ts +162 -21
  10. package/packages/core/src/terminal-replica-owned-row.ts +165 -10
  11. package/packages/core/src/terminal-replica.ts +43 -21
  12. package/packages/daemon/dist/lib/daemon-embed.js +19 -34
  13. package/packages/daemon/dist/lib/runtime-trace-writer.js +133 -0
  14. package/packages/daemon/dist/lib/tmux-client-execution.js +37 -2
  15. package/packages/daemon/dist/lib/tmux-server-owner.js +20 -4
  16. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  17. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  18. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  19. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  20. package/packages/daemon/dist/native/tmux/darwin-arm64/manifest.json +5 -1
  21. package/packages/daemon/dist/native/tmux/darwin-arm64/tmux +0 -0
  22. package/packages/daemon/dist/native/tmux/darwin-x64/manifest.json +5 -1
  23. package/packages/daemon/dist/native/tmux/darwin-x64/tmux +0 -0
  24. package/packages/daemon/dist/native/tmux/linux-arm64/manifest.json +5 -1
  25. package/packages/daemon/dist/native/tmux/linux-arm64/tmux +0 -0
  26. package/packages/daemon/dist/native/tmux/linux-x64/licenses/libevent_core-2.1.so.7.txt +0 -0
  27. package/packages/daemon/dist/native/tmux/linux-x64/manifest.json +5 -1
  28. package/packages/daemon/dist/native/tmux/linux-x64/tmux +0 -0
  29. package/packages/daemon/dist/terminal/attachments/native-runtime.js +6 -6
  30. package/packages/daemon/dist/terminal/mirror/control-channel.js +379 -75
  31. package/packages/daemon/dist/terminal/mirror/mirror-service.js +43 -3
  32. package/packages/daemon/dist/terminal/mirror/native-grid-projection.js +20 -11
  33. package/packages/daemon/dist/terminal/mirror/session-channel.js +639 -811
  34. package/packages/daemon/dist/terminal/mirror/stock-pane-snapshot.js +177 -0
  35. package/packages/daemon/dist/terminal/session-runtime/causal-cell-ledger.js +1 -1
  36. package/packages/daemon/dist/terminal/session-runtime/registry.js +145 -15
  37. package/packages/daemon/dist/terminal/session-runtime/stock-capture-fidelity.js +12 -0
  38. package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +128 -10
  39. package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +21 -4
  40. package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +19 -2
  41. package/packages/daemon/dist/terminal/session-runtime/xterm-terminal-interpreter-backend.js +17 -19
  42. package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
  43. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +64 -4
  44. package/packages/daemon/dist/tui/mirror/pane-surface.jsx +10 -1
  45. package/packages/daemon/dist/tui/mirror/reference-performance-trace.js +117 -71
  46. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +27 -3
  47. package/packages/daemon/dist/tui/mirror/runtime/pane-scoped-terminal-surface.jsx +17 -4
  48. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +19 -0
  49. package/packages/daemon/dist/tui/mirror/runtime/terminal-input-adapter.js +10 -2
  50. package/packages/daemon/dist/tui/mirror/testing/styled-frame-oracle.js +156 -0
  51. package/packages/daemon/src/lib/daemon-embed.ts +22 -34
  52. package/packages/daemon/src/lib/runtime-trace-writer.ts +132 -0
  53. package/packages/daemon/src/lib/tmux-client-execution.ts +47 -2
  54. package/packages/daemon/src/lib/tmux-server-owner.ts +19 -3
  55. package/packages/daemon/src/terminal/attachments/native-runtime.ts +6 -0
  56. package/packages/daemon/src/terminal/mirror/control-channel.ts +428 -84
  57. package/packages/daemon/src/terminal/mirror/events.ts +7 -7
  58. package/packages/daemon/src/terminal/mirror/mirror-service.ts +49 -3
  59. package/packages/daemon/src/terminal/mirror/native-grid-projection.ts +21 -11
  60. package/packages/daemon/src/terminal/mirror/pane-feed.ts +9 -4
  61. package/packages/daemon/src/terminal/mirror/session-channel.ts +705 -859
  62. package/packages/daemon/src/terminal/mirror/stock-pane-snapshot.ts +228 -0
  63. package/packages/daemon/src/terminal/session-runtime/causal-cell-ledger.ts +1 -1
  64. package/packages/daemon/src/terminal/session-runtime/registry.ts +203 -32
  65. package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +7 -0
  66. package/packages/daemon/src/terminal/session-runtime/stock-capture-fidelity.ts +13 -0
  67. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +142 -8
  68. package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +26 -3
  69. package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +25 -1
  70. package/packages/daemon/src/terminal/session-runtime/xterm-terminal-interpreter-backend.ts +26 -20
  71. package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
  72. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +63 -4
  73. package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -1
  74. package/packages/daemon/src/tui/mirror/reference-performance-trace.ts +141 -90
  75. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +27 -4
  76. package/packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface.tsx +61 -31
  77. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +31 -3
  78. package/packages/daemon/src/tui/mirror/runtime/terminal-input-adapter.ts +9 -6
  79. package/packages/daemon/src/tui/mirror/testing/styled-frame-oracle.ts +209 -0
  80. package/packages/daemon-client/src/terminal-fast-lane.ts +53 -4
  81. package/packages/daemon-client/src/workspace-client-types.ts +1 -0
  82. package/packages/daemon-client/src/workspace-client.ts +78 -5
  83. package/packages/tmux-bridge/src/index.ts +1 -0
  84. package/packages/tmux-bridge/src/runner.ts +25 -1
  85. package/skill/SKILL.md +30 -30
@@ -7,15 +7,15 @@ import type { NativeGridCapture } from "./native-grid-capture.ts";
7
7
  * addresses (`%N`, `@N`, `$N`) never cross this boundary. Pane content arrives
8
8
  * as a typed event stream a renderer can apply verbatim to a VT emulator:
9
9
  *
10
- * reset → seed → [held deltas] → cursor → live deltas…
10
+ * reset → seed → cursor → [held deltas] → live deltas…
11
11
  *
12
12
  * The seed batch is emitted ATOMICALLY (one synchronous callback sequence) so
13
13
  * a consumer can apply it as a single paint: `reset` replaces emulator state
14
14
  * (tmux is a painter, not a stream — never composite two captures), `seed`
15
- * carries one capture from one instant, any deltas the server emitted between
16
- * the capture and cursor probes replay next (they are strictly-after-capture
17
- * bytes the capture cannot contain), and `cursor` lands the emulator cursor on
18
- * tmux's truth. Everything after is a live delta.
15
+ * carries one capture from one instant, and `cursor` installs that capture's
16
+ * cursor and mode metadata. Strictly post-capture output then replays once;
17
+ * installing older cursor/mode metadata after those bytes would overwrite their
18
+ * effects. Everything after the held tail is a live delta.
19
19
  */
20
20
 
21
21
  /** Modes observed at the native capture seam; omitted fields stay unknown. */
@@ -72,8 +72,8 @@ export type MirrorPaneEvent =
72
72
  }
73
73
  | {
74
74
  /** Delivery state changed. `backpressure` mirrors tmux `%pause`;
75
- * `requested` is a subscriber's own freeze. A `resumed` flow event is
76
- * always followed by a fresh atomic seed batch. */
75
+ * `requested` is a subscriber's own freeze. A `resumed` flow event follows
76
+ * the fresh atomic seed batch and its held output. */
77
77
  type: "flow";
78
78
  state: "paused" | "resumed";
79
79
  reason: "backpressure" | "requested";
@@ -73,6 +73,10 @@ export interface MirrorServiceOptions {
73
73
  controlModeOwnershipRegistry?: ControlModeOwnershipRegistry;
74
74
  /** Emitted only after an event-triggered list-clients proof of a native client. */
75
75
  onNativeClientActivity?: (session: string) => void;
76
+ captureInputWrite?: (
77
+ session: string,
78
+ ...args: Parameters<NonNullable<SessionChannelOptions["captureInputWrite"]>>
79
+ ) => ReturnType<NonNullable<SessionChannelOptions["captureInputWrite"]>>;
76
80
  onInputWrite?: (
77
81
  session: string,
78
82
  action: InputAction,
@@ -496,6 +500,47 @@ export class MirrorService {
496
500
  return this.channels.get(session)?.channel.flowSnapshot() ?? null;
497
501
  }
498
502
 
503
+ // Cumulative scalar totals sampled after each retired channel finishes disposal.
504
+ // Keep evidence of residual listeners without retaining retired channel objects.
505
+ private readonly retiredListenerCounts = {
506
+ channels: 0,
507
+ failures: 0,
508
+ pane: 0,
509
+ layout: 0,
510
+ layoutAuthority: 0,
511
+ };
512
+
513
+ qualificationListeners() {
514
+ const active = { pane: 0, layout: 0, layoutAuthority: 0 };
515
+ for (const entry of this.channels.values()) {
516
+ const counts = entry.channel.qualificationListeners();
517
+ active.pane += counts.pane;
518
+ active.layout += counts.layout;
519
+ active.layoutAuthority += counts.layoutAuthority;
520
+ }
521
+ return Object.freeze({
522
+ active: Object.freeze(active),
523
+ retired: Object.freeze({ ...this.retiredListenerCounts }),
524
+ pendingDisposals: this.pendingDisposals.size,
525
+ sessionExit: this.sessionExitListeners.size,
526
+ });
527
+ }
528
+
529
+ private async disposeObservedChannel(channel: SessionChannel): Promise<void> {
530
+ try {
531
+ await channel.dispose();
532
+ } catch (error) {
533
+ this.retiredListenerCounts.failures += 1;
534
+ throw error;
535
+ } finally {
536
+ const counts = channel.qualificationListeners();
537
+ this.retiredListenerCounts.channels += 1;
538
+ this.retiredListenerCounts.pane += counts.pane;
539
+ this.retiredListenerCounts.layout += counts.layout;
540
+ this.retiredListenerCounts.layoutAuthority += counts.layoutAuthority;
541
+ }
542
+ }
543
+
499
544
  activeChannelCount(): number {
500
545
  return this.channels.size;
501
546
  }
@@ -512,7 +557,7 @@ export class MirrorService {
512
557
  this.updateWindowMembership(entry.channel, []);
513
558
  this.channelSessions.delete(entry.channel);
514
559
  try {
515
- await entry.channel.dispose();
560
+ await this.disposeObservedChannel(entry.channel);
516
561
  } finally {
517
562
  entry.releaseAuthority();
518
563
  }
@@ -625,6 +670,8 @@ export class MirrorService {
625
670
  }
626
671
  },
627
672
  onNativeClientActivity: () => this.opts.onNativeClientActivity?.(session),
673
+ captureInputWrite: (action, semanticPaneId) =>
674
+ this.opts.captureInputWrite?.(session, action, semanticPaneId),
628
675
  onInputWrite: (action, startedAtMicros, endedAtMicros, pendingBeforeSend) =>
629
676
  this.opts.onInputWrite?.(
630
677
  session,
@@ -686,8 +733,7 @@ export class MirrorService {
686
733
  this.updateWindowMembership(entry.channel, []);
687
734
  this.channelSessions.delete(entry.channel);
688
735
  if (this.channels.get(session) === entry) this.channels.delete(session);
689
- const disposal = entry.channel
690
- .dispose()
736
+ const disposal = this.disposeObservedChannel(entry.channel)
691
737
  .catch(() => {})
692
738
  .finally(() => entry.releaseAuthority());
693
739
  this.drainingChannels.set(session, disposal);
@@ -1,3 +1,4 @@
1
+ import { TERMINAL_REPLICA_EMPTY_CELL, TERMINAL_REPLICA_SPACE_CELL } from "@tmux-ide/core";
1
2
  import type {
2
3
  TerminalReplicaCell,
3
4
  TerminalReplicaColor,
@@ -23,13 +24,7 @@ export function nativeProjectedRowSource(row: TerminalReplicaRow) {
23
24
  }
24
25
 
25
26
  const defaultColor: TerminalReplicaColor = Object.freeze({ kind: "default" });
26
- const empty: TerminalReplicaCell = Object.freeze({
27
- grapheme: "",
28
- width: 1,
29
- foreground: defaultColor,
30
- background: defaultColor,
31
- attributes: 0,
32
- });
27
+ const empty = TERMINAL_REPLICA_EMPTY_CELL;
33
28
  const acsKeys = [..."+,-.0`abcdefghijklmnopqrstuvwxyz{|}~"];
34
29
  const acsValues = [..."→←↑↓▮◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"];
35
30
  const acs = new Map(acsKeys.map((key, index) => [key, acsValues[index]!]));
@@ -45,7 +40,7 @@ function color(value: number): TerminalReplicaColor {
45
40
 
46
41
  function paint(cell: NativeGridCaptureCell): TerminalReplicaCell {
47
42
  const bits = cell.attributes;
48
- return Object.freeze({
43
+ return {
49
44
  grapheme: bits & 0x80 ? (acs.get(cell.text) ?? cell.text) : cell.text,
50
45
  width: cell.width === 2 ? 2 : 1,
51
46
  foreground: color(cell.foreground),
@@ -58,7 +53,22 @@ function paint(cell: NativeGridCaptureCell): TerminalReplicaCell {
58
53
  (bits & 0x10 ? 32 : 0) |
59
54
  (bits & 0x20 ? 64 : 0) |
60
55
  (bits & 0x100 ? 128 : 0),
61
- });
56
+ };
57
+ }
58
+
59
+ // Only normalized, internally constructed paint can reuse trusted core values.
60
+ // Foreign native fields have already been read in the original paint order.
61
+ function freezePaint(cell: TerminalReplicaCell): TerminalReplicaCell {
62
+ if (
63
+ cell.width === 1 &&
64
+ cell.attributes === 0 &&
65
+ cell.foreground === defaultColor &&
66
+ cell.background === defaultColor
67
+ ) {
68
+ if (cell.grapheme === "") return TERMINAL_REPLICA_EMPTY_CELL;
69
+ if (cell.grapheme === " ") return TERMINAL_REPLICA_SPACE_CELL;
70
+ }
71
+ return Object.freeze(cell);
62
72
  }
63
73
 
64
74
  /**
@@ -105,12 +115,12 @@ export function projectNativeGridRow(
105
115
  x + native.width > columns
106
116
  ) {
107
117
  const span = native.flags & 0x80 ? Math.max(1, Math.min(native.width, columns - x)) : 1;
108
- const blank = Object.freeze({ ...styled, grapheme: "", width: 1 as const });
118
+ const blank = freezePaint({ ...styled, grapheme: "", width: 1 as const });
109
119
  for (let offset = 0; offset < span; offset++) cells[x + offset] = blank;
110
120
  x += span - 1;
111
121
  continue;
112
122
  }
113
- cells[x] = styled;
123
+ cells[x] = freezePaint(styled);
114
124
  if (native.width === 2) {
115
125
  cells[++x] = Object.freeze({ ...styled, grapheme: "", width: 0 });
116
126
  }
@@ -1,13 +1,12 @@
1
1
  import type { NativeGridCapture } from "./native-grid-capture.ts";
2
2
  /**
3
3
  * PaneFeed — PURE per-subscriber delivery gate implementing the atomic
4
- * seed/reseed recipe (m43 flood spike, verified on tmux 3.7b).
4
+ * seed/reseed recipe. This gate assumes the caller supplies a valid capture
5
+ * seam; FIFO command replies alone do not prove that precondition.
5
6
  *
6
7
  * tmux is a painter, not a stream: history and the live screen are separate
7
8
  * authorities, and a capture may only be spliced onto a live stream at an
8
- * explicit seam. The seam works because the control channel is FIFO — the
9
- * `capture-pane` reply comes back at the exact point in the byte stream where
10
- * the capture was taken, so:
9
+ * explicit seam. For a VALID seam the caller must establish:
11
10
  *
12
11
  * - every `%output` delta read BEFORE the capture reply was produced before
13
12
  * the capture instant → its bytes are already IN the capture → DISCARD
@@ -15,6 +14,12 @@ import type { NativeGridCapture } from "./native-grid-capture.ts";
15
14
  * - every delta read AFTER the capture reply is strictly-after-capture →
16
15
  * it must be applied on top of the seed → no gap.
17
16
  *
17
+ * Known limitation: a sibling pane backlog can hold a capture reply while
18
+ * post-capture target output drains ahead of it. Ordinary capture reply order
19
+ * therefore does not establish this seam; that path can discard newer output.
20
+ * See docs/guides/tmux-boundary-model.md and TM02 for the real-wire reproducer.
21
+ * Native atomic snapshot/offset commit has a separate, stronger contract.
22
+ *
18
23
  * The cursor probe is a second command issued back-to-back with the capture.
19
24
  * Deltas that land between the two replies (normally none — both commands sit
20
25
  * in one command-queue batch) are HELD and replayed after the seed but BEFORE