tmux-ide 2.9.2 → 2.9.4
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 +121 -140
- package/bin/cli.js +2457 -727
- package/package.json +23 -8
- package/packages/contracts/src/desktop-host.ts +3 -0
- package/packages/contracts/src/saved-machines.ts +19 -0
- package/packages/contracts/src/terminal-replica.ts +1 -1
- package/packages/core/package.json +3 -2
- package/packages/core/src/terminal-delivery.ts +51 -3
- package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
- package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
- package/packages/core/src/terminal-replica-owned-row.ts +27 -0
- package/packages/core/src/terminal-replica.ts +13 -28
- package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
- package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
- package/packages/daemon/dist/command-center/ws-events.js +5 -24
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
- package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
- package/packages/daemon/dist/lib/daemon-embed.js +1 -1
- package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
- package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
- package/packages/daemon/dist/lib/daemon-service.js +259 -0
- package/packages/daemon/dist/lib/install-origin.js +2 -0
- package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
- package/packages/daemon/dist/lib/log.js +6 -10
- package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
- package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
- package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
- package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
- package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
- package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
- package/packages/daemon/dist/lib/saved-machines.js +40 -2
- package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
- package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
- package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
- package/packages/daemon/dist/lib/tui-binary.js +2 -2
- package/packages/daemon/dist/lib/update.js +18 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
- package/packages/daemon/dist/machines.js +40 -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/terminal/attachments/direct-websocket.js +4 -1
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
- package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
- package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
- package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
- package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
- package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
- package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
- package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
- package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
- package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
- package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
- package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
- package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
- package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
- package/packages/daemon/src/command-center/ws-events.ts +5 -25
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
- package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
- package/packages/daemon/src/lib/daemon-embed.ts +1 -0
- package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
- package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
- package/packages/daemon/src/lib/daemon-service.ts +301 -0
- package/packages/daemon/src/lib/install-origin.ts +5 -0
- package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
- package/packages/daemon/src/lib/log.ts +13 -9
- package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
- package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
- package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
- package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
- package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
- package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
- package/packages/daemon/src/lib/saved-machines.ts +40 -1
- package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
- package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
- package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
- package/packages/daemon/src/lib/tui-binary.ts +2 -2
- package/packages/daemon/src/lib/update.ts +24 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
- package/packages/daemon/src/machines.ts +53 -5
- package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
- package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
- package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
- package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
- package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
- package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
- package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
- package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
- package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
- package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
- package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
- package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
- package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
- package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
- package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
- package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
- package/packages/daemon-client/src/workspace-client.ts +4 -1
- package/skill/SKILL.md +30 -30
- package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
- package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
- package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
- package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
- package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
- package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
- package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
- package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
- package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
- package/packages/contracts/src/__tests__/commands.test.ts +0 -86
- package/packages/contracts/src/__tests__/control.test.ts +0 -154
- package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
- package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
- package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
- package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
- package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
- package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
- package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
- package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
- package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
- package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
- package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
- package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
- package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
- package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
- package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
- package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
- package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
- package/packages/contracts/src/__tests__/missions.test.ts +0 -272
- package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
- package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
- package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
- package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
- package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
- package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
- package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
- package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
- package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
- package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
- package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
- package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
- package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
- package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
- package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
- package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
- package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
- package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
- package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
- package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
- package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
- package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
- package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
- package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
- package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
- package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
- package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
- package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
- package/packages/contracts/src/native-operation-identity.test.ts +0 -23
- package/packages/contracts/src/workspace-admission.test.ts +0 -21
- package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
- package/packages/core/src/agent-provisioning.test.ts +0 -42
- package/packages/core/src/application-shell-session.test.ts +0 -105
- package/packages/core/src/interaction-evidence.test.ts +0 -134
- package/packages/core/src/interaction-flow.test.ts +0 -66
- package/packages/core/src/interaction-receipts.test.ts +0 -671
- package/packages/core/src/navigator.test.ts +0 -34
- package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
- package/packages/core/src/optimistic-projection.test.ts +0 -118
- package/packages/core/src/performance-metrics.test.ts +0 -121
- package/packages/core/src/performance-qualification.test.ts +0 -283
- package/packages/core/src/saved-machines.test.ts +0 -80
- package/packages/core/src/terminal-compact-cold.test.ts +0 -87
- package/packages/core/src/terminal-conformance.test.ts +0 -48
- package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
- package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
- package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
- package/packages/core/src/terminal-delivery.test.ts +0 -1471
- package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
- package/packages/core/src/terminal-replica-cold.test.ts +0 -31
- package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
- package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
- package/packages/core/src/terminal-replica.test.ts +0 -347
- package/packages/core/src/workspace-selection.test.ts +0 -63
- package/packages/daemon/dist/__tests__/support.js +0 -33
- package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
- package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
- package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
- package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
- package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
- package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
- package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
- package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
- package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
- package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
- package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
- package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
- package/packages/daemon-client/src/automation-client.test.ts +0 -214
- package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
- package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
- package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
- package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
- package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
- package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
- package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
- package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
- package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
- package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
- package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
- package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
- package/packages/daemon-client/src/resource-replica.test.ts +0 -186
- package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
- package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
- package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
- package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
- package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
- package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
- package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
- package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
- package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
- package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
- package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
- package/packages/presentation/src/presentation-contract.test.ts +0 -63
- package/packages/tmux-bridge/src/runner.test.ts +0 -614
- package/packages/tmux-bridge/src/targeting.test.ts +0 -107
|
@@ -14,6 +14,7 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
14
14
|
let selectedId = LOCAL_MACHINE_ID;
|
|
15
15
|
let selectedEpoch = 0;
|
|
16
16
|
let disposed = false;
|
|
17
|
+
let stopProfiles = null;
|
|
17
18
|
const safe = (callback) => {
|
|
18
19
|
try {
|
|
19
20
|
callback();
|
|
@@ -31,7 +32,7 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
31
32
|
safe(() => listener(generation));
|
|
32
33
|
};
|
|
33
34
|
const changed = (machine, generation) => {
|
|
34
|
-
if (disposed)
|
|
35
|
+
if (disposed || machine.retired)
|
|
35
36
|
return;
|
|
36
37
|
machine.epoch++;
|
|
37
38
|
for (const listener of machine.listeners)
|
|
@@ -52,7 +53,7 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
52
53
|
changed(machine, generation);
|
|
53
54
|
})
|
|
54
55
|
.then((stop) => {
|
|
55
|
-
if (disposed || machine.owner !== owner)
|
|
56
|
+
if (disposed || machine.retired || machine.owner !== owner)
|
|
56
57
|
stop();
|
|
57
58
|
else
|
|
58
59
|
machine.stopObserver = stop;
|
|
@@ -80,23 +81,28 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
80
81
|
stopObserver: null,
|
|
81
82
|
observingOwner: null,
|
|
82
83
|
stopStatus: null,
|
|
84
|
+
retired: false,
|
|
83
85
|
};
|
|
84
86
|
const handle = Object.freeze({
|
|
85
87
|
id,
|
|
86
|
-
label
|
|
88
|
+
get label() {
|
|
89
|
+
return machine.label;
|
|
90
|
+
},
|
|
87
91
|
kind: machine.kind,
|
|
88
92
|
ready,
|
|
89
|
-
read: () => (disposed ? null : machine.owner.read()),
|
|
90
|
-
isAlive: (info) => disposed ? Promise.resolve(false) : machine.owner.isAlive(info),
|
|
93
|
+
read: () => (disposed || machine.retired ? null : machine.owner.read()),
|
|
94
|
+
isAlive: (info) => disposed || machine.retired ? Promise.resolve(false) : machine.owner.isAlive(info),
|
|
91
95
|
endpoint: () => Object.freeze({
|
|
92
96
|
...machine.owner.endpoint(),
|
|
93
97
|
kind: machine.kind,
|
|
94
|
-
label: profile ? label : null,
|
|
98
|
+
label: machine.profile ? machine.label : null,
|
|
95
99
|
epoch: machine.epoch,
|
|
96
|
-
...(disposed
|
|
100
|
+
...(disposed || machine.retired
|
|
101
|
+
? { state: "disconnected", remote: null, localBaseUrl: null }
|
|
102
|
+
: {}),
|
|
97
103
|
}),
|
|
98
104
|
observe: async (listener) => {
|
|
99
|
-
if (disposed)
|
|
105
|
+
if (disposed || machine.retired)
|
|
100
106
|
return () => { };
|
|
101
107
|
if (machine.kind === "local")
|
|
102
108
|
observe(machine, machine.owner);
|
|
@@ -119,7 +125,7 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
119
125
|
});
|
|
120
126
|
observe(machine, owner);
|
|
121
127
|
publish();
|
|
122
|
-
void initializing.then(() => settle(!disposed), () => settle(false));
|
|
128
|
+
void initializing.then(() => settle(!disposed && !machine.retired), () => settle(false));
|
|
123
129
|
return machine;
|
|
124
130
|
};
|
|
125
131
|
makeMachine(LOCAL_MACHINE_ID, "Local");
|
|
@@ -206,6 +212,48 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
206
212
|
if (profile.enabled)
|
|
207
213
|
manager.add(profile);
|
|
208
214
|
},
|
|
215
|
+
reconcile(profiles) {
|
|
216
|
+
if (disposed)
|
|
217
|
+
return;
|
|
218
|
+
const next = SavedMachineRegistrySchema.parse({ version: 1, machines: profiles });
|
|
219
|
+
const previousSelection = selectedId;
|
|
220
|
+
for (const machine of [...machines.values()]) {
|
|
221
|
+
if (!machine.profile)
|
|
222
|
+
continue;
|
|
223
|
+
const profile = next.machines.find((candidate) => candidate.id === machine.id && candidate.enabled);
|
|
224
|
+
if (profile &&
|
|
225
|
+
profile.sshTarget === machine.profile.sshTarget &&
|
|
226
|
+
profile.expectedEnvironmentId === machine.profile.expectedEnvironmentId) {
|
|
227
|
+
// Labels are presentation metadata, not transport identity. Preserve
|
|
228
|
+
// the connected owner and every consumer holding its stable handle.
|
|
229
|
+
machine.label = profile.label;
|
|
230
|
+
machine.profile = profile;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (selectedId === machine.id)
|
|
234
|
+
manager.select(LOCAL_MACHINE_ID);
|
|
235
|
+
machine.retired = true;
|
|
236
|
+
machine.stopStatus?.();
|
|
237
|
+
machine.stopObserver?.();
|
|
238
|
+
for (const listener of machine.listeners)
|
|
239
|
+
safe(() => listener(null));
|
|
240
|
+
machine.owner.dispose();
|
|
241
|
+
machine.listeners.clear();
|
|
242
|
+
machines.delete(machine.id);
|
|
243
|
+
}
|
|
244
|
+
for (const profile of next.machines)
|
|
245
|
+
if (profile.enabled)
|
|
246
|
+
manager.add(profile);
|
|
247
|
+
if (machines.has(previousSelection))
|
|
248
|
+
manager.select(previousSelection);
|
|
249
|
+
publish();
|
|
250
|
+
},
|
|
251
|
+
followProfiles(subscribe) {
|
|
252
|
+
if (disposed)
|
|
253
|
+
return;
|
|
254
|
+
stopProfiles?.();
|
|
255
|
+
stopProfiles = subscribe(manager.reconcile);
|
|
256
|
+
},
|
|
209
257
|
read: () => machines.get(selectedId)?.handle.read() ?? null,
|
|
210
258
|
isAlive: (info) => machines.get(selectedId)?.handle.isAlive(info) ?? Promise.resolve(false),
|
|
211
259
|
endpoint: () => Object.freeze({ ...machines.get(selectedId).handle.endpoint(), epoch: selectedEpoch }),
|
|
@@ -223,6 +271,8 @@ export function createApplicationMachineAuthorityManager(dependencies = {
|
|
|
223
271
|
if (disposed)
|
|
224
272
|
return;
|
|
225
273
|
disposed = true;
|
|
274
|
+
stopProfiles?.();
|
|
275
|
+
stopProfiles = null;
|
|
226
276
|
lifetime.abort();
|
|
227
277
|
selectedEpoch++;
|
|
228
278
|
selectedGeneration(null);
|
|
@@ -123,7 +123,9 @@ export function createApplicationMachineNavigation(options) {
|
|
|
123
123
|
return false;
|
|
124
124
|
}
|
|
125
125
|
const selected = candidates[0];
|
|
126
|
-
const sameSelection =
|
|
126
|
+
const sameSelection = manager.snapshot().selectedMachineId === id &&
|
|
127
|
+
activeSessionKey() === selected.id &&
|
|
128
|
+
options.sessionName() === name;
|
|
127
129
|
setActiveSessionKey(selected.id);
|
|
128
130
|
expectedLiveSessionId = selected.liveSessionId;
|
|
129
131
|
server = selected.server;
|
|
@@ -222,18 +224,25 @@ export function createApplicationMachineNavigation(options) {
|
|
|
222
224
|
tabs.observe(tab.key, tabAgents(tab));
|
|
223
225
|
});
|
|
224
226
|
const sidebar = {
|
|
225
|
-
groups: () =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
227
|
+
groups: () => {
|
|
228
|
+
const groupsById = new Map(agentGroups().map((group) => [group.machineId, group]));
|
|
229
|
+
return snapshot().groups.map((group) => {
|
|
230
|
+
// Read once per projection: local authority reads validate the record on disk.
|
|
231
|
+
// The next projection (and action admission) still observes replacement immediately.
|
|
232
|
+
const instanceId = manager.getMachine(group.id)?.read()?.instanceId;
|
|
233
|
+
const defaultSessions = group.sessions.filter((session) => !session.server || session.server.generation === instanceId);
|
|
234
|
+
const agents = defaultSessions.length ? groupsById.get(group.id) : undefined;
|
|
235
|
+
return {
|
|
236
|
+
...group,
|
|
237
|
+
agentsAvailable: agents?.available ?? false,
|
|
238
|
+
agents: (agents?.agents ?? []).map((agent) => ({
|
|
239
|
+
...agent,
|
|
240
|
+
server: defaultSessions.find((session) => session.liveSessionId === agent.liveSessionId)
|
|
241
|
+
?.server,
|
|
242
|
+
})),
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
},
|
|
237
246
|
tabs: () => {
|
|
238
247
|
tabRevision();
|
|
239
248
|
const value = tabs.snapshot();
|
|
@@ -409,7 +418,9 @@ export function createApplicationMachineNavigation(options) {
|
|
|
409
418
|
paletteCommands() {
|
|
410
419
|
const groupsById = new Map(agentGroups().map((group) => [group.machineId, group]));
|
|
411
420
|
return snapshot().groups.flatMap((group) => {
|
|
412
|
-
const
|
|
421
|
+
const daemon = manager.getMachine(group.id)?.read();
|
|
422
|
+
const isDefault = (session) => !session.server || session.server.generation === daemon?.instanceId;
|
|
423
|
+
const agentGroup = !group.sessions.some((session) => isDefault(session))
|
|
413
424
|
? undefined
|
|
414
425
|
: groupsById.get(group.id);
|
|
415
426
|
const agentsBySession = new Map();
|
|
@@ -420,7 +431,6 @@ export function createApplicationMachineNavigation(options) {
|
|
|
420
431
|
list.push(agent);
|
|
421
432
|
agentsBySession.set(agent.liveSessionId, list);
|
|
422
433
|
}
|
|
423
|
-
const daemon = manager.getMachine(group.id)?.read();
|
|
424
434
|
const sessions = group.sessions.flatMap((session) => {
|
|
425
435
|
if (!session.liveSessionId)
|
|
426
436
|
return [];
|
|
@@ -433,7 +443,7 @@ export function createApplicationMachineNavigation(options) {
|
|
|
433
443
|
liveSessionId: session.liveSessionId,
|
|
434
444
|
server: session.server,
|
|
435
445
|
hostLabel: `${group.label}${session.serverLabel ? ` / ${session.serverLabel}` : ""}`,
|
|
436
|
-
agentActivities:
|
|
446
|
+
agentActivities: isDefault(session) && agentGroup?.available
|
|
437
447
|
? (agentsBySession.get(session.liveSessionId) ?? []).map((a) => ({
|
|
438
448
|
paneId: a.paneId,
|
|
439
449
|
attention: a.attention,
|
|
@@ -445,9 +455,7 @@ export function createApplicationMachineNavigation(options) {
|
|
|
445
455
|
};
|
|
446
456
|
return [
|
|
447
457
|
{ kind: "open-session", sessionName: session.name, label: session.name, fleet },
|
|
448
|
-
...(
|
|
449
|
-
? (agentsBySession.get(session.liveSessionId) ?? [])
|
|
450
|
-
: []).map((a) => ({
|
|
458
|
+
...(isDefault(session) ? (agentsBySession.get(session.liveSessionId) ?? []) : []).map((a) => ({
|
|
451
459
|
kind: "jump-agent",
|
|
452
460
|
sessionName: session.name,
|
|
453
461
|
paneId: a.paneId,
|
|
@@ -5,13 +5,16 @@ import { fleetHostColor } from "./fleet-presentation.js";
|
|
|
5
5
|
/* @jsxImportSource @opentui/solid */
|
|
6
6
|
import { fleetConnectionMessage, } from "@tmux-ide/daemon-client/fleet-connection-status";
|
|
7
7
|
import { For, Show, createEffect, createMemo, createSignal, } from "solid-js";
|
|
8
|
-
import { friendlySessionLabel } from "../terminal-text.js";
|
|
8
|
+
import { friendlySessionLabel, wrapText } from "../terminal-text.js";
|
|
9
9
|
import { KeyHint } from "../ui/key-hint.jsx";
|
|
10
10
|
import { NavigationRow } from "../ui/navigation-row.jsx";
|
|
11
11
|
import { Surface } from "../ui/surface.jsx";
|
|
12
12
|
import { useKeyboardRoute } from "../ui/keyboard-router.jsx";
|
|
13
13
|
/** Pure machine navigation. All connection and session authority stays with the caller. */
|
|
14
14
|
export function ApplicationMachineSidebar(props) {
|
|
15
|
+
// All visible sections share one projection for the current reactive inputs.
|
|
16
|
+
// Event handlers retain the model's fresh authority checks at action time.
|
|
17
|
+
const groups = createMemo(() => props.model.groups());
|
|
15
18
|
const [localFocused, setLocalFocused] = createSignal(false);
|
|
16
19
|
const focused = () => props.model.focused?.() ?? localFocused();
|
|
17
20
|
const [localCollapsed, setCollapsed] = createSignal(new Set());
|
|
@@ -19,16 +22,14 @@ export function ApplicationMachineSidebar(props) {
|
|
|
19
22
|
const [selectedKey, setSelectedKey] = createSignal(null);
|
|
20
23
|
let scroll;
|
|
21
24
|
const agentHeight = () => (props.agentRows ?? 0) > 0
|
|
22
|
-
? Math.min((props.agentRows ?? 0) + 2, Math.max(0, Math.floor(props.height / 2)))
|
|
25
|
+
? Math.min((props.agentRows ?? 0) + 2, Math.max(0, Math.floor(props.height / 2)), Math.max(0, props.height - fixedHeight() - controlsHeight() - 2))
|
|
23
26
|
: 0;
|
|
24
|
-
const controlsHeight = () => (props.height >= 8 && controlGroup() ?
|
|
27
|
+
const controlsHeight = () => (props.height >= 8 && controlGroup() ? controlTextHeight() + 2 : 0);
|
|
25
28
|
const searchHeight = () => (props.model.onOpenSwitcher ? 1 : 0) + (props.model.onOpenAttention ? 1 : 0);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
controlsHeight() -
|
|
31
|
-
searchHeight());
|
|
29
|
+
const fixedHeight = () => 1 + (props.model.onAddMachine ? 1 : 0) + searchHeight();
|
|
30
|
+
// Keep both action rows and at least two machine rows available on short terminals.
|
|
31
|
+
const controlTextHeight = () => Math.min(controlTextLines().length, Math.max(1, props.height - fixedHeight() - 4));
|
|
32
|
+
const machineHeight = () => Math.max(0, props.height - fixedHeight() - agentHeight() - controlsHeight());
|
|
32
33
|
const active = (row) => row.group.id === props.model.activeMachineId() &&
|
|
33
34
|
(row.agent
|
|
34
35
|
? row.group.state === "ready" &&
|
|
@@ -46,7 +47,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
46
47
|
: row.session?.name === props.model.activeSessionName());
|
|
47
48
|
const preferenceKey = (group) => group.environmentId ?? group.id;
|
|
48
49
|
const rows = createMemo(() => [
|
|
49
|
-
...
|
|
50
|
+
...groups().flatMap((group) => groupApplicationTeamRows(group.agents ?? []).map((agent) => ({
|
|
50
51
|
key: JSON.stringify([
|
|
51
52
|
group.id,
|
|
52
53
|
"agent",
|
|
@@ -57,7 +58,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
57
58
|
group,
|
|
58
59
|
agent,
|
|
59
60
|
}))),
|
|
60
|
-
...
|
|
61
|
+
...groups().flatMap((group) => [
|
|
61
62
|
{ key: JSON.stringify([group.id]), group },
|
|
62
63
|
...group.sessions
|
|
63
64
|
.filter((session) => !collapsed().has(preferenceKey(group)) ||
|
|
@@ -118,6 +119,12 @@ export function ApplicationMachineSidebar(props) {
|
|
|
118
119
|
: "disconnected"
|
|
119
120
|
: diagnostic.phase;
|
|
120
121
|
};
|
|
122
|
+
const controlTextLines = createMemo(() => {
|
|
123
|
+
const group = controlGroup();
|
|
124
|
+
return group
|
|
125
|
+
? wrapText(group.diagnostic ? fleetConnectionMessage(group.diagnostic) : connectionDetail(group), Math.max(1, props.width))
|
|
126
|
+
: [];
|
|
127
|
+
});
|
|
121
128
|
const toggle = (group, value = !collapsed().has(preferenceKey(group))) => {
|
|
122
129
|
const next = new Set(collapsed());
|
|
123
130
|
if (value)
|
|
@@ -163,7 +170,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
163
170
|
const sectionGap = (row) => (!row.session && !row.agent && !row.server ? 1 : 0);
|
|
164
171
|
const rowHeight = (row) => (row.agent ? 2 : row.serverHeading ? 2 : 1) +
|
|
165
172
|
sectionGap(row) +
|
|
166
|
-
(hasAgents() && row.key === JSON.stringify([
|
|
173
|
+
(hasAgents() && row.key === JSON.stringify([groups()[0]?.id]) ? 1 : 0);
|
|
167
174
|
const revealFocusedRow = () => {
|
|
168
175
|
if (!focused() || !scroll)
|
|
169
176
|
return;
|
|
@@ -346,7 +353,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
346
353
|
props.model.onSelectServer?.(row.group.id, row.session.server);
|
|
347
354
|
}}/>
|
|
348
355
|
</Show>
|
|
349
|
-
<Show when={hasAgents() && row.key === JSON.stringify([
|
|
356
|
+
<Show when={hasAgents() && row.key === JSON.stringify([groups()[0]?.id])}>
|
|
350
357
|
<text height={1} fg={props.theme.roles.text.secondary}>
|
|
351
358
|
{" "}
|
|
352
359
|
Machines
|
|
@@ -399,14 +406,12 @@ export function ApplicationMachineSidebar(props) {
|
|
|
399
406
|
<KeyHint theme={props.theme} keys={CHROME_ACTIONS.sessions.keys} label="Browse all sessions" width={props.width} quiet button onPress={() => props.model.onOpenSwitcher?.()}/>
|
|
400
407
|
</Show>
|
|
401
408
|
<Show when={props.model.onOpenAttention}>
|
|
402
|
-
<KeyHint theme={props.theme} keys={CHROME_ACTIONS.attention.keys} label={`${CHROME_ACTIONS.attention.label} (${
|
|
409
|
+
<KeyHint theme={props.theme} keys={CHROME_ACTIONS.attention.keys} label={`${CHROME_ACTIONS.attention.label} (${groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)})`} width={props.width} quiet button onPress={() => props.model.onOpenAttention?.()}/>
|
|
403
410
|
</Show>
|
|
404
411
|
<Show when={controlsHeight() > 0 && controlGroup()}>
|
|
405
412
|
{(group) => (<>
|
|
406
|
-
<text height={
|
|
407
|
-
{
|
|
408
|
-
? fleetConnectionMessage(group().diagnostic)
|
|
409
|
-
: connectionDetail(group())}
|
|
413
|
+
<text height={controlTextHeight()} flexShrink={0} fg={props.theme.roles.text.secondary}>
|
|
414
|
+
{controlTextLines().join("\n")}
|
|
410
415
|
</text>
|
|
411
416
|
<KeyHint theme={props.theme} keys={SIDEBAR_ACTIONS.retry.keys} label={SIDEBAR_ACTIONS.retry.label} width={props.width} quiet button onPress={() => props.model.onRetryMachine?.(group().id)}/>
|
|
412
417
|
<KeyHint theme={props.theme} keys={SIDEBAR_ACTIONS.disconnect.keys} label={SIDEBAR_ACTIONS.disconnect.label} width={props.width} quiet button onPress={() => props.model.onDisconnectMachine?.(group().id)}/>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { resolveRuntimeNamespace } from "../../../lib/runtime-namespace.js";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { SavedMachineSchema } from "@tmux-ide/contracts";
|
|
4
|
-
import { loadSavedMachines } from "../../../lib/saved-machines.js";
|
|
4
|
+
import { loadSavedMachines, watchSavedMachines } from "../../../lib/saved-machines.js";
|
|
5
5
|
import { applicationMachineAuthorityManager } from "./application-machine-authority.js";
|
|
6
|
+
import { tuiPerfMark } from "./application-performance-log.js";
|
|
6
7
|
/** Labels describe access routes; they never become routing identities. */
|
|
7
8
|
export function ephemeralMachineProfile(alias, existing) {
|
|
8
9
|
const target = SavedMachineSchema.shape.sshTarget.parse(alias);
|
|
@@ -20,17 +21,45 @@ export function ephemeralMachineProfile(alias, existing) {
|
|
|
20
21
|
}
|
|
21
22
|
/** Local discovery and rendering never wait for any remote handshake. */
|
|
22
23
|
export function initializeApplicationMachines(aliases) {
|
|
23
|
-
const
|
|
24
|
+
const development = resolveRuntimeNamespace().development;
|
|
25
|
+
const profiles = development ? [] : [...loadSavedMachines().machines];
|
|
26
|
+
const ephemeral = [];
|
|
24
27
|
let preferred = null;
|
|
25
28
|
for (const alias of aliases) {
|
|
26
29
|
let profile = profiles.find((p) => p.sshTarget === alias && p.enabled);
|
|
27
30
|
if (!profile) {
|
|
28
31
|
profile = ephemeralMachineProfile(alias, profiles);
|
|
29
32
|
profiles.push(profile);
|
|
33
|
+
ephemeral.push(profile);
|
|
30
34
|
}
|
|
31
35
|
preferred ??= profile.id;
|
|
32
36
|
}
|
|
33
37
|
applicationMachineAuthorityManager.initialize(profiles);
|
|
34
38
|
if (preferred)
|
|
35
39
|
applicationMachineAuthorityManager.select(preferred);
|
|
40
|
+
if (!development) {
|
|
41
|
+
const failed = () => tuiPerfMark("machine-registry-error", { reason: "registry-unavailable-or-invalid" });
|
|
42
|
+
try {
|
|
43
|
+
applicationMachineAuthorityManager.followProfiles((onChange) => watchSavedMachines((registry) => {
|
|
44
|
+
const next = [...registry.machines];
|
|
45
|
+
for (let index = 0; index < ephemeral.length; index++) {
|
|
46
|
+
let profile = ephemeral[index];
|
|
47
|
+
// A newly saved route may claim an ephemeral display label. Keep the
|
|
48
|
+
// explicit connection's identity while making the label unambiguous.
|
|
49
|
+
if (next.some((entry) => entry.label.normalize("NFKC").toLowerCase() ===
|
|
50
|
+
profile.label.normalize("NFKC").toLowerCase()))
|
|
51
|
+
profile = {
|
|
52
|
+
...profile,
|
|
53
|
+
label: ephemeralMachineProfile(profile.sshTarget, next).label,
|
|
54
|
+
};
|
|
55
|
+
ephemeral[index] = profile;
|
|
56
|
+
next.push(profile);
|
|
57
|
+
}
|
|
58
|
+
onChange(next);
|
|
59
|
+
}, failed));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
failed();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
36
65
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createMemo, createSignal } from "solid-js";
|
|
1
|
+
import { createMemo, createSignal, untrack } from "solid-js";
|
|
2
2
|
import { applicationCommandDescription, filterApplicationCommands, } from "../workspace/application-command-description.js";
|
|
3
3
|
import { applicationPaletteKeyboardDisposition, } from "./application-palette-input.js";
|
|
4
4
|
import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
|
|
@@ -11,9 +11,13 @@ export function createApplicationPaletteSearchOwner(options) {
|
|
|
11
11
|
const [help, setHelp] = createSignal(false);
|
|
12
12
|
const [query, setQuery] = createSignal("");
|
|
13
13
|
const [selectedId, setSelectedId] = createSignal(null);
|
|
14
|
-
const
|
|
14
|
+
const readCommands = () => filterApplicationCommands(options
|
|
15
15
|
.commands()
|
|
16
|
-
.filter((c) => !localOnly() || typeof c === "string" || !c.fleet || c.fleet.machineId === "local"), query())
|
|
16
|
+
.filter((c) => !localOnly() || typeof c === "string" || !c.fleet || c.fleet.machineId === "local"), query());
|
|
17
|
+
// Closed overlays must not subscribe to catalog churn. Explicit reads still
|
|
18
|
+
// resolve current commands, including reset() before the open action commits.
|
|
19
|
+
const activeCommands = createMemo(() => (options.open() ? readCommands() : null));
|
|
20
|
+
const commands = () => activeCommands() ?? untrack(readCommands);
|
|
17
21
|
const selection = () => Math.max(0, commands().findIndex((command) => applicationCommandDescription(command).id === selectedId()));
|
|
18
22
|
const select = (index) => {
|
|
19
23
|
const command = commands()[index];
|
|
@@ -29,6 +29,8 @@ stream?.on("drain", drain);
|
|
|
29
29
|
export const tuiLifecycleStream = stream ? Object.freeze({ enabled: true }) : null;
|
|
30
30
|
export const tuiPerfStream = TUI_PERF_LOG ? tuiLifecycleStream : null;
|
|
31
31
|
const LIFECYCLE_PHASES = new Set([
|
|
32
|
+
"machine-connection-status",
|
|
33
|
+
"machine-registry-error",
|
|
32
34
|
"generation-status",
|
|
33
35
|
"generation-connection-start",
|
|
34
36
|
"generation-connection-resolved",
|
|
@@ -132,6 +134,7 @@ export function closeTuiPerfMarks() {
|
|
|
132
134
|
closePromise = (async () => {
|
|
133
135
|
if (!stream)
|
|
134
136
|
return;
|
|
137
|
+
await writer?.flush();
|
|
135
138
|
await flushTuiPerfMarks();
|
|
136
139
|
// No drain callback may enqueue another critical record after end().
|
|
137
140
|
stream.off("drain", drain);
|
|
@@ -7,6 +7,133 @@ import { terminalInputForOpenTuiKey, terminalInputsForPaste, } from "./terminal-
|
|
|
7
7
|
export function createApplicationTerminalInputIngress(interaction, generation, sessionOwner, focusedPane, setNote) {
|
|
8
8
|
const pending = createApplicationPendingTerminalInputOwner();
|
|
9
9
|
let ownsNote = false;
|
|
10
|
+
let recovery = null;
|
|
11
|
+
const scope = () => {
|
|
12
|
+
const active = generation();
|
|
13
|
+
const owner = sessionOwner();
|
|
14
|
+
const pane = focusedPane();
|
|
15
|
+
const session = owner?.sessionName();
|
|
16
|
+
if (!active ||
|
|
17
|
+
!["live", "rebinding"].includes(active.status) ||
|
|
18
|
+
!active.connection ||
|
|
19
|
+
!active.client ||
|
|
20
|
+
!active.daemonGeneration ||
|
|
21
|
+
!owner ||
|
|
22
|
+
!session ||
|
|
23
|
+
!pane)
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
const clientGeneration = active.client.getSnapshot().generation;
|
|
27
|
+
if (!Number.isSafeInteger(clientGeneration))
|
|
28
|
+
return null;
|
|
29
|
+
return {
|
|
30
|
+
owner,
|
|
31
|
+
connection: active.connection,
|
|
32
|
+
client: active.client,
|
|
33
|
+
daemon: active.daemonGeneration,
|
|
34
|
+
clientGeneration: clientGeneration,
|
|
35
|
+
session: session,
|
|
36
|
+
pane,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const sameScope = (left, right) => right !== null &&
|
|
44
|
+
left.owner === right.owner &&
|
|
45
|
+
left.connection === right.connection &&
|
|
46
|
+
left.client === right.client &&
|
|
47
|
+
left.daemon === right.daemon &&
|
|
48
|
+
left.clientGeneration === right.clientGeneration &&
|
|
49
|
+
left.session === right.session &&
|
|
50
|
+
left.pane === right.pane;
|
|
51
|
+
let recoveryVersion = 0;
|
|
52
|
+
const cancelRecovery = () => {
|
|
53
|
+
recoveryVersion++;
|
|
54
|
+
if (recovery)
|
|
55
|
+
clearTimeout(recovery.timer);
|
|
56
|
+
recovery = null;
|
|
57
|
+
};
|
|
58
|
+
const recover = (inputs, bytes, kind) => {
|
|
59
|
+
if (pending.snapshot().sessionName !== null ||
|
|
60
|
+
(!recovery && generation()?.status !== "rebinding"))
|
|
61
|
+
return false;
|
|
62
|
+
const target = scope();
|
|
63
|
+
if (!target)
|
|
64
|
+
return false;
|
|
65
|
+
if (recovery && !sameScope(recovery.scope, target))
|
|
66
|
+
cancelRecovery();
|
|
67
|
+
if (!recovery) {
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
cancelRecovery();
|
|
70
|
+
setNote("terminal recovery timed out · queued input was not sent");
|
|
71
|
+
}, 5_000);
|
|
72
|
+
timer.unref?.();
|
|
73
|
+
recovery = { scope: target, inputs: [], weight: 0, admitted: 0, draining: false, timer };
|
|
74
|
+
}
|
|
75
|
+
const weight = bytes.byteLength;
|
|
76
|
+
if (recovery.admitted >= 64 || recovery.weight + weight > 1024 * 1024) {
|
|
77
|
+
setNote("terminal input queue full · wait for the session to connect");
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
recovery.weight += weight;
|
|
81
|
+
recovery.admitted++;
|
|
82
|
+
const version = recoveryVersion;
|
|
83
|
+
const parserOrigin = captureOrigin()
|
|
84
|
+
? {
|
|
85
|
+
origin: kind === "paste" ? "bracketed-paste" : "keyboard",
|
|
86
|
+
payload: Buffer.from(bytes),
|
|
87
|
+
}
|
|
88
|
+
: undefined;
|
|
89
|
+
recovery.inputs.push(async () => {
|
|
90
|
+
for (const [index, input] of inputs.entries()) {
|
|
91
|
+
if (version !== recoveryVersion ||
|
|
92
|
+
!sameScope(target, scope()) ||
|
|
93
|
+
generation()?.status !== "live" ||
|
|
94
|
+
!(await interaction.sendInputToPane(target.pane, input, index === 0 ? parserOrigin : undefined)))
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
});
|
|
99
|
+
setNote(`${target.session} · terminal ${kind} queued`);
|
|
100
|
+
return true;
|
|
101
|
+
};
|
|
102
|
+
const flushRecovery = () => {
|
|
103
|
+
const waiting = recovery;
|
|
104
|
+
if (!waiting || waiting.draining)
|
|
105
|
+
return;
|
|
106
|
+
if (!sameScope(waiting.scope, scope())) {
|
|
107
|
+
cancelRecovery();
|
|
108
|
+
setNote("terminal changed · queued input was not sent");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (generation()?.status !== "live" || !generation()?.fastLane)
|
|
112
|
+
return;
|
|
113
|
+
waiting.draining = true;
|
|
114
|
+
const version = recoveryVersion;
|
|
115
|
+
void (async () => {
|
|
116
|
+
while (version === recoveryVersion && waiting.inputs.length > 0) {
|
|
117
|
+
const send = waiting.inputs.shift();
|
|
118
|
+
try {
|
|
119
|
+
if (await send())
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* An uncertain send is never retried. */
|
|
124
|
+
}
|
|
125
|
+
if (version !== recoveryVersion)
|
|
126
|
+
return;
|
|
127
|
+
cancelRecovery();
|
|
128
|
+
setNote("terminal unavailable · queued input was not sent");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (version === recoveryVersion) {
|
|
132
|
+
cancelRecovery();
|
|
133
|
+
setNote(null);
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
};
|
|
10
137
|
const captureOrigin = () => Boolean(currentTuiPerformanceEventSink()?.terminalInputOrigin);
|
|
11
138
|
const flush = () => {
|
|
12
139
|
const owner = sessionOwner();
|
|
@@ -36,9 +163,14 @@ export function createApplicationTerminalInputIngress(interaction, generation, s
|
|
|
36
163
|
else
|
|
37
164
|
setNote(`terminal unavailable · ${kind} was not sent`);
|
|
38
165
|
};
|
|
166
|
+
const cancelInteractionInput = () => {
|
|
167
|
+
cancelRecovery();
|
|
168
|
+
interaction.cancelPendingInput();
|
|
169
|
+
};
|
|
39
170
|
return {
|
|
40
171
|
wrapStarter(starter) {
|
|
41
172
|
const start = async (...args) => {
|
|
173
|
+
cancelInteractionInput();
|
|
42
174
|
const [sessionName] = args;
|
|
43
175
|
const identity = pending.begin(sessionName);
|
|
44
176
|
const result = await starter(...args);
|
|
@@ -52,24 +184,32 @@ export function createApplicationTerminalInputIngress(interaction, generation, s
|
|
|
52
184
|
};
|
|
53
185
|
return Object.assign(start, {
|
|
54
186
|
cancel() {
|
|
187
|
+
cancelInteractionInput();
|
|
55
188
|
pending.dispose();
|
|
56
189
|
starter.cancel();
|
|
57
190
|
},
|
|
58
191
|
});
|
|
59
192
|
},
|
|
60
|
-
adopt() {
|
|
193
|
+
adopt(terminalsActive = true) {
|
|
194
|
+
if (!terminalsActive)
|
|
195
|
+
cancelInteractionInput();
|
|
61
196
|
flush();
|
|
197
|
+
flushRecovery();
|
|
62
198
|
},
|
|
63
199
|
routeKey(event) {
|
|
64
200
|
const active = generation();
|
|
65
201
|
if (pending.snapshot().sessionName === null &&
|
|
202
|
+
recovery === null &&
|
|
66
203
|
active?.status === "live" &&
|
|
67
204
|
active.fastLane &&
|
|
68
205
|
focusedPane()) {
|
|
69
206
|
sendApplicationTerminalKey(interaction, event, captureOrigin());
|
|
70
207
|
return;
|
|
71
208
|
}
|
|
72
|
-
|
|
209
|
+
const recoveryInput = terminalInputForOpenTuiKey(event);
|
|
210
|
+
if (!recoveryInput)
|
|
211
|
+
return;
|
|
212
|
+
if (recover([recoveryInput], Buffer.from(recoveryInput.data, "utf8"), "input"))
|
|
73
213
|
return;
|
|
74
214
|
const copy = { name: event.name, ctrl: event.ctrl, meta: event.meta, shift: event.shift };
|
|
75
215
|
noteAdmission(pending.enqueue(() => sendApplicationTerminalKey(interaction, copy, captureOrigin()), Buffer.byteLength(event.name, "utf8")), "input");
|
|
@@ -87,15 +227,19 @@ export function createApplicationTerminalInputIngress(interaction, generation, s
|
|
|
87
227
|
}
|
|
88
228
|
const active = generation();
|
|
89
229
|
if (pending.snapshot().sessionName === null &&
|
|
230
|
+
recovery === null &&
|
|
90
231
|
active?.status === "live" &&
|
|
91
232
|
active.fastLane &&
|
|
92
233
|
focusedPane()) {
|
|
93
234
|
sendApplicationTerminalPaste(interaction, copy, captureOrigin());
|
|
94
235
|
return;
|
|
95
236
|
}
|
|
237
|
+
if (recover(terminalInputsForPaste(Buffer.from(copy).toString("utf8")), copy, "paste"))
|
|
238
|
+
return;
|
|
96
239
|
noteAdmission(pending.enqueue(() => sendApplicationTerminalPaste(interaction, copy, captureOrigin()), copy.byteLength), "paste");
|
|
97
240
|
},
|
|
98
241
|
dispose() {
|
|
242
|
+
cancelInteractionInput();
|
|
99
243
|
pending.dispose();
|
|
100
244
|
},
|
|
101
245
|
};
|