@maudecode/talaria-web 1.17.2 → 1.19.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 (623) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.d.ts +1 -1
  3. package/dist/api/auth-raw.js +20 -20
  4. package/dist/api/auth-raw.js.map +1 -1
  5. package/dist/api/automation-raw.d.ts +7 -1
  6. package/dist/api/automation-raw.js +74 -27
  7. package/dist/api/automation-raw.js.map +1 -1
  8. package/dist/api/automation-router.d.ts +128 -23
  9. package/dist/api/automation-router.js +29 -13
  10. package/dist/api/automation-router.js.map +1 -1
  11. package/dist/api/chat-router.d.ts +1845 -40
  12. package/dist/api/chat-router.js +196 -114
  13. package/dist/api/chat-router.js.map +1 -1
  14. package/dist/api/git-router.d.ts +37 -3
  15. package/dist/api/git-router.js +47 -48
  16. package/dist/api/git-router.js.map +1 -1
  17. package/dist/api/raw-routes.d.ts +9 -1
  18. package/dist/api/raw-routes.js +286 -73
  19. package/dist/api/raw-routes.js.map +1 -1
  20. package/dist/api/router.d.ts +16 -6
  21. package/dist/api/router.js +28 -26
  22. package/dist/api/router.js.map +1 -1
  23. package/dist/api/session-visibility.d.ts +4 -4
  24. package/dist/api/session-visibility.js +26 -16
  25. package/dist/api/session-visibility.js.map +1 -1
  26. package/dist/api/sessions-router.d.ts +3312 -131
  27. package/dist/api/sessions-router.js +287 -157
  28. package/dist/api/sessions-router.js.map +1 -1
  29. package/dist/api/settings-router.d.ts +457 -8
  30. package/dist/api/settings-router.js +54 -33
  31. package/dist/api/settings-router.js.map +1 -1
  32. package/dist/api/sse-routes.d.ts +7 -2
  33. package/dist/api/sse-routes.js +109 -62
  34. package/dist/api/sse-routes.js.map +1 -1
  35. package/dist/api/tools-raw.d.ts +5 -0
  36. package/dist/api/tools-raw.js +63 -55
  37. package/dist/api/tools-raw.js.map +1 -1
  38. package/dist/api/tools-router.d.ts +998 -85
  39. package/dist/api/tools-router.js +447 -61
  40. package/dist/api/tools-router.js.map +1 -1
  41. package/dist/api/transcribe-stream.d.ts +16 -0
  42. package/dist/api/transcribe-stream.js +172 -0
  43. package/dist/api/transcribe-stream.js.map +1 -0
  44. package/dist/app.d.ts +4 -1
  45. package/dist/app.js +98 -59
  46. package/dist/app.js.map +1 -1
  47. package/dist/auth/gate.js +15 -11
  48. package/dist/auth/gate.js.map +1 -1
  49. package/dist/auth/oidc.d.ts +5 -2
  50. package/dist/auth/oidc.js +8 -7
  51. package/dist/auth/oidc.js.map +1 -1
  52. package/dist/auth/passkeys.d.ts +15 -14
  53. package/dist/auth/passkeys.js +89 -91
  54. package/dist/auth/passkeys.js.map +1 -1
  55. package/dist/auth/store.d.ts +17 -7
  56. package/dist/auth/store.js +93 -52
  57. package/dist/auth/store.js.map +1 -1
  58. package/dist/bin/talaria-web.js +12 -81
  59. package/dist/bin/talaria-web.js.map +1 -1
  60. package/dist/cli/ctl.js +2 -1
  61. package/dist/cli/ctl.js.map +1 -1
  62. package/dist/cli/dotenv.d.ts +5 -0
  63. package/dist/cli/dotenv.js +15 -2
  64. package/dist/cli/dotenv.js.map +1 -1
  65. package/dist/cli/launcher.js +5 -5
  66. package/dist/cli/launcher.js.map +1 -1
  67. package/dist/cli/main.d.ts +1 -0
  68. package/dist/cli/main.js +85 -0
  69. package/dist/cli/main.js.map +1 -0
  70. package/dist/cli/supervise.d.ts +2 -0
  71. package/dist/cli/supervise.js +67 -4
  72. package/dist/cli/supervise.js.map +1 -1
  73. package/dist/config/agent-config.d.ts +34 -11
  74. package/dist/config/agent-config.js +116 -60
  75. package/dist/config/agent-config.js.map +1 -1
  76. package/dist/config.d.ts +4 -0
  77. package/dist/config.js +19 -6
  78. package/dist/config.js.map +1 -1
  79. package/dist/fs/atomic.d.ts +1 -0
  80. package/dist/fs/atomic.js +4 -1
  81. package/dist/fs/atomic.js.map +1 -1
  82. package/dist/fs/stat.d.ts +1 -0
  83. package/dist/fs/stat.js +11 -0
  84. package/dist/fs/stat.js.map +1 -0
  85. package/dist/http/context.d.ts +31 -12
  86. package/dist/http/context.js +60 -24
  87. package/dist/http/context.js.map +1 -1
  88. package/dist/http/csp.d.ts +2 -1
  89. package/dist/http/csp.js +3 -2
  90. package/dist/http/csp.js.map +1 -1
  91. package/dist/onboarding.d.ts +2 -2
  92. package/dist/onboarding.js +10 -8
  93. package/dist/onboarding.js.map +1 -1
  94. package/dist/profiles/profiles.d.ts +20 -4
  95. package/dist/profiles/profiles.js +115 -30
  96. package/dist/profiles/profiles.js.map +1 -1
  97. package/dist/projects.d.ts +15 -6
  98. package/dist/projects.js +55 -47
  99. package/dist/projects.js.map +1 -1
  100. package/dist/providers/catalog.d.ts +84 -14
  101. package/dist/providers/catalog.js +474 -151
  102. package/dist/providers/catalog.js.map +1 -1
  103. package/dist/providers/env-file.d.ts +10 -3
  104. package/dist/providers/env-file.js +24 -75
  105. package/dist/providers/env-file.js.map +1 -1
  106. package/dist/providers/runtime-env.d.ts +4 -2
  107. package/dist/providers/runtime-env.js +9 -6
  108. package/dist/providers/runtime-env.js.map +1 -1
  109. package/dist/providers/tables.d.ts +3 -1
  110. package/dist/providers/tables.js +4 -2
  111. package/dist/providers/tables.js.map +1 -1
  112. package/dist/raster-image.d.ts +7 -0
  113. package/dist/raster-image.js +267 -0
  114. package/dist/raster-image.js.map +1 -0
  115. package/dist/redact.js +342 -46
  116. package/dist/redact.js.map +1 -1
  117. package/dist/release.d.ts +7 -0
  118. package/dist/release.js +14 -0
  119. package/dist/release.js.map +1 -1
  120. package/dist/runtime.d.ts +5 -1
  121. package/dist/runtime.js +305 -96
  122. package/dist/runtime.js.map +1 -1
  123. package/dist/server.js +15 -0
  124. package/dist/server.js.map +1 -1
  125. package/dist/sessions/anchor.d.ts +9 -3
  126. package/dist/sessions/anchor.js +28 -36
  127. package/dist/sessions/anchor.js.map +1 -1
  128. package/dist/sessions/background-events.d.ts +2 -0
  129. package/dist/sessions/background-events.js +10 -0
  130. package/dist/sessions/background-events.js.map +1 -0
  131. package/dist/sessions/background-tasks.d.ts +26 -27
  132. package/dist/sessions/background-tasks.js +89 -97
  133. package/dist/sessions/background-tasks.js.map +1 -1
  134. package/dist/sessions/background-updates.d.ts +15 -8
  135. package/dist/sessions/background-updates.js +101 -25
  136. package/dist/sessions/background-updates.js.map +1 -1
  137. package/dist/sessions/cli-sessions.d.ts +27 -6
  138. package/dist/sessions/cli-sessions.js +152 -42
  139. package/dist/sessions/cli-sessions.js.map +1 -1
  140. package/dist/sessions/completions.d.ts +77 -9
  141. package/dist/sessions/completions.js +482 -45
  142. package/dist/sessions/completions.js.map +1 -1
  143. package/dist/sessions/compress.d.ts +8 -0
  144. package/dist/sessions/compress.js +2 -2
  145. package/dist/sessions/compress.js.map +1 -1
  146. package/dist/sessions/drafts.d.ts +14 -10
  147. package/dist/sessions/drafts.js +38 -43
  148. package/dist/sessions/drafts.js.map +1 -1
  149. package/dist/sessions/export.d.ts +5 -0
  150. package/dist/sessions/export.js +30 -0
  151. package/dist/sessions/export.js.map +1 -1
  152. package/dist/sessions/gateway-sessions.d.ts +46 -0
  153. package/dist/sessions/gateway-sessions.js +378 -0
  154. package/dist/sessions/gateway-sessions.js.map +1 -0
  155. package/dist/sessions/gateway-watcher.d.ts +7 -3
  156. package/dist/sessions/gateway-watcher.js +31 -19
  157. package/dist/sessions/gateway-watcher.js.map +1 -1
  158. package/dist/sessions/goals.d.ts +31 -0
  159. package/dist/sessions/goals.js +75 -0
  160. package/dist/sessions/goals.js.map +1 -0
  161. package/dist/sessions/handoff.d.ts +32 -0
  162. package/dist/sessions/handoff.js +114 -0
  163. package/dist/sessions/handoff.js.map +1 -0
  164. package/dist/sessions/journal.d.ts +5 -79
  165. package/dist/sessions/journal.js +15 -606
  166. package/dist/sessions/journal.js.map +1 -1
  167. package/dist/sessions/list.d.ts +31 -16
  168. package/dist/sessions/list.js +139 -129
  169. package/dist/sessions/list.js.map +1 -1
  170. package/dist/sessions/merge.d.ts +69 -13
  171. package/dist/sessions/merge.js +265 -64
  172. package/dist/sessions/merge.js.map +1 -1
  173. package/dist/sessions/notices.d.ts +22 -0
  174. package/dist/sessions/notices.js +72 -0
  175. package/dist/sessions/notices.js.map +1 -0
  176. package/dist/sessions/pending.d.ts +10 -4
  177. package/dist/sessions/pending.js +83 -7
  178. package/dist/sessions/pending.js.map +1 -1
  179. package/dist/sessions/prefix-index.d.ts +82 -0
  180. package/dist/sessions/prefix-index.js +232 -0
  181. package/dist/sessions/prefix-index.js.map +1 -0
  182. package/dist/sessions/recovery.d.ts +19 -0
  183. package/dist/sessions/recovery.js +322 -0
  184. package/dist/sessions/recovery.js.map +1 -0
  185. package/dist/sessions/relay.d.ts +38 -19
  186. package/dist/sessions/relay.js +302 -79
  187. package/dist/sessions/relay.js.map +1 -1
  188. package/dist/sessions/service.d.ts +260 -55
  189. package/dist/sessions/service.js +1448 -462
  190. package/dist/sessions/service.js.map +1 -1
  191. package/dist/sessions/session.d.ts +24 -2
  192. package/dist/sessions/session.js +69 -7
  193. package/dist/sessions/session.js.map +1 -1
  194. package/dist/sessions/shares.d.ts +7 -6
  195. package/dist/sessions/shares.js +22 -45
  196. package/dist/sessions/shares.js.map +1 -1
  197. package/dist/sessions/source-kind.js +1 -0
  198. package/dist/sessions/source-kind.js.map +1 -1
  199. package/dist/sessions/state-db-identity.d.ts +18 -0
  200. package/dist/sessions/state-db-identity.js +67 -0
  201. package/dist/sessions/state-db-identity.js.map +1 -0
  202. package/dist/sessions/state-db-worker.d.ts +1 -0
  203. package/dist/sessions/state-db-worker.js +35 -0
  204. package/dist/sessions/state-db-worker.js.map +1 -0
  205. package/dist/sessions/state-db.d.ts +113 -7
  206. package/dist/sessions/state-db.js +861 -44
  207. package/dist/sessions/state-db.js.map +1 -1
  208. package/dist/sessions/storage/backend.d.ts +162 -0
  209. package/dist/sessions/storage/backend.js +19 -0
  210. package/dist/sessions/storage/backend.js.map +1 -0
  211. package/dist/sessions/storage/conformance.d.ts +20 -0
  212. package/dist/sessions/storage/conformance.js +237 -0
  213. package/dist/sessions/storage/conformance.js.map +1 -0
  214. package/dist/sessions/storage/document.d.ts +16 -0
  215. package/dist/sessions/storage/document.js +44 -0
  216. package/dist/sessions/storage/document.js.map +1 -0
  217. package/dist/sessions/storage/json-documents.d.ts +23 -0
  218. package/dist/sessions/storage/json-documents.js +233 -0
  219. package/dist/sessions/storage/json-documents.js.map +1 -0
  220. package/dist/sessions/storage/json-files.d.ts +12 -0
  221. package/dist/sessions/storage/json-files.js +124 -0
  222. package/dist/sessions/storage/json-files.js.map +1 -0
  223. package/dist/sessions/storage/json-installation.d.ts +3 -0
  224. package/dist/sessions/storage/json-installation.js +31 -0
  225. package/dist/sessions/storage/json-installation.js.map +1 -0
  226. package/dist/sessions/storage/json-journal.d.ts +114 -0
  227. package/dist/sessions/storage/json-journal.js +686 -0
  228. package/dist/sessions/storage/json-journal.js.map +1 -0
  229. package/dist/sessions/storage/json-recovery.d.ts +4 -0
  230. package/dist/sessions/storage/json-recovery.js +195 -0
  231. package/dist/sessions/storage/json-recovery.js.map +1 -0
  232. package/dist/sessions/storage/json.d.ts +68 -0
  233. package/dist/sessions/storage/json.js +258 -0
  234. package/dist/sessions/storage/json.js.map +1 -0
  235. package/dist/sessions/storage/row.d.ts +6 -0
  236. package/dist/sessions/storage/row.js +49 -0
  237. package/dist/sessions/storage/row.js.map +1 -0
  238. package/dist/sessions/storage/search-text.d.ts +4 -0
  239. package/dist/sessions/storage/search-text.js +16 -0
  240. package/dist/sessions/storage/search-text.js.map +1 -0
  241. package/dist/sessions/store.d.ts +65 -68
  242. package/dist/sessions/store.js +305 -602
  243. package/dist/sessions/store.js.map +1 -1
  244. package/dist/sessions/streams.d.ts +4 -0
  245. package/dist/sessions/streams.js +9 -1
  246. package/dist/sessions/streams.js.map +1 -1
  247. package/dist/sessions/titles.d.ts +2 -0
  248. package/dist/sessions/titles.js +5 -1
  249. package/dist/sessions/titles.js.map +1 -1
  250. package/dist/sessions/todo.d.ts +5 -0
  251. package/dist/sessions/todo.js +28 -21
  252. package/dist/sessions/todo.js.map +1 -1
  253. package/dist/sessions/tool-display.d.ts +5 -4
  254. package/dist/sessions/tool-display.js +30 -50
  255. package/dist/sessions/tool-display.js.map +1 -1
  256. package/dist/sessions/turn-context.d.ts +11 -2
  257. package/dist/sessions/turn-context.js +18 -11
  258. package/dist/sessions/turn-context.js.map +1 -1
  259. package/dist/sessions/turn.d.ts +122 -17
  260. package/dist/sessions/turn.js +939 -358
  261. package/dist/sessions/turn.js.map +1 -1
  262. package/dist/sessions/wakeup-pause.d.ts +54 -0
  263. package/dist/sessions/wakeup-pause.js +163 -0
  264. package/dist/sessions/wakeup-pause.js.map +1 -0
  265. package/dist/sessions/window.js +13 -12
  266. package/dist/sessions/window.js.map +1 -1
  267. package/dist/settings.d.ts +19 -7
  268. package/dist/settings.js +178 -184
  269. package/dist/settings.js.map +1 -1
  270. package/dist/sidecar/client.d.ts +17 -3
  271. package/dist/sidecar/client.js +78 -8
  272. package/dist/sidecar/client.js.map +1 -1
  273. package/dist/sidecar/discover.d.ts +2 -1
  274. package/dist/sidecar/discover.js +93 -10
  275. package/dist/sidecar/discover.js.map +1 -1
  276. package/dist/sidecar/env.d.ts +4 -0
  277. package/dist/sidecar/env.js +3 -0
  278. package/dist/sidecar/env.js.map +1 -0
  279. package/dist/sidecar/fake.d.ts +2 -2
  280. package/dist/sidecar/fake.js +19 -2
  281. package/dist/sidecar/fake.js.map +1 -1
  282. package/dist/startup.d.ts +13 -5
  283. package/dist/startup.js +48 -0
  284. package/dist/startup.js.map +1 -1
  285. package/dist/test/env-file.d.ts +5 -0
  286. package/dist/test/env-file.js +88 -0
  287. package/dist/test/env-file.js.map +1 -0
  288. package/dist/test/harness.d.ts +1 -0
  289. package/dist/test/harness.js +1 -1
  290. package/dist/test/harness.js.map +1 -1
  291. package/dist/test/passkeys.d.ts +22 -0
  292. package/dist/test/passkeys.js +62 -0
  293. package/dist/test/passkeys.js.map +1 -0
  294. package/dist/test/storage.d.ts +15 -0
  295. package/dist/test/storage.js +26 -0
  296. package/dist/test/storage.js.map +1 -0
  297. package/dist/test/ts-worker-hooks.d.ts +1 -0
  298. package/dist/test/ts-worker-hooks.js +24 -0
  299. package/dist/test/ts-worker-hooks.js.map +1 -0
  300. package/dist/text/diff.d.ts +9 -14
  301. package/dist/text/diff.js +74 -108
  302. package/dist/text/diff.js.map +1 -1
  303. package/dist/tools/commands.d.ts +6 -1
  304. package/dist/tools/commands.js +8 -2
  305. package/dist/tools/commands.js.map +1 -1
  306. package/dist/tools/connectors.d.ts +33 -0
  307. package/dist/tools/connectors.js +121 -0
  308. package/dist/tools/connectors.js.map +1 -0
  309. package/dist/tools/crons.d.ts +38 -1
  310. package/dist/tools/crons.js +122 -18
  311. package/dist/tools/crons.js.map +1 -1
  312. package/dist/tools/dashboard-plugins.d.ts +53 -0
  313. package/dist/tools/dashboard-plugins.js +188 -0
  314. package/dist/tools/dashboard-plugins.js.map +1 -0
  315. package/dist/tools/extensions.d.ts +18 -5
  316. package/dist/tools/extensions.js +344 -266
  317. package/dist/tools/extensions.js.map +1 -1
  318. package/dist/tools/gateway-restart.d.ts +62 -0
  319. package/dist/tools/gateway-restart.js +142 -0
  320. package/dist/tools/gateway-restart.js.map +1 -0
  321. package/dist/tools/health.d.ts +44 -0
  322. package/dist/tools/health.js +201 -46
  323. package/dist/tools/health.js.map +1 -1
  324. package/dist/tools/hygiene.d.ts +4 -3
  325. package/dist/tools/hygiene.js +23 -9
  326. package/dist/tools/hygiene.js.map +1 -1
  327. package/dist/tools/kanban.d.ts +4 -2
  328. package/dist/tools/kanban.js +28 -19
  329. package/dist/tools/kanban.js.map +1 -1
  330. package/dist/tools/mcp.d.ts +13 -0
  331. package/dist/tools/mcp.js +88 -0
  332. package/dist/tools/mcp.js.map +1 -1
  333. package/dist/tools/memory-masks.d.ts +5 -0
  334. package/dist/tools/memory-masks.js +256 -0
  335. package/dist/tools/memory-masks.js.map +1 -0
  336. package/dist/tools/memory.d.ts +29 -3
  337. package/dist/tools/memory.js +69 -27
  338. package/dist/tools/memory.js.map +1 -1
  339. package/dist/tools/plugin-settings.d.ts +15 -0
  340. package/dist/tools/plugin-settings.js +90 -0
  341. package/dist/tools/plugin-settings.js.map +1 -0
  342. package/dist/tools/project-os.d.ts +11 -0
  343. package/dist/tools/project-os.js +267 -0
  344. package/dist/tools/project-os.js.map +1 -0
  345. package/dist/tools/skills.d.ts +14 -5
  346. package/dist/tools/skills.js +49 -16
  347. package/dist/tools/skills.js.map +1 -1
  348. package/dist/tools/update-notifications.d.ts +34 -15
  349. package/dist/tools/update-notifications.js +141 -57
  350. package/dist/tools/update-notifications.js.map +1 -1
  351. package/dist/tools/update-rollback.d.ts +11 -0
  352. package/dist/tools/update-rollback.js +28 -0
  353. package/dist/tools/update-rollback.js.map +1 -0
  354. package/dist/tools/updates.d.ts +110 -32
  355. package/dist/tools/updates.js +1112 -294
  356. package/dist/tools/updates.js.map +1 -1
  357. package/dist/tools/wiki.d.ts +68 -0
  358. package/dist/tools/wiki.js +413 -0
  359. package/dist/tools/wiki.js.map +1 -0
  360. package/dist/util.d.ts +2 -0
  361. package/dist/util.js +5 -0
  362. package/dist/util.js.map +1 -1
  363. package/dist/workspace/escape.d.ts +61 -0
  364. package/dist/workspace/escape.js +149 -0
  365. package/dist/workspace/escape.js.map +1 -0
  366. package/dist/workspace/extract.d.ts +22 -0
  367. package/dist/workspace/extract.js +308 -0
  368. package/dist/workspace/extract.js.map +1 -0
  369. package/dist/workspace/fs.d.ts +12 -0
  370. package/dist/workspace/fs.js +92 -23
  371. package/dist/workspace/fs.js.map +1 -1
  372. package/dist/workspace/git.d.ts +34 -1
  373. package/dist/workspace/git.js +328 -35
  374. package/dist/workspace/git.js.map +1 -1
  375. package/dist/workspace/media.d.ts +16 -13
  376. package/dist/workspace/media.js +43 -61
  377. package/dist/workspace/media.js.map +1 -1
  378. package/dist/workspace/rollback.d.ts +36 -22
  379. package/dist/workspace/rollback.js +178 -272
  380. package/dist/workspace/rollback.js.map +1 -1
  381. package/dist/workspace/unzip.js +4 -1
  382. package/dist/workspace/unzip.js.map +1 -1
  383. package/dist/workspace/upload.d.ts +7 -4
  384. package/dist/workspace/upload.js +5 -2
  385. package/dist/workspace/upload.js.map +1 -1
  386. package/dist/workspace/workspaces.d.ts +15 -19
  387. package/dist/workspace/workspaces.js +55 -127
  388. package/dist/workspace/workspaces.js.map +1 -1
  389. package/dist/workspace/worktrees.d.ts +2 -1
  390. package/dist/workspace/worktrees.js +179 -42
  391. package/dist/workspace/worktrees.js.map +1 -1
  392. package/package.json +7 -3
  393. package/sidecar/agent_dependency.json +4 -4
  394. package/sidecar/agent_support.json +26 -0
  395. package/sidecar/scripts/record-fixtures.py +12 -3
  396. package/sidecar/scripts/replay_sidecar.py +32 -0
  397. package/sidecar/scripts/suite_runner.py +302 -0
  398. package/sidecar/scripts/test.sh +12 -4
  399. package/sidecar/talaria_sidecar/__init__.py +6 -1
  400. package/sidecar/talaria_sidecar/__main__.py +4 -2
  401. package/sidecar/talaria_sidecar/agent_mutation.py +975 -0
  402. package/sidecar/talaria_sidecar/agent_services.py +1053 -0
  403. package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
  404. package/sidecar/talaria_sidecar/home.py +56 -3
  405. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  406. package/sidecar/talaria_sidecar/methods/aux.py +166 -24
  407. package/sidecar/talaria_sidecar/methods/chat.py +852 -135
  408. package/sidecar/talaria_sidecar/methods/checkpoints.py +89 -0
  409. package/sidecar/talaria_sidecar/methods/commands.py +294 -4
  410. package/sidecar/talaria_sidecar/methods/config.py +123 -4
  411. package/sidecar/talaria_sidecar/methods/connectors.py +343 -0
  412. package/sidecar/talaria_sidecar/methods/cron.py +395 -36
  413. package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
  414. package/sidecar/talaria_sidecar/methods/gateway.py +212 -55
  415. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +234 -0
  416. package/sidecar/talaria_sidecar/methods/goals.py +81 -41
  417. package/sidecar/talaria_sidecar/methods/kanban.py +108 -34
  418. package/sidecar/talaria_sidecar/methods/mcp.py +1 -1
  419. package/sidecar/talaria_sidecar/methods/memory.py +84 -0
  420. package/sidecar/talaria_sidecar/methods/oauth.py +5 -2
  421. package/sidecar/talaria_sidecar/methods/process.py +218 -15
  422. package/sidecar/talaria_sidecar/methods/profiles.py +121 -13
  423. package/sidecar/talaria_sidecar/methods/providers.py +217 -28
  424. package/sidecar/talaria_sidecar/methods/runtime.py +69 -1
  425. package/sidecar/talaria_sidecar/methods/skills.py +67 -4
  426. package/sidecar/talaria_sidecar/methods/state_db.py +316 -75
  427. package/sidecar/talaria_sidecar/methods/stt.py +159 -78
  428. package/sidecar/talaria_sidecar/methods/text.py +14 -1
  429. package/sidecar/talaria_sidecar/methods/toolsets.py +49 -0
  430. package/sidecar/talaria_sidecar/methods/usage.py +113 -14
  431. package/sidecar/talaria_sidecar/methods/worktree.py +26 -19
  432. package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
  433. package/sidecar/talaria_sidecar/rpc.py +45 -4
  434. package/sidecar/talaria_sidecar/runtime.py +98 -13
  435. package/sidecar/talaria_sidecar/speed.py +35 -0
  436. package/static/dist/FILES.txt +126 -126
  437. package/static/dist/assets/AppShell-cey33v9s.js +8 -0
  438. package/static/dist/assets/ChatPage-i8wnugr7.js +18 -0
  439. package/static/dist/assets/ConnectTargets-hg19553x.js +1 -0
  440. package/static/dist/assets/ExtensionRoute-cly4apm5.js +1 -0
  441. package/static/dist/assets/Field-j8uqoigz.js +9 -0
  442. package/static/dist/assets/HubRoute-e4bgz41j.js +2 -0
  443. package/static/dist/assets/Markdown-lahuichm.js +598 -0
  444. package/static/dist/assets/RightPanel-lbo4i0fn.js +1 -0
  445. package/static/dist/assets/Select-gqatm2e0.js +1 -0
  446. package/static/dist/assets/SessionListPanel-mfqayfjh.js +1 -0
  447. package/static/dist/assets/SettingsLayout-b8980u7k.js +1 -0
  448. package/static/dist/assets/States-e7dkgtxf.js +1 -0
  449. package/static/dist/assets/Toaster-bfqqy786.js +1 -0
  450. package/static/dist/assets/_app-n1n6pfx1.js +1 -0
  451. package/static/dist/assets/{_app.ext._extensionId-opyh9y0v.js → _app.ext._extensionId-buxjxav3.js} +1 -1
  452. package/static/dist/assets/_app.index-jt8zwiu2.js +1 -0
  453. package/static/dist/assets/_app.insights-cv76ml71.js +1 -0
  454. package/static/dist/assets/_app.kanban-h6ts53j7.js +1 -0
  455. package/static/dist/assets/_app.logs-dm873z90.js +1 -0
  456. package/static/dist/assets/_app.memory-glk5y6m8.js +1 -0
  457. package/static/dist/assets/_app.profiles-ka6zpstq.js +1 -0
  458. package/static/dist/assets/{_app.session._sessionId-bfd4h3id.js → _app.session._sessionId-g2vzghvc.js} +1 -1
  459. package/static/dist/assets/_app.settings-hgjdhzcd.js +1 -0
  460. package/static/dist/assets/_app.settings._section-jf2kmhae.js +3 -0
  461. package/static/dist/assets/_app.skills-cii852yq.js +1 -0
  462. package/static/dist/assets/_app.tasks-fp9q7atm.js +1 -0
  463. package/static/dist/assets/_app.todos-dw94qzue.js +1 -0
  464. package/static/dist/assets/_app.workspaces-inyygzze.js +1 -0
  465. package/static/dist/assets/{abnfDiagram-VCTEODGH-b2x1lqot.js → abnfDiagram-VCTEODGH-gjayq3ra.js} +1 -1
  466. package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +1 -0
  467. package/static/dist/assets/{architectureDiagram-5GKGNRK7-h38o5l7b.js → architectureDiagram-5GKGNRK7-nwb3xovn.js} +1 -1
  468. package/static/dist/assets/archive-b1j004vn.js +1 -0
  469. package/static/dist/assets/back-g74gkozu.js +1 -0
  470. package/static/dist/assets/{blockDiagram-I7D4REHJ-bqquxv2u.js → blockDiagram-I7D4REHJ-joedghi3.js} +1 -1
  471. package/static/dist/assets/{c4Diagram-7LVT6UL2-f3fb8w6z.js → c4Diagram-7LVT6UL2-hyrjrohe.js} +1 -1
  472. package/static/dist/assets/channel-es95o4y8.js +1 -0
  473. package/static/dist/assets/{chunk-4HAMMTFA-bzfyyzp3.js → chunk-4HAMMTFA-koouqbut.js} +1 -1
  474. package/static/dist/assets/{chunk-75Z2AOVW-ly99rfx5.js → chunk-75Z2AOVW-bl4bhdnt.js} +1 -1
  475. package/static/dist/assets/{chunk-DU6HZSFF-bg6ecfqf.js → chunk-DU6HZSFF-gpedttsp.js} +3 -3
  476. package/static/dist/assets/{chunk-F27PBJKO-e8k7mmqw.js → chunk-F27PBJKO-d8xxi0zi.js} +1 -1
  477. package/static/dist/assets/{chunk-GMAD6QVW-cmalnoaj.js → chunk-GMAD6QVW-jro0hl73.js} +1 -1
  478. package/static/dist/assets/{chunk-GVQU2GXP-gmif5o01.js → chunk-GVQU2GXP-djn6gbyu.js} +1 -1
  479. package/static/dist/assets/{chunk-IMKFNOWR-f1nwwh6u.js → chunk-IMKFNOWR-vw4p5p7o.js} +1 -1
  480. package/static/dist/assets/{chunk-L3NEJ4N5-oktfh5v7.js → chunk-L3NEJ4N5-n1hj77q8.js} +1 -1
  481. package/static/dist/assets/{chunk-OSK3NFVY-b1sdx96z.js → chunk-OSK3NFVY-oy0gwn2n.js} +1 -1
  482. package/static/dist/assets/{chunk-P2QGCYS3-fhu4nzxt.js → chunk-P2QGCYS3-onf1hz6c.js} +1 -1
  483. package/static/dist/assets/{chunk-POPQ4Y6H-em31rnef.js → chunk-POPQ4Y6H-m8sqn7cs.js} +1 -1
  484. package/static/dist/assets/{chunk-PWAF6VOD-bxhhyftw.js → chunk-PWAF6VOD-ek4smc8x.js} +1 -1
  485. package/static/dist/assets/{chunk-SHT3W25Y-fmrvwcav.js → chunk-SHT3W25Y-g9gfvefo.js} +3 -3
  486. package/static/dist/assets/{chunk-SVP7TREG-lymukl2j.js → chunk-SVP7TREG-f9nfv2cu.js} +1 -1
  487. package/static/dist/assets/{chunk-TICWLB2K-lmza6k23.js → chunk-TICWLB2K-bnk3m1mq.js} +1 -1
  488. package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +1 -0
  489. package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +1 -0
  490. package/static/dist/assets/copied-bli9tbcx.js +1 -0
  491. package/static/dist/assets/{copy-fw5b1pc6.js → copy-jamtd1pf.js} +1 -1
  492. package/static/dist/assets/{cynefin-OW5HDTMX-czlhbyhs.js → cynefin-OW5HDTMX-f8xfdd3f.js} +1 -1
  493. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-gq7mvswb.js → cynefinDiagram-5FMLGOSQ-n9lb99h9.js} +1 -1
  494. package/static/dist/assets/{dagre-GXQ25YYZ-cqdbf7dr.js → dagre-GXQ25YYZ-dgailddi.js} +4 -4
  495. package/static/dist/assets/{diagram-S7CK7UJ4-o6arxa58.js → diagram-S7CK7UJ4-m6aeoe7c.js} +1 -1
  496. package/static/dist/assets/{diagram-UQ7AKVKN-nhnd68tz.js → diagram-UQ7AKVKN-jall3j0x.js} +1 -1
  497. package/static/dist/assets/{diagram-VSXAHHWV-dk2sclzn.js → diagram-VSXAHHWV-hbawi7hn.js} +1 -1
  498. package/static/dist/assets/{diagram-VX7I27RA-n3k5nyx3.js → diagram-VX7I27RA-hendl2tt.js} +1 -1
  499. package/static/dist/assets/{diagram-Z3DM3KII-m6q9cwdh.js → diagram-Z3DM3KII-igwc35p3.js} +1 -1
  500. package/static/dist/assets/{ebnfDiagram-PWID7BFC-lsa5641p.js → ebnfDiagram-PWID7BFC-c64knz08.js} +1 -1
  501. package/static/dist/assets/{erDiagram-RLTQ6QDP-fo2ckbe6.js → erDiagram-RLTQ6QDP-brdu9lcm.js} +1 -1
  502. package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +1 -0
  503. package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +1 -0
  504. package/static/dist/assets/folder-plus-lvt9kudb.js +1 -0
  505. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-nzqz6x9g.js → ganttDiagram-EL5Y4UJY-ma6jxj5i.js} +1 -1
  506. package/static/dist/assets/{gitGraph-4MIJSDKK-gukvcxe6.js → gitGraph-4MIJSDKK-naa9w6xa.js} +1 -1
  507. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-lqhdp8f0.js → gitGraphDiagram-WWUBYQGX-hzltvsod.js} +1 -1
  508. package/static/dist/assets/{highlighted-body-KPVGNVTW-igw6cp39.js → highlighted-body-KPVGNVTW-ghoavj8d.js} +1 -1
  509. package/static/dist/assets/host-hzm8n85n.js +1 -0
  510. package/static/dist/assets/index-drdcaqeq.css +2 -0
  511. package/static/dist/assets/index-jzm844he.js +11 -0
  512. package/static/dist/assets/{info-A6RAGUB7-gvu7a93l.js → info-A6RAGUB7-mn2ut5ko.js} +1 -1
  513. package/static/dist/assets/{infoDiagram-27XIBGKW-m8t8g6w1.js → infoDiagram-27XIBGKW-h6kuqvsb.js} +1 -1
  514. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-l07vkbgj.js → ishikawaDiagram-5VMMS53U-e7bnwq7h.js} +1 -1
  515. package/static/dist/assets/{journeyDiagram-3NMN7TZE-hybkgj8l.js → journeyDiagram-3NMN7TZE-etd1bi6p.js} +1 -1
  516. package/static/dist/assets/{kanban-definition-UXKFOSKX-by13uvtn.js → kanban-definition-UXKFOSKX-emb1g9dz.js} +4 -4
  517. package/static/dist/assets/lazyRouteComponent-o816ytzl.js +1 -0
  518. package/static/dist/assets/{line-osxq6fq7.js → line-h0ojzsvs.js} +1 -1
  519. package/static/dist/assets/link-k01ryaf9.js +1 -0
  520. package/static/dist/assets/login-bsae49ew.js +1 -0
  521. package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +1 -0
  522. package/static/dist/assets/{mermaid-parser.core-i91oxmou.js → mermaid-parser.core-bnre27u8.js} +3 -3
  523. package/static/dist/assets/{mindmap-definition-YA3MSWOX-m82mx8ww.js → mindmap-definition-YA3MSWOX-o1vathns.js} +1 -1
  524. package/static/dist/assets/onboarding-c65tysya.js +1 -0
  525. package/static/dist/assets/onboarding_model_label-gpn28uix.js +1 -0
  526. package/static/dist/assets/{packet-AYTQ26CC-nbtgge2o.js → packet-AYTQ26CC-hatq7m2e.js} +1 -1
  527. package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
  528. package/static/dist/assets/{pegDiagram-XKGWAZYB-f1zxzga1.js → pegDiagram-XKGWAZYB-gwisz8jv.js} +1 -1
  529. package/static/dist/assets/{pie-WAS4IAKB-of8vuhxs.js → pie-WAS4IAKB-fpftf0mk.js} +1 -1
  530. package/static/dist/assets/{pieDiagram-E7YTZNPT-o7y8ftio.js → pieDiagram-E7YTZNPT-khm1o9a6.js} +1 -1
  531. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-iqiwia1t.js → quadrantDiagram-AXDQQJYC-owqc4uqn.js} +1 -1
  532. package/static/dist/assets/{radar-RG4KPBEZ-delnsy2v.js → radar-RG4KPBEZ-cntfwjpq.js} +1 -1
  533. package/static/dist/assets/{railroad-74A4TZTK-nspnyq9u.js → railroad-74A4TZTK-a7t5eibp.js} +1 -1
  534. package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +1 -0
  535. package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +1 -0
  536. package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +1 -0
  537. package/static/dist/assets/{railroadDiagram-O6MQD6OU-z41tqx5z.js → railroadDiagram-O6MQD6OU-etuh0qa0.js} +1 -1
  538. package/static/dist/assets/{refresh-cw-nt1dtsx4.js → refresh-cw-mqbkv2a3.js} +1 -1
  539. package/static/dist/assets/refresh-l29ljp76.js +1 -0
  540. package/static/dist/assets/{requirementDiagram-BXWQKSXE-bdvif194.js → requirementDiagram-BXWQKSXE-oq2ulk22.js} +1 -1
  541. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-h73e0rol.js → sankeyDiagram-P5KCCOFB-iqc02emv.js} +1 -1
  542. package/static/dist/assets/saved-ndd4t2xb.js +1 -0
  543. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-iv95mb1z.js → sequenceDiagram-WJ2MYXX4-i0v116q0.js} +2 -2
  544. package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +1 -0
  545. package/static/dist/assets/share._token-c1ri0hy1.js +2 -0
  546. package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +1 -0
  547. package/static/dist/assets/{stateDiagram-D77RDMKH-k417ixmf.js → stateDiagram-D77RDMKH-dnpt1ato.js} +1 -1
  548. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +1 -0
  549. package/static/dist/assets/{swimlanes-42K2YHIH-esola70w.js → swimlanes-42K2YHIH-loqbdazb.js} +1 -1
  550. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +8 -0
  551. package/static/dist/assets/{timeline-definition-24CTP7MA-getrz2y8.js → timeline-definition-24CTP7MA-ol2hnvma.js} +1 -1
  552. package/static/dist/assets/toast-i3h34v6o.js +64 -0
  553. package/static/dist/assets/{treeView-Q6P3EWNA-hc7ap1q7.js → treeView-Q6P3EWNA-idrj6iox.js} +1 -1
  554. package/static/dist/assets/{treemap-WGGIJYW6-b7k1syy5.js → treemap-WGGIJYW6-lzr49vv5.js} +1 -1
  555. package/static/dist/assets/untitled-oaoya4qj.js +1 -0
  556. package/static/dist/assets/useForm-nfaxgmsk.js +1 -0
  557. package/static/dist/assets/{useParams-lhg566ew.js → useParams-gjc70qru.js} +1 -1
  558. package/static/dist/assets/{useSearch-lbrh6ejn.js → useSearch-e6ae539u.js} +1 -1
  559. package/static/dist/assets/{vennDiagram-4TSXK5OY-n6pcy5qb.js → vennDiagram-4TSXK5OY-bvwr9o2z.js} +1 -1
  560. package/static/dist/assets/{wardley-WFR3VGLG-lpa18vz1.js → wardley-WFR3VGLG-31ui8tma.js} +1 -1
  561. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-kpclwwg7.js → wardleyDiagram-VM6X3IG4-k5wqtx4b.js} +1 -1
  562. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-paa2n8y0.js → xychartDiagram-S5SC5T6Z-aqbgvujg.js} +1 -1
  563. package/static/dist/index.html +1 -1
  564. package/static/dist/sw.js +1 -1
  565. package/static/dist/assets/AppShell-fb2td9zi.js +0 -1
  566. package/static/dist/assets/ChatPage-cj96kiy9.js +0 -25
  567. package/static/dist/assets/Dialog-kxcg7ard.js +0 -1
  568. package/static/dist/assets/ExtensionRoute-jf7h0754.js +0 -1
  569. package/static/dist/assets/HubRoute-gmanpd5y.js +0 -2
  570. package/static/dist/assets/Markdown-mdwlvj3w.js +0 -598
  571. package/static/dist/assets/RightPanel-ftvrjlcp.js +0 -1
  572. package/static/dist/assets/Select-bilaypej.js +0 -1
  573. package/static/dist/assets/SessionListPanel-og8n36qs.js +0 -1
  574. package/static/dist/assets/SettingsLayout-jonjefmj.js +0 -1
  575. package/static/dist/assets/States-nmejrwrm.js +0 -1
  576. package/static/dist/assets/Toaster-nnl8zkt9.js +0 -1
  577. package/static/dist/assets/_app-gdidzd94.js +0 -1
  578. package/static/dist/assets/_app.index-hbarzbjx.js +0 -1
  579. package/static/dist/assets/_app.insights-luywlarz.js +0 -1
  580. package/static/dist/assets/_app.kanban-kincnbr8.js +0 -1
  581. package/static/dist/assets/_app.logs-oi2gr3gs.js +0 -1
  582. package/static/dist/assets/_app.memory-b6jfivt6.js +0 -1
  583. package/static/dist/assets/_app.profiles-lpvixmqp.js +0 -1
  584. package/static/dist/assets/_app.settings-vowx3iq0.js +0 -1
  585. package/static/dist/assets/_app.settings._section-cnntg1tu.js +0 -3
  586. package/static/dist/assets/_app.skills-fb57lpau.js +0 -1
  587. package/static/dist/assets/_app.tasks-eszpx1q7.js +0 -2
  588. package/static/dist/assets/_app.todos-nr05ac6w.js +0 -1
  589. package/static/dist/assets/_app.workspaces-d2luomu6.js +0 -1
  590. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +0 -1
  591. package/static/dist/assets/back-bemaer09.js +0 -1
  592. package/static/dist/assets/cancel-drd0g6d9.js +0 -1
  593. package/static/dist/assets/channel-cpdgf867.js +0 -1
  594. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +0 -1
  595. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +0 -1
  596. package/static/dist/assets/copied-ima3pysb.js +0 -1
  597. package/static/dist/assets/endpoints-d33siokx.js +0 -64
  598. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +0 -1
  599. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +0 -1
  600. package/static/dist/assets/index-cxq58xb1.js +0 -11
  601. package/static/dist/assets/index-hgtx08n5.css +0 -2
  602. package/static/dist/assets/lazyRouteComponent-af0lvgjx.js +0 -1
  603. package/static/dist/assets/link-jyxicdhg.js +0 -1
  604. package/static/dist/assets/login-ec21t39x.js +0 -1
  605. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +0 -1
  606. package/static/dist/assets/onboarding-btnltvyz.js +0 -1
  607. package/static/dist/assets/onboarding_model_label-dp7xffoh.js +0 -1
  608. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +0 -1
  609. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +0 -1
  610. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +0 -1
  611. package/static/dist/assets/refresh-b4uams1i.js +0 -1
  612. package/static/dist/assets/saved-kg52csy7.js +0 -1
  613. package/static/dist/assets/settings_section_extensions_title-ej0iapxe.js +0 -1
  614. package/static/dist/assets/share._token-f97e94r0.js +0 -2
  615. package/static/dist/assets/sliders-horizontal-fj18it47.js +0 -1
  616. package/static/dist/assets/sse-fwpqtzbg.js +0 -1
  617. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +0 -1
  618. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +0 -8
  619. package/static/dist/assets/todoStore-eginf9h4.js +0 -1
  620. package/static/dist/assets/tts-lxdxnmjd.js +0 -1
  621. package/static/dist/assets/untitled-ovpfthg4.js +0 -1
  622. package/static/dist/assets/useForm-bcblv1rx.js +0 -1
  623. package/static/dist/assets/useLocale-fi5jb8ma.js +0 -1
@@ -3,38 +3,52 @@
3
3
  * ZIPs, transcript exports, and multipart uploads. Each mirrors its Python
4
4
  * handler and reuses the anchored file helpers.
5
5
  */
6
- import { closeSync, createReadStream, existsSync, readdirSync, statSync, realpathSync } from 'node:fs';
6
+ import { closeSync, createReadStream, existsSync, mkdirSync, readdirSync, statSync, realpathSync } from 'node:fs';
7
7
  import { Readable } from 'node:stream';
8
8
  import { basename, join, relative } from 'node:path';
9
9
  import { HttpError } from './router.js';
10
- import { fileOpsSession } from './sessions-router.js';
10
+ import { escapeRequest, fileError, fileOpsSession } from './sessions-router.js';
11
11
  import { HttpFailure } from '../sessions/service.js';
12
12
  import { SessionNotFound } from '../sessions/store.js';
13
- import { openAnchoredFd, safeResolve } from '../workspace/fs.js';
13
+ import { FileExistsError, makeAnchoredDir, openAnchoredCreateFd, openAnchoredFd, rmtreeAnchored, safeResolve, safeResolveWs, unlinkAnchored } from '../workspace/fs.js';
14
14
  import { isWithin, resolvePathLikePython } from '../workspace/paths.js';
15
- import { AUDIO_VIDEO_PDF_TYPES, contentDispositionValue, INLINE_IMAGE_TYPES, isValidDigest, mediaAnchorRoot, mediaTarget, mimeFor, serveFileBytes, serveInlineHtmlPreview, snapshotPathForDigest, snapshotServableForPath } from '../workspace/media.js';
15
+ import { isLoopback } from '../http/origin.js';
16
+ import { truthy } from '../config.js';
17
+ import { AUDIO_VIDEO_PDF_TYPES, contentDispositionValue, INLINE_IMAGE_TYPES, mediaAnchorRoot, mediaTarget, mimeFor, serveFileBytes, serveInlineHtmlPreview } from '../workspace/media.js';
16
18
  import { PREVIEW_PREFIX, previewGrantRoot } from '../workspace/preview.js';
17
19
  import { REMOTE_WORKSPACE_UNSUPPORTED_CODE, REMOTE_WORKSPACE_UNSUPPORTED_MESSAGE } from '../workspace/workspaces.js';
20
+ import { WORKSPACE_BUSY_MESSAGE } from '../workspace/git.js';
18
21
  import { ZipWriter } from '../workspace/zip.js';
19
- import { parseMultipart, UploadConflict, UploadRejected } from '../workspace/upload.js';
20
- import { pythonPrettyJson, renderSessionHtml } from '../sessions/export.js';
22
+ import { anchoredAt } from '../workspace/escape.js';
23
+ import { guessMime, parseMultipart, sanitizeUploadName, uploadDestination, UploadConflict, UploadRejected } from '../workspace/upload.js';
24
+ import { ArchiveRejected, CorruptArchive, extractArchive, isArchiveName } from '../workspace/extract.js';
25
+ import { writeFully } from '../fs/atomic.js';
26
+ import { pythonPrettyJson, renderSessionHtml, renderSessionMarkdown } from '../sessions/export.js';
21
27
  import { handleCspReport, handleTranscribe, handleTts } from './tools-raw.js';
22
28
  import { handleOidcCallback, handleOidcStart } from './auth-raw.js';
29
+ import { handleTranscribeStream } from './transcribe-stream.js';
23
30
  const SANDBOX_CSP = 'sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox';
24
31
  const DANGEROUS_TYPES = new Set(['text/html', 'application/xhtml+xml', 'image/svg+xml']);
25
32
  export const RAW_GET_ROUTES = {
26
33
  '/api/auth/oidc/start': handleOidcStart,
27
34
  '/api/auth/oidc/callback': handleOidcCallback,
28
35
  '/api/file/raw': handleFileRaw,
36
+ '/api/escape/file/raw': handleEscapeFileRaw,
29
37
  '/api/media': handleMedia,
30
38
  '/api/folder/download': handleFolderDownload,
31
39
  '/api/session/export': handleSessionExport,
40
+ '/api/transcribe/stream': handleTranscribeStream,
41
+ '/api/approval/inject_test': testHook(handleApprovalInject),
42
+ '/api/clarify/inject_test': testHook(handleClarifyInject),
32
43
  };
33
44
  export const RAW_POST_ROUTES = {
34
45
  '/api/upload': handleUpload,
46
+ '/api/upload/extract': handleUploadExtract,
47
+ '/api/workspace/upload': handleWorkspaceUpload,
35
48
  '/api/transcribe': handleTranscribe,
36
49
  '/api/tts': handleTts,
37
50
  '/api/csp-report': handleCspReport,
51
+ '/api/process-complete-ack': handleProcessCompleteAck,
38
52
  };
39
53
  /** Run a raw handler, translating thrown `HttpError`s into the JSON error body. */
40
54
  export async function runRaw(ctx, handler) {
@@ -45,20 +59,54 @@ export async function runRaw(ctx, handler) {
45
59
  if (ctx.isFinished)
46
60
  return;
47
61
  if (error instanceof HttpError) {
48
- ctx.json({ error: error.message, ...error.data }, { status: error.status });
62
+ await ctx.json({ error: error.message, ...error.data }, { status: error.status });
49
63
  return;
50
64
  }
51
65
  if (error instanceof HttpFailure) {
52
- ctx.json({ error: error.message, ...error.extra }, { status: error.status });
66
+ await ctx.json({ error: error.message, ...error.extra }, { status: error.status });
53
67
  return;
54
68
  }
55
69
  if (error instanceof SessionNotFound) {
56
- ctx.json({ error: 'Session not found' }, { status: 404 });
70
+ await ctx.json({ error: 'Session not found' }, { status: 404 });
57
71
  return;
58
72
  }
59
73
  throw error;
60
74
  }
61
75
  }
76
+ /** Retired by the `process_complete` -> `bg_task_complete` rename: always 410, naming the replacement. */
77
+ async function handleProcessCompleteAck(ctx) {
78
+ await ctx.json({ error: 'gone: /api/process-complete-ack was replaced by /api/bg-task-complete-ack as part of the process_complete -> bg_task_complete event rename', replaced_by: '/api/bg-task-complete-ack' }, { status: 410, headers: { 'X-Replaced-By': '/api/bg-task-complete-ack' } });
79
+ }
80
+ /**
81
+ * Automated-test hooks: served only to a loopback peer while `HERMES_WEBUI_TEST_HOOKS=1` is set (loopback alone means
82
+ * nothing behind a same-host reverse proxy); anything else gets the plain 404.
83
+ */
84
+ function testHook(handler) {
85
+ return async (ctx) => {
86
+ if (!truthy(ctx.deps.config.env.HERMES_WEBUI_TEST_HOOKS) || !isLoopback(ctx.peer)) {
87
+ await ctx.json({ error: 'not found' }, { status: 404 });
88
+ return;
89
+ }
90
+ return handler(ctx);
91
+ };
92
+ }
93
+ /** Queue a fake approval; `_injected` entries are answered locally, since no Agent is parked on them. */
94
+ async function handleApprovalInject(ctx) {
95
+ const sid = ctx.query.get('session_id') ?? '';
96
+ if (!sid)
97
+ throw new HttpError(400, 'session_id required');
98
+ const key = ctx.query.get('pattern_key') || 'test_pattern';
99
+ ctx.deps.pending.submitApproval(sid, { command: ctx.query.get('command') || 'rm -rf /tmp/test', pattern_key: key, pattern_keys: [key], description: 'test pattern', session_id: sid, _injected: true });
100
+ await ctx.json({ ok: true, session_id: sid });
101
+ }
102
+ /** Queue a fake clarify prompt (repeated `choices`); answered locally like an injected approval. */
103
+ async function handleClarifyInject(ctx) {
104
+ const sid = ctx.query.get('session_id') ?? '';
105
+ if (!sid)
106
+ throw new HttpError(400, 'session_id required');
107
+ ctx.deps.pending.submitClarify(sid, { question: ctx.query.get('question') || 'Which option?', choices_offered: ctx.query.getAll('choices').filter(Boolean), session_id: sid, kind: 'clarify', _injected: true });
108
+ await ctx.json({ ok: true, session_id: sid });
109
+ }
62
110
  function fileRawTarget(ctx, workspace, sid, rel) {
63
111
  try {
64
112
  const target = safeResolve(workspace, rel);
@@ -75,38 +123,54 @@ function fileRawTarget(ctx, workspace, sid, rel) {
75
123
  catch { /* none */ }
76
124
  return null;
77
125
  }
78
- function handleFileRaw(ctx) {
126
+ async function handleFileRaw(ctx) {
79
127
  const sid = ctx.query.get('session_id') ?? '';
80
128
  if (!sid)
81
129
  throw new HttpError(400, 'session_id is required');
82
- const s = fileOpsSession(ctx, sid);
130
+ const s = (await fileOpsSession(ctx, sid));
83
131
  const rel = ctx.query.get('path') ?? '';
84
132
  if (rel.includes('\0'))
85
133
  throw new HttpError(400, 'invalid path');
86
- const forceDownload = ctx.query.get('download') === '1';
87
134
  const resolved = fileRawTarget(ctx, s.workspace, sid, rel);
88
135
  if (!resolved) {
89
- ctx.json({ error: 'not found' }, { status: 404 });
136
+ await ctx.json({ error: 'not found' }, { status: 404 });
90
137
  return;
91
138
  }
92
- const [anchorRoot, target] = resolved;
139
+ await serveRawFile(ctx, resolved[0], resolved[1]);
140
+ }
141
+ /** Python `_handle_escape_file_raw`: `/api/file/raw` for a path under an escape grant, anchored at the grant root. */
142
+ async function handleEscapeFileRaw(ctx) {
143
+ const req = (await escapeRequest(ctx, ctx.query.get('session_id') ?? '', ctx.query.get('token') ?? '', ctx.query.get('path') ?? ''));
144
+ let target;
145
+ try {
146
+ target = ctx.deps.escapeGrants.rawTarget(req);
147
+ }
148
+ catch (error) {
149
+ throw fileError(error, 404);
150
+ }
151
+ // The anchored open and fstat in `serveRawFile` answer a missing or non-file target with 404 through the pinned root.
152
+ await anchoredAt(req, async () => { (await serveRawFile(ctx, req.externalRoot, target)); });
153
+ }
154
+ /** The `/api/file/raw` response: MIME, attachment for dangerous types unless an inline HTML preview, sandbox CSP, no-store. */
155
+ async function serveRawFile(ctx, anchorRoot, target) {
156
+ const forceDownload = ctx.query.get('download') === '1';
93
157
  const mime = mimeFor(target);
94
158
  const inlinePreview = ctx.query.get('inline') === '1';
95
159
  const htmlInlineOk = inlinePreview && mime === 'text/html';
96
160
  const disposition = forceDownload || (DANGEROUS_TYPES.has(mime) && !htmlInlineOk) ? 'attachment' : 'inline';
97
161
  const csp = inlinePreview && !forceDownload && disposition === 'inline' ? SANDBOX_CSP : null;
98
162
  if (htmlInlineOk) {
99
- serveInlineHtmlPreview(ctx, target, 'no-store', SANDBOX_CSP, anchorRoot);
163
+ await serveInlineHtmlPreview(ctx, target, 'no-store', SANDBOX_CSP, anchorRoot);
100
164
  return;
101
165
  }
102
- serveFileBytes(ctx, target, { mime, disposition, cacheControl: 'no-store', csp, anchorRoot });
166
+ await serveFileBytes(ctx, target, { mime, disposition, cacheControl: 'no-store', csp, anchorRoot });
103
167
  }
104
168
  /**
105
169
  * `/workspace-preview/<grant>/<path>` (TAL-566): the HTML preview frame and its relative assets, authorized by the
106
170
  * signed grant rather than a cookie. Every response carries the sandbox CSP and no CORS header, so the page's scripts
107
171
  * can run and load assets but cannot read workspace files.
108
172
  */
109
- export function handleWorkspacePreview(ctx) {
173
+ export async function handleWorkspacePreview(ctx) {
110
174
  const rest = ctx.path.slice(PREVIEW_PREFIX.length);
111
175
  const slash = rest.indexOf('/');
112
176
  const root = slash > 0 ? previewGrantRoot(ctx.deps.auth.signingKey(), rest.slice(0, slash)) : null;
@@ -121,16 +185,16 @@ export function handleWorkspacePreview(ctx) {
121
185
  catch { /* malformed escape or outside the root */ }
122
186
  }
123
187
  if (!root || !target) {
124
- ctx.json({ error: 'not found' }, { status: 404 });
188
+ await ctx.json({ error: 'not found' }, { status: 404 });
125
189
  return;
126
190
  }
127
191
  const mime = mimeFor(target);
128
192
  if (mime === 'text/html')
129
- serveInlineHtmlPreview(ctx, target, 'no-store', SANDBOX_CSP, root);
193
+ (await serveInlineHtmlPreview(ctx, target, 'no-store', SANDBOX_CSP, root));
130
194
  else
131
- serveFileBytes(ctx, target, { mime, disposition: 'inline', cacheControl: 'no-store', csp: SANDBOX_CSP, anchorRoot: root });
195
+ (await serveFileBytes(ctx, target, { mime, disposition: 'inline', cacheControl: 'no-store', csp: SANDBOX_CSP, anchorRoot: root }));
132
196
  }
133
- function handleMedia(ctx) {
197
+ async function handleMedia(ctx) {
134
198
  const { deps } = ctx;
135
199
  const rawPath = (ctx.query.get('path') ?? '').trim();
136
200
  if (!rawPath)
@@ -140,7 +204,7 @@ function handleMedia(ctx) {
140
204
  let localAllowed;
141
205
  if (sessionId) {
142
206
  try {
143
- mediaSession = deps.sessionStore.get(sessionId);
207
+ mediaSession = (await deps.sessionStore.get(sessionId));
144
208
  }
145
209
  catch {
146
210
  throw new HttpError(404, 'Session not found');
@@ -150,7 +214,7 @@ function handleMedia(ctx) {
150
214
  else
151
215
  localAllowed = deps.workspaces.profileSupportsLocalIo(null);
152
216
  if (!localAllowed) {
153
- ctx.json({ error: REMOTE_WORKSPACE_UNSUPPORTED_CODE, message: REMOTE_WORKSPACE_UNSUPPORTED_MESSAGE }, { status: 400 });
217
+ await ctx.json({ error: REMOTE_WORKSPACE_UNSUPPORTED_CODE, message: REMOTE_WORKSPACE_UNSUPPORTED_MESSAGE }, { status: 400 });
154
218
  return;
155
219
  }
156
220
  let target;
@@ -160,7 +224,7 @@ function handleMedia(ctx) {
160
224
  catch {
161
225
  throw new HttpError(400, 'Invalid path');
162
226
  }
163
- const anchorRoot = mediaAnchorRoot(target, mediaSession, deps.mediaAccess);
227
+ const anchorRoot = (await mediaAnchorRoot(target, mediaSession, deps.mediaAccess));
164
228
  if (!anchorRoot)
165
229
  throw new HttpError(403, 'Path not in allowed location');
166
230
  const mime = mimeFor(target);
@@ -169,24 +233,8 @@ function handleMedia(ctx) {
169
233
  const inlinePreviewTypes = new Set([...INLINE_IMAGE_TYPES, ...AUDIO_VIDEO_PDF_TYPES]);
170
234
  const disposition = mime !== 'image/svg+xml' && (INLINE_IMAGE_TYPES.has(mime) || (inlinePreview && inlinePreviewTypes.has(mime)) || htmlInlineOk) ? 'inline' : 'attachment';
171
235
  const csp = htmlInlineOk ? 'sandbox allow-scripts' : null;
172
- const snapDigest = (ctx.query.get('snap') ?? '').trim().toLowerCase();
173
- if (snapDigest) {
174
- const snapDir = resolvePathLikePython(deps.mediaPolicy.snapshotDir());
175
- if (isValidDigest(snapDigest)) {
176
- let snapshotFile = snapshotPathForDigest(snapDir, snapDigest);
177
- if (snapshotFile && !snapshotServableForPath(snapDir, snapDigest, target))
178
- snapshotFile = null;
179
- if (!snapshotFile) {
180
- ctx.json({ error: 'snapshot unavailable' }, { status: 410 });
181
- return;
182
- }
183
- // Same inode rule as live media: a `.snap` replaced by a hard link to a state file must not be served as the snapshot.
184
- serveFileBytes(ctx, snapshotFile, { mime, disposition, cacheControl: 'private, max-age=31536000, immutable', csp, downloadName: basename(target), anchorRoot: snapDir, denyHardLinks: true });
185
- return;
186
- }
187
- }
188
236
  // The pathname policy ran before the open; the hard-link rule is re-applied to the inode actually opened.
189
- serveFileBytes(ctx, target, { mime, disposition, cacheControl: mime === 'text/html' ? 'no-store' : 'private, no-cache', csp, anchorRoot, denyHardLinks: true });
237
+ await serveFileBytes(ctx, target, { mime, disposition, cacheControl: mime === 'text/html' ? 'no-store' : 'private, no-cache', csp, anchorRoot, denyHardLinks: true });
190
238
  }
191
239
  /** Python `_folder_zip_limits`: `HERMES_WEBUI_FOLDER_ZIP_MAX_MB` / `_MAX_FILES` are honoured as given (ZIP64 covers large archives). */
192
240
  export function folderZipMaxBytes(env) {
@@ -265,7 +313,7 @@ async function handleFolderDownload(ctx) {
265
313
  const sid = ctx.query.get('session_id') ?? '';
266
314
  if (!sid)
267
315
  throw new HttpError(400, 'session_id is required');
268
- const s = fileOpsSession(ctx, sid);
316
+ const s = (await fileOpsSession(ctx, sid));
269
317
  const rel = ctx.query.get('path') ?? '';
270
318
  let target;
271
319
  try {
@@ -275,7 +323,7 @@ async function handleFolderDownload(ctx) {
275
323
  throw new HttpError(400, 'invalid path');
276
324
  }
277
325
  if (!existsSync(target)) {
278
- ctx.json({ error: 'not found' }, { status: 404 });
326
+ await ctx.json({ error: 'not found' }, { status: 404 });
279
327
  return;
280
328
  }
281
329
  if (!statSync(target).isDirectory())
@@ -285,11 +333,11 @@ async function handleFolderDownload(ctx) {
285
333
  const maxFiles = folderZipMaxFiles(ctx.deps.config.env);
286
334
  const { files, limit } = collectFolder(target, workspaceRoot, maxBytes, maxFiles);
287
335
  if (limit === 'max_files') {
288
- ctx.json({ error: 'too many files', limit: maxFiles, configure: 'HERMES_WEBUI_FOLDER_ZIP_MAX_FILES' }, { status: 413 });
336
+ await ctx.json({ error: 'too many files', limit: maxFiles, configure: 'HERMES_WEBUI_FOLDER_ZIP_MAX_FILES' }, { status: 413 });
289
337
  return;
290
338
  }
291
339
  if (limit === 'max_bytes') {
292
- ctx.json({ error: 'folder too large', limit_bytes: maxBytes, configure: 'HERMES_WEBUI_FOLDER_ZIP_MAX_MB' }, { status: 413 });
340
+ await ctx.json({ error: 'folder too large', limit_bytes: maxBytes, configure: 'HERMES_WEBUI_FOLDER_ZIP_MAX_MB' }, { status: 413 });
293
341
  return;
294
342
  }
295
343
  const zipName = `${basename(target) || 'workspace'}.zip`;
@@ -329,13 +377,13 @@ async function handleFolderDownload(ctx) {
329
377
  ctx.res.end();
330
378
  }
331
379
  }
332
- function handleSessionExport(ctx) {
380
+ async function handleSessionExport(ctx) {
333
381
  const sid = ctx.query.get('session_id') ?? '';
334
382
  if (!sid)
335
383
  throw new HttpError(400, 'session_id is required');
336
384
  let session;
337
385
  try {
338
- session = ctx.deps.sessionStore.get(sid);
386
+ session = (await ctx.deps.sessionStore.get(sid));
339
387
  }
340
388
  catch {
341
389
  throw new HttpError(404, 'Session not found');
@@ -366,20 +414,26 @@ function handleSessionExport(ctx) {
366
414
  contentType = 'text/html; charset=utf-8';
367
415
  ext = 'html';
368
416
  }
417
+ else if (fmt === 'markdown') {
418
+ payload = renderSessionMarkdown(safe);
419
+ contentType = 'text/markdown; charset=utf-8';
420
+ ext = 'md';
421
+ }
369
422
  else {
370
423
  payload = pythonPrettyJson(safe);
371
424
  contentType = 'application/json; charset=utf-8';
372
425
  ext = 'json';
373
426
  }
374
- ctx.send({ status: 200, headers: { 'Content-Type': contentType, 'Content-Disposition': `attachment; filename="hermes-${sid}.${ext}"`, 'Cache-Control': 'no-store' }, body: Buffer.from(payload, 'utf8') });
427
+ await ctx.send({ status: 200, headers: { 'Content-Type': contentType, 'Content-Disposition': `attachment; filename="hermes-${sid}.${ext}"`, 'Cache-Control': 'no-store' }, body: Buffer.from(payload, 'utf8') });
375
428
  }
376
- async function handleUpload(ctx) {
429
+ /** The shared upload preamble: the declared-size gate, the bounded body read, and the multipart parse (answers on failure). */
430
+ export async function readUploadForm(ctx) {
377
431
  const maxBytes = ctx.deps.config.maxUploadBytes;
378
432
  const contentType = ctx.header('content-type') ?? '';
379
433
  const contentLength = Number.parseInt(ctx.header('content-length') ?? '0', 10) || 0;
380
434
  if (contentLength > maxBytes) {
381
- ctx.json({ error: `File too large (max ${String(Math.floor(maxBytes / 1024 / 1024))}MB)` }, { status: 413 });
382
- return;
435
+ await ctx.json({ error: `File too large (max ${String(Math.floor(maxBytes / 1024 / 1024))}MB)` }, { status: 413 });
436
+ return null;
383
437
  }
384
438
  let raw;
385
439
  try {
@@ -387,57 +441,216 @@ async function handleUpload(ctx) {
387
441
  }
388
442
  catch {
389
443
  // Node's parser already answers a garbage or negative Content-Length with 400 before this handler runs.
390
- ctx.json({ error: `Upload too large (max ${String(maxBytes)} bytes)` }, { status: 400 });
391
- return;
444
+ await ctx.json({ error: `Upload too large (max ${String(maxBytes)} bytes)` }, { status: 400 });
445
+ return null;
392
446
  }
393
- let parsed;
394
447
  try {
395
- parsed = parseMultipart(raw, contentType);
448
+ return parseMultipart(raw, contentType);
396
449
  }
397
450
  catch (error) {
398
- ctx.json({ error: error.message }, { status: 400 });
399
- return;
451
+ await ctx.json({ error: error.message }, { status: 400 });
452
+ return null;
400
453
  }
401
- const sessionId = parsed.fields.session_id ?? ctx.query.get('session_id') ?? '';
402
- const file = parsed.files.file;
454
+ }
455
+ /** The single `file` part of a chat upload, or null after answering 400. */
456
+ export async function uploadFile(ctx, form) {
457
+ const file = form.files.file;
403
458
  if (!file) {
404
- ctx.json({ error: 'No file field in request' }, { status: 400 });
405
- return;
459
+ await ctx.json({ error: 'No file field in request' }, { status: 400 });
460
+ return null;
406
461
  }
407
462
  if (!file.filename) {
408
- ctx.json({ error: 'No filename in upload' }, { status: 400 });
409
- return;
463
+ await ctx.json({ error: 'No filename in upload' }, { status: 400 });
464
+ return null;
410
465
  }
466
+ return file;
467
+ }
468
+ /** An unknown session and another profile's session both answer 404 before anything is written. */
469
+ async function uploadSessionVisible(ctx, sessionId) {
411
470
  let session;
412
471
  try {
413
- session = ctx.deps.sessionStore.get(sessionId, { metadataOnly: true });
472
+ session = (await ctx.deps.sessionStore.get(sessionId, { metadataOnly: true }));
414
473
  }
415
474
  catch {
416
- ctx.json({ error: 'Session not found' }, { status: 404 });
417
- return;
475
+ await ctx.json({ error: 'Session not found' }, { status: 404 });
476
+ return false;
418
477
  }
419
478
  if (!ctx.deps.profilesMatch(session.profile, ctx.deps.activeProfile())) {
420
- ctx.json({ error: 'Session not found' }, { status: 404 });
421
- return;
479
+ await ctx.json({ error: 'Session not found' }, { status: 404 });
480
+ return false;
422
481
  }
482
+ return true;
483
+ }
484
+ async function handleUpload(ctx) {
485
+ const form = await readUploadForm(ctx);
486
+ if (!form)
487
+ return;
488
+ const sessionId = form.fields.session_id ?? ctx.query.get('session_id') ?? '';
489
+ const file = (await uploadFile(ctx, form));
490
+ if (!file || !(await uploadSessionVisible(ctx, sessionId)))
491
+ return;
423
492
  try {
424
- ctx.json(ctx.deps.uploads.store(sessionId, file.filename, file.body));
493
+ await ctx.json(ctx.deps.uploads.store(sessionId, file.filename, file.body));
425
494
  }
426
495
  catch (error) {
427
496
  if (error instanceof UploadConflict) {
428
- ctx.json({ error: error.message }, { status: 409 });
497
+ (await ctx.json({ error: error.message }, { status: 409 }));
429
498
  return;
430
499
  }
431
500
  if (error instanceof UploadRejected) {
432
- ctx.json({ error: error.message }, { status: 403 });
501
+ (await ctx.json({ error: error.message }, { status: 403 }));
433
502
  return;
434
503
  }
435
504
  if (error instanceof Error && error.message === 'Invalid filename') {
436
- ctx.json({ error: error.message }, { status: 400 });
505
+ (await ctx.json({ error: error.message }, { status: 400 }));
437
506
  return;
438
507
  }
439
508
  ctx.deps.log(`[webui] upload error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
440
- ctx.json({ error: 'Upload failed' }, { status: 500 });
509
+ await ctx.json({ error: 'Upload failed' }, { status: 500 });
441
510
  }
442
511
  }
512
+ /** Python `handle_upload_extract`: unpack an archive into `<session inbox>/<stem>` with a directory rollback receipt. */
513
+ async function handleUploadExtract(ctx) {
514
+ const form = await readUploadForm(ctx);
515
+ if (!form)
516
+ return;
517
+ const sessionId = form.fields.session_id ?? '';
518
+ const file = (await uploadFile(ctx, form));
519
+ if (!file || !(await uploadSessionVisible(ctx, sessionId)))
520
+ return;
521
+ const { uploads } = ctx.deps;
522
+ try {
523
+ const inbox = uploads.sessionDir(sessionId);
524
+ mkdirSync(inbox, { recursive: true });
525
+ const result = await extractArchive(file.body, file.filename, inbox, inbox, ctx.deps.config.maxExtractedBytes);
526
+ let token;
527
+ try {
528
+ token = uploads.registerReceipt(sessionId, result.dest, true);
529
+ }
530
+ catch (error) {
531
+ try {
532
+ rmtreeAnchored(inbox, result.dest);
533
+ }
534
+ catch { /* ignore */ }
535
+ throw error;
536
+ }
537
+ await ctx.json({ ok: true, ...result, rollback_token: token });
538
+ }
539
+ catch (error) {
540
+ if (error instanceof ArchiveRejected) {
541
+ (await ctx.json({ error: error.message }, { status: 400 }));
542
+ return;
543
+ }
544
+ ctx.deps.log(`[webui] upload extract error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
545
+ await ctx.json({ error: 'Archive extraction failed' }, { status: 500 });
546
+ }
547
+ }
548
+ /**
549
+ * Python `handle_workspace_upload`: store each multipart file under `<workspace>/<path>` (deduplicated `-1`...`-999`);
550
+ * an archive is extracted into `<path>/<stem>` and removed, and a failed extraction is reported on that file.
551
+ */
552
+ async function handleWorkspaceUpload(ctx) {
553
+ const form = await readUploadForm(ctx);
554
+ if (!form)
555
+ return;
556
+ const sessionId = form.fields.session_id ?? '';
557
+ const subpath = form.fields.path ?? '';
558
+ if (!sessionId)
559
+ throw new HttpError(400, 'Missing session_id');
560
+ if (!form.parts.length)
561
+ throw new HttpError(400, 'No file field in request');
562
+ let workspace;
563
+ try {
564
+ workspace = (await fileOpsSession(ctx, sessionId, { strictWorkspace: true })).workspace;
565
+ }
566
+ catch (error) {
567
+ if (error instanceof HttpError && error.message === REMOTE_WORKSPACE_UNSUPPORTED_CODE)
568
+ throw new HttpError(400, REMOTE_WORKSPACE_UNSUPPORTED_MESSAGE, { code: REMOTE_WORKSPACE_UNSUPPORTED_CODE });
569
+ throw error;
570
+ }
571
+ workspace = resolvePathLikePython(workspace);
572
+ if (ctx.deps.git.workspaceBusy(workspace))
573
+ throw new HttpError(409, WORKSPACE_BUSY_MESSAGE);
574
+ let targetDir;
575
+ try {
576
+ targetDir = subpath ? safeResolveWs(workspace, subpath) : workspace;
577
+ }
578
+ catch (error) {
579
+ throw new HttpError(400, error.message);
580
+ }
581
+ try {
582
+ makeAnchoredDir(workspace, targetDir);
583
+ }
584
+ catch {
585
+ throw new HttpError(403, 'Upload target escapes workspace');
586
+ }
587
+ const results = [];
588
+ try {
589
+ // Held for the whole upload, extraction included, so a Git operation cannot start in this workspace meanwhile.
590
+ await ctx.deps.git.holdWrite(workspace, async () => {
591
+ for (const file of form.parts) {
592
+ if (!file.filename)
593
+ continue;
594
+ const safeName = sanitizeUploadName(file.filename);
595
+ let dest;
596
+ try {
597
+ dest = uploadDestination(targetDir, safeName);
598
+ }
599
+ catch (error) {
600
+ if (error instanceof UploadRejected)
601
+ throw new HttpError(400, error.message);
602
+ throw error;
603
+ }
604
+ let fd;
605
+ try {
606
+ fd = openAnchoredCreateFd(workspace, dest);
607
+ }
608
+ catch (error) {
609
+ if (error instanceof FileExistsError)
610
+ throw new HttpError(409, `Upload destination already exists: ${safeName}`);
611
+ throw new HttpError(403, `Path traversal blocked: ${safeName}`);
612
+ }
613
+ try {
614
+ writeFully(fd, file.body);
615
+ }
616
+ finally {
617
+ closeSync(fd);
618
+ }
619
+ const mime = guessMime(safeName);
620
+ if (isArchiveName(safeName)) {
621
+ let outcome;
622
+ try {
623
+ const extraction = await extractArchive(file.body, safeName, workspace, targetDir, ctx.deps.config.maxExtractedBytes);
624
+ outcome = { filename: safeName, path: extraction.dest, size: file.body.length, is_image: false, extracted: true, extracted_files: extraction.files, extracted_count: extraction.extracted };
625
+ }
626
+ catch (error) {
627
+ const reason = error instanceof ArchiveRejected || error instanceof CorruptArchive ? error.message : null;
628
+ ctx.deps.log(`[webui] workspace upload extract error: ${reason ?? (error instanceof Error ? (error.stack ?? error.message) : String(error))}`);
629
+ outcome = { filename: safeName, path: targetDir, size: file.body.length, mime, is_image: false, extracted: false, extract_error: reason || 'Archive extraction failed' };
630
+ }
631
+ // The archive itself never stays behind, whether or not it extracted.
632
+ try {
633
+ unlinkAnchored(workspace, dest);
634
+ }
635
+ catch (error) {
636
+ if (error.code !== 'ENOENT')
637
+ throw error;
638
+ }
639
+ results.push(outcome);
640
+ continue;
641
+ }
642
+ results.push({ filename: basename(dest), path: dest, size: file.body.length, mime, is_image: mime.startsWith('image/'), extracted: false });
643
+ }
644
+ });
645
+ }
646
+ catch (error) {
647
+ if (error instanceof HttpError)
648
+ throw error;
649
+ if (error instanceof Error && error.message === 'Invalid filename')
650
+ throw new HttpError(400, error.message);
651
+ ctx.deps.log(`[webui] workspace upload error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
652
+ throw new HttpError(500, 'Upload failed');
653
+ }
654
+ await ctx.json(results.length === 1 ? results[0] : { files: results, count: results.length });
655
+ }
443
656
  //# sourceMappingURL=raw-routes.js.map