@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
@@ -0,0 +1,89 @@
1
+ """``checkpoints.*``: a workspace's filesystem checkpoints through the Agent's own ``CheckpointManager`` (TAL-913).
2
+
3
+ The Agent keeps every project's checkpoints as refs in one shared git store under the profile home; listing, diffing and
4
+ restoring go through its manager so the store layout, the nested-repository refusal and the pre-rollback snapshot stay
5
+ the Agent's.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from ..errors import InvalidParams, RpcError
11
+ from ..home import profile_home_param, scoped_home
12
+ from ..rpc import CallContext
13
+
14
+
15
+ def checkpoint_kwargs() -> dict:
16
+ """The profile's ``checkpoints`` config as ``AIAgent`` kwargs, resolved as the Agent's gateway does
17
+ (``gateway/run.py::_checkpoint_agent_kwargs``): the legacy ``checkpoints: true`` form, else the section over
18
+ ``DEFAULT_CONFIG``. Only a real ``true`` turns them on, so a quoted ``"false"`` stays off."""
19
+ from hermes_cli.config import DEFAULT_CONFIG, load_config
20
+
21
+ section = (load_config() or {}).get("checkpoints", {})
22
+ if isinstance(section, bool):
23
+ section = {"enabled": section}
24
+ elif not isinstance(section, dict):
25
+ section = {}
26
+ defaults = DEFAULT_CONFIG["checkpoints"]
27
+ return {
28
+ "checkpoints_enabled": section.get("enabled", defaults["enabled"]) is True,
29
+ "checkpoint_max_snapshots": section.get("max_snapshots", defaults["max_snapshots"]),
30
+ "checkpoint_max_total_size_mb": section.get("max_total_size_mb", defaults["max_total_size_mb"]),
31
+ "checkpoint_max_file_size_mb": section.get("max_file_size_mb", defaults["max_file_size_mb"]),
32
+ }
33
+
34
+
35
+ def _manager():
36
+ from tools.checkpoint_manager import CheckpointManager
37
+
38
+ kwargs = checkpoint_kwargs()
39
+ return CheckpointManager(enabled=kwargs["checkpoints_enabled"], max_snapshots=kwargs["checkpoint_max_snapshots"],
40
+ max_total_size_mb=kwargs["checkpoint_max_total_size_mb"], max_file_size_mb=kwargs["checkpoint_max_file_size_mb"])
41
+
42
+
43
+ def _param(params: dict, name: str) -> str:
44
+ value = params.get(name)
45
+ if not isinstance(value, str) or not value.strip():
46
+ raise InvalidParams(f"{name} is required")
47
+ return value
48
+
49
+
50
+ def _agent_result(result: dict) -> dict:
51
+ """The Agent answers a refusal (unknown checkpoint, uncaptured nested repository, failed checkout) as ``success: False``."""
52
+ if not result.get("success"):
53
+ raise RpcError(str(result.get("error") or "checkpoint operation failed"), condition="checkpoint_failed")
54
+ return result
55
+
56
+
57
+ def _owned(manager, workspace: str, checkpoint: str) -> str:
58
+ """The full hash of ``checkpoint`` among ``workspace``'s own checkpoints. The store is shared by every project and the
59
+ Agent's diff and restore accept any commit in it, so another workspace's hash would check that tree out here."""
60
+ matches = [c["hash"] for c in manager.list_checkpoints(workspace) if c.get("hash") and str(c["hash"]).startswith(checkpoint.lower())]
61
+ if len(matches) != 1:
62
+ raise RpcError(f"Checkpoint '{checkpoint}' is not a checkpoint of this workspace", condition="checkpoint_failed")
63
+ return matches[0]
64
+
65
+
66
+ def register(registry) -> None:
67
+ @registry.method("checkpoints.list")
68
+ def list_(ctx: CallContext, params: dict) -> dict:
69
+ workspace = _param(params, "workspace")
70
+ with scoped_home(profile_home_param(params)):
71
+ from tools.checkpoint_manager import _store_path
72
+
73
+ return {"checkpoints": _manager().list_checkpoints(workspace), "store": str(_store_path())}
74
+
75
+ @registry.method("checkpoints.diff")
76
+ def diff(ctx: CallContext, params: dict) -> dict:
77
+ workspace, checkpoint = _param(params, "workspace"), _param(params, "checkpoint")
78
+ with scoped_home(profile_home_param(params)):
79
+ manager = _manager()
80
+ result = _agent_result(manager.diff(workspace, _owned(manager, workspace, checkpoint)))
81
+ return {"stat": str(result.get("stat") or ""), "diff": str(result.get("diff") or "")}
82
+
83
+ @registry.method("checkpoints.restore")
84
+ def restore(ctx: CallContext, params: dict) -> dict:
85
+ workspace, checkpoint = _param(params, "workspace"), _param(params, "checkpoint")
86
+ with scoped_home(profile_home_param(params)):
87
+ manager = _manager()
88
+ result = _agent_result(manager.restore(workspace, _owned(manager, workspace, checkpoint)))
89
+ return {"restored_to": str(result.get("restored_to") or ""), "reason": str(result.get("reason") or "")}
@@ -2,7 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import contextlib
6
+ import importlib.util
5
7
  import logging
8
+ import os
9
+ import pathlib
6
10
  import threading
7
11
  from typing import Any
8
12
 
@@ -40,6 +44,103 @@ def parse_slash_command(command: str) -> tuple[str, str]:
40
44
  return _ALIASES.get(base, base), parts[1] if len(parts) > 1 else ""
41
45
 
42
46
 
47
+ def _expand_plan(args: str, **_turn) -> str:
48
+ from agent.plan_prompt import build_plan_prompt
49
+
50
+ return build_plan_prompt(args)
51
+
52
+
53
+ def _expand_learn(args: str, **_turn) -> str:
54
+ from agent.learn_prompt import build_learn_prompt
55
+
56
+ return build_learn_prompt(args)
57
+
58
+
59
+ def _expand_init(args: str, *, workspace: str, **_turn) -> str:
60
+ from hermes_cli.init_command import build_init_prompt_for_cwd
61
+
62
+ # The Agent falls back to the process cwd for a missing directory; the sidecar's is never the user's project.
63
+ if not workspace or not os.path.isdir(workspace):
64
+ raise InvalidParams("workspace is not a directory")
65
+ return build_init_prompt_for_cwd(cwd=workspace, extra=args)
66
+
67
+
68
+ def _expand_initiate_setup(_args: str, *, enabled_toolsets, **_turn) -> str:
69
+ from agent.initiate_setup_prompt import build_initiate_setup_prompt
70
+ from hermes_cli.setup_profile import primary_profile
71
+ from hermes_constants import get_hermes_home
72
+ from model_tools import get_tool_definitions
73
+
74
+ from .chat import _turn_toolsets
75
+ from .toolsets import disabled_toolsets
76
+
77
+ # The gateway's ``_hm_cmd_initiate_setup``: the tools this turn's agent gets, by name. MCP servers connect first, as
78
+ # chat start connects them before its agent snapshots tools; a failure leaves them out, as it does for the turn.
79
+ try:
80
+ discover_profile_mcp_tools()
81
+ except Exception: # noqa: BLE001
82
+ log.warning("MCP discovery failed", exc_info=True)
83
+ tools = get_tool_definitions(enabled_toolsets=_turn_toolsets(enabled_toolsets), disabled_toolsets=disabled_toolsets(),
84
+ quiet_mode=True, skip_tool_search_assembly=True)
85
+ return build_initiate_setup_prompt("webui", [tool["function"]["name"] for tool in tools], primary_profile(get_hermes_home()))
86
+
87
+
88
+ # The Agent built-ins every surface answers by rewriting the turn to a builder's prompt (the gateway's
89
+ # ``_hm_rewrite_turn_to_prompt``), with the Agent module each expander needs. ``None``: no expander yet, so no client
90
+ # offers it (``/blueprint`` seeds a turn only after its own slot parsing and job wiring).
91
+ _PROMPT_BUILTINS: dict[str, tuple[Any, str] | None] = {
92
+ "plan": (_expand_plan, "agent.plan_prompt"),
93
+ "learn": (_expand_learn, "agent.learn_prompt"),
94
+ "init": (_expand_init, "hermes_cli.init_command"),
95
+ "initiate-setup": (_expand_initiate_setup, "agent.initiate_setup_prompt"),
96
+ "blueprint": None,
97
+ }
98
+
99
+
100
+ def _prompt_expander(name: str):
101
+ """The expander for a prompt built-in this Agent ships the builder for; None when it has none."""
102
+ entry = _PROMPT_BUILTINS.get(name)
103
+ if entry is None:
104
+ return None
105
+ try:
106
+ found = importlib.util.find_spec(entry[1]) is not None
107
+ except (ImportError, ValueError):
108
+ found = False
109
+ return entry[0] if found else None
110
+
111
+
112
+ def _prompt_mode(name: str) -> str | None:
113
+ if name not in _PROMPT_BUILTINS:
114
+ return None
115
+ return "expand" if _prompt_expander(name) else "unsupported"
116
+
117
+
118
+ def expand_prompt(command: str, workspace: str, enabled_toolsets) -> dict[str, Any]:
119
+ """``/<prompt built-in> [args]`` as the turn text the Agent's own builder writes. ``name`` is the built-in's (None
120
+ when the command is not one) and ``message`` None when this Agent has no builder for it."""
121
+ try:
122
+ name, args = parse_slash_command(command)
123
+ from hermes_cli.commands import resolve_command
124
+ except (InvalidParams, ImportError):
125
+ return {"name": None, "message": None}
126
+ cmd = resolve_command(name)
127
+ if cmd is None or cmd.name not in _PROMPT_BUILTINS:
128
+ return {"name": None, "message": None}
129
+ expander = _prompt_expander(cmd.name)
130
+ if expander is None:
131
+ return {"name": cmd.name, "message": None}
132
+ try:
133
+ message = str(expander(args, workspace=workspace, enabled_toolsets=enabled_toolsets) or "").strip()
134
+ except InvalidParams:
135
+ raise
136
+ except Exception as exc: # noqa: BLE001
137
+ log.warning("Failed to expand /%s", cmd.name, exc_info=True)
138
+ raise RpcError(f"Couldn't start /{cmd.name}", condition="prompt_expand_failed") from exc
139
+ if not message:
140
+ raise RpcError(f"Couldn't start /{cmd.name}", condition="prompt_expand_failed")
141
+ return {"name": cmd.name, "message": message}
142
+
143
+
43
144
  def list_commands() -> list[dict[str, Any]]:
44
145
  try:
45
146
  from hermes_cli.commands import COMMAND_REGISTRY
@@ -53,13 +154,13 @@ def list_commands() -> list[dict[str, Any]]:
53
154
  out.append({
54
155
  "name": cmd.name, "description": cmd.description, "category": cmd.category, "aliases": list(cmd.aliases),
55
156
  "args_hint": cmd.args_hint, "subcommands": list(cmd.subcommands), "cli_only": bool(cmd.cli_only), "gateway_only": bool(cmd.gateway_only),
56
- "exec": cmd.name in _ALLOWED,
157
+ "exec": cmd.name in _ALLOWED, "prompt": _prompt_mode(cmd.name),
57
158
  })
58
159
  listed = {c["name"] for c in out}
59
160
  for name, (description, category, args_hint) in _RUNTIME_COMMANDS.items():
60
161
  if name not in listed:
61
162
  out.append({"name": name, "description": description, "category": category, "aliases": [a for a, n in _ALIASES.items() if n == name],
62
- "args_hint": args_hint, "subcommands": [], "cli_only": False, "gateway_only": False, "exec": True})
163
+ "args_hint": args_hint, "subcommands": [], "cli_only": False, "gateway_only": False, "exec": True, "prompt": None})
63
164
  try:
64
165
  from hermes_cli.plugins import get_plugin_commands
65
166
 
@@ -68,7 +169,7 @@ def list_commands() -> list[dict[str, Any]]:
68
169
  if name in existing or name in _NEVER_EXPOSE:
69
170
  continue
70
171
  out.append({"name": name, "description": str(info.get("description", "Plugin command")), "category": "Plugin", "aliases": [],
71
- "args_hint": str(info.get("args_hint", "")), "subcommands": [], "cli_only": False, "gateway_only": False, "exec": True})
172
+ "args_hint": str(info.get("args_hint", "")), "subcommands": [], "cli_only": False, "gateway_only": False, "exec": True, "prompt": None})
72
173
  except Exception: # noqa: BLE001 - plugin registry is optional
73
174
  log.debug("plugin command listing failed", exc_info=True)
74
175
  return out
@@ -95,15 +196,27 @@ def retire_unscoped_launch_mcp_servers() -> set[str]:
95
196
  return names
96
197
 
97
198
 
199
+ def discover_profile_mcp_tools() -> list[str]:
200
+ """Connect this profile's configured servers, retiring pre-multiplex launch connections first."""
201
+ from tools.mcp_tool_discovery import discover_mcp_tools
202
+
203
+ retire_unscoped_launch_mcp_servers()
204
+ return discover_mcp_tools()
205
+
206
+
98
207
  def _reload_mcp() -> str:
99
208
  with _RELOAD_MCP_LOCK:
100
209
  try:
101
210
  from agent.secret_scope import is_multiplex_active
211
+ from hermes_constants import get_hermes_home
102
212
  from tools.mcp_tool import _lock, _mcp_tool_server_names, _server_visible_in_scope, _servers
213
+ from tools.mcp_tool_agent import reprobe_tool_availability
103
214
  from tools.mcp_tool_discovery import discover_mcp_tools
104
215
  from tools.mcp_tool_lifecycle import shutdown_mcp_servers
105
216
  from tools.mcp_tool_scope import _key_name
106
217
  from tools.registry import registry
218
+
219
+ from .chat import refresh_profile_agents_mcp_tools
107
220
  except Exception as exc: # noqa: BLE001
108
221
  raise RpcError("MCP runtime unavailable", condition="mcp_unavailable") from exc
109
222
  # Like the gateway's ``_execute_mcp_reload``: under multiplexing only this profile's connections go down and
@@ -117,7 +230,11 @@ def _reload_mcp() -> str:
117
230
  try:
118
231
  old = server_names() | retire_unscoped_launch_mcp_servers()
119
232
  shutdown_mcp_servers(scope=scope)
233
+ # As the gateway does: drop the cached tool availability verdicts, then hand the profile's open chats the
234
+ # rediscovered tools.
235
+ reprobe_tool_availability()
120
236
  tools = discover_mcp_tools() or []
237
+ refresh_profile_agents_mcp_tools(get_hermes_home())
121
238
  connected = server_names()
122
239
  if scope is not None:
123
240
  with _lock:
@@ -244,6 +361,77 @@ def execute_plugin_command(command: str) -> str | None:
244
361
  return f"Plugin command error: {type(exc).__name__}"
245
362
 
246
363
 
364
+ def _profile_bundles(skill_bundles) -> dict[str, dict[str, Any]]:
365
+ """The scoped profile's bundles keyed ``/slug``, read from disk the way ``scan_bundles`` reads them.
366
+
367
+ ``scan_bundles``/``get_skill_bundles`` publish one profile's bundles in a process-wide cache keyed only by mtime,
368
+ which cron jobs in other profiles read concurrently, so the sidecar never fills it."""
369
+ out: dict[str, dict[str, Any]] = {}
370
+ for path in skill_bundles._iter_bundle_files():
371
+ info = skill_bundles._load_bundle_file(path)
372
+ if info:
373
+ out.setdefault(f"/{info['slug']}", info)
374
+ return out
375
+
376
+
377
+ def list_command_bundles() -> list[dict[str, Any]]:
378
+ """The profile's skill bundles as slash-command rows; [] when the bundle runtime is missing or fails."""
379
+ try:
380
+ import agent.skill_bundles as skill_bundles
381
+ except ImportError:
382
+ return []
383
+ try:
384
+ bundles = sorted(_profile_bundles(skill_bundles).values(), key=lambda b: b["slug"])
385
+ except Exception: # noqa: BLE001
386
+ log.warning("Failed to list skill bundles", exc_info=True)
387
+ return []
388
+ return [{"name": str(b["slug"]).strip().lower(), "description": str(b.get("description") or "").strip() or "Skill bundle",
389
+ "skill_count": len(b.get("skills") or []), "source": "bundle"} for b in bundles if str(b.get("slug") or "").strip()]
390
+
391
+
392
+ def _bundle_invocation(info: dict[str, Any], instruction: str) -> tuple[str, list[str], list[str]] | None:
393
+ """``build_bundle_invocation_message`` for a bundle read by ``_profile_bundles`` (that one looks it up in the shared cache),
394
+ skipping the skills disabled for Web chat (``skills.platform_disabled.webui``)."""
395
+ from agent.skill_commands import _disabled_skill_names, _load_skill_blocks, _load_skill_payload, _scaffold_header
396
+
397
+ name = info["name"]
398
+ loaded, missing, disabled, blocks = _load_skill_blocks(
399
+ [(skill or "").strip() for skill in info["skills"]], _load_skill_payload, lambda _skill: f'[Loaded as part of the "{name}" skill bundle.]',
400
+ None, disabled_names=_disabled_skill_names("webui"))
401
+ if not blocks:
402
+ return None
403
+ header = _scaffold_header(f'"{name}" skill bundle', loaded, lead_lines=[f"Bundle: {name}"], missing=missing, disabled=disabled,
404
+ extra_instruction=info.get("instruction") or "", user_instruction=instruction)
405
+ return "\n\n".join([header, *blocks]), loaded, missing
406
+
407
+
408
+ def resolve_bundle_command(command: str) -> dict[str, Any]:
409
+ """Expand ``/<bundle> [instruction]`` into the user message that loads the bundle's skills."""
410
+ name, instruction = parse_slash_command(command)
411
+ try:
412
+ import agent.skill_bundles as skill_bundles
413
+ from agent.skill_commands import resolve_slash_key
414
+ except ImportError as exc:
415
+ raise RpcError("Skill bundle runtime unavailable", condition="bundle_unavailable") from exc
416
+ try:
417
+ bundles = _profile_bundles(skill_bundles)
418
+ key = resolve_slash_key(name, bundles)
419
+ result = _bundle_invocation(bundles[key], instruction) if key else None
420
+ except ValueError as exc:
421
+ raise InvalidParams(str(exc)) from exc
422
+ except Exception as exc: # noqa: BLE001
423
+ log.warning("Failed to resolve skill bundle command", exc_info=True)
424
+ raise RpcError("Skill bundle command unavailable", condition="bundle_unavailable") from exc
425
+ if key is None:
426
+ raise RpcError("Bundle command not found", condition="bundle_not_found")
427
+ message, loaded, missing = result or ("", [], [])
428
+ message = str(message or "").strip()
429
+ if not message:
430
+ raise RpcError("Bundle command returned no invocation text", condition="bundle_unavailable")
431
+ return {"name": key.lstrip("/"), "source": "bundle", "message": message, "loaded_skills": [str(s) for s in loaded or []],
432
+ "missing_skills": [str(s) for s in missing or []]}
433
+
434
+
247
435
  def resolve_moa_config(preset: str | None) -> dict:
248
436
  try:
249
437
  from hermes_cli.moa_config import moa_usage, normalize_moa_config
@@ -333,6 +521,74 @@ def plugin_visibility(selected_providers: dict) -> dict:
333
521
  return {"plugins": rows, "supported_hooks": list(PLUGIN_VISIBILITY_HOOKS)}
334
522
 
335
523
 
524
+ def isolation_survey(base_home: pathlib.Path) -> list[dict]:
525
+ """Each profile's ``plugins.isolation`` and installed third-party plugin count, read from disk without loading a plugin."""
526
+ from hermes_cli.plugin_isolation import isolation_mode
527
+ from hermes_cli.plugins_cmd import _scan_level
528
+
529
+ from .profiles import _yaml_load, named_profile_homes
530
+
531
+ rows = []
532
+ for name, home in [("default", base_home), *named_profile_homes(base_home)]:
533
+ seen: dict = {}
534
+ _scan_level(home / "plugins", "user", set(), "", 0, seen)
535
+ rows.append({"name": name, "isolation": isolation_mode(_yaml_load(home / "config.yaml") or {}), "third_party_plugins": len(seen)})
536
+ return rows
537
+
538
+
539
+ def _plugin_settings_modules():
540
+ """The Agent's manifest ``config_schema`` reader/writer (v0.21.6+) and its plugin resolver; ``None`` on older Agents."""
541
+ try:
542
+ import hermes_cli.plugins_cmd as plugins_cmd
543
+ import hermes_cli.plugins_settings as plugins_settings
544
+ except ImportError:
545
+ return None
546
+ return plugins_cmd, plugins_settings
547
+
548
+
549
+ def plugin_settings() -> dict:
550
+ """Every discovered plugin whose manifest declares settings, with its fields (secrets as ``env`` + ``has_value`` only)."""
551
+ modules = _plugin_settings_modules()
552
+ if modules is None:
553
+ return {"supported": False, "plugins": []}
554
+ plugins_cmd, plugins_settings = modules
555
+ rows = []
556
+ for _name, _version, _desc, _source, plugin_dir, key in plugins_cmd._discover_all_plugins():
557
+ fields = plugins_settings.plugin_settings_fields(key, pathlib.Path(str(plugin_dir)) if plugin_dir else None)
558
+ if fields:
559
+ rows.append({"key": key, "fields": fields})
560
+ return {"supported": True, "plugins": rows}
561
+
562
+
563
+ def save_plugin_settings(key: str, values: Any, secret_envs: Any = None) -> dict:
564
+ """Write manifest-declared, non-secret settings through the Agent's locked writer; returns the refreshed fields.
565
+
566
+ ``secret_envs`` (secret key -> ``.env`` name the caller wrote) must still match the manifest before anything is written.
567
+ """
568
+ if not key or not isinstance(values, dict) or not isinstance(secret_envs or {}, dict):
569
+ raise InvalidParams("plugins.settings_save requires a 'key' and a 'values' mapping")
570
+ modules = _plugin_settings_modules()
571
+ if modules is None:
572
+ raise RpcError("This Hermes Agent cannot edit plugin settings", condition="plugin_settings_unsupported")
573
+ plugins_cmd, plugins_settings = modules
574
+ found = plugins_cmd._find_plugin_entry(key)
575
+ if found is None:
576
+ raise RpcError(f"plugin {key!r} not found", condition="plugin_not_found")
577
+ plugin_dir = pathlib.Path(str(found[4])) if found[4] else None
578
+ current = {field["key"]: field for field in plugins_settings.plugin_settings_fields(found[5], plugin_dir)}
579
+ for secret, env in (secret_envs or {}).items():
580
+ field = current.get(str(secret))
581
+ if field is None or field.get("type") != "secret" or field.get("env") != env:
582
+ raise RpcError(f"{secret!r} is no longer stored in {env}", condition="plugin_settings_changed")
583
+ try:
584
+ written = plugins_settings.save_plugin_settings(found[5], plugin_dir, values)
585
+ except PermissionError as exc:
586
+ raise RpcError(str(exc), condition="plugin_settings_managed") from exc
587
+ except ValueError as exc:
588
+ raise InvalidParams(str(exc)) from exc
589
+ return {"key": found[5], "written": written, "fields": plugins_settings.plugin_settings_fields(found[5], plugin_dir)}
590
+
591
+
336
592
  def register(registry) -> None:
337
593
  @registry.method("commands.registry")
338
594
  def registry_(ctx: CallContext, params: dict) -> dict:
@@ -346,12 +602,32 @@ def register(registry) -> None:
346
602
  output = execute_agent_command(command)
347
603
  source = "agent"
348
604
  if output is None:
349
- output = execute_plugin_command(command)
605
+ from .chat import _turn_identity
606
+
607
+ # TAL-904: a chat's plugin command runs under that chat's session and workspace, as its turns do.
608
+ session_id = str(params.get("session_id") or "").strip()
609
+ with _turn_identity(session_id, str(params.get("workspace") or "")) if session_id else contextlib.nullcontext():
610
+ output = execute_plugin_command(command)
350
611
  source = "plugin"
351
612
  if output is None:
352
613
  raise RpcError(f"unknown command {parse_slash_command(command)[0]!r}", condition="command_not_found")
353
614
  return {"output": output, "source": source}
354
615
 
616
+ @registry.method("commands.expand_prompt")
617
+ def expand_prompt_(ctx: CallContext, params: dict) -> dict:
618
+ with scoped_home(profile_home_param(params)):
619
+ return expand_prompt(str(params.get("command") or ""), str(params.get("workspace") or ""), params.get("enabled_toolsets"))
620
+
621
+ @registry.method("commands.bundles")
622
+ def bundles(ctx: CallContext, params: dict) -> dict:
623
+ with scoped_home(profile_home_param(params)):
624
+ return {"bundles": list_command_bundles()}
625
+
626
+ @registry.method("commands.bundle_resolve")
627
+ def bundle_resolve(ctx: CallContext, params: dict) -> dict:
628
+ with scoped_home(profile_home_param(params)):
629
+ return resolve_bundle_command(str(params.get("command") or ""))
630
+
355
631
  @registry.method("commands.moa_preset")
356
632
  def moa(ctx: CallContext, params: dict) -> dict:
357
633
  with scoped_home(profile_home_param(params)):
@@ -366,3 +642,17 @@ def register(registry) -> None:
366
642
  def list_(ctx: CallContext, params: dict) -> dict:
367
643
  with scoped_home(profile_home_param(params)):
368
644
  return plugin_visibility(params.get("selected_providers") if isinstance(params.get("selected_providers"), dict) else {})
645
+
646
+ @registry.method("plugins.isolation")
647
+ def isolation(ctx: CallContext, params: dict) -> dict:
648
+ return {"profiles": isolation_survey(profile_home_param(params, "base_home"))}
649
+
650
+ @registry.method("plugins.settings")
651
+ def settings(ctx: CallContext, params: dict) -> dict:
652
+ with scoped_home(profile_home_param(params)):
653
+ return plugin_settings()
654
+
655
+ @registry.method("plugins.settings_save")
656
+ def settings_save(ctx: CallContext, params: dict) -> dict:
657
+ with scoped_home(profile_home_param(params)):
658
+ return save_plugin_settings(str(params.get("key") or "").strip(), params.get("values"), params.get("secret_envs"))
@@ -1,16 +1,20 @@
1
- """``config.*`` and ``models.reasoning_efforts``: a profile's ``config.yaml`` and the
2
- Agent's per-model reasoning capability.
1
+ """``config.*``, ``models.reasoning_efforts`` and ``personalities.list``: a profile's ``config.yaml`` and ``.env``,
2
+ the Agent's per-model reasoning capability, and the personalities the Agent offers for a config.
3
3
 
4
4
  The TypeScript server owns every config.yaml *policy* (which keys mean what);
5
5
  the sidecar only parses and serialises YAML with the Agent's own parser so the
6
6
  Node side needs no YAML dependency. Writes are atomic and keep the file's mode.
7
+ ``.env`` edits use the Agent's own line helpers under the ``<.env>.lock`` the
8
+ gateway, CLI and plugins also take, so concurrent writers never drop a key.
7
9
  """
8
10
 
9
11
  from __future__ import annotations
10
12
 
13
+ import contextlib
11
14
  import importlib
12
15
  import logging
13
16
  import os
17
+ import re
14
18
  import tempfile
15
19
  from pathlib import Path
16
20
 
@@ -74,7 +78,11 @@ def write_config(path: Path, config: dict) -> dict:
74
78
  raise InvalidParams("config must be an object")
75
79
  target = _write_target(path)
76
80
  target.parent.mkdir(parents=True, exist_ok=True)
77
- text = yaml_parser().safe_dump(config, sort_keys=False, allow_unicode=True)
81
+ try:
82
+ replace = getattr(importlib.import_module("hermes_cli.config"), "atomic_config_replace", None)
83
+ except Exception: # noqa: BLE001
84
+ # Operator config must remain writable even when Agent imports are broken.
85
+ replace = None
78
86
  mode = None
79
87
  try:
80
88
  mode = target.stat().st_mode & 0o777
@@ -83,9 +91,20 @@ def write_config(path: Path, config: dict) -> dict:
83
91
  fd, tmp = tempfile.mkstemp(dir=str(target.parent), prefix=".config_", suffix=".tmp")
84
92
  try:
85
93
  with os.fdopen(fd, "w", encoding="utf-8") as handle:
86
- handle.write(text)
94
+ if replace is not None:
95
+ # Stage the original document so the Agent retains comments; check the real link before committing.
96
+ try:
97
+ handle.write(target.read_text(encoding="utf-8"))
98
+ except FileNotFoundError:
99
+ pass
100
+ else:
101
+ parser = yaml_parser()
102
+ handle.write(parser.safe_dump(config, sort_keys=False, allow_unicode=True,
103
+ width=getattr(parser, "ROUNDTRIP_YAML_WIDTH", 2**31 - 1)))
87
104
  handle.flush()
88
105
  os.fsync(handle.fileno())
106
+ if replace is not None:
107
+ replace(Path(tmp), config)
89
108
  if mode is not None:
90
109
  os.chmod(tmp, mode)
91
110
  # A link retargeted while the temp file was being written would otherwise send the write to the wrong file.
@@ -101,6 +120,88 @@ def write_config(path: Path, config: dict) -> dict:
101
120
  return {"ok": True, "path": str(path)}
102
121
 
103
122
 
123
+ _ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
124
+
125
+
126
+ def _env_refusal(agent_config, key: str, action: str) -> str | None:
127
+ """The Agent's ``.env`` write-lock refusal (package-managed install or administrator-managed key)."""
128
+ refusal = getattr(agent_config, "env_write_refusal", None)
129
+ if refusal is not None:
130
+ return refusal(key, action)
131
+ # Agent 0.21.5 only reports the refusal on stderr.
132
+ return f"Cannot {action} {key}: this Hermes installation manages it" if agent_config._env_write_blocked(key, action) else None
133
+
134
+
135
+ def _env_lock(agent_config, path: Path):
136
+ """The Agent's ``<.env>.lock`` (Agent 0.21.6+); older Agents have none to share."""
137
+ lock = getattr(agent_config, "_env_write_lock", None)
138
+ return lock(path) if lock is not None else contextlib.nullcontext()
139
+
140
+
141
+ def env_write(home: Path, updates) -> dict:
142
+ """Upsert ``updates`` into ``home``'s ``.env`` (``None`` removes a key) with the Agent's own line helpers, under
143
+ the lock its ``save_env_value`` / ``remove_env_value`` take, in one atomic replace.
144
+
145
+ Unlike those two, nothing is published into ``os.environ``: other threads (a gateway spawn snapshotting the
146
+ environment) must never see another profile's key, and ``runtime.env`` alone owns this process's environment.
147
+ Values are trimmed and an empty one is skipped; a value the Agent would strip or quote is refused. Every key is
148
+ checked against the Agent's refusals before the write, and the file ends 0600.
149
+ """
150
+ if not isinstance(updates, dict) or not all(isinstance(k, str) and _ENV_NAME.match(k) and (v is None or isinstance(v, str)) for k, v in updates.items()):
151
+ raise InvalidParams("updates must map variable names to strings or null")
152
+ changes: dict[str, str | None] = {}
153
+ for key, value in updates.items():
154
+ if value is not None:
155
+ value = value.strip()
156
+ if not value:
157
+ continue
158
+ if "\n" in value or "\r" in value:
159
+ raise InvalidParams("API key must not contain newline characters.")
160
+ # The Agent would strip non-ASCII and quote the rest; refused instead, so the running process and the
161
+ # server's reader see exactly the bytes the file holds.
162
+ if any(not "!" <= c <= "~" or c in "#\"'" for c in value):
163
+ raise InvalidParams("API key must not contain spaces, quotes, '#' or non-ASCII characters.")
164
+ changes[key] = value
165
+ from hermes_cli import config as agent_config
166
+
167
+ with scoped_home(home):
168
+ for key, value in changes.items():
169
+ refusal = _env_refusal(agent_config, key, "remove" if value is None else "set")
170
+ if refusal:
171
+ raise RpcError(refusal, condition="env_write_refused")
172
+ try:
173
+ agent_config.ensure_hermes_home()
174
+ path = agent_config.get_env_path()
175
+ with _env_lock(agent_config, path):
176
+ existed = path.exists()
177
+ lines = agent_config._read_env_lines(path) if existed else []
178
+ before = list(lines)
179
+ for key, value in changes.items():
180
+ defines = agent_config._env_line_defines_key
181
+ if value is None:
182
+ lines = [line for line in lines if not defines(line, key)]
183
+ continue
184
+ agent_config.validate_env_var_name_for_write(key)
185
+ entry = f"{key}={value}\n"
186
+ at = next((i for i, line in enumerate(lines) if defines(line, key)), None)
187
+ if at is not None:
188
+ lines[at] = entry
189
+ else:
190
+ if lines and not lines[-1].endswith("\n"):
191
+ lines[-1] += "\n"
192
+ lines.append(entry)
193
+ if lines != before:
194
+ agent_config._write_env_lines(path, lines, preserve_mode=existed)
195
+ except TimeoutError as exc:
196
+ raise RpcError(str(exc), condition="env_locked") from exc
197
+ except ValueError as exc:
198
+ raise RpcError(str(exc), condition="env_write_refused") from exc
199
+ agent_config.invalidate_env_cache()
200
+ if path.exists():
201
+ os.chmod(path, 0o600)
202
+ return {"ok": True, "path": str(path)}
203
+
204
+
104
205
  def reasoning_efforts(model: str, provider: str) -> dict:
105
206
  """Levels the Agent would offer for ``model`` on ``provider`` (``hermes_cli.main_provider_setup``)."""
106
207
  efforts: list[str] | None = None
@@ -122,6 +223,16 @@ def reasoning_efforts(model: str, provider: str) -> dict:
122
223
  return {"efforts": [str(e) for e in (efforts or [])], "supports_reasoning": supports_reasoning}
123
224
 
124
225
 
226
+ def personalities(config) -> list[dict]:
227
+ """TAL-901: ``config``'s personalities as the Agent offers them (``hermes_cli.personality``), with its list preview
228
+ and rendered prompt."""
229
+ from hermes_cli.personality import available_personalities, describe_personality, render_personality_prompt
230
+
231
+ if not isinstance(config, dict):
232
+ raise InvalidParams("config must be a mapping")
233
+ return [{"name": name, "description": describe_personality(value), "prompt": render_personality_prompt(value)} for name, value in available_personalities(config).items()]
234
+
235
+
125
236
  def register(registry) -> None:
126
237
  @registry.method("config.get", requires_agent=False)
127
238
  def get(ctx: CallContext, params: dict) -> dict:
@@ -131,7 +242,15 @@ def register(registry) -> None:
131
242
  def set_(ctx: CallContext, params: dict) -> dict:
132
243
  return write_config(config_path_param(params), params.get("config"))
133
244
 
245
+ @registry.method("config.env_write")
246
+ def env_write_(ctx: CallContext, params: dict) -> dict:
247
+ return env_write(profile_home_param(params), params.get("updates"))
248
+
134
249
  @registry.method("models.reasoning_efforts")
135
250
  def efforts(ctx: CallContext, params: dict) -> dict:
136
251
  with scoped_home(profile_home_param(params)):
137
252
  return reasoning_efforts(str(params.get("model") or ""), str(params.get("provider") or ""))
253
+
254
+ @registry.method("personalities.list")
255
+ def personalities_(ctx: CallContext, params: dict) -> dict:
256
+ return {"personalities": personalities(params.get("config"))}