@shepai/cli 1.130.0 → 1.130.1

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 (151) hide show
  1. package/dist/packages/core/src/infrastructure/services/agents/common/agent-validator.service.js +1 -1
  2. package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.d.ts +5 -5
  3. package/dist/packages/core/src/infrastructure/services/agents/common/executors/cursor-executor.service.js +8 -8
  4. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts +21 -1
  5. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.d.ts.map +1 -1
  6. package/dist/packages/core/src/infrastructure/services/deployment/deployment.service.js +152 -12
  7. package/dist/packages/core/src/infrastructure/services/deployment/detect-dev-script.d.ts +1 -0
  8. package/dist/packages/core/src/infrastructure/services/deployment/detect-dev-script.d.ts.map +1 -1
  9. package/dist/packages/core/src/infrastructure/services/deployment/detect-dev-script.js +3 -2
  10. package/dist/src/presentation/web/app/actions/check-agent-auth.js +1 -1
  11. package/dist/src/presentation/web/dev-server.js +7 -0
  12. package/dist/tsconfig.build.tsbuildinfo +1 -1
  13. package/package.json +1 -1
  14. package/web/.next/BUILD_ID +1 -1
  15. package/web/.next/build-manifest.json +2 -2
  16. package/web/.next/fallback-build-manifest.json +2 -2
  17. package/web/.next/prerender-manifest.json +3 -3
  18. package/web/.next/required-server-files.js +2 -2
  19. package/web/.next/required-server-files.json +2 -2
  20. package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +24 -24
  21. package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
  22. package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
  23. package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +24 -24
  24. package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
  25. package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
  26. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +30 -30
  27. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  28. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  29. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +30 -30
  30. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
  31. package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
  32. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +22 -22
  33. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
  34. package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  35. package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +24 -24
  36. package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
  37. package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
  38. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +30 -30
  39. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
  40. package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
  41. package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +30 -30
  42. package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
  43. package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
  44. package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +22 -22
  45. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  46. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  47. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +22 -22
  48. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
  49. package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
  50. package/web/.next/server/app/_global-error.html +2 -2
  51. package/web/.next/server/app/_global-error.rsc +1 -1
  52. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  53. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  54. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  55. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  56. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  57. package/web/.next/server/app/_not-found/page/server-reference-manifest.json +3 -3
  58. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  60. package/web/.next/server/app/settings/page.js.nft.json +1 -1
  61. package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  62. package/web/.next/server/app/skills/page/server-reference-manifest.json +8 -8
  63. package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/tools/page/server-reference-manifest.json +8 -8
  65. package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/version/page/server-reference-manifest.json +3 -3
  67. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  68. package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
  69. package/web/.next/server/chunks/ssr/744ca_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_4e741df2.js +1 -1
  70. package/web/.next/server/chunks/ssr/744ca_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_4e741df2.js.map +1 -1
  71. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
  72. package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
  73. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_026fdffa.js +1 -1
  74. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_@drawer_repository_[repositoryId]_page_actions_026fdffa.js.map +1 -1
  75. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_8b91e197.js +1 -1
  76. package/web/.next/server/chunks/ssr/8ba4b_server_app_(dashboard)_repository_[repositoryId]_page_actions_8b91e197.js.map +1 -1
  77. package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
  78. package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js +1 -1
  79. package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js.map +1 -1
  80. package/web/.next/server/chunks/ssr/[root-of-the-server]__5142820f._.js +2 -2
  81. package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.js +2 -2
  82. package/web/.next/server/chunks/ssr/[root-of-the-server]__6bb00d90._.js.map +1 -1
  83. package/web/.next/server/chunks/ssr/[root-of-the-server]__755cfe02._.js +1 -1
  84. package/web/.next/server/chunks/ssr/[root-of-the-server]__755cfe02._.js.map +1 -1
  85. package/web/.next/server/chunks/ssr/[root-of-the-server]__7894f27f._.js +1 -1
  86. package/web/.next/server/chunks/ssr/[root-of-the-server]__7894f27f._.js.map +1 -1
  87. package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js +1 -1
  88. package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js.map +1 -1
  89. package/web/.next/server/chunks/ssr/[root-of-the-server]__84f72684._.js +1 -1
  90. package/web/.next/server/chunks/ssr/[root-of-the-server]__99ab2519._.js +1 -1
  91. package/web/.next/server/chunks/ssr/[root-of-the-server]__99ab2519._.js.map +1 -1
  92. package/web/.next/server/chunks/ssr/[root-of-the-server]__a628fb91._.js +1 -1
  93. package/web/.next/server/chunks/ssr/[root-of-the-server]__a628fb91._.js.map +1 -1
  94. package/web/.next/server/chunks/ssr/[root-of-the-server]__bfba58e6._.js +1 -1
  95. package/web/.next/server/chunks/ssr/[root-of-the-server]__bfba58e6._.js.map +1 -1
  96. package/web/.next/server/chunks/ssr/[root-of-the-server]__c3ab8422._.js +1 -1
  97. package/web/.next/server/chunks/ssr/[root-of-the-server]__c3ab8422._.js.map +1 -1
  98. package/web/.next/server/chunks/ssr/_02ec1aea._.js +1 -1
  99. package/web/.next/server/chunks/ssr/_02ec1aea._.js.map +1 -1
  100. package/web/.next/server/chunks/ssr/{_2081d51d._.js → _60d96b77._.js} +2 -2
  101. package/web/.next/server/chunks/ssr/{_2081d51d._.js.map → _60d96b77._.js.map} +1 -1
  102. package/web/.next/server/chunks/ssr/_7dca1882._.js +1 -1
  103. package/web/.next/server/chunks/ssr/_7dca1882._.js.map +1 -1
  104. package/web/.next/server/chunks/ssr/_a9f57758._.js +1 -1
  105. package/web/.next/server/chunks/ssr/_d8575088._.js +1 -1
  106. package/web/.next/server/chunks/ssr/_d8575088._.js.map +1 -1
  107. package/web/.next/server/chunks/ssr/_e44c1df8._.js +3 -0
  108. package/web/.next/server/chunks/ssr/{_c6edd1ef._.js.map → _e44c1df8._.js.map} +1 -1
  109. package/web/.next/server/chunks/ssr/_f39a1adb._.js +1 -1
  110. package/web/.next/server/chunks/ssr/_f39a1adb._.js.map +1 -1
  111. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
  112. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
  113. package/web/.next/server/chunks/ssr/{src_presentation_web_de41598a._.js → src_presentation_web_1b00ce26._.js} +2 -2
  114. package/web/.next/server/chunks/ssr/{src_presentation_web_de41598a._.js.map → src_presentation_web_1b00ce26._.js.map} +1 -1
  115. package/web/.next/server/chunks/ssr/src_presentation_web_8e7fe0d1._.js +2 -2
  116. package/web/.next/server/chunks/ssr/src_presentation_web_8e7fe0d1._.js.map +1 -1
  117. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_ca051134.js +1 -1
  118. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_ca051134.js.map +1 -1
  119. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js +1 -1
  120. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js.map +1 -1
  121. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js +1 -1
  122. package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js.map +1 -1
  123. package/web/.next/server/chunks/ssr/src_presentation_web_b9023017._.js +2 -2
  124. package/web/.next/server/chunks/ssr/src_presentation_web_b9023017._.js.map +1 -1
  125. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
  126. package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
  127. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
  128. package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
  129. package/web/.next/server/pages/500.html +2 -2
  130. package/web/.next/server/server-reference-manifest.js +1 -1
  131. package/web/.next/server/server-reference-manifest.json +38 -38
  132. package/web/.next/static/chunks/010e770e43094b0c.js +1 -0
  133. package/web/.next/static/chunks/{f8ce00057329618b.js → 1553f15c07eb7745.js} +1 -1
  134. package/web/.next/static/chunks/2fc5168fdc6be797.js +1 -0
  135. package/web/.next/static/chunks/{3b5d54efc5f21506.js → 499fe813285eb6b6.js} +1 -1
  136. package/web/.next/static/chunks/{bdf477379453ead2.js → 6a528a033ae55794.js} +2 -2
  137. package/web/.next/static/chunks/{090b4b644f4526bf.js → 7e5b7a8ca6303e76.js} +1 -1
  138. package/web/.next/static/chunks/8057ebdc852674de.js +2 -0
  139. package/web/.next/static/chunks/{339e72cfa282c94b.js → ad8d9368ff2f152d.js} +1 -1
  140. package/web/.next/static/chunks/c1bc8dbfa5d7fc35.js +1 -0
  141. package/web/.next/static/chunks/{747b3443a866e39f.js → c5019bfe2884e088.js} +1 -1
  142. package/web/.next/static/chunks/{8d936038e7d013d2.js → ceefd537b2a82fb5.js} +1 -1
  143. package/web/.next/static/chunks/{724dfca1907b26d7.js → f3e3c98b0dbdb7c5.js} +1 -1
  144. package/web/.next/server/chunks/ssr/_c6edd1ef._.js +0 -3
  145. package/web/.next/static/chunks/22d08ce7fbc67732.js +0 -1
  146. package/web/.next/static/chunks/90a32a641dd33f9a.js +0 -1
  147. package/web/.next/static/chunks/9387d6dde6140968.js +0 -2
  148. package/web/.next/static/chunks/bcdcdce9139bfd25.js +0 -1
  149. /package/web/.next/static/{FkranKu-K5LY21_CdI_Jp → JhvdO6haSso0T4F28qUg0}/_buildManifest.js +0 -0
  150. /package/web/.next/static/{FkranKu-K5LY21_CdI_Jp → JhvdO6haSso0T4F28qUg0}/_clientMiddlewareManifest.json +0 -0
  151. /package/web/.next/static/{FkranKu-K5LY21_CdI_Jp → JhvdO6haSso0T4F28qUg0}/_ssgManifest.js +0 -0
@@ -26,7 +26,7 @@ import { injectable, inject } from 'tsyringe';
26
26
  */
27
27
  const AGENT_BINARY_MAP = {
28
28
  'claude-code': 'claude',
29
- cursor: 'agent',
29
+ cursor: 'cursor-agent',
30
30
  'gemini-cli': 'gemini',
31
31
  };
32
32
  /**
@@ -2,7 +2,7 @@
2
2
  * Cursor Executor Service
3
3
  *
4
4
  * Infrastructure implementation of IAgentExecutor for the Cursor agent.
5
- * Executes prompts via the `agent` CLI subprocess with JSON and stream-json
5
+ * Executes prompts via the `cursor-agent` CLI subprocess with JSON and stream-json
6
6
  * output formats.
7
7
  *
8
8
  * Uses constructor dependency injection for the spawn function
@@ -13,7 +13,7 @@ import type { IAgentExecutor, AgentExecutionOptions, AgentExecutionResult, Agent
13
13
  import type { SpawnFunction } from '../types.js';
14
14
  /**
15
15
  * Executor service for Cursor agent.
16
- * Uses subprocess spawning to interact with the `agent` CLI.
16
+ * Uses subprocess spawning to interact with the `cursor-agent` CLI.
17
17
  */
18
18
  export declare class CursorExecutorService implements IAgentExecutor {
19
19
  private readonly spawn;
@@ -35,13 +35,13 @@ export declare class CursorExecutorService implements IAgentExecutor {
35
35
  /**
36
36
  * Spawn the agent process, handling Windows specially via PowerShell.
37
37
  *
38
- * On Windows, cursor CLI ships as `agent.cmd` which requires `shell: true`,
38
+ * On Windows, cursor CLI ships as `cursor-agent.cmd` which requires `shell: true`,
39
39
  * but cmd.exe mangles long `-p` arguments (8191-char limit + special chars).
40
- * Solution: write prompt to a temp file, invoke agent via PowerShell which
40
+ * Solution: write prompt to a temp file, invoke cursor-agent via PowerShell which
41
41
  * reads the file and passes the content as `-p`. PowerShell handles long
42
42
  * strings natively (32K limit) and doesn't mangle arguments.
43
43
  *
44
- * On Linux/macOS, spawn `agent` directly — no shell needed.
44
+ * On Linux/macOS, spawn `cursor-agent` directly — no shell needed.
45
45
  */
46
46
  private spawnAgent;
47
47
  private parseStreamLine;
@@ -2,7 +2,7 @@
2
2
  * Cursor Executor Service
3
3
  *
4
4
  * Infrastructure implementation of IAgentExecutor for the Cursor agent.
5
- * Executes prompts via the `agent` CLI subprocess with JSON and stream-json
5
+ * Executes prompts via the `cursor-agent` CLI subprocess with JSON and stream-json
6
6
  * output formats.
7
7
  *
8
8
  * Uses constructor dependency injection for the spawn function
@@ -32,7 +32,7 @@ function toCursorModelName(model) {
32
32
  const SUPPORTED_FEATURES = new Set(['session-resume', 'streaming']);
33
33
  /**
34
34
  * Executor service for Cursor agent.
35
- * Uses subprocess spawning to interact with the `agent` CLI.
35
+ * Uses subprocess spawning to interact with the `cursor-agent` CLI.
36
36
  */
37
37
  export class CursorExecutorService {
38
38
  spawn;
@@ -312,13 +312,13 @@ export class CursorExecutorService {
312
312
  /**
313
313
  * Spawn the agent process, handling Windows specially via PowerShell.
314
314
  *
315
- * On Windows, cursor CLI ships as `agent.cmd` which requires `shell: true`,
315
+ * On Windows, cursor CLI ships as `cursor-agent.cmd` which requires `shell: true`,
316
316
  * but cmd.exe mangles long `-p` arguments (8191-char limit + special chars).
317
- * Solution: write prompt to a temp file, invoke agent via PowerShell which
317
+ * Solution: write prompt to a temp file, invoke cursor-agent via PowerShell which
318
318
  * reads the file and passes the content as `-p`. PowerShell handles long
319
319
  * strings natively (32K limit) and doesn't mangle arguments.
320
320
  *
321
- * On Linux/macOS, spawn `agent` directly — no shell needed.
321
+ * On Linux/macOS, spawn `cursor-agent` directly — no shell needed.
322
322
  */
323
323
  spawnAgent(prompt, args, options) {
324
324
  const { CLAUDECODE: _, ...cleanEnv } = process.env;
@@ -330,7 +330,7 @@ export class CursorExecutorService {
330
330
  // Build the agent args WITHOUT -p and the prompt (they go via temp file)
331
331
  const agentFlags = args.filter((a) => a !== '-p' && a !== prompt).join(' ');
332
332
  const safePath = tmpFile.replace(/'/g, "''");
333
- const psCmd = `$p = Get-Content -Raw '${safePath}'; & agent ${agentFlags} -p $p`;
333
+ const psCmd = `$p = Get-Content -Raw '${safePath}'; & cursor-agent ${agentFlags} -p $p`;
334
334
  this.log(`Windows PowerShell mode: wrote ${prompt.length} chars to ${tmpFile}`);
335
335
  this.log(`PS command: ${psCmd.replace(prompt, `<${prompt.length} chars>`)}`);
336
336
  const proc = this.spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', psCmd], {
@@ -351,9 +351,9 @@ export class CursorExecutorService {
351
351
  };
352
352
  if (cwd)
353
353
  spawnOpts.cwd = cwd;
354
- this.log(`Spawning: agent ${args.map((a) => (a.length > 80 ? `${a.slice(0, 77)}...` : a)).join(' ')}`);
354
+ this.log(`Spawning: cursor-agent ${args.map((a) => (a.length > 80 ? `${a.slice(0, 77)}...` : a)).join(' ')}`);
355
355
  this.log(`Spawn cwd: ${cwd ?? '(inherited)'}`);
356
- const proc = this.spawn('agent', args, spawnOpts);
356
+ const proc = this.spawn('cursor-agent', args, spawnOpts);
357
357
  this.log(`Subprocess PID: ${proc.pid ?? 'undefined (spawn may have failed)'}`);
358
358
  if (proc.stdin)
359
359
  proc.stdin.end();
@@ -31,7 +31,16 @@ export declare class DeploymentService implements IDeploymentService {
31
31
  setDatabase(db: Database.Database): void;
32
32
  /**
33
33
  * Recover dev servers from the database on startup.
34
- * Validates each PID is still alive; removes dead rows.
34
+ *
35
+ * Three recovery strategies based on process state:
36
+ * 1. Alive + Ready (has URL) → re-adopt, then validate URL asynchronously
37
+ * 2. Alive + Booting (no URL) → kill orphan and re-spawn (can't re-attach stdout)
38
+ * 3. Dead → re-spawn if target directory still exists
39
+ *
40
+ * After sync recovery, fires off async URL health checks for re-adopted
41
+ * processes. Zombies (alive but not serving) are killed and re-spawned.
42
+ *
43
+ * All re-spawns are wrapped in try/catch so one failure doesn't block others.
35
44
  */
36
45
  recoverAll(): void;
37
46
  /**
@@ -77,5 +86,16 @@ export declare class DeploymentService implements IDeploymentService {
77
86
  private attachOutputListener;
78
87
  private pollUntilDead;
79
88
  private waitForExit;
89
+ /**
90
+ * Async health check for a recovered entry. Probes the URL port via TCP.
91
+ * If the port is not responding (zombie process), kills and re-spawns.
92
+ * Fire-and-forget — errors are logged, never thrown.
93
+ */
94
+ private validateAndRespawn;
95
+ /**
96
+ * TCP connect probe to check if a URL's port is accepting connections.
97
+ * Returns true if the port responds within 2 seconds, false otherwise.
98
+ */
99
+ private probePort;
80
100
  }
81
101
  //# sourceMappingURL=deployment.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deployment.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/deployment.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAO3C,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACT,MAAM,qEAAqE,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAgCzD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AA6BD,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,EAAE,CAAkC;gBAEhC,IAAI,GAAE,OAAO,CAAC,qBAAqB,CAAM;IAIrD;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAIxC;;;OAGG;IACH,UAAU,IAAI,IAAI;IA4ClB;;;OAGG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,SAAe,GAAG,IAAI;IAqF5E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IA4CpD;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0D3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI;IAM5C;;OAEG;IACH,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAI1D;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAM3D,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,MAAM;IAed,OAAO,CAAC,WAAW;IAQnB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YA4Dd,aAAa;IAW3B,OAAO,CAAC,WAAW;CASpB"}
1
+ {"version":3,"file":"deployment.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/deployment.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAmC,MAAM,oBAAoB,CAAC;AAK5E,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAK3C,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACT,MAAM,qEAAqE,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAiCzD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AA0CD,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,EAAE,CAAkC;gBAEhC,IAAI,GAAE,OAAO,CAAC,qBAAqB,CAAM;IAIrD;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,IAAI;IAIxC;;;;;;;;;;;;OAYG;IACH,UAAU,IAAI,IAAI;IAgFlB;;;OAGG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,SAAe,GAAG,IAAI;IA0G5E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IA4CpD;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0D3C;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI;IAM5C;;OAEG;IACH,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAI1D;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAM3D,OAAO,CAAC,QAAQ;IAuBhB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,MAAM;IAed,OAAO,CAAC,WAAW;IAQnB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;YA4Dd,aAAa;IAW3B,OAAO,CAAC,WAAW;IAUnB;;;;OAIG;YACW,kBAAkB;IAsChC;;;OAGG;IACH,OAAO,CAAC,SAAS;CAwBlB"}
@@ -9,18 +9,20 @@
9
9
  * On startup, call `recoverAll()` to reconcile the DB with actual
10
10
  * process state (clean up dead PIDs, re-adopt live ones).
11
11
  */
12
- import { spawn } from 'node:child_process';
12
+ import { spawn, execFileSync } from 'node:child_process';
13
+ import { existsSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import net from 'node:net';
13
16
  import { EventEmitter } from 'node:events';
14
- // NOTE: We intentionally do NOT use tree-kill here. tree-kill traverses
15
- // /proc to find child processes which can accidentally kill parent processes
16
- // (including the Shep web UI). Instead, we use process.kill(-pid) to send
17
- // signals to the process GROUP (since we spawn with detached: true, the
18
- // child gets its own process group via setsid()).
17
+ // On Unix we use process.kill(-pid) to send signals to the process GROUP
18
+ // (detached: true puts the child in its own group via setsid()).
19
+ // On Windows, negative PIDs are not supported use taskkill /T for tree kill.
19
20
  import { DeploymentState } from '../../../domain/generated/output.js';
20
21
  import { detectDevScript } from './detect-dev-script.js';
21
22
  import { createDeploymentLogger } from './deployment-logger.js';
22
23
  import { parsePort } from './parse-port.js';
23
24
  import { LogRingBuffer } from './log-ring-buffer.js';
25
+ import { IS_WINDOWS } from '../../platform.js';
24
26
  const log = createDeploymentLogger('[DeploymentService]');
25
27
  const POLL_INTERVAL_MS = 200;
26
28
  const MAX_WAIT_MS = 5000;
@@ -28,6 +30,20 @@ const defaultDeps = {
28
30
  spawn,
29
31
  detectDevScript,
30
32
  kill: (pid, signal) => {
33
+ if (IS_WINDOWS) {
34
+ // On Windows, negative PIDs are not supported. Use taskkill /T to
35
+ // kill the entire process tree (shell + child dev server).
36
+ try {
37
+ execFileSync('taskkill', ['/F', '/T', '/PID', String(pid)], {
38
+ stdio: 'ignore',
39
+ windowsHide: true,
40
+ });
41
+ }
42
+ catch {
43
+ // Process already dead
44
+ }
45
+ return;
46
+ }
31
47
  try {
32
48
  // Kill the entire process group (negative PID) — safe because
33
49
  // detached: true puts the child in its own group via setsid().
@@ -69,7 +85,16 @@ export class DeploymentService {
69
85
  }
70
86
  /**
71
87
  * Recover dev servers from the database on startup.
72
- * Validates each PID is still alive; removes dead rows.
88
+ *
89
+ * Three recovery strategies based on process state:
90
+ * 1. Alive + Ready (has URL) → re-adopt, then validate URL asynchronously
91
+ * 2. Alive + Booting (no URL) → kill orphan and re-spawn (can't re-attach stdout)
92
+ * 3. Dead → re-spawn if target directory still exists
93
+ *
94
+ * After sync recovery, fires off async URL health checks for re-adopted
95
+ * processes. Zombies (alive but not serving) are killed and re-spawned.
96
+ *
97
+ * All re-spawns are wrapped in try/catch so one failure doesn't block others.
73
98
  */
74
99
  recoverAll() {
75
100
  if (!this.db)
@@ -89,7 +114,10 @@ export class DeploymentService {
89
114
  }
90
115
  log.info(`Recovering ${rows.length} dev server(s) from database`);
91
116
  for (const row of rows) {
92
- if (this.deps.isAlive(row.pid)) {
117
+ const alive = this.deps.isAlive(row.pid);
118
+ const hasUrl = row.state === DeploymentState.Ready && row.url;
119
+ if (alive && hasUrl) {
120
+ // Strategy 1: Process alive and Ready — re-adopt, then validate URL async
93
121
  log.info(`Recovered "${row.target_id}" (pid=${row.pid}, state=${row.state}, url=${row.url})`);
94
122
  const entry = {
95
123
  pid: row.pid,
@@ -104,10 +132,36 @@ export class DeploymentService {
104
132
  logs: new LogRingBuffer(),
105
133
  };
106
134
  this.deployments.set(row.target_id, entry);
135
+ // Fire-and-forget: validate URL is actually responding, kill zombie if not
136
+ this.validateAndRespawn(entry);
137
+ continue;
138
+ }
139
+ if (alive) {
140
+ // Strategy 2: Process alive but stuck in Booting — can't re-attach stdout
141
+ // Kill the orphan so we can re-spawn with proper listeners
142
+ log.info(`Orphan "${row.target_id}" (pid=${row.pid}) stuck in ${row.state} — killing to re-spawn`);
143
+ try {
144
+ this.deps.kill(row.pid, 'SIGKILL');
145
+ }
146
+ catch {
147
+ // already dead
148
+ }
107
149
  }
108
150
  else {
109
- log.info(`Stale dev server "${row.target_id}" (pid=${row.pid}) — removing from DB`);
110
- this.dbDelete(row.target_id);
151
+ log.info(`Dev server "${row.target_id}" (pid=${row.pid}) is dead will re-spawn`);
152
+ }
153
+ // Strategy 2 & 3: Re-spawn if target directory still has a package.json
154
+ this.dbDelete(row.target_id);
155
+ if (!existsSync(join(row.target_path, 'package.json'))) {
156
+ log.warn(`Skipping re-spawn for "${row.target_id}" — no package.json at "${row.target_path}"`);
157
+ continue;
158
+ }
159
+ try {
160
+ log.info(`Re-spawning dev server for "${row.target_id}" at "${row.target_path}"`);
161
+ this.start(row.target_id, row.target_path, row.target_type);
162
+ }
163
+ catch (err) {
164
+ log.error(`Failed to re-spawn "${row.target_id}": ${err}`);
111
165
  }
112
166
  }
113
167
  }
@@ -132,13 +186,34 @@ export class DeploymentService {
132
186
  throw new Error(detection.error);
133
187
  }
134
188
  // Build spawn args based on package manager
135
- const { packageManager, scriptName, command } = detection;
189
+ const { packageManager, scriptName, command, needsInstall } = detection;
136
190
  const args = packageManager === 'npm' ? ['run', scriptName] : [scriptName];
191
+ // Install dependencies if node_modules is missing (e.g. fresh worktree)
192
+ if (needsInstall) {
193
+ log.info(`node_modules missing in "${targetPath}" — running ${packageManager} install`);
194
+ try {
195
+ execFileSync(packageManager, ['install'], {
196
+ cwd: targetPath,
197
+ shell: true,
198
+ stdio: 'ignore',
199
+ ...(IS_WINDOWS ? { windowsHide: true } : {}),
200
+ });
201
+ log.info('Dependencies installed successfully');
202
+ }
203
+ catch (err) {
204
+ log.error(`Dependency install failed: ${err}`);
205
+ throw new Error(`Failed to install dependencies: ${err}`);
206
+ }
207
+ }
137
208
  log.info(`Spawning dev server: command="${command}", packageManager="${packageManager}", scriptName="${scriptName}", cwd="${targetPath}"`);
138
209
  const child = this.deps.spawn(packageManager, args, {
139
210
  shell: true,
140
211
  cwd: targetPath,
141
- detached: true,
212
+ // On Unix, detached: true creates a process group via setsid() so we can
213
+ // kill the entire tree with process.kill(-pid). On Windows this flag causes
214
+ // CREATE_NEW_CONSOLE which opens a visible terminal window and disconnects
215
+ // stdout/stderr — we don't need it because taskkill /F /T handles tree kill.
216
+ ...(IS_WINDOWS ? { windowsHide: true } : { detached: true }),
142
217
  stdio: ['ignore', 'pipe', 'pipe'],
143
218
  });
144
219
  if (!child.pid) {
@@ -452,4 +527,69 @@ export class DeploymentService {
452
527
  });
453
528
  });
454
529
  }
530
+ /**
531
+ * Async health check for a recovered entry. Probes the URL port via TCP.
532
+ * If the port is not responding (zombie process), kills and re-spawns.
533
+ * Fire-and-forget — errors are logged, never thrown.
534
+ */
535
+ async validateAndRespawn(entry) {
536
+ if (!entry.url)
537
+ return;
538
+ const responding = await this.probePort(entry.url);
539
+ if (responding) {
540
+ log.info(`[${entry.targetId}] URL health check passed — "${entry.url}" is responding`);
541
+ return;
542
+ }
543
+ log.warn(`[${entry.targetId}] URL health check FAILED — "${entry.url}" not responding, killing zombie (pid=${entry.pid})`);
544
+ // Kill the zombie
545
+ try {
546
+ this.deps.kill(entry.pid, 'SIGKILL');
547
+ }
548
+ catch {
549
+ // already dead
550
+ }
551
+ this.deployments.delete(entry.targetId);
552
+ this.dbDelete(entry.targetId);
553
+ // Re-spawn if target still exists
554
+ if (!existsSync(join(entry.targetPath, 'package.json'))) {
555
+ log.warn(`Skipping re-spawn for "${entry.targetId}" — no package.json at "${entry.targetPath}"`);
556
+ return;
557
+ }
558
+ try {
559
+ log.info(`Re-spawning dev server for "${entry.targetId}" at "${entry.targetPath}"`);
560
+ this.start(entry.targetId, entry.targetPath, entry.targetType);
561
+ }
562
+ catch (err) {
563
+ log.error(`Failed to re-spawn "${entry.targetId}": ${err}`);
564
+ }
565
+ }
566
+ /**
567
+ * TCP connect probe to check if a URL's port is accepting connections.
568
+ * Returns true if the port responds within 2 seconds, false otherwise.
569
+ */
570
+ probePort(url) {
571
+ return new Promise((resolve) => {
572
+ try {
573
+ const parsed = new URL(url);
574
+ const port = parseInt(parsed.port || (parsed.protocol === 'https:' ? '443' : '80'), 10);
575
+ const socket = net.createConnection({ host: parsed.hostname, port });
576
+ const timeout = setTimeout(() => {
577
+ socket.destroy();
578
+ resolve(false);
579
+ }, 2000);
580
+ socket.on('connect', () => {
581
+ clearTimeout(timeout);
582
+ socket.destroy();
583
+ resolve(true);
584
+ });
585
+ socket.on('error', () => {
586
+ clearTimeout(timeout);
587
+ resolve(false);
588
+ });
589
+ }
590
+ catch {
591
+ resolve(false);
592
+ }
593
+ });
594
+ }
455
595
  }
@@ -10,6 +10,7 @@ export interface DetectDevScriptSuccess {
10
10
  packageManager: string;
11
11
  scriptName: string;
12
12
  command: string;
13
+ needsInstall: boolean;
13
14
  }
14
15
  export interface DetectDevScriptError {
15
16
  success: false;
@@ -1 +1 @@
1
- {"version":3,"file":"detect-dev-script.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/detect-dev-script.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,IAAI,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,oBAAoB,CAAC;AAIlF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAuCtE"}
1
+ {"version":3,"file":"detect-dev-script.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/deployment/detect-dev-script.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,IAAI,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,oBAAoB,CAAC;AAIlF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAwCtE"}
@@ -50,8 +50,9 @@ export function detectDevScript(dirPath) {
50
50
  const packageManager = detectPackageManager(dirPath);
51
51
  // Build the command — pnpm/yarn use `<pm> <script>`, npm uses `npm run <script>`
52
52
  const command = packageManager === 'npm' ? `npm run ${scriptName}` : `${packageManager} ${scriptName}`;
53
- log.info(`detected packageManager="${packageManager}", scriptName="${scriptName}", command="${command}"`);
54
- return { success: true, packageManager, scriptName, command };
53
+ const needsInstall = !existsSync(join(dirPath, 'node_modules'));
54
+ log.info(`detected packageManager="${packageManager}", scriptName="${scriptName}", command="${command}", needsInstall=${needsInstall}`);
55
+ return { success: true, packageManager, scriptName, command, needsInstall };
55
56
  }
56
57
  /**
57
58
  * Detect the package manager by checking for lockfile presence.
@@ -21,7 +21,7 @@ const AGENT_TOOL_MAP = {
21
21
  };
22
22
  const AGENT_BINARY_MAP = {
23
23
  'claude-code': 'claude',
24
- cursor: 'agent',
24
+ cursor: 'cursor-agent',
25
25
  'gemini-cli': 'gemini',
26
26
  };
27
27
  /**
@@ -107,6 +107,13 @@ async function main() {
107
107
  console.log('\n[dev-server] Shutting down...');
108
108
  const forceExit = setTimeout(() => process.exit(0), 2000);
109
109
  try {
110
+ try {
111
+ const deploymentService = container.resolve('IDeploymentService');
112
+ deploymentService.stopAll();
113
+ }
114
+ catch {
115
+ /* not initialized */
116
+ }
110
117
  try {
111
118
  getNotificationWatcher().stop();
112
119
  }