@getpaseo/server 0.1.96 → 0.1.97-beta.2
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/dist/server/{utils/executable.d.ts → executable-resolution/executable-resolution.d.ts} +2 -2
- package/dist/server/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
- package/dist/server/executable-resolution/windows.d.ts +18 -0
- package/dist/server/executable-resolution/windows.js +62 -0
- package/dist/server/server/agent/agent-loading.js +4 -1
- package/dist/server/server/agent/agent-manager.d.ts +10 -2
- package/dist/server/server/agent/agent-manager.js +34 -46
- package/dist/server/server/agent/agent-projections.js +3 -0
- package/dist/server/server/agent/agent-prompt.js +19 -1
- package/dist/server/server/agent/agent-response-loop.js +2 -4
- package/dist/server/server/agent/agent-storage.d.ts +18 -19
- package/dist/server/server/agent/agent-storage.js +6 -23
- package/dist/server/server/agent/create-agent/create.d.ts +2 -12
- package/dist/server/server/agent/create-agent/create.js +28 -30
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +4 -2
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +31 -22
- package/dist/server/server/agent/create-agent-title.d.ts +2 -0
- package/dist/server/server/agent/create-agent-title.js +5 -0
- package/dist/server/server/agent/import-sessions.d.ts +1 -10
- package/dist/server/server/agent/import-sessions.js +1 -53
- package/dist/server/server/agent/lifecycle-command.js +5 -4
- package/dist/server/server/agent/mcp-server.d.ts +8 -5
- package/dist/server/server/agent/mcp-server.js +41 -14
- package/dist/server/server/agent/mcp-shared.d.ts +6 -3
- package/dist/server/server/agent/mcp-shared.js +3 -0
- package/dist/server/server/agent/provider-launch-config.js +1 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts +5 -0
- package/dist/server/server/agent/providers/acp-agent.js +31 -26
- package/dist/server/server/agent/providers/claude/agent.js +45 -6
- package/dist/server/server/agent/providers/codex-app-server-agent.js +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +1 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +0 -7
- package/dist/server/server/agent/providers/cursor-acp-agent.js +0 -78
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +73 -1
- package/dist/server/server/agent/providers/opencode/server-manager.js +1 -1
- package/dist/server/server/agent/structured-generation-providers.js +45 -1
- package/dist/server/server/agent-attention-policy.d.ts +12 -3
- package/dist/server/server/agent-attention-policy.js +15 -3
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +7 -6
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +21 -16
- package/dist/server/server/bootstrap.d.ts +3 -0
- package/dist/server/server/bootstrap.js +125 -64
- package/dist/server/server/config.js +1 -0
- package/dist/server/server/daemon-config-store.js +1 -0
- package/dist/server/server/exports.d.ts +1 -1
- package/dist/server/server/exports.js +1 -1
- package/dist/server/server/loop-service.d.ts +24 -24
- package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +60 -0
- package/dist/server/server/paseo-worktree-service.d.ts +9 -0
- package/dist/server/server/paseo-worktree-service.js +74 -12
- package/dist/server/server/path-utils.d.ts +1 -0
- package/dist/server/server/path-utils.js +6 -1
- package/dist/server/server/persisted-config.d.ts +7 -0
- package/dist/server/server/persisted-config.js +1 -0
- package/dist/server/server/persistence-hooks.d.ts +1 -0
- package/dist/server/server/persistence-hooks.js +13 -5
- package/dist/server/server/resolve-workspace-id-for-path.d.ts +3 -0
- package/dist/server/server/resolve-workspace-id-for-path.js +41 -0
- package/dist/server/server/script-proxy.d.ts +1 -1
- package/dist/server/server/script-proxy.js +1 -1
- package/dist/server/server/service-proxy.js +1 -1
- package/dist/server/server/session.d.ts +33 -6
- package/dist/server/server/session.js +691 -202
- package/dist/server/server/websocket-server.d.ts +5 -0
- package/dist/server/server/websocket-server.js +137 -3
- package/dist/server/server/workspace-archive-service.d.ts +60 -3
- package/dist/server/server/workspace-archive-service.js +217 -4
- package/dist/server/server/workspace-directory.d.ts +20 -2
- package/dist/server/server/workspace-directory.js +148 -70
- package/dist/server/server/workspace-git-service.js +21 -21
- package/dist/server/server/workspace-reconciliation-service.d.ts +1 -1
- package/dist/server/server/workspace-reconciliation-service.js +21 -22
- package/dist/server/server/workspace-registry-bootstrap.js +23 -10
- package/dist/server/server/workspace-registry-model.d.ts +3 -3
- package/dist/server/server/workspace-registry-model.js +9 -10
- package/dist/server/server/workspace-registry.d.ts +17 -4
- package/dist/server/server/workspace-registry.js +27 -0
- package/dist/server/server/worktree/commands.d.ts +7 -5
- package/dist/server/server/worktree/commands.js +38 -18
- package/dist/server/server/worktree-bootstrap.d.ts +1 -0
- package/dist/server/server/worktree-bootstrap.js +4 -1
- package/dist/server/server/worktree-branch-name-generator.d.ts +5 -1
- package/dist/server/server/worktree-branch-name-generator.js +29 -7
- package/dist/server/server/worktree-session.d.ts +4 -5
- package/dist/server/server/worktree-session.js +9 -3
- package/dist/server/services/github-service.js +1 -1
- package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
- package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
- package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
- package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
- package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
- package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
- package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
- package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
- package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
- package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
- package/dist/server/terminal/terminal-manager-factory.d.ts +4 -1
- package/dist/server/terminal/terminal-manager-factory.js +2 -2
- package/dist/server/terminal/terminal-manager.d.ts +33 -2
- package/dist/server/terminal/terminal-manager.js +144 -18
- package/dist/server/terminal/terminal-output-coalescer.d.ts +4 -0
- package/dist/server/terminal/terminal-output-coalescer.js +18 -0
- package/dist/server/terminal/terminal-restore.d.ts +1 -0
- package/dist/server/terminal/terminal-restore.js +6 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +4 -2
- package/dist/server/terminal/terminal-session-controller.js +65 -24
- package/dist/server/terminal/terminal-worker-process.js +146 -63
- package/dist/server/terminal/terminal-worker-protocol.d.ts +19 -14
- package/dist/server/terminal/terminal.d.ts +42 -0
- package/dist/server/terminal/terminal.js +235 -16
- package/dist/server/terminal/worker-terminal-manager.d.ts +1 -0
- package/dist/server/terminal/worker-terminal-manager.js +220 -36
- package/dist/server/utils/build-metadata-prompt.d.ts +8 -3
- package/dist/server/utils/build-metadata-prompt.js +10 -9
- package/dist/server/utils/github-remote.js +1 -1
- package/dist/server/utils/tree-kill.d.ts +2 -2
- package/dist/src/{utils/executable.js → executable-resolution/executable-resolution.js} +16 -14
- package/dist/src/executable-resolution/windows.js +62 -0
- package/dist/src/server/agent/provider-launch-config.js +1 -1
- package/dist/src/server/persisted-config.js +1 -0
- package/package.json +10 -5
- package/dist/server/server/agent/agent-metadata-generator.d.ts +0 -36
- package/dist/server/server/agent/agent-metadata-generator.js +0 -112
- package/dist/server/server/paseo-worktree-archive-service.d.ts +0 -41
- package/dist/server/server/paseo-worktree-archive-service.js +0 -144
- package/dist/server/utils/wrap-user-instructions.d.ts +0 -2
- package/dist/server/utils/wrap-user-instructions.js +0 -13
|
@@ -2,31 +2,32 @@ import type { Logger } from "pino";
|
|
|
2
2
|
import type { AgentManager } from "../agent/agent-manager.js";
|
|
3
3
|
import type { AgentStorage } from "../agent/agent-storage.js";
|
|
4
4
|
import type { DaemonConfigStore } from "../daemon-config-store.js";
|
|
5
|
-
import {
|
|
6
|
-
import { isSameOrDescendantPath } from "../path-utils.js";
|
|
5
|
+
import { archiveByScope, type ActiveWorkspaceRef, killTerminalsForWorkspace, resolveWorkspaceIdAtPath } from "../workspace-archive-service.js";
|
|
7
6
|
import type { WorkspaceGitRuntimeSnapshot, WorkspaceGitServiceImpl } from "../workspace-git-service.js";
|
|
8
7
|
import type { GitHubService } from "../../services/github-service.js";
|
|
9
8
|
import type { TerminalManager } from "../../terminal/terminal-manager.js";
|
|
10
9
|
import { isPaseoOwnedWorktreeCwd } from "../../utils/worktree.js";
|
|
11
10
|
export interface AutoArchiveArchiveOptions {
|
|
12
11
|
paseoHome: string;
|
|
13
|
-
|
|
12
|
+
paseoWorktreesBaseRoot?: string;
|
|
14
13
|
daemonConfigStore: DaemonConfigStore;
|
|
15
14
|
workspaceGitService: WorkspaceGitServiceImpl;
|
|
16
15
|
github: GitHubService;
|
|
17
16
|
agentManager: AgentManager;
|
|
18
17
|
agentStorage: AgentStorage;
|
|
19
18
|
terminalManager: TerminalManager;
|
|
19
|
+
findWorkspaceIdForCwd: (cwd: string) => Promise<string | null>;
|
|
20
|
+
listActiveWorkspaces: () => Promise<ActiveWorkspaceRef[]>;
|
|
20
21
|
archiveWorkspaceRecord: (workspaceId: string) => Promise<void>;
|
|
21
22
|
markWorkspaceArchiving: (workspaceIds: Iterable<string>, archivingAt: string) => void;
|
|
22
23
|
clearWorkspaceArchiving: (workspaceIds: Iterable<string>) => void;
|
|
23
24
|
emitWorkspaceUpdatesForWorkspaceIds: (workspaceIds: Iterable<string>) => Promise<void>;
|
|
24
25
|
}
|
|
25
26
|
export interface ArchiveIfSafeDependencies {
|
|
26
|
-
|
|
27
|
+
archiveByScope: typeof archiveByScope;
|
|
28
|
+
resolveWorkspaceIdAtPath: typeof resolveWorkspaceIdAtPath;
|
|
27
29
|
isPaseoOwnedWorktreeCwd: typeof isPaseoOwnedWorktreeCwd;
|
|
28
|
-
|
|
29
|
-
isPathWithinRoot: typeof isSameOrDescendantPath;
|
|
30
|
+
killTerminalsForWorkspace: typeof killTerminalsForWorkspace;
|
|
30
31
|
}
|
|
31
32
|
export declare function archiveIfSafe(input: {
|
|
32
33
|
cwd: string;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isSameOrDescendantPath } from "../path-utils.js";
|
|
1
|
+
import { archiveByScope, killTerminalsForWorkspace, resolveWorkspaceIdAtPath, } from "../workspace-archive-service.js";
|
|
3
2
|
import { isPaseoOwnedWorktreeCwd } from "../../utils/worktree.js";
|
|
4
3
|
const defaultDependencies = {
|
|
5
|
-
|
|
4
|
+
archiveByScope,
|
|
5
|
+
resolveWorkspaceIdAtPath,
|
|
6
6
|
isPaseoOwnedWorktreeCwd,
|
|
7
|
-
|
|
8
|
-
isPathWithinRoot: isSameOrDescendantPath,
|
|
7
|
+
killTerminalsForWorkspace,
|
|
9
8
|
};
|
|
10
9
|
export async function archiveIfSafe(input) {
|
|
11
10
|
const { cwd, pullRequest, inFlight, options, log } = input;
|
|
@@ -42,36 +41,42 @@ export async function archiveIfSafe(input) {
|
|
|
42
41
|
}
|
|
43
42
|
const ownership = await deps.isPaseoOwnedWorktreeCwd(cwd, {
|
|
44
43
|
paseoHome: options.paseoHome,
|
|
45
|
-
worktreesRoot: options.
|
|
44
|
+
worktreesRoot: options.paseoWorktreesBaseRoot,
|
|
46
45
|
});
|
|
47
46
|
if (!ownership.allowed) {
|
|
48
47
|
return;
|
|
49
48
|
}
|
|
50
49
|
try {
|
|
51
|
-
await deps.
|
|
50
|
+
const workspaceId = await deps.resolveWorkspaceIdAtPath({
|
|
51
|
+
findWorkspaceIdForCwd: options.findWorkspaceIdForCwd,
|
|
52
|
+
listActiveWorkspaces: options.listActiveWorkspaces,
|
|
53
|
+
}, cwd);
|
|
54
|
+
if (!workspaceId) {
|
|
55
|
+
log.warn({ cwd }, "Auto-archive could not resolve a workspace for cwd; skipping");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await deps.archiveByScope({
|
|
52
59
|
paseoHome: options.paseoHome,
|
|
53
|
-
|
|
60
|
+
paseoWorktreesBaseRoot: options.paseoWorktreesBaseRoot,
|
|
54
61
|
github: options.github,
|
|
55
62
|
workspaceGitService: options.workspaceGitService,
|
|
56
63
|
agentManager: options.agentManager,
|
|
57
64
|
agentStorage: options.agentStorage,
|
|
65
|
+
findWorkspaceIdForCwd: options.findWorkspaceIdForCwd,
|
|
66
|
+
listActiveWorkspaces: options.listActiveWorkspaces,
|
|
58
67
|
archiveWorkspaceRecord: options.archiveWorkspaceRecord,
|
|
59
68
|
emitWorkspaceUpdatesForWorkspaceIds: options.emitWorkspaceUpdatesForWorkspaceIds,
|
|
60
69
|
markWorkspaceArchiving: options.markWorkspaceArchiving,
|
|
61
70
|
clearWorkspaceArchiving: options.clearWorkspaceArchiving,
|
|
62
|
-
|
|
63
|
-
killTerminalsUnderPath: (rootPath) => deps.killTerminalsUnderPath({
|
|
71
|
+
killTerminalsForWorkspace: (workspaceIdToKill) => deps.killTerminalsForWorkspace({
|
|
64
72
|
terminalManager: options.terminalManager,
|
|
65
|
-
isPathWithinRoot: deps.isPathWithinRoot,
|
|
66
|
-
killTrackedTerminal: () => { },
|
|
67
73
|
sessionLogger: log,
|
|
68
|
-
},
|
|
74
|
+
}, workspaceIdToKill),
|
|
69
75
|
sessionLogger: log,
|
|
70
76
|
}, {
|
|
71
|
-
|
|
77
|
+
scope: { kind: "workspace", workspaceId },
|
|
72
78
|
repoRoot: ownership.repoRoot ?? null,
|
|
73
|
-
|
|
74
|
-
worktreesBaseRoot: options.worktreesRoot,
|
|
79
|
+
paseoWorktreesBaseRoot: options.paseoWorktreesBaseRoot,
|
|
75
80
|
requestId: "auto-archive-on-merge",
|
|
76
81
|
});
|
|
77
82
|
log.info({ cwd }, "Auto-archived worktree after PR merge");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import express from "express";
|
|
1
2
|
import type { Logger } from "pino";
|
|
2
3
|
export type ListenTarget = {
|
|
3
4
|
type: "tcp";
|
|
@@ -26,6 +27,7 @@ import { type ServiceProxySubsystem } from "./service-proxy.js";
|
|
|
26
27
|
import { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
|
27
28
|
import { type HostnamesConfig } from "./hostnames.js";
|
|
28
29
|
import { type DaemonAuthConfig } from "./auth.js";
|
|
30
|
+
export declare function createTerminalActivityRouteHandler(terminalManager: TerminalManager): express.RequestHandler;
|
|
29
31
|
export type PaseoOpenAIConfig = OpenAiSpeechProviderConfig;
|
|
30
32
|
export type PaseoLocalSpeechConfig = LocalSpeechProviderConfig;
|
|
31
33
|
export interface PaseoSpeechSttLanguages {
|
|
@@ -57,6 +59,7 @@ export interface PaseoDaemonConfig {
|
|
|
57
59
|
mcpEnabled?: boolean;
|
|
58
60
|
mcpInjectIntoAgents?: boolean;
|
|
59
61
|
autoArchiveAfterMerge?: boolean;
|
|
62
|
+
enableTerminalAgentHooks?: boolean;
|
|
60
63
|
appendSystemPrompt?: string;
|
|
61
64
|
terminalProfiles?: TerminalProfile[];
|
|
62
65
|
staticDir: string;
|
|
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { hostname as getHostname } from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
|
-
import {
|
|
9
|
+
import { z } from "zod";
|
|
10
10
|
import { createBranchChangeRouteHandler } from "./script-route-branch-handler.js";
|
|
11
11
|
function resolveBoundListenTarget(listenTarget, httpServer) {
|
|
12
12
|
if (listenTarget.type !== "tcp") {
|
|
@@ -72,7 +72,7 @@ function formatListenTarget(listenTarget) {
|
|
|
72
72
|
}
|
|
73
73
|
import { VoiceAssistantWebSocketServer } from "./websocket-server.js";
|
|
74
74
|
import { createGitHubService } from "../services/github-service.js";
|
|
75
|
-
import { createPaseoWorktree as createRegisteredPaseoWorktree } from "./paseo-worktree-service.js";
|
|
75
|
+
import { createPaseoWorktree as createRegisteredPaseoWorktree, createLocalCheckoutWorkspace, } from "./paseo-worktree-service.js";
|
|
76
76
|
import { createPaseoWorktreeWorkflow } from "./worktree-session.js";
|
|
77
77
|
import { DownloadTokenStore } from "./file-download/token-store.js";
|
|
78
78
|
import { createSpeechService } from "./speech/speech-runtime.js";
|
|
@@ -90,10 +90,12 @@ import { LoopService } from "./loop-service.js";
|
|
|
90
90
|
import { ScheduleService } from "./schedule/service.js";
|
|
91
91
|
import { DaemonConfigStore } from "./daemon-config-store.js";
|
|
92
92
|
import { WorkspaceGitServiceImpl } from "./workspace-git-service.js";
|
|
93
|
-
import {
|
|
93
|
+
import { resolveWorkspaceIdForPath } from "./resolve-workspace-id-for-path.js";
|
|
94
|
+
import { archivePersistedWorkspaceRecord, } from "./workspace-archive-service.js";
|
|
94
95
|
import { setupAutoArchiveOnMerge } from "./auto-archive-on-merge/index.js";
|
|
95
96
|
import { wrapSessionMessage } from "./messages.js";
|
|
96
97
|
import { createConfiguredTerminalManager } from "../terminal/terminal-manager-factory.js";
|
|
98
|
+
import { applyTerminalAgentHookSetting } from "../terminal/agent-hooks/terminal-agent-hook-setting.js";
|
|
97
99
|
import { createConnectionOfferV2, encodeOfferToFragmentUrl } from "./connection-offer.js";
|
|
98
100
|
import { loadOrCreateDaemonKeyPair } from "./daemon-keypair.js";
|
|
99
101
|
import { startRelayTransport } from "./relay-transport.js";
|
|
@@ -127,6 +129,56 @@ function createAgentMcpBaseUrl(listenTarget) {
|
|
|
127
129
|
const host = resolveAgentMcpClientHost(listenTarget.host);
|
|
128
130
|
return new URL("/mcp/agents", `http://${formatHostForHttpUrl(host)}:${listenTarget.port}`).toString();
|
|
129
131
|
}
|
|
132
|
+
function createTerminalActivityUrl(listenTarget) {
|
|
133
|
+
if (!listenTarget || listenTarget.type !== "tcp") {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const host = resolveAgentMcpClientHost(listenTarget.host);
|
|
137
|
+
return new URL("/api/terminal-activity", `http://${formatHostForHttpUrl(host)}:${listenTarget.port}`).toString();
|
|
138
|
+
}
|
|
139
|
+
const TerminalActivityReportSchema = z.object({
|
|
140
|
+
terminalId: z.string().min(1),
|
|
141
|
+
token: z.string().min(1),
|
|
142
|
+
state: z.enum(["running", "idle", "needs-input"]),
|
|
143
|
+
});
|
|
144
|
+
const TERMINAL_ACTIVITY_STATE_MAP = {
|
|
145
|
+
running: "working",
|
|
146
|
+
idle: "idle",
|
|
147
|
+
"needs-input": "attention",
|
|
148
|
+
};
|
|
149
|
+
const LOOPBACK_REMOTE_ADDRESSES = new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
|
|
150
|
+
function isLoopbackRemoteAddress(remoteAddress) {
|
|
151
|
+
return remoteAddress !== undefined && LOOPBACK_REMOTE_ADDRESSES.has(remoteAddress);
|
|
152
|
+
}
|
|
153
|
+
export function createTerminalActivityRouteHandler(terminalManager) {
|
|
154
|
+
return async (req, res) => {
|
|
155
|
+
if (!isLoopbackRemoteAddress(req.socket.remoteAddress)) {
|
|
156
|
+
res.status(403).json({ error: "Forbidden" });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const parsed = TerminalActivityReportSchema.safeParse(req.body);
|
|
160
|
+
if (!parsed.success) {
|
|
161
|
+
res.status(400).json({ error: "Invalid terminal activity report" });
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const validation = terminalManager.validateTerminalActivityToken(parsed.data.terminalId, parsed.data.token);
|
|
165
|
+
if (validation !== "valid") {
|
|
166
|
+
res.status(403).json({ error: "Forbidden" });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const updated = await terminalManager.setTerminalActivity(parsed.data.terminalId, TERMINAL_ACTIVITY_STATE_MAP[parsed.data.state]);
|
|
171
|
+
if (!updated) {
|
|
172
|
+
res.status(403).json({ error: "Forbidden" });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
res.status(204).end();
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
res.status(500).json({ error: "Failed to update terminal activity" });
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
130
182
|
function summarizeAgentMcpDebugMessage(body) {
|
|
131
183
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
132
184
|
return {
|
|
@@ -173,6 +225,7 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
173
225
|
providers: config.metadataGeneration?.providers ?? [],
|
|
174
226
|
},
|
|
175
227
|
autoArchiveAfterMerge: config.autoArchiveAfterMerge ?? false,
|
|
228
|
+
enableTerminalAgentHooks: config.enableTerminalAgentHooks ?? false,
|
|
176
229
|
appendSystemPrompt: config.appendSystemPrompt ?? "",
|
|
177
230
|
...(config.terminalProfiles !== undefined
|
|
178
231
|
? { terminalProfiles: config.terminalProfiles }
|
|
@@ -198,6 +251,10 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
198
251
|
const app = express();
|
|
199
252
|
let boundListenTarget = null;
|
|
200
253
|
let workspaceRegistry = null;
|
|
254
|
+
const terminalManager = createConfiguredTerminalManager({
|
|
255
|
+
getTerminalActivityUrl: () => createTerminalActivityUrl(boundListenTarget),
|
|
256
|
+
});
|
|
257
|
+
applyTerminalAgentHookSetting({ store: daemonConfigStore, logger });
|
|
201
258
|
const serviceProxyPublicBaseUrl = config.serviceProxy?.publicBaseUrl
|
|
202
259
|
? config.serviceProxy.publicBaseUrl
|
|
203
260
|
: null;
|
|
@@ -274,13 +331,14 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
274
331
|
}
|
|
275
332
|
next();
|
|
276
333
|
});
|
|
334
|
+
// Local, harmless, and token-gated; deliberately skips daemon auth.
|
|
335
|
+
app.post("/api/terminal-activity", express.json(), createTerminalActivityRouteHandler(terminalManager));
|
|
277
336
|
app.use(createRequireBearerMiddleware(config.auth, (context) => {
|
|
278
337
|
logger.warn(context, "Rejected HTTP request with invalid daemon password");
|
|
279
338
|
}));
|
|
339
|
+
app.use(express.json());
|
|
280
340
|
// Serve static files from public directory
|
|
281
341
|
app.use("/public", express.static(staticDir));
|
|
282
|
-
// Middleware
|
|
283
|
-
app.use(express.json());
|
|
284
342
|
// Health check endpoint
|
|
285
343
|
app.get("/api/health", (_req, res) => {
|
|
286
344
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
@@ -361,7 +419,6 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
361
419
|
paseoHome: config.paseoHome,
|
|
362
420
|
logger,
|
|
363
421
|
});
|
|
364
|
-
const terminalManager = createConfiguredTerminalManager();
|
|
365
422
|
const github = createGitHubService();
|
|
366
423
|
const workspaceGitService = new WorkspaceGitServiceImpl({
|
|
367
424
|
logger,
|
|
@@ -468,9 +525,27 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
468
525
|
await archivePersistedWorkspaceRecord({
|
|
469
526
|
workspaceId,
|
|
470
527
|
workspaceRegistry,
|
|
471
|
-
projectRegistry,
|
|
472
528
|
});
|
|
473
529
|
};
|
|
530
|
+
// external path→workspace adapter, not ownership: archive-by-path requests that
|
|
531
|
+
// arrive with a worktree path and no workspaceId (old clients / CLI).
|
|
532
|
+
const findWorkspaceIdForCwdExternal = async (cwd) => {
|
|
533
|
+
return resolveWorkspaceIdForPath(cwd, await workspaceRegistry.list());
|
|
534
|
+
};
|
|
535
|
+
const ensureWorkspaceForCreateExternal = async (cwd) => {
|
|
536
|
+
const workspace = await createLocalCheckoutWorkspace({ cwd }, { projectRegistry, workspaceRegistry, workspaceGitService });
|
|
537
|
+
return workspace.workspaceId;
|
|
538
|
+
};
|
|
539
|
+
const listActiveWorkspacesExternal = async () => {
|
|
540
|
+
const workspaces = await workspaceRegistry.list();
|
|
541
|
+
return workspaces
|
|
542
|
+
.filter((workspace) => !workspace.archivedAt)
|
|
543
|
+
.map((workspace) => ({
|
|
544
|
+
workspaceId: workspace.workspaceId,
|
|
545
|
+
cwd: workspace.cwd,
|
|
546
|
+
kind: workspace.kind,
|
|
547
|
+
}));
|
|
548
|
+
};
|
|
474
549
|
const markWorkspaceArchivingExternal = (workspaceIds, archivingAt) => {
|
|
475
550
|
const workspaceIdList = Array.from(workspaceIds);
|
|
476
551
|
for (const session of wsServer?.listActiveSessions() ?? []) {
|
|
@@ -492,7 +567,7 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
492
567
|
};
|
|
493
568
|
setupAutoArchiveOnMerge({
|
|
494
569
|
paseoHome: config.paseoHome,
|
|
495
|
-
|
|
570
|
+
paseoWorktreesBaseRoot: config.worktreesRoot,
|
|
496
571
|
daemonConfigStore,
|
|
497
572
|
workspaceGitService,
|
|
498
573
|
github,
|
|
@@ -500,6 +575,8 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
500
575
|
agentStorage,
|
|
501
576
|
terminalManager,
|
|
502
577
|
logger,
|
|
578
|
+
findWorkspaceIdForCwd: findWorkspaceIdForCwdExternal,
|
|
579
|
+
listActiveWorkspaces: listActiveWorkspacesExternal,
|
|
503
580
|
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
|
|
504
581
|
markWorkspaceArchiving: markWorkspaceArchivingExternal,
|
|
505
582
|
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
|
|
@@ -509,8 +586,7 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
509
586
|
let agentMcpBaseUrl = null;
|
|
510
587
|
if (mcpEnabled) {
|
|
511
588
|
const agentMcpRoute = "/mcp/agents";
|
|
512
|
-
const
|
|
513
|
-
const createAgentMcpTransport = async (callerAgentId) => {
|
|
589
|
+
const createAgentMcpSession = async (callerAgentId) => {
|
|
514
590
|
const agentMcpServer = await createAgentMcpServer({
|
|
515
591
|
agentManager,
|
|
516
592
|
agentStorage,
|
|
@@ -520,10 +596,13 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
520
596
|
providerSnapshotManager,
|
|
521
597
|
github,
|
|
522
598
|
workspaceGitService,
|
|
599
|
+
findWorkspaceIdForCwd: findWorkspaceIdForCwdExternal,
|
|
600
|
+
listActiveWorkspaces: listActiveWorkspacesExternal,
|
|
523
601
|
archiveWorkspaceRecord: archiveWorkspaceRecordExternal,
|
|
524
602
|
emitWorkspaceUpdatesForWorkspaceIds: emitWorkspaceUpdatesExternal,
|
|
525
603
|
markWorkspaceArchiving: markWorkspaceArchivingExternal,
|
|
526
604
|
clearWorkspaceArchiving: clearWorkspaceArchivingExternal,
|
|
605
|
+
ensureWorkspaceForCreate: ensureWorkspaceForCreateExternal,
|
|
527
606
|
createPaseoWorktree: async (input, serviceOptions) => {
|
|
528
607
|
return createPaseoWorktreeWorkflow({
|
|
529
608
|
paseoHome: config.paseoHome,
|
|
@@ -546,11 +625,8 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
546
625
|
?.listActiveSessions()
|
|
547
626
|
.map((session) => session.warmWorkspaceGitDataForWorkspace(workspace)) ?? []);
|
|
548
627
|
},
|
|
549
|
-
|
|
550
|
-
await
|
|
551
|
-
?.listActiveSessions()
|
|
552
|
-
.map((session) => session.emitWorkspaceUpdatesForExternalCwds([cwd])) ?? []);
|
|
553
|
-
void emitOptions;
|
|
628
|
+
emitWorkspaceUpdateForWorkspaceId: async (workspaceId) => {
|
|
629
|
+
await emitWorkspaceUpdatesExternal([workspaceId]);
|
|
554
630
|
},
|
|
555
631
|
cacheWorkspaceSetupSnapshot: () => { },
|
|
556
632
|
emit: emitExternalSessionMessage,
|
|
@@ -573,32 +649,24 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
573
649
|
resolveCallerContext: (agentId) => wsServer?.resolveVoiceCallerContext(agentId) ?? null,
|
|
574
650
|
logger,
|
|
575
651
|
});
|
|
652
|
+
// Stateless mode: each HTTP request builds a fresh server + transport that is
|
|
653
|
+
// torn down when the response closes, so no per-session state is retained between
|
|
654
|
+
// requests. The agent control plane only lists and calls tools, neither of which
|
|
655
|
+
// needs cross-request state, so sessions would only pin memory for the life of the
|
|
656
|
+
// daemon (agents that exit without a clean DELETE never get reaped).
|
|
576
657
|
const transport = new StreamableHTTPServerTransport({
|
|
577
|
-
sessionIdGenerator:
|
|
578
|
-
onsessioninitialized: (sessionId) => {
|
|
579
|
-
agentMcpTransports.set(sessionId, transport);
|
|
580
|
-
logger.debug({ sessionId }, "Agent MCP session initialized");
|
|
581
|
-
},
|
|
582
|
-
onsessionclosed: (sessionId) => {
|
|
583
|
-
agentMcpTransports.delete(sessionId);
|
|
584
|
-
logger.debug({ sessionId }, "Agent MCP session closed");
|
|
585
|
-
},
|
|
658
|
+
sessionIdGenerator: undefined,
|
|
586
659
|
// NOTE: We enforce a Vite-like host allowlist at the app/websocket layer.
|
|
587
660
|
// StreamableHTTPServerTransport's built-in check requires exact Host header matches.
|
|
588
661
|
enableDnsRebindingProtection: false,
|
|
589
662
|
});
|
|
590
663
|
Object.assign(transport, {
|
|
591
|
-
onclose: () => {
|
|
592
|
-
if (transport.sessionId) {
|
|
593
|
-
agentMcpTransports.delete(transport.sessionId);
|
|
594
|
-
}
|
|
595
|
-
},
|
|
596
664
|
onerror: (err) => {
|
|
597
665
|
logger.error({ err }, "Agent MCP transport error");
|
|
598
666
|
},
|
|
599
667
|
});
|
|
600
668
|
await agentMcpServer.connect(transport);
|
|
601
|
-
return transport;
|
|
669
|
+
return { server: agentMcpServer, transport };
|
|
602
670
|
};
|
|
603
671
|
const runAgentMcpRequest = async (req, res) => {
|
|
604
672
|
// This route is exempt from the global daemon-password middleware, so it
|
|
@@ -623,41 +691,33 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
623
691
|
}, "Agent MCP request");
|
|
624
692
|
}
|
|
625
693
|
try {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
if (!isInitializeRequest(req.body)) {
|
|
641
|
-
res.status(400).json({
|
|
642
|
-
jsonrpc: "2.0",
|
|
643
|
-
error: {
|
|
644
|
-
code: -32000,
|
|
645
|
-
message: "Initialization request expected",
|
|
646
|
-
},
|
|
647
|
-
id: null,
|
|
648
|
-
});
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
const callerAgentIdRaw = req.query.callerAgentId;
|
|
652
|
-
let callerAgentId;
|
|
653
|
-
if (typeof callerAgentIdRaw === "string") {
|
|
654
|
-
callerAgentId = callerAgentIdRaw;
|
|
655
|
-
}
|
|
656
|
-
else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") {
|
|
657
|
-
callerAgentId = callerAgentIdRaw[0];
|
|
658
|
-
}
|
|
659
|
-
transport = await createAgentMcpTransport(callerAgentId);
|
|
694
|
+
// Stateless: GET (standalone SSE) and DELETE (session termination) have no
|
|
695
|
+
// meaning without sessions. The MCP client tolerates 405 on the GET stream
|
|
696
|
+
// and never issues a DELETE because it is never handed a session id.
|
|
697
|
+
if (req.method !== "POST") {
|
|
698
|
+
res.status(405).json({
|
|
699
|
+
jsonrpc: "2.0",
|
|
700
|
+
error: {
|
|
701
|
+
code: -32000,
|
|
702
|
+
message: "Method not allowed",
|
|
703
|
+
},
|
|
704
|
+
id: null,
|
|
705
|
+
});
|
|
706
|
+
return;
|
|
660
707
|
}
|
|
708
|
+
const callerAgentIdRaw = req.query.callerAgentId;
|
|
709
|
+
let callerAgentId;
|
|
710
|
+
if (typeof callerAgentIdRaw === "string") {
|
|
711
|
+
callerAgentId = callerAgentIdRaw;
|
|
712
|
+
}
|
|
713
|
+
else if (Array.isArray(callerAgentIdRaw) && typeof callerAgentIdRaw[0] === "string") {
|
|
714
|
+
callerAgentId = callerAgentIdRaw[0];
|
|
715
|
+
}
|
|
716
|
+
const { server, transport } = await createAgentMcpSession(callerAgentId);
|
|
717
|
+
res.on("close", () => {
|
|
718
|
+
void transport.close();
|
|
719
|
+
void server.close();
|
|
720
|
+
});
|
|
661
721
|
await transport.handleRequest(req, res, req.body);
|
|
662
722
|
}
|
|
663
723
|
catch (err) {
|
|
@@ -762,6 +822,7 @@ export async function createPaseoDaemon(config, rootLogger) {
|
|
|
762
822
|
}, projectRegistry, workspaceRegistry, chatService, loopService, scheduleService, checkoutDiffManager, serviceProxy, scriptRuntimeStore, handleBranchChange, () => (boundListenTarget?.type === "tcp" ? boundListenTarget.port : null), () => (boundListenTarget?.type === "tcp" ? boundListenTarget.host : null), (hostname) => scriptHealthMonitor.getHealthForHostname(hostname), workspaceGitService, github, config.pushNotificationSender, providerSnapshotManager, {
|
|
763
823
|
listen: formatListenTarget(boundListenTarget ?? listenTarget),
|
|
764
824
|
worktreesRoot: config.worktreesRoot,
|
|
825
|
+
appBaseUrl: config.appBaseUrl,
|
|
765
826
|
relay: {
|
|
766
827
|
enabled: relayEnabled,
|
|
767
828
|
endpoint: relayEndpoint,
|
|
@@ -229,6 +229,7 @@ export function loadConfig(paseoHome, options) {
|
|
|
229
229
|
mcpEnabled,
|
|
230
230
|
mcpInjectIntoAgents,
|
|
231
231
|
autoArchiveAfterMerge,
|
|
232
|
+
enableTerminalAgentHooks: persisted.daemon?.enableTerminalAgentHooks ?? false,
|
|
232
233
|
appendSystemPrompt,
|
|
233
234
|
terminalProfiles,
|
|
234
235
|
mcpDebug: env.MCP_DEBUG === "1",
|
|
@@ -146,6 +146,7 @@ function mergeMutableConfigIntoPersistedConfig(params) {
|
|
|
146
146
|
injectIntoAgents: mutable.mcp.injectIntoAgents,
|
|
147
147
|
},
|
|
148
148
|
autoArchiveAfterMerge: mutable.autoArchiveAfterMerge,
|
|
149
|
+
enableTerminalAgentHooks: mutable.enableTerminalAgentHooks,
|
|
149
150
|
appendSystemPrompt: mutable.appendSystemPrompt,
|
|
150
151
|
...(mutable.terminalProfiles !== undefined
|
|
151
152
|
? { terminalProfiles: mutable.terminalProfiles }
|
|
@@ -14,7 +14,7 @@ export { DirectTcpHostConnectionSchema, type DirectTcpHostConnection, type Norma
|
|
|
14
14
|
export { ensureLocalSpeechModels, listLocalSpeechModels, type LocalSpeechModelId, type LocalSttModelId, type LocalTtsModelId, } from "./speech/providers/local/models.js";
|
|
15
15
|
export { applySherpaLoaderEnv, resolveSherpaLoaderEnv, sherpaLoaderEnvKey, sherpaPlatformArch, sherpaPlatformPackageName, type SherpaLoaderEnvKey, type SherpaLoaderEnvResolution, } from "./speech/providers/local/sherpa/sherpa-runtime-env.js";
|
|
16
16
|
export { type ProviderOverride, type ProviderProfileModel, } from "./agent/provider-launch-config.js";
|
|
17
|
-
export { findExecutable } from "../
|
|
17
|
+
export { findExecutable } from "../executable-resolution/executable-resolution.js";
|
|
18
18
|
export { execCommand, spawnProcess } from "../utils/spawn.js";
|
|
19
19
|
export { AGENT_PROVIDER_DEFINITIONS, BUILTIN_PROVIDER_IDS, type AgentProviderDefinition, } from "@getpaseo/protocol/provider-manifest";
|
|
20
20
|
export type { AgentMode, AgentUsage, AgentCapabilityFlags, AgentPermissionRequest, AgentTimelineItem, ProviderSnapshotEntry, } from "./agent/agent-sdk-types.js";
|
|
@@ -14,7 +14,7 @@ export { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
|
|
|
14
14
|
export { DirectTcpHostConnectionSchema, } from "@getpaseo/protocol/host-connection-schema";
|
|
15
15
|
export { ensureLocalSpeechModels, listLocalSpeechModels, } from "./speech/providers/local/models.js";
|
|
16
16
|
export { applySherpaLoaderEnv, resolveSherpaLoaderEnv, sherpaLoaderEnvKey, sherpaPlatformArch, sherpaPlatformPackageName, } from "./speech/providers/local/sherpa/sherpa-runtime-env.js";
|
|
17
|
-
export { findExecutable } from "../
|
|
17
|
+
export { findExecutable } from "../executable-resolution/executable-resolution.js";
|
|
18
18
|
export { execCommand, spawnProcess } from "../utils/spawn.js";
|
|
19
19
|
// Provider manifest (source of truth for provider definitions)
|
|
20
20
|
export { AGENT_PROVIDER_DEFINITIONS, BUILTIN_PROVIDER_IDS, } from "@getpaseo/protocol/provider-manifest";
|