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
|
@@ -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;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code agent teams — opt-out enablement in the user's Claude settings.
|
|
3
|
+
*
|
|
4
|
+
* Agent teams let one Claude Code session spawn teammates; with
|
|
5
|
+
* `teammateMode: "auto"` Claude puts them in split panes when it runs inside
|
|
6
|
+
* tmux (in-process elsewhere), which is exactly what tmux-ide shows and
|
|
7
|
+
* routes. The installer enables this by default and `tmux-ide integration
|
|
8
|
+
* agent-teams enable|disable|status` manages it explicitly.
|
|
9
|
+
*
|
|
10
|
+
* Rules (pure, in {@link mergeAgentTeams}):
|
|
11
|
+
* - `env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is set to "1" only when absent.
|
|
12
|
+
* An explicit off value ("0", "false", false, 0…) is the user's opt-out: the
|
|
13
|
+
* installer never overrides it and touches nothing else. Only an explicit
|
|
14
|
+
* `tmux-ide integration agent-teams enable` (overrideOptOut) turns it back on.
|
|
15
|
+
* - `teammateMode: "auto"` is set only when `teammateMode` is absent.
|
|
16
|
+
* - Every other key is preserved. Nothing changed → nothing written.
|
|
17
|
+
*
|
|
18
|
+
* Writes (io) reuse the hooks integration's settings path resolution, including
|
|
19
|
+
* the `TMUX_IDE_CLAUDE_SETTINGS` override: one-time timestamped backup before
|
|
20
|
+
* the first modification, then an atomic temp-file + rename. Invalid JSON is
|
|
21
|
+
* reported and never rewritten.
|
|
22
|
+
*/
|
|
23
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, copyFileSync, unlinkSync, writeFileSync, accessSync, constants, } from "node:fs";
|
|
24
|
+
import { basename, delimiter, dirname, join } from "node:path";
|
|
25
|
+
import { resolveRuntimeNamespace, runtimeOwnedPath } from "../../lib/runtime-namespace.js";
|
|
26
|
+
export const AGENT_TEAMS_ENV = "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS";
|
|
27
|
+
export const DEFAULT_TEAMMATE_MODE = "auto";
|
|
28
|
+
const BACKUP_INFIX = ".tmux-ide-backup-";
|
|
29
|
+
/** PURE — an explicit off value is the user's opt-out. */
|
|
30
|
+
export function isOptOut(value) {
|
|
31
|
+
if (value === false || value === 0)
|
|
32
|
+
return true;
|
|
33
|
+
return typeof value === "string" && /^\s*(0|false|off|no)\s*$/iu.test(value);
|
|
34
|
+
}
|
|
35
|
+
function envOf(settings) {
|
|
36
|
+
const env = settings.env;
|
|
37
|
+
if (env === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (!env || typeof env !== "object" || Array.isArray(env))
|
|
40
|
+
throw new Error("settings env must be an object");
|
|
41
|
+
return env;
|
|
42
|
+
}
|
|
43
|
+
/** PURE — how the settings currently stand for agent teams. */
|
|
44
|
+
export function agentTeamsSetting(settings) {
|
|
45
|
+
const value = envOf(settings)?.[AGENT_TEAMS_ENV];
|
|
46
|
+
if (value === undefined)
|
|
47
|
+
return "not-configured";
|
|
48
|
+
return isOptOut(value) ? "disabled-by-user" : "enabled";
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* PURE — enable agent teams without overriding anything the user chose, unless
|
|
52
|
+
* `overrideOptOut` (the user explicitly asked to enable) replaces their "0".
|
|
53
|
+
*/
|
|
54
|
+
export function mergeAgentTeams(settings, { overrideOptOut = false } = {}) {
|
|
55
|
+
const setting = agentTeamsSetting(settings);
|
|
56
|
+
if (setting === "disabled-by-user" && !overrideOptOut)
|
|
57
|
+
return { settings, changes: [], setting };
|
|
58
|
+
const changes = [];
|
|
59
|
+
let next = settings;
|
|
60
|
+
if (setting !== "enabled") {
|
|
61
|
+
next = { ...next, env: { ...(envOf(settings) ?? {}), [AGENT_TEAMS_ENV]: "1" } };
|
|
62
|
+
changes.push(`env.${AGENT_TEAMS_ENV}=1`);
|
|
63
|
+
}
|
|
64
|
+
if (!("teammateMode" in settings)) {
|
|
65
|
+
next = { ...next, teammateMode: DEFAULT_TEAMMATE_MODE };
|
|
66
|
+
changes.push(`teammateMode=${DEFAULT_TEAMMATE_MODE}`);
|
|
67
|
+
}
|
|
68
|
+
return { settings: next, changes, setting: "enabled" };
|
|
69
|
+
}
|
|
70
|
+
/** PURE — record an explicit opt-out ("0"), which later enables respect. */
|
|
71
|
+
export function disableAgentTeams(settings) {
|
|
72
|
+
if (agentTeamsSetting(settings) === "disabled-by-user")
|
|
73
|
+
return { settings, changes: [], setting: "disabled-by-user" };
|
|
74
|
+
return {
|
|
75
|
+
settings: { ...settings, env: { ...(envOf(settings) ?? {}), [AGENT_TEAMS_ENV]: "0" } },
|
|
76
|
+
changes: [`env.${AGENT_TEAMS_ENV}=0`],
|
|
77
|
+
setting: "disabled-by-user",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function agentTeamsPaths() {
|
|
81
|
+
const namespace = resolveRuntimeNamespace();
|
|
82
|
+
return { settingsPath: namespace.claudeSettingsPath, claudeDir: namespace.claudeDir };
|
|
83
|
+
}
|
|
84
|
+
/** `claude` on PATH, without spawning anything. */
|
|
85
|
+
export function claudeOnPath(path = process.env.PATH ?? "") {
|
|
86
|
+
return path
|
|
87
|
+
.split(delimiter)
|
|
88
|
+
.filter(Boolean)
|
|
89
|
+
.some((dir) => {
|
|
90
|
+
try {
|
|
91
|
+
accessSync(join(dir, "claude"), constants.X_OK);
|
|
92
|
+
return statSync(join(dir, "claude")).isFile();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export class InvalidClaudeSettingsError extends Error {
|
|
100
|
+
}
|
|
101
|
+
function readSettings(path) {
|
|
102
|
+
if (!existsSync(path))
|
|
103
|
+
return { settings: {}, exists: false };
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
106
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
107
|
+
throw new Error("not an object");
|
|
108
|
+
envOf(parsed);
|
|
109
|
+
return { settings: parsed, exists: true };
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw new InvalidClaudeSettingsError(`${path} is not valid settings JSON — fix or move it, then retry`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Existing one-time backup, if tmux-ide already made one for this file. */
|
|
116
|
+
function existingBackup(settingsPath) {
|
|
117
|
+
const dir = dirname(settingsPath);
|
|
118
|
+
const prefix = `${basename(settingsPath)}${BACKUP_INFIX}`;
|
|
119
|
+
try {
|
|
120
|
+
const found = readdirSync(dir).find((name) => name.startsWith(prefix));
|
|
121
|
+
return found ? join(dir, found) : null;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function writeSettings(path, settings, exists) {
|
|
128
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
129
|
+
let backup = null;
|
|
130
|
+
if (exists) {
|
|
131
|
+
backup = existingBackup(path);
|
|
132
|
+
if (!backup) {
|
|
133
|
+
const stamp = new Date()
|
|
134
|
+
.toISOString()
|
|
135
|
+
.replace(/[-:]/gu, "")
|
|
136
|
+
.replace(/\.\d+Z$/u, "Z");
|
|
137
|
+
backup = `${path}${BACKUP_INFIX}${stamp}`;
|
|
138
|
+
copyFileSync(path, backup);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const temporary = join(dirname(path), `.${basename(path)}.tmux-ide-${process.pid}.tmp`);
|
|
142
|
+
try {
|
|
143
|
+
writeFileSync(temporary, `${JSON.stringify(settings, null, 2)}\n`, { mode: 0o600 });
|
|
144
|
+
if (exists)
|
|
145
|
+
chmodSync(temporary, statSync(path).mode & 0o777);
|
|
146
|
+
renameSync(temporary, path);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
try {
|
|
150
|
+
unlinkSync(temporary);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
/* already renamed or never written */
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
return backup;
|
|
158
|
+
}
|
|
159
|
+
function detected(paths, onPath) {
|
|
160
|
+
return existsSync(paths.claudeDir) || onPath();
|
|
161
|
+
}
|
|
162
|
+
function guarded(paths) {
|
|
163
|
+
if (!paths && resolveRuntimeNamespace().development)
|
|
164
|
+
throw new Error("Development agent-teams changes require explicit fixture paths");
|
|
165
|
+
const resolved = paths ?? agentTeamsPaths();
|
|
166
|
+
runtimeOwnedPath(resolved.settingsPath);
|
|
167
|
+
return resolved;
|
|
168
|
+
}
|
|
169
|
+
/** Enable agent teams when Claude Code is installed and the user hasn't opted out. */
|
|
170
|
+
export function enableClaudeAgentTeams(paths, onPath = () => claudeOnPath(), { overrideOptOut = false } = {}) {
|
|
171
|
+
const resolved = guarded(paths);
|
|
172
|
+
const { settingsPath } = resolved;
|
|
173
|
+
const base = { settingsPath, changes: [], backup: null };
|
|
174
|
+
if (!detected(resolved, onPath))
|
|
175
|
+
return {
|
|
176
|
+
...base,
|
|
177
|
+
action: "skipped",
|
|
178
|
+
claudeDetected: false,
|
|
179
|
+
message: `Claude Code not found (no ${resolved.claudeDir}, no claude on PATH); agent teams not configured.`,
|
|
180
|
+
};
|
|
181
|
+
let read;
|
|
182
|
+
try {
|
|
183
|
+
read = readSettings(settingsPath);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
return { ...base, action: "invalid", claudeDetected: true, message: error.message };
|
|
187
|
+
}
|
|
188
|
+
const merged = mergeAgentTeams(read.settings, { overrideOptOut });
|
|
189
|
+
if (merged.setting === "disabled-by-user")
|
|
190
|
+
return {
|
|
191
|
+
...base,
|
|
192
|
+
action: "disabled-by-user",
|
|
193
|
+
claudeDetected: true,
|
|
194
|
+
message: `Claude Code agent teams are disabled by user in ${settingsPath} (${AGENT_TEAMS_ENV}); ` +
|
|
195
|
+
"left unchanged. Enable explicitly: tmux-ide integration agent-teams enable",
|
|
196
|
+
};
|
|
197
|
+
if (merged.changes.length === 0)
|
|
198
|
+
return {
|
|
199
|
+
...base,
|
|
200
|
+
action: "unchanged",
|
|
201
|
+
claudeDetected: true,
|
|
202
|
+
message: `Claude Code agent teams already enabled in ${settingsPath}.`,
|
|
203
|
+
};
|
|
204
|
+
const backup = writeSettings(settingsPath, merged.settings, read.exists);
|
|
205
|
+
return {
|
|
206
|
+
settingsPath,
|
|
207
|
+
changes: merged.changes,
|
|
208
|
+
backup,
|
|
209
|
+
action: "enabled",
|
|
210
|
+
claudeDetected: true,
|
|
211
|
+
message: `Enabled Claude Code agent teams in ${settingsPath} (${merged.changes.join(", ")}` +
|
|
212
|
+
`${backup ? `; backup: ${backup}` : ""}). Disable: tmux-ide integration agent-teams disable`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/** Record the user's opt-out so neither the installer nor `enable` turns it back on silently. */
|
|
216
|
+
export function disableClaudeAgentTeams(paths) {
|
|
217
|
+
const resolved = guarded(paths);
|
|
218
|
+
const { settingsPath } = resolved;
|
|
219
|
+
const base = { settingsPath, changes: [], backup: null, claudeDetected: true };
|
|
220
|
+
let read;
|
|
221
|
+
try {
|
|
222
|
+
read = readSettings(settingsPath);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
return { ...base, action: "invalid", message: error.message };
|
|
226
|
+
}
|
|
227
|
+
const next = disableAgentTeams(read.settings);
|
|
228
|
+
if (next.changes.length === 0)
|
|
229
|
+
return {
|
|
230
|
+
...base,
|
|
231
|
+
action: "unchanged",
|
|
232
|
+
message: `Claude Code agent teams already disabled in ${settingsPath}.`,
|
|
233
|
+
};
|
|
234
|
+
const backup = writeSettings(settingsPath, next.settings, read.exists);
|
|
235
|
+
return {
|
|
236
|
+
...base,
|
|
237
|
+
changes: next.changes,
|
|
238
|
+
backup,
|
|
239
|
+
action: "disabled",
|
|
240
|
+
message: `Disabled Claude Code agent teams in ${settingsPath} (${next.changes.join(", ")}` +
|
|
241
|
+
`${backup ? `; backup: ${backup}` : ""}). Re-enable: tmux-ide integration agent-teams enable`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
export function claudeAgentTeamsStatus(paths = agentTeamsPaths(), onPath = () => claudeOnPath()) {
|
|
245
|
+
const claudeDetected = detected(paths, onPath);
|
|
246
|
+
try {
|
|
247
|
+
const { settings } = readSettings(paths.settingsPath);
|
|
248
|
+
const mode = settings.teammateMode;
|
|
249
|
+
return {
|
|
250
|
+
settingsPath: paths.settingsPath,
|
|
251
|
+
claudeDetected,
|
|
252
|
+
setting: agentTeamsSetting(settings),
|
|
253
|
+
teammateMode: typeof mode === "string" ? mode : null,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return {
|
|
258
|
+
settingsPath: paths.settingsPath,
|
|
259
|
+
claudeDetected,
|
|
260
|
+
setting: "invalid-settings",
|
|
261
|
+
teammateMode: null,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -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);
|