@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.
- package/dist/server/server/agent/agent-prompt.js +4 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +1 -0
- package/dist/server/server/agent/agent-storage.js +2 -9
- package/dist/server/server/agent/create-agent/create.js +10 -2
- package/dist/server/server/agent/create-agent-mode.d.ts +3 -8
- package/dist/server/server/agent/create-agent-mode.js +16 -2
- package/dist/server/server/agent/import-sessions.js +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +18 -2
- package/dist/server/server/agent/providers/acp-agent.d.ts +3 -3
- package/dist/server/server/agent/providers/acp-agent.js +18 -13
- package/dist/server/server/agent/providers/codex-app-server-agent.js +16 -22
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +69 -2
- package/dist/server/server/agent/providers/opencode-agent.js +19 -8
- package/dist/server/server/agent/timeline-projection.js +30 -1
- package/dist/server/server/atomic-file.d.ts +3 -0
- package/dist/server/server/atomic-file.js +19 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +4 -1
- package/dist/server/server/bootstrap.js +2 -0
- package/dist/server/server/chat/chat-service.js +2 -4
- package/dist/server/server/daemon-keypair.js +2 -2
- package/dist/server/server/loop-service.d.ts +4 -0
- package/dist/server/server/loop-service.js +27 -9
- package/dist/server/server/persisted-config.js +3 -3
- package/dist/server/server/private-files.d.ts +0 -1
- package/dist/server/server/private-files.js +0 -5
- package/dist/server/server/schedule/service.d.ts +6 -0
- package/dist/server/server/schedule/service.js +41 -18
- package/dist/server/server/schedule/store.js +3 -2
- package/dist/server/server/server-id.js +3 -3
- package/dist/server/server/session.d.ts +5 -15
- package/dist/server/server/session.js +184 -107
- package/dist/server/server/speech/providers/local/worker-client.js +1 -11
- package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
- package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
- package/dist/server/server/workspace-directory.d.ts +8 -0
- package/dist/server/server/workspace-directory.js +141 -15
- package/dist/server/server/workspace-registry.js +2 -6
- package/dist/server/utils/checkout-git.d.ts +0 -1
- package/dist/server/utils/checkout-git.js +23 -31
- package/dist/src/server/persisted-config.js +3 -3
- package/dist/src/server/private-files.js +0 -5
- package/package.json +9 -7
- package/dist/server/server/editor-targets.d.ts +0 -18
- 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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
978
|
+
async function getAheadOfOrigin(cwd, currentBranch, context) {
|
|
979
979
|
if (!currentBranch) {
|
|
980
980
|
return null;
|
|
981
981
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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 =
|
|
1016
|
-
|
|
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}
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
61
|
-
"@getpaseo/highlight": "0.1.
|
|
62
|
-
"@getpaseo/protocol": "0.1.
|
|
63
|
-
"@getpaseo/relay": "0.1.
|
|
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
|