tmux-ide 2.9.2 → 2.9.3
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/README.md +1 -1
- package/bin/cli.js +2071 -625
- package/package.json +14 -5
- 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/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/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/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
|
@@ -137,6 +137,11 @@ export class SessionRuntimeRegistry {
|
|
|
137
137
|
this.#mirror = new MirrorService({
|
|
138
138
|
...options.mirror,
|
|
139
139
|
...diagnosticMirrorOptions,
|
|
140
|
+
onSharedWindowConflict: (session, conflict) => {
|
|
141
|
+
options.mirror?.onSharedWindowConflict?.(session, conflict);
|
|
142
|
+
if (conflict)
|
|
143
|
+
this.#sessions.get(session)?.invalidateSharedWindowAuthority();
|
|
144
|
+
},
|
|
140
145
|
onNativeClientActivity: (session) => {
|
|
141
146
|
options.mirror?.onNativeClientActivity?.(session);
|
|
142
147
|
this.#sessions.get(session)?.noteNativeGeometryActivity();
|
|
@@ -171,6 +176,7 @@ export class SessionRuntimeRegistry {
|
|
|
171
176
|
signal?.throwIfAborted();
|
|
172
177
|
const runtime = this.#runtime(session);
|
|
173
178
|
await abortable(runtime.whenReady(), signal);
|
|
179
|
+
await abortable(this.#mirror.verifyRegisteredWindows(), signal);
|
|
174
180
|
if (this.#sessions.get(session) !== runtime) {
|
|
175
181
|
throw new Error(`SessionRuntime ${session} was retired while prewarming`);
|
|
176
182
|
}
|
|
@@ -213,6 +219,7 @@ export class SessionRuntimeRegistry {
|
|
|
213
219
|
throw new Error(`SessionRuntime ${session} is attached to a different tmux identity`);
|
|
214
220
|
}
|
|
215
221
|
signal?.throwIfAborted();
|
|
222
|
+
await abortable(this.#mirror.verifyRegisteredWindows(), signal);
|
|
216
223
|
runtime.qualifyTrustedInventory(runtimeSessionId);
|
|
217
224
|
}
|
|
218
225
|
finally {
|
|
@@ -235,24 +242,28 @@ export class SessionRuntimeRegistry {
|
|
|
235
242
|
if (!runtime?.trustedInventoryQualified()) {
|
|
236
243
|
throw new Error(`SessionRuntime ${session} has no proof-qualified inventory authority`);
|
|
237
244
|
}
|
|
245
|
+
const epoch = runtime.trustedInventoryEpoch;
|
|
238
246
|
const inventory = await this.#observeTerminalAttempt("terminal-trusted-inventory-attempt", abortable(runtime.describeTrustedInventory(), signal));
|
|
239
|
-
if (this.#sessions.get(session) !== runtime ||
|
|
247
|
+
if (this.#sessions.get(session) !== runtime ||
|
|
248
|
+
!runtime.trustedInventoryQualified() ||
|
|
249
|
+
runtime.trustedInventoryEpoch !== epoch) {
|
|
240
250
|
throw new Error(`SessionRuntime ${session} changed during trusted inventory discovery`);
|
|
241
251
|
}
|
|
242
252
|
if (!inventory) {
|
|
243
253
|
throw new Error(`SessionRuntime ${session} lost its retained inventory authority`);
|
|
244
254
|
}
|
|
245
255
|
const token = Object.freeze({});
|
|
246
|
-
this.#trustedInventoryTokens.set(token, runtime);
|
|
256
|
+
this.#trustedInventoryTokens.set(token, { runtime, epoch });
|
|
247
257
|
return Object.freeze({ inventory, token });
|
|
248
258
|
}
|
|
249
259
|
isTrustedSessionInventoryCandidateCurrent(session, token) {
|
|
250
260
|
if (this.#disposed)
|
|
251
261
|
return false;
|
|
252
|
-
const
|
|
253
|
-
return (
|
|
254
|
-
this.#sessions.get(session) === runtime &&
|
|
255
|
-
runtime.
|
|
262
|
+
const candidate = this.#trustedInventoryTokens.get(token);
|
|
263
|
+
return (candidate !== undefined &&
|
|
264
|
+
this.#sessions.get(session) === candidate.runtime &&
|
|
265
|
+
candidate.runtime.trustedInventoryEpoch === candidate.epoch &&
|
|
266
|
+
candidate.runtime.trustedInventoryQualified());
|
|
256
267
|
}
|
|
257
268
|
hasProofQualifiedInventory(session) {
|
|
258
269
|
return ((this.#sessions.get(session)?.trustedInventoryQualified() ?? false) &&
|
|
@@ -269,6 +280,14 @@ export class SessionRuntimeRegistry {
|
|
|
269
280
|
return Promise.reject(new Error("Session runtime disposed"));
|
|
270
281
|
return this.#mirror.executeWindowLinkAction(session, request);
|
|
271
282
|
}
|
|
283
|
+
/** Resolve pending ownership before an asynchronous owner-authorized mutation. */
|
|
284
|
+
async verifyWindowOwnership() {
|
|
285
|
+
await this.#mirror.verifyRegisteredWindows();
|
|
286
|
+
}
|
|
287
|
+
/** Install the bounded registered-session membership reader. */
|
|
288
|
+
setRegisteredWindowReader(reader) {
|
|
289
|
+
return this.#mirror.setRegisteredWindowReader(reader);
|
|
290
|
+
}
|
|
272
291
|
/** Retire one no-longer-registered session without disturbing siblings. */
|
|
273
292
|
async retireSession(session) {
|
|
274
293
|
const runtime = this.#sessions.get(session);
|
|
@@ -665,6 +684,10 @@ class SessionRuntime {
|
|
|
665
684
|
#releasedLeases = new Set();
|
|
666
685
|
#disposed = false;
|
|
667
686
|
#trustedInventoryRuntimeSessionId = null;
|
|
687
|
+
#trustedInventoryEpoch = 0;
|
|
688
|
+
get trustedInventoryEpoch() {
|
|
689
|
+
return this.#trustedInventoryEpoch;
|
|
690
|
+
}
|
|
668
691
|
#activeCausalCellProbes = 0;
|
|
669
692
|
constructor(generation, session, mirror, createControllerToken, scheduler, observability, createTraceCorrelator, nativeGeometryHysteresisMs, submitAuthorized) {
|
|
670
693
|
this.generation = generation;
|
|
@@ -725,6 +748,7 @@ class SessionRuntime {
|
|
|
725
748
|
this.#publishAuthority();
|
|
726
749
|
}
|
|
727
750
|
acquireAuthority(clientId, authority) {
|
|
751
|
+
this.assertNoSharedWindow();
|
|
728
752
|
if (authority === "input" && this.#controllerClientId !== clientId) {
|
|
729
753
|
// Input's executable proof is the compatibility controller lease. The
|
|
730
754
|
// transport synchronization seam must establish/handoff it first.
|
|
@@ -776,6 +800,7 @@ class SessionRuntime {
|
|
|
776
800
|
});
|
|
777
801
|
}
|
|
778
802
|
acquireController(clientId) {
|
|
803
|
+
this.assertNoSharedWindow();
|
|
779
804
|
this.#assertConnected(clientId);
|
|
780
805
|
if (this.#controllerClientId === clientId) {
|
|
781
806
|
this.#authority.updatePresence(clientId, "foreground");
|
|
@@ -836,6 +861,7 @@ class SessionRuntime {
|
|
|
836
861
|
this.#authority.release(callerClientId, "geometry");
|
|
837
862
|
}
|
|
838
863
|
assertController(lease, callerClientId) {
|
|
864
|
+
this.assertNoSharedWindow();
|
|
839
865
|
const parsedLease = this.#validatedLease(lease);
|
|
840
866
|
if (parsedLease.generation !== this.generation ||
|
|
841
867
|
parsedLease.session !== this.session ||
|
|
@@ -973,6 +999,7 @@ class SessionRuntime {
|
|
|
973
999
|
this.#mirror.fitWindowViewport(this.session, semanticWindowId, cols, rows);
|
|
974
1000
|
}
|
|
975
1001
|
fitViewportWithAuthority(clientId, lease, cols, rows, semanticWindowId) {
|
|
1002
|
+
this.assertNoSharedWindow();
|
|
976
1003
|
const parsed = SessionRuntimeAuthorityLeaseSchemaZ.parse(lease);
|
|
977
1004
|
let exact;
|
|
978
1005
|
try {
|
|
@@ -1016,13 +1043,16 @@ class SessionRuntime {
|
|
|
1016
1043
|
return await this.#mirror.describeSession(this.session);
|
|
1017
1044
|
}
|
|
1018
1045
|
qualifyTrustedInventory(runtimeSessionId) {
|
|
1046
|
+
this.assertNoSharedWindow();
|
|
1019
1047
|
if (this.#disposed || !this.#retention) {
|
|
1020
1048
|
throw new Error(`SessionRuntime ${this.session} is not retained`);
|
|
1021
1049
|
}
|
|
1022
1050
|
this.#trustedInventoryRuntimeSessionId = runtimeSessionId;
|
|
1023
1051
|
}
|
|
1024
1052
|
trustedInventoryQualified() {
|
|
1025
|
-
return !this.#disposed &&
|
|
1053
|
+
return (!this.#disposed &&
|
|
1054
|
+
this.#trustedInventoryRuntimeSessionId !== null &&
|
|
1055
|
+
!this.#mirror.hasSharedWindowConflict(this.session));
|
|
1026
1056
|
}
|
|
1027
1057
|
async describeTrustedInventory() {
|
|
1028
1058
|
if (!this.trustedInventoryQualified()) {
|
|
@@ -1176,8 +1206,26 @@ class SessionRuntime {
|
|
|
1176
1206
|
this.#authority.disconnect(consumer.clientId);
|
|
1177
1207
|
this.#publishAuthority();
|
|
1178
1208
|
}
|
|
1209
|
+
assertNoSharedWindow() {
|
|
1210
|
+
const message = this.#mirror.windowOwnershipMessage(this.session);
|
|
1211
|
+
if (message) {
|
|
1212
|
+
throw new SessionRuntimeControllerLeaseError("controller-conflict", message);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
invalidateSharedWindowAuthority() {
|
|
1216
|
+
this.#trustedInventoryRuntimeSessionId = null;
|
|
1217
|
+
this.#trustedInventoryEpoch += 1;
|
|
1218
|
+
this.#clearController();
|
|
1219
|
+
for (const clientId of this.#consumersByClientId.keys()) {
|
|
1220
|
+
for (const authority of ["input", "focus", "geometry"])
|
|
1221
|
+
this.#authority.release(clientId, authority);
|
|
1222
|
+
}
|
|
1223
|
+
this.#completedHandoffs.clear();
|
|
1224
|
+
this.#publishAuthority();
|
|
1225
|
+
}
|
|
1179
1226
|
noteControlExit() {
|
|
1180
1227
|
this.#trustedInventoryRuntimeSessionId = null;
|
|
1228
|
+
this.#trustedInventoryEpoch += 1;
|
|
1181
1229
|
const retention = this.#retention;
|
|
1182
1230
|
this.#retention = null;
|
|
1183
1231
|
this.#startPromise = null;
|
|
@@ -1198,6 +1246,7 @@ class SessionRuntime {
|
|
|
1198
1246
|
return;
|
|
1199
1247
|
this.#disposed = true;
|
|
1200
1248
|
this.#trustedInventoryRuntimeSessionId = null;
|
|
1249
|
+
this.#trustedInventoryEpoch += 1;
|
|
1201
1250
|
const consumers = [...this.#consumers];
|
|
1202
1251
|
await Promise.allSettled(consumers.map((consumer) => consumer.close()));
|
|
1203
1252
|
this.#clearController();
|
|
@@ -276,7 +276,9 @@ export class SessionSemanticMutationExecutor {
|
|
|
276
276
|
let trace = null;
|
|
277
277
|
if (this.#observability.enabled && this.#options.traceAuthority) {
|
|
278
278
|
try {
|
|
279
|
-
trace = this.#observability.beginTrace(intent.verb === "workspace.pane.select"
|
|
279
|
+
trace = this.#observability.beginTrace(intent.verb === "workspace.pane.select" || intent.verb === "workspace.window.link.select"
|
|
280
|
+
? "window-switch"
|
|
281
|
+
: "semantic-mutation", this.#options.traceAuthority, operationId);
|
|
280
282
|
}
|
|
281
283
|
catch {
|
|
282
284
|
trace = null;
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* Rows span the box interior in x: [left+1, left+width-1).
|
|
17
17
|
*/
|
|
18
18
|
import { fuzzyFilter } from "../team/fuzzy.js";
|
|
19
|
+
import { wrapText } from "./terminal-text.js";
|
|
20
|
+
export { wrapText } from "./terminal-text.js";
|
|
19
21
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
20
22
|
/** Box width — matches the palette so the two overlays read as one family. */
|
|
21
23
|
export const DIALOG_W = 60;
|
|
@@ -66,41 +68,6 @@ export function dialogPos(termW, termH, width) {
|
|
|
66
68
|
top: Math.max(1, Math.floor(termH / 6)),
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
|
-
/** PURE — greedy word-wrap for the confirm body (never returns empty for
|
|
70
|
-
* non-empty text; words longer than the width hard-break). */
|
|
71
|
-
export function wrapText(text, width) {
|
|
72
|
-
if (width <= 0)
|
|
73
|
-
return [text];
|
|
74
|
-
const out = [];
|
|
75
|
-
for (const para of text.split("\n")) {
|
|
76
|
-
let line = "";
|
|
77
|
-
for (const word of para.split(/\s+/).filter(Boolean)) {
|
|
78
|
-
if (line.length === 0) {
|
|
79
|
-
let w = word;
|
|
80
|
-
while (w.length > width) {
|
|
81
|
-
out.push(w.slice(0, width));
|
|
82
|
-
w = w.slice(width);
|
|
83
|
-
}
|
|
84
|
-
line = w;
|
|
85
|
-
}
|
|
86
|
-
else if (line.length + 1 + word.length <= width) {
|
|
87
|
-
line += ` ${word}`;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
out.push(line);
|
|
91
|
-
let w = word;
|
|
92
|
-
while (w.length > width) {
|
|
93
|
-
out.push(w.slice(0, width));
|
|
94
|
-
w = w.slice(width);
|
|
95
|
-
}
|
|
96
|
-
line = w;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (line.length > 0 || para.length === 0)
|
|
100
|
-
out.push(line);
|
|
101
|
-
}
|
|
102
|
-
return out.length > 0 ? out : [""];
|
|
103
|
-
}
|
|
104
71
|
/** PURE — the interior text width of the box (borders + 1-cell padding each side). */
|
|
105
72
|
export function dialogInnerW(width) {
|
|
106
73
|
return width - 4;
|
|
@@ -4,6 +4,7 @@ import { watch } from "node:fs";
|
|
|
4
4
|
import { basename, dirname } from "node:path";
|
|
5
5
|
import { getCanonicalDaemonInfoPath, isCanonicalDaemonAlive, readCanonicalDaemonInfo, } from "../../../lib/canonical-daemon.js";
|
|
6
6
|
import { SshConnectionError, openSshDaemonTransport, probeSshDaemonIdentity, } from "../../../lib/ssh-daemon-transport.js";
|
|
7
|
+
import { tuiPerfMark } from "./application-performance-log.js";
|
|
7
8
|
const defaults = {
|
|
8
9
|
readLocal: readCanonicalDaemonInfo,
|
|
9
10
|
isLocalAlive: isCanonicalDaemonAlive,
|
|
@@ -97,6 +98,13 @@ export function createApplicationDaemonAuthority(dependencies = defaults) {
|
|
|
97
98
|
const statusListeners = new Set();
|
|
98
99
|
const updateStatus = (next) => {
|
|
99
100
|
diagnostic = Object.freeze(next);
|
|
101
|
+
tuiPerfMark("machine-connection-status", {
|
|
102
|
+
machine: label,
|
|
103
|
+
...next,
|
|
104
|
+
instanceId: remote?.instanceId ?? null,
|
|
105
|
+
pid: remote?.pid ?? null,
|
|
106
|
+
port: remote?.port ?? null,
|
|
107
|
+
});
|
|
100
108
|
for (const listener of statusListeners) {
|
|
101
109
|
try {
|
|
102
110
|
listener();
|
|
@@ -49,6 +49,23 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
49
49
|
rows.sort((a, b) => a.key.localeCompare(b.key));
|
|
50
50
|
const missing = machines.filter((machine) => machine.state !== "ready" ||
|
|
51
51
|
!groups.find((group) => group.machineId === machine.id)?.available);
|
|
52
|
+
const activityPending = (machineId) => {
|
|
53
|
+
const observation = groups.find((group) => group.machineId === machineId)?.observation;
|
|
54
|
+
// A live observation whose binding is no longer available belongs to a
|
|
55
|
+
// retiring endpoint; its replacement must publish before it is current.
|
|
56
|
+
return !observation || observation.phase === "loading" || observation.phase === "live";
|
|
57
|
+
};
|
|
58
|
+
const coverageNotes = missing.map((machine) => {
|
|
59
|
+
if (machine.state !== "ready")
|
|
60
|
+
return `${machine.label}: ${machine.state}`;
|
|
61
|
+
const observation = groups.find((group) => group.machineId === machine.id)?.observation;
|
|
62
|
+
const status = activityPending(machine.id)
|
|
63
|
+
? "loading agent activity"
|
|
64
|
+
: observation?.phase === "partial"
|
|
65
|
+
? "agent activity incomplete"
|
|
66
|
+
: "agent activity unavailable";
|
|
67
|
+
return `${machine.label}: connected; ${status}`;
|
|
68
|
+
});
|
|
52
69
|
const query = filter.query?.trim().toLocaleLowerCase() ?? "";
|
|
53
70
|
const matching = query
|
|
54
71
|
? rows.filter((row) => [
|
|
@@ -65,7 +82,8 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
65
82
|
phase: missing.length
|
|
66
83
|
? rows.length || observedSessions
|
|
67
84
|
? "partial"
|
|
68
|
-
: machines.some((machine) => machine.state === "connecting")
|
|
85
|
+
: machines.some((machine) => machine.state === "connecting") ||
|
|
86
|
+
missing.some((machine) => machine.state === "ready" && activityPending(machine.id))
|
|
69
87
|
? "loading"
|
|
70
88
|
: "unavailable"
|
|
71
89
|
: "live",
|
|
@@ -78,7 +96,7 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
78
96
|
refreshingSessionKeys: [],
|
|
79
97
|
unavailableSessionKeys: [...unavailable],
|
|
80
98
|
note: missing.length
|
|
81
|
-
? `${
|
|
99
|
+
? `${coverageNotes.join(" · ")}${unavailable.size > 0 ? " · unavailable rows show last observed activity" : ""}`
|
|
82
100
|
: rows.length === 0 && filter.attentionOnly
|
|
83
101
|
? "No agents need attention in this view."
|
|
84
102
|
: null,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
1
2
|
import { machineResourceKey } from "@tmux-ide/core";
|
|
2
3
|
import { applicationMachineAuthorityManager, } from "./application-machine-authority.js";
|
|
3
4
|
import { createApplicationHomeAgentObserver, } from "./application-home-agent-observer.js";
|
|
@@ -29,7 +30,7 @@ export function createApplicationMachineAgents(options) {
|
|
|
29
30
|
const publish = () => {
|
|
30
31
|
if (disposed)
|
|
31
32
|
return;
|
|
32
|
-
|
|
33
|
+
const next = Object.freeze(catalog.groups.map((group) => {
|
|
33
34
|
const entry = entries.get(group.id);
|
|
34
35
|
const current = !!entry && entry.binding === bindingFor(entry.handle);
|
|
35
36
|
return Object.freeze({
|
|
@@ -47,6 +48,11 @@ export function createApplicationMachineAgents(options) {
|
|
|
47
48
|
}))),
|
|
48
49
|
});
|
|
49
50
|
}));
|
|
51
|
+
// Fresh authority checks above still run on every observation. Identical
|
|
52
|
+
// presentation data must not invalidate the Home/sidebar/tab subscribers.
|
|
53
|
+
if (isDeepStrictEqual(snapshot, next))
|
|
54
|
+
return;
|
|
55
|
+
snapshot = next;
|
|
50
56
|
for (const listener of listeners) {
|
|
51
57
|
try {
|
|
52
58
|
listener(snapshot);
|
|
@@ -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)}/>
|