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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.9.3",
4
- "description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
3
+ "version": "2.9.5",
4
+ "description": "The open-source workspace for coding agents: run Claude Code, Codex and other agents in tmux with live status",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "tmux-ide": "bin/cli.js"
@@ -36,6 +36,7 @@
36
36
  "!packages/daemon/dist/**/__snapshots__/**"
37
37
  ],
38
38
  "scripts": {
39
+ "test:tmux-boundary": "node scripts/test-tmux-boundary.mjs",
39
40
  "build": "pnpm build:cli",
40
41
  "build:web": "pnpm --filter @tmux-ide/desktop-renderer run build:web",
41
42
  "build:cli": "node scripts/build-cli.mjs",
@@ -64,7 +65,6 @@
64
65
  "typecheck:workspace": "turbo run typecheck",
65
66
  "docs:build": "turbo run build --filter=@tmux-ide/docs",
66
67
  "pack:check": "pnpm test:installer && node --test scripts/lib/npm-release-tag.test.mjs && node scripts/pack-tui-check.mjs",
67
- "pack:web:check": "pnpm build:web && node scripts/pack-web-check.mjs",
68
68
  "test:pack-installed": "node scripts/pack-check-run.mjs",
69
69
  "release:opentui:check": "node scripts/opentui-release-check.mjs",
70
70
  "check:native-deps": "node packages/daemon/scripts/check-native-deps.mjs",
@@ -72,7 +72,9 @@
72
72
  "postinstall": "node scripts/postinstall.js",
73
73
  "docs": "turbo run dev --filter=@tmux-ide/docs",
74
74
  "demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
75
- "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/agent-team-renderer.test.tsx ./scripts/solid-conditional-props.test.tsx",
75
+ "demo:font": "node docs/scripts/tui-demo-font/build.mjs",
76
+ "readme:media": "node docs/scripts/readme-media.mjs",
77
+ "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/testing/styled-frame-oracle.test.tsx ./packages/daemon/src/tui/mirror/runtime/terminal-transient-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/terminal-draw-failure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/agent-team-renderer.test.tsx ./scripts/solid-conditional-props.test.tsx",
76
78
  "test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
77
79
  "test:tui-live": "node scripts/tui-testdrive.mjs smoke",
78
80
  "test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
@@ -80,9 +82,9 @@
80
82
  "qualify:installed-latency": "node packages/daemon/scripts/qualify-installed-latency.mjs",
81
83
  "benchmark:comparative": "node scripts/comparative-terminal.mjs",
82
84
  "test:contracts-initializer-purity": "node --test scripts/lib/contracts-initializer-purity.test.mjs scripts/lib/owner-evidence-bundle.test.mjs",
83
- "test:benchmark-comparative": "node --test scripts/comparative-terminal-support.test.mjs scripts/lib/terminal-scroll-observations.test.mjs scripts/lib/comparative-terminal-resources.test.mjs scripts/lib/comparative-terminal-report.test.mjs",
85
+ "test:benchmark-comparative": "node --test scripts/comparative-terminal-scenario.test.mjs scripts/comparative-terminal-support.test.mjs scripts/lib/terminal-scroll-observations.test.mjs scripts/lib/comparative-terminal-resources.test.mjs scripts/lib/comparative-terminal-report.test.mjs scripts/lib/comparative-terminal-trace.test.mjs",
84
86
  "measure:performance-portable": "node scripts/performance-portable-evidence.mjs",
85
- "test:portable-release-contract": "node --experimental-strip-types --test scripts/qualify-divider-gestures.test.mjs scripts/lib/product-keyboard-pointer-resize.test.mjs scripts/lib/startup-launch-diagnostic.test.mjs scripts/lib/coherence-delivery-client.test.ts scripts/lib/coherence-trace.test.mjs scripts/lib/spark-qualification-descriptor.test.mjs scripts/lib/spark-qualification-ssh.test.mjs scripts/lib/performance-reference-target.test.mjs scripts/lib/performance-reference-workspace.test.mjs scripts/lib/performance-reference-runtime.test.mjs scripts/lib/performance-reference-report.test.mjs scripts/lib/product-rig-reference-environment.test.mjs && pnpm exec vitest run scripts/lib/portable-performance-evidence.test.mjs",
87
+ "test:portable-release-contract": "node --experimental-strip-types --test scripts/lib/styled-ssh-qualification.test.mjs scripts/lib/boundary-results.test.mjs scripts/lib/packed-cli-resolution.test.mjs scripts/qualify-divider-gestures.test.mjs scripts/lib/product-keyboard-pointer-resize.test.mjs scripts/lib/startup-launch-diagnostic.test.mjs scripts/lib/coherence-delivery-client.test.ts scripts/lib/coherence-trace.test.mjs scripts/lib/spark-qualification-descriptor.test.mjs scripts/lib/spark-qualification-ssh.test.mjs scripts/lib/performance-reference-target.test.mjs scripts/lib/performance-reference-workspace.test.mjs scripts/lib/performance-reference-runtime.test.mjs scripts/lib/performance-reference-report.test.mjs scripts/lib/performance-reference-input-mapping.test.mjs scripts/lib/transport-clock-evidence.test.mjs scripts/lib/performance-reference-output-content.test.mjs scripts/lib/daemon-trace-admission.test.mjs scripts/lib/owned-reference-capture.test.mjs scripts/lib/product-rig-reference-environment.test.mjs && pnpm exec vitest run scripts/lib/portable-performance-evidence.test.mjs",
86
88
  "measure:performance-reference": "node scripts/performance-reference.mjs",
87
89
  "test:performance-reference": "node scripts/performance-reference.mjs --require-complete",
88
90
  "test:tui-perf": "pnpm test:performance-qualification",
@@ -108,11 +110,16 @@
108
110
  "test:tui-gallery": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./scripts/tui-gallery/gallery.test.tsx",
109
111
  "typecheck:tui-gallery": "tsc --noEmit -p scripts/tsconfig.tui-gallery.json",
110
112
  "test:spark-qualification": "node --import tsx --test scripts/lib/spark-*.test.ts scripts/lib/spark-*.test.mjs",
111
- "test:installer": "node --test scripts/install.test.mjs"
113
+ "test:installer": "node --test scripts/install.test.mjs",
114
+ "qualify:styled-ssh": "node scripts/qualify-styled-ssh.mjs"
112
115
  },
113
116
  "keywords": [
114
117
  "tmux",
115
- "ide",
118
+ "coding-agents",
119
+ "claude-code",
120
+ "codex",
121
+ "ai-agents",
122
+ "agent-teams",
116
123
  "terminal",
117
124
  "workspace",
118
125
  "developer-tools"
@@ -124,7 +131,7 @@
124
131
  "type": "git",
125
132
  "url": "git+https://github.com/wavyrai/tmux-ide.git"
126
133
  },
127
- "homepage": "https://github.com/wavyrai/tmux-ide#readme",
134
+ "homepage": "https://tmux-ide.com",
128
135
  "bugs": {
129
136
  "url": "https://github.com/wavyrai/tmux-ide/issues"
130
137
  },
@@ -374,6 +374,8 @@ export interface SessionRuntimeTerminalSubscription<
374
374
  Tombstone = unknown,
375
375
  > {
376
376
  readonly generation: SessionRuntimeGeneration;
377
+ /** Pane-local terminal capability failure, replayed to late listeners. */
378
+ onUnavailable?(listener: (message: string) => void): () => void;
377
379
  close(): Promise<void>;
378
380
  freeze(): void;
379
381
  thaw(): void;
@@ -9,6 +9,10 @@ import {
9
9
  TerminalReplicaTombstonePayloadSchemaZ,
10
10
  } from "./terminal-replica.ts";
11
11
 
12
+ /** Stable capability diagnosis carried by the existing pane-scoped source-closed fault. */
13
+ export const STOCK_CAPTURE_TAB_UNAVAILABLE =
14
+ "This tmux server cannot provide an exact snapshot of saved tab cells. Continue this session in native tmux, or use a new session started with tmux-ide's bundled server.";
15
+
12
16
  export const TERMINAL_DELIVERY_PROTOCOL_VERSION = 1 as const;
13
17
  export const TERMINAL_DELIVERY_CHUNK_BYTES = 256 * 1024;
14
18
  export const TERMINAL_DELIVERY_PATCH_TO_SEED_BYTES = 512 * 1024;
@@ -4,6 +4,7 @@ interface CompactReplicaCapability {
4
4
  readonly baseline: TerminalReplicaSnapshot | null;
5
5
  readonly snapshot: TerminalReplicaSnapshot | null;
6
6
  readonly hash: string;
7
+ readonly tombstoneInterval?: Readonly<{ baseRevision: number; revision: number }>;
7
8
  }
8
9
 
9
10
  const compactReplicaCapabilities = new WeakMap<object, CompactReplicaCapability>();
@@ -14,8 +15,17 @@ export function grantCompactReplicaCapability(
14
15
  baseline: TerminalReplicaSnapshot | null,
15
16
  snapshot: TerminalReplicaSnapshot | null,
16
17
  hash: string,
18
+ tombstoneInterval?: Readonly<{ baseRevision: number; revision: number }>,
17
19
  ): void {
18
- compactReplicaCapabilities.set(owner, Object.freeze({ baseline, snapshot, hash }));
20
+ compactReplicaCapabilities.set(
21
+ owner,
22
+ Object.freeze({
23
+ baseline,
24
+ snapshot,
25
+ hash,
26
+ ...(tombstoneInterval ? { tombstoneInterval: Object.freeze({ ...tombstoneInterval }) } : {}),
27
+ }),
28
+ );
19
29
  }
20
30
 
21
31
  /** Package-private one-shot adoption: ordinary objects cannot manufacture this grant. */
@@ -29,3 +39,23 @@ export function consumeCompactReplicaCapability(
29
39
  if (!capability || capability.baseline !== baseline || capability.hash !== hash) return undefined;
30
40
  return capability.snapshot;
31
41
  }
42
+
43
+ /** Only an exact hash-verified decoded tombstone interval can bridge canonical revisions. */
44
+ export function consumeVerifiedTombstoneInterval(
45
+ owner: object,
46
+ baseline: TerminalReplicaSnapshot | null,
47
+ hash: string,
48
+ baseRevision: number,
49
+ revision: number,
50
+ ): boolean {
51
+ const capability = compactReplicaCapabilities.get(owner);
52
+ compactReplicaCapabilities.delete(owner);
53
+ return (
54
+ capability !== undefined &&
55
+ capability.baseline === baseline &&
56
+ capability.snapshot === null &&
57
+ capability.hash === hash &&
58
+ capability.tombstoneInterval?.baseRevision === baseRevision &&
59
+ capability.tombstoneInterval.revision === revision
60
+ );
61
+ }
@@ -1,5 +1,10 @@
1
1
  import { z } from "zod";
2
- import { isOwnedTerminalReplicaRow } from "./terminal-replica-owned-row.ts";
2
+ import {
3
+ isOwnedTerminalReplicaRow,
4
+ isSchemaValidProjectedTerminalReplicaRow,
5
+ TERMINAL_REPLICA_EMPTY_CELL,
6
+ TERMINAL_REPLICA_SPACE_CELL,
7
+ } from "./terminal-replica-owned-row.ts";
3
8
  import {
4
9
  TERMINAL_DELIVERY_CHUNK_BYTES,
5
10
  TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES,
@@ -11,6 +16,7 @@ import {
11
16
  TerminalSemanticDeliveryPayloadSchemaZ,
12
17
  TerminalReplicaSnapshotSchemaZ,
13
18
  TerminalReplicaRowSchemaZ,
19
+ TerminalReplicaPatchPayloadSchemaZ,
14
20
  TerminalReplicaCellSchemaZ,
15
21
  TerminalReplicaPlacementSchemaZ,
16
22
  type TerminalDeliveryAck,
@@ -38,6 +44,7 @@ import {
38
44
  } from "./terminal-replica.ts";
39
45
  import { grantCompactReplicaCapability } from "./terminal-compact-capability.ts";
40
46
  import {
47
+ createDecodedTerminalReplicaRowBuilder,
41
48
  hashTerminalReplicaRowCooperatively,
42
49
  hashTerminalReplicaRowRunsCooperatively,
43
50
  isTerminalReplicaRowDeeplyFrozen,
@@ -215,16 +222,124 @@ type CompactRow = readonly [0 | 1, readonly CompactCellRun[]];
215
222
  // with the strict contract before remembering it; ownership alone is not enough.
216
223
  // External rows still use the ordinary schema and detached parse result.
217
224
  const validatedOwnedEncodingRows = new WeakSet<TerminalReplicaRow>();
225
+ // Only these exact deeply immutable constants may reuse strict cell validation.
226
+ // Ownership alone never qualifies arbitrary cells, including frozen lookalikes.
227
+ const CompactCellSliceSchema = /* @__PURE__ */ TerminalReplicaCellSchemaZ.array();
228
+ let trustedBlankCellsValidated = false;
229
+ function validateTrustedBlankCells(): void {
230
+ if (trustedBlankCellsValidated) return;
231
+ CompactCellSliceSchema.parse([TERMINAL_REPLICA_EMPTY_CELL, TERMINAL_REPLICA_SPACE_CELL]);
232
+ trustedBlankCellsValidated = true;
233
+ }
234
+ const CompactOwnedCellSchema = z.union([
235
+ z.custom<TerminalReplicaCell>((cell) => {
236
+ if (cell !== TERMINAL_REPLICA_EMPTY_CELL && cell !== TERMINAL_REPLICA_SPACE_CELL) return false;
237
+ validateTrustedBlankCells();
238
+ return true;
239
+ }),
240
+ TerminalReplicaCellSchemaZ,
241
+ ]);
242
+ const CompactOwnedCellSliceSchema = CompactOwnedCellSchema.array();
243
+ const CompactOwnedEncodingRowSchema = TerminalReplicaRowSchemaZ.extend({
244
+ cells: CompactOwnedCellSliceSchema,
245
+ });
246
+
218
247
  const CompactEncodingRowSchema = z.union([
219
248
  z.custom<TerminalReplicaRow>((input) => {
220
249
  if (!isOwnedTerminalReplicaRow(input)) return false;
221
- if (validatedOwnedEncodingRows.has(input)) return true;
222
- if (!TerminalReplicaRowSchemaZ.safeParse(input).success) return false;
250
+ if (validatedOwnedEncodingRows.has(input) || isSchemaValidProjectedTerminalReplicaRow(input))
251
+ return true;
252
+ const schema = prefersTrustedCellValidation(input.cells)
253
+ ? CompactOwnedEncodingRowSchema
254
+ : TerminalReplicaRowSchemaZ;
255
+ if (!schema.safeParse(input).success) return false;
223
256
  validatedOwnedEncodingRows.add(input);
224
257
  return true;
225
258
  }),
226
259
  TerminalReplicaRowSchemaZ,
227
260
  ]);
261
+ // Patch rows keep original strict issues, including their abort/continue state.
262
+ // Catch exposes those issues before Zod formats away that state. The tagged
263
+ // failure is local to this parser; arbitrary foreign getter errors still escape.
264
+ class CompactPatchRowFailure {
265
+ readonly issues: z.RefinementCtx["issues"];
266
+ readonly value: TerminalReplicaRow;
267
+
268
+ constructor(issues: z.RefinementCtx["issues"], value: TerminalReplicaRow) {
269
+ this.issues = issues;
270
+ this.value = value;
271
+ }
272
+ }
273
+ const StrictCompactPatchRowSchema = TerminalReplicaRowSchemaZ.catch((context) => {
274
+ throw new CompactPatchRowFailure(context.issues, context.value as TerminalReplicaRow);
275
+ });
276
+ const CompactPatchEncodingRowSchema = z
277
+ .custom<TerminalReplicaRow>(() => true)
278
+ .transform((input, context) => {
279
+ const owned = isOwnedTerminalReplicaRow(input);
280
+ if (
281
+ owned &&
282
+ (validatedOwnedEncodingRows.has(input) || isSchemaValidProjectedTerminalReplicaRow(input))
283
+ )
284
+ return input;
285
+ if (owned && prefersTrustedCellValidation(input.cells)) {
286
+ const optimized = CompactOwnedEncodingRowSchema.safeParse(input);
287
+ if (optimized.success) {
288
+ validatedOwnedEncodingRows.add(input);
289
+ return input;
290
+ }
291
+ // Only immutable owned data can be read again to recover strict issues.
292
+ }
293
+ try {
294
+ const parsed = StrictCompactPatchRowSchema.parse(input);
295
+ if (!owned) return parsed;
296
+ validatedOwnedEncodingRows.add(input);
297
+ return input;
298
+ } catch (error) {
299
+ if (!(error instanceof CompactPatchRowFailure)) throw error;
300
+ for (const issue of error.issues) context.issues.push(issue);
301
+ return error.value;
302
+ }
303
+ });
304
+ const CompactPatchEncodingPayloadSchema =
305
+ TerminalSemanticDeliveryPayloadSchemaZ.options[1].safeExtend({
306
+ patch: TerminalReplicaPatchPayloadSchemaZ.extend({
307
+ rows: z.array(
308
+ TerminalReplicaPatchPayloadSchemaZ.shape.rows.element.extend({
309
+ row: CompactPatchEncodingRowSchema,
310
+ }),
311
+ ),
312
+ history: z.array(CompactPatchEncodingRowSchema).optional(),
313
+ historyDelta: TerminalReplicaPatchPayloadSchemaZ.shape.historyDelta
314
+ .unwrap()
315
+ .extend({ append: z.array(CompactPatchEncodingRowSchema) })
316
+ .optional(),
317
+ }),
318
+ });
319
+
320
+ function hasOwnedPatchRow(input: TerminalSemanticDeliveryPayload): boolean {
321
+ if (Object.getOwnPropertyDescriptor(input, "frame")?.value !== "patch") return false;
322
+ const patch = Object.getOwnPropertyDescriptor(input, "patch")?.value;
323
+ if (typeof patch !== "object" || patch === null) return false;
324
+ const first = (array: unknown): unknown =>
325
+ Array.isArray(array) ? Object.getOwnPropertyDescriptor(array, "0")?.value : undefined;
326
+ const entry = first(Object.getOwnPropertyDescriptor(patch, "rows")?.value);
327
+ if (
328
+ typeof entry === "object" &&
329
+ entry !== null &&
330
+ isOwnedTerminalReplicaRow(Object.getOwnPropertyDescriptor(entry, "row")?.value)
331
+ )
332
+ return true;
333
+ if (isOwnedTerminalReplicaRow(first(Object.getOwnPropertyDescriptor(patch, "history")?.value)))
334
+ return true;
335
+ const delta = Object.getOwnPropertyDescriptor(patch, "historyDelta")?.value;
336
+ return (
337
+ typeof delta === "object" &&
338
+ delta !== null &&
339
+ isOwnedTerminalReplicaRow(first(Object.getOwnPropertyDescriptor(delta, "append")?.value))
340
+ );
341
+ }
342
+
228
343
  const CompactEncodingPayloadSchema = z.discriminatedUnion("frame", [
229
344
  TerminalSemanticDeliveryPayloadSchemaZ.options[0].extend({
230
345
  snapshot: TerminalReplicaSnapshotSchemaZ.extend({
@@ -256,7 +371,11 @@ export function encodeCompactSemanticTerminalUpdate(
256
371
  input: TerminalSemanticDeliveryPayload,
257
372
  ): Uint8Array {
258
373
  const update = (
259
- hasOwnedSeedRow(input) ? CompactEncodingPayloadSchema : TerminalSemanticDeliveryPayloadSchemaZ
374
+ hasOwnedPatchRow(input)
375
+ ? CompactPatchEncodingPayloadSchema
376
+ : hasOwnedSeedRow(input)
377
+ ? CompactEncodingPayloadSchema
378
+ : TerminalSemanticDeliveryPayloadSchemaZ
260
379
  ).parse(input);
261
380
  // Compact payloads contain only arrays and primitives below this object.
262
381
  // Insert root keys in canonical order so the native serializer emits exactly
@@ -303,7 +422,38 @@ const CompactSeedMetadataSchema = /* @__PURE__ */ TerminalReplicaSnapshotSchemaZ
303
422
  placements: true,
304
423
  });
305
424
  const CompactRowHeaderSchema = /* @__PURE__ */ TerminalReplicaRowSchemaZ.omit({ cells: true });
306
- const CompactCellSliceSchema = /* @__PURE__ */ TerminalReplicaCellSchemaZ.array();
425
+ function isValidatedTrustedBlankSlice(cells: readonly TerminalReplicaCell[]): boolean {
426
+ if (
427
+ !cells.every(
428
+ (cell) => cell === TERMINAL_REPLICA_EMPTY_CELL || cell === TERMINAL_REPLICA_SPACE_CELL,
429
+ )
430
+ )
431
+ return false;
432
+ validateTrustedBlankCells();
433
+ return true;
434
+ }
435
+
436
+ // This is a conservative optimization choice, never a validation boundary.
437
+ // Low-density owned rows keep the ordinary parser rather than paying a failed
438
+ // union branch for each nontrusted cell. Foreign inputs never enter this scan.
439
+ function prefersTrustedCellValidation(cells: readonly TerminalReplicaCell[]): boolean {
440
+ let nontrusted = 0;
441
+ const limit = Math.floor(cells.length / 4);
442
+ for (const cell of cells) {
443
+ if (cell !== TERMINAL_REPLICA_EMPTY_CELL && cell !== TERMINAL_REPLICA_SPACE_CELL) {
444
+ if (++nontrusted > limit) return false;
445
+ }
446
+ }
447
+ return true;
448
+ }
449
+
450
+ function validateOwnedCellSlice(cells: readonly TerminalReplicaCell[]): TerminalReplicaCell[] {
451
+ if (!prefersTrustedCellValidation(cells)) return CompactCellSliceSchema.parse(cells);
452
+ const parsed = CompactOwnedCellSliceSchema.safeParse(cells);
453
+ // Preserve the original strict parser's issues on invalid owned input.
454
+ // External cells never enter this path, so their getter/copy semantics stay unchanged.
455
+ return parsed.success ? parsed.data : CompactCellSliceSchema.parse(cells);
456
+ }
307
457
 
308
458
  export function terminalSemanticUpdateNeedsCooperativeEncoding(
309
459
  input: TerminalSemanticDeliveryPayload,
@@ -387,7 +537,9 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
387
537
  for (let index = 0; index < rows.length; index++) {
388
538
  const row = rows[index]!;
389
539
  const owned = isOwnedTerminalReplicaRow(row);
390
- const validatedRow = owned && validatedOwnedEncodingRows.has(row);
540
+ const validatedRow =
541
+ owned &&
542
+ (validatedOwnedEncodingRows.has(row) || isSchemaValidProjectedTerminalReplicaRow(row));
391
543
  const { cells, ...rowHeaderInput } = row;
392
544
  const rowHeader = CompactRowHeaderSchema.parse(rowHeaderInput);
393
545
  if (++rowCount > COMPACT_MAX_ROWS || !Array.isArray(cells) || cells.length !== metadata.cols)
@@ -406,7 +558,12 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
406
558
  };
407
559
  for (let offset = 0; offset < cells.length; offset += 256) {
408
560
  const slice = cells.slice(offset, offset + 256);
409
- const validated = validatedRow ? slice : CompactCellSliceSchema.parse(slice);
561
+ const validated =
562
+ validatedRow || (owned && isValidatedTrustedBlankSlice(slice))
563
+ ? slice
564
+ : owned
565
+ ? validateOwnedCellSlice(slice)
566
+ : CompactCellSliceSchema.parse(slice);
410
567
  for (const cell of validated) {
411
568
  const encoded = compactCell(cell);
412
569
  if (prior && compactRunCellEqual(prior, encoded)) prior[0]++;
@@ -795,6 +952,7 @@ function expandPatch(value: unknown, budget: CompactDecodeBudget): TerminalRepli
795
952
  }
796
953
 
797
954
  interface CompactDecodeBudget {
955
+ verifiedSyncRows?: boolean;
798
956
  rows: number;
799
957
  runs: number;
800
958
  cells: number;
@@ -878,6 +1036,7 @@ function expandRow(
878
1036
  }
879
1037
  const runsBefore = budget.runs;
880
1038
  const cellsBefore = budget.cells;
1039
+ const builder = budget.verifiedSyncRows ? createDecodedTerminalReplicaRowBuilder() : null;
881
1040
  const cells: TerminalReplicaCell[] = [];
882
1041
  for (const valueRun of runs) {
883
1042
  if (++budget.runs > COMPACT_MAX_RUNS)
@@ -889,29 +1048,41 @@ function expandRow(
889
1048
  throw new TypeError("Compact semantic expanded cell budget exceeded");
890
1049
  const grapheme = compactString(run[1], "grapheme");
891
1050
  const width = compactInteger(run[2], 0, 2, "cell width") as 0 | 1 | 2;
892
- const cell = Object.freeze({
893
- grapheme,
894
- width,
895
- foreground: expandColor(run[3]),
896
- background: expandColor(run[4]),
897
- attributes: compactInteger(run[5], 0, 0xff, "cell attributes"),
898
- });
899
- for (let index = 0; index < count; index += 1) cells.push(cell);
1051
+ if (builder) {
1052
+ const foreground = expandScalarColor(run[3]);
1053
+ const background = expandScalarColor(run[4]);
1054
+ const attributes = compactInteger(run[5], 0, 0xff, "cell attributes");
1055
+ builder.appendRun(count, grapheme, width, foreground, background, attributes);
1056
+ } else {
1057
+ const cell = Object.freeze({
1058
+ grapheme,
1059
+ width,
1060
+ foreground: expandColor(run[3]),
1061
+ background: expandColor(run[4]),
1062
+ attributes: compactInteger(run[5], 0, 0xff, "cell attributes"),
1063
+ });
1064
+ for (let index = 0; index < count; index += 1) cells.push(cell);
1065
+ }
900
1066
  }
901
- if (cols !== null && cells.length !== cols) throw new TypeError("Compact row width mismatch");
902
- if (cells.length < 1 || cells.length > COMPACT_MAX_DIMENSION)
903
- throw new TypeError("Compact row width is out of bounds");
904
- for (let index = 0; index < cells.length; index += 1) {
905
- if (cells[index]!.width === 2 && cells[index + 1]?.width !== 0)
906
- throw new TypeError("Malformed compact wide cell");
907
- if (cells[index]!.width === 0 && (index === 0 || cells[index - 1]?.width !== 2))
908
- throw new TypeError("Malformed compact continuation cell");
1067
+ let row: TerminalReplicaRow;
1068
+ if (builder) {
1069
+ row = builder.finish(wrapped, cols);
1070
+ } else {
1071
+ if (cols !== null && cells.length !== cols) throw new TypeError("Compact row width mismatch");
1072
+ if (cells.length < 1 || cells.length > COMPACT_MAX_DIMENSION)
1073
+ throw new TypeError("Compact row width is out of bounds");
1074
+ for (let index = 0; index < cells.length; index += 1) {
1075
+ if (cells[index]!.width === 2 && cells[index + 1]?.width !== 0)
1076
+ throw new TypeError("Malformed compact wide cell");
1077
+ if (cells[index]!.width === 0 && (index === 0 || cells[index - 1]?.width !== 2))
1078
+ throw new TypeError("Malformed compact continuation cell");
1079
+ }
1080
+ row = Object.freeze({
1081
+ cells: Object.freeze(cells),
1082
+ wrapped,
1083
+ }) as unknown as TerminalReplicaRow;
909
1084
  }
910
- const row = Object.freeze({
911
- cells: Object.freeze(cells),
912
- wrapped,
913
- }) as unknown as TerminalReplicaRow;
914
- budget.allocatedCells += cells.length;
1085
+ budget.allocatedCells += row.cells.length;
915
1086
  if (cacheKey !== null)
916
1087
  budget.rowCache?.set(
917
1088
  cacheKey,
@@ -924,6 +1095,17 @@ function expandRow(
924
1095
  return row;
925
1096
  }
926
1097
 
1098
+ function expandScalarColor(value: unknown): number {
1099
+ if (value === 0) return -1;
1100
+ const pair = compactArray(value, 2, "color");
1101
+ if (pair[0] === 1) return compactInteger(pair[1], 0, 255, "indexed color");
1102
+ if (pair[0] === 2) {
1103
+ const value = compactInteger(pair[1], 0, 0xffffff, "rgb color");
1104
+ return Object.is(value, -0) ? -2 : 0x1000000 + value;
1105
+ }
1106
+ throw new TypeError("Invalid compact color kind");
1107
+ }
1108
+
927
1109
  function expandColor(value: unknown): TerminalReplicaColor {
928
1110
  if (value === 0) return COMPACT_DEFAULT_COLOR;
929
1111
  const pair = compactArray(value, 2, "color");
@@ -1238,7 +1420,8 @@ export function decodeVerifiedCompactSemanticTerminalUpdate(
1238
1420
  payload: TerminalSemanticDeliveryPayload;
1239
1421
  canonicalSnapshot: TerminalReplicaSnapshot | null;
1240
1422
  }> {
1241
- const decodeBudget = options?.onComplete ? compactDecodeBudget(bytes.byteLength) : undefined;
1423
+ const decodeBudget = compactDecodeBudget(bytes.byteLength);
1424
+ decodeBudget.verifiedSyncRows = true;
1242
1425
  const payload = decodeCompactSemanticTerminalUpdateInternal(bytes, decodeBudget);
1243
1426
  const snapshot =
1244
1427
  payload.frame === "seed"
@@ -1257,7 +1440,7 @@ export function decodeVerifiedCompactSemanticTerminalUpdate(
1257
1440
  );
1258
1441
  if (hash !== expectedHash) throw new TypeError("Canonical state hash mismatch");
1259
1442
  compactCommitCapabilities.set(payload, Object.freeze({ snapshot, hash }));
1260
- if (options?.grantReducerAdoption)
1443
+ if (options?.grantReducerAdoption) {
1261
1444
  grantCompactReplicaCapability(
1262
1445
  payload.frame === "seed"
1263
1446
  ? payload.snapshot
@@ -1267,7 +1450,13 @@ export function decodeVerifiedCompactSemanticTerminalUpdate(
1267
1450
  baseline,
1268
1451
  snapshot,
1269
1452
  hash,
1453
+ payload.frame === "tombstone"
1454
+ ? { baseRevision: payload.baseRevision, revision: payload.revision }
1455
+ : undefined,
1270
1456
  );
1457
+ if (payload.frame === "patch" && payload.revision > payload.baseRevision + 1 && snapshot)
1458
+ grantCompactReplicaCapability(snapshot, baseline, snapshot, hash);
1459
+ }
1271
1460
  if (options?.onComplete && decodeBudget) {
1272
1461
  try {
1273
1462
  options.onComplete(
@@ -1430,7 +1619,7 @@ export async function decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
1430
1619
  payload.frame === "tombstone" ? payload.tombstone.reason : "protocol",
1431
1620
  );
1432
1621
  if (hash !== expectedHash) throw new TypeError("Canonical state hash mismatch");
1433
- if (options.grantReducerAdoption)
1622
+ if (options.grantReducerAdoption) {
1434
1623
  grantCompactReplicaCapability(
1435
1624
  payload.frame === "seed"
1436
1625
  ? payload.snapshot
@@ -1440,7 +1629,13 @@ export async function decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
1440
1629
  baseline,
1441
1630
  snapshot,
1442
1631
  hash,
1632
+ payload.frame === "tombstone"
1633
+ ? { baseRevision: payload.baseRevision, revision: payload.revision }
1634
+ : undefined,
1443
1635
  );
1636
+ if (payload.frame === "patch" && payload.revision > payload.baseRevision + 1 && snapshot)
1637
+ grantCompactReplicaCapability(snapshot, baseline, snapshot, hash);
1638
+ }
1444
1639
  if (options.onComplete) {
1445
1640
  try {
1446
1641
  options.onComplete(
@@ -1498,6 +1693,7 @@ interface CompactParsedRowsSlice {
1498
1693
 
1499
1694
  class CooperativeJsonSource {
1500
1695
  readonly #bytes: Uint8Array;
1696
+ readonly #decoder = new TextDecoder("utf-8", { fatal: true });
1501
1697
  readonly length: number;
1502
1698
 
1503
1699
  constructor(bytes: Uint8Array) {
@@ -1516,7 +1712,8 @@ class CooperativeJsonSource {
1516
1712
  }
1517
1713
 
1518
1714
  slice(start: number, end: number): string {
1519
- return new TextDecoder("utf-8", { fatal: true }).decode(this.#bytes.subarray(start, end));
1715
+ // Non-streaming decode resets UTF-8/BOM state for every independent slice.
1716
+ return this.#decoder.decode(this.#bytes.subarray(start, end));
1520
1717
  }
1521
1718
 
1522
1719
  bytes(start: number, end: number): Uint8Array {
@@ -2434,7 +2631,12 @@ export function decodeVerifiedLegacySemanticTerminalUpdate(
2434
2631
  baseline,
2435
2632
  snapshot,
2436
2633
  hash,
2634
+ payload.frame === "tombstone"
2635
+ ? { baseRevision: payload.baseRevision, revision: payload.revision }
2636
+ : undefined,
2437
2637
  );
2638
+ if (payload.frame === "patch" && payload.revision > payload.baseRevision + 1 && snapshot)
2639
+ grantCompactReplicaCapability(snapshot, baseline, snapshot, hash);
2438
2640
  return Object.freeze({ payload, canonicalSnapshot: snapshot });
2439
2641
  }
2440
2642