@maudecode/talaria-web 1.18.0 → 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 (567) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.js +16 -16
  3. package/dist/api/auth-raw.js.map +1 -1
  4. package/dist/api/automation-raw.d.ts +2 -2
  5. package/dist/api/automation-raw.js +32 -32
  6. package/dist/api/automation-raw.js.map +1 -1
  7. package/dist/api/automation-router.d.ts +102 -22
  8. package/dist/api/automation-router.js +15 -13
  9. package/dist/api/automation-router.js.map +1 -1
  10. package/dist/api/chat-router.d.ts +1205 -34
  11. package/dist/api/chat-router.js +158 -78
  12. package/dist/api/chat-router.js.map +1 -1
  13. package/dist/api/git-router.d.ts +2 -2
  14. package/dist/api/git-router.js +29 -29
  15. package/dist/api/git-router.js.map +1 -1
  16. package/dist/api/raw-routes.d.ts +3 -3
  17. package/dist/api/raw-routes.js +63 -61
  18. package/dist/api/raw-routes.js.map +1 -1
  19. package/dist/api/router.d.ts +6 -6
  20. package/dist/api/router.js +22 -22
  21. package/dist/api/router.js.map +1 -1
  22. package/dist/api/session-visibility.d.ts +4 -4
  23. package/dist/api/session-visibility.js +26 -16
  24. package/dist/api/session-visibility.js.map +1 -1
  25. package/dist/api/sessions-router.d.ts +1928 -124
  26. package/dist/api/sessions-router.js +177 -173
  27. package/dist/api/sessions-router.js.map +1 -1
  28. package/dist/api/settings-router.d.ts +427 -9
  29. package/dist/api/settings-router.js +48 -32
  30. package/dist/api/settings-router.js.map +1 -1
  31. package/dist/api/sse-routes.d.ts +2 -2
  32. package/dist/api/sse-routes.js +63 -63
  33. package/dist/api/sse-routes.js.map +1 -1
  34. package/dist/api/tools-raw.d.ts +5 -0
  35. package/dist/api/tools-raw.js +58 -33
  36. package/dist/api/tools-raw.js.map +1 -1
  37. package/dist/api/tools-router.d.ts +663 -91
  38. package/dist/api/tools-router.js +309 -52
  39. package/dist/api/tools-router.js.map +1 -1
  40. package/dist/api/transcribe-stream.d.ts +16 -0
  41. package/dist/api/transcribe-stream.js +172 -0
  42. package/dist/api/transcribe-stream.js.map +1 -0
  43. package/dist/app.d.ts +4 -1
  44. package/dist/app.js +66 -47
  45. package/dist/app.js.map +1 -1
  46. package/dist/auth/gate.js +10 -9
  47. package/dist/auth/gate.js.map +1 -1
  48. package/dist/auth/passkeys.d.ts +15 -14
  49. package/dist/auth/passkeys.js +89 -91
  50. package/dist/auth/passkeys.js.map +1 -1
  51. package/dist/auth/store.d.ts +17 -7
  52. package/dist/auth/store.js +93 -52
  53. package/dist/auth/store.js.map +1 -1
  54. package/dist/bin/talaria-web.js +12 -81
  55. package/dist/bin/talaria-web.js.map +1 -1
  56. package/dist/cli/ctl.js +2 -1
  57. package/dist/cli/ctl.js.map +1 -1
  58. package/dist/cli/dotenv.d.ts +5 -0
  59. package/dist/cli/dotenv.js +14 -2
  60. package/dist/cli/dotenv.js.map +1 -1
  61. package/dist/cli/launcher.js +5 -5
  62. package/dist/cli/launcher.js.map +1 -1
  63. package/dist/cli/main.d.ts +1 -0
  64. package/dist/cli/main.js +85 -0
  65. package/dist/cli/main.js.map +1 -0
  66. package/dist/cli/supervise.d.ts +2 -0
  67. package/dist/cli/supervise.js +67 -4
  68. package/dist/cli/supervise.js.map +1 -1
  69. package/dist/config/agent-config.d.ts +34 -11
  70. package/dist/config/agent-config.js +113 -58
  71. package/dist/config/agent-config.js.map +1 -1
  72. package/dist/config.d.ts +2 -0
  73. package/dist/config.js +14 -5
  74. package/dist/config.js.map +1 -1
  75. package/dist/fs/atomic.d.ts +1 -0
  76. package/dist/fs/atomic.js +4 -1
  77. package/dist/fs/atomic.js.map +1 -1
  78. package/dist/fs/stat.d.ts +1 -0
  79. package/dist/fs/stat.js +11 -0
  80. package/dist/fs/stat.js.map +1 -0
  81. package/dist/http/context.d.ts +14 -9
  82. package/dist/http/context.js +29 -20
  83. package/dist/http/context.js.map +1 -1
  84. package/dist/onboarding.d.ts +2 -2
  85. package/dist/onboarding.js +10 -8
  86. package/dist/onboarding.js.map +1 -1
  87. package/dist/profiles/profiles.d.ts +10 -4
  88. package/dist/profiles/profiles.js +77 -30
  89. package/dist/profiles/profiles.js.map +1 -1
  90. package/dist/projects.d.ts +15 -6
  91. package/dist/projects.js +55 -47
  92. package/dist/projects.js.map +1 -1
  93. package/dist/providers/catalog.d.ts +78 -12
  94. package/dist/providers/catalog.js +445 -141
  95. package/dist/providers/catalog.js.map +1 -1
  96. package/dist/providers/env-file.d.ts +10 -3
  97. package/dist/providers/env-file.js +24 -75
  98. package/dist/providers/env-file.js.map +1 -1
  99. package/dist/providers/runtime-env.d.ts +4 -2
  100. package/dist/providers/runtime-env.js +9 -6
  101. package/dist/providers/runtime-env.js.map +1 -1
  102. package/dist/providers/tables.d.ts +3 -1
  103. package/dist/providers/tables.js +4 -2
  104. package/dist/providers/tables.js.map +1 -1
  105. package/dist/redact.js +18 -9
  106. package/dist/redact.js.map +1 -1
  107. package/dist/release.d.ts +7 -0
  108. package/dist/release.js +14 -0
  109. package/dist/release.js.map +1 -1
  110. package/dist/runtime.d.ts +5 -1
  111. package/dist/runtime.js +233 -90
  112. package/dist/runtime.js.map +1 -1
  113. package/dist/server.js +10 -0
  114. package/dist/server.js.map +1 -1
  115. package/dist/sessions/anchor.d.ts +9 -3
  116. package/dist/sessions/anchor.js +28 -36
  117. package/dist/sessions/anchor.js.map +1 -1
  118. package/dist/sessions/background-events.d.ts +2 -0
  119. package/dist/sessions/background-events.js +10 -0
  120. package/dist/sessions/background-events.js.map +1 -0
  121. package/dist/sessions/background-tasks.d.ts +26 -20
  122. package/dist/sessions/background-tasks.js +88 -86
  123. package/dist/sessions/background-tasks.js.map +1 -1
  124. package/dist/sessions/background-updates.d.ts +15 -8
  125. package/dist/sessions/background-updates.js +101 -25
  126. package/dist/sessions/background-updates.js.map +1 -1
  127. package/dist/sessions/cli-sessions.d.ts +15 -5
  128. package/dist/sessions/cli-sessions.js +62 -43
  129. package/dist/sessions/cli-sessions.js.map +1 -1
  130. package/dist/sessions/completions.d.ts +30 -2
  131. package/dist/sessions/completions.js +294 -53
  132. package/dist/sessions/completions.js.map +1 -1
  133. package/dist/sessions/compress.d.ts +8 -0
  134. package/dist/sessions/compress.js +2 -2
  135. package/dist/sessions/compress.js.map +1 -1
  136. package/dist/sessions/drafts.d.ts +14 -10
  137. package/dist/sessions/drafts.js +38 -43
  138. package/dist/sessions/drafts.js.map +1 -1
  139. package/dist/sessions/gateway-sessions.d.ts +46 -0
  140. package/dist/sessions/gateway-sessions.js +378 -0
  141. package/dist/sessions/gateway-sessions.js.map +1 -0
  142. package/dist/sessions/goals.d.ts +31 -0
  143. package/dist/sessions/goals.js +75 -0
  144. package/dist/sessions/goals.js.map +1 -0
  145. package/dist/sessions/journal.d.ts +5 -79
  146. package/dist/sessions/journal.js +15 -606
  147. package/dist/sessions/journal.js.map +1 -1
  148. package/dist/sessions/list.d.ts +27 -18
  149. package/dist/sessions/list.js +133 -131
  150. package/dist/sessions/list.js.map +1 -1
  151. package/dist/sessions/merge.d.ts +68 -10
  152. package/dist/sessions/merge.js +259 -51
  153. package/dist/sessions/merge.js.map +1 -1
  154. package/dist/sessions/notices.d.ts +22 -0
  155. package/dist/sessions/notices.js +72 -0
  156. package/dist/sessions/notices.js.map +1 -0
  157. package/dist/sessions/pending.d.ts +10 -4
  158. package/dist/sessions/pending.js +83 -7
  159. package/dist/sessions/pending.js.map +1 -1
  160. package/dist/sessions/prefix-index.d.ts +82 -0
  161. package/dist/sessions/prefix-index.js +232 -0
  162. package/dist/sessions/prefix-index.js.map +1 -0
  163. package/dist/sessions/recovery.d.ts +4 -7
  164. package/dist/sessions/recovery.js +97 -281
  165. package/dist/sessions/recovery.js.map +1 -1
  166. package/dist/sessions/relay.d.ts +20 -20
  167. package/dist/sessions/relay.js +205 -127
  168. package/dist/sessions/relay.js.map +1 -1
  169. package/dist/sessions/service.d.ts +162 -59
  170. package/dist/sessions/service.js +1133 -521
  171. package/dist/sessions/service.js.map +1 -1
  172. package/dist/sessions/session.d.ts +18 -2
  173. package/dist/sessions/session.js +29 -5
  174. package/dist/sessions/session.js.map +1 -1
  175. package/dist/sessions/shares.d.ts +7 -6
  176. package/dist/sessions/shares.js +22 -45
  177. package/dist/sessions/shares.js.map +1 -1
  178. package/dist/sessions/source-kind.js +1 -0
  179. package/dist/sessions/source-kind.js.map +1 -1
  180. package/dist/sessions/state-db-identity.d.ts +18 -0
  181. package/dist/sessions/state-db-identity.js +67 -0
  182. package/dist/sessions/state-db-identity.js.map +1 -0
  183. package/dist/sessions/state-db-worker.js +3 -1
  184. package/dist/sessions/state-db-worker.js.map +1 -1
  185. package/dist/sessions/state-db.d.ts +53 -8
  186. package/dist/sessions/state-db.js +576 -50
  187. package/dist/sessions/state-db.js.map +1 -1
  188. package/dist/sessions/storage/backend.d.ts +162 -0
  189. package/dist/sessions/storage/backend.js +19 -0
  190. package/dist/sessions/storage/backend.js.map +1 -0
  191. package/dist/sessions/storage/conformance.d.ts +20 -0
  192. package/dist/sessions/storage/conformance.js +237 -0
  193. package/dist/sessions/storage/conformance.js.map +1 -0
  194. package/dist/sessions/storage/document.d.ts +16 -0
  195. package/dist/sessions/storage/document.js +44 -0
  196. package/dist/sessions/storage/document.js.map +1 -0
  197. package/dist/sessions/storage/json-documents.d.ts +23 -0
  198. package/dist/sessions/storage/json-documents.js +233 -0
  199. package/dist/sessions/storage/json-documents.js.map +1 -0
  200. package/dist/sessions/storage/json-files.d.ts +12 -0
  201. package/dist/sessions/storage/json-files.js +124 -0
  202. package/dist/sessions/storage/json-files.js.map +1 -0
  203. package/dist/sessions/storage/json-installation.d.ts +3 -0
  204. package/dist/sessions/storage/json-installation.js +31 -0
  205. package/dist/sessions/storage/json-installation.js.map +1 -0
  206. package/dist/sessions/storage/json-journal.d.ts +114 -0
  207. package/dist/sessions/storage/json-journal.js +686 -0
  208. package/dist/sessions/storage/json-journal.js.map +1 -0
  209. package/dist/sessions/storage/json-recovery.d.ts +4 -0
  210. package/dist/sessions/storage/json-recovery.js +195 -0
  211. package/dist/sessions/storage/json-recovery.js.map +1 -0
  212. package/dist/sessions/storage/json.d.ts +68 -0
  213. package/dist/sessions/storage/json.js +258 -0
  214. package/dist/sessions/storage/json.js.map +1 -0
  215. package/dist/sessions/storage/row.d.ts +6 -0
  216. package/dist/sessions/storage/row.js +49 -0
  217. package/dist/sessions/storage/row.js.map +1 -0
  218. package/dist/sessions/storage/search-text.d.ts +4 -0
  219. package/dist/sessions/storage/search-text.js +16 -0
  220. package/dist/sessions/storage/search-text.js.map +1 -0
  221. package/dist/sessions/store.d.ts +63 -73
  222. package/dist/sessions/store.js +305 -611
  223. package/dist/sessions/store.js.map +1 -1
  224. package/dist/sessions/streams.d.ts +4 -0
  225. package/dist/sessions/streams.js +9 -1
  226. package/dist/sessions/streams.js.map +1 -1
  227. package/dist/sessions/titles.js +1 -1
  228. package/dist/sessions/titles.js.map +1 -1
  229. package/dist/sessions/todo.d.ts +5 -0
  230. package/dist/sessions/todo.js +28 -21
  231. package/dist/sessions/todo.js.map +1 -1
  232. package/dist/sessions/tool-display.d.ts +5 -4
  233. package/dist/sessions/tool-display.js +22 -16
  234. package/dist/sessions/tool-display.js.map +1 -1
  235. package/dist/sessions/turn-context.d.ts +10 -1
  236. package/dist/sessions/turn-context.js +7 -4
  237. package/dist/sessions/turn-context.js.map +1 -1
  238. package/dist/sessions/turn.d.ts +87 -17
  239. package/dist/sessions/turn.js +795 -347
  240. package/dist/sessions/turn.js.map +1 -1
  241. package/dist/sessions/wakeup-pause.d.ts +8 -13
  242. package/dist/sessions/wakeup-pause.js +31 -49
  243. package/dist/sessions/wakeup-pause.js.map +1 -1
  244. package/dist/sessions/window.js +13 -12
  245. package/dist/sessions/window.js.map +1 -1
  246. package/dist/settings.d.ts +19 -7
  247. package/dist/settings.js +179 -183
  248. package/dist/settings.js.map +1 -1
  249. package/dist/sidecar/client.d.ts +17 -3
  250. package/dist/sidecar/client.js +78 -8
  251. package/dist/sidecar/client.js.map +1 -1
  252. package/dist/sidecar/discover.d.ts +2 -1
  253. package/dist/sidecar/discover.js +93 -10
  254. package/dist/sidecar/discover.js.map +1 -1
  255. package/dist/sidecar/env.d.ts +4 -0
  256. package/dist/sidecar/env.js +3 -0
  257. package/dist/sidecar/env.js.map +1 -0
  258. package/dist/sidecar/fake.js +19 -2
  259. package/dist/sidecar/fake.js.map +1 -1
  260. package/dist/test/env-file.d.ts +5 -0
  261. package/dist/test/env-file.js +88 -0
  262. package/dist/test/env-file.js.map +1 -0
  263. package/dist/test/harness.d.ts +1 -0
  264. package/dist/test/harness.js +1 -1
  265. package/dist/test/harness.js.map +1 -1
  266. package/dist/test/passkeys.d.ts +22 -0
  267. package/dist/test/passkeys.js +62 -0
  268. package/dist/test/passkeys.js.map +1 -0
  269. package/dist/test/storage.d.ts +15 -0
  270. package/dist/test/storage.js +26 -0
  271. package/dist/test/storage.js.map +1 -0
  272. package/dist/tools/commands.d.ts +6 -1
  273. package/dist/tools/commands.js +8 -2
  274. package/dist/tools/commands.js.map +1 -1
  275. package/dist/tools/connectors.d.ts +33 -0
  276. package/dist/tools/connectors.js +121 -0
  277. package/dist/tools/connectors.js.map +1 -0
  278. package/dist/tools/crons.d.ts +38 -1
  279. package/dist/tools/crons.js +82 -15
  280. package/dist/tools/crons.js.map +1 -1
  281. package/dist/tools/dashboard-plugins.d.ts +5 -5
  282. package/dist/tools/dashboard-plugins.js +14 -14
  283. package/dist/tools/dashboard-plugins.js.map +1 -1
  284. package/dist/tools/extensions.d.ts +5 -4
  285. package/dist/tools/extensions.js +213 -207
  286. package/dist/tools/extensions.js.map +1 -1
  287. package/dist/tools/gateway-restart.d.ts +62 -0
  288. package/dist/tools/gateway-restart.js +142 -0
  289. package/dist/tools/gateway-restart.js.map +1 -0
  290. package/dist/tools/health.d.ts +15 -3
  291. package/dist/tools/health.js +97 -34
  292. package/dist/tools/health.js.map +1 -1
  293. package/dist/tools/hygiene.d.ts +4 -3
  294. package/dist/tools/hygiene.js +23 -9
  295. package/dist/tools/hygiene.js.map +1 -1
  296. package/dist/tools/kanban.d.ts +4 -2
  297. package/dist/tools/kanban.js +28 -19
  298. package/dist/tools/kanban.js.map +1 -1
  299. package/dist/tools/memory-masks.d.ts +5 -0
  300. package/dist/tools/memory-masks.js +256 -0
  301. package/dist/tools/memory-masks.js.map +1 -0
  302. package/dist/tools/memory.d.ts +29 -3
  303. package/dist/tools/memory.js +69 -27
  304. package/dist/tools/memory.js.map +1 -1
  305. package/dist/tools/plugin-settings.d.ts +15 -0
  306. package/dist/tools/plugin-settings.js +90 -0
  307. package/dist/tools/plugin-settings.js.map +1 -0
  308. package/dist/tools/skills.d.ts +9 -4
  309. package/dist/tools/skills.js +44 -13
  310. package/dist/tools/skills.js.map +1 -1
  311. package/dist/tools/update-notifications.d.ts +34 -15
  312. package/dist/tools/update-notifications.js +141 -57
  313. package/dist/tools/update-notifications.js.map +1 -1
  314. package/dist/tools/update-rollback.d.ts +11 -0
  315. package/dist/tools/update-rollback.js +28 -0
  316. package/dist/tools/update-rollback.js.map +1 -0
  317. package/dist/tools/updates.d.ts +93 -33
  318. package/dist/tools/updates.js +1051 -345
  319. package/dist/tools/updates.js.map +1 -1
  320. package/dist/util.d.ts +2 -0
  321. package/dist/util.js +5 -0
  322. package/dist/util.js.map +1 -1
  323. package/dist/workspace/git.d.ts +16 -1
  324. package/dist/workspace/git.js +291 -11
  325. package/dist/workspace/git.js.map +1 -1
  326. package/dist/workspace/media.d.ts +12 -9
  327. package/dist/workspace/media.js +31 -20
  328. package/dist/workspace/media.js.map +1 -1
  329. package/dist/workspace/rollback.d.ts +2 -2
  330. package/dist/workspace/rollback.js +5 -5
  331. package/dist/workspace/rollback.js.map +1 -1
  332. package/dist/workspace/workspaces.d.ts +15 -19
  333. package/dist/workspace/workspaces.js +55 -127
  334. package/dist/workspace/workspaces.js.map +1 -1
  335. package/dist/workspace/worktrees.js +170 -32
  336. package/dist/workspace/worktrees.js.map +1 -1
  337. package/package.json +5 -3
  338. package/sidecar/agent_dependency.json +4 -4
  339. package/sidecar/agent_support.json +26 -0
  340. package/sidecar/scripts/record-fixtures.py +12 -3
  341. package/sidecar/scripts/replay_sidecar.py +8 -0
  342. package/sidecar/scripts/suite_runner.py +302 -0
  343. package/sidecar/scripts/test.sh +12 -4
  344. package/sidecar/talaria_sidecar/__init__.py +6 -1
  345. package/sidecar/talaria_sidecar/__main__.py +4 -2
  346. package/sidecar/talaria_sidecar/agent_mutation.py +975 -0
  347. package/sidecar/talaria_sidecar/agent_services.py +1053 -0
  348. package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
  349. package/sidecar/talaria_sidecar/home.py +43 -1
  350. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  351. package/sidecar/talaria_sidecar/methods/aux.py +139 -31
  352. package/sidecar/talaria_sidecar/methods/chat.py +847 -134
  353. package/sidecar/talaria_sidecar/methods/commands.py +213 -4
  354. package/sidecar/talaria_sidecar/methods/config.py +123 -4
  355. package/sidecar/talaria_sidecar/methods/connectors.py +343 -0
  356. package/sidecar/talaria_sidecar/methods/cron.py +366 -34
  357. package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
  358. package/sidecar/talaria_sidecar/methods/gateway.py +194 -54
  359. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +234 -0
  360. package/sidecar/talaria_sidecar/methods/goals.py +81 -41
  361. package/sidecar/talaria_sidecar/methods/kanban.py +80 -27
  362. package/sidecar/talaria_sidecar/methods/memory.py +84 -0
  363. package/sidecar/talaria_sidecar/methods/oauth.py +5 -2
  364. package/sidecar/talaria_sidecar/methods/process.py +160 -11
  365. package/sidecar/talaria_sidecar/methods/profiles.py +44 -6
  366. package/sidecar/talaria_sidecar/methods/providers.py +196 -26
  367. package/sidecar/talaria_sidecar/methods/runtime.py +69 -1
  368. package/sidecar/talaria_sidecar/methods/skills.py +67 -4
  369. package/sidecar/talaria_sidecar/methods/state_db.py +277 -70
  370. package/sidecar/talaria_sidecar/methods/stt.py +159 -78
  371. package/sidecar/talaria_sidecar/methods/text.py +14 -1
  372. package/sidecar/talaria_sidecar/methods/toolsets.py +49 -0
  373. package/sidecar/talaria_sidecar/methods/usage.py +113 -14
  374. package/sidecar/talaria_sidecar/methods/worktree.py +26 -19
  375. package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
  376. package/sidecar/talaria_sidecar/rpc.py +45 -4
  377. package/sidecar/talaria_sidecar/runtime.py +81 -12
  378. package/sidecar/talaria_sidecar/speed.py +35 -0
  379. package/static/dist/FILES.txt +126 -127
  380. package/static/dist/assets/AppShell-cey33v9s.js +8 -0
  381. package/static/dist/assets/ChatPage-i8wnugr7.js +18 -0
  382. package/static/dist/assets/ConnectTargets-hg19553x.js +1 -0
  383. package/static/dist/assets/ExtensionRoute-cly4apm5.js +1 -0
  384. package/static/dist/assets/Field-j8uqoigz.js +9 -0
  385. package/static/dist/assets/HubRoute-e4bgz41j.js +2 -0
  386. package/static/dist/assets/Markdown-lahuichm.js +598 -0
  387. package/static/dist/assets/RightPanel-lbo4i0fn.js +1 -0
  388. package/static/dist/assets/Select-gqatm2e0.js +1 -0
  389. package/static/dist/assets/SessionListPanel-mfqayfjh.js +1 -0
  390. package/static/dist/assets/SettingsLayout-b8980u7k.js +1 -0
  391. package/static/dist/assets/States-e7dkgtxf.js +1 -0
  392. package/static/dist/assets/Toaster-bfqqy786.js +1 -0
  393. package/static/dist/assets/_app-n1n6pfx1.js +1 -0
  394. package/static/dist/assets/{_app.ext._extensionId-gfbzpoou.js → _app.ext._extensionId-buxjxav3.js} +1 -1
  395. package/static/dist/assets/_app.index-jt8zwiu2.js +1 -0
  396. package/static/dist/assets/_app.insights-cv76ml71.js +1 -0
  397. package/static/dist/assets/_app.kanban-h6ts53j7.js +1 -0
  398. package/static/dist/assets/_app.logs-dm873z90.js +1 -0
  399. package/static/dist/assets/_app.memory-glk5y6m8.js +1 -0
  400. package/static/dist/assets/_app.profiles-ka6zpstq.js +1 -0
  401. package/static/dist/assets/{_app.session._sessionId-fn1bhde6.js → _app.session._sessionId-g2vzghvc.js} +1 -1
  402. package/static/dist/assets/_app.settings-hgjdhzcd.js +1 -0
  403. package/static/dist/assets/_app.settings._section-jf2kmhae.js +3 -0
  404. package/static/dist/assets/_app.skills-cii852yq.js +1 -0
  405. package/static/dist/assets/_app.tasks-fp9q7atm.js +1 -0
  406. package/static/dist/assets/_app.todos-dw94qzue.js +1 -0
  407. package/static/dist/assets/_app.workspaces-inyygzze.js +1 -0
  408. package/static/dist/assets/{abnfDiagram-VCTEODGH-c8kdixke.js → abnfDiagram-VCTEODGH-gjayq3ra.js} +1 -1
  409. package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +1 -0
  410. package/static/dist/assets/{architectureDiagram-5GKGNRK7-cku06x1p.js → architectureDiagram-5GKGNRK7-nwb3xovn.js} +1 -1
  411. package/static/dist/assets/archive-b1j004vn.js +1 -0
  412. package/static/dist/assets/back-g74gkozu.js +1 -0
  413. package/static/dist/assets/{blockDiagram-I7D4REHJ-jzthp1wa.js → blockDiagram-I7D4REHJ-joedghi3.js} +1 -1
  414. package/static/dist/assets/{c4Diagram-7LVT6UL2-mwr67mt8.js → c4Diagram-7LVT6UL2-hyrjrohe.js} +1 -1
  415. package/static/dist/assets/channel-es95o4y8.js +1 -0
  416. package/static/dist/assets/{chunk-4HAMMTFA-gpq1y228.js → chunk-4HAMMTFA-koouqbut.js} +1 -1
  417. package/static/dist/assets/{chunk-75Z2AOVW-ktje58c4.js → chunk-75Z2AOVW-bl4bhdnt.js} +1 -1
  418. package/static/dist/assets/{chunk-DU6HZSFF-ofajdixh.js → chunk-DU6HZSFF-gpedttsp.js} +3 -3
  419. package/static/dist/assets/{chunk-F27PBJKO-cmev85ti.js → chunk-F27PBJKO-d8xxi0zi.js} +1 -1
  420. package/static/dist/assets/{chunk-GMAD6QVW-htjgt22y.js → chunk-GMAD6QVW-jro0hl73.js} +1 -1
  421. package/static/dist/assets/{chunk-GVQU2GXP-qndvoxl7.js → chunk-GVQU2GXP-djn6gbyu.js} +1 -1
  422. package/static/dist/assets/{chunk-IMKFNOWR-e5bkd5q6.js → chunk-IMKFNOWR-vw4p5p7o.js} +1 -1
  423. package/static/dist/assets/{chunk-L3NEJ4N5-mj1155r6.js → chunk-L3NEJ4N5-n1hj77q8.js} +1 -1
  424. package/static/dist/assets/{chunk-OSK3NFVY-e7gkikcw.js → chunk-OSK3NFVY-oy0gwn2n.js} +1 -1
  425. package/static/dist/assets/{chunk-P2QGCYS3-f7t54xfm.js → chunk-P2QGCYS3-onf1hz6c.js} +1 -1
  426. package/static/dist/assets/{chunk-POPQ4Y6H-i927v9oa.js → chunk-POPQ4Y6H-m8sqn7cs.js} +1 -1
  427. package/static/dist/assets/{chunk-PWAF6VOD-m84lmnyt.js → chunk-PWAF6VOD-ek4smc8x.js} +1 -1
  428. package/static/dist/assets/{chunk-SHT3W25Y-mi0tlcu9.js → chunk-SHT3W25Y-g9gfvefo.js} +3 -3
  429. package/static/dist/assets/{chunk-SVP7TREG-dnau702j.js → chunk-SVP7TREG-f9nfv2cu.js} +1 -1
  430. package/static/dist/assets/{chunk-TICWLB2K-edem1gkg.js → chunk-TICWLB2K-bnk3m1mq.js} +1 -1
  431. package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +1 -0
  432. package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +1 -0
  433. package/static/dist/assets/copied-bli9tbcx.js +1 -0
  434. package/static/dist/assets/{copy-magtofyp.js → copy-jamtd1pf.js} +1 -1
  435. package/static/dist/assets/{cynefin-OW5HDTMX-siqlulkl.js → cynefin-OW5HDTMX-f8xfdd3f.js} +1 -1
  436. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-i648tah9.js → cynefinDiagram-5FMLGOSQ-n9lb99h9.js} +1 -1
  437. package/static/dist/assets/{dagre-GXQ25YYZ-mwn6uc17.js → dagre-GXQ25YYZ-dgailddi.js} +4 -4
  438. package/static/dist/assets/{diagram-S7CK7UJ4-l8abxaes.js → diagram-S7CK7UJ4-m6aeoe7c.js} +1 -1
  439. package/static/dist/assets/{diagram-UQ7AKVKN-mekq7zgq.js → diagram-UQ7AKVKN-jall3j0x.js} +1 -1
  440. package/static/dist/assets/{diagram-VSXAHHWV-fnky6lvc.js → diagram-VSXAHHWV-hbawi7hn.js} +1 -1
  441. package/static/dist/assets/{diagram-VX7I27RA-xe6r5qfw.js → diagram-VX7I27RA-hendl2tt.js} +1 -1
  442. package/static/dist/assets/{diagram-Z3DM3KII-nq07dg7k.js → diagram-Z3DM3KII-igwc35p3.js} +1 -1
  443. package/static/dist/assets/{ebnfDiagram-PWID7BFC-c62ipm3g.js → ebnfDiagram-PWID7BFC-c64knz08.js} +1 -1
  444. package/static/dist/assets/{erDiagram-RLTQ6QDP-h39kmnzy.js → erDiagram-RLTQ6QDP-brdu9lcm.js} +1 -1
  445. package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +1 -0
  446. package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +1 -0
  447. package/static/dist/assets/folder-plus-lvt9kudb.js +1 -0
  448. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ngbsyp37.js → ganttDiagram-EL5Y4UJY-ma6jxj5i.js} +1 -1
  449. package/static/dist/assets/{gitGraph-4MIJSDKK-fr2i7caa.js → gitGraph-4MIJSDKK-naa9w6xa.js} +1 -1
  450. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-eravdti4.js → gitGraphDiagram-WWUBYQGX-hzltvsod.js} +1 -1
  451. package/static/dist/assets/{highlighted-body-KPVGNVTW-jwpop9p8.js → highlighted-body-KPVGNVTW-ghoavj8d.js} +1 -1
  452. package/static/dist/assets/host-hzm8n85n.js +1 -0
  453. package/static/dist/assets/index-drdcaqeq.css +2 -0
  454. package/static/dist/assets/index-jzm844he.js +11 -0
  455. package/static/dist/assets/{info-A6RAGUB7-h11x0dxo.js → info-A6RAGUB7-mn2ut5ko.js} +1 -1
  456. package/static/dist/assets/{infoDiagram-27XIBGKW-dfh47dw9.js → infoDiagram-27XIBGKW-h6kuqvsb.js} +1 -1
  457. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-i6ypq351.js → ishikawaDiagram-5VMMS53U-e7bnwq7h.js} +1 -1
  458. package/static/dist/assets/{journeyDiagram-3NMN7TZE-lduf1z95.js → journeyDiagram-3NMN7TZE-etd1bi6p.js} +1 -1
  459. package/static/dist/assets/{kanban-definition-UXKFOSKX-e18bu4pg.js → kanban-definition-UXKFOSKX-emb1g9dz.js} +4 -4
  460. package/static/dist/assets/lazyRouteComponent-o816ytzl.js +1 -0
  461. package/static/dist/assets/{line-meizqsvn.js → line-h0ojzsvs.js} +1 -1
  462. package/static/dist/assets/link-k01ryaf9.js +1 -0
  463. package/static/dist/assets/login-bsae49ew.js +1 -0
  464. package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +1 -0
  465. package/static/dist/assets/{mermaid-parser.core-ntne45i9.js → mermaid-parser.core-bnre27u8.js} +3 -3
  466. package/static/dist/assets/{mindmap-definition-YA3MSWOX-27b26eok.js → mindmap-definition-YA3MSWOX-o1vathns.js} +1 -1
  467. package/static/dist/assets/onboarding-c65tysya.js +1 -0
  468. package/static/dist/assets/onboarding_model_label-gpn28uix.js +1 -0
  469. package/static/dist/assets/{packet-AYTQ26CC-l6jdka8p.js → packet-AYTQ26CC-hatq7m2e.js} +1 -1
  470. package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
  471. package/static/dist/assets/{pegDiagram-XKGWAZYB-g1er3bxj.js → pegDiagram-XKGWAZYB-gwisz8jv.js} +1 -1
  472. package/static/dist/assets/{pie-WAS4IAKB-i0ylcrt9.js → pie-WAS4IAKB-fpftf0mk.js} +1 -1
  473. package/static/dist/assets/{pieDiagram-E7YTZNPT-fbz1e55z.js → pieDiagram-E7YTZNPT-khm1o9a6.js} +1 -1
  474. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-hhue7d8o.js → quadrantDiagram-AXDQQJYC-owqc4uqn.js} +1 -1
  475. package/static/dist/assets/{radar-RG4KPBEZ-f9b70aa4.js → radar-RG4KPBEZ-cntfwjpq.js} +1 -1
  476. package/static/dist/assets/{railroad-74A4TZTK-lgsuoeda.js → railroad-74A4TZTK-a7t5eibp.js} +1 -1
  477. package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +1 -0
  478. package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +1 -0
  479. package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +1 -0
  480. package/static/dist/assets/{railroadDiagram-O6MQD6OU-eqpy9fnz.js → railroadDiagram-O6MQD6OU-etuh0qa0.js} +1 -1
  481. package/static/dist/assets/{refresh-cw-hlwb02qv.js → refresh-cw-mqbkv2a3.js} +1 -1
  482. package/static/dist/assets/refresh-l29ljp76.js +1 -0
  483. package/static/dist/assets/{requirementDiagram-BXWQKSXE-ogf6puhi.js → requirementDiagram-BXWQKSXE-oq2ulk22.js} +1 -1
  484. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-fqb8xcjf.js → sankeyDiagram-P5KCCOFB-iqc02emv.js} +1 -1
  485. package/static/dist/assets/saved-ndd4t2xb.js +1 -0
  486. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-dlcz6qjz.js → sequenceDiagram-WJ2MYXX4-i0v116q0.js} +2 -2
  487. package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +1 -0
  488. package/static/dist/assets/share._token-c1ri0hy1.js +2 -0
  489. package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +1 -0
  490. package/static/dist/assets/{stateDiagram-D77RDMKH-fyoznpcs.js → stateDiagram-D77RDMKH-dnpt1ato.js} +1 -1
  491. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +1 -0
  492. package/static/dist/assets/{swimlanes-42K2YHIH-oefldhfy.js → swimlanes-42K2YHIH-loqbdazb.js} +1 -1
  493. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +8 -0
  494. package/static/dist/assets/{timeline-definition-24CTP7MA-b1vl2zil.js → timeline-definition-24CTP7MA-ol2hnvma.js} +1 -1
  495. package/static/dist/assets/toast-i3h34v6o.js +64 -0
  496. package/static/dist/assets/{treeView-Q6P3EWNA-f7hcci5o.js → treeView-Q6P3EWNA-idrj6iox.js} +1 -1
  497. package/static/dist/assets/{treemap-WGGIJYW6-nl85071a.js → treemap-WGGIJYW6-lzr49vv5.js} +1 -1
  498. package/static/dist/assets/untitled-oaoya4qj.js +1 -0
  499. package/static/dist/assets/useForm-nfaxgmsk.js +1 -0
  500. package/static/dist/assets/{useParams-b4dq6n4q.js → useParams-gjc70qru.js} +1 -1
  501. package/static/dist/assets/{useSearch-ipfmv3he.js → useSearch-e6ae539u.js} +1 -1
  502. package/static/dist/assets/{vennDiagram-4TSXK5OY-h5hzbk0n.js → vennDiagram-4TSXK5OY-bvwr9o2z.js} +1 -1
  503. package/static/dist/assets/{wardley-WFR3VGLG-jftvpm3e.js → wardley-WFR3VGLG-31ui8tma.js} +1 -1
  504. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-hedh85ge.js → wardleyDiagram-VM6X3IG4-k5wqtx4b.js} +1 -1
  505. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-e49523ab.js → xychartDiagram-S5SC5T6Z-aqbgvujg.js} +1 -1
  506. package/static/dist/index.html +1 -1
  507. package/static/dist/sw.js +1 -1
  508. package/static/dist/assets/AppShell-g3bkgnvt.js +0 -1
  509. package/static/dist/assets/ChatPage-oo7bdan6.js +0 -25
  510. package/static/dist/assets/Dialog-g355qxhp.js +0 -1
  511. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +0 -1
  512. package/static/dist/assets/HubRoute-lmkrbw47.js +0 -2
  513. package/static/dist/assets/Markdown-j4kut4j2.js +0 -598
  514. package/static/dist/assets/RightPanel-ohlsdzfl.js +0 -1
  515. package/static/dist/assets/Select-kddq75qc.js +0 -1
  516. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +0 -1
  517. package/static/dist/assets/SettingsLayout-o9bv4mgb.js +0 -1
  518. package/static/dist/assets/States-eq6fe29m.js +0 -1
  519. package/static/dist/assets/Toaster-pc247u9f.js +0 -1
  520. package/static/dist/assets/_app-f8n1i0m1.js +0 -1
  521. package/static/dist/assets/_app.index-bzzv80hk.js +0 -1
  522. package/static/dist/assets/_app.insights-dzixbryi.js +0 -1
  523. package/static/dist/assets/_app.kanban-c6hep2us.js +0 -1
  524. package/static/dist/assets/_app.logs-i69qds98.js +0 -1
  525. package/static/dist/assets/_app.memory-kl4vf0gd.js +0 -1
  526. package/static/dist/assets/_app.profiles-b8zinmyp.js +0 -1
  527. package/static/dist/assets/_app.settings-cg8plwmf.js +0 -1
  528. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +0 -3
  529. package/static/dist/assets/_app.skills-cs47sbvs.js +0 -1
  530. package/static/dist/assets/_app.tasks-dwvk64tc.js +0 -1
  531. package/static/dist/assets/_app.todos-c8xjm8p3.js +0 -1
  532. package/static/dist/assets/_app.workspaces-ehvd1gds.js +0 -1
  533. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +0 -1
  534. package/static/dist/assets/back-h7aun7ew.js +0 -1
  535. package/static/dist/assets/cancel-fhi0vcnu.js +0 -1
  536. package/static/dist/assets/channel-b2zo73z1.js +0 -1
  537. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +0 -1
  538. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +0 -1
  539. package/static/dist/assets/copied-hcplv46p.js +0 -1
  540. package/static/dist/assets/endpoints-klbxod4t.js +0 -64
  541. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +0 -1
  542. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +0 -1
  543. package/static/dist/assets/host-i828xuo7.js +0 -1
  544. package/static/dist/assets/index-eh8zilnv.css +0 -2
  545. package/static/dist/assets/index-r648f4o1.js +0 -11
  546. package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +0 -1
  547. package/static/dist/assets/link-oot2yn73.js +0 -1
  548. package/static/dist/assets/login-gu28l9ou.js +0 -1
  549. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +0 -1
  550. package/static/dist/assets/onboarding-lkphzu1s.js +0 -1
  551. package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +0 -1
  552. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +0 -1
  553. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +0 -1
  554. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +0 -1
  555. package/static/dist/assets/refresh-abtu93ke.js +0 -1
  556. package/static/dist/assets/saved-girnabkf.js +0 -1
  557. package/static/dist/assets/settings_section_extensions_title-h14cwk6f.js +0 -1
  558. package/static/dist/assets/share._token-kl0r7ido.js +0 -2
  559. package/static/dist/assets/sliders-horizontal-n1zm34qq.js +0 -1
  560. package/static/dist/assets/sse-iwku0sjo.js +0 -1
  561. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +0 -1
  562. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +0 -8
  563. package/static/dist/assets/todoStore-jjr5yr78.js +0 -1
  564. package/static/dist/assets/units-kgxzxfhi.js +0 -1
  565. package/static/dist/assets/untitled-cikfjntn.js +0 -1
  566. package/static/dist/assets/useForm-kqoqg0k9.js +0 -1
  567. package/static/dist/assets/useLocale-o59tbiea.js +0 -1
@@ -0,0 +1,234 @@
1
+ """Authenticated observations and fenced controls of an existing native owner.
2
+
3
+ Never ensure/start a gateway, build an Agent, or write its transcript locally.
4
+ Each call owns its connection and subscription; cancellation only detaches it.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import asyncio
9
+ from contextlib import asynccontextmanager, closing, suppress
10
+ import json
11
+ from pathlib import Path
12
+ import sqlite3
13
+
14
+ from ..errors import InvalidParams
15
+ from ..home import profile_home_param, scoped_home
16
+
17
+ _ACTIONS = {"snapshot", "watch", "submit", "receipt", "cancel", "interrupt", "approval", "clarify", "discard", "mutate"}
18
+ _CAPABILITIES = {"durable-admission-v1", "event-replay-v1"}
19
+ _CONTROL_REFUSALS = {"native_clarify_batch_unavailable", "owner_controls_unverified"}
20
+
21
+
22
+ def _control_refusal(home: Path, sid: str) -> str | None:
23
+ """Only canonical PendingControls can be recovered on v0.22.1. Never infer a missing batch.
24
+
25
+ Read the owner's committed admission in a read-only connection; opening SessionDB here
26
+ would run its schema writer alongside the owner. Compression keeps the logical target.
27
+ """
28
+ try:
29
+ with closing(sqlite3.connect((home / "state.db").as_uri() + "?mode=ro", uri=True, timeout=2)) as db:
30
+ rows = db.execute("""SELECT payload_json FROM session_admissions
31
+ WHERE target_session_id=? AND status IN ('queued', 'started')""", (sid,))
32
+ for (raw,) in rows:
33
+ payload = json.loads(raw)
34
+ policy = payload.get("cli_turn_v1", {})
35
+ if not isinstance(policy, dict):
36
+ return "owner_controls_unverified"
37
+ if "clarify_batch" in policy and not isinstance(policy["clarify_batch"], bool):
38
+ return "owner_controls_unverified"
39
+ if policy.get("clarify_batch") is True:
40
+ return "native_clarify_batch_unavailable"
41
+ except (sqlite3.Error, OSError, ValueError, TypeError, AttributeError):
42
+ return "owner_controls_unverified"
43
+ return None
44
+
45
+
46
+ def _snapshot(value: dict) -> dict:
47
+ # Raw conversation rows contain private principal/admission metadata; the client needs
48
+ # display rows, not those bindings or the inventory's system prompt and credential hint.
49
+ message_keys = {"role", "content", "tool_calls", "tool_call_id", "name", "timestamp", "message_id", "row_id",
50
+ "reasoning", "reasoning_content", "display_kind", "finish_reason"}
51
+ info_keys = {"title", "model", "cwd", "source", "provider"}
52
+ return {**value, "messages": [{k: v for k, v in row.items() if k in message_keys} for row in value["messages"]],
53
+ "info": {k: v for k, v in value["info"].items() if k in info_keys}}
54
+
55
+
56
+ def _refused(state: str, reason: str) -> dict:
57
+ return {"state": state, "reason": reason, "snapshot": None, "receipt": None}
58
+
59
+
60
+ @asynccontextmanager
61
+ async def _connect(home: Path, endpoint):
62
+ from hermes_constants import get_hermes_home
63
+ from hermes_cli.gateway_client import GatewayClientError, connect_gateway
64
+
65
+ # The supplied endpoint selects the native client's attach-only branch. The home
66
+ # override is mandatory so its bootstrap ticket cannot fall back to the launch profile.
67
+ if Path(get_hermes_home()).resolve() != home:
68
+ raise GatewayClientError("profile_mismatch")
69
+ async with connect_gateway(endpoint) as client:
70
+ websocket = client.websocket
71
+ # GatewayClient queues server requests as events. Drain continuously, including
72
+ # during an RPC, so unsupported sudo/secure inputs never wait for a Web renderer.
73
+ events = asyncio.Queue(maxsize=4096)
74
+
75
+ async def drain():
76
+ while True:
77
+ frame = await client.events.get()
78
+ if isinstance(frame, dict) and frame.get("method") in {"sudo", "secret"} and "id" in frame:
79
+ await websocket.send(json.dumps({"jsonrpc": "2.0", "id": frame["id"], "result": {"value": ""}}))
80
+ else:
81
+ await events.put(frame)
82
+
83
+ reader = asyncio.create_task(drain())
84
+ try:
85
+ yield client, events
86
+ finally:
87
+ reader.cancel()
88
+ with suppress(asyncio.CancelledError):
89
+ await reader
90
+
91
+
92
+ async def _run(ctx, params: dict, home: Path, discovery) -> dict:
93
+ from hermes_cli.gateway_client import GatewayRPCError
94
+
95
+ endpoint = discovery.endpoint
96
+ # v1's served LOCAL route can lose its frozen policy when the shared runner pins
97
+ # transport identity. A dedicated owner's profile is isolated; a served profile
98
+ # needs a verified execution contract before Talaria may submit through it.
99
+ if endpoint.control_home and Path(endpoint.control_home).resolve() != home:
100
+ return _refused("incompatible", "served_profile_sessions_unavailable")
101
+ subscription = None
102
+ async with _connect(home, endpoint) as (client, events):
103
+ described = await client.rpc("runtime.describe")
104
+ if (described.get("instance_id") != endpoint.instance_id
105
+ or described.get("profile_id") != endpoint.profile_id
106
+ or described.get("authority_epoch") != endpoint.authority_epoch
107
+ or not _CAPABILITIES.issubset(described.get("capabilities", []))):
108
+ return _refused("incompatible", "owner_capabilities")
109
+ sid = params["session_id"]
110
+
111
+ async def snapshot():
112
+ nonlocal subscription, sid
113
+ value = await client.rpc("session.resume", session_id=sid)
114
+ sid = value["session_id"]
115
+ # Native attach reuses this actor's subscription; overflow retires it before
116
+ # a replacement is issued. A resume does not accumulate live subscriptions.
117
+ subscription = value["subscription_id"]
118
+ if reason := await asyncio.to_thread(_control_refusal, home, sid):
119
+ raise GatewayRPCError(reason)
120
+ return _snapshot(value)
121
+
122
+ receipt = None
123
+ try:
124
+ snap = await snapshot()
125
+ action = params["action"]
126
+ common = {"session_id": sid}
127
+ if action == "submit":
128
+ # The caller retains this identity across lost ACKs. An ambiguous error never
129
+ # causes this adapter to generate a replacement or replay started work.
130
+ receipt = await client.rpc("prompt.submit", **common, submission_id=params["request_id"], text=params["text"])
131
+ elif action == "receipt":
132
+ receipt = await client.rpc("prompt.receipt", **common, admission_id=params["admission_id"])
133
+ elif action in {"cancel", "discard"}:
134
+ method = "prompt.cancel" if action == "cancel" else "prompt.resolve_unknown"
135
+ receipt = await client.rpc(method, **common, admission_id=params["admission_id"],
136
+ **({"execution_generation": params["execution_generation"]} if action == "discard" else {}))
137
+ elif action == "interrupt":
138
+ await client.rpc("session.interrupt", **common, execution_generation=params["execution_generation"])
139
+ elif action in {"approval", "clarify"}:
140
+ await client.rpc(action + ".respond", **common, prompt_id=params["prompt_id"],
141
+ execution_generation=params["execution_generation"],
142
+ **({"choice": params["choice"]} if action == "approval" else {"answer": params["text"]}))
143
+ elif action == "mutate":
144
+ await client.rpc("session.mutate", **common, request_id=params["request_id"],
145
+ expected_revision=params["expected_revision"], expected_generation=params["execution_generation"],
146
+ operation=params["operation"], payload=params["payload"], _timeout=660)
147
+ if action not in {"snapshot", "watch"}:
148
+ snap = await snapshot()
149
+ result = {"state": "attached", "reason": None, "snapshot": snap, "receipt": receipt}
150
+ if action != "watch":
151
+ return result
152
+ ctx.emit("snapshot", result)
153
+ epoch, sequence = snap["replay_epoch"], snap["last_sequence"]
154
+ # Rebuild the active turn's display from retained events, even when its initial
155
+ # tokens predate this viewer. A gap replaces the subscription, never invents replay.
156
+ replay = await client.rpc("session.events.since", **common, replay_epoch=epoch, last_sequence=0)
157
+ if replay.get("snapshot_required"):
158
+ snap = await snapshot()
159
+ ctx.emit("snapshot", {**result, "snapshot": snap, "replay_gap": True})
160
+ epoch, sequence = snap["replay_epoch"], snap["last_sequence"]
161
+ else:
162
+ for event in replay["events"]:
163
+ if (event.get("execution_generation") == snap["execution_generation"]
164
+ or event.get("type") == "session.info" and event.get("payload", {}).get("execution_generation") == snap["execution_generation"]):
165
+ ctx.emit("event", event)
166
+ sequence = replay["last_sequence"]
167
+ while not ctx.cancelled:
168
+ try:
169
+ frame = await asyncio.wait_for(events.get(), .25)
170
+ except TimeoutError:
171
+ continue
172
+ if isinstance(frame, Exception):
173
+ raise frame
174
+ if frame.get("method") == "clarify" and "id" in frame:
175
+ raise GatewayRPCError("native_clarify_batch_unavailable")
176
+ event = frame.get("params", {})
177
+ if event.get("session_id") != sid:
178
+ continue
179
+ if event.get("type") == "session.info":
180
+ if reason := await asyncio.to_thread(_control_refusal, home, sid):
181
+ raise GatewayRPCError(reason)
182
+ if (event.get("type") == "session.replay_gap" or event.get("replay_epoch") != epoch
183
+ or event.get("seq", sequence + 1) > sequence + 1):
184
+ snap = await snapshot()
185
+ ctx.emit("snapshot", {**result, "snapshot": snap, "replay_gap": True})
186
+ epoch, sequence = snap["replay_epoch"], snap["last_sequence"]
187
+ continue
188
+ if event.get("seq", 0) <= sequence:
189
+ continue
190
+ sequence = event["seq"]
191
+ ctx.emit("event", event)
192
+ return result
193
+ except GatewayRPCError as exc:
194
+ return {**_refused("incompatible" if str(exc) in _CONTROL_REFUSALS else "refused", str(exc)), "receipt": receipt}
195
+ except Exception: # noqa: BLE001 - native transport/contract failures never authorize another writer
196
+ # A received receipt remains admission proof even if the following observation
197
+ # disconnects. Losing that ACK would invite a new request identity for admitted work.
198
+ return {**_refused("unavailable", "owner_connection_lost"), "receipt": receipt}
199
+ finally:
200
+ if subscription:
201
+ with suppress(Exception):
202
+ await client.rpc("session.detach", session_id=sid, subscription_id=subscription)
203
+
204
+
205
+ def session(ctx, params: dict) -> dict:
206
+ home = profile_home_param(params).resolve()
207
+ action = params.get("action", "snapshot")
208
+ sid = params.get("session_id")
209
+ if action not in _ACTIONS or not isinstance(sid, str) or not sid.strip() or sid != sid.strip():
210
+ raise InvalidParams("session_id and supported action are required")
211
+ params = {**params, "action": action}
212
+ required = {
213
+ "submit": ("request_id", "text"), "receipt": ("admission_id",), "cancel": ("admission_id",),
214
+ "interrupt": ("execution_generation",), "approval": ("execution_generation", "prompt_id", "choice"),
215
+ "clarify": ("execution_generation", "prompt_id", "text"), "discard": ("execution_generation", "admission_id"),
216
+ "mutate": ("request_id", "expected_revision", "execution_generation", "operation", "payload"),
217
+ }.get(action, ())
218
+ if any(key not in params for key in required):
219
+ raise InvalidParams("missing gateway session action fields")
220
+ try:
221
+ from hermes_constants import set_hermes_home_override, reset_hermes_home_override # noqa: F401
222
+ from hermes_cli.gateway_runtime import discover_gateway_endpoint
223
+ from hermes_cli.gateway_client import connect_gateway # noqa: F401
224
+ except ImportError:
225
+ return _refused("incompatible", "native_sessions_unavailable")
226
+ with scoped_home(home):
227
+ discovery = discover_gateway_endpoint(home)
228
+ if discovery.state != "ready" or discovery.endpoint is None:
229
+ return _refused("stopped" if discovery.state in {"stopped", "absent"} else "unavailable", discovery.reason_code or discovery.state)
230
+ try:
231
+ return asyncio.run(_run(ctx, params, home, discovery))
232
+ except Exception: # noqa: BLE001 - native transport/contract failures never authorize another writer
233
+ # Do not expose peer diagnostics, endpoint paths or bootstrap credentials.
234
+ return _refused("unavailable", "owner_connection_lost")
@@ -1,7 +1,8 @@
1
1
  """``goals.*``: Hermes persistent session goals (ported from api/goals.py).
2
2
 
3
- The server passes the session's ``profile_home`` and the configured turn
4
- budget; the sidecar never reads WebUI settings itself.
3
+ The server passes the session's ``profile_home``, its workspace and the configured
4
+ turn budget; the sidecar never reads WebUI settings itself. ``/goal`` parses through
5
+ the Agent's own ``dispatch_goal_command``, like its CLI, gateway and TUI.
5
6
  """
6
7
 
7
8
  from __future__ import annotations
@@ -14,7 +15,7 @@ from pathlib import Path
14
15
  from typing import Any
15
16
 
16
17
  from ..errors import InvalidParams, RpcError
17
- from ..home import profile_home_param, scoped_home
18
+ from ..home import profile_home_param, scoped_home, session_workspace
18
19
  from ..rpc import CallContext
19
20
 
20
21
  log = logging.getLogger("talaria_sidecar.goals")
@@ -29,17 +30,18 @@ def _native():
29
30
 
30
31
 
31
32
  class _Manager:
32
- """Native GoalManager scoped to one profile home."""
33
+ """Native GoalManager scoped to one profile home and the session's workspace, where its gates run."""
33
34
 
34
- def __init__(self, session_id: str, profile_home: Path, default_max_turns: int):
35
+ def __init__(self, session_id: str, profile_home: Path, default_max_turns: int, workspace: str | None = None):
35
36
  _, default, GoalManager, _, _ = _native()
36
37
  self.session_id = session_id
37
38
  self.profile_home = profile_home
39
+ self.workspace = workspace
38
40
  with scoped_home(profile_home):
39
41
  self._manager = GoalManager(session_id=session_id, default_max_turns=int(default_max_turns or default or 20))
40
42
 
41
43
  def scoped(self, func, *args, **kwargs):
42
- with scoped_home(self.profile_home):
44
+ with scoped_home(self.profile_home), session_workspace(self.workspace):
43
45
  return func(*args, **kwargs)
44
46
 
45
47
  @property
@@ -123,13 +125,22 @@ def _decision_payload(decision: dict, state: Any) -> dict:
123
125
  turns_used, max_turns = _turns_from_message(str(decision.get("message") or ""))
124
126
  if status == "done":
125
127
  return {**decision, "message_key": "goal_achieved", "message_args": [reason]}
126
- if status == "paused":
128
+ if status == "paused" and str(getattr(state, "paused_reason", "") or "").startswith("turn budget exhausted"):
127
129
  return {**decision, "message_key": "goal_paused_budget_exhausted", "message_args": [turns_used, max_turns]}
128
130
  if decision.get("should_continue"):
129
131
  return {**decision, "message_key": "goal_continuing", "message_args": [turns_used, max_turns, reason]}
130
132
  return decision
131
133
 
132
134
 
135
+ def _parked(mgr: _Manager) -> dict | None:
136
+ """TAL-808: an active goal's live wait barrier, as the Agent's own ``is_waiting`` (which clears a satisfied one) sees it."""
137
+ if not mgr.is_waiting():
138
+ return None
139
+ state = mgr.state
140
+ until = float(getattr(state, "waiting_until", 0.0) or 0.0)
141
+ return {"reason": getattr(state, "waiting_reason", None) or None, "until": until or None}
142
+
143
+
133
144
  def _inactive(reason: str, status=None) -> dict:
134
145
  return {"status": status, "should_continue": False, "continuation_prompt": None, "verdict": "inactive", "reason": reason, "message": ""}
135
146
 
@@ -139,7 +150,41 @@ def _manager_from(params: dict) -> _Manager:
139
150
  if not session_id:
140
151
  raise InvalidParams("session_id is required")
141
152
  max_turns = params.get("default_max_turns") or 20
142
- return _Manager(session_id, profile_home_param(params), int(max_turns))
153
+ return _Manager(session_id, profile_home_param(params), int(max_turns), str(params.get("workspace") or "") or None)
154
+
155
+
156
+ #: Older Agents run gates in their launch directory, where a gate checks another project and can pass for it.
157
+ _GATES_NEED_WORKSPACE = "Goal gates need Hermes Agent 0.21.6 or later, which runs them in the session workspace."
158
+
159
+
160
+ def _gates_run_in_workspace() -> bool:
161
+ from hermes_cli import goals as agent_goals
162
+
163
+ return hasattr(agent_goals, "_gate_workspace")
164
+
165
+
166
+ #: The response ``action`` of each control verb the Agent dispatches; any other command sets the goal.
167
+ _CONTROL_ACTIONS = {
168
+ "": "status", "status": "status", "show": "show", "pause": "pause", "resume": "resume", "continue": "resume",
169
+ "unpause": "resume", "clear": "clear", "stop": "clear", "done": "clear", "unwait": "unwait", "wait": "wait", "gate": "gate",
170
+ }
171
+
172
+
173
+ def _localized(action: str, state: Any, had_goal: bool) -> dict:
174
+ """``message_key``/``message_args`` for the verbs Web already localizes; other verbs show the Agent's text."""
175
+ status = str(getattr(state, "status", "") or "")
176
+ goal = str(getattr(state, "goal", "") or "")
177
+ if action == "status":
178
+ return {k: v for k, v in _status_payload(state).items() if k != "message"}
179
+ if action == "set":
180
+ return {"message_key": "goal_set", "message_args": [int(getattr(state, "max_turns", 0) or 0), goal]}
181
+ if action == "pause":
182
+ return {"message_key": "goal_paused", "message_args": [goal]} if had_goal and status == "paused" else {"message_key": "goal_no_goal"}
183
+ if action == "resume":
184
+ return {"message_key": "goal_resumed", "message_args": [goal]} if had_goal and status == "active" else {"message_key": "goal_no_goal"}
185
+ if action == "clear":
186
+ return {"message_key": "goal_cleared" if had_goal else "goal_no_goal"}
187
+ return {}
143
188
 
144
189
 
145
190
  def register(registry) -> None:
@@ -147,45 +192,35 @@ def register(registry) -> None:
147
192
  def get(ctx: CallContext, params: dict) -> dict:
148
193
  mgr = _manager_from(params)
149
194
  state = mgr.state
150
- return {"goal": _state_payload(state), "active": bool(state is not None and getattr(state, "status", None) == "active"), **_status_payload(state)}
195
+ active = bool(state is not None and getattr(state, "status", None) == "active")
196
+ return {"goal": _state_payload(state), "active": active, "parked": _parked(mgr) if active else None, **_status_payload(state)}
151
197
 
152
198
  @registry.method("goals.command")
153
199
  def command(ctx: CallContext, params: dict) -> dict:
154
- """/goal <args> with the same semantics as the gateway command."""
200
+ """/goal <args> through the Agent's ``dispatch_goal_command``, as its TUI runs it."""
155
201
  mgr = _manager_from(params)
202
+ try:
203
+ from hermes_cli import goals as agent_goals
204
+ from hermes_cli.goal_command import dispatch_goal_command, is_goal_control
205
+ except Exception as exc: # noqa: BLE001
206
+ raise RpcError(f"Hermes goals unavailable: {exc}", condition="goals_unavailable")
156
207
  text = str(params.get("args") or "").strip()
157
- lower = text.lower()
158
- stream_running = bool(params.get("stream_running", False))
159
- if not text or lower == "status":
160
- state = mgr.state
161
- return _payload(action="status", state=state, **_status_payload(state))
162
- if lower == "pause":
163
- state = mgr.pause(reason="user-paused")
164
- if state is None:
165
- return _payload(ok=False, action="pause", error="no_goal", message="No goal set.", message_key="goal_no_goal")
166
- return _payload(action="pause", message=f"⏸ Goal paused: {state.goal}", message_key="goal_paused", message_args=[str(state.goal)], state=state)
167
- if lower == "resume":
168
- state = mgr.resume()
169
- if state is None:
170
- return _payload(ok=False, action="resume", error="no_goal", message="No goal to resume.", message_key="goal_no_goal")
171
- return _payload(action="resume", message=f"▶ Goal resumed: {state.goal}\nSend a new message, or type continue, to kick it off.", message_key="goal_resumed", message_args=[str(state.goal)], state=state)
172
- if lower in ("clear", "stop", "done"):
173
- had = bool(mgr.has_goal())
174
- mgr.clear()
175
- return _payload(action="clear", message="Goal cleared." if had else "No active goal.", message_key="goal_cleared" if had else "goal_no_goal", state=mgr.state)
176
- if stream_running:
208
+ tokens = text.lower().split()
209
+ action = _CONTROL_ACTIONS.get(tokens[0] if tokens else "", "set") if is_goal_control(text) else "set"
210
+ if action == "set" and params.get("stream_running"):
177
211
  return _payload(ok=False, action="set", error="agent_running", message="Agent is running — use /goal status / pause / clear mid-run, or /stop before setting a new goal.")
178
- try:
179
- state = mgr.set(text)
180
- except ValueError as exc:
181
- return _payload(ok=False, action="set", error="invalid_goal", message=f"Invalid goal: {exc}")
182
- return _payload(
183
- action="set",
184
- message=(f"⊙ Goal set ({state.max_turns}-turn budget): {state.goal}\n"
185
- "I'll keep working until the goal is done, you pause/clear it, or the budget is exhausted.\n"
186
- "Controls: /goal status · /goal pause · /goal resume · /goal clear"),
187
- message_key="goal_set", message_args=[state.max_turns, state.goal], state=state, kickoff_prompt=state.goal,
188
- )
212
+ if tokens[:2] == ["gate", "add"] and not _gates_run_in_workspace():
213
+ return _payload(ok=False, action="gate", error="gates_unsupported", state=mgr.state, message=_GATES_NEED_WORKSPACE)
214
+ had_goal = bool(mgr.has_goal())
215
+ # Only goal-setting text reads it: a re-pasted goal kicks off without repeating itself.
216
+ last_user_message = mgr.scoped(agent_goals.last_user_message_from_db, mgr.session_id) if action == "set" else None
217
+ result = mgr.scoped(dispatch_goal_command, mgr._manager, text, authorize_gate=lambda: None, last_user_message=last_user_message)
218
+ state = mgr.state
219
+ if result.error:
220
+ return _payload(ok=False, action=action, error="invalid_goal" if action == "set" else "invalid_command", message=result.output, state=state)
221
+ # A running turn's judge continues a resumed goal itself.
222
+ kickoff = result.prompt if result.prompt and not params.get("stream_running") else None
223
+ return _payload(action=action, message=result.output, state=state, kickoff_prompt=kickoff, **_localized(action, state, had_goal))
189
224
 
190
225
  @registry.method("goals.snapshot")
191
226
  def snapshot(ctx: CallContext, params: dict) -> dict:
@@ -212,6 +247,11 @@ def register(registry) -> None:
212
247
  try:
213
248
  if not mgr.is_active():
214
249
  return _inactive("no active goal", getattr(mgr.state, "status", None) if mgr.state is not None else None)
250
+ # Gates set on a newer Agent persist across a channel switch; this one would run them outside the workspace.
251
+ if getattr(mgr.state, "gates", None) and not _gates_run_in_workspace():
252
+ mgr.pause(reason="gates need Hermes Agent 0.21.6 or later")
253
+ return {"status": "paused", "should_continue": False, "continuation_prompt": None, "verdict": "gate_failed", "reason": "gates not run",
254
+ "message": f"⏸ Goal paused — quality gates not run. {_GATES_NEED_WORKSPACE} /goal gate clear, then /goal resume."}
215
255
  decision = mgr.evaluate_after_turn(str(params.get("last_response") or ""), user_initiated=bool(params.get("user_initiated", True)))
216
256
  except Exception as exc: # noqa: BLE001 - never fail a turn on goal evaluation
217
257
  log.debug("goal evaluation failed", exc_info=True)
@@ -7,17 +7,20 @@ server; the sidecar receives typed params.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import functools
10
11
  import json
11
12
  import secrets
12
13
  import time
13
14
  from dataclasses import asdict, is_dataclass
14
- from typing import Any
15
+ from typing import Any, NamedTuple
15
16
 
16
17
  from ..errors import InvalidParams, RpcError
17
18
  from ..home import profile_home_param, scoped_home
18
19
  from ..rpc import CallContext
19
20
 
20
- BOARD_COLUMNS = ["triage", "todo", "ready", "running", "blocked", "done"]
21
+ # Agent 2026.9.24 predates ``kanban_workflow``; its ``VALID_STATUSES`` set holds these columns, in this board order.
22
+ # Delete with that release's entry in ``agent_support.json``.
23
+ _LEGACY_COLUMNS = ("triage", "todo", "scheduled", "ready", "running", "blocked", "review", "done")
21
24
 
22
25
 
23
26
  class NotFound(RpcError):
@@ -38,6 +41,27 @@ def _kb():
38
41
  return kb
39
42
 
40
43
 
44
+ class _Workflow(NamedTuple):
45
+ columns: tuple[tuple[str, str], ...] # (key, label) in board order
46
+ manual: dict | None # src -> [dst]; None on an Agent without a move table
47
+
48
+
49
+ @functools.cache
50
+ def _workflow() -> _Workflow:
51
+ """The Agent's board columns and manual move table from ``kanban_workflow.DEFAULT_WORKFLOW`` (Agent 0.21.6+, TAL-796)."""
52
+ try:
53
+ from hermes_cli.kanban_workflow import DEFAULT_WORKFLOW
54
+ except ImportError:
55
+ statuses = getattr(_kb(), "VALID_STATUSES", _LEGACY_COLUMNS)
56
+ return _Workflow(tuple((key, key.title()) for key in _LEGACY_COLUMNS if key in statuses), None)
57
+ workflow = DEFAULT_WORKFLOW.to_dict()
58
+ return _Workflow(tuple((c["key"], c["label"]) for c in workflow["columns"]), workflow["manual"])
59
+
60
+
61
+ def _columns() -> list[str]:
62
+ return [key for key, _ in _workflow().columns]
63
+
64
+
41
65
  def _board(params: dict):
42
66
  raw = params.get("board")
43
67
  if raw is None or (isinstance(raw, str) and raw.strip() == ""):
@@ -49,9 +73,16 @@ def _board(params: dict):
49
73
  raise InvalidParams(f"invalid board slug: {raw!r}") from exc
50
74
  if not normed:
51
75
  return None
52
- default_slug = getattr(kb, "DEFAULT_BOARD", "default")
53
- if normed != default_slug and not kb.board_exists(normed):
54
- raise NotFound(f"board {normed!r} does not exist")
76
+ if normed == getattr(kb, "DEFAULT_BOARD", "default"):
77
+ return normed
78
+ return _live_board(kb, normed, normed)
79
+
80
+
81
+ def _live_board(kb, normed: str, shown, *, archived_ok: bool = False) -> str:
82
+ """``normed`` when it names a live board. Agent 0.21.6 leaves an ``archived`` ``board.json`` tombstone at an
83
+ archived board's slug, so ``board_exists`` alone would let a stale link reach it (TAL-795)."""
84
+ if not kb.board_exists(normed) or (not archived_ok and kb.read_board_metadata(normed).get("archived")):
85
+ raise NotFound(f"board {shown!r} does not exist")
55
86
  return normed
56
87
 
57
88
 
@@ -197,6 +228,8 @@ def _task_dict(task, conn):
197
228
  # stored or supplied evidence, and refuses to release a live worker's claim.
198
229
  data["claim_live"] = data.get("status") == "running" and _claim_live(conn, data.get("id"))
199
230
  data["has_completion_evidence"] = bool(str(data.get("result") or "").strip())
231
+ manual = _workflow().manual
232
+ data["agent_move_targets"] = None if manual is None else list(manual.get(data.get("status"), []))
200
233
  try:
201
234
  age = _kb().task_age(task)
202
235
  except Exception: # noqa: BLE001
@@ -237,8 +270,13 @@ def _comment_counts(conn):
237
270
 
238
271
  def _validate_status(status: str) -> str:
239
272
  value = str(status or "").strip().lower()
240
- if value not in set(BOARD_COLUMNS) | {"archived"}:
273
+ if value not in {*_columns(), "archived"}:
241
274
  raise InvalidParams(f"invalid status: {value}")
275
+ if value == "running":
276
+ raise InvalidParams("Cannot set status to 'running' directly; use the dispatcher/claim path")
277
+ if value in ("scheduled", "review"):
278
+ # The Agent reaches these through verbs with extra input (a wake time, a reviewer), which Talaria does not offer.
279
+ raise InvalidParams(f"Cannot set status to {value!r} directly")
242
280
  return value
243
281
 
244
282
 
@@ -327,19 +365,21 @@ def _patch_task(conn, task_id: str, body: dict) -> None:
327
365
  elif status == "archived":
328
366
  if not _fenced(conn, task_id, lambda: kb.archive_task(conn, task_id), confirm_running_exit=confirm, releases_claim=False):
329
367
  raise NotFound("task not found")
330
- elif status == "running":
331
- raise InvalidParams("Cannot set status to 'running' directly; use the dispatcher/claim path")
332
- elif status == "ready":
368
+ else:
333
369
  current = kb.get_task(conn, task_id)
334
370
  if not current:
335
371
  raise NotFound("task not found")
336
- if current.status == "blocked":
337
- if not kb.unblock_task(conn, task_id):
338
- raise NotFound("task not found")
339
- elif not _set_status_direct(conn, task_id, "ready", confirm_running_exit=confirm):
340
- raise NotFound("task not found")
341
- elif status in ("triage", "todo"):
342
- if not _set_status_direct(conn, task_id, status, confirm_running_exit=confirm):
372
+ # Like the Agent's dashboard: leaving review reopens it, and blocked/scheduled -> ready unblocks; both re-gate on
373
+ # parents. Triage is a direct write there too. The reopen lands in ready whatever the target, so todo is refused.
374
+ if current.status == "review" and status == "todo":
375
+ raise Conflict("A task in review reopens to ready")
376
+ if current.status == "review" and status == "ready":
377
+ ok = kb.reopen_review_task(conn, task_id)
378
+ elif status == "ready" and current.status in ("blocked", "scheduled"):
379
+ ok = kb.unblock_task(conn, task_id)
380
+ else:
381
+ ok = _set_status_direct(conn, task_id, status, confirm_running_exit=confirm)
382
+ if not ok:
343
383
  raise NotFound("task not found")
344
384
 
345
385
 
@@ -373,15 +413,15 @@ def _board_counts(slug):
373
413
  pass
374
414
 
375
415
 
376
- def _normalize_existing_slug(slug) -> str:
416
+ def _normalize_existing_slug(slug, *, archived_ok: bool = False) -> str:
377
417
  kb = _kb()
378
418
  try:
379
419
  normed = kb._normalize_board_slug(slug)
380
420
  except (ValueError, AttributeError) as exc:
381
421
  raise InvalidParams(f"invalid board slug: {slug!r}") from exc
382
- if not normed or not kb.board_exists(normed):
422
+ if not normed:
383
423
  raise NotFound(f"board {slug!r} does not exist")
384
- return normed
424
+ return _live_board(kb, normed, slug, archived_ok=archived_ok)
385
425
 
386
426
 
387
427
  def _current_board() -> str:
@@ -449,9 +489,9 @@ def board_payload(params: dict) -> dict:
449
489
  data["comment_count"] = comment_counts.get(task.id, 0)
450
490
  return data
451
491
 
452
- columns = [{"name": name, "tasks": [row(t) for t in tasks if t.status == name]} for name in BOARD_COLUMNS]
492
+ columns = [{"name": key, "label": label, "tasks": [row(t) for t in tasks if t.status == key]} for key, label in _workflow().columns]
453
493
  if include_archived:
454
- columns.append({"name": "archived", "tasks": [row(t) for t in tasks if t.status == "archived"]})
494
+ columns.append({"name": "archived", "label": "Archived", "tasks": [row(t) for t in tasks if t.status == "archived"]})
455
495
  return {
456
496
  "columns": columns,
457
497
  "tenants": sorted({t.tenant for t in tasks if getattr(t, "tenant", None)}),
@@ -472,6 +512,8 @@ def create_task_payload(params: dict) -> dict:
472
512
  priority = int(body.get("priority") or 0)
473
513
  except (TypeError, ValueError):
474
514
  raise InvalidParams("priority must be an integer")
515
+ # Checked before the insert, so a refused status leaves no task behind.
516
+ status = _validate_status(body.get("status")) if body.get("status") else None
475
517
  kb = _kb()
476
518
  with _conn(board=_board(params)) as conn:
477
519
  task_id = kb.create_task(
@@ -480,8 +522,8 @@ def create_task_payload(params: dict) -> dict:
480
522
  workspace_kind=body.get("workspace_kind") or "scratch", workspace_path=body.get("workspace_path") or None,
481
523
  idempotency_key=body.get("idempotency_key") or None, max_runtime_seconds=body.get("max_runtime_seconds") or None, skills=body.get("skills") or None,
482
524
  )
483
- if body.get("status"):
484
- _patch_task(conn, task_id, {"status": body.get("status")})
525
+ if status:
526
+ _patch_task(conn, task_id, {"status": status})
485
527
  return {"task": _task_dict(kb.get_task(conn, task_id), conn), "read_only": False}
486
528
 
487
529
 
@@ -534,11 +576,17 @@ def task_detail_payload(params: dict) -> dict:
534
576
  task = kb.get_task(conn, task_id)
535
577
  if not task:
536
578
  raise NotFound("task not found")
579
+ data = _task_dict(task, conn)
580
+ # ``started_at`` is the first-ever start; a retried task's run clock counts from its current run (TAL-809).
581
+ data["current_run_started_at"] = kb.current_run_started_ats(conn, [task_id]).get(task_id)
582
+ links = {"parents": kb.parent_ids(conn, task_id), "children": kb.child_ids(conn, task_id)}
583
+ linked = (kb.get_task(conn, link_id) for link_id in dict.fromkeys([*links["parents"], *links["children"]]))
537
584
  return {
538
- "task": _task_dict(task, conn),
585
+ "task": data,
539
586
  "comments": [_obj_dict(c) for c in kb.list_comments(conn, task_id)],
540
587
  "events": [_obj_dict(e) for e in kb.list_events(conn, task_id)],
541
- "links": {"parents": kb.parent_ids(conn, task_id), "children": kb.child_ids(conn, task_id)},
588
+ "links": links,
589
+ "link_tasks": [{"id": t.id, "title": t.title, "status": t.status} for t in linked if t],
542
590
  "runs": [_obj_dict(r) for r in kb.list_runs(conn, task_id)],
543
591
  "read_only": False,
544
592
  }
@@ -583,13 +631,17 @@ def config_payload(params: dict) -> dict:
583
631
  cfg = {}
584
632
  k = ((cfg.get("dashboard") or {}).get("kanban") or {})
585
633
  return {
586
- "columns": BOARD_COLUMNS, "assignees": assignees, "default_tenant": k.get("default_tenant") or "",
634
+ "columns": _columns(), "assignees": assignees, "default_tenant": k.get("default_tenant") or "",
587
635
  "lane_by_profile": bool(k.get("lane_by_profile", True)), "include_archived_by_default": bool(k.get("include_archived_by_default", False)),
588
636
  "render_markdown": bool(k.get("render_markdown", True)), "read_only": False,
589
637
  }
590
638
 
591
639
 
592
640
  def stats_payload(params: dict) -> dict:
641
+ return {**_stats(params), "column_order": _columns()}
642
+
643
+
644
+ def _stats(params: dict) -> dict:
593
645
  kb = _kb()
594
646
  with _conn(board=_board(params)) as conn:
595
647
  if hasattr(kb, "board_stats"):
@@ -761,7 +813,8 @@ def create_board_payload(params: dict) -> dict:
761
813
  def update_board_payload(params: dict) -> dict:
762
814
  kb = _kb()
763
815
  body = params.get("board_spec") or {}
764
- normed = _normalize_existing_slug(params.get("slug"))
816
+ # Metadata edits stay open on an archived board: clearing ``archived`` is how one archived in place comes back.
817
+ normed = _normalize_existing_slug(params.get("slug"), archived_ok=True)
765
818
  kwargs = {}
766
819
  if "default_workdir" in body:
767
820
  kwargs["default_workdir"] = str(body.get("default_workdir") or "")