@qvac/harness 0.3.3 → 0.5.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 (301) hide show
  1. package/CHANGELOG.md +700 -126
  2. package/README.md +156 -0
  3. package/dist/harness.bundle +71 -103
  4. package/dist/prebuilds-manifest.json +0 -8
  5. package/dist-lib/index.d.ts +42 -40
  6. package/dist-lib/index.js +307 -117
  7. package/dist-lib/lib/audio-transcode-bare.d.ts +2 -2
  8. package/dist-lib/lib/audio-transcode-bare.js +28 -18
  9. package/dist-lib/lib/audio-transcode-node.d.ts +2 -2
  10. package/dist-lib/lib/capabilities/audio-resample.d.ts +7 -6
  11. package/dist-lib/lib/capabilities/audio-resample.js +30 -18
  12. package/dist-lib/lib/capabilities/completion.d.ts +20 -19
  13. package/dist-lib/lib/capabilities/completion.js +194 -135
  14. package/dist-lib/lib/capabilities/download.d.ts +6 -6
  15. package/dist-lib/lib/capabilities/download.js +15 -8
  16. package/dist-lib/lib/capabilities/embed.js +2 -1
  17. package/dist-lib/lib/capabilities/enhance.d.ts +6 -1
  18. package/dist-lib/lib/capabilities/enhance.js +7 -4
  19. package/dist-lib/lib/capabilities/invoke.js +7 -4
  20. package/dist-lib/lib/capabilities/kv-cache.js +2 -1
  21. package/dist-lib/lib/capabilities/preview-model-source.d.ts +69 -0
  22. package/dist-lib/lib/capabilities/preview-model-source.js +439 -0
  23. package/dist-lib/lib/capabilities/speech-text.js +5 -4
  24. package/dist-lib/lib/capabilities/tool-selection.js +10 -9
  25. package/dist-lib/lib/capabilities/tool-turns.js +30 -16
  26. package/dist-lib/lib/capabilities/usage-breakdown.js +2 -1
  27. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  28. package/dist-lib/lib/capabilities/voice.js +22 -12
  29. package/dist-lib/lib/connect.d.ts +15 -37
  30. package/dist-lib/lib/connect.js +161 -112
  31. package/dist-lib/lib/context-window.js +25 -21
  32. package/dist-lib/lib/contract-types.d.ts +26 -20
  33. package/dist-lib/lib/diagram-repair.d.ts +4 -4
  34. package/dist-lib/lib/diagram-repair.js +6 -3
  35. package/dist-lib/lib/docx/index.d.ts +3 -2
  36. package/dist-lib/lib/{error-fields.d.ts → guards.d.ts} +1 -0
  37. package/dist-lib/lib/{error-fields.js → guards.js} +3 -0
  38. package/dist-lib/lib/hardware/capability-probe.js +20 -10
  39. package/dist-lib/lib/hardware/gpu-classify.js +6 -3
  40. package/dist-lib/lib/hardware/gpu-probe-addon.js +8 -4
  41. package/dist-lib/lib/hardware/gpu-probe.js +4 -2
  42. package/dist-lib/lib/hardware/gpu-stats.native.cjs +8 -9
  43. package/dist-lib/lib/hardware/parsers.js +8 -4
  44. package/dist-lib/lib/helpers/buffer-burst.js +4 -2
  45. package/dist-lib/lib/helpers/control-tokens.js +4 -2
  46. package/dist-lib/lib/helpers/join-guard.d.ts +2 -2
  47. package/dist-lib/lib/helpers/join-guard.js +4 -2
  48. package/dist-lib/lib/helpers/load-stall.js +4 -2
  49. package/dist-lib/lib/helpers/markup-stream-filter.d.ts +2 -2
  50. package/dist-lib/lib/helpers/markup-stream-filter.js +6 -3
  51. package/dist-lib/lib/helpers/race-abort.js +2 -1
  52. package/dist-lib/lib/helpers/race-timeout.d.ts +5 -0
  53. package/dist-lib/lib/helpers/race-timeout.js +14 -0
  54. package/dist-lib/lib/helpers/run-outcome.d.ts +6 -8
  55. package/dist-lib/lib/helpers/run-outcome.js +36 -20
  56. package/dist-lib/lib/helpers/spawn-path.js +6 -3
  57. package/dist-lib/lib/helpers/text-coalescer.js +6 -3
  58. package/dist-lib/lib/helpers/thinking.js +38 -45
  59. package/dist-lib/lib/helpers/tool-call-markup.d.ts +9 -7
  60. package/dist-lib/lib/helpers/tool-call-markup.js +36 -20
  61. package/dist-lib/lib/helpers/tool-intent.d.ts +3 -2
  62. package/dist-lib/lib/helpers/tool-intent.js +4 -2
  63. package/dist-lib/lib/helpers/tool-label.js +18 -9
  64. package/dist-lib/lib/hooks/built-in/arg-repair.js +23 -13
  65. package/dist-lib/lib/hooks/built-in/egress.js +2 -1
  66. package/dist-lib/lib/hooks/built-in/skill-catalog.js +2 -1
  67. package/dist-lib/lib/hooks/pipeline.d.ts +5 -4
  68. package/dist-lib/lib/hooks/pipeline.js +6 -3
  69. package/dist-lib/lib/hooks/run.js +12 -8
  70. package/dist-lib/lib/model-download-progress.js +10 -8
  71. package/dist-lib/lib/models/context.d.ts +1 -1
  72. package/dist-lib/lib/models/context.js +5 -1
  73. package/dist-lib/lib/models/custom-model-cache.d.ts +13 -0
  74. package/dist-lib/lib/models/custom-model-cache.js +70 -0
  75. package/dist-lib/lib/models/gguf-header.d.ts +31 -0
  76. package/dist-lib/lib/models/gguf-header.js +230 -0
  77. package/dist-lib/lib/models/idle-unload.d.ts +14 -0
  78. package/dist-lib/lib/models/idle-unload.js +51 -0
  79. package/dist-lib/lib/models/kv-cache.d.ts +3 -2
  80. package/dist-lib/lib/models/kv-cache.js +4 -3
  81. package/dist-lib/lib/models/llm-presets.d.ts +2 -2
  82. package/dist-lib/lib/models/llm-presets.js +4 -2
  83. package/dist-lib/lib/models/model-catalog-constants.js +3 -2
  84. package/dist-lib/lib/models/model-catalog.d.ts +19 -21
  85. package/dist-lib/lib/models/model-catalog.js +66 -33
  86. package/dist-lib/lib/models/model-delete.d.ts +4 -1
  87. package/dist-lib/lib/models/model-delete.js +30 -15
  88. package/dist-lib/lib/models/model-handle.js +2 -1
  89. package/dist-lib/lib/models/model-list.d.ts +14 -3
  90. package/dist-lib/lib/models/model-list.js +33 -6
  91. package/dist-lib/lib/models/model-pool.d.ts +44 -10
  92. package/dist-lib/lib/models/model-pool.js +47 -42
  93. package/dist-lib/lib/models/model-source.js +11 -6
  94. package/dist-lib/lib/models/music-catalog.d.ts +2 -2
  95. package/dist-lib/lib/models/nmt-catalog.d.ts +4 -3
  96. package/dist-lib/lib/models/nmt-catalog.js +10 -5
  97. package/dist-lib/lib/models/opencl-cache.d.ts +6 -1
  98. package/dist-lib/lib/models/opencl-cache.js +5 -3
  99. package/dist-lib/lib/models/sdk-faults.d.ts +11 -8
  100. package/dist-lib/lib/models/sdk-faults.js +9 -6
  101. package/dist-lib/lib/models/shards.d.ts +11 -0
  102. package/dist-lib/lib/models/shards.js +15 -0
  103. package/dist-lib/lib/models/tool-dialect.js +10 -5
  104. package/dist-lib/lib/no-redirect-fetch-bare.d.ts +3 -3
  105. package/dist-lib/lib/no-redirect-fetch-bare.js +1 -1
  106. package/dist-lib/lib/no-redirect-fetch-impl.js +6 -3
  107. package/dist-lib/lib/no-redirect-fetch-node.d.ts +1 -1
  108. package/dist-lib/lib/no-redirect-fetch-node.js +9 -9
  109. package/dist-lib/lib/office/index.d.ts +3 -2
  110. package/dist-lib/lib/office/python.d.ts +3 -2
  111. package/dist-lib/lib/office/python.js +2 -1
  112. package/dist-lib/lib/prompt.d.ts +5 -4
  113. package/dist-lib/lib/prompt.js +8 -4
  114. package/dist-lib/lib/queue.js +10 -5
  115. package/dist-lib/lib/resolution-detector.js +21 -11
  116. package/dist-lib/lib/rpc-session.js +2 -1
  117. package/dist-lib/lib/sdk-loader.js +2 -1
  118. package/dist-lib/lib/sdk-storage.js +4 -2
  119. package/dist-lib/lib/serve.js +128 -68
  120. package/dist-lib/lib/spawn.js +7 -4
  121. package/dist-lib/lib/tmp-path.d.ts +7 -6
  122. package/dist-lib/lib/tool-call-display.d.ts +2 -2
  123. package/dist-lib/lib/tools/create-documents.js +18 -10
  124. package/dist-lib/lib/tools/edit-documents.js +23 -13
  125. package/dist-lib/lib/tools/edit-image.d.ts +2 -2
  126. package/dist-lib/lib/tools/edit-image.js +60 -36
  127. package/dist-lib/lib/tools/exec/adapters/adapter.d.ts +10 -10
  128. package/dist-lib/lib/tools/exec/adapters/python/attachments.d.ts +17 -11
  129. package/dist-lib/lib/tools/exec/adapters/python/attachments.js +53 -40
  130. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +2 -2
  131. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +60 -32
  132. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.d.ts +33 -22
  133. package/dist-lib/lib/tools/exec/adapters/python/guarded-fetch.js +58 -29
  134. package/dist-lib/lib/tools/exec/adapters/python/interrupt-timer.d.ts +2 -2
  135. package/dist-lib/lib/tools/exec/adapters/python/loader.js +23 -12
  136. package/dist-lib/lib/tools/exec/adapters/python/memfs.d.ts +13 -11
  137. package/dist-lib/lib/tools/exec/adapters/python/memfs.js +10 -5
  138. package/dist-lib/lib/tools/exec/adapters/python/net-shim.js +3 -1
  139. package/dist-lib/lib/tools/exec/adapters/python/output-policy.d.ts +6 -4
  140. package/dist-lib/lib/tools/exec/adapters/python/output-policy.js +20 -10
  141. package/dist-lib/lib/tools/exec/adapters/python/pyodide.d.ts +2 -2
  142. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +14 -7
  143. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +41 -34
  144. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +316 -166
  145. package/dist-lib/lib/tools/exec/adapters/python/run-session.d.ts +9 -8
  146. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +56 -28
  147. package/dist-lib/lib/tools/exec/adapters/python/wheels.d.ts +8 -10
  148. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +22 -11
  149. package/dist-lib/lib/tools/exec/adapters/python/worker/client.d.ts +3 -2
  150. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +41 -26
  151. package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +27 -18
  152. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.d.ts +2 -2
  153. package/dist-lib/lib/tools/exec/adapters/python/worker/protocol.js +14 -13
  154. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.d.ts +4 -4
  155. package/dist-lib/lib/tools/exec/adapters/python/worker/spawn-node.js +10 -5
  156. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.d.ts +4 -4
  157. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +2 -1
  158. package/dist-lib/lib/tools/exec/obsidian/cli-process.d.ts +6 -1
  159. package/dist-lib/lib/tools/exec/obsidian/cli-process.js +8 -5
  160. package/dist-lib/lib/tools/exec/obsidian/cli.d.ts +6 -6
  161. package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
  162. package/dist-lib/lib/tools/exec/obsidian/command.d.ts +3 -2
  163. package/dist-lib/lib/tools/exec/obsidian/command.js +6 -3
  164. package/dist-lib/lib/tools/exec/obsidian/output.js +2 -1
  165. package/dist-lib/lib/tools/exec/obsidian/route.d.ts +4 -6
  166. package/dist-lib/lib/tools/exec/obsidian/route.js +17 -11
  167. package/dist-lib/lib/tools/exec/obsidian/safe-path.js +2 -1
  168. package/dist-lib/lib/tools/exec/obsidian/vault-create.js +15 -7
  169. package/dist-lib/lib/tools/exec/obsidian/vault-paths.d.ts +2 -2
  170. package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +20 -10
  171. package/dist-lib/lib/tools/exec/obsidian/vault-read.js +20 -13
  172. package/dist-lib/lib/tools/exec/obsidian/vault-write.js +24 -12
  173. package/dist-lib/lib/tools/exec/runner.d.ts +14 -14
  174. package/dist-lib/lib/tools/exec/runner.js +5 -3
  175. package/dist-lib/lib/tools/exec/scope.js +52 -28
  176. package/dist-lib/lib/tools/exec/shell-argv.js +31 -18
  177. package/dist-lib/lib/tools/exec/shell-guidance.d.ts +6 -1
  178. package/dist-lib/lib/tools/exec/shell-guidance.js +35 -20
  179. package/dist-lib/lib/tools/exec/video-tools.js +2 -2
  180. package/dist-lib/lib/tools/exec.d.ts +6 -8
  181. package/dist-lib/lib/tools/exec.js +113 -54
  182. package/dist-lib/lib/tools/file-glob.d.ts +4 -5
  183. package/dist-lib/lib/tools/file-glob.js +16 -11
  184. package/dist-lib/lib/tools/fs/glob.js +13 -18
  185. package/dist-lib/lib/tools/fs/remote-protocol.d.ts +10 -10
  186. package/dist-lib/lib/tools/fs/remote.d.ts +3 -2
  187. package/dist-lib/lib/tools/fs/remote.js +13 -7
  188. package/dist-lib/lib/tools/fs/scope.d.ts +8 -3
  189. package/dist-lib/lib/tools/fs/scope.js +20 -12
  190. package/dist-lib/lib/tools/fs/walk.js +6 -3
  191. package/dist-lib/lib/tools/fs/write.d.ts +3 -2
  192. package/dist-lib/lib/tools/generate-image.d.ts +11 -9
  193. package/dist-lib/lib/tools/generate-image.js +73 -37
  194. package/dist-lib/lib/tools/generate-music.d.ts +22 -11
  195. package/dist-lib/lib/tools/generate-music.js +79 -48
  196. package/dist-lib/lib/tools/grep.js +66 -41
  197. package/dist-lib/lib/tools/http-request/encode-fields.js +18 -9
  198. package/dist-lib/lib/tools/http-request/{utils.js → request.js} +20 -11
  199. package/dist-lib/lib/tools/http-request/truncate-response.js +57 -46
  200. package/dist-lib/lib/tools/http-request.d.ts +1 -1
  201. package/dist-lib/lib/tools/http-request.js +33 -18
  202. package/dist-lib/lib/tools/mcp/protocol.d.ts +53 -0
  203. package/dist-lib/lib/tools/mcp/{utils.js → protocol.js} +45 -23
  204. package/dist-lib/lib/tools/mcp-call.d.ts +1 -1
  205. package/dist-lib/lib/tools/mcp-call.js +37 -16
  206. package/dist-lib/lib/tools/operations/builders.js +10 -6
  207. package/dist-lib/lib/tools/operations/compile.js +20 -10
  208. package/dist-lib/lib/tools/operations/decoders.js +12 -6
  209. package/dist-lib/lib/tools/operations/gmail-message.js +3 -2
  210. package/dist-lib/lib/tools/outbound-guard.d.ts +4 -3
  211. package/dist-lib/lib/tools/outbound-guard.js +14 -8
  212. package/dist-lib/lib/tools/private-ip.js +8 -7
  213. package/dist-lib/lib/tools/rag-search.js +14 -7
  214. package/dist-lib/lib/tools/read-files.js +23 -20
  215. package/dist-lib/lib/tools/skill.js +19 -10
  216. package/dist-lib/lib/tools/skills/auth-resolver.d.ts +11 -18
  217. package/dist-lib/lib/tools/skills/auth-resolver.js +30 -18
  218. package/dist-lib/lib/tools/skills/bin-check.d.ts +3 -2
  219. package/dist-lib/lib/tools/skills/bin-check.js +10 -3
  220. package/dist-lib/lib/tools/skills/bundled-dir.js +20 -10
  221. package/dist-lib/lib/tools/skills/bundled-hash.d.ts +3 -2
  222. package/dist-lib/lib/tools/skills/bundled-hash.js +6 -1
  223. package/dist-lib/lib/tools/skills/catalog.d.ts +3 -2
  224. package/dist-lib/lib/tools/skills/catalog.js +33 -28
  225. package/dist-lib/lib/tools/skills/cli-argv.d.ts +3 -2
  226. package/dist-lib/lib/tools/skills/cli-argv.js +55 -52
  227. package/dist-lib/lib/tools/skills/cli-schema-check.js +21 -10
  228. package/dist-lib/lib/tools/skills/cli-schema-load.js +28 -20
  229. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +6 -6
  230. package/dist-lib/lib/tools/skills/cli-schema.js +2 -1
  231. package/dist-lib/lib/tools/skills/credential-check.d.ts +6 -3
  232. package/dist-lib/lib/tools/skills/credential-check.js +24 -12
  233. package/dist-lib/lib/tools/skills/credential-grants.js +1 -6
  234. package/dist-lib/lib/tools/skills/credential-health.d.ts +5 -1
  235. package/dist-lib/lib/tools/skills/credential-health.js +5 -3
  236. package/dist-lib/lib/tools/skills/frontmatter-parser.js +10 -5
  237. package/dist-lib/lib/tools/skills/manifest-parse.js +84 -64
  238. package/dist-lib/lib/tools/skills/mention.js +7 -6
  239. package/dist-lib/lib/tools/skills/obsidian-readiness.d.ts +2 -2
  240. package/dist-lib/lib/tools/skills/obsidian-readiness.js +11 -3
  241. package/dist-lib/lib/tools/skills/overlay-crud.d.ts +6 -4
  242. package/dist-lib/lib/tools/skills/overlay-crud.js +12 -6
  243. package/dist-lib/lib/tools/skills/overlay-dir.js +4 -2
  244. package/dist-lib/lib/tools/skills/overlay-watch.d.ts +3 -2
  245. package/dist-lib/lib/tools/skills/overlay-watch.js +8 -4
  246. package/dist-lib/lib/tools/skills/setup-routes.js +1 -3
  247. package/dist-lib/lib/tools/skills/skill-settings.js +4 -1
  248. package/dist-lib/lib/tools/skills/skill-state.d.ts +6 -11
  249. package/dist-lib/lib/tools/skills/skill-state.js +48 -29
  250. package/dist-lib/lib/tools/skills/tool-grants.js +6 -3
  251. package/dist-lib/lib/tools/toolsets.js +2 -1
  252. package/dist-lib/lib/tools/translate.d.ts +4 -5
  253. package/dist-lib/lib/tools/translate.js +31 -14
  254. package/dist-lib/lib/tools/url-guard.js +2 -1
  255. package/dist-lib/lib/tools/weather-lookup/place.js +8 -4
  256. package/dist-lib/lib/tools/weather-lookup/report.d.ts +7 -1
  257. package/dist-lib/lib/tools/weather-lookup/report.js +46 -9
  258. package/dist-lib/lib/tools/weather-lookup.js +18 -12
  259. package/dist-lib/lib/tools/web-fetch.js +47 -24
  260. package/dist-lib/lib/tools/web-search.js +8 -3
  261. package/dist-lib/lib/validating-lookup.d.ts +4 -4
  262. package/dist-lib/lib/wav-encode.d.ts +1 -1
  263. package/dist-lib/lib/wav-encode.js +11 -10
  264. package/dist-lib/lib/web-fetch/embedded-state.js +24 -12
  265. package/dist-lib/lib/web-fetch/extract.js +4 -2
  266. package/dist-lib/lib/web-fetch/images.js +8 -4
  267. package/dist-lib/lib/web-fetch/video.d.ts +2 -2
  268. package/dist-lib/lib/web-fetch/video.js +8 -4
  269. package/dist-lib/lib/web-search/engine-registry.d.ts +2 -2
  270. package/dist-lib/lib/web-search/engines/aol.js +1 -1
  271. package/dist-lib/lib/web-search/engines/bing.js +1 -1
  272. package/dist-lib/lib/web-search/engines/brave.js +4 -1
  273. package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -2
  274. package/dist-lib/lib/web-search/engines/google.js +7 -3
  275. package/dist-lib/lib/web-search/engines/wikipedia.js +5 -4
  276. package/dist-lib/lib/web-search/engines/yahoo.js +1 -1
  277. package/dist-lib/lib/web-search/fetch-html.d.ts +5 -4
  278. package/dist-lib/lib/web-search/fetch-html.js +13 -7
  279. package/dist-lib/lib/web-search/orchestrator.d.ts +9 -5
  280. package/dist-lib/lib/web-search/orchestrator.js +3 -3
  281. package/dist-lib/lib/wire.d.ts +3 -2
  282. package/dist-lib/lib/wire.js +23 -15
  283. package/dist-lib/schema/baseline.js +10 -5
  284. package/dist-lib/schema/build.js +2 -1
  285. package/dist-lib/schema/harness.d.ts +15 -2
  286. package/dist-lib/schema/harness.js +55 -2
  287. package/dist-lib/schema/release-tags.d.ts +3 -2
  288. package/dist-lib/schema/release-tags.js +6 -3
  289. package/dist-lib/spec/bind.js +6 -0
  290. package/dist-lib/spec/calls.js +2 -0
  291. package/dist-lib/spec/hrpc/hrpc.json +26 -0
  292. package/dist-lib/spec/hrpc/index.js +27 -3
  293. package/dist-lib/spec/hrpc/messages.js +207 -8
  294. package/dist-lib/spec/hyperschema/index.js +207 -8
  295. package/dist-lib/spec/hyperschema/schema.json +165 -1
  296. package/dist-lib/spec/hyperschema/types.d.ts +42 -1
  297. package/package.json +13 -14
  298. package/worker/harness-sidecar.mjs +16 -22
  299. package/worker/stow-entry.mjs +7 -4
  300. package/dist-lib/lib/tools/mcp/utils.d.ts +0 -47
  301. /package/dist-lib/lib/tools/http-request/{utils.d.ts → request.d.ts} +0 -0
@@ -19,8 +19,9 @@ function isAsciiWs(c) {
19
19
  return c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '\f';
20
20
  }
21
21
  function skipWs(s, i) {
22
- while (i < s.length && isAsciiWs(s[i]))
22
+ while (i < s.length && isAsciiWs(s.charAt(i))) {
23
23
  i++;
24
+ }
24
25
  return i;
25
26
  }
26
27
  function leadingToken(s, tokens) {
@@ -28,48 +29,60 @@ function leadingToken(s, tokens) {
28
29
  if (!s.startsWith(t))
29
30
  continue;
30
31
  const next = s[t.length];
31
- if (next !== undefined && /[A-Za-z0-9_]/.test(next))
32
+ if (next !== undefined && /[A-Za-z0-9_]/.test(next)) {
32
33
  continue;
34
+ }
33
35
  return t;
34
36
  }
35
37
  return null;
36
38
  }
37
39
  function unquote(payload) {
38
40
  const quote = payload[0];
39
- if (quote !== '"' && quote !== "'")
41
+ if (quote !== '"' && quote !== "'") {
40
42
  return payload;
43
+ }
41
44
  const body = payload.endsWith(quote) ? payload.slice(1, -1) : payload.slice(1);
42
45
  return quote === '"' ? body.replace(/\\"/g, '"') : body;
43
46
  }
44
47
  function unwrapHeredoc(s) {
45
48
  const interp = leadingToken(s, INTERPS);
46
- if (!interp)
49
+ if (!interp) {
47
50
  return null;
51
+ }
48
52
  let i = skipWs(s, interp.length);
49
- if (!s.startsWith('<<', i))
53
+ if (!s.startsWith('<<', i)) {
50
54
  return null;
55
+ }
51
56
  i += 2;
52
- if (s[i] === '-')
57
+ if (s[i] === '-') {
53
58
  i++;
59
+ }
54
60
  i = skipWs(s, i);
55
- const quote = s[i] === '"' || s[i] === "'" ? s[i] : null;
56
- if (quote)
61
+ const open = s.charAt(i);
62
+ const quote = open === '"' || open === "'" ? open : null;
63
+ if (quote) {
57
64
  i++;
65
+ }
58
66
  const tagStart = i;
59
- while (i < s.length && /[A-Za-z0-9_]/.test(s[i]))
67
+ while (i < s.length && /[A-Za-z0-9_]/.test(s.charAt(i))) {
60
68
  i++;
61
- if (i === tagStart)
69
+ }
70
+ if (i === tagStart) {
62
71
  return null;
72
+ }
63
73
  const tag = s.slice(tagStart, i);
64
74
  if (quote) {
65
- if (s[i] !== quote)
75
+ if (s[i] !== quote) {
66
76
  return null;
77
+ }
67
78
  i++;
68
79
  }
69
- while (i < s.length && (s[i] === ' ' || s[i] === '\t'))
80
+ while (i < s.length && (s[i] === ' ' || s[i] === '\t')) {
70
81
  i++;
71
- if (s[i] !== '\n')
82
+ }
83
+ if (s[i] !== '\n') {
72
84
  return null;
85
+ }
73
86
  i++;
74
87
  const body = s.slice(i);
75
88
  for (let j = 0; j < body.length; j++) {
@@ -77,54 +90,66 @@ function unwrapHeredoc(s) {
77
90
  continue;
78
91
  let k = j + 1;
79
92
  const closeInterp = leadingToken(body.slice(k), INTERPS);
80
- if (closeInterp)
93
+ if (closeInterp) {
81
94
  k += closeInterp.length;
95
+ }
82
96
  k = skipWs(body, k);
83
- if (!body.startsWith(tag, k))
97
+ if (!body.startsWith(tag, k)) {
84
98
  continue;
99
+ }
85
100
  k += tag.length;
86
101
  k = skipWs(body, k);
87
- if (k === body.length)
102
+ if (k === body.length) {
88
103
  return body.slice(0, j).trim();
104
+ }
89
105
  }
90
106
  return body.trim();
91
107
  }
92
108
  function unwrapDashC(s) {
93
109
  const interp = leadingToken(s, INTERPS);
94
- if (!interp)
110
+ if (!interp) {
95
111
  return null;
112
+ }
96
113
  let i = skipWs(s, interp.length);
97
- if (!s.startsWith('-c', i))
114
+ if (!s.startsWith('-c', i)) {
98
115
  return null;
116
+ }
99
117
  i += 2;
100
- if (i >= s.length || !isAsciiWs(s[i]))
118
+ if (i >= s.length || !isAsciiWs(s.charAt(i))) {
101
119
  return null;
120
+ }
102
121
  i = skipWs(s, i);
103
- if (i >= s.length)
122
+ if (i >= s.length) {
104
123
  return null;
124
+ }
105
125
  const source = unquote(s.slice(i).trim()).trim();
106
126
  return source || null;
107
127
  }
108
128
  function unwrapBareInterpreter(s) {
109
129
  const interp = leadingToken(s, INTERPS);
110
- if (!interp)
130
+ if (!interp) {
111
131
  return null;
132
+ }
112
133
  let i = interp.length;
113
- while (i < s.length && (s[i] === ' ' || s[i] === '\t'))
134
+ while (i < s.length && (s[i] === ' ' || s[i] === '\t')) {
114
135
  i++;
115
- if (s[i] !== '\n')
136
+ }
137
+ if (s[i] !== '\n') {
116
138
  return null;
139
+ }
117
140
  const body = s.slice(i + 1).trim();
118
141
  return body || null;
119
142
  }
120
143
  export function unwrapPythonSource(command) {
121
144
  const trimmed = withoutProgramDir(command).trim();
122
145
  const heredoc = unwrapHeredoc(trimmed);
123
- if (heredoc !== null)
146
+ if (heredoc !== null) {
124
147
  return heredoc;
148
+ }
125
149
  const dashC = unwrapDashC(trimmed);
126
- if (dashC !== null)
150
+ if (dashC !== null) {
127
151
  return dashC;
152
+ }
128
153
  return unwrapBareInterpreter(trimmed);
129
154
  }
130
155
  export function withoutProgramDir(command) {
@@ -139,40 +164,50 @@ function pipInstallRest(command) {
139
164
  }
140
165
  else {
141
166
  const interp = leadingToken(s, INTERPS);
142
- if (!interp)
167
+ if (!interp) {
143
168
  return null;
169
+ }
144
170
  i = skipWs(s, interp.length);
145
- if (!s.startsWith('-m', i))
171
+ if (!s.startsWith('-m', i)) {
146
172
  return null;
173
+ }
147
174
  i += 2;
148
- if (i >= s.length || !isAsciiWs(s[i]))
175
+ if (i >= s.length || !isAsciiWs(s.charAt(i))) {
149
176
  return null;
177
+ }
150
178
  i = skipWs(s, i);
151
- if (!s.startsWith('pip', i))
179
+ if (!s.startsWith('pip', i)) {
152
180
  return null;
181
+ }
153
182
  const after = s[i + 3];
154
- if (after !== undefined && /[A-Za-z0-9_]/.test(after))
183
+ if (after !== undefined && /[A-Za-z0-9_]/.test(after)) {
155
184
  return null;
185
+ }
156
186
  i += 3;
157
187
  }
158
- if (i >= s.length || !isAsciiWs(s[i]))
188
+ if (i >= s.length || !isAsciiWs(s.charAt(i))) {
159
189
  return null;
190
+ }
160
191
  i = skipWs(s, i);
161
- if (!s.startsWith('install', i))
192
+ if (!s.startsWith('install', i)) {
162
193
  return null;
194
+ }
163
195
  i += 'install'.length;
164
- if (i >= s.length || !isAsciiWs(s[i]))
196
+ if (i >= s.length || !isAsciiWs(s.charAt(i))) {
165
197
  return null;
198
+ }
166
199
  i = skipWs(s, i);
167
- if (i >= s.length)
200
+ if (i >= s.length) {
168
201
  return null;
202
+ }
169
203
  return s.slice(i);
170
204
  }
171
205
  const REQUIREMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*(\[[A-Za-z0-9,._-]+\])?([=<>!~]=?[^\s]+)?$/;
172
206
  export function pipInstallTargets(command) {
173
207
  const rest = pipInstallRest(command);
174
- if (!rest)
208
+ if (!rest) {
175
209
  return null;
210
+ }
176
211
  const targets = [];
177
212
  for (const raw of rest.split(/\s+/)) {
178
213
  if (/[|;&<>()`$]/.test(raw))
@@ -181,22 +216,25 @@ export function pipInstallTargets(command) {
181
216
  if (/^(-r|--requirement|-e|--editable|-c|--constraint|-f|--find-links)$/.test(token)) {
182
217
  return null;
183
218
  }
184
- if (!token || token.startsWith('-'))
219
+ if (!token || token.startsWith('-')) {
185
220
  continue;
186
- if (!REQUIREMENT.test(token))
221
+ }
222
+ if (!REQUIREMENT.test(token)) {
187
223
  return null;
188
- if (/^pip3?$/i.test(token.split(/[=<>!~[]/)[0]))
224
+ }
225
+ if (/^pip3?$/i.test(token.split(/[=<>!~[]/)[0] ?? '')) {
189
226
  continue;
227
+ }
190
228
  targets.push(token);
191
229
  }
192
230
  return targets.length > 0 ? targets : null;
193
231
  }
194
232
  const SANDBOX_NO_INSTALL = 'and sandbox mode installs only the packages bundled with this app, which do not include it. Use only what is already installed, or tell the user it is unavailable while sandbox mode is on.';
195
- const specName = (spec) => normalizeDistribution(spec.split(/[=<>!~[]/)[0]);
233
+ const specName = (spec) => normalizeDistribution(spec.split(/[=<>!~[]/)[0] ?? '');
196
234
  export function pinned(spec) {
197
235
  return /[=<>!~]/.test(spec) ? spec : (vendoredSpec(spec) ?? spec);
198
236
  }
199
- export function pipMessage(targets, sandbox = false) {
237
+ export function pipMessage(targets, { sandbox = false } = {}) {
200
238
  const specs = targets?.map(pinned) ?? null;
201
239
  if (sandbox && !(specs?.length && planVendoredInstall(specs))) {
202
240
  return `pip is not available, ${SANDBOX_NO_INSTALL}`;
@@ -207,24 +245,27 @@ export function pipMessage(targets, sandbox = false) {
207
245
  const PIP_CALL = /\b(?:subprocess\.\w+|os\.(?:system|popen)|Popen|check_(?:output|call))\s*\(/;
208
246
  const STRING_LITERAL = /"([^"\n]*)"|'([^'\n]*)'/g;
209
247
  function pipLineTargets(line) {
210
- const words = [...line.matchAll(STRING_LITERAL)].map((match) => (match[1] ?? match[2]));
248
+ const words = [...line.matchAll(STRING_LITERAL)].map((match) => match[1] ?? match[2] ?? '');
211
249
  const whole = words.find((word) => /\bpip3?\b[^]*\binstall\b/.test(word));
212
- if (whole !== undefined)
250
+ if (whole !== undefined) {
213
251
  return pipInstallTargets(whole.trim());
252
+ }
214
253
  const at = words.findIndex((word) => /^pip3?$/.test(word));
215
- if (at === -1)
254
+ if (at === -1) {
216
255
  return undefined;
256
+ }
217
257
  return pipInstallTargets(words.slice(at).join(' '));
218
258
  }
219
- export function pipFromSourceHint(stderr, source, packages, sandbox = false) {
259
+ export function pipFromSourceHint(stderr, source, { packages, sandbox = false } = {}) {
220
260
  if (!/emscripten does not support processes/.test(stderr))
221
261
  return null;
222
262
  for (const line of source.split('\n')) {
223
263
  if (!PIP_CALL.test(line))
224
264
  continue;
225
265
  const targets = pipLineTargets(line);
226
- if (targets === undefined)
266
+ if (targets === undefined) {
227
267
  continue;
268
+ }
228
269
  const declared = packages ?? [];
229
270
  const has = new Set(declared.map(specName));
230
271
  const missing = (targets ?? []).filter((t) => !has.has(specName(t))).map(pinned);
@@ -246,12 +287,15 @@ export function pipFromSourceHint(stderr, source, packages, sandbox = false) {
246
287
  }
247
288
  export function stringLiteralProgramHint(source) {
248
289
  const first = source.trimStart()[0];
249
- if (first !== '"' && first !== "'")
290
+ if (first !== '"' && first !== "'") {
250
291
  return null;
251
- if (!source.trimEnd().endsWith(first))
292
+ }
293
+ if (!source.trimEnd().endsWith(first)) {
252
294
  return null;
253
- if (!/\bimport\b/.test(source))
295
+ }
296
+ if (!/\bimport\b/.test(source)) {
254
297
  return null;
298
+ }
255
299
  return ('command is wrapped in a string literal, so Python evaluates it as text and runs ' +
256
300
  'nothing: remove the wrapping quotes (and any escaped \\" quotes) so the first line ' +
257
301
  'of command is the first line of the program, e.g. from fpdf import FPDF.');
@@ -287,8 +331,9 @@ export function mangledPptxApiHint(source) {
287
331
  found.push(`${bad} → ${good}`);
288
332
  }
289
333
  }
290
- if (found.length === 0)
334
+ if (found.length === 0) {
291
335
  return null;
336
+ }
292
337
  return (`python-pptx names keep their underscores — found stripped forms (${found.join(', ')}). ` +
293
338
  'Restore them (text_frame not textframe, add_slide not addslide) and keep the skill ' +
294
339
  'sample multi-line with real newlines.');
@@ -311,8 +356,9 @@ export function fpdfSizeKeywordHint(source) {
311
356
  if (!/\bfpdf\b|\bFPDF\b/.test(source))
312
357
  return null;
313
358
  const match = FPDF_SIZE_KEYWORD.exec(source);
314
- if (!match)
359
+ if (!match) {
315
360
  return null;
361
+ }
316
362
  return (`fpdf2 ${match[1]}() has no "${match[2]}" keyword — the size parameters are the positional ` +
317
363
  'w and h: write every line (headings and body) with pdf.multi_cell(0, 6, text, ' +
318
364
  'new_x="LMARGIN", new_y="NEXT"). Rename the arguments and keep new_x/new_y; ' +
@@ -320,8 +366,9 @@ export function fpdfSizeKeywordHint(source) {
320
366
  }
321
367
  export function fpdfKeywordErrorHint(stderr) {
322
368
  const match = /TypeError: FPDF\.(\w+)\(\) got an unexpected keyword argument '(\w+)'/.exec(stderr);
323
- if (!match)
369
+ if (!match) {
324
370
  return null;
371
+ }
325
372
  const sized = match[2] === 'width' || match[2] === 'height';
326
373
  const fix = sized
327
374
  ? 'the size parameters are the positional w and h'
@@ -349,8 +396,9 @@ export function normalizeFpdfSource(source) {
349
396
  if (!/\bfpdf\b|\bFPDF\b/.test(source))
350
397
  return source;
351
398
  let out = source;
352
- for (const [from, to] of LATIN1_SUBSTITUTES)
399
+ for (const [from, to] of LATIN1_SUBSTITUTES) {
353
400
  out = out.replace(from, to);
401
+ }
354
402
  return out;
355
403
  }
356
404
  const CURLY_QUOTE = /[\u2018\u2019\u201a\u2032\u201c\u201d\u201e\u2033]/u;
@@ -376,8 +424,9 @@ export function fpdfNonLatin1Hint(source) {
376
424
  if (source.includes('.replace('))
377
425
  return null;
378
426
  const bad = /[^\u0000-\u00ff]/u.exec(normalizeFpdfSource(source))?.[0];
379
- if (!bad)
427
+ if (!bad) {
380
428
  return null;
429
+ }
381
430
  if (CURLY_QUOTE.test(bad)) {
382
431
  return (`fpdf2 core fonts are latin-1 only and the curly quote ${bad} is outside it — rewrite ` +
383
432
  'the strings with straight quotes \' and " and rerun.');
@@ -389,8 +438,9 @@ export function fpdfNonLatin1Hint(source) {
389
438
  export function fpdfUnicodeErrorHint(stderr) {
390
439
  const encoding = /FPDFUnicodeEncodingException/.test(stderr) ||
391
440
  (/UnicodeEncodeError: 'latin-1' codec/.test(stderr) && /fpdf/.test(stderr));
392
- if (!encoding)
441
+ if (!encoding) {
393
442
  return null;
443
+ }
394
444
  return ('a character outside latin-1 reached an fpdf2 core font: replace it with ASCII ' +
395
445
  '(bullets "- ", straight quotes, plain hyphens). CJK and emoji cannot be rendered — ' +
396
446
  'tell the user instead of retrying with the same string.');
@@ -404,46 +454,58 @@ export function fpdfHorizontalSpaceHint(stderr) {
404
454
  }
405
455
  export function rgbColorImportHint(stderr) {
406
456
  const name = /cannot import name '([^']+)' from 'pptx\.dml\.color'/.exec(stderr)?.[1];
407
- if (!name || name === 'RGBColor')
457
+ if (!name || name === 'RGBColor') {
408
458
  return null;
409
- if (!/^rgb_?color$/i.test(name))
459
+ }
460
+ if (!/^rgb_?color$/i.test(name)) {
410
461
  return null;
462
+ }
411
463
  return (`pptx.dml.color exports RGBColor (all-caps RGB), not ${name}: ` +
412
464
  'from pptx.dml.color import RGBColor');
413
465
  }
414
466
  export function checkPythonSource(command) {
415
467
  const source = unwrapPythonSource(command) ?? command;
416
468
  const literal = stringLiteralProgramHint(source);
417
- if (literal)
469
+ if (literal) {
418
470
  return literal;
471
+ }
419
472
  const collapsed = checkCollapsedCompound(source);
420
- if (collapsed)
473
+ if (collapsed) {
421
474
  return collapsed;
475
+ }
422
476
  const mangled = mangledPptxApiHint(source);
423
- if (mangled)
477
+ if (mangled) {
424
478
  return mangled;
479
+ }
425
480
  const rgb = wrongRgbColorHint(source);
426
- if (rgb)
481
+ if (rgb) {
427
482
  return rgb;
483
+ }
428
484
  const fpdfKeyword = fpdfSizeKeywordHint(source);
429
- if (fpdfKeyword)
485
+ if (fpdfKeyword) {
430
486
  return fpdfKeyword;
487
+ }
431
488
  const fpdfLatin1 = fpdfNonLatin1Hint(source);
432
- if (fpdfLatin1)
489
+ if (fpdfLatin1) {
433
490
  return fpdfLatin1;
491
+ }
434
492
  const fpdfText = fpdfTextCallHint(source);
435
- if (fpdfText)
493
+ if (fpdfText) {
436
494
  return fpdfText;
437
- const first = command.trimStart().split('\n')[0];
438
- if (!SHELL_WRAPPER.test(first))
495
+ }
496
+ const first = command.trimStart().split('\n')[0] ?? '';
497
+ if (!SHELL_WRAPPER.test(first)) {
439
498
  return null;
440
- if (pipInstallTargets(command))
499
+ }
500
+ if (pipInstallTargets(command)) {
441
501
  return null;
502
+ }
442
503
  if (/^pip3?\b/.test(first) || /^(python3?|py)\s+-m\s+pip\b/.test(first)) {
443
504
  return 'language "python" does not run shell commands: install packages via the packages argument (e.g. packages: ["python-pptx==1.0.2"]), not pip';
444
505
  }
445
- if (unwrapPythonSource(command))
506
+ if (unwrapPythonSource(command)) {
446
507
  return null;
508
+ }
447
509
  return 'language "python" runs command as Python source: its first line is the first line of the program, so command must start with the code itself. There is no shell, no interpreter, and no script file to name.';
448
510
  }
449
511
  export function syntaxErrorHint(stderr, source) {
@@ -454,27 +516,36 @@ export function syntaxErrorHint(stderr, source) {
454
516
  'SyntaxError means the Python source is invalid as written — keep real newlines for for/if/with/def bodies; do not collapse a multi-line program into one semicolon-separated line.');
455
517
  }
456
518
  export function checkPptxBuildSource(source, outputs, packages) {
457
- if (!outputs?.some((name) => name.toLowerCase().endsWith('.pptx')))
519
+ if (!outputs?.some((name) => name.toLowerCase().endsWith('.pptx'))) {
458
520
  return null;
521
+ }
459
522
  const pkgPptx = (packages ?? []).some((p) => {
460
- const name = p.split(/[=<>!~[]/)[0].trim().toLowerCase();
523
+ const name = (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase();
461
524
  return name === 'python-pptx' || name === 'pptx';
462
525
  });
463
526
  const srcPptx = /\bpptx\b|\bPresentation\s*\(/.test(source);
464
- if (!pkgPptx && !srcPptx)
527
+ if (!pkgPptx && !srcPptx) {
465
528
  return null;
529
+ }
466
530
  const builds = /\bPresentation\s*\(/.test(source) && /\.save\s*\(/.test(source);
467
- if (builds)
531
+ if (builds) {
468
532
  return null;
533
+ }
469
534
  return ('outputs declare a .pptx but command does not build one: pass multi-line python-pptx ' +
470
535
  'source that constructs Presentation(...) and calls save("deck.pptx") matching an ' +
471
536
  'outputs entry. Do not use python -c, os.listdir, pip, or a different package pin — ' +
472
537
  'language="python" already runs bare source, and packages must stay ["python-pptx==1.0.2"].');
473
538
  }
539
+ export function tripleQuoteAt(source, index) {
540
+ if (source.startsWith('"""', index))
541
+ return '"""';
542
+ return source.startsWith("'''", index) ? "'''" : null;
543
+ }
474
544
  export function isEscaped(source, index) {
475
545
  let slashes = 0;
476
- while (index - slashes > 0 && source[index - slashes - 1] === '\\')
546
+ while (index - slashes > 0 && source[index - slashes - 1] === '\\') {
477
547
  slashes++;
548
+ }
478
549
  return slashes % 2 === 1;
479
550
  }
480
551
  function splitCallArgs(source, open) {
@@ -483,11 +554,12 @@ function splitCallArgs(source, open) {
483
554
  let depth = 0;
484
555
  let quote = null;
485
556
  for (let i = open; i < source.length; i++) {
486
- const ch = source[i];
557
+ const ch = source.charAt(i);
487
558
  if (quote) {
488
559
  arg += ch;
489
- if (ch === quote && !isEscaped(source, i))
560
+ if (ch === quote && !isEscaped(source, i)) {
490
561
  quote = null;
562
+ }
491
563
  continue;
492
564
  }
493
565
  if (ch === '"' || ch === "'") {
@@ -497,8 +569,9 @@ function splitCallArgs(source, open) {
497
569
  }
498
570
  if (ch === '(' || ch === '[' || ch === '{') {
499
571
  depth++;
500
- if (depth > 1)
572
+ if (depth > 1) {
501
573
  arg += ch;
574
+ }
502
575
  continue;
503
576
  }
504
577
  if (ch === ')' || ch === ']' || ch === '}') {
@@ -530,20 +603,22 @@ function stripCommentsAndStrings(source) {
530
603
  let out = '';
531
604
  let i = 0;
532
605
  while (i < source.length) {
533
- const ch = source[i];
534
- const triple = source.startsWith('"""', i) ? '"""' : source.startsWith("'''", i) ? "'''" : null;
606
+ const ch = source.charAt(i);
607
+ const triple = tripleQuoteAt(source, i);
535
608
  const quote = triple ?? (ch === '"' || ch === "'" ? ch : null);
536
609
  if (quote) {
537
610
  i += quote.length;
538
- while (i < source.length && !(source.startsWith(quote, i) && !isEscaped(source, i)))
611
+ while (i < source.length && !(source.startsWith(quote, i) && !isEscaped(source, i))) {
539
612
  i++;
613
+ }
540
614
  out += '""';
541
615
  i += quote.length;
542
616
  continue;
543
617
  }
544
618
  if (ch === '#') {
545
- while (i < source.length && source[i] !== '\n')
619
+ while (i < source.length && source[i] !== '\n') {
546
620
  i++;
621
+ }
547
622
  continue;
548
623
  }
549
624
  out += ch;
@@ -564,15 +639,19 @@ const LENGTH_LIBRARY = {
564
639
  }
565
640
  };
566
641
  function lengthLibrary(source, packages) {
567
- const names = (packages ?? []).map((p) => p.split(/[=<>!~[]/)[0].trim().toLowerCase());
568
- if (names.includes('python-pptx') || names.includes('pptx'))
642
+ const names = (packages ?? []).map((p) => (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase());
643
+ if (names.includes('python-pptx') || names.includes('pptx')) {
569
644
  return 'pptx';
570
- if (names.includes('python-docx') || names.includes('docx'))
645
+ }
646
+ if (names.includes('python-docx') || names.includes('docx')) {
571
647
  return 'docx';
572
- if (/\bpptx\b|\bPresentation\s*\(/.test(source))
648
+ }
649
+ if (/\bpptx\b|\bPresentation\s*\(/.test(source)) {
573
650
  return 'pptx';
574
- if (/\bdocx\b|\bDocument\s*\(/.test(source))
651
+ }
652
+ if (/\bdocx\b|\bDocument\s*\(/.test(source)) {
575
653
  return 'docx';
654
+ }
576
655
  return null;
577
656
  }
578
657
  const LENGTH_KEYWORDS = ['left', 'top', 'width', 'height'];
@@ -582,22 +661,25 @@ function callLibrary(method, before, fallback) {
582
661
  return 'pptx';
583
662
  if (/\bshapes\s*\.\s*$/.test(before))
584
663
  return 'pptx';
585
- if (/\b(?:doc|document|run|paragraph|cell)\s*\.\s*$/.test(before))
664
+ if (/\b(?:doc|document|run|paragraph|cell)\s*\.\s*$/.test(before)) {
586
665
  return 'docx';
666
+ }
587
667
  return fallback;
588
668
  }
589
669
  export function checkBareLengthArgs(source, packages) {
590
670
  const clean = stripCommentsAndStrings(source);
591
671
  const library = lengthLibrary(clean, packages);
592
- if (!library)
672
+ if (!library) {
593
673
  return null;
674
+ }
594
675
  for (const method of ['add_textbox', 'add_picture']) {
595
676
  const calls = new RegExp(`\\b${method}\\s*\\(`, 'g');
596
677
  let call;
597
678
  while ((call = calls.exec(clean)) !== null) {
598
679
  const args = splitCallArgs(clean, calls.lastIndex - 1);
599
- if (!args)
680
+ if (!args) {
600
681
  continue;
682
+ }
601
683
  const offender = args.find((arg, index) => {
602
684
  const eq = arg.indexOf('=');
603
685
  const name = eq === -1 ? '' : arg.slice(0, eq).trim();
@@ -616,7 +698,7 @@ export function checkBareLengthArgs(source, packages) {
616
698
  const assignments = /\bslide_(?:width|height)\s*=\s*([^\n]+)/g;
617
699
  let assigned;
618
700
  while ((assigned = assignments.exec(clean)) !== null) {
619
- const value = assigned[1].trim();
701
+ const value = (assigned[1] ?? '').trim();
620
702
  if (bareEmuMistake(value)) {
621
703
  return bareLengthError('slide_width / slide_height', value, library);
622
704
  }
@@ -636,15 +718,17 @@ export function checkPdfBuildSource(source, outputs, packages) {
636
718
  if (!outputs?.some((name) => name.toLowerCase().endsWith('.pdf')))
637
719
  return null;
638
720
  const pkgPdf = (packages ?? []).some((p) => {
639
- const name = p.split(/[=<>!~[]/)[0].trim().toLowerCase();
721
+ const name = (p.split(/[=<>!~[]/)[0] ?? '').trim().toLowerCase();
640
722
  return name === 'fpdf2' || name === 'pypdf';
641
723
  });
642
724
  const srcPdf = /\b(fpdf|FPDF|pypdf|PdfWriter|PdfReader)\b/.test(source);
643
- if (!pkgPdf && !srcPdf)
725
+ if (!pkgPdf && !srcPdf) {
644
726
  return null;
727
+ }
645
728
  const builds = /\.output\s*\(|\.write\s*\(/.test(source);
646
- if (builds)
729
+ if (builds) {
647
730
  return null;
731
+ }
648
732
  return ('outputs declare a .pdf but command never writes one: pass multi-line Python that ' +
649
733
  'builds the document and saves it under the declared name — fpdf2 ends with ' +
650
734
  'pdf.output("name.pdf"), pypdf with writer.write("name.pdf"). Do not send diagnostics ' +
@@ -654,12 +738,13 @@ export function stripCommentsAndDocstrings(source) {
654
738
  let out = '';
655
739
  let i = 0;
656
740
  while (i < source.length) {
657
- const ch = source[i];
658
- const triple = source.startsWith('"""', i) ? '"""' : source.startsWith("'''", i) ? "'''" : null;
741
+ const ch = source.charAt(i);
742
+ const triple = tripleQuoteAt(source, i);
659
743
  if (triple) {
660
744
  i += triple.length;
661
- while (i < source.length && !(source.startsWith(triple, i) && !isEscaped(source, i)))
745
+ while (i < source.length && !(source.startsWith(triple, i) && !isEscaped(source, i))) {
662
746
  i++;
747
+ }
663
748
  out += '""';
664
749
  i += triple.length;
665
750
  continue;
@@ -667,15 +752,17 @@ export function stripCommentsAndDocstrings(source) {
667
752
  if (ch === '"' || ch === "'") {
668
753
  const start = i;
669
754
  i++;
670
- while (i < source.length && !(source[i] === ch && !isEscaped(source, i)))
755
+ while (i < source.length && !(source[i] === ch && !isEscaped(source, i))) {
671
756
  i++;
757
+ }
672
758
  i++;
673
759
  out += source.slice(start, Math.min(i, source.length));
674
760
  continue;
675
761
  }
676
762
  if (ch === '#') {
677
- while (i < source.length && source[i] !== '\n')
763
+ while (i < source.length && source[i] !== '\n') {
678
764
  i++;
765
+ }
679
766
  continue;
680
767
  }
681
768
  out += ch;
@@ -692,15 +779,18 @@ export function checkUnstagedRead(source, inputs) {
692
779
  const reads = new RegExp(pattern.source, 'g');
693
780
  let match;
694
781
  while ((match = reads.exec(clean)) !== null) {
695
- const name = match.slice(1).find((group) => group !== undefined);
782
+ const name = match.slice(1).find((group) => group !== undefined) ?? '';
696
783
  const mode = match.groups?.mode;
697
- if (mode !== undefined && /[wax]/.test(mode))
784
+ if (mode !== undefined && /[wax]/.test(mode)) {
698
785
  continue;
699
- if (staged.has(name))
786
+ }
787
+ if (staged.has(name)) {
700
788
  continue;
789
+ }
701
790
  const before = clean.slice(0, match.index);
702
- if (before.includes(`"${name}"`) || before.includes(`'${name}'`))
791
+ if (before.includes(`"${name}"`) || before.includes(`'${name}'`)) {
703
792
  continue;
793
+ }
704
794
  return (`${name} does not exist in the working directory — it starts empty on every run, so ` +
705
795
  'files from earlier calls are gone. Stage the attachment via inputs: ' +
706
796
  `[{ "attachmentId": "<id from the tool result that produced it, or from the ` +
@@ -714,8 +804,9 @@ export function checkUnstagedRead(source, inputs) {
714
804
  const ATTACHMENT_ID = /^[0-9a-f]{32}$/i;
715
805
  export function missingFileHint(stderr) {
716
806
  const name = /FileNotFoundError: \[Errno \d+\] No such file or directory: '([^']+)'/.exec(stderr)?.[1];
717
- if (!name)
807
+ if (!name) {
718
808
  return null;
809
+ }
719
810
  if (ATTACHMENT_ID.test(name)) {
720
811
  return (`"${name}" is an attachment id, not a file name, and nothing is on disk under it. ` +
721
812
  'An id belongs in `attachmentId`; `path` is a bare file name you choose, and the code ' +
@@ -751,30 +842,31 @@ function inputEntry(value) {
751
842
  export function parseInputs(value) {
752
843
  if (value === undefined || value === null)
753
844
  return undefined;
754
- if (!Array.isArray(value))
845
+ if (!Array.isArray(value)) {
755
846
  return { error: 'inputs must be an array' };
756
- if (value.length > MAX_INPUTS)
847
+ }
848
+ if (value.length > MAX_INPUTS) {
757
849
  return { error: `inputs must declare at most ${MAX_INPUTS} files` };
850
+ }
758
851
  const parsed = [];
759
852
  const paths = new Set();
760
853
  for (const raw of value) {
761
854
  const entry = inputEntry(raw);
762
- if (!entry)
855
+ if (!entry) {
763
856
  return { error: 'each input must be an { attachmentId, path } object' };
764
- const attachmentId = entry.attachmentId === undefined
765
- ? undefined
766
- : typeof entry.attachmentId === 'string'
767
- ? entry.attachmentId.trim()
768
- : '';
857
+ }
858
+ const attachmentId = attachmentIdOf(entry.attachmentId);
769
859
  if (attachmentId === '') {
770
860
  return { error: 'attachmentId must be a non-empty string, or omitted for a user upload' };
771
861
  }
772
862
  const path = typeof entry.path === 'string' ? entry.path.trim() : '';
773
863
  const nameError = checkFileName(path);
774
- if (nameError)
864
+ if (nameError) {
775
865
  return { error: `input path: ${nameError}` };
776
- if (paths.has(path))
866
+ }
867
+ if (paths.has(path)) {
777
868
  return { error: 'input paths must be unique' };
869
+ }
778
870
  paths.add(path);
779
871
  parsed.push({ attachmentId, path });
780
872
  }
@@ -792,47 +884,57 @@ export function parseOutputs(value) {
792
884
  const trimmed = [...new Set(value.map((o) => o.trim()))];
793
885
  for (const name of trimmed) {
794
886
  const nameError = checkOutputName(name);
795
- if (nameError)
887
+ if (nameError) {
796
888
  return { error: `output name: ${nameError}` };
889
+ }
797
890
  }
798
891
  return trimmed;
799
892
  }
800
893
  export function parseWorkdir(value) {
801
894
  if (value === undefined || value === null)
802
895
  return undefined;
803
- if (typeof value !== 'string')
896
+ if (typeof value !== 'string') {
804
897
  return { error: 'workdir must be a string' };
898
+ }
805
899
  const trimmed = value.trim();
806
- if (!trimmed)
900
+ if (!trimmed) {
807
901
  return undefined;
902
+ }
808
903
  const nameError = checkFileName(trimmed);
809
- if (nameError)
904
+ if (nameError) {
810
905
  return { error: `workdir: ${nameError}` };
906
+ }
811
907
  return trimmed;
812
908
  }
813
909
  function prepare(args, cwd) {
814
910
  const packages = parsePackages(args.packages);
815
- if (packages && 'error' in packages)
911
+ if (packages && 'error' in packages) {
816
912
  return packages;
913
+ }
817
914
  const inputs = parseInputs(args.inputs);
818
- if (inputs && 'error' in inputs)
915
+ if (inputs && 'error' in inputs) {
819
916
  return inputs;
917
+ }
820
918
  const outputs = parseOutputs(args.outputs);
821
- if (outputs && 'error' in outputs)
919
+ if (outputs && 'error' in outputs) {
822
920
  return outputs;
921
+ }
823
922
  const workdir = parseWorkdir(cwd);
824
- if (workdir && typeof workdir === 'object')
923
+ if (workdir && typeof workdir === 'object') {
825
924
  return workdir;
925
+ }
826
926
  return { packages, inputs, outputs, workdir };
827
927
  }
828
- export function missingModuleHint(stderr, packages, sandbox = false, network = false) {
928
+ export function missingModuleHint(stderr, { packages, sandbox = false, network = false } = {}) {
829
929
  const module = /ModuleNotFoundError: No module named '([^']+)'/.exec(stderr)?.[1];
830
- if (!module)
930
+ if (!module) {
831
931
  return null;
832
- const root = module.split('.')[0];
932
+ }
933
+ const root = module.split('.')[0] ?? '';
833
934
  const tool = videoTool(root);
834
- if (tool && (!network || isTranscoder(tool)))
935
+ if (tool && (!network || isTranscoder(tool))) {
835
936
  return network ? TRANSCODE_ERROR : VIDEO_TOOLS_ERROR;
937
+ }
836
938
  if (module !== root) {
837
939
  const then = sandbox
838
940
  ? 'If the path is right it ships as its own distribution under a shared namespace, which sandbox mode cannot install: tell the user.'
@@ -842,8 +944,9 @@ export function missingModuleHint(stderr, packages, sandbox = false, network = f
842
944
  const distribution = moduleDistribution(root);
843
945
  const provider = distribution ?? root;
844
946
  const vendored = vendoredSpec(provider);
845
- if (sandbox && !vendored)
947
+ if (sandbox && !vendored) {
846
948
  return `module "${root}" is not installed, ${SANDBOX_NO_INSTALL}`;
949
+ }
847
950
  const spec = vendored ?? provider;
848
951
  const renamed = distribution !== null && distribution.toLowerCase() !== root.toLowerCase();
849
952
  const declared = (packages ?? []).map(specName);
@@ -863,15 +966,17 @@ export function nonePlaceholderHint(stderr) {
863
966
  }
864
967
  export function slideShapesHint(stderr) {
865
968
  const attribute = /AttributeError: 'Slide' object has no attribute '(add_picture|add_textbox|addpicture|addtextbox)'/.exec(stderr)?.[1];
866
- if (!attribute)
969
+ if (!attribute) {
867
970
  return null;
971
+ }
868
972
  const method = attribute.startsWith('addp') ? 'add_picture' : 'add_textbox';
869
973
  return `a Slide has no add_* methods of its own — they live on its shapes collection: slide.shapes.${method}(...), not slide.${method}(...).`;
870
974
  }
871
975
  export function shapeFontHint(stderr) {
872
976
  const attribute = /AttributeError: '\w*(?:Placeholder|Shape|Slide)\w*' object has no attribute '(font|alignment|text_align|word_wrap|paragraphs|add_paragraph)'/.exec(stderr)?.[1];
873
- if (!attribute)
977
+ if (!attribute) {
874
978
  return null;
979
+ }
875
980
  return `"${attribute}" belongs to the text frame or a paragraph inside it, not to the shape: go through shape.text_frame first (shape.text_frame.word_wrap, shape.text_frame.paragraphs[0].font.size, shape.text_frame.paragraphs[0].alignment). shape.text is the one shortcut that reads through to the frame.`;
876
981
  }
877
982
  export function shapesBackgroundHint(stderr) {
@@ -902,8 +1007,9 @@ export function paragraphIndexHint(stderr, outputs) {
902
1007
  }
903
1008
  export function shellNameHint(stderr) {
904
1009
  const name = /NameError: name '(python3?|py|pip3?)' is not defined/.exec(stderr)?.[1];
905
- if (!name)
1010
+ if (!name) {
906
1011
  return null;
1012
+ }
907
1013
  return `"${name}" is not defined because command already runs as Python source — remove the ${name} invocation and pass only the Python code itself`;
908
1014
  }
909
1015
  export function noProcessHint(stderr, sandbox = false) {
@@ -929,14 +1035,16 @@ export function noNetworkHint(stderr, tools, network = false) {
929
1035
  }
930
1036
  export function nativeWheelHint(stderr) {
931
1037
  const name = /Can't find a pure Python 3 wheel for '([^']+)'/.exec(stderr)?.[1];
932
- if (!name)
1038
+ if (!name) {
933
1039
  return null;
1040
+ }
934
1041
  return `"${name}" is built from compiled code, and this device installs only pure-Python packages plus the set already built into its Python. Nothing about this call can install it: use a pure-Python library instead, or tell the user it is not available here.`;
935
1042
  }
936
1043
  export function packageFetchFailedHint(stderr) {
937
1044
  const name = /Can't fetch metadata for '([^']+)'/.exec(stderr)?.[1];
938
- if (!name)
1045
+ if (!name) {
939
1046
  return null;
1047
+ }
940
1048
  return `"${name}" could not be fetched: either it is not a PyPI distribution name or this device has no network. Where a skill documents an exact spec for that library, send that spec verbatim; otherwise the package cannot be installed here — tell the user and stop. Do not try other names.`;
941
1049
  }
942
1050
  function fail(stderr) {
@@ -966,23 +1074,26 @@ export function softFailedDeckHint(stdout, exitCode, source) {
966
1074
  return ('the deck soft-failed (caught an image/import error and continued): remove try/except ' +
967
1075
  'around imports and add_picture, fix the error, and rerun once — a partial deck must not be saved');
968
1076
  }
969
- export function stagedImagesMissingFromDeck(result, inputs, outputs, source) {
1077
+ export function stagedImagesMissingFromDeck(result, { inputs, outputs, source }) {
970
1078
  if (result.exitCode !== 0)
971
1079
  return null;
972
1080
  if (!/\bPresentation\s*\(/.test(source))
973
1081
  return null;
974
1082
  const images = (inputs ?? []).filter((i) => IMAGE_INPUT.test(i.path));
975
- if (!images.length)
1083
+ const [first] = images;
1084
+ if (!first) {
976
1085
  return null;
1086
+ }
977
1087
  for (const name of outputs ?? []) {
978
1088
  if (!name.toLowerCase().endsWith('.pptx'))
979
1089
  continue;
980
1090
  const bytes = result.files?.get(name);
981
- if (!bytes)
1091
+ if (!bytes) {
982
1092
  continue;
1093
+ }
983
1094
  if (!pptxHasEmbeddedMedia(bytes)) {
984
1095
  return (`staged image(s) ${images.map((i) => i.path).join(', ')} were not embedded: ` +
985
- `call slide.shapes.add_picture("${images[0].path}", …) without try/except, then save`);
1096
+ `call slide.shapes.add_picture("${first.path}", …) without try/except, then save`);
986
1097
  }
987
1098
  }
988
1099
  return null;
@@ -992,11 +1103,13 @@ export function checkStagedImagesUsed(source, inputs, outputs) {
992
1103
  return null;
993
1104
  if (!outputs?.some((n) => n.toLowerCase().endsWith('.pptx')))
994
1105
  return null;
995
- if (!/\bPresentation\s*\(/.test(source))
1106
+ if (!/\bPresentation\s*\(/.test(source)) {
996
1107
  return null;
1108
+ }
997
1109
  const images = inputs.filter((i) => IMAGE_INPUT.test(i.path));
998
- if (!images.length)
1110
+ if (!images.length) {
999
1111
  return null;
1112
+ }
1000
1113
  if (!/\.add_picture\s*\(/.test(source)) {
1001
1114
  return ('inputs stage image(s) but command never calls slide.shapes.add_picture(...): ' +
1002
1115
  'embed each staged path or drop it from inputs');
@@ -1011,26 +1124,30 @@ export function checkStagedImagesUsed(source, inputs, outputs) {
1011
1124
  }
1012
1125
  return null;
1013
1126
  }
1014
- export function checkTurnImagesStaged(source, inputs, outputs, turnImages) {
1127
+ export function checkTurnImagesStaged(source, { inputs, outputs, turnImages }) {
1015
1128
  if (!turnImages?.length)
1016
1129
  return null;
1017
1130
  if (!outputs?.some((n) => n.toLowerCase().endsWith('.pptx')))
1018
1131
  return null;
1019
- if (!/\bPresentation\s*\(/.test(source))
1132
+ if (!/\bPresentation\s*\(/.test(source)) {
1020
1133
  return null;
1021
- if ((inputs ?? []).some((i) => i.attachmentId === undefined))
1134
+ }
1135
+ if ((inputs ?? []).some((i) => i.attachmentId === undefined)) {
1022
1136
  return null;
1137
+ }
1023
1138
  return (`the user's message has ${turnImages.length} uploaded image(s) but inputs stage none of them — ` +
1024
1139
  'add one id-less { "path": "…" } entry per image and embed each with slide.shapes.add_picture(...)');
1025
1140
  }
1026
1141
  export function deckDeliveredStop(attachments, exitCode, source) {
1027
1142
  if (exitCode !== 0 || !attachments?.length)
1028
1143
  return null;
1029
- if (source !== undefined && !/\bpptx\b|\bPresentation\s*\(/.test(source))
1144
+ if (source !== undefined && !/\bpptx\b|\bPresentation\s*\(/.test(source)) {
1030
1145
  return null;
1146
+ }
1031
1147
  const decks = attachments.filter((a) => a.fileName.toLowerCase().endsWith('.pptx'));
1032
- if (decks.length === 0)
1148
+ if (decks.length === 0) {
1033
1149
  return null;
1150
+ }
1034
1151
  const names = decks.map((a) => a.fileName).join(', ');
1035
1152
  const verb = decks.length === 1 ? 'is' : 'are';
1036
1153
  return (`${names} ${verb} attached to the chat. STOP: do not call exec again for this deck. ` +
@@ -1040,18 +1157,20 @@ export function deckDeliveredStop(attachments, exitCode, source) {
1040
1157
  async function stage(inputs, fetchAttachment, opts = {}) {
1041
1158
  if (!inputs?.length)
1042
1159
  return undefined;
1043
- if (!fetchAttachment)
1160
+ if (!fetchAttachment) {
1044
1161
  return { error: 'this device cannot read chat attachments' };
1162
+ }
1045
1163
  return stageInputs(fetchAttachment, inputs, opts);
1046
1164
  }
1047
- async function collect(result, declared, attach, runBudget, source) {
1165
+ async function collect(result, { declared, attach, runBudget, source }) {
1048
1166
  const base = {
1049
1167
  stdout: result.stdout,
1050
1168
  stderr: result.stderr,
1051
1169
  exitCode: result.exitCode
1052
1170
  };
1053
- if (!declared?.length || !attach)
1171
+ if (!declared?.length || !attach) {
1054
1172
  return base;
1173
+ }
1055
1174
  const pptxBuild = source !== undefined && /\bpptx\b|\bPresentation\s*\(/.test(source);
1056
1175
  const pptxNames = declared.filter((n) => n.toLowerCase().endsWith('.pptx') && result.files?.has(n));
1057
1176
  const wouldAttachPptx = result.exitCode === 0 && pptxBuild && pptxNames.length > 0;
@@ -1064,7 +1183,7 @@ async function collect(result, declared, attach, runBudget, source) {
1064
1183
  }
1065
1184
  }
1066
1185
  }
1067
- const { attachments, missingOutputs, rejectedOutputs } = await attachOutputs(attach, (result.exitCode === 0 && result.files) || new Map(), declared, runBudget);
1186
+ const { attachments, missingOutputs, rejectedOutputs } = await attachOutputs(attach, (result.exitCode === 0 && result.files) || new Map(), { declared, runBudget });
1068
1187
  const rejected = [...(result.rejected ?? []), ...rejectedOutputs];
1069
1188
  const stop = deckDeliveredStop(attachments, result.exitCode, source);
1070
1189
  if (stop && result.exitCode === 0) {
@@ -1073,8 +1192,9 @@ async function collect(result, declared, attach, runBudget, source) {
1073
1192
  attached = new Set();
1074
1193
  deckAttachedThisTurn.set(attach, attached);
1075
1194
  }
1076
- for (const n of pptxNames)
1195
+ for (const n of pptxNames) {
1077
1196
  attached.add(n.toLowerCase());
1197
+ }
1078
1198
  }
1079
1199
  const stdout = stop && result.exitCode === 0
1080
1200
  ? `${base.stdout}${base.stdout && !base.stdout.endsWith('\n') ? '\n' : ''}${stop}`
@@ -1082,9 +1202,9 @@ async function collect(result, declared, attach, runBudget, source) {
1082
1202
  return {
1083
1203
  ...base,
1084
1204
  stdout,
1085
- ...(attachments.length > 0 && { attachments }),
1086
- ...(missingOutputs.length > 0 && { missingOutputs }),
1087
- ...(rejected.length > 0 && { rejectedOutputs: rejected })
1205
+ attachments: attachments.length > 0 ? attachments : undefined,
1206
+ missingOutputs: missingOutputs.length > 0 ? missingOutputs : undefined,
1207
+ rejectedOutputs: rejected.length > 0 ? rejected : undefined
1088
1208
  };
1089
1209
  }
1090
1210
  export function createPythonAdapter(disabled, loadPyodide, network = false) {
@@ -1105,33 +1225,46 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
1105
1225
  const confirmConnect = ctx?.confirmConnect;
1106
1226
  return {
1107
1227
  async run(options) {
1108
- if (!command)
1228
+ if (!command) {
1109
1229
  return fail('command is required');
1230
+ }
1110
1231
  const sourceError = checkPythonSource(command);
1111
- if (sourceError)
1232
+ if (sourceError) {
1112
1233
  return fail(sourceError);
1234
+ }
1113
1235
  const source = normalizeFpdfSource(unwrapPythonSource(command) ?? command);
1114
1236
  const prepared = prepare(args, options.cwd);
1115
- if ('error' in prepared)
1237
+ if ('error' in prepared) {
1116
1238
  return fail(prepared.error);
1239
+ }
1117
1240
  const pptxError = checkPptxBuildSource(source, prepared.outputs, prepared.packages);
1118
- if (pptxError)
1241
+ if (pptxError) {
1119
1242
  return fail(pptxError);
1243
+ }
1120
1244
  const pdfError = checkPdfBuildSource(source, prepared.outputs, prepared.packages);
1121
- if (pdfError)
1245
+ if (pdfError) {
1122
1246
  return fail(pdfError);
1247
+ }
1123
1248
  const bareLength = checkBareLengthArgs(source, prepared.packages);
1124
- if (bareLength)
1249
+ if (bareLength) {
1125
1250
  return fail(bareLength);
1251
+ }
1126
1252
  const unstagedRead = checkUnstagedRead(source, prepared.inputs);
1127
- if (unstagedRead)
1253
+ if (unstagedRead) {
1128
1254
  return fail(unstagedRead);
1255
+ }
1129
1256
  const stagedUse = checkStagedImagesUsed(source, prepared.inputs, prepared.outputs);
1130
- if (stagedUse)
1257
+ if (stagedUse) {
1131
1258
  return fail(stagedUse);
1132
- const turnStage = checkTurnImagesStaged(source, prepared.inputs, prepared.outputs, turnImages);
1133
- if (turnStage)
1259
+ }
1260
+ const turnStage = checkTurnImagesStaged(source, {
1261
+ inputs: prepared.inputs,
1262
+ outputs: prepared.outputs,
1263
+ turnImages
1264
+ });
1265
+ if (turnStage) {
1134
1266
  return fail(turnStage);
1267
+ }
1135
1268
  if (prepared.outputs?.length && !attach) {
1136
1269
  return fail('this device cannot persist produced files');
1137
1270
  }
@@ -1139,14 +1272,15 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
1139
1272
  turnImages,
1140
1273
  signal: options.signal
1141
1274
  });
1142
- if (staged && 'error' in staged)
1275
+ if (staged && 'error' in staged) {
1143
1276
  return fail(staged.error);
1277
+ }
1144
1278
  const install = pipInstallTargets(command)?.map(pinned) ?? null;
1145
1279
  const packages = install
1146
1280
  ? [...new Set([...(prepared.packages ?? []), ...install])]
1147
1281
  : prepared.packages;
1148
1282
  if (sandbox && install && !planVendoredInstall(packages ?? [])) {
1149
- return fail(pipMessage(install, true));
1283
+ return fail(pipMessage(install, { sandbox: true }));
1150
1284
  }
1151
1285
  const result = await runPython(install ? 'pass' : source, {
1152
1286
  timeoutMs: options.timeoutMs,
@@ -1162,15 +1296,21 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
1162
1296
  askConnect: gated ? confirmConnect : undefined
1163
1297
  });
1164
1298
  const soft = softFailedDeckHint(result.stdout, result.exitCode, source);
1165
- if (soft)
1299
+ if (soft) {
1166
1300
  return fail(soft);
1167
- const missingPic = stagedImagesMissingFromDeck(result, prepared.inputs, prepared.outputs, source);
1168
- if (missingPic)
1301
+ }
1302
+ const missingPic = stagedImagesMissingFromDeck(result, {
1303
+ inputs: prepared.inputs,
1304
+ outputs: prepared.outputs,
1305
+ source
1306
+ });
1307
+ if (missingPic) {
1169
1308
  return fail(missingPic);
1309
+ }
1170
1310
  const hint = packageFetchFailedHint(result.stderr) ??
1171
1311
  nativeWheelHint(result.stderr) ??
1172
- missingModuleHint(result.stderr, packages, sandbox, reach) ??
1173
- pipFromSourceHint(result.stderr, source, packages, sandbox) ??
1312
+ missingModuleHint(result.stderr, { packages, sandbox, network: reach }) ??
1313
+ pipFromSourceHint(result.stderr, source, { packages, sandbox }) ??
1174
1314
  noProcessHint(result.stderr, sandbox) ??
1175
1315
  noNetworkHint(result.stderr, toolNames, reach) ??
1176
1316
  missingFileHint(result.stderr) ??
@@ -1202,9 +1342,19 @@ export function createPythonAdapter(disabled, loadPyodide, network = false) {
1202
1342
  stderr: `${installed.stderr}${installed.stderr ? '\n' : ''}${result.undeclared.join(', ')} ${result.undeclared.length === 1 ? 'was' : 'were'} written but not declared in outputs, so ${result.undeclared.length === 1 ? 'it was' : 'they were'} discarded and nothing reached the user. There is no shell to inspect it with and nothing to look for on disk: declare it in outputs and run the same code again.`
1203
1343
  }
1204
1344
  : installed;
1205
- return collect(undelivered, prepared.outputs, attach, runBudget, source);
1345
+ return collect(undelivered, {
1346
+ declared: prepared.outputs,
1347
+ attach,
1348
+ runBudget,
1349
+ source
1350
+ });
1206
1351
  }
1207
1352
  };
1208
1353
  }
1209
1354
  };
1210
1355
  }
1356
+ function attachmentIdOf(value) {
1357
+ if (value === undefined)
1358
+ return undefined;
1359
+ return typeof value === 'string' ? value.trim() : '';
1360
+ }