claude-code-session-manager 0.87.0 → 0.88.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 (291) hide show
  1. package/README.md +26 -53
  2. package/dist/assets/{AgentLibrary-DyLWzZDf.js → AgentLibrary-kQF8_wAw.js} +1 -1
  3. package/dist/assets/{DataModel--mISIJ6h.js → DataModel-CY_mMnsr.js} +1 -1
  4. package/dist/assets/{History-C2ahUXTg.js → History-Sji2brb0.js} +1 -1
  5. package/dist/assets/{Hooks-BiC6oyR2.js → Hooks-CtqpO-p5.js} +1 -1
  6. package/dist/assets/{HostBilko-BPleEOld.js → HostBilko-DN0ydOCr.js} +1 -1
  7. package/dist/assets/{Library-Dc8Qst1R.js → Library-CoiLZTKh.js} +1 -1
  8. package/dist/assets/{ListDetail-DIXh-OLX.js → ListDetail-CmqiQ_io.js} +1 -1
  9. package/dist/assets/{MarkdownEditor-C90bkLXK.js → MarkdownEditor-HPoNeKhT.js} +1 -1
  10. package/dist/assets/{McpServers-DqcbLOLZ.js → McpServers-Bj1X3_NT.js} +1 -1
  11. package/dist/assets/{Memory-CW62MXlh.js → Memory-1PKjsM3b.js} +1 -1
  12. package/dist/assets/{Panel-Bw1FhRuF.js → Panel-I1s6ghPo.js} +1 -1
  13. package/dist/assets/{Permissions-BcUC-5y8.js → Permissions-BhiiZjHQ.js} +1 -1
  14. package/dist/assets/{Plugins-BnKx9flD.js → Plugins-gv3_fkPX.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-Bw5vNVPT.js → ProvenanceBadge-BGcgBd8V.js} +1 -1
  16. package/dist/assets/{SaveBar-CWr0O_w-.js → SaveBar-BGr9eZ4g.js} +1 -1
  17. package/dist/assets/{Scheduler-DYdLuUqq.js → Scheduler-Bdo_awPq.js} +7 -7
  18. package/dist/assets/{ScopeSwitcher-CrBLbg8s.js → ScopeSwitcher-ukl0qWaA.js} +1 -1
  19. package/dist/assets/{Settings-DluB-vN1.js → Settings-DJkFj22G.js} +1 -1
  20. package/dist/assets/{SkillReferenceGraph-CHLSseay.js → SkillReferenceGraph-0t1Qblp6.js} +1 -1
  21. package/dist/assets/{Skills-gNdo_HNK.js → Skills-CeVdBGE-.js} +1 -1
  22. package/dist/assets/{SystemPrompt-Cru05-Ia.js → SystemPrompt-CfKvNHi3.js} +1 -1
  23. package/dist/assets/{TagLibrary-DNHY0xou.js → TagLibrary-DAzNaUPd.js} +1 -1
  24. package/dist/assets/{TiptapBody-I4lmbCgP.js → TiptapBody--S_rGXoA.js} +1 -1
  25. package/dist/assets/{Toggle-bWMHjmRh.js → Toggle-Dszm0xnp.js} +1 -1
  26. package/dist/assets/{index-DV3PorRY.css → index-BHTX4OTc.css} +1 -1
  27. package/dist/assets/{index-fc_JjdxL.js → index-Dp4Rc--q.js} +356 -356
  28. package/dist/assets/{settingsSchema-BfhtZnGD.js → settingsSchema-BLbssMYo.js} +1 -1
  29. package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
  30. package/dist/index.html +2 -2
  31. package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
  32. package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  33. package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
  34. package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
  35. package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
  36. package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
  37. package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
  38. package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
  39. package/package.json +10 -13
  40. package/scripts/README.md +59 -0
  41. package/scripts/audit-ops-hygiene.cjs +350 -0
  42. package/scripts/hooks/guard-destructive-git.cjs +10 -34
  43. package/scripts/hooks/guard-inline-implementation.cjs +14 -8
  44. package/scripts/hooks/guard-prd-writes.cjs +7 -36
  45. package/scripts/hooks/guard-self-schedule.cjs +175 -0
  46. package/scripts/ops-sweep.cjs +355 -0
  47. package/scripts/scheduler-mcp-server.cjs +28 -71
  48. package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
  49. package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
  50. package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
  51. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
  52. package/src/main/__tests__/chat-queue.test.cjs +2 -2
  53. package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
  54. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
  55. package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
  56. package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
  57. package/src/main/__tests__/dod-report.test.cjs +2 -2
  58. package/src/main/__tests__/dod-reverify.test.cjs +2 -2
  59. package/src/main/__tests__/epicMint.test.cjs +2 -2
  60. package/src/main/__tests__/exchanges.test.cjs +2 -2
  61. package/src/main/__tests__/extractJson.test.cjs +2 -2
  62. package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
  63. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
  64. package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
  65. package/src/main/__tests__/health-delegation-chain.test.cjs +15 -1
  66. package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
  67. package/src/main/__tests__/health-tick-liveness.test.cjs +27 -3
  68. package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
  69. package/src/main/__tests__/historyRollup.test.cjs +2 -2
  70. package/src/main/__tests__/kg-augment.test.cjs +2 -2
  71. package/src/main/__tests__/mcpStatus.test.cjs +2 -2
  72. package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
  73. package/src/main/__tests__/memoryStale.test.cjs +1 -1
  74. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +25 -1
  75. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +33 -3
  76. package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
  77. package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
  78. package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
  79. package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
  80. package/src/main/__tests__/prdLocationsArchived.test.cjs +20 -13
  81. package/src/main/__tests__/proc-role-env.test.cjs +125 -0
  82. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
  83. package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
  84. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
  85. package/src/main/__tests__/projectPages.test.cjs +63 -149
  86. package/src/main/__tests__/queue-health-verdict.test.cjs +9 -9
  87. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
  88. package/src/main/__tests__/queueHistory.test.cjs +2 -2
  89. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
  90. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
  91. package/src/main/__tests__/runVerify.test.cjs +5 -5
  92. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
  93. package/src/main/__tests__/scheduleJobTransitions.test.cjs +2 -2
  94. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
  95. package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
  96. package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
  97. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
  98. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +78 -97
  99. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
  100. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
  101. package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
  102. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
  103. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
  104. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +46 -0
  105. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
  106. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
  107. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
  108. package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
  109. package/src/main/__tests__/scheduler-looks-done.test.cjs +26 -7
  110. package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
  111. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
  112. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
  113. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
  114. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
  115. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
  116. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
  117. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +23 -4
  118. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
  119. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
  120. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
  121. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
  122. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +81 -1
  123. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +14 -0
  124. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +4 -4
  125. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
  126. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
  127. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
  128. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
  129. package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
  130. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +37 -11
  131. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
  132. package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
  133. package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
  134. package/src/main/bilkoHost.cjs +16 -7
  135. package/src/main/build-info.json +8 -0
  136. package/src/main/chatRunner.cjs +4 -2
  137. package/src/main/config.cjs +2 -3
  138. package/src/main/docEdit.cjs +4 -2
  139. package/src/main/health.cjs +276 -52
  140. package/src/main/heapSnapshot.cjs +2 -2
  141. package/src/main/historyAggregator.cjs +1 -1
  142. package/src/main/index.cjs +61 -10
  143. package/src/main/ipcSchemas.cjs +6 -17
  144. package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
  145. package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
  146. package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
  147. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
  148. package/src/main/lib/__tests__/delegationReadiness.test.cjs +1 -1
  149. package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
  150. package/src/main/lib/__tests__/gateFixtures.json +20 -0
  151. package/src/main/lib/__tests__/gitWorktree.test.cjs +8 -1
  152. package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
  153. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
  154. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
  155. package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
  156. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
  157. package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
  158. package/src/main/lib/__tests__/procName.test.cjs +92 -0
  159. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
  160. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
  161. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
  162. package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
  163. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
  164. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
  165. package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
  166. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
  167. package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
  168. package/src/main/lib/__tests__/usageCircuit.test.cjs +61 -0
  169. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +63 -0
  170. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +73 -0
  171. package/src/main/lib/activeIndexRebuild.cjs +1 -3
  172. package/src/main/lib/activeSessions.cjs +20 -88
  173. package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
  174. package/src/main/lib/agentModelResolve.cjs +33 -1
  175. package/src/main/lib/agentPersonaSchema.cjs +2 -2
  176. package/src/main/lib/auditLog.cjs +30 -5
  177. package/src/main/lib/buildIdentity.cjs +113 -0
  178. package/src/main/lib/classifyPromptTicket.cjs +3 -2
  179. package/src/main/lib/claudeBin.cjs +37 -2
  180. package/src/main/lib/cleanEnv.cjs +27 -1
  181. package/src/main/lib/credentials.cjs +4 -2
  182. package/src/main/lib/cwdClassify.cjs +185 -0
  183. package/src/main/lib/definitionOfDone.cjs +296 -52
  184. package/src/main/lib/dispatchLoop.cjs +40 -0
  185. package/src/main/lib/effectiveModelInfo.cjs +9 -10
  186. package/src/main/lib/ephemeralCwd.cjs +7 -28
  187. package/src/main/lib/gitWorktree.cjs +95 -8
  188. package/src/main/lib/guardShims.cjs +3 -3
  189. package/src/main/lib/historyRollup.cjs +6 -7
  190. package/src/main/lib/instanceLock.cjs +32 -5
  191. package/src/main/lib/jobSupervisorRecord.cjs +147 -0
  192. package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
  193. package/src/main/lib/localAdminHttp.cjs +9 -27
  194. package/src/main/lib/mcpToolCatalog.cjs +29 -77
  195. package/src/main/lib/opsOwnership.cjs +27 -19
  196. package/src/main/lib/prdAuthoringSeed.cjs +36 -0
  197. package/src/main/lib/prdLocations.cjs +48 -1
  198. package/src/main/lib/procIdentity.cjs +126 -0
  199. package/src/main/lib/procName.cjs +98 -0
  200. package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
  201. package/src/main/lib/queueHistory.cjs +8 -7
  202. package/src/main/lib/queueStore.cjs +57 -37
  203. package/src/main/lib/quietMachineLease.cjs +19 -2
  204. package/src/main/lib/reservationExpiry.cjs +30 -0
  205. package/src/main/lib/runClaudeP.cjs +4 -2
  206. package/src/main/lib/runLogRetention.cjs +3 -2
  207. package/src/main/lib/scheduleJobSchema.cjs +2 -2
  208. package/src/main/lib/scheduleJobTransitions.cjs +29 -2
  209. package/src/main/lib/schedulerBatch.cjs +27 -2
  210. package/src/main/lib/schedulerPaths.cjs +175 -0
  211. package/src/main/lib/schedulerRuntimeState.cjs +59 -0
  212. package/src/main/lib/sessionSlots.cjs +47 -9
  213. package/src/main/lib/smProcNames.cjs +51 -0
  214. package/src/main/lib/upgradeDrain.cjs +188 -0
  215. package/src/main/lib/usageCircuit.cjs +53 -6
  216. package/src/main/lib/watchdogHelpers.cjs +70 -37
  217. package/src/main/lib/withTimeout.cjs +33 -0
  218. package/src/main/mcpStatus.cjs +4 -2
  219. package/src/main/pluginInstall.cjs +6 -2
  220. package/src/main/projectPages.cjs +41 -145
  221. package/src/main/pty.cjs +8 -1
  222. package/src/main/queueOps.cjs +10 -10
  223. package/src/main/runVerify.cjs +69 -3
  224. package/src/main/scheduler.cjs +1607 -383
  225. package/src/main/seedAgentPersonas.cjs +1 -1
  226. package/src/main/seedDevPlugin.cjs +1 -1
  227. package/src/main/seedSchedulerMcp.cjs +7 -6
  228. package/src/main/seedStatus.cjs +1 -1
  229. package/src/main/supervisor.cjs +5 -3
  230. package/src/main/usage.cjs +126 -62
  231. package/src/preload/api.d.ts +18 -64
  232. package/src/preload/index.cjs +2 -4
  233. package/src/seed/agents/project-home-builder.md +31 -48
  234. package/screenshots/.gitkeep +0 -0
  235. package/screenshots/README-screenshots.md +0 -13
  236. package/src/main/lib/projectPageSummarySchema.cjs +0 -181
  237. package/src/main/teams.cjs +0 -95
  238. package/src/main/templates/project-pages-catalog.json +0 -741
  239. package/src/main/templates/project-pages-default-home.html +0 -123
  240. package/src/main/templates/project-pages-pipeline.md +0 -417
  241. package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
  242. package/src/seed/prompts/code-review/correctness-only.md +0 -8
  243. package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
  244. package/src/seed/prompts/code-review/hallucination-check.md +0 -8
  245. package/src/seed/prompts/code-review/public-api-compat.md +0 -8
  246. package/src/seed/prompts/code-review/readability-naming.md +0 -8
  247. package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
  248. package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
  249. package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
  250. package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
  251. package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
  252. package/src/seed/prompts/documentation/adr-from-change.md +0 -8
  253. package/src/seed/prompts/documentation/module-readme.md +0 -8
  254. package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
  255. package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
  256. package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
  257. package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
  258. package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
  259. package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
  260. package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
  261. package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
  262. package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
  263. package/src/seed/prompts/performance/complexity-audit.md +0 -8
  264. package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
  265. package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
  266. package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
  267. package/src/seed/prompts/qa/api-contract-tests.md +0 -8
  268. package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
  269. package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
  270. package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
  271. package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
  272. package/src/seed/prompts/qa/tdd-red-first.md +0 -8
  273. package/src/seed/prompts/qa/visual-regression-review.md +0 -8
  274. package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
  275. package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
  276. package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
  277. package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
  278. package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
  279. package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
  280. package/src/seed/prompts/security/authz-audit.md +0 -8
  281. package/src/seed/prompts/security/crypto-correctness.md +0 -8
  282. package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
  283. package/src/seed/prompts/security/dependency-audit.md +0 -8
  284. package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
  285. package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
  286. package/src/seed/prompts/security/secret-credential-scan.md +0 -8
  287. package/web/README.md +0 -41
  288. package/web/project-pages/logic/dist/logic.cjs +0 -4709
  289. package/web/project-pages/render.cjs +0 -70
  290. package/web/project-pages/renderer/dist/renderer.cjs +0 -18900
  291. package/web/project-pages/validate-summary.cjs +0 -62
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * adoptedRunSupervisor.cjs — re-arms the budget watchdog, idle-tail kill and
5
+ * deadman for an executor the boot SPARED (scheduler.cjs stamps
6
+ * `adoptedAtBoot`), from the durable `<slug>.supervisor.json` record.
7
+ *
8
+ * executeJob attaches those three timers to the child it spawned; an adopted
9
+ * executor belongs to a dead app process, so nothing supervised it (and a
10
+ * quietMachine row held no lease). Here the same three conditions are polled
11
+ * against the record's own numbers (budgetMs / startedAt, idleKillMs vs the
12
+ * log mtime, maxDurationMs) by the app process that owns the row. The
13
+ * signal goes to the recorded process group only after the pid is proven to
14
+ * still be the recorded process (procIdentity, fail-closed). It never mints
15
+ * a slot token: the adopted row stays `untrackedRunning` in pickNextBatch.
16
+ *
17
+ * Plain Node, no electron — every effect is injected so it is unit-testable
18
+ * with fake timers and without scheduler.cjs.
19
+ */
20
+
21
+ /**
22
+ * evaluateAdoptedRun(record, { now, logMtimeMs, defaultMaxDurationMs })
23
+ * → { kind: 'deadman'|'budget'|'idle-tail', reason } | null
24
+ * Pure. Precedence deadman > budget > idle-tail (hardest kill first).
25
+ */
26
+ function evaluateAdoptedRun(record, { now, logMtimeMs, defaultMaxDurationMs = 0 }) {
27
+ const started = Number.isFinite(record.startedAt) ? record.startedAt : null;
28
+ const elapsed = started === null ? null : now - started;
29
+ const deadmanMs = record.maxDurationMs > 0 ? record.maxDurationMs : defaultMaxDurationMs;
30
+ if (elapsed !== null && deadmanMs > 0 && elapsed >= deadmanMs) {
31
+ return { kind: 'deadman', reason: `deadman: ran ${Math.round(elapsed / 60_000)}m (>= ${Math.round(deadmanMs / 60_000)}m)` };
32
+ }
33
+ if (elapsed !== null && record.budgetMs > 0 && elapsed >= record.budgetMs) {
34
+ return {
35
+ kind: 'budget',
36
+ reason: `wall-clock budget exceeded: ran ${Math.round(elapsed / 60_000)}m against a ${Math.round(record.budgetMs / 60_000)}m budget (adopted after app restart)`,
37
+ };
38
+ }
39
+ if (record.idleKillMs > 0 && Number.isFinite(logMtimeMs) && now - logMtimeMs > record.idleKillMs) {
40
+ return { kind: 'idle-tail', reason: `idle-output: log mtime stalled ${Math.round((now - logMtimeMs) / 1000)}s (> ${Math.round(record.idleKillMs / 1000)}s)` };
41
+ }
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * superviseAdoptedRun(record, deps) → { cancel(), check() }
47
+ * One poll timer per adopted run; `check()` runs once immediately so a run
48
+ * already past its budget at boot is killed without waiting a full interval.
49
+ * deps: { logPath, now, statLogMtimeMs, pidAlive, identityOf, isDifferentProcess,
50
+ * killGroup(pid, signal), stampKill(kind, reason) → Promise, log(msg),
51
+ * checkIntervalMs, sigkillAfterMs, defaultMaxDurationMs }
52
+ */
53
+ function superviseAdoptedRun(record, deps) {
54
+ const {
55
+ logPath, now = Date.now, statLogMtimeMs, pidAlive, identityOf, isDifferentProcess, killGroup,
56
+ stampKill, log = () => {}, checkIntervalMs, sigkillAfterMs, defaultMaxDurationMs = 0,
57
+ } = deps;
58
+ let done = false;
59
+ let timer = null;
60
+ let killTimer = null;
61
+
62
+ const isOurs = () => pidAlive(record.pid) && !isDifferentProcess(record.identity, identityOf(record.pid));
63
+ const cancel = () => {
64
+ done = true;
65
+ if (timer) clearInterval(timer);
66
+ };
67
+
68
+ async function check() {
69
+ if (done) return null;
70
+ if (!isOurs()) { cancel(); return 'gone'; } // exited (reaper finalizes) or pid recycled — never signal
71
+ const verdict = evaluateAdoptedRun(record, {
72
+ now: now(), logMtimeMs: statLogMtimeMs(logPath), defaultMaxDurationMs,
73
+ });
74
+ if (!verdict) return null;
75
+ cancel();
76
+ try { await stampKill(verdict.kind, verdict.reason); } catch (e) { log(`stampKill failed: ${e?.message ?? e}`); }
77
+ if (!isOurs()) return 'gone'; // exited while the stamp was in flight
78
+ const first = verdict.kind === 'deadman' ? 'SIGKILL' : 'SIGTERM';
79
+ log(`adopted-run ${verdict.kind} watchdog: ${verdict.reason} — ${first} process group`);
80
+ killGroup(record.pgid || record.pid, first, record.pid);
81
+ if (first === 'SIGTERM') {
82
+ killTimer = setTimeout(() => {
83
+ if (isOurs()) killGroup(record.pgid || record.pid, 'SIGKILL', record.pid);
84
+ }, sigkillAfterMs);
85
+ if (killTimer.unref) killTimer.unref();
86
+ }
87
+ return verdict.kind;
88
+ }
89
+
90
+ timer = setInterval(() => { check().catch((e) => log(`check failed: ${e?.message ?? e}`)); }, checkIntervalMs);
91
+ if (timer.unref) timer.unref();
92
+ return {
93
+ cancel() { cancel(); if (killTimer) clearTimeout(killTimer); },
94
+ check,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * superviseAdoptedRuns(jobs, ctx) → Promise<string[]> (slugs newly armed)
100
+ * Arms a supervisor for every `running` row stamped adoptedAtBoot that has
101
+ * none in `ctx.registry` (Map key `slug|runId`), drops registry entries whose
102
+ * row is no longer that run, re-acquires the quietMachine lease and stamps
103
+ * supervisedAt. ctx: { registry, runDir(row), readRecord(runDir, slug),
104
+ * lease: { acquire, holder }, markSupervised(row), makeDeps(row, record) }
105
+ * Complexity: O(jobs).
106
+ */
107
+ async function superviseAdoptedRuns(jobs, ctx) {
108
+ const { registry, runDir, readRecord, lease, markSupervised, makeDeps } = ctx;
109
+ const live = new Set();
110
+ const armed = [];
111
+ for (const j of jobs) {
112
+ if (j.status !== 'running' || !j.adoptedAtBoot) continue;
113
+ const key = `${j.slug}|${j.runId ?? ''}`;
114
+ live.add(key);
115
+ if (registry.has(key)) continue;
116
+ const dir = runDir(j);
117
+ const record = dir ? readRecord(dir, j.slug) : null;
118
+ if (!record || !record.pid) continue; // no durable record → nothing to re-arm from
119
+ if (j.quietMachine === true && !lease.acquire(j.slug) && lease.holder() !== j.slug) {
120
+ console.warn(`[scheduler] adopted quietMachine run ${j.slug}: lease held by ${lease.holder()} — not re-acquired`);
121
+ }
122
+ const handle = superviseAdoptedRun(record, makeDeps(j, record));
123
+ registry.set(key, handle);
124
+ armed.push(j.slug);
125
+ await markSupervised(j);
126
+ handle.check().catch(() => {});
127
+ }
128
+ for (const [key, handle] of registry) {
129
+ if (live.has(key)) continue;
130
+ handle.cancel();
131
+ registry.delete(key);
132
+ }
133
+ return armed;
134
+ }
135
+
136
+ module.exports = { evaluateAdoptedRun, superviseAdoptedRun, superviseAdoptedRuns };
@@ -27,6 +27,7 @@ const path = require('node:path');
27
27
  const os = require('node:os');
28
28
  const { splitFrontmatter } = require('./prdFrontmatter.cjs');
29
29
  const { readActiveIndex, resolvePersonaPaths } = require('./epicMint.cjs');
30
+ const { PERSONA_NAME_RE } = require('../agentLibrary.cjs');
30
31
  const configMgr = require('../config.cjs');
31
32
 
32
33
  /** The hardcoded floor every branch of resolveEpicModel falls back to — --model must never be omitted (CLAUDE.md "Automation model pinning"). */
@@ -125,12 +126,42 @@ function readModelFromCandidatePaths(candidates, agentType, deps) {
125
126
  * — never throws.
126
127
  */
127
128
  function readOverlayAwarePersonaModel(agentType, deps = {}) {
128
- if (!agentType) return null;
129
+ // Same guard agentLibrary.cjs's getPersonaBody applies before it joins
130
+ // `name` into a path: without it, a caller-supplied agentType like
131
+ // "../../other-project/CLAUDE" survives resolvePersonaPaths' plain
132
+ // path.join and validatePath's allowed-roots check (which only confirms
133
+ // the resolved path stays under the home dir / an opened project), letting
134
+ // any readable .md under those roots be read on this --model spawn path.
135
+ if (!agentType || !PERSONA_NAME_RE.test(agentType)) return null;
129
136
  const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
130
137
  const { projectPath, globalPath } = resolvePaths(deps.cwd, agentType, deps);
131
138
  return readModelFromCandidatePaths([projectPath, globalPath], agentType, deps);
132
139
  }
133
140
 
141
+ /**
142
+ * True when `personaPath` (an already-resolved persona file path, e.g. from
143
+ * agentLibrary.cjs's getPersonaBody) is the PROJECT overlay candidate rather
144
+ * than the global one — the same "which of resolvePersonaPaths' two
145
+ * candidates won" question readModelFromCandidatePaths above answers
146
+ * structurally by trying the project path first. Exported so a caller that
147
+ * already has a resolved persona path from a DIFFERENT reader (one that
148
+ * needs async I/O or richer provenance than this module's sync
149
+ * readOverlayAwarePersonaModel returns — e.g. effectiveModelInfo.cjs) can
150
+ * ask this module the overlay question instead of re-deriving its own
151
+ * resolvePersonaPaths-based comparison. Never throws; a resolution failure
152
+ * conservatively answers "not overlay" (assume global).
153
+ */
154
+ function isProjectOverlayPersonaPath(cwd, agentType, personaPath, deps = {}) {
155
+ if (!personaPath) return false;
156
+ try {
157
+ const resolvePaths = deps.resolvePersonaPaths || resolvePersonaPaths;
158
+ const { projectPath } = resolvePaths(cwd, agentType, deps);
159
+ return Boolean(projectPath) && path.resolve(projectPath) === path.resolve(personaPath);
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
134
165
  /**
135
166
  * Resolves the `--model` value for an Epic-backed session launch. Never
136
167
  * throws — any lookup failure (missing Epic, missing persona, unreadable
@@ -220,5 +251,6 @@ module.exports = {
220
251
  resolveEpicModel,
221
252
  findAgentTypeByClaudeSessionId,
222
253
  readOverlayAwarePersonaModel,
254
+ isProjectOverlayPersonaPath,
223
255
  resolvePrdPersonaForSpawn,
224
256
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Canonical runtime schema for an Agent persona WRITE payload, mirroring the
3
- * TS `AgentPersonaSaveInput` interface at src/preload/api.d.ts:229-246
3
+ * TS `AgentPersonaSaveInput` interface in src/preload/api.d.ts
4
4
  * field-for-field.
5
5
  *
6
6
  * Modelled on promptSessionSchema.cjs / scheduleJobSchema.cjs's "zod schema
@@ -39,7 +39,7 @@ const ProjectEntrySchema = z.string().refine(
39
39
  `must be "${ALL_PROJECTS}" or an absolute path`,
40
40
  );
41
41
 
42
- // Mirrors AgentPersonaSaveInput (src/preload/api.d.ts:229-246). `tags` is the
42
+ // Mirrors AgentPersonaSaveInput (src/preload/api.d.ts). `tags` is the
43
43
  // Epic-mission WorkType union, NOT a free-form string list — the same
44
44
  // concept as PromptSessionSchema's `tag` / scheduleRetagPrd's `tag`, just
45
45
  // plural here since a persona can be associated with more than one mission.
@@ -16,10 +16,9 @@
16
16
  'use strict';
17
17
 
18
18
  const fs = require('node:fs');
19
- const os = require('node:os');
20
19
  const path = require('node:path');
21
20
 
22
- const AUDIT_LOG_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'audit-log.jsonl');
21
+ const { auditLogPath } = require('./schedulerPaths.cjs');
23
22
 
24
23
  /**
25
24
  * Best-effort identity of the process making this call — there is no
@@ -50,11 +49,37 @@ function appendAuditEvent(kind, fields = {}) {
50
49
  ...fields,
51
50
  caller: callerContext(),
52
51
  };
53
- fs.mkdirSync(path.dirname(AUDIT_LOG_PATH), { recursive: true });
54
- fs.appendFileSync(AUDIT_LOG_PATH, `${JSON.stringify(record)}\n`);
52
+ const target = auditLogPath();
53
+ fs.mkdirSync(path.dirname(target), { recursive: true });
54
+ fs.appendFileSync(target, `${JSON.stringify(record)}\n`);
55
55
  } catch (e) {
56
56
  console.error('[auditLog] failed to append audit event', e?.message ?? String(e));
57
57
  }
58
58
  }
59
59
 
60
- module.exports = { appendAuditEvent, AUDIT_LOG_PATH };
60
+ /**
61
+ * readTail(maxBytes, target?) → string[] — the complete JSONL lines within the
62
+ * last `maxBytes` of the log, oldest first. O(maxBytes), never O(file): the
63
+ * audit log is a never-rotated provenance record and grows without bound. A
64
+ * line cut by the window's start is dropped. Missing/unreadable file → [].
65
+ */
66
+ function readTail(maxBytes, target = auditLogPath()) {
67
+ let fd;
68
+ try {
69
+ fd = fs.openSync(target, 'r');
70
+ const { size } = fs.fstatSync(fd);
71
+ const readSize = Math.min(Math.max(0, maxBytes), size);
72
+ if (readSize === 0) return [];
73
+ const buf = Buffer.alloc(readSize);
74
+ fs.readSync(fd, buf, 0, readSize, size - readSize);
75
+ const lines = buf.toString('utf8').split('\n');
76
+ if (readSize < size) lines.shift(); // first element is a partial line
77
+ return lines.filter(Boolean);
78
+ } catch {
79
+ return [];
80
+ } finally {
81
+ if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* */ } }
82
+ }
83
+ }
84
+
85
+ module.exports = { appendAuditEvent, auditLogPath, readTail };
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * buildIdentity.cjs — answers "which code produced this run" for both a
5
+ * published npx install (no .git shipped) and a dev checkout / job worktree
6
+ * (a real .git, but never the source of truth once a build has been baked).
7
+ *
8
+ * Resolution order, memoized after first call:
9
+ * 1. src/main/build-info.json (written by scripts/write-build-info.cjs at
10
+ * `prepack`/`prepublishOnly` time, from the PUBLISHING checkout) — the
11
+ * only artifact that is honest in production, since the npx cache the
12
+ * app actually runs from ships no .git directory at all.
13
+ * 2. `git rev-parse` against the package root, but ONLY when a .git
14
+ * directory exists AT that exact root — never git's own upward walk.
15
+ * Without this guard, a relocated/nested install whose npm cache
16
+ * happens to sit under an ancestor .git would silently stamp a FOREIGN
17
+ * sha (git -C only sets the starting point; it still searches upward
18
+ * for the nearest .git by default). Checking existence at the package
19
+ * root ourselves, and only then invoking git, means the first (and
20
+ * only) .git git can find IS the one we already verified.
21
+ * 3. null.
22
+ */
23
+
24
+ const fs = require('node:fs');
25
+ const path = require('node:path');
26
+ const { execFileSync } = require('node:child_process');
27
+ const { resolveInstallChannel } = require('./machineProfile.cjs');
28
+
29
+ // src/main/lib -> src/main -> src -> package root (repo root in dev, or the
30
+ // npx-cached package dir in production).
31
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..', '..');
32
+
33
+ function loadBuildInfoJson(packageRoot) {
34
+ try {
35
+ const parsed = JSON.parse(fs.readFileSync(path.join(packageRoot, 'src', 'main', 'build-info.json'), 'utf8'));
36
+ return parsed && typeof parsed === 'object' ? parsed : null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function resolvePackageVersion(packageRoot) {
43
+ try {
44
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
45
+ return typeof pkg.version === 'string' ? pkg.version : null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function resolveGitCodeSha(packageRoot) {
52
+ if (!fs.existsSync(path.join(packageRoot, '.git'))) return null;
53
+ try {
54
+ return execFileSync('git', ['-C', packageRoot, 'rev-parse', '--short', 'HEAD'], {
55
+ timeout: 5000,
56
+ encoding: 'utf8',
57
+ stdio: ['ignore', 'pipe', 'ignore'],
58
+ }).trim();
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function computeBuildIdentity(packageRoot) {
65
+ const buildInfo = loadBuildInfoJson(packageRoot);
66
+ const version = (buildInfo && typeof buildInfo.version === 'string') ? buildInfo.version : resolvePackageVersion(packageRoot);
67
+ const codeSha = buildInfo
68
+ ? ((typeof buildInfo.gitShortSha === 'string') ? buildInfo.gitShortSha : null)
69
+ : resolveGitCodeSha(packageRoot);
70
+ const builtAt = (buildInfo && typeof buildInfo.builtAt === 'string') ? buildInfo.builtAt : null;
71
+ return {
72
+ version,
73
+ codeSha,
74
+ builtAt,
75
+ appPath: packageRoot,
76
+ installChannel: resolveInstallChannel({ appPath: packageRoot, devFlag: !!process.env.SM_DEV }),
77
+ };
78
+ }
79
+
80
+ // Keyed by resolved root: a real process only ever resolves ONE root (this
81
+ // module's own package root), so in production/dev this behaves as a plain
82
+ // singleton memo — the Map only grows past size 1 under test, where each
83
+ // case injects a distinct fixture packageRoot.
84
+ const identityCache = new Map();
85
+
86
+ /**
87
+ * @param {object} [opts]
88
+ * @param {string|null} [opts.bootedAt] - the caller's OWN already-captured
89
+ * process-boot timestamp (e.g. scheduler.cjs's SCHEDULER_BOOTED_AT). Never
90
+ * re-captured here — a memoized resolver that stamped its own "now" would
91
+ * drift from what SCHEDULER_BOOTED_AT was introduced to expose (PRD 812:
92
+ * process staleness relative to on-disk source).
93
+ * @param {string} [opts.packageRoot] - test-only override; production/dev
94
+ * callers never pass this, so it always resolves to this module's own
95
+ * package root.
96
+ */
97
+ function resolveBuildIdentity(opts = {}) {
98
+ const packageRoot = opts.packageRoot || PACKAGE_ROOT;
99
+ if (!identityCache.has(packageRoot)) identityCache.set(packageRoot, computeBuildIdentity(packageRoot));
100
+ return { ...identityCache.get(packageRoot), bootedAt: opts.bootedAt ?? null };
101
+ }
102
+
103
+ /**
104
+ * readInstalledBuildInfo() → the build-info.json currently ON DISK at this
105
+ * package root (fresh read, never the boot-time memo), or null. Compared with
106
+ * the running identity's codeSha to detect that an install/update already
107
+ * replaced the files under a live process.
108
+ */
109
+ function readInstalledBuildInfo(packageRoot = PACKAGE_ROOT) {
110
+ return loadBuildInfoJson(packageRoot);
111
+ }
112
+
113
+ module.exports = { resolveBuildIdentity, readInstalledBuildInfo };
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  const { spawn } = require('node:child_process');
19
- const { resolveClaudeBin } = require('./claudeBin.cjs');
19
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./claudeBin.cjs');
20
20
  const { cleanChildEnv, pathWithUserBins } = require('./cleanEnv.cjs');
21
21
 
22
22
  const CLASSIFY_TIMEOUT_MS = 30_000;
@@ -78,7 +78,8 @@ function classifyPromptTicket(text) {
78
78
 
79
79
  try {
80
80
  // stdin closed — `claude -p` otherwise blocks waiting for piped stdin.
81
- child = spawnImpl(claudeBin, args, { env: childEnv, stdio: ['ignore', 'pipe', 'pipe'] });
81
+ const target = claudeSpawnTarget('aux', 'classify', claudeBin);
82
+ child = spawnImpl(target.command, args, { env: childEnv, stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
82
83
  } catch {
83
84
  finish('inline');
84
85
  return;
@@ -39,6 +39,40 @@ function resolveClaudeBin() {
39
39
  return cached;
40
40
  }
41
41
 
42
+ // ---------- process naming ----------
43
+ //
44
+ // Every `claude` spawn goes through claudeSpawnTarget so GNOME System Monitor
45
+ // shows a distinct comm per role (sm-claude-job / -chat / -aux) and a labelled
46
+ // argv0. See lib/procName.cjs for the mechanics. Invariants:
47
+ // - SM_CLAUDE_BIN set → aliasing SKIPPED, raw value used verbatim (tests point
48
+ // it at self-locating stubs; it must stay ONE binary, no path splitting).
49
+ // - aliasBinFor fails open (returns realBin) → no argv0 is set either, so the
50
+ // fallback is byte-identical to the pre-naming spawn.
51
+ // - Every alias and argv0 label contains the word `claude`, which the four
52
+ // /\bclaude\b/ cmdline gates (killOrphanClaudePid, claudePidAlive,
53
+ // findLiveProcessForJob x2) depend on.
54
+ const ROLE_ALIASES = { job: 'sm-claude-job', chat: 'sm-claude-chat', aux: 'sm-claude-aux' };
55
+
56
+ /**
57
+ * @param {'job'|'chat'|'aux'} role
58
+ * @param {string} [detail] argv0 label detail (job slug / epic session id)
59
+ * @param {string} [realBin] defaults to resolveClaudeBin()
60
+ * @returns {{ command: string, argv0?: string }} spread `argv0` into spawn options
61
+ */
62
+ function claudeSpawnTarget(role, detail, realBin = resolveClaudeBin()) {
63
+ if (process.env.SM_CLAUDE_BIN) return { command: realBin };
64
+ const alias = ROLE_ALIASES[role];
65
+ if (!alias) return { command: realBin };
66
+ try {
67
+ const { aliasBinFor, smArgv0 } = require('./procName.cjs');
68
+ const command = aliasBinFor(realBin, alias);
69
+ if (command === realBin) return { command: realBin };
70
+ return { command, argv0: smArgv0(role, detail) };
71
+ } catch {
72
+ return { command: realBin };
73
+ }
74
+ }
75
+
42
76
  // ---------- version probe ----------
43
77
  //
44
78
  // `claude --version`, cached. The scheduler's launch circuit breaker
@@ -75,7 +109,8 @@ function probeClaudeVersion({ now = Date.now(), execFileImpl } = {}) {
75
109
  // it, and the binary must never be handed the caller's working tree —
76
110
  // a stub `claude` (tests point SM_CLAUDE_BIN at one) that writes
77
111
  // markers or commits into process.cwd() once did so in the real repo.
78
- execFile(bin, ['--version'], { timeout: VERSION_PROBE_TIMEOUT_MS, windowsHide: true, cwd: os.tmpdir() }, (err, stdout) => {
112
+ const target = claudeSpawnTarget('aux', 'version', bin);
113
+ execFile(target.command, ['--version'], { timeout: VERSION_PROBE_TIMEOUT_MS, windowsHide: true, cwd: os.tmpdir(), ...(target.argv0 ? { argv0: target.argv0 } : {}) }, (err, stdout) => {
79
114
  if (err) return done(null);
80
115
  const m = /(\d+\.\d+\.\d+[^\s]*)/.exec(String(stdout || ''));
81
116
  done(m ? m[1] : (String(stdout || '').trim() || null));
@@ -92,4 +127,4 @@ function resetClaudeVersionCache() {
92
127
  versionCache = { at: 0, value: null, inflight: null };
93
128
  }
94
129
 
95
- module.exports = { resolveClaudeBin, probeClaudeVersion, resetClaudeVersionCache };
130
+ module.exports = { resolveClaudeBin, claudeSpawnTarget, probeClaudeVersion, resetClaudeVersionCache };
@@ -35,8 +35,34 @@ function pathWithUserBins() {
35
35
  return base ? `${base}:${userBinDirs().join(':')}` : userBinDirs().join(':');
36
36
  }
37
37
 
38
+ /**
39
+ * SM_PROC_ROLE attributes a child (and, by inheritance, every MCP server the
40
+ * `claude` child spawns — we cannot rename those) via /proc/<pid>/environ.
41
+ * Read by scripts/sm-ps.cjs. NOT SM_PROC_ROOT (procIdentity.cjs's /proc-root
42
+ * test override) — unrelated variable.
43
+ */
44
+ const PROC_ROLE_ENV = 'SM_PROC_ROLE';
45
+
46
+ /** Explicit `SM_PROC_ROLE` in `extra` wins; else inferred from the attribution
47
+ * vars each spawn site already sets (job slug → job, tab id → shell, chat
48
+ * session → chat); else `aux`. Inference keeps role stamping in ONE place. */
49
+ function inferProcRole(extra) {
50
+ if (extra[PROC_ROLE_ENV]) return String(extra[PROC_ROLE_ENV]);
51
+ if (extra.SM_SCHEDULER_JOB_SLUG) return 'job';
52
+ if (extra.SESSION_MANAGER_TAB_ID) return 'shell';
53
+ if (extra.SM_CHAT_SESSION_ID) return 'chat';
54
+ return 'aux';
55
+ }
56
+
57
+ /** Stamp SM_PROC_ROLE onto a raw env object (for sites that deliberately do not
58
+ * use cleanChildEnv's secret stripping, e.g. runClaudeP/docEdit). Returns a copy. */
59
+ function withProcRole(env, role) {
60
+ return { ...env, [PROC_ROLE_ENV]: role };
61
+ }
62
+
38
63
  function cleanChildEnv(extra = {}) {
39
64
  const env = { ...process.env, ...extra };
65
+ env[PROC_ROLE_ENV] = inferProcRole(extra);
40
66
  for (const k of Object.keys(env)) {
41
67
  if (
42
68
  k === 'CLAUDE_EFFORT' ||
@@ -52,4 +78,4 @@ function cleanChildEnv(extra = {}) {
52
78
  return env;
53
79
  }
54
80
 
55
- module.exports = { cleanChildEnv, userBinDirs, pathWithUserBins };
81
+ module.exports = { cleanChildEnv, withProcRole, inferProcRole, PROC_ROLE_ENV, userBinDirs, pathWithUserBins };
@@ -6,7 +6,7 @@ const path = require('node:path');
6
6
  const os = require('node:os');
7
7
  const { spawn, execFileSync } = require('node:child_process');
8
8
  const { cleanChildEnv, pathWithUserBins } = require('./cleanEnv.cjs');
9
- const { resolveClaudeBin } = require('./claudeBin.cjs');
9
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./claudeBin.cjs');
10
10
 
11
11
  const CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
12
12
 
@@ -188,7 +188,9 @@ function tryCliFallback() {
188
188
  try {
189
189
  // Resolve the absolute binary + inject Homebrew/user bins so this works
190
190
  // even when Electron launched from Finder/Dock with a stripped PATH (mac).
191
- child = spawn(resolveClaudeBin(), ['--version'], {
191
+ const target = claudeSpawnTarget('aux', 'credcheck');
192
+ child = spawn(target.command, ['--version'], {
193
+ ...(target.argv0 ? { argv0: target.argv0 } : {}),
192
194
  stdio: 'ignore',
193
195
  env: cleanChildEnv({ PATH: pathWithUserBins() }),
194
196
  });