tmux-ide 2.9.6 → 2.9.7
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/bin/cli.js +2597 -1322
- package/package.json +1 -1
- package/packages/contracts/src/index.ts +5 -0
- package/packages/contracts/src/pane-stream.ts +37 -0
- package/packages/contracts/src/terminal-graphics.ts +92 -0
- package/packages/contracts/src/terminal-history-anchor.ts +61 -0
- package/packages/contracts/src/tmux-server-diagnostics.ts +16 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/kitty-placeholders.ts +190 -0
- package/packages/daemon/dist/command-center/tmux-server-diagnostics.js +24 -0
- package/packages/daemon/dist/command-center/tmux-server-history-anchor.js +48 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +11 -0
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +23 -1
- package/packages/daemon/dist/lib/daemon-embed.js +21 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +10 -1
- package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +2 -1
- 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/lib/libevent_core-2.1.7.dylib +0 -0
- package/packages/daemon/dist/native/tmux/darwin-arm64/lib/libutf8proc.3.3.0.dylib +0 -0
- package/packages/daemon/dist/native/tmux/darwin-arm64/manifest.json +8 -4
- package/packages/daemon/dist/native/tmux/darwin-arm64/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/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-x64/tmux +0 -0
- package/packages/daemon/dist/terminal/graphics/kitty-graphics.js +578 -0
- package/packages/daemon/dist/terminal/mirror/control-channel.js +17 -1
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +1 -0
- package/packages/daemon/dist/terminal/mirror/native-grid-capture.js +8 -0
- package/packages/daemon/dist/terminal/mirror/native-grid-reader.js +6 -1
- package/packages/daemon/dist/terminal/mirror/session-channel.js +11 -2
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +78 -1
- package/packages/daemon/dist/terminal/protocol/session-descriptor-discovery.js +3 -2
- package/packages/daemon/dist/terminal/session-runtime/history-anchor-owner.js +171 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +20 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +20 -1
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +68 -1
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +30 -0
- package/packages/daemon/dist/terminal/session-runtime/transport-binding.js +49 -21
- package/packages/daemon/dist/terminal/session-runtime/xterm-terminal-interpreter-backend.js +11 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +7 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +35 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-paste.js +2 -2
- package/packages/daemon/dist/tui/mirror/runtime/terminal-input-adapter.js +2 -2
- package/packages/daemon/src/command-center/tmux-server-diagnostics.ts +38 -0
- package/packages/daemon/src/command-center/tmux-server-history-anchor.ts +69 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +11 -0
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +20 -2
- package/packages/daemon/src/lib/daemon-embed.ts +26 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +10 -0
- package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +2 -1
- package/packages/daemon/src/terminal/graphics/kitty-graphics.ts +641 -0
- package/packages/daemon/src/terminal/mirror/control-channel.ts +18 -1
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +3 -0
- package/packages/daemon/src/terminal/mirror/native-grid-capture.ts +11 -0
- package/packages/daemon/src/terminal/mirror/native-grid-reader.ts +10 -1
- package/packages/daemon/src/terminal/mirror/session-channel.ts +14 -1
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +82 -0
- package/packages/daemon/src/terminal/protocol/session-descriptor-discovery.ts +3 -2
- package/packages/daemon/src/terminal/session-runtime/history-anchor-owner.ts +190 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +20 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +38 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-interpreter-backend.ts +4 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +83 -1
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +39 -0
- package/packages/daemon/src/terminal/session-runtime/transport-binding.ts +53 -24
- package/packages/daemon/src/terminal/session-runtime/xterm-terminal-interpreter-backend.ts +13 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +6 -1
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +44 -9
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-paste.ts +2 -1
- package/packages/daemon/src/tui/mirror/runtime/terminal-input-adapter.ts +5 -2
- package/packages/daemon-client/src/fleet-connection-status.ts +1 -1
- package/packages/daemon-client/src/pane-creation-refusal.ts +48 -0
- package/packages/daemon-client/src/pane-stream-client.ts +29 -0
- package/packages/daemon-client/src/tmux-server-client.ts +133 -6
- package/packages/daemon/dist/native/tmux/darwin-arm64/lib/libutf8proc.3.2.3.dylib +0 -0
- /package/packages/daemon/dist/native/tmux/darwin-arm64/licenses/{libutf8proc.3.2.3.dylib.txt → libutf8proc.3.3.0.dylib.txt} +0 -0
package/package.json
CHANGED
|
@@ -49,6 +49,7 @@ export * from "./semantic-identity.ts";
|
|
|
49
49
|
export * from "./session-runtime.ts";
|
|
50
50
|
export * from "./terminal-replica.ts";
|
|
51
51
|
export * from "./terminal-delivery.ts";
|
|
52
|
+
export * from "./terminal-graphics.ts";
|
|
52
53
|
export * from "./terminal-runtime-inventory.ts";
|
|
53
54
|
export * from "./experience-shell.ts";
|
|
54
55
|
export * from "./application-shell.ts";
|
|
@@ -115,3 +116,7 @@ export * from "./window-split-layout.ts";
|
|
|
115
116
|
export * from "./pane-editor-context.ts";
|
|
116
117
|
|
|
117
118
|
export * from "./tmux-agent-open.ts";
|
|
119
|
+
|
|
120
|
+
export * from "./tmux-server-diagnostics.ts";
|
|
121
|
+
|
|
122
|
+
export * from "./terminal-history-anchor.ts";
|
|
@@ -18,6 +18,13 @@ import {
|
|
|
18
18
|
TerminalAttachmentSemanticWindowIdSchemaZ,
|
|
19
19
|
} from "./semantic-identity.ts";
|
|
20
20
|
import { TerminalAttachmentViewerModeSchemaZ } from "./terminal-attachments.ts";
|
|
21
|
+
import {
|
|
22
|
+
TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES,
|
|
23
|
+
TERMINAL_GRAPHICS_MAX_REQUEST_DIGESTS,
|
|
24
|
+
TerminalGraphicsBlobSchemaZ,
|
|
25
|
+
TerminalGraphicsDigestSchemaZ,
|
|
26
|
+
TerminalGraphicsStateSchemaZ,
|
|
27
|
+
} from "./terminal-graphics.ts";
|
|
21
28
|
import { WorkspaceIdSchemaZ } from "./workspace-state.ts";
|
|
22
29
|
import {
|
|
23
30
|
TerminalDeliveryAckSchemaZ,
|
|
@@ -341,6 +348,18 @@ export const PaneStreamClockProbeFrameSchemaZ = z
|
|
|
341
348
|
})
|
|
342
349
|
.strict();
|
|
343
350
|
|
|
351
|
+
/** Ask for image bytes by digest; only after the daemon sent graphics state. */
|
|
352
|
+
export const PaneStreamTerminalGraphicsRequestFrameSchemaZ = z
|
|
353
|
+
.object({
|
|
354
|
+
type: z.literal("terminal-graphics-request"),
|
|
355
|
+
pane: PaneStreamSemanticPaneIdSchemaZ,
|
|
356
|
+
digests: z
|
|
357
|
+
.array(TerminalGraphicsDigestSchemaZ)
|
|
358
|
+
.min(1)
|
|
359
|
+
.max(TERMINAL_GRAPHICS_MAX_REQUEST_DIGESTS),
|
|
360
|
+
})
|
|
361
|
+
.strict();
|
|
362
|
+
|
|
344
363
|
export const PaneStreamClientFrameSchemaZ = z.union([
|
|
345
364
|
PaneStreamInputFrameSchemaZ,
|
|
346
365
|
PaneStreamConsumedFrameSchemaZ,
|
|
@@ -354,6 +373,7 @@ export const PaneStreamClientFrameSchemaZ = z.union([
|
|
|
354
373
|
PaneStreamAuthorityRequestFrameSchemaZ,
|
|
355
374
|
PaneStreamAuthorityReleaseFrameSchemaZ,
|
|
356
375
|
PaneStreamClockProbeFrameSchemaZ,
|
|
376
|
+
PaneStreamTerminalGraphicsRequestFrameSchemaZ,
|
|
357
377
|
]);
|
|
358
378
|
export type PaneStreamClientFrame = z.infer<typeof PaneStreamClientFrameSchemaZ>;
|
|
359
379
|
|
|
@@ -645,6 +665,21 @@ export const PaneStreamTerminalDeliveryFaultFrameSchemaZ = z
|
|
|
645
665
|
fault: TerminalDeliveryFaultSchemaZ,
|
|
646
666
|
})
|
|
647
667
|
.strict();
|
|
668
|
+
export const PaneStreamTerminalGraphicsFrameSchemaZ = z
|
|
669
|
+
.object({
|
|
670
|
+
type: z.literal("terminal-graphics"),
|
|
671
|
+
pane: PaneStreamSemanticPaneIdSchemaZ,
|
|
672
|
+
state: TerminalGraphicsStateSchemaZ,
|
|
673
|
+
})
|
|
674
|
+
.strict();
|
|
675
|
+
export const PaneStreamTerminalGraphicsBlobFrameSchemaZ = z
|
|
676
|
+
.object({
|
|
677
|
+
type: z.literal("terminal-graphics-blob"),
|
|
678
|
+
pane: PaneStreamSemanticPaneIdSchemaZ,
|
|
679
|
+
blob: TerminalGraphicsBlobSchemaZ,
|
|
680
|
+
data: Base64SchemaZ(Math.ceil(TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES / 3) * 4).min(4),
|
|
681
|
+
})
|
|
682
|
+
.strict();
|
|
648
683
|
export const PaneStreamSemanticIntentAckFrameSchemaZ = z
|
|
649
684
|
.object({
|
|
650
685
|
type: z.literal("semantic-intent-ack"),
|
|
@@ -749,6 +784,8 @@ export const PaneStreamServerFrameSchemaZ = z.discriminatedUnion("type", [
|
|
|
749
784
|
PaneStreamTerminalDeliveryEnvelopeFrameSchemaZ,
|
|
750
785
|
PaneStreamTerminalDeliveryChunkFrameSchemaZ,
|
|
751
786
|
PaneStreamTerminalDeliveryFaultFrameSchemaZ,
|
|
787
|
+
PaneStreamTerminalGraphicsFrameSchemaZ,
|
|
788
|
+
PaneStreamTerminalGraphicsBlobFrameSchemaZ,
|
|
752
789
|
PaneStreamSemanticIntentAckFrameSchemaZ,
|
|
753
790
|
PaneStreamViewportAckFrameSchemaZ,
|
|
754
791
|
PaneStreamAuthoritySnapshotFrameSchemaZ,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kitty graphics protocol side channel.
|
|
5
|
+
*
|
|
6
|
+
* Image bytes never enter the canonical terminal replica: the replica keeps
|
|
7
|
+
* the cells (including Unicode placeholder cells, which tmux retains as text)
|
|
8
|
+
* and this channel carries, per pane, the image registry those cells refer to
|
|
9
|
+
* plus best-effort cursor-anchored placements. Bytes travel separately, once
|
|
10
|
+
* per content digest, and only when a renderer asks for them.
|
|
11
|
+
*
|
|
12
|
+
* Capability: a renderer that understands these frames adds
|
|
13
|
+
* {@link TERMINAL_GRAPHICS_CAPABILITY_VERSION} to its terminal-delivery offer's
|
|
14
|
+
* `protocolVersions`. Daemons that predate the channel ignore the extra
|
|
15
|
+
* version; daemons that know it send graphics frames only to such renderers.
|
|
16
|
+
*/
|
|
17
|
+
export const TERMINAL_GRAPHICS_CAPABILITY_VERSION = 101;
|
|
18
|
+
/** Raw image bytes per blob frame (base64 on the wire stays below 1 MiB). */
|
|
19
|
+
export const TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES = 512 * 1024;
|
|
20
|
+
/** Largest single image a daemon publishes. */
|
|
21
|
+
export const TERMINAL_GRAPHICS_MAX_IMAGE_BYTES = 32 * 1024 * 1024;
|
|
22
|
+
export const TERMINAL_GRAPHICS_MAX_IMAGES = 256;
|
|
23
|
+
export const TERMINAL_GRAPHICS_MAX_PLACEMENTS = 256;
|
|
24
|
+
export const TERMINAL_GRAPHICS_MAX_REQUEST_DIGESTS = 64;
|
|
25
|
+
|
|
26
|
+
export const TerminalGraphicsDigestSchemaZ = z.string().regex(/^[0-9a-f]{32}$/u);
|
|
27
|
+
export const TerminalGraphicsFormatSchemaZ = z.enum(["png", "rgba", "rgb"]);
|
|
28
|
+
export type TerminalGraphicsFormat = z.infer<typeof TerminalGraphicsFormatSchemaZ>;
|
|
29
|
+
const Dimension = z.number().int().min(1).max(10_000);
|
|
30
|
+
const Cells = z.number().int().min(1).max(1_000);
|
|
31
|
+
const CellIndex = z.number().int().min(0).max(1_000);
|
|
32
|
+
|
|
33
|
+
export const TerminalGraphicsImageSchemaZ = z
|
|
34
|
+
.object({
|
|
35
|
+
/** Program-chosen kitty image id (placeholder cells encode it in colour). */
|
|
36
|
+
imageId: z.number().int().min(0).max(0xffff_ffff),
|
|
37
|
+
digest: TerminalGraphicsDigestSchemaZ,
|
|
38
|
+
format: TerminalGraphicsFormatSchemaZ,
|
|
39
|
+
width: Dimension,
|
|
40
|
+
height: Dimension,
|
|
41
|
+
bytes: z.number().int().min(1).max(TERMINAL_GRAPHICS_MAX_IMAGE_BYTES),
|
|
42
|
+
/** The `U=1` virtual placement's cell grid, when placeholders may show it. */
|
|
43
|
+
placeholder: z.object({ columns: Cells, rows: Cells }).strict().nullable(),
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
export type TerminalGraphicsImage = z.infer<typeof TerminalGraphicsImageSchemaZ>;
|
|
47
|
+
|
|
48
|
+
/** A cursor-anchored placement clipped to the pane viewport (best effort). */
|
|
49
|
+
export const TerminalGraphicsPlacementSchemaZ = z
|
|
50
|
+
.object({
|
|
51
|
+
digest: TerminalGraphicsDigestSchemaZ,
|
|
52
|
+
row: CellIndex,
|
|
53
|
+
column: CellIndex,
|
|
54
|
+
rows: Cells,
|
|
55
|
+
columns: Cells,
|
|
56
|
+
sourceRow: CellIndex,
|
|
57
|
+
sourceColumn: CellIndex,
|
|
58
|
+
totalRows: Cells,
|
|
59
|
+
totalColumns: Cells,
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
export type TerminalGraphicsPlacement = z.infer<typeof TerminalGraphicsPlacementSchemaZ>;
|
|
63
|
+
|
|
64
|
+
export const TerminalGraphicsStateSchemaZ = z
|
|
65
|
+
.object({
|
|
66
|
+
revision: z.number().int().nonnegative(),
|
|
67
|
+
images: z.array(TerminalGraphicsImageSchemaZ).max(TERMINAL_GRAPHICS_MAX_IMAGES),
|
|
68
|
+
placements: z.array(TerminalGraphicsPlacementSchemaZ).max(TERMINAL_GRAPHICS_MAX_PLACEMENTS),
|
|
69
|
+
})
|
|
70
|
+
.strict();
|
|
71
|
+
export type TerminalGraphicsState = z.infer<typeof TerminalGraphicsStateSchemaZ>;
|
|
72
|
+
|
|
73
|
+
export const TerminalGraphicsBlobSchemaZ = z
|
|
74
|
+
.object({
|
|
75
|
+
digest: TerminalGraphicsDigestSchemaZ,
|
|
76
|
+
format: TerminalGraphicsFormatSchemaZ,
|
|
77
|
+
width: Dimension,
|
|
78
|
+
height: Dimension,
|
|
79
|
+
total: z.number().int().min(1).max(TERMINAL_GRAPHICS_MAX_IMAGE_BYTES),
|
|
80
|
+
offset: z.number().int().nonnegative().max(TERMINAL_GRAPHICS_MAX_IMAGE_BYTES),
|
|
81
|
+
})
|
|
82
|
+
.strict();
|
|
83
|
+
export type TerminalGraphicsBlob = z.infer<typeof TerminalGraphicsBlobSchemaZ>;
|
|
84
|
+
|
|
85
|
+
/** Hub → transport messages (in-process; the transport frames them). */
|
|
86
|
+
export type TerminalGraphicsServerMessage =
|
|
87
|
+
| { readonly type: "terminal.graphics"; readonly state: TerminalGraphicsState }
|
|
88
|
+
| {
|
|
89
|
+
readonly type: "terminal.graphics.blob";
|
|
90
|
+
readonly blob: TerminalGraphicsBlob;
|
|
91
|
+
readonly bytes: Uint8Array;
|
|
92
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { WorkspaceIdSchemaZ } from "./workspace-state.ts";
|
|
3
|
+
import { TerminalAttachmentSemanticPaneIdSchemaZ } from "./semantic-identity.ts";
|
|
4
|
+
import { TmuxServerScopeSchemaZ } from "./tmux-server-scope.ts";
|
|
5
|
+
import { WorkspaceCatalogLiveSessionIdSchemaZ } from "./workspace-catalog-resource.ts";
|
|
6
|
+
export const HistoryAnchorProofSchemaZ = z
|
|
7
|
+
.object({
|
|
8
|
+
workspaceName: WorkspaceIdSchemaZ,
|
|
9
|
+
semanticPaneId: TerminalAttachmentSemanticPaneIdSchemaZ,
|
|
10
|
+
generation: z.uuid(),
|
|
11
|
+
incarnation: z.string().min(1).max(256),
|
|
12
|
+
revision: z.number().int().nonnegative().safe(),
|
|
13
|
+
stateHash: z.string().regex(/^[0-9a-f]{16}$/u),
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
export const HistoryAnchorIdentitySchemaZ = HistoryAnchorProofSchemaZ.omit({
|
|
17
|
+
revision: true,
|
|
18
|
+
stateHash: true,
|
|
19
|
+
});
|
|
20
|
+
export const HistoryAnchorRequestSchemaZ = z.union([
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
23
|
+
version: z.literal(1),
|
|
24
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
|
|
25
|
+
proof: HistoryAnchorProofSchemaZ,
|
|
26
|
+
operation: z
|
|
27
|
+
.object({ kind: z.literal("capture"), historyIndex: z.number().int().nonnegative().safe() })
|
|
28
|
+
.strict(),
|
|
29
|
+
})
|
|
30
|
+
.strict(),
|
|
31
|
+
z
|
|
32
|
+
.object({
|
|
33
|
+
version: z.literal(1),
|
|
34
|
+
liveSessionId: WorkspaceCatalogLiveSessionIdSchemaZ,
|
|
35
|
+
proof: HistoryAnchorIdentitySchemaZ,
|
|
36
|
+
operation: z.object({ kind: z.literal("resolve"), token: z.uuid() }).strict(),
|
|
37
|
+
})
|
|
38
|
+
.strict(),
|
|
39
|
+
]);
|
|
40
|
+
export const HistoryAnchorResultSchemaZ = z.discriminatedUnion("status", [
|
|
41
|
+
z.object({ status: z.literal("captured"), token: z.uuid() }).strict(),
|
|
42
|
+
z
|
|
43
|
+
.object({
|
|
44
|
+
status: z.literal("resolved"),
|
|
45
|
+
proof: HistoryAnchorProofSchemaZ,
|
|
46
|
+
historyIndex: z.number().int().nonnegative().safe(),
|
|
47
|
+
})
|
|
48
|
+
.strict(),
|
|
49
|
+
z.object({ status: z.literal("unavailable") }).strict(),
|
|
50
|
+
z.object({ status: z.literal("stale") }).strict(),
|
|
51
|
+
z.object({ status: z.literal("lost") }).strict(),
|
|
52
|
+
]);
|
|
53
|
+
export const HistoryAnchorResponseSchemaZ = z
|
|
54
|
+
.object({
|
|
55
|
+
version: z.literal(1),
|
|
56
|
+
server: TmuxServerScopeSchemaZ,
|
|
57
|
+
result: HistoryAnchorResultSchemaZ,
|
|
58
|
+
})
|
|
59
|
+
.strict();
|
|
60
|
+
export type HistoryAnchorRequest = z.infer<typeof HistoryAnchorRequestSchemaZ>;
|
|
61
|
+
export type HistoryAnchorResult = z.infer<typeof HistoryAnchorResultSchemaZ>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TmuxServerScopeSchemaZ } from "./tmux-server-scope.ts";
|
|
3
|
+
|
|
4
|
+
/** Server format output only; never accept freeform diagnostic text. */
|
|
5
|
+
export const TmuxServerVersionSchemaZ = z
|
|
6
|
+
.string()
|
|
7
|
+
.max(32)
|
|
8
|
+
.regex(/^[0-9]+\.[0-9]+[a-z]?$/u);
|
|
9
|
+
export const TmuxServerDiagnosticsSchemaZ = z
|
|
10
|
+
.object({
|
|
11
|
+
version: z.literal(1),
|
|
12
|
+
server: TmuxServerScopeSchemaZ,
|
|
13
|
+
tmuxVersion: TmuxServerVersionSchemaZ,
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
export type TmuxServerDiagnostics = z.infer<typeof TmuxServerDiagnosticsSchemaZ>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty graphics Unicode placeholders, decoded from replica cells.
|
|
3
|
+
*
|
|
4
|
+
* A program places an image with a `U=1` virtual placement and then prints
|
|
5
|
+
* U+10EEEE cells whose combining diacritics give the image row and column and
|
|
6
|
+
* whose foreground colour gives the image id. To tmux these are ordinary text,
|
|
7
|
+
* so they survive scrolling, history, capture and reattach; renderers derive
|
|
8
|
+
* placements from the cells they already hold and stay in exact sync.
|
|
9
|
+
*/
|
|
10
|
+
import type {
|
|
11
|
+
TerminalGraphicsPlacement,
|
|
12
|
+
TerminalGraphicsState,
|
|
13
|
+
TerminalReplicaRow,
|
|
14
|
+
} from "@tmux-ide/contracts";
|
|
15
|
+
|
|
16
|
+
export const KITTY_PLACEHOLDER = 0x10eeee;
|
|
17
|
+
|
|
18
|
+
type KittyCellColor =
|
|
19
|
+
| { readonly kind: "default" }
|
|
20
|
+
| { readonly kind: "indexed"; readonly index: number }
|
|
21
|
+
| { readonly kind: "rgb"; readonly value: number };
|
|
22
|
+
|
|
23
|
+
// Row/column diacritics for Unicode placeholders, in protocol order (index = value).
|
|
24
|
+
const DIACRITICS = [
|
|
25
|
+
"0305 030D 030E 0310 0312 033D 033E 033F 0346 034A 034B 034C 0350 0351 0352 0357 035B 0363 0364",
|
|
26
|
+
"0365 0366 0367 0368 0369 036A 036B 036C 036D 036E 036F 0483 0484 0485 0486 0487 0592 0593 0594",
|
|
27
|
+
"0595 0597 0598 0599 059C 059D 059E 059F 05A0 05A1 05A8 05A9 05AB 05AC 05AF 05C4 0610 0611 0612",
|
|
28
|
+
"0613 0614 0615 0616 0617 0657 0658 0659 065A 065B 065D 065E 06D6 06D7 06D8 06D9 06DA 06DB 06DC",
|
|
29
|
+
"06DF 06E0 06E1 06E2 06E4 06E7 06E8 06EB 06EC 0730 0732 0733 0735 0736 073A 073D 073F 0740 0741",
|
|
30
|
+
"0743 0745 0747 0749 074A 07EB 07EC 07ED 07EE 07EF 07F0 07F1 07F3 0816 0817 0818 0819 081B 081C",
|
|
31
|
+
"081D 081E 081F 0820 0821 0822 0823 0825 0826 0827 0829 082A 082B 082C 082D 0951 0953 0954 0F82",
|
|
32
|
+
"0F83 0F86 0F87 135D 135E 135F 17DD 193A 1A17 1A75 1A76 1A77 1A78 1A79 1A7A 1A7B 1A7C 1B6B 1B6D",
|
|
33
|
+
"1B6E 1B6F 1B70 1B71 1B72 1B73 1CD0 1CD1 1CD2 1CDA 1CDB 1CE0 1DC0 1DC1 1DC3 1DC4 1DC5 1DC6 1DC7",
|
|
34
|
+
"1DC8 1DC9 1DCB 1DCC 1DD1 1DD2 1DD3 1DD4 1DD5 1DD6 1DD7 1DD8 1DD9 1DDA 1DDB 1DDC 1DDD 1DDE 1DDF",
|
|
35
|
+
"1DE0 1DE1 1DE2 1DE3 1DE4 1DE5 1DE6 1DFE 20D0 20D1 20D4 20D5 20D6 20D7 20DB 20DC 20E1 20E7 20E9",
|
|
36
|
+
"20F0 2CEF 2CF0 2CF1 2DE0 2DE1 2DE2 2DE3 2DE4 2DE5 2DE6 2DE7 2DE8 2DE9 2DEA 2DEB 2DEC 2DED 2DEE",
|
|
37
|
+
"2DEF 2DF0 2DF1 2DF2 2DF3 2DF4 2DF5 2DF6 2DF7 2DF8 2DF9 2DFA 2DFB 2DFC 2DFD 2DFE 2DFF A66F A67C",
|
|
38
|
+
"A67D A6F0 A6F1 A8E0 A8E1 A8E2 A8E3 A8E4 A8E5 A8E6 A8E7 A8E8 A8E9 A8EA A8EB A8EC A8ED A8EE A8EF",
|
|
39
|
+
"A8F0 A8F1 AAB0 AAB2 AAB3 AAB7 AAB8 AABE AABF AAC1 FE20 FE21 FE22 FE23 FE24 FE25 FE26 10A0F 10A38",
|
|
40
|
+
"1D185 1D186 1D187 1D188 1D189 1D1AA 1D1AB 1D1AC 1D1AD 1D242 1D243 1D244",
|
|
41
|
+
]
|
|
42
|
+
.join(" ")
|
|
43
|
+
.split(" ");
|
|
44
|
+
const DIACRITIC_VALUE = new Map(DIACRITICS.map((hex, index) => [Number.parseInt(hex, 16), index]));
|
|
45
|
+
|
|
46
|
+
export interface PlaceholderCell {
|
|
47
|
+
imageId: number;
|
|
48
|
+
row: number;
|
|
49
|
+
col: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** PURE — decode one cell; null when it is not a placeholder. */
|
|
53
|
+
export function decodeKittyPlaceholderCell(
|
|
54
|
+
grapheme: string,
|
|
55
|
+
foreground: KittyCellColor,
|
|
56
|
+
left: PlaceholderCell | null,
|
|
57
|
+
): PlaceholderCell | null {
|
|
58
|
+
const points = [...grapheme].map((ch) => ch.codePointAt(0)!);
|
|
59
|
+
if (points[0] !== KITTY_PLACEHOLDER) return null;
|
|
60
|
+
const marks = points.slice(1).map((p) => DIACRITIC_VALUE.get(p));
|
|
61
|
+
let imageId =
|
|
62
|
+
foreground.kind === "indexed"
|
|
63
|
+
? foreground.index
|
|
64
|
+
: foreground.kind === "rgb"
|
|
65
|
+
? foreground.value & 0xffffff
|
|
66
|
+
: 0;
|
|
67
|
+
if (marks[2] !== undefined) imageId += marks[2] * 0x1000000;
|
|
68
|
+
const continues = left !== null && (marks[0] === undefined || marks[0] === left.row);
|
|
69
|
+
const row = marks[0] ?? (continues ? left!.row : 0);
|
|
70
|
+
const col = marks[1] ?? (continues ? left!.col + 1 : 0);
|
|
71
|
+
if (marks[2] === undefined && continues && imageId === (left!.imageId & 0xffffff))
|
|
72
|
+
imageId = left!.imageId;
|
|
73
|
+
return { imageId, row, col };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface PlaceholderRect {
|
|
77
|
+
imageId: number;
|
|
78
|
+
row: number;
|
|
79
|
+
column: number;
|
|
80
|
+
rows: number;
|
|
81
|
+
columns: number;
|
|
82
|
+
srcRow: number;
|
|
83
|
+
srcCol: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function kittyPlaceholderRects(
|
|
87
|
+
grid: readonly {
|
|
88
|
+
readonly cells: readonly { readonly grapheme: string; readonly foreground: KittyCellColor }[];
|
|
89
|
+
}[],
|
|
90
|
+
): PlaceholderRect[] {
|
|
91
|
+
// Horizontal runs: same image and source row, consecutive source columns.
|
|
92
|
+
const open: PlaceholderRect[] = [];
|
|
93
|
+
const done: PlaceholderRect[] = [];
|
|
94
|
+
for (let y = 0; y < grid.length; y += 1) {
|
|
95
|
+
const runs: PlaceholderRect[] = [];
|
|
96
|
+
let left: PlaceholderCell | null = null;
|
|
97
|
+
let run: PlaceholderRect | null = null;
|
|
98
|
+
const cells = grid[y]!.cells;
|
|
99
|
+
for (let x = 0; x < cells.length; x += 1) {
|
|
100
|
+
const cell = decodeKittyPlaceholderCell(cells[x]!.grapheme, cells[x]!.foreground, left);
|
|
101
|
+
left = cell;
|
|
102
|
+
if (
|
|
103
|
+
cell &&
|
|
104
|
+
run &&
|
|
105
|
+
cell.imageId === run.imageId &&
|
|
106
|
+
cell.row === run.srcRow &&
|
|
107
|
+
cell.col === run.srcCol + run.columns
|
|
108
|
+
) {
|
|
109
|
+
run.columns += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (run) runs.push(run);
|
|
113
|
+
run = cell
|
|
114
|
+
? {
|
|
115
|
+
imageId: cell.imageId,
|
|
116
|
+
row: y,
|
|
117
|
+
column: x,
|
|
118
|
+
rows: 1,
|
|
119
|
+
columns: 1,
|
|
120
|
+
srcRow: cell.row,
|
|
121
|
+
srcCol: cell.col,
|
|
122
|
+
}
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
if (run) runs.push(run);
|
|
126
|
+
// Merge with the rectangle directly above when it continues the image.
|
|
127
|
+
const stillOpen: PlaceholderRect[] = [];
|
|
128
|
+
for (const candidate of runs) {
|
|
129
|
+
const above = open.find(
|
|
130
|
+
(rect) =>
|
|
131
|
+
rect.imageId === candidate.imageId &&
|
|
132
|
+
rect.column === candidate.column &&
|
|
133
|
+
rect.columns === candidate.columns &&
|
|
134
|
+
rect.srcCol === candidate.srcCol &&
|
|
135
|
+
rect.row + rect.rows === y &&
|
|
136
|
+
rect.srcRow + rect.rows === candidate.srcRow,
|
|
137
|
+
);
|
|
138
|
+
if (above) {
|
|
139
|
+
above.rows += 1;
|
|
140
|
+
open.splice(open.indexOf(above), 1);
|
|
141
|
+
stillOpen.push(above);
|
|
142
|
+
} else stillOpen.push(candidate);
|
|
143
|
+
}
|
|
144
|
+
done.push(...open);
|
|
145
|
+
open.length = 0;
|
|
146
|
+
open.push(...stillOpen);
|
|
147
|
+
}
|
|
148
|
+
done.push(...open);
|
|
149
|
+
return done;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* PURE — every visible image placement for one pane: placeholder rectangles
|
|
154
|
+
* resolved through the pane's image registry, then the daemon's best-effort
|
|
155
|
+
* cursor-anchored placements. Cells naming an unknown image are skipped.
|
|
156
|
+
*/
|
|
157
|
+
export function resolveKittyPlacements(
|
|
158
|
+
grid: readonly TerminalReplicaRow[],
|
|
159
|
+
state: TerminalGraphicsState,
|
|
160
|
+
): TerminalGraphicsPlacement[] {
|
|
161
|
+
const placements: TerminalGraphicsPlacement[] = [];
|
|
162
|
+
const byId = new Map(state.images.map((image) => [image.imageId, image]));
|
|
163
|
+
let scan = false;
|
|
164
|
+
for (const image of state.images) if (image.placeholder) scan = true;
|
|
165
|
+
if (scan) {
|
|
166
|
+
for (const rect of kittyPlaceholderRects(grid)) {
|
|
167
|
+
const image = byId.get(rect.imageId);
|
|
168
|
+
const virtualGrid = image?.placeholder;
|
|
169
|
+
if (!image || !virtualGrid) continue;
|
|
170
|
+
if (
|
|
171
|
+
rect.srcRow + rect.rows > virtualGrid.rows ||
|
|
172
|
+
rect.srcCol + rect.columns > virtualGrid.columns
|
|
173
|
+
)
|
|
174
|
+
continue;
|
|
175
|
+
placements.push({
|
|
176
|
+
digest: image.digest,
|
|
177
|
+
row: rect.row,
|
|
178
|
+
column: rect.column,
|
|
179
|
+
rows: rect.rows,
|
|
180
|
+
columns: rect.columns,
|
|
181
|
+
sourceRow: rect.srcRow,
|
|
182
|
+
sourceColumn: rect.srcCol,
|
|
183
|
+
totalRows: virtualGrid.rows,
|
|
184
|
+
totalColumns: virtualGrid.columns,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
placements.push(...state.placements);
|
|
189
|
+
return placements;
|
|
190
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { TmuxServerScopeSchemaZ, TmuxServerDiagnosticsSchemaZ } from "@tmux-ide/contracts";
|
|
2
|
+
import { TmuxServerScopeError } from "../lib/tmux-server-owners.js";
|
|
3
|
+
/** Mount beneath the existing owner-authenticated, no-store server router. */
|
|
4
|
+
export function mountTmuxServerDiagnosticsRoute(app, options) {
|
|
5
|
+
app.get(`${options.scopedPath}/diagnostics`, async (c) => {
|
|
6
|
+
const scope = TmuxServerScopeSchemaZ.safeParse({
|
|
7
|
+
serverId: c.req.param("serverId"),
|
|
8
|
+
generation: c.req.param("generation"),
|
|
9
|
+
});
|
|
10
|
+
if (!scope.success)
|
|
11
|
+
return c.json({ error: { code: "invalid-request" } }, 400);
|
|
12
|
+
try {
|
|
13
|
+
c.req.raw.signal.throwIfAborted();
|
|
14
|
+
const tmuxVersion = await options.owners.withOwner(scope.data, (owner) => owner.readServerVersion(c.req.raw.signal));
|
|
15
|
+
c.req.raw.signal.throwIfAborted();
|
|
16
|
+
return c.json(TmuxServerDiagnosticsSchemaZ.parse({ version: 1, server: scope.data, tmuxVersion }));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof TmuxServerScopeError)
|
|
20
|
+
return c.json({ error: { code: "scope-unavailable" } }, error.code === "not-found" ? 404 : 409);
|
|
21
|
+
return c.json({ error: { code: "diagnostics-unavailable" } }, 503);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { HistoryAnchorRequestSchemaZ, HistoryAnchorResponseSchemaZ, TmuxServerScopeSchemaZ, } from "@tmux-ide/contracts";
|
|
2
|
+
import { TmuxServerScopeError } from "../lib/tmux-server-owners.js";
|
|
3
|
+
/** Mounted below owner-authenticated no-store middleware. Never opens a runtime. */
|
|
4
|
+
export function mountTmuxServerHistoryAnchorRoute(app, options) {
|
|
5
|
+
app.post(`${options.scopedPath}/history-anchor`, async (c) => {
|
|
6
|
+
const scope = TmuxServerScopeSchemaZ.safeParse({
|
|
7
|
+
serverId: c.req.param("serverId"),
|
|
8
|
+
generation: c.req.param("generation"),
|
|
9
|
+
});
|
|
10
|
+
if (!scope.success)
|
|
11
|
+
return c.json({ error: { code: "invalid-request" } }, 400);
|
|
12
|
+
let body;
|
|
13
|
+
try {
|
|
14
|
+
body = HistoryAnchorRequestSchemaZ.parse(await options.readBody(c.req.raw));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return c.json({ error: { code: "invalid-request" } }, 400);
|
|
18
|
+
}
|
|
19
|
+
if (body.proof.generation !== scope.data.generation)
|
|
20
|
+
return c.json({ error: { code: "stale-generation" } }, 409);
|
|
21
|
+
try {
|
|
22
|
+
c.req.raw.signal.throwIfAborted();
|
|
23
|
+
const result = await options.owners.withOwner(scope.data, async (owner) => {
|
|
24
|
+
const sessions = await owner.catalog();
|
|
25
|
+
c.req.raw.signal.throwIfAborted();
|
|
26
|
+
const workspace = owner.workspaceRegistry.get(body.proof.workspaceName);
|
|
27
|
+
if (!workspace ||
|
|
28
|
+
!sessions.some((row) => row.sessionName === workspace.sessionName && row.liveSessionId === body.liveSessionId))
|
|
29
|
+
return { status: "stale" };
|
|
30
|
+
const result = owner.sessionRuntimeRegistry.historyAnchor(workspace.sessionName, HistoryAnchorRequestSchemaZ.parse({
|
|
31
|
+
...body,
|
|
32
|
+
proof: { ...body.proof, workspaceName: workspace.sessionName },
|
|
33
|
+
}));
|
|
34
|
+
// Delivery aliases are transport addresses, not canonical session names.
|
|
35
|
+
return result.status === "resolved"
|
|
36
|
+
? { ...result, proof: { ...result.proof, workspaceName: body.proof.workspaceName } }
|
|
37
|
+
: result;
|
|
38
|
+
});
|
|
39
|
+
c.req.raw.signal.throwIfAborted();
|
|
40
|
+
return c.json(HistoryAnchorResponseSchemaZ.parse({ version: 1, server: scope.data, result }));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof TmuxServerScopeError)
|
|
44
|
+
return c.json({ error: { code: "scope-unavailable" } }, error.code === "not-found" ? 404 : 409);
|
|
45
|
+
return c.json({ error: { code: "history-unavailable" } }, 503);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mountTmuxServerHistoryAnchorRoute } from "./tmux-server-history-anchor.js";
|
|
2
|
+
import { mountTmuxServerDiagnosticsRoute } from "./tmux-server-diagnostics.js";
|
|
1
3
|
import { WindowSplitLayoutUnavailable } from "../terminal/mirror/window-split-authority.js";
|
|
2
4
|
import { streamTmuxInteractions } from "./tmux-server-interaction-events.js";
|
|
3
5
|
import { mountTmuxServerNativeBackingRoute } from "./tmux-server-native-backing.js";
|
|
@@ -10,6 +12,7 @@ import { z } from "zod";
|
|
|
10
12
|
import { TmuxAgentOpenRequestSchemaZ, TmuxAgentOpenResponseSchemaZ, WorkspaceCatalogLiveSessionIdSchemaZ, WindowLinkTargetSchemaZ, WindowSplitLayoutResourceSchemaZ, TMUX_SERVERS_API_PATH, PANE_STREAM_ISSUE_PATH, TmuxServerIdSchemaZ, TmuxServerScopeSchemaZ, TmuxServersResourceSchemaZ, TmuxServerSessionsResourceSchemaZ, WorkspaceMultiplexerMutationRequestSchemaZ, WorkspacePaneCreateMutationRequestSchemaZ, WorkspaceSessionCreateArgumentsSchemaZ, } from "@tmux-ide/contracts";
|
|
11
13
|
import { TmuxServerRegistrationSchemaZ, TmuxServerScopeError, } from "../lib/tmux-server-owners.js";
|
|
12
14
|
import { WorkspaceMultiplexerError } from "../lib/workspace-multiplexer-verbs.js";
|
|
15
|
+
import { WorkspacePaneCreationError } from "../lib/workspace-pane-creation.js";
|
|
13
16
|
import { ownerBearerMatches } from "./owner-authority.js";
|
|
14
17
|
import { mountPaneStreamIssueRoute } from "./pane-stream-issue.js";
|
|
15
18
|
import { projectTerminalRuntimeInventory } from "./resources/terminal-runtime-inventory.js";
|
|
@@ -54,6 +57,8 @@ function failure(c, error) {
|
|
|
54
57
|
return c.json({ error: { code: "split-layout-stale" } }, 409);
|
|
55
58
|
if (error instanceof TmuxServerScopeError)
|
|
56
59
|
return c.json({ error: { code: error.code } }, error.code === "not-found" ? 404 : error.code === "capacity" ? 429 : 409);
|
|
60
|
+
if (error instanceof WorkspacePaneCreationError && error.code === "harness_unavailable")
|
|
61
|
+
return c.json({ error: { code: "harness_unavailable" } }, 409);
|
|
57
62
|
if (error instanceof WorkspaceMultiplexerError)
|
|
58
63
|
return c.json({ error: { code: error.code } }, 409);
|
|
59
64
|
if (error instanceof z.ZodError || error instanceof SyntaxError || error instanceof TypeError)
|
|
@@ -90,6 +95,12 @@ export function mountTmuxServerRoutes(app, options) {
|
|
|
90
95
|
return c.json({ removed: true });
|
|
91
96
|
}));
|
|
92
97
|
const scoped = `${base}/:serverId/:generation`;
|
|
98
|
+
mountTmuxServerHistoryAnchorRoute(app, {
|
|
99
|
+
scopedPath: scoped,
|
|
100
|
+
owners: options.owners,
|
|
101
|
+
readBody: (request) => boundedJson(request, 4096),
|
|
102
|
+
});
|
|
103
|
+
mountTmuxServerDiagnosticsRoute(app, { scopedPath: scoped, owners: options.owners });
|
|
93
104
|
mountTmuxServerEditorContextRoute(app, {
|
|
94
105
|
scopedPath: scoped,
|
|
95
106
|
owners: options.owners,
|
|
@@ -32,6 +32,13 @@ function spawnOwner(entryPath, cwd) {
|
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
+
class ShutdownTransportError extends Error {
|
|
36
|
+
original;
|
|
37
|
+
constructor(original) {
|
|
38
|
+
super("Canonical daemon shutdown transport failed.", { cause: original });
|
|
39
|
+
this.original = original;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
35
42
|
async function shutdownOlderOwner(info) {
|
|
36
43
|
const headers = { "content-type": "application/json" };
|
|
37
44
|
if (info.authToken)
|
|
@@ -45,6 +52,8 @@ async function shutdownOlderOwner(info) {
|
|
|
45
52
|
expectedInstanceId: info.instanceId,
|
|
46
53
|
}),
|
|
47
54
|
signal: AbortSignal.timeout(2_000),
|
|
55
|
+
}).catch((error) => {
|
|
56
|
+
throw new ShutdownTransportError(error);
|
|
48
57
|
});
|
|
49
58
|
const envelope = (await response.json().catch(() => null));
|
|
50
59
|
// The authenticated handler verifies expectedInstanceId before reporting this
|
|
@@ -141,7 +150,18 @@ async function replaceOlderCanonicalDaemon(deps, info, timeoutMs, expectedProduc
|
|
|
141
150
|
"tmux-ide --headless --supervised <id> before restarting it. " +
|
|
142
151
|
"The existing daemon has been left running.", { reason: "canonical-record-invalid" });
|
|
143
152
|
}
|
|
144
|
-
|
|
153
|
+
let transportFailure;
|
|
154
|
+
try {
|
|
155
|
+
await deps.shutdownOlderOwner(info);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (!(error instanceof ShutdownTransportError))
|
|
159
|
+
throw error;
|
|
160
|
+
transportFailure = error;
|
|
161
|
+
}
|
|
162
|
+
// A competing upgrader may close the authenticated owner's listener before
|
|
163
|
+
// removing its record. Observe retirement, without repeating the mutation.
|
|
164
|
+
// A transport failure alone never authorizes starting a replacement.
|
|
145
165
|
const deadline = deps.now() + timeoutMs;
|
|
146
166
|
while (deps.now() < deadline) {
|
|
147
167
|
const state = deps.inspect();
|
|
@@ -153,6 +173,8 @@ async function replaceOlderCanonicalDaemon(deps, info, timeoutMs, expectedProduc
|
|
|
153
173
|
return;
|
|
154
174
|
await deps.sleep(25);
|
|
155
175
|
}
|
|
176
|
+
if (transportFailure)
|
|
177
|
+
throw transportFailure.original;
|
|
156
178
|
throw new DaemonBootstrapError("control-timeout", "The older canonical daemon did not retire after accepting the version upgrade.", { reason: "protocol-mismatch" });
|
|
157
179
|
}
|
|
158
180
|
async function assertServerIntent(deps, info, identity, intent) {
|
|
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { TmuxServerVersionSchemaZ } from "@tmux-ide/contracts";
|
|
9
10
|
import { createNativeSplitCapabilityProbe, createNativeSplitReadiness, } from "./native-split-capability.js";
|
|
10
11
|
import { executeCanonicalSplitMutation } from "./canonical-split-mutation.js";
|
|
11
12
|
import { createGuardedNativeSplitResize } from "./guarded-native-split-resize.js";
|
|
@@ -956,6 +957,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
956
957
|
let paneStreamRuntime = null;
|
|
957
958
|
let serverOwners = null;
|
|
958
959
|
let defaultEditorContextRetired = false;
|
|
960
|
+
const defaultDiagnosticsLifetime = new AbortController();
|
|
959
961
|
const defaultSessionCreator = createNativeTmuxSessionCreator({
|
|
960
962
|
generation: instanceId,
|
|
961
963
|
registry: workspaceRegistry,
|
|
@@ -1287,6 +1289,24 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1287
1289
|
stateDirectory: resolveRuntimeNamespace().runtimeDir,
|
|
1288
1290
|
webSocketBaseUrl: canonicalDaemonUrl("ws", bindHostname, port),
|
|
1289
1291
|
defaultOwner: {
|
|
1292
|
+
readServerVersion: async (signal) => {
|
|
1293
|
+
const assertCurrent = () => {
|
|
1294
|
+
if (defaultEditorContextRetired ||
|
|
1295
|
+
serverOwners?.defaultRetired ||
|
|
1296
|
+
!initialNativeServerIdentity)
|
|
1297
|
+
throw new Error("Default tmux diagnostics owner is unavailable");
|
|
1298
|
+
};
|
|
1299
|
+
assertCurrent();
|
|
1300
|
+
const combined = AbortSignal.any([
|
|
1301
|
+
defaultDiagnosticsLifetime.signal,
|
|
1302
|
+
...(signal ? [signal] : []),
|
|
1303
|
+
]);
|
|
1304
|
+
combined.throwIfAborted();
|
|
1305
|
+
const output = await fleetFactsTmuxRunner(["display-message", "-p", "#{version}"], combined);
|
|
1306
|
+
combined.throwIfAborted();
|
|
1307
|
+
assertCurrent();
|
|
1308
|
+
return TmuxServerVersionSchemaZ.parse(output.trim());
|
|
1309
|
+
},
|
|
1290
1310
|
get interactionObservation() {
|
|
1291
1311
|
return interactionObservation;
|
|
1292
1312
|
},
|
|
@@ -1375,6 +1395,7 @@ async function startEmbeddedDaemonGeneration(opts) {
|
|
|
1375
1395
|
paneStreamRuntime,
|
|
1376
1396
|
dispose: async () => {
|
|
1377
1397
|
defaultEditorContextRetired = true;
|
|
1398
|
+
defaultDiagnosticsLifetime.abort();
|
|
1378
1399
|
sessionMonitor?.stop();
|
|
1379
1400
|
paneSourceCredentials.dispose();
|
|
1380
1401
|
const transportResults = await Promise.allSettled([
|