@getpaseo/server 0.1.59 → 0.1.60
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/scripts/dev-runner.js +26 -7
- package/dist/scripts/dev-runner.js.map +1 -1
- package/dist/server/client/daemon-client-runtime-metrics.d.ts +39 -0
- package/dist/server/client/daemon-client-runtime-metrics.d.ts.map +1 -0
- package/dist/server/client/daemon-client-runtime-metrics.js +173 -0
- package/dist/server/client/daemon-client-runtime-metrics.js.map +1 -0
- package/dist/server/client/daemon-client.d.ts +58 -9
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +151 -10
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +55 -48
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +541 -331
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts +3 -2
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +31 -16
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts +2 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +29 -1
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -5
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +76 -69
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +13 -6
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
- package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +54 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts.map +1 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store-types.js.map +1 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +32 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -0
- package/dist/server/server/agent/agent-timeline-store.js +245 -0
- package/dist/server/server/agent/agent-timeline-store.js.map +1 -0
- package/dist/server/server/agent/mcp-server.d.ts +12 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +276 -65
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/mcp-shared.d.ts +196 -152
- package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-shared.js +40 -42
- package/dist/server/server/agent/mcp-shared.js.map +1 -1
- package/dist/server/server/agent/model-resolver.d.ts.map +1 -1
- package/dist/server/server/agent/model-resolver.js +3 -1
- package/dist/server/server/agent/model-resolver.js.map +1 -1
- package/dist/server/server/agent/prompt-attachments.d.ts +6 -0
- package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -0
- package/dist/server/server/agent/prompt-attachments.js +31 -0
- package/dist/server/server/agent/prompt-attachments.js.map +1 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +12 -10
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +34 -0
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts +1 -0
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +22 -1
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts +5 -2
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +20 -9
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +17 -5
- package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +150 -61
- package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts +8 -4
- package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.js +73 -8
- package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
- package/dist/server/server/agent/providers/claude-agent.d.ts +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +8 -7
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +37 -4
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +61 -31
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +3 -2
- package/dist/server/server/agent/providers/copilot-acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +64 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +585 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +19 -4
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +227 -118
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts +69 -0
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js +1177 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +7 -4
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts +13 -13
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.js +20 -36
- package/dist/server/server/agent-attention-policy.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +6 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +113 -11
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/chat/chat-rpc-schemas.d.ts +44 -44
- package/dist/server/server/chat/chat-types.d.ts +6 -6
- package/dist/server/server/checkout-diff-manager.d.ts +0 -1
- package/dist/server/server/checkout-diff-manager.d.ts.map +1 -1
- package/dist/server/server/checkout-diff-manager.js +6 -4
- package/dist/server/server/checkout-diff-manager.js.map +1 -1
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +1 -0
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +2 -1
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/loop/rpc-schemas.d.ts +392 -392
- package/dist/server/server/loop-service.d.ts +52 -52
- package/dist/server/server/paseo-worktree-archive-service.d.ts +41 -0
- package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -0
- package/dist/server/server/paseo-worktree-archive-service.js +137 -0
- package/dist/server/server/paseo-worktree-archive-service.js.map +1 -0
- package/dist/server/server/paseo-worktree-service.d.ts +24 -0
- package/dist/server/server/paseo-worktree-service.d.ts.map +1 -0
- package/dist/server/server/paseo-worktree-service.js +94 -0
- package/dist/server/server/paseo-worktree-service.js.map +1 -0
- package/dist/server/server/path-utils.d.ts +1 -0
- package/dist/server/server/path-utils.d.ts.map +1 -1
- package/dist/server/server/path-utils.js +9 -0
- package/dist/server/server/path-utils.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +73 -73
- package/dist/server/server/persistence-hooks.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.js +3 -0
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +30 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +163 -0
- package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -0
- package/dist/server/server/schedule/rpc-schemas.d.ts +192 -192
- package/dist/server/server/schedule/service.d.ts +1 -1
- package/dist/server/server/schedule/service.d.ts.map +1 -1
- package/dist/server/server/schedule/types.d.ts +44 -44
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.d.ts.map +1 -0
- package/dist/server/server/script-health-monitor.js +158 -0
- package/dist/server/server/script-health-monitor.js.map +1 -0
- package/dist/server/server/script-proxy.d.ts +40 -0
- package/dist/server/server/script-proxy.d.ts.map +1 -0
- package/dist/server/server/script-proxy.js +245 -0
- package/dist/server/server/script-proxy.js.map +1 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.d.ts.map +1 -0
- package/dist/server/server/script-route-branch-handler.js +45 -0
- package/dist/server/server/script-route-branch-handler.js.map +1 -0
- package/dist/server/server/script-status-projection.d.ts +29 -0
- package/dist/server/server/script-status-projection.d.ts.map +1 -0
- package/dist/server/server/script-status-projection.js +133 -0
- package/dist/server/server/script-status-projection.js.map +1 -0
- package/dist/server/server/session.d.ts +77 -13
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +1290 -548
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +27 -3
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +112 -29
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-archive-service.d.ts +8 -0
- package/dist/server/server/workspace-archive-service.d.ts.map +1 -0
- package/dist/server/server/workspace-archive-service.js +17 -0
- package/dist/server/server/workspace-archive-service.js.map +1 -0
- package/dist/server/server/workspace-git-metadata.d.ts +24 -0
- package/dist/server/server/workspace-git-metadata.d.ts.map +1 -0
- package/dist/server/server/workspace-git-metadata.js +78 -0
- package/dist/server/server/workspace-git-metadata.js.map +1 -0
- package/dist/server/server/workspace-git-service.d.ts +104 -5
- package/dist/server/server/workspace-git-service.d.ts.map +1 -1
- package/dist/server/server/workspace-git-service.js +442 -56
- package/dist/server/server/workspace-git-service.js.map +1 -1
- package/dist/server/server/workspace-reconciliation-service.d.ts +54 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -0
- package/dist/server/server/workspace-reconciliation-service.js +176 -0
- package/dist/server/server/workspace-reconciliation-service.js.map +1 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
- package/dist/server/server/workspace-registry-bootstrap.js +4 -3
- package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
- package/dist/server/server/workspace-registry.d.ts +8 -8
- package/dist/server/server/workspace-registry.test-helpers.d.ts +37 -0
- package/dist/server/server/workspace-registry.test-helpers.d.ts.map +1 -0
- package/dist/server/server/workspace-registry.test-helpers.js +121 -0
- package/dist/server/server/workspace-registry.test-helpers.js.map +1 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts.map +1 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-script-runtime-store.js.map +1 -0
- package/dist/server/server/workspace-service-env.d.ts +17 -0
- package/dist/server/server/workspace-service-env.d.ts.map +1 -0
- package/dist/server/server/workspace-service-env.js +80 -0
- package/dist/server/server/workspace-service-env.js.map +1 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
- package/dist/server/server/workspace-service-port-registry.d.ts.map +1 -0
- package/dist/server/server/workspace-service-port-registry.js +59 -0
- package/dist/server/server/workspace-service-port-registry.js.map +1 -0
- package/dist/server/server/worktree-bootstrap.d.ts +55 -10
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +290 -112
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/server/worktree-core.d.ts +25 -0
- package/dist/server/server/worktree-core.d.ts.map +1 -0
- package/dist/server/server/worktree-core.js +75 -0
- package/dist/server/server/worktree-core.js.map +1 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.d.ts.map +1 -0
- package/dist/server/server/worktree-errors.js +31 -0
- package/dist/server/server/worktree-errors.js.map +1 -0
- package/dist/server/server/worktree-session.d.ts +56 -70
- package/dist/server/server/worktree-session.d.ts.map +1 -1
- package/dist/server/server/worktree-session.js +176 -251
- package/dist/server/server/worktree-session.js.map +1 -1
- package/dist/server/services/github-service.d.ts +225 -0
- package/dist/server/services/github-service.d.ts.map +1 -0
- package/dist/server/services/github-service.js +1381 -0
- package/dist/server/services/github-service.js.map +1 -0
- package/dist/server/shared/messages.d.ts +29408 -12268
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +391 -65
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/server/terminal/terminal-manager.d.ts +9 -0
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/terminal-manager.js +27 -0
- package/dist/server/terminal/terminal-manager.js.map +1 -1
- package/dist/server/terminal/terminal-output-coalescer.d.ts +30 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts.map +1 -0
- package/dist/server/terminal/terminal-output-coalescer.js +55 -0
- package/dist/server/terminal/terminal-output-coalescer.js.map +1 -0
- package/dist/server/terminal/terminal.d.ts +32 -1
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +397 -17
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +63 -10
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +321 -229
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.d.ts.map +1 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/promise-timeout.js.map +1 -0
- package/dist/server/utils/script-hostname.d.ts +8 -0
- package/dist/server/utils/script-hostname.d.ts.map +1 -0
- package/dist/server/utils/script-hostname.js +14 -0
- package/dist/server/utils/script-hostname.js.map +1 -0
- package/dist/server/utils/string-command-shell.d.ts +10 -0
- package/dist/server/utils/string-command-shell.d.ts.map +1 -0
- package/dist/server/utils/string-command-shell.js +21 -0
- package/dist/server/utils/string-command-shell.js.map +1 -0
- package/dist/server/utils/worktree.d.ts +54 -7
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +434 -129
- package/dist/server/utils/worktree.js.map +1 -1
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/package.json +11 -14
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts +0 -28
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts.map +0 -1
- package/dist/server/server/agent/providers/pi-acp-agent.js +0 -302
- package/dist/server/server/agent/providers/pi-acp-agent.js.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import equal from "fast-deep-equal";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { promisify } from "util";
|
|
3
|
+
import { TTLCache } from "@isaacs/ttlcache";
|
|
4
|
+
import pMemoize from "p-memoize";
|
|
6
5
|
import { resolve, sep } from "path";
|
|
7
6
|
import { homedir } from "node:os";
|
|
8
7
|
import { z } from "zod";
|
|
9
8
|
import { isLegacyEditorTargetId, serializeAgentStreamEvent, } from "./messages.js";
|
|
10
9
|
import { captureTerminalLines } from "../terminal/terminal.js";
|
|
10
|
+
import { TerminalOutputCoalescer } from "../terminal/terminal-output-coalescer.js";
|
|
11
11
|
import { TerminalStreamOpcode, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, decodeTerminalResizePayload, } from "../shared/terminal-stream-protocol.js";
|
|
12
12
|
import { TTSManager } from "./agent/tts-manager.js";
|
|
13
13
|
import { STTManager } from "./agent/stt-manager.js";
|
|
@@ -21,32 +21,43 @@ import { ensureAgentLoaded } from "./agent/agent-loading.js";
|
|
|
21
21
|
import { sendPromptToAgent, unarchiveAgentState } from "./agent/mcp-shared.js";
|
|
22
22
|
import { experimental_createMCPClient } from "ai";
|
|
23
23
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
24
|
+
import { buildWorkspaceScriptPayloads } from "./script-status-projection.js";
|
|
25
|
+
import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
|
26
|
+
import { spawnWorkspaceScript } from "./worktree-bootstrap.js";
|
|
24
27
|
import { buildProviderRegistry } from "./agent/provider-registry.js";
|
|
28
|
+
import { resolveSnapshotCwd } from "./agent/provider-snapshot-manager.js";
|
|
25
29
|
import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
|
|
26
30
|
import { buildStoredAgentPayload, resolveEffectiveThinkingOptionId, resolveStoredAgentPayloadUpdatedAt, toAgentPayload, } from "./agent/agent-projections.js";
|
|
27
31
|
import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from "./agent/agent-title-limits.js";
|
|
28
32
|
import { appendTimelineItemIfAgentKnown, emitLiveTimelineItemIfAgentKnown, } from "./agent/timeline-append.js";
|
|
29
33
|
import { projectTimelineRows, selectTimelineWindowByProjectedLimit, } from "./agent/timeline-projection.js";
|
|
30
34
|
import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, StructuredAgentFallbackError, StructuredAgentResponseError, generateStructuredAgentResponseWithFallback, } from "./agent/agent-response-loop.js";
|
|
31
|
-
import {
|
|
35
|
+
import { checkoutLiteFromGitSnapshot, normalizeWorkspaceId as normalizePersistedWorkspaceId, deriveProjectGroupingName, deriveWorkspaceId, deriveProjectRootPath, deriveProjectKind, deriveWorkspaceKind, deriveWorkspaceDisplayName, buildProjectPlacementForCwd as buildProjectPlacementForCwdStandalone, } from "./workspace-registry-model.js";
|
|
32
36
|
import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from "./workspace-registry.js";
|
|
33
37
|
import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, wrapSpokenInput, } from "./voice-config.js";
|
|
34
38
|
import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
|
|
35
39
|
import { listDirectoryEntries, readExplorerFile, getDownloadableFileInfo, } from "./file-explorer/service.js";
|
|
36
40
|
import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
|
|
37
|
-
import {
|
|
41
|
+
import { archivePersistedWorkspaceRecord } from "./workspace-archive-service.js";
|
|
42
|
+
import { WorkspaceReconciliationService } from "./workspace-reconciliation-service.js";
|
|
43
|
+
import { checkoutResolvedBranch, commitChanges, mergeToBase, mergeFromBase, pullCurrentBranch, pushCurrentBranch, createPullRequest, } from "../utils/checkout-git.js";
|
|
38
44
|
import { getProjectIcon } from "../utils/project-icon.js";
|
|
39
45
|
import { expandTilde } from "../utils/path.js";
|
|
40
46
|
import { searchHomeDirectories, searchWorkspaceEntries } from "../utils/directory-suggestions.js";
|
|
41
|
-
import {
|
|
47
|
+
import { toCheckoutError } from "./checkout-git-utils.js";
|
|
42
48
|
import { toResolver } from "./speech/provider-resolver.js";
|
|
43
49
|
import { resolveClientMessageId } from "./client-message-id.js";
|
|
44
50
|
import { ChatServiceError } from "./chat/chat-service.js";
|
|
45
51
|
import { notifyChatMentions } from "./chat/chat-mentions.js";
|
|
46
52
|
import { execCommand } from "../utils/spawn.js";
|
|
47
|
-
import {
|
|
48
|
-
|
|
53
|
+
import { createGitHubService, } from "../services/github-service.js";
|
|
54
|
+
import { createPaseoWorktree, } from "./paseo-worktree-service.js";
|
|
55
|
+
import { createWorktreeCoreDeps } from "./worktree-core.js";
|
|
56
|
+
import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig as buildWorktreeAgentSessionConfig, runWorktreeSetupInBackground as runWorktreeSetupInBackgroundSession, handleCreatePaseoWorktreeRequest as handleCreateWorktreeRequest, handlePaseoWorktreeArchiveRequest as handleWorktreeArchiveRequest, handlePaseoWorktreeListRequest as handleWorktreeListRequest, handleWorkspaceSetupStatusRequest as handleWorkspaceSetupStatusRequestMessage, } from "./worktree-session.js";
|
|
57
|
+
import { killTerminalsUnderPath as killWorktreeTerminalsUnderPath } from "./paseo-worktree-archive-service.js";
|
|
58
|
+
import { toWorktreeWireError } from "./worktree-errors.js";
|
|
49
59
|
const MAX_INITIAL_AGENT_TITLE_CHARS = Math.min(60, MAX_EXPLICIT_AGENT_TITLE_CHARS);
|
|
60
|
+
const WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT = "__removed__";
|
|
50
61
|
// TODO: Remove once all app store clients are on >=0.1.45 and understand arbitrary provider strings.
|
|
51
62
|
// Clients before 0.1.45 validate providers with z.enum(["claude", "codex", "opencode"]) and reject
|
|
52
63
|
// the entire session message if they encounter an unknown provider.
|
|
@@ -56,7 +67,7 @@ const MIN_VERSION_FLEXIBLE_EDITOR_IDS = "0.1.50";
|
|
|
56
67
|
function isAppVersionAtLeast(appVersion, minVersion) {
|
|
57
68
|
if (!appVersion)
|
|
58
69
|
return false;
|
|
59
|
-
// Strip
|
|
70
|
+
// Strip prerelease suffix: "0.1.45-beta.4" -> "0.1.45"
|
|
60
71
|
const base = appVersion.replace(/-.*$/, "");
|
|
61
72
|
const parts = base.split(".").map(Number);
|
|
62
73
|
const minParts = minVersion.split(".").map(Number);
|
|
@@ -77,6 +88,11 @@ function clientSupportsFlexibleEditorIds(appVersion) {
|
|
|
77
88
|
return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
|
|
78
89
|
}
|
|
79
90
|
const MAX_TERMINAL_STREAM_SLOTS = 256;
|
|
91
|
+
function beginAgentDeleteIfSupported(agentStorage, agentId) {
|
|
92
|
+
if ("beginDelete" in agentStorage && typeof agentStorage.beginDelete === "function") {
|
|
93
|
+
agentStorage.beginDelete(agentId);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
80
96
|
function deriveInitialAgentTitle(prompt) {
|
|
81
97
|
const firstContentLine = prompt
|
|
82
98
|
.split(/\r?\n/)
|
|
@@ -146,6 +162,8 @@ const MIN_STREAMING_SEGMENT_DURATION_MS = 1000;
|
|
|
146
162
|
const MIN_STREAMING_SEGMENT_BYTES = Math.round(PCM_BYTES_PER_MS * MIN_STREAMING_SEGMENT_DURATION_MS);
|
|
147
163
|
const AgentIdSchema = z.string().uuid();
|
|
148
164
|
const VOICE_INTERRUPT_CONFIRMATION_MS = 500;
|
|
165
|
+
const AVAILABLE_EDITOR_TARGETS_CACHE_TTL_MS = 60000;
|
|
166
|
+
const AVAILABLE_EDITOR_TARGETS_CACHE_KEY = "available";
|
|
149
167
|
class VoiceFeatureUnavailableError extends Error {
|
|
150
168
|
constructor(context) {
|
|
151
169
|
super(context.message);
|
|
@@ -216,7 +234,18 @@ export class Session {
|
|
|
216
234
|
this.nextTerminalSlot = 0;
|
|
217
235
|
this.inflightRequests = 0;
|
|
218
236
|
this.peakInflightRequests = 0;
|
|
237
|
+
this.availableEditorTargetsCache = new TTLCache({
|
|
238
|
+
ttl: AVAILABLE_EDITOR_TARGETS_CACHE_TTL_MS,
|
|
239
|
+
max: 1,
|
|
240
|
+
checkAgeOnGet: true,
|
|
241
|
+
});
|
|
242
|
+
this.getMemoizedAvailableEditorTargets = pMemoize(async () => this.resolveAvailableEditorTargets(), {
|
|
243
|
+
cache: this.availableEditorTargetsCache,
|
|
244
|
+
cacheKey: () => AVAILABLE_EDITOR_TARGETS_CACHE_KEY,
|
|
245
|
+
});
|
|
219
246
|
this.checkoutDiffSubscriptions = new Map();
|
|
247
|
+
this.workspaceGitWatchTargets = new Map();
|
|
248
|
+
this.workspaceGitFetchSubscriptions = new Map();
|
|
220
249
|
this.workspaceGitSubscriptions = new Map();
|
|
221
250
|
this.voiceModeAgentId = null;
|
|
222
251
|
this.voiceModeBaseConfig = null;
|
|
@@ -227,9 +256,9 @@ export class Session {
|
|
|
227
256
|
attention: 3,
|
|
228
257
|
done: 4,
|
|
229
258
|
};
|
|
230
|
-
const { clientId, appVersion, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, providerOverrides, } = options;
|
|
259
|
+
const { clientId, appVersion, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, paseoHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, github, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, scriptRouteStore, scriptRuntimeStore, workspaceSetupSnapshots, onBranchChanged, getDaemonTcpPort, getDaemonTcpHost, resolveScriptHealth, voice, voiceBridge, dictation, agentProviderRuntimeSettings, providerOverrides, isDev, } = options;
|
|
231
260
|
this.clientId = clientId;
|
|
232
|
-
this.appVersion = appVersion;
|
|
261
|
+
this.appVersion = appVersion ?? null;
|
|
233
262
|
this.sessionId = uuidv4();
|
|
234
263
|
this.onMessage = onMessage;
|
|
235
264
|
this.onBinaryMessage = onBinaryMessage ?? null;
|
|
@@ -237,6 +266,11 @@ export class Session {
|
|
|
237
266
|
this.downloadTokenStore = downloadTokenStore;
|
|
238
267
|
this.pushTokenStore = pushTokenStore;
|
|
239
268
|
this.paseoHome = paseoHome;
|
|
269
|
+
this.sessionLogger = logger.child({
|
|
270
|
+
module: "session",
|
|
271
|
+
clientId: this.clientId,
|
|
272
|
+
sessionId: this.sessionId,
|
|
273
|
+
});
|
|
240
274
|
this.agentManager = agentManager;
|
|
241
275
|
this.agentStorage = agentStorage;
|
|
242
276
|
this.projectRegistry = projectRegistry;
|
|
@@ -245,11 +279,19 @@ export class Session {
|
|
|
245
279
|
this.scheduleService = scheduleService;
|
|
246
280
|
this.loopService = loopService;
|
|
247
281
|
this.checkoutDiffManager = checkoutDiffManager;
|
|
282
|
+
this.github = github ?? createGitHubService();
|
|
248
283
|
this.workspaceGitService = workspaceGitService;
|
|
249
284
|
this.daemonConfigStore = daemonConfigStore;
|
|
250
285
|
this.mcpBaseUrl = mcpBaseUrl ?? null;
|
|
251
286
|
this.terminalManager = terminalManager;
|
|
252
287
|
this.providerSnapshotManager = providerSnapshotManager ?? null;
|
|
288
|
+
this.scriptRouteStore = scriptRouteStore ?? null;
|
|
289
|
+
this.scriptRuntimeStore = scriptRuntimeStore ?? null;
|
|
290
|
+
this.workspaceSetupSnapshots = workspaceSetupSnapshots ?? new Map();
|
|
291
|
+
this.onBranchChanged = onBranchChanged;
|
|
292
|
+
this.getDaemonTcpPort = getDaemonTcpPort ?? null;
|
|
293
|
+
this.getDaemonTcpHost = getDaemonTcpHost ?? null;
|
|
294
|
+
this.resolveScriptHealth = resolveScriptHealth ?? null;
|
|
253
295
|
if (this.terminalManager) {
|
|
254
296
|
this.unsubscribeTerminalsChanged = this.terminalManager.subscribeTerminalsChanged((event) => this.handleTerminalsChanged(event));
|
|
255
297
|
}
|
|
@@ -279,15 +321,13 @@ export class Session {
|
|
|
279
321
|
this.getSpeechReadiness = dictation?.getSpeechReadiness;
|
|
280
322
|
this.agentProviderRuntimeSettings = agentProviderRuntimeSettings;
|
|
281
323
|
this.providerOverrides = providerOverrides;
|
|
324
|
+
this.isDev = isDev === true;
|
|
282
325
|
this.abortController = new AbortController();
|
|
283
|
-
this.sessionLogger = logger.child({
|
|
284
|
-
module: "session",
|
|
285
|
-
clientId: this.clientId,
|
|
286
|
-
sessionId: this.sessionId,
|
|
287
|
-
});
|
|
288
326
|
this.providerRegistry = buildProviderRegistry(this.sessionLogger, {
|
|
289
327
|
runtimeSettings: this.agentProviderRuntimeSettings,
|
|
290
328
|
providerOverrides: this.providerOverrides,
|
|
329
|
+
workspaceGitService: this.workspaceGitService,
|
|
330
|
+
isDev: this.isDev,
|
|
291
331
|
});
|
|
292
332
|
// Initialize per-session managers
|
|
293
333
|
this.ttsManager = new TTSManager(this.sessionId, this.sessionLogger, tts);
|
|
@@ -309,6 +349,23 @@ export class Session {
|
|
|
309
349
|
this.appVersion = appVersion;
|
|
310
350
|
}
|
|
311
351
|
}
|
|
352
|
+
async primeWorkspaceGitWatchFingerprintForWorkspace(workspace) {
|
|
353
|
+
const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
|
|
354
|
+
await this.primeWorkspaceGitWatchFingerprints([descriptor]);
|
|
355
|
+
}
|
|
356
|
+
async emitWorkspaceUpdateForWorkspaceId(workspaceId) {
|
|
357
|
+
await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], { skipReconcile: true });
|
|
358
|
+
}
|
|
359
|
+
async archiveWorkspaceRecordForExternalMutation(workspaceId) {
|
|
360
|
+
await this.archiveWorkspaceRecord(workspaceId);
|
|
361
|
+
}
|
|
362
|
+
async emitWorkspaceUpdatesForExternalCwds(cwds) {
|
|
363
|
+
await this.emitWorkspaceUpdatesForCwds(cwds);
|
|
364
|
+
}
|
|
365
|
+
async warmWorkspaceGitDataForWorkspace(workspace) {
|
|
366
|
+
await this.primeWorkspaceGitWatchFingerprintForWorkspace(workspace);
|
|
367
|
+
await this.emitWorkspaceUpdateForWorkspaceId(workspace.workspaceId);
|
|
368
|
+
}
|
|
312
369
|
/**
|
|
313
370
|
* Get the client's current activity state
|
|
314
371
|
*/
|
|
@@ -323,6 +380,9 @@ export class Session {
|
|
|
323
380
|
peakInflightRequests: this.peakInflightRequests,
|
|
324
381
|
};
|
|
325
382
|
}
|
|
383
|
+
emitServerMessage(message) {
|
|
384
|
+
this.emit(message);
|
|
385
|
+
}
|
|
326
386
|
/**
|
|
327
387
|
* Send initial state to client after connection
|
|
328
388
|
*/
|
|
@@ -332,18 +392,23 @@ export class Session {
|
|
|
332
392
|
/**
|
|
333
393
|
* Normalize a user prompt (with optional image metadata) for AgentManager
|
|
334
394
|
*/
|
|
335
|
-
buildAgentPrompt(text, images) {
|
|
395
|
+
buildAgentPrompt(text, images, attachments) {
|
|
336
396
|
const normalized = text?.trim() ?? "";
|
|
337
|
-
|
|
397
|
+
const hasImages = Boolean(images && images.length > 0);
|
|
398
|
+
const hasAttachments = Boolean(attachments && attachments.length > 0);
|
|
399
|
+
if (!hasImages && !hasAttachments) {
|
|
338
400
|
return normalized;
|
|
339
401
|
}
|
|
340
402
|
const blocks = [];
|
|
341
403
|
if (normalized.length > 0) {
|
|
342
404
|
blocks.push({ type: "text", text: normalized });
|
|
343
405
|
}
|
|
344
|
-
for (const image of images) {
|
|
406
|
+
for (const image of images ?? []) {
|
|
345
407
|
blocks.push({ type: "image", data: image.data, mimeType: image.mimeType });
|
|
346
408
|
}
|
|
409
|
+
for (const attachment of attachments ?? []) {
|
|
410
|
+
blocks.push(attachment);
|
|
411
|
+
}
|
|
347
412
|
return blocks;
|
|
348
413
|
}
|
|
349
414
|
/**
|
|
@@ -546,8 +611,7 @@ export class Session {
|
|
|
546
611
|
if (storedUpdatedAt) {
|
|
547
612
|
const liveUpdatedAt = Date.parse(payload.updatedAt);
|
|
548
613
|
const persistedUpdatedAt = Date.parse(storedUpdatedAt);
|
|
549
|
-
if (
|
|
550
|
-
(Number.isNaN(liveUpdatedAt) || persistedUpdatedAt > liveUpdatedAt)) {
|
|
614
|
+
if (Number.isNaN(liveUpdatedAt) || persistedUpdatedAt > liveUpdatedAt) {
|
|
551
615
|
payload.updatedAt = storedUpdatedAt;
|
|
552
616
|
}
|
|
553
617
|
}
|
|
@@ -557,10 +621,10 @@ export class Session {
|
|
|
557
621
|
buildStoredAgentPayload(record) {
|
|
558
622
|
return buildStoredAgentPayload(record, this.providerRegistry, this.sessionLogger);
|
|
559
623
|
}
|
|
560
|
-
// TODO: Remove once all app store clients are on >=0.1.45.
|
|
561
624
|
isProviderVisibleToClient(provider) {
|
|
562
|
-
if (clientSupportsAllProviders(this.appVersion))
|
|
625
|
+
if (clientSupportsAllProviders(this.appVersion)) {
|
|
563
626
|
return true;
|
|
627
|
+
}
|
|
564
628
|
return LEGACY_PROVIDER_IDS.has(provider);
|
|
565
629
|
}
|
|
566
630
|
filterEditorsForClient(editors) {
|
|
@@ -618,7 +682,6 @@ export class Session {
|
|
|
618
682
|
return update.kind === "remove" ? update.agentId : update.agent.id;
|
|
619
683
|
}
|
|
620
684
|
bufferOrEmitAgentUpdate(subscription, payload) {
|
|
621
|
-
// TODO: Remove once all app store clients are on >=0.1.45.
|
|
622
685
|
if (payload.kind === "upsert" && !this.isProviderVisibleToClient(payload.agent.provider)) {
|
|
623
686
|
return;
|
|
624
687
|
}
|
|
@@ -655,156 +718,103 @@ export class Session {
|
|
|
655
718
|
});
|
|
656
719
|
}
|
|
657
720
|
}
|
|
658
|
-
async
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
buildPersistedProjectRecord(input) {
|
|
665
|
-
return createPersistedProjectRecord({
|
|
666
|
-
projectId: input.placement.projectKey,
|
|
667
|
-
rootPath: deriveProjectRootPath({
|
|
668
|
-
cwd: input.workspaceId,
|
|
669
|
-
checkout: input.placement.checkout,
|
|
670
|
-
}),
|
|
671
|
-
kind: deriveProjectKind(input.placement.checkout),
|
|
672
|
-
displayName: input.placement.projectName,
|
|
673
|
-
createdAt: input.createdAt,
|
|
674
|
-
updatedAt: input.updatedAt,
|
|
675
|
-
archivedAt: null,
|
|
676
|
-
});
|
|
721
|
+
async findWorkspaceByDirectory(cwd, options) {
|
|
722
|
+
const normalizedCwd = await this.resolveWorkspaceDirectory(cwd, options);
|
|
723
|
+
const workspaces = await this.workspaceRegistry.list();
|
|
724
|
+
const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(normalizedCwd, workspaces);
|
|
725
|
+
return workspaces.find((workspace) => workspace.workspaceId === workspaceId) ?? null;
|
|
677
726
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
kind: deriveWorkspaceKind(input.placement.checkout),
|
|
684
|
-
displayName: deriveWorkspaceDisplayName({
|
|
685
|
-
cwd: input.workspaceId,
|
|
686
|
-
checkout: input.placement.checkout,
|
|
687
|
-
}),
|
|
688
|
-
createdAt: input.createdAt,
|
|
689
|
-
updatedAt: input.updatedAt,
|
|
690
|
-
archivedAt: null,
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
async archiveProjectRecordIfEmpty(projectId, archivedAt) {
|
|
694
|
-
const siblingWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === projectId && !workspace.archivedAt);
|
|
695
|
-
if (siblingWorkspaces.length === 0) {
|
|
696
|
-
await this.projectRegistry.archive(projectId, archivedAt);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
async reconcileWorkspaceRecord(workspaceId) {
|
|
700
|
-
const normalizedCwd = normalizePersistedWorkspaceId(workspaceId);
|
|
701
|
-
const placement = await this.buildProjectPlacement(normalizedCwd);
|
|
702
|
-
const resolvedWorkspaceId = deriveWorkspaceId(normalizedCwd, placement.checkout);
|
|
703
|
-
const staleWorkspace = resolvedWorkspaceId === normalizedCwd
|
|
704
|
-
? null
|
|
705
|
-
: await this.workspaceRegistry.get(normalizedCwd);
|
|
706
|
-
const existing = (await this.workspaceRegistry.get(resolvedWorkspaceId)) ?? staleWorkspace;
|
|
707
|
-
await this.syncWorkspaceGitWatchTarget(resolvedWorkspaceId, {
|
|
708
|
-
isGit: placement.checkout.isGit,
|
|
709
|
-
});
|
|
710
|
-
const now = new Date().toISOString();
|
|
711
|
-
const nextProjectCreatedAt = existing?.createdAt ?? now;
|
|
712
|
-
const nextWorkspaceCreatedAt = existing?.createdAt ?? now;
|
|
713
|
-
const currentProjectRecord = await this.projectRegistry.get(placement.projectKey);
|
|
714
|
-
const nextProjectRecord = this.buildPersistedProjectRecord({
|
|
715
|
-
workspaceId: resolvedWorkspaceId,
|
|
716
|
-
placement,
|
|
717
|
-
createdAt: currentProjectRecord?.createdAt ?? nextProjectCreatedAt,
|
|
718
|
-
updatedAt: now,
|
|
719
|
-
});
|
|
720
|
-
const nextWorkspaceRecord = this.buildPersistedWorkspaceRecord({
|
|
721
|
-
workspaceId: resolvedWorkspaceId,
|
|
722
|
-
placement,
|
|
723
|
-
createdAt: nextWorkspaceCreatedAt,
|
|
724
|
-
updatedAt: now,
|
|
725
|
-
});
|
|
726
|
-
const needsWorkspaceUpdate = !existing ||
|
|
727
|
-
existing.archivedAt ||
|
|
728
|
-
existing.projectId !== nextWorkspaceRecord.projectId ||
|
|
729
|
-
existing.kind !== nextWorkspaceRecord.kind ||
|
|
730
|
-
existing.displayName !== nextWorkspaceRecord.displayName;
|
|
731
|
-
const needsProjectUpdate = !currentProjectRecord ||
|
|
732
|
-
currentProjectRecord.archivedAt ||
|
|
733
|
-
currentProjectRecord.rootPath !== nextProjectRecord.rootPath ||
|
|
734
|
-
currentProjectRecord.kind !== nextProjectRecord.kind ||
|
|
735
|
-
currentProjectRecord.displayName !== nextProjectRecord.displayName;
|
|
736
|
-
const needsStaleWorkspaceCleanup = !!staleWorkspace &&
|
|
737
|
-
!staleWorkspace.archivedAt &&
|
|
738
|
-
staleWorkspace.workspaceId !== resolvedWorkspaceId;
|
|
739
|
-
let removedWorkspaceId = null;
|
|
740
|
-
if (needsStaleWorkspaceCleanup) {
|
|
741
|
-
await this.workspaceRegistry.archive(staleWorkspace.workspaceId, now);
|
|
742
|
-
this.removeWorkspaceGitSubscription(staleWorkspace.workspaceId);
|
|
743
|
-
removedWorkspaceId = staleWorkspace.workspaceId;
|
|
744
|
-
}
|
|
745
|
-
if (!needsWorkspaceUpdate && !needsProjectUpdate && !needsStaleWorkspaceCleanup) {
|
|
746
|
-
return {
|
|
747
|
-
workspace: existing,
|
|
748
|
-
changed: false,
|
|
749
|
-
removedWorkspaceId: null,
|
|
750
|
-
};
|
|
727
|
+
async resolveWorkspaceDirectory(cwd, options) {
|
|
728
|
+
const normalizedCwd = normalizePersistedWorkspaceId(cwd);
|
|
729
|
+
if (options?.refreshGit === false) {
|
|
730
|
+
const snapshot = this.workspaceGitService.peekSnapshot(normalizedCwd);
|
|
731
|
+
return normalizePersistedWorkspaceId(snapshot?.git.repoRoot ?? normalizedCwd);
|
|
751
732
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
await this.workspaceRegistry.archive(existing.workspaceId, now);
|
|
756
|
-
this.removeWorkspaceGitSubscription(existing.workspaceId);
|
|
757
|
-
removedWorkspaceId ?? (removedWorkspaceId = existing.workspaceId);
|
|
733
|
+
try {
|
|
734
|
+
const snapshot = await this.workspaceGitService.getSnapshot(normalizedCwd);
|
|
735
|
+
return normalizePersistedWorkspaceId(snapshot.git.repoRoot ?? normalizedCwd);
|
|
758
736
|
}
|
|
759
|
-
|
|
760
|
-
|
|
737
|
+
catch {
|
|
738
|
+
return normalizedCwd;
|
|
761
739
|
}
|
|
740
|
+
}
|
|
741
|
+
async buildProjectPlacementForWorkspace(workspace, projectRecord) {
|
|
742
|
+
const project = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
|
|
743
|
+
if (!project) {
|
|
744
|
+
throw new Error(`Project not found for workspace ${workspace.workspaceId}`);
|
|
745
|
+
}
|
|
746
|
+
const checkout = project.kind !== "git"
|
|
747
|
+
? {
|
|
748
|
+
cwd: workspace.cwd,
|
|
749
|
+
isGit: false,
|
|
750
|
+
currentBranch: null,
|
|
751
|
+
remoteUrl: null,
|
|
752
|
+
worktreeRoot: null,
|
|
753
|
+
isPaseoOwnedWorktree: false,
|
|
754
|
+
mainRepoRoot: null,
|
|
755
|
+
}
|
|
756
|
+
: workspace.kind === "worktree"
|
|
757
|
+
? {
|
|
758
|
+
cwd: workspace.cwd,
|
|
759
|
+
isGit: true,
|
|
760
|
+
currentBranch: workspace.displayName,
|
|
761
|
+
remoteUrl: null,
|
|
762
|
+
worktreeRoot: workspace.cwd,
|
|
763
|
+
isPaseoOwnedWorktree: true,
|
|
764
|
+
mainRepoRoot: project.rootPath,
|
|
765
|
+
}
|
|
766
|
+
: {
|
|
767
|
+
cwd: workspace.cwd,
|
|
768
|
+
isGit: true,
|
|
769
|
+
currentBranch: workspace.displayName,
|
|
770
|
+
remoteUrl: null,
|
|
771
|
+
worktreeRoot: workspace.cwd,
|
|
772
|
+
isPaseoOwnedWorktree: false,
|
|
773
|
+
mainRepoRoot: null,
|
|
774
|
+
};
|
|
762
775
|
return {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
776
|
+
projectKey: project.projectId,
|
|
777
|
+
projectName: project.displayName,
|
|
778
|
+
checkout,
|
|
766
779
|
};
|
|
767
780
|
}
|
|
768
|
-
async
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
const staleWorkspaceIds = await detectStaleWorkspaces({
|
|
772
|
-
activeWorkspaces,
|
|
773
|
-
checkDirectoryExists: async (cwd) => {
|
|
774
|
-
try {
|
|
775
|
-
await stat(cwd);
|
|
776
|
-
return true;
|
|
777
|
-
}
|
|
778
|
-
catch {
|
|
779
|
-
return false;
|
|
780
|
-
}
|
|
781
|
-
},
|
|
781
|
+
async buildProjectPlacementForCwd(cwd, options) {
|
|
782
|
+
const workspace = await this.findWorkspaceByDirectory(cwd, {
|
|
783
|
+
refreshGit: options?.refreshGit,
|
|
782
784
|
});
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
}
|
|
787
|
-
for (const workspace of activeWorkspaces) {
|
|
788
|
-
if (staleWorkspaceIds.has(workspace.workspaceId)) {
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
const result = await this.reconcileWorkspaceRecord(workspace.workspaceId);
|
|
792
|
-
if (result.changed) {
|
|
793
|
-
changedWorkspaceIds.add(result.workspace.workspaceId);
|
|
794
|
-
if (result.removedWorkspaceId) {
|
|
795
|
-
changedWorkspaceIds.add(result.removedWorkspaceId);
|
|
796
|
-
}
|
|
785
|
+
if (!workspace) {
|
|
786
|
+
if (!options?.fallback) {
|
|
787
|
+
return null;
|
|
797
788
|
}
|
|
789
|
+
const normalizedCwd = normalizePersistedWorkspaceId(cwd);
|
|
790
|
+
return {
|
|
791
|
+
projectKey: normalizedCwd,
|
|
792
|
+
projectName: deriveProjectGroupingName(normalizedCwd),
|
|
793
|
+
checkout: {
|
|
794
|
+
cwd: normalizedCwd,
|
|
795
|
+
isGit: false,
|
|
796
|
+
currentBranch: null,
|
|
797
|
+
remoteUrl: null,
|
|
798
|
+
worktreeRoot: null,
|
|
799
|
+
isPaseoOwnedWorktree: false,
|
|
800
|
+
mainRepoRoot: null,
|
|
801
|
+
},
|
|
802
|
+
};
|
|
798
803
|
}
|
|
799
|
-
return
|
|
804
|
+
return this.buildProjectPlacementForWorkspace(workspace);
|
|
800
805
|
}
|
|
801
806
|
async forwardAgentUpdate(agent) {
|
|
802
807
|
try {
|
|
803
|
-
await this.ensureWorkspaceRegistered(agent.cwd);
|
|
804
808
|
const subscription = this.agentUpdatesSubscription;
|
|
805
809
|
const payload = await this.buildAgentPayload(agent);
|
|
806
810
|
if (subscription) {
|
|
807
|
-
const project = await this.
|
|
811
|
+
const project = await this.buildProjectPlacementForCwd(payload.cwd, {
|
|
812
|
+
refreshGit: false,
|
|
813
|
+
fallback: true,
|
|
814
|
+
});
|
|
815
|
+
if (!project) {
|
|
816
|
+
throw new Error(`Workspace not found for agent ${payload.id}`);
|
|
817
|
+
}
|
|
808
818
|
const matches = this.matchesAgentFilter({
|
|
809
819
|
agent: payload,
|
|
810
820
|
project,
|
|
@@ -854,6 +864,9 @@ export class Session {
|
|
|
854
864
|
case "fetch_agents_request":
|
|
855
865
|
await this.handleFetchAgents(msg);
|
|
856
866
|
break;
|
|
867
|
+
case "fetch_agent_history_request":
|
|
868
|
+
await this.handleFetchAgentHistory(msg);
|
|
869
|
+
break;
|
|
857
870
|
case "fetch_workspaces_request":
|
|
858
871
|
await this.handleFetchWorkspacesRequest(msg);
|
|
859
872
|
break;
|
|
@@ -1019,6 +1032,12 @@ export class Session {
|
|
|
1019
1032
|
case "checkout_pr_status_request":
|
|
1020
1033
|
await this.handleCheckoutPrStatusRequest(msg);
|
|
1021
1034
|
break;
|
|
1035
|
+
case "pull_request_timeline_request":
|
|
1036
|
+
await this.handlePullRequestTimelineRequest(msg);
|
|
1037
|
+
break;
|
|
1038
|
+
case "github_search_request":
|
|
1039
|
+
await this.handleGitHubSearchRequest(msg);
|
|
1040
|
+
break;
|
|
1022
1041
|
case "paseo_worktree_list_request":
|
|
1023
1042
|
await this.handlePaseoWorktreeListRequest(msg);
|
|
1024
1043
|
break;
|
|
@@ -1028,6 +1047,9 @@ export class Session {
|
|
|
1028
1047
|
case "create_paseo_worktree_request":
|
|
1029
1048
|
await this.handleCreatePaseoWorktreeRequest(msg);
|
|
1030
1049
|
break;
|
|
1050
|
+
case "workspace_setup_status_request":
|
|
1051
|
+
await this.handleWorkspaceSetupStatusRequest(msg);
|
|
1052
|
+
break;
|
|
1031
1053
|
case "list_available_editors_request":
|
|
1032
1054
|
await this.handleListAvailableEditorsRequest(msg);
|
|
1033
1055
|
break;
|
|
@@ -1107,6 +1129,9 @@ export class Session {
|
|
|
1107
1129
|
case "create_terminal_request":
|
|
1108
1130
|
await this.handleCreateTerminalRequest(msg);
|
|
1109
1131
|
break;
|
|
1132
|
+
case "start_workspace_script_request":
|
|
1133
|
+
await this.handleStartWorkspaceScriptRequest(msg);
|
|
1134
|
+
break;
|
|
1110
1135
|
case "subscribe_terminal_request":
|
|
1111
1136
|
await this.handleSubscribeTerminalRequest(msg);
|
|
1112
1137
|
break;
|
|
@@ -1306,19 +1331,23 @@ export class Session {
|
|
|
1306
1331
|
const knownCwd = this.agentManager.getAgent(agentId)?.cwd ??
|
|
1307
1332
|
(await this.agentStorage.get(agentId))?.cwd ??
|
|
1308
1333
|
null;
|
|
1309
|
-
//
|
|
1310
|
-
this.agentStorage
|
|
1334
|
+
// File-backed storage still needs an early delete fence before closeAgent().
|
|
1335
|
+
beginAgentDeleteIfSupported(this.agentStorage, agentId);
|
|
1311
1336
|
try {
|
|
1312
1337
|
await this.agentManager.closeAgent(agentId);
|
|
1313
1338
|
}
|
|
1314
1339
|
catch (error) {
|
|
1315
1340
|
this.sessionLogger.warn({ err: error, agentId }, `Failed to close agent ${agentId} during delete`);
|
|
1316
1341
|
}
|
|
1342
|
+
// Drain queued persistence from the just-closed agent before removing its
|
|
1343
|
+
// durable snapshot, otherwise an in-flight background write can recreate it.
|
|
1344
|
+
await this.agentManager.flush();
|
|
1317
1345
|
try {
|
|
1318
1346
|
await this.agentStorage.remove(agentId);
|
|
1347
|
+
await this.agentManager.deleteCommittedTimeline(agentId);
|
|
1319
1348
|
}
|
|
1320
1349
|
catch (error) {
|
|
1321
|
-
this.sessionLogger.error({ err: error, agentId }, `Failed to
|
|
1350
|
+
this.sessionLogger.error({ err: error, agentId }, `Failed to fully delete agent ${agentId}`);
|
|
1322
1351
|
}
|
|
1323
1352
|
this.emit({
|
|
1324
1353
|
type: "agent_deleted",
|
|
@@ -1338,12 +1367,13 @@ export class Session {
|
|
|
1338
1367
|
}
|
|
1339
1368
|
}
|
|
1340
1369
|
async handleArchiveAgentRequest(agentId, requestId) {
|
|
1341
|
-
|
|
1370
|
+
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
1371
|
+
const { archivedAt } = await this.archiveAgentForClose(agentId);
|
|
1342
1372
|
this.emit({
|
|
1343
1373
|
type: "agent_archived",
|
|
1344
1374
|
payload: {
|
|
1345
|
-
agentId
|
|
1346
|
-
archivedAt
|
|
1375
|
+
agentId,
|
|
1376
|
+
archivedAt,
|
|
1347
1377
|
requestId,
|
|
1348
1378
|
},
|
|
1349
1379
|
});
|
|
@@ -1360,22 +1390,10 @@ export class Session {
|
|
|
1360
1390
|
};
|
|
1361
1391
|
}
|
|
1362
1392
|
const archivedAt = new Date().toISOString();
|
|
1363
|
-
|
|
1364
|
-
? "idle"
|
|
1365
|
-
: existing.lastStatus;
|
|
1366
|
-
await this.agentStorage.upsert({
|
|
1367
|
-
...existing,
|
|
1368
|
-
archivedAt,
|
|
1369
|
-
updatedAt: archivedAt,
|
|
1370
|
-
lastStatus: normalizedStatus,
|
|
1371
|
-
requiresAttention: false,
|
|
1372
|
-
attentionReason: null,
|
|
1373
|
-
attentionTimestamp: null,
|
|
1374
|
-
});
|
|
1393
|
+
await this.agentManager.archiveSnapshot(agentId, archivedAt);
|
|
1375
1394
|
return { agentId, archivedAt };
|
|
1376
1395
|
}
|
|
1377
1396
|
async archiveAgentForClose(agentId) {
|
|
1378
|
-
this.sessionLogger.info({ agentId }, `Archiving agent ${agentId}`);
|
|
1379
1397
|
const liveAgent = this.agentManager.getAgent(agentId);
|
|
1380
1398
|
if (liveAgent) {
|
|
1381
1399
|
await this.interruptAgentIfRunning(agentId);
|
|
@@ -1391,22 +1409,30 @@ export class Session {
|
|
|
1391
1409
|
}
|
|
1392
1410
|
if (this.agentUpdatesSubscription) {
|
|
1393
1411
|
const payload = this.buildStoredAgentPayload(archivedRecord);
|
|
1394
|
-
const project = await this.
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
project,
|
|
1398
|
-
filter: this.agentUpdatesSubscription.filter,
|
|
1399
|
-
});
|
|
1400
|
-
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, matches
|
|
1401
|
-
? {
|
|
1402
|
-
kind: "upsert",
|
|
1412
|
+
const project = await this.buildProjectPlacementForCwd(payload.cwd);
|
|
1413
|
+
if (project) {
|
|
1414
|
+
const matches = this.matchesAgentFilter({
|
|
1403
1415
|
agent: payload,
|
|
1404
1416
|
project,
|
|
1405
|
-
|
|
1406
|
-
|
|
1417
|
+
filter: this.agentUpdatesSubscription.filter,
|
|
1418
|
+
});
|
|
1419
|
+
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, matches
|
|
1420
|
+
? {
|
|
1421
|
+
kind: "upsert",
|
|
1422
|
+
agent: payload,
|
|
1423
|
+
project,
|
|
1424
|
+
}
|
|
1425
|
+
: {
|
|
1426
|
+
kind: "remove",
|
|
1427
|
+
agentId,
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
this.bufferOrEmitAgentUpdate(this.agentUpdatesSubscription, {
|
|
1407
1432
|
kind: "remove",
|
|
1408
1433
|
agentId,
|
|
1409
1434
|
});
|
|
1435
|
+
}
|
|
1410
1436
|
await this.emitWorkspaceUpdateForCwd(payload.cwd);
|
|
1411
1437
|
}
|
|
1412
1438
|
if (!archivedRecord.archivedAt) {
|
|
@@ -1477,26 +1503,10 @@ export class Session {
|
|
|
1477
1503
|
return;
|
|
1478
1504
|
}
|
|
1479
1505
|
try {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
}
|
|
1485
|
-
if (normalizedLabels) {
|
|
1486
|
-
await this.agentManager.setLabels(agentId, normalizedLabels);
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
else {
|
|
1490
|
-
const existing = await this.agentStorage.get(agentId);
|
|
1491
|
-
if (!existing) {
|
|
1492
|
-
throw new Error(`Agent not found: ${agentId}`);
|
|
1493
|
-
}
|
|
1494
|
-
await this.agentStorage.upsert({
|
|
1495
|
-
...existing,
|
|
1496
|
-
...(normalizedName ? { title: normalizedName } : {}),
|
|
1497
|
-
...(normalizedLabels ? { labels: { ...existing.labels, ...normalizedLabels } } : {}),
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1506
|
+
await this.agentManager.updateAgentMetadata(agentId, {
|
|
1507
|
+
...(normalizedName ? { title: normalizedName } : {}),
|
|
1508
|
+
...(normalizedLabels ? { labels: normalizedLabels } : {}),
|
|
1509
|
+
});
|
|
1500
1510
|
this.emit({
|
|
1501
1511
|
type: "update_agent_response",
|
|
1502
1512
|
payload: { requestId, agentId, accepted: true, error: null },
|
|
@@ -1824,10 +1834,17 @@ export class Session {
|
|
|
1824
1834
|
/**
|
|
1825
1835
|
* Handle text message to agent (with optional image attachments)
|
|
1826
1836
|
*/
|
|
1827
|
-
async handleSendAgentMessage(agentId, text, messageId, images, runOptions, options) {
|
|
1828
|
-
this.sessionLogger.info({
|
|
1837
|
+
async handleSendAgentMessage(agentId, text, messageId, images, attachments, runOptions, options) {
|
|
1838
|
+
this.sessionLogger.info({
|
|
1839
|
+
agentId,
|
|
1840
|
+
textPreview: text.substring(0, 50),
|
|
1841
|
+
imageCount: images?.length ?? 0,
|
|
1842
|
+
attachmentCount: attachments?.length ?? 0,
|
|
1843
|
+
}, `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}${attachments && attachments.length > 0
|
|
1844
|
+
? ` and ${attachments.length} structured attachment(s)`
|
|
1845
|
+
: ""}`);
|
|
1829
1846
|
const promptText = options?.spokenInput ? wrapSpokenInput(text) : text;
|
|
1830
|
-
const prompt = this.buildAgentPrompt(promptText, images);
|
|
1847
|
+
const prompt = this.buildAgentPrompt(promptText, images, attachments);
|
|
1831
1848
|
try {
|
|
1832
1849
|
await sendPromptToAgent({
|
|
1833
1850
|
agentManager: this.agentManager,
|
|
@@ -1853,7 +1870,7 @@ export class Session {
|
|
|
1853
1870
|
* Handle create agent request
|
|
1854
1871
|
*/
|
|
1855
1872
|
async handleCreateAgentRequest(msg) {
|
|
1856
|
-
const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, labels, } = msg;
|
|
1873
|
+
const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, attachments, labels, } = msg;
|
|
1857
1874
|
this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ""}`);
|
|
1858
1875
|
try {
|
|
1859
1876
|
const trimmedPrompt = initialPrompt?.trim();
|
|
@@ -1865,11 +1882,24 @@ export class Session {
|
|
|
1865
1882
|
...config,
|
|
1866
1883
|
...(provisionalTitle ? { title: provisionalTitle } : {}),
|
|
1867
1884
|
};
|
|
1868
|
-
const { sessionConfig,
|
|
1869
|
-
|
|
1870
|
-
|
|
1885
|
+
const { sessionConfig, worktreeBootstrap } = await this.buildAgentSessionConfig(resolvedConfig, git, worktreeName, attachments);
|
|
1886
|
+
const resolvedWorkspace = msg.workspaceId
|
|
1887
|
+
? await this.workspaceRegistry.get(msg.workspaceId)
|
|
1888
|
+
: ((await this.findWorkspaceByDirectory(sessionConfig.cwd)) ??
|
|
1889
|
+
(await this.findOrCreateWorkspaceForDirectory(sessionConfig.cwd)));
|
|
1890
|
+
if (!resolvedWorkspace) {
|
|
1891
|
+
throw new Error(`Workspace not found: ${msg.workspaceId}`);
|
|
1892
|
+
}
|
|
1893
|
+
const snapshot = await this.agentManager.createAgent({
|
|
1894
|
+
...sessionConfig,
|
|
1895
|
+
cwd: resolvedWorkspace.cwd,
|
|
1896
|
+
}, undefined, {
|
|
1897
|
+
labels,
|
|
1898
|
+
workspaceId: resolvedWorkspace.workspaceId,
|
|
1899
|
+
initialPrompt: trimmedPrompt,
|
|
1900
|
+
});
|
|
1871
1901
|
await this.forwardAgentUpdate(snapshot);
|
|
1872
|
-
if (trimmedPrompt) {
|
|
1902
|
+
if (trimmedPrompt || (images?.length ?? 0) > 0 || (attachments?.length ?? 0) > 0) {
|
|
1873
1903
|
scheduleAgentMetadataGeneration({
|
|
1874
1904
|
agentManager: this.agentManager,
|
|
1875
1905
|
agentId: snapshot.id,
|
|
@@ -1878,17 +1908,17 @@ export class Session {
|
|
|
1878
1908
|
explicitTitle,
|
|
1879
1909
|
paseoHome: this.paseoHome,
|
|
1880
1910
|
logger: this.sessionLogger,
|
|
1911
|
+
deps: {
|
|
1912
|
+
workspaceGitService: this.workspaceGitService,
|
|
1913
|
+
},
|
|
1881
1914
|
});
|
|
1882
|
-
const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined);
|
|
1915
|
+
const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt || "", resolveClientMessageId(clientMessageId), images, attachments, outputSchema ? { outputSchema } : undefined);
|
|
1883
1916
|
if (!started.ok) {
|
|
1884
1917
|
throw new Error(started.error);
|
|
1885
1918
|
}
|
|
1886
1919
|
}
|
|
1887
1920
|
if (requestId) {
|
|
1888
|
-
const agentPayload = await this.
|
|
1889
|
-
if (!agentPayload) {
|
|
1890
|
-
throw new Error(`Agent ${snapshot.id} not found after creation`);
|
|
1891
|
-
}
|
|
1921
|
+
const agentPayload = await this.buildAgentPayload(snapshot);
|
|
1892
1922
|
this.emit({
|
|
1893
1923
|
type: "status",
|
|
1894
1924
|
payload: {
|
|
@@ -1899,10 +1929,11 @@ export class Session {
|
|
|
1899
1929
|
},
|
|
1900
1930
|
});
|
|
1901
1931
|
}
|
|
1902
|
-
if (
|
|
1932
|
+
if (worktreeBootstrap) {
|
|
1903
1933
|
void runAsyncWorktreeBootstrap({
|
|
1904
1934
|
agentId: snapshot.id,
|
|
1905
|
-
worktree:
|
|
1935
|
+
worktree: worktreeBootstrap.worktree,
|
|
1936
|
+
shouldBootstrap: worktreeBootstrap.shouldBootstrap,
|
|
1906
1937
|
terminalManager: this.terminalManager,
|
|
1907
1938
|
appendTimelineItem: (item) => appendTimelineItemIfAgentKnown({
|
|
1908
1939
|
agentManager: this.agentManager,
|
|
@@ -1920,6 +1951,7 @@ export class Session {
|
|
|
1920
1951
|
this.sessionLogger.info({ agentId: snapshot.id, provider: snapshot.provider }, `Created agent ${snapshot.id} (${snapshot.provider})`);
|
|
1921
1952
|
}
|
|
1922
1953
|
catch (error) {
|
|
1954
|
+
const wireError = toWorktreeWireError(error);
|
|
1923
1955
|
this.sessionLogger.error({ err: error }, "Failed to create agent");
|
|
1924
1956
|
if (requestId) {
|
|
1925
1957
|
this.emit({
|
|
@@ -1927,7 +1959,8 @@ export class Session {
|
|
|
1927
1959
|
payload: {
|
|
1928
1960
|
status: "agent_create_failed",
|
|
1929
1961
|
requestId,
|
|
1930
|
-
error:
|
|
1962
|
+
error: wireError.message,
|
|
1963
|
+
errorCode: wireError.code,
|
|
1931
1964
|
},
|
|
1932
1965
|
});
|
|
1933
1966
|
}
|
|
@@ -1937,7 +1970,7 @@ export class Session {
|
|
|
1937
1970
|
id: uuidv4(),
|
|
1938
1971
|
timestamp: new Date(),
|
|
1939
1972
|
type: "error",
|
|
1940
|
-
content: `Failed to create agent: ${
|
|
1973
|
+
content: `Failed to create agent: ${wireError.message}`,
|
|
1941
1974
|
},
|
|
1942
1975
|
});
|
|
1943
1976
|
}
|
|
@@ -1966,10 +1999,7 @@ export class Session {
|
|
|
1966
1999
|
await this.forwardAgentUpdate(snapshot);
|
|
1967
2000
|
const timelineSize = this.agentManager.getTimeline(snapshot.id).length;
|
|
1968
2001
|
if (requestId) {
|
|
1969
|
-
const agentPayload = await this.
|
|
1970
|
-
if (!agentPayload) {
|
|
1971
|
-
throw new Error(`Agent ${snapshot.id} not found after resume`);
|
|
1972
|
-
}
|
|
2002
|
+
const agentPayload = await this.buildAgentPayload(snapshot);
|
|
1973
2003
|
this.emit({
|
|
1974
2004
|
type: "status",
|
|
1975
2005
|
payload: {
|
|
@@ -2066,50 +2096,140 @@ export class Session {
|
|
|
2066
2096
|
this.handleAgentRunError(agentId, error, "Failed to cancel running agent on request");
|
|
2067
2097
|
}
|
|
2068
2098
|
}
|
|
2069
|
-
async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName,
|
|
2099
|
+
async buildAgentSessionConfig(config, gitOptions, legacyWorktreeName, attachments) {
|
|
2070
2100
|
return buildWorktreeAgentSessionConfig({
|
|
2071
2101
|
paseoHome: this.paseoHome,
|
|
2072
2102
|
sessionLogger: this.sessionLogger,
|
|
2073
2103
|
workspaceGitService: this.workspaceGitService,
|
|
2104
|
+
createPaseoWorktree: (input, serviceOptions) => this.createPaseoWorktree(input, serviceOptions),
|
|
2074
2105
|
checkoutExistingBranch: (cwd, branch) => this.checkoutExistingBranch(cwd, branch),
|
|
2075
2106
|
createBranchFromBase: (params) => this.createBranchFromBase(params),
|
|
2076
|
-
|
|
2107
|
+
github: this.github,
|
|
2108
|
+
}, config, gitOptions, legacyWorktreeName, attachments);
|
|
2077
2109
|
}
|
|
2078
2110
|
async handleListProviderModelsRequest(msg) {
|
|
2111
|
+
const cwd = resolveSnapshotCwd(msg.cwd ? expandTilde(msg.cwd) : undefined);
|
|
2079
2112
|
const fetchedAt = new Date().toISOString();
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2113
|
+
const manager = this.providerSnapshotManager;
|
|
2114
|
+
if (!manager) {
|
|
2115
|
+
try {
|
|
2116
|
+
const models = await this.providerRegistry[msg.provider].fetchModels({
|
|
2117
|
+
cwd,
|
|
2118
|
+
force: false,
|
|
2119
|
+
});
|
|
2120
|
+
this.emit({
|
|
2121
|
+
type: "list_provider_models_response",
|
|
2122
|
+
payload: {
|
|
2123
|
+
provider: msg.provider,
|
|
2124
|
+
models,
|
|
2125
|
+
error: null,
|
|
2126
|
+
fetchedAt,
|
|
2127
|
+
requestId: msg.requestId,
|
|
2128
|
+
},
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
catch (error) {
|
|
2132
|
+
this.sessionLogger.error({ err: error, provider: msg.provider }, `Failed to list models for ${msg.provider}`);
|
|
2133
|
+
this.emit({
|
|
2134
|
+
type: "list_provider_models_response",
|
|
2135
|
+
payload: {
|
|
2136
|
+
provider: msg.provider,
|
|
2137
|
+
error: error?.message ?? String(error),
|
|
2138
|
+
fetchedAt,
|
|
2139
|
+
requestId: msg.requestId,
|
|
2140
|
+
},
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const entry = await this.getProviderSnapshotEntryForRead(cwd, msg.provider);
|
|
2146
|
+
if (!entry) {
|
|
2084
2147
|
this.emit({
|
|
2085
2148
|
type: "list_provider_models_response",
|
|
2086
2149
|
payload: {
|
|
2087
2150
|
provider: msg.provider,
|
|
2088
|
-
|
|
2089
|
-
error: null,
|
|
2151
|
+
error: `Unknown provider: ${msg.provider}`,
|
|
2090
2152
|
fetchedAt,
|
|
2091
2153
|
requestId: msg.requestId,
|
|
2092
2154
|
},
|
|
2093
2155
|
});
|
|
2156
|
+
return;
|
|
2094
2157
|
}
|
|
2095
|
-
|
|
2096
|
-
this.sessionLogger.error({ err: error, provider: msg.provider }, `Failed to list models for ${msg.provider}`);
|
|
2158
|
+
if (entry.status === "ready") {
|
|
2097
2159
|
this.emit({
|
|
2098
2160
|
type: "list_provider_models_response",
|
|
2099
2161
|
payload: {
|
|
2100
2162
|
provider: msg.provider,
|
|
2101
|
-
|
|
2102
|
-
|
|
2163
|
+
models: entry.models ?? [],
|
|
2164
|
+
error: null,
|
|
2165
|
+
fetchedAt: entry.fetchedAt ?? fetchedAt,
|
|
2103
2166
|
requestId: msg.requestId,
|
|
2104
2167
|
},
|
|
2105
2168
|
});
|
|
2169
|
+
return;
|
|
2106
2170
|
}
|
|
2171
|
+
const errorMessage = entry.status === "error"
|
|
2172
|
+
? (entry.error ?? `Failed to list models for ${msg.provider}`)
|
|
2173
|
+
: `Provider ${msg.provider} is not available`;
|
|
2174
|
+
this.emit({
|
|
2175
|
+
type: "list_provider_models_response",
|
|
2176
|
+
payload: {
|
|
2177
|
+
provider: msg.provider,
|
|
2178
|
+
error: errorMessage,
|
|
2179
|
+
fetchedAt,
|
|
2180
|
+
requestId: msg.requestId,
|
|
2181
|
+
},
|
|
2182
|
+
});
|
|
2107
2183
|
}
|
|
2108
2184
|
async handleListProviderModesRequest(msg) {
|
|
2109
2185
|
const fetchedAt = new Date().toISOString();
|
|
2186
|
+
const cwd = resolveSnapshotCwd(msg.cwd ? expandTilde(msg.cwd) : undefined);
|
|
2187
|
+
const manager = this.providerSnapshotManager;
|
|
2188
|
+
if (manager) {
|
|
2189
|
+
const entry = await this.getProviderSnapshotEntryForRead(cwd, msg.provider);
|
|
2190
|
+
if (!entry) {
|
|
2191
|
+
this.emit({
|
|
2192
|
+
type: "list_provider_modes_response",
|
|
2193
|
+
payload: {
|
|
2194
|
+
provider: msg.provider,
|
|
2195
|
+
error: `Unknown provider: ${msg.provider}`,
|
|
2196
|
+
fetchedAt,
|
|
2197
|
+
requestId: msg.requestId,
|
|
2198
|
+
},
|
|
2199
|
+
});
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
if (entry.status === "ready") {
|
|
2203
|
+
this.emit({
|
|
2204
|
+
type: "list_provider_modes_response",
|
|
2205
|
+
payload: {
|
|
2206
|
+
provider: msg.provider,
|
|
2207
|
+
modes: entry.modes ?? [],
|
|
2208
|
+
error: null,
|
|
2209
|
+
fetchedAt: entry.fetchedAt ?? fetchedAt,
|
|
2210
|
+
requestId: msg.requestId,
|
|
2211
|
+
},
|
|
2212
|
+
});
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
const errorMessage = entry.status === "error"
|
|
2216
|
+
? (entry.error ?? `Failed to list modes for ${msg.provider}`)
|
|
2217
|
+
: `Provider ${msg.provider} is not available`;
|
|
2218
|
+
this.emit({
|
|
2219
|
+
type: "list_provider_modes_response",
|
|
2220
|
+
payload: {
|
|
2221
|
+
provider: msg.provider,
|
|
2222
|
+
error: errorMessage,
|
|
2223
|
+
fetchedAt,
|
|
2224
|
+
requestId: msg.requestId,
|
|
2225
|
+
},
|
|
2226
|
+
});
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2110
2229
|
try {
|
|
2111
2230
|
const modes = await this.providerRegistry[msg.provider].fetchModes({
|
|
2112
|
-
cwd
|
|
2231
|
+
cwd,
|
|
2232
|
+
force: false,
|
|
2113
2233
|
});
|
|
2114
2234
|
this.emit({
|
|
2115
2235
|
type: "list_provider_modes_response",
|
|
@@ -2135,6 +2255,21 @@ export class Session {
|
|
|
2135
2255
|
});
|
|
2136
2256
|
}
|
|
2137
2257
|
}
|
|
2258
|
+
async getProviderSnapshotEntryForRead(cwd, provider) {
|
|
2259
|
+
const manager = this.providerSnapshotManager;
|
|
2260
|
+
if (!manager) {
|
|
2261
|
+
return undefined;
|
|
2262
|
+
}
|
|
2263
|
+
const findEntry = () => manager.getSnapshot(cwd).find((candidate) => candidate.provider === provider);
|
|
2264
|
+
let entry = findEntry();
|
|
2265
|
+
if (!entry || entry.status === "loading") {
|
|
2266
|
+
// Awaits the in-flight warmup (deduped per-cwd) so old clients still get
|
|
2267
|
+
// a resolved answer rather than a loading placeholder.
|
|
2268
|
+
await manager.warmUpSnapshotForCwd({ cwd, providers: [provider] });
|
|
2269
|
+
entry = findEntry();
|
|
2270
|
+
}
|
|
2271
|
+
return entry;
|
|
2272
|
+
}
|
|
2138
2273
|
buildDraftAgentSessionConfig(draftConfig) {
|
|
2139
2274
|
return {
|
|
2140
2275
|
provider: draftConfig.provider,
|
|
@@ -2177,9 +2312,7 @@ export class Session {
|
|
|
2177
2312
|
async handleListAvailableProvidersRequest(msg) {
|
|
2178
2313
|
const fetchedAt = new Date().toISOString();
|
|
2179
2314
|
try {
|
|
2180
|
-
|
|
2181
|
-
// TODO: Remove once all app store clients are on >=0.1.45.
|
|
2182
|
-
providers = providers.filter((p) => this.isProviderVisibleToClient(p.provider));
|
|
2315
|
+
const providers = (await this.agentManager.listProviderAvailability()).filter((provider) => this.isProviderVisibleToClient(provider.provider));
|
|
2183
2316
|
this.emit({
|
|
2184
2317
|
type: "list_available_providers_response",
|
|
2185
2318
|
payload: {
|
|
@@ -2220,10 +2353,17 @@ export class Session {
|
|
|
2220
2353
|
});
|
|
2221
2354
|
}
|
|
2222
2355
|
async handleRefreshProvidersSnapshotRequest(msg) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2356
|
+
if (msg.cwd) {
|
|
2357
|
+
await this.providerSnapshotManager?.refreshSnapshotForCwd({
|
|
2358
|
+
cwd: expandTilde(msg.cwd),
|
|
2359
|
+
providers: msg.providers,
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
else {
|
|
2363
|
+
await this.providerSnapshotManager?.refreshSettingsSnapshot({
|
|
2364
|
+
providers: msg.providers,
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2227
2367
|
this.emit({
|
|
2228
2368
|
type: "refresh_providers_snapshot_response",
|
|
2229
2369
|
payload: {
|
|
@@ -2276,7 +2416,10 @@ export class Session {
|
|
|
2276
2416
|
return resolvedCandidate.startsWith(resolvedRoot + sep);
|
|
2277
2417
|
}
|
|
2278
2418
|
async generateCommitMessage(cwd) {
|
|
2279
|
-
const diff = await getCheckoutDiff(cwd, {
|
|
2419
|
+
const diff = await this.workspaceGitService.getCheckoutDiff(cwd, {
|
|
2420
|
+
mode: "uncommitted",
|
|
2421
|
+
includeStructured: true,
|
|
2422
|
+
});
|
|
2280
2423
|
const schema = z.object({
|
|
2281
2424
|
message: z
|
|
2282
2425
|
.string()
|
|
@@ -2331,11 +2474,11 @@ export class Session {
|
|
|
2331
2474
|
}
|
|
2332
2475
|
}
|
|
2333
2476
|
async generatePullRequestText(cwd, baseRef) {
|
|
2334
|
-
const diff = await getCheckoutDiff(cwd, {
|
|
2477
|
+
const diff = await this.workspaceGitService.getCheckoutDiff(cwd, {
|
|
2335
2478
|
mode: "base",
|
|
2336
2479
|
baseRef,
|
|
2337
2480
|
includeStructured: true,
|
|
2338
|
-
}
|
|
2481
|
+
});
|
|
2339
2482
|
const schema = z.object({
|
|
2340
2483
|
title: z.string().min(1).max(72),
|
|
2341
2484
|
body: z.string().min(1),
|
|
@@ -2396,11 +2539,8 @@ export class Session {
|
|
|
2396
2539
|
}
|
|
2397
2540
|
async isWorkingTreeDirty(cwd) {
|
|
2398
2541
|
try {
|
|
2399
|
-
const
|
|
2400
|
-
|
|
2401
|
-
env: READ_ONLY_GIT_ENV,
|
|
2402
|
-
});
|
|
2403
|
-
return stdout.trim().length > 0;
|
|
2542
|
+
const snapshot = await this.workspaceGitService.getSnapshot(cwd);
|
|
2543
|
+
return snapshot.git.isDirty === true;
|
|
2404
2544
|
}
|
|
2405
2545
|
catch (error) {
|
|
2406
2546
|
throw new Error(`Unable to inspect git status for ${cwd}: ${error.message}`);
|
|
@@ -2408,30 +2548,24 @@ export class Session {
|
|
|
2408
2548
|
}
|
|
2409
2549
|
async checkoutExistingBranch(cwd, branch) {
|
|
2410
2550
|
this.assertSafeGitRef(branch, "branch");
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
}
|
|
2414
|
-
catch (error) {
|
|
2551
|
+
const resolution = await this.workspaceGitService.validateBranchRef(cwd, branch);
|
|
2552
|
+
if (resolution.kind === "not-found") {
|
|
2415
2553
|
throw new Error(`Branch not found: ${branch}`);
|
|
2416
2554
|
}
|
|
2417
|
-
|
|
2555
|
+
await this.ensureCleanWorkingTree(cwd);
|
|
2556
|
+
const result = await checkoutResolvedBranch({
|
|
2418
2557
|
cwd,
|
|
2558
|
+
resolution,
|
|
2419
2559
|
});
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
await this.ensureCleanWorkingTree(cwd);
|
|
2425
|
-
await execCommand("git", ["checkout", branch], { cwd });
|
|
2560
|
+
await this.notifyGitMutation(cwd, "switch-branch", { invalidateGithub: true });
|
|
2561
|
+
return result;
|
|
2426
2562
|
}
|
|
2427
2563
|
async createBranchFromBase(params) {
|
|
2428
2564
|
const { cwd, baseBranch, newBranchName } = params;
|
|
2429
2565
|
this.assertSafeGitRef(baseBranch, "base branch");
|
|
2430
2566
|
this.assertSafeGitRef(newBranchName, "new branch");
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
}
|
|
2434
|
-
catch (error) {
|
|
2567
|
+
const baseResolution = await this.workspaceGitService.validateBranchRef(cwd, baseBranch);
|
|
2568
|
+
if (baseResolution.kind === "not-found") {
|
|
2435
2569
|
throw new Error(`Base branch not found: ${baseBranch}`);
|
|
2436
2570
|
}
|
|
2437
2571
|
const exists = await this.doesLocalBranchExist(cwd, newBranchName);
|
|
@@ -2442,17 +2576,21 @@ export class Session {
|
|
|
2442
2576
|
await execCommand("git", ["checkout", "-b", newBranchName, baseBranch], {
|
|
2443
2577
|
cwd,
|
|
2444
2578
|
});
|
|
2579
|
+
await this.notifyGitMutation(cwd, "create-branch");
|
|
2445
2580
|
}
|
|
2446
2581
|
async doesLocalBranchExist(cwd, branch) {
|
|
2447
2582
|
this.assertSafeGitRef(branch, "branch");
|
|
2583
|
+
return this.workspaceGitService.hasLocalBranch(cwd, branch);
|
|
2584
|
+
}
|
|
2585
|
+
async notifyGitMutation(cwd, reason, options) {
|
|
2586
|
+
if (options?.invalidateGithub) {
|
|
2587
|
+
this.github.invalidate({ cwd });
|
|
2588
|
+
}
|
|
2448
2589
|
try {
|
|
2449
|
-
await
|
|
2450
|
-
cwd,
|
|
2451
|
-
});
|
|
2452
|
-
return true;
|
|
2590
|
+
await this.workspaceGitService.getSnapshot(cwd, { force: true, reason });
|
|
2453
2591
|
}
|
|
2454
2592
|
catch (error) {
|
|
2455
|
-
|
|
2593
|
+
this.sessionLogger.warn({ err: error, cwd, reason }, "Failed to force-refresh workspace git snapshot after mutation");
|
|
2456
2594
|
}
|
|
2457
2595
|
}
|
|
2458
2596
|
/**
|
|
@@ -2658,7 +2796,15 @@ export class Session {
|
|
|
2658
2796
|
return;
|
|
2659
2797
|
}
|
|
2660
2798
|
if (!agent && draftConfig) {
|
|
2661
|
-
const sessionConfig =
|
|
2799
|
+
const sessionConfig = {
|
|
2800
|
+
provider: draftConfig.provider,
|
|
2801
|
+
cwd: expandTilde(draftConfig.cwd),
|
|
2802
|
+
...(draftConfig.modeId ? { modeId: draftConfig.modeId } : {}),
|
|
2803
|
+
...(draftConfig.model ? { model: draftConfig.model } : {}),
|
|
2804
|
+
...(draftConfig.thinkingOptionId
|
|
2805
|
+
? { thinkingOptionId: draftConfig.thinkingOptionId }
|
|
2806
|
+
: {}),
|
|
2807
|
+
};
|
|
2662
2808
|
const commands = await this.agentManager.listDraftCommands(sessionConfig);
|
|
2663
2809
|
this.emit({
|
|
2664
2810
|
type: "list_commands_response",
|
|
@@ -2725,70 +2871,14 @@ export class Session {
|
|
|
2725
2871
|
const { cwd, requestId } = msg;
|
|
2726
2872
|
const resolvedCwd = expandTilde(cwd);
|
|
2727
2873
|
try {
|
|
2728
|
-
const
|
|
2729
|
-
if (!status.isGit) {
|
|
2730
|
-
this.emit({
|
|
2731
|
-
type: "checkout_status_response",
|
|
2732
|
-
payload: {
|
|
2733
|
-
cwd,
|
|
2734
|
-
isGit: false,
|
|
2735
|
-
repoRoot: null,
|
|
2736
|
-
currentBranch: null,
|
|
2737
|
-
isDirty: null,
|
|
2738
|
-
baseRef: null,
|
|
2739
|
-
aheadBehind: null,
|
|
2740
|
-
aheadOfOrigin: null,
|
|
2741
|
-
behindOfOrigin: null,
|
|
2742
|
-
hasRemote: false,
|
|
2743
|
-
remoteUrl: null,
|
|
2744
|
-
isPaseoOwnedWorktree: false,
|
|
2745
|
-
error: null,
|
|
2746
|
-
requestId,
|
|
2747
|
-
},
|
|
2748
|
-
});
|
|
2749
|
-
return;
|
|
2750
|
-
}
|
|
2751
|
-
if (status.isPaseoOwnedWorktree) {
|
|
2752
|
-
this.emit({
|
|
2753
|
-
type: "checkout_status_response",
|
|
2754
|
-
payload: {
|
|
2755
|
-
cwd,
|
|
2756
|
-
isGit: true,
|
|
2757
|
-
repoRoot: status.repoRoot ?? null,
|
|
2758
|
-
mainRepoRoot: status.mainRepoRoot,
|
|
2759
|
-
currentBranch: status.currentBranch ?? null,
|
|
2760
|
-
isDirty: status.isDirty ?? null,
|
|
2761
|
-
baseRef: status.baseRef,
|
|
2762
|
-
aheadBehind: status.aheadBehind ?? null,
|
|
2763
|
-
aheadOfOrigin: status.aheadOfOrigin ?? null,
|
|
2764
|
-
behindOfOrigin: status.behindOfOrigin ?? null,
|
|
2765
|
-
hasRemote: status.hasRemote,
|
|
2766
|
-
remoteUrl: status.remoteUrl,
|
|
2767
|
-
isPaseoOwnedWorktree: true,
|
|
2768
|
-
error: null,
|
|
2769
|
-
requestId,
|
|
2770
|
-
},
|
|
2771
|
-
});
|
|
2772
|
-
return;
|
|
2773
|
-
}
|
|
2874
|
+
const snapshot = await this.workspaceGitService.getSnapshot(resolvedCwd);
|
|
2774
2875
|
this.emit({
|
|
2775
2876
|
type: "checkout_status_response",
|
|
2776
|
-
payload: {
|
|
2877
|
+
payload: this.buildCheckoutStatusPayloadFromSnapshot({
|
|
2777
2878
|
cwd,
|
|
2778
|
-
isGit: true,
|
|
2779
|
-
repoRoot: status.repoRoot ?? null,
|
|
2780
|
-
currentBranch: status.currentBranch ?? null,
|
|
2781
|
-
isDirty: status.isDirty ?? null,
|
|
2782
|
-
baseRef: status.baseRef ?? null,
|
|
2783
|
-
aheadBehind: status.aheadBehind ?? null,
|
|
2784
|
-
aheadOfOrigin: status.aheadOfOrigin ?? null,
|
|
2785
|
-
behindOfOrigin: status.behindOfOrigin ?? null,
|
|
2786
|
-
hasRemote: status.hasRemote,
|
|
2787
|
-
remoteUrl: status.remoteUrl,
|
|
2788
|
-
isPaseoOwnedWorktree: false,
|
|
2789
|
-
error: null,
|
|
2790
2879
|
requestId,
|
|
2791
|
-
|
|
2880
|
+
snapshot,
|
|
2881
|
+
}),
|
|
2792
2882
|
});
|
|
2793
2883
|
}
|
|
2794
2884
|
catch (error) {
|
|
@@ -2818,55 +2908,76 @@ export class Session {
|
|
|
2818
2908
|
try {
|
|
2819
2909
|
const resolvedCwd = expandTilde(cwd);
|
|
2820
2910
|
this.assertSafeGitRef(branchName, "branch");
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2911
|
+
const resolution = await this.workspaceGitService.validateBranchRef(resolvedCwd, branchName);
|
|
2912
|
+
switch (resolution.kind) {
|
|
2913
|
+
case "local":
|
|
2914
|
+
this.emit({
|
|
2915
|
+
type: "validate_branch_response",
|
|
2916
|
+
payload: {
|
|
2917
|
+
exists: true,
|
|
2918
|
+
resolvedRef: resolution.name,
|
|
2919
|
+
isRemote: false,
|
|
2920
|
+
error: null,
|
|
2921
|
+
requestId,
|
|
2922
|
+
},
|
|
2923
|
+
});
|
|
2924
|
+
return;
|
|
2925
|
+
case "remote-only":
|
|
2926
|
+
this.emit({
|
|
2927
|
+
type: "validate_branch_response",
|
|
2928
|
+
payload: {
|
|
2929
|
+
exists: true,
|
|
2930
|
+
resolvedRef: resolution.remoteRef,
|
|
2931
|
+
isRemote: true,
|
|
2932
|
+
error: null,
|
|
2933
|
+
requestId,
|
|
2934
|
+
},
|
|
2935
|
+
});
|
|
2936
|
+
return;
|
|
2937
|
+
case "not-found":
|
|
2938
|
+
this.emit({
|
|
2939
|
+
type: "validate_branch_response",
|
|
2940
|
+
payload: {
|
|
2941
|
+
exists: false,
|
|
2942
|
+
resolvedRef: null,
|
|
2943
|
+
isRemote: false,
|
|
2944
|
+
error: null,
|
|
2945
|
+
requestId,
|
|
2946
|
+
},
|
|
2947
|
+
});
|
|
2948
|
+
return;
|
|
2949
|
+
default: {
|
|
2950
|
+
const exhaustiveCheck = resolution;
|
|
2951
|
+
throw new Error(`Unhandled branch resolution: ${exhaustiveCheck}`);
|
|
2952
|
+
}
|
|
2862
2953
|
}
|
|
2863
|
-
|
|
2954
|
+
}
|
|
2955
|
+
catch (error) {
|
|
2864
2956
|
this.emit({
|
|
2865
2957
|
type: "validate_branch_response",
|
|
2866
2958
|
payload: {
|
|
2867
2959
|
exists: false,
|
|
2868
2960
|
resolvedRef: null,
|
|
2869
2961
|
isRemote: false,
|
|
2962
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2963
|
+
requestId,
|
|
2964
|
+
},
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
async handleBranchSuggestionsRequest(msg) {
|
|
2969
|
+
const { cwd, query, limit, requestId } = msg;
|
|
2970
|
+
try {
|
|
2971
|
+
const resolvedCwd = expandTilde(cwd);
|
|
2972
|
+
const branchDetails = await this.workspaceGitService.suggestBranchesForCwd(resolvedCwd, {
|
|
2973
|
+
query,
|
|
2974
|
+
limit,
|
|
2975
|
+
});
|
|
2976
|
+
this.emit({
|
|
2977
|
+
type: "branch_suggestions_response",
|
|
2978
|
+
payload: {
|
|
2979
|
+
branches: branchDetails.map((branch) => branch.name),
|
|
2980
|
+
branchDetails,
|
|
2870
2981
|
error: null,
|
|
2871
2982
|
requestId,
|
|
2872
2983
|
},
|
|
@@ -2874,26 +2985,31 @@ export class Session {
|
|
|
2874
2985
|
}
|
|
2875
2986
|
catch (error) {
|
|
2876
2987
|
this.emit({
|
|
2877
|
-
type: "
|
|
2988
|
+
type: "branch_suggestions_response",
|
|
2878
2989
|
payload: {
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
isRemote: false,
|
|
2990
|
+
branches: [],
|
|
2991
|
+
branchDetails: [],
|
|
2882
2992
|
error: error instanceof Error ? error.message : String(error),
|
|
2883
2993
|
requestId,
|
|
2884
2994
|
},
|
|
2885
2995
|
});
|
|
2886
2996
|
}
|
|
2887
2997
|
}
|
|
2888
|
-
async
|
|
2889
|
-
const { cwd, query, limit, requestId } = msg;
|
|
2998
|
+
async handleGitHubSearchRequest(msg) {
|
|
2999
|
+
const { cwd, query, limit, kinds, requestId } = msg;
|
|
2890
3000
|
try {
|
|
2891
3001
|
const resolvedCwd = expandTilde(cwd);
|
|
2892
|
-
const
|
|
3002
|
+
const result = await this.github.searchIssuesAndPrs({
|
|
3003
|
+
cwd: resolvedCwd,
|
|
3004
|
+
query,
|
|
3005
|
+
limit,
|
|
3006
|
+
kinds,
|
|
3007
|
+
});
|
|
2893
3008
|
this.emit({
|
|
2894
|
-
type: "
|
|
3009
|
+
type: "github_search_response",
|
|
2895
3010
|
payload: {
|
|
2896
|
-
|
|
3011
|
+
items: result.items,
|
|
3012
|
+
githubFeaturesEnabled: result.githubFeaturesEnabled,
|
|
2897
3013
|
error: null,
|
|
2898
3014
|
requestId,
|
|
2899
3015
|
},
|
|
@@ -2901,9 +3017,10 @@ export class Session {
|
|
|
2901
3017
|
}
|
|
2902
3018
|
catch (error) {
|
|
2903
3019
|
this.emit({
|
|
2904
|
-
type: "
|
|
3020
|
+
type: "github_search_response",
|
|
2905
3021
|
payload: {
|
|
2906
|
-
|
|
3022
|
+
items: [],
|
|
3023
|
+
githubFeaturesEnabled: true,
|
|
2907
3024
|
error: error instanceof Error ? error.message : String(error),
|
|
2908
3025
|
requestId,
|
|
2909
3026
|
},
|
|
@@ -2952,24 +3069,97 @@ export class Session {
|
|
|
2952
3069
|
});
|
|
2953
3070
|
}
|
|
2954
3071
|
}
|
|
3072
|
+
closeWorkspaceGitWatchTarget(target) {
|
|
3073
|
+
if (target.debounceTimer) {
|
|
3074
|
+
clearTimeout(target.debounceTimer);
|
|
3075
|
+
target.debounceTimer = null;
|
|
3076
|
+
}
|
|
3077
|
+
for (const watcher of target.watchers) {
|
|
3078
|
+
try {
|
|
3079
|
+
watcher.close();
|
|
3080
|
+
}
|
|
3081
|
+
catch {
|
|
3082
|
+
// Ignore watcher close errors
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
target.watchers.length = 0;
|
|
3086
|
+
}
|
|
3087
|
+
async removeWorkspaceGitWatchTarget(cwd) {
|
|
3088
|
+
const normalizedCwd = normalizePersistedWorkspaceId(cwd);
|
|
3089
|
+
const target = this.workspaceGitWatchTargets.get(normalizedCwd);
|
|
3090
|
+
if (target) {
|
|
3091
|
+
this.closeWorkspaceGitWatchTarget(target);
|
|
3092
|
+
this.workspaceGitWatchTargets.delete(normalizedCwd);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
2955
3095
|
removeWorkspaceGitSubscription(cwd) {
|
|
2956
|
-
const
|
|
2957
|
-
this.
|
|
2958
|
-
|
|
3096
|
+
const normalizedCwd = normalizePersistedWorkspaceId(cwd);
|
|
3097
|
+
const target = this.workspaceGitWatchTargets.get(normalizedCwd);
|
|
3098
|
+
if (target) {
|
|
3099
|
+
const unsubscribeFetch = this.workspaceGitFetchSubscriptions.get(normalizedCwd);
|
|
3100
|
+
unsubscribeFetch?.();
|
|
3101
|
+
this.workspaceGitFetchSubscriptions.delete(normalizedCwd);
|
|
3102
|
+
this.closeWorkspaceGitWatchTarget(target);
|
|
3103
|
+
this.workspaceGitWatchTargets.delete(normalizedCwd);
|
|
3104
|
+
}
|
|
3105
|
+
this.workspaceGitSubscriptions.get(normalizedCwd)?.();
|
|
3106
|
+
this.workspaceGitSubscriptions.delete(normalizedCwd);
|
|
3107
|
+
}
|
|
3108
|
+
workspaceGitDescriptorFingerprint(workspace) {
|
|
3109
|
+
if (!workspace) {
|
|
3110
|
+
return WORKSPACE_GIT_WATCH_REMOVED_FINGERPRINT;
|
|
3111
|
+
}
|
|
3112
|
+
return JSON.stringify([
|
|
3113
|
+
workspace.name,
|
|
3114
|
+
workspace.diffStat ? [workspace.diffStat.additions, workspace.diffStat.deletions] : null,
|
|
3115
|
+
]);
|
|
3116
|
+
}
|
|
3117
|
+
shouldSkipWorkspaceGitWatchUpdate(workspaceId, workspace) {
|
|
3118
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
3119
|
+
if (!target) {
|
|
3120
|
+
return false;
|
|
3121
|
+
}
|
|
3122
|
+
const nextFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
|
|
3123
|
+
if (target.latestFingerprint === nextFingerprint) {
|
|
3124
|
+
return true;
|
|
3125
|
+
}
|
|
3126
|
+
target.latestFingerprint = nextFingerprint;
|
|
3127
|
+
return false;
|
|
3128
|
+
}
|
|
3129
|
+
rememberWorkspaceGitWatchFingerprint(workspaceId, workspace) {
|
|
3130
|
+
const target = this.workspaceGitWatchTargets.get(workspaceId);
|
|
3131
|
+
if (!target) {
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
target.latestFingerprint = this.workspaceGitDescriptorFingerprint(workspace);
|
|
3135
|
+
target.lastBranchName = workspace?.name ?? null;
|
|
3136
|
+
}
|
|
3137
|
+
async primeWorkspaceGitWatchFingerprints(workspaces) {
|
|
3138
|
+
for (const workspace of workspaces) {
|
|
3139
|
+
const persistedWorkspace = await this.workspaceRegistry.get(workspace.id);
|
|
3140
|
+
if (!persistedWorkspace) {
|
|
3141
|
+
continue;
|
|
3142
|
+
}
|
|
3143
|
+
await this.syncWorkspaceGitWatchTarget(persistedWorkspace.cwd, {
|
|
3144
|
+
isGit: workspace.projectKind === "git",
|
|
3145
|
+
});
|
|
3146
|
+
this.rememberWorkspaceGitWatchFingerprint(persistedWorkspace.cwd, workspace);
|
|
3147
|
+
}
|
|
2959
3148
|
}
|
|
2960
3149
|
async syncWorkspaceGitWatchTarget(cwd, options) {
|
|
2961
|
-
const
|
|
3150
|
+
const normalizedCwd = normalizePersistedWorkspaceId(cwd);
|
|
2962
3151
|
if (!options.isGit) {
|
|
2963
|
-
this.removeWorkspaceGitSubscription(
|
|
3152
|
+
this.removeWorkspaceGitSubscription(normalizedCwd);
|
|
2964
3153
|
return;
|
|
2965
3154
|
}
|
|
2966
|
-
if (this.workspaceGitSubscriptions.has(
|
|
3155
|
+
if (this.workspaceGitSubscriptions.has(normalizedCwd)) {
|
|
2967
3156
|
return;
|
|
2968
3157
|
}
|
|
2969
|
-
const subscription = await this.workspaceGitService.subscribe({ cwd:
|
|
2970
|
-
void this.emitWorkspaceUpdateForCwd(
|
|
3158
|
+
const subscription = await this.workspaceGitService.subscribe({ cwd: normalizedCwd }, (snapshot) => {
|
|
3159
|
+
void this.emitWorkspaceUpdateForCwd(normalizedCwd);
|
|
3160
|
+
this.emitCheckoutStatusUpdate(normalizedCwd, snapshot);
|
|
2971
3161
|
});
|
|
2972
|
-
this.workspaceGitSubscriptions.set(
|
|
3162
|
+
this.workspaceGitSubscriptions.set(normalizedCwd, subscription.unsubscribe);
|
|
2973
3163
|
}
|
|
2974
3164
|
async handleSubscribeCheckoutDiffRequest(msg) {
|
|
2975
3165
|
const cwd = expandTilde(msg.cwd);
|
|
@@ -2998,10 +3188,108 @@ export class Session {
|
|
|
2998
3188
|
this.checkoutDiffSubscriptions.get(msg.subscriptionId)?.();
|
|
2999
3189
|
this.checkoutDiffSubscriptions.delete(msg.subscriptionId);
|
|
3000
3190
|
}
|
|
3191
|
+
buildCheckoutStatusPayloadFromSnapshot({ cwd, requestId, snapshot, }) {
|
|
3192
|
+
if (!snapshot.git.isGit) {
|
|
3193
|
+
return {
|
|
3194
|
+
cwd,
|
|
3195
|
+
isGit: false,
|
|
3196
|
+
repoRoot: null,
|
|
3197
|
+
currentBranch: null,
|
|
3198
|
+
isDirty: null,
|
|
3199
|
+
baseRef: null,
|
|
3200
|
+
aheadBehind: null,
|
|
3201
|
+
aheadOfOrigin: null,
|
|
3202
|
+
behindOfOrigin: null,
|
|
3203
|
+
hasRemote: false,
|
|
3204
|
+
remoteUrl: null,
|
|
3205
|
+
isPaseoOwnedWorktree: false,
|
|
3206
|
+
error: null,
|
|
3207
|
+
requestId,
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
if (snapshot.git.repoRoot === null || snapshot.git.isDirty === null) {
|
|
3211
|
+
throw new Error("Workspace git snapshot is missing required checkout status fields");
|
|
3212
|
+
}
|
|
3213
|
+
if (snapshot.git.isPaseoOwnedWorktree) {
|
|
3214
|
+
if (snapshot.git.mainRepoRoot === null || snapshot.git.baseRef === null) {
|
|
3215
|
+
throw new Error("Workspace git snapshot is missing required worktree status fields");
|
|
3216
|
+
}
|
|
3217
|
+
return {
|
|
3218
|
+
cwd,
|
|
3219
|
+
isGit: true,
|
|
3220
|
+
repoRoot: snapshot.git.repoRoot,
|
|
3221
|
+
mainRepoRoot: snapshot.git.mainRepoRoot,
|
|
3222
|
+
currentBranch: snapshot.git.currentBranch ?? null,
|
|
3223
|
+
isDirty: snapshot.git.isDirty,
|
|
3224
|
+
baseRef: snapshot.git.baseRef,
|
|
3225
|
+
aheadBehind: snapshot.git.aheadBehind ?? null,
|
|
3226
|
+
aheadOfOrigin: snapshot.git.aheadOfOrigin ?? null,
|
|
3227
|
+
behindOfOrigin: snapshot.git.behindOfOrigin ?? null,
|
|
3228
|
+
hasRemote: snapshot.git.hasRemote,
|
|
3229
|
+
remoteUrl: snapshot.git.remoteUrl,
|
|
3230
|
+
isPaseoOwnedWorktree: true,
|
|
3231
|
+
error: null,
|
|
3232
|
+
requestId,
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
return {
|
|
3236
|
+
cwd,
|
|
3237
|
+
isGit: true,
|
|
3238
|
+
repoRoot: snapshot.git.repoRoot,
|
|
3239
|
+
currentBranch: snapshot.git.currentBranch ?? null,
|
|
3240
|
+
isDirty: snapshot.git.isDirty,
|
|
3241
|
+
baseRef: snapshot.git.baseRef ?? null,
|
|
3242
|
+
aheadBehind: snapshot.git.aheadBehind ?? null,
|
|
3243
|
+
aheadOfOrigin: snapshot.git.aheadOfOrigin ?? null,
|
|
3244
|
+
behindOfOrigin: snapshot.git.behindOfOrigin ?? null,
|
|
3245
|
+
hasRemote: snapshot.git.hasRemote,
|
|
3246
|
+
remoteUrl: snapshot.git.remoteUrl,
|
|
3247
|
+
isPaseoOwnedWorktree: false,
|
|
3248
|
+
error: null,
|
|
3249
|
+
requestId,
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
buildCheckoutPrStatusPayloadFromSnapshot({ cwd, requestId, snapshot, }) {
|
|
3253
|
+
return {
|
|
3254
|
+
cwd,
|
|
3255
|
+
status: normalizeCheckoutPrStatusPayload(snapshot.github.pullRequest),
|
|
3256
|
+
githubFeaturesEnabled: snapshot.github.featuresEnabled,
|
|
3257
|
+
error: snapshot.github.error
|
|
3258
|
+
? {
|
|
3259
|
+
code: "UNKNOWN",
|
|
3260
|
+
message: snapshot.github.error.message,
|
|
3261
|
+
}
|
|
3262
|
+
: null,
|
|
3263
|
+
requestId,
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
emitCheckoutStatusUpdate(cwd, snapshot) {
|
|
3267
|
+
try {
|
|
3268
|
+
const requestId = `subscription:${cwd}`;
|
|
3269
|
+
this.emit({
|
|
3270
|
+
type: "checkout_status_update",
|
|
3271
|
+
payload: {
|
|
3272
|
+
...this.buildCheckoutStatusPayloadFromSnapshot({
|
|
3273
|
+
cwd,
|
|
3274
|
+
requestId,
|
|
3275
|
+
snapshot,
|
|
3276
|
+
}),
|
|
3277
|
+
prStatus: this.buildCheckoutPrStatusPayloadFromSnapshot({
|
|
3278
|
+
cwd,
|
|
3279
|
+
requestId,
|
|
3280
|
+
snapshot,
|
|
3281
|
+
}),
|
|
3282
|
+
},
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
catch (error) {
|
|
3286
|
+
this.sessionLogger.warn({ err: error, cwd }, "Failed to emit workspace checkout status update");
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3001
3289
|
async handleCheckoutSwitchBranchRequest(msg) {
|
|
3002
3290
|
const { cwd, branch, requestId } = msg;
|
|
3003
3291
|
try {
|
|
3004
|
-
await this.checkoutExistingBranch(cwd, branch);
|
|
3292
|
+
const checkoutResult = await this.checkoutExistingBranch(cwd, branch);
|
|
3005
3293
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3006
3294
|
// Push a workspace_update immediately so the sidebar/header reflect
|
|
3007
3295
|
// the new branch name without waiting for the background git watcher.
|
|
@@ -3012,6 +3300,7 @@ export class Session {
|
|
|
3012
3300
|
cwd,
|
|
3013
3301
|
success: true,
|
|
3014
3302
|
branch,
|
|
3303
|
+
source: checkoutResult.source,
|
|
3015
3304
|
error: null,
|
|
3016
3305
|
requestId,
|
|
3017
3306
|
},
|
|
@@ -3038,6 +3327,7 @@ export class Session {
|
|
|
3038
3327
|
? `${Session.PASEO_STASH_PREFIX} ${branchLabel}`
|
|
3039
3328
|
: `${Session.PASEO_STASH_PREFIX} unnamed`;
|
|
3040
3329
|
await execCommand("git", ["stash", "push", "--include-untracked", "-m", message], { cwd });
|
|
3330
|
+
await this.notifyGitMutation(cwd, "stash-push");
|
|
3041
3331
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3042
3332
|
this.emit({
|
|
3043
3333
|
type: "stash_save_response",
|
|
@@ -3055,6 +3345,7 @@ export class Session {
|
|
|
3055
3345
|
const { cwd, stashIndex, requestId } = msg;
|
|
3056
3346
|
try {
|
|
3057
3347
|
await execCommand("git", ["stash", "pop", `stash@{${stashIndex}}`], { cwd });
|
|
3348
|
+
await this.notifyGitMutation(cwd, "stash-pop");
|
|
3058
3349
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3059
3350
|
this.emit({
|
|
3060
3351
|
type: "stash_pop_response",
|
|
@@ -3072,31 +3363,7 @@ export class Session {
|
|
|
3072
3363
|
const { cwd, requestId } = msg;
|
|
3073
3364
|
const paseoOnly = msg.paseoOnly !== false;
|
|
3074
3365
|
try {
|
|
3075
|
-
const
|
|
3076
|
-
cwd,
|
|
3077
|
-
env: READ_ONLY_GIT_ENV,
|
|
3078
|
-
});
|
|
3079
|
-
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
3080
|
-
const entries = [];
|
|
3081
|
-
for (const line of lines) {
|
|
3082
|
-
const sepIdx = line.indexOf("\0");
|
|
3083
|
-
if (sepIdx < 0)
|
|
3084
|
-
continue;
|
|
3085
|
-
const refPart = line.slice(0, sepIdx);
|
|
3086
|
-
const subject = line.slice(sepIdx + 1);
|
|
3087
|
-
const indexMatch = refPart.match(/\{(\d+)\}/);
|
|
3088
|
-
if (!indexMatch)
|
|
3089
|
-
continue;
|
|
3090
|
-
const index = Number(indexMatch[1]);
|
|
3091
|
-
const prefixIdx = subject.indexOf(Session.PASEO_STASH_PREFIX);
|
|
3092
|
-
const isPaseo = prefixIdx >= 0;
|
|
3093
|
-
const branch = isPaseo
|
|
3094
|
-
? subject.slice(prefixIdx + Session.PASEO_STASH_PREFIX.length).trim() || null
|
|
3095
|
-
: null;
|
|
3096
|
-
if (paseoOnly && !isPaseo)
|
|
3097
|
-
continue;
|
|
3098
|
-
entries.push({ index, message: subject, branch, isPaseo });
|
|
3099
|
-
}
|
|
3366
|
+
const entries = await this.workspaceGitService.listStashes(cwd, { paseoOnly });
|
|
3100
3367
|
this.emit({
|
|
3101
3368
|
type: "stash_list_response",
|
|
3102
3369
|
payload: { cwd, entries, error: null, requestId },
|
|
@@ -3123,6 +3390,7 @@ export class Session {
|
|
|
3123
3390
|
message,
|
|
3124
3391
|
addAll: msg.addAll ?? true,
|
|
3125
3392
|
});
|
|
3393
|
+
await this.notifyGitMutation(cwd, "commit-changes");
|
|
3126
3394
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3127
3395
|
this.emit({
|
|
3128
3396
|
type: "checkout_commit_response",
|
|
@@ -3149,43 +3417,30 @@ export class Session {
|
|
|
3149
3417
|
async handleCheckoutMergeRequest(msg) {
|
|
3150
3418
|
const { cwd, requestId } = msg;
|
|
3151
3419
|
try {
|
|
3152
|
-
const
|
|
3153
|
-
if (!
|
|
3154
|
-
|
|
3155
|
-
await execAsync("git rev-parse --is-inside-work-tree", {
|
|
3156
|
-
cwd,
|
|
3157
|
-
env: READ_ONLY_GIT_ENV,
|
|
3158
|
-
});
|
|
3159
|
-
}
|
|
3160
|
-
catch (error) {
|
|
3161
|
-
const details = typeof error?.stderr === "string"
|
|
3162
|
-
? String(error.stderr).trim()
|
|
3163
|
-
: error instanceof Error
|
|
3164
|
-
? error.message
|
|
3165
|
-
: String(error);
|
|
3166
|
-
throw new Error(`Not a git repository: ${cwd}\n${details}`.trim());
|
|
3167
|
-
}
|
|
3420
|
+
const snapshot = await this.workspaceGitService.getSnapshot(cwd);
|
|
3421
|
+
if (!snapshot.git.isGit) {
|
|
3422
|
+
throw new Error(`Not a git repository: ${cwd}`);
|
|
3168
3423
|
}
|
|
3169
3424
|
if (msg.requireCleanTarget) {
|
|
3170
|
-
|
|
3171
|
-
cwd,
|
|
3172
|
-
env: READ_ONLY_GIT_ENV,
|
|
3173
|
-
});
|
|
3174
|
-
if (stdout.trim().length > 0) {
|
|
3425
|
+
if (snapshot.git.isDirty) {
|
|
3175
3426
|
throw new Error("Working directory has uncommitted changes.");
|
|
3176
3427
|
}
|
|
3177
3428
|
}
|
|
3178
|
-
let baseRef = msg.baseRef ??
|
|
3429
|
+
let baseRef = msg.baseRef ?? snapshot.git.baseRef;
|
|
3179
3430
|
if (!baseRef) {
|
|
3180
3431
|
throw new Error("Base branch is required for merge");
|
|
3181
3432
|
}
|
|
3182
3433
|
if (baseRef.startsWith("origin/")) {
|
|
3183
3434
|
baseRef = baseRef.slice("origin/".length);
|
|
3184
3435
|
}
|
|
3185
|
-
await mergeToBase(cwd, {
|
|
3436
|
+
const mutatedCwd = await mergeToBase(cwd, {
|
|
3186
3437
|
baseRef,
|
|
3187
3438
|
mode: msg.strategy === "squash" ? "squash" : "merge",
|
|
3188
3439
|
}, { paseoHome: this.paseoHome });
|
|
3440
|
+
await Promise.all([
|
|
3441
|
+
this.notifyGitMutation(mutatedCwd, "merge-to-base", { invalidateGithub: true }),
|
|
3442
|
+
...(mutatedCwd !== cwd ? [this.notifyGitMutation(cwd, "merge-to-base")] : []),
|
|
3443
|
+
]);
|
|
3189
3444
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3190
3445
|
this.emit({
|
|
3191
3446
|
type: "checkout_merge_response",
|
|
@@ -3213,11 +3468,8 @@ export class Session {
|
|
|
3213
3468
|
const { cwd, requestId } = msg;
|
|
3214
3469
|
try {
|
|
3215
3470
|
if (msg.requireCleanTarget ?? true) {
|
|
3216
|
-
const
|
|
3217
|
-
|
|
3218
|
-
env: READ_ONLY_GIT_ENV,
|
|
3219
|
-
});
|
|
3220
|
-
if (stdout.trim().length > 0) {
|
|
3471
|
+
const snapshot = await this.workspaceGitService.getSnapshot(cwd);
|
|
3472
|
+
if (snapshot.git.isDirty) {
|
|
3221
3473
|
throw new Error("Working directory has uncommitted changes.");
|
|
3222
3474
|
}
|
|
3223
3475
|
}
|
|
@@ -3225,6 +3477,7 @@ export class Session {
|
|
|
3225
3477
|
baseRef: msg.baseRef,
|
|
3226
3478
|
requireCleanTarget: msg.requireCleanTarget ?? true,
|
|
3227
3479
|
});
|
|
3480
|
+
await this.notifyGitMutation(cwd, "merge-from-base", { invalidateGithub: true });
|
|
3228
3481
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3229
3482
|
this.emit({
|
|
3230
3483
|
type: "checkout_merge_from_base_response",
|
|
@@ -3252,6 +3505,7 @@ export class Session {
|
|
|
3252
3505
|
const { cwd, requestId } = msg;
|
|
3253
3506
|
try {
|
|
3254
3507
|
await pullCurrentBranch(cwd);
|
|
3508
|
+
await this.notifyGitMutation(cwd, "pull", { invalidateGithub: true });
|
|
3255
3509
|
this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
|
|
3256
3510
|
this.emit({
|
|
3257
3511
|
type: "checkout_pull_response",
|
|
@@ -3279,6 +3533,7 @@ export class Session {
|
|
|
3279
3533
|
const { cwd, requestId } = msg;
|
|
3280
3534
|
try {
|
|
3281
3535
|
await pushCurrentBranch(cwd);
|
|
3536
|
+
await this.notifyGitMutation(cwd, "push", { invalidateGithub: true });
|
|
3282
3537
|
this.emit({
|
|
3283
3538
|
type: "checkout_push_response",
|
|
3284
3539
|
payload: {
|
|
@@ -3317,7 +3572,8 @@ export class Session {
|
|
|
3317
3572
|
title,
|
|
3318
3573
|
body,
|
|
3319
3574
|
base: msg.baseRef,
|
|
3320
|
-
});
|
|
3575
|
+
}, this.github, this.workspaceGitService);
|
|
3576
|
+
await this.notifyGitMutation(cwd, "create-pr", { invalidateGithub: true });
|
|
3321
3577
|
this.emit({
|
|
3322
3578
|
type: "checkout_pr_create_response",
|
|
3323
3579
|
payload: {
|
|
@@ -3350,7 +3606,7 @@ export class Session {
|
|
|
3350
3606
|
type: "checkout_pr_status_response",
|
|
3351
3607
|
payload: {
|
|
3352
3608
|
cwd,
|
|
3353
|
-
status: snapshot.github.pullRequest,
|
|
3609
|
+
status: normalizeCheckoutPrStatusPayload(snapshot.github.pullRequest),
|
|
3354
3610
|
githubFeaturesEnabled: snapshot.github.featuresEnabled,
|
|
3355
3611
|
error: snapshot.github.error
|
|
3356
3612
|
? {
|
|
@@ -3375,22 +3631,108 @@ export class Session {
|
|
|
3375
3631
|
});
|
|
3376
3632
|
}
|
|
3377
3633
|
}
|
|
3634
|
+
async handlePullRequestTimelineRequest(msg) {
|
|
3635
|
+
const { cwd, prNumber, repoOwner, repoName, requestId } = msg;
|
|
3636
|
+
if (!isValidPullRequestTimelineIdentity({ prNumber, repoOwner, repoName })) {
|
|
3637
|
+
this.emit({
|
|
3638
|
+
type: "pull_request_timeline_response",
|
|
3639
|
+
payload: {
|
|
3640
|
+
cwd,
|
|
3641
|
+
prNumber,
|
|
3642
|
+
items: [],
|
|
3643
|
+
truncated: false,
|
|
3644
|
+
error: {
|
|
3645
|
+
kind: "unknown",
|
|
3646
|
+
message: "Pull request timeline request has invalid PR identity",
|
|
3647
|
+
},
|
|
3648
|
+
requestId,
|
|
3649
|
+
githubFeaturesEnabled: true,
|
|
3650
|
+
},
|
|
3651
|
+
});
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
const githubFeaturesEnabled = await this.github.isAuthenticated({ cwd });
|
|
3655
|
+
if (!githubFeaturesEnabled) {
|
|
3656
|
+
this.emit({
|
|
3657
|
+
type: "pull_request_timeline_response",
|
|
3658
|
+
payload: {
|
|
3659
|
+
cwd,
|
|
3660
|
+
prNumber,
|
|
3661
|
+
items: [],
|
|
3662
|
+
truncated: false,
|
|
3663
|
+
error: {
|
|
3664
|
+
kind: "unknown",
|
|
3665
|
+
message: "GitHub CLI is unavailable or not authenticated",
|
|
3666
|
+
},
|
|
3667
|
+
requestId,
|
|
3668
|
+
githubFeaturesEnabled: false,
|
|
3669
|
+
},
|
|
3670
|
+
});
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
try {
|
|
3674
|
+
const timeline = await this.github.getPullRequestTimeline({
|
|
3675
|
+
cwd,
|
|
3676
|
+
prNumber,
|
|
3677
|
+
repoOwner,
|
|
3678
|
+
repoName,
|
|
3679
|
+
});
|
|
3680
|
+
this.emit({
|
|
3681
|
+
type: "pull_request_timeline_response",
|
|
3682
|
+
payload: {
|
|
3683
|
+
cwd,
|
|
3684
|
+
prNumber: timeline.prNumber,
|
|
3685
|
+
items: timeline.items.map(toPullRequestTimelinePayloadItem),
|
|
3686
|
+
truncated: timeline.truncated,
|
|
3687
|
+
error: timeline.error,
|
|
3688
|
+
requestId,
|
|
3689
|
+
githubFeaturesEnabled: true,
|
|
3690
|
+
},
|
|
3691
|
+
});
|
|
3692
|
+
}
|
|
3693
|
+
catch (error) {
|
|
3694
|
+
this.emit({
|
|
3695
|
+
type: "pull_request_timeline_response",
|
|
3696
|
+
payload: {
|
|
3697
|
+
cwd,
|
|
3698
|
+
prNumber,
|
|
3699
|
+
items: [],
|
|
3700
|
+
truncated: false,
|
|
3701
|
+
error: {
|
|
3702
|
+
kind: "unknown",
|
|
3703
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3704
|
+
},
|
|
3705
|
+
requestId,
|
|
3706
|
+
githubFeaturesEnabled: true,
|
|
3707
|
+
},
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3378
3711
|
async handlePaseoWorktreeListRequest(msg) {
|
|
3379
3712
|
return handleWorktreeListRequest({
|
|
3380
3713
|
emit: (message) => this.emit(message),
|
|
3381
3714
|
paseoHome: this.paseoHome,
|
|
3715
|
+
workspaceGitService: this.workspaceGitService,
|
|
3382
3716
|
}, msg);
|
|
3383
3717
|
}
|
|
3384
3718
|
async handlePaseoWorktreeArchiveRequest(msg) {
|
|
3385
3719
|
return handleWorktreeArchiveRequest({
|
|
3386
3720
|
paseoHome: this.paseoHome,
|
|
3721
|
+
github: this.github,
|
|
3722
|
+
workspaceGitService: this.workspaceGitService,
|
|
3387
3723
|
agentManager: this.agentManager,
|
|
3388
3724
|
agentStorage: this.agentStorage,
|
|
3389
|
-
archiveWorkspaceRecord: (
|
|
3725
|
+
archiveWorkspaceRecord: async (workspaceDirectory) => {
|
|
3726
|
+
const workspace = await this.findWorkspaceByDirectory(workspaceDirectory);
|
|
3727
|
+
if (workspace) {
|
|
3728
|
+
await this.archiveWorkspaceRecord(workspace.workspaceId);
|
|
3729
|
+
}
|
|
3730
|
+
},
|
|
3390
3731
|
emit: (message) => this.emit(message),
|
|
3391
3732
|
emitWorkspaceUpdatesForCwds: (cwds) => this.emitWorkspaceUpdatesForCwds(cwds),
|
|
3392
3733
|
isPathWithinRoot: (rootPath, candidatePath) => this.isPathWithinRoot(rootPath, candidatePath),
|
|
3393
3734
|
killTerminalsUnderPath: (rootPath) => this.killTerminalsUnderPath(rootPath),
|
|
3735
|
+
sessionLogger: this.sessionLogger,
|
|
3394
3736
|
}, msg);
|
|
3395
3737
|
}
|
|
3396
3738
|
/**
|
|
@@ -3578,6 +3920,7 @@ export class Session {
|
|
|
3578
3920
|
.filter((record) => !liveIds.has(record.id) && !record.internal)
|
|
3579
3921
|
.map((record) => this.buildStoredAgentPayload(record));
|
|
3580
3922
|
let agents = [...liveAgents, ...persistedAgents];
|
|
3923
|
+
agents = agents.filter((agent) => this.isProviderVisibleToClient(agent.provider));
|
|
3581
3924
|
// Filter by labels if filter provided
|
|
3582
3925
|
if (filter?.labels) {
|
|
3583
3926
|
const filterLabels = filter.labels;
|
|
@@ -3633,13 +3976,15 @@ export class Session {
|
|
|
3633
3976
|
async getAgentPayloadById(agentId) {
|
|
3634
3977
|
const live = this.agentManager.getAgent(agentId);
|
|
3635
3978
|
if (live) {
|
|
3636
|
-
|
|
3979
|
+
const payload = await this.buildAgentPayload(live);
|
|
3980
|
+
return this.isProviderVisibleToClient(payload.provider) ? payload : null;
|
|
3637
3981
|
}
|
|
3638
3982
|
const record = await this.agentStorage.get(agentId);
|
|
3639
3983
|
if (!record || record.internal) {
|
|
3640
3984
|
return null;
|
|
3641
3985
|
}
|
|
3642
|
-
|
|
3986
|
+
const payload = this.buildStoredAgentPayload(record);
|
|
3987
|
+
return this.isProviderVisibleToClient(payload.provider) ? payload : null;
|
|
3643
3988
|
}
|
|
3644
3989
|
normalizeFetchAgentsSort(sort) {
|
|
3645
3990
|
const fallback = [{ key: "updated_at", direction: "desc" }];
|
|
@@ -3795,19 +4140,51 @@ export class Session {
|
|
|
3795
4140
|
}
|
|
3796
4141
|
return agent.id.localeCompare(cursor.id);
|
|
3797
4142
|
}
|
|
4143
|
+
async buildActiveProjectPlacementsByWorkspaceCwd() {
|
|
4144
|
+
const [persistedWorkspaces, persistedProjects] = await Promise.all([
|
|
4145
|
+
this.workspaceRegistry.list(),
|
|
4146
|
+
this.projectRegistry.list(),
|
|
4147
|
+
]);
|
|
4148
|
+
const activeProjects = new Map(persistedProjects
|
|
4149
|
+
.filter((project) => !project.archivedAt)
|
|
4150
|
+
.map((project) => [project.projectId, project]));
|
|
4151
|
+
const placementsByCwd = new Map();
|
|
4152
|
+
for (const workspace of persistedWorkspaces) {
|
|
4153
|
+
if (workspace.archivedAt) {
|
|
4154
|
+
continue;
|
|
4155
|
+
}
|
|
4156
|
+
const project = activeProjects.get(workspace.projectId);
|
|
4157
|
+
if (!project) {
|
|
4158
|
+
continue;
|
|
4159
|
+
}
|
|
4160
|
+
placementsByCwd.set(normalizePersistedWorkspaceId(workspace.cwd), await this.buildProjectPlacementForWorkspace(workspace, project));
|
|
4161
|
+
}
|
|
4162
|
+
return placementsByCwd;
|
|
4163
|
+
}
|
|
3798
4164
|
async listFetchAgentsEntries(request) {
|
|
3799
|
-
const filter = request.
|
|
4165
|
+
const filter = request.type === "fetch_agent_history_request" &&
|
|
4166
|
+
request.filter?.includeArchived === undefined
|
|
4167
|
+
? { ...request.filter, includeArchived: true }
|
|
4168
|
+
: request.filter;
|
|
4169
|
+
const scope = request.type === "fetch_agents_request" ? request.scope : undefined;
|
|
3800
4170
|
const sort = this.normalizeFetchAgentsSort(request.sort);
|
|
3801
|
-
|
|
4171
|
+
let agents = await this.listAgentPayloads({
|
|
3802
4172
|
labels: filter?.labels,
|
|
3803
4173
|
});
|
|
4174
|
+
const activePlacementsByCwd = scope === "active" ? await this.buildActiveProjectPlacementsByWorkspaceCwd() : null;
|
|
4175
|
+
if (activePlacementsByCwd) {
|
|
4176
|
+
agents = agents.filter((agent) => !agent.archivedAt && activePlacementsByCwd.has(normalizePersistedWorkspaceId(agent.cwd)));
|
|
4177
|
+
}
|
|
3804
4178
|
const placementByCwd = new Map();
|
|
3805
4179
|
const getPlacement = (cwd) => {
|
|
4180
|
+
if (activePlacementsByCwd) {
|
|
4181
|
+
return Promise.resolve(activePlacementsByCwd.get(normalizePersistedWorkspaceId(cwd)) ?? null);
|
|
4182
|
+
}
|
|
3806
4183
|
const existing = placementByCwd.get(cwd);
|
|
3807
4184
|
if (existing) {
|
|
3808
4185
|
return existing;
|
|
3809
4186
|
}
|
|
3810
|
-
const placementPromise = this.
|
|
4187
|
+
const placementPromise = this.buildProjectPlacementForCwd(cwd);
|
|
3811
4188
|
placementByCwd.set(cwd, placementPromise);
|
|
3812
4189
|
return placementPromise;
|
|
3813
4190
|
};
|
|
@@ -3823,11 +4200,14 @@ export class Session {
|
|
|
3823
4200
|
const batchSize = 25;
|
|
3824
4201
|
for (let start = 0; start < candidates.length && matchedEntries.length <= limit; start += batchSize) {
|
|
3825
4202
|
const batch = candidates.slice(start, start + batchSize);
|
|
3826
|
-
const batchEntries = await Promise.all(batch.map(async (agent) =>
|
|
3827
|
-
agent
|
|
3828
|
-
project
|
|
3829
|
-
}))
|
|
4203
|
+
const batchEntries = await Promise.all(batch.map(async (agent) => {
|
|
4204
|
+
const project = await getPlacement(agent.cwd);
|
|
4205
|
+
return project ? { agent, project } : null;
|
|
4206
|
+
}));
|
|
3830
4207
|
for (const entry of batchEntries) {
|
|
4208
|
+
if (!entry) {
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
3831
4211
|
if (!this.matchesAgentFilter({
|
|
3832
4212
|
agent: entry.agent,
|
|
3833
4213
|
project: entry.project,
|
|
@@ -3873,17 +4253,34 @@ export class Session {
|
|
|
3873
4253
|
}
|
|
3874
4254
|
async describeWorkspaceRecord(workspace, projectRecord) {
|
|
3875
4255
|
const resolvedProjectRecord = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
|
|
4256
|
+
let diffStat = null;
|
|
4257
|
+
const snapshot = this.workspaceGitService.peekSnapshot(workspace.cwd);
|
|
4258
|
+
if (snapshot?.git.diffStat) {
|
|
4259
|
+
diffStat = snapshot.git.diffStat;
|
|
4260
|
+
}
|
|
3876
4261
|
return {
|
|
3877
4262
|
id: workspace.workspaceId,
|
|
3878
4263
|
projectId: workspace.projectId,
|
|
3879
|
-
projectDisplayName: resolvedProjectRecord?.displayName ?? workspace.projectId,
|
|
4264
|
+
projectDisplayName: resolvedProjectRecord?.displayName ?? String(workspace.projectId),
|
|
3880
4265
|
projectRootPath: resolvedProjectRecord?.rootPath ?? workspace.cwd,
|
|
3881
|
-
|
|
4266
|
+
workspaceDirectory: workspace.cwd,
|
|
4267
|
+
projectKind: (resolvedProjectRecord?.kind ?? "directory") === "git" ? "git" : "non_git",
|
|
3882
4268
|
workspaceKind: workspace.kind,
|
|
3883
4269
|
name: workspace.displayName,
|
|
3884
4270
|
status: "done",
|
|
3885
4271
|
activityAt: null,
|
|
3886
|
-
diffStat
|
|
4272
|
+
diffStat,
|
|
4273
|
+
scripts: this.scriptRouteStore && this.scriptRuntimeStore
|
|
4274
|
+
? buildWorkspaceScriptPayloads({
|
|
4275
|
+
workspaceId: workspace.workspaceId,
|
|
4276
|
+
workspaceDirectory: workspace.cwd,
|
|
4277
|
+
routeStore: this.scriptRouteStore,
|
|
4278
|
+
runtimeStore: this.scriptRuntimeStore,
|
|
4279
|
+
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
|
4280
|
+
gitMetadata: this.resolveWorkspaceScriptGitMetadata(workspace.cwd),
|
|
4281
|
+
resolveHealth: this.resolveScriptHealth ?? undefined,
|
|
4282
|
+
})
|
|
4283
|
+
: [],
|
|
3887
4284
|
};
|
|
3888
4285
|
}
|
|
3889
4286
|
buildWorkspaceGitRuntimePayload(snapshot) {
|
|
@@ -3905,17 +4302,12 @@ export class Session {
|
|
|
3905
4302
|
featuresEnabled: snapshot.github.featuresEnabled,
|
|
3906
4303
|
pullRequest: snapshot.github.pullRequest,
|
|
3907
4304
|
error: snapshot.github.error,
|
|
3908
|
-
refreshedAt: snapshot.github.refreshedAt,
|
|
3909
4305
|
};
|
|
3910
4306
|
}
|
|
3911
4307
|
async describeWorkspaceRecordWithGitData(workspace, projectRecord) {
|
|
3912
4308
|
const base = await this.describeWorkspaceRecord(workspace, projectRecord);
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
snapshot = await this.workspaceGitService.getSnapshot(workspace.cwd);
|
|
3916
|
-
}
|
|
3917
|
-
catch (error) {
|
|
3918
|
-
this.sessionLogger.warn({ err: error, cwd: workspace.cwd }, "Failed to load git snapshot for workspace");
|
|
4309
|
+
const snapshot = this.workspaceGitService.peekSnapshot(workspace.cwd);
|
|
4310
|
+
if (!snapshot) {
|
|
3919
4311
|
return base;
|
|
3920
4312
|
}
|
|
3921
4313
|
const checkout = checkoutLiteFromGitSnapshot(workspace.cwd, snapshot.git);
|
|
@@ -3924,10 +4316,37 @@ export class Session {
|
|
|
3924
4316
|
...base,
|
|
3925
4317
|
name: displayName,
|
|
3926
4318
|
diffStat: snapshot.git.diffStat ?? null,
|
|
3927
|
-
gitRuntime: this.buildWorkspaceGitRuntimePayload(snapshot),
|
|
4319
|
+
gitRuntime: this.buildWorkspaceGitRuntimePayload(snapshot) ?? undefined,
|
|
3928
4320
|
githubRuntime: this.buildWorkspaceGitHubRuntimePayload(snapshot),
|
|
3929
4321
|
};
|
|
3930
4322
|
}
|
|
4323
|
+
async describeCreatedWorktreeWorkspace(result) {
|
|
4324
|
+
const projectRecord = await this.projectRegistry.get(result.workspace.projectId);
|
|
4325
|
+
return {
|
|
4326
|
+
id: result.workspace.workspaceId,
|
|
4327
|
+
projectId: result.workspace.projectId,
|
|
4328
|
+
projectDisplayName: projectRecord?.displayName ?? String(result.workspace.projectId),
|
|
4329
|
+
projectRootPath: projectRecord?.rootPath ?? result.repoRoot,
|
|
4330
|
+
workspaceDirectory: result.workspace.cwd,
|
|
4331
|
+
projectKind: "git",
|
|
4332
|
+
workspaceKind: result.workspace.kind,
|
|
4333
|
+
name: result.worktree.branchName || result.workspace.displayName,
|
|
4334
|
+
status: "done",
|
|
4335
|
+
activityAt: null,
|
|
4336
|
+
diffStat: { additions: 0, deletions: 0 },
|
|
4337
|
+
scripts: [],
|
|
4338
|
+
gitRuntime: {
|
|
4339
|
+
currentBranch: result.worktree.branchName || null,
|
|
4340
|
+
remoteUrl: null,
|
|
4341
|
+
isPaseoOwnedWorktree: true,
|
|
4342
|
+
isDirty: false,
|
|
4343
|
+
aheadBehind: null,
|
|
4344
|
+
aheadOfOrigin: null,
|
|
4345
|
+
behindOfOrigin: null,
|
|
4346
|
+
},
|
|
4347
|
+
githubRuntime: null,
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
3931
4350
|
async buildWorkspaceDescriptor(input) {
|
|
3932
4351
|
if (input.includeGitData && input.projectRecord?.kind === "git") {
|
|
3933
4352
|
return this.describeWorkspaceRecordWithGitData(input.workspace, input.projectRecord);
|
|
@@ -3940,14 +4359,14 @@ export class Session {
|
|
|
3940
4359
|
this.workspaceRegistry.list(),
|
|
3941
4360
|
this.projectRegistry.list(),
|
|
3942
4361
|
]);
|
|
3943
|
-
const activeRecords = persistedWorkspaces.filter((workspace) => !workspace.archivedAt);
|
|
3944
4362
|
const activeProjects = new Map(persistedProjects
|
|
3945
4363
|
.filter((project) => !project.archivedAt)
|
|
3946
4364
|
.map((project) => [project.projectId, project]));
|
|
4365
|
+
const archivedProjectIds = new Set(persistedProjects.filter((project) => project.archivedAt).map((project) => project.projectId));
|
|
4366
|
+
const activeRecords = persistedWorkspaces.filter((workspace) => !workspace.archivedAt && !archivedProjectIds.has(workspace.projectId));
|
|
3947
4367
|
const descriptorsByWorkspaceId = new Map();
|
|
3948
|
-
const workspaceIds = options.workspaceIds
|
|
3949
|
-
|
|
3950
|
-
: null;
|
|
4368
|
+
const workspaceIds = options.workspaceIds ? new Set(options.workspaceIds) : null;
|
|
4369
|
+
const workspaceIdsByDirectory = new Map(activeRecords.map((workspace) => [normalizePersistedWorkspaceId(workspace.cwd), workspace.workspaceId]));
|
|
3951
4370
|
for (const workspace of activeRecords) {
|
|
3952
4371
|
if (workspaceIds && !workspaceIds.has(workspace.workspaceId)) {
|
|
3953
4372
|
continue;
|
|
@@ -3963,7 +4382,13 @@ export class Session {
|
|
|
3963
4382
|
if (agent.archivedAt) {
|
|
3964
4383
|
continue;
|
|
3965
4384
|
}
|
|
3966
|
-
|
|
4385
|
+
if (!this.isProviderVisibleToClient(agent.provider)) {
|
|
4386
|
+
continue;
|
|
4387
|
+
}
|
|
4388
|
+
const workspaceId = workspaceIdsByDirectory.get(normalizePersistedWorkspaceId(agent.cwd));
|
|
4389
|
+
if (workspaceId === undefined) {
|
|
4390
|
+
continue;
|
|
4391
|
+
}
|
|
3967
4392
|
const existing = descriptorsByWorkspaceId.get(workspaceId);
|
|
3968
4393
|
if (!existing) {
|
|
3969
4394
|
continue;
|
|
@@ -3977,19 +4402,17 @@ export class Session {
|
|
|
3977
4402
|
}
|
|
3978
4403
|
resolveRegisteredWorkspaceIdForCwd(cwd, workspaces) {
|
|
3979
4404
|
const normalizedCwd = normalizePersistedWorkspaceId(cwd);
|
|
3980
|
-
const exact = workspaces.find((workspace) => workspace.
|
|
4405
|
+
const exact = workspaces.find((workspace) => workspace.cwd === normalizedCwd);
|
|
3981
4406
|
if (exact) {
|
|
3982
4407
|
return exact.workspaceId;
|
|
3983
4408
|
}
|
|
3984
4409
|
let bestMatch = null;
|
|
3985
4410
|
for (const workspace of workspaces) {
|
|
3986
|
-
const prefix = workspace.
|
|
3987
|
-
? workspace.workspaceId
|
|
3988
|
-
: `${workspace.workspaceId}${sep}`;
|
|
4411
|
+
const prefix = workspace.cwd.endsWith(sep) ? workspace.cwd : `${workspace.cwd}${sep}`;
|
|
3989
4412
|
if (!normalizedCwd.startsWith(prefix)) {
|
|
3990
4413
|
continue;
|
|
3991
4414
|
}
|
|
3992
|
-
if (!bestMatch || workspace.
|
|
4415
|
+
if (!bestMatch || workspace.cwd.length > bestMatch.cwd.length) {
|
|
3993
4416
|
bestMatch = workspace;
|
|
3994
4417
|
}
|
|
3995
4418
|
}
|
|
@@ -4095,7 +4518,7 @@ export class Session {
|
|
|
4095
4518
|
return {
|
|
4096
4519
|
sort: cursorSort,
|
|
4097
4520
|
values: payload.values,
|
|
4098
|
-
id: payload.id,
|
|
4521
|
+
id: String(payload.id),
|
|
4099
4522
|
};
|
|
4100
4523
|
}
|
|
4101
4524
|
compareWorkspaceWithCursor(workspace, cursor, sort) {
|
|
@@ -4121,13 +4544,13 @@ export class Session {
|
|
|
4121
4544
|
}
|
|
4122
4545
|
}
|
|
4123
4546
|
if (filter.idPrefix && filter.idPrefix.trim().length > 0) {
|
|
4124
|
-
if (!workspace.id.startsWith(filter.idPrefix.trim())) {
|
|
4547
|
+
if (!String(workspace.id).startsWith(filter.idPrefix.trim())) {
|
|
4125
4548
|
return false;
|
|
4126
4549
|
}
|
|
4127
4550
|
}
|
|
4128
4551
|
if (filter.query && filter.query.trim().length > 0) {
|
|
4129
4552
|
const query = filter.query.trim().toLocaleLowerCase();
|
|
4130
|
-
const haystacks = [workspace.name, workspace.projectId, workspace.id];
|
|
4553
|
+
const haystacks = [workspace.name, String(workspace.projectId), String(workspace.id)];
|
|
4131
4554
|
if (!haystacks.some((value) => value.toLocaleLowerCase().includes(query))) {
|
|
4132
4555
|
return false;
|
|
4133
4556
|
}
|
|
@@ -4213,32 +4636,95 @@ export class Session {
|
|
|
4213
4636
|
});
|
|
4214
4637
|
}
|
|
4215
4638
|
}
|
|
4216
|
-
async
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4639
|
+
async findOrCreateWorkspaceForDirectory(cwd) {
|
|
4640
|
+
const normalizedCwd = await this.resolveWorkspaceDirectory(cwd);
|
|
4641
|
+
const existingWorkspace = await this.findWorkspaceByDirectory(normalizedCwd);
|
|
4642
|
+
if (existingWorkspace) {
|
|
4643
|
+
return this.ensureWorkspaceRecordUnarchived(existingWorkspace);
|
|
4644
|
+
}
|
|
4645
|
+
const placement = await buildProjectPlacementForCwdStandalone({
|
|
4646
|
+
cwd: normalizedCwd,
|
|
4647
|
+
workspaceGitService: this.workspaceGitService,
|
|
4648
|
+
});
|
|
4649
|
+
const workspaceId = deriveWorkspaceId(normalizedCwd, placement.checkout);
|
|
4650
|
+
const timestamp = new Date().toISOString();
|
|
4651
|
+
const projectRecord = createPersistedProjectRecord({
|
|
4652
|
+
projectId: placement.projectKey,
|
|
4653
|
+
rootPath: deriveProjectRootPath({ cwd: normalizedCwd, checkout: placement.checkout }),
|
|
4654
|
+
kind: deriveProjectKind(placement.checkout),
|
|
4655
|
+
displayName: placement.projectName,
|
|
4656
|
+
createdAt: timestamp,
|
|
4657
|
+
updatedAt: timestamp,
|
|
4658
|
+
});
|
|
4659
|
+
await this.projectRegistry.upsert(projectRecord);
|
|
4660
|
+
const workspaceRecord = createPersistedWorkspaceRecord({
|
|
4661
|
+
workspaceId,
|
|
4662
|
+
projectId: placement.projectKey,
|
|
4663
|
+
cwd: normalizedCwd,
|
|
4664
|
+
kind: deriveWorkspaceKind(placement.checkout),
|
|
4665
|
+
displayName: deriveWorkspaceDisplayName({
|
|
4666
|
+
cwd: normalizedCwd,
|
|
4667
|
+
checkout: placement.checkout,
|
|
4668
|
+
}),
|
|
4669
|
+
createdAt: timestamp,
|
|
4670
|
+
updatedAt: timestamp,
|
|
4671
|
+
});
|
|
4672
|
+
await this.workspaceRegistry.upsert(workspaceRecord);
|
|
4673
|
+
return workspaceRecord;
|
|
4674
|
+
}
|
|
4675
|
+
async ensureWorkspaceRecordUnarchived(workspace) {
|
|
4676
|
+
const project = await this.projectRegistry.get(workspace.projectId);
|
|
4677
|
+
if (!workspace.archivedAt && (!project || !project.archivedAt)) {
|
|
4678
|
+
return workspace;
|
|
4679
|
+
}
|
|
4680
|
+
const timestamp = new Date().toISOString();
|
|
4681
|
+
let unarchivedWorkspace = workspace;
|
|
4682
|
+
if (workspace.archivedAt) {
|
|
4683
|
+
unarchivedWorkspace = { ...workspace, archivedAt: null, updatedAt: timestamp };
|
|
4684
|
+
await this.workspaceRegistry.upsert(unarchivedWorkspace);
|
|
4685
|
+
}
|
|
4686
|
+
if (project?.archivedAt) {
|
|
4687
|
+
await this.projectRegistry.upsert({
|
|
4688
|
+
...project,
|
|
4689
|
+
archivedAt: null,
|
|
4690
|
+
updatedAt: timestamp,
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
4693
|
+
return unarchivedWorkspace;
|
|
4694
|
+
}
|
|
4695
|
+
async createPaseoWorktree(input, options) {
|
|
4696
|
+
const coreDeps = createWorktreeCoreDeps(this.github);
|
|
4697
|
+
const result = await createPaseoWorktree(input, {
|
|
4698
|
+
...coreDeps,
|
|
4699
|
+
...(options?.resolveDefaultBranch
|
|
4700
|
+
? { resolveDefaultBranch: options.resolveDefaultBranch }
|
|
4701
|
+
: {}),
|
|
4224
4702
|
projectRegistry: this.projectRegistry,
|
|
4225
4703
|
workspaceRegistry: this.workspaceRegistry,
|
|
4226
|
-
|
|
4227
|
-
}
|
|
4704
|
+
workspaceGitService: this.workspaceGitService,
|
|
4705
|
+
});
|
|
4706
|
+
void Promise.all([
|
|
4707
|
+
this.notifyGitMutation(input.cwd, "create-worktree"),
|
|
4708
|
+
this.notifyGitMutation(result.worktree.worktreePath, "create-worktree"),
|
|
4709
|
+
]).catch((error) => {
|
|
4710
|
+
this.sessionLogger.warn({ err: error, cwd: input.cwd, worktreePath: result.worktree.worktreePath }, "Failed to warm git snapshots after creating worktree");
|
|
4711
|
+
});
|
|
4712
|
+
return result;
|
|
4228
4713
|
}
|
|
4229
4714
|
async archiveWorkspaceRecord(workspaceId, archivedAt) {
|
|
4230
|
-
const
|
|
4231
|
-
|
|
4715
|
+
const existingWorkspace = await archivePersistedWorkspaceRecord({
|
|
4716
|
+
workspaceId,
|
|
4717
|
+
archivedAt,
|
|
4718
|
+
workspaceRegistry: this.workspaceRegistry,
|
|
4719
|
+
projectRegistry: this.projectRegistry,
|
|
4720
|
+
});
|
|
4721
|
+
if (!existingWorkspace) {
|
|
4232
4722
|
this.removeWorkspaceGitSubscription(workspaceId);
|
|
4233
4723
|
return;
|
|
4234
4724
|
}
|
|
4235
|
-
|
|
4236
|
-
|
|
4725
|
+
await this.removeWorkspaceGitWatchTarget(existingWorkspace.cwd);
|
|
4726
|
+
this.scriptRuntimeStore?.removeForWorkspace(existingWorkspace.cwd);
|
|
4237
4727
|
this.removeWorkspaceGitSubscription(workspaceId);
|
|
4238
|
-
const siblingWorkspaces = (await this.workspaceRegistry.list()).filter((workspace) => workspace.projectId === existing.projectId && !workspace.archivedAt);
|
|
4239
|
-
if (siblingWorkspaces.length === 0) {
|
|
4240
|
-
await this.projectRegistry.archive(existing.projectId, nextArchivedAt);
|
|
4241
|
-
}
|
|
4242
4728
|
}
|
|
4243
4729
|
async reconcileAndEmitWorkspaceUpdates() {
|
|
4244
4730
|
if (!this.workspaceUpdatesSubscription) {
|
|
@@ -4257,12 +4743,48 @@ export class Session {
|
|
|
4257
4743
|
this.sessionLogger.error({ err: error }, "Background workspace reconciliation failed");
|
|
4258
4744
|
}
|
|
4259
4745
|
}
|
|
4746
|
+
async reconcileActiveWorkspaceRecords() {
|
|
4747
|
+
const service = new WorkspaceReconciliationService({
|
|
4748
|
+
projectRegistry: this.projectRegistry,
|
|
4749
|
+
workspaceRegistry: this.workspaceRegistry,
|
|
4750
|
+
logger: this.sessionLogger,
|
|
4751
|
+
workspaceGitService: this.workspaceGitService,
|
|
4752
|
+
});
|
|
4753
|
+
const result = await service.runOnce();
|
|
4754
|
+
const changedWorkspaceIds = new Set();
|
|
4755
|
+
const changedProjectIds = new Set();
|
|
4756
|
+
for (const change of result.changesApplied) {
|
|
4757
|
+
switch (change.kind) {
|
|
4758
|
+
case "workspace_archived":
|
|
4759
|
+
await this.removeWorkspaceGitWatchTarget(change.directory);
|
|
4760
|
+
this.scriptRuntimeStore?.removeForWorkspace(change.directory);
|
|
4761
|
+
this.removeWorkspaceGitSubscription(change.workspaceId);
|
|
4762
|
+
changedWorkspaceIds.add(change.workspaceId);
|
|
4763
|
+
break;
|
|
4764
|
+
case "workspace_updated":
|
|
4765
|
+
changedWorkspaceIds.add(change.workspaceId);
|
|
4766
|
+
break;
|
|
4767
|
+
case "project_archived":
|
|
4768
|
+
case "project_updated":
|
|
4769
|
+
changedProjectIds.add(change.projectId);
|
|
4770
|
+
break;
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
if (changedProjectIds.size > 0) {
|
|
4774
|
+
for (const workspace of await this.workspaceRegistry.list()) {
|
|
4775
|
+
if (changedProjectIds.has(workspace.projectId)) {
|
|
4776
|
+
changedWorkspaceIds.add(workspace.workspaceId);
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
return changedWorkspaceIds;
|
|
4781
|
+
}
|
|
4260
4782
|
async emitWorkspaceUpdatesForWorkspaceIds(workspaceIds, options) {
|
|
4261
4783
|
const subscription = this.workspaceUpdatesSubscription;
|
|
4262
4784
|
if (!subscription) {
|
|
4263
4785
|
return;
|
|
4264
4786
|
}
|
|
4265
|
-
const uniqueWorkspaceIds = new Set(Array.from(workspaceIds
|
|
4787
|
+
const uniqueWorkspaceIds = new Set(Array.from(workspaceIds));
|
|
4266
4788
|
if (uniqueWorkspaceIds.size === 0) {
|
|
4267
4789
|
return;
|
|
4268
4790
|
}
|
|
@@ -4275,6 +4797,18 @@ export class Session {
|
|
|
4275
4797
|
const nextWorkspace = workspace && this.matchesWorkspaceFilter({ workspace, filter: subscription.filter })
|
|
4276
4798
|
? workspace
|
|
4277
4799
|
: null;
|
|
4800
|
+
if (options?.dedupeGitState &&
|
|
4801
|
+
this.shouldSkipWorkspaceGitWatchUpdate(workspaceId, nextWorkspace)) {
|
|
4802
|
+
continue;
|
|
4803
|
+
}
|
|
4804
|
+
const watchTarget = this.workspaceGitWatchTargets.get(workspaceId);
|
|
4805
|
+
if (watchTarget && this.onBranchChanged) {
|
|
4806
|
+
const newBranchName = nextWorkspace?.name ?? null;
|
|
4807
|
+
if (newBranchName !== watchTarget.lastBranchName) {
|
|
4808
|
+
this.onBranchChanged(workspaceId, watchTarget.lastBranchName, newBranchName);
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
this.rememberWorkspaceGitWatchFingerprint(workspaceId, nextWorkspace);
|
|
4278
4812
|
if (!nextWorkspace) {
|
|
4279
4813
|
subscription.lastEmittedByWorkspaceId.delete(workspaceId);
|
|
4280
4814
|
this.bufferOrEmitWorkspaceUpdate(subscription, {
|
|
@@ -4300,15 +4834,15 @@ export class Session {
|
|
|
4300
4834
|
}
|
|
4301
4835
|
}
|
|
4302
4836
|
async emitWorkspaceUpdateForCwd(cwd, options) {
|
|
4303
|
-
const
|
|
4304
|
-
const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(cwd,
|
|
4837
|
+
const workspaces = await this.workspaceRegistry.list();
|
|
4838
|
+
const workspaceId = this.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces);
|
|
4305
4839
|
await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], options);
|
|
4306
4840
|
}
|
|
4307
4841
|
async emitWorkspaceUpdatesForCwds(cwds) {
|
|
4308
|
-
const
|
|
4842
|
+
const workspaces = await this.workspaceRegistry.list();
|
|
4309
4843
|
const uniqueWorkspaceIds = new Set();
|
|
4310
4844
|
for (const cwd of cwds) {
|
|
4311
|
-
uniqueWorkspaceIds.add(this.resolveRegisteredWorkspaceIdForCwd(cwd,
|
|
4845
|
+
uniqueWorkspaceIds.add(this.resolveRegisteredWorkspaceIdForCwd(cwd, workspaces));
|
|
4312
4846
|
}
|
|
4313
4847
|
await this.emitWorkspaceUpdatesForWorkspaceIds(uniqueWorkspaceIds);
|
|
4314
4848
|
}
|
|
@@ -4329,8 +4863,6 @@ export class Session {
|
|
|
4329
4863
|
};
|
|
4330
4864
|
}
|
|
4331
4865
|
const payload = await this.listFetchAgentsEntries(request);
|
|
4332
|
-
// TODO: Remove once all app store clients are on >=0.1.45.
|
|
4333
|
-
payload.entries = payload.entries.filter((entry) => this.isProviderVisibleToClient(entry.agent.provider));
|
|
4334
4866
|
const snapshotUpdatedAtByAgentId = new Map();
|
|
4335
4867
|
for (const entry of payload.entries) {
|
|
4336
4868
|
const parsedUpdatedAt = Date.parse(entry.agent.updatedAt);
|
|
@@ -4368,6 +4900,32 @@ export class Session {
|
|
|
4368
4900
|
});
|
|
4369
4901
|
}
|
|
4370
4902
|
}
|
|
4903
|
+
async handleFetchAgentHistory(request) {
|
|
4904
|
+
try {
|
|
4905
|
+
const payload = await this.listFetchAgentsEntries(request);
|
|
4906
|
+
this.emit({
|
|
4907
|
+
type: "fetch_agent_history_response",
|
|
4908
|
+
payload: {
|
|
4909
|
+
requestId: request.requestId,
|
|
4910
|
+
...payload,
|
|
4911
|
+
},
|
|
4912
|
+
});
|
|
4913
|
+
}
|
|
4914
|
+
catch (error) {
|
|
4915
|
+
const code = error instanceof SessionRequestError ? error.code : "fetch_agent_history_failed";
|
|
4916
|
+
const message = error instanceof Error ? error.message : "Failed to fetch agent history";
|
|
4917
|
+
this.sessionLogger.error({ err: error }, "Failed to handle fetch_agent_history_request");
|
|
4918
|
+
this.emit({
|
|
4919
|
+
type: "rpc_error",
|
|
4920
|
+
payload: {
|
|
4921
|
+
requestId: request.requestId,
|
|
4922
|
+
requestType: request.type,
|
|
4923
|
+
error: message,
|
|
4924
|
+
code,
|
|
4925
|
+
},
|
|
4926
|
+
});
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4371
4929
|
async handleFetchWorkspacesRequest(request) {
|
|
4372
4930
|
const requestedSubscriptionId = request.subscribe?.subscriptionId?.trim();
|
|
4373
4931
|
const subscriptionId = request.subscribe
|
|
@@ -4393,6 +4951,7 @@ export class Session {
|
|
|
4393
4951
|
};
|
|
4394
4952
|
}
|
|
4395
4953
|
const payload = await this.listFetchWorkspacesEntries(request);
|
|
4954
|
+
await this.primeWorkspaceGitWatchFingerprints(payload.entries);
|
|
4396
4955
|
this.sessionLogger.debug({
|
|
4397
4956
|
requestId: request.requestId,
|
|
4398
4957
|
subscriptionId,
|
|
@@ -4441,10 +5000,8 @@ export class Session {
|
|
|
4441
5000
|
}
|
|
4442
5001
|
async handleOpenProjectRequest(request) {
|
|
4443
5002
|
try {
|
|
4444
|
-
const workspace = await this.
|
|
4445
|
-
await this.emitWorkspaceUpdateForCwd(workspace.cwd
|
|
4446
|
-
skipReconcile: true,
|
|
4447
|
-
});
|
|
5003
|
+
const workspace = await this.findOrCreateWorkspaceForDirectory(request.cwd);
|
|
5004
|
+
await this.emitWorkspaceUpdateForCwd(workspace.cwd);
|
|
4448
5005
|
const descriptor = await this.describeWorkspaceRecordWithGitData(workspace);
|
|
4449
5006
|
this.emit({
|
|
4450
5007
|
type: "open_project_response",
|
|
@@ -4468,12 +5025,105 @@ export class Session {
|
|
|
4468
5025
|
});
|
|
4469
5026
|
}
|
|
4470
5027
|
}
|
|
5028
|
+
buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory) {
|
|
5029
|
+
if (!this.scriptRouteStore || !this.scriptRuntimeStore) {
|
|
5030
|
+
return [];
|
|
5031
|
+
}
|
|
5032
|
+
return buildWorkspaceScriptPayloads({
|
|
5033
|
+
workspaceId,
|
|
5034
|
+
workspaceDirectory,
|
|
5035
|
+
routeStore: this.scriptRouteStore,
|
|
5036
|
+
runtimeStore: this.scriptRuntimeStore,
|
|
5037
|
+
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
|
5038
|
+
gitMetadata: this.resolveWorkspaceScriptGitMetadata(workspaceDirectory),
|
|
5039
|
+
resolveHealth: this.resolveScriptHealth ?? undefined,
|
|
5040
|
+
});
|
|
5041
|
+
}
|
|
5042
|
+
resolveWorkspaceScriptGitMetadata(workspaceDirectory) {
|
|
5043
|
+
const snapshot = this.workspaceGitService.peekSnapshot(workspaceDirectory);
|
|
5044
|
+
if (!snapshot) {
|
|
5045
|
+
return undefined;
|
|
5046
|
+
}
|
|
5047
|
+
return {
|
|
5048
|
+
projectSlug: deriveProjectSlug(workspaceDirectory, snapshot.git.isGit ? snapshot.git.remoteUrl : null),
|
|
5049
|
+
currentBranch: snapshot.git.currentBranch,
|
|
5050
|
+
};
|
|
5051
|
+
}
|
|
5052
|
+
emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory) {
|
|
5053
|
+
this.emit({
|
|
5054
|
+
type: "script_status_update",
|
|
5055
|
+
payload: {
|
|
5056
|
+
workspaceId,
|
|
5057
|
+
scripts: this.buildWorkspaceScriptPayloadSnapshot(workspaceId, workspaceDirectory),
|
|
5058
|
+
},
|
|
5059
|
+
});
|
|
5060
|
+
}
|
|
5061
|
+
async resolveAvailableEditorTargets() {
|
|
5062
|
+
return listAvailableEditorTargets();
|
|
5063
|
+
}
|
|
4471
5064
|
async getAvailableEditorTargets() {
|
|
4472
|
-
return this.filterEditorsForClient(await
|
|
5065
|
+
return this.filterEditorsForClient(await this.getMemoizedAvailableEditorTargets());
|
|
4473
5066
|
}
|
|
4474
5067
|
async openEditorTarget(options) {
|
|
4475
5068
|
await openInEditorTarget(options);
|
|
4476
5069
|
}
|
|
5070
|
+
async handleStartWorkspaceScriptRequest(request) {
|
|
5071
|
+
try {
|
|
5072
|
+
if (!this.terminalManager || !this.scriptRouteStore || !this.scriptRuntimeStore) {
|
|
5073
|
+
throw new Error("Workspace scripts are not available on this daemon");
|
|
5074
|
+
}
|
|
5075
|
+
const workspace = await this.workspaceRegistry.get(request.workspaceId);
|
|
5076
|
+
if (!workspace) {
|
|
5077
|
+
throw new Error(`Workspace not found: ${request.workspaceId}`);
|
|
5078
|
+
}
|
|
5079
|
+
const gitMetadata = await this.workspaceGitService.getWorkspaceGitMetadata(workspace.cwd);
|
|
5080
|
+
const serviceResult = await spawnWorkspaceScript({
|
|
5081
|
+
repoRoot: workspace.cwd,
|
|
5082
|
+
workspaceId: workspace.workspaceId,
|
|
5083
|
+
projectSlug: gitMetadata.projectSlug,
|
|
5084
|
+
branchName: gitMetadata.currentBranch,
|
|
5085
|
+
scriptName: request.scriptName,
|
|
5086
|
+
daemonPort: this.getDaemonTcpPort?.() ?? null,
|
|
5087
|
+
daemonListenHost: this.getDaemonTcpHost?.() ?? null,
|
|
5088
|
+
routeStore: this.scriptRouteStore,
|
|
5089
|
+
runtimeStore: this.scriptRuntimeStore,
|
|
5090
|
+
terminalManager: this.terminalManager,
|
|
5091
|
+
logger: this.sessionLogger,
|
|
5092
|
+
onLifecycleChanged: () => {
|
|
5093
|
+
this.emitWorkspaceScriptStatusUpdate(workspace.workspaceId, workspace.cwd);
|
|
5094
|
+
},
|
|
5095
|
+
});
|
|
5096
|
+
this.emitWorkspaceScriptStatusUpdate(workspace.workspaceId, workspace.cwd);
|
|
5097
|
+
this.emit({
|
|
5098
|
+
type: "start_workspace_script_response",
|
|
5099
|
+
payload: {
|
|
5100
|
+
requestId: request.requestId,
|
|
5101
|
+
workspaceId: request.workspaceId,
|
|
5102
|
+
scriptName: request.scriptName,
|
|
5103
|
+
terminalId: serviceResult.terminalId,
|
|
5104
|
+
error: null,
|
|
5105
|
+
},
|
|
5106
|
+
});
|
|
5107
|
+
}
|
|
5108
|
+
catch (error) {
|
|
5109
|
+
const message = error instanceof Error ? error.message : "Failed to start workspace script";
|
|
5110
|
+
this.sessionLogger.error({
|
|
5111
|
+
err: error,
|
|
5112
|
+
workspaceId: request.workspaceId,
|
|
5113
|
+
scriptName: request.scriptName,
|
|
5114
|
+
}, "Failed to start workspace script");
|
|
5115
|
+
this.emit({
|
|
5116
|
+
type: "start_workspace_script_response",
|
|
5117
|
+
payload: {
|
|
5118
|
+
requestId: request.requestId,
|
|
5119
|
+
workspaceId: request.workspaceId,
|
|
5120
|
+
scriptName: request.scriptName,
|
|
5121
|
+
terminalId: null,
|
|
5122
|
+
error: message,
|
|
5123
|
+
},
|
|
5124
|
+
});
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
4477
5127
|
async handleListAvailableEditorsRequest(request) {
|
|
4478
5128
|
try {
|
|
4479
5129
|
const editors = await this.getAvailableEditorTargets();
|
|
@@ -4530,11 +5180,10 @@ export class Session {
|
|
|
4530
5180
|
async handleCreatePaseoWorktreeRequest(request) {
|
|
4531
5181
|
return handleCreateWorktreeRequest({
|
|
4532
5182
|
paseoHome: this.paseoHome,
|
|
4533
|
-
|
|
4534
|
-
describeWorkspaceRecord: (workspace) => this.describeWorkspaceRecordWithGitData(workspace),
|
|
5183
|
+
describeWorkspaceRecord: (result) => this.describeCreatedWorktreeWorkspace(result),
|
|
4535
5184
|
emit: (message) => this.emit(message),
|
|
4536
|
-
|
|
4537
|
-
|
|
5185
|
+
createPaseoWorktree: (input) => this.createPaseoWorktree(input),
|
|
5186
|
+
warmWorkspaceGitData: (workspace) => this.warmWorkspaceGitDataForWorkspace(workspace),
|
|
4538
5187
|
sessionLogger: this.sessionLogger,
|
|
4539
5188
|
runWorktreeSetupInBackground: (options) => this.runWorktreeSetupInBackground(options),
|
|
4540
5189
|
}, request);
|
|
@@ -4542,11 +5191,29 @@ export class Session {
|
|
|
4542
5191
|
async runWorktreeSetupInBackground(options) {
|
|
4543
5192
|
return runWorktreeSetupInBackgroundSession({
|
|
4544
5193
|
paseoHome: this.paseoHome,
|
|
4545
|
-
emitWorkspaceUpdateForCwd: (cwd) => this.emitWorkspaceUpdateForCwd(cwd),
|
|
5194
|
+
emitWorkspaceUpdateForCwd: (cwd, emitOptions) => this.emitWorkspaceUpdateForCwd(cwd, emitOptions),
|
|
5195
|
+
cacheWorkspaceSetupSnapshot: (workspaceId, snapshot) => {
|
|
5196
|
+
this.workspaceSetupSnapshots.set(workspaceId, snapshot);
|
|
5197
|
+
},
|
|
5198
|
+
emit: (message) => this.emit(message),
|
|
4546
5199
|
sessionLogger: this.sessionLogger,
|
|
4547
5200
|
terminalManager: this.terminalManager,
|
|
5201
|
+
archiveWorkspaceRecord: (workspaceId) => this.archiveWorkspaceRecord(workspaceId),
|
|
5202
|
+
scriptRouteStore: this.scriptRouteStore,
|
|
5203
|
+
scriptRuntimeStore: this.scriptRuntimeStore,
|
|
5204
|
+
getDaemonTcpPort: this.getDaemonTcpPort,
|
|
5205
|
+
getDaemonTcpHost: this.getDaemonTcpHost,
|
|
5206
|
+
onScriptsChanged: (workspaceId, workspaceDirectory) => {
|
|
5207
|
+
this.emitWorkspaceScriptStatusUpdate(workspaceId, workspaceDirectory);
|
|
5208
|
+
},
|
|
4548
5209
|
}, options);
|
|
4549
5210
|
}
|
|
5211
|
+
async handleWorkspaceSetupStatusRequest(request) {
|
|
5212
|
+
return handleWorkspaceSetupStatusRequestMessage({
|
|
5213
|
+
emit: (message) => this.emit(message),
|
|
5214
|
+
workspaceSetupSnapshots: this.workspaceSetupSnapshots,
|
|
5215
|
+
}, request);
|
|
5216
|
+
}
|
|
4550
5217
|
async handleArchiveWorkspaceRequest(request) {
|
|
4551
5218
|
try {
|
|
4552
5219
|
const existing = await this.workspaceRegistry.get(request.workspaceId);
|
|
@@ -4557,7 +5224,7 @@ export class Session {
|
|
|
4557
5224
|
throw new Error("Use worktree archive for Paseo worktrees");
|
|
4558
5225
|
}
|
|
4559
5226
|
const archivedAt = new Date().toISOString();
|
|
4560
|
-
await this.archiveWorkspaceRecord(
|
|
5227
|
+
await this.archiveWorkspaceRecord(existing.workspaceId, archivedAt);
|
|
4561
5228
|
await this.emitWorkspaceUpdateForCwd(existing.cwd);
|
|
4562
5229
|
this.emit({
|
|
4563
5230
|
type: "archive_workspace_response",
|
|
@@ -4605,7 +5272,7 @@ export class Session {
|
|
|
4605
5272
|
});
|
|
4606
5273
|
return;
|
|
4607
5274
|
}
|
|
4608
|
-
const project = await this.
|
|
5275
|
+
const project = await this.buildProjectPlacementForCwd(agent.cwd);
|
|
4609
5276
|
this.emit({
|
|
4610
5277
|
type: "fetch_agent_response",
|
|
4611
5278
|
payload: { requestId, agent, project, error: null },
|
|
@@ -4764,7 +5431,7 @@ export class Session {
|
|
|
4764
5431
|
}
|
|
4765
5432
|
try {
|
|
4766
5433
|
const agentId = resolved.agentId;
|
|
4767
|
-
const prompt = this.buildAgentPrompt(msg.text, msg.images);
|
|
5434
|
+
const prompt = this.buildAgentPrompt(msg.text, msg.images, msg.attachments);
|
|
4768
5435
|
this.sessionLogger.trace({ agentId, messageId: msg.messageId, textPrefix: msg.text.slice(0, 80) }, "send_agent_message_request: dispatching shared sendPromptToAgent");
|
|
4769
5436
|
try {
|
|
4770
5437
|
await sendPromptToAgent({
|
|
@@ -5232,9 +5899,7 @@ export class Session {
|
|
|
5232
5899
|
await this.flushPendingAudioSegments("no active voice agent");
|
|
5233
5900
|
return;
|
|
5234
5901
|
}
|
|
5235
|
-
await this.handleSendAgentMessage(agentId, result.text, undefined, undefined, undefined, {
|
|
5236
|
-
spokenInput: true,
|
|
5237
|
-
});
|
|
5902
|
+
await this.handleSendAgentMessage(agentId, result.text, undefined, undefined, undefined, undefined, { spokenInput: true });
|
|
5238
5903
|
await this.flushPendingAudioSegments("transcription complete");
|
|
5239
5904
|
}
|
|
5240
5905
|
registerVoiceBridgeForAgent(agentId) {
|
|
@@ -5492,9 +6157,9 @@ export class Session {
|
|
|
5492
6157
|
}
|
|
5493
6158
|
this.workspaceGitSubscriptions.clear();
|
|
5494
6159
|
}
|
|
5495
|
-
//
|
|
6160
|
+
// ----------------------------------------------------------------------------
|
|
5496
6161
|
// Terminal Handlers
|
|
5497
|
-
//
|
|
6162
|
+
// ----------------------------------------------------------------------------
|
|
5498
6163
|
ensureTerminalExitSubscription(terminal) {
|
|
5499
6164
|
if (this.terminalExitSubscriptions.has(terminal.id)) {
|
|
5500
6165
|
return;
|
|
@@ -5933,15 +6598,27 @@ export class Session {
|
|
|
5933
6598
|
},
|
|
5934
6599
|
});
|
|
5935
6600
|
}
|
|
6601
|
+
filterStandaloneTerminals(terminals) {
|
|
6602
|
+
return terminals;
|
|
6603
|
+
}
|
|
6604
|
+
toTerminalInfo(terminal) {
|
|
6605
|
+
const title = terminal.getTitle();
|
|
6606
|
+
return {
|
|
6607
|
+
id: terminal.id,
|
|
6608
|
+
name: terminal.name,
|
|
6609
|
+
...(title ? { title } : {}),
|
|
6610
|
+
};
|
|
6611
|
+
}
|
|
5936
6612
|
handleTerminalsChanged(event) {
|
|
5937
6613
|
if (!this.subscribedTerminalDirectories.has(event.cwd)) {
|
|
5938
6614
|
return;
|
|
5939
6615
|
}
|
|
5940
6616
|
this.emitTerminalsChangedSnapshot({
|
|
5941
6617
|
cwd: event.cwd,
|
|
5942
|
-
terminals: event.terminals.map((terminal) => ({
|
|
6618
|
+
terminals: this.filterStandaloneTerminals(event.terminals).map((terminal) => ({
|
|
5943
6619
|
id: terminal.id,
|
|
5944
6620
|
name: terminal.name,
|
|
6621
|
+
...(terminal.title ? { title: terminal.title } : {}),
|
|
5945
6622
|
})),
|
|
5946
6623
|
});
|
|
5947
6624
|
}
|
|
@@ -5957,7 +6634,7 @@ export class Session {
|
|
|
5957
6634
|
return;
|
|
5958
6635
|
}
|
|
5959
6636
|
try {
|
|
5960
|
-
const terminals = await this.terminalManager.getTerminals(cwd);
|
|
6637
|
+
const terminals = this.filterStandaloneTerminals(await this.terminalManager.getTerminals(cwd));
|
|
5961
6638
|
for (const terminal of terminals) {
|
|
5962
6639
|
this.ensureTerminalExitSubscription(terminal);
|
|
5963
6640
|
}
|
|
@@ -5966,10 +6643,7 @@ export class Session {
|
|
|
5966
6643
|
}
|
|
5967
6644
|
this.emitTerminalsChangedSnapshot({
|
|
5968
6645
|
cwd,
|
|
5969
|
-
terminals: terminals.map((terminal) => (
|
|
5970
|
-
id: terminal.id,
|
|
5971
|
-
name: terminal.name,
|
|
5972
|
-
})),
|
|
6646
|
+
terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
|
|
5973
6647
|
});
|
|
5974
6648
|
}
|
|
5975
6649
|
catch (error) {
|
|
@@ -5989,9 +6663,9 @@ export class Session {
|
|
|
5989
6663
|
return;
|
|
5990
6664
|
}
|
|
5991
6665
|
try {
|
|
5992
|
-
const terminals = typeof msg.cwd === "string"
|
|
6666
|
+
const terminals = this.filterStandaloneTerminals(typeof msg.cwd === "string"
|
|
5993
6667
|
? await this.terminalManager.getTerminals(msg.cwd)
|
|
5994
|
-
: await this.getAllTerminalSessions();
|
|
6668
|
+
: await this.getAllTerminalSessions());
|
|
5995
6669
|
for (const terminal of terminals) {
|
|
5996
6670
|
this.ensureTerminalExitSubscription(terminal);
|
|
5997
6671
|
}
|
|
@@ -5999,7 +6673,7 @@ export class Session {
|
|
|
5999
6673
|
type: "list_terminals_response",
|
|
6000
6674
|
payload: {
|
|
6001
6675
|
...(msg.cwd ? { cwd: msg.cwd } : {}),
|
|
6002
|
-
terminals: terminals.map((
|
|
6676
|
+
terminals: terminals.map((terminal) => this.toTerminalInfo(terminal)),
|
|
6003
6677
|
requestId: msg.requestId,
|
|
6004
6678
|
},
|
|
6005
6679
|
});
|
|
@@ -6037,15 +6711,33 @@ export class Session {
|
|
|
6037
6711
|
return;
|
|
6038
6712
|
}
|
|
6039
6713
|
try {
|
|
6714
|
+
if (msg.agentId) {
|
|
6715
|
+
this.emit({
|
|
6716
|
+
type: "create_terminal_response",
|
|
6717
|
+
payload: {
|
|
6718
|
+
terminal: null,
|
|
6719
|
+
error: `Agent-backed terminals are no longer supported for agent ${msg.agentId}`,
|
|
6720
|
+
requestId: msg.requestId,
|
|
6721
|
+
},
|
|
6722
|
+
});
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6040
6725
|
const session = await this.terminalManager.createTerminal({
|
|
6041
6726
|
cwd: msg.cwd,
|
|
6042
6727
|
name: msg.name,
|
|
6728
|
+
command: msg.command,
|
|
6729
|
+
args: msg.args,
|
|
6043
6730
|
});
|
|
6044
6731
|
this.ensureTerminalExitSubscription(session);
|
|
6045
6732
|
this.emit({
|
|
6046
6733
|
type: "create_terminal_response",
|
|
6047
6734
|
payload: {
|
|
6048
|
-
terminal: {
|
|
6735
|
+
terminal: {
|
|
6736
|
+
id: session.id,
|
|
6737
|
+
name: session.name,
|
|
6738
|
+
cwd: session.cwd,
|
|
6739
|
+
...(session.getTitle() ? { title: session.getTitle() } : {}),
|
|
6740
|
+
},
|
|
6049
6741
|
error: null,
|
|
6050
6742
|
requestId: msg.requestId,
|
|
6051
6743
|
},
|
|
@@ -6147,6 +6839,7 @@ export class Session {
|
|
|
6147
6839
|
return killWorktreeTerminalsUnderPath({
|
|
6148
6840
|
isPathWithinRoot: (pathRoot, candidatePath) => this.isPathWithinRoot(pathRoot, candidatePath),
|
|
6149
6841
|
killTrackedTerminal: (terminalId, options) => this.killTrackedTerminal(terminalId, options),
|
|
6842
|
+
detachTerminalStream: (terminalId, options) => void this.detachTerminalStream(terminalId, options),
|
|
6150
6843
|
sessionLogger: this.sessionLogger,
|
|
6151
6844
|
terminalManager: this.terminalManager,
|
|
6152
6845
|
}, rootPath);
|
|
@@ -6253,6 +6946,19 @@ export class Session {
|
|
|
6253
6946
|
slot,
|
|
6254
6947
|
unsubscribe: () => { },
|
|
6255
6948
|
needsSnapshot: true,
|
|
6949
|
+
outputCoalescer: new TerminalOutputCoalescer({
|
|
6950
|
+
timers: { setTimeout, clearTimeout },
|
|
6951
|
+
onFlush: ({ payload }) => {
|
|
6952
|
+
if (this.activeTerminalStreams.get(slot) !== activeStream) {
|
|
6953
|
+
return;
|
|
6954
|
+
}
|
|
6955
|
+
this.emitBinary(encodeTerminalStreamFrame({
|
|
6956
|
+
opcode: TerminalStreamOpcode.Output,
|
|
6957
|
+
slot,
|
|
6958
|
+
payload,
|
|
6959
|
+
}));
|
|
6960
|
+
},
|
|
6961
|
+
}),
|
|
6256
6962
|
};
|
|
6257
6963
|
this.activeTerminalStreams.set(slot, activeStream);
|
|
6258
6964
|
this.terminalIdToSlot.set(terminal.id, slot);
|
|
@@ -6261,17 +6967,18 @@ export class Session {
|
|
|
6261
6967
|
return;
|
|
6262
6968
|
}
|
|
6263
6969
|
if (message.type === "snapshot") {
|
|
6970
|
+
activeStream.outputCoalescer.flush();
|
|
6971
|
+
activeStream.needsSnapshot = true;
|
|
6264
6972
|
this.trySendTerminalSnapshot(activeStream);
|
|
6265
6973
|
return;
|
|
6266
6974
|
}
|
|
6975
|
+
if (message.type === "titleChange") {
|
|
6976
|
+
return;
|
|
6977
|
+
}
|
|
6267
6978
|
if (activeStream.needsSnapshot || message.data.length === 0) {
|
|
6268
6979
|
return;
|
|
6269
6980
|
}
|
|
6270
|
-
|
|
6271
|
-
opcode: TerminalStreamOpcode.Output,
|
|
6272
|
-
slot,
|
|
6273
|
-
payload: new Uint8Array(Buffer.from(message.data, "utf8")),
|
|
6274
|
-
}));
|
|
6981
|
+
activeStream.outputCoalescer.handle(message.data);
|
|
6275
6982
|
});
|
|
6276
6983
|
return slot;
|
|
6277
6984
|
}
|
|
@@ -6285,6 +6992,7 @@ export class Session {
|
|
|
6285
6992
|
this.detachTerminalStream(activeStream.terminalId, { emitExit: true });
|
|
6286
6993
|
return;
|
|
6287
6994
|
}
|
|
6995
|
+
activeStream.outputCoalescer.flush();
|
|
6288
6996
|
activeStream.needsSnapshot = false;
|
|
6289
6997
|
this.emitBinary(encodeTerminalStreamFrame({
|
|
6290
6998
|
opcode: TerminalStreamOpcode.Snapshot,
|
|
@@ -6313,6 +7021,7 @@ export class Session {
|
|
|
6313
7021
|
this.terminalIdToSlot.delete(terminalId);
|
|
6314
7022
|
return false;
|
|
6315
7023
|
}
|
|
7024
|
+
activeStream.outputCoalescer.flush();
|
|
6316
7025
|
this.activeTerminalStreams.delete(slot);
|
|
6317
7026
|
this.terminalIdToSlot.delete(terminalId);
|
|
6318
7027
|
try {
|
|
@@ -6341,4 +7050,37 @@ export class Session {
|
|
|
6341
7050
|
// Stash handlers
|
|
6342
7051
|
// ---------------------------------------------------------------------------
|
|
6343
7052
|
Session.PASEO_STASH_PREFIX = "paseo-auto-stash:";
|
|
7053
|
+
export function normalizeCheckoutPrStatusPayload(status) {
|
|
7054
|
+
if (!status) {
|
|
7055
|
+
return null;
|
|
7056
|
+
}
|
|
7057
|
+
return {
|
|
7058
|
+
number: status.number,
|
|
7059
|
+
url: status.url,
|
|
7060
|
+
title: status.title,
|
|
7061
|
+
state: status.state,
|
|
7062
|
+
repoOwner: status.repoOwner,
|
|
7063
|
+
repoName: status.repoName,
|
|
7064
|
+
baseRefName: status.baseRefName,
|
|
7065
|
+
headRefName: status.headRefName,
|
|
7066
|
+
isMerged: status.isMerged,
|
|
7067
|
+
isDraft: status.isDraft ?? false,
|
|
7068
|
+
checks: status.checks ?? [],
|
|
7069
|
+
checksStatus: status.checksStatus,
|
|
7070
|
+
reviewDecision: status.reviewDecision,
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
7073
|
+
function isValidPullRequestTimelineIdentity(options) {
|
|
7074
|
+
if (!Number.isInteger(options.prNumber) || options.prNumber <= 0) {
|
|
7075
|
+
return false;
|
|
7076
|
+
}
|
|
7077
|
+
return isValidGitHubRepoSegment(options.repoOwner) && isValidGitHubRepoSegment(options.repoName);
|
|
7078
|
+
}
|
|
7079
|
+
function isValidGitHubRepoSegment(value) {
|
|
7080
|
+
return /^[A-Za-z0-9._-]+$/.test(value);
|
|
7081
|
+
}
|
|
7082
|
+
function toPullRequestTimelinePayloadItem(item) {
|
|
7083
|
+
const { authorUrl: _authorUrl, ...payload } = item;
|
|
7084
|
+
return payload;
|
|
7085
|
+
}
|
|
6344
7086
|
//# sourceMappingURL=session.js.map
|