@caupulican/pi-adaptative 0.81.37 → 0.81.39

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 (451) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +1 -1
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
  14. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
  15. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
  16. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
  17. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
  18. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
  19. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
  20. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
  21. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
  22. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
  23. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
  24. package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
  25. package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
  26. package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
  27. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
  28. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
  29. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
  30. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
  31. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +3 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +15 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-paths.d.ts +49 -0
  38. package/dist/core/agent-paths.d.ts.map +1 -0
  39. package/dist/core/agent-paths.js +107 -0
  40. package/dist/core/agent-paths.js.map +1 -0
  41. package/dist/core/agent-session-services.d.ts.map +1 -1
  42. package/dist/core/agent-session-services.js +3 -3
  43. package/dist/core/agent-session-services.js.map +1 -1
  44. package/dist/core/agent-session.d.ts +234 -17
  45. package/dist/core/agent-session.d.ts.map +1 -1
  46. package/dist/core/agent-session.js +484 -62
  47. package/dist/core/agent-session.js.map +1 -1
  48. package/dist/core/autonomy/contracts.d.ts +9 -0
  49. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  50. package/dist/core/autonomy/contracts.js.map +1 -1
  51. package/dist/core/autonomy/lane-tracker.d.ts +10 -1
  52. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  53. package/dist/core/autonomy/lane-tracker.js +5 -1
  54. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  55. package/dist/core/background-lane-controller.d.ts +122 -6
  56. package/dist/core/background-lane-controller.d.ts.map +1 -1
  57. package/dist/core/background-lane-controller.js +447 -90
  58. package/dist/core/background-lane-controller.js.map +1 -1
  59. package/dist/core/bash-execution-controller.d.ts +4 -0
  60. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  61. package/dist/core/bash-execution-controller.js +7 -1
  62. package/dist/core/bash-execution-controller.js.map +1 -1
  63. package/dist/core/compaction-support.d.ts +13 -3
  64. package/dist/core/compaction-support.d.ts.map +1 -1
  65. package/dist/core/compaction-support.js +43 -7
  66. package/dist/core/compaction-support.js.map +1 -1
  67. package/dist/core/context/context-audit.d.ts +33 -1
  68. package/dist/core/context/context-audit.d.ts.map +1 -1
  69. package/dist/core/context/context-audit.js +31 -5
  70. package/dist/core/context/context-audit.js.map +1 -1
  71. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  72. package/dist/core/context/sqlite-runtime-index.js +13 -7
  73. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  74. package/dist/core/context-gc.d.ts.map +1 -1
  75. package/dist/core/context-gc.js +16 -1
  76. package/dist/core/context-gc.js.map +1 -1
  77. package/dist/core/context-pipeline.d.ts +26 -4
  78. package/dist/core/context-pipeline.d.ts.map +1 -1
  79. package/dist/core/context-pipeline.js +137 -14
  80. package/dist/core/context-pipeline.js.map +1 -1
  81. package/dist/core/cost-guard.d.ts +19 -3
  82. package/dist/core/cost-guard.d.ts.map +1 -1
  83. package/dist/core/cost-guard.js +18 -3
  84. package/dist/core/cost-guard.js.map +1 -1
  85. package/dist/core/delegation/session-worker-result.d.ts +34 -4
  86. package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
  87. package/dist/core/delegation/session-worker-result.js +64 -4
  88. package/dist/core/delegation/session-worker-result.js.map +1 -1
  89. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  90. package/dist/core/delegation/worker-actions.js +1 -1
  91. package/dist/core/delegation/worker-actions.js.map +1 -1
  92. package/dist/core/delegation/worker-result.d.ts +42 -1
  93. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  94. package/dist/core/delegation/worker-result.js +68 -0
  95. package/dist/core/delegation/worker-result.js.map +1 -1
  96. package/dist/core/extensions/loader.d.ts.map +1 -1
  97. package/dist/core/extensions/loader.js +23 -7
  98. package/dist/core/extensions/loader.js.map +1 -1
  99. package/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/dist/core/extensions/runner.js +1 -0
  101. package/dist/core/extensions/runner.js.map +1 -1
  102. package/dist/core/extensions/types.d.ts +51 -0
  103. package/dist/core/extensions/types.d.ts.map +1 -1
  104. package/dist/core/extensions/types.js.map +1 -1
  105. package/dist/core/goal-loop-controller.d.ts +17 -1
  106. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  107. package/dist/core/goal-loop-controller.js +79 -11
  108. package/dist/core/goal-loop-controller.js.map +1 -1
  109. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  110. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  111. package/dist/core/goals/goal-continuation-controller.js +77 -0
  112. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  113. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  114. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  115. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  116. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  117. package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
  118. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  119. package/dist/core/goals/goal-continuation-prompt.js +51 -10
  120. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  121. package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
  122. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  123. package/dist/core/goals/goal-runtime-snapshot.js +87 -4
  124. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  125. package/dist/core/goals/goal-state.d.ts +89 -1
  126. package/dist/core/goals/goal-state.d.ts.map +1 -1
  127. package/dist/core/goals/goal-state.js +71 -5
  128. package/dist/core/goals/goal-state.js.map +1 -1
  129. package/dist/core/goals/goal-tool-core.d.ts +56 -2
  130. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  131. package/dist/core/goals/goal-tool-core.js +111 -5
  132. package/dist/core/goals/goal-tool-core.js.map +1 -1
  133. package/dist/core/goals/session-goal-state.d.ts +11 -2
  134. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  135. package/dist/core/goals/session-goal-state.js +32 -17
  136. package/dist/core/goals/session-goal-state.js.map +1 -1
  137. package/dist/core/keybindings.d.ts +10 -0
  138. package/dist/core/keybindings.d.ts.map +1 -1
  139. package/dist/core/keybindings.js +10 -2
  140. package/dist/core/keybindings.js.map +1 -1
  141. package/dist/core/learning/observation-store.d.ts +12 -3
  142. package/dist/core/learning/observation-store.d.ts.map +1 -1
  143. package/dist/core/learning/observation-store.js +30 -15
  144. package/dist/core/learning/observation-store.js.map +1 -1
  145. package/dist/core/learning/skill-curator.d.ts +5 -1
  146. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  147. package/dist/core/learning/skill-curator.js +21 -19
  148. package/dist/core/learning/skill-curator.js.map +1 -1
  149. package/dist/core/local-runtime-controller.d.ts +65 -3
  150. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  151. package/dist/core/local-runtime-controller.js +186 -26
  152. package/dist/core/local-runtime-controller.js.map +1 -1
  153. package/dist/core/memory/providers/file-store.d.ts +1 -1
  154. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  155. package/dist/core/memory/providers/file-store.js +6 -6
  156. package/dist/core/memory/providers/file-store.js.map +1 -1
  157. package/dist/core/model-capability.d.ts +34 -0
  158. package/dist/core/model-capability.d.ts.map +1 -1
  159. package/dist/core/model-capability.js +42 -1
  160. package/dist/core/model-capability.js.map +1 -1
  161. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  162. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  163. package/dist/core/model-router/tool-escalation.js +23 -1
  164. package/dist/core/model-router/tool-escalation.js.map +1 -1
  165. package/dist/core/model-router-controller.d.ts +34 -7
  166. package/dist/core/model-router-controller.d.ts.map +1 -1
  167. package/dist/core/model-router-controller.js +95 -16
  168. package/dist/core/model-router-controller.js.map +1 -1
  169. package/dist/core/models/adaptation-store.d.ts +7 -0
  170. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  171. package/dist/core/models/adaptation-store.js +47 -11
  172. package/dist/core/models/adaptation-store.js.map +1 -1
  173. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  174. package/dist/core/models/default-model-suggestions.js +17 -0
  175. package/dist/core/models/default-model-suggestions.js.map +1 -1
  176. package/dist/core/models/fitness-store.d.ts +3 -0
  177. package/dist/core/models/fitness-store.d.ts.map +1 -1
  178. package/dist/core/models/fitness-store.js +11 -2
  179. package/dist/core/models/fitness-store.js.map +1 -1
  180. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  181. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  182. package/dist/core/models/llamacpp-runtime.js +475 -0
  183. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  184. package/dist/core/models/local-registration.d.ts +40 -0
  185. package/dist/core/models/local-registration.d.ts.map +1 -1
  186. package/dist/core/models/local-registration.js +94 -5
  187. package/dist/core/models/local-registration.js.map +1 -1
  188. package/dist/core/models/local-runtime.d.ts.map +1 -1
  189. package/dist/core/models/local-runtime.js +9 -7
  190. package/dist/core/models/local-runtime.js.map +1 -1
  191. package/dist/core/models/model-ref.d.ts +7 -0
  192. package/dist/core/models/model-ref.d.ts.map +1 -1
  193. package/dist/core/models/model-ref.js +26 -0
  194. package/dist/core/models/model-ref.js.map +1 -1
  195. package/dist/core/models/needle-runtime.d.ts +257 -0
  196. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  197. package/dist/core/models/needle-runtime.js +519 -0
  198. package/dist/core/models/needle-runtime.js.map +1 -0
  199. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  200. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  201. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  202. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  203. package/dist/core/package-manager.d.ts.map +1 -1
  204. package/dist/core/package-manager.js +11 -10
  205. package/dist/core/package-manager.js.map +1 -1
  206. package/dist/core/process-matrix/codes.d.ts +72 -0
  207. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  208. package/dist/core/process-matrix/codes.js +15 -0
  209. package/dist/core/process-matrix/codes.js.map +1 -0
  210. package/dist/core/process-matrix/runtime.d.ts +63 -0
  211. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  212. package/dist/core/process-matrix/runtime.js +310 -0
  213. package/dist/core/process-matrix/runtime.js.map +1 -0
  214. package/dist/core/process-matrix/store.d.ts +22 -0
  215. package/dist/core/process-matrix/store.d.ts.map +1 -0
  216. package/dist/core/process-matrix/store.js +80 -0
  217. package/dist/core/process-matrix/store.js.map +1 -0
  218. package/dist/core/process-matrix/supervisor.d.ts +72 -0
  219. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  220. package/dist/core/process-matrix/supervisor.js +130 -0
  221. package/dist/core/process-matrix/supervisor.js.map +1 -0
  222. package/dist/core/profile-registry.d.ts +7 -0
  223. package/dist/core/profile-registry.d.ts.map +1 -1
  224. package/dist/core/profile-registry.js +20 -0
  225. package/dist/core/profile-registry.js.map +1 -1
  226. package/dist/core/python-runtime.d.ts.map +1 -1
  227. package/dist/core/python-runtime.js +3 -3
  228. package/dist/core/python-runtime.js.map +1 -1
  229. package/dist/core/reflection-controller.d.ts +29 -5
  230. package/dist/core/reflection-controller.d.ts.map +1 -1
  231. package/dist/core/reflection-controller.js +215 -126
  232. package/dist/core/reflection-controller.js.map +1 -1
  233. package/dist/core/reload-blockers.d.ts +36 -0
  234. package/dist/core/reload-blockers.d.ts.map +1 -1
  235. package/dist/core/reload-blockers.js +44 -0
  236. package/dist/core/reload-blockers.js.map +1 -1
  237. package/dist/core/resource-loader.d.ts.map +1 -1
  238. package/dist/core/resource-loader.js +8 -7
  239. package/dist/core/resource-loader.js.map +1 -1
  240. package/dist/core/runtime-builder.d.ts +58 -5
  241. package/dist/core/runtime-builder.d.ts.map +1 -1
  242. package/dist/core/runtime-builder.js +209 -25
  243. package/dist/core/runtime-builder.js.map +1 -1
  244. package/dist/core/scout-controller.d.ts +6 -0
  245. package/dist/core/scout-controller.d.ts.map +1 -1
  246. package/dist/core/scout-controller.js +66 -52
  247. package/dist/core/scout-controller.js.map +1 -1
  248. package/dist/core/sdk.d.ts.map +1 -1
  249. package/dist/core/sdk.js +3 -3
  250. package/dist/core/sdk.js.map +1 -1
  251. package/dist/core/session-role.d.ts +31 -0
  252. package/dist/core/session-role.d.ts.map +1 -0
  253. package/dist/core/session-role.js +52 -0
  254. package/dist/core/session-role.js.map +1 -0
  255. package/dist/core/settings-manager.d.ts +42 -3
  256. package/dist/core/settings-manager.d.ts.map +1 -1
  257. package/dist/core/settings-manager.js +94 -60
  258. package/dist/core/settings-manager.js.map +1 -1
  259. package/dist/core/system-prompt-builder.d.ts +12 -0
  260. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  261. package/dist/core/system-prompt-builder.js +6 -0
  262. package/dist/core/system-prompt-builder.js.map +1 -1
  263. package/dist/core/system-prompt.d.ts +14 -1
  264. package/dist/core/system-prompt.d.ts.map +1 -1
  265. package/dist/core/system-prompt.js +20 -3
  266. package/dist/core/system-prompt.js.map +1 -1
  267. package/dist/core/tasks/session-task-state.d.ts +11 -2
  268. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  269. package/dist/core/tasks/session-task-state.js +27 -11
  270. package/dist/core/tasks/session-task-state.js.map +1 -1
  271. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  272. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  273. package/dist/core/tasks/task-contract-monitor.js +56 -0
  274. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  275. package/dist/core/tasks/task-state.d.ts +8 -0
  276. package/dist/core/tasks/task-state.d.ts.map +1 -1
  277. package/dist/core/tasks/task-state.js +28 -1
  278. package/dist/core/tasks/task-state.js.map +1 -1
  279. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  280. package/dist/core/tool-gate-controller.js +5 -0
  281. package/dist/core/tool-gate-controller.js.map +1 -1
  282. package/dist/core/tool-recovery-log-records.d.ts +7 -0
  283. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  284. package/dist/core/tool-recovery-log-records.js +14 -6
  285. package/dist/core/tool-recovery-log-records.js.map +1 -1
  286. package/dist/core/tool-selection/promotion.d.ts +54 -0
  287. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  288. package/dist/core/tool-selection/promotion.js +81 -0
  289. package/dist/core/tool-selection/promotion.js.map +1 -0
  290. package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
  291. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  292. package/dist/core/tool-selection/tool-performance-store.js +87 -3
  293. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  294. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  295. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  296. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  297. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  298. package/dist/core/tools/bash.d.ts +18 -0
  299. package/dist/core/tools/bash.d.ts.map +1 -1
  300. package/dist/core/tools/bash.js +97 -18
  301. package/dist/core/tools/bash.js.map +1 -1
  302. package/dist/core/tools/delegate-status.d.ts +14 -0
  303. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  304. package/dist/core/tools/delegate-status.js +82 -7
  305. package/dist/core/tools/delegate-status.js.map +1 -1
  306. package/dist/core/tools/delegate.d.ts.map +1 -1
  307. package/dist/core/tools/delegate.js +25 -8
  308. package/dist/core/tools/delegate.js.map +1 -1
  309. package/dist/core/tools/find.d.ts.map +1 -1
  310. package/dist/core/tools/find.js +52 -44
  311. package/dist/core/tools/find.js.map +1 -1
  312. package/dist/core/tools/goal.d.ts +94 -3
  313. package/dist/core/tools/goal.d.ts.map +1 -1
  314. package/dist/core/tools/goal.js +165 -15
  315. package/dist/core/tools/goal.js.map +1 -1
  316. package/dist/core/tools/grep.d.ts.map +1 -1
  317. package/dist/core/tools/grep.js +5 -4
  318. package/dist/core/tools/grep.js.map +1 -1
  319. package/dist/core/tools/model-fitness.d.ts +7 -0
  320. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  321. package/dist/core/tools/model-fitness.js +2 -2
  322. package/dist/core/tools/model-fitness.js.map +1 -1
  323. package/dist/core/tools/render-utils.d.ts.map +1 -1
  324. package/dist/core/tools/render-utils.js +1 -1
  325. package/dist/core/tools/render-utils.js.map +1 -1
  326. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  327. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  328. package/dist/core/tools/shell-contract-router.js +69 -13
  329. package/dist/core/tools/shell-contract-router.js.map +1 -1
  330. package/dist/core/tools/shell-session.d.ts +89 -0
  331. package/dist/core/tools/shell-session.d.ts.map +1 -0
  332. package/dist/core/tools/shell-session.js +432 -0
  333. package/dist/core/tools/shell-session.js.map +1 -0
  334. package/dist/core/tools/task-steps.d.ts +4 -0
  335. package/dist/core/tools/task-steps.d.ts.map +1 -1
  336. package/dist/core/tools/task-steps.js +63 -8
  337. package/dist/core/tools/task-steps.js.map +1 -1
  338. package/dist/core/tools/tmux-dispatch.d.ts +86 -0
  339. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  340. package/dist/core/tools/tmux-dispatch.js +91 -0
  341. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  342. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  343. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  344. package/dist/core/tools/windows-shell-engine.js +153 -0
  345. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  346. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  347. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  348. package/dist/core/tools/windows-shell-state.js +59 -0
  349. package/dist/core/tools/windows-shell-state.js.map +1 -0
  350. package/dist/core/tools/worktree-sync.d.ts +24 -0
  351. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  352. package/dist/core/tools/worktree-sync.js +338 -0
  353. package/dist/core/tools/worktree-sync.js.map +1 -0
  354. package/dist/core/trust-manager.d.ts +4 -1
  355. package/dist/core/trust-manager.d.ts.map +1 -1
  356. package/dist/core/trust-manager.js +20 -2
  357. package/dist/core/trust-manager.js.map +1 -1
  358. package/dist/core/util/atomic-file.d.ts +55 -0
  359. package/dist/core/util/atomic-file.d.ts.map +1 -0
  360. package/dist/core/util/atomic-file.js +255 -0
  361. package/dist/core/util/atomic-file.js.map +1 -0
  362. package/dist/core/util/minimatch-cache.d.ts +33 -0
  363. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  364. package/dist/core/util/minimatch-cache.js +0 -0
  365. package/dist/core/util/minimatch-cache.js.map +1 -0
  366. package/dist/core/worktree-sync/codes.d.ts +227 -0
  367. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  368. package/dist/core/worktree-sync/codes.js +14 -0
  369. package/dist/core/worktree-sync/codes.js.map +1 -0
  370. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  371. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  372. package/dist/core/worktree-sync/git-engine.js +1191 -0
  373. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  374. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  375. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  376. package/dist/core/worktree-sync/lane-gate.js +360 -0
  377. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  378. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  379. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  380. package/dist/core/worktree-sync/runtime.js +96 -0
  381. package/dist/core/worktree-sync/runtime.js.map +1 -0
  382. package/dist/core/worktree-sync/store.d.ts +69 -0
  383. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  384. package/dist/core/worktree-sync/store.js +247 -0
  385. package/dist/core/worktree-sync/store.js.map +1 -0
  386. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  387. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  388. package/dist/core/worktree-sync/watcher.js +93 -0
  389. package/dist/core/worktree-sync/watcher.js.map +1 -0
  390. package/dist/main.d.ts.map +1 -1
  391. package/dist/main.js +94 -0
  392. package/dist/main.js.map +1 -1
  393. package/dist/migrations.d.ts +9 -0
  394. package/dist/migrations.d.ts.map +1 -1
  395. package/dist/migrations.js +38 -0
  396. package/dist/migrations.js.map +1 -1
  397. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  398. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  399. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  400. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  401. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  402. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  403. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  404. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  405. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  406. package/dist/modes/interactive/interactive-mode.js +4 -0
  407. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  408. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  409. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  410. package/dist/modes/interactive/local-model-commands.js +290 -3
  411. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  412. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  413. package/dist/modes/interactive/session-flow-commands.js +24 -4
  414. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  415. package/dist/utils/fs-watch.d.ts +11 -0
  416. package/dist/utils/fs-watch.d.ts.map +1 -1
  417. package/dist/utils/fs-watch.js +20 -2
  418. package/dist/utils/fs-watch.js.map +1 -1
  419. package/dist/utils/highlight-js-languages.d.ts +4 -0
  420. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  421. package/dist/utils/highlight-js-languages.js +573 -0
  422. package/dist/utils/highlight-js-languages.js.map +1 -0
  423. package/dist/utils/shell.d.ts +7 -1
  424. package/dist/utils/shell.d.ts.map +1 -1
  425. package/dist/utils/shell.js +39 -9
  426. package/dist/utils/shell.js.map +1 -1
  427. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  428. package/dist/utils/syntax-highlight.js +53 -5
  429. package/dist/utils/syntax-highlight.js.map +1 -1
  430. package/dist/utils/tools-manager.d.ts.map +1 -1
  431. package/dist/utils/tools-manager.js +112 -1
  432. package/dist/utils/tools-manager.js.map +1 -1
  433. package/docs/development.md +2 -0
  434. package/docs/packages.md +1 -1
  435. package/docs/process-matrix.md +120 -0
  436. package/docs/settings.md +5 -2
  437. package/docs/tmux-agent-manager.md +85 -2
  438. package/docs/windows.md +52 -3
  439. package/docs/work-directory.md +29 -0
  440. package/docs/worktree-sync.md +250 -0
  441. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  442. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  443. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  444. package/examples/extensions/sandbox/package-lock.json +2 -2
  445. package/examples/extensions/sandbox/package.json +1 -1
  446. package/examples/extensions/with-deps/package-lock.json +2 -2
  447. package/examples/extensions/with-deps/package.json +1 -1
  448. package/npm-shrinkwrap.json +12 -12
  449. package/package.json +10 -4
  450. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  451. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -0,0 +1,734 @@
1
+ """Executes the frozen AST: lists, pipelines, redirections, subshells, dispatch.
2
+
3
+ Owned by WP-C. Reaches expansion/builtins ONLY through `ExecContext` (never imports
4
+ `expand` or `commands`). See windows-shell-workpackages-2026-07-19.md §3.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import io
10
+ import os
11
+ import subprocess
12
+ import threading
13
+ from typing import BinaryIO
14
+
15
+ import nodes
16
+ import parser as parser_module
17
+ import proc
18
+ import tokens as tokens_module
19
+ from context import RUNNER_BUILTINS, STATE_BUILTINS, BuiltinContext, ExecContext
20
+ from errors import UnsupportedConstruct
21
+ from state import ShellState
22
+
23
+ DEVNULL = os.devnull
24
+
25
+
26
+ class _Redirected:
27
+ """Tracks open file handles opened for a command's redirects, closed after run."""
28
+
29
+ def __init__(self) -> None:
30
+ self.handles: list[BinaryIO] = []
31
+
32
+ def open(self, path: str, mode: str) -> BinaryIO:
33
+ handle = open(path, mode) # noqa: SIM115 - closed explicitly in close()
34
+ self.handles.append(handle)
35
+ return handle
36
+
37
+ def close(self) -> None:
38
+ for handle in self.handles:
39
+ try:
40
+ handle.close()
41
+ except OSError:
42
+ pass
43
+
44
+
45
+ def _redirect_target_path(redirect: nodes.Redirect, ctx: ExecContext) -> str:
46
+ if redirect.target is None:
47
+ raise UnsupportedConstruct("malformed-syntax", "redirect missing a target")
48
+ parts = ctx.expand_word(redirect.target, ctx)
49
+ if not parts:
50
+ raise UnsupportedConstruct("malformed-syntax", "redirect target expanded to nothing")
51
+ if len(parts) != 1:
52
+ raise UnsupportedConstruct("malformed-syntax", "redirect target expanded to multiple words (ambiguous redirect)")
53
+ path = parts[0]
54
+ if path == "/dev/null":
55
+ path = DEVNULL
56
+ if not os.path.isabs(path):
57
+ path = os.path.join(ctx.state.cwd, path)
58
+ return path
59
+
60
+
61
+ def _apply_redirects(
62
+ redirects: list[nodes.Redirect],
63
+ ctx: ExecContext,
64
+ stdin_stream: BinaryIO | int,
65
+ stdout_stream: BinaryIO | int,
66
+ stderr_stream: BinaryIO | int,
67
+ tracker: _Redirected,
68
+ ) -> tuple[BinaryIO | int, BinaryIO | int, BinaryIO | int]:
69
+ """Return the (stdin, stdout, stderr) streams after applying `redirects` in order."""
70
+ cur_in, cur_out, cur_err = stdin_stream, stdout_stream, stderr_stream
71
+ for redirect in redirects:
72
+ op = redirect.op
73
+ if op in (">", "1>"):
74
+ cur_out = tracker.open(_redirect_target_path(redirect, ctx), "wb")
75
+ elif op in (">>", "1>>"):
76
+ cur_out = tracker.open(_redirect_target_path(redirect, ctx), "ab")
77
+ elif op == "<":
78
+ cur_in = tracker.open(_redirect_target_path(redirect, ctx), "rb")
79
+ elif op == "2>":
80
+ cur_err = tracker.open(_redirect_target_path(redirect, ctx), "wb")
81
+ elif op == "2>>":
82
+ cur_err = tracker.open(_redirect_target_path(redirect, ctx), "ab")
83
+ elif op in ("2>&1",):
84
+ cur_err = cur_out
85
+ elif op in ("&>", ">&"):
86
+ target = tracker.open(_redirect_target_path(redirect, ctx), "wb")
87
+ cur_out = target
88
+ cur_err = target
89
+ elif op == "1>&2":
90
+ cur_out = cur_err
91
+ else:
92
+ raise UnsupportedConstruct("malformed-syntax", f"unsupported redirect operator {op!r}")
93
+ return cur_in, cur_out, cur_err
94
+
95
+
96
+ class _ChildStream:
97
+ """An OS-level fd usable by a spawned child, plus optional pipe-bridge cleanup.
98
+
99
+ `stream_or_fd` handed to us may already be a real fd (a redirected file, or the
100
+ process's own stdin/stdout) — in that case we hand it straight through. It may also
101
+ be a plain in-memory `BinaryIO` (e.g. the merged-sink `io.BytesIO` used by `main.py`
102
+ and command substitution) which has no fd a child process can write/read directly;
103
+ for that case we bridge through a real `os.pipe()` with a background thread copying
104
+ bytes to/from the memory stream, so external children work identically either way.
105
+ """
106
+
107
+ def __init__(self, fd: int, owns_fd: bool, join: object | None = None) -> None:
108
+ self.fd = fd
109
+ self.owns_fd = owns_fd
110
+ self.join = join
111
+
112
+
113
+ def _real_fd(stream: BinaryIO | int) -> int | None:
114
+ if isinstance(stream, int):
115
+ return stream
116
+ try:
117
+ return stream.fileno()
118
+ except (io.UnsupportedOperation, AttributeError, ValueError, OSError):
119
+ return None
120
+
121
+
122
+ def _prepare_child_output(stream: BinaryIO | int) -> _ChildStream:
123
+ fd = _real_fd(stream)
124
+ if fd is not None:
125
+ return _ChildStream(fd, owns_fd=False)
126
+ assert not isinstance(stream, int)
127
+ read_fd, write_fd = os.pipe()
128
+
129
+ def pump() -> None:
130
+ with os.fdopen(read_fd, "rb", closefd=True) as reader:
131
+ while True:
132
+ chunk = reader.read(65536)
133
+ if not chunk:
134
+ break
135
+ stream.write(chunk)
136
+
137
+ thread = threading.Thread(target=pump, daemon=True)
138
+ thread.start()
139
+ return _ChildStream(write_fd, owns_fd=True, join=thread.join)
140
+
141
+
142
+ def _prepare_child_input(stream: BinaryIO | int) -> _ChildStream:
143
+ fd = _real_fd(stream)
144
+ if fd is not None:
145
+ return _ChildStream(fd, owns_fd=False)
146
+ assert not isinstance(stream, int)
147
+ data = stream.read()
148
+ read_fd, write_fd = os.pipe()
149
+
150
+ def feed() -> None:
151
+ with os.fdopen(write_fd, "wb", closefd=True) as writer:
152
+ writer.write(data)
153
+
154
+ thread = threading.Thread(target=feed, daemon=True)
155
+ thread.start()
156
+ return _ChildStream(read_fd, owns_fd=True, join=thread.join)
157
+
158
+
159
+ def _spawn_with_bridges(
160
+ argv: list[str],
161
+ state: ShellState,
162
+ stdin_stream: BinaryIO | int,
163
+ stdout_stream: BinaryIO | int,
164
+ stderr_stream: BinaryIO | int,
165
+ deadline: float | None,
166
+ ) -> tuple["subprocess.Popen[bytes]", _ChildStream, _ChildStream, _ChildStream]:
167
+ """Spawn `argv`, bridging any non-fd streams through a pipe.
168
+
169
+ Returns `(child, in_prep, out_prep, err_prep)`; the caller must wait on `child` and
170
+ then call `_finish_bridges(in_prep, out_prep, err_prep)` so any bridge thread has
171
+ fully drained/fed the memory stream before the caller reads its contents.
172
+ """
173
+ in_prep = _prepare_child_input(stdin_stream)
174
+ out_prep = _prepare_child_output(stdout_stream)
175
+ err_prep = out_prep if stderr_stream is stdout_stream else _prepare_child_output(stderr_stream)
176
+ try:
177
+ child = proc.spawn_external(argv, state, in_prep.fd, out_prep.fd, err_prep.fd, deadline)
178
+ finally:
179
+ if in_prep.owns_fd:
180
+ _safe_close(in_prep.fd)
181
+ if out_prep.owns_fd:
182
+ _safe_close(out_prep.fd)
183
+ if err_prep is not out_prep and err_prep.owns_fd:
184
+ _safe_close(err_prep.fd)
185
+ return child, in_prep, out_prep, err_prep
186
+
187
+
188
+ def _finish_bridges(*preps: _ChildStream) -> None:
189
+ for prep in preps:
190
+ if prep.join is not None:
191
+ prep.join()
192
+
193
+
194
+ def _merged_sink(ctx: ExecContext) -> BinaryIO | int:
195
+ """The session merged sink for un-redirected stderr: `ctx.stderr` if set, else `ctx.stdout`."""
196
+ return ctx.stderr if ctx.stderr is not None else ctx.stdout
197
+
198
+
199
+ def _write_merged(stream: BinaryIO | int, data: bytes, ctx: ExecContext) -> None:
200
+ if isinstance(stream, int):
201
+ os.write(stream, data)
202
+ else:
203
+ stream.write(data)
204
+ stream.flush()
205
+
206
+
207
+ def execute(list_node: nodes.CommandList, ctx: ExecContext) -> int:
208
+ """Run a `CommandList`; returns the exit code of the last executed entry."""
209
+ exit_code = 0
210
+ for entry in list_node.entries:
211
+ exit_code = _execute_andor(entry, ctx)
212
+ return exit_code
213
+
214
+
215
+ def _execute_andor(andor: nodes.AndOr, ctx: ExecContext) -> int:
216
+ exit_code = 0
217
+ for i, pipeline in enumerate(andor.pipelines):
218
+ if i > 0:
219
+ operator = andor.operators[i - 1]
220
+ if operator == "&&" and exit_code != 0:
221
+ continue
222
+ if operator == "||" and exit_code == 0:
223
+ continue
224
+ exit_code = _execute_pipeline(pipeline, ctx)
225
+ return exit_code
226
+
227
+
228
+ def _execute_pipeline(pipeline: nodes.Pipeline, ctx: ExecContext) -> int:
229
+ elements = pipeline.elements
230
+ exit_code = _run_pipeline_elements(elements, ctx)
231
+ if pipeline.negated:
232
+ exit_code = 0 if exit_code != 0 else 1
233
+ return exit_code
234
+
235
+
236
+ def _run_pipeline_elements(elements: list, ctx: ExecContext) -> int:
237
+ stderr_base = _merged_sink(ctx)
238
+ if len(elements) == 1:
239
+ return _dispatch_element(elements[0], ctx, ctx.stdin, ctx.stdout, stderr_base)
240
+
241
+ n = len(elements)
242
+ read_ends: list[int] = []
243
+ write_ends: list[int] = []
244
+ for _ in range(n - 1):
245
+ r, w = os.pipe()
246
+ read_ends.append(r)
247
+ write_ends.append(w)
248
+
249
+ # Every fd number actually closed so far. `os.pipe()` recycles fd numbers, so a
250
+ # bridge pipe created later in this same loop (for a non-fd stdout/stdin stream)
251
+ # may reuse a number we already closed; guard every close through this set so a
252
+ # live, recycled fd is never closed a second time.
253
+ closed_fds: set[int] = set()
254
+
255
+ def close_once(fd: int) -> None:
256
+ if fd not in closed_fds:
257
+ closed_fds.add(fd)
258
+ _safe_close(fd)
259
+
260
+ results: list[int] = [0] * n
261
+ refusals: list[UnsupportedConstruct | None] = [None] * n
262
+
263
+ # External-process stages spawn as real OS processes (concurrent by construction).
264
+ # Builtin/state stages run in-process; each runs on its own thread so a stage that
265
+ # writes more than a pipe's kernel buffer cannot deadlock waiting for a downstream
266
+ # reader that hasn't started yet (real bash pipes are all concurrent).
267
+ procs: list[subprocess.Popen | None] = [None] * n
268
+ proc_preps: list[tuple[_ChildStream, _ChildStream, _ChildStream] | None] = [None] * n
269
+ threads: list[threading.Thread | None] = [None] * n
270
+
271
+ for index in range(n):
272
+ stdin_stream = ctx.stdin if index == 0 else read_ends[index - 1]
273
+ stdout_stream = ctx.stdout if index == n - 1 else write_ends[index]
274
+ element = elements[index]
275
+ is_write_end_owned_here = index < n - 1
276
+
277
+ if isinstance(element, nodes.SimpleCommand) and _is_external_dispatch(element, ctx):
278
+ spawned = _spawn_external_stage(element, ctx, stdin_stream, stdout_stream, stderr_base)
279
+ if spawned is None:
280
+ # Command-not-found in a pipeline stage: report it, record 127 for this
281
+ # stage, and close the stage's owned write end so downstream sees EOF —
282
+ # never let this crash the engine or stall the rest of the pipeline.
283
+ results[index] = 127
284
+ else:
285
+ child, preps = spawned
286
+ procs[index] = child
287
+ proc_preps[index] = preps
288
+ # Parent's real-pipe copies were dup'd into the child by fork/exec (or were
289
+ # never used, on the not-found path); close ours so downstream sees EOF
290
+ # once the (now independent) child process exits or the stage is skipped.
291
+ if is_write_end_owned_here:
292
+ close_once(write_ends[index])
293
+ if index > 0:
294
+ close_once(read_ends[index - 1])
295
+ else:
296
+ # Inline (in-process) stage: the thread reads/writes these SAME fds directly
297
+ # (no fork/dup), so the main loop must not close them out from under it.
298
+
299
+ def run_inline(idx=index, sin=stdin_stream, sout=stdout_stream, owns_write=is_write_end_owned_here) -> None:
300
+ try:
301
+ # Pipeline stages are subshell-like: state mutations belong to the stage,
302
+ # never to the parent command context or a sibling stage.
303
+ stage_ctx = _sub_ctx(ctx, ctx.state.copy(), sin, sout, stderr_base)
304
+ results[idx] = _dispatch_element(elements[idx], stage_ctx, sin, sout, stderr_base, is_pipeline=True)
305
+ except UnsupportedConstruct as exc:
306
+ # A structured refusal from an inline stage means the WHOLE command
307
+ # is outside the frozen grammar/behavior contract — captured here and
308
+ # re-raised on the main thread after all stages join (first one wins),
309
+ # so the caller sees the normal structured-refusal frame, never a
310
+ # fabricated exit-0/empty-output success.
311
+ refusals[idx] = exc
312
+ results[idx] = 1
313
+ except BaseException as exc: # noqa: BLE001 - any other stage failure must not crash the engine
314
+ # Any OTHER exception is a real per-stage failure, not a refusal: it
315
+ # gets a named, actionable one-line message on the merged sink (never
316
+ # a Python traceback) and this stage records exit 1.
317
+ message = f"{_stage_label(elements[idx])}: {exc}\n"
318
+ _write_merged(stderr_base, message.encode("utf-8", errors="replace"), ctx)
319
+ results[idx] = 1
320
+ finally:
321
+ if owns_write:
322
+ close_once(sout)
323
+
324
+ thread = threading.Thread(target=run_inline, daemon=True)
325
+ threads[index] = thread
326
+ thread.start()
327
+
328
+ for thread in threads:
329
+ if thread is not None:
330
+ thread.join()
331
+ for index, proc_obj in enumerate(procs):
332
+ if proc_obj is not None:
333
+ results[index] = proc.wait_with_deadline(proc_obj, ctx.deadline)
334
+ preps = proc_preps[index]
335
+ if preps is not None:
336
+ _finish_bridges(*preps)
337
+
338
+ # Defensive cleanup: any pipe fd an inline stage held onto (rather than an external
339
+ # stage's already-closed dup) is now unreachable and can be closed — but never a
340
+ # number already closed and potentially recycled by a bridge pipe above.
341
+ for fd in (*read_ends, *write_ends):
342
+ close_once(fd)
343
+
344
+ for refusal in refusals:
345
+ if refusal is not None:
346
+ # First refusal wins; re-raise on the main thread (after every stage has
347
+ # joined/waited and every fd is cleaned up) so `execute()`'s caller sees the
348
+ # normal structured-refusal frame for the whole command — never a fabricated
349
+ # exit-0/empty-output success.
350
+ raise refusal
351
+
352
+ return results[-1]
353
+
354
+
355
+ def _stage_label(element) -> str:
356
+ """A short, human-actionable name for a pipeline stage used in error messages.
357
+
358
+ Uses the unexpanded command word text when it is a plain `Lit`/`Raw` segment (no
359
+ expansion needed to read it); falls back to the node's type name otherwise.
360
+ """
361
+ if isinstance(element, nodes.SimpleCommand) and element.words:
362
+ segments = element.words[0].segments
363
+ if len(segments) == 1 and isinstance(segments[0], (nodes.Lit, nodes.Raw)):
364
+ return segments[0].text
365
+ return type(element).__name__
366
+
367
+
368
+ def _safe_close(fd_or_stream) -> None:
369
+ if isinstance(fd_or_stream, int):
370
+ try:
371
+ os.close(fd_or_stream)
372
+ except OSError:
373
+ pass
374
+
375
+
376
+ def _is_external_dispatch(command: nodes.SimpleCommand, ctx: ExecContext) -> bool:
377
+ if not command.words:
378
+ return False
379
+ expanded = ctx.expand_word(command.words[0], ctx)
380
+ if not expanded:
381
+ return False
382
+ name = expanded[0]
383
+ if name in STATE_BUILTINS or name in RUNNER_BUILTINS or name in ctx.builtins:
384
+ return False
385
+ return True
386
+
387
+
388
+ def _spawn_external_stage(
389
+ command: nodes.SimpleCommand,
390
+ ctx: ExecContext,
391
+ stdin_stream: BinaryIO | int,
392
+ stdout_stream: BinaryIO | int,
393
+ stderr_base: BinaryIO | int,
394
+ ) -> tuple[subprocess.Popen, tuple[_ChildStream, _ChildStream, _ChildStream]] | None:
395
+ """Spawn one external pipeline stage, applying ITS OWN redirects first.
396
+
397
+ Un-redirected stdout stays wired to the pipe; un-redirected stderr routes to
398
+ `stderr_base` (the session merged sink), never into the pipe. Returns `None` (after
399
+ reporting "command not found" to `stderr_base`) instead of raising, so a bad stage
400
+ name cannot crash the whole pipeline — the caller records exit 127 for this stage.
401
+ """
402
+ tracker = _Redirected()
403
+ try:
404
+ r_in, r_out, r_err = _apply_redirects(command.redirects, ctx, stdin_stream, stdout_stream, stderr_base, tracker)
405
+ argv = _expand_argv(command, ctx)
406
+ env = _apply_transient_assignments(command, ctx)
407
+ scratch_state = ShellState(cwd=ctx.state.cwd, env=env)
408
+ try:
409
+ child, in_prep, out_prep, err_prep = _spawn_with_bridges(
410
+ argv, scratch_state, r_in, r_out, r_err, ctx.deadline
411
+ )
412
+ except FileNotFoundError:
413
+ message = f"{argv[0]}: command not found\n"
414
+ _write_merged(stderr_base, message.encode("utf-8", errors="replace"), ctx)
415
+ return None
416
+ return child, (in_prep, out_prep, err_prep)
417
+ finally:
418
+ tracker.close()
419
+
420
+
421
+ def _expand_argv(command: nodes.SimpleCommand, ctx: ExecContext) -> list[str]:
422
+ argv: list[str] = []
423
+ for word in command.words:
424
+ argv.extend(ctx.expand_word(word, ctx))
425
+ return argv
426
+
427
+
428
+ def _apply_transient_assignments(command: nodes.SimpleCommand, ctx: ExecContext) -> dict[str, str]:
429
+ env = ctx.state.env.copy()
430
+ for name, word in command.assignments:
431
+ values = ctx.expand_word(word, ctx)
432
+ env[name] = "".join(values) if values else ""
433
+ return env
434
+
435
+
436
+ def _dispatch_element(
437
+ element,
438
+ ctx: ExecContext,
439
+ stdin_stream: BinaryIO | int,
440
+ stdout_stream: BinaryIO | int,
441
+ stderr_stream: BinaryIO | int,
442
+ is_pipeline: bool = False,
443
+ ) -> int:
444
+ if isinstance(element, nodes.Subshell):
445
+ return _execute_subshell(element, ctx, stdin_stream, stdout_stream, stderr_stream)
446
+ if isinstance(element, nodes.BraceGroup):
447
+ return _execute_brace_group(element, ctx, stdin_stream, stdout_stream, stderr_stream)
448
+ if isinstance(element, nodes.SimpleCommand):
449
+ return _dispatch_simple_command(element, ctx, stdin_stream, stdout_stream, stderr_stream)
450
+ raise UnsupportedConstruct("malformed-syntax", f"unrecognized pipeline element {type(element)!r}")
451
+
452
+
453
+ def _sub_ctx(
454
+ ctx: ExecContext,
455
+ state: ShellState,
456
+ stdin_stream: BinaryIO | int,
457
+ stdout_stream: BinaryIO | int,
458
+ stderr_stream: BinaryIO | int,
459
+ ) -> ExecContext:
460
+ return ExecContext(
461
+ state=state,
462
+ stdin=stdin_stream,
463
+ stdout=stdout_stream,
464
+ expand_word=ctx.expand_word,
465
+ run_command_substitution=ctx.run_command_substitution,
466
+ builtins=ctx.builtins,
467
+ deadline=ctx.deadline,
468
+ stderr=stderr_stream,
469
+ )
470
+
471
+
472
+ def _execute_subshell(node: nodes.Subshell, ctx: ExecContext, stdin_stream, stdout_stream, stderr_stream) -> int:
473
+ tracker = _Redirected()
474
+ try:
475
+ r_in, r_out, r_err = _apply_redirects(
476
+ node.redirects, ctx, stdin_stream, stdout_stream, stderr_stream, tracker
477
+ )
478
+ isolated_state = ctx.state.copy()
479
+ inner_ctx = _sub_ctx(ctx, isolated_state, r_in, r_out, r_err)
480
+ return execute(node.body, inner_ctx)
481
+ finally:
482
+ tracker.close()
483
+
484
+
485
+ def _execute_brace_group(node: nodes.BraceGroup, ctx: ExecContext, stdin_stream, stdout_stream, stderr_stream) -> int:
486
+ tracker = _Redirected()
487
+ try:
488
+ r_in, r_out, r_err = _apply_redirects(
489
+ node.redirects, ctx, stdin_stream, stdout_stream, stderr_stream, tracker
490
+ )
491
+ inner_ctx = _sub_ctx(ctx, ctx.state, r_in, r_out, r_err)
492
+ return execute(node.body, inner_ctx)
493
+ finally:
494
+ tracker.close()
495
+
496
+
497
+ def _dispatch_simple_command(
498
+ command: nodes.SimpleCommand,
499
+ ctx: ExecContext,
500
+ stdin_stream: BinaryIO | int,
501
+ stdout_stream: BinaryIO | int,
502
+ stderr_stream: BinaryIO | int,
503
+ ) -> int:
504
+ tracker = _Redirected()
505
+ try:
506
+ r_in, r_out, r_err = _apply_redirects(
507
+ command.redirects, ctx, stdin_stream, stdout_stream, stderr_stream, tracker
508
+ )
509
+
510
+ if not command.words:
511
+ # Pure assignment(s), no command word: mutate ctx.state.
512
+ for name, word in command.assignments:
513
+ values = ctx.expand_word(word, ctx)
514
+ ctx.state.setenv(name, "".join(values) if values else "")
515
+ return 0
516
+
517
+ argv = _expand_argv(command, ctx)
518
+ if not argv:
519
+ # All words expanded to zero argv strings (e.g. an unset/empty `$VAR` alone):
520
+ # bash runs no command here. Redirects/assignments above still applied; exit 0.
521
+ return 0
522
+ name = argv[0]
523
+
524
+ if name in STATE_BUILTINS:
525
+ return _run_state_builtin(name, argv, ctx, r_out)
526
+
527
+ if name in RUNNER_BUILTINS:
528
+ return _run_xargs(argv, ctx, r_in, r_out)
529
+
530
+ if name in ctx.builtins:
531
+ env = _apply_transient_assignments(command, ctx)
532
+ builtin_ctx = BuiltinContext(
533
+ argv=argv,
534
+ cwd=ctx.state.cwd,
535
+ env=env,
536
+ stdin=_as_stream(r_in, "rb"),
537
+ stdout=_as_stream(r_out, "wb"),
538
+ stderr=_as_stream(r_err, "wb"),
539
+ )
540
+ return ctx.builtins[name](builtin_ctx)
541
+
542
+ # External command.
543
+ env = _apply_transient_assignments(command, ctx)
544
+ scratch_state = ShellState(cwd=ctx.state.cwd, env=env)
545
+ try:
546
+ child, in_prep, out_prep, err_prep = _spawn_with_bridges(argv, scratch_state, r_in, r_out, r_err, ctx.deadline)
547
+ except FileNotFoundError:
548
+ message = f"{name}: command not found\n"
549
+ _write_merged(r_err, message.encode("utf-8", errors="replace"), ctx)
550
+ return 127
551
+ exit_code = proc.wait_with_deadline(child, ctx.deadline)
552
+ _finish_bridges(in_prep, out_prep, err_prep)
553
+ return exit_code
554
+ finally:
555
+ tracker.close()
556
+
557
+
558
+ def _as_stream(fd_or_stream: BinaryIO | int, mode: str) -> BinaryIO:
559
+ """Wrap `fd_or_stream` as a `BinaryIO` opened in the CALLER-KNOWN `mode`.
560
+
561
+ A pipeline stage's stdin/stdout fd is an arbitrary kernel fd number picked by
562
+ `os.pipe()` — it is never reliably 0 or 1 — so the read/write intent must come from
563
+ the call site (which knows whether this is stdin or stdout), never be guessed from
564
+ the fd's numeric value.
565
+ """
566
+ if isinstance(fd_or_stream, int):
567
+ return os.fdopen(fd_or_stream, mode, closefd=False)
568
+ return fd_or_stream
569
+
570
+
571
+ def _run_state_builtin(name: str, argv: list[str], ctx: ExecContext, out_stream: BinaryIO | int) -> int:
572
+ if name == "cd":
573
+ target = argv[1] if len(argv) > 1 else ctx.state.env.get("HOME")
574
+ print_new_cwd = argv[1:2] == ["-"]
575
+ if print_new_cwd:
576
+ oldpwd = ctx.state.env.get("OLDPWD")
577
+ if not oldpwd:
578
+ _write_merged(out_stream, b"cd: OLDPWD not set\n", ctx)
579
+ return 1
580
+ target = oldpwd
581
+ if target is None:
582
+ _write_merged(out_stream, b"cd: HOME not set\n", ctx)
583
+ return 1
584
+ try:
585
+ ctx.state.chdir(target)
586
+ except FileNotFoundError:
587
+ _write_merged(out_stream, f"cd: {target}: No such file or directory\n".encode("utf-8"), ctx)
588
+ return 1
589
+ if print_new_cwd:
590
+ _write_merged(out_stream, f"{ctx.state.cwd}\n".encode("utf-8"), ctx)
591
+ return 0
592
+
593
+ if name == "export":
594
+ for item in argv[1:]:
595
+ if "=" in item:
596
+ key, _, value = item.partition("=")
597
+ ctx.state.setenv(key, value)
598
+ else:
599
+ ctx.state.env.setdefault(item, "")
600
+ return 0
601
+
602
+ if name == "unset":
603
+ for item in argv[1:]:
604
+ ctx.state.unsetenv(item)
605
+ return 0
606
+
607
+ raise UnsupportedConstruct("unsupported-builtin", f"unsupported state builtin {name!r}")
608
+
609
+
610
+ def _run_xargs(argv: list[str], ctx: ExecContext, in_stream: BinaryIO | int, out_stream: BinaryIO | int) -> int:
611
+ args = argv[1:]
612
+ null_sep = False
613
+ n_count: int | None = None
614
+ replace_marker: str | None = None
615
+ i = 0
616
+ while i < len(args):
617
+ a = args[i]
618
+ if a == "-0":
619
+ null_sep = True
620
+ i += 1
621
+ elif a == "-n":
622
+ i += 1
623
+ if i >= len(args):
624
+ raise UnsupportedConstruct("unsupported-flag", "xargs -n requires an argument")
625
+ n_count = int(args[i])
626
+ i += 1
627
+ elif a.startswith("-n") and len(a) > 2 and a[2:].isdigit():
628
+ n_count = int(a[2:])
629
+ i += 1
630
+ elif a == "-I":
631
+ i += 1
632
+ if i >= len(args):
633
+ raise UnsupportedConstruct("unsupported-flag", "xargs -I requires an argument")
634
+ replace_marker = args[i]
635
+ i += 1
636
+ elif a.startswith("-I") and len(a) > 2:
637
+ replace_marker = a[2:]
638
+ i += 1
639
+ elif a.startswith("-") and a not in ("-",):
640
+ raise UnsupportedConstruct("unsupported-flag", f"xargs: unsupported flag {a!r}")
641
+ else:
642
+ break
643
+ cmd_argv = args[i:]
644
+ if not cmd_argv:
645
+ raise UnsupportedConstruct("malformed-syntax", "xargs requires a command")
646
+
647
+ data = _read_all(in_stream)
648
+ if null_sep:
649
+ raw_tokens = [t for t in data.split(b"\x00") if t != b""]
650
+ items = [t.decode("utf-8", errors="replace") for t in raw_tokens]
651
+ else:
652
+ items = data.decode("utf-8", errors="replace").split()
653
+
654
+ exit_code = 0
655
+ if replace_marker is not None:
656
+ for item in items:
657
+ # GNU xargs -I replaces every OCCURRENCE of the marker inside each argument
658
+ # (a substring replace), not only args that are exactly the marker token.
659
+ batch_argv = [piece.replace(replace_marker, item) for piece in cmd_argv]
660
+ exit_code = _run_xargs_batch(batch_argv, ctx, out_stream)
661
+ return exit_code
662
+
663
+ batch_size = n_count if n_count is not None else len(items) if items else 1
664
+ if batch_size <= 0:
665
+ batch_size = 1
666
+ if not items:
667
+ return 0
668
+ for start in range(0, len(items), batch_size):
669
+ batch = items[start : start + batch_size]
670
+ exit_code = _run_xargs_batch([*cmd_argv, *batch], ctx, out_stream)
671
+ return exit_code
672
+
673
+
674
+ def _run_xargs_batch(argv: list[str], ctx: ExecContext, out_stream: BinaryIO | int) -> int:
675
+ name = argv[0]
676
+ if name in ctx.builtins:
677
+ builtin_ctx = BuiltinContext(
678
+ argv=argv,
679
+ cwd=ctx.state.cwd,
680
+ env=ctx.state.env.copy(),
681
+ stdin=_empty_stream(),
682
+ stdout=_as_stream(out_stream, "wb"),
683
+ stderr=_as_stream(out_stream, "wb"),
684
+ )
685
+ return ctx.builtins[name](builtin_ctx)
686
+ if name in STATE_BUILTINS:
687
+ return _run_state_builtin(name, argv, ctx, out_stream)
688
+ scratch_state = ShellState(cwd=ctx.state.cwd, env=ctx.state.env.copy())
689
+ try:
690
+ child, in_prep, out_prep, err_prep = _spawn_with_bridges(
691
+ argv, scratch_state, subprocess.DEVNULL, out_stream, out_stream, ctx.deadline
692
+ )
693
+ except FileNotFoundError:
694
+ _write_merged(out_stream, f"{name}: command not found\n".encode("utf-8"), ctx)
695
+ return 127
696
+ exit_code = proc.wait_with_deadline(child, ctx.deadline)
697
+ _finish_bridges(in_prep, out_prep, err_prep)
698
+ return exit_code
699
+
700
+
701
+ def _empty_stream() -> BinaryIO:
702
+ return io.BytesIO(b"")
703
+
704
+
705
+ def _read_all(stream: BinaryIO | int) -> bytes:
706
+ if isinstance(stream, int):
707
+ chunks = []
708
+ while True:
709
+ chunk = os.read(stream, 65536)
710
+ if not chunk:
711
+ break
712
+ chunks.append(chunk)
713
+ return b"".join(chunks)
714
+ return stream.read()
715
+
716
+
717
+ def run_command_substitution(src: str, ctx: ExecContext) -> tuple[str, int]:
718
+ """Tokenize+parse+execute `src` with stdout captured; returns (stripped_stdout, exit_code)."""
719
+ tokens = tokens_module.tokenize(src)
720
+ ast = parser_module.parse(tokens)
721
+ buffer = io.BytesIO()
722
+ sub_ctx = ExecContext(
723
+ state=ctx.state.copy(),
724
+ stdin=ctx.stdin,
725
+ stdout=buffer,
726
+ expand_word=ctx.expand_word,
727
+ run_command_substitution=ctx.run_command_substitution,
728
+ builtins=ctx.builtins,
729
+ deadline=ctx.deadline,
730
+ stderr=ctx.stderr,
731
+ )
732
+ exit_code = execute(ast, sub_ctx)
733
+ text = buffer.getvalue().decode("utf-8", errors="replace")
734
+ return text.rstrip("\n"), exit_code