aimux-cli 0.1.16 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +184 -67
- package/bin/aimux-dev +10 -0
- package/dist/agent-events.js +0 -1
- package/dist/agent-output-parser.js +0 -1
- package/dist/agent-prompt-delivery.js +0 -1
- package/dist/agent-tracker.js +0 -1
- package/dist/agent-watcher.js +0 -1
- package/dist/alert-display.d.ts +21 -0
- package/dist/alert-display.js +85 -0
- package/dist/atomic-write.js +0 -1
- package/dist/attachment-store.d.ts +0 -7
- package/dist/attachment-store.js +2 -87
- package/dist/builtin-metadata-watchers.js +4 -5
- package/dist/claude-hooks.d.ts +1 -0
- package/dist/claude-hooks.js +25 -1
- package/dist/config.d.ts +19 -13
- package/dist/config.js +28 -15
- package/dist/connection-targets.d.ts +8 -0
- package/dist/connection-targets.js +27 -0
- package/dist/context/compactor.js +0 -1
- package/dist/context/context-bridge.js +0 -1
- package/dist/context/context-file.js +0 -1
- package/dist/context/history.js +0 -1
- package/dist/credentials.d.ts +12 -0
- package/dist/credentials.js +48 -0
- package/dist/daemon.d.ts +23 -0
- package/dist/daemon.js +391 -67
- package/dist/dashboard/command-spec.js +0 -1
- package/dist/dashboard/feedback.js +0 -1
- package/dist/dashboard/index.d.ts +13 -10
- package/dist/dashboard/index.js +3 -27
- package/dist/dashboard/operation-failures.js +0 -1
- package/dist/dashboard/order.d.ts +22 -0
- package/dist/dashboard/order.js +54 -0
- package/dist/dashboard/pending-actions.d.ts +39 -10
- package/dist/dashboard/pending-actions.js +166 -37
- package/dist/dashboard/quick-jump.d.ts +2 -1
- package/dist/dashboard/quick-jump.js +7 -5
- package/dist/dashboard/runtime-evidence.js +0 -1
- package/dist/dashboard/session-actions.d.ts +4 -4
- package/dist/dashboard/session-actions.js +1 -2
- package/dist/dashboard/session-registry.d.ts +4 -3
- package/dist/dashboard/session-registry.js +16 -51
- package/dist/dashboard/sort.js +0 -1
- package/dist/dashboard/state.d.ts +1 -1
- package/dist/dashboard/state.js +0 -1
- package/dist/dashboard/targets.js +0 -1
- package/dist/dashboard/ui-state-store.d.ts +16 -1
- package/dist/dashboard/ui-state-store.js +73 -3
- package/dist/debug-state.d.ts +97 -0
- package/dist/debug-state.js +540 -0
- package/dist/debug.d.ts +38 -0
- package/dist/debug.js +219 -16
- package/dist/default-plugins/gh-pr-context.d.ts +2 -1
- package/dist/default-plugins/gh-pr-context.js +17 -12
- package/dist/default-plugins/transcript-length.js +15 -3
- package/dist/fast-control.js +37 -20
- package/dist/hotkeys.js +0 -1
- package/dist/http-client.js +31 -3
- package/dist/key-parser.js +0 -1
- package/dist/last-used.js +0 -1
- package/dist/local-ui-server.d.ts +22 -0
- package/dist/local-ui-server.js +185 -0
- package/dist/login-flow.d.ts +7 -0
- package/dist/login-flow.js +119 -0
- package/dist/main.js +821 -152
- package/dist/managed-launch-env.js +14 -1
- package/dist/metadata-server.d.ts +36 -36
- package/dist/metadata-server.js +638 -138
- package/dist/metadata-store.d.ts +4 -1
- package/dist/metadata-store.js +30 -3
- package/dist/multiplexer/agent-io-methods.d.ts +2 -10
- package/dist/multiplexer/agent-io-methods.js +12 -44
- package/dist/multiplexer/archives.js +8 -10
- package/dist/multiplexer/dashboard-actions-methods.js +0 -1
- package/dist/multiplexer/dashboard-control.js +45 -14
- package/dist/multiplexer/dashboard-interaction.d.ts +8 -2
- package/dist/multiplexer/dashboard-interaction.js +187 -29
- package/dist/multiplexer/dashboard-model.d.ts +10 -3
- package/dist/multiplexer/dashboard-model.js +417 -36
- package/dist/multiplexer/dashboard-ops.d.ts +9 -7
- package/dist/multiplexer/dashboard-ops.js +178 -69
- package/dist/multiplexer/dashboard-state-methods.d.ts +2 -1
- package/dist/multiplexer/dashboard-state-methods.js +3 -3
- package/dist/multiplexer/dashboard-tail-methods.d.ts +22 -10
- package/dist/multiplexer/dashboard-tail-methods.js +164 -48
- package/dist/multiplexer/dashboard-view-methods.d.ts +1 -1
- package/dist/multiplexer/dashboard-view-methods.js +23 -9
- package/dist/multiplexer/graveyard-view-model.d.ts +9 -1
- package/dist/multiplexer/graveyard-view-model.js +39 -1
- package/dist/multiplexer/index.d.ts +15 -12
- package/dist/multiplexer/index.js +64 -44
- package/dist/multiplexer/navigation.js +0 -1
- package/dist/multiplexer/notifications.js +107 -25
- package/dist/multiplexer/persistence-methods.d.ts +31 -4
- package/dist/multiplexer/persistence-methods.js +304 -309
- package/dist/multiplexer/runtime-lifecycle-methods.d.ts +8 -10
- package/dist/multiplexer/runtime-lifecycle-methods.js +104 -87
- package/dist/multiplexer/runtime-state.d.ts +8 -10
- package/dist/multiplexer/runtime-state.js +82 -146
- package/dist/multiplexer/runtime-sync.d.ts +2 -10
- package/dist/multiplexer/runtime-sync.js +3 -19
- package/dist/multiplexer/service-state-snapshot.d.ts +2 -4
- package/dist/multiplexer/service-state-snapshot.js +4 -52
- package/dist/multiplexer/services.d.ts +1 -0
- package/dist/multiplexer/services.js +55 -6
- package/dist/multiplexer/session-capture.d.ts +1 -0
- package/dist/multiplexer/session-capture.js +23 -0
- package/dist/multiplexer/session-launch.d.ts +4 -1
- package/dist/multiplexer/session-launch.js +152 -64
- package/dist/multiplexer/session-runtime-core.d.ts +8 -20
- package/dist/multiplexer/session-runtime-core.js +40 -136
- package/dist/multiplexer/subscreens.js +10 -4
- package/dist/multiplexer/tool-picker.js +0 -1
- package/dist/multiplexer/worktree-graveyard.d.ts +0 -1
- package/dist/multiplexer/worktree-graveyard.js +15 -17
- package/dist/multiplexer/worktrees.js +96 -41
- package/dist/notification-context.js +8 -5
- package/dist/notifications.js +163 -102
- package/dist/notify.d.ts +4 -0
- package/dist/notify.js +14 -1
- package/dist/orchestration-actions.js +0 -1
- package/dist/orchestration-routing.js +0 -1
- package/dist/orchestration.js +0 -1
- package/dist/osc-notifications.js +0 -1
- package/dist/paths.d.ts +32 -7
- package/dist/paths.js +82 -59
- package/dist/pending-actions.d.ts +5 -0
- package/dist/pending-actions.js +13 -0
- package/dist/plugin-runtime.js +9 -3
- package/dist/project-events.d.ts +1 -10
- package/dist/project-events.js +0 -11
- package/dist/project-scanner.d.ts +2 -3
- package/dist/project-scanner.js +58 -130
- package/dist/project-service-manifest.d.ts +1 -3
- package/dist/project-service-manifest.js +1 -4
- package/dist/recency.js +0 -1
- package/dist/recorder.js +0 -1
- package/dist/relay-client.d.ts +30 -0
- package/dist/relay-client.js +190 -0
- package/dist/remote-access.d.ts +16 -0
- package/dist/remote-access.js +90 -0
- package/dist/runtime-core/exchange-derived.d.ts +2 -0
- package/dist/runtime-core/exchange-derived.js +153 -0
- package/dist/runtime-core/exchange-import.d.ts +24 -0
- package/dist/runtime-core/exchange-import.js +317 -0
- package/dist/runtime-core/exchange-store.d.ts +157 -0
- package/dist/runtime-core/exchange-store.js +452 -0
- package/dist/runtime-core/topology-services.d.ts +38 -0
- package/dist/runtime-core/topology-services.js +170 -0
- package/dist/runtime-core/topology-sessions.d.ts +52 -0
- package/dist/runtime-core/topology-sessions.js +238 -0
- package/dist/runtime-core/topology-store.d.ts +171 -0
- package/dist/runtime-core/topology-store.js +419 -0
- package/dist/runtime-core/topology-worktrees.d.ts +60 -0
- package/dist/runtime-core/topology-worktrees.js +199 -0
- package/dist/runtime-migration.d.ts +69 -0
- package/dist/runtime-migration.js +398 -0
- package/dist/session-bootstrap.d.ts +8 -6
- package/dist/session-bootstrap.js +51 -159
- package/dist/session-runtime.d.ts +2 -0
- package/dist/session-runtime.js +1 -1
- package/dist/session-semantics.d.ts +12 -4
- package/dist/session-semantics.js +14 -1
- package/dist/shell-args.js +0 -1
- package/dist/shell-hooks.js +32 -11
- package/dist/shell-state.d.ts +2 -0
- package/dist/shell-state.js +26 -2
- package/dist/status-detector.js +0 -1
- package/dist/statusline-model.d.ts +10 -2
- package/dist/statusline-model.js +106 -31
- package/dist/task-workflow.d.ts +6 -9
- package/dist/task-workflow.js +37 -85
- package/dist/tasks.d.ts +6 -33
- package/dist/tasks.js +46 -89
- package/dist/team.d.ts +29 -0
- package/dist/team.js +40 -1
- package/dist/terminal-host.js +0 -1
- package/dist/threads.d.ts +6 -35
- package/dist/threads.js +89 -99
- package/dist/tmux/doctor.js +0 -1
- package/dist/tmux/inbox-popup.js +37 -16
- package/dist/tmux/runtime-manager.d.ts +3 -0
- package/dist/tmux/runtime-manager.js +21 -5
- package/dist/tmux/session-transport.js +0 -1
- package/dist/tmux/statusline-cache.js +0 -1
- package/dist/tmux/statusline.js +49 -10
- package/dist/tmux/switcher.js +0 -1
- package/dist/tmux/window-open.js +1 -3
- package/dist/tool-output-watchers.d.ts +0 -18
- package/dist/tool-output-watchers.js +0 -323
- package/dist/tui/render/box.js +0 -1
- package/dist/tui/render/text.js +0 -1
- package/dist/tui/screens/dashboard-renderers.js +37 -26
- package/dist/tui/screens/overlay-renderers.d.ts +2 -0
- package/dist/tui/screens/overlay-renderers.js +37 -2
- package/dist/tui/screens/subscreen-renderers.js +7 -1
- package/dist/workflow.js +0 -1
- package/dist/worktree.js +17 -1
- package/dist-ui/_expo/static/css/web-30453ede1678c16acb08b97e83e8646d.css +1 -0
- package/dist-ui/_expo/static/js/web/entry-477c745b2adc79367a4380ecf07d9ff6.js +14620 -0
- package/dist-ui/assets/assets/images/icon.a5413dcd2e811c9f2317d01a28118d8a.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist-ui/favicon.ico +0 -0
- package/dist-ui/index.html +38 -0
- package/dist-ui/metadata.json +1 -0
- package/package.json +29 -12
- package/dist/agent-events.js.map +0 -1
- package/dist/agent-message-parts.d.ts +0 -17
- package/dist/agent-message-parts.js +0 -31
- package/dist/agent-message-parts.js.map +0 -1
- package/dist/agent-output-parser.js.map +0 -1
- package/dist/agent-prompt-delivery.js.map +0 -1
- package/dist/agent-tracker.js.map +0 -1
- package/dist/agent-watcher.js.map +0 -1
- package/dist/atomic-write.js.map +0 -1
- package/dist/attachment-store.js.map +0 -1
- package/dist/builtin-metadata-watchers.js.map +0 -1
- package/dist/claude-hooks.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/context/compactor.js.map +0 -1
- package/dist/context/context-bridge.js.map +0 -1
- package/dist/context/context-file.js.map +0 -1
- package/dist/context/history.js.map +0 -1
- package/dist/daemon.js.map +0 -1
- package/dist/dashboard/command-spec.js.map +0 -1
- package/dist/dashboard/feedback.js.map +0 -1
- package/dist/dashboard/index.js.map +0 -1
- package/dist/dashboard/operation-failures.js.map +0 -1
- package/dist/dashboard/pending-actions.js.map +0 -1
- package/dist/dashboard/quick-jump.js.map +0 -1
- package/dist/dashboard/runtime-evidence.js.map +0 -1
- package/dist/dashboard/session-actions.js.map +0 -1
- package/dist/dashboard/session-registry.js.map +0 -1
- package/dist/dashboard/sort.js.map +0 -1
- package/dist/dashboard/state.js.map +0 -1
- package/dist/dashboard/targets.js.map +0 -1
- package/dist/dashboard/ui-state-store.js.map +0 -1
- package/dist/debug.js.map +0 -1
- package/dist/default-plugins/gh-pr-context.js.map +0 -1
- package/dist/default-plugins/transcript-length.js.map +0 -1
- package/dist/fast-control.js.map +0 -1
- package/dist/hotkeys.js.map +0 -1
- package/dist/http-client.js.map +0 -1
- package/dist/instance-directory.d.ts +0 -32
- package/dist/instance-directory.js +0 -82
- package/dist/instance-directory.js.map +0 -1
- package/dist/instance-registry.d.ts +0 -39
- package/dist/instance-registry.js +0 -208
- package/dist/instance-registry.js.map +0 -1
- package/dist/key-parser.js.map +0 -1
- package/dist/last-used.js.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/managed-launch-env.js.map +0 -1
- package/dist/metadata-server.js.map +0 -1
- package/dist/metadata-store.js.map +0 -1
- package/dist/multiplexer/agent-io-methods.js.map +0 -1
- package/dist/multiplexer/archives.js.map +0 -1
- package/dist/multiplexer/dashboard-actions-methods.js.map +0 -1
- package/dist/multiplexer/dashboard-control.js.map +0 -1
- package/dist/multiplexer/dashboard-interaction.js.map +0 -1
- package/dist/multiplexer/dashboard-model.js.map +0 -1
- package/dist/multiplexer/dashboard-ops.js.map +0 -1
- package/dist/multiplexer/dashboard-state-methods.js.map +0 -1
- package/dist/multiplexer/dashboard-tail-methods.js.map +0 -1
- package/dist/multiplexer/dashboard-view-methods.js.map +0 -1
- package/dist/multiplexer/graveyard-view-model.js.map +0 -1
- package/dist/multiplexer/index.js.map +0 -1
- package/dist/multiplexer/navigation.js.map +0 -1
- package/dist/multiplexer/notifications.js.map +0 -1
- package/dist/multiplexer/persistence-methods.js.map +0 -1
- package/dist/multiplexer/runtime-lifecycle-methods.js.map +0 -1
- package/dist/multiplexer/runtime-state.js.map +0 -1
- package/dist/multiplexer/runtime-sync.js.map +0 -1
- package/dist/multiplexer/service-state-snapshot.js.map +0 -1
- package/dist/multiplexer/services.js.map +0 -1
- package/dist/multiplexer/session-actions.d.ts +0 -40
- package/dist/multiplexer/session-actions.js +0 -110
- package/dist/multiplexer/session-actions.js.map +0 -1
- package/dist/multiplexer/session-launch.js.map +0 -1
- package/dist/multiplexer/session-runtime-core.js.map +0 -1
- package/dist/multiplexer/subscreens.js.map +0 -1
- package/dist/multiplexer/tool-picker.js.map +0 -1
- package/dist/multiplexer/worktree-graveyard.js.map +0 -1
- package/dist/multiplexer/worktrees.js.map +0 -1
- package/dist/notification-context.js.map +0 -1
- package/dist/notifications.js.map +0 -1
- package/dist/notify.js.map +0 -1
- package/dist/orchestration-actions.js.map +0 -1
- package/dist/orchestration-dispatcher.d.ts +0 -25
- package/dist/orchestration-dispatcher.js +0 -59
- package/dist/orchestration-dispatcher.js.map +0 -1
- package/dist/orchestration-routing.js.map +0 -1
- package/dist/orchestration.js.map +0 -1
- package/dist/osc-notifications.js.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/plugin-runtime.js.map +0 -1
- package/dist/project-events.js.map +0 -1
- package/dist/project-scanner.js.map +0 -1
- package/dist/project-service-manifest.js.map +0 -1
- package/dist/recency.js.map +0 -1
- package/dist/recorder.js.map +0 -1
- package/dist/session-bootstrap.js.map +0 -1
- package/dist/session-input-operations.d.ts +0 -19
- package/dist/session-input-operations.js +0 -46
- package/dist/session-input-operations.js.map +0 -1
- package/dist/session-message-history.d.ts +0 -27
- package/dist/session-message-history.js +0 -105
- package/dist/session-message-history.js.map +0 -1
- package/dist/session-runtime.js.map +0 -1
- package/dist/session-semantics.js.map +0 -1
- package/dist/shell-args.js.map +0 -1
- package/dist/shell-hooks.js.map +0 -1
- package/dist/shell-state.js.map +0 -1
- package/dist/status-detector.js.map +0 -1
- package/dist/statusline-model.js.map +0 -1
- package/dist/task-dispatcher.d.ts +0 -64
- package/dist/task-dispatcher.js +0 -213
- package/dist/task-dispatcher.js.map +0 -1
- package/dist/task-workflow.js.map +0 -1
- package/dist/tasks.js.map +0 -1
- package/dist/team.js.map +0 -1
- package/dist/terminal-host.js.map +0 -1
- package/dist/threads.js.map +0 -1
- package/dist/tmux/doctor.js.map +0 -1
- package/dist/tmux/inbox-popup.js.map +0 -1
- package/dist/tmux/runtime-manager.js.map +0 -1
- package/dist/tmux/session-transport.js.map +0 -1
- package/dist/tmux/statusline-cache.js.map +0 -1
- package/dist/tmux/statusline.js.map +0 -1
- package/dist/tmux/switcher.js.map +0 -1
- package/dist/tmux/window-open.js.map +0 -1
- package/dist/tool-output-watchers.js.map +0 -1
- package/dist/tui/render/box.js.map +0 -1
- package/dist/tui/render/text.js.map +0 -1
- package/dist/tui/screens/dashboard-renderers.js.map +0 -1
- package/dist/tui/screens/overlay-renderers.js.map +0 -1
- package/dist/tui/screens/subscreen-renderers.js.map +0 -1
- package/dist/workflow.js.map +0 -1
- package/dist/worktree.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const BLOCKING_PENDING_DASHBOARD_ACTIONS = new Set([
|
|
2
|
+
"creating",
|
|
3
|
+
"forking",
|
|
4
|
+
"migrating",
|
|
5
|
+
"starting",
|
|
6
|
+
"stopping",
|
|
7
|
+
"graveyarding",
|
|
8
|
+
"renaming",
|
|
9
|
+
"removing",
|
|
10
|
+
]);
|
|
11
|
+
export function isBlockingPendingDashboardActionKind(kind) {
|
|
12
|
+
return Boolean(kind && BLOCKING_PENDING_DASHBOARD_ACTIONS.has(kind));
|
|
13
|
+
}
|
package/dist/plugin-runtime.js
CHANGED
|
@@ -6,6 +6,8 @@ import { updateSessionMetadata, clearSessionLogs, loadMetadataState, } from "./m
|
|
|
6
6
|
import { debug } from "./debug.js";
|
|
7
7
|
import { createBuiltinMetadataWatchers } from "./builtin-metadata-watchers.js";
|
|
8
8
|
import { AgentTracker } from "./agent-tracker.js";
|
|
9
|
+
import { listTopologySessionStates } from "./runtime-core/topology-sessions.js";
|
|
10
|
+
import { contextualizeAlertInput, metadataDisplayContext } from "./alert-display.js";
|
|
9
11
|
function listPluginFiles(dir) {
|
|
10
12
|
if (!existsSync(dir))
|
|
11
13
|
return [];
|
|
@@ -122,7 +124,12 @@ export class PluginRuntime {
|
|
|
122
124
|
const alert = deriveAlertFromAgentEvent(sessionId, event);
|
|
123
125
|
if (!alert)
|
|
124
126
|
return;
|
|
125
|
-
|
|
127
|
+
let context;
|
|
128
|
+
try {
|
|
129
|
+
context = metadataDisplayContext(loadMetadataState().sessions[sessionId]);
|
|
130
|
+
}
|
|
131
|
+
catch { }
|
|
132
|
+
this.eventBus.publishAlert(contextualizeAlertInput(alert, context));
|
|
126
133
|
}
|
|
127
134
|
async start() {
|
|
128
135
|
const tracker = new AgentTracker();
|
|
@@ -226,7 +233,7 @@ export class PluginRuntime {
|
|
|
226
233
|
},
|
|
227
234
|
},
|
|
228
235
|
sessions: {
|
|
229
|
-
list: () =>
|
|
236
|
+
list: () => listTopologySessionStates().map((session) => ({ id: session.id })),
|
|
230
237
|
},
|
|
231
238
|
};
|
|
232
239
|
for (const watcher of createBuiltinMetadataWatchers(api)) {
|
|
@@ -316,4 +323,3 @@ export function deriveAlertFromAgentEvent(sessionId, event) {
|
|
|
316
323
|
cooldownMs: kind === "task_done" ? 10_000 : 15_000,
|
|
317
324
|
};
|
|
318
325
|
}
|
|
319
|
-
//# sourceMappingURL=plugin-runtime.js.map
|
package/dist/project-events.d.ts
CHANGED
|
@@ -13,22 +13,13 @@ export interface AlertEvent {
|
|
|
13
13
|
dedupeKey?: string;
|
|
14
14
|
forceNotify?: boolean;
|
|
15
15
|
}
|
|
16
|
-
export
|
|
17
|
-
type: "history_update";
|
|
18
|
-
projectId: string;
|
|
19
|
-
sessionId: string;
|
|
20
|
-
ts: string;
|
|
21
|
-
messages: unknown[];
|
|
22
|
-
lastN?: number;
|
|
23
|
-
}
|
|
24
|
-
export type ProjectStreamEvent = AlertEvent | HistoryUpdateEvent;
|
|
16
|
+
export type ProjectStreamEvent = AlertEvent;
|
|
25
17
|
type ProjectEventListener = (event: ProjectStreamEvent) => void;
|
|
26
18
|
export declare class ProjectEventBus {
|
|
27
19
|
private listeners;
|
|
28
20
|
private alertCooldowns;
|
|
29
21
|
subscribe(listener: ProjectEventListener): () => void;
|
|
30
22
|
publish(event: ProjectStreamEvent): void;
|
|
31
|
-
publishHistoryUpdate(input: Omit<HistoryUpdateEvent, "type" | "projectId" | "ts">): void;
|
|
32
23
|
publishAlert(alert: Omit<AlertEvent, "type" | "projectId" | "ts"> & {
|
|
33
24
|
dedupeKey?: string;
|
|
34
25
|
cooldownMs?: number;
|
package/dist/project-events.js
CHANGED
|
@@ -15,16 +15,6 @@ export class ProjectEventBus {
|
|
|
15
15
|
listener(event);
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
publishHistoryUpdate(input) {
|
|
19
|
-
this.publish({
|
|
20
|
-
type: "history_update",
|
|
21
|
-
projectId: getProjectId(),
|
|
22
|
-
ts: new Date().toISOString(),
|
|
23
|
-
sessionId: input.sessionId,
|
|
24
|
-
messages: input.messages,
|
|
25
|
-
lastN: input.lastN,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
18
|
publishAlert(alert) {
|
|
29
19
|
const cooldownMs = alert.cooldownMs ?? 15_000;
|
|
30
20
|
const dedupeKey = alert.dedupeKey?.trim() || undefined;
|
|
@@ -63,4 +53,3 @@ export class ProjectEventBus {
|
|
|
63
53
|
return true;
|
|
64
54
|
}
|
|
65
55
|
}
|
|
66
|
-
//# sourceMappingURL=project-events.js.map
|
|
@@ -7,7 +7,6 @@ export interface GlobalSession {
|
|
|
7
7
|
headline?: string;
|
|
8
8
|
role?: string;
|
|
9
9
|
worktreePath?: string;
|
|
10
|
-
ownerPid?: number;
|
|
11
10
|
}
|
|
12
11
|
export interface ProjectInfo {
|
|
13
12
|
name: string;
|
|
@@ -20,11 +19,11 @@ export interface DesktopProjectInfo {
|
|
|
20
19
|
path: string;
|
|
21
20
|
lastSeen?: string;
|
|
22
21
|
dashboardSessionName: string;
|
|
23
|
-
sessions: GlobalSession[];
|
|
24
22
|
}
|
|
25
23
|
/**
|
|
26
24
|
* Discover local projects with aimux state.
|
|
27
|
-
* Uses the global projects registry
|
|
25
|
+
* Uses the global projects registry. Runtime topology lives in the
|
|
26
|
+
* registry-owned project state directory, not in legacy presence files.
|
|
28
27
|
*/
|
|
29
28
|
export declare function discoverProjects(): string[];
|
|
30
29
|
/**
|
package/dist/project-scanner.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import { existsSync, readFileSync,
|
|
1
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { join, basename } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
4
|
import { getAimuxDirFor, getProjectStateDirById, listProjects } from "./paths.js";
|
|
5
5
|
import { TmuxRuntimeManager } from "./tmux/runtime-manager.js";
|
|
6
|
+
import { RuntimeTopologyStore } from "./runtime-core/topology-store.js";
|
|
7
|
+
import { topologySessionToSessionState } from "./runtime-core/topology-sessions.js";
|
|
8
|
+
function topologyStatusToGlobalStatus(status) {
|
|
9
|
+
if (status === "running" || status === "idle" || status === "waiting" || status === "offline")
|
|
10
|
+
return status;
|
|
11
|
+
return "offline";
|
|
12
|
+
}
|
|
6
13
|
/**
|
|
7
14
|
* Discover local projects with aimux state.
|
|
8
|
-
* Uses the global projects registry
|
|
15
|
+
* Uses the global projects registry. Runtime topology lives in the
|
|
16
|
+
* registry-owned project state directory, not in legacy presence files.
|
|
9
17
|
*/
|
|
10
18
|
export function discoverProjects() {
|
|
11
19
|
const found = new Set();
|
|
@@ -15,34 +23,6 @@ export function discoverProjects() {
|
|
|
15
23
|
found.add(entry.repoRoot);
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
|
-
// Fallback: scan common dev directories for old-style in-repo .aimux/
|
|
19
|
-
const home = homedir();
|
|
20
|
-
const scanDirs = [
|
|
21
|
-
join(home, "cs"),
|
|
22
|
-
join(home, "projects"),
|
|
23
|
-
join(home, "dev"),
|
|
24
|
-
join(home, "src"),
|
|
25
|
-
join(home, "code"),
|
|
26
|
-
join(home, "work"),
|
|
27
|
-
];
|
|
28
|
-
for (const scanDir of scanDirs) {
|
|
29
|
-
try {
|
|
30
|
-
const entries = readdirSync(scanDir);
|
|
31
|
-
for (const entry of entries) {
|
|
32
|
-
const projectPath = join(scanDir, entry);
|
|
33
|
-
try {
|
|
34
|
-
if (!statSync(projectPath).isDirectory())
|
|
35
|
-
continue;
|
|
36
|
-
const aimuxDir = join(projectPath, ".aimux");
|
|
37
|
-
if (existsSync(join(aimuxDir, "instances.json")) || existsSync(join(aimuxDir, "state.json"))) {
|
|
38
|
-
found.add(projectPath);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
catch { }
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch { }
|
|
45
|
-
}
|
|
46
26
|
return [...found];
|
|
47
27
|
}
|
|
48
28
|
/**
|
|
@@ -53,133 +33,85 @@ export function scanProject(projectPath) {
|
|
|
53
33
|
const seenIds = new Set();
|
|
54
34
|
const registryEntry = listProjects().find((entry) => entry.repoRoot === projectPath);
|
|
55
35
|
const sessionById = new Map();
|
|
56
|
-
// Check both global state dir and in-repo .aimux/ for instances
|
|
57
|
-
const instancesPaths = [join(getAimuxDirFor(projectPath), "instances.json")];
|
|
58
36
|
const statusDirs = [join(getAimuxDirFor(projectPath), "status")];
|
|
59
37
|
// Also check global project state dirs for registered projects
|
|
60
38
|
if (registryEntry) {
|
|
61
39
|
const projectStateDir = getProjectStateDirById(registryEntry.id);
|
|
62
|
-
const globalInstances = join(projectStateDir, "instances.json");
|
|
63
|
-
if (!instancesPaths.includes(globalInstances)) {
|
|
64
|
-
instancesPaths.unshift(globalInstances);
|
|
65
|
-
}
|
|
66
40
|
statusDirs.unshift(join(projectStateDir, "status"));
|
|
67
41
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
process.kill(inst.pid, 0);
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
for (const s of inst.sessions) {
|
|
82
|
-
if (seenIds.has(s.id))
|
|
83
|
-
continue;
|
|
84
|
-
seenIds.add(s.id);
|
|
85
|
-
let headline;
|
|
86
|
-
for (const statusDir of statusDirs) {
|
|
87
|
-
try {
|
|
88
|
-
const statusPath = join(statusDir, `${s.id}.md`);
|
|
89
|
-
if (existsSync(statusPath)) {
|
|
90
|
-
const content = readFileSync(statusPath, "utf-8").trim();
|
|
91
|
-
if (content) {
|
|
92
|
-
headline = content.split("\n")[0].slice(0, 80);
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// Try the next candidate directory.
|
|
99
|
-
}
|
|
42
|
+
function readStatusHeadline(sessionId) {
|
|
43
|
+
for (const statusDir of statusDirs) {
|
|
44
|
+
try {
|
|
45
|
+
const statusPath = join(statusDir, `${sessionId}.md`);
|
|
46
|
+
if (existsSync(statusPath)) {
|
|
47
|
+
const content = readFileSync(statusPath, "utf-8").trim();
|
|
48
|
+
if (content) {
|
|
49
|
+
return content.split("\n")[0].slice(0, 80);
|
|
100
50
|
}
|
|
101
|
-
sessions.push({
|
|
102
|
-
id: s.id,
|
|
103
|
-
tool: s.tool,
|
|
104
|
-
status: "running",
|
|
105
|
-
headline,
|
|
106
|
-
worktreePath: s.worktreePath,
|
|
107
|
-
ownerPid: inst.pid,
|
|
108
|
-
});
|
|
109
|
-
sessionById.set(s.id, sessions[sessions.length - 1]);
|
|
110
51
|
}
|
|
111
52
|
}
|
|
53
|
+
catch {
|
|
54
|
+
// Try the next candidate directory.
|
|
55
|
+
}
|
|
112
56
|
}
|
|
113
|
-
|
|
57
|
+
return undefined;
|
|
114
58
|
}
|
|
115
|
-
|
|
116
|
-
const statuslinePaths = [join(getAimuxDirFor(projectPath), "statusline.json")];
|
|
59
|
+
const topologyPaths = [];
|
|
117
60
|
if (registryEntry) {
|
|
118
|
-
|
|
119
|
-
if (!statuslinePaths.includes(globalStatusline)) {
|
|
120
|
-
statuslinePaths.unshift(globalStatusline);
|
|
121
|
-
}
|
|
61
|
+
topologyPaths.push(join(getProjectStateDirById(registryEntry.id), "runtime-topology.yaml"));
|
|
122
62
|
}
|
|
123
|
-
for (const
|
|
124
|
-
if (!existsSync(
|
|
63
|
+
for (const topologyPath of topologyPaths) {
|
|
64
|
+
if (!existsSync(topologyPath))
|
|
125
65
|
continue;
|
|
126
66
|
try {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
existing.role = s.role ?? existing.role;
|
|
67
|
+
const topology = new RuntimeTopologyStore(topologyPath).read();
|
|
68
|
+
for (const topologySession of topology.sessions.filter((session) => session.status !== "graveyard")) {
|
|
69
|
+
const s = topologySessionToSessionState(topologySession, topology);
|
|
70
|
+
if (seenIds.has(s.id)) {
|
|
71
|
+
const existing = sessionById.get(s.id);
|
|
72
|
+
if (existing) {
|
|
73
|
+
existing.status = topologyStatusToGlobalStatus(topologySession.status);
|
|
74
|
+
existing.label = s.label ?? existing.label;
|
|
75
|
+
existing.headline = s.headline ?? existing.headline ?? readStatusHeadline(s.id);
|
|
76
|
+
existing.worktreePath = s.worktreePath ?? existing.worktreePath;
|
|
77
|
+
}
|
|
139
78
|
continue;
|
|
140
79
|
}
|
|
141
80
|
const session = {
|
|
142
81
|
id: s.id,
|
|
143
|
-
tool: s.tool ?? "unknown",
|
|
82
|
+
tool: s.command ?? s.tool ?? "unknown",
|
|
83
|
+
status: topologyStatusToGlobalStatus(topologySession.status),
|
|
144
84
|
label: s.label,
|
|
145
|
-
headline: s.headline,
|
|
146
|
-
|
|
147
|
-
role: s.role,
|
|
85
|
+
headline: s.headline ?? readStatusHeadline(s.id),
|
|
86
|
+
worktreePath: s.worktreePath,
|
|
148
87
|
};
|
|
149
88
|
sessions.push(session);
|
|
150
89
|
sessionById.set(s.id, session);
|
|
151
90
|
seenIds.add(s.id);
|
|
152
91
|
}
|
|
153
|
-
break;
|
|
154
92
|
}
|
|
155
93
|
catch { }
|
|
156
94
|
}
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (!statePaths.includes(globalState)) {
|
|
162
|
-
statePaths.unshift(globalState);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
for (const statePath of statePaths) {
|
|
166
|
-
if (!existsSync(statePath))
|
|
95
|
+
// Enrich known sessions with the global statusline projection when available.
|
|
96
|
+
const statuslinePaths = registryEntry ? [join(getProjectStateDirById(registryEntry.id), "statusline.json")] : [];
|
|
97
|
+
for (const statuslinePath of statuslinePaths) {
|
|
98
|
+
if (!existsSync(statuslinePath))
|
|
167
99
|
continue;
|
|
168
100
|
try {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
101
|
+
const stat = statSync(statuslinePath);
|
|
102
|
+
if (Date.now() - stat.mtimeMs > 10_000)
|
|
103
|
+
continue;
|
|
104
|
+
const statusline = JSON.parse(readFileSync(statuslinePath, "utf-8"));
|
|
105
|
+
for (const s of statusline.sessions ?? []) {
|
|
106
|
+
const existing = sessionById.get(s.id);
|
|
107
|
+
if (!existing)
|
|
172
108
|
continue;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
status: "offline",
|
|
178
|
-
label: s.label,
|
|
179
|
-
headline: s.headline,
|
|
180
|
-
worktreePath: s.worktreePath,
|
|
181
|
-
});
|
|
109
|
+
existing.tool = s.tool ?? existing.tool;
|
|
110
|
+
existing.label = s.label ?? existing.label;
|
|
111
|
+
existing.headline = s.headline ?? existing.headline;
|
|
112
|
+
existing.role = s.role ?? existing.role;
|
|
182
113
|
}
|
|
114
|
+
break;
|
|
183
115
|
}
|
|
184
116
|
catch { }
|
|
185
117
|
}
|
|
@@ -224,7 +156,6 @@ export function listDesktopProjects(tmux = new TmuxRuntimeManager()) {
|
|
|
224
156
|
for (const entry of listProjects()) {
|
|
225
157
|
if (shouldHideDesktopProject(entry.repoRoot))
|
|
226
158
|
continue;
|
|
227
|
-
const scanned = scannedByPath.get(entry.repoRoot);
|
|
228
159
|
const tmuxSession = tmux.getProjectSession(entry.repoRoot);
|
|
229
160
|
projects.set(entry.repoRoot, {
|
|
230
161
|
id: entry.id,
|
|
@@ -232,7 +163,6 @@ export function listDesktopProjects(tmux = new TmuxRuntimeManager()) {
|
|
|
232
163
|
path: entry.repoRoot,
|
|
233
164
|
lastSeen: entry.lastSeen,
|
|
234
165
|
dashboardSessionName: tmuxSession.sessionName,
|
|
235
|
-
sessions: scanned?.sessions ?? [],
|
|
236
166
|
});
|
|
237
167
|
}
|
|
238
168
|
for (const scanned of scannedByPath.values()) {
|
|
@@ -246,9 +176,7 @@ export function listDesktopProjects(tmux = new TmuxRuntimeManager()) {
|
|
|
246
176
|
name: scanned.name,
|
|
247
177
|
path: scanned.path,
|
|
248
178
|
dashboardSessionName: tmuxSession.sessionName,
|
|
249
|
-
sessions: scanned.sessions,
|
|
250
179
|
});
|
|
251
180
|
}
|
|
252
181
|
return [...projects.values()].sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
253
182
|
}
|
|
254
|
-
//# sourceMappingURL=project-scanner.js.map
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export declare const PROJECT_SERVICE_API_VERSION = 4;
|
|
2
2
|
export declare const PROJECT_SERVICE_CAPABILITIES: {
|
|
3
|
-
readonly structuredAgentInput: true;
|
|
4
3
|
readonly parsedAgentOutput: true;
|
|
5
|
-
readonly
|
|
6
|
-
readonly agentHistory: true;
|
|
4
|
+
readonly attachmentRead: true;
|
|
7
5
|
readonly chatEventStream: true;
|
|
8
6
|
};
|
|
9
7
|
export interface ProjectServiceManifest {
|
|
@@ -3,10 +3,8 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
export const PROJECT_SERVICE_API_VERSION = 4;
|
|
5
5
|
export const PROJECT_SERVICE_CAPABILITIES = {
|
|
6
|
-
structuredAgentInput: true,
|
|
7
6
|
parsedAgentOutput: true,
|
|
8
|
-
|
|
9
|
-
agentHistory: true,
|
|
7
|
+
attachmentRead: true,
|
|
10
8
|
chatEventStream: true,
|
|
11
9
|
};
|
|
12
10
|
function computeBuildStamp() {
|
|
@@ -53,4 +51,3 @@ export function manifestsMatch(expected, actual) {
|
|
|
53
51
|
const actualCapabilities = actual.capabilities || {};
|
|
54
52
|
return Object.entries(expected.capabilities).every(([key, value]) => actualCapabilities[key] === value);
|
|
55
53
|
}
|
|
56
|
-
//# sourceMappingURL=project-service-manifest.js.map
|
package/dist/recency.js
CHANGED
package/dist/recorder.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AimuxDaemon } from "./daemon.js";
|
|
2
|
+
export type RelayConnectionStatus = "connected" | "connecting" | "reconnecting" | "disconnected" | "auth_failed";
|
|
3
|
+
export interface RelayStatusSnapshot {
|
|
4
|
+
status: RelayConnectionStatus;
|
|
5
|
+
relayUrl: string;
|
|
6
|
+
lastConnectedAt: string | null;
|
|
7
|
+
lastError: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare class RelayClient {
|
|
10
|
+
private readonly token;
|
|
11
|
+
private readonly daemon;
|
|
12
|
+
private ws;
|
|
13
|
+
private retryMs;
|
|
14
|
+
private stopped;
|
|
15
|
+
private retryTimer;
|
|
16
|
+
private status;
|
|
17
|
+
private lastConnectedAt;
|
|
18
|
+
private lastError;
|
|
19
|
+
private readonly relayUrl;
|
|
20
|
+
private handshakeFailures;
|
|
21
|
+
private readonly recentRemoteClientNotifications;
|
|
22
|
+
constructor(relayUrl: string, token: string, daemon: AimuxDaemon);
|
|
23
|
+
getStatus(): RelayStatusSnapshot;
|
|
24
|
+
connect(): void;
|
|
25
|
+
disconnect(): void;
|
|
26
|
+
private handleMessage;
|
|
27
|
+
private sendRaw;
|
|
28
|
+
private shouldNotifyRemoteClientConnected;
|
|
29
|
+
private scheduleRetry;
|
|
30
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { notifyRemoteClientConnected } from "./notify.js";
|
|
2
|
+
const INITIAL_RETRY_MS = 1_000;
|
|
3
|
+
const MAX_RETRY_MS = 30_000;
|
|
4
|
+
const MAX_HANDSHAKE_FAILURES = 5;
|
|
5
|
+
const REMOTE_CLIENT_NOTIFICATION_DEDUPE_MS = 5 * 60 * 1000;
|
|
6
|
+
const TOKEN_PROTOCOL_PREFIX = "aimux-token.";
|
|
7
|
+
export class RelayClient {
|
|
8
|
+
token;
|
|
9
|
+
daemon;
|
|
10
|
+
ws = null;
|
|
11
|
+
retryMs = INITIAL_RETRY_MS;
|
|
12
|
+
stopped = false;
|
|
13
|
+
retryTimer = null;
|
|
14
|
+
status = "disconnected";
|
|
15
|
+
lastConnectedAt = null;
|
|
16
|
+
lastError = null;
|
|
17
|
+
relayUrl;
|
|
18
|
+
handshakeFailures = 0;
|
|
19
|
+
recentRemoteClientNotifications = new Map();
|
|
20
|
+
constructor(relayUrl, token, daemon) {
|
|
21
|
+
this.token = token;
|
|
22
|
+
this.daemon = daemon;
|
|
23
|
+
this.relayUrl = relayUrl.replace(/\/+$/, "");
|
|
24
|
+
}
|
|
25
|
+
getStatus() {
|
|
26
|
+
return {
|
|
27
|
+
status: this.status,
|
|
28
|
+
relayUrl: this.relayUrl,
|
|
29
|
+
lastConnectedAt: this.lastConnectedAt,
|
|
30
|
+
lastError: this.lastError,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
connect() {
|
|
34
|
+
if (this.stopped)
|
|
35
|
+
return;
|
|
36
|
+
// Aimux targets Node 24+; fail fast when an older runtime lacks WebSocket.
|
|
37
|
+
// clear error so the user doesn't see an endless reconnect loop full of
|
|
38
|
+
// ReferenceErrors when running on an older runtime.
|
|
39
|
+
if (typeof globalThis.WebSocket !== "function") {
|
|
40
|
+
this.status = "disconnected";
|
|
41
|
+
this.lastError = "Node runtime is missing globalThis.WebSocket — upgrade to Node 24+ to use the aimux relay";
|
|
42
|
+
this.stopped = true;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.status = this.lastConnectedAt ? "reconnecting" : "connecting";
|
|
46
|
+
const url = `${this.relayUrl}/daemon/connect`;
|
|
47
|
+
try {
|
|
48
|
+
this.ws = new WebSocket(url, ["aimux", `${TOKEN_PROTOCOL_PREFIX}${this.token}`]);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
this.lastError = err instanceof Error ? err.message : String(err);
|
|
52
|
+
this.scheduleRetry();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.ws.addEventListener("open", () => {
|
|
56
|
+
this.retryMs = INITIAL_RETRY_MS;
|
|
57
|
+
this.handshakeFailures = 0;
|
|
58
|
+
this.status = "connected";
|
|
59
|
+
this.lastConnectedAt = new Date().toISOString();
|
|
60
|
+
this.lastError = null;
|
|
61
|
+
console.log("[relay] Connected to relay server");
|
|
62
|
+
});
|
|
63
|
+
this.ws.addEventListener("message", (event) => {
|
|
64
|
+
void this.handleMessage(String(event.data));
|
|
65
|
+
});
|
|
66
|
+
this.ws.addEventListener("close", (event) => {
|
|
67
|
+
this.ws = null;
|
|
68
|
+
const code = event.code;
|
|
69
|
+
// 1008/4001 = auth rejected by relay; don't hammer with retries.
|
|
70
|
+
if (code === 1008 || code === 4001) {
|
|
71
|
+
this.status = "auth_failed";
|
|
72
|
+
this.lastError = "Relay rejected credentials — run `aimux login` again";
|
|
73
|
+
console.warn(`[relay] Auth failed (code ${code}). Stopping reconnect.`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// 1006 = abnormal close, often a failed HTTP upgrade (e.g. 401).
|
|
77
|
+
// Stop after repeated handshake failures to avoid infinite retry spam.
|
|
78
|
+
if (code === 1006) {
|
|
79
|
+
this.handshakeFailures++;
|
|
80
|
+
if (this.handshakeFailures >= MAX_HANDSHAKE_FAILURES) {
|
|
81
|
+
this.status = "auth_failed";
|
|
82
|
+
this.lastError = "Too many handshake failures — token may be expired, run `aimux login` again";
|
|
83
|
+
console.warn(`[relay] ${MAX_HANDSHAKE_FAILURES} consecutive handshake failures. Stopping reconnect.`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!this.stopped) {
|
|
88
|
+
this.status = "reconnecting";
|
|
89
|
+
console.log(`[relay] Disconnected, retrying in ${this.retryMs}ms`);
|
|
90
|
+
this.scheduleRetry();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.status = "disconnected";
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
this.ws.addEventListener("error", () => {
|
|
97
|
+
try {
|
|
98
|
+
this.ws?.close();
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
disconnect() {
|
|
104
|
+
this.stopped = true;
|
|
105
|
+
this.status = "disconnected";
|
|
106
|
+
if (this.retryTimer)
|
|
107
|
+
clearTimeout(this.retryTimer);
|
|
108
|
+
if (this.ws) {
|
|
109
|
+
try {
|
|
110
|
+
this.ws.close(1000, "Daemon shutting down");
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
this.ws = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async handleMessage(data) {
|
|
117
|
+
let msg;
|
|
118
|
+
try {
|
|
119
|
+
msg = JSON.parse(data);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (msg.type === "ping") {
|
|
125
|
+
this.sendRaw(JSON.stringify({ type: "pong" }));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (msg.type === "connected") {
|
|
129
|
+
console.log("[relay] Registered as daemon");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (msg.type === "error") {
|
|
133
|
+
console.warn("[relay] Server error:", msg.message);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (msg.type === "security_event") {
|
|
137
|
+
if (msg.event?.kind === "client_connected") {
|
|
138
|
+
const dedupeKey = msg.event.deviceId ?? `${msg.event.title}:${msg.event.body}`;
|
|
139
|
+
if (this.shouldNotifyRemoteClientConnected(dedupeKey)) {
|
|
140
|
+
notifyRemoteClientConnected({
|
|
141
|
+
title: msg.event.title,
|
|
142
|
+
body: msg.event.body,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (msg.type === "request") {
|
|
149
|
+
try {
|
|
150
|
+
const result = await this.daemon.routeRequest(msg.method, msg.path, msg.body, msg.headers);
|
|
151
|
+
this.sendRaw(JSON.stringify({ id: msg.id, type: "response", status: result.status, body: result.body }));
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
this.sendRaw(JSON.stringify({
|
|
155
|
+
id: msg.id,
|
|
156
|
+
type: "response",
|
|
157
|
+
status: 500,
|
|
158
|
+
body: { ok: false, error: err instanceof Error ? err.message : "Internal error" },
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
sendRaw(data) {
|
|
164
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
165
|
+
this.ws.send(data);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
shouldNotifyRemoteClientConnected(key) {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
for (const [existingKey, lastNotifiedAt] of this.recentRemoteClientNotifications) {
|
|
171
|
+
if (now - lastNotifiedAt > REMOTE_CLIENT_NOTIFICATION_DEDUPE_MS) {
|
|
172
|
+
this.recentRemoteClientNotifications.delete(existingKey);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const previous = this.recentRemoteClientNotifications.get(key);
|
|
176
|
+
if (previous && now - previous <= REMOTE_CLIENT_NOTIFICATION_DEDUPE_MS)
|
|
177
|
+
return false;
|
|
178
|
+
this.recentRemoteClientNotifications.set(key, now);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
scheduleRetry() {
|
|
182
|
+
if (this.stopped)
|
|
183
|
+
return;
|
|
184
|
+
this.retryTimer = setTimeout(() => {
|
|
185
|
+
this.retryTimer = null;
|
|
186
|
+
this.connect();
|
|
187
|
+
}, this.retryMs);
|
|
188
|
+
this.retryMs = Math.min(this.retryMs * 2, MAX_RETRY_MS);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type RemoteActorRole = "owner" | "guest";
|
|
2
|
+
export interface RemoteActor {
|
|
3
|
+
role: RemoteActorRole;
|
|
4
|
+
userId?: string;
|
|
5
|
+
displayName?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
shareId?: string;
|
|
8
|
+
shareSessionId?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RemoteAccessDecision {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
status?: number;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseRemoteActor(headers: Record<string, string> | undefined): RemoteActor | null;
|
|
16
|
+
export declare function assertRemoteAccessAllowed(actor: RemoteActor | null, method: string, pathname: string, searchParams: URLSearchParams): RemoteAccessDecision;
|