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.
- package/LICENSE +1 -1
- package/README.md +120 -139
- package/bin/cli.js +4847 -3585
- package/package.json +16 -9
- package/packages/contracts/src/session-runtime.ts +2 -0
- package/packages/contracts/src/terminal-delivery.ts +4 -0
- package/packages/core/src/terminal-compact-capability.ts +31 -1
- package/packages/core/src/terminal-delivery.ts +234 -32
- package/packages/core/src/terminal-replica-hash-cache.ts +162 -21
- package/packages/core/src/terminal-replica-owned-row.ts +165 -10
- package/packages/core/src/terminal-replica.ts +43 -21
- package/packages/daemon/dist/lib/daemon-embed.js +19 -34
- package/packages/daemon/dist/lib/runtime-trace-writer.js +133 -0
- package/packages/daemon/dist/lib/tmux-client-execution.js +37 -2
- package/packages/daemon/dist/lib/tmux-server-owner.js +20 -4
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
- package/packages/daemon/dist/native/tmux/darwin-arm64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/darwin-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/darwin-x64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/darwin-x64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-arm64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/licenses/libevent_core-2.1.so.7.txt +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-x64/tmux +0 -0
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +6 -6
- package/packages/daemon/dist/terminal/mirror/control-channel.js +379 -75
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +43 -3
- package/packages/daemon/dist/terminal/mirror/native-grid-projection.js +20 -11
- package/packages/daemon/dist/terminal/mirror/session-channel.js +639 -811
- package/packages/daemon/dist/terminal/mirror/stock-pane-snapshot.js +177 -0
- package/packages/daemon/dist/terminal/session-runtime/causal-cell-ledger.js +1 -1
- package/packages/daemon/dist/terminal/session-runtime/registry.js +145 -15
- package/packages/daemon/dist/terminal/session-runtime/stock-capture-fidelity.js +12 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +128 -10
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +21 -4
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +19 -2
- package/packages/daemon/dist/terminal/session-runtime/xterm-terminal-interpreter-backend.js +17 -19
- package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +64 -4
- package/packages/daemon/dist/tui/mirror/pane-surface.jsx +10 -1
- package/packages/daemon/dist/tui/mirror/reference-performance-trace.js +117 -71
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +27 -3
- package/packages/daemon/dist/tui/mirror/runtime/pane-scoped-terminal-surface.jsx +17 -4
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +19 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-input-adapter.js +10 -2
- package/packages/daemon/dist/tui/mirror/testing/styled-frame-oracle.js +156 -0
- package/packages/daemon/src/lib/daemon-embed.ts +22 -34
- package/packages/daemon/src/lib/runtime-trace-writer.ts +132 -0
- package/packages/daemon/src/lib/tmux-client-execution.ts +47 -2
- package/packages/daemon/src/lib/tmux-server-owner.ts +19 -3
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +6 -0
- package/packages/daemon/src/terminal/mirror/control-channel.ts +428 -84
- package/packages/daemon/src/terminal/mirror/events.ts +7 -7
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +49 -3
- package/packages/daemon/src/terminal/mirror/native-grid-projection.ts +21 -11
- package/packages/daemon/src/terminal/mirror/pane-feed.ts +9 -4
- package/packages/daemon/src/terminal/mirror/session-channel.ts +705 -859
- package/packages/daemon/src/terminal/mirror/stock-pane-snapshot.ts +228 -0
- package/packages/daemon/src/terminal/session-runtime/causal-cell-ledger.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/registry.ts +203 -32
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +7 -0
- package/packages/daemon/src/terminal/session-runtime/stock-capture-fidelity.ts +13 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +142 -8
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +26 -3
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +25 -1
- package/packages/daemon/src/terminal/session-runtime/xterm-terminal-interpreter-backend.ts +26 -20
- package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +63 -4
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -1
- package/packages/daemon/src/tui/mirror/reference-performance-trace.ts +141 -90
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +27 -4
- package/packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface.tsx +61 -31
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +31 -3
- package/packages/daemon/src/tui/mirror/runtime/terminal-input-adapter.ts +9 -6
- package/packages/daemon/src/tui/mirror/testing/styled-frame-oracle.ts +209 -0
- package/packages/daemon-client/src/terminal-fast-lane.ts +53 -4
- package/packages/daemon-client/src/workspace-client-types.ts +1 -0
- package/packages/daemon-client/src/workspace-client.ts +78 -5
- package/packages/tmux-bridge/src/index.ts +1 -0
- package/packages/tmux-bridge/src/runner.ts +25 -1
- package/skill/SKILL.md +30 -30
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { StockCaptureTabUnavailableError } from "./stock-capture-fidelity.js";
|
|
1
2
|
import { MAX_RETAINED_NATIVE_BACKING_BYTES, takeNativeSeedBacking, } from "./native-seed-backing.js";
|
|
2
3
|
import { TERMINAL_DELIVERY_PATCH_TO_SEED_BYTES, TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES, TerminalDeliveryEnvelopeSchemaZ, TerminalDeliveryAckSchemaZ, TerminalDeliveryNackSchemaZ, TerminalDeliveryOfferSchemaZ, TerminalDeliveryVisibilitySchemaZ, } from "@tmux-ide/contracts";
|
|
3
|
-
import { TerminalDeliveryStateTooLargeError, applyTerminalReplicaUpdate, applyTerminalReplicaUpdateCooperatively, terminalReplicaUpdateNeedsCooperativeReduction, encodeCompactSemanticTerminalUpdate, encodeCompactSemanticTerminalUpdateCooperatively, terminalSemanticUpdateNeedsCooperativeEncoding, encodeAnsiTerminalRepresentation, encodeSemanticTerminalUpdate, hashTerminalDeliveryRepresentation, negotiateTerminalDelivery, preaccountSemanticTerminalUpdateBytes, } from "@tmux-ide/core";
|
|
4
|
+
import { TerminalDeliveryStateTooLargeError, terminalReplicaRowsEqual, TERMINAL_COMPACT_COOPERATIVE_SEED_CELLS, applyTerminalReplicaUpdate, applyTerminalReplicaUpdateCooperatively, terminalReplicaUpdateNeedsCooperativeReduction, encodeCompactSemanticTerminalUpdate, encodeCompactSemanticTerminalUpdateCooperatively, terminalSemanticUpdateNeedsCooperativeEncoding, encodeAnsiTerminalRepresentation, encodeSemanticTerminalUpdate, hashTerminalDeliveryRepresentation, negotiateTerminalDelivery, preaccountSemanticTerminalUpdateBytes, } from "@tmux-ide/core";
|
|
4
5
|
import { SYSTEM_SESSION_RUNTIME_SCHEDULER, } from "./runtime-scheduler.js";
|
|
5
6
|
import { DISABLED_SESSION_RUNTIME_OBSERVABILITY, } from "./runtime-observability.js";
|
|
6
7
|
import { registerTerminalDeliveryObservationOrdinal } from "./terminal-delivery-observation-identity.js";
|
|
@@ -64,6 +65,8 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
64
65
|
#observability;
|
|
65
66
|
#panes = new Map();
|
|
66
67
|
#clients = new Map();
|
|
68
|
+
#sourceCloseFailures = 0;
|
|
69
|
+
#pendingSourceCloses = 0;
|
|
67
70
|
/** Synchronous reservations held while an async pane source is starting. */
|
|
68
71
|
#pendingClients = new Map();
|
|
69
72
|
#cache = new Map();
|
|
@@ -140,6 +143,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
140
143
|
outgoing: [],
|
|
141
144
|
sending: false,
|
|
142
145
|
retireAfterDrain: false,
|
|
146
|
+
captureUnavailable: false,
|
|
143
147
|
lastAck: null,
|
|
144
148
|
sourceClosedFlight: null,
|
|
145
149
|
backgroundTimer: null,
|
|
@@ -166,10 +170,37 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
166
170
|
close: async () => this.#closeClient(client),
|
|
167
171
|
};
|
|
168
172
|
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (!(error instanceof StockCaptureTabUnavailableError))
|
|
175
|
+
throw error;
|
|
176
|
+
// Negotiation still settles this pane; no invented canonical seed is published.
|
|
177
|
+
await accept({
|
|
178
|
+
type: "terminal.delivery.fault",
|
|
179
|
+
reason: "source-closed",
|
|
180
|
+
message: error.message,
|
|
181
|
+
deliveryNonce: negotiation.negotiated.deliveryNonce,
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
negotiation,
|
|
185
|
+
closed: Promise.resolve("closed"),
|
|
186
|
+
ack: () => { },
|
|
187
|
+
nack: () => { },
|
|
188
|
+
setVisibility: () => { },
|
|
189
|
+
close: async () => { },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
169
192
|
finally {
|
|
170
193
|
this.#pendingClients.delete(key);
|
|
171
194
|
}
|
|
172
195
|
}
|
|
196
|
+
/** Retire only this pane's delivery owners before its canonical owner is disposed. */
|
|
197
|
+
failPaneCapture(semanticPaneId, error) {
|
|
198
|
+
for (const client of this.#clients.values())
|
|
199
|
+
if (client.paneId === semanticPaneId && !client.closed && !client.captureUnavailable) {
|
|
200
|
+
client.captureUnavailable = true;
|
|
201
|
+
this.#fault(client, "source-closed", error.message);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
173
204
|
retainedNativeBacking(paneId, expected) {
|
|
174
205
|
const pane = this.#panes.get(paneId);
|
|
175
206
|
const record = pane?.revisions.get(expected.revision);
|
|
@@ -230,6 +261,12 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
230
261
|
return Object.freeze({
|
|
231
262
|
clients: new Set([...this.#clients.values()].map((client) => client.clientId)).size,
|
|
232
263
|
connections: this.#clients.size,
|
|
264
|
+
sourceSubscriptions: [...this.#panes.values()].filter((pane) => pane.source !== null).length,
|
|
265
|
+
pendingSourceSubscriptions: [...this.#panes.values()].filter((pane) => pane.source === null)
|
|
266
|
+
.length,
|
|
267
|
+
pendingClients: this.#pendingClients.size,
|
|
268
|
+
sourceCloseFailures: this.#sourceCloseFailures,
|
|
269
|
+
pendingSourceCloses: this.#pendingSourceCloses,
|
|
233
270
|
inFlight: [...this.#clients.values()].filter((client) => client.inFlight).length,
|
|
234
271
|
latestPointers: [...this.#clients.values()].filter((client) => client.latestRevision !== null)
|
|
235
272
|
.length,
|
|
@@ -293,9 +330,22 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
293
330
|
pane.pendingCanonicalCells = 0;
|
|
294
331
|
pane.canonicalScheduled = false;
|
|
295
332
|
}
|
|
296
|
-
await Promise.allSettled(panes.map((pane) => pane.source
|
|
333
|
+
await Promise.allSettled(panes.map((pane) => pane.source && this.#closeSource(pane.source)));
|
|
297
334
|
this.#clearCache();
|
|
298
335
|
}
|
|
336
|
+
async #closeSource(source) {
|
|
337
|
+
this.#pendingSourceCloses += 1;
|
|
338
|
+
try {
|
|
339
|
+
await source.close();
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
this.#sourceCloseFailures += 1;
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
this.#pendingSourceCloses -= 1;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
299
349
|
async close() {
|
|
300
350
|
if (this.#closed)
|
|
301
351
|
return;
|
|
@@ -346,7 +396,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
346
396
|
if (this.#closed ||
|
|
347
397
|
this.#panes.get(semanticPaneId) !== pane ||
|
|
348
398
|
pane?.canonicalAbort.signal.aborted) {
|
|
349
|
-
await source
|
|
399
|
+
await this.#closeSource(source);
|
|
350
400
|
throw new Error("Terminal delivery source retired during startup");
|
|
351
401
|
}
|
|
352
402
|
pane.source = source;
|
|
@@ -510,7 +560,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
510
560
|
pane.revisions.set(update.revision, record);
|
|
511
561
|
this.#pruneCanonicalRevisions(semanticPaneId);
|
|
512
562
|
for (const client of this.#clients.values()) {
|
|
513
|
-
if (client.paneId !== semanticPaneId || client.closed)
|
|
563
|
+
if (client.paneId !== semanticPaneId || client.closed || client.captureUnavailable)
|
|
514
564
|
continue;
|
|
515
565
|
if (!client.lifecycleOpenRecorded)
|
|
516
566
|
client.lifecycleOpenRecorded = this.#recordDeliveryLifecycle(client, pane, "open");
|
|
@@ -540,7 +590,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
540
590
|
pane.pendingCanonical.length = 0;
|
|
541
591
|
pane.pendingCanonicalCells = 0;
|
|
542
592
|
pane.canonicalScheduled = false;
|
|
543
|
-
void pane.source
|
|
593
|
+
void (pane.source && this.#closeSource(pane.source).catch(() => undefined));
|
|
544
594
|
this.#clearCache();
|
|
545
595
|
}, 0);
|
|
546
596
|
}
|
|
@@ -581,6 +631,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
581
631
|
}
|
|
582
632
|
#schedule(client) {
|
|
583
633
|
if (client.closed ||
|
|
634
|
+
client.captureUnavailable ||
|
|
584
635
|
client.inFlight ||
|
|
585
636
|
client.encoding ||
|
|
586
637
|
client.latestRevision === null ||
|
|
@@ -613,6 +664,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
613
664
|
if (!pane ||
|
|
614
665
|
!target ||
|
|
615
666
|
client.closed ||
|
|
667
|
+
client.captureUnavailable ||
|
|
616
668
|
client.inFlight ||
|
|
617
669
|
client.encoding ||
|
|
618
670
|
client.visibility === "hidden" ||
|
|
@@ -936,7 +988,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
936
988
|
this.#reseeds += 1;
|
|
937
989
|
}
|
|
938
990
|
else {
|
|
939
|
-
const patchPayload = semanticPayload(client.baselineRevision, baseline, target);
|
|
991
|
+
const patchPayload = semanticPayload(client.baselineRevision, baseline, target, pane.revisions.get(client.baselineRevision)?.update.incarnation);
|
|
940
992
|
const seedPayload = patchPayload.frame === "tombstone" ? null : semanticSeed(target);
|
|
941
993
|
const legacyAttempts = null;
|
|
942
994
|
if (patchPayload.frame !== "patch") {
|
|
@@ -1205,6 +1257,20 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1205
1257
|
}
|
|
1206
1258
|
#nack(client, input) {
|
|
1207
1259
|
const nack = TerminalDeliveryNackSchemaZ.parse(input);
|
|
1260
|
+
const closed = client.sourceClosedFlight;
|
|
1261
|
+
// A decoder may have sent this NACK before observing the pane-local fault.
|
|
1262
|
+
// Only the exact displaced transaction is inert; unrelated feedback still
|
|
1263
|
+
// follows the normal protocol-violation path below.
|
|
1264
|
+
if (client.captureUnavailable &&
|
|
1265
|
+
!client.inFlight &&
|
|
1266
|
+
closed &&
|
|
1267
|
+
nack.workspaceName === closed.workspaceName &&
|
|
1268
|
+
nack.semanticPaneId === closed.semanticPaneId &&
|
|
1269
|
+
nack.generation === closed.generation &&
|
|
1270
|
+
nack.incarnation === closed.incarnation &&
|
|
1271
|
+
nack.deliveryNonce === closed.deliveryNonce &&
|
|
1272
|
+
nack.transactionId === closed.transactionId)
|
|
1273
|
+
return;
|
|
1208
1274
|
const envelope = client.inFlight?.envelope;
|
|
1209
1275
|
if (!envelope ||
|
|
1210
1276
|
nack.workspaceName !== envelope.workspaceName ||
|
|
@@ -1350,7 +1416,8 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1350
1416
|
pane.pendingCanonicalCells = 0;
|
|
1351
1417
|
pane.canonicalScheduled = false;
|
|
1352
1418
|
}
|
|
1353
|
-
|
|
1419
|
+
if (pane?.source)
|
|
1420
|
+
await this.#closeSource(pane.source).catch(() => undefined);
|
|
1354
1421
|
this.#clearCache();
|
|
1355
1422
|
}
|
|
1356
1423
|
#recordDeliveryLifecycle(client, pane, event) {
|
|
@@ -1689,7 +1756,7 @@ function semanticSelectionObservationFromError(error) {
|
|
|
1689
1756
|
? error.selectionObservation
|
|
1690
1757
|
: null;
|
|
1691
1758
|
}
|
|
1692
|
-
function semanticPayload(baselineRevision, baseline, target) {
|
|
1759
|
+
function semanticPayload(baselineRevision, baseline, target, baselineIncarnation) {
|
|
1693
1760
|
const update = target.update;
|
|
1694
1761
|
if (!target.state.snapshot) {
|
|
1695
1762
|
if (update.type !== "terminal.tombstone")
|
|
@@ -1701,8 +1768,8 @@ function semanticPayload(baselineRevision, baseline, target) {
|
|
|
1701
1768
|
tombstone: update.tombstone,
|
|
1702
1769
|
};
|
|
1703
1770
|
}
|
|
1704
|
-
// Adjacent canonical patches are already the cheapest exact diff.
|
|
1705
|
-
//
|
|
1771
|
+
// Adjacent canonical patches are already the cheapest exact diff. Preserve
|
|
1772
|
+
// this direct path before considering an exact coalesced snapshot patch.
|
|
1706
1773
|
if (baseline && update.type === "terminal.patch" && update.baseRevision === baselineRevision)
|
|
1707
1774
|
return {
|
|
1708
1775
|
frame: "patch",
|
|
@@ -1710,6 +1777,57 @@ function semanticPayload(baselineRevision, baseline, target) {
|
|
|
1710
1777
|
revision: update.revision,
|
|
1711
1778
|
patch: update.patch,
|
|
1712
1779
|
};
|
|
1780
|
+
// A skipped revision may still have an exact small representation. Only
|
|
1781
|
+
// append-only history is proved here; trim/reflow and changed dimensions
|
|
1782
|
+
// retain the full seed boundary. Equality includes wrapped and all cells.
|
|
1783
|
+
const snapshot = target.state.snapshot;
|
|
1784
|
+
if (baseline &&
|
|
1785
|
+
baselineIncarnation === update.incarnation &&
|
|
1786
|
+
baseline.cols === snapshot.cols &&
|
|
1787
|
+
baseline.rows === snapshot.rows &&
|
|
1788
|
+
baseline.history.length <= snapshot.history.length) {
|
|
1789
|
+
// Identity reuse is cheap, but detached equal histories must not turn a
|
|
1790
|
+
// cooperative seed into an unbounded synchronous cell comparison.
|
|
1791
|
+
let comparisonCells = 0;
|
|
1792
|
+
const equal = (left, right) => {
|
|
1793
|
+
if (left === right)
|
|
1794
|
+
return true;
|
|
1795
|
+
comparisonCells += right.cells.length;
|
|
1796
|
+
return comparisonCells <= TERMINAL_COMPACT_COOPERATIVE_SEED_CELLS
|
|
1797
|
+
? terminalReplicaRowsEqual(left, right)
|
|
1798
|
+
: null;
|
|
1799
|
+
};
|
|
1800
|
+
for (let index = 0; index < baseline.history.length; index++)
|
|
1801
|
+
if (equal(baseline.history[index], snapshot.history[index]) !== true)
|
|
1802
|
+
return semanticSeed(target);
|
|
1803
|
+
const rows = [];
|
|
1804
|
+
for (let index = 0; index < snapshot.grid.length; index++) {
|
|
1805
|
+
const same = equal(baseline.grid[index], snapshot.grid[index]);
|
|
1806
|
+
if (same === null)
|
|
1807
|
+
return semanticSeed(target);
|
|
1808
|
+
if (!same)
|
|
1809
|
+
rows.push({ index, row: snapshot.grid[index] });
|
|
1810
|
+
}
|
|
1811
|
+
const appendedRows = snapshot.history.length - baseline.history.length;
|
|
1812
|
+
// Compact patches serialize synchronously. Do not move a large appended
|
|
1813
|
+
// suffix or repaint across the seed-only cooperative encoding boundary.
|
|
1814
|
+
if ((rows.length + appendedRows) * snapshot.cols >= TERMINAL_COMPACT_COOPERATIVE_SEED_CELLS)
|
|
1815
|
+
return semanticSeed(target);
|
|
1816
|
+
const append = snapshot.history.slice(baseline.history.length);
|
|
1817
|
+
return {
|
|
1818
|
+
frame: "patch",
|
|
1819
|
+
baseRevision: baselineRevision,
|
|
1820
|
+
revision: update.revision,
|
|
1821
|
+
patch: {
|
|
1822
|
+
rows,
|
|
1823
|
+
...(append.length ? { historyDelta: { trim: 0, append } } : {}),
|
|
1824
|
+
cursor: snapshot.cursor,
|
|
1825
|
+
modes: snapshot.modes,
|
|
1826
|
+
placements: snapshot.placements,
|
|
1827
|
+
bootstrap: snapshot.bootstrap,
|
|
1828
|
+
},
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1713
1831
|
return semanticSeed(target);
|
|
1714
1832
|
}
|
|
1715
1833
|
/** Account represented cells without traversing/serializing the snapshot. */
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { assertStockCaptureRepresentable } from "./stock-capture-fidelity.js";
|
|
1
2
|
import { rememberNativeSeedBacking } from "./native-seed-backing.js";
|
|
2
3
|
import { detectWidgetMarkerFromReplicaRows, } from "@tmux-ide/contracts";
|
|
3
4
|
import { applyTerminalReplicaPatch, assembleTerminalReplicaSnapshot, blankTerminalReplicaSnapshot, hashTerminalReplicaSnapshot, hashTerminalReplicaTombstone, hashTerminalWidgetContent, terminalReplicaRowsEqual, } from "@tmux-ide/core";
|
|
4
5
|
import { SYSTEM_SESSION_RUNTIME_SCHEDULER, } from "./runtime-scheduler.js";
|
|
5
6
|
import { DISABLED_SESSION_RUNTIME_OBSERVABILITY, } from "./runtime-observability.js";
|
|
6
7
|
import { createXtermTerminalInterpreterBackend } from "./xterm-terminal-interpreter-backend.js";
|
|
7
|
-
import { CAUSAL_CELL_OSC, CausalCellLedger, } from "./causal-cell-ledger.js";
|
|
8
|
+
import { CAUSAL_CELL_OSC, CausalCellLedger, cellsEqual, } from "./causal-cell-ledger.js";
|
|
8
9
|
/** Daemon-owned, sans-I/O VT interpreter with one FIFO for bytes and geometry. */
|
|
9
10
|
export class TerminalReplicaInterpreter {
|
|
10
11
|
#nativeSeedBackingCandidate;
|
|
@@ -110,7 +111,13 @@ export class TerminalReplicaInterpreter {
|
|
|
110
111
|
}
|
|
111
112
|
this.#flushWrites();
|
|
112
113
|
const admitted = operation.type === "reseed"
|
|
113
|
-
? {
|
|
114
|
+
? {
|
|
115
|
+
...operation,
|
|
116
|
+
chunks: operation.chunks.map((chunk) => Uint8Array.from(chunk)),
|
|
117
|
+
...(operation.captureChunks
|
|
118
|
+
? { captureChunks: operation.captureChunks.map((chunk) => Uint8Array.from(chunk)) }
|
|
119
|
+
: {}),
|
|
120
|
+
}
|
|
114
121
|
: operation;
|
|
115
122
|
return this.#append(admitted);
|
|
116
123
|
}
|
|
@@ -141,8 +148,7 @@ export class TerminalReplicaInterpreter {
|
|
|
141
148
|
seed.incarnation !== probe.incarnation ||
|
|
142
149
|
this.#snapshot.cols !== probe.geometry.cols ||
|
|
143
150
|
this.#snapshot.rows !== probe.geometry.rows ||
|
|
144
|
-
|
|
145
|
-
JSON.stringify(probe.before))
|
|
151
|
+
!cellsEqual(this.#snapshot.grid[probe.geometry.row]?.cells[probe.geometry.column], probe.before))
|
|
146
152
|
throw new Error("Causal-cell baseline drifted before admission");
|
|
147
153
|
const ledger = new CausalCellLedger({
|
|
148
154
|
probe,
|
|
@@ -192,6 +198,8 @@ export class TerminalReplicaInterpreter {
|
|
|
192
198
|
if (this.#closed)
|
|
193
199
|
return;
|
|
194
200
|
if (operation.type === "reseed") {
|
|
201
|
+
if (operation.bootstrap === "painted-capture" && !operation.native)
|
|
202
|
+
assertStockCaptureRepresentable(operation.captureChunks ?? operation.chunks);
|
|
195
203
|
this.#causalCell?.fail("reseeded");
|
|
196
204
|
const nativeCols = operation.nativeCols ?? operation.cols;
|
|
197
205
|
const nativeRows = operation.nativeRows ?? operation.rows;
|
|
@@ -241,6 +249,15 @@ export class TerminalReplicaInterpreter {
|
|
|
241
249
|
try {
|
|
242
250
|
this.#commit(true, undefined, operation.trace ?? null);
|
|
243
251
|
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
try {
|
|
254
|
+
previous.dispose();
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Preserve the commit failure; replacement remains owned until close.
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
244
261
|
finally {
|
|
245
262
|
this.#nativeSeedBackingCandidate = undefined;
|
|
246
263
|
}
|
|
@@ -28,6 +28,7 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
28
28
|
#reseedRetryCount = 0;
|
|
29
29
|
#reseed = null;
|
|
30
30
|
#bootstrapped = false;
|
|
31
|
+
#publishedIncarnation = null;
|
|
31
32
|
#waitingForGeometryCapture = false;
|
|
32
33
|
#historyTimer = null;
|
|
33
34
|
#historyChecking = false;
|
|
@@ -61,6 +62,7 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
61
62
|
}));
|
|
62
63
|
},
|
|
63
64
|
onUpdate: (update, trace) => {
|
|
65
|
+
this.#publishedIncarnation = update.incarnation;
|
|
64
66
|
if (update.type === "terminal.seed") {
|
|
65
67
|
this.#bootstrapped = true;
|
|
66
68
|
this.#reseedRetryCount = 0;
|
|
@@ -135,6 +137,10 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
135
137
|
failCausalCell(reason, traceId) {
|
|
136
138
|
this.#interpreter.failCausalCell(reason, traceId);
|
|
137
139
|
}
|
|
140
|
+
/** Scalar diagnostic only: no seed construction, hashing, or owner creation. */
|
|
141
|
+
inputDispatchIncarnation() {
|
|
142
|
+
return this.#disposed ? null : this.#publishedIncarnation;
|
|
143
|
+
}
|
|
138
144
|
qualificationSnapshot() {
|
|
139
145
|
const seed = this.#interpreter.currentSeed();
|
|
140
146
|
return Object.freeze({
|
|
@@ -142,6 +148,11 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
142
148
|
revision: seed?.revision ?? null,
|
|
143
149
|
stateHash: seed?.stateHash ?? null,
|
|
144
150
|
stats: this.#interpreter.stats(),
|
|
151
|
+
listeners: Object.freeze({
|
|
152
|
+
canonical: this.#listeners.size,
|
|
153
|
+
raw: this.#rawListeners.size,
|
|
154
|
+
upstream: this.#upstream ? 1 : 0,
|
|
155
|
+
}),
|
|
145
156
|
});
|
|
146
157
|
}
|
|
147
158
|
async captureNativeBacking() {
|
|
@@ -325,6 +336,7 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
325
336
|
layoutLease: this.#layoutLease,
|
|
326
337
|
subscriptionEpoch: this.#subscriptionEpoch,
|
|
327
338
|
chunks: [],
|
|
339
|
+
captureChunks: [],
|
|
328
340
|
trace: this.#consumeOutputTrace(),
|
|
329
341
|
};
|
|
330
342
|
}
|
|
@@ -336,8 +348,12 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
336
348
|
this.#reseed.requiresNativeRecapture = true;
|
|
337
349
|
if (event.type === "seed" && event.native)
|
|
338
350
|
this.#reseed.native = event.native;
|
|
339
|
-
else
|
|
340
|
-
|
|
351
|
+
else {
|
|
352
|
+
const bytes = Uint8Array.from(event.data);
|
|
353
|
+
this.#reseed.chunks.push(bytes);
|
|
354
|
+
if (event.type === "seed")
|
|
355
|
+
this.#reseed.captureChunks.push(bytes);
|
|
356
|
+
}
|
|
341
357
|
}
|
|
342
358
|
else
|
|
343
359
|
this.#supervise(this.#interpreter.enqueue({
|
|
@@ -359,6 +375,7 @@ export class SessionRuntimeTerminalReplicaOwner {
|
|
|
359
375
|
cols: reseed.nativeCols,
|
|
360
376
|
rows: reseed.nativeRows,
|
|
361
377
|
chunks: reseed.chunks,
|
|
378
|
+
captureChunks: reseed.captureChunks,
|
|
362
379
|
native: reseed.native,
|
|
363
380
|
historyLimit: event.historyLimit,
|
|
364
381
|
historySize: event.historySize,
|
|
@@ -2,7 +2,7 @@ import { isNativeBootstrapCapture } from "../mirror/native-grid-capture.js";
|
|
|
2
2
|
import { projectNativeGridRow } from "../mirror/native-grid-projection.js";
|
|
3
3
|
import { Terminal } from "@tmux-ide/xterm-headless";
|
|
4
4
|
import { Unicode11Addon } from "@xterm/addon-unicode11";
|
|
5
|
-
import {
|
|
5
|
+
import { createProjectedTerminalReplicaRowBuilder } from "@tmux-ide/core";
|
|
6
6
|
/** Pinned xterm 6 adapter and the rollback oracle for native backends. */
|
|
7
7
|
export class XtermTerminalInterpreterBackend {
|
|
8
8
|
kind = "xterm";
|
|
@@ -429,21 +429,20 @@ function projectRowCached(cache, buffer, index, cols) {
|
|
|
429
429
|
return prior.row;
|
|
430
430
|
}
|
|
431
431
|
const cell = buffer.getNullCell();
|
|
432
|
-
const
|
|
432
|
+
const builder = createProjectedTerminalReplicaRowBuilder();
|
|
433
433
|
for (let column = 0; column < cols; column += 1) {
|
|
434
434
|
line?.getCell(column, cell);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
});
|
|
435
|
+
// Compute the same clipped cell values before selecting trusted blanks.
|
|
436
|
+
const grapheme = line && column + cell.getWidth() <= cols ? cell.getChars() : "";
|
|
437
|
+
const width = line && column + cell.getWidth() <= cols ? cell.getWidth() : 1;
|
|
438
|
+
const foregroundKind = line ? cellColorKind(cell, "foreground") : "default";
|
|
439
|
+
const foregroundValue = line ? cell.getFgColor() : 0;
|
|
440
|
+
const backgroundKind = line ? cellColorKind(cell, "background") : "default";
|
|
441
|
+
const backgroundValue = line ? cell.getBgColor() : 0;
|
|
442
|
+
const attributes = line ? cellAttributes(cell) : 0;
|
|
443
|
+
builder.append(grapheme, width, attributes, foregroundKind, foregroundValue, backgroundKind, backgroundValue);
|
|
445
444
|
}
|
|
446
|
-
const row =
|
|
445
|
+
const row = builder.finish(line?.isWrapped ?? false);
|
|
447
446
|
if (line && cacheKey)
|
|
448
447
|
cache.set(cacheKey, {
|
|
449
448
|
data: lineData(line)?.slice() ?? null,
|
|
@@ -474,15 +473,14 @@ function rawRowsEqual(left, right) {
|
|
|
474
473
|
return false;
|
|
475
474
|
return true;
|
|
476
475
|
}
|
|
477
|
-
function
|
|
476
|
+
function cellColorKind(cell, channel) {
|
|
478
477
|
const rgb = channel === "foreground" ? cell.isFgRGB() : cell.isBgRGB();
|
|
479
478
|
const palette = channel === "foreground" ? cell.isFgPalette() : cell.isBgPalette();
|
|
480
|
-
const value = channel === "foreground" ? cell.getFgColor() : cell.getBgColor();
|
|
481
479
|
if (rgb)
|
|
482
|
-
return
|
|
480
|
+
return "rgb";
|
|
483
481
|
if (palette)
|
|
484
|
-
return
|
|
485
|
-
return
|
|
482
|
+
return "indexed";
|
|
483
|
+
return "default";
|
|
486
484
|
}
|
|
487
485
|
function cellAttributes(cell) {
|
|
488
486
|
return ((cell.isBold() ? 1 : 0) |
|
|
@@ -494,7 +492,7 @@ function cellAttributes(cell) {
|
|
|
494
492
|
(cell.isInvisible() ? 64 : 0) |
|
|
495
493
|
(cell.isStrikethrough() ? 128 : 0));
|
|
496
494
|
}
|
|
497
|
-
/** Inverse of this pinned adapter's
|
|
495
|
+
/** Inverse of this pinned adapter's cellColorKind/cellAttributes projection. */
|
|
498
496
|
function nativeImportAttributes(cell) {
|
|
499
497
|
const color = (value) => value.kind === "default"
|
|
500
498
|
? 0
|