@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
@@ -1,4 +1,3 @@
1
- /** Skills, memory, prompts, commands, notes, insights, logs, health, MCP, plugins, updates, diagnostics (Python `api/routes.py` handlers of the same paths). */
2
1
  import { implement } from '@orpc/server';
3
2
  import { createHash } from 'node:crypto';
4
3
  import { join } from 'node:path';
@@ -6,19 +5,32 @@ import { toolsContract } from '@maudecode/talaria-web-contracts';
6
5
  import { HttpError, requireFields } from './router.js';
7
6
  import { requestSessionIdGuard } from './session-visibility.js';
8
7
  import { activeProfileName, ensureTrustedAuthSession, sessionCanManageServer } from '../auth/gate.js';
9
- import { HttpFailure } from '../sessions/service.js';
8
+ import { currentChatReadiness, HttpFailure } from '../sessions/service.js';
10
9
  import { SessionNotFound } from '../sessions/store.js';
11
10
  import { insightsSessionRows } from '../sessions/state-db.js';
12
11
  import { ConfigUnavailable } from '../config/agent-config.js';
13
12
  import { SidecarError } from '../sidecar/client.js';
14
13
  import { createPrompt, deletePrompt, externalNotesEnabled, loadPrompts, readMemory, writeMemory } from '../tools/memory.js';
15
- import { notesSources } from '../tools/mcp.js';
14
+ import { JoplinError, joplinNote, joplinSearch, notesSources } from '../tools/mcp.js';
15
+ import { catalogNames, connectorOperation, connectorsPage, connectorTools, rememberCatalogNames } from '../tools/connectors.js';
16
+ import { resolveWikiLocation, wikiPages } from '../tools/wiki.js';
16
17
  import { buildInsights } from '../tools/insights.js';
17
- import { commandCatalog } from '../tools/commands.js';
18
- import { agentHealth, dashboardStatus, readLogTail, systemHealth } from '../tools/health.js';
18
+ import { commandCatalog, SESSION_EXEC_COMMANDS } from '../tools/commands.js';
19
+ import { spokenText } from './tools-raw.js';
20
+ import { agentHealth, applyDashboardConfig, dashboardConfig, dashboardStatus, gatewayStatus, parseDashboardConfig, readLogTail, remoteGatewayBaseUrl, systemHealth } from '../tools/health.js';
21
+ import { restartGateway } from '../tools/gateway-restart.js';
22
+ import { projectOsDashboard } from '../tools/project-os.js';
23
+ import { PluginSettingsInvalid, planPluginSettings, pluginSettingsView, serializePluginSettingsSave } from '../tools/plugin-settings.js';
24
+ import { credentialShadowed, RuntimeCredentialError, writeRuntimeCredential } from '../providers/runtime-env.js';
25
+ import { EnvWriteError, loadEnvFile } from '../providers/env-file.js';
26
+ import { loadGatewaySessionIdentityMap } from '../sessions/list.js';
27
+ import { gitBadge } from '../workspace/git.js';
28
+ import { sanitizeError } from '../workspace/media.js';
19
29
  import { normalizeChannel, updatesCheckView } from '../tools/updates.js';
20
30
  import { pyBool } from '../settings.js';
21
31
  import { str } from '../util.js';
32
+ import { redactString } from '../redact.js';
33
+ import { GATEWAY_RESTART_NOW } from '../tools/update-notifications.js';
22
34
  const os = implement(toolsContract).$context().use(requestSessionIdGuard);
23
35
  function failure(error) {
24
36
  if (error instanceof HttpError)
@@ -43,6 +55,128 @@ async function run(fn) {
43
55
  }
44
56
  }
45
57
  const home = (ctx) => ctx.deps.profileHome(activeProfileName(ctx));
58
+ /** Profile homes with an `stt.warm` in flight (TAL-916). */
59
+ const warmingHomes = new Set();
60
+ function runningSidecar(ctx) {
61
+ const sidecar = ctx.deps.sidecar();
62
+ if (!sidecar)
63
+ throw new HttpError(503, 'Hermes Agent sidecar is not running');
64
+ return sidecar;
65
+ }
66
+ /** A slash command's sidecar failure: `notFound` is 404, bad input 400, the rest as `failure` maps them. */
67
+ function commandFailure(error, notFound, message) {
68
+ if (error instanceof SidecarError && error.condition === notFound)
69
+ throw new HttpError(404, message);
70
+ if (error instanceof SidecarError && (error.condition === 'invalid_params' || error.code === -32602))
71
+ throw new HttpError(400, error.message);
72
+ return failure(error);
73
+ }
74
+ /** TAL-820: several profiles have third-party plugins and one of them loads its plugins into the shared sidecar process. */
75
+ async function pluginIsolationAdvisory(ctx) {
76
+ if (ctx.deps.isolatedProfileMode())
77
+ return false;
78
+ const { profiles } = await runningSidecar(ctx).call('plugins.isolation', { base_home: ctx.deps.profileHome('default') });
79
+ const withPlugins = profiles.filter((profile) => profile.third_party_plugins > 0);
80
+ return withPlugins.length > 1 && withPlugins.some((profile) => profile.isolation === 'in_process');
81
+ }
82
+ /** TAL-806: the active profile's plugins that declare settings; none on an Agent without the settings module. */
83
+ async function pluginSettingsList(ctx) {
84
+ const result = await runningSidecar(ctx).call('plugins.settings', { profile_home: home(ctx) });
85
+ return result.plugins.map((entry) => ({ key: entry.key, fields: pluginSettingsFieldsView(ctx, entry.fields) }));
86
+ }
87
+ const pluginSettingsFieldsView = (ctx, fields) => {
88
+ const rootProfile = ctx.deps.isRootProfile(activeProfileName(ctx));
89
+ return pluginSettingsView(fields, (envVar) => credentialShadowed(ctx.deps.config.env, rootProfile, envVar));
90
+ };
91
+ /** A refusal that proves nothing was written; a timeout or a sidecar exit leaves the outcome unknown. */
92
+ const definitiveRefusal = (error) => error instanceof SidecarError ? ['plugin_settings_managed', 'plugin_settings_changed', 'plugin_not_found'].includes(error.condition) || error.code === -32602
93
+ : error instanceof EnvWriteError ? error.condition !== 'sidecar_unavailable'
94
+ : error instanceof RuntimeCredentialError;
95
+ /** TAL-818: a sidecar `connectors.*` condition as the HTTP status the contract names; the body's `code` is the condition. */
96
+ const CONNECTOR_STATUS = {
97
+ connectors_unavailable: 404, connector_not_found: 404, connection_not_found: 404, operation_not_found: 404,
98
+ connectors_sign_in: 403, connectors_forbidden: 403, policy_invalid: 400, connectors_failed: 502,
99
+ policy_conflict: 409, connectors_org_required: 409, operation_settled: 409, operation_refused: 409,
100
+ };
101
+ async function connectorCall(ctx, method, params) {
102
+ try {
103
+ // A connect waits up to 31 s for its link; every portal request inside a call is bounded at 30 s.
104
+ return await runningSidecar(ctx).call(method, { ...params, profile_home: home(ctx) }, { timeoutMs: 90_000 });
105
+ }
106
+ catch (error) {
107
+ const status = error instanceof SidecarError ? CONNECTOR_STATUS[error.condition] : undefined;
108
+ if (status !== undefined)
109
+ throw new HttpError(status, error.message, { code: error.condition });
110
+ if (error instanceof SidecarError && error.code === -32602)
111
+ throw new HttpError(400, error.message);
112
+ throw error;
113
+ }
114
+ }
115
+ /** The catalog's app names for the active profile: the last page read's, else one catalog read; none when it fails. */
116
+ async function connectorNames(ctx) {
117
+ return catalogNames(home(ctx)) ?? await connectorCall(ctx, 'connectors.catalog', {}).then((r) => rememberCatalogNames(home(ctx), r.connectors), () => ({}));
118
+ }
119
+ /** An operation as the contract shows it, with the apps' catalog names. */
120
+ const operationView = async (ctx, snapshot) => connectorOperation(snapshot, await connectorNames(ctx));
121
+ /** The rules, or why they cannot be read (a portal refusal or failure): the page then fails closed and says why. */
122
+ const connectorPolicy = (ctx) => connectorCall(ctx, 'connectors.policy.get', {}).then((policy) => ({ policy, error: null }), (error) => { if (error instanceof HttpError)
123
+ return { policy: null, error: error.message }; throw error; });
124
+ const health = (ctx) => agentHealth({ env: ctx.deps.config.env, hermesHome: ctx.deps.config.hermesHome, profileHome: () => home(ctx), fetch: () => ctx.deps.fetch, now: ctx.deps.nowSeconds });
125
+ /**
126
+ * TAL-805: the active profile's gateway mode from sidecar `gateway.mode`; null for a remote gateway. A named profile never
127
+ * borrows the root's health: when the sidecar cannot answer, it reads `stopped`, while the root profile reads null and keeps
128
+ * its health. The root profile's gateway in another container (`health` alive from its fresh runtime status) is invisible
129
+ * to the sidecar's process checks, so it stays `own`.
130
+ */
131
+ async function gatewayMode(ctx, health) {
132
+ if (remoteGatewayBaseUrl(ctx.deps.config.env) !== null)
133
+ return null;
134
+ const sidecar = ctx.deps.sidecar();
135
+ const name = activeProfileName(ctx);
136
+ const isDefault = ctx.deps.isRootProfile(name);
137
+ let mode = null;
138
+ try {
139
+ if (sidecar)
140
+ mode = (await sidecar.call('gateway.mode', { profile_home: home(ctx), name, is_default: isDefault }, { timeoutMs: 10_000 })).gateway_mode;
141
+ }
142
+ catch {
143
+ mode = null;
144
+ }
145
+ if (mode === null)
146
+ return isDefault ? null : 'stopped';
147
+ return mode === 'stopped' && isDefault && health.alive === true ? 'own' : mode;
148
+ }
149
+ /** Python `_gateway_status_payload` for the active profile. */
150
+ async function currentGatewayStatus(ctx) {
151
+ const sessionsPath = join(home(ctx), 'sessions', 'sessions.json');
152
+ const probe = await health(ctx);
153
+ return gatewayStatus(probe, loadGatewaySessionIdentityMap(sessionsPath), sessionsPath, await gatewayMode(ctx, probe));
154
+ }
155
+ const GATEWAY_ACTION_TIMEOUT_S = 60;
156
+ /** Python `_handle_gateway_lifecycle` through sidecar `gateway.control`: 409 busy, 504 timeout, 500 failure; only the Agent's first error line (`reason`) leaves the server, never its CLI output (TAL-884). */
157
+ async function gatewayAction(ctx, action) {
158
+ const sidecar = ctx.deps.sidecar();
159
+ if (!sidecar)
160
+ throw new HttpError(500, 'Hermes Agent sidecar is not running', { ok: false, action });
161
+ let outcome;
162
+ try {
163
+ outcome = await sidecar.call('gateway.control', { profile_home: home(ctx), action, background_wait_seconds: GATEWAY_ACTION_TIMEOUT_S }, { timeoutMs: 300_000 });
164
+ }
165
+ catch (error) {
166
+ throw new HttpError(500, sanitizeError(error), { ok: false, action });
167
+ }
168
+ if (outcome.status === 'busy')
169
+ throw new HttpError(409, 'Another gateway action is already in progress; try again shortly.', { ok: false, action });
170
+ if (outcome.timed_out)
171
+ throw new HttpError(504, `Gateway ${action} timed out after ${String(GATEWAY_ACTION_TIMEOUT_S)} seconds`, { ok: false, action });
172
+ if (outcome.status === 'failed' && outcome.returncode !== undefined) {
173
+ const error = outcome.reason ? `Gateway ${action} failed: ${outcome.reason}` : `Gateway ${action} failed with exit code ${String(outcome.returncode)}`;
174
+ throw new HttpError(500, error, { ok: false, action, returncode: outcome.returncode });
175
+ }
176
+ if (outcome.status === 'failed')
177
+ throw new HttpError(500, outcome.message, { ok: false, action });
178
+ return { ok: true, action, message: `Gateway ${action} completed.`, status: await currentGatewayStatus(ctx) };
179
+ }
46
180
  const principalHash = (...parts) => createHash('sha256').update(JSON.stringify(parts), 'utf8').digest('hex');
47
181
  export function updateNotificationOwner(session) {
48
182
  const authType = str(session?.auth_type).trim();
@@ -67,39 +201,54 @@ async function updateNotificationScope(ctx, tab) {
67
201
  tab: tab ?? null,
68
202
  };
69
203
  }
70
- async function applyWithNotification(ctx, target, apply, confirmed = false, tab) {
71
- const scope = await updateNotificationScope(ctx, tab);
204
+ async function applyWithNotification(ctx, input, action) {
205
+ const target = updateTarget(input.target);
206
+ const confirmed = input.confirmed_agent_revision !== undefined || input.agent_refresh_token !== undefined;
207
+ const scope = await updateNotificationScope(ctx, input.tab_id);
72
208
  const active = ctx.deps.updateNotifications.activeUpdate(scope, target);
73
209
  if (active)
74
- ctx.deps.updateNotifications.watch(scope, active.id);
210
+ (await ctx.deps.updateNotifications.watch(scope, active.id));
75
211
  if (active && (active.phase === 'applying' || active.phase === 'restarting')) {
76
212
  return { ok: false, status: 'already_in_progress', message: 'Update already in progress', notification_id: active.id };
77
213
  }
78
- const notification = active ?? ctx.deps.updateNotifications.begin(scope, target);
214
+ const notification = active ?? (await ctx.deps.updateNotifications.begin(scope, target));
79
215
  if (active?.phase === 'awaiting_confirmation' && confirmed)
80
- ctx.deps.updateNotifications.transition(notification.id, 'applying');
216
+ (await ctx.deps.updateNotifications.transition(notification.id, 'applying'));
81
217
  try {
82
- const result = await apply();
218
+ const options = { agentChannel: input.agent_channel, confirmedRevision: input.confirmed_agent_revision, version: input.version, refreshToken: input.agent_refresh_token, mutationIntent: principalHash(scope.owner, scope.profile, notification.id) };
219
+ const result = action === 'apply' ? await ctx.deps.updates.apply(target, bodyChannel(input.channel), () => true, options)
220
+ : action === 'force' ? await ctx.deps.updates.force(target, bodyChannel(input.channel), options)
221
+ : await ctx.deps.updates.clearLock(target, options);
83
222
  const verifiedIdentity = typeof result.verified_revision === 'string'
84
223
  ? { revision: result.verified_revision, version: typeof result.verified_version === 'string' ? result.verified_version : null }
85
224
  : undefined;
86
225
  if (result.confirmation_required === true)
87
- ctx.deps.updateNotifications.transition(notification.id, 'awaiting_confirmation', null, verifiedIdentity);
226
+ (await ctx.deps.updateNotifications.transition(notification.id, 'awaiting_confirmation', null, verifiedIdentity, result.message));
88
227
  else if (result.restart_blocked === true)
89
- ctx.deps.updateNotifications.transition(notification.id, 'blocked', null, verifiedIdentity, result.message);
228
+ (await ctx.deps.updateNotifications.transition(notification.id, 'blocked', null, verifiedIdentity, result.message));
90
229
  else if (result.ok !== true)
91
- ctx.deps.updateNotifications.transition(notification.id, 'failed', null, verifiedIdentity, result.message || result.error);
230
+ (await ctx.deps.updateNotifications.transition(notification.id, 'failed', null, verifiedIdentity, result.message || result.error));
92
231
  else if (target === 'webui' && result.restart_scheduled === true) {
93
- ctx.deps.updateNotifications.transition(notification.id, 'restarting', str(result.sourceRevision || result.candidate_revision), verifiedIdentity);
232
+ await ctx.deps.updateNotifications.transition(notification.id, 'restarting', str(result.sourceRevision || result.candidate_revision), verifiedIdentity);
233
+ }
234
+ else if (result.gateway_restart === 'waiting_for_idle' || (target === 'agent' && ctx.deps.gatewayRestarts.pendingId() !== null)) {
235
+ // TAL-953: the gateway restarts once its own work drains. A restart already waiting covers this update too, and a
236
+ // repeated request that found the Agent current follows that wait rather than reporting success before it.
237
+ const detail = result.stash_conflict === true ? str(result.message) : null;
238
+ await ctx.deps.updateNotifications.transition(notification.id, 'waiting_for_idle', null, verifiedIdentity, detail);
239
+ const pending = (await ctx.deps.gatewayRestarts.schedule(notification.id, scope.profile, detail));
240
+ if (pending !== notification.id)
241
+ (await ctx.deps.updateNotifications.watch(scope, pending));
242
+ return { ...result, notification_id: pending };
94
243
  }
95
244
  else {
96
245
  // A kept Agent stash needs the user's attention; its recovery commands ride on the success record.
97
- ctx.deps.updateNotifications.transition(notification.id, 'succeeded', null, verifiedIdentity, result.stash_conflict === true ? result.message : undefined);
246
+ await ctx.deps.updateNotifications.transition(notification.id, 'succeeded', null, verifiedIdentity, result.stash_conflict === true ? result.message : undefined);
98
247
  }
99
248
  return { ...result, notification_id: notification.id };
100
249
  }
101
250
  catch (error) {
102
- ctx.deps.updateNotifications.transition(notification.id, 'failed', null, undefined, error instanceof Error ? error.message : null);
251
+ await ctx.deps.updateNotifications.transition(notification.id, 'failed', null, undefined, error instanceof Error ? error.message : null);
103
252
  throw error;
104
253
  }
105
254
  }
@@ -177,10 +326,10 @@ export function sanitizeClientEvent(payload) {
177
326
  out.event = 'unknown';
178
327
  return out;
179
328
  }
180
- function memoryWorkspace(ctx, sessionId, workspace) {
329
+ async function memoryWorkspace(ctx, sessionId, workspace) {
181
330
  if (sessionId) {
182
331
  try {
183
- const session = ctx.deps.sessionStore.get(sessionId, { metadataOnly: true });
332
+ const session = (await ctx.deps.sessionStore.get(sessionId, { metadataOnly: true }));
184
333
  if (!ctx.deps.workspaces.profileSupportsLocalIo(session.profile))
185
334
  return null;
186
335
  const ws = str(session.workspace).trim();
@@ -193,17 +342,49 @@ function memoryWorkspace(ctx, sessionId, workspace) {
193
342
  const profile = activeProfileName(ctx);
194
343
  if (!ctx.deps.workspaces.profileSupportsLocalIo(profile))
195
344
  return null;
196
- const raw = workspace || (ctx.deps.config.env.TERMINAL_CWD ?? '') || ctx.deps.workspaces.lastWorkspace(profile);
345
+ const raw = workspace || (ctx.deps.config.env.TERMINAL_CWD ?? '') || (await ctx.deps.workspaces.lastWorkspace(profile));
197
346
  if (!raw)
198
347
  return null;
199
348
  try {
200
- return ctx.deps.workspaces.resolveTrusted(raw, profile);
349
+ return (await ctx.deps.workspaces.resolveTrusted(raw, profile));
201
350
  }
202
351
  catch {
203
352
  return null;
204
353
  }
205
354
  }
355
+ /** The external notes drawer's Joplin access; 404 while the drawer is disabled. */
356
+ async function joplinDeps(ctx, disabled) {
357
+ const config = await ctx.deps.agentConfig.read(home(ctx));
358
+ if (!externalNotesEnabled(ctx.deps.config.env, config))
359
+ throw new HttpError(404, 'External notes sources are disabled.', { source: 'disabled', ...disabled });
360
+ return { config, env: ctx.deps.config.env, fetch: ctx.deps.fetch, redact: ctx.deps.sessions.deps.redactEnabled() };
361
+ }
362
+ /** A Joplin failure answers 502 with its fixed message. */
363
+ async function joplin(extra, fn) {
364
+ try {
365
+ return await fn();
366
+ }
367
+ catch (error) {
368
+ if (error instanceof JoplinError)
369
+ throw new HttpError(502, error.message, { source: 'joplin', ...extra });
370
+ throw error;
371
+ }
372
+ }
373
+ /** Python `int(limit or 20)`, falling back to 20 for anything unparseable or zero. */
374
+ const notesLimit = (raw) => (/^\s*[+-]?\d+\s*$/.test(raw ?? '') ? Number.parseInt(String(raw).trim(), 10) : 0) || 20;
375
+ /** The wiki root for the active profile; an unreadable config.yaml falls through to the default. */
376
+ async function wikiLocation(ctx) {
377
+ const profileHome = home(ctx);
378
+ const config = await ctx.deps.agentConfig.read(profileHome).catch(() => ({}));
379
+ return resolveWikiLocation(ctx.deps.config.env, profileHome, config, ctx.deps.config.homeDir);
380
+ }
381
+ /** TAL-819: the Agent's review handler per write; its message can quote the entry text an approval replaced or removed. */
382
+ async function reviewPendingWrites(ctx, verb, input) {
383
+ const { results } = await runningSidecar(ctx).call(`write_approval.${verb}`, { profile_home: home(ctx), kind: input.kind, writes: input.writes });
384
+ return { results: results.map((r) => ({ ...r, message: redactString(r.message, ctx.deps.sessions.deps.redactEnabled()) })) };
385
+ }
206
386
  export const toolsRouter = os.router({
387
+ toolsetsCatalog: os.toolsetsCatalog.handler(({ input, context: { ctx } }) => run(() => runningSidecar(ctx).call('toolsets.catalog', { profile_home: home(ctx), enabled_toolsets: input.toolsets }))),
207
388
  skills: {
208
389
  list: os.skills.list.handler(({ input, context: { ctx } }) => run(async () => ({ skills: await ctx.deps.skills.list(home(ctx), str(input.category).trim() || null) }))),
209
390
  usage: os.skills.usage.handler(({ context: { ctx } }) => run(() => ctx.deps.skills.usage(home(ctx)))),
@@ -234,14 +415,31 @@ export const toolsRouter = os.router({
234
415
  memory: {
235
416
  get: os.memory.get.handler(({ input, context: { ctx } }) => run(async () => {
236
417
  const config = await ctx.deps.agentConfig.read(home(ctx));
237
- return readMemory(home(ctx), config, memoryWorkspace(ctx, str(input.session_id), str(input.workspace)), ctx.deps.config.env, ctx.deps.sessions.deps.redactEnabled());
418
+ return readMemory(home(ctx), config, (await memoryWorkspace(ctx, str(input.session_id), str(input.workspace))), ctx.deps.config.env, ctx.deps.sessions.deps.redactEnabled(), ctx.deps.auth.signingKey());
238
419
  })),
239
420
  write: os.memory.write.handler(({ input, context: { ctx } }) => run(async () => {
240
421
  const section = input.section ?? input.target;
241
422
  if (section === undefined || input.content === undefined)
242
423
  throw new HttpError(400, 'Missing required field(s): section, content');
243
- return writeMemory(home(ctx), await ctx.deps.agentConfig.read(home(ctx)), str(section), str(input.content));
424
+ const profileHome = home(ctx);
425
+ return writeMemory(profileHome, await ctx.deps.agentConfig.read(profileHome), str(section), str(input.content), input.revision, {
426
+ key: ctx.deps.auth.signingKey(),
427
+ check: (target, content) => runningSidecar(ctx).call('memory.check', { profile_home: profileHome, target, content }),
428
+ write: (target, base, content) => runningSidecar(ctx).call('memory.write', { profile_home: profileHome, section: target, base, content }),
429
+ });
430
+ })),
431
+ },
432
+ pendingWrites: {
433
+ list: os.pendingWrites.list.handler(({ input, context: { ctx } }) => run(async () => {
434
+ // Without the Agent nothing can be reviewed, so the page shows no panel (the Agent's own status reports it down).
435
+ const sidecar = ctx.deps.sidecar();
436
+ const { enabled, items } = sidecar ? await sidecar.call('write_approval.pending', { profile_home: home(ctx), kind: input.kind }) : { enabled: false, items: [] };
437
+ const redact = (text) => redactString(text, ctx.deps.sessions.deps.redactEnabled());
438
+ // Display text is redacted like the memory page; `revision` stays the sidecar's hash of the raw diff it rechecks.
439
+ return { kind: input.kind, visible: items.length > 0, enabled, items: items.map((item) => ({ ...item, summary: redact(item.summary), diff: redact(item.diff) })) };
244
440
  })),
441
+ approve: os.pendingWrites.approve.handler(({ input, context: { ctx } }) => run(() => reviewPendingWrites(ctx, 'approve', input))),
442
+ reject: os.pendingWrites.reject.handler(({ input, context: { ctx } }) => run(() => reviewPendingWrites(ctx, 'reject', input))),
245
443
  },
246
444
  prompts: {
247
445
  list: os.prompts.list.handler(({ context: { ctx } }) => run(() => ({ prompts: loadPrompts(home(ctx)) }))),
@@ -263,20 +461,51 @@ export const toolsRouter = os.router({
263
461
  const command = str(input.command).trim();
264
462
  if (!command)
265
463
  throw new HttpError(400, 'command is required');
464
+ const [, name = '', args = ''] = /^\/?(\S*)\s*([\s\S]*)$/.exec(command) ?? [];
465
+ const sessionId = str(input.session_id).trim();
466
+ if (sessionId)
467
+ (await ctx.deps.sessions.assertWritable(sessionId));
468
+ // TAL-902: a session command runs on the chat's own agent (the request guard already checked the chat's profile).
469
+ if (SESSION_EXEC_COMMANDS.has(name.toLowerCase())) {
470
+ return { output: await ctx.deps.turns.refine(sessionId ? (await ctx.deps.sessionStore.get(sessionId)) : null, args.trim()) };
471
+ }
472
+ // TAL-904: a chat's command runs under that chat's session and the workspace its next turn would use. An unknown
473
+ // chat (404) or a workspace that turn would refuse (400) refuses the command rather than run it half-bound.
474
+ const chat = sessionId ? { session_id: sessionId, workspace: (await ctx.deps.sessions.turnWorkspace((await ctx.deps.sessionStore.get(sessionId, { metadataOnly: true })))) } : {};
475
+ try {
476
+ return { output: (await runningSidecar(ctx).call('commands.exec', { profile_home: home(ctx), command, ...chat }, { timeoutMs: 120_000 })).output };
477
+ }
478
+ catch (error) {
479
+ return commandFailure(error, 'command_not_found', 'Plugin command not found');
480
+ }
481
+ })),
482
+ bundles: os.commands.bundles.handler(({ context: { ctx } }) => run(async () => {
266
483
  const sidecar = ctx.deps.sidecar();
267
- if (!sidecar)
268
- throw new HttpError(503, 'Hermes Agent sidecar is not running');
269
484
  try {
270
- return { output: (await sidecar.call('commands.exec', { profile_home: home(ctx), command }, { timeoutMs: 120_000 })).output };
485
+ return { bundles: sidecar ? (await sidecar.call('commands.bundles', { profile_home: home(ctx) })).bundles : [] };
486
+ }
487
+ catch {
488
+ return { bundles: [] };
489
+ }
490
+ })),
491
+ resolveBundle: os.commands.resolveBundle.handler(({ input, context: { ctx } }) => run(async () => {
492
+ const command = str(input.command).trim();
493
+ if (!command)
494
+ throw new HttpError(400, 'command is required');
495
+ try {
496
+ return await runningSidecar(ctx).call('commands.bundle_resolve', { profile_home: home(ctx), command }, { timeoutMs: 120_000 });
497
+ }
498
+ catch (error) {
499
+ return commandFailure(error, 'bundle_not_found', 'Bundle command not found');
500
+ }
501
+ })),
502
+ resolveMoa: os.commands.resolveMoa.handler(({ input, context: { ctx } }) => run(async () => {
503
+ try {
504
+ return (await runningSidecar(ctx).call('commands.moa_preset', { profile_home: home(ctx), preset: str(input.preset).trim() || null })).moa;
271
505
  }
272
506
  catch (error) {
273
- if (error instanceof SidecarError) {
274
- if (error.condition === 'command_not_found')
275
- throw new HttpError(404, 'Plugin command not found');
276
- if (error.condition === 'invalid_params' || error.code === -32602)
277
- throw new HttpError(400, error.message);
278
- throw new HttpError(500, error.message);
279
- }
507
+ if (error instanceof SidecarError && error.condition === 'moa_unavailable')
508
+ throw new HttpError(503, error.message);
280
509
  throw error;
281
510
  }
282
511
  })),
@@ -287,17 +516,30 @@ export const toolsRouter = os.router({
287
516
  return notesSources(config, externalNotesEnabled(ctx.deps.config.env, config));
288
517
  })),
289
518
  search: os.notes.search.handler(({ input, context: { ctx } }) => run(async () => {
290
- const config = await ctx.deps.agentConfig.read(home(ctx));
291
- if (!externalNotesEnabled(ctx.deps.config.env, config))
292
- throw new HttpError(404, 'External notes sources are disabled.', { source: 'disabled', results: [] });
293
- const source = str(input.source ?? 'joplin').trim().toLowerCase() || 'joplin';
294
- throw new HttpError(source === 'joplin' ? 502 : 400, source === 'joplin' ? 'Joplin search is not available in this release.' : 'Search is currently implemented for Joplin sources only.', { source, results: [] });
519
+ const deps = await joplinDeps(ctx, { results: [] });
520
+ const source = str(input.source).trim().toLowerCase() || 'joplin';
521
+ const query = str(input.q).trim();
522
+ if (source !== 'joplin')
523
+ throw new HttpError(400, 'Search is currently implemented for Joplin sources only.', { source, results: [] });
524
+ return { source: 'joplin', query, results: await joplin({ query, results: [] }, () => joplinSearch(deps, query, notesLimit(input.limit))) };
525
+ })),
526
+ item: os.notes.item.handler(({ input, context: { ctx } }) => run(async () => {
527
+ const deps = await joplinDeps(ctx, {});
528
+ const source = str(input.source).trim().toLowerCase() || 'joplin';
529
+ if (source !== 'joplin')
530
+ throw new HttpError(400, 'Preview is currently implemented for Joplin sources only.', { source });
531
+ return { source: 'joplin', note: await joplin({}, () => joplinNote(deps, str(input.id))) };
295
532
  })),
296
533
  },
297
- insights: os.insights.handler(({ input, context: { ctx } }) => run(() => {
534
+ wiki: {
535
+ status: os.wiki.status.handler(({ context: { ctx } }) => run(async () => wikiPages.status(await wikiLocation(ctx)))),
536
+ browse: os.wiki.browse.handler(({ context: { ctx } }) => run(async () => wikiPages.browse(await wikiLocation(ctx)))),
537
+ page: os.wiki.page.handler(({ input, context: { ctx } }) => run(async () => wikiPages.page(await wikiLocation(ctx), str(input.path)))),
538
+ },
539
+ insights: os.insights.handler(({ input, context: { ctx } }) => run(async () => {
298
540
  let entries = [];
299
541
  try {
300
- entries = ctx.deps.sessionStore.readIndexEntries();
542
+ entries = (await ctx.deps.sessionStore.readIndexEntries());
301
543
  }
302
544
  catch {
303
545
  entries = [];
@@ -306,13 +548,13 @@ export const toolsRouter = os.router({
306
548
  })),
307
549
  logs: os.logs.handler(({ input, context: { ctx } }) => run(() => readLogTail(home(ctx), input.file, input.tail))),
308
550
  ops: {
309
- agent: os.ops.agent.handler(({ context: { ctx } }) => run(() => agentHealth({ env: ctx.deps.config.env, hermesHome: ctx.deps.config.hermesHome, profileHome: () => ctx.deps.profileHome(ctx.deps.activeProfile()), fetch: () => ctx.deps.fetch, now: ctx.deps.nowSeconds }))),
551
+ agent: os.ops.agent.handler(({ context: { ctx } }) => run(async () => { const [gateway, chat] = await Promise.all([health(ctx), currentChatReadiness(ctx.deps.sidecar())]); return { ...gateway, chat }; })),
310
552
  system: os.ops.system.handler(({ context: { ctx } }) => run(() => systemHealth(ctx.deps.config.homeDir, ctx.deps.runtimeDiagnostics()))),
311
553
  restart: os.ops.restart.handler(({ context: { ctx } }) => run(async () => {
312
554
  const sidecar = ctx.deps.sidecar();
313
555
  if (!sidecar)
314
556
  throw new HttpError(500, 'Hermes Agent sidecar is not running');
315
- const outcome = await sidecar.call('gateway.restart', { profile_home: home(ctx) }, { timeoutMs: 300_000 });
557
+ const outcome = await restartGateway(sidecar, home(ctx));
316
558
  if (outcome.status === 'completed')
317
559
  return { ok: true, message: 'Gateway service restarted successfully' };
318
560
  if (outcome.status === 'busy')
@@ -320,12 +562,34 @@ export const toolsRouter = os.router({
320
562
  throw new HttpError(500, outcome.message || 'Internal error running restart');
321
563
  })),
322
564
  dashboard: os.ops.dashboard.handler(({ context: { ctx } }) => run(async () => { const config = await ctx.deps.agentConfig.read(home(ctx)).catch(() => ({})); return dashboardStatus(config, ctx.deps.config.env, ctx.deps.fetch); })),
565
+ dashboardConfig: os.ops.dashboardConfig.handler(({ context: { ctx } }) => run(async () => dashboardConfig(await ctx.deps.agentConfig.read(home(ctx)).catch(() => ({}))))),
566
+ saveDashboardConfig: os.ops.saveDashboardConfig.handler(({ input, context: { ctx } }) => run(async () => {
567
+ const next = parseDashboardConfig(input);
568
+ await ctx.deps.agentConfig.update(home(ctx), (config) => { applyDashboardConfig(config, next); });
569
+ return next;
570
+ })),
571
+ projectOs: os.ops.projectOs.handler(({ input, context: { ctx } }) => run(async () => {
572
+ const profile = activeProfileName(ctx);
573
+ return projectOsDashboard(str(input.board), {
574
+ localIo: ctx.deps.workspaces.profileSupportsLocalIo(profile),
575
+ lastWorkspace: (await ctx.deps.workspaces.lastWorkspace(profile)),
576
+ boards: async () => (await runningSidecar(ctx).call('kanban.boards', { profile_home: home(ctx), include_archived: true })).boards,
577
+ git: async (repoRoot) => gitBadge(await ctx.deps.git.status(repoRoot, { useCache: true })),
578
+ cwd: process.cwd(),
579
+ });
580
+ })),
323
581
  shutdown: os.ops.shutdown.handler(({ context: { ctx } }) => run(() => {
324
582
  ctx.deps.log(`[shutdown-request] remote=${ctx.peer || 'unknown'} method=${ctx.method} path=${ctx.path.slice(0, 240)} ua=${(ctx.header('user-agent') ?? 'no-ua').replace(/[\x00-\x1f\x7f]+/g, '?').slice(0, 240)}`);
325
583
  ctx.deps.requestShutdown();
326
584
  return { status: 'shutting_down' };
327
585
  })),
328
586
  },
587
+ gateway: {
588
+ status: os.gateway.status.handler(({ context: { ctx } }) => run(() => currentGatewayStatus(ctx))),
589
+ start: os.gateway.start.handler(({ context: { ctx } }) => run(() => gatewayAction(ctx, 'start'))),
590
+ stop: os.gateway.stop.handler(({ context: { ctx } }) => run(() => gatewayAction(ctx, 'stop'))),
591
+ restart: os.gateway.restart.handler(({ context: { ctx } }) => run(() => gatewayAction(ctx, 'restart'))),
592
+ },
329
593
  mcp: {
330
594
  servers: os.mcp.servers.handler(({ context: { ctx } }) => run(() => ctx.deps.mcp.servers(home(ctx)))),
331
595
  tools: os.mcp.tools.handler(({ input, context: { ctx } }) => run(() => ctx.deps.mcp.tools(home(ctx), input.q))),
@@ -342,9 +606,48 @@ export const toolsRouter = os.router({
342
606
  update: os.mcp.update.handler(({ input, context: { ctx } }) => run(() => { const { name, ...body } = input; return ctx.deps.mcp.update(home(ctx), decodeURIComponent(name), body); })),
343
607
  delete: os.mcp.delete.handler(({ input, context: { ctx } }) => run(() => ctx.deps.mcp.delete(home(ctx), decodeURIComponent(input.name)))),
344
608
  },
609
+ connectors: {
610
+ // Fails closed: no sidecar, or one that cannot answer, hides the page.
611
+ available: os.connectors.available.handler(({ context: { ctx } }) => run(async () => ({ available: ctx.deps.sidecar() ? await connectorCall(ctx, 'connectors.available', {}).then((r) => r.available, () => false) : false }))),
612
+ page: os.connectors.page.handler(({ context: { ctx } }) => run(async () => {
613
+ if (!ctx.deps.sidecar() || !(await connectorCall(ctx, 'connectors.available', {})).available)
614
+ return { available: false, connectors: [], policy_revision: null, rules_error: null };
615
+ const [catalog, accounts, policy] = await Promise.all([connectorCall(ctx, 'connectors.catalog', {}), connectorCall(ctx, 'connectors.accounts', {}), connectorPolicy(ctx)]);
616
+ rememberCatalogNames(home(ctx), catalog.connectors);
617
+ return connectorsPage(catalog.connectors, accounts.accounts, policy.policy, policy.error);
618
+ })),
619
+ tools: os.connectors.tools.handler(({ input, context: { ctx } }) => run(async () => {
620
+ const [listing, policy] = await Promise.all([connectorCall(ctx, 'connectors.tools', { connector: input.connector }), connectorPolicy(ctx)]);
621
+ return connectorTools(listing, policy.policy);
622
+ })),
623
+ setPolicy: os.connectors.setPolicy.handler(({ input, context: { ctx } }) => run(async () => {
624
+ const change = input.tool === undefined ? (typeof input.enabled === 'boolean' ? { enabled: input.enabled } : null)
625
+ : typeof input.tool_enabled === 'boolean' && input.enabled === undefined ? { tool: input.tool, tool_enabled: input.tool_enabled } : null;
626
+ if (!change)
627
+ throw new HttpError(400, 'Send `enabled`, or `tool` with `tool_enabled`.', { code: 'policy_invalid' });
628
+ const { revision } = await connectorCall(ctx, 'connectors.policy.set', { connector: input.connector, expected_revision: input.revision, ...change });
629
+ return { ok: true, revision };
630
+ })),
631
+ connect: os.connectors.connect.handler(({ input, context: { ctx } }) => run(async () => operationView(ctx, await connectorCall(ctx, 'connectors.connect', { connector: input.connector })))),
632
+ removeAccount: os.connectors.removeAccount.handler(({ input, context: { ctx } }) => run(async () => {
633
+ await connectorCall(ctx, 'connectors.remove', { connection_id: input.connection_id });
634
+ return { ok: true };
635
+ })),
636
+ operation: os.connectors.operation.handler(({ input, context: { ctx } }) => run(async () => operationView(ctx, await connectorCall(ctx, 'connectors.operation', { op_id: input.op_id })))),
637
+ respond: os.connectors.respond.handler(({ input, context: { ctx } }) => run(async () => {
638
+ // Only a move the operation offers now reaches the Agent: the view decides, the sidecar applies.
639
+ const view = connectorOperation(await connectorCall(ctx, 'connectors.operation', { op_id: input.op_id }));
640
+ const { action } = input;
641
+ const offered = action === 'continue' ? view.can_continue : view.targets.some((t) => t.name === input.target && t.actions.includes(action));
642
+ if (!offered)
643
+ throw new HttpError(409, 'That move is not open on this connection.', { code: 'operation_refused' });
644
+ return operationView(ctx, await connectorCall(ctx, 'connectors.respond', { op_id: input.op_id, action, ...(input.target ? { target: input.target } : {}), ...(input.env ? { env: input.env } : {}) }));
645
+ })),
646
+ },
345
647
  plugins: os.plugins.handler(({ context: { ctx } }) => run(async () => {
346
648
  const sidecar = ctx.deps.sidecar();
347
649
  const hooks = ['pre_tool_call', 'post_tool_call', 'pre_llm_call', 'post_llm_call'];
650
+ const editable = await sessionCanManageServer(ctx, await ensureTrustedAuthSession(ctx));
348
651
  if (!sidecar)
349
652
  return { plugins: [], empty: true, supported_hooks: hooks, read_only: true, unavailable: true };
350
653
  try {
@@ -354,22 +657,80 @@ export const toolsRouter = os.router({
354
657
  if (v && typeof v === 'object' && !Array.isArray(v) && typeof v.provider === 'string')
355
658
  selected[k] = str(v.provider).trim().toLowerCase();
356
659
  const result = await sidecar.call('plugins.list', { profile_home: home(ctx), selected_providers: selected });
357
- return { plugins: result.plugins, empty: result.plugins.length === 0, supported_hooks: result.supported_hooks, read_only: true };
660
+ // A settings failure leaves the list itself readable.
661
+ const declared = await pluginSettingsList(ctx).catch((error) => { ctx.deps.log(`[plugins] settings fields failed: ${str(error.message)}`); return []; });
662
+ const settings = new Map(declared.map((entry) => [entry.key, entry.fields]));
663
+ const plugins = result.plugins.map((plugin) => settings.has(plugin.key) ? { ...plugin, settings: settings.get(plugin.key) } : plugin);
664
+ // An advisory failure leaves the list itself readable.
665
+ const advisory = await pluginIsolationAdvisory(ctx).catch((error) => { ctx.deps.log(`[plugins] isolation advisory failed: ${str(error.message)}`); return false; });
666
+ return { plugins, empty: plugins.length === 0, supported_hooks: result.supported_hooks, read_only: !editable, plugin_isolation_advisory: advisory };
358
667
  }
359
668
  catch (error) {
360
669
  ctx.deps.log(`[plugins] visibility payload failed: ${str(error.message)}`);
361
670
  return { plugins: [], empty: true, supported_hooks: hooks, read_only: true, unavailable: true };
362
671
  }
363
672
  })),
673
+ pluginSettings: os.pluginSettings.handler(({ input, context: { ctx } }) => run(() => serializePluginSettingsSave(async () => {
674
+ const sidecar = runningSidecar(ctx);
675
+ const fields = (await pluginSettingsList(ctx)).find((entry) => entry.key === input.key)?.fields;
676
+ if (!fields)
677
+ throw new HttpError(404, `Plugin '${input.key}' declares no settings`);
678
+ let plan;
679
+ try {
680
+ plan = planPluginSettings(fields, input.values ?? {}, input.secrets ?? {});
681
+ }
682
+ catch (error) {
683
+ if (error instanceof PluginSettingsInvalid)
684
+ throw new HttpError(400, error.message);
685
+ throw error;
686
+ }
687
+ const secretFields = fields.filter((field) => field.env && field.env in plan.secrets);
688
+ // Secrets never reach config.yaml: each goes to the profile `.env` under the name the manifest field declares. They are
689
+ // written first and restored if a later write fails, so a refused save changes neither file. The save call always runs:
690
+ // it resolves the plugin again and refuses (before writing) when a secret's `.env` name no longer matches its manifest.
691
+ const credential = (envVar, value) => writeRuntimeCredential({ env: ctx.deps.config.env, sidecar: ctx.deps.sidecar, log: ctx.deps.log }, ctx.deps.isRootProfile(activeProfileName(ctx)), home(ctx), envVar, value);
692
+ const previous = loadEnvFile(join(home(ctx), '.env'));
693
+ const applied = [];
694
+ let result;
695
+ try {
696
+ for (const [envVar, value] of Object.entries(plan.secrets)) {
697
+ await credential(envVar, value);
698
+ applied.push(envVar);
699
+ }
700
+ result = await sidecar.call('plugins.settings_save', { profile_home: home(ctx), key: input.key, values: plan.values, secret_envs: Object.fromEntries(secretFields.map((field) => [field.key, field.env])) });
701
+ }
702
+ catch (error) {
703
+ if (!definitiveRefusal(error)) {
704
+ throw new HttpError(503, 'The Agent sidecar stopped before confirming the save, so it may be partly applied; reload Settings to see what was saved', { condition: 'plugin_settings_unconfirmed' });
705
+ }
706
+ for (const envVar of applied.reverse()) {
707
+ await credential(envVar, previous[envVar] ?? null).catch((restore) => { ctx.deps.log(`[plugins] could not restore ${envVar} after a failed settings save: ${str(restore.message)}`); });
708
+ }
709
+ if (error instanceof SidecarError && error.condition === 'plugin_settings_managed')
710
+ throw new HttpError(403, error.message, { condition: error.condition });
711
+ if (error instanceof SidecarError && error.condition === 'plugin_settings_changed')
712
+ throw new HttpError(409, `${error.message}; reload and try again`, { condition: error.condition });
713
+ if (error instanceof SidecarError && error.condition === 'plugin_not_found')
714
+ throw new HttpError(404, error.message);
715
+ if (error instanceof SidecarError && error.code === -32602)
716
+ throw new HttpError(400, error.message);
717
+ if (error instanceof RuntimeCredentialError)
718
+ throw new HttpError(503, error.message);
719
+ if (error instanceof EnvWriteError)
720
+ throw new HttpError(error.status, `Failed to save a secret: ${error.message}`, { condition: error.condition });
721
+ throw error;
722
+ }
723
+ return { ok: true, key: result.key, written: [...result.written, ...secretFields.map((field) => field.key)], settings: pluginSettingsFieldsView(ctx, result.fields) };
724
+ }))),
364
725
  updates: {
365
- check: os.updates.check.handler(({ context: { ctx } }) => run(() => {
366
- const settings = ctx.deps.settings.load();
726
+ check: os.updates.check.handler(({ context: { ctx } }) => run(async () => {
727
+ const settings = (await ctx.deps.settings.load());
367
728
  if (settings.check_for_updates === false)
368
729
  return updatesCheckView({ disabled: true });
369
730
  return updatesCheckView(ctx.deps.updates.cachedStatus(!pyBool(settings.ignore_agent_updates)));
370
731
  })),
371
732
  checkNow: os.updates.checkNow.handler(({ input, context: { ctx } }) => run(async () => {
372
- const settings = ctx.deps.settings.load();
733
+ const settings = (await ctx.deps.settings.load());
373
734
  const force = pyBool(input.force);
374
735
  if (settings.check_for_updates === false && !force)
375
736
  return updatesCheckView({ disabled: true });
@@ -378,58 +739,83 @@ export const toolsRouter = os.router({
378
739
  ctx.deps.log(`[updates] checking for updates (force=${String(force)}, channel=${channel})`);
379
740
  return updatesCheckView(await ctx.deps.updates.check(force, !pyBool(settings.ignore_agent_updates), channel, input.agent_channel));
380
741
  })),
381
- apply: os.updates.apply.handler(({ input, context: { ctx } }) => run(() => { const target = updateTarget(input.target); return applyWithNotification(ctx, target, () => ctx.deps.updates.apply(target, bodyChannel(input.channel), () => true, { agentChannel: input.agent_channel, confirmedRevision: input.confirmed_agent_revision }), input.confirmed_agent_revision !== undefined, input.tab_id); })),
382
- force: os.updates.force.handler(({ input, context: { ctx } }) => run(() => { const target = updateTarget(input.target); return applyWithNotification(ctx, target, () => ctx.deps.updates.force(target, bodyChannel(input.channel), { agentChannel: input.agent_channel, confirmedRevision: input.confirmed_agent_revision }), input.confirmed_agent_revision !== undefined, input.tab_id); })),
383
- clearLock: os.updates.clearLock.handler(({ input, context: { ctx } }) => run(() => { const target = updateTarget(input.target); return applyWithNotification(ctx, target, () => ctx.deps.updates.clearLock(target, { agentChannel: input.agent_channel, confirmedRevision: input.confirmed_agent_revision }), input.confirmed_agent_revision !== undefined, input.tab_id); })),
384
- summary: os.updates.summary.handler(({ input, context: { ctx } }) => run(() => ctx.deps.updates.summarize(input.updates ?? {}, input.target))),
742
+ apply: os.updates.apply.handler(({ input, context: { ctx } }) => run(() => applyWithNotification(ctx, input, 'apply'))),
743
+ force: os.updates.force.handler(({ input, context: { ctx } }) => run(() => applyWithNotification(ctx, input, 'force'))),
744
+ clearLock: os.updates.clearLock.handler(({ input, context: { ctx } }) => run(() => applyWithNotification(ctx, input, 'clearLock'))),
745
+ summary: os.updates.summary.handler(({ input, context: { ctx } }) => run(async () => ctx.deps.updates.summarize(input.target, (await ctx.deps.settings.load()).check_for_updates === false))),
385
746
  },
386
747
  updateNotifications: {
387
748
  list: os.updateNotifications.list.handler(({ input, context: { ctx } }) => run(async () => {
388
749
  const scope = await updateNotificationScope(ctx, input.tab_id);
389
- const frontendBuild = ctx.deps.updateNotifications.syncTabBuild(scope, input.loaded_build ?? null, ctx.deps.spa.buildId());
390
- return { ...ctx.deps.updateNotifications.list(scope), frontend_build: frontendBuild };
750
+ const frontendBuild = (await ctx.deps.updateNotifications.syncTabBuild(scope, input.loaded_build ?? null, ctx.deps.spa.buildId()));
751
+ return { ...(await ctx.deps.updateNotifications.list(scope)), frontend_build: frontendBuild };
391
752
  })),
392
753
  read: os.updateNotifications.read.handler(({ input, context: { ctx } }) => run(async () => {
393
- const notification = ctx.deps.updateNotifications.read(await updateNotificationScope(ctx, input.tab_id), input.id);
754
+ const notification = (await ctx.deps.updateNotifications.read(await updateNotificationScope(ctx, input.tab_id), input.id));
394
755
  if (!notification)
395
756
  throw new HttpError(404, 'Update notification not found');
396
757
  return notification;
397
758
  })),
398
759
  dismiss: os.updateNotifications.dismiss.handler(({ input, context: { ctx } }) => run(async () => {
399
- const result = ctx.deps.updateNotifications.dismiss(await updateNotificationScope(ctx, input.tab_id), input.id);
760
+ const result = (await ctx.deps.updateNotifications.dismiss(await updateNotificationScope(ctx, input.tab_id), input.id));
400
761
  if (result === 'acknowledgement_required')
401
762
  throw new HttpError(409, 'This notification requires acknowledgement before it can be dismissed');
402
763
  return { ok: true };
403
764
  })),
404
765
  clear: os.updateNotifications.clear.handler(({ input, context: { ctx } }) => run(async () => {
405
766
  const scope = await updateNotificationScope(ctx, input.tab_id);
406
- const frontendBuild = ctx.deps.updateNotifications.syncTabBuild(scope, input.loaded_build ?? null, ctx.deps.spa.buildId());
407
- return { ...ctx.deps.updateNotifications.clear(scope), frontend_build: frontendBuild };
767
+ const frontendBuild = (await ctx.deps.updateNotifications.syncTabBuild(scope, input.loaded_build ?? null, ctx.deps.spa.buildId()));
768
+ return { ...(await ctx.deps.updateNotifications.clear(scope)), frontend_build: frontendBuild };
408
769
  })),
409
770
  cancel: os.updateNotifications.cancel.handler(({ input, context: { ctx } }) => run(async () => {
410
- const notification = ctx.deps.updateNotifications.cancelUpdate(await updateNotificationScope(ctx), input.id);
771
+ const notification = (await ctx.deps.updateNotifications.cancelUpdate(await updateNotificationScope(ctx), input.id));
411
772
  if (!notification)
412
773
  throw new HttpError(404, 'Awaiting update notification not found');
413
774
  return notification;
414
775
  })),
415
776
  action: os.updateNotifications.action.handler(({ input, context: { ctx } }) => run(async () => {
416
- const notification = ctx.deps.updateNotifications.action(await updateNotificationScope(ctx, input.tab_id), input.id, input.action_id);
777
+ const notification = (await ctx.deps.updateNotifications.action(await updateNotificationScope(ctx, input.tab_id), input.id, input.action_id));
417
778
  if (!notification)
418
779
  throw new HttpError(404, 'Notification action not found');
780
+ if (input.action_id === GATEWAY_RESTART_NOW)
781
+ ctx.deps.gatewayRestarts.restartNow(notification.id);
419
782
  return notification;
420
783
  })),
421
784
  },
422
785
  transcribeCapability: os.transcribeCapability.handler(({ context: { ctx } }) => run(async () => {
786
+ const unavailable = { ok: true, available: false, provider: 'none', streaming: false, warm_supported: false };
423
787
  const sidecar = ctx.deps.sidecar();
424
788
  if (!sidecar)
425
- return { ok: true, available: false, provider: 'none' };
789
+ return unavailable;
426
790
  try {
427
791
  const cap = await sidecar.call('stt.capability', { profile_home: home(ctx) });
428
- return { ok: true, available: cap.available, provider: cap.provider || 'none' };
792
+ return { ok: true, available: cap.available, provider: cap.provider || 'none', streaming: cap.available && cap.streaming, warm_supported: cap.available && cap.warm_supported };
429
793
  }
430
794
  catch {
431
- return { ok: true, available: false, provider: 'none' };
795
+ return unavailable;
796
+ }
797
+ })),
798
+ speech: {
799
+ prepare: os.speech.prepare.handler(({ input, context: { ctx } }) => run(async () => ({ spoken_text: await spokenText(ctx, input.text.trim()) }))),
800
+ },
801
+ transcribeWarm: os.transcribeWarm.handler(({ context: { ctx } }) => run(() => {
802
+ const sidecar = ctx.deps.sidecar();
803
+ const profileHome = home(ctx);
804
+ // One load per profile at a time: a record start during a running load joins it rather than holding another sidecar slot.
805
+ if (sidecar && !warmingHomes.has(profileHome)) {
806
+ warmingHomes.add(profileHome);
807
+ // The load outlives this request's write lease: it is the profile's activity until it settles, so the profile
808
+ // cannot be deleted under it. Taken while the request's lease still refuses a deletion in progress.
809
+ const release = ctx.deps.profileActivity(activeProfileName(ctx));
810
+ // A first use downloads the model, and the Agent's load cannot be cancelled, so the call has no deadline: the entry
811
+ // lives exactly as long as the sidecar worker (a sidecar exit rejects it). Nobody waits on it.
812
+ void sidecar.call('stt.warm', { profile_home: profileHome }, { timeoutMs: 0 })
813
+ .then((result) => { if (result.action === 'error')
814
+ ctx.deps.log(`[stt] warm-up failed (${result.provider}): ${str(result.error)}`); })
815
+ .catch((error) => { ctx.deps.log(`[stt] warm-up failed: ${str(error.message)}`); })
816
+ .finally(() => { warmingHomes.delete(profileHome); release(); });
432
817
  }
818
+ return { ok: true };
433
819
  })),
434
820
  clientEvents: os.clientEvents.handler(({ input, context: { ctx } }) => run(() => {
435
821
  if (ctx.deps.clientEventLimiter.limited(ctx.peer || 'unknown'))