@animalabs/agent-framework 0.21.1 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/README.md +54 -4
  2. package/dist/src/agent.d.ts +17 -4
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +105 -43
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +41 -4
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/code-execution/py-runner.d.ts +22 -1
  10. package/dist/src/code-execution/py-runner.d.ts.map +1 -1
  11. package/dist/src/code-execution/py-runner.js +64 -9
  12. package/dist/src/code-execution/py-runner.js.map +1 -1
  13. package/dist/src/code-execution/runtime-py.d.ts +4 -0
  14. package/dist/src/code-execution/runtime-py.d.ts.map +1 -1
  15. package/dist/src/code-execution/runtime-py.js +215 -38
  16. package/dist/src/code-execution/runtime-py.js.map +1 -1
  17. package/dist/src/framework.d.ts +569 -59
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +3091 -542
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/gate/event-gate.d.ts +21 -0
  22. package/dist/src/gate/event-gate.d.ts.map +1 -1
  23. package/dist/src/gate/event-gate.js +40 -7
  24. package/dist/src/gate/event-gate.js.map +1 -1
  25. package/dist/src/index.d.ts +7 -2
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +4 -0
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  30. package/dist/src/mcpl/channel-registry.js +26 -3
  31. package/dist/src/mcpl/channel-registry.js.map +1 -1
  32. package/dist/src/mcpl/conversation-router.d.ts +28 -1
  33. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  34. package/dist/src/mcpl/conversation-router.js +17 -0
  35. package/dist/src/mcpl/conversation-router.js.map +1 -1
  36. package/dist/src/mcpl/feature-set-manager.d.ts +11 -0
  37. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  38. package/dist/src/mcpl/feature-set-manager.js +31 -7
  39. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  40. package/dist/src/mcpl/index.d.ts +4 -0
  41. package/dist/src/mcpl/index.d.ts.map +1 -1
  42. package/dist/src/mcpl/index.js +1 -0
  43. package/dist/src/mcpl/index.js.map +1 -1
  44. package/dist/src/mcpl/prose-stream-router.d.ts +27 -6
  45. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -1
  46. package/dist/src/mcpl/prose-stream-router.js +85 -22
  47. package/dist/src/mcpl/prose-stream-router.js.map +1 -1
  48. package/dist/src/mcpl/push-coalescer.d.ts +32 -4
  49. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  50. package/dist/src/mcpl/push-coalescer.js +46 -13
  51. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  52. package/dist/src/mcpl/push-handler.d.ts +5 -3
  53. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  54. package/dist/src/mcpl/push-handler.js +41 -11
  55. package/dist/src/mcpl/push-handler.js.map +1 -1
  56. package/dist/src/mcpl/reference-fetcher.d.ts +2 -1
  57. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  58. package/dist/src/mcpl/reference-fetcher.js +20 -9
  59. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  60. package/dist/src/mcpl/references.d.ts +29 -6
  61. package/dist/src/mcpl/references.d.ts.map +1 -1
  62. package/dist/src/mcpl/references.js +57 -16
  63. package/dist/src/mcpl/references.js.map +1 -1
  64. package/dist/src/mcpl/server-connection.d.ts +32 -1
  65. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  66. package/dist/src/mcpl/server-connection.js +76 -11
  67. package/dist/src/mcpl/server-connection.js.map +1 -1
  68. package/dist/src/mcpl/server-registry.d.ts +5 -2
  69. package/dist/src/mcpl/server-registry.d.ts.map +1 -1
  70. package/dist/src/mcpl/server-registry.js +25 -7
  71. package/dist/src/mcpl/server-registry.js.map +1 -1
  72. package/dist/src/mcpl/tool-lifecycle.d.ts +52 -2
  73. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  74. package/dist/src/mcpl/tool-lifecycle.js +47 -1
  75. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  76. package/dist/src/mcpl/tool-pattern-check.d.ts +51 -0
  77. package/dist/src/mcpl/tool-pattern-check.d.ts.map +1 -0
  78. package/dist/src/mcpl/tool-pattern-check.js +79 -0
  79. package/dist/src/mcpl/tool-pattern-check.js.map +1 -0
  80. package/dist/src/mcpl/types.d.ts +15 -5
  81. package/dist/src/mcpl/types.d.ts.map +1 -1
  82. package/dist/src/mcpl/types.js.map +1 -1
  83. package/dist/src/mcpl/visible-content.d.ts +39 -0
  84. package/dist/src/mcpl/visible-content.d.ts.map +1 -0
  85. package/dist/src/mcpl/visible-content.js +54 -0
  86. package/dist/src/mcpl/visible-content.js.map +1 -0
  87. package/dist/src/module-registry.d.ts +90 -6
  88. package/dist/src/module-registry.d.ts.map +1 -1
  89. package/dist/src/module-registry.js +260 -18
  90. package/dist/src/module-registry.js.map +1 -1
  91. package/dist/src/modules/workspace/branch-intent.d.ts +72 -0
  92. package/dist/src/modules/workspace/branch-intent.d.ts.map +1 -0
  93. package/dist/src/modules/workspace/branch-intent.js +83 -0
  94. package/dist/src/modules/workspace/branch-intent.js.map +1 -0
  95. package/dist/src/modules/workspace/disk-agreement.d.ts +167 -0
  96. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -0
  97. package/dist/src/modules/workspace/disk-agreement.js +259 -0
  98. package/dist/src/modules/workspace/disk-agreement.js.map +1 -0
  99. package/dist/src/modules/workspace/effects.d.ts +84 -0
  100. package/dist/src/modules/workspace/effects.d.ts.map +1 -0
  101. package/dist/src/modules/workspace/effects.js +368 -0
  102. package/dist/src/modules/workspace/effects.js.map +1 -0
  103. package/dist/src/modules/workspace/index.d.ts +155 -17
  104. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  105. package/dist/src/modules/workspace/index.js +1101 -424
  106. package/dist/src/modules/workspace/index.js.map +1 -1
  107. package/dist/src/modules/workspace/observe.d.ts +288 -0
  108. package/dist/src/modules/workspace/observe.d.ts.map +1 -0
  109. package/dist/src/modules/workspace/observe.js +825 -0
  110. package/dist/src/modules/workspace/observe.js.map +1 -0
  111. package/dist/src/modules/workspace/reconcile.d.ts +238 -0
  112. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -0
  113. package/dist/src/modules/workspace/reconcile.js +909 -0
  114. package/dist/src/modules/workspace/reconcile.js.map +1 -0
  115. package/dist/src/modules/workspace/sync.d.ts +6 -59
  116. package/dist/src/modules/workspace/sync.d.ts.map +1 -1
  117. package/dist/src/modules/workspace/sync.js +6 -257
  118. package/dist/src/modules/workspace/sync.js.map +1 -1
  119. package/dist/src/modules/workspace/types.d.ts +77 -10
  120. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  121. package/dist/src/modules/workspace/types.js.map +1 -1
  122. package/dist/src/operator-log.d.ts +41 -0
  123. package/dist/src/operator-log.d.ts.map +1 -1
  124. package/dist/src/operator-log.js.map +1 -1
  125. package/dist/src/provider-waits.d.ts +216 -0
  126. package/dist/src/provider-waits.d.ts.map +1 -0
  127. package/dist/src/provider-waits.js +482 -0
  128. package/dist/src/provider-waits.js.map +1 -0
  129. package/dist/src/record-journal.d.ts +127 -0
  130. package/dist/src/record-journal.d.ts.map +1 -0
  131. package/dist/src/record-journal.js +202 -0
  132. package/dist/src/record-journal.js.map +1 -0
  133. package/dist/src/refusal-reactions.d.ts +14 -12
  134. package/dist/src/refusal-reactions.d.ts.map +1 -1
  135. package/dist/src/refusal-reactions.js +14 -12
  136. package/dist/src/refusal-reactions.js.map +1 -1
  137. package/dist/src/shutdown.d.ts +13 -0
  138. package/dist/src/shutdown.d.ts.map +1 -0
  139. package/dist/src/shutdown.js +46 -0
  140. package/dist/src/shutdown.js.map +1 -0
  141. package/dist/src/silent-heartbeat.d.ts +74 -0
  142. package/dist/src/silent-heartbeat.d.ts.map +1 -0
  143. package/dist/src/silent-heartbeat.js +77 -0
  144. package/dist/src/silent-heartbeat.js.map +1 -0
  145. package/dist/src/tool-presentation.d.ts +50 -0
  146. package/dist/src/tool-presentation.d.ts.map +1 -0
  147. package/dist/src/tool-presentation.js +235 -0
  148. package/dist/src/tool-presentation.js.map +1 -0
  149. package/dist/src/tool-result-guard.d.ts +142 -6
  150. package/dist/src/tool-result-guard.d.ts.map +1 -1
  151. package/dist/src/tool-result-guard.js +357 -36
  152. package/dist/src/tool-result-guard.js.map +1 -1
  153. package/dist/src/tool-result-history.d.ts +13 -0
  154. package/dist/src/tool-result-history.d.ts.map +1 -1
  155. package/dist/src/tool-result-history.js +21 -1
  156. package/dist/src/tool-result-history.js.map +1 -1
  157. package/dist/src/types/agent.d.ts +18 -2
  158. package/dist/src/types/agent.d.ts.map +1 -1
  159. package/dist/src/types/events.d.ts +16 -0
  160. package/dist/src/types/events.d.ts.map +1 -1
  161. package/dist/src/types/framework.d.ts +12 -0
  162. package/dist/src/types/framework.d.ts.map +1 -1
  163. package/dist/src/types/module.d.ts +26 -4
  164. package/dist/src/types/module.d.ts.map +1 -1
  165. package/dist/src/types/trace.d.ts +87 -3
  166. package/dist/src/types/trace.d.ts.map +1 -1
  167. package/dist/test/batched-wake-locus.test.d.ts +2 -0
  168. package/dist/test/batched-wake-locus.test.d.ts.map +1 -0
  169. package/dist/test/batched-wake-locus.test.js +166 -0
  170. package/dist/test/batched-wake-locus.test.js.map +1 -0
  171. package/dist/test/code-execution.test.js +386 -2
  172. package/dist/test/code-execution.test.js.map +1 -1
  173. package/dist/test/component-defaults.test.d.ts +2 -0
  174. package/dist/test/component-defaults.test.d.ts.map +1 -0
  175. package/dist/test/component-defaults.test.js +54 -0
  176. package/dist/test/component-defaults.test.js.map +1 -0
  177. package/dist/test/conversation-routing.test.js +57 -1
  178. package/dist/test/conversation-routing.test.js.map +1 -1
  179. package/dist/test/discord-awareness-outbox.test.js +3 -0
  180. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  181. package/dist/test/ephemeral-settle.test.js +3 -0
  182. package/dist/test/ephemeral-settle.test.js.map +1 -1
  183. package/dist/test/event-gate.test.js +118 -0
  184. package/dist/test/event-gate.test.js.map +1 -1
  185. package/dist/test/explicit-prose-routing.test.js +975 -0
  186. package/dist/test/explicit-prose-routing.test.js.map +1 -1
  187. package/dist/test/framework.test.js +160 -1
  188. package/dist/test/framework.test.js.map +1 -1
  189. package/dist/test/helpers/coalescing-fixture.d.ts.map +1 -1
  190. package/dist/test/helpers/coalescing-fixture.js +3 -2
  191. package/dist/test/helpers/coalescing-fixture.js.map +1 -1
  192. package/dist/test/helpers/pytc-stop-rows.d.ts +11 -0
  193. package/dist/test/helpers/pytc-stop-rows.d.ts.map +1 -0
  194. package/dist/test/helpers/pytc-stop-rows.js +143 -0
  195. package/dist/test/helpers/pytc-stop-rows.js.map +1 -0
  196. package/dist/test/inference-exhaustion-rewind.test.js +142 -14
  197. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -1
  198. package/dist/test/inference-failure-bounds.test.d.ts +2 -0
  199. package/dist/test/inference-failure-bounds.test.d.ts.map +1 -0
  200. package/dist/test/inference-failure-bounds.test.js +1201 -0
  201. package/dist/test/inference-failure-bounds.test.js.map +1 -0
  202. package/dist/test/late-events-after-stop.test.d.ts +2 -0
  203. package/dist/test/late-events-after-stop.test.d.ts.map +1 -0
  204. package/dist/test/late-events-after-stop.test.js +122 -0
  205. package/dist/test/late-events-after-stop.test.js.map +1 -0
  206. package/dist/test/mcpl-awareness-barrier.test.js +141 -0
  207. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  208. package/dist/test/mcpl-coalescing.test.js +97 -22
  209. package/dist/test/mcpl-coalescing.test.js.map +1 -1
  210. package/dist/test/mcpl-config-diagnostics.test.d.ts +2 -0
  211. package/dist/test/mcpl-config-diagnostics.test.d.ts.map +1 -0
  212. package/dist/test/mcpl-config-diagnostics.test.js +452 -0
  213. package/dist/test/mcpl-config-diagnostics.test.js.map +1 -0
  214. package/dist/test/mcpl-empty-content.test.d.ts +2 -0
  215. package/dist/test/mcpl-empty-content.test.d.ts.map +1 -0
  216. package/dist/test/mcpl-empty-content.test.js +368 -0
  217. package/dist/test/mcpl-empty-content.test.js.map +1 -0
  218. package/dist/test/mcpl-manifest-changed.test.js +2 -1
  219. package/dist/test/mcpl-manifest-changed.test.js.map +1 -1
  220. package/dist/test/mcpl-reference-fetcher.test.js +232 -4
  221. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -1
  222. package/dist/test/mcpl-reference-fields.test.js +69 -2
  223. package/dist/test/mcpl-reference-fields.test.js.map +1 -1
  224. package/dist/test/mcpl-tool-lifecycle-emitter.test.js +106 -1
  225. package/dist/test/mcpl-tool-lifecycle-emitter.test.js.map +1 -1
  226. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts +2 -0
  227. package/dist/test/mcpl-tool-lifecycle-script.test.d.ts.map +1 -0
  228. package/dist/test/mcpl-tool-lifecycle-script.test.js +288 -0
  229. package/dist/test/mcpl-tool-lifecycle-script.test.js.map +1 -0
  230. package/dist/test/mcpl-ws-transport.test.js +103 -0
  231. package/dist/test/mcpl-ws-transport.test.js.map +1 -1
  232. package/dist/test/ops-alert.test.js +2 -1
  233. package/dist/test/ops-alert.test.js.map +1 -1
  234. package/dist/test/placed-reaction-baseline.test.d.ts +2 -0
  235. package/dist/test/placed-reaction-baseline.test.d.ts.map +1 -0
  236. package/dist/test/placed-reaction-baseline.test.js +312 -0
  237. package/dist/test/placed-reaction-baseline.test.js.map +1 -0
  238. package/dist/test/prose-stream-router.test.js +41 -0
  239. package/dist/test/prose-stream-router.test.js.map +1 -1
  240. package/dist/test/provider-acceleration-cooldown.test.js +5 -1
  241. package/dist/test/provider-acceleration-cooldown.test.js.map +1 -1
  242. package/dist/test/provider-waits.test.d.ts +2 -0
  243. package/dist/test/provider-waits.test.d.ts.map +1 -0
  244. package/dist/test/provider-waits.test.js +2196 -0
  245. package/dist/test/provider-waits.test.js.map +1 -0
  246. package/dist/test/reaction-baseline-platform.test.d.ts +2 -0
  247. package/dist/test/reaction-baseline-platform.test.d.ts.map +1 -0
  248. package/dist/test/reaction-baseline-platform.test.js +124 -0
  249. package/dist/test/reaction-baseline-platform.test.js.map +1 -0
  250. package/dist/test/record-journal.test.d.ts +6 -0
  251. package/dist/test/record-journal.test.d.ts.map +1 -0
  252. package/dist/test/record-journal.test.js +269 -0
  253. package/dist/test/record-journal.test.js.map +1 -0
  254. package/dist/test/refusal-reaction-baseline.test.js +10 -3
  255. package/dist/test/refusal-reaction-baseline.test.js.map +1 -1
  256. package/dist/test/save-recent-image.test.js +240 -2
  257. package/dist/test/save-recent-image.test.js.map +1 -1
  258. package/dist/test/shutdown-errors.test.d.ts +2 -0
  259. package/dist/test/shutdown-errors.test.d.ts.map +1 -0
  260. package/dist/test/shutdown-errors.test.js +662 -0
  261. package/dist/test/shutdown-errors.test.js.map +1 -0
  262. package/dist/test/silent-heartbeat-rows.test.d.ts +2 -0
  263. package/dist/test/silent-heartbeat-rows.test.d.ts.map +1 -0
  264. package/dist/test/silent-heartbeat-rows.test.js +451 -0
  265. package/dist/test/silent-heartbeat-rows.test.js.map +1 -0
  266. package/dist/test/silent-heartbeat.test.js +49 -38
  267. package/dist/test/silent-heartbeat.test.js.map +1 -1
  268. package/dist/test/stop-with-work-in-flight.test.d.ts +23 -0
  269. package/dist/test/stop-with-work-in-flight.test.d.ts.map +1 -0
  270. package/dist/test/stop-with-work-in-flight.test.js +672 -0
  271. package/dist/test/stop-with-work-in-flight.test.js.map +1 -0
  272. package/dist/test/surgery-marks-receipt.test.d.ts +2 -0
  273. package/dist/test/surgery-marks-receipt.test.d.ts.map +1 -0
  274. package/dist/test/surgery-marks-receipt.test.js +540 -0
  275. package/dist/test/surgery-marks-receipt.test.js.map +1 -0
  276. package/dist/test/tool-batch-hook.test.d.ts +8 -0
  277. package/dist/test/tool-batch-hook.test.d.ts.map +1 -0
  278. package/dist/test/tool-batch-hook.test.js +275 -0
  279. package/dist/test/tool-batch-hook.test.js.map +1 -0
  280. package/dist/test/tool-presentation.test.d.ts +2 -0
  281. package/dist/test/tool-presentation.test.d.ts.map +1 -0
  282. package/dist/test/tool-presentation.test.js +268 -0
  283. package/dist/test/tool-presentation.test.js.map +1 -0
  284. package/dist/test/tool-result-guard.test.js +2545 -19
  285. package/dist/test/tool-result-guard.test.js.map +1 -1
  286. package/dist/test/tool-result-history.test.js +2 -1
  287. package/dist/test/tool-result-history.test.js.map +1 -1
  288. package/dist/test/turn-trigger-provenance.test.js +35 -0
  289. package/dist/test/turn-trigger-provenance.test.js.map +1 -1
  290. package/dist/test/unconvertible-tool-result.test.d.ts +2 -0
  291. package/dist/test/unconvertible-tool-result.test.d.ts.map +1 -0
  292. package/dist/test/unconvertible-tool-result.test.js +358 -0
  293. package/dist/test/unconvertible-tool-result.test.js.map +1 -0
  294. package/dist/test/workspace-materialize-freshness.test.d.ts +16 -0
  295. package/dist/test/workspace-materialize-freshness.test.d.ts.map +1 -0
  296. package/dist/test/workspace-materialize-freshness.test.js +252 -0
  297. package/dist/test/workspace-materialize-freshness.test.js.map +1 -0
  298. package/dist/test/workspace-three-way.test.d.ts +12 -0
  299. package/dist/test/workspace-three-way.test.d.ts.map +1 -0
  300. package/dist/test/workspace-three-way.test.js +3194 -0
  301. package/dist/test/workspace-three-way.test.js.map +1 -0
  302. package/dist/tsconfig.tsbuildinfo +1 -1
  303. package/package.json +2 -2
  304. package/src/agent.ts +124 -45
  305. package/src/api/server.ts +43 -4
  306. package/src/code-execution/py-runner.ts +66 -9
  307. package/src/code-execution/runtime-py.ts +215 -38
  308. package/src/framework.ts +3191 -544
  309. package/src/gate/event-gate.ts +59 -7
  310. package/src/index.ts +18 -2
  311. package/src/mcpl/channel-registry.ts +24 -3
  312. package/src/mcpl/conversation-router.ts +34 -1
  313. package/src/mcpl/feature-set-manager.ts +36 -11
  314. package/src/mcpl/index.ts +4 -0
  315. package/src/mcpl/prose-stream-router.ts +79 -23
  316. package/src/mcpl/push-coalescer.ts +62 -13
  317. package/src/mcpl/push-handler.ts +41 -12
  318. package/src/mcpl/reference-fetcher.ts +18 -8
  319. package/src/mcpl/references.ts +57 -13
  320. package/src/mcpl/server-connection.ts +95 -13
  321. package/src/mcpl/server-registry.ts +24 -7
  322. package/src/mcpl/tool-lifecycle.ts +77 -2
  323. package/src/mcpl/tool-pattern-check.ts +106 -0
  324. package/src/mcpl/types.ts +15 -5
  325. package/src/mcpl/visible-content.ts +57 -0
  326. package/src/module-registry.ts +269 -25
  327. package/src/modules/workspace/branch-intent.ts +123 -0
  328. package/src/modules/workspace/disk-agreement.ts +325 -0
  329. package/src/modules/workspace/effects.ts +362 -0
  330. package/src/modules/workspace/index.ts +1098 -446
  331. package/src/modules/workspace/observe.ts +865 -0
  332. package/src/modules/workspace/reconcile.ts +1157 -0
  333. package/src/modules/workspace/sync.ts +6 -336
  334. package/src/modules/workspace/types.ts +75 -10
  335. package/src/operator-log.ts +30 -0
  336. package/src/provider-waits.ts +523 -0
  337. package/src/record-journal.ts +246 -0
  338. package/src/refusal-reactions.ts +14 -12
  339. package/src/shutdown.ts +49 -0
  340. package/src/silent-heartbeat.ts +141 -0
  341. package/src/tool-presentation.ts +188 -0
  342. package/src/tool-result-guard.ts +384 -34
  343. package/src/tool-result-history.ts +25 -1
  344. package/src/types/agent.ts +18 -2
  345. package/src/types/events.ts +16 -0
  346. package/src/types/framework.ts +12 -0
  347. package/src/types/module.ts +27 -4
  348. package/src/types/trace.ts +94 -3
@@ -37,6 +37,10 @@
37
37
  * {op:"wake", id, exec_id, line, payload}
38
38
  * {op:"exec_result", id, stdout, stderr, return_code, tail?}
39
39
  *
40
+ * At a deadline the host sends cancel and then SIGINT, repeated until the
41
+ * script reports (not on Windows): the cancel stops a script waiting on an
42
+ * await, the signal also one stuck in blocking code (see _on_sigint).
43
+ *
40
44
  * IMPORTANT: the python source below must not contain backticks or the
41
45
  * sequence dollar+brace (TS template literal syntax). String.raw preserves
42
46
  * backslashes, so \n inside python string literals is fine.
@@ -48,6 +52,7 @@ import inspect
48
52
  import io
49
53
  import json
50
54
  import os
55
+ import signal
51
56
  import sys
52
57
  import threading
53
58
  import traceback
@@ -168,6 +173,90 @@ _next_wake_id = 0
168
173
  _current_exec_id = None
169
174
  _current_exec_task = None
170
175
 
176
+ # How the host stops a script (its time limit, or an abort): ONE per-script
177
+ # state, so the script is stopped exactly once, by whichever path gets there
178
+ # first. Every other path then does nothing to that script.
179
+ # PENDING -> nothing asked yet.
180
+ # REQUESTED -> asked while the script was outside its own code (waiting on
181
+ # an await, writing a protocol line, or not started yet). A
182
+ # cancellation is queued on the loop, or delivered as the
183
+ # script starts.
184
+ # DELIVERED -> KeyboardInterrupt raised in its code, or its task cancelled.
185
+ # Final: whatever the script does next (except/finally, awaits
186
+ # in its cleanup) runs to completion or to the host's kill.
187
+ # Transitions happen only in the main thread: in the SIGINT handler (between
188
+ # bytecodes, so it never cancels anything itself -- see _on_sigint) and in
189
+ # loop callbacks. Neither can interleave a transition of the other halfway
190
+ # in a way that matters: the handler only moves PENDING to REQUESTED or, with
191
+ # the script's own frame on the stack, to DELIVERED; a loop callback never
192
+ # runs while the script's frame is on the stack.
193
+ #
194
+ # Where the signal (or the cancel op) can land, and what happens:
195
+ # 1. script's own code, running or blocking -> KeyboardInterrupt there
196
+ # 2. script waiting on an await (loop idle) -> cancellation queued; it wakes
197
+ # the loop and lands at that await
198
+ # 3. main() mid-dispatch, between the done() check of a future the script
199
+ # awaits and its set_result (reply behind the cancellation) -> queued;
200
+ # set_result completes; the script gets CancelledError instead of it
201
+ # 4. the script writing a protocol line (send) -> queued; the line stays
202
+ # whole; the cancellation lands at the script's next await
203
+ # 5. reply ahead of the queued cancellation, script then awaits -> the
204
+ # cancellation lands at that await
205
+ # 6. reply ahead, script then blocks -> the next signal (the host repeats
206
+ # it) raises KeyboardInterrupt there; the queued cancellation then does
207
+ # nothing, so cleanup that awaits is not cut short
208
+ # 7. reply ahead, script finishes before the queued cancellation runs ->
209
+ # it does nothing, and never reaches the next script
210
+ # 8. script not started yet (signal, or the cancel op) -> delivered as
211
+ # CancelledError when it starts, so it still reports
212
+ # 9. cancel op before the signal -> delivered; the signal and its repeats
213
+ # do nothing, so cleanup after a caught CancelledError runs on
214
+ # 10. cancel op or repeated signal after delivery -> nothing
215
+ # 11. script finished, reporting its result -> nothing
216
+ # 12. between scripts -> nothing
217
+ # 13. a previous script installed an eager task factory -> no effect: the
218
+ # runtime builds its script task directly, and the state is in place
219
+ # before the script's first step
220
+ _PENDING = "pending"
221
+ _REQUESTED = "requested"
222
+ _DELIVERED = "delivered"
223
+
224
+
225
+ class _ScriptStop:
226
+ __slots__ = ("task", "running", "state", "interrupt")
227
+
228
+ def __init__(self):
229
+ self.task = None
230
+ self.running = False # the script's own code may be on the stack
231
+ self.state = _PENDING
232
+ self.interrupt = None # the KeyboardInterrupt raised in its code
233
+
234
+
235
+ _stop = None # the running script's _ScriptStop; None between scripts
236
+
237
+
238
+ def _deliver_cancel(st):
239
+ # Loop context only: the cancel op in main()'s dispatch, or queued by the
240
+ # SIGINT handler. Never from inside the handler, where a cancellation
241
+ # could land between main()'s done() check of a future the script awaits
242
+ # and its set_result.
243
+ if st is None or st is not _stop or st.state == _DELIVERED:
244
+ return
245
+ if not st.running:
246
+ st.state = _REQUESTED # not started yet: delivered as it starts
247
+ return
248
+ st.state = _DELIVERED
249
+ st.task.cancel()
250
+
251
+
252
+ def _resolve(fut, value):
253
+ # Defense in depth: the script may have been cancelled since the lookup.
254
+ if fut is not None and not fut.done():
255
+ try:
256
+ fut.set_result(value)
257
+ except asyncio.InvalidStateError:
258
+ pass
259
+
171
260
 
172
261
  def _make_tool_fn(tool_name, py_name):
173
262
  async def tool_fn(args=None):
@@ -278,8 +367,8 @@ def handle_init(msg):
278
367
  SCRIPT_GLOBALS.pop("wake_agent", None)
279
368
 
280
369
 
281
- async def _run_script(exec_id, code):
282
- global _current_exec_id, _current_exec_task
370
+ async def _run_script(exec_id, code, st):
371
+ global _current_exec_id, _current_exec_task, _stop
283
372
  if BACKGROUND:
284
373
  out = LogTee(LOG_PATH, TAIL_CHARS)
285
374
  err = out # interleave, terminal-style; tail is shared
@@ -292,17 +381,38 @@ async def _run_script(exec_id, code):
292
381
  return_code = 0
293
382
  try:
294
383
  compiled = compile(code, "<script>", "exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT)
295
- result = eval(compiled, SCRIPT_GLOBALS)
296
- if inspect.iscoroutine(result):
297
- await result
384
+ st.running = True
385
+ try:
386
+ if st.state == _REQUESTED:
387
+ # Stopped before it started (row 8).
388
+ st.state = _DELIVERED
389
+ raise asyncio.CancelledError()
390
+ result = eval(compiled, SCRIPT_GLOBALS)
391
+ if inspect.iscoroutine(result):
392
+ await result
393
+ finally:
394
+ st.running = False
298
395
  except asyncio.CancelledError:
299
396
  err.write("\nKeyboardInterrupt: script cancelled by host\n")
300
397
  return_code = 1
301
- except BaseException:
302
- traceback.print_exc(file=err)
398
+ except BaseException as exc:
399
+ if exc is st.interrupt:
400
+ # Where the script was when it was stopped, without the runtime's frames.
401
+ frames = [
402
+ f for f in traceback.extract_tb(exc.__traceback__)
403
+ if f.filename != _RUN_SCRIPT_CODE.co_filename
404
+ ]
405
+ err.write("Traceback (most recent call last):\n")
406
+ err.write("".join(traceback.format_list(frames)))
407
+ err.write("KeyboardInterrupt: script interrupted by host\n")
408
+ else:
409
+ traceback.print_exc(file=err)
303
410
  return_code = 1
304
411
  finally:
305
412
  sys.stdout, sys.stderr, sys.stdin = old_out, old_err, old_in
413
+ st.interrupt = None
414
+ if _stop is st:
415
+ _stop = None
306
416
  _current_exec_id = None
307
417
  _current_exec_task = None
308
418
  for fut in list(_pending_tool_futures.values()):
@@ -342,8 +452,47 @@ async def _run_script(exec_id, code):
342
452
  })
343
453
 
344
454
 
455
+ def _dispatch(msg):
456
+ """Handle one protocol message from the host; False means stop reading."""
457
+ global _current_exec_id, _current_exec_task, _stop
458
+ op = msg.get("op")
459
+ if op == "init":
460
+ handle_init(msg)
461
+ elif op == "exec":
462
+ if _current_exec_task is not None and not _current_exec_task.done():
463
+ send({
464
+ "op": "exec_result",
465
+ "id": msg.get("id"),
466
+ "stdout": "",
467
+ "stderr": "RuntimeError: another script is already running in this interpreter",
468
+ "return_code": 1,
469
+ })
470
+ return True
471
+ # Everything _on_sigint reads is in place before the script can run
472
+ # (row 13). The task is built directly, not through the loop's task
473
+ # factory: a script may have installed one (interpreter state
474
+ # persists), and an eager one would run the next script inside this
475
+ # call, before the assignments below.
476
+ st = _ScriptStop()
477
+ _stop = st
478
+ _current_exec_id = msg.get("id")
479
+ _current_exec_task = asyncio.Task(
480
+ _run_script(msg.get("id"), msg.get("code") or "", st),
481
+ loop=asyncio.get_running_loop(),
482
+ )
483
+ st.task = _current_exec_task
484
+ elif op == "tool_result":
485
+ _resolve(_pending_tool_futures.get(msg.get("id")), str(msg.get("result", "")))
486
+ elif op == "wake_ack":
487
+ _resolve(_pending_wake_futures.get(msg.get("id")), msg.get("error") or None)
488
+ elif op == "cancel":
489
+ _deliver_cancel(_stop)
490
+ elif op == "exit":
491
+ return False
492
+ return True
493
+
494
+
345
495
  async def main():
346
- global _current_exec_id, _current_exec_task
347
496
  loop = asyncio.get_running_loop()
348
497
  queue = asyncio.Queue()
349
498
 
@@ -352,7 +501,17 @@ async def main():
352
501
  loop.call_soon_threadsafe(queue.put_nowait, raw_line)
353
502
  loop.call_soon_threadsafe(queue.put_nowait, None)
354
503
 
355
- threading.Thread(target=reader, daemon=True).start()
504
+ # The reader thread blocks SIGINT (it inherits the mask while it starts),
505
+ # so the signal reaches the main thread, where a blocking call in the
506
+ # script is waiting to be interrupted.
507
+ masked = hasattr(signal, "pthread_sigmask") and hasattr(signal, "SIGINT")
508
+ if masked:
509
+ previous_mask = signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
510
+ try:
511
+ threading.Thread(target=reader, daemon=True).start()
512
+ finally:
513
+ if masked:
514
+ signal.pthread_sigmask(signal.SIG_SETMASK, previous_mask)
356
515
  send({"op": "ready"})
357
516
 
358
517
  while True:
@@ -368,35 +527,7 @@ async def main():
368
527
  REAL_STDERR.write("[pytc-runtime] bad protocol line\n")
369
528
  REAL_STDERR.flush()
370
529
  continue
371
- op = msg.get("op")
372
- if op == "init":
373
- handle_init(msg)
374
- elif op == "exec":
375
- if _current_exec_task is not None and not _current_exec_task.done():
376
- send({
377
- "op": "exec_result",
378
- "id": msg.get("id"),
379
- "stdout": "",
380
- "stderr": "RuntimeError: another script is already running in this interpreter",
381
- "return_code": 1,
382
- })
383
- continue
384
- _current_exec_id = msg.get("id")
385
- _current_exec_task = asyncio.ensure_future(
386
- _run_script(msg.get("id"), msg.get("code") or "")
387
- )
388
- elif op == "tool_result":
389
- fut = _pending_tool_futures.get(msg.get("id"))
390
- if fut is not None and not fut.done():
391
- fut.set_result(str(msg.get("result", "")))
392
- elif op == "wake_ack":
393
- fut = _pending_wake_futures.get(msg.get("id"))
394
- if fut is not None and not fut.done():
395
- fut.set_result(msg.get("error") or None)
396
- elif op == "cancel":
397
- if _current_exec_task is not None and not _current_exec_task.done():
398
- _current_exec_task.cancel()
399
- elif op == "exit":
530
+ if not _dispatch(msg):
400
531
  break
401
532
 
402
533
  # Drain: give a cancelled exec a moment to emit its exec_result.
@@ -408,7 +539,53 @@ async def main():
408
539
  pass
409
540
 
410
541
 
542
+ # At the deadline the host sends a cancel op, which lands only when the script
543
+ # awaits: a script blocked in time.sleep(), a busy loop or a blocking read
544
+ # never gives the event loop control back. So the host also sends SIGINT,
545
+ # repeated until the script reports, and this stops the script through its
546
+ # _ScriptStop (see the table there):
547
+ # - With the script's own code on the stack (_run_script's frame, reached
548
+ # before any send frame), it raises KeyboardInterrupt there; _run_script
549
+ # catches it, so the output so far and the interpreter's globals survive.
550
+ # Checking the current task instead is not enough: asyncio runs its own
551
+ # code for the task between steps, and an exception raised there escapes
552
+ # the event loop and ends the interpreter.
553
+ # - Anywhere else it only queues the cancellation on the loop (which also
554
+ # wakes select(), otherwise resuming its wait after a signal): cancelling
555
+ # here could cancel a future between main()'s done() check and its
556
+ # set_result, and an exception mid-send would tear a protocol line.
557
+ _RUN_SCRIPT_CODE = _run_script.__code__
558
+ _SEND_CODE = send.__code__
559
+
560
+
561
+ def _on_sigint(signum, frame):
562
+ st = _stop
563
+ if st is None or st.state == _DELIVERED:
564
+ return
565
+ if st.running:
566
+ while frame is not None:
567
+ code = frame.f_code
568
+ if code is _SEND_CODE:
569
+ break
570
+ if code is _RUN_SCRIPT_CODE:
571
+ st.state = _DELIVERED
572
+ st.interrupt = KeyboardInterrupt("script interrupted by host")
573
+ raise st.interrupt
574
+ frame = frame.f_back
575
+ if st.state == _PENDING:
576
+ st.state = _REQUESTED
577
+ if st.running:
578
+ st.task.get_loop().call_soon_threadsafe(_deliver_cancel, st)
579
+
580
+
411
581
  if __name__ == "__main__":
582
+ # A handler of our own also keeps asyncio.run from installing one that
583
+ # would cancel main() and end the interpreter.
584
+ if hasattr(signal, "SIGINT"):
585
+ try:
586
+ signal.signal(signal.SIGINT, _on_sigint)
587
+ except (ValueError, OSError):
588
+ pass
412
589
  asyncio.run(main())
413
590
  `;
414
591
  //# sourceMappingURL=runtime-py.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-py.js","sourceRoot":"","sources":["../../../src/code-execution/runtime-py.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAW,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiXtD,CAAC"}
1
+ {"version":3,"file":"runtime-py.js","sourceRoot":"","sources":["../../../src/code-execution/runtime-py.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAW,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8hBtD,CAAC"}