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
|
@@ -2,13 +2,22 @@ import type { Hono } from "hono";
|
|
|
2
2
|
import { bodyLimit } from "hono/body-limit";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { SavedMachineRegistrySchema, type DaemonInstanceIdentity } from "@tmux-ide/contracts";
|
|
5
|
-
import {
|
|
5
|
+
import { SavedMachineMutationSchema } from "@tmux-ide/contracts/saved-machines";
|
|
6
|
+
import {
|
|
7
|
+
loadSavedMachines,
|
|
8
|
+
mergeSavedMachines,
|
|
9
|
+
updateSavedMachines,
|
|
10
|
+
} from "../../lib/saved-machines.ts";
|
|
6
11
|
import { ownerAuthorityGate } from "../owner-authority.ts";
|
|
7
12
|
|
|
8
13
|
const Request = z.strictObject({
|
|
9
14
|
expectedInstanceId: z.uuid(),
|
|
10
15
|
registry: SavedMachineRegistrySchema,
|
|
11
16
|
});
|
|
17
|
+
const MutationRequest = z.strictObject({
|
|
18
|
+
expectedInstanceId: z.uuid(),
|
|
19
|
+
change: SavedMachineMutationSchema,
|
|
20
|
+
});
|
|
12
21
|
export function mountSavedMachineRoute(
|
|
13
22
|
app: Hono,
|
|
14
23
|
options: {
|
|
@@ -16,6 +25,7 @@ export function mountSavedMachineRoute(
|
|
|
16
25
|
ownerToken: string | null;
|
|
17
26
|
read?: typeof loadSavedMachines;
|
|
18
27
|
merge?: typeof mergeSavedMachines;
|
|
28
|
+
update?: typeof updateSavedMachines;
|
|
19
29
|
},
|
|
20
30
|
) {
|
|
21
31
|
const authorize = ownerAuthorityGate(options.ownerToken, {
|
|
@@ -56,4 +66,41 @@ export function mountSavedMachineRoute(
|
|
|
56
66
|
);
|
|
57
67
|
}
|
|
58
68
|
});
|
|
69
|
+
app.patch("/api/resources/saved-machines", bodyLimit({ maxSize: 1024 }), async (c) => {
|
|
70
|
+
const gate = authorize(c);
|
|
71
|
+
if (gate) return gate;
|
|
72
|
+
c.header("Cache-Control", "no-store");
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = MutationRequest.safeParse(await c.req.json());
|
|
76
|
+
} catch {
|
|
77
|
+
return c.json({ error: "Invalid machine mutation" }, 400);
|
|
78
|
+
}
|
|
79
|
+
if (!parsed.success) return c.json({ error: "Invalid machine mutation" }, 400);
|
|
80
|
+
if (parsed.data.expectedInstanceId !== options.daemon.instanceId)
|
|
81
|
+
return c.json({ error: "Daemon generation changed" }, 409);
|
|
82
|
+
const change = parsed.data.change;
|
|
83
|
+
const { id, operation } = change;
|
|
84
|
+
try {
|
|
85
|
+
const registry = (options.update ?? updateSavedMachines)(
|
|
86
|
+
operation === "remove"
|
|
87
|
+
? { type: "remove", id }
|
|
88
|
+
: {
|
|
89
|
+
type: "update",
|
|
90
|
+
id,
|
|
91
|
+
patch:
|
|
92
|
+
change.operation === "edit" ? change.patch : { enabled: operation === "enable" },
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
return c.json({ daemon: options.daemon, registry });
|
|
96
|
+
} catch {
|
|
97
|
+
return c.json(
|
|
98
|
+
{
|
|
99
|
+
error:
|
|
100
|
+
"Machine not found, edit conflicts with another profile, or registry unavailable; nothing changed",
|
|
101
|
+
},
|
|
102
|
+
409,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
59
106
|
}
|
|
@@ -17,11 +17,8 @@ import { discoverSessions, buildOverviews, buildProjectDetail } from "./discover
|
|
|
17
17
|
import type { AgentTurnCompletion } from "./agent-status-watch.ts";
|
|
18
18
|
import {
|
|
19
19
|
DaemonFleetFactsObserver,
|
|
20
|
-
AGENT_STATE_TMUX_ARGS,
|
|
21
|
-
parseAgentStateFacts,
|
|
22
|
-
parseSessionCompositionFacts,
|
|
23
20
|
createDefaultFleetFactsReaders,
|
|
24
|
-
|
|
21
|
+
createSharedFleetFactsReaders,
|
|
25
22
|
type DaemonFleetFactsObserverOptions,
|
|
26
23
|
type DaemonFleetFactsObserverDiagnostic,
|
|
27
24
|
} from "./daemon-fleet-facts-observer.ts";
|
|
@@ -431,28 +428,11 @@ export function setFleetFactsTmuxRunner(
|
|
|
431
428
|
runTmux: ((args: readonly string[], signal?: AbortSignal) => string | Promise<string>) | null,
|
|
432
429
|
): void {
|
|
433
430
|
stopFleetFactsObserver();
|
|
434
|
-
|
|
435
|
-
?
|
|
436
|
-
try {
|
|
437
|
-
return parseSessionCompositionFacts(await runTmux(SESSION_COMPOSITION_TMUX_ARGS, signal));
|
|
438
|
-
} catch (error) {
|
|
439
|
-
// A daemon may legitimately precede the first tmux server. Socket
|
|
440
|
-
// absence is an authoritative empty fleet baseline, not a failed
|
|
441
|
-
// observation: catalog clients must be allowed to render their
|
|
442
|
-
// actionable first-run state while the observer waits for tmux.
|
|
443
|
-
return isTmuxServerUnavailableError(error) ? parseSessionCompositionFacts("") : null;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
: null;
|
|
447
|
-
agentStateReaderOverride = runTmux
|
|
448
|
-
? async (signal) => {
|
|
449
|
-
try {
|
|
450
|
-
return parseAgentStateFacts(await runTmux(AGENT_STATE_TMUX_ARGS, signal));
|
|
451
|
-
} catch {
|
|
452
|
-
return null;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
431
|
+
const readers = runTmux
|
|
432
|
+
? createSharedFleetFactsReaders(runTmux, isTmuxServerUnavailableError)
|
|
455
433
|
: null;
|
|
434
|
+
sessionCompositionReaderOverride = readers?.readSessions ?? null;
|
|
435
|
+
agentStateReaderOverride = readers?.readAgents ?? null;
|
|
456
436
|
}
|
|
457
437
|
|
|
458
438
|
interface ResourceObservationHandle {
|
|
@@ -236,6 +236,18 @@ async function replaceOlderCanonicalDaemon(
|
|
|
236
236
|
reason: "identity-mismatch",
|
|
237
237
|
});
|
|
238
238
|
}
|
|
239
|
+
const supervisor = latest.info.provenance?.supervisor;
|
|
240
|
+
if (!latest.info.supervisionId && (supervisor === "systemd" || supervisor === "launchd")) {
|
|
241
|
+
throw new DaemonBootstrapError(
|
|
242
|
+
"incompatible",
|
|
243
|
+
`The older canonical daemon is managed by ${supervisor} without a supervisor reservation. ` +
|
|
244
|
+
"Stop and disable the old service, reserve the stopped namespace with " +
|
|
245
|
+
"tmux-ide daemon reserve-supervisor <id>, then configure the service to run " +
|
|
246
|
+
"tmux-ide --headless --supervised <id> before restarting it. " +
|
|
247
|
+
"The existing daemon has been left running.",
|
|
248
|
+
{ reason: "canonical-record-invalid" },
|
|
249
|
+
);
|
|
250
|
+
}
|
|
239
251
|
await deps.shutdownOlderOwner(info);
|
|
240
252
|
const deadline = deps.now() + timeoutMs;
|
|
241
253
|
while (deps.now() < deadline) {
|
|
@@ -115,7 +115,12 @@ export type CanonicalDaemonInfoState =
|
|
|
115
115
|
};
|
|
116
116
|
|
|
117
117
|
export function getCanonicalDaemonInfoPath(): string {
|
|
118
|
-
|
|
118
|
+
const namespace = resolveRuntimeNamespace();
|
|
119
|
+
const path = join(namespace.daemonInfoDir, DAEMON_INFO_FILE);
|
|
120
|
+
// Resolution already validates ordinary and isolated namespaces. Only
|
|
121
|
+
// development adds a per-path containment check; do not resolve twice on
|
|
122
|
+
// every ordinary daemon read. Each call still resolves the current namespace.
|
|
123
|
+
return namespace.development ? runtimeOwnedPath(path) : path;
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
export function getCanonicalDaemonClaimPath(): string {
|
|
@@ -687,11 +692,48 @@ export function assertCanonicalDaemonSupervision(supervisionId: string): void {
|
|
|
687
692
|
}
|
|
688
693
|
|
|
689
694
|
/** Explicit installation only; startup never calls this to recreate a reservation. */
|
|
695
|
+
/** Sanitized reservation guidance; never includes credentials or filesystem errors. */
|
|
696
|
+
export class CanonicalDaemonReservationError extends Error {
|
|
697
|
+
constructor(message: string) {
|
|
698
|
+
super(`Supervisor reservation refused: ${message}`);
|
|
699
|
+
this.name = "CanonicalDaemonReservationError";
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function reservationRefusal(
|
|
704
|
+
attempt: Exclude<CanonicalDaemonClaimAttempt, { status: "acquired" }>,
|
|
705
|
+
): CanonicalDaemonReservationError {
|
|
706
|
+
if (attempt.status === "busy")
|
|
707
|
+
return new CanonicalDaemonReservationError(
|
|
708
|
+
`another lifecycle operation holds the namespace (PID ${attempt.owner.pid}). Wait for it to finish, then retry.`,
|
|
709
|
+
);
|
|
710
|
+
const state = inspectCanonicalDaemonInfo();
|
|
711
|
+
if (state.status === "valid")
|
|
712
|
+
return new CanonicalDaemonReservationError(
|
|
713
|
+
`the recorded ${state.info.supervisionId ? "supervised" : "unsupervised"} daemon (PID ${state.info.pid}) has not been released. Inspect it with tmux-ide daemon info --json, then stop it through its owning process or service before reserving.`,
|
|
714
|
+
);
|
|
715
|
+
if (state.status === "reserved")
|
|
716
|
+
return new CanonicalDaemonReservationError(
|
|
717
|
+
`this namespace is reserved for supervisor ${state.reservation.supervisionId}. Remove that service and release its reservation before assigning a different ID.`,
|
|
718
|
+
);
|
|
719
|
+
if (state.status === "invalid")
|
|
720
|
+
return new CanonicalDaemonReservationError(
|
|
721
|
+
`the namespace record cannot be trusted (${state.reason}). Run tmux-ide daemon info --json for diagnosis; repair ownership or permissions before retrying.`,
|
|
722
|
+
);
|
|
723
|
+
return new CanonicalDaemonReservationError(
|
|
724
|
+
"the namespace changed or its lifecycle claim is unavailable. Run tmux-ide daemon info --json, then retry after the owning operation finishes.",
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
|
|
690
728
|
export function reserveCanonicalDaemonSupervision(
|
|
691
729
|
supervisionId: string,
|
|
692
730
|
): CanonicalDaemonReservation {
|
|
731
|
+
if (!DaemonSupervisionIdSchema.safeParse(supervisionId).success)
|
|
732
|
+
throw new CanonicalDaemonReservationError(
|
|
733
|
+
"use a 1–128 character ID starting with a letter or digit and containing only letters, digits, periods, underscores or hyphens.",
|
|
734
|
+
);
|
|
693
735
|
const attempt = tryAcquireCanonicalDaemonClaim({ kind: "reserve", supervisionId });
|
|
694
|
-
if (attempt.status !== "acquired") throw
|
|
736
|
+
if (attempt.status !== "acquired") throw reservationRefusal(attempt);
|
|
695
737
|
const claim = attempt.claim;
|
|
696
738
|
try {
|
|
697
739
|
const before = inspectCanonicalDaemonInfo();
|
|
@@ -1436,6 +1436,7 @@ async function startEmbeddedDaemonGeneration(
|
|
|
1436
1436
|
if (!sessionRuntimeRegistry) throw new Error("Session runtime unavailable");
|
|
1437
1437
|
return sessionRuntimeRegistry.executeWindowLinkAction(session, action);
|
|
1438
1438
|
},
|
|
1439
|
+
timing,
|
|
1439
1440
|
);
|
|
1440
1441
|
}
|
|
1441
1442
|
if (intent.verb === "workspace.pane.resize") {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import type { DaemonServicePlan } from "./daemon-service-plan.ts";
|
|
3
|
+
|
|
4
|
+
export interface DaemonServiceState {
|
|
5
|
+
readonly loaded: boolean;
|
|
6
|
+
readonly active: boolean;
|
|
7
|
+
readonly pid: number | null;
|
|
8
|
+
readonly definitionPath: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ServiceCommand = (
|
|
12
|
+
file: string,
|
|
13
|
+
args: readonly string[],
|
|
14
|
+
) => Promise<{ code: number | null; stdout: string; stderr: string }>;
|
|
15
|
+
|
|
16
|
+
const runCommand: ServiceCommand = (file, args) =>
|
|
17
|
+
new Promise((resolve) => {
|
|
18
|
+
execFile(
|
|
19
|
+
file,
|
|
20
|
+
[...args],
|
|
21
|
+
{
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
timeout: 15_000,
|
|
24
|
+
maxBuffer: 256 * 1024,
|
|
25
|
+
env: { ...process.env, LC_ALL: "C", LANG: "C" },
|
|
26
|
+
},
|
|
27
|
+
(error, stdout, stderr) =>
|
|
28
|
+
resolve({
|
|
29
|
+
code: error ? (typeof error.code === "number" ? error.code : null) : 0,
|
|
30
|
+
stdout,
|
|
31
|
+
stderr,
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export class DaemonServiceManagerError extends Error {
|
|
37
|
+
readonly code = "DAEMON_SERVICE_MANAGER_FAILED";
|
|
38
|
+
constructor(
|
|
39
|
+
readonly operation: string,
|
|
40
|
+
readonly manager: string,
|
|
41
|
+
) {
|
|
42
|
+
super(`${manager} user service ${operation} failed; check the user manager and service logs`);
|
|
43
|
+
this.name = "DaemonServiceManagerError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Bounded argv-only OS boundary. Never expose manager output (it may contain environment data). */
|
|
48
|
+
export function createDaemonServiceManager(plan: DaemonServicePlan, run = runCommand) {
|
|
49
|
+
const launchd = plan.manager === "launchd";
|
|
50
|
+
const file = launchd ? "/bin/launchctl" : "systemctl";
|
|
51
|
+
const domain = plan.target.slice(0, plan.target.lastIndexOf("/"));
|
|
52
|
+
const command = async (operation: string, args: readonly string[]) => {
|
|
53
|
+
const result = await run(file, launchd ? args : ["--user", ...args]);
|
|
54
|
+
if (result.code !== 0) throw new DaemonServiceManagerError(operation, plan.manager);
|
|
55
|
+
return result;
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
async available(): Promise<void> {
|
|
59
|
+
await command(
|
|
60
|
+
"availability check",
|
|
61
|
+
launchd ? ["print-disabled", domain] : ["show-environment"],
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
async inspect(): Promise<DaemonServiceState> {
|
|
65
|
+
const result = await run(
|
|
66
|
+
file,
|
|
67
|
+
launchd
|
|
68
|
+
? ["print", plan.target]
|
|
69
|
+
: [
|
|
70
|
+
"--user",
|
|
71
|
+
"show",
|
|
72
|
+
plan.target,
|
|
73
|
+
"--property=LoadState,ActiveState,MainPID,FragmentPath",
|
|
74
|
+
],
|
|
75
|
+
);
|
|
76
|
+
const absent = { loaded: false, active: false, pid: null, definitionPath: null } as const;
|
|
77
|
+
if (launchd) {
|
|
78
|
+
if (result.code === 113 && result.stderr.includes("Could not find service")) return absent;
|
|
79
|
+
if (result.code !== 0) throw new DaemonServiceManagerError("inspection", plan.manager);
|
|
80
|
+
const field = (name: string) =>
|
|
81
|
+
result.stdout.match(new RegExp(`^\\t${name} = (.*)$`, "m"))?.[1];
|
|
82
|
+
const path = field("path");
|
|
83
|
+
const pidText = field("pid");
|
|
84
|
+
const state = field("state");
|
|
85
|
+
if (!path || !state || (pidText !== undefined && !/^[1-9][0-9]*$/u.test(pidText)))
|
|
86
|
+
throw new DaemonServiceManagerError("inspection format", plan.manager);
|
|
87
|
+
return {
|
|
88
|
+
loaded: true,
|
|
89
|
+
active: state === "running",
|
|
90
|
+
pid: pidText ? Number(pidText) : null,
|
|
91
|
+
definitionPath: path,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const fields = new Map<string, string>();
|
|
95
|
+
for (const line of result.stdout.trim().split("\n")) {
|
|
96
|
+
const index = line.indexOf("=");
|
|
97
|
+
if (index < 1 || fields.has(line.slice(0, index)))
|
|
98
|
+
throw new DaemonServiceManagerError("inspection format", plan.manager);
|
|
99
|
+
fields.set(line.slice(0, index), line.slice(index + 1));
|
|
100
|
+
}
|
|
101
|
+
if (fields.get("LoadState") === "not-found" && (result.code === 0 || result.code === 4))
|
|
102
|
+
return absent;
|
|
103
|
+
if (result.code !== 0 || fields.get("LoadState") !== "loaded" || !fields.has("ActiveState"))
|
|
104
|
+
throw new DaemonServiceManagerError("inspection", plan.manager);
|
|
105
|
+
const pidText = fields.get("MainPID");
|
|
106
|
+
const path = fields.get("FragmentPath");
|
|
107
|
+
if (!pidText || !/^(0|[1-9][0-9]*)$/u.test(pidText) || !path)
|
|
108
|
+
throw new DaemonServiceManagerError("inspection format", plan.manager);
|
|
109
|
+
return {
|
|
110
|
+
loaded: true,
|
|
111
|
+
active: fields.get("ActiveState") === "active",
|
|
112
|
+
pid: Number(pidText) || null,
|
|
113
|
+
definitionPath: path,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
async install(): Promise<void> {
|
|
117
|
+
if (launchd) await command("bootstrap", ["bootstrap", domain, plan.unitPath]);
|
|
118
|
+
else {
|
|
119
|
+
await command("reload", ["daemon-reload"]);
|
|
120
|
+
// The manager may have a different XDG_CONFIG_HOME from the invoking
|
|
121
|
+
// shell. Enable the owned file explicitly so it is linked into its search path.
|
|
122
|
+
await command("enable/start", ["enable", "--now", plan.unitPath]);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
async restart(hasProcess = true): Promise<void> {
|
|
126
|
+
// An explicit recovery after a failed launcher must also clear systemd's
|
|
127
|
+
// start-rate counter. The lifecycle owner has already verified this unit's
|
|
128
|
+
// private definition and reservation; never reset all user units.
|
|
129
|
+
if (!launchd && !hasProcess)
|
|
130
|
+
await command("reset failed state", ["reset-failed", plan.target]);
|
|
131
|
+
// KeepAlive relaunches after a graceful TERM. kickstart -k would force-kill
|
|
132
|
+
// the daemon instead of allowing canonical ownership to be released.
|
|
133
|
+
await command(
|
|
134
|
+
"restart",
|
|
135
|
+
launchd
|
|
136
|
+
? hasProcess
|
|
137
|
+
? ["kill", "SIGTERM", plan.target]
|
|
138
|
+
: ["kickstart", plan.target]
|
|
139
|
+
: ["restart", plan.target],
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
async stop(): Promise<void> {
|
|
143
|
+
await command(
|
|
144
|
+
"stop/remove",
|
|
145
|
+
launchd ? ["bootout", plan.target] : ["disable", "--now", plan.target],
|
|
146
|
+
);
|
|
147
|
+
},
|
|
148
|
+
async reload(): Promise<void> {
|
|
149
|
+
if (!launchd) await command("reload", ["daemon-reload"]);
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { runtimeNamespaceEnvironment, type RuntimeNamespace } from "./runtime-namespace.ts";
|
|
4
|
+
|
|
5
|
+
export interface DaemonServicePlan {
|
|
6
|
+
readonly manager: "launchd" | "systemd";
|
|
7
|
+
readonly supervisionId: string;
|
|
8
|
+
readonly target: string;
|
|
9
|
+
readonly unitPath: string;
|
|
10
|
+
readonly recordPath: string;
|
|
11
|
+
readonly executable: string;
|
|
12
|
+
readonly contents: string;
|
|
13
|
+
readonly stdoutPath: string;
|
|
14
|
+
readonly stderrPath: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function safeValue(value: string, name: string): string {
|
|
18
|
+
if (!value || [...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127))
|
|
19
|
+
throw new TypeError(`${name} must be nonempty and contain no control characters`);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function absolute(value: string, name: string): string {
|
|
24
|
+
safeValue(value, name);
|
|
25
|
+
if (!isAbsolute(value)) throw new TypeError(`${name} must be an absolute path`);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function xml(value: string): string {
|
|
30
|
+
return value.replace(
|
|
31
|
+
/[&<>"']/gu,
|
|
32
|
+
(char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]!,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// systemd applies specifier expansion to paths, environment values and ExecStart.
|
|
37
|
+
// ExecStart additionally expands dollars unless the ':' executable prefix is used.
|
|
38
|
+
function systemd(value: string): string {
|
|
39
|
+
return `"${value.replace(/%/gu, "%%").replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Pure rendering only. Reservation and OS-manager mutations belong to the lifecycle owner. */
|
|
43
|
+
export function planDaemonService(options: {
|
|
44
|
+
platform: string;
|
|
45
|
+
uid: number;
|
|
46
|
+
home: string;
|
|
47
|
+
configHome?: string;
|
|
48
|
+
executable: string;
|
|
49
|
+
path: string;
|
|
50
|
+
namespace: RuntimeNamespace;
|
|
51
|
+
}): DaemonServicePlan {
|
|
52
|
+
const { namespace } = options;
|
|
53
|
+
if (options.platform !== "darwin" && options.platform !== "linux")
|
|
54
|
+
throw new TypeError("Daemon services support macOS launchd and Linux systemd user managers");
|
|
55
|
+
if (!Number.isSafeInteger(options.uid) || options.uid <= 0)
|
|
56
|
+
throw new TypeError("Daemon services require a non-root user");
|
|
57
|
+
if (namespace.mode === "development")
|
|
58
|
+
throw new TypeError("Development instances own their lifecycle; use dev:instance instead");
|
|
59
|
+
const home = absolute(options.home, "Home directory");
|
|
60
|
+
const executable = absolute(options.executable, "Stable CLI executable");
|
|
61
|
+
// Keep the supplied stable launcher path. realpath would pin installer services
|
|
62
|
+
// to a retired version instead of following the next verified activation.
|
|
63
|
+
const identity = createHash("sha256")
|
|
64
|
+
.update(absolute(namespace.daemonInfoDir, "Daemon state directory"))
|
|
65
|
+
.digest("hex")
|
|
66
|
+
.slice(0, 24);
|
|
67
|
+
const supervisionId = `tmux-ide.${identity}`;
|
|
68
|
+
const stdoutPath = join(namespace.daemonInfoDir, "service.stdout.log");
|
|
69
|
+
const stderrPath = join(namespace.daemonInfoDir, "service.stderr.log");
|
|
70
|
+
const environment = {
|
|
71
|
+
HOME: home,
|
|
72
|
+
PATH: safeValue(options.path, "Service PATH"),
|
|
73
|
+
...runtimeNamespaceEnvironment(namespace),
|
|
74
|
+
TMUX_IDE_CONFIG: namespace.configPath,
|
|
75
|
+
TMUX_IDE_SETTINGS_DIR: namespace.settingsDir,
|
|
76
|
+
TMUX_IDE_CLAUDE_DIR: namespace.claudeDir,
|
|
77
|
+
TMUX_IDE_CLAUDE_SETTINGS: namespace.claudeSettingsPath,
|
|
78
|
+
TMUX_IDE_CLAUDE_HOOK_PATH: namespace.claudeHookPath,
|
|
79
|
+
TMUX_IDE_OPENCODE_DIR: namespace.opencodeDir,
|
|
80
|
+
};
|
|
81
|
+
for (const [name, value] of Object.entries(environment)) safeValue(value, name);
|
|
82
|
+
const args = [executable, "--headless", "--supervised", supervisionId];
|
|
83
|
+
const shared = {
|
|
84
|
+
supervisionId,
|
|
85
|
+
executable,
|
|
86
|
+
recordPath: join(namespace.daemonInfoDir, "service.json"),
|
|
87
|
+
stdoutPath,
|
|
88
|
+
stderrPath,
|
|
89
|
+
};
|
|
90
|
+
if (options.platform === "darwin") {
|
|
91
|
+
const label = `com.${supervisionId}`;
|
|
92
|
+
const string = (value: string) => `<string>${xml(value)}</string>`;
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
...shared,
|
|
95
|
+
manager: "launchd",
|
|
96
|
+
target: `gui/${options.uid}/${label}`,
|
|
97
|
+
unitPath: join(home, "Library", "LaunchAgents", `${label}.plist`),
|
|
98
|
+
contents: `<?xml version="1.0" encoding="UTF-8"?>
|
|
99
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
100
|
+
<plist version="1.0"><dict>
|
|
101
|
+
<key>Label</key>${string(label)}
|
|
102
|
+
<key>ProgramArguments</key><array>${args.map(string).join("")}</array>
|
|
103
|
+
<key>EnvironmentVariables</key><dict>${Object.entries(environment)
|
|
104
|
+
.map(([key, value]) => `<key>${xml(key)}</key>${string(value)}`)
|
|
105
|
+
.join("")}</dict>
|
|
106
|
+
<key>WorkingDirectory</key>${string(home)}
|
|
107
|
+
<key>RunAtLoad</key><true/>
|
|
108
|
+
<key>KeepAlive</key><true/>
|
|
109
|
+
<key>ThrottleInterval</key><integer>3</integer>
|
|
110
|
+
<key>ExitTimeOut</key><integer>30</integer>
|
|
111
|
+
<key>AbandonProcessGroup</key><true/>
|
|
112
|
+
<key>StandardOutPath</key>${string(stdoutPath)}
|
|
113
|
+
<key>StandardErrorPath</key>${string(stderrPath)}
|
|
114
|
+
</dict></plist>
|
|
115
|
+
`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const configHome = absolute(options.configHome ?? join(home, ".config"), "Config directory");
|
|
119
|
+
const target = `${supervisionId}.service`;
|
|
120
|
+
// WorkingDirectory is a raw path, not an ExecStart-style quoted word.
|
|
121
|
+
// The equivalent '/.' suffix preserves trailing spaces/backslashes without
|
|
122
|
+
// allowing the unit parser to trim them or treat them as line continuation.
|
|
123
|
+
const workingDirectory = `${home.replace(/%/gu, "%%")}/.`;
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
...shared,
|
|
126
|
+
manager: "systemd",
|
|
127
|
+
target,
|
|
128
|
+
unitPath: join(configHome, "systemd", "user", target),
|
|
129
|
+
contents: `[Unit]
|
|
130
|
+
Description=tmux-ide canonical user daemon
|
|
131
|
+
StartLimitIntervalSec=60
|
|
132
|
+
StartLimitBurst=10
|
|
133
|
+
|
|
134
|
+
[Service]
|
|
135
|
+
Type=exec
|
|
136
|
+
ExecStart=:${args.map(systemd).join(" ")}
|
|
137
|
+
WorkingDirectory=${workingDirectory}
|
|
138
|
+
${Object.entries(environment)
|
|
139
|
+
.map(([key, value]) => `Environment=${systemd(`${key}=${value}`)}`)
|
|
140
|
+
.join("\n")}
|
|
141
|
+
Restart=always
|
|
142
|
+
RestartSec=3
|
|
143
|
+
TimeoutStopSec=30
|
|
144
|
+
# tmux pane work has an independent lifetime; never kill the whole service cgroup.
|
|
145
|
+
KillMode=process
|
|
146
|
+
StandardOutput=journal
|
|
147
|
+
StandardError=journal
|
|
148
|
+
|
|
149
|
+
[Install]
|
|
150
|
+
WantedBy=default.target
|
|
151
|
+
`,
|
|
152
|
+
});
|
|
153
|
+
}
|