@kruntime/komputer 0.1.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 (353) hide show
  1. package/README.md +368 -0
  2. package/dist/agent/approvals.d.ts +28 -0
  3. package/dist/agent/approvals.js +109 -0
  4. package/dist/agent/approvals.js.map +1 -0
  5. package/dist/agent/messages.d.ts +67 -0
  6. package/dist/agent/messages.js +213 -0
  7. package/dist/agent/messages.js.map +1 -0
  8. package/dist/agent/recovery.d.ts +13 -0
  9. package/dist/agent/recovery.js +112 -0
  10. package/dist/agent/recovery.js.map +1 -0
  11. package/dist/agent/session-utils.d.ts +17 -0
  12. package/dist/agent/session-utils.js +77 -0
  13. package/dist/agent/session-utils.js.map +1 -0
  14. package/dist/agent-session.d.ts +303 -0
  15. package/dist/agent-session.js +3031 -0
  16. package/dist/agent-session.js.map +1 -0
  17. package/dist/authority.d.ts +34 -0
  18. package/dist/authority.js +105 -0
  19. package/dist/authority.js.map +1 -0
  20. package/dist/content.d.ts +17 -0
  21. package/dist/content.js +69 -0
  22. package/dist/content.js.map +1 -0
  23. package/dist/cron.d.ts +20 -0
  24. package/dist/cron.js +25 -0
  25. package/dist/cron.js.map +1 -0
  26. package/dist/drive.d.ts +198 -0
  27. package/dist/drive.js +656 -0
  28. package/dist/drive.js.map +1 -0
  29. package/dist/events.d.ts +20 -0
  30. package/dist/events.js +149 -0
  31. package/dist/events.js.map +1 -0
  32. package/dist/fs-mode.d.ts +9 -0
  33. package/dist/fs-mode.js +98 -0
  34. package/dist/fs-mode.js.map +1 -0
  35. package/dist/index.d.ts +26 -0
  36. package/dist/index.js +15 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/komputer.d.ts +123 -0
  39. package/dist/komputer.js +733 -0
  40. package/dist/komputer.js.map +1 -0
  41. package/dist/kstate.d.ts +107 -0
  42. package/dist/kstate.js +529 -0
  43. package/dist/kstate.js.map +1 -0
  44. package/dist/model.d.ts +81 -0
  45. package/dist/model.js +824 -0
  46. package/dist/model.js.map +1 -0
  47. package/dist/node.d.ts +2 -0
  48. package/dist/node.js +3 -0
  49. package/dist/node.js.map +1 -0
  50. package/dist/path.d.ts +4 -0
  51. package/dist/path.js +38 -0
  52. package/dist/path.js.map +1 -0
  53. package/dist/pool.d.ts +56 -0
  54. package/dist/pool.js +172 -0
  55. package/dist/pool.js.map +1 -0
  56. package/dist/receipts.d.ts +44 -0
  57. package/dist/receipts.js +35 -0
  58. package/dist/receipts.js.map +1 -0
  59. package/dist/remote-node.d.ts +2 -0
  60. package/dist/remote-node.js +122 -0
  61. package/dist/remote-node.js.map +1 -0
  62. package/dist/remote.d.ts +84 -0
  63. package/dist/remote.js +11 -0
  64. package/dist/remote.js.map +1 -0
  65. package/dist/sandbox.d.ts +4 -0
  66. package/dist/sandbox.js +70 -0
  67. package/dist/sandbox.js.map +1 -0
  68. package/dist/shell/builtins/catalog.d.ts +6 -0
  69. package/dist/shell/builtins/catalog.js +12 -0
  70. package/dist/shell/builtins/catalog.js.map +1 -0
  71. package/dist/shell/builtins/commands/filesystem/basename.d.ts +2 -0
  72. package/dist/shell/builtins/commands/filesystem/basename.js +23 -0
  73. package/dist/shell/builtins/commands/filesystem/basename.js.map +1 -0
  74. package/dist/shell/builtins/commands/filesystem/cat.d.ts +2 -0
  75. package/dist/shell/builtins/commands/filesystem/cat.js +85 -0
  76. package/dist/shell/builtins/commands/filesystem/cat.js.map +1 -0
  77. package/dist/shell/builtins/commands/filesystem/cd.d.ts +2 -0
  78. package/dist/shell/builtins/commands/filesystem/cd.js +14 -0
  79. package/dist/shell/builtins/commands/filesystem/cd.js.map +1 -0
  80. package/dist/shell/builtins/commands/filesystem/chmod.d.ts +2 -0
  81. package/dist/shell/builtins/commands/filesystem/chmod.js +46 -0
  82. package/dist/shell/builtins/commands/filesystem/chmod.js.map +1 -0
  83. package/dist/shell/builtins/commands/filesystem/copy-directory.d.ts +4 -0
  84. package/dist/shell/builtins/commands/filesystem/copy-directory.js +48 -0
  85. package/dist/shell/builtins/commands/filesystem/copy-directory.js.map +1 -0
  86. package/dist/shell/builtins/commands/filesystem/cp.d.ts +2 -0
  87. package/dist/shell/builtins/commands/filesystem/cp.js +92 -0
  88. package/dist/shell/builtins/commands/filesystem/cp.js.map +1 -0
  89. package/dist/shell/builtins/commands/filesystem/dirname.d.ts +2 -0
  90. package/dist/shell/builtins/commands/filesystem/dirname.js +23 -0
  91. package/dist/shell/builtins/commands/filesystem/dirname.js.map +1 -0
  92. package/dist/shell/builtins/commands/filesystem/du.d.ts +2 -0
  93. package/dist/shell/builtins/commands/filesystem/du.js +93 -0
  94. package/dist/shell/builtins/commands/filesystem/du.js.map +1 -0
  95. package/dist/shell/builtins/commands/filesystem/find.d.ts +2 -0
  96. package/dist/shell/builtins/commands/filesystem/find.js +111 -0
  97. package/dist/shell/builtins/commands/filesystem/find.js.map +1 -0
  98. package/dist/shell/builtins/commands/filesystem/install.d.ts +2 -0
  99. package/dist/shell/builtins/commands/filesystem/install.js +89 -0
  100. package/dist/shell/builtins/commands/filesystem/install.js.map +1 -0
  101. package/dist/shell/builtins/commands/filesystem/ln.d.ts +2 -0
  102. package/dist/shell/builtins/commands/filesystem/ln.js +57 -0
  103. package/dist/shell/builtins/commands/filesystem/ln.js.map +1 -0
  104. package/dist/shell/builtins/commands/filesystem/ls.d.ts +2 -0
  105. package/dist/shell/builtins/commands/filesystem/ls.js +153 -0
  106. package/dist/shell/builtins/commands/filesystem/ls.js.map +1 -0
  107. package/dist/shell/builtins/commands/filesystem/mkdir.d.ts +2 -0
  108. package/dist/shell/builtins/commands/filesystem/mkdir.js +41 -0
  109. package/dist/shell/builtins/commands/filesystem/mkdir.js.map +1 -0
  110. package/dist/shell/builtins/commands/filesystem/mktemp.d.ts +2 -0
  111. package/dist/shell/builtins/commands/filesystem/mktemp.js +94 -0
  112. package/dist/shell/builtins/commands/filesystem/mktemp.js.map +1 -0
  113. package/dist/shell/builtins/commands/filesystem/mv.d.ts +2 -0
  114. package/dist/shell/builtins/commands/filesystem/mv.js +41 -0
  115. package/dist/shell/builtins/commands/filesystem/mv.js.map +1 -0
  116. package/dist/shell/builtins/commands/filesystem/pwd.d.ts +2 -0
  117. package/dist/shell/builtins/commands/filesystem/pwd.js +2 -0
  118. package/dist/shell/builtins/commands/filesystem/pwd.js.map +1 -0
  119. package/dist/shell/builtins/commands/filesystem/realpath.d.ts +3 -0
  120. package/dist/shell/builtins/commands/filesystem/realpath.js +61 -0
  121. package/dist/shell/builtins/commands/filesystem/realpath.js.map +1 -0
  122. package/dist/shell/builtins/commands/filesystem/rm.d.ts +2 -0
  123. package/dist/shell/builtins/commands/filesystem/rm.js +38 -0
  124. package/dist/shell/builtins/commands/filesystem/rm.js.map +1 -0
  125. package/dist/shell/builtins/commands/filesystem/stat.d.ts +2 -0
  126. package/dist/shell/builtins/commands/filesystem/stat.js +141 -0
  127. package/dist/shell/builtins/commands/filesystem/stat.js.map +1 -0
  128. package/dist/shell/builtins/commands/filesystem/touch.d.ts +2 -0
  129. package/dist/shell/builtins/commands/filesystem/touch.js +143 -0
  130. package/dist/shell/builtins/commands/filesystem/touch.js.map +1 -0
  131. package/dist/shell/builtins/commands/filesystem.d.ts +2 -0
  132. package/dist/shell/builtins/commands/filesystem.js +44 -0
  133. package/dist/shell/builtins/commands/filesystem.js.map +1 -0
  134. package/dist/shell/builtins/commands/network/curl.d.ts +2 -0
  135. package/dist/shell/builtins/commands/network/curl.js +25 -0
  136. package/dist/shell/builtins/commands/network/curl.js.map +1 -0
  137. package/dist/shell/builtins/commands/network/http.d.ts +27 -0
  138. package/dist/shell/builtins/commands/network/http.js +226 -0
  139. package/dist/shell/builtins/commands/network/http.js.map +1 -0
  140. package/dist/shell/builtins/commands/network/wget.d.ts +2 -0
  141. package/dist/shell/builtins/commands/network/wget.js +32 -0
  142. package/dist/shell/builtins/commands/network/wget.js.map +1 -0
  143. package/dist/shell/builtins/commands/network.d.ts +2 -0
  144. package/dist/shell/builtins/commands/network.js +9 -0
  145. package/dist/shell/builtins/commands/network.js.map +1 -0
  146. package/dist/shell/builtins/commands/process/bg.d.ts +2 -0
  147. package/dist/shell/builtins/commands/process/bg.js +10 -0
  148. package/dist/shell/builtins/commands/process/bg.js.map +1 -0
  149. package/dist/shell/builtins/commands/process/fg.d.ts +2 -0
  150. package/dist/shell/builtins/commands/process/fg.js +14 -0
  151. package/dist/shell/builtins/commands/process/fg.js.map +1 -0
  152. package/dist/shell/builtins/commands/process/jobs.d.ts +2 -0
  153. package/dist/shell/builtins/commands/process/jobs.js +8 -0
  154. package/dist/shell/builtins/commands/process/jobs.js.map +1 -0
  155. package/dist/shell/builtins/commands/process/jobspec.d.ts +4 -0
  156. package/dist/shell/builtins/commands/process/jobspec.js +20 -0
  157. package/dist/shell/builtins/commands/process/jobspec.js.map +1 -0
  158. package/dist/shell/builtins/commands/process/kill.d.ts +2 -0
  159. package/dist/shell/builtins/commands/process/kill.js +35 -0
  160. package/dist/shell/builtins/commands/process/kill.js.map +1 -0
  161. package/dist/shell/builtins/commands/process/ps.d.ts +2 -0
  162. package/dist/shell/builtins/commands/process/ps.js +6 -0
  163. package/dist/shell/builtins/commands/process/ps.js.map +1 -0
  164. package/dist/shell/builtins/commands/process/sleep.d.ts +2 -0
  165. package/dist/shell/builtins/commands/process/sleep.js +6 -0
  166. package/dist/shell/builtins/commands/process/sleep.js.map +1 -0
  167. package/dist/shell/builtins/commands/process/wait.d.ts +2 -0
  168. package/dist/shell/builtins/commands/process/wait.js +21 -0
  169. package/dist/shell/builtins/commands/process/wait.js.map +1 -0
  170. package/dist/shell/builtins/commands/process.d.ts +2 -0
  171. package/dist/shell/builtins/commands/process.js +19 -0
  172. package/dist/shell/builtins/commands/process.js.map +1 -0
  173. package/dist/shell/builtins/commands/system/command.d.ts +2 -0
  174. package/dist/shell/builtins/commands/system/command.js +42 -0
  175. package/dist/shell/builtins/commands/system/command.js.map +1 -0
  176. package/dist/shell/builtins/commands/system/date.d.ts +2 -0
  177. package/dist/shell/builtins/commands/system/date.js +121 -0
  178. package/dist/shell/builtins/commands/system/date.js.map +1 -0
  179. package/dist/shell/builtins/commands/system/env.d.ts +2 -0
  180. package/dist/shell/builtins/commands/system/env.js +84 -0
  181. package/dist/shell/builtins/commands/system/env.js.map +1 -0
  182. package/dist/shell/builtins/commands/system/export.d.ts +2 -0
  183. package/dist/shell/builtins/commands/system/export.js +18 -0
  184. package/dist/shell/builtins/commands/system/export.js.map +1 -0
  185. package/dist/shell/builtins/commands/system/false.d.ts +2 -0
  186. package/dist/shell/builtins/commands/system/false.js +4 -0
  187. package/dist/shell/builtins/commands/system/false.js.map +1 -0
  188. package/dist/shell/builtins/commands/system/help.d.ts +2 -0
  189. package/dist/shell/builtins/commands/system/help.js +38 -0
  190. package/dist/shell/builtins/commands/system/help.js.map +1 -0
  191. package/dist/shell/builtins/commands/system/hostname.d.ts +2 -0
  192. package/dist/shell/builtins/commands/system/hostname.js +6 -0
  193. package/dist/shell/builtins/commands/system/hostname.js.map +1 -0
  194. package/dist/shell/builtins/commands/system/id.d.ts +2 -0
  195. package/dist/shell/builtins/commands/system/id.js +55 -0
  196. package/dist/shell/builtins/commands/system/id.js.map +1 -0
  197. package/dist/shell/builtins/commands/system/printenv.d.ts +2 -0
  198. package/dist/shell/builtins/commands/system/printenv.js +22 -0
  199. package/dist/shell/builtins/commands/system/printenv.js.map +1 -0
  200. package/dist/shell/builtins/commands/system/read.d.ts +2 -0
  201. package/dist/shell/builtins/commands/system/read.js +75 -0
  202. package/dist/shell/builtins/commands/system/read.js.map +1 -0
  203. package/dist/shell/builtins/commands/system/resolve-command.d.ts +3 -0
  204. package/dist/shell/builtins/commands/system/resolve-command.js +34 -0
  205. package/dist/shell/builtins/commands/system/resolve-command.js.map +1 -0
  206. package/dist/shell/builtins/commands/system/set.d.ts +2 -0
  207. package/dist/shell/builtins/commands/system/set.js +49 -0
  208. package/dist/shell/builtins/commands/system/set.js.map +1 -0
  209. package/dist/shell/builtins/commands/system/sh.d.ts +2 -0
  210. package/dist/shell/builtins/commands/system/sh.js +55 -0
  211. package/dist/shell/builtins/commands/system/sh.js.map +1 -0
  212. package/dist/shell/builtins/commands/system/source.d.ts +2 -0
  213. package/dist/shell/builtins/commands/system/source.js +42 -0
  214. package/dist/shell/builtins/commands/system/source.js.map +1 -0
  215. package/dist/shell/builtins/commands/system/test-expression.d.ts +2 -0
  216. package/dist/shell/builtins/commands/system/test-expression.js +93 -0
  217. package/dist/shell/builtins/commands/system/test-expression.js.map +1 -0
  218. package/dist/shell/builtins/commands/system/test.d.ts +4 -0
  219. package/dist/shell/builtins/commands/system/test.js +19 -0
  220. package/dist/shell/builtins/commands/system/test.js.map +1 -0
  221. package/dist/shell/builtins/commands/system/true.d.ts +2 -0
  222. package/dist/shell/builtins/commands/system/true.js +2 -0
  223. package/dist/shell/builtins/commands/system/true.js.map +1 -0
  224. package/dist/shell/builtins/commands/system/type.d.ts +2 -0
  225. package/dist/shell/builtins/commands/system/type.js +77 -0
  226. package/dist/shell/builtins/commands/system/type.js.map +1 -0
  227. package/dist/shell/builtins/commands/system/uname.d.ts +2 -0
  228. package/dist/shell/builtins/commands/system/uname.js +39 -0
  229. package/dist/shell/builtins/commands/system/uname.js.map +1 -0
  230. package/dist/shell/builtins/commands/system/unset.d.ts +2 -0
  231. package/dist/shell/builtins/commands/system/unset.js +9 -0
  232. package/dist/shell/builtins/commands/system/unset.js.map +1 -0
  233. package/dist/shell/builtins/commands/system/which.d.ts +2 -0
  234. package/dist/shell/builtins/commands/system/which.js +38 -0
  235. package/dist/shell/builtins/commands/system/which.js.map +1 -0
  236. package/dist/shell/builtins/commands/system/whoami.d.ts +2 -0
  237. package/dist/shell/builtins/commands/system/whoami.js +6 -0
  238. package/dist/shell/builtins/commands/system/whoami.js.map +1 -0
  239. package/dist/shell/builtins/commands/system.d.ts +2 -0
  240. package/dist/shell/builtins/commands/system.js +49 -0
  241. package/dist/shell/builtins/commands/system.js.map +1 -0
  242. package/dist/shell/builtins/commands/text/awk.d.ts +2 -0
  243. package/dist/shell/builtins/commands/text/awk.js +8 -0
  244. package/dist/shell/builtins/commands/text/awk.js.map +1 -0
  245. package/dist/shell/builtins/commands/text/base64.d.ts +2 -0
  246. package/dist/shell/builtins/commands/text/base64.js +69 -0
  247. package/dist/shell/builtins/commands/text/base64.js.map +1 -0
  248. package/dist/shell/builtins/commands/text/cut.d.ts +2 -0
  249. package/dist/shell/builtins/commands/text/cut.js +35 -0
  250. package/dist/shell/builtins/commands/text/cut.js.map +1 -0
  251. package/dist/shell/builtins/commands/text/diff.d.ts +2 -0
  252. package/dist/shell/builtins/commands/text/diff.js +83 -0
  253. package/dist/shell/builtins/commands/text/diff.js.map +1 -0
  254. package/dist/shell/builtins/commands/text/echo.d.ts +2 -0
  255. package/dist/shell/builtins/commands/text/echo.js +6 -0
  256. package/dist/shell/builtins/commands/text/echo.js.map +1 -0
  257. package/dist/shell/builtins/commands/text/grep.d.ts +2 -0
  258. package/dist/shell/builtins/commands/text/grep.js +123 -0
  259. package/dist/shell/builtins/commands/text/grep.js.map +1 -0
  260. package/dist/shell/builtins/commands/text/head.d.ts +2 -0
  261. package/dist/shell/builtins/commands/text/head.js +8 -0
  262. package/dist/shell/builtins/commands/text/head.js.map +1 -0
  263. package/dist/shell/builtins/commands/text/jq.d.ts +2 -0
  264. package/dist/shell/builtins/commands/text/jq.js +239 -0
  265. package/dist/shell/builtins/commands/text/jq.js.map +1 -0
  266. package/dist/shell/builtins/commands/text/printf.d.ts +2 -0
  267. package/dist/shell/builtins/commands/text/printf.js +3 -0
  268. package/dist/shell/builtins/commands/text/printf.js.map +1 -0
  269. package/dist/shell/builtins/commands/text/sed.d.ts +2 -0
  270. package/dist/shell/builtins/commands/text/sed.js +21 -0
  271. package/dist/shell/builtins/commands/text/sed.js.map +1 -0
  272. package/dist/shell/builtins/commands/text/sha256sum.d.ts +2 -0
  273. package/dist/shell/builtins/commands/text/sha256sum.js +63 -0
  274. package/dist/shell/builtins/commands/text/sha256sum.js.map +1 -0
  275. package/dist/shell/builtins/commands/text/sort.d.ts +2 -0
  276. package/dist/shell/builtins/commands/text/sort.js +134 -0
  277. package/dist/shell/builtins/commands/text/sort.js.map +1 -0
  278. package/dist/shell/builtins/commands/text/tail.d.ts +2 -0
  279. package/dist/shell/builtins/commands/text/tail.js +10 -0
  280. package/dist/shell/builtins/commands/text/tail.js.map +1 -0
  281. package/dist/shell/builtins/commands/text/tee.d.ts +2 -0
  282. package/dist/shell/builtins/commands/text/tee.js +29 -0
  283. package/dist/shell/builtins/commands/text/tee.js.map +1 -0
  284. package/dist/shell/builtins/commands/text/tr.d.ts +2 -0
  285. package/dist/shell/builtins/commands/text/tr.js +3 -0
  286. package/dist/shell/builtins/commands/text/tr.js.map +1 -0
  287. package/dist/shell/builtins/commands/text/uniq.d.ts +2 -0
  288. package/dist/shell/builtins/commands/text/uniq.js +8 -0
  289. package/dist/shell/builtins/commands/text/uniq.js.map +1 -0
  290. package/dist/shell/builtins/commands/text/wc.d.ts +2 -0
  291. package/dist/shell/builtins/commands/text/wc.js +30 -0
  292. package/dist/shell/builtins/commands/text/wc.js.map +1 -0
  293. package/dist/shell/builtins/commands/text/xargs.d.ts +2 -0
  294. package/dist/shell/builtins/commands/text/xargs.js +138 -0
  295. package/dist/shell/builtins/commands/text/xargs.js.map +1 -0
  296. package/dist/shell/builtins/commands/text.d.ts +2 -0
  297. package/dist/shell/builtins/commands/text.js +41 -0
  298. package/dist/shell/builtins/commands/text.js.map +1 -0
  299. package/dist/shell/builtins/help-text.d.ts +69 -0
  300. package/dist/shell/builtins/help-text.js +381 -0
  301. package/dist/shell/builtins/help-text.js.map +1 -0
  302. package/dist/shell/builtins/path.d.ts +28 -0
  303. package/dist/shell/builtins/path.js +184 -0
  304. package/dist/shell/builtins/path.js.map +1 -0
  305. package/dist/shell/builtins/registry.d.ts +3 -0
  306. package/dist/shell/builtins/registry.js +75 -0
  307. package/dist/shell/builtins/registry.js.map +1 -0
  308. package/dist/shell/builtins/run.d.ts +4 -0
  309. package/dist/shell/builtins/run.js +29 -0
  310. package/dist/shell/builtins/run.js.map +1 -0
  311. package/dist/shell/builtins/runtime.d.ts +83 -0
  312. package/dist/shell/builtins/runtime.js +46 -0
  313. package/dist/shell/builtins/runtime.js.map +1 -0
  314. package/dist/shell/builtins/text.d.ts +90 -0
  315. package/dist/shell/builtins/text.js +970 -0
  316. package/dist/shell/builtins/text.js.map +1 -0
  317. package/dist/shell/control.d.ts +31 -0
  318. package/dist/shell/control.js +232 -0
  319. package/dist/shell/control.js.map +1 -0
  320. package/dist/shell/expansion.d.ts +30 -0
  321. package/dist/shell/expansion.js +514 -0
  322. package/dist/shell/expansion.js.map +1 -0
  323. package/dist/shell/functions.d.ts +10 -0
  324. package/dist/shell/functions.js +108 -0
  325. package/dist/shell/functions.js.map +1 -0
  326. package/dist/shell/runtime.d.ts +44 -0
  327. package/dist/shell/runtime.js +276 -0
  328. package/dist/shell/runtime.js.map +1 -0
  329. package/dist/shell/time.d.ts +1 -0
  330. package/dist/shell/time.js +68 -0
  331. package/dist/shell/time.js.map +1 -0
  332. package/dist/state-backend.d.ts +61 -0
  333. package/dist/state-backend.js +275 -0
  334. package/dist/state-backend.js.map +1 -0
  335. package/dist/state-node.d.ts +2 -0
  336. package/dist/state-node.js +266 -0
  337. package/dist/state-node.js.map +1 -0
  338. package/dist/types.d.ts +462 -0
  339. package/dist/types.js +2 -0
  340. package/dist/types.js.map +1 -0
  341. package/dist/utils.d.ts +5 -0
  342. package/dist/utils.js +117 -0
  343. package/dist/utils.js.map +1 -0
  344. package/dist/vbash.d.ts +40 -0
  345. package/dist/vbash.js +389 -0
  346. package/dist/vbash.js.map +1 -0
  347. package/dist/vfs.d.ts +64 -0
  348. package/dist/vfs.js +248 -0
  349. package/dist/vfs.js.map +1 -0
  350. package/dist/vprocess.d.ts +139 -0
  351. package/dist/vprocess.js +756 -0
  352. package/dist/vprocess.js.map +1 -0
  353. package/package.json +28 -0
@@ -0,0 +1,3031 @@
1
+ import { blocksToText, normalizeInput } from "./content.js";
2
+ import { assertRuntimeEvent, EventLog } from "./events.js";
3
+ import { KFileSystem } from "./vfs.js";
4
+ import { VProcessTable } from "./vprocess.js";
5
+ import { overlayMountTable } from "./drive.js";
6
+ import { finishReceipt, makeReceipt } from "./receipts.js";
7
+ import { normalizeKPath } from "./kstate.js";
8
+ import { dirnamePath } from "./path.js";
9
+ import { isExecutable } from "./fs-mode.js";
10
+ import { id, normalizeBy, now, sleep } from "./utils.js";
11
+ import { isGovernableShellScript, isSimpleShellCommand, parseShellCommandSegments, rewriteKPathsForRemote } from "./vbash.js";
12
+ import { decideCommand, decideFs, decideModel, decideNetwork, decideTool, normalizeMode } from "./authority.js";
13
+ import { K_BUILTINS } from "./shell/builtins/registry.js";
14
+ import { builtinErrorText, runBuiltin } from "./shell/builtins/run.js";
15
+ import { globMatcher } from "./shell/builtins/path.js";
16
+ import { commandHelpText, commandResultToShellResult, isShellAssignmentToken, isShellIdentifier, parseShellRedirects, quoteShellWord, recordStringMap, shellArgsToInput, shellExitCode, shellWordToken } from "./shell/runtime.js";
17
+ import { expandBraceFields, ShellExpander } from "./shell/expansion.js";
18
+ import { parseShellControlProgram } from "./shell/control.js";
19
+ import { parseShellFunctionProgram } from "./shell/functions.js";
20
+ import { hardenShellSandboxRequest, validateShellSandboxRequest } from "./sandbox.js";
21
+ import { SessionMessages } from "./agent/messages.js";
22
+ import { SessionApprovals, normalizeApprovalDecision } from "./agent/approvals.js";
23
+ import { sealInterruptedTurns, sealUnfinishedToolEvents, sealUnfinishedTools } from "./agent/recovery.js";
24
+ import { abortedShellResult, defaultShellOptions, executableScriptBody, isModelResult, networkHostFromUrl, normalizeAssistantMessage, normalizeShellOptions, positionalShellEnv, recordField, resultType, signalReason, splitShellFields, stringArrayField, stringField, } from "./agent/session-utils.js";
25
+ export { isFinalTurnEvent } from "./agent/session-utils.js";
26
+ const MAX_K_LOCAL_CONTROL_ITERATIONS = 1000;
27
+ const MAX_SHELL_CAPTURE_CHARS = 64 * 1024;
28
+ const MAX_SHELL_OUTPUT_EVENT_CHARS = 16 * 1024;
29
+ const SHELL_OUTPUT_EVENT_PREVIEW_CHARS = 4 * 1024;
30
+ export class AgentSession {
31
+ runtime;
32
+ computer;
33
+ kstate;
34
+ agentName;
35
+ sessionId;
36
+ modeState;
37
+ mode;
38
+ disk;
39
+ definition;
40
+ home;
41
+ modelAlias;
42
+ system;
43
+ eventLog;
44
+ handlers;
45
+ hooks;
46
+ shellEnv;
47
+ shellEnvBase;
48
+ shellFunctions;
49
+ shellOptions;
50
+ messageLog;
51
+ approvalLog;
52
+ cwd;
53
+ fs;
54
+ localComputer;
55
+ vprocess;
56
+ shellExpander;
57
+ turnStopDepth;
58
+ maxTurnStopContinuations;
59
+ functionDepth;
60
+ sourceDepth;
61
+ fsActorStack;
62
+ lastExitCode;
63
+ closed;
64
+ initResult;
65
+ lease;
66
+ leaseHeartbeat;
67
+ constructor({ computer, agentName, sessionId, mode = 'normal', disk = 'shared' }) {
68
+ this.runtime = computer;
69
+ this.kstate = computer.kstate;
70
+ this.agentName = agentName;
71
+ this.sessionId = sessionId;
72
+ this.disk = disk;
73
+ this.definition = computer.image.agents?.[agentName] || {};
74
+ this.home = this.definition.home || `/home/${agentName}`;
75
+ this.modeState = this.normalizeModeForAgent(mode);
76
+ this.mode = this.modeState.name;
77
+ this.modelAlias = this.definition.model || 'default';
78
+ this.system = '';
79
+ this.eventLog = new EventLog(this.kstate, sessionId, agentName);
80
+ this.handlers = new Map();
81
+ this.hooks = new Map();
82
+ this.shellEnv = {};
83
+ this.shellEnvBase = undefined;
84
+ this.shellFunctions = {};
85
+ this.shellOptions = defaultShellOptions();
86
+ this.messageLog = new SessionMessages({
87
+ kstate: this.kstate,
88
+ sessionId,
89
+ isClosed: () => this.closed,
90
+ system: () => this.system,
91
+ });
92
+ this.approvalLog = new SessionApprovals(this.kstate, sessionId);
93
+ this.cwd = `${this.home}/work`;
94
+ this.fs = new KFileSystem(this.kstate, {
95
+ home: this.home,
96
+ sessionId,
97
+ mounts: disk === 'overlay' ? overlayMountTable(computer.kstate, sessionId, computer.mounts) : computer.mounts,
98
+ trace: (type, data) => this.traceFs(type, data),
99
+ by: () => this.currentFsActor(),
100
+ authorize: (request) => this.authorizeFs(request),
101
+ });
102
+ this.localComputer = {
103
+ fs: this.fs,
104
+ shell: {
105
+ cd: async (path) => {
106
+ this.cwd = this.fs.path(path);
107
+ await this.saveSession({ cwd: this.cwd });
108
+ },
109
+ path: {
110
+ get: () => this.effectiveShellEnv().PATH,
111
+ set: (value) => {
112
+ this.shellEnv.PATH = value;
113
+ },
114
+ prepend: (path) => {
115
+ this.shellEnv.PATH = updatePathList(this.effectiveShellEnv().PATH, this.fs.path(path), 'prepend');
116
+ },
117
+ append: (path) => {
118
+ this.shellEnv.PATH = updatePathList(this.effectiveShellEnv().PATH, this.fs.path(path), 'append');
119
+ },
120
+ },
121
+ },
122
+ env: {
123
+ get: (name) => {
124
+ const env = this.effectiveShellEnv();
125
+ return typeof name === 'string' ? env[name] : env;
126
+ },
127
+ set: (name, value) => {
128
+ this.shellEnv[name] = value;
129
+ },
130
+ unset: (name) => {
131
+ delete this.shellEnv[name];
132
+ },
133
+ },
134
+ exec: (command, options = {}) => this.execCommand(command, options),
135
+ };
136
+ this.computer = this.localComputer;
137
+ this.vprocess = new VProcessTable(this.kstate, sessionId, this.eventLog, computer.defaultRemote(), (name) => {
138
+ try {
139
+ return computer.getRemote(name);
140
+ }
141
+ catch {
142
+ return undefined;
143
+ }
144
+ }, (event) => this.emit(event));
145
+ this.shellExpander = new ShellExpander({
146
+ exec: (command) => this.withTemporaryShellState({}, () => this.execInternal(command, { by: 'system:shell-substitution' })),
147
+ getLastExitCode: () => this.lastExitCode,
148
+ setLastExitCode: (code) => { this.lastExitCode = code; },
149
+ getShellOptions: () => this.shellOptions,
150
+ });
151
+ this.turnStopDepth = 0;
152
+ this.maxTurnStopContinuations = 5;
153
+ this.functionDepth = 0;
154
+ this.sourceDepth = 0;
155
+ this.fsActorStack = [];
156
+ this.lastExitCode = 0;
157
+ this.closed = false;
158
+ }
159
+ async init() {
160
+ this.lease = await this.kstate.acquireSessionLease(this.sessionId, {
161
+ owner: `${this.runtime.workerId}:${this.agentName}`,
162
+ ttlMs: this.runtime.leaseTtlMs,
163
+ });
164
+ this.startLeaseHeartbeat();
165
+ await this.kstate.initSession(this.sessionId);
166
+ const existing = await this.kstate.readSession(this.sessionId);
167
+ this.initResult = { firstLogin: !existing, recovered: Boolean(existing) };
168
+ if (existing) {
169
+ if (existing.agent && existing.agent !== this.agentName) {
170
+ throw new Error(`session ${this.sessionId} belongs to agent ${existing.agent}, not ${this.agentName}`);
171
+ }
172
+ this.cwd = existing.cwd || this.cwd;
173
+ this.system = existing.system || this.system;
174
+ this.shellEnv = recordStringMap(existing.env);
175
+ const shellRecord = recordField(existing, 'shell') || {};
176
+ this.shellOptions = normalizeShellOptions(recordField(shellRecord, 'options'));
177
+ this.shellFunctions = recordStringMap(recordField(shellRecord, 'functions'));
178
+ this.lastExitCode = typeof existing.lastExitCode === 'number' ? existing.lastExitCode : this.lastExitCode;
179
+ this.modeState = this.normalizeModeForAgent(existing.mode || this.mode, existing.scope);
180
+ this.mode = this.modeState.name;
181
+ }
182
+ else {
183
+ await this.kstate.ensureRootDir(this.home);
184
+ await this.kstate.ensureRootDir(this.cwd);
185
+ await this.saveSession({
186
+ schema: 'k.session.v1',
187
+ id: this.sessionId,
188
+ agent: this.agentName,
189
+ home: this.home,
190
+ cwd: this.cwd,
191
+ env: this.shellEnv,
192
+ shell: this.shellSessionRecord(),
193
+ lastExitCode: this.lastExitCode,
194
+ mode: this.mode,
195
+ scope: this.modeState.scope,
196
+ model: this.modelAlias,
197
+ worker: {
198
+ owner: this.lease.owner,
199
+ token: this.lease.token,
200
+ expiresAt: this.lease.expiresAt,
201
+ },
202
+ createdAt: now(),
203
+ status: 'ready',
204
+ });
205
+ }
206
+ const recoveryId = existing ? id('recovery') : undefined;
207
+ if (recoveryId)
208
+ await this.trace('recovery.start', { recoveryId, by: 'system:recovery' });
209
+ const recoveredProcesses = await this.vprocess.recover();
210
+ const recoveredReceipts = await this.reconcileRecoveredProcessReceipts(recoveredProcesses);
211
+ const sealedTools = await this.sealUnfinishedTools();
212
+ const sealedToolEvents = await this.sealUnfinishedToolEvents();
213
+ const sealedTurns = await this.sealInterruptedTurns();
214
+ if (recoveryId) {
215
+ const summary = { recoveryId, by: 'system:recovery', recoveredProcesses: recoveredProcesses.length, recoveredReceipts, sealedTools, sealedToolEvents, sealedTurns };
216
+ await this.trace('recovery.end', summary);
217
+ await this.signal('recovery.completed', summary);
218
+ }
219
+ return this;
220
+ }
221
+ async reconcileRecoveredProcessReceipts(processes) {
222
+ if (!processes.length)
223
+ return 0;
224
+ const receipts = await this.kstate.readSessionJsonl(this.sessionId, 'receipts.jsonl');
225
+ let reconciled = 0;
226
+ for (const process of processes) {
227
+ if (!process.receiptId || process.status === 'running')
228
+ continue;
229
+ const receipt = receipts.filter((item) => item.id === process.receiptId).at(-1);
230
+ if (!receipt || receipt.status !== 'uncertain')
231
+ continue;
232
+ const status = process.status === 'exited'
233
+ ? 'committed'
234
+ : process.status === 'failed'
235
+ ? 'failed'
236
+ : 'uncertain';
237
+ const error = status === 'committed'
238
+ ? undefined
239
+ : process.error || process.signal || (typeof process.exitCode === 'number' ? `exit ${process.exitCode}` : 'process outcome is uncertain after worker recovery; reconcile required');
240
+ const final = finishReceipt(receipt, {
241
+ status,
242
+ output: {
243
+ vpid: process.vpid,
244
+ jobId: process.jobId,
245
+ status: process.status,
246
+ exitCode: process.exitCode,
247
+ signal: process.signal,
248
+ },
249
+ error,
250
+ });
251
+ await this.assertWritableSession();
252
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', final);
253
+ await this.trace('receipt.finalized', {
254
+ receiptId: final.id,
255
+ kind: final.kind,
256
+ status,
257
+ code: process.exitCode,
258
+ signal: process.signal,
259
+ by: 'system:process-recovery',
260
+ });
261
+ reconciled += 1;
262
+ }
263
+ return reconciled;
264
+ }
265
+ async finalizeProcessReceiptFromWait(result) {
266
+ if (!result.recovered)
267
+ return;
268
+ if (!result.receiptId || result.status === 'running')
269
+ return;
270
+ const receipts = await this.kstate.readSessionJsonl(this.sessionId, 'receipts.jsonl');
271
+ const receipt = receipts.filter((item) => item.id === result.receiptId).at(-1);
272
+ if (!receipt || receipt.status !== 'uncertain')
273
+ return;
274
+ const status = result.status === 'exited'
275
+ ? 'committed'
276
+ : result.status === 'failed'
277
+ ? 'failed'
278
+ : 'uncertain';
279
+ const final = finishReceipt(receipt, {
280
+ status,
281
+ output: {
282
+ vpid: result.vpid,
283
+ jobId: result.jobId,
284
+ status: result.status,
285
+ exitCode: result.code,
286
+ signal: result.signal,
287
+ },
288
+ error: status === 'committed'
289
+ ? undefined
290
+ : result.stderr || result.signal || (typeof result.code === 'number' ? `exit ${result.code}` : 'process outcome is uncertain after wait'),
291
+ });
292
+ await this.assertWritableSession();
293
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', final);
294
+ await this.trace('receipt.finalized', {
295
+ receiptId: final.id,
296
+ kind: final.kind,
297
+ status,
298
+ code: result.code,
299
+ signal: result.signal,
300
+ by: 'system:process-wait',
301
+ });
302
+ }
303
+ async saveSession(patch) {
304
+ await this.saveSessionUnchecked(patch, false);
305
+ }
306
+ async saveSessionUnchecked(patch, _leaseChecked) {
307
+ if (this.lease)
308
+ await this.assertActiveLease();
309
+ const current = await this.kstate.readSession(this.sessionId);
310
+ if (this.lease && current?.worker?.token && current.worker.token !== this.lease.token && !isExpiredWorker(current.worker)) {
311
+ throw new Error(`lease lost: ${this.sessionId}`);
312
+ }
313
+ const hasWorkerPatch = Object.prototype.hasOwnProperty.call(patch, 'worker');
314
+ const worker = hasWorkerPatch
315
+ ? patch.worker
316
+ : this.lease
317
+ ? { owner: this.lease.owner, token: this.lease.token, expiresAt: this.lease.expiresAt }
318
+ : current?.worker;
319
+ const next = { ...(current || {}), ...patch, updatedAt: now() };
320
+ if (worker)
321
+ next.worker = worker;
322
+ else
323
+ delete next.worker;
324
+ if (this.lease)
325
+ await this.assertActiveLease();
326
+ await this.kstate.writeSession(this.sessionId, next);
327
+ }
328
+ startLeaseHeartbeat() {
329
+ if (!this.runtime.leaseHeartbeat || this.leaseHeartbeat || !this.lease)
330
+ return;
331
+ const intervalMs = Math.max(10, Math.floor(this.runtime.leaseTtlMs / 3));
332
+ this.leaseHeartbeat = setInterval(() => {
333
+ this.renewLease().catch(() => {
334
+ this.closed = true;
335
+ });
336
+ }, intervalMs);
337
+ this.leaseHeartbeat.unref?.();
338
+ }
339
+ async renewLease() {
340
+ if (!this.lease || this.closed)
341
+ return;
342
+ await this.lease.renew(this.runtime.leaseTtlMs);
343
+ await this.saveSessionUnchecked({}, true);
344
+ }
345
+ async assertWritableSession() {
346
+ if (this.closed)
347
+ throw new Error(`session closed: ${this.sessionId}`);
348
+ await this.assertActiveLease();
349
+ }
350
+ async assertActiveLease() {
351
+ if (!this.lease)
352
+ return;
353
+ if (this.lease.assertActive) {
354
+ await this.lease.assertActive();
355
+ return;
356
+ }
357
+ await this.lease.renew(this.runtime.leaseTtlMs);
358
+ }
359
+ async canWriteWithCurrentLease() {
360
+ if (!this.lease)
361
+ return false;
362
+ try {
363
+ await this.assertActiveLease();
364
+ return true;
365
+ }
366
+ catch {
367
+ return false;
368
+ }
369
+ }
370
+ normalizeModeForAgent(mode = 'normal', scope = undefined) {
371
+ const state = normalizeMode(mode, scope);
372
+ if (!state.scope?.fs?.length)
373
+ return state;
374
+ return {
375
+ ...state,
376
+ scope: {
377
+ ...state.scope,
378
+ fs: state.scope.fs.map((path) => normalizeKPath(path, this.home)),
379
+ },
380
+ };
381
+ }
382
+ async ready() {
383
+ await this.saveSession({ status: 'ready', system: this.system });
384
+ }
385
+ async authorizeFs(request) {
386
+ await this.assertWritableSession();
387
+ const before = await this.runHook('fs.before', request);
388
+ if (before.rejected) {
389
+ if (before.waitingApproval)
390
+ throw new Error(`waiting approval: ${String(before.approval?.id || '')}`);
391
+ throw new Error(before.reason || `fs ${request.op} rejected`);
392
+ }
393
+ const current = before.current;
394
+ const op = stringField(current, 'op');
395
+ const path = stringField(current, 'path');
396
+ const decision = decideFs({ mode: this.modeState, op, path });
397
+ if (!decision.ok)
398
+ throw new Error(decision.reason);
399
+ return current;
400
+ }
401
+ async trace(type, data = {}) {
402
+ await this.assertWritableSession();
403
+ const record = await this.kstate.appendTrace(this.sessionId, type, data);
404
+ await this.saveSessionUnchecked({ lastTraceSeq: record.traceSeq }, true);
405
+ return record;
406
+ }
407
+ async traceFs(type, data = {}) {
408
+ const record = await this.trace(type, data);
409
+ if (!isFsEffectTrace(type))
410
+ return record;
411
+ const by = normalizeBy(typeof data.by === 'string' ? data.by : this.currentFsActor());
412
+ const target = fsReceiptTarget(type, data);
413
+ const receipt = makeReceipt({
414
+ traceId: record.traceSeq,
415
+ sessionId: this.sessionId,
416
+ agent: this.agentName,
417
+ kind: type.replace(/^fs\./, 'vfs.'),
418
+ target,
419
+ summary: `${type.replace(/^fs\./, '')} ${target}`,
420
+ status: 'committed',
421
+ by,
422
+ input: fsReceiptInput(type, data),
423
+ output: { ok: true },
424
+ });
425
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
426
+ await this.trace('receipt.recorded', { receiptId: receipt.id, kind: receipt.kind, by, target });
427
+ return record;
428
+ }
429
+ currentFsActor() {
430
+ return normalizeBy(this.fsActorStack.at(-1));
431
+ }
432
+ async withFsActor(by, fn) {
433
+ this.fsActorStack.push(normalizeBy(by));
434
+ try {
435
+ return await fn();
436
+ }
437
+ finally {
438
+ this.fsActorStack.pop();
439
+ }
440
+ }
441
+ async emit(event) {
442
+ await this.assertWritableSession();
443
+ assertRuntimeEvent(event);
444
+ const boundedEvent = this.boundRuntimeEvent(event);
445
+ const envelope = await this.eventLog.emit(boundedEvent);
446
+ const handlers = this.handlers.get(boundedEvent.type) || [];
447
+ for (const handler of handlers) {
448
+ try {
449
+ await handler(boundedEvent, envelope);
450
+ }
451
+ catch (error) {
452
+ await this.trace('event.handler.error', {
453
+ event: boundedEvent.type,
454
+ message: error instanceof Error ? error.message : String(error),
455
+ });
456
+ }
457
+ }
458
+ return envelope;
459
+ }
460
+ events(options = {}) {
461
+ return this.eventLog.events({ ...options, until: () => this.closed });
462
+ }
463
+ on(type, handler) {
464
+ if (!this.handlers.has(type))
465
+ this.handlers.set(type, []);
466
+ const handlers = this.handlers.get(type);
467
+ if (!handlers)
468
+ throw new Error(`handler list not initialized for ${type}`);
469
+ handlers.push(handler);
470
+ return () => {
471
+ const list = this.handlers.get(type) || [];
472
+ this.handlers.set(type, list.filter((item) => item !== handler));
473
+ };
474
+ }
475
+ hook(type, handler) {
476
+ if (!this.hooks.has(type))
477
+ this.hooks.set(type, []);
478
+ const hooks = this.hooks.get(type);
479
+ if (!hooks)
480
+ throw new Error(`hook list not initialized for ${type}`);
481
+ hooks.push(handler);
482
+ return () => {
483
+ const list = this.hooks.get(type) || [];
484
+ this.hooks.set(type, list.filter((item) => item !== handler));
485
+ };
486
+ }
487
+ hasHooks(type) {
488
+ return Boolean(this.hooks.get(type)?.length);
489
+ }
490
+ async runHook(type, current) {
491
+ let value = current;
492
+ const hooks = this.hooks.get(type) || [];
493
+ for (const handler of hooks) {
494
+ const ctx = this.hookContext(type, value);
495
+ const result = await handler(ctx);
496
+ const decision = result || ctx.decision || { type: 'pass' };
497
+ if (decision.type === 'update') {
498
+ value = { ...value, ...decision.patch };
499
+ continue;
500
+ }
501
+ if (decision.type === 'reject') {
502
+ return { rejected: true, reason: decision.reason, current: value };
503
+ }
504
+ if (decision.type === 'ask') {
505
+ const remembered = await this.approvalLog.forAction(type, value);
506
+ if (remembered?.status === 'allowed') {
507
+ await this.consumeApproval(remembered);
508
+ continue;
509
+ }
510
+ if (remembered?.status === 'pending') {
511
+ await this.emitApprovalRequest(remembered);
512
+ return { rejected: true, waitingApproval: true, approval: remembered, current: value };
513
+ }
514
+ const approval = await this.approvalLog.create(type, value, decision.request);
515
+ await this.emitApprovalRequest(approval);
516
+ return { rejected: true, waitingApproval: true, approval, current: value };
517
+ }
518
+ }
519
+ return { rejected: false, current: value };
520
+ }
521
+ hookContext(type, current) {
522
+ return {
523
+ type,
524
+ agent: this,
525
+ ctx: this.runtime.runtimeContext(this, { kind: 'hook', name: type, input: current, args: current }),
526
+ current,
527
+ data: current,
528
+ update: (patch) => ({ type: 'update', patch }),
529
+ reject: (reason) => ({ type: 'reject', reason }),
530
+ ask: (request) => ({ type: 'ask', request }),
531
+ emit: (name, data, options) => this.signal(name, data, options),
532
+ };
533
+ }
534
+ async signal(name, data = {}, options = {}) {
535
+ const level = options.level === 'warn' || options.level === 'error' ? options.level : 'info';
536
+ return this.emit({ type: 'signal', name, data, level });
537
+ }
538
+ async emitApprovalRequest(approval) {
539
+ const request = approval.request || {};
540
+ await this.emit({
541
+ type: 'approval',
542
+ status: 'requested',
543
+ approvalId: approval.id,
544
+ hook: approval.hook,
545
+ message: typeof request.message === 'string' ? request.message : 'Approval required',
546
+ risk: request.risk === 'low' || request.risk === 'medium' || request.risk === 'high' ? request.risk : undefined,
547
+ preview: request.preview,
548
+ current: approval.current,
549
+ approval,
550
+ });
551
+ }
552
+ async decide(approvalId, decision, options = {}) {
553
+ const approval = await this.approvalLog.byId(approvalId);
554
+ if (!approval)
555
+ throw new Error(`approval not found: ${approvalId}`);
556
+ const status = normalizeApprovalDecision(decision);
557
+ const row = {
558
+ id: approvalId,
559
+ status,
560
+ decision,
561
+ by: normalizeBy(options.by),
562
+ decidedAt: now(),
563
+ };
564
+ await this.approvalLog.append(row);
565
+ await this.trace('approval.decided', row);
566
+ await this.emit({
567
+ type: 'approval',
568
+ status: 'resolved',
569
+ approvalId,
570
+ hook: approval.hook,
571
+ decision: status === 'allowed' ? 'approved' : 'rejected',
572
+ approval,
573
+ });
574
+ if (status === 'rejected')
575
+ return row;
576
+ const result = await this.resumeApproval({ ...approval, status });
577
+ const done = { ...row, result };
578
+ await this.approvalLog.resumed(approvalId, result);
579
+ return done;
580
+ }
581
+ async consumeApproval(approval) {
582
+ await this.approvalLog.consume(approval);
583
+ await this.trace('approval.consumed', {
584
+ id: approval.id,
585
+ hook: approval.hook,
586
+ actionKey: approval.actionKey,
587
+ });
588
+ }
589
+ async resumeApproval(approval) {
590
+ const hook = approval.hook;
591
+ const current = approval.current || {};
592
+ await this.trace('approval.resume', { id: approval.id, hook });
593
+ if (hook === 'send.before') {
594
+ return this.send(current.content, recordField(current, 'options') || {});
595
+ }
596
+ if (hook === 'shell.before' || hook === 'cmd.before') {
597
+ return this.execInternal(stringField(current, 'shellScript') || stringField(current, 'script'), {
598
+ by: stringField(current, 'by') || `approval:${approval.id}`,
599
+ foreground: typeof current.foreground === 'boolean' ? current.foreground : undefined,
600
+ });
601
+ }
602
+ if (hook === 'tool.before') {
603
+ return this.command(stringField(current, 'command'), recordField(current, 'input') || {}, {
604
+ by: stringField(current, 'by') || `approval:${approval.id}`,
605
+ });
606
+ }
607
+ if (hook === 'tool.after') {
608
+ await this.trace('tool.ended', { name: current.command, resultType: resultType(current.result), approvedBy: approval.id });
609
+ await this.emit({ type: 'tool', status: 'completed', tool: String(current.tool || 'tool'), result: current.result, approvalId: approval.id });
610
+ return current.result;
611
+ }
612
+ if (hook === 'fs.before') {
613
+ return this.resumeFsApproval(current);
614
+ }
615
+ if (hook === 'model.before') {
616
+ return this.nextTurn({ inputId: stringField(current, 'inputId'), model: stringField(current, 'model') || undefined });
617
+ }
618
+ if (hook === 'model.after') {
619
+ const result = isModelResult(current.result) ? current.result : current;
620
+ return this.publishModelResult({
621
+ turnId: stringField(current, 'turnId') || id('turn'),
622
+ inputId: stringField(current, 'inputId') || undefined,
623
+ model: stringField(current, 'model') || this.modelAlias,
624
+ result,
625
+ });
626
+ }
627
+ if (hook === 'turn.stop') {
628
+ await this.trace('turn.closed', current);
629
+ const inputId = stringField(current, 'inputId');
630
+ if (inputId) {
631
+ await this.recordInput({
632
+ inputId,
633
+ turnId: stringField(current, 'turnId'),
634
+ status: stringField(current, 'status') || 'completed',
635
+ completedAt: now(),
636
+ reason: current.reason,
637
+ });
638
+ }
639
+ await this.emit({ type: 'turn', ...current, status: turnStatus(current.status), turnId: stringField(current, 'turnId') || id('turn'), approvalId: approval.id });
640
+ return current;
641
+ }
642
+ throw new Error(`approval hook is not resumable: ${hook}`);
643
+ }
644
+ async resumeFsApproval(current) {
645
+ const op = stringField(current, 'op');
646
+ const path = stringField(current, 'path');
647
+ if (op === 'write') {
648
+ await this.fs.writeText(path, stringField(current, 'text'));
649
+ return { status: 'committed', op, path };
650
+ }
651
+ if (op === 'append') {
652
+ await this.fs.appendText(path, stringField(current, 'text'));
653
+ return { status: 'committed', op, path };
654
+ }
655
+ if (op === 'mkdir') {
656
+ await this.fs.mkdir(path);
657
+ return { status: 'committed', op, path };
658
+ }
659
+ if (op === 'rm') {
660
+ const options = recordField(current, 'options');
661
+ await this.fs.rm(path, options || {});
662
+ return { status: 'committed', op, path };
663
+ }
664
+ if (op === 'rename') {
665
+ await this.fs.rename(path, stringField(current, 'to'));
666
+ return { status: 'committed', op, path, to: stringField(current, 'to') };
667
+ }
668
+ throw new Error(`fs approval cannot resume unknown op: ${op}`);
669
+ }
670
+ async close() {
671
+ if (this.closed)
672
+ return;
673
+ this.vprocess.detachActive();
674
+ try {
675
+ if (await this.canWriteWithCurrentLease()) {
676
+ await this.saveSessionUnchecked({ status: 'closed' }, true);
677
+ await this.signal('session.closed', { sessionId: this.sessionId, agent: this.agentName });
678
+ await this.saveSessionUnchecked({ worker: undefined }, true);
679
+ }
680
+ }
681
+ catch {
682
+ // Close is local cleanup first. A lost or expired lease must not keep
683
+ // event/message waiters, heartbeats, or process handles alive.
684
+ }
685
+ finally {
686
+ this.closed = true;
687
+ this.eventLog.wake();
688
+ this.messageLog.wake();
689
+ await this.releaseLease();
690
+ this.clearRuntimeHandles();
691
+ }
692
+ }
693
+ async detach() {
694
+ if (this.closed)
695
+ return;
696
+ this.vprocess.detachActive();
697
+ try {
698
+ if (await this.canWriteWithCurrentLease()) {
699
+ await this.trace('session.detached', { sessionId: this.sessionId, agent: this.agentName });
700
+ await this.signal('session.detached', { sessionId: this.sessionId, agent: this.agentName });
701
+ await this.saveSessionUnchecked({ worker: undefined }, true);
702
+ }
703
+ }
704
+ catch {
705
+ // Detach cannot be allowed to leak local runtime handles merely because
706
+ // the durable worker lease was already taken over.
707
+ }
708
+ finally {
709
+ this.closed = true;
710
+ this.eventLog.wake();
711
+ this.messageLog.wake();
712
+ await this.releaseLease();
713
+ this.clearRuntimeHandles();
714
+ }
715
+ }
716
+ /** @internal */
717
+ async abandonStartup() {
718
+ if (this.closed)
719
+ return;
720
+ this.closed = true;
721
+ this.eventLog.wake();
722
+ this.messageLog.wake();
723
+ await this.releaseLease();
724
+ this.clearRuntimeHandles();
725
+ }
726
+ clearRuntimeHandles() {
727
+ this.handlers.clear();
728
+ this.hooks.clear();
729
+ this.eventLog.clear();
730
+ this.messageLog.clear();
731
+ }
732
+ async releaseLease() {
733
+ if (this.leaseHeartbeat) {
734
+ clearInterval(this.leaseHeartbeat);
735
+ this.leaseHeartbeat = undefined;
736
+ }
737
+ await this.lease?.release();
738
+ this.lease = undefined;
739
+ }
740
+ async send(input, options = {}) {
741
+ const by = normalizeBy(options.by);
742
+ const blocks = normalizeInput(input);
743
+ const before = await this.runHook('send.before', { content: blocks, by, options });
744
+ if (before.rejected) {
745
+ await this.trace('message.rejected', { by, reason: before.reason, hook: 'send.before' });
746
+ if (!before.waitingApproval) {
747
+ await this.emit({
748
+ type: 'message',
749
+ status: 'rejected',
750
+ messageId: id('message'),
751
+ role: 'user',
752
+ by,
753
+ reason: before.reason,
754
+ content: blocks,
755
+ });
756
+ }
757
+ return { status: before.waitingApproval ? 'waiting_approval' : 'rejected', approval: before.approval, reason: before.reason };
758
+ }
759
+ const inputId = id('input');
760
+ const content = Array.isArray(before.current.content) ? before.current.content : blocks;
761
+ const message = await this.appendMessage({ role: 'user', content, by, inputId });
762
+ const record = { inputId, messageId: message.id, by, content, status: 'queued', queuedAt: now() };
763
+ await this.recordInput(record);
764
+ await this.trace('message.appended', { inputId, by, content });
765
+ await this.emit({ type: 'message', status: 'appended', messageId: String(message.id), role: 'user', by, content });
766
+ return { status: 'queued', inputId, messageId: message.id, by };
767
+ }
768
+ async run(input, options = {}) {
769
+ const sent = await this.send(input, options);
770
+ if (sent.status !== 'queued')
771
+ return { status: sent.status, text: '', reason: sent.reason, approval: sent.approval };
772
+ return this.nextTurn({ inputId: stringField(sent, 'inputId'), model: options.model });
773
+ }
774
+ async queue(inputs, options = {}) {
775
+ const results = [];
776
+ for (const input of inputs) {
777
+ results.push(await this.run(input, options));
778
+ }
779
+ return results;
780
+ }
781
+ async upload(path, data, options = {}) {
782
+ const text = typeof data === 'string'
783
+ ? data
784
+ : data instanceof Uint8Array
785
+ ? new TextDecoder().decode(data)
786
+ : JSON.stringify(data, null, 2);
787
+ await this.fs.writeFile(path, text);
788
+ const kPath = this.fs.path(path);
789
+ const bytes = new TextEncoder().encode(text).byteLength;
790
+ const record = { path: kPath, bytes, by: normalizeBy(options.by), at: now() };
791
+ await this.trace('file.uploaded', record);
792
+ if (options.announce) {
793
+ await this.send(`Uploaded ${kPath}`, { by: options.by || 'program:upload' });
794
+ }
795
+ return record;
796
+ }
797
+ async download(path, _options = {}) {
798
+ const text = await this.fs.readText(path);
799
+ const kPath = this.fs.path(path);
800
+ const bytes = new TextEncoder().encode(text).byteLength;
801
+ await this.trace('file.downloaded', { path: kPath, bytes });
802
+ return { path: kPath, text, bytes };
803
+ }
804
+ async nextTurn({ inputId, model } = {}) {
805
+ inputId = inputId || await this.nextQueuedInputId();
806
+ const turnId = id('turn');
807
+ if (inputId)
808
+ await this.recordInput({ inputId, status: 'running', turnId, startedAt: now() });
809
+ await this.trace('turn.opened', { turnId, inputId });
810
+ await this.emit({ type: 'turn', status: 'running', turnId });
811
+ let modelReq = {
812
+ turnId,
813
+ inputId,
814
+ model: model || this.modelAlias,
815
+ messages: await this.messagesForModel(),
816
+ mode: this.mode,
817
+ };
818
+ const before = await this.runHook('model.before', modelReq);
819
+ if (before.rejected) {
820
+ return this.endTurn({
821
+ turnId,
822
+ inputId,
823
+ status: before.waitingApproval ? 'waiting_approval' : 'blocked',
824
+ reason: before.reason || (before.waitingApproval ? 'waiting approval' : undefined),
825
+ });
826
+ }
827
+ modelReq = before.current;
828
+ const modelAuthority = decideModel({ mode: this.modeState, model: modelReq.model || 'default' });
829
+ if (!modelAuthority.ok) {
830
+ await this.trace('model.rejected', { turnId, inputId, model: modelReq.model, reason: modelAuthority.reason, mode: this.mode });
831
+ return this.endTurn({ turnId, inputId, status: 'blocked', reason: modelAuthority.reason });
832
+ }
833
+ const llm = this.runtime.models.get(modelReq.model || 'default');
834
+ const modelTrace = await this.trace('model.request', {
835
+ turnId,
836
+ inputId,
837
+ model: modelReq.model,
838
+ messageCount: modelReq.messages.length,
839
+ });
840
+ let result;
841
+ let contentAlreadyEmitted = false;
842
+ try {
843
+ if (llm.stream) {
844
+ const streamed = await this.consumeModelStream(llm.stream(modelReq), {
845
+ turnId,
846
+ inputId,
847
+ emit: !this.hasHooks('model.after'),
848
+ });
849
+ result = streamed.result;
850
+ contentAlreadyEmitted = streamed.emitted;
851
+ }
852
+ else {
853
+ result = await llm.complete(modelReq);
854
+ }
855
+ }
856
+ catch (error) {
857
+ const message = error instanceof Error ? error.message : String(error);
858
+ await this.trace('model.error', { turnId, inputId, message });
859
+ return this.endTurn({ turnId, inputId, status: 'failed', reason: message });
860
+ }
861
+ const after = await this.runHook('model.after', { result, model: modelReq.model, turnId, inputId });
862
+ if (after.rejected) {
863
+ return this.endTurn({
864
+ turnId,
865
+ inputId,
866
+ status: after.waitingApproval ? 'waiting_approval' : 'blocked',
867
+ reason: after.reason || (after.waitingApproval ? 'waiting approval' : undefined),
868
+ });
869
+ }
870
+ result = isModelResult(after.current.result) ? after.current.result : after.current;
871
+ return this.publishModelResult({ turnId, inputId, model: modelReq.model, modelTraceSeq: modelTrace.traceSeq, result, emitContent: !contentAlreadyEmitted });
872
+ }
873
+ async consumeModelStream(stream, { turnId, inputId, emit, }) {
874
+ const content = [];
875
+ let text = '';
876
+ let doneMessage;
877
+ let usage;
878
+ let finishReason;
879
+ let provider;
880
+ let emitted = false;
881
+ for await (const event of stream) {
882
+ if (event.type === 'content') {
883
+ content.push(event.content);
884
+ if (event.text)
885
+ text += event.text;
886
+ else if (event.content.type === 'text' && event.content.text)
887
+ text += event.content.text;
888
+ if (emit) {
889
+ await this.emit({ type: 'content', kind: contentKind(event.content), content: event.content, text: event.text ?? event.content.text, ref: event.content.ref, path: event.content.path, turnId });
890
+ emitted = true;
891
+ }
892
+ }
893
+ else if (event.type === 'tool') {
894
+ const block = {
895
+ type: 'tool_use',
896
+ id: event.toolUseId || id('tool'),
897
+ name: event.tool,
898
+ input: event.input,
899
+ };
900
+ content.push(block);
901
+ if (emit) {
902
+ await this.emit({ type: 'tool', status: 'requested', tool: event.tool, input: event.input, opId: block.id, turnId });
903
+ emitted = true;
904
+ }
905
+ }
906
+ else if (event.type === 'message') {
907
+ doneMessage = event.message;
908
+ usage = event.usage;
909
+ finishReason = event.finishReason;
910
+ provider = event.provider;
911
+ }
912
+ else if (event.type === 'error') {
913
+ throw event.error instanceof Error ? event.error : new Error(String(event.error));
914
+ }
915
+ else {
916
+ await this.trace('provider', { turnId, inputId, event });
917
+ }
918
+ }
919
+ const message = doneMessage || { role: 'assistant', content };
920
+ return {
921
+ emitted,
922
+ result: {
923
+ text: text || blocksToText(message.content || []),
924
+ message,
925
+ usage,
926
+ finishReason,
927
+ provider,
928
+ },
929
+ };
930
+ }
931
+ async publishModelResult({ turnId, inputId, model, modelTraceSeq, result, emitContent = true, }) {
932
+ const message = normalizeAssistantMessage(result);
933
+ const provider = modelProviderSummary(result.provider);
934
+ await this.appendMessage({ ...message, by: `model:${model || this.modelAlias}`, inputId, turnId });
935
+ await this.trace('model.output', {
936
+ turnId,
937
+ inputId,
938
+ modelTraceSeq,
939
+ text: result.text || blocksToText(message.content),
940
+ usage: result.usage,
941
+ provider,
942
+ });
943
+ let text = '';
944
+ for (const block of message.content || []) {
945
+ if (block.type === 'text') {
946
+ text += block.text ?? '';
947
+ if (emitContent)
948
+ await this.emit({ type: 'content', kind: contentKind(block), content: block, text: block.text, ref: block.ref, path: block.path, turnId });
949
+ }
950
+ else if (block.type === 'tool_use') {
951
+ if (emitContent)
952
+ await this.emit({ type: 'tool', status: 'requested', tool: String(block.name || 'tool'), input: block.input, opId: block.id, turnId });
953
+ }
954
+ else {
955
+ if (emitContent)
956
+ await this.emit({ type: 'content', kind: contentKind(block), content: block, ref: block.ref, path: block.path, turnId });
957
+ }
958
+ }
959
+ const hasToolUse = (message.content || []).some((block) => block.type === 'tool_use');
960
+ if (hasToolUse) {
961
+ return this.endTurn({ turnId, inputId, status: 'blocked', text, reason: 'tool call pending', provider });
962
+ }
963
+ return this.endTurn({ turnId, inputId, status: 'completed', text, provider });
964
+ }
965
+ async endTurn({ turnId, inputId, status, text = '', reason = undefined, provider = undefined }) {
966
+ let payload = { turnId, inputId, status, text, reason, provider };
967
+ if (this.turnStopDepth >= this.maxTurnStopContinuations) {
968
+ const message = `turn.stop skipped after ${this.maxTurnStopContinuations} nested continuations to prevent recursive agent maintenance loops`;
969
+ await this.trace('turn.stop.skipped', { turnId, inputId, status, reason: message, maxContinuations: this.maxTurnStopContinuations });
970
+ await this.signal('turn.stop.max_continuations', {
971
+ turnId,
972
+ inputId,
973
+ status,
974
+ message,
975
+ maxContinuations: this.maxTurnStopContinuations,
976
+ }, { level: 'warn' });
977
+ }
978
+ else {
979
+ this.turnStopDepth += 1;
980
+ try {
981
+ const stop = await this.runHook('turn.stop', payload);
982
+ if (stop.rejected) {
983
+ payload = {
984
+ ...payload,
985
+ ...stop.current,
986
+ status: stop.waitingApproval ? 'waiting_approval' : 'blocked',
987
+ reason: stop.reason || (stop.waitingApproval ? 'waiting approval' : 'turn.stop rejected finalization'),
988
+ };
989
+ }
990
+ else {
991
+ payload = { ...payload, ...stop.current };
992
+ }
993
+ }
994
+ finally {
995
+ this.turnStopDepth -= 1;
996
+ }
997
+ }
998
+ await this.trace('turn.closed', payload);
999
+ if (inputId) {
1000
+ await this.recordInput({
1001
+ inputId,
1002
+ turnId,
1003
+ status: stringField(payload, 'status') || 'completed',
1004
+ completedAt: now(),
1005
+ reason: payload.reason,
1006
+ });
1007
+ }
1008
+ await this.emit({ type: 'turn', ...payload, status: turnStatus(payload.status), turnId });
1009
+ return payload;
1010
+ }
1011
+ async recordInput(record) {
1012
+ await this.assertWritableSession();
1013
+ await this.kstate.appendSessionJsonl(this.sessionId, 'inputs.jsonl', record);
1014
+ }
1015
+ async nextQueuedInputId() {
1016
+ const rows = await this.kstate.readSessionJsonl(this.sessionId, 'inputs.jsonl');
1017
+ const latest = new Map();
1018
+ for (const row of rows) {
1019
+ const inputId = stringField(row, 'inputId');
1020
+ if (inputId)
1021
+ latest.set(inputId, row);
1022
+ }
1023
+ for (const row of latest.values()) {
1024
+ if (row.status === 'queued')
1025
+ return stringField(row, 'inputId');
1026
+ }
1027
+ return undefined;
1028
+ }
1029
+ stats() {
1030
+ return {
1031
+ sessionId: this.sessionId,
1032
+ agent: this.agentName,
1033
+ mode: this.mode,
1034
+ disk: this.disk,
1035
+ cwd: this.cwd,
1036
+ closed: this.closed,
1037
+ localEventHandlers: countHandlerMap(this.handlers),
1038
+ localHooks: countHandlerMap(this.hooks),
1039
+ localActiveProcesses: this.vprocess.localActiveProcessCount(),
1040
+ shellFunctions: Object.keys(this.shellFunctions).length,
1041
+ };
1042
+ }
1043
+ async exec(command, options = {}) {
1044
+ const toolUseId = id('tool');
1045
+ const by = normalizeBy(options.by);
1046
+ const script = String(command);
1047
+ const toolUseMessage = {
1048
+ role: 'assistant',
1049
+ by,
1050
+ content: [{
1051
+ type: 'tool_use',
1052
+ id: toolUseId,
1053
+ name: 'shell',
1054
+ input: {
1055
+ script,
1056
+ cwd: this.cwd,
1057
+ foreground: options.foreground ?? !script.trimEnd().endsWith('&'),
1058
+ },
1059
+ }],
1060
+ };
1061
+ try {
1062
+ await this.emit({ type: 'tool', status: 'requested', tool: 'shell', input: toolUseMessage.content[0]?.input, opId: toolUseId });
1063
+ const result = await this.execShell(command, options);
1064
+ const bounded = this.boundShellResult(result);
1065
+ await this.appendExecToolExchange(toolUseMessage, toolUseId, bounded, by);
1066
+ await this.emit({ type: 'tool', status: bounded.code === 0 ? 'completed' : 'failed', tool: 'shell', result: bounded, opId: toolUseId });
1067
+ return bounded;
1068
+ }
1069
+ catch (error) {
1070
+ const result = {
1071
+ status: 'failed',
1072
+ code: 1,
1073
+ stdout: '',
1074
+ stderr: error instanceof Error ? error.message : String(error),
1075
+ };
1076
+ const bounded = this.boundShellResult(result);
1077
+ await this.appendExecToolExchange(toolUseMessage, toolUseId, bounded, by);
1078
+ await this.emit({ type: 'tool', status: 'failed', tool: 'shell', result: bounded, opId: toolUseId });
1079
+ throw error;
1080
+ }
1081
+ }
1082
+ async execInternal(command, options = {}) {
1083
+ return this.boundShellResult(await this.execShell(command, options));
1084
+ }
1085
+ async execCommand(command, options = {}) {
1086
+ if (typeof command === 'string')
1087
+ return this.execInternal(command, options);
1088
+ const remote = command.remote || options.remote;
1089
+ const limits = command.limits || options.limits;
1090
+ const stdin = command.stdin ?? options.stdin;
1091
+ return this.execInternal([command.command, ...(command.argv || [])].filter(Boolean).map(quoteShellWord).join(' '), {
1092
+ ...options,
1093
+ stdin,
1094
+ remote,
1095
+ limits,
1096
+ forceRemote: Boolean(remote),
1097
+ });
1098
+ }
1099
+ async appendExecToolExchange(toolUseMessage, toolUseId, result, by) {
1100
+ await this.assertWritableSession();
1101
+ const code = typeof result.code === 'number' ? result.code : result.status === 'committed' ? 0 : 1;
1102
+ const bounded = this.boundShellResult(result);
1103
+ const stdout = typeof bounded.stdout === 'string' ? bounded.stdout : '';
1104
+ const stderr = typeof bounded.stderr === 'string' ? bounded.stderr : '';
1105
+ await this.messageLog.appendMany([
1106
+ toolUseMessage,
1107
+ {
1108
+ role: 'tool',
1109
+ by,
1110
+ content: [{
1111
+ type: 'tool_result',
1112
+ toolUseId,
1113
+ name: 'shell',
1114
+ ok: code === 0 && result.status !== 'failed' && result.status !== 'rejected',
1115
+ status: typeof result.status === 'string' ? result.status : code === 0 ? 'committed' : 'failed',
1116
+ text: stdout || stderr,
1117
+ stdout,
1118
+ stderr,
1119
+ code,
1120
+ truncated: bounded.truncated === true ? true : undefined,
1121
+ stdoutTruncated: bounded.stdoutTruncated === true ? true : undefined,
1122
+ stderrTruncated: bounded.stderrTruncated === true ? true : undefined,
1123
+ stdoutOriginalChars: typeof bounded.stdoutOriginalChars === 'number' ? bounded.stdoutOriginalChars : undefined,
1124
+ stderrOriginalChars: typeof bounded.stderrOriginalChars === 'number' ? bounded.stderrOriginalChars : undefined,
1125
+ receiptId: recordField(bounded, 'receipt')?.id,
1126
+ }],
1127
+ },
1128
+ ]);
1129
+ }
1130
+ async execShell(command, options = {}) {
1131
+ const aborted = abortedShellResult(options.signal);
1132
+ if (aborted)
1133
+ return aborted;
1134
+ const shell = String(command);
1135
+ const foreground = options.foreground ?? !shell.trimEnd().endsWith('&');
1136
+ let script = foreground ? shell : shell.trimEnd().replace(/&\s*$/, '');
1137
+ const beforeShell = await this.runHook('shell.before', { script, cwd: this.hookCwd(), by: normalizeBy(options.by) });
1138
+ if (beforeShell.rejected) {
1139
+ await this.trace('process.rejected', { script, reason: beforeShell.reason });
1140
+ if (beforeShell.waitingApproval) {
1141
+ return { status: 'waiting_approval', code: undefined, stdout: '', stderr: '', approval: beforeShell.approval, reason: beforeShell.reason };
1142
+ }
1143
+ return { status: 'rejected', code: 126, stdout: '', stderr: String(beforeShell.reason || 'rejected') };
1144
+ }
1145
+ script = stringField(beforeShell.current, 'script');
1146
+ const functionProgram = parseShellFunctionProgram(script);
1147
+ if (functionProgram) {
1148
+ return this.rememberExit(await this.execKLocalFunctionProgram(functionProgram, { by: options.by, signal: options.signal }));
1149
+ }
1150
+ const control = parseShellControlProgram(script);
1151
+ if (control) {
1152
+ return this.rememberExit(await this.execKLocalControlProgram(control, { by: options.by, signal: options.signal }));
1153
+ }
1154
+ const redirect = parseShellRedirects(script);
1155
+ const governedScript = redirect?.script || script;
1156
+ if (this.mode !== 'normal' && !isGovernableShellScript(governedScript)) {
1157
+ const reason = `${this.mode} mode denies shell syntax that K cannot govern safely`;
1158
+ await this.trace('process.rejected', { script, reason, mode: this.mode });
1159
+ return { status: 'rejected', code: 126, stdout: '', stderr: reason };
1160
+ }
1161
+ const parsed = parseShellCommandSegments(governedScript).map((segment) => this.normalizeShellSegment(segment));
1162
+ const segmentRedirects = [];
1163
+ for (let index = 0; index < parsed.length; index += 1) {
1164
+ const segmentRedirect = parseShellRedirects(parsed[index].script);
1165
+ if (!segmentRedirect)
1166
+ continue;
1167
+ const stripped = parseShellCommandSegments(segmentRedirect.script)[0];
1168
+ if (!stripped)
1169
+ continue;
1170
+ parsed[index] = this.normalizeShellSegment({ ...stripped, separator: parsed[index].separator, background: parsed[index].background });
1171
+ segmentRedirects[index] = segmentRedirect;
1172
+ }
1173
+ for (let index = 0; index < parsed.length; index += 1) {
1174
+ const concrete = parsed[index];
1175
+ if (concrete.assignmentOnly)
1176
+ continue;
1177
+ const beforeCmd = await this.runHook('cmd.before', {
1178
+ cmd: concrete.cmd,
1179
+ args: concrete.args,
1180
+ env: concrete.env,
1181
+ script: concrete.script,
1182
+ shellScript: script,
1183
+ cwd: this.hookCwd(),
1184
+ by: normalizeBy(options.by),
1185
+ foreground: foreground && !concrete.background,
1186
+ background: !foreground || concrete.background,
1187
+ });
1188
+ if (beforeCmd.rejected) {
1189
+ await this.trace('process.rejected', { cmd: concrete.cmd, args: concrete.args, reason: beforeCmd.reason });
1190
+ if (beforeCmd.waitingApproval) {
1191
+ return { status: 'waiting_approval', code: undefined, stdout: '', stderr: '', approval: beforeCmd.approval, reason: beforeCmd.reason };
1192
+ }
1193
+ return { status: 'rejected', code: 126, stdout: '', stderr: String(beforeCmd.reason || 'rejected') };
1194
+ }
1195
+ const currentCmd = stringField(beforeCmd.current, 'cmd') || concrete.cmd;
1196
+ const currentArgs = stringArrayField(beforeCmd.current, 'args') || concrete.args;
1197
+ const currentScript = stringField(beforeCmd.current, 'script') || concrete.script;
1198
+ const words = [currentCmd, ...currentArgs];
1199
+ const preservesTokens = currentCmd === concrete.cmd
1200
+ && currentArgs.length === concrete.args.length
1201
+ && currentArgs.every((arg, argIndex) => arg === concrete.args[argIndex]);
1202
+ parsed[index] = {
1203
+ ...concrete,
1204
+ cmd: currentCmd,
1205
+ args: currentArgs,
1206
+ script: currentScript,
1207
+ words,
1208
+ wordTokens: preservesTokens ? concrete.wordTokens : words.map(shellWordToken),
1209
+ background: concrete.background,
1210
+ };
1211
+ const authority = decideCommand({ mode: this.modeState, cmd: currentCmd });
1212
+ if (!authority.ok) {
1213
+ await this.trace('process.rejected', { cmd: currentCmd, args: currentArgs, reason: authority.reason, mode: this.mode });
1214
+ return { status: 'rejected', code: 126, stdout: '', stderr: authority.reason };
1215
+ }
1216
+ }
1217
+ const cmd = parsed[0]?.cmd || 'bash';
1218
+ const args = parsed[0]?.args || [];
1219
+ const env = parsed[0]?.env;
1220
+ if (parsed.length === 1 && parsed[0]?.assignmentOnly) {
1221
+ return this.rememberExit(await this.execKLocalSequence(parsed, { by: options.by, redirects: segmentRedirects, foreground, signal: options.signal }));
1222
+ }
1223
+ if (!options.forceRemote && foreground && parsed.length === 1 && redirect && (K_BUILTINS.has(cmd)
1224
+ || this.runtime.commands.has(cmd)
1225
+ || this.shellFunctions[cmd] !== undefined
1226
+ || await this.resolveKExecutable(cmd, env))) {
1227
+ return this.rememberExit(await this.execKLocalSequence(parsed, { by: options.by, redirects: [redirect], foreground, signal: options.signal }));
1228
+ }
1229
+ if (!options.forceRemote && !foreground && await this.isKLocalSequence(parsed)) {
1230
+ return this.rememberExit(await this.execBackgroundKLocalSequence(governedScript, parsed, {
1231
+ by: options.by,
1232
+ redirects: segmentRedirects,
1233
+ signal: options.signal,
1234
+ }));
1235
+ }
1236
+ if (!options.forceRemote && await this.isKLocalSequence(parsed)) {
1237
+ return this.rememberExit(await this.execKLocalSequence(parsed, { by: options.by, redirects: segmentRedirects, foreground, signal: options.signal }));
1238
+ }
1239
+ if (!options.forceRemote && this.isKCommandInvocation(governedScript, parsed)) {
1240
+ return this.rememberExit(await this.applyRedirect(await this.execKCommandFromShell({ cmd, args, argTokens: parsed[0]?.wordTokens.slice(1), script: governedScript, foreground, by: options.by, env }), redirect, options.by));
1241
+ }
1242
+ if (!options.forceRemote && this.isKBuiltinInvocation(governedScript, parsed)) {
1243
+ return this.rememberExit(await this.applyRedirect(await this.execKBuiltinFromShell({ cmd, args, argTokens: parsed[0]?.wordTokens.slice(1), script: governedScript, foreground, by: options.by, env, signal: options.signal }), redirect, options.by));
1244
+ }
1245
+ if (parsed.length === 1 && parsed[0]?.cmd === 'return') {
1246
+ return this.rememberExit(await this.applyRedirect(await this.execKLocalSequence(parsed, { by: options.by, redirects: segmentRedirects, foreground, signal: options.signal }), redirect, options.by));
1247
+ }
1248
+ if (!options.forceRemote && this.isKShellFunctionInvocation(governedScript, parsed)) {
1249
+ return this.rememberExit(await this.applyRedirect(await this.execKShellFunctionFromShell({ cmd, args, argTokens: parsed[0]?.wordTokens.slice(1), script: governedScript, foreground, by: options.by, env, signal: options.signal }), redirect, options.by));
1250
+ }
1251
+ const executable = !options.forceRemote && parsed.length === 1 ? await this.resolveKExecutable(cmd, env) : undefined;
1252
+ if (executable) {
1253
+ return this.rememberExit(await this.applyRedirect(await this.execKExecutableFromShell({ path: executable, cmd, args, argTokens: parsed[0]?.wordTokens.slice(1), script: governedScript, foreground, by: options.by, env, signal: options.signal }), redirect, options.by));
1254
+ }
1255
+ const remote = options.remote ? this.runtime.getRemote(options.remote) : this.runtime.defaultRemote();
1256
+ if (!remote.capabilities.shell) {
1257
+ const reason = `remote ${remote.name} does not provide shell execution`;
1258
+ await this.trace('process.unavailable', { script: shell, reason, remote: remote.name });
1259
+ await this.emit({ type: 'process', status: 'failed', command: 'shell', argv: [], script: shell, code: 127, reason });
1260
+ return { status: 'rejected', code: 127, stdout: '', stderr: reason };
1261
+ }
1262
+ let sandbox;
1263
+ try {
1264
+ sandbox = this.remoteShellSandbox(remote);
1265
+ }
1266
+ catch (error) {
1267
+ const reason = error instanceof Error ? error.message : String(error);
1268
+ await this.trace('process.rejected', { script, reason, mode: this.mode, remote: remote.name });
1269
+ return { status: 'rejected', code: 126, stdout: '', stderr: reason };
1270
+ }
1271
+ if (this.mode !== 'normal' && !sandbox) {
1272
+ const reason = `${this.mode} mode requires a remote shell with hard sandbox enforcement`;
1273
+ await this.trace('process.rejected', { script, reason, mode: this.mode, remote: remote.name });
1274
+ return { status: 'rejected', code: 126, stdout: '', stderr: reason };
1275
+ }
1276
+ const remoteRewrite = rewriteKPathsForRemote(script, (path) => {
1277
+ const resolved = this.runtime.realPathInfo(path);
1278
+ return resolved ? { kPath: normalizeKPath(path), realPath: resolved.realPath, mode: resolved.mode } : undefined;
1279
+ }, this.home);
1280
+ if (remoteRewrite.unresolved.length) {
1281
+ const reason = `remote shell cannot access K paths without a real backing path: ${remoteRewrite.unresolved.join(', ')}`;
1282
+ await this.trace('process.rejected', { script, reason, paths: remoteRewrite.unresolved, remote: remote.name });
1283
+ return { status: 'rejected', code: 126, stdout: '', stderr: reason };
1284
+ }
1285
+ try {
1286
+ sandbox = this.hardenReadonlyMountsForRemoteShell(remote, sandbox, remoteRewrite.resolved);
1287
+ }
1288
+ catch (error) {
1289
+ const reason = error instanceof Error ? error.message : String(error);
1290
+ await this.trace('process.rejected', { script, reason, mode: this.mode, remote: remote.name });
1291
+ return { status: 'rejected', code: 126, stdout: '', stderr: reason };
1292
+ }
1293
+ const remoteScript = remoteRewrite.script;
1294
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script });
1295
+ const trace = await this.trace('process.started', { cmd, args, script, remoteScript, foreground, sandbox });
1296
+ let result;
1297
+ const remoteCwd = this.remoteCwd();
1298
+ if (!remoteCwd && this.kstate.realRootPath('/') !== undefined) {
1299
+ result = {
1300
+ vpid: id('vpid'),
1301
+ jobId: id('job'),
1302
+ background: false,
1303
+ code: 1,
1304
+ stdout: '',
1305
+ stderr: `remote shell cwd without a real backing path: ${this.cwd}`,
1306
+ };
1307
+ }
1308
+ else {
1309
+ try {
1310
+ result = await this.vprocess.spawnShell(remoteScript, { cwd: remoteCwd, env: this.effectiveShellEnv(options.env), stdin: options.stdin, foreground, remote, limits: options.limits, sandbox });
1311
+ }
1312
+ catch (error) {
1313
+ result = {
1314
+ vpid: id('vpid'),
1315
+ jobId: id('job'),
1316
+ background: false,
1317
+ code: 1,
1318
+ stdout: '',
1319
+ stderr: error instanceof Error ? error.message : String(error),
1320
+ };
1321
+ }
1322
+ }
1323
+ const status = result.background ? 'uncertain' : result.code === 0 ? 'committed' : 'failed';
1324
+ const receipt = makeReceipt({
1325
+ traceId: trace.traceSeq,
1326
+ sessionId: this.sessionId,
1327
+ agent: this.agentName,
1328
+ kind: 'remote.exec',
1329
+ target: `${remote.name}:${cmd || 'bash'}`,
1330
+ summary: script,
1331
+ status,
1332
+ by: normalizeBy(options.by),
1333
+ input: { remote: remote.name, script, sandbox, limits: options.limits },
1334
+ output: this.boundShellReceiptOutput(result),
1335
+ policy: sandbox ? { sandbox: { request: sandbox, attestation: result.sandbox } } : result.sandbox ? { sandbox: { attestation: result.sandbox } } : undefined,
1336
+ error: result.code === 0 ? undefined : compactShellCaptureText(result.stderr || '').text,
1337
+ });
1338
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1339
+ if (result.background)
1340
+ await this.trackBackgroundReceipt(result, receipt);
1341
+ await this.trace('process.ended', { cmd, args, script, status, code: result.code, receiptId: receipt.id });
1342
+ await this.emit({ type: 'process', status: processStatus(status, result.code), command: cmd, argv: args, script, code: result.code, receiptId: receipt.id });
1343
+ return this.rememberExit({ status, receipt, ...result });
1344
+ }
1345
+ remoteShellSandbox(remote) {
1346
+ if (this.mode === 'normal')
1347
+ return undefined;
1348
+ if (remote.capabilities.sandbox?.shell !== 'hard')
1349
+ return undefined;
1350
+ const scopedRealPaths = this.remoteRealPathsForScope(this.modeState.scope?.fs || []);
1351
+ const request = hardenShellSandboxRequest({
1352
+ mode: this.mode,
1353
+ enforcement: 'hard',
1354
+ readonly: this.mode === 'readonly' || this.mode === 'plan',
1355
+ allowedPaths: scopedRealPaths.length ? scopedRealPaths : undefined,
1356
+ allowWrite: this.mode === 'limited' ? scopedRealPaths : [],
1357
+ allowedHosts: this.modeState.scope?.hosts ?? [],
1358
+ });
1359
+ request.denyWrite = this.remoteDenyWritePaths(request.denyWrite || []);
1360
+ const errors = validateShellSandboxRequest(request);
1361
+ if (errors.length)
1362
+ throw new Error(`invalid remote shell sandbox request: ${errors.join('; ')}`);
1363
+ return request;
1364
+ }
1365
+ hardenReadonlyMountsForRemoteShell(remote, sandbox, paths) {
1366
+ const readonlyRealPaths = [...new Set(paths
1367
+ .filter((path) => path.mode === 'ro')
1368
+ .map((path) => path.realPath))];
1369
+ if (!readonlyRealPaths.length)
1370
+ return sandbox;
1371
+ if (remote.capabilities.sandbox?.shell !== 'hard') {
1372
+ throw new Error('remote shell cannot access ro K mounts without hard sandbox enforcement');
1373
+ }
1374
+ const request = hardenShellSandboxRequest({
1375
+ ...(sandbox || { mode: this.mode, enforcement: 'hard' }),
1376
+ denyWrite: [...(sandbox?.denyWrite || []), ...readonlyRealPaths],
1377
+ allowRead: [...(sandbox?.allowRead || []), ...readonlyRealPaths],
1378
+ });
1379
+ const errors = validateShellSandboxRequest(request);
1380
+ if (errors.length)
1381
+ throw new Error(`invalid remote shell sandbox request: ${errors.join('; ')}`);
1382
+ return request;
1383
+ }
1384
+ remoteRealPathsForScope(paths) {
1385
+ return paths.map((path) => {
1386
+ const kPath = this.shellPath(path);
1387
+ const real = this.runtime.realPath(kPath);
1388
+ if (!real)
1389
+ throw new Error(`remote shell sandbox scope path has no real backing path: ${kPath}`);
1390
+ return real;
1391
+ });
1392
+ }
1393
+ remoteDenyWritePaths(paths) {
1394
+ return [...new Set(paths.map((path) => this.remoteSandboxPath(path)))];
1395
+ }
1396
+ remoteSandboxPath(path) {
1397
+ if (path === '~' || path.startsWith('~/'))
1398
+ return this.requireRealKPath(`${this.home}${path.slice(1)}`);
1399
+ if (path.startsWith('./') || path.startsWith('.'))
1400
+ return this.requireRealKPath(`${this.cwd}/${path}`);
1401
+ if (path.startsWith('/home/') || path.startsWith('/workspace') || path.startsWith('/tmp/')) {
1402
+ return this.requireRealKPath(path);
1403
+ }
1404
+ return path;
1405
+ }
1406
+ requireRealKPath(path) {
1407
+ const kPath = normalizeKPath(path);
1408
+ const real = this.runtime.realPath(kPath);
1409
+ if (!real)
1410
+ throw new Error(`remote shell sandbox path has no real backing path: ${kPath}`);
1411
+ return real;
1412
+ }
1413
+ isKCommandInvocation(script, parsed) {
1414
+ if (parsed.length !== 1)
1415
+ return false;
1416
+ if (!isSimpleShellCommand(script))
1417
+ return false;
1418
+ const cmd = parsed[0]?.cmd;
1419
+ return Boolean(cmd && this.runtime.commands.has(cmd));
1420
+ }
1421
+ isKBuiltinInvocation(script, parsed) {
1422
+ if (parsed.length !== 1)
1423
+ return false;
1424
+ if (!isSimpleShellCommand(script))
1425
+ return false;
1426
+ const cmd = parsed[0]?.cmd;
1427
+ return Boolean(cmd && K_BUILTINS.has(cmd));
1428
+ }
1429
+ isKShellFunctionInvocation(script, parsed) {
1430
+ if (parsed.length !== 1)
1431
+ return false;
1432
+ if (!isSimpleShellCommand(script))
1433
+ return false;
1434
+ const cmd = parsed[0]?.cmd;
1435
+ return Boolean(cmd && this.shellFunctions[cmd] !== undefined);
1436
+ }
1437
+ async isKLocalSequence(parsed) {
1438
+ if (parsed.length <= 1)
1439
+ return false;
1440
+ for (let index = 0; index < parsed.length; index += 1) {
1441
+ const segment = parsed[index];
1442
+ if (!segment)
1443
+ return false;
1444
+ if (segment.assignmentOnly || segment.cmd === '(' || segment.cmd === 'return' || K_BUILTINS.has(segment.cmd) || this.runtime.commands.has(segment.cmd) || this.shellFunctions[segment.cmd] !== undefined)
1445
+ continue;
1446
+ const participatesInPipeline = segment.separator === '|' || parsed[index + 1]?.separator === '|';
1447
+ if (!participatesInPipeline && await this.resolveKExecutable(segment.cmd, segment.env))
1448
+ continue;
1449
+ if (!this.runtime.defaultRemote().capabilities.shell)
1450
+ continue;
1451
+ return false;
1452
+ }
1453
+ return true;
1454
+ }
1455
+ async resolveKExecutable(cmd, env) {
1456
+ const expandedEnv = await this.expandShellEnv(env || {});
1457
+ const candidates = cmd.includes('/')
1458
+ ? [cmd]
1459
+ : this.effectiveShellEnv(expandedEnv).PATH.split(':').filter(Boolean).map((dir) => `${dir.replace(/\/$/, '')}/${cmd}`);
1460
+ for (const candidate of candidates) {
1461
+ const kPath = this.shellPath(candidate);
1462
+ try {
1463
+ const stat = await this.fs.stat(kPath);
1464
+ if (stat.type === 'file' && isExecutable(stat))
1465
+ return this.fs.path(kPath);
1466
+ if (stat.type === 'symlink') {
1467
+ const target = await this.fs.stat(await this.fs.realpath(kPath));
1468
+ if (target.type === 'file' && isExecutable(target))
1469
+ return this.fs.path(kPath);
1470
+ }
1471
+ }
1472
+ catch {
1473
+ // PATH lookup should behave like Unix: missing entries are ignored.
1474
+ }
1475
+ }
1476
+ return undefined;
1477
+ }
1478
+ async execKLocalSequence(parsed, { by, redirects = [], foreground = true, signal } = {}) {
1479
+ let lastCode = 0;
1480
+ let lastResult = { status: 'committed', code: 0, stdout: '', stderr: '' };
1481
+ let stdout = '';
1482
+ let stderr = '';
1483
+ let pipelineInput = '';
1484
+ let pipelineFailureCode = 0;
1485
+ for (let index = 0; index < parsed.length; index += 1) {
1486
+ const aborted = abortedShellResult(signal);
1487
+ if (aborted)
1488
+ return { ...lastResult, ...aborted, stdout, stderr };
1489
+ const segment = parsed[index];
1490
+ if (segment.separator === '&&' && lastCode !== 0)
1491
+ continue;
1492
+ if (segment.separator === '||' && lastCode === 0)
1493
+ continue;
1494
+ const redirect = redirects[index];
1495
+ const segmentForeground = foreground && !segment.background;
1496
+ const stdin = segmentForeground
1497
+ ? segment.separator === '|'
1498
+ ? pipelineInput
1499
+ : redirect?.stdinText !== undefined
1500
+ ? redirect.stdinText
1501
+ : redirect?.stdin
1502
+ ? await this.fs.readText(await this.expandRedirectPath(redirect.stdin))
1503
+ : ''
1504
+ : '';
1505
+ if (redirect) {
1506
+ const prepared = await this.prepareRedirect(redirect, by);
1507
+ if (!prepared.ok) {
1508
+ return { status: 'rejected', code: 126, stdout, stderr: `${stderr}${prepared.stderr}` };
1509
+ }
1510
+ }
1511
+ const inPipeline = segment.separator === '|' || parsed[index + 1]?.separator === '|';
1512
+ const result = inPipeline
1513
+ ? await this.withTemporaryShellState({}, () => this.execKLocalSegment(segment, { stdin, by, foreground: segmentForeground, signal }))
1514
+ : await this.execKLocalSegment(segment, { stdin, by, foreground: segmentForeground, signal });
1515
+ const abortedAfterSegment = abortedShellResult(signal);
1516
+ lastResult = result;
1517
+ let segmentStdout = typeof result.stdout === 'string' ? result.stdout : '';
1518
+ let segmentStderr = typeof result.stderr === 'string' ? result.stderr : '';
1519
+ if (redirect) {
1520
+ const redirected = await this.applyRedirectedStreams(segmentStdout, segmentStderr, redirect, by);
1521
+ if (!redirected.ok) {
1522
+ return { ...result, status: 'rejected', code: 126, stdout, stderr: `${stderr}${redirected.stderr}` };
1523
+ }
1524
+ segmentStdout = redirected.stdout;
1525
+ segmentStderr = redirected.stderr;
1526
+ }
1527
+ if (parsed[index + 1]?.separator === '|')
1528
+ pipelineInput = segmentStdout;
1529
+ else {
1530
+ stdout = appendBoundedShellCapture(stdout, segmentStdout);
1531
+ pipelineInput = '';
1532
+ }
1533
+ stderr = appendBoundedShellCapture(stderr, segmentStderr);
1534
+ const segmentCode = shellExitCode(result);
1535
+ const pipelineEndsHere = inPipeline && parsed[index + 1]?.separator !== '|';
1536
+ if (inPipeline && segmentCode !== 0 && pipelineFailureCode === 0)
1537
+ pipelineFailureCode = segmentCode;
1538
+ lastCode = pipelineEndsHere && this.shellOptions.pipefail && pipelineFailureCode !== 0 ? pipelineFailureCode : segmentCode;
1539
+ if (!inPipeline || pipelineEndsHere)
1540
+ pipelineFailureCode = 0;
1541
+ this.lastExitCode = lastCode;
1542
+ if (result.status === 'rejected' || result.status === 'waiting_approval' || result.status === 'return')
1543
+ break;
1544
+ if (abortedAfterSegment) {
1545
+ lastResult = { ...result, ...abortedAfterSegment };
1546
+ break;
1547
+ }
1548
+ const nextSeparator = parsed[index + 1]?.separator;
1549
+ if (this.shellOptions.errexit && lastCode !== 0 && nextSeparator !== '&&' && nextSeparator !== '||' && nextSeparator !== '|')
1550
+ break;
1551
+ }
1552
+ const status = lastCode === 0 || lastResult.status !== 'committed' ? lastResult.status : 'failed';
1553
+ return {
1554
+ ...lastResult,
1555
+ status,
1556
+ code: lastCode,
1557
+ stdout,
1558
+ stderr,
1559
+ };
1560
+ }
1561
+ async execBackgroundKLocalSequence(script, parsed, { by, redirects = [], signal } = {}) {
1562
+ await this.emit({ type: 'process', status: 'started', command: 'shell', argv: [], script, kind: 'k.shell', background: true });
1563
+ const trace = await this.trace('process.started', { cmd: 'shell', args: [], script, kind: 'k.shell', foreground: false, background: true });
1564
+ const background = await this.vprocess.spawnVirtual(script, async (backgroundSignal) => {
1565
+ const result = await this.withTemporaryShellState({}, () => this.execKLocalSequence(parsed, {
1566
+ by,
1567
+ redirects,
1568
+ foreground: true,
1569
+ signal: backgroundSignal || signal,
1570
+ }));
1571
+ return {
1572
+ code: shellExitCode(result),
1573
+ signal: typeof result.signal === 'string' ? result.signal : null,
1574
+ stdout: typeof result.stdout === 'string' ? result.stdout : '',
1575
+ stderr: typeof result.stderr === 'string' ? result.stderr : '',
1576
+ };
1577
+ }, { cwd: this.cwd, foreground: false });
1578
+ const receipt = makeReceipt({
1579
+ traceId: trace.traceSeq,
1580
+ sessionId: this.sessionId,
1581
+ agent: this.agentName,
1582
+ kind: 'k.shell',
1583
+ target: 'shell',
1584
+ summary: script,
1585
+ status: 'uncertain',
1586
+ by: normalizeBy(by),
1587
+ input: { script, background: true },
1588
+ output: { vpid: background.vpid, jobId: background.jobId },
1589
+ });
1590
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1591
+ await this.trackBackgroundReceipt(background, receipt);
1592
+ await this.trace('process.ended', { cmd: 'shell', args: [], script, status: 'uncertain', receiptId: receipt.id, kind: 'k.shell', background: true, vpid: background.vpid, jobId: background.jobId });
1593
+ await this.emit({ type: 'process', status: 'uncertain', command: 'shell', argv: [], script, receiptId: receipt.id, kind: 'k.shell', background: true, opId: background.vpid, jobId: background.jobId });
1594
+ return { status: 'uncertain', receipt, ...background };
1595
+ }
1596
+ async execKLocalSegment(segment, { stdin = '', by, foreground = true, signal } = {}) {
1597
+ if (segment.assignmentOnly) {
1598
+ await this.setShellEnv(await this.expandShellEnv(segment.env || {}) ?? {});
1599
+ return { status: 'committed', code: 0, stdout: '', stderr: '' };
1600
+ }
1601
+ if (segment.cmd === '(')
1602
+ return this.execKSubshellFromShell({ segment, by, signal });
1603
+ if (segment.cmd === 'return')
1604
+ return this.execShellReturn(segment);
1605
+ return K_BUILTINS.has(segment.cmd)
1606
+ ? this.execKBuiltinFromShell({ cmd: segment.cmd, args: segment.args, argTokens: segment.wordTokens.slice(1), script: segment.script, foreground, by, stdin, env: segment.env, signal })
1607
+ : this.runtime.commands.has(segment.cmd)
1608
+ ? this.execKCommandFromShell({ cmd: segment.cmd, args: segment.args, argTokens: segment.wordTokens.slice(1), script: segment.script, foreground, by, stdin, env: segment.env })
1609
+ : this.shellFunctions[segment.cmd] !== undefined
1610
+ ? this.execKShellFunctionFromShell({ cmd: segment.cmd, args: segment.args, argTokens: segment.wordTokens.slice(1), script: segment.script, foreground, by, env: segment.env, signal })
1611
+ : this.execKExecutableFromShell({
1612
+ path: await this.resolveKExecutable(segment.cmd, segment.env) || segment.cmd,
1613
+ cmd: segment.cmd,
1614
+ args: segment.args,
1615
+ argTokens: segment.wordTokens.slice(1),
1616
+ script: segment.script,
1617
+ foreground,
1618
+ by,
1619
+ env: segment.env,
1620
+ signal,
1621
+ });
1622
+ }
1623
+ async execKSubshellFromShell({ segment, by, signal }) {
1624
+ const inner = segment.args[0] || parseShellGroup(segment.script);
1625
+ if (!inner)
1626
+ return { status: 'failed', code: 2, stdout: '', stderr: 'subshell: missing closing )\n' };
1627
+ const env = await this.expandShellEnv(segment.env || {});
1628
+ return this.withTemporaryShellState({ env }, () => this.execInternal(inner, { by: by || 'shell:subshell', signal }));
1629
+ }
1630
+ async execKLocalFunctionProgram(program, { by, signal } = {}) {
1631
+ let last = { status: 'committed', code: 0, stdout: '', stderr: '' };
1632
+ let stdout = '';
1633
+ let stderr = '';
1634
+ if (program.prefix) {
1635
+ last = await this.execInternal(program.prefix, { by, signal });
1636
+ stdout = appendBoundedShellCapture(stdout, typeof last.stdout === 'string' ? last.stdout : '');
1637
+ stderr = appendBoundedShellCapture(stderr, typeof last.stderr === 'string' ? last.stderr : '');
1638
+ if (last.status === 'rejected' || last.status === 'waiting_approval' || shellExitCode(last) !== 0 && this.shellOptions.errexit) {
1639
+ return { ...last, stdout, stderr };
1640
+ }
1641
+ }
1642
+ this.shellFunctions[program.definition.name] = program.definition.body;
1643
+ await this.saveSession({ shell: this.shellSessionRecord() });
1644
+ await this.trace('shell.function', { name: program.definition.name });
1645
+ if (program.suffix) {
1646
+ last = await this.execInternal(program.suffix, { by, signal });
1647
+ stdout = appendBoundedShellCapture(stdout, typeof last.stdout === 'string' ? last.stdout : '');
1648
+ stderr = appendBoundedShellCapture(stderr, typeof last.stderr === 'string' ? last.stderr : '');
1649
+ }
1650
+ return { ...last, code: shellExitCode(last), stdout, stderr };
1651
+ }
1652
+ execShellReturn(segment) {
1653
+ if (this.functionDepth <= 0 && this.sourceDepth <= 0)
1654
+ return { status: 'failed', code: 2, stdout: '', stderr: 'return: can only return from a function or sourced script\n' };
1655
+ const value = segment.args[0] ?? '0';
1656
+ const parsed = Number.parseInt(value, 10);
1657
+ const code = Number.isFinite(parsed) ? ((parsed % 256) + 256) % 256 : 2;
1658
+ return { status: 'return', code, stdout: '', stderr: Number.isFinite(parsed) ? '' : `return: ${value}: numeric argument required\n` };
1659
+ }
1660
+ async execKLocalControlProgram(program, { by, signal } = {}) {
1661
+ let last = { status: 'committed', code: 0, stdout: '', stderr: '' };
1662
+ let stdout = '';
1663
+ let stderr = '';
1664
+ const runPart = async (script) => {
1665
+ last = await this.execInternal(script, { by, signal });
1666
+ stdout = appendBoundedShellCapture(stdout, typeof last.stdout === 'string' ? last.stdout : '');
1667
+ stderr = appendBoundedShellCapture(stderr, typeof last.stderr === 'string' ? last.stderr : '');
1668
+ if (last.status === 'rejected' || last.status === 'waiting_approval')
1669
+ return false;
1670
+ if (this.shellOptions.errexit && shellExitCode(last) !== 0)
1671
+ return false;
1672
+ return true;
1673
+ };
1674
+ if (program.prefix && !await runPart(program.prefix))
1675
+ return { ...last, stdout, stderr };
1676
+ last = await this.execKLocalControl(program.control, { by, signal });
1677
+ stdout = appendBoundedShellCapture(stdout, typeof last.stdout === 'string' ? last.stdout : '');
1678
+ stderr = appendBoundedShellCapture(stderr, typeof last.stderr === 'string' ? last.stderr : '');
1679
+ if (last.status === 'rejected' || last.status === 'waiting_approval')
1680
+ return { ...last, stdout, stderr };
1681
+ if (this.shellOptions.errexit && shellExitCode(last) !== 0)
1682
+ return { ...last, stdout, stderr };
1683
+ if (program.suffix)
1684
+ await runPart(program.suffix);
1685
+ return { ...last, code: shellExitCode(last), stdout, stderr };
1686
+ }
1687
+ async execKLocalControl(control, { by, signal } = {}) {
1688
+ if (control.type === 'if') {
1689
+ const condition = await this.execInternal(control.condition, { by, signal });
1690
+ const conditionCode = shellExitCode(condition);
1691
+ const branchScript = conditionCode === 0 ? control.thenScript : control.elseScript;
1692
+ const branch = branchScript ? await this.execInternal(branchScript, { by, signal }) : { status: 'committed', code: 0, stdout: '', stderr: '' };
1693
+ const code = shellExitCode(branch);
1694
+ return {
1695
+ ...branch,
1696
+ code,
1697
+ stdout: appendBoundedShellCapture(typeof condition.stdout === 'string' ? condition.stdout : '', typeof branch.stdout === 'string' ? branch.stdout : ''),
1698
+ stderr: appendBoundedShellCapture(typeof condition.stderr === 'string' ? condition.stderr : '', typeof branch.stderr === 'string' ? branch.stderr : ''),
1699
+ };
1700
+ }
1701
+ if (control.type === 'for') {
1702
+ const values = await this.expandShellArgs(control.items);
1703
+ let last = { status: 'committed', code: 0, stdout: '', stderr: '' };
1704
+ let stdout = '';
1705
+ let stderr = '';
1706
+ for (const value of values) {
1707
+ await this.setShellEnv({ [control.name]: value });
1708
+ last = await this.execInternal(control.body, { by, signal });
1709
+ stdout = appendBoundedShellCapture(stdout, typeof last.stdout === 'string' ? last.stdout : '');
1710
+ stderr = appendBoundedShellCapture(stderr, typeof last.stderr === 'string' ? last.stderr : '');
1711
+ const code = shellExitCode(last);
1712
+ if (last.status === 'rejected' || last.status === 'waiting_approval')
1713
+ break;
1714
+ if (this.shellOptions.errexit && code !== 0)
1715
+ break;
1716
+ }
1717
+ const code = shellExitCode(last);
1718
+ return { ...last, code, stdout, stderr };
1719
+ }
1720
+ if (control.type === 'case') {
1721
+ const value = await this.expandCaseToken(control.word);
1722
+ for (const arm of control.arms) {
1723
+ const patterns = await Promise.all(arm.patterns.map((pattern) => this.expandCaseToken(pattern)));
1724
+ if (!patterns.some((pattern) => globMatcher(pattern)(value)))
1725
+ continue;
1726
+ const branch = arm.body ? await this.execInternal(arm.body, { by, signal }) : { status: 'committed', code: 0, stdout: '', stderr: '' };
1727
+ return { ...branch, code: shellExitCode(branch) };
1728
+ }
1729
+ return { status: 'committed', code: 0, stdout: '', stderr: '' };
1730
+ }
1731
+ let last = { status: 'committed', code: 0, stdout: '', stderr: '' };
1732
+ let stdout = '';
1733
+ let stderr = '';
1734
+ let hitLimit = true;
1735
+ for (let iteration = 0; iteration < MAX_K_LOCAL_CONTROL_ITERATIONS; iteration += 1) {
1736
+ const condition = await this.execInternal(control.condition, { by, signal });
1737
+ stdout = appendBoundedShellCapture(stdout, typeof condition.stdout === 'string' ? condition.stdout : '');
1738
+ stderr = appendBoundedShellCapture(stderr, typeof condition.stderr === 'string' ? condition.stderr : '');
1739
+ if (condition.status === 'rejected' || condition.status === 'waiting_approval')
1740
+ return { ...condition, stdout, stderr };
1741
+ const conditionPassed = shellExitCode(condition) === 0;
1742
+ if (control.type === 'while' ? !conditionPassed : conditionPassed) {
1743
+ hitLimit = false;
1744
+ break;
1745
+ }
1746
+ last = await this.execInternal(control.body, { by, signal });
1747
+ stdout = appendBoundedShellCapture(stdout, typeof last.stdout === 'string' ? last.stdout : '');
1748
+ stderr = appendBoundedShellCapture(stderr, typeof last.stderr === 'string' ? last.stderr : '');
1749
+ const code = shellExitCode(last);
1750
+ if (last.status === 'rejected' || last.status === 'waiting_approval') {
1751
+ hitLimit = false;
1752
+ break;
1753
+ }
1754
+ if (this.shellOptions.errexit && code !== 0) {
1755
+ hitLimit = false;
1756
+ break;
1757
+ }
1758
+ }
1759
+ if (hitLimit) {
1760
+ return { status: 'failed', code: 124, stdout, stderr: `${stderr}K local shell loop exceeded ${MAX_K_LOCAL_CONTROL_ITERATIONS} iterations\n` };
1761
+ }
1762
+ const code = shellExitCode(last);
1763
+ return { ...last, code, stdout, stderr };
1764
+ }
1765
+ async execKCommandFromShell({ cmd, args, argTokens, script, foreground, by, stdin = '', env }) {
1766
+ let expandedArgs;
1767
+ let expandedEnv;
1768
+ try {
1769
+ expandedEnv = await this.expandShellEnv(env);
1770
+ expandedArgs = await this.expandShellArgs(argTokens || args.map(shellWordToken));
1771
+ }
1772
+ catch (error) {
1773
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.command', by: by || `shell:${cmd}`, error });
1774
+ }
1775
+ const command = this.runtime.commands.get(cmd);
1776
+ if (command && expandedArgs.length === 1 && isHelpArg(expandedArgs[0])) {
1777
+ const stdout = `${commandHelpText(cmd, command)}\n`;
1778
+ return { status: 'committed', code: 0, stdout, stderr: '' };
1779
+ }
1780
+ if (!foreground) {
1781
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind: 'k.command', background: true });
1782
+ const trace = await this.trace('process.started', { cmd, args, script, kind: 'k.command', foreground, background: true });
1783
+ const background = await this.vprocess.spawnVirtual(script, async () => {
1784
+ try {
1785
+ const input = shellArgsToInput(expandedArgs);
1786
+ if (stdin)
1787
+ input.stdin = stdin;
1788
+ const result = await this.command(cmd, input, { by: by || `shell:${cmd}` });
1789
+ return { signal: null, ...commandResultToShellResult(result) };
1790
+ }
1791
+ catch (error) {
1792
+ return { code: 1, signal: null, stdout: '', stderr: commandErrorText(cmd, error) };
1793
+ }
1794
+ }, { cwd: this.cwd, foreground: false });
1795
+ const receipt = makeReceipt({
1796
+ traceId: trace.traceSeq,
1797
+ sessionId: this.sessionId,
1798
+ agent: this.agentName,
1799
+ kind: 'k.command',
1800
+ target: cmd,
1801
+ summary: script,
1802
+ status: 'uncertain',
1803
+ by: normalizeBy(by || `shell:${cmd}`),
1804
+ input: { cmd, args: expandedArgs, background: true },
1805
+ output: { vpid: background.vpid, jobId: background.jobId },
1806
+ });
1807
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1808
+ await this.trackBackgroundReceipt(background, receipt);
1809
+ await this.trace('process.ended', { cmd, args, script, status: 'uncertain', receiptId: receipt.id, kind: 'k.command', background: true, vpid: background.vpid, jobId: background.jobId });
1810
+ await this.emit({ type: 'process', status: 'uncertain', command: cmd, argv: args, script, receiptId: receipt.id, kind: 'k.command', background: true, opId: background.vpid, jobId: background.jobId });
1811
+ return { status: 'uncertain', receipt, ...background };
1812
+ }
1813
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind: 'k.command' });
1814
+ const trace = await this.trace('process.started', { cmd, args, script, kind: 'k.command', foreground });
1815
+ try {
1816
+ const input = shellArgsToInput(expandedArgs);
1817
+ if (stdin)
1818
+ input.stdin = stdin;
1819
+ const result = await this.command(cmd, input, { by: by || `shell:${cmd}` });
1820
+ const shellResult = commandResultToShellResult(result);
1821
+ const status = shellResult.code === 0 ? 'committed' : 'failed';
1822
+ const receipt = makeReceipt({
1823
+ traceId: trace.traceSeq,
1824
+ sessionId: this.sessionId,
1825
+ agent: this.agentName,
1826
+ kind: 'k.command',
1827
+ target: cmd,
1828
+ summary: script,
1829
+ status,
1830
+ by: normalizeBy(by || `shell:${cmd}`),
1831
+ input: { cmd, args: expandedArgs },
1832
+ output: this.boundShellReceiptOutput(result),
1833
+ error: status === 'failed' ? compactShellCaptureText(shellResult.stderr || `exit ${shellResult.code}`).text : undefined,
1834
+ });
1835
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1836
+ await this.emitShellOutput(shellResult);
1837
+ await this.trace('process.ended', { cmd, args, script, status, code: shellResult.code, receiptId: receipt.id, kind: 'k.command' });
1838
+ await this.emit({ type: 'process', status: processStatus(status, shellResult.code), command: cmd, argv: args, script, code: shellResult.code, receiptId: receipt.id, kind: 'k.command' });
1839
+ return { status, receipt, ...shellResult };
1840
+ }
1841
+ catch (error) {
1842
+ const stderr = commandErrorText(cmd, error);
1843
+ const receipt = makeReceipt({
1844
+ traceId: trace.traceSeq,
1845
+ sessionId: this.sessionId,
1846
+ agent: this.agentName,
1847
+ kind: 'k.command',
1848
+ target: cmd,
1849
+ summary: script,
1850
+ status: 'failed',
1851
+ by: normalizeBy(by || `shell:${cmd}`),
1852
+ input: { cmd, args: expandedArgs },
1853
+ error: stderr,
1854
+ });
1855
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1856
+ if (stderr)
1857
+ await this.emit({ type: 'process', status: 'output', text: stderr, stream: 'stderr', command: cmd, kind: 'k.command' });
1858
+ await this.trace('process.ended', { cmd, args, script, status: 'failed', code: 1, receiptId: receipt.id, kind: 'k.command' });
1859
+ await this.emit({ type: 'process', status: 'failed', command: cmd, argv: args, script, code: 1, receiptId: receipt.id, kind: 'k.command' });
1860
+ return { status: 'failed', code: 1, stdout: '', stderr, receipt };
1861
+ }
1862
+ }
1863
+ async execKBuiltinFromShell({ cmd, args, argTokens, script, foreground, by, stdin = '', env, signal }) {
1864
+ let expandedArgs;
1865
+ let expandedEnv;
1866
+ try {
1867
+ expandedEnv = await this.expandShellEnv(env);
1868
+ expandedArgs = await this.expandShellArgs(argTokens || args.map(shellWordToken));
1869
+ }
1870
+ catch (error) {
1871
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.builtin', by: by || `shell:${cmd}`, error });
1872
+ }
1873
+ if (!foreground) {
1874
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind: 'k.builtin', background: true });
1875
+ const trace = await this.trace('process.started', { cmd, args, script, kind: 'k.builtin', foreground, background: true });
1876
+ const background = await this.vprocess.spawnVirtual(script, async (signal) => {
1877
+ try {
1878
+ const output = await this.runBuiltin(cmd, expandedArgs, '', signal, expandedEnv, by || `shell:${cmd}`);
1879
+ return { code: output.code, signal: null, stdout: output.stdout, stderr: output.stderr };
1880
+ }
1881
+ catch (error) {
1882
+ if (signal.aborted)
1883
+ return { code: 130, signal: 'SIGTERM', stdout: '', stderr: '' };
1884
+ return { code: 1, signal: null, stdout: '', stderr: builtinErrorText(cmd, error) };
1885
+ }
1886
+ }, { cwd: this.cwd, foreground: false });
1887
+ const receipt = makeReceipt({
1888
+ traceId: trace.traceSeq,
1889
+ sessionId: this.sessionId,
1890
+ agent: this.agentName,
1891
+ kind: 'k.builtin',
1892
+ target: cmd,
1893
+ summary: script,
1894
+ status: 'uncertain',
1895
+ by: normalizeBy(by || `shell:${cmd}`),
1896
+ input: { cmd, args: expandedArgs, background: true },
1897
+ output: { vpid: background.vpid, jobId: background.jobId },
1898
+ });
1899
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1900
+ await this.trackBackgroundReceipt(background, receipt);
1901
+ await this.trace('process.ended', { cmd, args, script, status: 'uncertain', receiptId: receipt.id, kind: 'k.builtin', background: true, vpid: background.vpid, jobId: background.jobId });
1902
+ await this.emit({ type: 'process', status: 'uncertain', command: cmd, argv: args, script, receiptId: receipt.id, kind: 'k.builtin', background: true, opId: background.vpid, jobId: background.jobId });
1903
+ return { status: 'uncertain', receipt, ...background };
1904
+ }
1905
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind: 'k.builtin' });
1906
+ const trace = await this.trace('process.started', { cmd, args, script, kind: 'k.builtin', foreground });
1907
+ try {
1908
+ const output = await this.runBuiltin(cmd, expandedArgs, stdin, signal, expandedEnv, by || `shell:${cmd}`);
1909
+ const status = output.code === 0 ? 'committed' : 'failed';
1910
+ const receipt = makeReceipt({
1911
+ traceId: trace.traceSeq,
1912
+ sessionId: this.sessionId,
1913
+ agent: this.agentName,
1914
+ kind: 'k.builtin',
1915
+ target: cmd,
1916
+ summary: script,
1917
+ status,
1918
+ by: normalizeBy(by || `shell:${cmd}`),
1919
+ input: { cmd, args: expandedArgs, stdin: stdin ? '[stdin]' : undefined },
1920
+ output: this.boundShellReceiptOutput(output),
1921
+ error: status === 'failed' ? compactShellCaptureText(output.stderr || `exit ${output.code}`).text : undefined,
1922
+ });
1923
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1924
+ if (output.stdout)
1925
+ await this.emit({ type: 'process', status: 'output', text: output.stdout, stream: 'stdout', command: cmd, kind: 'k.builtin' });
1926
+ if (output.stderr)
1927
+ await this.emit({ type: 'process', status: 'output', text: output.stderr, stream: 'stderr', command: cmd, kind: 'k.builtin' });
1928
+ await this.trace('process.ended', { cmd, args, script, status, code: output.code, receiptId: receipt.id, kind: 'k.builtin' });
1929
+ await this.emit({ type: 'process', status: processStatus(status, output.code), command: cmd, argv: args, script, code: output.code, receiptId: receipt.id, kind: 'k.builtin' });
1930
+ return { status, code: output.code, stdout: output.stdout, stderr: output.stderr, receipt };
1931
+ }
1932
+ catch (error) {
1933
+ const stderr = builtinErrorText(cmd, error);
1934
+ const code = signal?.aborted ? 130 : 1;
1935
+ const signalName = signal?.aborted ? signalReason(signal) : undefined;
1936
+ const receipt = makeReceipt({
1937
+ traceId: trace.traceSeq,
1938
+ sessionId: this.sessionId,
1939
+ agent: this.agentName,
1940
+ kind: 'k.builtin',
1941
+ target: cmd,
1942
+ summary: script,
1943
+ status: 'failed',
1944
+ by: normalizeBy(by || `shell:${cmd}`),
1945
+ input: { cmd, args: expandedArgs, stdin: stdin ? '[stdin]' : undefined },
1946
+ error: stderr,
1947
+ });
1948
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
1949
+ if (stderr)
1950
+ await this.emit({ type: 'process', status: 'output', text: stderr, stream: 'stderr', command: cmd, kind: 'k.builtin' });
1951
+ await this.trace('process.ended', { cmd, args, script, status: 'failed', code, signal: signalName, receiptId: receipt.id, kind: 'k.builtin' });
1952
+ await this.emit({ type: 'process', status: 'failed', command: cmd, argv: args, script, code, signal: signalName, receiptId: receipt.id, kind: 'k.builtin' });
1953
+ return { status: 'failed', code, signal: signalName, stdout: '', stderr, receipt };
1954
+ }
1955
+ }
1956
+ async execKShellFunctionFromShell({ cmd, args, argTokens, script, foreground, by, env, signal }) {
1957
+ const body = this.shellFunctions[cmd];
1958
+ if (body === undefined)
1959
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.executable', by: by || `function:${cmd}`, error: new Error(`function not found: ${cmd}`) });
1960
+ let expandedArgs;
1961
+ let expandedEnv;
1962
+ try {
1963
+ expandedEnv = await this.expandShellEnv(env);
1964
+ expandedArgs = await this.expandShellArgs(argTokens || args.map(shellWordToken));
1965
+ }
1966
+ catch (error) {
1967
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.executable', by: by || `function:${cmd}`, error });
1968
+ }
1969
+ const run = async (runSignal) => {
1970
+ const positional = { ...(expandedEnv || {}), ...positionalShellEnv(cmd, expandedArgs) };
1971
+ this.functionDepth += 1;
1972
+ try {
1973
+ const result = await this.withTemporaryShellEnvKeys(positional, () => this.execInternal(body, { by: by || `function:${cmd}`, signal: runSignal || signal }));
1974
+ if (result.status !== 'return')
1975
+ return result;
1976
+ const code = shellExitCode(result);
1977
+ return { ...result, status: code === 0 ? 'committed' : 'failed', code };
1978
+ }
1979
+ finally {
1980
+ this.functionDepth -= 1;
1981
+ }
1982
+ };
1983
+ if (!foreground) {
1984
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind: 'k.function', background: true });
1985
+ const trace = await this.trace('process.started', { cmd, args, script, kind: 'k.function', foreground, background: true });
1986
+ const background = await this.vprocess.spawnVirtual(script, async (backgroundSignal) => {
1987
+ const result = await run(backgroundSignal);
1988
+ return {
1989
+ code: shellExitCode(result),
1990
+ signal: typeof result.signal === 'string' ? result.signal : null,
1991
+ stdout: typeof result.stdout === 'string' ? result.stdout : '',
1992
+ stderr: typeof result.stderr === 'string' ? result.stderr : '',
1993
+ };
1994
+ }, { cwd: this.cwd, foreground: false });
1995
+ const receipt = makeReceipt({
1996
+ traceId: trace.traceSeq,
1997
+ sessionId: this.sessionId,
1998
+ agent: this.agentName,
1999
+ kind: 'k.function',
2000
+ target: cmd,
2001
+ summary: script,
2002
+ status: 'uncertain',
2003
+ by: normalizeBy(by || `function:${cmd}`),
2004
+ input: { cmd, args: expandedArgs, background: true },
2005
+ output: { vpid: background.vpid, jobId: background.jobId },
2006
+ });
2007
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
2008
+ await this.trackBackgroundReceipt(background, receipt);
2009
+ await this.trace('process.ended', { cmd, args, script, status: 'uncertain', receiptId: receipt.id, kind: 'k.function', background: true, vpid: background.vpid, jobId: background.jobId });
2010
+ await this.emit({ type: 'process', status: 'uncertain', command: cmd, argv: args, script, receiptId: receipt.id, kind: 'k.function', background: true, opId: background.vpid, jobId: background.jobId });
2011
+ return { status: 'uncertain', receipt, ...background };
2012
+ }
2013
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind: 'k.function' });
2014
+ const trace = await this.trace('process.started', { cmd, args, script, kind: 'k.function', foreground });
2015
+ try {
2016
+ const result = await run(signal);
2017
+ const code = shellExitCode(result);
2018
+ const status = code === 0 ? 'committed' : 'failed';
2019
+ const shellResult = { stdout: typeof result.stdout === 'string' ? result.stdout : '', stderr: typeof result.stderr === 'string' ? result.stderr : '' };
2020
+ const receipt = makeReceipt({
2021
+ traceId: trace.traceSeq,
2022
+ sessionId: this.sessionId,
2023
+ agent: this.agentName,
2024
+ kind: 'k.function',
2025
+ target: cmd,
2026
+ summary: script,
2027
+ status,
2028
+ by: normalizeBy(by || `function:${cmd}`),
2029
+ input: { cmd, args: expandedArgs },
2030
+ output: this.boundShellReceiptOutput(result),
2031
+ error: status === 'failed' ? compactShellCaptureText(shellResult.stderr).text : undefined,
2032
+ });
2033
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
2034
+ await this.trace('process.ended', { cmd, args, script, status, code, receiptId: receipt.id, kind: 'k.function' });
2035
+ await this.emit({ type: 'process', status: processStatus(status, code), command: cmd, argv: args, script, code, receiptId: receipt.id, kind: 'k.function' });
2036
+ return { status, code, receipt, ...shellResult };
2037
+ }
2038
+ catch (error) {
2039
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.executable', by: by || `function:${cmd}`, error });
2040
+ }
2041
+ }
2042
+ async execKExecutableFromShell({ path, cmd, args, argTokens, script, foreground, by, env, signal }) {
2043
+ let expandedArgs;
2044
+ let expandedEnv;
2045
+ try {
2046
+ expandedEnv = await this.expandShellEnv(env);
2047
+ expandedArgs = await this.expandShellArgs(argTokens || args.map(shellWordToken));
2048
+ }
2049
+ catch (error) {
2050
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.executable', by: by || `shell:${cmd}`, error });
2051
+ }
2052
+ const run = async (runSignal) => {
2053
+ const body = executableScriptBody(await this.fs.readText(path));
2054
+ if (!body.trim())
2055
+ return { status: 'committed', code: 0, stdout: '', stderr: '' };
2056
+ const positional = positionalShellEnv(path, expandedArgs);
2057
+ return this.withTemporaryShellState({ env: positional }, () => this.execInternal(body, { by: by || `shell:${cmd}`, signal: runSignal || signal }));
2058
+ };
2059
+ if (!foreground) {
2060
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, path, kind: 'k.executable', background: true });
2061
+ const trace = await this.trace('process.started', { cmd, args, script, path, kind: 'k.executable', foreground, background: true });
2062
+ const background = await this.vprocess.spawnVirtual(script, async (backgroundSignal) => {
2063
+ const result = await run(backgroundSignal);
2064
+ return {
2065
+ code: shellExitCode(result),
2066
+ signal: typeof result.signal === 'string' ? result.signal : null,
2067
+ stdout: typeof result.stdout === 'string' ? result.stdout : '',
2068
+ stderr: typeof result.stderr === 'string' ? result.stderr : '',
2069
+ };
2070
+ }, { cwd: this.cwd, foreground: false });
2071
+ const receipt = makeReceipt({
2072
+ traceId: trace.traceSeq,
2073
+ sessionId: this.sessionId,
2074
+ agent: this.agentName,
2075
+ kind: 'k.executable',
2076
+ target: path,
2077
+ summary: script,
2078
+ status: 'uncertain',
2079
+ by: normalizeBy(by || `shell:${cmd}`),
2080
+ input: { path, cmd, args: expandedArgs, background: true },
2081
+ output: { vpid: background.vpid, jobId: background.jobId },
2082
+ });
2083
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
2084
+ await this.trackBackgroundReceipt(background, receipt);
2085
+ await this.trace('process.ended', { cmd, args, script, path, status: 'uncertain', receiptId: receipt.id, kind: 'k.executable', background: true, vpid: background.vpid, jobId: background.jobId });
2086
+ await this.emit({ type: 'process', status: 'uncertain', command: cmd, argv: args, script, path, receiptId: receipt.id, kind: 'k.executable', background: true, opId: background.vpid, jobId: background.jobId });
2087
+ return { status: 'uncertain', receipt, ...background };
2088
+ }
2089
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, path, kind: 'k.executable' });
2090
+ const trace = await this.trace('process.started', { cmd, args, script, path, kind: 'k.executable', foreground });
2091
+ try {
2092
+ const result = await run(signal);
2093
+ const code = shellExitCode(result);
2094
+ const status = code === 0 ? 'committed' : 'failed';
2095
+ const shellResult = { stdout: typeof result.stdout === 'string' ? result.stdout : '', stderr: typeof result.stderr === 'string' ? result.stderr : '' };
2096
+ const receipt = makeReceipt({
2097
+ traceId: trace.traceSeq,
2098
+ sessionId: this.sessionId,
2099
+ agent: this.agentName,
2100
+ kind: 'k.executable',
2101
+ target: path,
2102
+ summary: script,
2103
+ status,
2104
+ by: normalizeBy(by || `shell:${cmd}`),
2105
+ input: { path, cmd, args: expandedArgs },
2106
+ output: this.boundShellReceiptOutput(result),
2107
+ error: status === 'failed' ? compactShellCaptureText(shellResult.stderr).text : undefined,
2108
+ });
2109
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
2110
+ await this.trace('process.ended', { cmd, args, script, path, status, code, receiptId: receipt.id, kind: 'k.executable' });
2111
+ await this.emit({ type: 'process', status: processStatus(status, code), command: cmd, argv: args, script, path, code, receiptId: receipt.id, kind: 'k.executable' });
2112
+ return { status, code, receipt, ...shellResult };
2113
+ }
2114
+ catch (error) {
2115
+ return this.failShellCommandBeforeRun({ cmd, args, script, kind: 'k.executable', by: by || `shell:${cmd}`, error });
2116
+ }
2117
+ }
2118
+ async failShellCommandBeforeRun({ cmd, args, script, kind, by, error }) {
2119
+ const stderr = error instanceof Error ? error.message : String(error);
2120
+ await this.emit({ type: 'process', status: 'started', command: cmd, argv: args, script, kind });
2121
+ const trace = await this.trace('process.started', { cmd, args, script, kind, foreground: true });
2122
+ const receipt = makeReceipt({
2123
+ traceId: trace.traceSeq,
2124
+ sessionId: this.sessionId,
2125
+ agent: this.agentName,
2126
+ kind,
2127
+ target: cmd,
2128
+ summary: script,
2129
+ status: 'failed',
2130
+ by: normalizeBy(by),
2131
+ input: { cmd, args },
2132
+ error: stderr,
2133
+ });
2134
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', receipt);
2135
+ if (stderr)
2136
+ await this.emit({ type: 'process', status: 'output', text: stderr, stream: 'stderr', command: cmd, kind });
2137
+ await this.trace('process.ended', { cmd, args, script, status: 'failed', code: 1, receiptId: receipt.id, kind });
2138
+ await this.emit({ type: 'process', status: 'failed', command: cmd, argv: args, script, code: 1, receiptId: receipt.id, kind });
2139
+ return { status: 'failed', code: 1, stdout: '', stderr, receipt };
2140
+ }
2141
+ finalizeBackgroundReceipt(receipt, done) {
2142
+ if (!done)
2143
+ return;
2144
+ void done.then(async (result) => {
2145
+ if (this.closed)
2146
+ return;
2147
+ await this.assertWritableSession();
2148
+ const detached = result.signal === 'SIGDETACH';
2149
+ const status = detached ? 'uncertain' : result.code === 0 ? 'committed' : 'failed';
2150
+ const final = finishReceipt(receipt, {
2151
+ status,
2152
+ output: this.boundShellReceiptOutput(result),
2153
+ error: detached
2154
+ ? 'background process detached before completion; recovery must reconcile outcome'
2155
+ : status === 'failed'
2156
+ ? compactShellCaptureText(result.stderr || result.signal || `exit ${result.code}`).text
2157
+ : undefined,
2158
+ });
2159
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', final);
2160
+ await this.trace('receipt.finalized', {
2161
+ receiptId: receipt.id,
2162
+ kind: receipt.kind,
2163
+ status,
2164
+ code: result.code,
2165
+ signal: result.signal,
2166
+ });
2167
+ }).catch(async (error) => {
2168
+ if (this.closed)
2169
+ return;
2170
+ await this.assertWritableSession();
2171
+ const message = error instanceof Error ? error.message : String(error);
2172
+ const final = finishReceipt(receipt, {
2173
+ status: 'uncertain',
2174
+ error: message,
2175
+ });
2176
+ await this.kstate.appendSessionJsonl(this.sessionId, 'receipts.jsonl', final);
2177
+ await this.trace('receipt.finalized', {
2178
+ receiptId: receipt.id,
2179
+ kind: receipt.kind,
2180
+ status: 'uncertain',
2181
+ error: message,
2182
+ });
2183
+ }).catch(() => undefined);
2184
+ }
2185
+ async trackBackgroundReceipt(process, receipt) {
2186
+ await this.vprocess.attachReceipt(process.vpid, receipt.id);
2187
+ this.finalizeBackgroundReceipt(receipt, process.done);
2188
+ }
2189
+ async runBuiltin(cmd, args, stdin = '', signal, localEnv, by) {
2190
+ return this.withFsActor(by || `builtin:${cmd}`, () => runBuiltin({
2191
+ cwd: this.cwd,
2192
+ home: this.home,
2193
+ agentName: this.agentName,
2194
+ sessionId: this.sessionId,
2195
+ fs: this.fs,
2196
+ commands: this.runtime.commands,
2197
+ functions: new Set(Object.keys(this.shellFunctions)),
2198
+ shellPath: (path) => this.shellPath(path),
2199
+ cd: (path) => this.localComputer.shell.cd(path),
2200
+ env: () => this.effectiveShellEnv(localEnv),
2201
+ setEnv: (name, value) => this.setShellEnv({ [name]: value }),
2202
+ unsetEnv: (name) => this.unsetShellEnv(name),
2203
+ shellOptions: () => this.shellOptions,
2204
+ setShellOptions: (patch) => this.setShellOptions(patch),
2205
+ sleep: (ms, sleepSignal) => sleep(ms, sleepSignal),
2206
+ jobs: () => this.jobs(),
2207
+ processes: () => this.processes(),
2208
+ kill: (target, signal) => this.vprocess.kill(target, signal),
2209
+ wait: async (target) => {
2210
+ const result = await this.vprocess.wait(target);
2211
+ await this.finalizeProcessReceiptFromWait(result);
2212
+ return result;
2213
+ },
2214
+ foreground: async (target) => {
2215
+ const result = await this.vprocess.foreground(target);
2216
+ await this.finalizeProcessReceiptFromWait(result);
2217
+ return result;
2218
+ },
2219
+ background: (target) => this.vprocess.background(target),
2220
+ fetchNetwork: (url, init) => this.fetchNetwork(url, init),
2221
+ exec: (script, options) => {
2222
+ const scopedEnv = options?.replaceEnv ? options?.env : { ...(localEnv || {}), ...(options?.env || {}) };
2223
+ const hasScopedEnv = Boolean(scopedEnv && Object.keys(scopedEnv).length);
2224
+ return hasScopedEnv || options?.replaceEnv
2225
+ ? this.withTemporaryShellState({
2226
+ env: scopedEnv,
2227
+ replaceEnv: options?.replaceEnv,
2228
+ }, () => this.execInternal(script, { by: `builtin:${cmd}`, signal: options?.signal || signal }))
2229
+ : this.execInternal(script, { by: `builtin:${cmd}`, signal: options?.signal || signal });
2230
+ },
2231
+ source: (script, options) => this.withTemporaryShellEnvKeys({ ...(localEnv || {}), ...(options?.env || {}) }, async () => {
2232
+ this.sourceDepth += 1;
2233
+ try {
2234
+ return await this.execInternal(script, { by: `builtin:${cmd}`, signal: options?.signal || signal });
2235
+ }
2236
+ finally {
2237
+ this.sourceDepth -= 1;
2238
+ }
2239
+ }),
2240
+ trace: (type, data) => this.trace(type, data).then(() => undefined),
2241
+ }, cmd, args, stdin, signal));
2242
+ }
2243
+ async fetchNetwork(url, init = {}) {
2244
+ const host = networkHostFromUrl(url);
2245
+ const authority = decideNetwork({ mode: this.modeState, host });
2246
+ if (!authority.ok) {
2247
+ await this.trace('net.rejected', { url, host, reason: authority.reason, mode: this.mode });
2248
+ throw new Error(authority.reason);
2249
+ }
2250
+ return this.runtime.fetchNetwork(url, init);
2251
+ }
2252
+ async applyRedirectedStreams(stdout, stderr, redirect, by) {
2253
+ let stdoutTarget = { kind: 'visible', stream: 'stdout' };
2254
+ let stderrTarget = { kind: 'visible', stream: 'stderr' };
2255
+ for (const op of redirect.ops) {
2256
+ if (op.fd === 'stdout')
2257
+ stdoutTarget = { kind: 'file', path: op.path, append: op.append };
2258
+ else if ('to' in op)
2259
+ stderrTarget = stdoutTarget;
2260
+ else
2261
+ stderrTarget = { kind: 'file', path: op.path, append: op.append };
2262
+ }
2263
+ const visible = { stdout: '', stderr: '' };
2264
+ const files = [];
2265
+ const route = (target, text) => {
2266
+ if (target.kind === 'visible') {
2267
+ if (text)
2268
+ visible[target.stream] += text;
2269
+ }
2270
+ else
2271
+ files.push({ ...target, text });
2272
+ };
2273
+ route(stdoutTarget, stdout);
2274
+ route(stderrTarget, stderr);
2275
+ try {
2276
+ const firstWrite = new Set();
2277
+ await this.withFsActor(by || 'shell:redirection', async () => {
2278
+ for (const file of files) {
2279
+ const target = await this.expandRedirectPath(file.path);
2280
+ await this.assertRedirectTargetWritable(target);
2281
+ const key = target;
2282
+ if (file.append || firstWrite.has(key))
2283
+ await this.fs.appendText(target, file.text);
2284
+ else {
2285
+ await this.fs.writeText(target, file.text);
2286
+ firstWrite.add(key);
2287
+ }
2288
+ }
2289
+ });
2290
+ return { ok: true, stdout: visible.stdout, stderr: visible.stderr };
2291
+ }
2292
+ catch (error) {
2293
+ return { ok: false, stderr: error instanceof Error ? error.message : String(error) };
2294
+ }
2295
+ }
2296
+ async prepareRedirect(redirect, by) {
2297
+ try {
2298
+ const opened = new Set();
2299
+ await this.withFsActor(by || 'shell:redirection', async () => {
2300
+ for (const op of redirect.ops) {
2301
+ if (!('path' in op))
2302
+ continue;
2303
+ const target = await this.expandRedirectPath(op.path);
2304
+ await this.assertRedirectTargetWritable(target);
2305
+ if (op.append || opened.has(target))
2306
+ await this.fs.appendText(target, '');
2307
+ else {
2308
+ await this.fs.writeText(target, '');
2309
+ opened.add(target);
2310
+ }
2311
+ }
2312
+ });
2313
+ return { ok: true };
2314
+ }
2315
+ catch (error) {
2316
+ return { ok: false, stderr: error instanceof Error ? error.message : String(error) };
2317
+ }
2318
+ }
2319
+ async expandRedirectPath(path) {
2320
+ const expanded = await this.shellExpander.expandToken(shellWordToken(path), this.effectiveShellEnv());
2321
+ return this.shellPath(expanded);
2322
+ }
2323
+ async assertRedirectTargetWritable(path) {
2324
+ const parent = dirnamePath(path);
2325
+ const stat = await this.fs.stat(parent);
2326
+ if (stat.type !== 'dir')
2327
+ throw new Error(`redirection target parent is not a directory: ${parent}`);
2328
+ const target = await this.fs.stat(path).catch(() => undefined);
2329
+ if (target?.type === 'dir')
2330
+ throw new Error(`redirection target is a directory: ${path}`);
2331
+ }
2332
+ async expandShellArgs(args, localEnv = {}) {
2333
+ const env = this.effectiveShellEnv(localEnv);
2334
+ const expanded = [];
2335
+ for (const arg of args) {
2336
+ const word = await this.shellExpander.expandWord(arg, env);
2337
+ const fields = word.split ? splitShellFields(word.text) : [word.text];
2338
+ for (const field of fields) {
2339
+ const braceFields = shouldExpandBraces(arg) ? expandBraceFields(field) : [field];
2340
+ for (const braceField of braceFields)
2341
+ expanded.push(...await this.expandGlobArg({ ...arg, text: braceField }));
2342
+ }
2343
+ }
2344
+ return expanded;
2345
+ }
2346
+ async expandCaseToken(token, localEnv = {}) {
2347
+ const env = this.effectiveShellEnv(localEnv);
2348
+ return (await this.shellExpander.expandWord(token, env)).text;
2349
+ }
2350
+ async expandShellEnv(env = {}) {
2351
+ const entries = Object.entries(env);
2352
+ if (!entries.length)
2353
+ return undefined;
2354
+ const expanded = {};
2355
+ for (const [name, value] of entries) {
2356
+ expanded[name] = await this.shellExpander.expandToken(shellWordToken(value), { ...this.effectiveShellEnv(), ...expanded });
2357
+ }
2358
+ return expanded;
2359
+ }
2360
+ async expandGlobArg(arg) {
2361
+ if (!arg.globbable || !/[*?\[]/.test(arg.text))
2362
+ return [arg.text];
2363
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(arg.text))
2364
+ return [arg.text];
2365
+ const slashIndex = arg.text.lastIndexOf('/');
2366
+ const inputDir = slashIndex >= 0 ? arg.text.slice(0, slashIndex) || '/' : '.';
2367
+ const pattern = slashIndex >= 0 ? arg.text.slice(slashIndex + 1) : arg.text;
2368
+ const absolute = this.shellPath(inputDir);
2369
+ try {
2370
+ const entries = await this.fs.readdir(absolute);
2371
+ const matcher = globMatcher(pattern);
2372
+ const prefix = slashIndex >= 0 ? `${arg.text.slice(0, slashIndex).replace(/\/$/, '')}/` : '';
2373
+ const matches = entries
2374
+ .filter((entry) => matcher(entry.name))
2375
+ .map((entry) => `${prefix}${entry.name}`)
2376
+ .sort();
2377
+ return matches.length ? matches : [arg.text];
2378
+ }
2379
+ catch {
2380
+ return [arg.text];
2381
+ }
2382
+ }
2383
+ defaultShellEnv() {
2384
+ return {
2385
+ HOME: this.home,
2386
+ PWD: this.cwd,
2387
+ USER: this.agentName,
2388
+ K_SESSION: this.sessionId,
2389
+ PATH: `${this.home}/bin:/usr/local/bin:/usr/bin:/bin`,
2390
+ };
2391
+ }
2392
+ effectiveShellEnv(...patches) {
2393
+ return {
2394
+ ...(this.shellEnvBase ?? this.defaultShellEnv()),
2395
+ ...this.shellEnv,
2396
+ ...Object.assign({}, ...patches.filter(Boolean)),
2397
+ };
2398
+ }
2399
+ async setShellEnv(patch) {
2400
+ for (const [name, value] of Object.entries(patch)) {
2401
+ if (!isShellIdentifier(name))
2402
+ throw new Error(`invalid environment variable name: ${name}`);
2403
+ this.shellEnv[name] = value;
2404
+ }
2405
+ await this.saveSession({ env: this.shellEnv });
2406
+ }
2407
+ async unsetShellEnv(name) {
2408
+ if (!isShellIdentifier(name))
2409
+ throw new Error(`invalid environment variable name: ${name}`);
2410
+ delete this.shellEnv[name];
2411
+ await this.saveSession({ env: this.shellEnv });
2412
+ }
2413
+ async setShellOptions(patch) {
2414
+ this.shellOptions = normalizeShellOptions({ ...this.shellOptions, ...patch });
2415
+ await this.saveSession({ shell: this.shellSessionRecord() });
2416
+ }
2417
+ async withTemporaryShellState({ env, replaceEnv = false }, fn) {
2418
+ const previousBase = this.shellEnvBase ? { ...this.shellEnvBase } : undefined;
2419
+ const previous = { ...this.shellEnv };
2420
+ const previousCwd = this.cwd;
2421
+ const previousOptions = { ...this.shellOptions };
2422
+ if (replaceEnv) {
2423
+ this.shellEnvBase = {};
2424
+ this.shellEnv = { ...(env || {}) };
2425
+ }
2426
+ else {
2427
+ this.shellEnv = { ...this.shellEnv, ...(env || {}) };
2428
+ }
2429
+ try {
2430
+ return await fn();
2431
+ }
2432
+ finally {
2433
+ this.shellEnvBase = previousBase;
2434
+ this.shellEnv = previous;
2435
+ this.cwd = previousCwd;
2436
+ this.shellOptions = previousOptions;
2437
+ await this.saveSession({ env: this.shellEnv });
2438
+ await this.saveSession({ cwd: this.cwd, shell: this.shellSessionRecord() });
2439
+ }
2440
+ }
2441
+ shellSessionRecord() {
2442
+ return { options: this.shellOptions, functions: this.shellFunctions };
2443
+ }
2444
+ async withTemporaryShellEnvKeys(env, fn) {
2445
+ const keys = Object.keys(env);
2446
+ const previous = new Map(keys.map((key) => [key, this.shellEnv[key]]));
2447
+ this.shellEnv = { ...this.shellEnv, ...env };
2448
+ await this.saveSession({ env: this.shellEnv });
2449
+ try {
2450
+ return await fn();
2451
+ }
2452
+ finally {
2453
+ for (const key of keys) {
2454
+ const value = previous.get(key);
2455
+ if (value === undefined)
2456
+ delete this.shellEnv[key];
2457
+ else
2458
+ this.shellEnv[key] = value;
2459
+ }
2460
+ await this.saveSession({ env: this.shellEnv });
2461
+ }
2462
+ }
2463
+ async rememberExit(result) {
2464
+ if (result.background !== true) {
2465
+ this.lastExitCode = shellExitCode(result);
2466
+ await this.saveSession({ lastExitCode: this.lastExitCode });
2467
+ }
2468
+ return result;
2469
+ }
2470
+ normalizeShellSegment(segment) {
2471
+ const group = parseShellGroup(segment.script);
2472
+ if (group !== undefined) {
2473
+ return {
2474
+ ...segment,
2475
+ cmd: '(',
2476
+ args: [group],
2477
+ words: ['(', group],
2478
+ wordTokens: [shellWordToken('('), shellWordToken(group)],
2479
+ };
2480
+ }
2481
+ const env = {};
2482
+ const wordTokens = [...segment.wordTokens];
2483
+ while (wordTokens.length && isShellAssignmentToken(wordTokens[0])) {
2484
+ const assignment = wordTokens.shift();
2485
+ if (!assignment)
2486
+ continue;
2487
+ const index = assignment.text.indexOf('=');
2488
+ env[assignment.text.slice(0, index)] = assignment.text.slice(index + 1);
2489
+ }
2490
+ const words = wordTokens.map((word) => word.text);
2491
+ if (!words.length && Object.keys(env).length) {
2492
+ return { ...segment, cmd: 'export', args: [], words: [], wordTokens: [], env, assignmentOnly: true };
2493
+ }
2494
+ if (wordTokens.length === segment.wordTokens.length)
2495
+ return segment;
2496
+ return {
2497
+ ...segment,
2498
+ cmd: words[0] || segment.cmd,
2499
+ args: words.slice(1),
2500
+ words,
2501
+ wordTokens,
2502
+ env,
2503
+ script: words.map(quoteShellWord).join(' '),
2504
+ };
2505
+ }
2506
+ async withTemporaryMode(mode, fn) {
2507
+ const previous = this.modeState;
2508
+ const next = this.normalizeModeForAgent(mode);
2509
+ this.modeState = next;
2510
+ this.mode = next.name;
2511
+ try {
2512
+ return await fn();
2513
+ }
2514
+ finally {
2515
+ this.modeState = previous;
2516
+ this.mode = previous.name;
2517
+ }
2518
+ }
2519
+ async emitShellOutput(result, extra = {}) {
2520
+ if (result.stdout)
2521
+ await this.emit({ type: 'process', status: 'output', text: result.stdout, stream: 'stdout', ...extra });
2522
+ if (result.stderr)
2523
+ await this.emit({ type: 'process', status: 'output', text: result.stderr, stream: 'stderr', ...extra });
2524
+ }
2525
+ boundRuntimeEvent(event) {
2526
+ if (event.type !== 'process' || event.status !== 'output' || typeof event.text !== 'string')
2527
+ return event;
2528
+ const compact = compactShellOutputEventText(event.text);
2529
+ if (!compact.truncated)
2530
+ return event;
2531
+ return {
2532
+ ...event,
2533
+ text: compact.text,
2534
+ truncated: true,
2535
+ originalChars: compact.originalChars,
2536
+ omittedChars: compact.omittedChars,
2537
+ previewChars: compact.previewChars,
2538
+ tailChars: compact.tailChars,
2539
+ };
2540
+ }
2541
+ boundShellResult(result) {
2542
+ const next = { ...result };
2543
+ let truncated = result.truncated === true;
2544
+ const stdout = typeof result.stdout === 'string' ? compactShellCaptureText(result.stdout) : undefined;
2545
+ const stderr = typeof result.stderr === 'string' ? compactShellCaptureText(result.stderr) : undefined;
2546
+ if (stdout) {
2547
+ next.stdout = stdout.text;
2548
+ if (stdout.truncated || isCompactedShellText(stdout.text)) {
2549
+ truncated = true;
2550
+ next.stdoutTruncated = true;
2551
+ if (stdout.truncated) {
2552
+ next.stdoutOriginalChars = stdout.originalChars;
2553
+ next.stdoutOmittedChars = stdout.omittedChars;
2554
+ next.stdoutTailChars = stdout.tailChars;
2555
+ }
2556
+ }
2557
+ }
2558
+ if (stderr) {
2559
+ next.stderr = stderr.text;
2560
+ if (stderr.truncated || isCompactedShellText(stderr.text)) {
2561
+ truncated = true;
2562
+ next.stderrTruncated = true;
2563
+ if (stderr.truncated) {
2564
+ next.stderrOriginalChars = stderr.originalChars;
2565
+ next.stderrOmittedChars = stderr.omittedChars;
2566
+ next.stderrTailChars = stderr.tailChars;
2567
+ }
2568
+ }
2569
+ }
2570
+ if (truncated)
2571
+ next.truncated = true;
2572
+ return next;
2573
+ }
2574
+ boundShellReceiptOutput(output) {
2575
+ if (!output || typeof output !== 'object' || Array.isArray(output))
2576
+ return output;
2577
+ const record = output;
2578
+ const next = { ...record };
2579
+ let truncated = record.truncated === true;
2580
+ for (const field of ['stdout', 'stderr', 'text']) {
2581
+ if (typeof record[field] !== 'string')
2582
+ continue;
2583
+ const compact = compactShellCaptureText(record[field]);
2584
+ next[field] = compact.text;
2585
+ if (compact.truncated || isCompactedShellText(compact.text)) {
2586
+ truncated = true;
2587
+ next[`${field}Truncated`] = true;
2588
+ if (compact.truncated) {
2589
+ next[`${field}OriginalChars`] = compact.originalChars;
2590
+ next[`${field}OmittedChars`] = compact.omittedChars;
2591
+ next[`${field}TailChars`] = compact.tailChars;
2592
+ }
2593
+ }
2594
+ }
2595
+ if (truncated)
2596
+ next.truncated = true;
2597
+ return next;
2598
+ }
2599
+ async applyRedirect(result, redirect, by) {
2600
+ if (!redirect)
2601
+ return result;
2602
+ const stdout = typeof result.stdout === 'string' ? result.stdout : '';
2603
+ const stderr = typeof result.stderr === 'string' ? result.stderr : '';
2604
+ try {
2605
+ const redirected = await this.applyRedirectedStreams(stdout, stderr, redirect, by);
2606
+ if (!redirected.ok)
2607
+ return { ...result, status: 'rejected', code: 126, stdout: '', stderr: redirected.stderr };
2608
+ const next = { ...result };
2609
+ next.stdout = redirected.stdout;
2610
+ next.stderr = redirected.stderr;
2611
+ return next;
2612
+ }
2613
+ catch (error) {
2614
+ const stderr = error instanceof Error ? error.message : String(error);
2615
+ return { ...result, status: 'rejected', code: 126, stdout: '', stderr };
2616
+ }
2617
+ }
2618
+ async command(name, input = {}, options = {}) {
2619
+ const command = this.runtime.commands.get(name);
2620
+ if (!command)
2621
+ throw new Error(`command not found: ${name}`);
2622
+ if (command.agent && command.agent !== this.agentName)
2623
+ throw new Error(`command ${name} belongs to agent ${command.agent}`);
2624
+ const by = normalizeBy(options.by || (typeof input.by === 'string' ? input.by : undefined));
2625
+ const toolName = `command:${name}`;
2626
+ const before = await this.runHook('tool.before', { tool: toolName, command: name, input, by });
2627
+ if (before.rejected) {
2628
+ await this.trace('tool.rejected', { name, input, by, reason: before.reason, hook: 'tool.before' });
2629
+ if (before.waitingApproval)
2630
+ return { status: 'waiting_approval', approval: before.approval, reason: before.reason };
2631
+ throw new Error(before.reason || `tool.before rejected ${toolName}`);
2632
+ }
2633
+ const currentInput = recordField(before.current, 'input') || input;
2634
+ const authority = decideTool({ mode: this.modeState, tool: toolName, command: name });
2635
+ if (!authority.ok) {
2636
+ await this.trace('tool.rejected', { name, input: currentInput, by, reason: authority.reason, mode: this.mode });
2637
+ throw new Error(authority.reason);
2638
+ }
2639
+ const toolUseId = id('tool');
2640
+ const ctx = this.runtime.runtimeContext(this, { kind: 'command', name, input: currentInput, args: currentInput });
2641
+ await this.emit({ type: 'tool', status: 'requested', tool: toolName, input: currentInput, opId: toolUseId });
2642
+ await this.trace('tool.started', { name, input: currentInput, by, toolUseId });
2643
+ let result;
2644
+ if (command.kind === 'shell') {
2645
+ const argv = stringArrayField(currentInput, 'argv') || [];
2646
+ const invocationMode = command.readonly === true
2647
+ ? 'readonly'
2648
+ : typeof command.mode === 'string'
2649
+ ? command.mode
2650
+ : undefined;
2651
+ const runShell = () => this.withTemporaryShellState({ env: positionalShellEnv(name, argv) }, () => this.execInternal(String(command.script ?? ''), { by: options.by || `command:${name}` }));
2652
+ result = invocationMode ? await this.withTemporaryMode(invocationMode, runShell) : await runShell();
2653
+ }
2654
+ else if (command.kind === 'markdown') {
2655
+ const args = Object.keys(currentInput).length ? `\n\nInput:\n${JSON.stringify(currentInput, null, 2)}` : '';
2656
+ const invocationMode = command.readonly === true
2657
+ ? 'readonly'
2658
+ : typeof command.mode === 'string'
2659
+ ? command.mode
2660
+ : undefined;
2661
+ const runMarkdown = () => this.run(`${String(command.prompt ?? '')}${args}`, { by: options.by || `command:${name}`, model: typeof command.model === 'string' ? command.model : undefined });
2662
+ result = invocationMode ? await this.withTemporaryMode(invocationMode, runMarkdown) : await runMarkdown();
2663
+ }
2664
+ else {
2665
+ if (!command.handler)
2666
+ throw new Error(`command has no handler: ${name}`);
2667
+ const handler = command.handler;
2668
+ result = await this.withFsActor(options.by ? by : `command:${name}`, () => Promise.resolve(handler(ctx, currentInput)));
2669
+ }
2670
+ const after = await this.runHook('tool.after', { tool: toolName, command: name, input: currentInput, result, by });
2671
+ if (after.rejected) {
2672
+ await this.trace('tool.output_rejected', { name, by, reason: after.reason, hook: 'tool.after' });
2673
+ if (after.waitingApproval)
2674
+ return { status: 'waiting_approval', approval: after.approval, reason: after.reason };
2675
+ throw new Error(after.reason || `tool.after rejected ${toolName}`);
2676
+ }
2677
+ result = 'result' in after.current ? after.current.result : result;
2678
+ await this.trace('tool.ended', { name, resultType: resultType(result), toolUseId });
2679
+ await this.emit({ type: 'tool', status: 'completed', tool: toolName, result, opId: toolUseId });
2680
+ return result;
2681
+ }
2682
+ async processes() {
2683
+ return this.vprocess.processes();
2684
+ }
2685
+ async jobs() {
2686
+ return this.vprocess.jobs();
2687
+ }
2688
+ remoteCwd() {
2689
+ return this.runtime.realPath(this.cwd);
2690
+ }
2691
+ hookCwd() {
2692
+ return this.kstate.realRootPath(this.cwd) || this.cwd;
2693
+ }
2694
+ shellPath(path) {
2695
+ if (!path)
2696
+ return this.cwd;
2697
+ if (path === '~' || path.startsWith('~/') || path.startsWith('/'))
2698
+ return path;
2699
+ return `${this.cwd.replace(/\/$/, '')}/${path}`;
2700
+ }
2701
+ async appendMessage(message) {
2702
+ await this.assertWritableSession();
2703
+ return this.messageLog.append(message);
2704
+ }
2705
+ async messagesForModel() {
2706
+ return this.messageLog.forModel();
2707
+ }
2708
+ get messages() {
2709
+ return this.messageLog.view();
2710
+ }
2711
+ async checkpoint({ label } = {}) {
2712
+ await this.assertWritableSession();
2713
+ const traceRows = await this.kstate.trace(this.sessionId);
2714
+ const messages = await this.kstate.readSessionJsonl(this.sessionId, 'messages.full.jsonl');
2715
+ const checkpoint = {
2716
+ id: id('checkpoint'),
2717
+ label,
2718
+ createdAt: now(),
2719
+ sessionId: this.sessionId,
2720
+ traceSeq: traceRows.at(-1)?.traceSeq ?? -1,
2721
+ messageSeq: messages.at(-1)?.seq ?? -1,
2722
+ cwd: this.cwd,
2723
+ env: { ...this.shellEnv },
2724
+ shell: this.shellSessionRecord(),
2725
+ lastExitCode: this.lastExitCode,
2726
+ mode: this.mode,
2727
+ scope: this.modeState.scope,
2728
+ system: this.system,
2729
+ };
2730
+ await this.assertWritableSession();
2731
+ await this.kstate.writeSessionJson(this.sessionId, `checkpoints/${checkpoint.id}.json`, checkpoint);
2732
+ await this.trace('checkpoint.created', checkpoint);
2733
+ return checkpoint;
2734
+ }
2735
+ async fork({ from, mode = this.mode, scope, disk = 'shared', hooks = true } = {}) {
2736
+ await this.assertWritableSession();
2737
+ const forkId = id(`${this.sessionId}.fork`);
2738
+ const forkMode = scope
2739
+ ? { name: typeof mode === 'string' ? mode : mode.name, scope }
2740
+ : mode;
2741
+ const forked = new AgentSession({
2742
+ computer: this.runtime,
2743
+ agentName: this.agentName,
2744
+ sessionId: forkId,
2745
+ mode: forkMode,
2746
+ disk,
2747
+ });
2748
+ try {
2749
+ await forked.init();
2750
+ if (hooks)
2751
+ this.runtime.installHooks(forked);
2752
+ const checkpoint = typeof from === 'string'
2753
+ ? await this.kstate.readSessionJson(this.sessionId, `checkpoints/${from}.json`, undefined)
2754
+ : from;
2755
+ await this.assertWritableSession();
2756
+ const maxSeq = typeof checkpoint?.messageSeq === 'number' ? checkpoint.messageSeq : undefined;
2757
+ for (const name of ['messages.full.jsonl', 'messages.current.jsonl']) {
2758
+ const rows = await this.kstate.readSessionJsonl(this.sessionId, name);
2759
+ const selected = maxSeq === undefined ? rows : rows.filter((row) => (row.seq ?? -1) <= maxSeq);
2760
+ await this.assertWritableSession();
2761
+ await this.kstate.writeSessionJsonl(forkId, name, selected);
2762
+ }
2763
+ const modeState = forked.normalizeModeForAgent(forkMode);
2764
+ forked.modeState = modeState;
2765
+ forked.mode = modeState.name;
2766
+ forked.cwd = typeof checkpoint?.cwd === 'string' ? checkpoint.cwd : this.cwd;
2767
+ forked.shellEnv = recordStringMap(recordField(checkpoint || {}, 'env') || this.shellEnv);
2768
+ const checkpointShell = recordField(checkpoint || {}, 'shell') || this.shellSessionRecord();
2769
+ forked.shellOptions = normalizeShellOptions(recordField(checkpointShell, 'options'));
2770
+ forked.shellFunctions = recordStringMap(recordField(checkpointShell, 'functions'));
2771
+ forked.lastExitCode = typeof checkpoint?.lastExitCode === 'number' ? checkpoint.lastExitCode : this.lastExitCode;
2772
+ forked.system = typeof checkpoint?.system === 'string' ? checkpoint.system : this.system;
2773
+ await this.assertWritableSession();
2774
+ await forked.saveSession({
2775
+ parentSessionId: this.sessionId,
2776
+ disk,
2777
+ forkedFrom: checkpoint?.id,
2778
+ mode: modeState.name,
2779
+ scope: modeState.scope,
2780
+ cwd: forked.cwd,
2781
+ env: forked.shellEnv,
2782
+ shell: forked.shellSessionRecord(),
2783
+ lastExitCode: forked.lastExitCode,
2784
+ system: forked.system,
2785
+ hooks: hooks ? 'inherit' : 'none',
2786
+ });
2787
+ await this.assertWritableSession();
2788
+ this.runtime.sessions.set(`${this.agentName}\0${forkId}`, forked);
2789
+ await this.trace('session.forked', { childSessionId: forkId, mode: modeState.name, scope: modeState.scope, disk, from: checkpoint?.id });
2790
+ return forked;
2791
+ }
2792
+ catch (error) {
2793
+ this.runtime.sessions.delete(`${this.agentName}\0${forkId}`);
2794
+ await forked.abandonStartup().catch(() => undefined);
2795
+ await this.kstate.removeSession(forkId).catch(() => undefined);
2796
+ throw error;
2797
+ }
2798
+ }
2799
+ async sealUnfinishedTools() {
2800
+ return sealUnfinishedTools(this);
2801
+ }
2802
+ async sealUnfinishedToolEvents() {
2803
+ return sealUnfinishedToolEvents(this);
2804
+ }
2805
+ async sealInterruptedTurns() {
2806
+ return sealInterruptedTurns(this);
2807
+ }
2808
+ }
2809
+ function parseShellGroup(script) {
2810
+ const text = script.trim();
2811
+ if (!text.startsWith('(') || !text.endsWith(')'))
2812
+ return undefined;
2813
+ let quote;
2814
+ let escaping = false;
2815
+ let substitutionDepth = 0;
2816
+ let depth = 0;
2817
+ for (let index = 0; index < text.length; index += 1) {
2818
+ const char = text[index];
2819
+ const next = text[index + 1];
2820
+ if (escaping) {
2821
+ escaping = false;
2822
+ continue;
2823
+ }
2824
+ if (char === '\\' && quote !== "'") {
2825
+ escaping = true;
2826
+ continue;
2827
+ }
2828
+ if (quote) {
2829
+ if (char === quote)
2830
+ quote = undefined;
2831
+ continue;
2832
+ }
2833
+ if (char === '"' || char === "'") {
2834
+ quote = char;
2835
+ continue;
2836
+ }
2837
+ if (char === '$' && next === '(') {
2838
+ substitutionDepth += 1;
2839
+ index += 1;
2840
+ continue;
2841
+ }
2842
+ if (substitutionDepth > 0) {
2843
+ if (char === '(')
2844
+ substitutionDepth += 1;
2845
+ else if (char === ')')
2846
+ substitutionDepth -= 1;
2847
+ continue;
2848
+ }
2849
+ if (char === '(')
2850
+ depth += 1;
2851
+ else if (char === ')') {
2852
+ depth -= 1;
2853
+ if (depth === 0 && index !== text.length - 1)
2854
+ return undefined;
2855
+ if (depth < 0)
2856
+ return undefined;
2857
+ }
2858
+ }
2859
+ return depth === 0 ? text.slice(1, -1).trim() : undefined;
2860
+ }
2861
+ function isFsEffectTrace(type) {
2862
+ return type === 'fs.write'
2863
+ || type === 'fs.append'
2864
+ || type === 'fs.mkdir'
2865
+ || type === 'fs.rm'
2866
+ || type === 'fs.rename'
2867
+ || type === 'fs.symlink'
2868
+ || type === 'fs.chmod'
2869
+ || type === 'fs.utime';
2870
+ }
2871
+ function fsReceiptTarget(type, data) {
2872
+ if (type === 'fs.rename')
2873
+ return `${String(data.from || '')} -> ${String(data.to || '')}`;
2874
+ return String(data.path || '');
2875
+ }
2876
+ function fsReceiptInput(type, data) {
2877
+ if (type === 'fs.write')
2878
+ return { path: data.path, bytes: data.bytes };
2879
+ if (type === 'fs.append')
2880
+ return { path: data.path, bytes: data.bytes };
2881
+ if (type === 'fs.rename')
2882
+ return { from: data.from, to: data.to };
2883
+ if (type === 'fs.rm')
2884
+ return { path: data.path, recursive: data.recursive, force: data.force };
2885
+ if (type === 'fs.symlink')
2886
+ return { path: data.path, target: data.target };
2887
+ if (type === 'fs.chmod')
2888
+ return { path: data.path, mode: data.mode };
2889
+ if (type === 'fs.utime')
2890
+ return { path: data.path, mtime: data.mtime };
2891
+ return { path: data.path };
2892
+ }
2893
+ function modelProviderSummary(provider) {
2894
+ if (!provider)
2895
+ return undefined;
2896
+ const summary = {};
2897
+ for (const key of ['name', 'model', 'requestId', 'cacheRef']) {
2898
+ const value = provider[key];
2899
+ if (typeof value === 'string')
2900
+ summary[key] = value;
2901
+ }
2902
+ return Object.keys(summary).length ? summary : undefined;
2903
+ }
2904
+ function countHandlerMap(map) {
2905
+ let total = 0;
2906
+ for (const handlers of map.values())
2907
+ total += handlers.length;
2908
+ return total;
2909
+ }
2910
+ function appendBoundedShellCapture(current, next) {
2911
+ if (!next)
2912
+ return current;
2913
+ return compactShellCaptureText(`${current}${next}`).text;
2914
+ }
2915
+ function compactShellCaptureText(text) {
2916
+ return compactText(String(text), MAX_SHELL_CAPTURE_CHARS, 0);
2917
+ }
2918
+ function compactShellOutputEventText(text) {
2919
+ return compactText(String(text), MAX_SHELL_OUTPUT_EVENT_CHARS, SHELL_OUTPUT_EVENT_PREVIEW_CHARS);
2920
+ }
2921
+ function compactText(text, limit, requestedPreviewChars) {
2922
+ const originalChars = text.length;
2923
+ if (originalChars <= limit) {
2924
+ return { text, truncated: false, originalChars, omittedChars: 0, previewChars: requestedPreviewChars || undefined, tailChars: originalChars };
2925
+ }
2926
+ const previewChars = Math.min(requestedPreviewChars, Math.max(0, limit));
2927
+ let tailChars = Math.max(0, limit - previewChars);
2928
+ let omittedChars = Math.max(0, originalChars - previewChars - tailChars);
2929
+ let marker = truncationMarker(omittedChars);
2930
+ for (let attempt = 0; attempt < 4; attempt += 1) {
2931
+ tailChars = Math.max(0, limit - previewChars - marker.length);
2932
+ omittedChars = Math.max(0, originalChars - previewChars - tailChars);
2933
+ const nextMarker = truncationMarker(omittedChars);
2934
+ if (nextMarker.length === marker.length) {
2935
+ marker = nextMarker;
2936
+ break;
2937
+ }
2938
+ marker = nextMarker;
2939
+ }
2940
+ if (marker.length >= limit) {
2941
+ const tail = text.slice(-limit);
2942
+ return { text: tail, truncated: true, originalChars, omittedChars: originalChars - tail.length, tailChars: tail.length };
2943
+ }
2944
+ tailChars = Math.max(0, limit - previewChars - marker.length);
2945
+ omittedChars = Math.max(0, originalChars - previewChars - tailChars);
2946
+ const preview = previewChars > 0 ? text.slice(0, previewChars) : '';
2947
+ const tail = tailChars > 0 ? text.slice(-tailChars) : '';
2948
+ return {
2949
+ text: `${preview}${truncationMarker(omittedChars)}${tail}`,
2950
+ truncated: true,
2951
+ originalChars,
2952
+ omittedChars,
2953
+ previewChars: previewChars || undefined,
2954
+ tailChars,
2955
+ };
2956
+ }
2957
+ function truncationMarker(omittedChars) {
2958
+ return `\n...[truncated ${omittedChars} chars]...\n`;
2959
+ }
2960
+ function isCompactedShellText(text) {
2961
+ return /\n\.\.\.\[truncated \d+ chars\]\.\.\.\n/.test(text);
2962
+ }
2963
+ function commandErrorText(command, error) {
2964
+ const message = error instanceof Error ? error.message : String(error);
2965
+ return message.startsWith(`${command}:`) ? message : `${command}: ${message}`;
2966
+ }
2967
+ function isHelpArg(value) {
2968
+ return value === '--help' || value === '-h';
2969
+ }
2970
+ function isExpiredWorker(worker) {
2971
+ const expiresAt = typeof worker.expiresAt === 'string' ? Date.parse(worker.expiresAt) : Number.NaN;
2972
+ return Number.isFinite(expiresAt) && expiresAt <= Date.now();
2973
+ }
2974
+ function contentKind(block) {
2975
+ if (block.type === 'text')
2976
+ return 'text';
2977
+ if (block.type === 'reasoning' || block.type === 'thinking')
2978
+ return 'reasoning';
2979
+ if (block.type === 'image')
2980
+ return 'image';
2981
+ if (block.type === 'audio')
2982
+ return 'audio';
2983
+ if (block.type === 'video')
2984
+ return 'video';
2985
+ return 'data';
2986
+ }
2987
+ function processStatus(status, code) {
2988
+ if (status === 'uncertain')
2989
+ return 'uncertain';
2990
+ if (status === 'cancelled' || status === 'aborted')
2991
+ return 'cancelled';
2992
+ if (status === 'failed' || status === 'rejected')
2993
+ return 'failed';
2994
+ if (typeof code === 'number')
2995
+ return code === 0 ? 'completed' : 'failed';
2996
+ return status === 'committed' || status === 'completed' ? 'completed' : 'failed';
2997
+ }
2998
+ function turnStatus(status) {
2999
+ if (status === 'queued')
3000
+ return 'queued';
3001
+ if (status === 'running')
3002
+ return 'running';
3003
+ if (status === 'waiting_approval')
3004
+ return 'waiting_approval';
3005
+ if (status === 'blocked')
3006
+ return 'blocked';
3007
+ if (status === 'failed')
3008
+ return 'failed';
3009
+ if (status === 'cancelled')
3010
+ return 'cancelled';
3011
+ return 'completed';
3012
+ }
3013
+ function shouldExpandBraces(token) {
3014
+ let hasUnquotedBrace = false;
3015
+ for (const part of token.parts) {
3016
+ if (!part.text.includes('{') && !part.text.includes('}'))
3017
+ continue;
3018
+ if (part.quote)
3019
+ return false;
3020
+ hasUnquotedBrace = true;
3021
+ }
3022
+ return hasUnquotedBrace;
3023
+ }
3024
+ function updatePathList(current, entry, side) {
3025
+ const normalized = entry.replace(/\/+$/, '') || '/';
3026
+ const parts = String(current || '').split(':').filter(Boolean).filter((part) => part !== normalized);
3027
+ return side === 'prepend'
3028
+ ? [normalized, ...parts].join(':')
3029
+ : [...parts, normalized].join(':');
3030
+ }
3031
+ //# sourceMappingURL=agent-session.js.map