dev3000 0.0.173 → 0.0.174

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.
@@ -0,0 +1,75 @@
1
+ import chalk from "chalk";
2
+ import { spawn } from "child_process";
3
+ import { basename } from "path";
4
+ import { checkBinaryExists } from "../utils/agent-selection.js";
5
+ import { readProjectAgentName as readRememberedProjectAgentName } from "../utils/project-metadata.js";
6
+ import { loadUserConfig } from "../utils/user-config.js";
7
+ function ensureCommandPath(env) {
8
+ if (!env.PATH || env.PATH === "") {
9
+ env.PATH = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
10
+ }
11
+ return env;
12
+ }
13
+ export function readProjectAgentName(cwd = process.cwd()) {
14
+ return readRememberedProjectAgentName(cwd);
15
+ }
16
+ export function resolveResumeAgentName(cwd = process.cwd()) {
17
+ const projectAgentName = readProjectAgentName(cwd);
18
+ if (projectAgentName) {
19
+ return projectAgentName;
20
+ }
21
+ const defaultAgentName = loadUserConfig().defaultAgent?.name;
22
+ return typeof defaultAgentName === "string" && defaultAgentName.trim().length > 0 ? defaultAgentName.trim() : null;
23
+ }
24
+ export function getResumeLaunchSpec(agentName, cwd = process.cwd()) {
25
+ switch (agentName.toLowerCase()) {
26
+ case "claude":
27
+ return { agentName, binary: "claude", args: ["-c"] };
28
+ case "claude-yolo":
29
+ return { agentName, binary: "claude", args: ["--dangerously-skip-permissions", "-c"] };
30
+ case "codex":
31
+ return { agentName, binary: "codex", args: ["resume", "--last", "-C", cwd] };
32
+ case "codex-yolo":
33
+ return {
34
+ agentName,
35
+ binary: "codex",
36
+ args: ["resume", "--last", "--dangerously-bypass-approvals-and-sandbox", "-C", cwd]
37
+ };
38
+ case "opencode":
39
+ return { agentName, binary: "opencode", args: ["-c"] };
40
+ default:
41
+ return null;
42
+ }
43
+ }
44
+ export async function resumeLastAgent(cwd = process.cwd()) {
45
+ const agentName = resolveResumeAgentName(cwd);
46
+ if (!agentName) {
47
+ console.error(chalk.red("❌ No previous agent found for this project."));
48
+ console.error(chalk.gray("Start d3k with an agent once, then `d3k resume` can reopen it here."));
49
+ process.exit(1);
50
+ }
51
+ const spec = getResumeLaunchSpec(agentName, cwd);
52
+ if (!spec) {
53
+ console.error(chalk.red(`❌ Agent "${agentName}" does not support automatic resume yet.`));
54
+ console.error(chalk.gray("Supported today: claude, claude-yolo, codex, codex-yolo, opencode"));
55
+ process.exit(1);
56
+ }
57
+ if (!checkBinaryExists(spec.binary)) {
58
+ console.error(chalk.red(`❌ ${spec.binary} is not installed or not on PATH.`));
59
+ process.exit(1);
60
+ }
61
+ console.log(chalk.cyan(`↩ Resuming ${spec.agentName} for ${basename(cwd)}...`));
62
+ const env = ensureCommandPath({ ...process.env });
63
+ const child = spawn(spec.binary, spec.args, {
64
+ cwd,
65
+ env,
66
+ stdio: "inherit",
67
+ shell: false
68
+ });
69
+ const exitCode = await new Promise((resolve, reject) => {
70
+ child.once("error", reject);
71
+ child.once("exit", (code) => resolve(code ?? 1));
72
+ });
73
+ process.exit(exitCode);
74
+ }
75
+ //# sourceMappingURL=resume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.js","sourceRoot":"","sources":["../../src/commands/resume.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAC/B,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,oBAAoB,IAAI,8BAA8B,EAAE,MAAM,8BAA8B,CAAA;AACrG,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAQxD,SAAS,iBAAiB,CAAC,GAAsB;IAC/C,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,GAAG,gEAAgE,CAAA;IAC7E,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC9D,OAAO,8BAA8B,CAAC,GAAG,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAChE,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAA;IAClD,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,gBAAgB,CAAA;IACzB,CAAC;IAED,MAAM,gBAAgB,GAAG,cAAc,EAAE,CAAC,YAAY,EAAE,IAAI,CAAA;IAC5D,OAAO,OAAO,gBAAgB,KAAK,QAAQ,IAAI,gBAAgB,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACpH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAChF,QAAQ,SAAS,CAAC,WAAW,EAAE,EAAE,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAA;QACtD,KAAK,aAAa;YAChB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,gCAAgC,EAAE,IAAI,CAAC,EAAE,CAAA;QACxF,KAAK,OAAO;YACV,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAA;QAC9E,KAAK,YAAY;YACf,OAAO;gBACL,SAAS;gBACT,MAAM,EAAE,OAAO;gBACf,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,4CAA4C,EAAE,IAAI,EAAE,GAAG,CAAC;aACpF,CAAA;QACH,KAAK,UAAU;YACb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAA;QACxD;YACE,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC/D,MAAM,SAAS,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAA;IAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC,CAAA;QACvE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC,CAAA;QAChG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,IAAI,GAAG,mBAAmB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAChD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,SAAS,0CAA0C,CAAC,CAAC,CAAA;QACzF,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC,CAAA;QAC9F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,MAAM,mCAAmC,CAAC,CAAC,CAAA;QAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,SAAS,QAAQ,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;IAE/E,MAAM,GAAG,GAAG,iBAAiB,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE;QAC1C,GAAG;QACH,GAAG;QACH,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK;KACb,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC7D,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC3B,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;AACxB,CAAC"}
@@ -39,6 +39,7 @@ interface DevEnvironmentOptions {
39
39
  port: string;
40
40
  serverCommand: string;
41
41
  startupTimeoutSeconds: number;
42
+ browserNavigationTimeoutSeconds: number;
42
43
  profileDir: string;
43
44
  browserTool: "agent-browser" | "next-browser";
44
45
  logFile: string;
@@ -57,6 +58,7 @@ interface DevEnvironmentOptions {
57
58
  debugPort?: number;
58
59
  headless?: boolean;
59
60
  withAgent?: string;
61
+ agentName?: string;
60
62
  skillsAgentId?: string;
61
63
  autoSkills?: boolean;
62
64
  installSkills?: boolean;
@@ -69,6 +71,26 @@ interface DevEnvironmentOptions {
69
71
  * This function is used to reason about multi-instance shutdown behavior.
70
72
  */
71
73
  export declare function countActiveD3kInstances(excludeCurrentPid?: boolean): number;
74
+ type D3kLockInfo = {
75
+ pid: number;
76
+ cwd?: string | null;
77
+ createdAt?: string | null;
78
+ processStartTime?: string | null;
79
+ };
80
+ type LockValidationResult = {
81
+ active: boolean;
82
+ reason: string;
83
+ lockInfo: D3kLockInfo | null;
84
+ };
85
+ type LockValidationDeps = {
86
+ processExists?: (pid: number) => boolean;
87
+ getProcessStartTime?: (pid: number) => string | null;
88
+ getProcessCommand?: (pid: number) => string | null;
89
+ };
90
+ export declare function parseD3kLockContent(content: string): D3kLockInfo | null;
91
+ export declare function getProcessStartTime(pid: number): string | null;
92
+ export declare function getProcessCommand(pid: number): string | null;
93
+ export declare function validateD3kLockContent(content: string, deps?: LockValidationDeps): LockValidationResult;
72
94
  /**
73
95
  * Check if a server is actually listening and responding on a port.
74
96
  * Used for waiting for a dev server to start up.
@@ -94,7 +116,7 @@ export declare function createPersistentLogFile(): string;
94
116
  * processes belong to THIS d3k instance so we don't accidentally kill
95
117
  * Chrome instances from other d3k sessions.
96
118
  */
97
- export declare function writeSessionInfo(projectName: string, logFilePath: string, appPort: string, publicUrl?: string | null, cdpUrl?: string | null, chromePids?: number[], serverCommand?: string, framework?: "nextjs" | "svelte" | "other", serverPid?: number, skillsInstalled?: string[], skillsAgentId?: string | null, preferredBrowserTool?: "agent-browser" | "next-browser"): void;
119
+ export declare function writeSessionInfo(projectName: string, logFilePath: string, appPort: string, publicUrl?: string | null, cdpUrl?: string | null, chromePids?: number[], serverCommand?: string, framework?: "nextjs" | "svelte" | "other", serverPid?: number, skillsInstalled?: string[], skillsAgentId?: string | null, preferredBrowserTool?: "agent-browser" | "next-browser", agentName?: string | null): void;
98
120
  export declare function getSessionChromePids(projectName: string): number[];
99
121
  export declare class DevEnvironment {
100
122
  private serverProcess;
@@ -1 +1 @@
1
- {"version":3,"file":"dev-environment.d.ts","sourceRoot":"","sources":["../src/dev-environment.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dev-environment.d.ts","sourceRoot":"","sources":["../src/dev-environment.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import chalk from "chalk";
2
2
  import { execSync, spawn, spawnSync } from "child_process";
3
3
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "fs";
4
4
  import https from "https";
5
- import { createServer } from "net";
5
+ import { createConnection, createServer } from "net";
6
6
  import ora from "ora";
7
7
  import { homedir, tmpdir } from "os";
8
8
  import { dirname, join, resolve, sep } from "path";
@@ -14,6 +14,7 @@ import { ScreencastManager } from "./screencast-manager.js";
14
14
  import { NextJsErrorDetector, OutputProcessor, StandardLogParser } from "./services/parsers/index.js";
15
15
  import { getBundledSkillsPath, listAvailableSkills } from "./skills/index.js";
16
16
  import { DevTUI } from "./tui-interface.js";
17
+ import { readProjectAgentName, rememberProjectAgentName } from "./utils/project-metadata.js";
17
18
  import { getProjectDir, getProjectDisplayName, getProjectName } from "./utils/project-name.js";
18
19
  import { getApplicablePackages, getSkillsPathForLocation, installSkillPackage, isPackageInstalled } from "./utils/skill-installer.js";
19
20
  import { formatTimestamp } from "./utils/timestamp.js";
@@ -163,6 +164,115 @@ export function countActiveD3kInstances(excludeCurrentPid = false) {
163
164
  return excludeCurrentPid ? 0 : 1;
164
165
  }
165
166
  }
167
+ export function parseD3kLockContent(content) {
168
+ const trimmed = content.trim();
169
+ if (!trimmed)
170
+ return null;
171
+ if (/^\d+$/.test(trimmed)) {
172
+ const pid = Number.parseInt(trimmed, 10);
173
+ return Number.isInteger(pid) && pid > 0 ? { pid } : null;
174
+ }
175
+ try {
176
+ const parsed = JSON.parse(trimmed);
177
+ const pid = parsed?.pid;
178
+ if (!parsed || typeof parsed !== "object" || typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) {
179
+ return null;
180
+ }
181
+ return {
182
+ pid,
183
+ cwd: typeof parsed.cwd === "string" ? parsed.cwd : null,
184
+ createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : null,
185
+ processStartTime: typeof parsed.processStartTime === "string" ? parsed.processStartTime : null
186
+ };
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }
192
+ function getProcessPsField(pid, field) {
193
+ try {
194
+ const result = spawnSync("ps", ["-p", String(pid), "-o", `${field}=`], {
195
+ encoding: "utf8",
196
+ stdio: ["ignore", "pipe", "ignore"]
197
+ });
198
+ if (result.status !== 0)
199
+ return null;
200
+ const output = result.stdout.trim();
201
+ return output || null;
202
+ }
203
+ catch {
204
+ return null;
205
+ }
206
+ }
207
+ export function getProcessStartTime(pid) {
208
+ return getProcessPsField(pid, "lstart");
209
+ }
210
+ export function getProcessCommand(pid) {
211
+ return getProcessPsField(pid, "command");
212
+ }
213
+ function isLikelyD3kCommand(command) {
214
+ const normalized = command.toLowerCase();
215
+ return normalized.includes("d3k") || normalized.includes("dev3000");
216
+ }
217
+ export function validateD3kLockContent(content, deps = {}) {
218
+ const lockInfo = parseD3kLockContent(content);
219
+ if (!lockInfo) {
220
+ return {
221
+ active: false,
222
+ reason: "lock file contents are invalid",
223
+ lockInfo: null
224
+ };
225
+ }
226
+ const processExists = deps.processExists ||
227
+ ((pid) => {
228
+ try {
229
+ process.kill(pid, 0);
230
+ return true;
231
+ }
232
+ catch {
233
+ return false;
234
+ }
235
+ });
236
+ if (!processExists(lockInfo.pid)) {
237
+ return {
238
+ active: false,
239
+ reason: `process ${lockInfo.pid} is not running`,
240
+ lockInfo
241
+ };
242
+ }
243
+ const getStartTime = deps.getProcessStartTime || getProcessStartTime;
244
+ if (lockInfo.processStartTime) {
245
+ const liveStartTime = getStartTime(lockInfo.pid);
246
+ if (liveStartTime && liveStartTime !== lockInfo.processStartTime) {
247
+ return {
248
+ active: false,
249
+ reason: `pid ${lockInfo.pid} was reused by a different process`,
250
+ lockInfo
251
+ };
252
+ }
253
+ if (liveStartTime === lockInfo.processStartTime) {
254
+ return {
255
+ active: true,
256
+ reason: `process ${lockInfo.pid} matches recorded start time`,
257
+ lockInfo
258
+ };
259
+ }
260
+ }
261
+ const getCommand = deps.getProcessCommand || getProcessCommand;
262
+ const liveCommand = getCommand(lockInfo.pid);
263
+ if (liveCommand && !isLikelyD3kCommand(liveCommand)) {
264
+ return {
265
+ active: false,
266
+ reason: `pid ${lockInfo.pid} belongs to a non-d3k process`,
267
+ lockInfo
268
+ };
269
+ }
270
+ return {
271
+ active: true,
272
+ reason: `process ${lockInfo.pid} still owns the lock`,
273
+ lockInfo
274
+ };
275
+ }
166
276
  /**
167
277
  * Check if a port is available for binding (no process is listening on it).
168
278
  * Used for finding available ports before starting servers.
@@ -172,6 +282,40 @@ async function isPortAvailable(port) {
172
282
  if (!Number.isInteger(portNumber) || portNumber < 0 || portNumber > 65535) {
173
283
  return false;
174
284
  }
285
+ const hasActiveListener = await Promise.any(["127.0.0.1", "::1", "localhost"].map((host) => new Promise((resolve, reject) => {
286
+ const socket = createConnection({ host, port: portNumber });
287
+ let settled = false;
288
+ const finish = (result, shouldReject = false) => {
289
+ if (settled)
290
+ return;
291
+ settled = true;
292
+ socket.destroy();
293
+ if (shouldReject) {
294
+ reject(new Error("unreachable"));
295
+ }
296
+ else {
297
+ resolve(result);
298
+ }
299
+ };
300
+ socket.once("connect", () => finish(true));
301
+ socket.once("timeout", () => finish(false, true));
302
+ socket.once("error", (error) => {
303
+ if (error.code === "ECONNREFUSED" ||
304
+ error.code === "EHOSTUNREACH" ||
305
+ error.code === "ENETUNREACH" ||
306
+ error.code === "EINVAL") {
307
+ finish(false, true);
308
+ return;
309
+ }
310
+ finish(false);
311
+ });
312
+ socket.setTimeout(150);
313
+ })))
314
+ .then((result) => result)
315
+ .catch(() => false);
316
+ if (hasActiveListener) {
317
+ return false;
318
+ }
175
319
  return new Promise((resolve) => {
176
320
  const server = createServer();
177
321
  server.once("error", (error) => {
@@ -358,13 +502,15 @@ export function createPersistentLogFile() {
358
502
  * processes belong to THIS d3k instance so we don't accidentally kill
359
503
  * Chrome instances from other d3k sessions.
360
504
  */
361
- export function writeSessionInfo(projectName, logFilePath, appPort, publicUrl, cdpUrl, chromePids, serverCommand, framework, serverPid, skillsInstalled, skillsAgentId, preferredBrowserTool) {
505
+ export function writeSessionInfo(projectName, logFilePath, appPort, publicUrl, cdpUrl, chromePids, serverCommand, framework, serverPid, skillsInstalled, skillsAgentId, preferredBrowserTool, agentName) {
362
506
  const projectDir = getProjectDir();
363
507
  try {
364
508
  // Create project directory if it doesn't exist
365
509
  if (!existsSync(projectDir)) {
366
510
  mkdirSync(projectDir, { recursive: true });
367
511
  }
512
+ const sessionFile = join(projectDir, "session.json");
513
+ const persistedAgentName = readProjectAgentName();
368
514
  // Session file contains project info
369
515
  const sessionInfo = {
370
516
  projectName,
@@ -381,11 +527,14 @@ export function writeSessionInfo(projectName, logFilePath, appPort, publicUrl, c
381
527
  serverPid: serverPid || null,
382
528
  skillsInstalled: skillsInstalled || [],
383
529
  skillsAgentId: skillsAgentId || null,
384
- preferredBrowserTool: preferredBrowserTool || "agent-browser"
530
+ preferredBrowserTool: preferredBrowserTool || "agent-browser",
531
+ agentName: agentName || persistedAgentName || null
385
532
  };
386
533
  // Write session file in project directory
387
- const sessionFile = join(projectDir, "session.json");
388
534
  writeFileSync(sessionFile, JSON.stringify(sessionInfo, null, 2));
535
+ if (sessionInfo.agentName) {
536
+ rememberProjectAgentName(sessionInfo.agentName);
537
+ }
389
538
  }
390
539
  catch (error) {
391
540
  // Non-fatal - just log a warning
@@ -1189,21 +1338,21 @@ export class DevEnvironment {
1189
1338
  // Check if lock file exists
1190
1339
  if (existsSync(this.lockFile)) {
1191
1340
  const lockContent = readFileSync(this.lockFile, "utf8");
1192
- const oldPID = parseInt(lockContent, 10);
1193
- // Check if the process is still running
1194
- try {
1195
- process.kill(oldPID, 0); // Signal 0 just checks if process exists
1196
- // Process is running, lock is valid
1341
+ const validation = validateD3kLockContent(lockContent);
1342
+ if (validation.active) {
1343
+ this.debugLog(`Lock file is active: ${validation.reason}`);
1197
1344
  return false;
1198
1345
  }
1199
- catch {
1200
- // Process doesn't exist, remove stale lock
1201
- this.debugLog(`Removing stale lock file for PID ${oldPID}`);
1202
- unlinkSync(this.lockFile);
1203
- }
1346
+ this.debugLog(`Removing stale lock file: ${validation.reason}`);
1347
+ unlinkSync(this.lockFile);
1204
1348
  }
1205
- // Create lock file with our PID
1206
- writeFileSync(this.lockFile, process.pid.toString());
1349
+ // Store enough metadata to detect PID reuse after crashes.
1350
+ writeFileSync(this.lockFile, JSON.stringify({
1351
+ pid: process.pid,
1352
+ cwd: process.cwd(),
1353
+ createdAt: new Date().toISOString(),
1354
+ processStartTime: getProcessStartTime(process.pid)
1355
+ }));
1207
1356
  this.debugLog(`Acquired lock file: ${this.lockFile}`);
1208
1357
  return true;
1209
1358
  }
@@ -1247,7 +1396,7 @@ export class DevEnvironment {
1247
1396
  const cdpUrl = this.cdpMonitor?.getCdpUrl() || null;
1248
1397
  const chromePids = this.cdpMonitor?.getChromePids() || [];
1249
1398
  const skillsInstalled = listAvailableSkills(process.cwd());
1250
- writeSessionInfo(projectName, this.options.logFile, this.options.port, this.publicAppUrl, cdpUrl, chromePids, this.options.serverCommand, this.options.framework, this.serverProcess?.pid, skillsInstalled, this.options.skillsAgentId ?? null, this.options.browserTool);
1399
+ writeSessionInfo(projectName, this.options.logFile, this.options.port, this.publicAppUrl, cdpUrl, chromePids, this.options.serverCommand, this.options.framework, this.serverProcess?.pid, skillsInstalled, this.options.skillsAgentId ?? null, this.options.browserTool, this.options.agentName ?? null);
1251
1400
  }
1252
1401
  detectPortChange(text) {
1253
1402
  // Detect Next.js port switch: "⚠ Port 3000 is in use by process 39543, using available port 3001 instead."
@@ -1561,7 +1710,7 @@ export class DevEnvironment {
1561
1710
  this.cdpMonitor = new CDPMonitor(this.options.profileDir, this.screenshotDir, (_source, message) => {
1562
1711
  this.logger.log("browser", message);
1563
1712
  }, this.options.debug, this.options.browser, this.options.pluginReactScan, this.options.port, // App server port to monitor
1564
- this.preferredAppUrl, this.options.debugPort, // Chrome debug port
1713
+ this.preferredAppUrl, this.options.browserNavigationTimeoutSeconds * 1000, this.options.debugPort, // Chrome debug port
1565
1714
  this.options.headless, // Headless mode for serverless/CI environments
1566
1715
  this.options.framework // Framework hint for optional React DevTools launch args
1567
1716
  );
@@ -1594,9 +1743,7 @@ export class DevEnvironment {
1594
1743
  this.writeCurrentSessionInfo(projectName);
1595
1744
  this.debugLog(`Updated session info with CDP URL: ${cdpUrl}, Chrome PIDs: [${chromePids.join(", ")}]`);
1596
1745
  this.logger.log("browser", `[CDP] Session info written with cdpUrl: ${cdpUrl ? "available" : "null"}`);
1597
- // Navigate to the app
1598
- await this.cdpMonitor.navigateToUrl(this.preferredAppUrl);
1599
- this.logger.log("browser", `[CDP] Navigated to ${this.preferredAppUrl}`);
1746
+ this.logger.log("browser", `[CDP] Loading page will hand off to ${this.preferredAppUrl}`);
1600
1747
  }
1601
1748
  catch (error) {
1602
1749
  // Log error and throw to trigger graceful shutdown
@@ -1932,12 +2079,14 @@ export class DevEnvironment {
1932
2079
  console.log(chalk.yellow("\n🛑 Received interrupt signal. Cleaning up processes..."));
1933
2080
  }
1934
2081
  // Shutdown CDP monitor FIRST - this should close Chrome
2082
+ let chromeShutdownHandled = false;
1935
2083
  if (this.cdpMonitor) {
1936
2084
  try {
1937
2085
  if (!this.options.tui) {
1938
2086
  console.log(chalk.cyan("🔄 Closing Chrome browser..."));
1939
2087
  }
1940
2088
  await this.cdpMonitor.shutdown();
2089
+ chromeShutdownHandled = true;
1941
2090
  if (!this.options.tui) {
1942
2091
  console.log(chalk.green("✅ Chrome browser closed"));
1943
2092
  }
@@ -1973,8 +2122,10 @@ export class DevEnvironment {
1973
2122
  }
1974
2123
  }
1975
2124
  }
1976
- // Safety net: ensure tracked Chrome processes are always terminated on shutdown.
1977
- this.killTrackedChromePids(sessionInfo?.chromePids ?? [], "handleShutdown");
2125
+ if (!chromeShutdownHandled) {
2126
+ // Safety net: ensure tracked Chrome processes are always terminated on shutdown.
2127
+ this.killTrackedChromePids(sessionInfo?.chromePids ?? [], "handleShutdown");
2128
+ }
1978
2129
  // REMOVED: No longer clean up CLI config files on shutdown
1979
2130
  // This was causing Claude Code instances to crash when dev3000 was killed
1980
2131
  // Config file cleanup removed; keep user config files untouched on shutdown