@the-open-engine/zeroshot 6.38.0 → 6.39.0

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 (381) hide show
  1. package/cli/event-copy.js +8 -7
  2. package/cli/event-copy.ts +12 -0
  3. package/cli/index.js +7 -31
  4. package/cli/json-export.js +61 -64
  5. package/cli/json-export.ts +112 -0
  6. package/cli/message-formatter-utils.js +68 -56
  7. package/cli/message-formatter-utils.ts +121 -0
  8. package/cli/message-formatters-normal.js +124 -259
  9. package/cli/message-formatters-normal.ts +250 -0
  10. package/cli/message-formatters-watch.js +81 -84
  11. package/cli/message-formatters-watch.ts +189 -0
  12. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  13. package/lib/agent-cli-provider/adapters/codex.js +3 -1
  14. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  15. package/lib/cluster/generated/protocol-schema.cjs +1 -1
  16. package/lib/cluster/generated/protocol-schema.mjs +1 -1
  17. package/lib/cluster/generated/protocol.d.cts +8 -0
  18. package/lib/cluster/generated/protocol.d.mts +8 -0
  19. package/lib/cluster/generated/protocol.d.ts +8 -0
  20. package/lib/clusters-registry.d.ts +23 -0
  21. package/lib/clusters-registry.js +17 -29
  22. package/lib/completion.d.ts +12 -0
  23. package/lib/completion.js +84 -94
  24. package/lib/compose-utils.d.ts +32 -0
  25. package/lib/compose-utils.js +57 -72
  26. package/lib/detached-startup.d.ts +83 -0
  27. package/lib/detached-startup.js +231 -302
  28. package/lib/docker-config.d.ts +43 -0
  29. package/lib/docker-config.js +150 -325
  30. package/lib/docker-env-auth.d.ts +17 -0
  31. package/lib/docker-env-auth.js +75 -0
  32. package/lib/git-remote-utils.d.ts +48 -0
  33. package/lib/git-remote-utils.js +147 -213
  34. package/lib/id-detector.d.ts +15 -0
  35. package/lib/id-detector.js +37 -41
  36. package/lib/path-check.d.ts +30 -0
  37. package/lib/path-check.js +42 -45
  38. package/lib/process-liveness.d.ts +16 -0
  39. package/lib/process-liveness.js +19 -12
  40. package/lib/provider-credential-path.d.ts +16 -0
  41. package/lib/provider-credential-path.js +31 -29
  42. package/lib/provider-defaults.d.ts +30 -0
  43. package/lib/provider-defaults.js +47 -42
  44. package/lib/provider-detection.d.ts +11 -0
  45. package/lib/provider-detection.js +91 -54
  46. package/lib/provider-names.d.ts +38 -0
  47. package/lib/provider-names.js +56 -64
  48. package/lib/repo-settings.d.ts +28 -0
  49. package/lib/repo-settings.js +83 -59
  50. package/lib/run-mode.d.ts +21 -0
  51. package/lib/run-mode.js +21 -20
  52. package/lib/run-plan.d.ts +28 -0
  53. package/lib/run-plan.js +17 -16
  54. package/lib/settings/claude-auth.d.ts +35 -0
  55. package/lib/settings/claude-auth.js +30 -47
  56. package/lib/settings-defaults.d.ts +20 -0
  57. package/lib/settings-defaults.js +149 -0
  58. package/lib/settings-error.d.ts +7 -0
  59. package/lib/settings-error.js +8 -0
  60. package/lib/settings-issue-providers.d.ts +10 -0
  61. package/lib/settings-issue-providers.js +12 -0
  62. package/lib/settings-models.d.ts +22 -0
  63. package/lib/settings-models.js +93 -0
  64. package/lib/settings-storage.d.ts +23 -0
  65. package/lib/settings-storage.js +205 -0
  66. package/lib/settings-validation.d.ts +7 -0
  67. package/lib/settings-validation.js +182 -0
  68. package/lib/settings.d.ts +29 -0
  69. package/lib/settings.js +41 -736
  70. package/lib/setup-apply-types.d.ts +66 -0
  71. package/lib/setup-apply-types.js +2 -0
  72. package/lib/setup-apply.d.ts +39 -0
  73. package/lib/setup-apply.js +267 -264
  74. package/lib/setup-journal.d.ts +41 -0
  75. package/lib/setup-journal.js +119 -76
  76. package/lib/setup-plan-types.d.ts +102 -0
  77. package/lib/setup-plan-types.js +2 -0
  78. package/lib/setup-plan.d.ts +30 -0
  79. package/lib/setup-plan.js +300 -324
  80. package/lib/setup-undo.d.ts +44 -0
  81. package/lib/setup-undo.js +61 -74
  82. package/lib/start-cluster-config.d.ts +26 -0
  83. package/lib/start-cluster-config.js +56 -0
  84. package/lib/start-cluster-environment.d.ts +40 -0
  85. package/lib/start-cluster-environment.js +134 -0
  86. package/lib/start-cluster-run-options.d.ts +53 -0
  87. package/lib/start-cluster-run-options.js +147 -0
  88. package/lib/start-cluster.d.ts +64 -0
  89. package/lib/start-cluster.js +98 -454
  90. package/lib/stream-json-parser.d.ts +17 -0
  91. package/lib/stream-json-parser.js +51 -52
  92. package/npm-shrinkwrap.json +2 -2
  93. package/package.json +12 -6
  94. package/scripts/postinstall.js +27 -0
  95. package/scripts/rust-distribution.js +23 -14
  96. package/src/agent/agent-command-proofs-context.js +35 -45
  97. package/src/agent/agent-command-proofs-context.ts +87 -0
  98. package/src/agent/agent-config-settings.js +19 -0
  99. package/src/agent/agent-config-settings.ts +41 -0
  100. package/src/agent/agent-config.js +122 -231
  101. package/src/agent/agent-config.ts +285 -0
  102. package/src/agent/agent-context-builder.js +38 -273
  103. package/src/agent/agent-context-builder.ts +51 -0
  104. package/src/agent/agent-context-environment-sections.js +105 -0
  105. package/src/agent/agent-context-environment-sections.ts +134 -0
  106. package/src/agent/agent-context-packs.js +165 -0
  107. package/src/agent/agent-context-packs.ts +296 -0
  108. package/src/agent/agent-context-prompt-sections.js +100 -0
  109. package/src/agent/agent-context-prompt-sections.ts +148 -0
  110. package/src/agent/agent-context-sections.js +25 -334
  111. package/src/agent/agent-context-sections.ts +36 -0
  112. package/src/agent/agent-context-sources.js +96 -149
  113. package/src/agent/agent-context-sources.ts +253 -0
  114. package/src/agent/agent-context-types.js +2 -0
  115. package/src/agent/agent-context-types.ts +132 -0
  116. package/src/agent/agent-context-validation-sections.js +73 -0
  117. package/src/agent/agent-context-validation-sections.ts +129 -0
  118. package/src/agent/agent-input-injector.js +99 -118
  119. package/src/agent/agent-input-injector.ts +195 -0
  120. package/src/agent/agent-liveness-poll.js +74 -78
  121. package/src/agent/agent-liveness-poll.ts +163 -0
  122. package/src/agent/agent-quality-gate-schema.js +77 -86
  123. package/src/agent/agent-quality-gate-schema.ts +135 -0
  124. package/src/agent/agent-quality-gates-context.js +44 -50
  125. package/src/agent/agent-quality-gates-context.ts +91 -0
  126. package/src/agent/agent-stuck-detector.js +171 -239
  127. package/src/agent/agent-stuck-detector.ts +288 -0
  128. package/src/agent/agent-trigger-evaluator.js +29 -46
  129. package/src/agent/agent-trigger-evaluator.ts +98 -0
  130. package/src/agent/context-metrics.js +110 -141
  131. package/src/agent/context-metrics.ts +261 -0
  132. package/src/agent/context-pack-builder.js +72 -358
  133. package/src/agent/context-pack-builder.ts +105 -0
  134. package/src/agent/context-pack-core.js +70 -0
  135. package/src/agent/context-pack-core.ts +96 -0
  136. package/src/agent/context-pack-selection.js +119 -0
  137. package/src/agent/context-pack-selection.ts +187 -0
  138. package/src/agent/context-pack-truncation.js +139 -0
  139. package/src/agent/context-pack-truncation.ts +208 -0
  140. package/src/agent/context-pack-types.js +2 -0
  141. package/src/agent/context-pack-types.ts +98 -0
  142. package/src/agent/context-replay-policy.js +30 -39
  143. package/src/agent/context-replay-policy.ts +71 -0
  144. package/src/agent/critical-agent-policy.js +6 -10
  145. package/src/agent/critical-agent-policy.ts +20 -0
  146. package/src/agent/guidance-queue.js +74 -87
  147. package/src/agent/guidance-queue.ts +166 -0
  148. package/src/agent/output-extraction-error-detail.js +45 -0
  149. package/src/agent/output-extraction-error-detail.ts +49 -0
  150. package/src/agent/output-extraction-failures.js +125 -0
  151. package/src/agent/output-extraction-failures.ts +149 -0
  152. package/src/agent/output-extraction-json.js +234 -0
  153. package/src/agent/output-extraction-json.ts +235 -0
  154. package/src/agent/output-extraction-pi.js +124 -0
  155. package/src/agent/output-extraction-pi.ts +137 -0
  156. package/src/agent/output-extraction-types.js +2 -0
  157. package/src/agent/output-extraction-types.ts +49 -0
  158. package/src/agent/output-extraction.js +17 -617
  159. package/src/agent/output-extraction.ts +30 -0
  160. package/src/agent/output-reformatter-errors.js +83 -0
  161. package/src/agent/output-reformatter-errors.ts +106 -0
  162. package/src/agent/output-reformatter-types.js +2 -0
  163. package/src/agent/output-reformatter-types.ts +86 -0
  164. package/src/agent/output-reformatter.js +140 -185
  165. package/src/agent/output-reformatter.ts +238 -0
  166. package/src/agent/pi-terminal-lifecycle-types.js +2 -0
  167. package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
  168. package/src/agent/pi-terminal-lifecycle.js +186 -206
  169. package/src/agent/pi-terminal-lifecycle.ts +297 -0
  170. package/src/agent/provider-control-plane.js +22 -21
  171. package/src/agent/provider-control-plane.ts +52 -0
  172. package/src/agent/provider-terminal-failure-types.js +2 -0
  173. package/src/agent/provider-terminal-failure-types.ts +60 -0
  174. package/src/agent/provider-terminal-failure.js +194 -191
  175. package/src/agent/provider-terminal-failure.ts +262 -0
  176. package/src/agent/rate-limit-backoff.js +31 -74
  177. package/src/agent/rate-limit-backoff.ts +71 -0
  178. package/src/agent/schema-utils.js +85 -125
  179. package/src/agent/schema-utils.ts +148 -0
  180. package/src/agent/structured-output-error.js +34 -33
  181. package/src/agent/structured-output-error.ts +105 -0
  182. package/src/agent/validation-platform.js +27 -27
  183. package/src/agent/validation-platform.ts +49 -0
  184. package/src/agent-cli-provider/adapters/codex.ts +3 -1
  185. package/src/attach/attach-client.js +232 -405
  186. package/src/attach/attach-client.ts +300 -0
  187. package/src/attach/attach-server-cleanup.js +43 -0
  188. package/src/attach/attach-server-cleanup.ts +39 -0
  189. package/src/attach/attach-server-clients.js +117 -0
  190. package/src/attach/attach-server-clients.ts +122 -0
  191. package/src/attach/attach-server-events.js +38 -0
  192. package/src/attach/attach-server-events.ts +36 -0
  193. package/src/attach/attach-server-pty.js +45 -0
  194. package/src/attach/attach-server-pty.ts +64 -0
  195. package/src/attach/attach-server-runtime.js +73 -0
  196. package/src/attach/attach-server-runtime.ts +62 -0
  197. package/src/attach/attach-server-socket.js +28 -0
  198. package/src/attach/attach-server-socket.ts +23 -0
  199. package/src/attach/attach-server-types.js +2 -0
  200. package/src/attach/attach-server-types.ts +82 -0
  201. package/src/attach/attach-server.js +106 -523
  202. package/src/attach/attach-server.ts +158 -0
  203. package/src/attach/index.js +18 -35
  204. package/src/attach/index.ts +18 -0
  205. package/src/attach/protocol.js +95 -184
  206. package/src/attach/protocol.ts +181 -0
  207. package/src/attach/ring-buffer.js +76 -102
  208. package/src/attach/ring-buffer.ts +109 -0
  209. package/src/attach/send-input.js +64 -75
  210. package/src/attach/send-input.ts +94 -0
  211. package/src/attach/socket-discovery.js +164 -218
  212. package/src/attach/socket-discovery.ts +230 -0
  213. package/src/attach/socket-paths.js +42 -61
  214. package/src/attach/socket-paths.ts +81 -0
  215. package/src/claude-credentials.js +51 -54
  216. package/src/claude-credentials.ts +89 -0
  217. package/src/cluster/generated/protocol-schema.ts +1 -1
  218. package/src/cluster/generated/protocol.ts +2 -1
  219. package/src/command-proofs.js +110 -141
  220. package/src/command-proofs.ts +216 -0
  221. package/src/config-router.js +81 -73
  222. package/src/config-router.ts +131 -0
  223. package/src/copy-worker.js +53 -29
  224. package/src/copy-worker.ts +75 -0
  225. package/src/darwin-keychain-boundary.js +80 -111
  226. package/src/darwin-keychain-boundary.ts +148 -0
  227. package/src/guidance-topics.js +4 -5
  228. package/src/guidance-topics.ts +10 -0
  229. package/src/input-helpers.js +33 -54
  230. package/src/input-helpers.ts +58 -0
  231. package/src/ledger-sequence.js +45 -48
  232. package/src/ledger-sequence.ts +63 -0
  233. package/src/legacy-lib/clusters-registry.ts +40 -0
  234. package/src/legacy-lib/completion.ts +148 -0
  235. package/src/legacy-lib/compose-utils.ts +102 -0
  236. package/src/legacy-lib/detached-startup.ts +449 -0
  237. package/src/legacy-lib/docker-config.ts +282 -0
  238. package/src/legacy-lib/docker-env-auth.ts +116 -0
  239. package/src/legacy-lib/git-remote-utils.ts +244 -0
  240. package/src/legacy-lib/id-detector.ts +57 -0
  241. package/src/legacy-lib/js-yaml.d.ts +3 -0
  242. package/src/legacy-lib/omelette.d.ts +15 -0
  243. package/src/legacy-lib/path-check.ts +89 -0
  244. package/src/legacy-lib/process-liveness.ts +36 -0
  245. package/src/legacy-lib/provider-credential-path.ts +57 -0
  246. package/src/legacy-lib/provider-defaults.ts +114 -0
  247. package/src/legacy-lib/provider-detection.ts +67 -0
  248. package/src/legacy-lib/provider-names.ts +126 -0
  249. package/src/legacy-lib/repo-settings.ts +88 -0
  250. package/src/legacy-lib/run-mode.ts +58 -0
  251. package/src/legacy-lib/run-plan.ts +49 -0
  252. package/src/legacy-lib/settings/claude-auth.ts +80 -0
  253. package/src/legacy-lib/settings-defaults.ts +212 -0
  254. package/src/legacy-lib/settings-error.ts +8 -0
  255. package/src/legacy-lib/settings-issue-providers.ts +18 -0
  256. package/src/legacy-lib/settings-models.ts +124 -0
  257. package/src/legacy-lib/settings-storage.ts +277 -0
  258. package/src/legacy-lib/settings-validation.ts +220 -0
  259. package/src/legacy-lib/settings.ts +87 -0
  260. package/src/legacy-lib/setup-apply-types.ts +70 -0
  261. package/src/legacy-lib/setup-apply.ts +456 -0
  262. package/src/legacy-lib/setup-journal.ts +185 -0
  263. package/src/legacy-lib/setup-plan-types.ts +96 -0
  264. package/src/legacy-lib/setup-plan.ts +500 -0
  265. package/src/legacy-lib/setup-undo.ts +142 -0
  266. package/src/legacy-lib/start-cluster-config.ts +124 -0
  267. package/src/legacy-lib/start-cluster-environment.ts +165 -0
  268. package/src/legacy-lib/start-cluster-run-options.ts +279 -0
  269. package/src/legacy-lib/start-cluster.ts +237 -0
  270. package/src/legacy-lib/stream-json-parser.ts +88 -0
  271. package/src/message-buffer.js +55 -71
  272. package/src/message-buffer.ts +107 -0
  273. package/src/message-bus-bridge.js +92 -133
  274. package/src/message-bus-bridge.ts +149 -0
  275. package/src/name-generator-shared.js +220 -0
  276. package/src/name-generator-shared.ts +231 -0
  277. package/src/name-generator.js +8 -217
  278. package/src/name-generator.ts +27 -0
  279. package/src/omp-blob-root.js +83 -64
  280. package/src/omp-blob-root.ts +116 -0
  281. package/src/omp-config-overlay.js +65 -38
  282. package/src/omp-config-overlay.ts +110 -0
  283. package/src/omp-execution-fingerprint.js +38 -43
  284. package/src/omp-execution-fingerprint.ts +92 -0
  285. package/src/omp-session-limits.js +10 -15
  286. package/src/omp-session-limits.ts +53 -0
  287. package/src/omp-session-partition.js +2 -2
  288. package/src/omp-session-verifier.js +1 -1
  289. package/src/orchestrator.js +19 -4
  290. package/src/providers/anthropic/index.js +3 -1
  291. package/src/providers/anthropic/index.ts +13 -0
  292. package/src/providers/capabilities.js +19 -27
  293. package/src/providers/capabilities.ts +44 -0
  294. package/src/providers/google/index.js +3 -1
  295. package/src/providers/google/index.ts +13 -0
  296. package/src/providers/openai/index.js +3 -1
  297. package/src/providers/openai/index.ts +13 -0
  298. package/src/providers/opencode/index.js +3 -1
  299. package/src/providers/opencode/index.ts +13 -0
  300. package/src/quality-gates.js +67 -127
  301. package/src/quality-gates.ts +149 -0
  302. package/src/repo-settings-access.js +40 -0
  303. package/src/repo-settings-access.ts +64 -0
  304. package/src/schemas/sub-cluster.js +127 -183
  305. package/src/schemas/sub-cluster.ts +238 -0
  306. package/src/state-snapshot-normalization.js +224 -0
  307. package/src/state-snapshot-normalization.ts +222 -0
  308. package/src/state-snapshot.js +192 -359
  309. package/src/state-snapshot.ts +291 -0
  310. package/src/state-snapshotter.js +118 -130
  311. package/src/state-snapshotter.ts +167 -0
  312. package/src/sub-cluster-wrapper.js +4 -13
  313. package/src/task-run-model-args.js +87 -124
  314. package/src/task-run-model-args.ts +204 -0
  315. package/src/task-runner.js +5 -25
  316. package/src/task-runner.ts +28 -0
  317. package/src/task-startup-error.js +73 -58
  318. package/src/task-startup-error.ts +126 -0
  319. package/src/template-validation/consensus-gate-contracts.js +2 -0
  320. package/src/template-validation/consensus-gate-contracts.ts +70 -0
  321. package/src/template-validation/consensus-gate-scenarios.js +76 -0
  322. package/src/template-validation/consensus-gate-scenarios.ts +130 -0
  323. package/src/template-validation/consensus-gate-stage.js +34 -0
  324. package/src/template-validation/consensus-gate-stage.ts +56 -0
  325. package/src/template-validation/random-topology-contracts.js +2 -0
  326. package/src/template-validation/random-topology-contracts.ts +54 -0
  327. package/src/template-validation/random-topology-dispatch.js +25 -0
  328. package/src/template-validation/random-topology-dispatch.ts +33 -0
  329. package/src/template-validation/random-topology-message-loop.js +53 -0
  330. package/src/template-validation/random-topology-message-loop.ts +73 -0
  331. package/src/template-validation/random-topology-operations.js +79 -0
  332. package/src/template-validation/random-topology-operations.ts +101 -0
  333. package/src/template-validation/random-topology-runtime.js +25 -0
  334. package/src/template-validation/random-topology-runtime.ts +43 -0
  335. package/src/template-validation/random-topology-scenario.js +52 -0
  336. package/src/template-validation/random-topology-scenario.ts +57 -0
  337. package/src/template-validation/random-topology-schema.js +159 -0
  338. package/src/template-validation/random-topology-schema.ts +190 -0
  339. package/src/template-validation/random-topology-state.js +28 -0
  340. package/src/template-validation/random-topology-state.ts +30 -0
  341. package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
  342. package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
  343. package/src/template-validation/random-topology-trigger.js +75 -0
  344. package/src/template-validation/random-topology-trigger.ts +88 -0
  345. package/src/template-validation/report-formatter.js +40 -46
  346. package/src/template-validation/report-formatter.ts +101 -0
  347. package/src/template-validation/simulate-consensus-gates.js +111 -302
  348. package/src/template-validation/simulate-consensus-gates.ts +196 -0
  349. package/src/template-validation/simulate-random-topology.js +26 -519
  350. package/src/template-validation/simulate-random-topology.ts +57 -0
  351. package/src/template-validation/simulate-two-stage-validation.js +41 -265
  352. package/src/template-validation/simulate-two-stage-validation.ts +55 -0
  353. package/src/template-validation/simulation-agent-runtime.js +25 -0
  354. package/src/template-validation/simulation-agent-runtime.ts +40 -0
  355. package/src/template-validation/simulation-agent.js +29 -0
  356. package/src/template-validation/simulation-agent.ts +73 -0
  357. package/src/template-validation/simulation-runtime.js +34 -0
  358. package/src/template-validation/simulation-runtime.ts +72 -0
  359. package/src/template-validation/two-stage-contracts.js +2 -0
  360. package/src/template-validation/two-stage-contracts.ts +41 -0
  361. package/src/template-validation/two-stage-inputs.js +21 -0
  362. package/src/template-validation/two-stage-inputs.ts +29 -0
  363. package/src/template-validation/two-stage-results.js +30 -0
  364. package/src/template-validation/two-stage-results.ts +40 -0
  365. package/src/template-validation/two-stage-scenario.js +86 -0
  366. package/src/template-validation/two-stage-scenario.ts +120 -0
  367. package/src/worktree-claude-config.js +182 -220
  368. package/src/worktree-claude-config.ts +272 -0
  369. package/src/worktree-tooling-env.js +103 -125
  370. package/src/worktree-tooling-env.ts +142 -0
  371. package/task-lib/completion.js +2 -3
  372. package/task-lib/completion.ts +9 -0
  373. package/task-lib/config.js +1 -4
  374. package/task-lib/config.ts +11 -0
  375. package/task-lib/name-generator.js +4 -226
  376. package/task-lib/name-generator.ts +11 -0
  377. package/task-lib/omp-session-cleanup.js +1 -1
  378. package/task-lib/omp-storage-root.js +8 -12
  379. package/task-lib/omp-storage-root.ts +62 -0
  380. package/task-lib/process-termination.js +143 -166
  381. package/task-lib/process-termination.ts +268 -0
@@ -1,256 +1,188 @@
1
- /**
2
- * AgentStuckDetector - Multi-indicator process health analysis
3
- *
4
- * Detects stuck Claude processes using multiple indicators:
5
- * - Process state (S=sleeping vs R=running)
6
- * - Wait channel (ep_poll = blocked on epoll_wait)
7
- * - CPU usage over sample period
8
- * - Context switches (activity indicator)
9
- * - Network socket state (data in flight)
10
- *
11
- * CRITICAL: Single-indicator detection (just output freshness) has HIGH false positive risk.
12
- * Multi-indicator approach ONLY flags processes that fail ALL indicators.
13
- *
14
- * Scoring system:
15
- * - isSleeping: +1
16
- * - isBlockedOnPoll: +1
17
- * - lowCpuUsage: +1
18
- * - lowCtxSwitches: +1
19
- * - noDataInFlight: +0.5 (secondary signal)
20
- * - hasSynSent: +1 (stuck trying to connect)
21
- * - hasDataInFlight: -2 (active I/O = working)
22
- *
23
- * Threshold: stuckScore >= 3.5 = likely stuck
24
- */
25
-
26
- const { execSync } = require('../lib/safe-exec'); // Enforces timeouts
27
- const fs = require('fs');
28
-
29
- // Stuck detection thresholds
30
- const STUCK_THRESHOLD = 3.5; // Score at which we consider process stuck
1
+ "use strict";
2
+ const fs = require("fs");
3
+ function isSafeExecModule(value) {
4
+ return (typeof value === 'object' &&
5
+ value !== null &&
6
+ 'execSync' in value &&
7
+ typeof value.execSync === 'function');
8
+ }
9
+ function errorMessage(error) {
10
+ if (typeof error === 'object' && error !== null && 'message' in error) {
11
+ return error.message;
12
+ }
13
+ return undefined;
14
+ }
15
+ function parseProcInteger(value) {
16
+ return value === undefined ? Number.NaN : parseInt(value, 10);
17
+ }
18
+ function hasSocketFields(fields) {
19
+ return fields?.length === 5;
20
+ }
21
+ const STUCK_THRESHOLD = 3.5;
31
22
  const HIGH_CONFIDENCE_THRESHOLD = 4.5;
32
- const CPU_LOW_THRESHOLD = 1; // Percent - below this is considered "low"
33
- const CTX_SWITCHES_LOW_THRESHOLD = 10; // Below this is considered "inactive"
34
-
35
- /**
36
- * Get process state from /proc filesystem
37
- * @param {number} pid - Process ID
38
- * @returns {object} Process state info
39
- */
23
+ const CPU_LOW_THRESHOLD = 1;
24
+ const CTX_SWITCHES_LOW_THRESHOLD = 10;
25
+ function stuckScoreFor(indicators, hasDataInFlight) {
26
+ const score = Number(indicators.isSleeping) +
27
+ Number(indicators.isBlockedOnPoll) +
28
+ Number(indicators.lowCpuUsage) +
29
+ Number(indicators.lowCtxSwitches) +
30
+ Number(indicators.noDataInFlight) * 0.5 +
31
+ Number(Boolean(indicators.hasSynSent));
32
+ return hasDataInFlight ? Math.max(0, score - 2) : score;
33
+ }
34
+ function confidenceFor(stuckScore) {
35
+ if (stuckScore >= HIGH_CONFIDENCE_THRESHOLD)
36
+ return 'high';
37
+ if (stuckScore >= STUCK_THRESHOLD)
38
+ return 'medium';
39
+ return 'low';
40
+ }
41
+ const rawSafeExec = require('../lib/safe-exec');
42
+ if (!isSafeExecModule(rawSafeExec)) {
43
+ throw new TypeError('safe-exec must export execSync');
44
+ }
45
+ const { execSync } = rawSafeExec;
40
46
  function getProcessState(pid) {
41
- try {
42
- const statPath = `/proc/${pid}/stat`;
43
- if (!fs.existsSync(statPath)) {
44
- return { exists: false };
45
- }
46
-
47
- const stat = fs.readFileSync(statPath, 'utf8');
48
- const parts = stat.split(' ');
49
-
50
- // stat fields: pid, comm, state, ppid, pgrp, ...
51
- // State is the 3rd field (index 2): R=running, S=sleeping, D=disk sleep, Z=zombie
52
- const state = parts[2];
53
-
54
- // Get wchan (what the process is waiting on)
55
- let wchan = '';
56
47
  try {
57
- wchan = fs.readFileSync(`/proc/${pid}/wchan`, 'utf8').trim();
58
- } catch {
59
- // wchan may not be readable
48
+ const statPath = `/proc/${pid}/stat`;
49
+ if (!fs.existsSync(statPath)) {
50
+ return { exists: false };
51
+ }
52
+ const stat = fs.readFileSync(statPath, 'utf8');
53
+ const parts = stat.split(' ');
54
+ const state = parts[2];
55
+ let wchan = '';
56
+ try {
57
+ wchan = fs.readFileSync(`/proc/${pid}/wchan`, 'utf8').trim();
58
+ }
59
+ catch {
60
+ // wchan may not be readable.
61
+ }
62
+ const utime = parseProcInteger(parts[13]);
63
+ const stime = parseProcInteger(parts[14]);
64
+ const status = fs.readFileSync(`/proc/${pid}/status`, 'utf8');
65
+ const threads = /Threads:\s+(\d+)/.exec(status)?.[1] || '1';
66
+ const volCtxSwitches = /voluntary_ctxt_switches:\s+(\d+)/.exec(status)?.[1] || '0';
67
+ return {
68
+ exists: true,
69
+ state,
70
+ wchan,
71
+ cpuTicks: utime + stime,
72
+ threads: parseInt(threads, 10),
73
+ volCtxSwitches: parseInt(volCtxSwitches, 10),
74
+ };
75
+ }
76
+ catch (error) {
77
+ return { exists: false, error: errorMessage(error) };
60
78
  }
61
-
62
- // Get CPU usage from stat
63
- // utime (field 14) + stime (field 15) = total CPU ticks
64
- const utime = parseInt(parts[13], 10);
65
- const stime = parseInt(parts[14], 10);
66
-
67
- // Get status for more info
68
- const status = fs.readFileSync(`/proc/${pid}/status`, 'utf8');
69
- const threads = status.match(/Threads:\s+(\d+)/)?.[1] || '1';
70
- const volCtxSwitches = status.match(/voluntary_ctxt_switches:\s+(\d+)/)?.[1] || '0';
71
-
72
- return {
73
- exists: true,
74
- state,
75
- wchan,
76
- cpuTicks: utime + stime,
77
- threads: parseInt(threads, 10),
78
- volCtxSwitches: parseInt(volCtxSwitches, 10),
79
- };
80
- } catch (err) {
81
- return { exists: false, error: err.message };
82
- }
83
79
  }
84
-
85
- /**
86
- * Get network socket activity for a process
87
- * @param {number} pid - Process ID
88
- * @returns {object} Network state info
89
- */
90
80
  function getNetworkState(pid) {
91
- try {
92
- const fdPath = `/proc/${pid}/fd`;
93
- if (!fs.existsSync(fdPath)) {
94
- return { hasNetwork: false };
95
- }
96
-
97
- // Use ss to get socket states for this process
98
- let ssOutput = '';
99
81
  try {
100
- ssOutput = execSync(`ss -tunp 2>/dev/null | grep ",pid=${pid}," || true`, {
101
- encoding: 'utf8',
102
- timeout: 5000,
103
- });
104
- } catch {
105
- return { hasNetwork: false };
82
+ const fdPath = `/proc/${pid}/fd`;
83
+ if (!fs.existsSync(fdPath)) {
84
+ return { hasNetwork: false };
85
+ }
86
+ let ssOutput = '';
87
+ try {
88
+ ssOutput = execSync(`ss -tunp 2>/dev/null | grep ",pid=${pid}," || true`, {
89
+ encoding: 'utf8',
90
+ timeout: 5000,
91
+ });
92
+ }
93
+ catch {
94
+ return { hasNetwork: false };
95
+ }
96
+ if (!ssOutput.trim()) {
97
+ return { hasNetwork: false, connections: [] };
98
+ }
99
+ const connections = [];
100
+ for (const line of ssOutput.trim().split('\n')) {
101
+ const match = /^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/.exec(line);
102
+ const fields = match?.slice(1, 6);
103
+ if (!hasSocketFields(fields))
104
+ continue;
105
+ const [state, recvQ, sendQ, local, peer] = fields;
106
+ connections.push({
107
+ state,
108
+ recvQ: parseInt(recvQ, 10),
109
+ sendQ: parseInt(sendQ, 10),
110
+ local,
111
+ peer,
112
+ });
113
+ }
114
+ return {
115
+ hasNetwork: connections.length > 0,
116
+ connections,
117
+ establishedCount: connections.filter((connection) => connection.state === 'ESTAB').length,
118
+ hasDataInFlight: connections.some((connection) => connection.recvQ > 0 || connection.sendQ > 0),
119
+ hasSynSent: connections.some((connection) => connection.state === 'SYN-SENT'),
120
+ };
106
121
  }
107
-
108
- if (!ssOutput.trim()) {
109
- return { hasNetwork: false, connections: [] };
122
+ catch (error) {
123
+ return { hasNetwork: false, error: errorMessage(error) };
110
124
  }
111
-
112
- const connections = [];
113
- const lines = ssOutput.trim().split('\n');
114
-
115
- for (const line of lines) {
116
- // Parse ss output: State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
117
- const match = line.match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/);
118
- if (match) {
119
- connections.push({
120
- state: match[1],
121
- recvQ: parseInt(match[2], 10),
122
- sendQ: parseInt(match[3], 10),
123
- local: match[4],
124
- peer: match[5],
125
- });
126
- }
125
+ }
126
+ async function analyzeProcessHealth(pid, samplePeriodMs = 5000) {
127
+ const t0 = getProcessState(pid);
128
+ if (!t0.exists) {
129
+ return { isLikelyStuck: null, reason: 'Process does not exist', pid };
127
130
  }
128
-
129
- // Analyze connection health
130
- const establishedCount = connections.filter((c) => c.state === 'ESTAB').length;
131
- const hasDataInFlight = connections.some((c) => c.recvQ > 0 || c.sendQ > 0);
132
- const hasSynSent = connections.some((c) => c.state === 'SYN-SENT');
133
-
131
+ await new Promise((resolve) => setTimeout(resolve, samplePeriodMs));
132
+ const t1 = getProcessState(pid);
133
+ if (!t1.exists) {
134
+ return { isLikelyStuck: null, reason: 'Process died during analysis', pid };
135
+ }
136
+ const cpuTicksDelta = t1.cpuTicks - t0.cpuTicks;
137
+ const ctxSwitchesDelta = t1.volCtxSwitches - t0.volCtxSwitches;
138
+ const cpuSeconds = cpuTicksDelta / 100;
139
+ const sampleSeconds = samplePeriodMs / 1000;
140
+ const cpuPercent = (cpuSeconds / sampleSeconds) * 100;
141
+ const network = getNetworkState(pid);
142
+ const indicators = {
143
+ isSleeping: t1.state === 'S',
144
+ isBlockedOnPoll: t1.wchan.includes('poll') || t1.wchan.includes('wait'),
145
+ lowCpuUsage: cpuPercent < CPU_LOW_THRESHOLD,
146
+ lowCtxSwitches: ctxSwitchesDelta < CTX_SWITCHES_LOW_THRESHOLD,
147
+ noDataInFlight: network.hasNetwork && !network.hasDataInFlight,
148
+ hasSynSent: network.hasSynSent,
149
+ };
150
+ const stuckScore = stuckScoreFor(indicators, network.hasDataInFlight);
151
+ const isLikelyStuck = stuckScore >= STUCK_THRESHOLD;
152
+ const confidence = confidenceFor(stuckScore);
134
153
  return {
135
- hasNetwork: connections.length > 0,
136
- connections,
137
- establishedCount,
138
- hasDataInFlight,
139
- hasSynSent,
154
+ pid,
155
+ state: t1.state,
156
+ wchan: t1.wchan,
157
+ cpuPercent: parseFloat(cpuPercent.toFixed(2)),
158
+ ctxSwitchesDelta,
159
+ threads: t1.threads,
160
+ network: {
161
+ hasConnections: network.hasNetwork,
162
+ establishedCount: network.establishedCount || 0,
163
+ hasDataInFlight: network.hasDataInFlight || false,
164
+ hasSynSent: network.hasSynSent || false,
165
+ },
166
+ indicators,
167
+ stuckScore: parseFloat(stuckScore.toFixed(1)),
168
+ isLikelyStuck,
169
+ confidence,
170
+ analysis: isLikelyStuck
171
+ ? `Process appears STUCK: sleeping on ${t1.wchan}, ${cpuPercent.toFixed(1)}% CPU, ` +
172
+ `${ctxSwitchesDelta} ctx switches`
173
+ : `Process appears WORKING: ${cpuPercent.toFixed(1)}% CPU, ${ctxSwitchesDelta} ctx switches, state=${t1.state}`,
140
174
  };
141
- } catch (err) {
142
- return { hasNetwork: false, error: err.message };
143
- }
144
- }
145
-
146
- /**
147
- * Analyze process health using multi-indicator approach
148
- *
149
- * @param {number} pid - Process ID
150
- * @param {number} samplePeriodMs - How long to sample (default 5000ms)
151
- * @returns {Promise<object>} Analysis result with isLikelyStuck, stuckScore, indicators
152
- */
153
- async function analyzeProcessHealth(pid, samplePeriodMs = 5000) {
154
- const t0 = getProcessState(pid);
155
- if (!t0.exists) {
156
- return { isLikelyStuck: null, reason: 'Process does not exist', pid };
157
- }
158
-
159
- // Wait and sample again
160
- await new Promise((r) => setTimeout(r, samplePeriodMs));
161
-
162
- const t1 = getProcessState(pid);
163
- if (!t1.exists) {
164
- return { isLikelyStuck: null, reason: 'Process died during analysis', pid };
165
- }
166
-
167
- // Calculate CPU usage during sample period
168
- const cpuTicksDelta = t1.cpuTicks - t0.cpuTicks;
169
- const ctxSwitchesDelta = t1.volCtxSwitches - t0.volCtxSwitches;
170
-
171
- // Get clock ticks per second (typically 100 on Linux)
172
- const clockTicks = 100;
173
-
174
- // CPU seconds used during sample
175
- const cpuSeconds = cpuTicksDelta / clockTicks;
176
- const sampleSeconds = samplePeriodMs / 1000;
177
- const cpuPercent = (cpuSeconds / sampleSeconds) * 100;
178
-
179
- // Get network state
180
- const network = getNetworkState(pid);
181
-
182
- // Analyze stuck indicators
183
- const indicators = {
184
- isSleeping: t1.state === 'S',
185
- isBlockedOnPoll: t1.wchan.includes('poll') || t1.wchan.includes('wait'),
186
- lowCpuUsage: cpuPercent < CPU_LOW_THRESHOLD,
187
- lowCtxSwitches: ctxSwitchesDelta < CTX_SWITCHES_LOW_THRESHOLD,
188
- // Network indicators (only apply if process has network connections)
189
- noDataInFlight: network.hasNetwork && !network.hasDataInFlight,
190
- hasSynSent: network.hasSynSent, // Stuck trying to connect
191
- };
192
-
193
- // Calculate stuck score using weighted indicators
194
- let stuckScore = 0;
195
- if (indicators.isSleeping) stuckScore += 1;
196
- if (indicators.isBlockedOnPoll) stuckScore += 1;
197
- if (indicators.lowCpuUsage) stuckScore += 1;
198
- if (indicators.lowCtxSwitches) stuckScore += 1;
199
- if (indicators.noDataInFlight) stuckScore += 0.5; // Secondary signal
200
- if (indicators.hasSynSent) stuckScore += 1; // Strong signal - stuck connecting
201
-
202
- // CRITICAL: If data IS flowing, REDUCE stuck score (legitimate work)
203
- if (network.hasDataInFlight) {
204
- stuckScore = Math.max(0, stuckScore - 2); // Active I/O = likely working
205
- }
206
-
207
- const isLikelyStuck = stuckScore >= STUCK_THRESHOLD;
208
- let confidence = 'low';
209
- if (stuckScore >= HIGH_CONFIDENCE_THRESHOLD) {
210
- confidence = 'high';
211
- } else if (stuckScore >= STUCK_THRESHOLD) {
212
- confidence = 'medium';
213
- }
214
-
215
- return {
216
- pid,
217
- state: t1.state,
218
- wchan: t1.wchan,
219
- cpuPercent: parseFloat(cpuPercent.toFixed(2)),
220
- ctxSwitchesDelta,
221
- threads: t1.threads,
222
- network: {
223
- hasConnections: network.hasNetwork,
224
- establishedCount: network.establishedCount || 0,
225
- hasDataInFlight: network.hasDataInFlight || false,
226
- hasSynSent: network.hasSynSent || false,
227
- },
228
- indicators,
229
- stuckScore: parseFloat(stuckScore.toFixed(1)),
230
- isLikelyStuck,
231
- confidence,
232
- analysis: isLikelyStuck
233
- ? `Process appears STUCK: sleeping on ${t1.wchan}, ${cpuPercent.toFixed(1)}% CPU, ${ctxSwitchesDelta} ctx switches`
234
- : `Process appears WORKING: ${cpuPercent.toFixed(1)}% CPU, ${ctxSwitchesDelta} ctx switches, state=${t1.state}`,
235
- };
236
175
  }
237
-
238
- /**
239
- * Check if we're on a platform that supports /proc filesystem
240
- * @returns {boolean}
241
- */
242
176
  function isPlatformSupported() {
243
- return process.platform === 'linux' && fs.existsSync('/proc');
177
+ return process.platform === 'linux' && fs.existsSync('/proc');
244
178
  }
245
-
246
179
  module.exports = {
247
- analyzeProcessHealth,
248
- getProcessState,
249
- getNetworkState,
250
- isPlatformSupported,
251
- // Export thresholds for testing
252
- STUCK_THRESHOLD,
253
- HIGH_CONFIDENCE_THRESHOLD,
254
- CPU_LOW_THRESHOLD,
255
- CTX_SWITCHES_LOW_THRESHOLD,
180
+ analyzeProcessHealth,
181
+ getProcessState,
182
+ getNetworkState,
183
+ isPlatformSupported,
184
+ STUCK_THRESHOLD,
185
+ HIGH_CONFIDENCE_THRESHOLD,
186
+ CPU_LOW_THRESHOLD,
187
+ CTX_SWITCHES_LOW_THRESHOLD,
256
188
  };