@getpaseo/server 0.1.97-beta.3 → 0.1.98
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-manager.d.ts +11 -3
- package/dist/server/server/agent/agent-manager.js +95 -23
- package/dist/server/server/agent/agent-prompt.d.ts +1 -1
- package/dist/server/server/agent/agent-prompt.js +3 -10
- package/dist/server/server/agent/agent-response-loop.js +9 -3
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -3
- package/dist/server/server/agent/agent-storage.d.ts +20 -240
- package/dist/server/server/agent/agent-storage.js +6 -6
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +8 -7
- package/dist/server/server/agent/lifecycle-command.d.ts +15 -1
- package/dist/server/server/agent/lifecycle-command.js +9 -2
- package/dist/server/server/agent/mcp-server.js +263 -119
- package/dist/server/server/agent/mcp-shared.d.ts +35 -179
- package/dist/server/server/agent/provider-notices.d.ts +3 -0
- package/dist/server/server/agent/provider-notices.js +5 -0
- package/dist/server/server/agent/provider-registry.d.ts +2 -0
- package/dist/server/server/agent/provider-registry.js +10 -3
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -0
- package/dist/server/server/agent/provider-snapshot-manager.js +11 -2
- package/dist/server/server/agent/providers/claude/agent.js +257 -143
- package/dist/server/server/agent/providers/claude/models.js +7 -3
- package/dist/server/server/agent/providers/claude/project-dir.js +9 -6
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -22
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +8 -118
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +4 -3
- package/dist/server/server/agent/providers/codex-app-server-agent.js +43 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +4 -1
- package/dist/server/server/agent/providers/diagnostic-utils.d.ts +9 -0
- package/dist/server/server/agent/providers/diagnostic-utils.js +188 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +1 -5
- package/dist/server/server/agent/providers/mock-slow-provider.js +1 -1
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts +29 -2
- package/dist/server/server/agent/providers/opencode/server-manager.js +83 -17
- package/dist/server/server/agent/providers/opencode-agent.d.ts +2 -0
- package/dist/server/server/agent/providers/opencode-agent.js +14 -9
- package/dist/server/server/agent/providers/pi/agent.d.ts +1 -5
- package/dist/server/server/agent/providers/pi/agent.js +27 -14
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +391 -1261
- package/dist/server/server/agent/providers/tool-call-detail-primitives.js +26 -16
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.js +32 -2
- package/dist/server/server/loop-service.d.ts +60 -359
- package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
- package/dist/server/server/managed-processes/managed-processes.js +326 -0
- package/dist/server/server/migrations/backfill-workspace-id.migration.js +10 -6
- package/dist/server/server/package-version.d.ts +1 -7
- package/dist/server/server/paseo-worktree-service.js +15 -1
- package/dist/server/server/persisted-config.d.ts +138 -1009
- package/dist/server/server/persisted-config.js +1 -1
- package/dist/server/server/pid-lock.d.ts +1 -15
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +3 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +3 -3
- package/dist/server/server/session.d.ts +18 -1
- package/dist/server/server/session.js +424 -64
- package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +2 -2
- package/dist/server/server/speech/providers/openai/runtime.js +3 -4
- package/dist/server/server/speech/speech-types.d.ts +9 -11
- package/dist/server/server/websocket-server.d.ts +1 -0
- package/dist/server/server/websocket-server.js +15 -0
- package/dist/server/server/workspace-archive-service.js +2 -3
- package/dist/server/server/workspace-directory.js +5 -5
- package/dist/server/server/workspace-reconciliation-service.js +2 -2
- package/dist/server/server/workspace-registry.d.ts +17 -48
- package/dist/server/server/workspace-registry.js +9 -0
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +5 -1
- package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
- package/dist/server/services/quota-fetcher/manifest.js +47 -0
- package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
- package/dist/server/services/quota-fetcher/provider.js +2 -0
- package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
- package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
- package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
- package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
- package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
- package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
- package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
- package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
- package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
- package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
- package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
- package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
- package/dist/server/services/quota-fetcher/service.d.ts +28 -0
- package/dist/server/services/quota-fetcher/service.js +58 -0
- package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
- package/dist/server/services/quota-fetcher/usage.js +49 -0
- package/dist/server/terminal/terminal-session-controller.d.ts +8 -0
- package/dist/server/terminal/terminal-session-controller.js +23 -3
- package/dist/server/utils/checkout-git.js +36 -76
- package/dist/server/utils/directory-suggestions.js +98 -2
- package/dist/server/utils/worktree-metadata.d.ts +7 -59
- package/dist/src/server/persisted-config.js +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Logger } from "pino";
|
|
2
|
+
import type { ProcessTerminator, TreeKillTarget } from "../../utils/tree-kill.js";
|
|
3
|
+
export interface ManagedProcessSnapshot {
|
|
4
|
+
pid: number;
|
|
5
|
+
commandLine: string | null;
|
|
6
|
+
startedAt: string | null;
|
|
7
|
+
}
|
|
8
|
+
export type ManagedProcessInspection = {
|
|
9
|
+
status: "alive";
|
|
10
|
+
snapshot: ManagedProcessSnapshot;
|
|
11
|
+
} | {
|
|
12
|
+
status: "not-found";
|
|
13
|
+
} | {
|
|
14
|
+
status: "error";
|
|
15
|
+
error: unknown;
|
|
16
|
+
};
|
|
17
|
+
export interface ManagedProcessTable {
|
|
18
|
+
inspect(pid: number): Promise<ManagedProcessInspection>;
|
|
19
|
+
}
|
|
20
|
+
export interface ManagedProcessCommandRunner {
|
|
21
|
+
exec(command: string, args: string[]): Promise<{
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
export interface ManagedProcessOwner {
|
|
27
|
+
provider: string;
|
|
28
|
+
kind: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ManagedProcessRecordInput {
|
|
31
|
+
owner: ManagedProcessOwner;
|
|
32
|
+
pid: number;
|
|
33
|
+
command: string;
|
|
34
|
+
args: string[];
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
export interface ManagedProcessRecord extends ManagedProcessRecordInput {
|
|
38
|
+
id: string;
|
|
39
|
+
metadata: Record<string, unknown>;
|
|
40
|
+
identity: {
|
|
41
|
+
commandLine: string | null;
|
|
42
|
+
startedAt: string | null;
|
|
43
|
+
};
|
|
44
|
+
createdAt: string;
|
|
45
|
+
}
|
|
46
|
+
export interface ManagedProcessReapResult {
|
|
47
|
+
checked: number;
|
|
48
|
+
dead: number;
|
|
49
|
+
mismatched: number;
|
|
50
|
+
removed: number;
|
|
51
|
+
terminated: number;
|
|
52
|
+
errors: Array<{
|
|
53
|
+
id: string;
|
|
54
|
+
message: string;
|
|
55
|
+
}>;
|
|
56
|
+
}
|
|
57
|
+
export interface ManagedProcessRegistry {
|
|
58
|
+
record(input: ManagedProcessRecordInput): Promise<ManagedProcessRecord>;
|
|
59
|
+
remove(id: string): Promise<void>;
|
|
60
|
+
list(): Promise<ManagedProcessRecord[]>;
|
|
61
|
+
reapStale(): Promise<ManagedProcessReapResult>;
|
|
62
|
+
}
|
|
63
|
+
interface ManagedProcessRegistryOptions {
|
|
64
|
+
paseoHome: string;
|
|
65
|
+
processTable: ManagedProcessTable;
|
|
66
|
+
terminateProcess: ProcessTerminator;
|
|
67
|
+
logger: Logger;
|
|
68
|
+
}
|
|
69
|
+
export declare function createManagedProcessRegistry(options: ManagedProcessRegistryOptions): ManagedProcessRegistry;
|
|
70
|
+
export declare function createSystemManagedProcessTable(options?: {
|
|
71
|
+
platform?: NodeJS.Platform;
|
|
72
|
+
commandRunner?: ManagedProcessCommandRunner;
|
|
73
|
+
}): ManagedProcessTable;
|
|
74
|
+
export declare function createPidTarget(pid: number): TreeKillTarget;
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=managed-processes.d.ts.map
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { writeJsonFileAtomic } from "../atomic-file.js";
|
|
6
|
+
import { execCommand } from "../../utils/spawn.js";
|
|
7
|
+
const MANAGED_PROCESS_GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
8
|
+
const MANAGED_PROCESS_FORCE_SHUTDOWN_TIMEOUT_MS = 1000;
|
|
9
|
+
const MANAGED_PROCESS_EXIT_POLL_INTERVAL_MS = 50;
|
|
10
|
+
const MANAGED_PROCESS_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
11
|
+
// `ps -o lstart` emits a fixed-width 24-char ctime stamp, e.g. "Sat Jun 20 10:30:40 2026".
|
|
12
|
+
const POSIX_LSTART_WIDTH = 24;
|
|
13
|
+
const ManagedProcessRecordSchema = z.object({
|
|
14
|
+
id: z.string().min(1),
|
|
15
|
+
owner: z.object({
|
|
16
|
+
provider: z.string().min(1),
|
|
17
|
+
kind: z.string().min(1),
|
|
18
|
+
}),
|
|
19
|
+
pid: z.number().int().positive(),
|
|
20
|
+
command: z.string().min(1),
|
|
21
|
+
args: z.array(z.string()),
|
|
22
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
23
|
+
identity: z.object({
|
|
24
|
+
commandLine: z.string().nullable(),
|
|
25
|
+
startedAt: z.string().nullable(),
|
|
26
|
+
}),
|
|
27
|
+
createdAt: z.string().min(1),
|
|
28
|
+
});
|
|
29
|
+
const WindowsProcessSnapshotSchema = z.object({
|
|
30
|
+
ProcessId: z.number().int().positive(),
|
|
31
|
+
CommandLine: z.string().nullable().optional(),
|
|
32
|
+
CreationDate: z.string().nullable().optional(),
|
|
33
|
+
});
|
|
34
|
+
export function createManagedProcessRegistry(options) {
|
|
35
|
+
return new FileBackedManagedProcessRegistry(options);
|
|
36
|
+
}
|
|
37
|
+
export function createSystemManagedProcessTable(options) {
|
|
38
|
+
return new SystemManagedProcessTable({
|
|
39
|
+
platform: options?.platform ?? process.platform,
|
|
40
|
+
commandRunner: options?.commandRunner ?? {
|
|
41
|
+
exec: execCommand,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
class SystemManagedProcessTable {
|
|
46
|
+
constructor(options) {
|
|
47
|
+
this.platform = options.platform;
|
|
48
|
+
this.commandRunner = options.commandRunner;
|
|
49
|
+
}
|
|
50
|
+
async inspect(pid) {
|
|
51
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
52
|
+
return { status: "not-found" };
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return this.platform === "win32"
|
|
56
|
+
? await this.inspectWindows(pid)
|
|
57
|
+
: await this.inspectPosix(pid);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return { status: "error", error };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async inspectPosix(pid) {
|
|
64
|
+
let stdout;
|
|
65
|
+
try {
|
|
66
|
+
({ stdout } = await this.commandRunner.exec("ps", [
|
|
67
|
+
"-ww",
|
|
68
|
+
"-p",
|
|
69
|
+
String(pid),
|
|
70
|
+
"-o",
|
|
71
|
+
"lstart=",
|
|
72
|
+
"-o",
|
|
73
|
+
"command=",
|
|
74
|
+
]));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
// `ps -p <pid>` exits non-zero when no process matches the pid; a numeric
|
|
78
|
+
// exit code means ps ran and found nothing, distinct from ps failing to run.
|
|
79
|
+
return isCommandExitFailure(error) ? { status: "not-found" } : { status: "error", error };
|
|
80
|
+
}
|
|
81
|
+
const line = stdout.trimEnd();
|
|
82
|
+
if (!line) {
|
|
83
|
+
return { status: "not-found" };
|
|
84
|
+
}
|
|
85
|
+
const startedAt = line.slice(0, POSIX_LSTART_WIDTH).trim();
|
|
86
|
+
const commandLine = line.slice(POSIX_LSTART_WIDTH).trim();
|
|
87
|
+
return {
|
|
88
|
+
status: "alive",
|
|
89
|
+
snapshot: {
|
|
90
|
+
pid,
|
|
91
|
+
commandLine: commandLine || null,
|
|
92
|
+
startedAt: startedAt || null,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async inspectWindows(pid) {
|
|
97
|
+
const command = [
|
|
98
|
+
`$process = Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}';`,
|
|
99
|
+
"if ($process) { $process | Select-Object ProcessId,CommandLine,CreationDate | ConvertTo-Json -Compress }",
|
|
100
|
+
].join(" ");
|
|
101
|
+
const { stdout } = await this.commandRunner.exec("powershell.exe", [
|
|
102
|
+
"-NoProfile",
|
|
103
|
+
"-NonInteractive",
|
|
104
|
+
"-Command",
|
|
105
|
+
command,
|
|
106
|
+
]);
|
|
107
|
+
const trimmed = stdout.trim();
|
|
108
|
+
if (!trimmed) {
|
|
109
|
+
return { status: "not-found" };
|
|
110
|
+
}
|
|
111
|
+
const parsed = WindowsProcessSnapshotSchema.parse(JSON.parse(trimmed));
|
|
112
|
+
return {
|
|
113
|
+
status: "alive",
|
|
114
|
+
snapshot: {
|
|
115
|
+
pid,
|
|
116
|
+
commandLine: parsed.CommandLine ?? null,
|
|
117
|
+
startedAt: parsed.CreationDate ?? null,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
class FileBackedManagedProcessRegistry {
|
|
123
|
+
constructor(options) {
|
|
124
|
+
this.directory = path.join(options.paseoHome, "runtime", "managed-processes");
|
|
125
|
+
this.processTable = options.processTable;
|
|
126
|
+
this.terminateProcess = options.terminateProcess;
|
|
127
|
+
this.logger = options.logger.child({ module: "managed-processes" });
|
|
128
|
+
}
|
|
129
|
+
async record(input) {
|
|
130
|
+
const inspection = await this.processTable.inspect(input.pid);
|
|
131
|
+
const snapshot = inspection.status === "alive" ? inspection.snapshot : null;
|
|
132
|
+
const record = {
|
|
133
|
+
id: randomUUID(),
|
|
134
|
+
owner: input.owner,
|
|
135
|
+
pid: input.pid,
|
|
136
|
+
command: input.command,
|
|
137
|
+
args: input.args,
|
|
138
|
+
metadata: input.metadata ?? {},
|
|
139
|
+
identity: {
|
|
140
|
+
commandLine: snapshot?.commandLine ?? null,
|
|
141
|
+
startedAt: snapshot?.startedAt ?? null,
|
|
142
|
+
},
|
|
143
|
+
createdAt: new Date().toISOString(),
|
|
144
|
+
};
|
|
145
|
+
await writeJsonFileAtomic(this.recordPath(record.id), record);
|
|
146
|
+
return record;
|
|
147
|
+
}
|
|
148
|
+
async remove(id) {
|
|
149
|
+
await fs.rm(this.recordPath(id), { force: true });
|
|
150
|
+
}
|
|
151
|
+
async list() {
|
|
152
|
+
const entries = await this.readEntries();
|
|
153
|
+
return entries.map((entry) => entry.record);
|
|
154
|
+
}
|
|
155
|
+
async reapStale() {
|
|
156
|
+
const result = {
|
|
157
|
+
checked: 0,
|
|
158
|
+
dead: 0,
|
|
159
|
+
mismatched: 0,
|
|
160
|
+
removed: 0,
|
|
161
|
+
terminated: 0,
|
|
162
|
+
errors: [],
|
|
163
|
+
};
|
|
164
|
+
for (const entry of await this.readEntries()) {
|
|
165
|
+
result.checked += 1;
|
|
166
|
+
try {
|
|
167
|
+
const inspection = await this.processTable.inspect(entry.record.pid);
|
|
168
|
+
if (inspection.status === "not-found") {
|
|
169
|
+
await fs.rm(entry.path, { force: true });
|
|
170
|
+
result.dead += 1;
|
|
171
|
+
result.removed += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (inspection.status === "error") {
|
|
175
|
+
// Inspection failed, so we cannot tell whether the helper is still
|
|
176
|
+
// alive. Keep the record and retry on the next reconcile rather than
|
|
177
|
+
// orphaning a live process by deleting its record without killing it.
|
|
178
|
+
const message = inspection.error instanceof Error ? inspection.error.message : String(inspection.error);
|
|
179
|
+
result.errors.push({ id: entry.record.id, message });
|
|
180
|
+
this.logger.warn({
|
|
181
|
+
err: inspection.error,
|
|
182
|
+
id: entry.record.id,
|
|
183
|
+
pid: entry.record.pid,
|
|
184
|
+
owner: entry.record.owner,
|
|
185
|
+
}, "Could not inspect managed helper process; leaving record for next reconcile");
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const snapshot = inspection.snapshot;
|
|
189
|
+
if (!processIdentityMatches(entry.record, snapshot)) {
|
|
190
|
+
await fs.rm(entry.path, { force: true });
|
|
191
|
+
result.mismatched += 1;
|
|
192
|
+
result.removed += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
await this.terminateProcess(createPidTarget(entry.record.pid), {
|
|
196
|
+
gracefulTimeoutMs: MANAGED_PROCESS_GRACEFUL_SHUTDOWN_TIMEOUT_MS,
|
|
197
|
+
forceTimeoutMs: MANAGED_PROCESS_FORCE_SHUTDOWN_TIMEOUT_MS,
|
|
198
|
+
onForceSignal: () => {
|
|
199
|
+
this.logger.warn({
|
|
200
|
+
pid: entry.record.pid,
|
|
201
|
+
owner: entry.record.owner,
|
|
202
|
+
timeoutMs: MANAGED_PROCESS_GRACEFUL_SHUTDOWN_TIMEOUT_MS,
|
|
203
|
+
}, "Managed helper process did not exit after SIGTERM; sending SIGKILL");
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
await fs.rm(entry.path, { force: true });
|
|
207
|
+
result.terminated += 1;
|
|
208
|
+
result.removed += 1;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
212
|
+
result.errors.push({ id: entry.record.id, message });
|
|
213
|
+
this.logger.warn({ err: error, id: entry.record.id, pid: entry.record.pid, owner: entry.record.owner }, "Failed to reap managed helper process");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
recordPath(id) {
|
|
219
|
+
if (!MANAGED_PROCESS_ID_PATTERN.test(id)) {
|
|
220
|
+
throw new Error(`Invalid managed process record id: ${id}`);
|
|
221
|
+
}
|
|
222
|
+
return path.join(this.directory, `${id}.json`);
|
|
223
|
+
}
|
|
224
|
+
async readEntries() {
|
|
225
|
+
let fileNames;
|
|
226
|
+
try {
|
|
227
|
+
fileNames = await fs.readdir(this.directory);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
if (isNodeErrorWithCode(error, "ENOENT")) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
const entries = [];
|
|
236
|
+
for (const fileName of fileNames) {
|
|
237
|
+
if (!fileName.endsWith(".json")) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const filePath = path.join(this.directory, fileName);
|
|
241
|
+
try {
|
|
242
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
243
|
+
const parsed = ManagedProcessRecordSchema.parse(JSON.parse(raw));
|
|
244
|
+
entries.push({ path: filePath, record: parsed });
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
// A single corrupt or partially-written record must not abort the whole
|
|
248
|
+
// reconcile and leave every other leftover un-reaped. Skip it.
|
|
249
|
+
this.logger.warn({ err: error, file: fileName }, "Skipping unreadable managed process record");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return entries;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function processIdentityMatches(record, snapshot) {
|
|
256
|
+
if (record.identity.startedAt && snapshot.startedAt) {
|
|
257
|
+
if (record.identity.startedAt !== snapshot.startedAt) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return snapshot.commandLine ? commandLineMatchesRecord(record, snapshot.commandLine) : true;
|
|
261
|
+
}
|
|
262
|
+
if (record.identity.commandLine && snapshot.commandLine) {
|
|
263
|
+
return (normalizeCommandLine(record.identity.commandLine) ===
|
|
264
|
+
normalizeCommandLine(snapshot.commandLine));
|
|
265
|
+
}
|
|
266
|
+
return snapshot.commandLine ? commandLineMatchesRecord(record, snapshot.commandLine) : false;
|
|
267
|
+
}
|
|
268
|
+
function commandLineMatchesRecord(record, commandLine) {
|
|
269
|
+
// Require the command name and args as one contiguous run, not scattered
|
|
270
|
+
// tokens. Without exact process identity (lstart), a reused PID whose command
|
|
271
|
+
// line merely mentions "opencode", "serve" and the port elsewhere must not be
|
|
272
|
+
// mistaken for our leftover and killed.
|
|
273
|
+
const normalized = normalizeCommandLine(commandLine);
|
|
274
|
+
const commandName = path.basename(record.command).toLowerCase();
|
|
275
|
+
const signature = [commandName, ...record.args].map((token) => token.toLowerCase()).join(" ");
|
|
276
|
+
return normalized.includes(signature);
|
|
277
|
+
}
|
|
278
|
+
function normalizeCommandLine(commandLine) {
|
|
279
|
+
return commandLine.replace(/\s+/g, " ").trim().toLowerCase();
|
|
280
|
+
}
|
|
281
|
+
export function createPidTarget(pid) {
|
|
282
|
+
return {
|
|
283
|
+
pid,
|
|
284
|
+
exitCode: null,
|
|
285
|
+
signalCode: null,
|
|
286
|
+
kill(signal) {
|
|
287
|
+
process.kill(pid, signal);
|
|
288
|
+
return true;
|
|
289
|
+
},
|
|
290
|
+
// The reaper has no ChildProcess handle for a leftover from a previous
|
|
291
|
+
// daemon, so it observes exit by polling the pid. Without this, termination
|
|
292
|
+
// can never see a graceful SIGTERM exit and always waits out the full
|
|
293
|
+
// graceful+force window before escalating to SIGKILL.
|
|
294
|
+
once(_event, listener) {
|
|
295
|
+
const timer = setInterval(() => {
|
|
296
|
+
if (!isProcessAlive(pid)) {
|
|
297
|
+
clearInterval(timer);
|
|
298
|
+
listener();
|
|
299
|
+
}
|
|
300
|
+
}, MANAGED_PROCESS_EXIT_POLL_INTERVAL_MS);
|
|
301
|
+
timer.unref();
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function isProcessAlive(pid) {
|
|
306
|
+
try {
|
|
307
|
+
process.kill(pid, 0);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
return isNodeErrorWithCode(error, "EPERM");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function isCommandExitFailure(error) {
|
|
315
|
+
// execFile rejects with a numeric `code` (the process exit status) when the
|
|
316
|
+
// command ran and exited non-zero; a string `code` (e.g. "ENOENT") means it
|
|
317
|
+
// never ran.
|
|
318
|
+
return typeof error?.code === "number";
|
|
319
|
+
}
|
|
320
|
+
function isNodeErrorWithCode(error, code) {
|
|
321
|
+
return (typeof error === "object" &&
|
|
322
|
+
error !== null &&
|
|
323
|
+
"code" in error &&
|
|
324
|
+
error.code === code);
|
|
325
|
+
}
|
|
326
|
+
//# sourceMappingURL=managed-processes.js.map
|
|
@@ -12,16 +12,18 @@ import { resolve, sep } from "node:path";
|
|
|
12
12
|
// Picks the workspace that owned `cwd` for a legacy, unstamped agent record.
|
|
13
13
|
// Prefers an exact-cwd workspace (oldest wins) and otherwise attributes to the
|
|
14
14
|
// deepest enclosing workspace directory, never letting the home directory own
|
|
15
|
-
// descendants.
|
|
16
|
-
|
|
15
|
+
// descendants. Live records only consider live workspaces; archived records can
|
|
16
|
+
// resolve to archived workspaces so History/restore retains legacy ownership.
|
|
17
|
+
// Used only by the one-time backfill below.
|
|
18
|
+
function resolveLegacyWorkspaceOwner(cwd, workspaces, options) {
|
|
17
19
|
const normalizedCwd = resolve(cwd);
|
|
18
20
|
const userHome = resolve(homedir());
|
|
19
|
-
const
|
|
20
|
-
const exactMatches =
|
|
21
|
+
const candidateWorkspaces = Array.from(workspaces).filter((workspace) => options?.includeArchived === true || !workspace.archivedAt);
|
|
22
|
+
const exactMatches = candidateWorkspaces.filter((workspace) => resolve(workspace.cwd) === normalizedCwd);
|
|
21
23
|
if (exactMatches.length > 0) {
|
|
22
24
|
return oldestWorkspace(exactMatches).workspaceId;
|
|
23
25
|
}
|
|
24
|
-
const prefixMatches =
|
|
26
|
+
const prefixMatches = candidateWorkspaces.filter((workspace) => {
|
|
25
27
|
const workspaceCwd = resolve(workspace.cwd);
|
|
26
28
|
if (workspaceCwd === userHome) {
|
|
27
29
|
return false;
|
|
@@ -45,7 +47,9 @@ export async function backfillWorkspaceIdForLegacyAgents(options) {
|
|
|
45
47
|
if (record.workspaceId) {
|
|
46
48
|
continue;
|
|
47
49
|
}
|
|
48
|
-
const workspaceId = resolveLegacyWorkspaceOwner(record.cwd, workspaceRecords
|
|
50
|
+
const workspaceId = resolveLegacyWorkspaceOwner(record.cwd, workspaceRecords, {
|
|
51
|
+
includeArchived: record.archivedAt != null,
|
|
52
|
+
});
|
|
49
53
|
if (!workspaceId) {
|
|
50
54
|
continue;
|
|
51
55
|
}
|
|
@@ -6,13 +6,7 @@ interface ResolvePackageVersionParams {
|
|
|
6
6
|
export declare const packageJsonSchema: z.ZodObject<{
|
|
7
7
|
name: z.ZodOptional<z.ZodString>;
|
|
8
8
|
version: z.ZodOptional<z.ZodString>;
|
|
9
|
-
},
|
|
10
|
-
name?: string | undefined;
|
|
11
|
-
version?: string | undefined;
|
|
12
|
-
}, {
|
|
13
|
-
name?: string | undefined;
|
|
14
|
-
version?: string | undefined;
|
|
15
|
-
}>;
|
|
9
|
+
}, z.core.$strip>;
|
|
16
10
|
export type PackageJson = z.infer<typeof packageJsonSchema>;
|
|
17
11
|
export declare class PackageVersionResolutionError extends Error {
|
|
18
12
|
constructor(params: {
|
|
@@ -4,7 +4,7 @@ import { classifyDirectoryForProjectMembership, deriveProjectGroupingName, gener
|
|
|
4
4
|
import { createWorktreeCore, } from "./worktree-core.js";
|
|
5
5
|
import { validateBranchSlug } from "../utils/worktree.js";
|
|
6
6
|
import { getCurrentBranch, localBranchExists, renameCurrentBranch } from "../utils/checkout-git.js";
|
|
7
|
-
import { markPaseoWorktreeFirstAgentBranchAutoNameAttempted, readPaseoWorktreeMetadata, writePaseoWorktreeFirstAgentBranchAutoNameMetadata, } from "../utils/worktree-metadata.js";
|
|
7
|
+
import { markPaseoWorktreeFirstAgentBranchAutoNameAttempted, normalizeBaseRefName, readPaseoWorktreeMetadata, writePaseoWorktreeFirstAgentBranchAutoNameMetadata, } from "../utils/worktree-metadata.js";
|
|
8
8
|
import { resolveFirstAgentPromptTitle } from "./agent/create-agent-title.js";
|
|
9
9
|
import { buildAgentBranchNameSeed } from "./agent/prompt-attachments.js";
|
|
10
10
|
export async function createPaseoWorktree(input, deps) {
|
|
@@ -15,6 +15,7 @@ export async function createPaseoWorktree(input, deps) {
|
|
|
15
15
|
projectId: input.projectId,
|
|
16
16
|
repoRoot: createdWorktree.repoRoot,
|
|
17
17
|
worktree: createdWorktree.worktree,
|
|
18
|
+
baseBranch: resolveIntentBaseBranch(createdWorktree.intent),
|
|
18
19
|
title: resolveFirstAgentPromptTitle(input.firstAgentContext),
|
|
19
20
|
deps,
|
|
20
21
|
});
|
|
@@ -109,6 +110,18 @@ function maybeMarkFirstAgentBranchAutoNameEligible(options) {
|
|
|
109
110
|
placeholderBranchName: createdWorktree.worktree.branchName,
|
|
110
111
|
});
|
|
111
112
|
}
|
|
113
|
+
// The base branch is normalized to match worktree.json's baseRefName (origin/
|
|
114
|
+
// stripped). checkout-branch worktrees have no distinct base, so they stay null.
|
|
115
|
+
function resolveIntentBaseBranch(intent) {
|
|
116
|
+
switch (intent.kind) {
|
|
117
|
+
case "branch-off":
|
|
118
|
+
return normalizeBaseRefName(intent.baseBranch);
|
|
119
|
+
case "checkout-github-pr":
|
|
120
|
+
return normalizeBaseRefName(intent.baseRefName);
|
|
121
|
+
case "checkout-branch":
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
112
125
|
async function upsertWorkspaceForWorktree(options) {
|
|
113
126
|
const normalizedCwd = resolve(options.worktree.worktreePath);
|
|
114
127
|
const normalizedInputCwd = resolve(options.inputCwd);
|
|
@@ -142,6 +155,7 @@ async function upsertWorkspaceForWorktree(options) {
|
|
|
142
155
|
kind: "worktree",
|
|
143
156
|
displayName: options.worktree.branchName || normalizedCwd,
|
|
144
157
|
branch: options.worktree.branchName || null,
|
|
158
|
+
baseBranch: options.baseBranch ?? null,
|
|
145
159
|
title: options.title ?? null,
|
|
146
160
|
createdAt: now,
|
|
147
161
|
updatedAt: now,
|