@getpaseo/server 0.1.89 → 0.1.90

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.
Files changed (46) hide show
  1. package/dist/server/server/agent/agent-prompt.js +4 -1
  2. package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
  3. package/dist/server/server/agent/agent-storage.js +2 -9
  4. package/dist/server/server/agent/create-agent/create.js +10 -2
  5. package/dist/server/server/agent/create-agent-mode.d.ts +3 -8
  6. package/dist/server/server/agent/create-agent-mode.js +16 -2
  7. package/dist/server/server/agent/import-sessions.js +1 -1
  8. package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -1
  9. package/dist/server/server/agent/provider-snapshot-manager.js +18 -2
  10. package/dist/server/server/agent/providers/acp-agent.d.ts +3 -3
  11. package/dist/server/server/agent/providers/acp-agent.js +18 -13
  12. package/dist/server/server/agent/providers/codex-app-server-agent.js +16 -22
  13. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
  14. package/dist/server/server/agent/providers/mock-load-test-agent.js +69 -2
  15. package/dist/server/server/agent/providers/opencode-agent.js +19 -8
  16. package/dist/server/server/agent/timeline-projection.js +30 -1
  17. package/dist/server/server/atomic-file.d.ts +3 -0
  18. package/dist/server/server/atomic-file.js +19 -0
  19. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +4 -1
  20. package/dist/server/server/bootstrap.js +2 -0
  21. package/dist/server/server/chat/chat-service.js +2 -4
  22. package/dist/server/server/daemon-keypair.js +2 -2
  23. package/dist/server/server/loop-service.d.ts +4 -0
  24. package/dist/server/server/loop-service.js +27 -9
  25. package/dist/server/server/persisted-config.js +3 -3
  26. package/dist/server/server/private-files.d.ts +0 -1
  27. package/dist/server/server/private-files.js +0 -5
  28. package/dist/server/server/schedule/service.d.ts +6 -0
  29. package/dist/server/server/schedule/service.js +41 -18
  30. package/dist/server/server/schedule/store.js +3 -2
  31. package/dist/server/server/server-id.js +3 -3
  32. package/dist/server/server/session.d.ts +5 -15
  33. package/dist/server/server/session.js +184 -107
  34. package/dist/server/server/speech/providers/local/worker-client.js +1 -11
  35. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  36. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  37. package/dist/server/server/workspace-directory.d.ts +8 -0
  38. package/dist/server/server/workspace-directory.js +141 -15
  39. package/dist/server/server/workspace-registry.js +2 -6
  40. package/dist/server/utils/checkout-git.d.ts +0 -1
  41. package/dist/server/utils/checkout-git.js +23 -31
  42. package/dist/src/server/persisted-config.js +3 -3
  43. package/dist/src/server/private-files.js +0 -5
  44. package/package.json +9 -7
  45. package/dist/server/server/editor-targets.d.ts +0 -18
  46. package/dist/server/server/editor-targets.js +0 -109
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { sep } from "node:path";
3
3
  import { deriveAgentStateBucket, getWorkspaceStateBucketPriority, } from "@getpaseo/protocol/agent-state-bucket";
4
- import { isDelegatedAgent } from "@getpaseo/protocol/agent-labels";
4
+ import { getParentAgentIdFromLabels, isDelegatedAgent } from "@getpaseo/protocol/agent-labels";
5
5
  import { SortablePager } from "./pagination/sortable-pager.js";
6
6
  import { normalizeWorkspaceId } from "./workspace-registry-model.js";
7
7
  const FETCH_WORKSPACES_SORT_KEYS = [
@@ -35,6 +35,12 @@ export class WorkspaceDirectory {
35
35
  constructor(deps) {
36
36
  this.deps = deps;
37
37
  this.archivingByWorkspaceId = new Map();
38
+ /**
39
+ * Per-workspace last-seen winning bucket + entered-at. Persists across
40
+ * `buildDescriptorMap` calls inside the daemon process; reset on cold start.
41
+ * Server-internal; never crosses the wire.
42
+ */
43
+ this.bucketHistoryByWorkspaceId = new Map();
38
44
  this.pager = new SortablePager({
39
45
  validKeys: FETCH_WORKSPACES_SORT_KEYS,
40
46
  defaultSort: [{ key: "activity_at", direction: "desc" }],
@@ -93,17 +99,31 @@ export class WorkspaceDirectory {
93
99
  archivingAt: this.archivingByWorkspaceId.get(workspaceId) ?? null,
94
100
  });
95
101
  }
96
- for (const agent of agents) {
97
- if (agent.archivedAt) {
98
- continue;
99
- }
100
- if (!this.deps.isProviderVisibleToClient(agent.provider)) {
101
- continue;
102
- }
102
+ const activeAgents = agents.filter((agent) => !agent.archivedAt && this.deps.isProviderVisibleToClient(agent.provider));
103
+ const activeAgentsById = new Map(activeAgents.map((agent) => [agent.id, agent]));
104
+ for (const agent of activeAgents) {
105
+ let workspaceAgent = agent;
106
+ let bucket;
103
107
  if (isDelegatedAgent(agent)) {
104
- continue;
108
+ if (agent.status !== "running") {
109
+ continue;
110
+ }
111
+ const parentAgent = resolveDelegationRootAgent(agent, activeAgentsById);
112
+ if (!parentAgent) {
113
+ continue;
114
+ }
115
+ workspaceAgent = parentAgent;
116
+ bucket = "running";
105
117
  }
106
- const workspaceId = workspaceIdsByDirectory.get(normalizeWorkspaceId(agent.cwd));
118
+ else {
119
+ bucket = deriveAgentStateBucket({
120
+ status: agent.status,
121
+ pendingPermissionCount: agent.pendingPermissions?.length ?? 0,
122
+ requiresAttention: agent.requiresAttention,
123
+ attentionReason: agent.attentionReason ?? null,
124
+ });
125
+ }
126
+ const workspaceId = workspaceIdsByDirectory.get(normalizeWorkspaceId(workspaceAgent.cwd));
107
127
  if (workspaceId === undefined) {
108
128
  continue;
109
129
  }
@@ -111,17 +131,104 @@ export class WorkspaceDirectory {
111
131
  if (!existing) {
112
132
  continue;
113
133
  }
114
- const bucket = deriveAgentStateBucket({
134
+ if (getWorkspaceStateBucketPriority(bucket) < getWorkspaceStateBucketPriority(existing.status)) {
135
+ existing.status = bucket;
136
+ }
137
+ }
138
+ // Resolve the workspace-level `statusEnteredAt` (see aggregate semantics
139
+ // on `resolveStatusEnteredAt`).
140
+ const nowIso = new Date().toISOString();
141
+ for (const [workspaceId, descriptor] of descriptorsByWorkspaceId) {
142
+ const contributingAgents = agents.filter((agent) => !agent.archivedAt &&
143
+ this.deps.isProviderVisibleToClient(agent.provider) &&
144
+ workspaceIdsByDirectory.get(normalizeWorkspaceId(agent.cwd)) === workspaceId);
145
+ const result = this.resolveStatusEnteredAt({
146
+ workspaceId,
147
+ winningBucket: descriptor.status,
148
+ contributingAgents,
149
+ previous: this.bucketHistoryByWorkspaceId.get(workspaceId) ?? null,
150
+ nowIso,
151
+ });
152
+ descriptor.statusEnteredAt = result.statusEnteredAt;
153
+ if (result.recordUpdate) {
154
+ this.bucketHistoryByWorkspaceId.set(workspaceId, result.recordUpdate);
155
+ }
156
+ else if (result.recordDelete) {
157
+ this.bucketHistoryByWorkspaceId.delete(workspaceId);
158
+ }
159
+ }
160
+ return descriptorsByWorkspaceId;
161
+ }
162
+ // Aggregate the workspace-level `statusEnteredAt` from its contributing
163
+ // agents. Aggregate semantics:
164
+ // - winning bucket = highest-priority across contributing agents;
165
+ // - entry time = best-effort timestamp from agents in the winning bucket;
166
+ // - priority unmasking: when the winning bucket transitions (e.g. a
167
+ // higher-priority bucket cleared), the new entry time is "now";
168
+ // - same-bucket emits reuse the previous entered-at;
169
+ // - empty workspaces that never had contributing agents get
170
+ // `statusEnteredAt: null`.
171
+ // - when archived agents leave a previously active workspace empty, keep
172
+ // the previous done timestamp or stamp the transition to done now.
173
+ resolveStatusEnteredAt(params) {
174
+ const { winningBucket, contributingAgents, previous, nowIso } = params;
175
+ if (contributingAgents.length === 0) {
176
+ if (!previous) {
177
+ return { statusEnteredAt: null };
178
+ }
179
+ const enteredAt = previous.bucket === "done" ? previous.enteredAt : nowIso;
180
+ return {
181
+ statusEnteredAt: enteredAt,
182
+ recordUpdate: { bucket: "done", enteredAt },
183
+ };
184
+ }
185
+ if (!previous) {
186
+ const newestInWinningBucket = this.findNewestAgentTimestampInBucket(contributingAgents, winningBucket);
187
+ const enteredAt = newestInWinningBucket ?? nowIso;
188
+ return {
189
+ statusEnteredAt: enteredAt,
190
+ recordUpdate: { bucket: winningBucket, enteredAt },
191
+ };
192
+ }
193
+ if (previous.bucket !== winningBucket) {
194
+ return {
195
+ statusEnteredAt: nowIso,
196
+ recordUpdate: { bucket: winningBucket, enteredAt: nowIso },
197
+ };
198
+ }
199
+ return {
200
+ statusEnteredAt: previous.enteredAt,
201
+ recordUpdate: previous,
202
+ };
203
+ }
204
+ // Best-effort newest timestamp across contributing agents whose derived
205
+ // bucket matches `winningBucket`. Uses available agent fields:
206
+ // - `attentionTimestamp` when attention is set (covers attention/failed)
207
+ // - `updatedAt` as a general fallback for any bucket
208
+ // Returns `null` if no matching agent has a parseable timestamp.
209
+ findNewestAgentTimestampInBucket(contributingAgents, winningBucket) {
210
+ const candidates = contributingAgents
211
+ .filter((agent) => {
212
+ const derived = deriveAgentStateBucket({
115
213
  status: agent.status,
116
214
  pendingPermissionCount: agent.pendingPermissions?.length ?? 0,
117
215
  requiresAttention: agent.requiresAttention,
118
216
  attentionReason: agent.attentionReason ?? null,
119
217
  });
120
- if (getWorkspaceStateBucketPriority(bucket) < getWorkspaceStateBucketPriority(existing.status)) {
121
- existing.status = bucket;
218
+ return derived === winningBucket;
219
+ })
220
+ .map((agent) => {
221
+ // Prefer attentionTimestamp when the agent has attention set — this is
222
+ // the most accurate "entered current status" signal.
223
+ if (agent.attentionTimestamp) {
224
+ return agent.attentionTimestamp;
122
225
  }
123
- }
124
- return descriptorsByWorkspaceId;
226
+ // Fall back to updatedAt as a general proxy for recent activity.
227
+ return agent.updatedAt;
228
+ })
229
+ .filter((value) => typeof value === "string" && value.length > 0)
230
+ .sort();
231
+ return candidates.at(-1) ?? null;
125
232
  }
126
233
  resolveRegisteredWorkspaceIdForCwd(cwd, workspaces) {
127
234
  const normalizedCwd = normalizeWorkspaceId(cwd);
@@ -215,4 +322,23 @@ export class WorkspaceDirectory {
215
322
  };
216
323
  }
217
324
  }
325
+ function resolveDelegationRootAgent(agent, activeAgentsById) {
326
+ const seen = new Set([agent.id]);
327
+ let current = agent;
328
+ while (true) {
329
+ const parentAgentId = getParentAgentIdFromLabels(current.labels);
330
+ if (!parentAgentId) {
331
+ return current;
332
+ }
333
+ if (seen.has(parentAgentId)) {
334
+ return null;
335
+ }
336
+ const parent = activeAgentsById.get(parentAgentId);
337
+ if (!parent) {
338
+ return null;
339
+ }
340
+ seen.add(parentAgentId);
341
+ current = parent;
342
+ }
343
+ }
218
344
  //# sourceMappingURL=workspace-directory.js.map
@@ -1,7 +1,6 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import { promises as fs } from "node:fs";
3
- import path from "node:path";
4
2
  import { z } from "zod";
3
+ import { writeJsonFileAtomic } from "./atomic-file.js";
5
4
  const PersistedProjectRecordSchema = z.object({
6
5
  projectId: z.string(),
7
6
  rootPath: z.string(),
@@ -110,10 +109,7 @@ class FileBackedRegistry {
110
109
  }
111
110
  async persist() {
112
111
  const records = Array.from(this.cache.values());
113
- await fs.mkdir(path.dirname(this.filePath), { recursive: true });
114
- const tempPath = `${this.filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
115
- await fs.writeFile(tempPath, JSON.stringify(records, null, 2), "utf8");
116
- await fs.rename(tempPath, this.filePath);
112
+ await writeJsonFileAtomic(this.filePath, records);
117
113
  }
118
114
  async enqueuePersist() {
119
115
  const nextPersist = this.persistQueue.then(() => this.persist());
@@ -150,7 +150,6 @@ export type CheckoutSnapshotFacts = {
150
150
  comparisonBaseRef: string | null;
151
151
  branchRemoteName: string | null;
152
152
  branchMergeRef: string | null;
153
- trackedOriginBranch: string | null;
154
153
  pullRequestLookupTarget: PullRequestStatusLookupTarget | null;
155
154
  };
156
155
  export declare function getCurrentBranch(cwd: string): Promise<string | null>;
@@ -975,52 +975,46 @@ async function getAheadBehind(cwd, baseRef, currentBranch, context) {
975
975
  }
976
976
  return { ahead, behind };
977
977
  }
978
- async function getAheadOfOrigin(cwd, currentBranch, baseRef, context) {
978
+ async function getAheadOfOrigin(cwd, currentBranch, context) {
979
979
  if (!currentBranch) {
980
980
  return null;
981
981
  }
982
- const trackedOriginBranch = await getTrackedOriginBranch(cwd, currentBranch, context);
983
- const originBranch = trackedOriginBranch ?? currentBranch;
982
+ const upstreamRef = await getConfiguredUpstreamRef(cwd, currentBranch, context);
983
+ if (!upstreamRef) {
984
+ return null;
985
+ }
984
986
  try {
985
- const { stdout } = await runGitCommand(["rev-list", "--count", `origin/${originBranch}..${currentBranch}`], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
987
+ const { stdout } = await runGitCommand(["rev-list", "--count", `${upstreamRef}..${currentBranch}`], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
986
988
  const count = Number.parseInt(stdout.trim(), 10);
987
989
  return Number.isNaN(count) ? null : count;
988
990
  }
989
991
  catch {
990
- if (trackedOriginBranch) {
991
- return null;
992
- }
993
- if (!baseRef || normalizeLocalBranchRefName(baseRef) === currentBranch) {
994
- return null;
995
- }
996
- try {
997
- const comparisonBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef, context);
998
- const { stdout } = await runGitCommand(["rev-list", "--count", `${comparisonBaseRef}..${currentBranch}`], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
999
- const count = Number.parseInt(stdout.trim(), 10);
1000
- return Number.isNaN(count) ? null : count;
1001
- }
1002
- catch {
1003
- return null;
1004
- }
992
+ return null;
1005
993
  }
1006
994
  }
1007
- async function getTrackedOriginBranch(cwd, currentBranch, context) {
1008
- if (context?.facts?.isGit && context.facts.currentBranch === currentBranch) {
1009
- return context.facts.trackedOriginBranch;
1010
- }
1011
- const remoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.remote`, context);
1012
- if (remoteName !== "origin") {
995
+ async function getConfiguredUpstreamRef(cwd, currentBranch, context) {
996
+ const remoteName = context?.facts?.isGit && context.facts.currentBranch === currentBranch
997
+ ? context.facts.branchRemoteName
998
+ : await getGitConfigValue(cwd, `branch.${currentBranch}.remote`, context);
999
+ if (!remoteName) {
1013
1000
  return null;
1014
1001
  }
1015
- const mergeRef = await getGitConfigValue(cwd, `branch.${currentBranch}.merge`, context);
1016
- return parseBranchMergeHeadRef(mergeRef);
1002
+ const mergeRef = context?.facts?.isGit && context.facts.currentBranch === currentBranch
1003
+ ? context.facts.branchMergeRef
1004
+ : await getGitConfigValue(cwd, `branch.${currentBranch}.merge`, context);
1005
+ const upstreamBranch = parseBranchMergeHeadRef(mergeRef);
1006
+ return upstreamBranch ? `${remoteName}/${upstreamBranch}` : null;
1017
1007
  }
1018
1008
  async function getBehindOfOrigin(cwd, currentBranch, context) {
1019
1009
  if (!currentBranch) {
1020
1010
  return null;
1021
1011
  }
1012
+ const upstreamRef = await getConfiguredUpstreamRef(cwd, currentBranch, context);
1013
+ if (!upstreamRef) {
1014
+ return null;
1015
+ }
1022
1016
  try {
1023
- const { stdout } = await runGitCommand(["rev-list", "--count", `${currentBranch}..origin/${currentBranch}`], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
1017
+ const { stdout } = await runGitCommand(["rev-list", "--count", `${currentBranch}..${upstreamRef}`], { cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger });
1024
1018
  const count = Number.parseInt(stdout.trim(), 10);
1025
1019
  return Number.isNaN(count) ? null : count;
1026
1020
  }
@@ -1105,7 +1099,6 @@ export async function getCheckoutSnapshotFacts(cwd, context) {
1105
1099
  }
1106
1100
  }
1107
1101
  }
1108
- const trackedOriginBranch = branchRemoteName === "origin" ? parseBranchMergeHeadRef(branchMergeRef) : null;
1109
1102
  const pullRequestLookupTarget = inspected.currentBranch
1110
1103
  ? buildPullRequestLookupTargetFromBranchConfig({
1111
1104
  currentBranch: inspected.currentBranch,
@@ -1128,7 +1121,6 @@ export async function getCheckoutSnapshotFacts(cwd, context) {
1128
1121
  comparisonBaseRef,
1129
1122
  branchRemoteName,
1130
1123
  branchMergeRef,
1131
- trackedOriginBranch,
1132
1124
  pullRequestLookupTarget,
1133
1125
  };
1134
1126
  }
@@ -1238,7 +1230,7 @@ export async function getCheckoutStatus(cwd, context) {
1238
1230
  ? getAheadBehind(cwd, baseRef, currentBranch, factsContext)
1239
1231
  : Promise.resolve(null),
1240
1232
  hasRemote && currentBranch
1241
- ? getAheadOfOrigin(cwd, currentBranch, baseRef, factsContext)
1233
+ ? getAheadOfOrigin(cwd, currentBranch, factsContext)
1242
1234
  : Promise.resolve(null),
1243
1235
  hasRemote && currentBranch
1244
1236
  ? getBehindOfOrigin(cwd, currentBranch, factsContext)
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { z } from "zod";
4
4
  import { AgentProviderRuntimeSettingsMapSchema, migrateProviderSettings, ProviderOverridesSchema, } from "./agent/provider-launch-config.js";
5
- import { ensurePrivateFile, writePrivateFileSync } from "./private-files.js";
5
+ import { ensurePrivateFile, writePrivateFileAtomicSync } from "./private-files.js";
6
6
  export const LogLevelSchema = z.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
7
7
  export const LogFormatSchema = z.enum(["pretty", "json"]);
8
8
  const LogConfigSchema = z
@@ -296,7 +296,7 @@ export function loadPersistedConfig(paseoHome, logger) {
296
296
  const configPath = getConfigPath(paseoHome);
297
297
  if (!existsSync(configPath)) {
298
298
  try {
299
- writePrivateFileSync(configPath, JSON.stringify(DEFAULT_PERSISTED_CONFIG, null, 2) + "\n");
299
+ writePrivateFileAtomicSync(configPath, JSON.stringify(DEFAULT_PERSISTED_CONFIG, null, 2) + "\n");
300
300
  log?.info(`Initialized config file at ${configPath}`);
301
301
  }
302
302
  catch (err) {
@@ -347,7 +347,7 @@ export function savePersistedConfig(paseoHome, config, logger) {
347
347
  throw new Error(`[Config] Invalid config to save:\n${issues}`);
348
348
  }
349
349
  try {
350
- writePrivateFileSync(configPath, JSON.stringify(result.data, null, 2) + "\n");
350
+ writePrivateFileAtomicSync(configPath, JSON.stringify(result.data, null, 2) + "\n");
351
351
  log?.info(`Saved to ${configPath}`);
352
352
  }
353
353
  catch (err) {
@@ -21,11 +21,6 @@ export function ensurePrivateDirectory(directoryPath) {
21
21
  export function ensurePrivateFile(filePath) {
22
22
  chmodBestEffort(filePath, PRIVATE_FILE_MODE);
23
23
  }
24
- export function writePrivateFileSync(filePath, data) {
25
- ensurePrivateDirectory(path.dirname(filePath));
26
- writeFileSync(filePath, data, { mode: PRIVATE_FILE_MODE });
27
- ensurePrivateFile(filePath);
28
- }
29
24
  export function writePrivateFileAtomicSync(filePath, data) {
30
25
  ensurePrivateDirectory(path.dirname(filePath));
31
26
  const tmpPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getpaseo/server",
3
- "version": "0.1.89",
3
+ "version": "0.1.90",
4
4
  "description": "Paseo backend server",
5
5
  "files": [
6
6
  "dist/server",
@@ -30,10 +30,12 @@
30
30
  "scripts": {
31
31
  "dev": "cross-env PASEO_NODE_ENV=development node --import tsx scripts/dev-runner.ts",
32
32
  "dev:tsx": "cross-env PASEO_NODE_ENV=development tsx watch --ignore '**/*.timestamp-*' scripts/dev-runner.ts",
33
- "build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && npm run build:lib && npm run build:scripts",
33
+ "clean": "node ../../scripts/clean-package-dist.mjs",
34
+ "build": "npm run build:lib && npm run build:scripts",
35
+ "build:clean": "npm run clean && npm run build",
34
36
  "build:lib": "tsc -p tsconfig.server.json --incremental false && node -e \"const fs=require('node:fs'); fs.mkdirSync('dist/server/server/speech/providers/local/sherpa/assets',{recursive:true}); fs.copyFileSync('src/server/speech/providers/local/sherpa/assets/silero_vad.onnx','dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx'); fs.cpSync('src/terminal/shell-integration','dist/server/terminal/shell-integration',{recursive:true}); fs.cpSync('src/terminal/shell-integration','dist/src/terminal/shell-integration',{recursive:true}); fs.copyFileSync('src/terminal/terminal-ts-loader.mjs','dist/server/terminal/terminal-ts-loader.mjs');\"",
35
37
  "build:scripts": "tsc -p tsconfig.scripts.json --incremental false && node -e \"const fs=require('node:fs'); fs.mkdirSync('dist/scripts',{recursive:true}); fs.copyFileSync('scripts/mcp-stdio-socket-bridge-cli.mjs','dist/scripts/mcp-stdio-socket-bridge-cli.mjs');\"",
36
- "prepack": "npm run build",
38
+ "prepack": "npm run build:clean",
37
39
  "start": "node dist/scripts/supervisor-entrypoint.js",
38
40
  "typecheck": "tsgo -p tsconfig.server.typecheck.json --noEmit",
39
41
  "generate:config-schema": "tsx scripts/generate-config-schema.ts",
@@ -57,10 +59,10 @@
57
59
  "dependencies": {
58
60
  "@agentclientprotocol/sdk": "^0.17.1",
59
61
  "@anthropic-ai/claude-agent-sdk": "^0.2.133",
60
- "@getpaseo/client": "0.1.89",
61
- "@getpaseo/highlight": "0.1.89",
62
- "@getpaseo/protocol": "0.1.89",
63
- "@getpaseo/relay": "0.1.89",
62
+ "@getpaseo/client": "0.1.90",
63
+ "@getpaseo/highlight": "0.1.90",
64
+ "@getpaseo/protocol": "0.1.90",
65
+ "@getpaseo/relay": "0.1.90",
64
66
  "@isaacs/ttlcache": "^2.1.4",
65
67
  "@modelcontextprotocol/sdk": "^1.20.1",
66
68
  "@opencode-ai/sdk": "1.14.46",
@@ -1,18 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import type { EditorTargetDescriptorPayload, EditorTargetId } from "@getpaseo/protocol/messages";
3
- import { spawnProcess } from "../utils/spawn.js";
4
- interface ListAvailableEditorTargetsDependencies {
5
- platform?: NodeJS.Platform;
6
- findExecutable?: (command: string) => string | null | Promise<string | null>;
7
- }
8
- type OpenInEditorTargetDependencies = ListAvailableEditorTargetsDependencies & {
9
- existsSync?: typeof existsSync;
10
- spawn?: typeof spawnProcess;
11
- };
12
- export declare function listAvailableEditorTargets(dependencies?: ListAvailableEditorTargetsDependencies): Promise<EditorTargetDescriptorPayload[]>;
13
- export declare function openInEditorTarget(input: {
14
- editorId: EditorTargetId;
15
- path: string;
16
- }, dependencies?: OpenInEditorTargetDependencies): Promise<void>;
17
- export {};
18
- //# sourceMappingURL=editor-targets.d.ts.map
@@ -1,109 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { posix, win32 } from "node:path";
3
- import { createExternalProcessEnv } from "./paseo-env.js";
4
- import { findExecutable } from "../utils/executable.js";
5
- import { spawnProcess } from "../utils/spawn.js";
6
- const EDITOR_TARGETS = [
7
- { id: "cursor", label: "Cursor", command: "cursor" },
8
- { id: "vscode", label: "VS Code", command: "code" },
9
- { id: "webstorm", label: "WebStorm", command: "webstorm" },
10
- { id: "zed", label: "Zed", command: "zed" },
11
- { id: "finder", label: "Finder", command: "open", platforms: ["darwin"] },
12
- { id: "explorer", label: "Explorer", command: "explorer", platforms: ["win32"] },
13
- {
14
- id: "file-manager",
15
- label: "File Manager",
16
- command: "xdg-open",
17
- excludedPlatforms: ["darwin", "win32"],
18
- },
19
- ];
20
- function isAbsolutePath(value) {
21
- return posix.isAbsolute(value) || win32.isAbsolute(value);
22
- }
23
- function isTargetSupportedOnPlatform(target, platform) {
24
- if (target.platforms && !target.platforms.includes(platform)) {
25
- return false;
26
- }
27
- if (target.excludedPlatforms?.includes(platform)) {
28
- return false;
29
- }
30
- return true;
31
- }
32
- function resolveEditorTargetDefinition(editorId) {
33
- const target = EDITOR_TARGETS.find((entry) => entry.id === editorId);
34
- if (!target) {
35
- throw new Error(`Unknown editor target: ${editorId}`);
36
- }
37
- return target;
38
- }
39
- export async function listAvailableEditorTargets(dependencies = {}) {
40
- const platform = dependencies.platform ?? process.platform;
41
- const findExecutableFn = dependencies.findExecutable ?? findExecutable;
42
- const supportedTargets = EDITOR_TARGETS.filter((target) => isTargetSupportedOnPlatform(target, platform));
43
- const executables = await Promise.all(supportedTargets.map((target) => findExecutableFn(target.command)));
44
- const results = [];
45
- for (let i = 0; i < supportedTargets.length; i += 1) {
46
- if (!executables[i])
47
- continue;
48
- const target = supportedTargets[i];
49
- results.push({
50
- id: target.id,
51
- label: target.label,
52
- });
53
- }
54
- return results;
55
- }
56
- async function resolveEditorLaunch(input) {
57
- const target = resolveEditorTargetDefinition(input.editorId);
58
- if (!isTargetSupportedOnPlatform(target, input.platform)) {
59
- throw new Error(`Editor target unavailable: ${target.label}`);
60
- }
61
- const executable = await input.findExecutableFn(target.command);
62
- if (!executable) {
63
- throw new Error(`Editor target unavailable: ${target.label}`);
64
- }
65
- return {
66
- command: executable,
67
- args: [input.path],
68
- };
69
- }
70
- export async function openInEditorTarget(input, dependencies = {}) {
71
- const platform = dependencies.platform ?? process.platform;
72
- const pathToOpen = input.path.trim();
73
- const existsSyncFn = dependencies.existsSync ?? existsSync;
74
- const findExecutableFn = dependencies.findExecutable ?? findExecutable;
75
- const spawnFn = dependencies.spawn ?? spawnProcess;
76
- if (!pathToOpen || !isAbsolutePath(pathToOpen)) {
77
- throw new Error("Editor target path must be an absolute local path");
78
- }
79
- if (!existsSyncFn(pathToOpen)) {
80
- throw new Error(`Path does not exist: ${pathToOpen}`);
81
- }
82
- const launch = await resolveEditorLaunch({
83
- editorId: input.editorId,
84
- path: pathToOpen,
85
- platform,
86
- findExecutableFn,
87
- });
88
- await new Promise((resolve, reject) => {
89
- let child;
90
- try {
91
- child = spawnFn(launch.command, launch.args, {
92
- detached: true,
93
- env: createExternalProcessEnv(process.env),
94
- shell: platform === "win32",
95
- stdio: "ignore",
96
- });
97
- }
98
- catch (error) {
99
- reject(error);
100
- return;
101
- }
102
- child.once("error", reject);
103
- child.once("spawn", () => {
104
- child.unref();
105
- resolve();
106
- });
107
- });
108
- }
109
- //# sourceMappingURL=editor-targets.js.map