aimux-cli 0.1.15 → 0.1.18
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/alert-display.d.ts +21 -0
- package/dist/alert-display.js +86 -0
- package/dist/alert-display.js.map +1 -0
- package/dist/attachment-store.d.ts +0 -7
- package/dist/attachment-store.js +2 -86
- package/dist/attachment-store.js.map +1 -1
- package/dist/builtin-metadata-watchers.js +4 -4
- package/dist/builtin-metadata-watchers.js.map +1 -1
- package/dist/claude-hooks.d.ts +1 -0
- package/dist/claude-hooks.js +25 -0
- package/dist/claude-hooks.js.map +1 -1
- package/dist/config.d.ts +19 -13
- package/dist/config.js +28 -14
- package/dist/config.js.map +1 -1
- package/dist/connection-targets.d.ts +8 -0
- package/dist/connection-targets.js +28 -0
- package/dist/connection-targets.js.map +1 -0
- package/dist/credentials.d.ts +12 -0
- package/dist/credentials.js +49 -0
- package/dist/credentials.js.map +1 -0
- package/dist/daemon.d.ts +23 -0
- package/dist/daemon.js +391 -66
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/index.d.ts +13 -10
- package/dist/dashboard/index.js +3 -26
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/order.d.ts +22 -0
- package/dist/dashboard/order.js +55 -0
- package/dist/dashboard/order.js.map +1 -0
- package/dist/dashboard/pending-actions.d.ts +39 -10
- package/dist/dashboard/pending-actions.js +166 -36
- package/dist/dashboard/pending-actions.js.map +1 -1
- package/dist/dashboard/quick-jump.d.ts +2 -1
- package/dist/dashboard/quick-jump.js +7 -4
- package/dist/dashboard/quick-jump.js.map +1 -1
- package/dist/dashboard/session-actions.d.ts +4 -4
- package/dist/dashboard/session-actions.js +1 -1
- package/dist/dashboard/session-actions.js.map +1 -1
- package/dist/dashboard/session-registry.d.ts +4 -3
- package/dist/dashboard/session-registry.js +16 -50
- package/dist/dashboard/session-registry.js.map +1 -1
- package/dist/dashboard/state.d.ts +1 -1
- package/dist/dashboard/state.js.map +1 -1
- package/dist/dashboard/ui-state-store.d.ts +16 -1
- package/dist/dashboard/ui-state-store.js +73 -2
- package/dist/dashboard/ui-state-store.js.map +1 -1
- package/dist/debug-state.d.ts +97 -0
- package/dist/debug-state.js +541 -0
- package/dist/debug-state.js.map +1 -0
- package/dist/debug.d.ts +38 -0
- package/dist/debug.js +219 -15
- package/dist/debug.js.map +1 -1
- package/dist/default-plugins/gh-pr-context.d.ts +2 -1
- package/dist/default-plugins/gh-pr-context.js +17 -11
- package/dist/default-plugins/gh-pr-context.js.map +1 -1
- package/dist/default-plugins/transcript-length.js +15 -2
- package/dist/default-plugins/transcript-length.js.map +1 -1
- package/dist/fast-control.js +37 -19
- package/dist/fast-control.js.map +1 -1
- package/dist/http-client.js +31 -2
- package/dist/http-client.js.map +1 -1
- package/dist/local-ui-server.d.ts +22 -0
- package/dist/local-ui-server.js +186 -0
- package/dist/local-ui-server.js.map +1 -0
- package/dist/login-flow.d.ts +7 -0
- package/dist/login-flow.js +120 -0
- package/dist/login-flow.js.map +1 -0
- package/dist/main.js +824 -152
- package/dist/main.js.map +1 -1
- package/dist/managed-launch-env.js +14 -0
- package/dist/managed-launch-env.js.map +1 -1
- package/dist/metadata-server.d.ts +36 -36
- package/dist/metadata-server.js +638 -137
- package/dist/metadata-server.js.map +1 -1
- package/dist/metadata-store.d.ts +4 -1
- package/dist/metadata-store.js +30 -2
- package/dist/metadata-store.js.map +1 -1
- package/dist/multiplexer/agent-io-methods.d.ts +2 -10
- package/dist/multiplexer/agent-io-methods.js +12 -43
- package/dist/multiplexer/agent-io-methods.js.map +1 -1
- package/dist/multiplexer/archives.js +8 -9
- package/dist/multiplexer/archives.js.map +1 -1
- package/dist/multiplexer/dashboard-control.js +45 -13
- package/dist/multiplexer/dashboard-control.js.map +1 -1
- package/dist/multiplexer/dashboard-interaction.d.ts +8 -2
- package/dist/multiplexer/dashboard-interaction.js +187 -28
- package/dist/multiplexer/dashboard-interaction.js.map +1 -1
- package/dist/multiplexer/dashboard-model.d.ts +10 -3
- package/dist/multiplexer/dashboard-model.js +417 -35
- package/dist/multiplexer/dashboard-model.js.map +1 -1
- package/dist/multiplexer/dashboard-ops.d.ts +9 -7
- package/dist/multiplexer/dashboard-ops.js +178 -68
- package/dist/multiplexer/dashboard-ops.js.map +1 -1
- package/dist/multiplexer/dashboard-state-methods.d.ts +2 -1
- package/dist/multiplexer/dashboard-state-methods.js +3 -2
- package/dist/multiplexer/dashboard-state-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-tail-methods.d.ts +22 -10
- package/dist/multiplexer/dashboard-tail-methods.js +164 -47
- package/dist/multiplexer/dashboard-tail-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-view-methods.d.ts +1 -1
- package/dist/multiplexer/dashboard-view-methods.js +23 -8
- package/dist/multiplexer/dashboard-view-methods.js.map +1 -1
- package/dist/multiplexer/graveyard-view-model.d.ts +9 -1
- package/dist/multiplexer/graveyard-view-model.js +39 -0
- package/dist/multiplexer/graveyard-view-model.js.map +1 -1
- package/dist/multiplexer/index.d.ts +15 -12
- package/dist/multiplexer/index.js +64 -43
- package/dist/multiplexer/index.js.map +1 -1
- package/dist/multiplexer/notifications.js +107 -24
- package/dist/multiplexer/notifications.js.map +1 -1
- package/dist/multiplexer/persistence-methods.d.ts +31 -4
- package/dist/multiplexer/persistence-methods.js +304 -308
- package/dist/multiplexer/persistence-methods.js.map +1 -1
- package/dist/multiplexer/runtime-lifecycle-methods.d.ts +8 -10
- package/dist/multiplexer/runtime-lifecycle-methods.js +104 -86
- package/dist/multiplexer/runtime-lifecycle-methods.js.map +1 -1
- package/dist/multiplexer/runtime-state.d.ts +8 -10
- package/dist/multiplexer/runtime-state.js +82 -145
- package/dist/multiplexer/runtime-state.js.map +1 -1
- package/dist/multiplexer/runtime-sync.d.ts +2 -10
- package/dist/multiplexer/runtime-sync.js +3 -18
- package/dist/multiplexer/runtime-sync.js.map +1 -1
- package/dist/multiplexer/service-state-snapshot.d.ts +2 -4
- package/dist/multiplexer/service-state-snapshot.js +4 -51
- package/dist/multiplexer/service-state-snapshot.js.map +1 -1
- package/dist/multiplexer/services.d.ts +1 -0
- package/dist/multiplexer/services.js +55 -5
- package/dist/multiplexer/services.js.map +1 -1
- package/dist/multiplexer/session-capture.d.ts +1 -0
- package/dist/multiplexer/session-capture.js +24 -0
- package/dist/multiplexer/session-capture.js.map +1 -0
- package/dist/multiplexer/session-launch.d.ts +4 -1
- package/dist/multiplexer/session-launch.js +152 -63
- package/dist/multiplexer/session-launch.js.map +1 -1
- package/dist/multiplexer/session-runtime-core.d.ts +8 -20
- package/dist/multiplexer/session-runtime-core.js +40 -135
- package/dist/multiplexer/session-runtime-core.js.map +1 -1
- package/dist/multiplexer/subscreens.js +10 -3
- package/dist/multiplexer/subscreens.js.map +1 -1
- package/dist/multiplexer/worktree-graveyard.d.ts +0 -1
- package/dist/multiplexer/worktree-graveyard.js +15 -16
- package/dist/multiplexer/worktree-graveyard.js.map +1 -1
- package/dist/multiplexer/worktrees.js +96 -40
- package/dist/multiplexer/worktrees.js.map +1 -1
- package/dist/notification-context.js +8 -4
- package/dist/notification-context.js.map +1 -1
- package/dist/notifications.js +163 -101
- package/dist/notifications.js.map +1 -1
- package/dist/notify.d.ts +4 -0
- package/dist/notify.js +14 -0
- package/dist/notify.js.map +1 -1
- package/dist/paths.d.ts +32 -7
- package/dist/paths.js +82 -58
- package/dist/paths.js.map +1 -1
- package/dist/pending-actions.d.ts +5 -0
- package/dist/pending-actions.js +14 -0
- package/dist/pending-actions.js.map +1 -0
- package/dist/plugin-runtime.js +9 -2
- package/dist/plugin-runtime.js.map +1 -1
- package/dist/project-events.d.ts +1 -10
- package/dist/project-events.js +0 -10
- package/dist/project-events.js.map +1 -1
- package/dist/project-scanner.d.ts +2 -3
- package/dist/project-scanner.js +58 -129
- package/dist/project-scanner.js.map +1 -1
- package/dist/project-service-manifest.d.ts +1 -3
- package/dist/project-service-manifest.js +1 -3
- package/dist/project-service-manifest.js.map +1 -1
- package/dist/relay-client.d.ts +30 -0
- package/dist/relay-client.js +191 -0
- package/dist/relay-client.js.map +1 -0
- package/dist/remote-access.d.ts +16 -0
- package/dist/remote-access.js +91 -0
- package/dist/remote-access.js.map +1 -0
- package/dist/runtime-core/exchange-derived.d.ts +2 -0
- package/dist/runtime-core/exchange-derived.js +154 -0
- package/dist/runtime-core/exchange-derived.js.map +1 -0
- package/dist/runtime-core/exchange-import.d.ts +24 -0
- package/dist/runtime-core/exchange-import.js +318 -0
- package/dist/runtime-core/exchange-import.js.map +1 -0
- package/dist/runtime-core/exchange-store.d.ts +157 -0
- package/dist/runtime-core/exchange-store.js +453 -0
- package/dist/runtime-core/exchange-store.js.map +1 -0
- package/dist/runtime-core/topology-services.d.ts +38 -0
- package/dist/runtime-core/topology-services.js +171 -0
- package/dist/runtime-core/topology-services.js.map +1 -0
- package/dist/runtime-core/topology-sessions.d.ts +52 -0
- package/dist/runtime-core/topology-sessions.js +239 -0
- package/dist/runtime-core/topology-sessions.js.map +1 -0
- package/dist/runtime-core/topology-store.d.ts +171 -0
- package/dist/runtime-core/topology-store.js +420 -0
- package/dist/runtime-core/topology-store.js.map +1 -0
- package/dist/runtime-core/topology-worktrees.d.ts +60 -0
- package/dist/runtime-core/topology-worktrees.js +200 -0
- package/dist/runtime-core/topology-worktrees.js.map +1 -0
- package/dist/runtime-migration.d.ts +69 -0
- package/dist/runtime-migration.js +399 -0
- package/dist/runtime-migration.js.map +1 -0
- package/dist/session-bootstrap.d.ts +8 -6
- package/dist/session-bootstrap.js +51 -158
- package/dist/session-bootstrap.js.map +1 -1
- package/dist/session-runtime.d.ts +2 -0
- package/dist/session-runtime.js +1 -0
- package/dist/session-runtime.js.map +1 -1
- package/dist/session-semantics.d.ts +12 -4
- package/dist/session-semantics.js +14 -0
- package/dist/session-semantics.js.map +1 -1
- package/dist/shell-hooks.js +32 -10
- package/dist/shell-hooks.js.map +1 -1
- package/dist/shell-state.d.ts +2 -0
- package/dist/shell-state.js +26 -1
- package/dist/shell-state.js.map +1 -1
- package/dist/statusline-model.d.ts +10 -2
- package/dist/statusline-model.js +106 -30
- package/dist/statusline-model.js.map +1 -1
- package/dist/task-workflow.d.ts +6 -9
- package/dist/task-workflow.js +37 -84
- package/dist/task-workflow.js.map +1 -1
- package/dist/tasks.d.ts +6 -33
- package/dist/tasks.js +46 -88
- package/dist/tasks.js.map +1 -1
- package/dist/team.d.ts +29 -0
- package/dist/team.js +40 -0
- package/dist/team.js.map +1 -1
- package/dist/threads.d.ts +6 -35
- package/dist/threads.js +89 -98
- package/dist/threads.js.map +1 -1
- package/dist/tmux/inbox-popup.js +37 -15
- package/dist/tmux/inbox-popup.js.map +1 -1
- package/dist/tmux/runtime-manager.d.ts +3 -0
- package/dist/tmux/runtime-manager.js +21 -4
- package/dist/tmux/runtime-manager.js.map +1 -1
- package/dist/tmux/statusline.js +49 -9
- package/dist/tmux/statusline.js.map +1 -1
- package/dist/tmux/window-open.js +1 -2
- package/dist/tmux/window-open.js.map +1 -1
- package/dist/tool-output-watchers.d.ts +0 -18
- package/dist/tool-output-watchers.js +0 -322
- package/dist/tool-output-watchers.js.map +1 -1
- package/dist/tui/screens/dashboard-renderers.js +37 -25
- package/dist/tui/screens/dashboard-renderers.js.map +1 -1
- package/dist/tui/screens/overlay-renderers.d.ts +2 -0
- package/dist/tui/screens/overlay-renderers.js +37 -1
- package/dist/tui/screens/overlay-renderers.js.map +1 -1
- package/dist/tui/screens/subscreen-renderers.js +7 -0
- package/dist/tui/screens/subscreen-renderers.js.map +1 -1
- package/dist/worktree.js +17 -0
- package/dist/worktree.js.map +1 -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 +31 -15
- 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/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/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/orchestration-dispatcher.d.ts +0 -25
- package/dist/orchestration-dispatcher.js +0 -59
- package/dist/orchestration-dispatcher.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/task-dispatcher.d.ts +0 -64
- package/dist/task-dispatcher.js +0 -213
- package/dist/task-dispatcher.js.map +0 -1
package/dist/metadata-server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
import { getDashboardClientUiStatePath, getProjectId, getProjectStateDir } from "./paths.js";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { getDashboardClientUiStatePath, getPlansDir, getProjectId, getProjectStateDir } from "./paths.js";
|
|
6
6
|
import { updateSessionMetadata, clearSessionLogs, saveMetadataEndpoint, loadMetadataState, } from "./metadata-store.js";
|
|
7
|
+
import { contextualizeAlertInput, mergeDisplayContext, metadataDisplayContext, } from "./alert-display.js";
|
|
7
8
|
import { notifyAlert } from "./notify.js";
|
|
8
9
|
import { clearNotifications, listNotifications, markNotificationsRead, unreadNotificationCount, } from "./notifications.js";
|
|
9
10
|
import { updateNotificationContext } from "./notification-context.js";
|
|
@@ -11,84 +12,60 @@ import { AgentTracker } from "./agent-tracker.js";
|
|
|
11
12
|
import { createThread, listThreadSummaries, markThreadSeen, readMessages, readThread, setThreadStatus, } from "./threads.js";
|
|
12
13
|
import { sendDirectMessage, sendThreadMessage } from "./orchestration.js";
|
|
13
14
|
import { acceptHandoff, approveReview, acceptTask, assignTask, blockTask, completeHandoff, completeTask, reopenTask, requestTaskChanges, sendHandoff, } from "./orchestration-actions.js";
|
|
15
|
+
import { readAllTasks, readTask } from "./tasks.js";
|
|
14
16
|
import { buildWorkflowEntries } from "./workflow.js";
|
|
15
17
|
import { markLastUsed } from "./last-used.js";
|
|
16
18
|
import { formatRelativeRecency } from "./recency.js";
|
|
17
|
-
import { getAttachment, getAttachmentContent
|
|
19
|
+
import { getAttachment, getAttachmentContent } from "./attachment-store.js";
|
|
18
20
|
import { ProjectEventBus } from "./project-events.js";
|
|
19
21
|
import { getProjectServiceManifest } from "./project-service-manifest.js";
|
|
20
22
|
import { applyShellStateTransition } from "./shell-state.js";
|
|
23
|
+
import { isTeammateSession, selectDirectTeammates } from "./team.js";
|
|
21
24
|
import { listSwitchableAgentItems, resolveAttentionAgent, resolveNextAgent, resolvePrevAgent, serializeFastControlItem, } from "./fast-control.js";
|
|
22
25
|
import { TmuxRuntimeManager } from "./tmux/runtime-manager.js";
|
|
23
26
|
import { openTargetForClient } from "./tmux/window-open.js";
|
|
24
27
|
import { getDashboardCommandSpec } from "./dashboard/command-spec.js";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
import { createRuntimeExchangeStore, } from "./runtime-core/exchange-store.js";
|
|
29
|
+
import { listTopologySessionStates } from "./runtime-core/topology-sessions.js";
|
|
30
|
+
const LIBRARY_DOC_ALLOWLIST = [
|
|
31
|
+
{ path: "AGENTS.md", kind: "instructions", title: "AGENTS.md" },
|
|
32
|
+
{ path: "CLAUDE.md", kind: "adapter", title: "CLAUDE.md" },
|
|
33
|
+
{ path: "CODEX.md", kind: "adapter", title: "CODEX.md" },
|
|
34
|
+
{ path: "README.md", kind: "project", title: "README.md" },
|
|
35
|
+
];
|
|
36
|
+
function isLibraryPathExposed(path) {
|
|
37
|
+
const normalized = path.replaceAll("\\", "/").toLowerCase();
|
|
38
|
+
return !normalized.startsWith(".aimux/") && !normalized.endsWith("config.json");
|
|
28
39
|
}
|
|
29
|
-
function
|
|
30
|
-
return {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return undefined;
|
|
59
|
-
const label = context?.label?.trim() || context?.command?.trim() || compactSessionId(sessionId);
|
|
60
|
-
const worktree = context ? displayWorktreeLabel(context) : undefined;
|
|
61
|
-
return worktree ? `${label} @ ${worktree}` : label;
|
|
62
|
-
}
|
|
63
|
-
function sessionAlertTitle(kind, sessionId, fallback, context) {
|
|
64
|
-
const title = fallback?.trim();
|
|
65
|
-
const subject = sessionAlertSubject(sessionId, context);
|
|
66
|
-
if (!subject)
|
|
67
|
-
return title || "aimux";
|
|
68
|
-
if (kind === "needs_input")
|
|
69
|
-
return `${subject} needs input`;
|
|
70
|
-
if (kind === "blocked") {
|
|
71
|
-
if (!title || (sessionId && title === `${sessionId} is blocked`))
|
|
72
|
-
return `${subject} is blocked`;
|
|
73
|
-
return title;
|
|
74
|
-
}
|
|
75
|
-
if (kind === "task_failed") {
|
|
76
|
-
if (!title || (sessionId && title === `${sessionId} errored`))
|
|
77
|
-
return `${subject} errored`;
|
|
78
|
-
return title;
|
|
79
|
-
}
|
|
80
|
-
if (kind === "task_done") {
|
|
81
|
-
if (!title || (sessionId && title === `${sessionId} finished`))
|
|
82
|
-
return `${subject} finished`;
|
|
83
|
-
return title;
|
|
84
|
-
}
|
|
85
|
-
if (!title)
|
|
86
|
-
return subject;
|
|
87
|
-
if (title.includes(subject))
|
|
88
|
-
return title;
|
|
89
|
-
if (sessionId && title.includes(sessionId))
|
|
90
|
-
return title.replace(sessionId, subject);
|
|
91
|
-
return `${subject}: ${title}`;
|
|
40
|
+
function listLibraryDocuments(projectRoot = process.cwd()) {
|
|
41
|
+
return LIBRARY_DOC_ALLOWLIST.flatMap((entry) => {
|
|
42
|
+
if (!isLibraryPathExposed(entry.path))
|
|
43
|
+
return [];
|
|
44
|
+
try {
|
|
45
|
+
const fullPath = join(projectRoot, entry.path);
|
|
46
|
+
if (!existsSync(fullPath))
|
|
47
|
+
return [];
|
|
48
|
+
const stat = statSync(fullPath);
|
|
49
|
+
if (!stat.isFile())
|
|
50
|
+
return [];
|
|
51
|
+
const content = readFileSync(fullPath, "utf8");
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
id: entry.path,
|
|
55
|
+
title: entry.title,
|
|
56
|
+
path: entry.path,
|
|
57
|
+
kind: entry.kind,
|
|
58
|
+
size: stat.size,
|
|
59
|
+
updatedAt: stat.mtime.toISOString(),
|
|
60
|
+
content: content.slice(0, 40_000),
|
|
61
|
+
truncated: content.length > 40_000,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
92
69
|
}
|
|
93
70
|
function dashboardClientKeyFromSession(sessionName) {
|
|
94
71
|
return sessionName.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -136,6 +113,7 @@ function markActiveWindowFocused(tmux, projectRoot, currentClientSession, curren
|
|
|
136
113
|
return false;
|
|
137
114
|
updateNotificationContext("tui", {
|
|
138
115
|
focused: true,
|
|
116
|
+
screen: match.metadata.kind === "service" ? "service" : "agent",
|
|
139
117
|
sessionId: match.metadata.sessionId,
|
|
140
118
|
panelOpen: false,
|
|
141
119
|
});
|
|
@@ -162,6 +140,17 @@ function desiredPort() {
|
|
|
162
140
|
const hash = createHash("sha1").update(getProjectId()).digest("hex").slice(0, 6);
|
|
163
141
|
return 43000 + (parseInt(hash, 16) % 10000);
|
|
164
142
|
}
|
|
143
|
+
// Plan paths join this directly into `${sessionId}.md`, so restrict to a
|
|
144
|
+
// conservative charset (no whitespace, no separators, no traversal) and
|
|
145
|
+
// cap length so we don't produce surprising filenames.
|
|
146
|
+
const SESSION_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
147
|
+
function validateSessionId(raw) {
|
|
148
|
+
if (!SESSION_ID_PATTERN.test(raw))
|
|
149
|
+
return { ok: false };
|
|
150
|
+
if (raw.includes(".."))
|
|
151
|
+
return { ok: false };
|
|
152
|
+
return { ok: true, value: raw };
|
|
153
|
+
}
|
|
165
154
|
async function readJson(req) {
|
|
166
155
|
const chunks = [];
|
|
167
156
|
for await (const chunk of req) {
|
|
@@ -192,6 +181,153 @@ function sendSseEvent(res, event, data) {
|
|
|
192
181
|
res.write(`event: ${event}\n`);
|
|
193
182
|
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
194
183
|
}
|
|
184
|
+
function topologyDesktopSessionList(statuses) {
|
|
185
|
+
return listTopologySessionStates({ statuses }).map((session) => ({
|
|
186
|
+
...session,
|
|
187
|
+
status: session.status ?? "offline",
|
|
188
|
+
team: session.team,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
function firstLine(value) {
|
|
192
|
+
return (value
|
|
193
|
+
.split(/\r?\n/)
|
|
194
|
+
.map((line) => line.trim())
|
|
195
|
+
.find(Boolean) ?? "");
|
|
196
|
+
}
|
|
197
|
+
function teammateTaskDescription(body) {
|
|
198
|
+
const explicitTitle = typeof body.title === "string" ? body.title.trim() : "";
|
|
199
|
+
if (explicitTitle)
|
|
200
|
+
return explicitTitle;
|
|
201
|
+
const explicitDescription = typeof body.description === "string" ? body.description.trim() : "";
|
|
202
|
+
if (explicitDescription)
|
|
203
|
+
return explicitDescription;
|
|
204
|
+
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
205
|
+
const bodyText = typeof body.body === "string" ? body.body.trim() : "";
|
|
206
|
+
const text = bodyText || prompt;
|
|
207
|
+
const line = firstLine(text);
|
|
208
|
+
return line ? line.slice(0, 120) : "Teammate task";
|
|
209
|
+
}
|
|
210
|
+
function teammateTaskPrompt(body) {
|
|
211
|
+
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
212
|
+
if (prompt)
|
|
213
|
+
return prompt;
|
|
214
|
+
const text = typeof body.body === "string" ? body.body.trim() : "";
|
|
215
|
+
return text || undefined;
|
|
216
|
+
}
|
|
217
|
+
function optionalString(value) {
|
|
218
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
219
|
+
}
|
|
220
|
+
function optionalStringOrFirst(value) {
|
|
221
|
+
if (typeof value === "string")
|
|
222
|
+
return optionalString(value);
|
|
223
|
+
if (!Array.isArray(value))
|
|
224
|
+
return undefined;
|
|
225
|
+
return value.map(optionalString).find(Boolean);
|
|
226
|
+
}
|
|
227
|
+
function runtimeInboxEntries(input = {}) {
|
|
228
|
+
const exchange = createRuntimeExchangeStore().read();
|
|
229
|
+
const threadById = new Map(exchange.threads.map((thread) => [thread.id, thread]));
|
|
230
|
+
const taskById = new Map(exchange.tasks.map((task) => [task.id, task]));
|
|
231
|
+
const latestMessageByThread = new Map();
|
|
232
|
+
for (const message of exchange.messages) {
|
|
233
|
+
const existing = latestMessageByThread.get(message.threadId);
|
|
234
|
+
if (!existing || existing.ts < message.ts)
|
|
235
|
+
latestMessageByThread.set(message.threadId, message);
|
|
236
|
+
}
|
|
237
|
+
const entries = exchange.inbox
|
|
238
|
+
.filter((entry) => !input.participantId || entry.participantId === input.participantId)
|
|
239
|
+
.filter((entry) => input.includeNotifications || threadById.get(entry.subjectId)?.tags?.includes("notification") !== true)
|
|
240
|
+
.filter((entry) => input.includeDone || entry.state !== "done")
|
|
241
|
+
.filter((entry) => !input.unreadOnly || entry.state !== "done")
|
|
242
|
+
.map((entry) => runtimeInboxEntry(entry, threadById, taskById, latestMessageByThread))
|
|
243
|
+
.filter((entry) => Boolean(entry));
|
|
244
|
+
return entries.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
|
|
245
|
+
}
|
|
246
|
+
function runtimeInboxEntry(entry, threadById, taskById, latestMessageByThread) {
|
|
247
|
+
if (entry.subjectKind === "thread" || entry.subjectKind === "handoff" || entry.subjectKind === "message") {
|
|
248
|
+
const thread = threadById.get(entry.subjectId);
|
|
249
|
+
if (!thread)
|
|
250
|
+
return undefined;
|
|
251
|
+
const message = latestMessageByThread.get(thread.id);
|
|
252
|
+
return {
|
|
253
|
+
id: entry.id,
|
|
254
|
+
subjectKind: entry.subjectKind,
|
|
255
|
+
subjectId: entry.subjectId,
|
|
256
|
+
participantId: entry.participantId,
|
|
257
|
+
sessionId: entry.participantId,
|
|
258
|
+
title: thread.title,
|
|
259
|
+
subtitle: `${thread.kind} · ${thread.status}`,
|
|
260
|
+
body: message?.body ?? thread.title,
|
|
261
|
+
createdAt: entry.updatedAt,
|
|
262
|
+
unread: entry.state !== "done",
|
|
263
|
+
state: entry.state,
|
|
264
|
+
urgency: entry.urgency,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const task = taskById.get(entry.subjectId);
|
|
268
|
+
if (!task)
|
|
269
|
+
return undefined;
|
|
270
|
+
return {
|
|
271
|
+
id: entry.id,
|
|
272
|
+
subjectKind: entry.subjectKind,
|
|
273
|
+
subjectId: entry.subjectId,
|
|
274
|
+
participantId: entry.participantId,
|
|
275
|
+
sessionId: entry.participantId,
|
|
276
|
+
title: task.description,
|
|
277
|
+
subtitle: `${task.type ?? "task"} · ${task.status}`,
|
|
278
|
+
body: task.result ?? task.error ?? task.prompt,
|
|
279
|
+
createdAt: entry.updatedAt,
|
|
280
|
+
unread: entry.state !== "done",
|
|
281
|
+
state: entry.state,
|
|
282
|
+
urgency: entry.urgency,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function markRuntimeInboxRead(input = {}) {
|
|
286
|
+
const store = createRuntimeExchangeStore();
|
|
287
|
+
const exchange = store.read();
|
|
288
|
+
const threadById = new Map(exchange.threads.map((thread) => [thread.id, thread]));
|
|
289
|
+
const entries = store
|
|
290
|
+
.read()
|
|
291
|
+
.inbox.filter((entry) => !input.id || entry.id === input.id)
|
|
292
|
+
.filter((entry) => !input.participantId || entry.participantId === input.participantId)
|
|
293
|
+
.filter((entry) => input.includeNotifications || threadById.get(entry.subjectId)?.tags?.includes("notification") !== true);
|
|
294
|
+
let updated = 0;
|
|
295
|
+
for (const entry of entries) {
|
|
296
|
+
if (entry.state !== "done")
|
|
297
|
+
updated += 1;
|
|
298
|
+
if (entry.subjectKind === "thread" || entry.subjectKind === "handoff" || entry.subjectKind === "message") {
|
|
299
|
+
markThreadSeen(entry.subjectId, entry.participantId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
store.update((exchange) => ({
|
|
303
|
+
...exchange,
|
|
304
|
+
inbox: exchange.inbox.map((entry) => (!input.id || entry.id === input.id) &&
|
|
305
|
+
(!input.participantId || entry.participantId === input.participantId) &&
|
|
306
|
+
(input.includeNotifications || threadById.get(entry.subjectId)?.tags?.includes("notification") !== true)
|
|
307
|
+
? { ...entry, state: "done" }
|
|
308
|
+
: entry),
|
|
309
|
+
}));
|
|
310
|
+
return updated;
|
|
311
|
+
}
|
|
312
|
+
function teammateApiRecord(session) {
|
|
313
|
+
return {
|
|
314
|
+
id: session.id,
|
|
315
|
+
sessionId: session.id,
|
|
316
|
+
tool: session.command,
|
|
317
|
+
command: session.command,
|
|
318
|
+
label: session.team?.label ?? session.label,
|
|
319
|
+
role: session.team?.role,
|
|
320
|
+
status: session.status,
|
|
321
|
+
worktreePath: session.worktreePath,
|
|
322
|
+
headline: session.headline,
|
|
323
|
+
preview: session.preview,
|
|
324
|
+
createdAt: session.createdAt,
|
|
325
|
+
lastUsedAt: session.lastUsedAt,
|
|
326
|
+
pending: session.pending,
|
|
327
|
+
pendingAction: session.pendingAction,
|
|
328
|
+
team: session.team,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
195
331
|
export class MetadataServer {
|
|
196
332
|
options;
|
|
197
333
|
server = null;
|
|
@@ -241,6 +377,57 @@ export class MetadataServer {
|
|
|
241
377
|
notifyChange() {
|
|
242
378
|
this.options.onChange?.();
|
|
243
379
|
}
|
|
380
|
+
resolveDirectTeammates(parentSessionId) {
|
|
381
|
+
if (!parentSessionId.trim()) {
|
|
382
|
+
return { ok: false, status: 400, error: "parentSessionId is required" };
|
|
383
|
+
}
|
|
384
|
+
const topologySessions = topologyDesktopSessionList(["running", "idle", "offline"]);
|
|
385
|
+
const sessions = topologySessions.filter((session) => !isTeammateSession(session));
|
|
386
|
+
const teammates = topologySessions.filter(isTeammateSession);
|
|
387
|
+
const parent = [...sessions, ...teammates].find((session) => session.id === parentSessionId);
|
|
388
|
+
if (!parent) {
|
|
389
|
+
return { ok: false, status: 404, error: `parent agent "${parentSessionId}" not found` };
|
|
390
|
+
}
|
|
391
|
+
if (isTeammateSession(parent)) {
|
|
392
|
+
return { ok: false, status: 400, error: "teammate agents cannot create or delegate to nested teams" };
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
ok: true,
|
|
396
|
+
parent,
|
|
397
|
+
teammates: selectDirectTeammates(teammates, parentSessionId),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
resolveDirectTeammate(parentSessionId, teammateSessionId) {
|
|
401
|
+
if (!teammateSessionId.trim()) {
|
|
402
|
+
return { ok: false, status: 400, error: "teammateSessionId is required" };
|
|
403
|
+
}
|
|
404
|
+
const resolved = this.resolveDirectTeammates(parentSessionId);
|
|
405
|
+
if (!resolved.ok)
|
|
406
|
+
return resolved;
|
|
407
|
+
const teammate = resolved.teammates.find((session) => session.id === teammateSessionId);
|
|
408
|
+
if (!teammate) {
|
|
409
|
+
return {
|
|
410
|
+
ok: false,
|
|
411
|
+
status: 404,
|
|
412
|
+
error: `teammate "${teammateSessionId}" is not attached to parent "${parentSessionId}"`,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return { ok: true, parent: resolved.parent, teammate };
|
|
416
|
+
}
|
|
417
|
+
resolveDirectGraveyardTeammate(parentSessionId, teammateSessionId) {
|
|
418
|
+
const resolved = this.resolveDirectTeammates(parentSessionId);
|
|
419
|
+
if (!resolved.ok)
|
|
420
|
+
return resolved;
|
|
421
|
+
const teammate = selectDirectTeammates(topologyDesktopSessionList(["graveyard"]), resolved.parent.id).find((session) => session.id === teammateSessionId);
|
|
422
|
+
if (!teammate) {
|
|
423
|
+
return {
|
|
424
|
+
ok: false,
|
|
425
|
+
status: 404,
|
|
426
|
+
error: `graveyard teammate "${teammateSessionId}" is not attached to parent "${parentSessionId}"`,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
return { ok: true, parent: resolved.parent, teammate };
|
|
430
|
+
}
|
|
244
431
|
listen(port) {
|
|
245
432
|
return new Promise((resolve, reject) => {
|
|
246
433
|
if (!this.server)
|
|
@@ -258,11 +445,7 @@ export class MetadataServer {
|
|
|
258
445
|
}
|
|
259
446
|
emitAlert(input) {
|
|
260
447
|
const displayContext = this.resolveSessionAlertDisplayContext(input.sessionId, input.worktreePath);
|
|
261
|
-
this.eventBus.publishAlert(
|
|
262
|
-
...input,
|
|
263
|
-
title: sessionAlertTitle(input.kind, input.sessionId, input.title, displayContext),
|
|
264
|
-
worktreePath: input.worktreePath ?? displayContext?.worktreePath,
|
|
265
|
-
});
|
|
448
|
+
this.eventBus.publishAlert(contextualizeAlertInput(input, displayContext));
|
|
266
449
|
}
|
|
267
450
|
resolveSessionAlertDisplayContext(sessionId, worktreePath) {
|
|
268
451
|
if (!sessionId)
|
|
@@ -343,6 +526,14 @@ export class MetadataServer {
|
|
|
343
526
|
return [...new Set(fallbackRecipients.map((value) => value?.trim()).filter(Boolean))];
|
|
344
527
|
}
|
|
345
528
|
async handle(req, res) {
|
|
529
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
530
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
|
|
531
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
532
|
+
if (req.method === "OPTIONS") {
|
|
533
|
+
res.statusCode = 204;
|
|
534
|
+
res.end();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
346
537
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
347
538
|
if (req.method === "GET" && url.pathname === "/events") {
|
|
348
539
|
const sessionFilter = url.searchParams.get("sessionId")?.trim() || null;
|
|
@@ -452,6 +643,26 @@ export class MetadataServer {
|
|
|
452
643
|
});
|
|
453
644
|
return;
|
|
454
645
|
}
|
|
646
|
+
if (req.method === "GET" && url.pathname === "/library") {
|
|
647
|
+
send(res, 200, {
|
|
648
|
+
ok: true,
|
|
649
|
+
documents: listLibraryDocuments(),
|
|
650
|
+
});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (req.method === "GET" && url.pathname === "/inbox") {
|
|
654
|
+
const unreadOnly = url.searchParams.get("unread") === "1";
|
|
655
|
+
const participantId = url.searchParams.get("participant")?.trim() || undefined;
|
|
656
|
+
const includeDone = url.searchParams.get("includeDone") === "1";
|
|
657
|
+
const includeNotifications = url.searchParams.get("includeNotifications") === "1";
|
|
658
|
+
const inbox = runtimeInboxEntries({ unreadOnly, participantId, includeDone, includeNotifications });
|
|
659
|
+
send(res, 200, {
|
|
660
|
+
ok: true,
|
|
661
|
+
inbox,
|
|
662
|
+
unreadCount: inbox.filter((entry) => entry.unread).length,
|
|
663
|
+
});
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
455
666
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
456
667
|
send(res, 200, {
|
|
457
668
|
ok: true,
|
|
@@ -518,6 +729,15 @@ export class MetadataServer {
|
|
|
518
729
|
send(res, 200, buildWorkflowEntries(url.searchParams.get("participant") ?? "user"));
|
|
519
730
|
return;
|
|
520
731
|
}
|
|
732
|
+
if (req.method === "GET" && url.pathname === "/tasks") {
|
|
733
|
+
const sessionId = url.searchParams.get("session")?.trim();
|
|
734
|
+
const status = url.searchParams.get("status")?.trim();
|
|
735
|
+
const tasks = readAllTasks()
|
|
736
|
+
.filter((task) => !sessionId || task.assignedTo === sessionId || task.assignedBy === sessionId)
|
|
737
|
+
.filter((task) => !status || task.status === status);
|
|
738
|
+
send(res, 200, { ok: true, tasks });
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
521
741
|
if (req.method === "POST" && url.pathname === "/usage/mark") {
|
|
522
742
|
const body = (await readJson(req));
|
|
523
743
|
const itemId = body.itemId?.trim() || "";
|
|
@@ -635,7 +855,14 @@ export class MetadataServer {
|
|
|
635
855
|
return;
|
|
636
856
|
}
|
|
637
857
|
if (req.method === "GET" && url.pathname.startsWith("/threads/")) {
|
|
638
|
-
|
|
858
|
+
let threadId;
|
|
859
|
+
try {
|
|
860
|
+
threadId = decodeURIComponent(url.pathname.slice("/threads/".length));
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
send(res, 400, { ok: false, error: "invalid threadId" });
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
639
866
|
const thread = readThread(threadId);
|
|
640
867
|
if (!thread) {
|
|
641
868
|
send(res, 404, { ok: false, error: "thread not found" });
|
|
@@ -644,7 +871,92 @@ export class MetadataServer {
|
|
|
644
871
|
send(res, 200, { thread, messages: readMessages(threadId) });
|
|
645
872
|
return;
|
|
646
873
|
}
|
|
874
|
+
if (req.method === "GET" && url.pathname.startsWith("/tasks/")) {
|
|
875
|
+
let taskId;
|
|
876
|
+
try {
|
|
877
|
+
taskId = decodeURIComponent(url.pathname.slice("/tasks/".length));
|
|
878
|
+
}
|
|
879
|
+
catch {
|
|
880
|
+
send(res, 400, { ok: false, error: "invalid taskId" });
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const task = readTask(taskId);
|
|
884
|
+
if (!task) {
|
|
885
|
+
send(res, 404, { ok: false, error: "task not found" });
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const thread = task.threadId ? readThread(task.threadId) : undefined;
|
|
889
|
+
const messages = task.threadId ? readMessages(task.threadId) : [];
|
|
890
|
+
send(res, 200, { ok: true, task, thread, messages });
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
647
893
|
try {
|
|
894
|
+
if (req.method === "GET" && url.pathname.startsWith("/plans/")) {
|
|
895
|
+
let raw;
|
|
896
|
+
try {
|
|
897
|
+
raw = decodeURIComponent(url.pathname.slice("/plans/".length));
|
|
898
|
+
}
|
|
899
|
+
catch {
|
|
900
|
+
send(res, 400, { ok: false, error: "invalid sessionId" });
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const validation = validateSessionId(raw);
|
|
904
|
+
if (!validation.ok) {
|
|
905
|
+
send(res, 400, { ok: false, error: "invalid sessionId" });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const sessionId = validation.value;
|
|
909
|
+
const planPath = join(getPlansDir(), `${sessionId}.md`);
|
|
910
|
+
try {
|
|
911
|
+
const content = readFileSync(planPath, "utf8");
|
|
912
|
+
send(res, 200, { ok: true, sessionId, content });
|
|
913
|
+
}
|
|
914
|
+
catch (err) {
|
|
915
|
+
if (err?.code === "ENOENT") {
|
|
916
|
+
send(res, 404, { ok: false, error: "Plan not found" });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
send(res, 500, { ok: false, error: "Failed to read plan" });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (req.method === "PUT" && url.pathname.startsWith("/plans/")) {
|
|
925
|
+
let raw;
|
|
926
|
+
try {
|
|
927
|
+
raw = decodeURIComponent(url.pathname.slice("/plans/".length));
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
send(res, 400, { ok: false, error: "invalid sessionId" });
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const validation = validateSessionId(raw);
|
|
934
|
+
if (!validation.ok) {
|
|
935
|
+
send(res, 400, { ok: false, error: "invalid sessionId" });
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const sessionId = validation.value;
|
|
939
|
+
const body = (await readJson(req));
|
|
940
|
+
if (typeof body.content !== "string") {
|
|
941
|
+
send(res, 400, { ok: false, error: "content must be a string" });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const planPath = join(getPlansDir(), `${sessionId}.md`);
|
|
945
|
+
const dir = dirname(planPath);
|
|
946
|
+
const tmpPath = join(dir, `.${sessionId}.${randomUUID()}.tmp`);
|
|
947
|
+
try {
|
|
948
|
+
mkdirSync(dir, { recursive: true });
|
|
949
|
+
writeFileSync(tmpPath, body.content, "utf8");
|
|
950
|
+
renameSync(tmpPath, planPath);
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
rmSync(tmpPath, { force: true });
|
|
954
|
+
send(res, 500, { ok: false, error: "Failed to write plan" });
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
send(res, 200, { ok: true, sessionId });
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
648
960
|
if (req.method === "POST" && url.pathname === "/set-status") {
|
|
649
961
|
const body = (await readJson(req));
|
|
650
962
|
updateSessionMetadata(body.session, (current) => ({
|
|
@@ -728,7 +1040,7 @@ export class MetadataServer {
|
|
|
728
1040
|
const currentClientSession = body.currentClientSession?.trim() || url.searchParams.get("currentClientSession")?.trim() || undefined;
|
|
729
1041
|
const clientTty = body.clientTty?.trim() || url.searchParams.get("clientTty")?.trim() || undefined;
|
|
730
1042
|
const desktop = this.options.desktop.getState();
|
|
731
|
-
const session = (desktop.sessions ?? []).find((entry) => entry.id === sessionId);
|
|
1043
|
+
const session = [...(desktop.sessions ?? []), ...(desktop.teammates ?? [])].find((entry) => entry.id === sessionId);
|
|
732
1044
|
const service = (desktop.services ?? []).find((entry) => entry.id === sessionId);
|
|
733
1045
|
const tmux = new TmuxRuntimeManager();
|
|
734
1046
|
const openWindowId = (windowId, itemId) => {
|
|
@@ -989,7 +1301,7 @@ export class MetadataServer {
|
|
|
989
1301
|
this.emitAlert({
|
|
990
1302
|
kind: "notification",
|
|
991
1303
|
sessionId: body.session,
|
|
992
|
-
title:
|
|
1304
|
+
title: body.event.source ?? "notification",
|
|
993
1305
|
message: body.event.message || "Agent notification.",
|
|
994
1306
|
dedupeKey: body.event.message ? `notify:${body.session}:${body.event.message}` : undefined,
|
|
995
1307
|
cooldownMs: 15_000,
|
|
@@ -1092,7 +1404,7 @@ export class MetadataServer {
|
|
|
1092
1404
|
this.emitAlert({
|
|
1093
1405
|
kind,
|
|
1094
1406
|
sessionId,
|
|
1095
|
-
title:
|
|
1407
|
+
title: body.title ?? "",
|
|
1096
1408
|
message: [body.subtitle?.trim(), body.message?.trim() || body.title?.trim() || "aimux"]
|
|
1097
1409
|
.filter(Boolean)
|
|
1098
1410
|
.join(" — "),
|
|
@@ -1132,12 +1444,23 @@ export class MetadataServer {
|
|
|
1132
1444
|
send(res, 200, { ok: true, cleared });
|
|
1133
1445
|
return;
|
|
1134
1446
|
}
|
|
1447
|
+
if (req.method === "POST" && (url.pathname === "/inbox/read" || url.pathname === "/inbox/clear")) {
|
|
1448
|
+
const body = (await readJson(req));
|
|
1449
|
+
const updated = markRuntimeInboxRead({
|
|
1450
|
+
id: body.id?.trim() || undefined,
|
|
1451
|
+
participantId: body.participant?.trim() || body.sessionId?.trim() || undefined,
|
|
1452
|
+
includeNotifications: body.includeNotifications === true,
|
|
1453
|
+
});
|
|
1454
|
+
send(res, 200, { ok: true, updated });
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1135
1457
|
if (req.method === "POST" && url.pathname === "/shell-state") {
|
|
1136
1458
|
const body = (await readJson(req));
|
|
1137
1459
|
const result = applyShellStateTransition({
|
|
1138
1460
|
state: body.state,
|
|
1139
1461
|
sessionId: body.sessionId,
|
|
1140
1462
|
tool: body.tool,
|
|
1463
|
+
command: body.command,
|
|
1141
1464
|
tracker: this.tracker,
|
|
1142
1465
|
emitAlert: (input) => this.emitAlert(input),
|
|
1143
1466
|
});
|
|
@@ -1209,7 +1532,12 @@ export class MetadataServer {
|
|
|
1209
1532
|
}
|
|
1210
1533
|
if (req.method === "POST" && url.pathname === "/threads/mark-seen") {
|
|
1211
1534
|
const body = (await readJson(req));
|
|
1212
|
-
const
|
|
1535
|
+
const sessionId = (body.session ?? body.sessionId ?? "").trim();
|
|
1536
|
+
if (!sessionId) {
|
|
1537
|
+
send(res, 400, { ok: false, error: "session is required" });
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
const thread = markThreadSeen(body.threadId, sessionId);
|
|
1213
1541
|
if (!thread) {
|
|
1214
1542
|
send(res, 404, { ok: false, error: "thread not found" });
|
|
1215
1543
|
return;
|
|
@@ -1238,7 +1566,9 @@ export class MetadataServer {
|
|
|
1238
1566
|
? this.options.actions.sendHandoff(body)
|
|
1239
1567
|
: sendHandoff({
|
|
1240
1568
|
from: body.from?.trim() || "user",
|
|
1241
|
-
to: body.to
|
|
1569
|
+
to: body.to?.length
|
|
1570
|
+
? body.to
|
|
1571
|
+
: [body.assignee, body.tool].map(optionalString).filter((value) => Boolean(value)),
|
|
1242
1572
|
body: body.body,
|
|
1243
1573
|
title: body.title,
|
|
1244
1574
|
worktreePath: body.worktreePath,
|
|
@@ -1287,7 +1617,7 @@ export class MetadataServer {
|
|
|
1287
1617
|
const body = (await readJson(req));
|
|
1288
1618
|
const result = await assignTask({
|
|
1289
1619
|
from: body.from?.trim() || "user",
|
|
1290
|
-
to: body.to
|
|
1620
|
+
to: optionalStringOrFirst(body.to),
|
|
1291
1621
|
assignee: body.assignee?.trim(),
|
|
1292
1622
|
tool: body.tool?.trim(),
|
|
1293
1623
|
description: body.description,
|
|
@@ -1373,6 +1703,207 @@ export class MetadataServer {
|
|
|
1373
1703
|
send(res, 200, { ok: true, ...result });
|
|
1374
1704
|
return;
|
|
1375
1705
|
}
|
|
1706
|
+
if (req.method === "GET" && url.pathname === "/agents/teammates") {
|
|
1707
|
+
const parentSessionId = url.searchParams.get("parentSessionId")?.trim() ?? "";
|
|
1708
|
+
const result = this.resolveDirectTeammates(parentSessionId);
|
|
1709
|
+
if (!result.ok) {
|
|
1710
|
+
send(res, result.status, { ok: false, error: result.error });
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
send(res, 200, {
|
|
1714
|
+
ok: true,
|
|
1715
|
+
parentSessionId: result.parent.id,
|
|
1716
|
+
teammates: result.teammates.map(teammateApiRecord),
|
|
1717
|
+
});
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/create") {
|
|
1721
|
+
const body = (await readJson(req));
|
|
1722
|
+
const parentSessionId = body.parentSessionId?.trim() ?? "";
|
|
1723
|
+
if (!parentSessionId) {
|
|
1724
|
+
send(res, 400, { ok: false, error: "parentSessionId is required" });
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
body.parentSessionId = parentSessionId;
|
|
1728
|
+
if (this.options.desktop?.getState) {
|
|
1729
|
+
const resolved = this.resolveDirectTeammates(parentSessionId);
|
|
1730
|
+
if (!resolved.ok) {
|
|
1731
|
+
send(res, resolved.status, { ok: false, error: resolved.error });
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
const initialTaskPrompt = body.initialTask ? teammateTaskPrompt(body.initialTask) : undefined;
|
|
1736
|
+
if (body.initialTask && !initialTaskPrompt) {
|
|
1737
|
+
send(res, 400, { ok: false, error: "initialTask requires body or prompt" });
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
if (!this.options.lifecycle?.createTeammateAgent) {
|
|
1741
|
+
send(res, 501, { ok: false, error: "teammate creation not supported by this service" });
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
const result = await this.options.lifecycle.createTeammateAgent({
|
|
1745
|
+
parentSessionId: body.parentSessionId,
|
|
1746
|
+
role: body.role,
|
|
1747
|
+
label: body.label,
|
|
1748
|
+
tool: body.tool,
|
|
1749
|
+
sessionId: body.sessionId,
|
|
1750
|
+
worktreePath: body.worktreePath,
|
|
1751
|
+
open: body.open,
|
|
1752
|
+
extraArgs: body.extraArgs,
|
|
1753
|
+
order: body.order,
|
|
1754
|
+
});
|
|
1755
|
+
const taskResult = body.initialTask && initialTaskPrompt
|
|
1756
|
+
? await assignTask({
|
|
1757
|
+
from: result.parentSessionId,
|
|
1758
|
+
to: result.sessionId,
|
|
1759
|
+
description: teammateTaskDescription(body.initialTask),
|
|
1760
|
+
prompt: initialTaskPrompt,
|
|
1761
|
+
worktreePath: optionalString(body.initialTask.worktreePath) ?? optionalString(body.worktreePath),
|
|
1762
|
+
})
|
|
1763
|
+
: undefined;
|
|
1764
|
+
if (taskResult) {
|
|
1765
|
+
this.emitAssignedTaskAlert(taskResult);
|
|
1766
|
+
}
|
|
1767
|
+
this.options.onChange?.();
|
|
1768
|
+
send(res, 200, { ok: true, ...result, task: taskResult?.task, thread: taskResult?.thread });
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/tasks") {
|
|
1772
|
+
const body = (await readJson(req));
|
|
1773
|
+
const parentSessionId = body.parentSessionId?.trim() ?? "";
|
|
1774
|
+
const teammateSessionId = body.teammateSessionId?.trim() ?? "";
|
|
1775
|
+
if (!teammateSessionId) {
|
|
1776
|
+
send(res, 400, { ok: false, error: "teammateSessionId is required" });
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
const resolved = this.resolveDirectTeammates(parentSessionId);
|
|
1780
|
+
if (!resolved.ok) {
|
|
1781
|
+
send(res, resolved.status, { ok: false, error: resolved.error });
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
const teammate = resolved.teammates.find((session) => session.id === teammateSessionId);
|
|
1785
|
+
if (!teammate) {
|
|
1786
|
+
send(res, 404, {
|
|
1787
|
+
ok: false,
|
|
1788
|
+
error: `teammate "${teammateSessionId}" is not attached to parent "${parentSessionId}"`,
|
|
1789
|
+
});
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
const prompt = teammateTaskPrompt(body);
|
|
1793
|
+
if (!prompt) {
|
|
1794
|
+
send(res, 400, { ok: false, error: "teammate task requires body or prompt" });
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const result = await assignTask({
|
|
1798
|
+
from: resolved.parent.id,
|
|
1799
|
+
to: teammate.id,
|
|
1800
|
+
description: teammateTaskDescription(body),
|
|
1801
|
+
prompt,
|
|
1802
|
+
worktreePath: optionalString(body.worktreePath) ??
|
|
1803
|
+
optionalString(teammate.worktreePath) ??
|
|
1804
|
+
optionalString(resolved.parent.worktreePath),
|
|
1805
|
+
});
|
|
1806
|
+
this.emitAssignedTaskAlert(result);
|
|
1807
|
+
this.options.onChange?.();
|
|
1808
|
+
send(res, 200, { ok: true, parentSessionId: resolved.parent.id, teammateSessionId: teammate.id, ...result });
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/send") {
|
|
1812
|
+
send(res, 410, {
|
|
1813
|
+
ok: false,
|
|
1814
|
+
error: "raw teammate send has been removed; create durable teammate work with /agents/teammates/tasks",
|
|
1815
|
+
});
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/stop") {
|
|
1819
|
+
const body = (await readJson(req));
|
|
1820
|
+
const resolved = this.resolveDirectTeammate(body.parentSessionId?.trim() ?? "", body.teammateSessionId?.trim() ?? "");
|
|
1821
|
+
if (!resolved.ok) {
|
|
1822
|
+
send(res, resolved.status, { ok: false, error: resolved.error });
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if (!this.options.lifecycle?.stopAgent) {
|
|
1826
|
+
send(res, 501, { ok: false, error: "agent stop not supported by this service" });
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const result = await this.options.lifecycle.stopAgent({ sessionId: resolved.teammate.id });
|
|
1830
|
+
this.options.onChange?.();
|
|
1831
|
+
send(res, 200, {
|
|
1832
|
+
ok: true,
|
|
1833
|
+
parentSessionId: resolved.parent.id,
|
|
1834
|
+
teammateSessionId: resolved.teammate.id,
|
|
1835
|
+
...result,
|
|
1836
|
+
});
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/resume") {
|
|
1840
|
+
const body = (await readJson(req));
|
|
1841
|
+
const resolved = this.resolveDirectTeammate(body.parentSessionId?.trim() ?? "", body.teammateSessionId?.trim() ?? "");
|
|
1842
|
+
if (!resolved.ok) {
|
|
1843
|
+
send(res, resolved.status, { ok: false, error: resolved.error });
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
if (!this.options.desktop?.resumeAgent) {
|
|
1847
|
+
send(res, 501, { ok: false, error: "agent resume not supported by this service" });
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const result = await this.options.desktop.resumeAgent({
|
|
1851
|
+
sessionId: resolved.teammate.id,
|
|
1852
|
+
session: resolved.teammate,
|
|
1853
|
+
});
|
|
1854
|
+
this.options.onChange?.();
|
|
1855
|
+
send(res, 200, {
|
|
1856
|
+
ok: true,
|
|
1857
|
+
parentSessionId: resolved.parent.id,
|
|
1858
|
+
teammateSessionId: resolved.teammate.id,
|
|
1859
|
+
...result,
|
|
1860
|
+
});
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/kill") {
|
|
1864
|
+
const body = (await readJson(req));
|
|
1865
|
+
const resolved = this.resolveDirectTeammate(body.parentSessionId?.trim() ?? "", body.teammateSessionId?.trim() ?? "");
|
|
1866
|
+
if (!resolved.ok) {
|
|
1867
|
+
send(res, resolved.status, { ok: false, error: resolved.error });
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
if (!this.options.lifecycle?.killAgent) {
|
|
1871
|
+
send(res, 501, { ok: false, error: "agent kill not supported by this service" });
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const result = await this.options.lifecycle.killAgent({
|
|
1875
|
+
sessionId: resolved.teammate.id,
|
|
1876
|
+
});
|
|
1877
|
+
this.options.onChange?.();
|
|
1878
|
+
send(res, 200, {
|
|
1879
|
+
ok: true,
|
|
1880
|
+
parentSessionId: resolved.parent.id,
|
|
1881
|
+
teammateSessionId: resolved.teammate.id,
|
|
1882
|
+
...result,
|
|
1883
|
+
});
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
if (req.method === "POST" && url.pathname === "/agents/teammates/resurrect") {
|
|
1887
|
+
const body = (await readJson(req));
|
|
1888
|
+
const resolved = this.resolveDirectGraveyardTeammate(body.parentSessionId?.trim() ?? "", body.teammateSessionId?.trim() ?? "");
|
|
1889
|
+
if (!resolved.ok) {
|
|
1890
|
+
send(res, resolved.status, { ok: false, error: resolved.error });
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
if (!this.options.desktop?.resurrectGraveyard) {
|
|
1894
|
+
send(res, 501, { ok: false, error: "agent graveyard resurrection not supported by this service" });
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const result = await this.options.desktop.resurrectGraveyard({ sessionId: resolved.teammate.id });
|
|
1898
|
+
this.options.onChange?.();
|
|
1899
|
+
send(res, 200, {
|
|
1900
|
+
ok: true,
|
|
1901
|
+
parentSessionId: resolved.parent.id,
|
|
1902
|
+
teammateSessionId: resolved.teammate.id,
|
|
1903
|
+
...result,
|
|
1904
|
+
});
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1376
1907
|
if (req.method === "POST" && url.pathname === "/agents/fork") {
|
|
1377
1908
|
const body = (await readJson(req));
|
|
1378
1909
|
if (!this.options.lifecycle?.forkAgent) {
|
|
@@ -1401,7 +1932,7 @@ export class MetadataServer {
|
|
|
1401
1932
|
send(res, 501, { ok: false, error: "agent resume not supported by this service" });
|
|
1402
1933
|
return;
|
|
1403
1934
|
}
|
|
1404
|
-
const result = await this.options.desktop.resumeAgent(body);
|
|
1935
|
+
const result = await this.options.desktop.resumeAgent({ sessionId: body.sessionId });
|
|
1405
1936
|
this.options.onChange?.();
|
|
1406
1937
|
send(res, 200, { ok: true, ...result });
|
|
1407
1938
|
return;
|
|
@@ -1424,10 +1955,6 @@ export class MetadataServer {
|
|
|
1424
1955
|
return;
|
|
1425
1956
|
}
|
|
1426
1957
|
const result = await this.options.lifecycle.renameAgent(body);
|
|
1427
|
-
updateSessionMetadata(body.sessionId, (current) => ({
|
|
1428
|
-
...current,
|
|
1429
|
-
label: body.label?.trim() || undefined,
|
|
1430
|
-
}));
|
|
1431
1958
|
this.options.onChange?.();
|
|
1432
1959
|
send(res, 200, { ok: true, ...result });
|
|
1433
1960
|
return;
|
|
@@ -1449,45 +1976,30 @@ export class MetadataServer {
|
|
|
1449
1976
|
send(res, 501, { ok: false, error: "agent kill not supported by this service" });
|
|
1450
1977
|
return;
|
|
1451
1978
|
}
|
|
1452
|
-
const result = await this.options.lifecycle.killAgent(body);
|
|
1979
|
+
const result = await this.options.lifecycle.killAgent({ sessionId: body.sessionId });
|
|
1453
1980
|
this.options.onChange?.();
|
|
1454
1981
|
send(res, 200, { ok: true, ...result });
|
|
1455
1982
|
return;
|
|
1456
1983
|
}
|
|
1457
1984
|
if (req.method === "POST" && url.pathname === "/agents/input") {
|
|
1458
1985
|
const body = (await readJson(req));
|
|
1459
|
-
|
|
1460
|
-
|
|
1986
|
+
const sessionId = body.sessionId?.trim() ?? "";
|
|
1987
|
+
if (!sessionId) {
|
|
1988
|
+
send(res, 400, { ok: false, error: "sessionId is required" });
|
|
1461
1989
|
return;
|
|
1462
1990
|
}
|
|
1463
|
-
const
|
|
1464
|
-
if (
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
catch {
|
|
1474
|
-
// History update is best-effort; the write result should still succeed.
|
|
1475
|
-
}
|
|
1991
|
+
const text = typeof body.text === "string" ? body.text : "";
|
|
1992
|
+
if (!text.trim()) {
|
|
1993
|
+
send(res, 400, { ok: false, error: "text is required" });
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
if (!this.options.lifecycle?.sendAgentInput) {
|
|
1997
|
+
send(res, 501, { ok: false, error: "agent input not supported by this service" });
|
|
1998
|
+
return;
|
|
1476
1999
|
}
|
|
2000
|
+
const result = await this.options.lifecycle.sendAgentInput({ sessionId, text });
|
|
1477
2001
|
this.options.onChange?.();
|
|
1478
|
-
send(res, 200, { ok:
|
|
1479
|
-
return;
|
|
1480
|
-
}
|
|
1481
|
-
if (req.method === "POST" && url.pathname === "/attachments") {
|
|
1482
|
-
const body = (await readJson(req));
|
|
1483
|
-
const attachment = body.path?.trim()
|
|
1484
|
-
? ingestAttachmentFromPath(body.path)
|
|
1485
|
-
: ingestAttachmentFromBase64({
|
|
1486
|
-
filename: body.filename,
|
|
1487
|
-
mimeType: body.mimeType,
|
|
1488
|
-
contentBase64: String(body.contentBase64 ?? ""),
|
|
1489
|
-
});
|
|
1490
|
-
send(res, 200, { ok: true, attachment });
|
|
2002
|
+
send(res, 200, { ok: true, ...result });
|
|
1491
2003
|
return;
|
|
1492
2004
|
}
|
|
1493
2005
|
const attachmentContentMatch = url.pathname.match(/^\/attachments\/([^/]+)\/content$/);
|
|
@@ -1531,23 +2043,7 @@ export class MetadataServer {
|
|
|
1531
2043
|
return;
|
|
1532
2044
|
}
|
|
1533
2045
|
if (req.method === "GET" && url.pathname === "/agents/history") {
|
|
1534
|
-
|
|
1535
|
-
const lastNRaw = url.searchParams.get("lastN");
|
|
1536
|
-
if (!sessionId) {
|
|
1537
|
-
send(res, 400, { ok: false, error: "sessionId is required" });
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
if (!this.options.lifecycle?.readAgentHistory) {
|
|
1541
|
-
send(res, 501, { ok: false, error: "agent history not supported by this service" });
|
|
1542
|
-
return;
|
|
1543
|
-
}
|
|
1544
|
-
const lastN = lastNRaw === null || lastNRaw.trim() === "" ? undefined : Number.parseInt(lastNRaw, 10);
|
|
1545
|
-
if (lastNRaw !== null && Number.isNaN(lastN)) {
|
|
1546
|
-
send(res, 400, { ok: false, error: "lastN must be an integer" });
|
|
1547
|
-
return;
|
|
1548
|
-
}
|
|
1549
|
-
const result = await this.options.lifecycle.readAgentHistory({ sessionId, lastN });
|
|
1550
|
-
send(res, 200, { ok: true, ...result });
|
|
2046
|
+
send(res, 410, { ok: false, error: "agent message history requires the runtime core replacement" });
|
|
1551
2047
|
return;
|
|
1552
2048
|
}
|
|
1553
2049
|
if (req.method === "POST" && url.pathname === "/worktrees/create") {
|
|
@@ -1663,11 +2159,16 @@ export class MetadataServer {
|
|
|
1663
2159
|
}
|
|
1664
2160
|
if (req.method === "POST" && url.pathname === "/graveyard/resurrect") {
|
|
1665
2161
|
const body = (await readJson(req));
|
|
2162
|
+
const sessionId = (body.sessionId ?? body.id ?? "").trim();
|
|
2163
|
+
if (!sessionId) {
|
|
2164
|
+
send(res, 400, { ok: false, error: "sessionId is required" });
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
1666
2167
|
if (!this.options.desktop?.resurrectGraveyard) {
|
|
1667
|
-
send(res, 501, { ok: false, error: "graveyard
|
|
2168
|
+
send(res, 501, { ok: false, error: "agent graveyard resurrection not supported by this service" });
|
|
1668
2169
|
return;
|
|
1669
2170
|
}
|
|
1670
|
-
const result = await this.options.desktop.resurrectGraveyard(
|
|
2171
|
+
const result = await this.options.desktop.resurrectGraveyard({ sessionId });
|
|
1671
2172
|
this.options.onChange?.();
|
|
1672
2173
|
send(res, 200, { ok: true, ...result });
|
|
1673
2174
|
return;
|
|
@@ -1675,7 +2176,7 @@ export class MetadataServer {
|
|
|
1675
2176
|
if (req.method === "POST" && url.pathname === "/graveyard/worktrees/resurrect") {
|
|
1676
2177
|
const body = (await readJson(req));
|
|
1677
2178
|
if (!this.options.desktop?.resurrectGraveyardWorktree) {
|
|
1678
|
-
send(res, 501, { ok: false, error: "worktree graveyard
|
|
2179
|
+
send(res, 501, { ok: false, error: "worktree graveyard resurrection not supported by this service" });
|
|
1679
2180
|
return;
|
|
1680
2181
|
}
|
|
1681
2182
|
const result = await this.options.desktop.resurrectGraveyardWorktree(body);
|