@owlmeans/viable-sdk 0.1.18-rc.8 → 0.1.18

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 (498) hide show
  1. package/README.md +29 -11
  2. package/agent-meta/manifest.json +16 -0
  3. package/agent-meta/skills/viable-sdk/SKILL.md +1033 -0
  4. package/build/api/collect/types.d.ts +30 -0
  5. package/build/api/collect/types.d.ts.map +1 -0
  6. package/build/api/collect/types.js +2 -0
  7. package/build/api/collect/types.js.map +1 -0
  8. package/build/api/collect.d.ts +14 -0
  9. package/build/api/collect.d.ts.map +1 -0
  10. package/build/api/collect.js +132 -0
  11. package/build/api/collect.js.map +1 -0
  12. package/build/api/consts.local.d.ts +21 -0
  13. package/build/api/consts.local.d.ts.map +1 -0
  14. package/build/api/consts.local.js +25 -0
  15. package/build/api/consts.local.js.map +1 -0
  16. package/build/api/index.d.ts +5 -0
  17. package/build/api/index.d.ts.map +1 -1
  18. package/build/api/index.js +2 -0
  19. package/build/api/index.js.map +1 -1
  20. package/build/api/remote.d.ts +13 -6
  21. package/build/api/remote.d.ts.map +1 -1
  22. package/build/api/remote.js +302 -35
  23. package/build/api/remote.js.map +1 -1
  24. package/build/api/transport/types.d.ts +14 -0
  25. package/build/api/transport/types.d.ts.map +1 -0
  26. package/build/api/transport/types.js +2 -0
  27. package/build/api/transport/types.js.map +1 -0
  28. package/build/api/transport.d.ts +4 -0
  29. package/build/api/transport.d.ts.map +1 -0
  30. package/build/api/transport.js +32 -0
  31. package/build/api/transport.js.map +1 -0
  32. package/build/api/types.d.ts +9 -0
  33. package/build/api/types.d.ts.map +1 -0
  34. package/build/api/types.js +2 -0
  35. package/build/api/types.js.map +1 -0
  36. package/build/api/types.local.d.ts +3 -0
  37. package/build/api/types.local.d.ts.map +1 -0
  38. package/build/api/types.local.js +2 -0
  39. package/build/api/types.local.js.map +1 -0
  40. package/build/consts.d.ts +29 -14
  41. package/build/consts.d.ts.map +1 -1
  42. package/build/consts.js +30 -14
  43. package/build/consts.js.map +1 -1
  44. package/build/context/consts.local.d.ts +18 -0
  45. package/build/context/consts.local.d.ts.map +1 -0
  46. package/build/context/consts.local.js +18 -0
  47. package/build/context/consts.local.js.map +1 -0
  48. package/build/context/index.d.ts +3 -7
  49. package/build/context/index.d.ts.map +1 -1
  50. package/build/context/index.js +58 -25
  51. package/build/context/index.js.map +1 -1
  52. package/build/context/types.d.ts +26 -0
  53. package/build/context/types.d.ts.map +1 -0
  54. package/build/context/types.js +2 -0
  55. package/build/context/types.js.map +1 -0
  56. package/build/executor/boot-check.d.ts +1 -40
  57. package/build/executor/boot-check.d.ts.map +1 -1
  58. package/build/executor/boot-check.js +22 -19
  59. package/build/executor/boot-check.js.map +1 -1
  60. package/build/executor/consts.d.ts +55 -0
  61. package/build/executor/consts.d.ts.map +1 -0
  62. package/build/executor/consts.js +66 -0
  63. package/build/executor/consts.js.map +1 -0
  64. package/build/executor/consts.local.d.ts +63 -0
  65. package/build/executor/consts.local.d.ts.map +1 -0
  66. package/build/executor/consts.local.js +69 -0
  67. package/build/executor/consts.local.js.map +1 -0
  68. package/build/executor/env/types.d.ts +40 -0
  69. package/build/executor/env/types.d.ts.map +1 -0
  70. package/build/executor/env/types.js +2 -0
  71. package/build/executor/env/types.js.map +1 -0
  72. package/build/executor/env.d.ts +11 -20
  73. package/build/executor/env.d.ts.map +1 -1
  74. package/build/executor/env.js +39 -59
  75. package/build/executor/env.js.map +1 -1
  76. package/build/executor/errors.d.ts +15 -0
  77. package/build/executor/errors.d.ts.map +1 -1
  78. package/build/executor/errors.js +19 -2
  79. package/build/executor/errors.js.map +1 -1
  80. package/build/executor/files/types.d.ts +106 -0
  81. package/build/executor/files/types.d.ts.map +1 -0
  82. package/build/executor/files/types.js +2 -0
  83. package/build/executor/files/types.js.map +1 -0
  84. package/build/executor/files.d.ts +2 -50
  85. package/build/executor/files.d.ts.map +1 -1
  86. package/build/executor/files.js +225 -68
  87. package/build/executor/files.js.map +1 -1
  88. package/build/executor/git/types.d.ts +19 -0
  89. package/build/executor/git/types.d.ts.map +1 -0
  90. package/build/executor/git/types.js +2 -0
  91. package/build/executor/git/types.js.map +1 -0
  92. package/build/executor/git.d.ts +4 -36
  93. package/build/executor/git.d.ts.map +1 -1
  94. package/build/executor/git.js +206 -241
  95. package/build/executor/git.js.map +1 -1
  96. package/build/executor/glob/types.d.ts +12 -0
  97. package/build/executor/glob/types.d.ts.map +1 -0
  98. package/build/executor/glob/types.js +2 -0
  99. package/build/executor/glob/types.js.map +1 -0
  100. package/build/executor/glob.d.ts +4 -0
  101. package/build/executor/glob.d.ts.map +1 -0
  102. package/build/executor/glob.js +11 -0
  103. package/build/executor/glob.js.map +1 -0
  104. package/build/executor/health/types.d.ts +39 -0
  105. package/build/executor/health/types.d.ts.map +1 -0
  106. package/build/executor/health/types.js +2 -0
  107. package/build/executor/health/types.js.map +1 -0
  108. package/build/executor/health.d.ts +3 -83
  109. package/build/executor/health.d.ts.map +1 -1
  110. package/build/executor/health.js +76 -91
  111. package/build/executor/health.js.map +1 -1
  112. package/build/executor/index.d.ts +14 -15
  113. package/build/executor/index.d.ts.map +1 -1
  114. package/build/executor/index.js +36 -18
  115. package/build/executor/index.js.map +1 -1
  116. package/build/executor/integrity/types.d.ts +25 -0
  117. package/build/executor/integrity/types.d.ts.map +1 -0
  118. package/build/executor/integrity/types.js +2 -0
  119. package/build/executor/integrity/types.js.map +1 -0
  120. package/build/executor/integrity.d.ts +5 -15
  121. package/build/executor/integrity.d.ts.map +1 -1
  122. package/build/executor/integrity.js +28 -39
  123. package/build/executor/integrity.js.map +1 -1
  124. package/build/executor/layout/types.d.ts +69 -0
  125. package/build/executor/layout/types.d.ts.map +1 -0
  126. package/build/executor/layout/types.js +2 -0
  127. package/build/executor/layout/types.js.map +1 -0
  128. package/build/executor/layout.d.ts +9 -56
  129. package/build/executor/layout.d.ts.map +1 -1
  130. package/build/executor/layout.js +35 -82
  131. package/build/executor/layout.js.map +1 -1
  132. package/build/executor/shell/types.d.ts +76 -0
  133. package/build/executor/shell/types.d.ts.map +1 -0
  134. package/build/executor/shell/types.js +2 -0
  135. package/build/executor/shell/types.js.map +1 -0
  136. package/build/executor/shell.d.ts +3 -78
  137. package/build/executor/shell.d.ts.map +1 -1
  138. package/build/executor/shell.js +61 -91
  139. package/build/executor/shell.js.map +1 -1
  140. package/build/executor/spawn/types.d.ts +98 -0
  141. package/build/executor/spawn/types.d.ts.map +1 -0
  142. package/build/executor/spawn/types.js +2 -0
  143. package/build/executor/spawn/types.js.map +1 -0
  144. package/build/executor/spawn.d.ts +3 -103
  145. package/build/executor/spawn.d.ts.map +1 -1
  146. package/build/executor/spawn.js +192 -274
  147. package/build/executor/spawn.js.map +1 -1
  148. package/build/executor/types.d.ts +123 -0
  149. package/build/executor/types.d.ts.map +1 -0
  150. package/build/executor/types.js +2 -0
  151. package/build/executor/types.js.map +1 -0
  152. package/build/executor/types.local.d.ts +6 -0
  153. package/build/executor/types.local.d.ts.map +1 -0
  154. package/build/executor/types.local.js +2 -0
  155. package/build/executor/types.local.js.map +1 -0
  156. package/build/index.d.ts +14 -1
  157. package/build/index.d.ts.map +1 -1
  158. package/build/index.js +5 -1
  159. package/build/index.js.map +1 -1
  160. package/build/project/consts.d.ts +17 -0
  161. package/build/project/consts.d.ts.map +1 -0
  162. package/build/project/consts.js +17 -0
  163. package/build/project/consts.js.map +1 -0
  164. package/build/project/consts.local.d.ts +4 -0
  165. package/build/project/consts.local.d.ts.map +1 -0
  166. package/build/project/consts.local.js +4 -0
  167. package/build/project/consts.local.js.map +1 -0
  168. package/build/project/dotenv/types.d.ts +39 -0
  169. package/build/project/dotenv/types.d.ts.map +1 -0
  170. package/build/project/dotenv/types.js +2 -0
  171. package/build/project/dotenv/types.js.map +1 -0
  172. package/build/project/dotenv.d.ts +4 -0
  173. package/build/project/dotenv.d.ts.map +1 -0
  174. package/build/project/dotenv.js +56 -0
  175. package/build/project/dotenv.js.map +1 -0
  176. package/build/project/env/types.d.ts +40 -0
  177. package/build/project/env/types.d.ts.map +1 -0
  178. package/build/project/env/types.js +2 -0
  179. package/build/project/env/types.js.map +1 -0
  180. package/build/project/env.d.ts +2 -98
  181. package/build/project/env.d.ts.map +1 -1
  182. package/build/project/env.js +63 -215
  183. package/build/project/env.js.map +1 -1
  184. package/build/project/marker/types.d.ts +33 -0
  185. package/build/project/marker/types.d.ts.map +1 -0
  186. package/build/project/marker/types.js +2 -0
  187. package/build/project/marker/types.js.map +1 -0
  188. package/build/project/marker.d.ts +4 -25
  189. package/build/project/marker.d.ts.map +1 -1
  190. package/build/project/marker.js +40 -43
  191. package/build/project/marker.js.map +1 -1
  192. package/build/project/probe/types.d.ts +22 -0
  193. package/build/project/probe/types.d.ts.map +1 -0
  194. package/build/project/probe/types.js +2 -0
  195. package/build/project/probe/types.js.map +1 -0
  196. package/build/project/probe.d.ts +4 -0
  197. package/build/project/probe.d.ts.map +1 -0
  198. package/build/project/probe.js +39 -0
  199. package/build/project/probe.js.map +1 -0
  200. package/build/project/report/types.d.ts +18 -0
  201. package/build/project/report/types.d.ts.map +1 -0
  202. package/build/project/report/types.js +2 -0
  203. package/build/project/report/types.js.map +1 -0
  204. package/build/project/report.d.ts +7 -0
  205. package/build/project/report.d.ts.map +1 -0
  206. package/build/project/report.js +68 -0
  207. package/build/project/report.js.map +1 -0
  208. package/build/project/setup/types.d.ts +32 -0
  209. package/build/project/setup/types.d.ts.map +1 -0
  210. package/build/project/setup/types.js +2 -0
  211. package/build/project/setup/types.js.map +1 -0
  212. package/build/project/setup.d.ts +4 -53
  213. package/build/project/setup.d.ts.map +1 -1
  214. package/build/project/setup.js +112 -175
  215. package/build/project/setup.js.map +1 -1
  216. package/build/project/types.d.ts +37 -0
  217. package/build/project/types.d.ts.map +1 -0
  218. package/build/project/types.js +2 -0
  219. package/build/project/types.js.map +1 -0
  220. package/build/run/consts.local.d.ts +3 -0
  221. package/build/run/consts.local.d.ts.map +1 -0
  222. package/build/run/consts.local.js +26 -0
  223. package/build/run/consts.local.js.map +1 -0
  224. package/build/run/index.d.ts +11 -69
  225. package/build/run/index.d.ts.map +1 -1
  226. package/build/run/index.js +9 -147
  227. package/build/run/index.js.map +1 -1
  228. package/build/run/local/types.d.ts +39 -0
  229. package/build/run/local/types.d.ts.map +1 -0
  230. package/build/run/local/types.js +2 -0
  231. package/build/run/local/types.js.map +1 -0
  232. package/build/run/local.d.ts +3 -0
  233. package/build/run/local.d.ts.map +1 -0
  234. package/build/run/local.js +130 -0
  235. package/build/run/local.js.map +1 -0
  236. package/build/run/process/types.d.ts +15 -0
  237. package/build/run/process/types.d.ts.map +1 -0
  238. package/build/run/process/types.js +2 -0
  239. package/build/run/process/types.js.map +1 -0
  240. package/build/run/process.d.ts +4 -0
  241. package/build/run/process.d.ts.map +1 -0
  242. package/build/run/process.js +20 -0
  243. package/build/run/process.js.map +1 -0
  244. package/build/run/serve.d.ts +14 -9
  245. package/build/run/serve.d.ts.map +1 -1
  246. package/build/run/serve.js +1 -25
  247. package/build/run/serve.js.map +1 -1
  248. package/build/run/state/types.d.ts +35 -0
  249. package/build/run/state/types.d.ts.map +1 -0
  250. package/build/run/state/types.js +2 -0
  251. package/build/run/state/types.js.map +1 -0
  252. package/build/run/state.d.ts +3 -62
  253. package/build/run/state.d.ts.map +1 -1
  254. package/build/run/state.js +96 -123
  255. package/build/run/state.js.map +1 -1
  256. package/build/run/types.d.ts +78 -0
  257. package/build/run/types.d.ts.map +1 -0
  258. package/build/run/types.js +2 -0
  259. package/build/run/types.js.map +1 -0
  260. package/build/session/consts.local.d.ts +5 -0
  261. package/build/session/consts.local.d.ts.map +1 -0
  262. package/build/session/consts.local.js +5 -0
  263. package/build/session/consts.local.js.map +1 -0
  264. package/build/session/index.d.ts +5 -10
  265. package/build/session/index.d.ts.map +1 -1
  266. package/build/session/index.js +84 -15
  267. package/build/session/index.js.map +1 -1
  268. package/build/session/questions.d.ts +16 -0
  269. package/build/session/questions.d.ts.map +1 -0
  270. package/build/session/questions.js +17 -0
  271. package/build/session/questions.js.map +1 -0
  272. package/build/session/queue.d.ts +61 -0
  273. package/build/session/queue.d.ts.map +1 -0
  274. package/build/session/queue.js +156 -0
  275. package/build/session/queue.js.map +1 -0
  276. package/build/session/tasks.d.ts +11 -30
  277. package/build/session/tasks.d.ts.map +1 -1
  278. package/build/session/tasks.js +18 -78
  279. package/build/session/tasks.js.map +1 -1
  280. package/build/session/types.d.ts +20 -0
  281. package/build/session/types.d.ts.map +1 -0
  282. package/build/session/types.js +2 -0
  283. package/build/session/types.js.map +1 -0
  284. package/build/task/consts.d.ts +12 -0
  285. package/build/task/consts.d.ts.map +1 -0
  286. package/build/task/consts.js +12 -0
  287. package/build/task/consts.js.map +1 -0
  288. package/build/task/consts.local.d.ts +19 -0
  289. package/build/task/consts.local.d.ts.map +1 -0
  290. package/build/task/consts.local.js +72 -0
  291. package/build/task/consts.local.js.map +1 -0
  292. package/build/task/driver.d.ts +1 -28
  293. package/build/task/driver.d.ts.map +1 -1
  294. package/build/task/driver.js +2 -9
  295. package/build/task/driver.js.map +1 -1
  296. package/build/task/envelope/types.d.ts +29 -0
  297. package/build/task/envelope/types.d.ts.map +1 -0
  298. package/build/task/envelope/types.js +2 -0
  299. package/build/task/envelope/types.js.map +1 -0
  300. package/build/task/envelope.d.ts +4 -34
  301. package/build/task/envelope.d.ts.map +1 -1
  302. package/build/task/envelope.js +103 -165
  303. package/build/task/envelope.js.map +1 -1
  304. package/build/task/index.d.ts +5 -0
  305. package/build/task/index.d.ts.map +1 -1
  306. package/build/task/index.js +2 -0
  307. package/build/task/index.js.map +1 -1
  308. package/build/task/inquiry/types.d.ts +38 -0
  309. package/build/task/inquiry/types.d.ts.map +1 -0
  310. package/build/task/inquiry/types.js +2 -0
  311. package/build/task/inquiry/types.js.map +1 -0
  312. package/build/task/inquiry.d.ts +5 -0
  313. package/build/task/inquiry.d.ts.map +1 -0
  314. package/build/task/inquiry.js +188 -0
  315. package/build/task/inquiry.js.map +1 -0
  316. package/build/task/types.d.ts +48 -0
  317. package/build/task/types.d.ts.map +1 -0
  318. package/build/task/types.js +2 -0
  319. package/build/task/types.js.map +1 -0
  320. package/build/tools/account/types.d.ts +24 -0
  321. package/build/tools/account/types.d.ts.map +1 -0
  322. package/build/tools/account/types.js +2 -0
  323. package/build/tools/account/types.js.map +1 -0
  324. package/build/tools/account.d.ts +4 -0
  325. package/build/tools/account.d.ts.map +1 -0
  326. package/build/tools/account.js +72 -0
  327. package/build/tools/account.js.map +1 -0
  328. package/build/tools/blueprints/types.d.ts +5 -0
  329. package/build/tools/blueprints/types.d.ts.map +1 -0
  330. package/build/tools/blueprints/types.js +2 -0
  331. package/build/tools/blueprints/types.js.map +1 -0
  332. package/build/tools/blueprints.d.ts +4 -0
  333. package/build/tools/blueprints.d.ts.map +1 -0
  334. package/build/tools/blueprints.js +23 -0
  335. package/build/tools/blueprints.js.map +1 -0
  336. package/build/tools/catalogue/types.d.ts +8 -0
  337. package/build/tools/catalogue/types.d.ts.map +1 -0
  338. package/build/tools/catalogue/types.js +2 -0
  339. package/build/tools/catalogue/types.js.map +1 -0
  340. package/build/tools/catalogue.d.ts +8 -6
  341. package/build/tools/catalogue.d.ts.map +1 -1
  342. package/build/tools/catalogue.js +2565 -213
  343. package/build/tools/catalogue.js.map +1 -1
  344. package/build/tools/config/types.d.ts +19 -0
  345. package/build/tools/config/types.d.ts.map +1 -0
  346. package/build/tools/config/types.js +2 -0
  347. package/build/tools/config/types.js.map +1 -0
  348. package/build/tools/config.d.ts +4 -0
  349. package/build/tools/config.d.ts.map +1 -0
  350. package/build/tools/config.js +42 -0
  351. package/build/tools/config.js.map +1 -0
  352. package/build/tools/consts.d.ts +61 -0
  353. package/build/tools/consts.d.ts.map +1 -0
  354. package/build/tools/consts.js +515 -0
  355. package/build/tools/consts.js.map +1 -0
  356. package/build/tools/consts.local.d.ts +131 -0
  357. package/build/tools/consts.local.d.ts.map +1 -0
  358. package/build/tools/consts.local.js +236 -0
  359. package/build/tools/consts.local.js.map +1 -0
  360. package/build/tools/control/types.d.ts +17 -0
  361. package/build/tools/control/types.d.ts.map +1 -0
  362. package/build/tools/control/types.js +2 -0
  363. package/build/tools/control/types.js.map +1 -0
  364. package/build/tools/control.d.ts +4 -0
  365. package/build/tools/control.d.ts.map +1 -0
  366. package/build/tools/control.js +44 -0
  367. package/build/tools/control.js.map +1 -0
  368. package/build/tools/feed/types.d.ts +21 -0
  369. package/build/tools/feed/types.d.ts.map +1 -0
  370. package/build/tools/feed/types.js +2 -0
  371. package/build/tools/feed/types.js.map +1 -0
  372. package/build/tools/feed.d.ts +4 -0
  373. package/build/tools/feed.d.ts.map +1 -0
  374. package/build/tools/feed.js +75 -0
  375. package/build/tools/feed.js.map +1 -0
  376. package/build/tools/git/types.d.ts +25 -0
  377. package/build/tools/git/types.d.ts.map +1 -0
  378. package/build/tools/git/types.js +2 -0
  379. package/build/tools/git/types.js.map +1 -0
  380. package/build/tools/git.d.ts +4 -0
  381. package/build/tools/git.d.ts.map +1 -0
  382. package/build/tools/git.js +83 -0
  383. package/build/tools/git.js.map +1 -0
  384. package/build/tools/handover/types.d.ts +49 -0
  385. package/build/tools/handover/types.d.ts.map +1 -0
  386. package/build/tools/handover/types.js +2 -0
  387. package/build/tools/handover/types.js.map +1 -0
  388. package/build/tools/handover.d.ts +8 -0
  389. package/build/tools/handover.d.ts.map +1 -0
  390. package/build/tools/handover.js +138 -0
  391. package/build/tools/handover.js.map +1 -0
  392. package/build/tools/host/types.d.ts +34 -0
  393. package/build/tools/host/types.d.ts.map +1 -0
  394. package/build/tools/host/types.js +2 -0
  395. package/build/tools/host/types.js.map +1 -0
  396. package/build/tools/host.d.ts +4 -0
  397. package/build/tools/host.d.ts.map +1 -0
  398. package/build/tools/host.js +13 -0
  399. package/build/tools/host.js.map +1 -0
  400. package/build/tools/iam/types.d.ts +44 -0
  401. package/build/tools/iam/types.d.ts.map +1 -0
  402. package/build/tools/iam/types.js +2 -0
  403. package/build/tools/iam/types.js.map +1 -0
  404. package/build/tools/iam.d.ts +4 -0
  405. package/build/tools/iam.d.ts.map +1 -0
  406. package/build/tools/iam.js +229 -0
  407. package/build/tools/iam.js.map +1 -0
  408. package/build/tools/index.d.ts +27 -1
  409. package/build/tools/index.d.ts.map +1 -1
  410. package/build/tools/index.js +14 -1
  411. package/build/tools/index.js.map +1 -1
  412. package/build/tools/inflight/types.d.ts +47 -0
  413. package/build/tools/inflight/types.d.ts.map +1 -0
  414. package/build/tools/inflight/types.js +2 -0
  415. package/build/tools/inflight/types.js.map +1 -0
  416. package/build/tools/inflight.d.ts +3 -0
  417. package/build/tools/inflight.d.ts.map +1 -0
  418. package/build/tools/inflight.js +88 -0
  419. package/build/tools/inflight.js.map +1 -0
  420. package/build/tools/kits/types.d.ts +12 -0
  421. package/build/tools/kits/types.d.ts.map +1 -0
  422. package/build/tools/kits/types.js +2 -0
  423. package/build/tools/kits/types.js.map +1 -0
  424. package/build/tools/kits.d.ts +4 -0
  425. package/build/tools/kits.d.ts.map +1 -0
  426. package/build/tools/kits.js +33 -0
  427. package/build/tools/kits.js.map +1 -0
  428. package/build/tools/mcp.d.ts +13 -19
  429. package/build/tools/mcp.d.ts.map +1 -1
  430. package/build/tools/mcp.js +117 -38
  431. package/build/tools/mcp.js.map +1 -1
  432. package/build/tools/platform.d.ts +14 -0
  433. package/build/tools/platform.d.ts.map +1 -0
  434. package/build/tools/platform.js +90 -0
  435. package/build/tools/platform.js.map +1 -0
  436. package/build/tools/production/types.d.ts +22 -0
  437. package/build/tools/production/types.d.ts.map +1 -0
  438. package/build/tools/production/types.js +2 -0
  439. package/build/tools/production/types.js.map +1 -0
  440. package/build/tools/production.d.ts +4 -0
  441. package/build/tools/production.d.ts.map +1 -0
  442. package/build/tools/production.js +80 -0
  443. package/build/tools/production.js.map +1 -0
  444. package/build/tools/refusal/types.d.ts +95 -0
  445. package/build/tools/refusal/types.d.ts.map +1 -0
  446. package/build/tools/refusal/types.js +2 -0
  447. package/build/tools/refusal/types.js.map +1 -0
  448. package/build/tools/refusal.d.ts +4 -0
  449. package/build/tools/refusal.d.ts.map +1 -0
  450. package/build/tools/refusal.js +663 -0
  451. package/build/tools/refusal.js.map +1 -0
  452. package/build/tools/settings/types.d.ts +42 -0
  453. package/build/tools/settings/types.d.ts.map +1 -0
  454. package/build/tools/settings/types.js +2 -0
  455. package/build/tools/settings/types.js.map +1 -0
  456. package/build/tools/settings.d.ts +4 -0
  457. package/build/tools/settings.d.ts.map +1 -0
  458. package/build/tools/settings.js +71 -0
  459. package/build/tools/settings.js.map +1 -0
  460. package/build/tools/status/types.d.ts +25 -0
  461. package/build/tools/status/types.d.ts.map +1 -0
  462. package/build/tools/status/types.js +2 -0
  463. package/build/tools/status/types.js.map +1 -0
  464. package/build/tools/status.d.ts +4 -0
  465. package/build/tools/status.d.ts.map +1 -0
  466. package/build/tools/status.js +183 -0
  467. package/build/tools/status.js.map +1 -0
  468. package/build/tools/stories/types.d.ts +48 -0
  469. package/build/tools/stories/types.d.ts.map +1 -0
  470. package/build/tools/stories/types.js +2 -0
  471. package/build/tools/stories/types.js.map +1 -0
  472. package/build/tools/stories.d.ts +8 -0
  473. package/build/tools/stories.d.ts.map +1 -0
  474. package/build/tools/stories.js +79 -0
  475. package/build/tools/stories.js.map +1 -0
  476. package/build/tools/types.d.ts +177 -26
  477. package/build/tools/types.d.ts.map +1 -1
  478. package/build/tools/types.js +1 -32
  479. package/build/tools/types.js.map +1 -1
  480. package/build/types.d.ts +391 -26
  481. package/build/types.d.ts.map +1 -1
  482. package/build/utils.d.ts +8 -0
  483. package/build/utils.d.ts.map +1 -0
  484. package/build/utils.js +12 -0
  485. package/build/utils.js.map +1 -0
  486. package/package.json +30 -34
  487. package/build/harness/index.d.ts +0 -28
  488. package/build/harness/index.d.ts.map +0 -1
  489. package/build/harness/index.js +0 -83
  490. package/build/harness/index.js.map +0 -1
  491. package/build/harness/templates.d.ts +0 -30
  492. package/build/harness/templates.d.ts.map +0 -1
  493. package/build/harness/templates.js +0 -154
  494. package/build/harness/templates.js.map +0 -1
  495. package/build/tools/jobs.d.ts +0 -12
  496. package/build/tools/jobs.d.ts.map +0 -1
  497. package/build/tools/jobs.js +0 -47
  498. package/build/tools/jobs.js.map +0 -1
@@ -0,0 +1,1033 @@
1
+ ---
2
+ name: viable-sdk
3
+ description: How to use @owlmeans/viable-sdk — the connector SDK an external coding agent drives the OwlMeans Viable platform with — the token-authenticated client context, the two host kinds and their tool catalogue, the session operation loop, the local slot executor and local run, the model-task envelope, and the extension seam a host adds its own tools through. Auto-invoked when building or changing a connector, an MCP host, a connector tool, the task envelope, or anything that executes platform slot commands on a developer's machine.
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # @owlmeans/viable-sdk
9
+
10
+ **Layer:** Tooling (Node/Bun; not a browser or React package)
11
+ **Install:** `"@owlmeans/viable-sdk": "^0.1.18"` in `dependencies`
12
+ **Subpaths:** `.` · `./executor` · `./run` · `./tools` · `./task`
13
+ **Contracts:** `@owlmeans/viable-common` (`./connect`, `./slot`, `./integrity`, and the planning
14
+ vocabulary — story type and story flow) and `@owlmeans/planning` (the planning protocol tree
15
+ and facade) — every name on the wire is declared there, so the SDK and the platform cannot spell one
16
+ differently.
17
+
18
+ Everything a coding agent needs in order to drive the platform: authenticate with one access token,
19
+ attach a session to a project, execute the platform's slot commands against a directory on this
20
+ machine, deliver its model calls to the parent agent, and run the generated application locally.
21
+ `@owlmeans/viable-mcp` is one host built on it.
22
+
23
+ ## Key Exports
24
+
25
+ | Export | Description |
26
+ |--------|-------------|
27
+ | `makeSdkContext({ apiUrl, token, service?, timeout?, llm?, onRejected? })` | A client context authenticated by one token — a literal or a thunk — with the connector protocols and the planning tree bound, the planning client registered (`timeout` is its HTTP deadline) and the call-collect transport in front of the API client (`llm: ConnectLlm.Local` turns collection on) |
28
+ | `createCallCollectTransport({ delegated })` → `CallCollectTransport` · `appendCallCollectTransport(context, opts)` · `CallCollectOptions` · `CollectedCall` | The context's HTTP transport (`transport:http`): request now, collect later in the delegated mode |
29
+ | `makeRemoteConnectorApi(context, { timeout? })` · `RemoteConnectorOptions` | `ConnectorApi` over HTTP; its `planning` is the context's planning client facade; `timeout` bounds every request but the session verbs' (one request — a collected call's hops carry their own) |
30
+ | `openSession(opts)` → `SessionRuntime` (`nextTask(waitMs, signal?)`, `taskAvailable(waitMs, signal?)`, …) | One attached session: the pull loop, the serial worker, the task queue, `stats` |
31
+ | `createInflightCalls()` → `InflightCalls` · `makeHandoverHelper(deps)` → `HandoverHelper` (`run`/`contain`/`collect`/`report`/`pending`) | The delegated mode's handover: the calls still running after their tool answered, and how a blocked tool answers with its model task |
32
+ | `makeTaskEnvelopeModel(task)` — `.renderTaskEnvelope({ harness })` · `.parseTaskResult(raw)` | What the parent agent is told; what its answer is checked against |
33
+ | `makeModelTaskDriver({ models })` · `TaskDriver` | A reference parent agent backed by a chat model (tests, CLIs) |
34
+ | `ToolHost.extensions` · `EXTENSION_TOOLS` | Tools the hosting process adds beyond the catalogue (the agent-setup pair `describe_harness` / `install_harness` is one such extension, shipped outside this package); listed, filtered by their own `availability` and registered like the catalogue's own |
35
+ | `catalogue` · `catalogueHelper` (`.visibleTools(host)` · `.toolByName`) · `registerCatalogue(server, deps)` · `serverInstructions({ host })` | The tools and how they reach an MCP server |
36
+ | `statusTextHelper` — `.renderProjectStatus`, `.renderStoryStatus(status, { landing? })`, `.renderPipelineStatus`, `.renderBatchStatus`, `.conversionNext` | Domain status as concise lines ending in the next valid action |
37
+ | `controlToolHelper` — `.stopNeedsConfirm`, `.unconfirmedStop`, `.unconfirmedCancel`, `.renderStop`, `.renderSteer`, `.renderCancel` | The agent controls' words: what a stop rolls back, what a steering message does |
38
+ | `storyHelper` — `.resolveStory(deps, projectId, ref)` · `.storyQuery(projectId, filter?)` · `.renderStories(items, page, total)` · `.isLandingStory(card)`; `STORY_ORDER` · `LANDING_MARK` · `LANDING_NOTE` | The story tools' reading of planning cards |
39
+ | `PROJECT_SETTINGS` · `settingsHelper` — `.projectSettingOf(key)` · `.settingsPatch(args)` · `.renderProjectSettings(projectId, settings)` · `.settingsReach(target)` | The project-settings tools: each setting's label and rule, the patch a call asks for, the rendering |
40
+ | `PLATFORM_CATALOGUE` (`pipelines`, `features`, `capabilities`) · `renderPlatform(catalogue, host)` · `GENERATED_SUMMARY` | What `describe_platform` renders, and the one-sentence product summary `describe_capabilities` ends with |
41
+ | `ToolHostKind` (`Stdio`/`Http`) · `ToolHost` · `ToolDeps` (`detach?`, `release?`, `inflight?` beside `attach`) · `ToolAnnotations` · `toolHostHelper` (`anyHost`/`localTarget`/`cloudTarget`/`withExecutor`/`delegatedLlm`/`sessionCapable`/`performsModelTasks`) | The host description and the availability predicates |
42
+ | `./executor`: `makeLocalSlotExecutor(dir, opts?)`, `createLocalFileHelper`, `createLocalShellHelper`, `makeLocalGitHelper(dir).dispatchGitCommand`, `integrityHelper` (`verifyTarget`/`forgetIntegrity`), `makeLayoutHelper(dir)` (`targetPaths`/`apiPath`/`webPath`/`workerPath`), `makeTargetEnvHelper(dir)` (`backendEnv`/`frontendEnv`), `healthHelper` (`classifyTargetHealth`/`readTargetHealth`), `runBootCheck`, `confineToProject`, `spawnHelper` | The publisher's workload, on somebody's laptop |
43
+ | `./run`: `makeLocalRunHelper(dir)` (`runLocal`, `stopLocal`, `localStatus`), `createLocalServer`, `makeRunStateHelper(dir)` (`startApi`/`startWorker`/`restartApi`, `readRun`/`writeRun`/`clearRun`), `runProcessHelper.stopProcess` | Building and running the generated app locally |
44
+ | `makeMarkerHelper(dir)` (`readMarker`/`writeMarker`/`discoverProject`/`isViableTree`) · `makeProjectEnvHelper(dir)` (`readEnv`/`writeEnv`/`envStatus`) · `dotenvHelper.replaceManagedBlock` | The `.viable/connect.json` marker and the managed `.env` block |
45
+ | `SdkError`, `SdkAuthError`, `SdkMisconfigured`, `SdkUnsupported` | Registered `ResilientError` classes |
46
+ | `ENV_TOKEN`, `ENV_API_URL`, `ENV_MCP_URL`, `ENV_TARGET`, `ENV_LLM`, `ENV_HARNESS`, `ENV_PROJECT_DIR` · `DEFAULT_MCP_URL` · `resolveMcpUrl(values)` · `TOOL_DEADLINE_MS` (45 s) · `HANDOVER_WAIT_MS` (40 s) · `COMMIT_WAIT_MS` (20 s) · `COMMIT_POLL_SEC` · `STORY_PAGE_SIZE` · `NEXT_TASK_WAIT_MS` · `PULL_WAIT_MS` | Configuration and deadlines |
47
+
48
+ ## One credential, and the routes the server declares
49
+
50
+ `makeSdkContext` registers `makeTokenCarrierGuard` under `DEFAULT_GUARD` and binds the SAME
51
+ immutable `connectProtocols({ guard })` tree the server mounts (on the platform's public API host),
52
+ so a path or a schema cannot be right on one end and wrong on the other. The planning tree is bound
53
+ the same way: `makePlanningProtocols({ base: { alias: 'viable:manager-api:planning', path:
54
+ '/planning' }, guards: DEFAULT_GUARD, socketBase: <the /update base> })` — the platform's mount minus
55
+ its ownership gate, which is the server's to apply — followed by `appendPlanningClient(context, {
56
+ protocols, bind: false, poll: COMMIT_POLL_SEC, timeout: opts.timeout ?? TOOL_DEADLINE_MS, schemas: false })`. The
57
+ base alias and path are literals beside the `/update` base for the same reason that one is: they
58
+ belong to the platform, and every planning alias and path derives from them. The aliases are this
59
+ context's registry keys only; the wire is the path, which is why the platform may register its copy
60
+ under aliases of its own. No socket opener is passed, so a commit is awaited by long
61
+ poll only, and the schema bundle is not fetched at startup — nothing a tool does reads a flow, and a
62
+ server nobody has asked anything yet makes no call. There is deliberately **no second credential path**: a connector that could
63
+ fall back to another form of authentication is a connector whose access nobody can revoke by
64
+ revoking a token. A literal token that is empty or lacks `CONNECT_TOKEN_PREFIX` is refused locally
65
+ (`SdkMisconfigured` / `SdkAuthError`), because a 401 says nothing about which of several plausible
66
+ mistakes was made and the answer is always the same one.
67
+
68
+ **`token` may be a thunk** (`() => string | Promise<string>`), resolved on every request — what a
69
+ credential holder (`@owlmeans/cli-auth`) hands over, since it is `''` until a browser sign-in
70
+ completes and a real token after, with no reconfiguration. Only a literal is validated eagerly; a
71
+ thunk's value is unknown at construction, and an empty one is simply "not signed in". `onRejected`
72
+ is passed to the carrier guard and fires when the platform 401s the presented token, which is where
73
+ the holder forgets a dead file token (or reports a dead environment one) — see [[auth-token]].
74
+
75
+ **The `/mcp` URL is a value the SDK resolves, never one it derives:** `resolveMcpUrl(values)` returns
76
+ `values[ENV_MCP_URL]` (`VIABLE_MCP_URL`, empty = unset) else `DEFAULT_MCP_URL`
77
+ (`https://api.owlmeans.com/mcp`), trailing slash dropped so a resource URI has one spelling. The
78
+ caller passes the already merged environment + `~/.owlmeans` values (environment wins). A deployment's
79
+ own resource identifier stays host-derived on the server.
80
+
81
+ **The context must also declare the platform's `/update` base itself.** The planning commit feed
82
+ hangs under that namespace (the SDK never opens it), and a parent an entrypoint registry cannot
83
+ resolve fails the **whole context at init** rather than the one call that would have used it — a server that exited at startup
84
+ with `Entrypoint viable:manager-api:update:base not found`. It is declared with the path the
85
+ platform declares and never binds: it is a namespace, and nothing calls it.
86
+
87
+ **`cfg.webService` names the API CLIENT, never the backend.** It is the alias the entrypoint handler
88
+ looks up with `context.service(...)`, and the only thing registered under it is the `ApiClient` that
89
+ `makeClientContext` appends (`web-client`). Naming the backend service there — which is exactly what
90
+ `addWebService` does — makes every call fail with `Service <backend> not found` before a request is
91
+ sent. The backend belongs in `cfg.services` as an ordinary descriptor marked `default: true`; the
92
+ connect routes name no service of their own, so that is the one they resolve to. Its host is `apiUrl`,
93
+ the platform's PUBLIC API origin (path-less: the connector tree, planning, `/mcp`, OAuth); its alias
94
+ (`service`, default `viable-manager-api`) is a registry key of this context only, never on the wire.
95
+
96
+ **A POST with nothing to say still sends `{}`.** The client sets no `Content-Type` for an absent
97
+ body, and Fastify answers **415 Unsupported Media Type** before the handler runs — an error about
98
+ media types for a call whose only fault was having no arguments. `makeRemoteConnectorApi` fills an
99
+ empty body for any POST route that was given none; `session.close` is exactly that shape.
100
+
101
+ ## Two hosts, one interface
102
+
103
+ `ConnectorApi` is an interface because two implementations must stay interchangeable — one calling
104
+ the API over HTTP (`makeRemoteConnectorApi`, the npx server) and one calling the platform's own
105
+ handlers in process (`makeInProcessConnectorApi` in `viable-public-api`, behind `POST /mcp`). Every
106
+ tool is written against it, so a tool cannot accidentally work in only one of the two. Which route
107
+ `openSession` calls fixes the mode: the delegated route carries the entitlement gate, so a caller
108
+ without the capability is refused at the boundary rather than by a check somewhere inside.
109
+
110
+ `ConnectorApi` has exactly one member per connector route plus the planning facade — the session
111
+ (`openSession`, `closeSession`, `pullOps`, `submitOp`), projects (with the agent controls
112
+ `project.stop(id, AgentStopArgs)` → `ConnectAgentLock`, `project.steer(id, { prompt })` → `AgentSteerResult`
113
+ and the story batch `project.batch.start(id, { stories? })` / `.get(id)` (`null` when none ran) /
114
+ `.cancel(id)` → `StoryBatchView` — only the fields named cross the wire), branding (`projectBranding(id, scope?)`,
115
+ `saveProjectBranding(id, patch, scope?)`, `setPlatformCredit(id, hidden, scope?)`,
116
+ `copyOrganizationBranding(id, scope?)` — a scope sent only when named), the configuration
117
+ (`config.get(id, scope?)`, `config.save(id, body, scope?)`, `config.recollect(id)`), the
118
+ organization's defaults (`account.branding.get()`, `.save(patch)`, `.backfill()`), the person's
119
+ inference preference (`account.llm.get()`, `.set(mode)`) and a project's override
120
+ (`project.llm(id)`, `project.setLlm(id, mode | null)` — `null` crosses as `null`), the person's
121
+ tokens (`account.tokens.list()`, `.revoke(id)` — never a create), consents
122
+ (`account.privacy.status()`, `.withdraw(keys)` — never a grant) and the intent pickup
123
+ (`account.intent.pickup(ref)`), story status, files
124
+ (`list`, `get(projectId, path)`, `save(projectId, path, content)`, `remove(projectId, path)`,
125
+ `meta(projectId, { kind, category? })` — the category sent only when named), the preview's
126
+ `sandbox` (`run`, `restart`, `stop`, `rebuild` → `ConnectSlotView`), the organization's `slot.list`,
127
+ pipeline state, conversion (`create`, `check`, `start(projectId, body?)`, `proceed(projectId, body)`
128
+ — the bodies are the route's, `confirm` included —, `status`, `purge`) and inquiry answers. There is
129
+ no capability view, no session read or heartbeat, no conversion cancel, no token create and no
130
+ consent grant. A member is added together with the route in `connectProtocols` and its
131
+ `connectRef` entry — never one without the others. The one exception is `call.collect`: it belongs to
132
+ the context's transport (below), which collects a delegated write's outcome under whichever member
133
+ made it.
134
+
135
+ ## A SESSION belongs to a host that stays; `toolHostHelper.sessionCapable` is what says so
136
+
137
+ A session is a connector ATTACHED — a process that drains the project's operations and holds a model
138
+ task until its answer comes back. The URL-configured host answers one request and forgets, so
139
+ opening one there would claim the project's single connector slot, **supersede the stdio connector
140
+ legitimately holding it**, and be abandoned before the first operation was delivered.
141
+
142
+ So `ensureSession` — what `confirm_project`, `reinitialize_project`, `develop_story`, `start_story_batch`,
143
+ `steer_agent`, `reset_story`,
144
+ `complete_story`,
145
+ `modify_project`, `rename_project` and `resume_pipeline` call before returning their job, every
146
+ story write, `update_project_settings`, `set_platform_credit`, `update_project_configuration`,
147
+ `recollect_configuration`, `apply_planning_kit`, `write_file` (its content check is a
148
+ model call the delegating session performs) and the conversion verbs before their
149
+ write, and `answer_question` in the delegated mode before an answer by id resumes a run — opens one
150
+ only on a `toolHostHelper.sessionCapable` host. `next_task`/`submit_task_result` are available on
151
+ `.performsModelTasks` (delegated **and** session-capable) and nowhere else: in the cloud mode the
152
+ platform performs every model call itself, a conversion's included, so there is never a task to
153
+ collect. `next_question`/`answer_question` stay on `.sessionCapable` — a person is asked whoever
154
+ performs the models. `serverInstructions` reads the same predicates: a parent told to call
155
+ `next_task` when the tool is not in its list is a parent that waits for a run nobody will advance.
156
+ A host that cannot serve the delegated mode says so in one sentence instead, naming the stdio
157
+ connector.
158
+
159
+ The in-process implementation refuses the four session verbs (`openSession`, `closeSession`,
160
+ `pullOps`, `submitOp`) with a message naming `@owlmeans/viable-mcp`, because "unsupported" alone
161
+ leaves the reader with nothing to do about it.
162
+
163
+ ## A tool that cannot work in a mode is HIDDEN, not failing
164
+
165
+ `ToolDefinition.availability` and `catalogueHelper.visibleTools(host)` decide the catalogue a parent actually sees. A
166
+ tool a host cannot serve is a tool the parent tries once, is refused, and remembers as broken — so
167
+ the list it reads is exactly the set of things that work for it. Predicates (`toolHostHelper`): `anyHost`,
168
+ `localTarget`, `cloudTarget`, `withExecutor`, `delegatedLlm`, `sessionCapable`, `performsModelTasks`.
169
+
170
+ Every catalogue tool but `describe_platform` belongs to EXACTLY ONE `PLATFORM_CATALOGUE.capabilities`
171
+ group, and every group carries an `absent` sentence rendered where a host hides it. `serverInstructions`
172
+ is the compact map a parent reads first: the workflow, ONE line naming each capability family by a few
173
+ of its tools ("everything the web application does except billing"), the cursor rule of the feeds, the
174
+ `confirm: true` set, the browser-only acts (payments and the plan, token creation, giving a marketing
175
+ consent or accepting terms, approving a connector, finishing a GitHub authorization) and the mode
176
+ rules — and it names NO tool the host hides: the files/preview, git/GitHub and production families and
177
+ `file_changes` appear only for a cloud target, the local block says those are the machine's.
178
+
179
+ ## The delegated mode: every model call is the parent's, and a blocked tool hands its task over
180
+
181
+ `llm=local` is the ONE switch for who performs the platform's model calls. In the delegated mode
182
+ every one of them is this session's parent's — project drafting, every content check, story
183
+ formatting, every run's calls and a conversion's; in the cloud mode none is. The wording says it in
184
+ those terms everywhere it is said (`serverInstructions`, `describe_capabilities`, `describe_platform`,
185
+ the `model-tasks` group, `CONVERSION_COST`, `WORKING_RULE`): all or none, nothing in between.
186
+
187
+ A platform call can therefore stop mid-way for a model call the parent itself must perform — the
188
+ very parent blocked on that tool. `registerCatalogue` answers that with the HANDOVER
189
+ (`makeHandoverHelper(deps).run`) for every tool except the task/question loop and the tools that
190
+ never reach the platform (`HANDOVER_EXEMPT`):
191
+
192
+ - The call starts and races (i) finishing, (ii) a model task becoming available in the CURRENT
193
+ session's queue (`taskAvailable`, looked for in 1 s slices because a delegated create opens its
194
+ session from inside the call), (iii) `HANDOVER_WAIT_MS`. A finished call answers as it always did.
195
+ - A task first: it is taken (`nextTask(0)`; null means another caller was quicker and the race goes
196
+ on) and the tool answers "`<tool>` is NOT finished — the platform is waiting on a model call you
197
+ must perform. Do not call `<tool>` again. Run the task below in a clean subagent at LOW reasoning
198
+ effort, then call submit_task_result {taskId, result}; its reply is `<tool>`'s result, or the next
199
+ model call it waits on." followed by the task exactly as `next_task` renders it.
200
+ - Neither: "still running on the platform — call next_task (or read `<status tool>`)".
201
+ - The call is never abandoned. It lives in `ToolDeps.inflight` (`createInflightCalls()`, held beside
202
+ the server — never on the session, which reopens whenever the connector moves), keyed by tool name
203
+ + canonical JSON args, so an identical repeat JOINS it. A call some tool still waits on is that
204
+ tool's to answer; once nobody waits it is detached, and `submit_task_result` and `next_task`,
205
+ after their own work, race any detached call settling, the next task and the wait, then report
206
+ every settled call (its text phrased exactly as the wrapper's catch phrases it; each in
207
+ `structuredContent.settled[]`, a refusal marked `isError` there and never on the submit itself).
208
+ - The cloud mode and the URL host keep the plain deadline path, unchanged.
209
+
210
+ **A create names an UNATTACHED session.** In the delegated mode `create_project` and
211
+ `convert_project`'s create path first open a session with no project (`ToolDeps.detach?()` when one
212
+ is attached, then `deps.session()`), send its id as `sessionId` (`ConnectorApi.project.create(prompt,
213
+ target?, sessionId?)`, `ConnectConvertCreateBody.sessionId`) — the platform hands the checks it runs
214
+ before the card exists to that session's parent — and once the platform answers with project P,
215
+ `deps.attach(P); ensureSession(deps, P)`. A host without `detach` sends none while attached
216
+ elsewhere. The cloud mode sends none and opens nothing.
217
+
218
+ **Request now, collect later.** A delegated write may wait on a model call this very parent
219
+ performs — create, convert create, confirm, reinit, modify, rename, a settings save, a story create or
220
+ update (the platform formats the narrative in its planning `execute`) — for longer than an edge holds
221
+ a response open (~100 s). So `makeSdkContext({ llm: ConnectLlm.Local })` registers ONE transport
222
+ (`createCallCollectTransport`, under `transportAlias('http')`, which every client entrypoint asks for
223
+ before the API client) in front of the context's API client — the connector routes and the planning
224
+ client both go through it, with no per-method code:
225
+
226
+ - every NON-GET request carries `x-viable-call: <randomUUID()>` (`CONNECT_CALL_HEADER`); a request
227
+ that already names one keeps it, so a retry of that request is the same call to the platform;
228
+ - an answer `{ pending: <that id> }` (202, after the platform's `CONNECT_CALL_ACCEPT_MS` hold) becomes
229
+ `connect.call.collect` long polls (`wait` = `CONNECT_CALL_COLLECT_WAIT_SEC`, HTTP deadline wait +
230
+ 10 s), repeated while `pending` (an unheld one — under 1 s — rests 1 s first) until `settled`: its
231
+ `value` and `outcome` resolve the original call exactly as a direct answer would have (the planning
232
+ facade still builds its receipt), its `error` is `ResilientError.ensure`d back into its own class;
233
+ `lost` throws `ConnectCallLost` (phrased in `REFUSALS`: the outcome was lost, read the matching
234
+ status tool before repeating);
235
+ - a hop the LINE dropped (a transient transport error, axios's own timeout, an edge 502/503/504) is
236
+ asked again, up to `COLLECT_DROPPED_HOPS` in a row; any other answer — a direct value, a `pending`
237
+ of another id, a refusal, every GET, the cloud mode — passes through untouched.
238
+
239
+ `collect` is the transport's route, not a `ConnectorApi` member. Every request and collect hop stays
240
+ under `TOOL_DEADLINE_MS`; nothing caps the call as a whole but the platform's own model-task deadline,
241
+ and the handover is unchanged — its in-flight promise simply lasts as long as the collection.
242
+
243
+ ## 45 seconds is the ceiling, so long operations return domain status
244
+
245
+ Every MCP host bounds a tool call and the strictest default in the field is sixty seconds (Codex).
246
+ `TOOL_DEADLINE_MS` leaves room for the round trip and keeps the connector inside every host's
247
+ ceiling without configuration. A tool that outlives its host's ceiling is reported to the user as a
248
+ **broken server**, and the true answer — the platform was slow — never reaches them.
249
+
250
+ Anything that takes minutes returns the owning domain's status and continues server-side.
251
+ `project_status`, `story_status`, `conversion_status` and `pipeline_status` compose the durable
252
+ records and run state a parent needs. `registerCatalogue` enforces the deadline per call (the
253
+ delegated mode's handover answers within `HANDOVER_WAIT_MS` instead) and
254
+ converts a throw into an `isError` result the model can read and act on, because an exception
255
+ crossing the transport tells it only that something went wrong somewhere. Each status renderer
256
+ ends with a single `next:` line so the parent follows the domain workflow and does not invent a
257
+ polling strategy or treat a parked run as failed. What HAPPENED along the way is read from the cursor feeds
258
+ (`project_activity`, below) — a feed is never the answer to where a run stands.
259
+
260
+ ## The story tools speak planning
261
+
262
+ A user story is a planning CARD of `VIABLE_STORY_TYPE` under its project card, and every change to one
263
+ is a transition executed through `ConnectorApi.planning` — a `PlanningFacade`: the client facade
264
+ `appendPlanningClient` registers over HTTP, and `ensurePlanningService(ctx).for(scope)` in the
265
+ platform's in-process host. The scope a call answers for is the CREDENTIAL's; a tool sends none.
266
+
267
+ | Tool | Facade call |
268
+ |---|---|
269
+ | `list_stories` | `cards.list({ ...storyHelper.storyQuery(project, { status, area }), page, size, sort: STORY_ORDER })` → `storyHelper.renderStories` |
270
+ | `search_stories` | the same with `{ q }` |
271
+ | `create_story` | `execute({ action: create, card: { kind: card, type: VIABLE_STORY_TYPE, parent, title, fields: { primary: false } } }, { wait: true, timeout: COMMIT_WAIT_MS })` |
272
+ | `update_story` | `storyHelper.resolveStory` → `execute({ card, action: update, changes: { title }, expectSeq: head ?? seq }, { wait: true, … })` |
273
+ | `delete_story` | `storyHelper.resolveStory` → `execute({ card, action: delete }, { wait: true, … })` → the project-lock poll |
274
+ | `develop_story` | `storyHelper.transit(deps, project, storyId, start, 'connector:develop-story')` |
275
+ | `reset_story` | `storyHelper.transit(…, reset, 'connector:reset-story')` — any status back to planned |
276
+ | `complete_story` | `storyHelper.transit(…, complete, 'connector:complete-story')` — in progress only |
277
+ | `story_status` | `storyHelper.resolveStory` → the card, its development run, pending inquiry, warning and landing mark |
278
+ | `start_story_batch` | `ensureSession` → each `storyIds` ref `storyHelper.resolveStory`d to its card id (deduplicated; none = every planned story, `{}`) → `api.project.batch.start` → `statusTextHelper.renderBatchStatus` |
279
+ | `story_batch_status` | `api.project.batch.get` → the batch in implementation order (code, state, ordering reason), progress, halt reason phrased, `next:`; `null` → "No story batch has run" |
280
+ | `cancel_story_batch` | `confirm: true` → `api.project.batch.cancel` FIRST, then — when `current` has no outcome — `api.project.stop({ scope: story, storyId: current, confirm: true })`; `AgentNotRunning` there means the batch was between stories |
281
+
282
+ Rules the table rests on:
283
+
284
+ - **A tool NAME is a parent agent's vocabulary and is never renamed**, and neither is an argument:
285
+ `storyId` stays `storyId` although the platform keys on card ids. New facts are added (`designSystem`
286
+ on `confirm_project`, the project status line and the design-system section of `project_status`),
287
+ never substituted.
288
+ - **`storyId` accepts a code or an id**, because `list_stories` prints the CODE — the handle every
289
+ generated file names a story by. `storyHelper.resolveStory` asks for both at once, the id wins, a code is also
290
+ tried uppercased, and neither answers for a story of another project (`ProjectStoryNotFound`).
291
+ - **A story moves through its flow by ONE helper.** `storyHelper.transit(deps, projectId, ref,
292
+ transition, cause)` resolves the story, executes the transition awaited to its commit and answers
293
+ `{ card, status }` from `story.status`. Development is the story's `start`, not a call of its own:
294
+ the platform begins the run once that move COMMITS, and refuses it there too (the flow, one story
295
+ in progress, the balance). `reset_story` (`* → planned`, the manual recovery — the code generated
296
+ for it stays) and `complete_story` (`in-progress → completed`, nothing generated or stopped) are
297
+ the browser's two manual moves. The wait is `COMMIT_WAIT_MS`, well inside the tool deadline; a late
298
+ commit is not a failure — the transition is durable — so the tool answers from `story_status`
299
+ rather than outliving the host's ceiling. A refusal is thrown, and no status is read to hide it.
300
+ - **A story a person writes goes as written, with no area.** Re-formatting the narrative and deciding
301
+ the area are the platform's, done in its planning middleware for the `connect` channel; a connector
302
+ that guessed an area would be a second answer. `update_story` carries only `title` and the head it
303
+ read, so a change made in between is refused (`WorkcardConflict`) rather than overwritten.
304
+ - **The delete commit is no proof the slot is done.** It says the card is gone and nothing about the
305
+ placeholder screens still being retired under the project lock, so `delete_story` keeps its two
306
+ unlocked observations.
307
+ - **Stories are read in `order`, then `createdAt`**: `order` is the analysis's flow ordinal (a
308
+ connective story sits at a fraction between two steps). `storyHelper.renderStories` keeps the line shape a parent
309
+ already reads — `code · status[ · primary][ · landing gate][ · area]` over the narrative — under a
310
+ header counting the page by intrinsic state. A new fact is a new FLAG beside `primary`; the area
311
+ stays last.
312
+ - **The landing gate story is read off the card the tool already holds** (`fields.landing`, at most
313
+ one per project, decided by the platform at initialization — a connector never sets it). The list
314
+ flags it; `story_status` and `develop_story` pass the resolved card to `statusTextHelper.renderStoryStatus`, which
315
+ adds `LANDING_NOTE` under the status line, and their structured result gains `landing: true`. No
316
+ second call and no change to `connect.story.status`: that route carries the run, not the card's
317
+ fields. It is said because developing that story also replaces the guest-home sketch with the real
318
+ component, which the narrative alone never tells a parent.
319
+ - **A planning refusal is phrased like any other**: `planning:illegal-transition:`,
320
+ `workcard-conflict:`, `workcard-not-found:`, `fields-invalid:`, `commit-timeout:`, `commit-failed:`
321
+ and the story markers (`viable-project:story:not-found:` / `:missconfigured:`) each have a sentence in
322
+ `REFUSALS`.
323
+
324
+ - **A sign-in refusal is phrased**: `oauth:sign-in-required:` (detail `<url> <code>`, the code absent
325
+ while no device sign-in is pending), `oauth:token-rejected:` (an environment token that was refused
326
+ is never replaced) and `api:auth:guard:auth-token` (a file token the platform refused — forgotten,
327
+ the next call signs in). Each ends with "call this tool again".
328
+
329
+ - **Run all, Stop and Steer are the browser's own controls.** A batch is the PROJECT's (no story
330
+ route), started over planned stories only, ordered by the platform before its first story, and
331
+ each of its stories runs exactly as `develop_story`'s would; `start_story_batch` is never a near-twin
332
+ of `develop_story` in its description. `stop_agent { scope?: run | story | batch, storyId?, confirm? }`
333
+ sends only what it was given (a `storyId` resolved to its card): a story stop rolls the story back to
334
+ where that run started and returns it to planned, `batch` also cancels the batch, `run` (an open-ended
335
+ change) stops once the model's current answer arrives and keeps its edits — so every scope but `run`
336
+ (an omitted scope included) needs `confirm: true`. A stop opens no session; `steer_agent { prompt }`
337
+ does (`ensureSession`: its content check is a model call) and answers the queued message id.
338
+ `AgentNotRunning`, `StoryBatchActive` and `StoryBatchEmpty` are phrased in `REFUSALS` by their
339
+ `viable-common` markers ("Nothing is running on this project right now …"). `project_status` says
340
+ `agent: stopping (…)` while `agent.stopping`, and names `steer_agent` / `stop_agent` while
341
+ `agent.steerable`.
342
+
343
+ ## An irreversible tool asks for `confirm: true`, and every tool declares its annotations
344
+
345
+ `delete_project`, `unlock_project_agent`, `delete_file`, `purge_origin`, `git_discard`, `git_revert`,
346
+ `disconnect_github`, `revoke_access_token`, `publish_production` and `cancel_story_batch` take `confirm` (`z.boolean()`,
347
+ described, NO default), and `manage_app_user`'s `remove`, `manage_app_group`'s `delete` and every
348
+ `stop_agent` scope but `run` take it as an optional field the action requires; each answers "Not done … ask the user" without a single platform
349
+ call unless it is `true` — the confirmation is the TOOL's, a person agreed before the route is reached.
350
+ `serverInstructions` names the same set in one sentence. `delete_project` also
351
+ REQUIRES `projectId` (`z.string().min(1)`, never the attached project by default) and, on a
352
+ session-capable host attached to that project, closes the session first (`deps.release`, else
353
+ `currentSession()?.close()`) and `deps.detach()`es, then `api.project.destroy` → `{ id, name, alias }`;
354
+ on a local target it says the files and `.viable/` stay. `unlock_project_agent` (`api.project.unlock`
355
+ → `{ locked, task?, lockedAt? }`) defaults to the attached project and says when a run took the lock
356
+ straight back.
357
+
358
+ `ToolDefinition.annotations` is REQUIRED — the MCP tool annotations a host shows the person approving
359
+ a call — and `registerCatalogue` passes it through beside the title, description and input schema.
360
+ The shapes live in `tools/consts.local.ts`: `READ_ONLY` (reads, statuses, descriptions), `WRITE`
361
+ (additive changes, the task and question loop), `IDEMPOTENT_WRITE` (setting a value: settings,
362
+ `update_story`, `reset_story`, `attach_project`, `stop_local`, `set_local_service`) and
363
+ `DESTRUCTIVE` (`delete_project`, `delete_story`, `purge_origin`, `reinitialize_project`, `git_revert`,
364
+ `cancel_story_batch` and `stop_agent` (both idempotent — a story stop rolls code back),
365
+ `publish_production` (it replaces the live site), and — also idempotent — `unlock_project_agent`,
366
+ `write_file` (it replaces a file whole), `delete_file`, `git_discard`, `disconnect_github`,
367
+ `revoke_access_token` and `production_control` (it takes the live site down or restarts it); `preview_control` and
368
+ `link_github_origin` are `IDEMPOTENT_WRITE`); `openWorldHint` is `false` everywhere except
369
+ `convert_project`, which may clone a repository from the internet. A new tool picks one; a read is
370
+ never destructive (`catalogue.spec.ts` pins the destructive set).
371
+
372
+ ## A cloud target's files and preview are the web editor's and sandbox card's own calls
373
+
374
+ All cloud-target only (`toolHostHelper.cloudTarget`), on both hosts; a local project's tree and
375
+ process are on the user's machine, read and run directly.
376
+
377
+ - **`list_files { kind?, category? }`** — no kind (or `sources`): `files.list`, the generated
378
+ sources; `stories` / `meta` / `all`: `files.meta` (the story documents, the specifications beside
379
+ the sources narrowed by `category` — sent with `meta` only — or every metadata document).
380
+ - **`read_file { path }`** answers the content as the text, cut at `FILE_READ_CAP` (100 000
381
+ characters) with a note naming the full length; `structured` carries `path`, `length`, never the
382
+ content twice.
383
+ - **`write_file { path, content }`** replaces the whole file; **`delete_file { path, confirm }`**
384
+ refuses without `confirm: true` and makes no call. Both answer "the preview was rebuilt" or,
385
+ when `buildWarning` came back, that the rebuild FAILED and the last good build still serves — the
386
+ change stands either way. A protected file (manifests, build configuration) is refused by the
387
+ platform as `target-integrity:<path> … cannot be edited`, phrased as that one file
388
+ (`PROTECTED_FILE_DETAIL`), never as the tree failing its shape check.
389
+ - **`preview_control { action: start | restart | stop | rebuild }`** → `sandbox.run` / `restart` /
390
+ `stop` / `rebuild`, answered with the workload line (`statusTextHelper.renderSlot`); a rebuild
391
+ goes on after the answer under the project lock. Never the production site.
392
+ - **`list_slots`** (any host, any target) — every workload of the organization, one line each plus
393
+ its warnings. `viable-slot:not-found:` is phrased as a project with no workload yet.
394
+
395
+ ## A cloud target's git and its GitHub connection are the web Git dialog's own calls
396
+
397
+ All cloud-target only (`toolHostHelper.cloudTarget`), on both hosts — a local project's repository is
398
+ on the user's machine; group `git` in `PLATFORM_CATALOGUE.capabilities`; rendered by `gitToolHelper`
399
+ (`tools/git.ts`, words in `consts.local.ts`: `GITHUB_STATUS_WORDS`, `GIT_SYNC_WORDS`). Members
400
+ `ConnectorApi.git.{status,log,commit,discard,revert}` and `ConnectorApi.github.{authorize,publish,push,
401
+ pull,disconnect,repos,branches,link}` (`connect.git.*` / `connect.github.*`, the library's mirror types
402
+ `ConnectGit*` / `ConnectGithub*` — no `@owlmeans/git` dependency, statuses are strings).
403
+
404
+ - **`git_status`** (read-only): the connection line (login → repository · status words) and the
405
+ tree — branch, head, ahead/behind, the first `GIT_FILES_SHOWN` changed paths. `git: null` reads as
406
+ "the preview is not ready" with `preview_control { action: start }` as the cure — never as an error.
407
+ - **`git_history`** (read-only), **`git_commit { message }`** (`WRITE`, trimmed, ≤ 200),
408
+ **`git_discard { confirm }`** and **`git_revert { hash, confirm }`** (`DESTRUCTIVE`; refused without
409
+ `confirm: true` before any call; the hash is lower-cased and must match `^[0-9a-f]{7,40}$`; a revert
410
+ is a NEW commit and its `dbWarning` is printed).
411
+ - **`connect_github`** answers the `authorizeUrl` the USER opens in the browser where they are signed
412
+ in; GitHub returns to the OwlMeans web application, which completes it. No tool, route or member
413
+ completes an authorization (`git-tools.spec.ts` pins it), and nothing ever carries the token.
414
+ - **`publish_to_github { repoName?, private? } | { owner, repo }`** (`WRITE`, in `answering`: the
415
+ `conversion:publish-origin` refusal is the tool's own sentence); half an existing repository is
416
+ refused locally. **`github_sync { direction: push | pull }`** answers a refused outcome
417
+ (`conflict`, `rejected`, `no-remote`, `auth-failed`) as `isError` with the cure; a pull rebuilds.
418
+ - **`github_repositories { owner?, repo?, page?, search? }`** (read-only): repositories, or — with
419
+ both `owner` and `repo` — that repository's branches; one of the two alone is refused locally.
420
+ **`link_github_origin { owner, repo, branch? }`** (`IDEMPOTENT_WRITE`) records where an import comes
421
+ FROM. **`disconnect_github { confirm }`** (`DESTRUCTIVE`, idempotent).
422
+ - The writes map to `git_status` for the handover's domain status (`STATUS_TOOL_OF`). The refusals
423
+ `github:not-connected`, `github:not-published` and `oauth:invalid-or-expired-state` name
424
+ `connect_github` / `publish_to_github`; `git-busy:` is the agent holding the project.
425
+ - Specs: `tests/git-tools.spec.ts`, `remote.spec.ts` (the thirteen routes and their closed bodies).
426
+
427
+ ## A cloud target's production site is the web Publish dialog's own calls
428
+
429
+ All cloud-target only (`toolHostHelper.cloudTarget`), on both hosts — a local project is deployed from
430
+ its own machine; group `production` in `PLATFORM_CATALOGUE.capabilities`; rendered by
431
+ `productionToolHelper` (`tools/production.ts`, words in `consts.local.ts`: `PRODUCTION_STATUS_WORDS`,
432
+ `DOMAIN_STATUS_WORDS`, the actions `PRODUCTION_ACTIONS`, `DOMAIN_ACTIONS`). Members
433
+ `ConnectorApi.production.{status,publish,restart,stop,domain.{attach,verify,detach},auth,setRedirects}`
434
+ (`connect.production.*`). Never the preview — that is `preview_control`.
435
+
436
+ - **`production_status`** (read-only): never published, or the status words and — when live — the
437
+ address (the custom domain once `linked`, else the generated host), the warnings, the domain line,
438
+ and the next step.
439
+ - **`publish_production { confirm }`** (`DESTRUCTIVE`; refused without `confirm: true` before any
440
+ call) — the publish starts and goes on after the answer; **`production_control { action: restart |
441
+ stop }`** (`DESTRUCTIVE`, idempotent). Both in `answering`: the published-sites `limit-exhausted:`
442
+ refusal (a stopped site's restart takes a unit too) is the tool's own sentence.
443
+ - **`custom_domain { action: attach | verify | detach, domain? }`** (`WRITE`, in `answering`): an attach
444
+ needs `domain` (trimmed, lower-cased) and answers the two DNS records the USER creates
445
+ (`CNAME <domain> → <cnameTarget>`, `CNAME <dcvName> → <dcvValue>`) with the provider's state of each,
446
+ until the domain is `linked`; `null` from an attach is "publish first". `viable-domain:taken:` and
447
+ `capability-required:feature:domain--custom` are phrased.
448
+ - **`production_auth`** (read-only) prints the issuer, the client id and the redirect addresses — the
449
+ client secret only as set or not (`secretSet`; the user reads it in the web application).
450
+ **`set_production_redirects { redirects }`** (`IDEMPOTENT_WRITE`) replaces the whole list, applied on
451
+ the next publish.
452
+ - The writes map to `production_status` for the handover (`STATUS_TOOL_OF`).
453
+ - Specs: `tests/production-tools.spec.ts`, `remote.spec.ts` (the nine routes and their closed bodies).
454
+
455
+ ## The generated app's sign-in is the owner console's own calls
456
+
457
+ Any host and any target (`toolHostHelper.anyHost`) — the IAM is the PLATFORM's, a local project's app
458
+ included; group `app-sign-in` in `PLATFORM_CATALOGUE.capabilities`; checked and rendered by
459
+ `iamToolHelper` (`tools/iam.ts`: `missing`, `grantBody`, `render*`; actions `APP_USER_ACTIONS`,
460
+ `APP_GRANT_ACTIONS`, `APP_ORGANIZATION_ACTIONS`, `APP_GROUP_ACTIONS`, `IAM_ROWS_SHOWN`, `IAM_EXTERNAL`
461
+ in `consts.local.ts`). Members `ConnectorApi.iam.{organizationUsers, permissions, setDefault,
462
+ grants.*, users.*, organizations.*, groups.*}` over `connect.iam.*` and `connect.account.iam.users`;
463
+ the bodies and answers are the library's owner-console types (`Iam*`, `@owlmeans/viable-common`). Every
464
+ tool takes `scope` (`ephemeral` | `production`, absent = the preview's client): a read sends it in the
465
+ query, a write — a removal included — in its body.
466
+
467
+ - Reads (`READ_ONLY`): **`app_users { projectId?, scope?, all? }`** (`all: true` = every end user of
468
+ every app of the organization, read-only), **`app_permissions`** (definitions + tenancy flags),
469
+ **`app_grants { profileId? | group? + entitySlug }`**, **`app_organizations { entitySlug? }`** (members
470
+ of one when a slug is named), **`app_groups { entitySlug, group? }`** (members of one when a key is named).
471
+ - Writes: **`set_app_permission_default { permission, defaultClass?, entityScoped? }`**
472
+ (`IDEMPOTENT_WRITE`); `DESTRUCTIVE`: **`manage_app_user { action: invite | update | remove }`**,
473
+ **`manage_app_grant { action: assign | revoke, permission, profileId | group + entitySlug,
474
+ resources?, mode? }`**, **`manage_app_organization { action: rename | add-member | update-member |
475
+ remove-member, entitySlug }`**, **`manage_app_group { action: create | update | delete | add-members |
476
+ remove-members, entitySlug, group }`**.
477
+ - **Grants restrict as well as grant.** Every subject (person or group) has its OWN blanket value per
478
+ binding: granted, restricted, or absent (inherits). A whole-permission revoke (`blanket`/`all`, or no
479
+ mode without `resources`) RESTRICTS a declared permission — the subject also loses what it inherits
480
+ from a default class, a group or a bundle; `mode: inherit` (revoke-only) takes back only the
481
+ subject's own value, so it holds what it inherits again; a `resources` revoke removes those ids only.
482
+ `iamToolHelper.renderRevoke` words the result by that mode. The tool descriptions and the `mode`
483
+ enum text say the same, and nothing here re-implements the IAM's evaluation.
484
+ - `app_grants` prints what is HELD and what a subject's own restriction withholds (`restricted: true`
485
+ — listed, never held): the head counts them apart (`Grants (N held, M restricted)`), a restricted row
486
+ ends `· restricted`, and `IAM_RESTRICTED_NOTE` closes a listing that has any. Origins print as
487
+ given: `direct`, `default`, `group` (with `through`), `bundle` (a group's own listing: the names its
488
+ bundles select).
489
+ - The platform's staff group (`IAM_MEMBERS_GROUP`, `managed: true`) keeps its members, bundles and
490
+ existence: `manage_app_group` writes to them are refused by the IAM; its PERMISSIONS are the owner's,
491
+ granted and restricted with `manage_app_grant { group }`. `renderGroup` and the `manage_app_group`
492
+ description say exactly that — never "read-only".
493
+ - **Action-specific fields are checked BEFORE any call** (`iamToolHelper.missing`): an invite needs
494
+ `email`, an update something to change, a grant exactly ONE subject (a person, or a group with its
495
+ organization's `entitySlug` — never both), an `assign` no revoke-only mode (`REVOKE_ONLY_GRANT_MODES`:
496
+ `all`, `inherit`), a group's update `title` or the WHOLE `bundles` list, the members' moves
497
+ `profileIds`; **`remove` and `delete` need `confirm: true`**. A refusal is `isError` with nothing
498
+ called. The contract's schema admits every mode on both bodies; the IAM refuses the revoke-only
499
+ ones on a grant.
500
+ - A listing answered `external: true` prints `IAM_EXTERNAL` (the app's sign-in is managed in a console
501
+ of its own), never "nobody". `refusalHelper` phrases `iam:group:managed:` (the staff group: members,
502
+ bundles and existence the platform's, its permissions editable — placed ABOVE `iam-refused:`) and
503
+ `iam-refused:` (a managed definition or member, the last owner, an empty organization); every tool
504
+ runs in `answering`. The writes map to their read for the handover (`STATUS_TOOL_OF`). No
505
+ staff-synchronization tool.
506
+ - Specs: `tests/iam-tools.spec.ts` (hosts, annotations, every refused shape with no call — revoke-only
507
+ modes on assign included —, the calls made, `inherit` and records revokes, held/restricted counts and
508
+ the `bundle` origin, the staff group's line, the staff-group and generic refusals), `remote.spec.ts`
509
+ (scope placement, the account-base listing).
510
+
511
+ ## What the browser streams is read by cursor
512
+
513
+ The browser's sockets have no connector twin; their content does, as three read-only cursor feeds
514
+ (group `feeds` in `PLATFORM_CATALOGUE.capabilities`, rendered by `feedToolHelper` in `tools/feed.ts`):
515
+ **`project_activity { projectId?, after?, limit?, wait?, detail? }`** and **`notifications { after?,
516
+ wait? }`** on any host, **`file_changes { projectId?, after?, wait? }`** on a cloud target. Members
517
+ `ConnectorApi.project.activity`, `account.notifications`, `files.changes` over `connect.project.activity`,
518
+ `connect.account.notifications`, `connect.files.changes`; answers `ConnectFeedPage { cursor, entries,
519
+ gap }` (`ConnectFileChanges` adds `watching`).
520
+
521
+ - **The cursor is ECHOED** in every answer's structured content (`cursor`, `gap`, `entries`), and the
522
+ text ends with the exact call that reads on (`feedToolHelper.nextCall`: the same arguments, `after:
523
+ <cursor>`, `wait: 20`). Without `after` a read answers the latest entries; an empty feed answers
524
+ `CONNECT_FEED_START`. A `gap` line sends the parent to the domain status, never to a replay.
525
+ - `wait` is at most `CONNECT_FEED_WAIT_MAX_SEC` (20 s) — inside the 45 s deadline; the remote API gives
526
+ a held read ten seconds beyond its wait (`feedTimeout`) and sends only the query fields named.
527
+ - `detail: thinking` adds the model's own words (coalesced excerpts); the default `progress` leaves
528
+ them out. One line per entry, folded and cut at `FEED_LINE_MAX`; a toast is phrased from
529
+ `FEED_TOAST_WORDS`, never shown as its marker. `file_changes` says when nothing watches the tree
530
+ (`watching: false` — `preview_control { action: start }`).
531
+ - Every feed tool runs in `answering`. Specs: `tests/feed-tools.spec.ts` (hosts, annotations, the
532
+ echoed cursor and the read-on call, the gap and empty answers, the toast phrasing, `watching`, a
533
+ refusal answered, every kind one bounded line), `remote.spec.ts` (paths, queries, deadlines).
534
+
535
+ ## The project settings are ONE record, and the platform is the only validator
536
+
537
+ `project_settings` and `update_project_settings` read and change what a person edits on the project's
538
+ control panel — the copyright line, the organization name, the Terms and Privacy links and the Google
539
+ tag — through `ConnectorApi.projectBranding(projectId, scope?)` and
540
+ `saveProjectBranding(projectId, patch, scope?)` (`connect.project.branding.get` / `.save`,
541
+ `ConnectProjectBranding` / `ConnectProjectBrandingSave` from `@owlmeans/viable-common`). Every
542
+ settings and configuration tool takes `scope` (`CONFIG_SCOPES`: `ephemeral` or `production`) —
543
+ production's own set, taken at its next Publish (`settingsHelper.settingsReach(target, scope)` says so).
544
+
545
+ - **A save is a PATCH.** `settingsHelper.settingsPatch` keeps exactly the settings the call named, trimmed; an
546
+ omitted one keeps its stored value, and an EMPTY string is sent as given — for the Google tag that
547
+ is the removal, for the others a value the platform refuses. A call naming none is refused locally
548
+ and saves nothing. The answer is the merged record the platform stored, led by where the change
549
+ shows (`settingsHelper.settingsReach`): a cloud preview is rebuilt, production takes it at the next Publish; a
550
+ local project gets it in its `.env` and `run_local` builds with it.
551
+ - **`useOrganizationDefaults: true`** calls `copyOrganizationBranding` instead — ALONE: with any
552
+ other setting the tool refuses before calling anything, so one call never asks for two pushes.
553
+ - **The connector states the rules and checks none of them.** `PROJECT_SETTINGS[].rule` is the web
554
+ save's validation in words — never-empty copyright and organization; an `https://` address without
555
+ credentials or a single-slash path on the application (`/terms`, `/privacy` are the generated
556
+ pages); a `GTM-`/`G-`/`GT-`/`AW-`/`DC-` id or `''` — used by the tool description and by the
557
+ refusal. A second copy of the check would be a second answer that drifts from the platform's.
558
+ - **A refusal names the setting and its rule.** The platform refuses a field with
559
+ `AuthenPayloadError(<wire field>)`; the `authen:payload:` phrase answers a known setting with its
560
+ label and rule and "Nothing was changed", any other field with a generic sentence. The tool runs in
561
+ `answering`, so the refusal is its own `isError` result and is still logged.
562
+ - **The save attaches the connector first** (`ensureSession`), like a story mutation: it ends in the
563
+ platform's configuration push, which for a local project is a `Configure` operation this connector
564
+ answers.
565
+ - **A relative link is annotated, never "fixed"**: `/terms` renders as "the generated Terms page", so
566
+ a parent does not rewrite it into an absolute address that points away from the generated page.
567
+ - **The credit is read here and switched elsewhere.** `ConnectProjectBranding.credit` renders as one
568
+ line (`settingsHelper.creditLine`: hidden; shown with the request kept until the plan includes
569
+ white label; shown and hideable; shown and not on the plan). `set_platform_credit { hidden }` →
570
+ `setPlatformCredit` is its own gated route: a plan without white label is a `capability-required:`
571
+ person refusal, phrased and notified inside the tool (`answering`). The patch never carries it.
572
+
573
+ ## Configuration variables: a backend value is never shown
574
+
575
+ `project_configuration`, `update_project_configuration { backend?, frontend? }` (maps of
576
+ NAME → value; `configHelper.configBody` turns them into the wire's `ConnectConfigValue` lists, a side
577
+ the call did not name left out) and `recollect_configuration` (the platform's own model run) call
578
+ `ConnectorApi.config.*`; `configHelper.renderConfiguration` prints a backend variable as `set` /
579
+ `NOT SET` — never a value, even one an older platform sent — and a frontend variable with its public
580
+ value, then what still needs one. A save's answer names the variables set and never echoes a value.
581
+ Writes call `ensureSession` first. A refused name (`authen:payload:<NAME>` — malformed, named twice,
582
+ or already declared on the other side) reads as a sentence naming it.
583
+
584
+ ## The organization's defaults
585
+
586
+ `organization_branding`, `update_organization_branding { organizationName?, copyright? }` (a PATCH,
587
+ trimmed; a call naming neither is refused locally) and `backfill_project_branding` call
588
+ `ConnectorApi.account.branding.*` (`settingsHelper.renderOrganizationBranding`). A project keeps its
589
+ own copy after creation: `update_project_settings { useOrganizationDefaults: true }` copies them in.
590
+
591
+ ## The person's own records: inference, tokens, consents, the intent pickup
592
+
593
+ All `toolHostHelper.anyHost`, rendered by `accountHelper` (`tools/account.ts`); groups `inference`,
594
+ `access-tokens`, `privacy`, `intent` in `PLATFORM_CATALOGUE.capabilities`.
595
+
596
+ - `inference_settings { projectId? }` (read-only: the default, the attached or named project's
597
+ override and its effective mode, whether the plan allows `local`) and `set_inference_mode { level:
598
+ account | project, mode: cloud | local | inherit, projectId? }` (`inherit` = `null`, a project
599
+ only; an account `inherit` is refused locally). Both end with `INFERENCE_REACH`: the setting is the
600
+ default of the web application and connector hosts. A running stdio connector keeps its resolved
601
+ mode until restarted; explicit `--llm` or `VIABLE_LLM` overrides outrank the account preference.
602
+ A plan without the local mode is the
603
+ `capability-required:` person refusal, notified.
604
+ - `list_access_tokens` (read-only, never a secret) and `revoke_access_token { tokenId, confirm }`
605
+ (destructive, idempotent; revoking this connector's own token signs it out). No tool creates a
606
+ token. A foreign id is phrased (`authorization:forbidden:token`).
607
+ - `privacy_choices` (read-only, the SAVED answers: given / not given / never answered / wording
608
+ revised) and `withdraw_marketing_consent { keys | all }` (`all` reads the status and withdraws the
609
+ given ones; both together refused). No tool gives a consent. An unknown key is phrased
610
+ (`marketing-consent:unknown:`).
611
+ - `pickup_intent { code }` — the ref, or the whole `/start?ref=…` address (`accountHelper.intentRefOf`);
612
+ answers the prompt and points at `create_project` once the user confirms. `viable-intent:expired:`
613
+ and `viable-intent:throttled:` are phrased.
614
+ - Spec: `tests/account-tools.spec.ts`.
615
+
616
+ ## Planning kits are described, then applied; the platform writes and rebuilds
617
+
618
+ A planning kit is a ready set of card types and status flows for one kind of work-management
619
+ product, which the platform writes into the target's common package (`PLANNING`).
620
+ `describe_planning_kits` (`ConnectorApi.project.kitDescribe(projectId)`, GET
621
+ `connect.project.kit.describe` → `{ kits: PlanningKitView[] }`) lists each kit's purpose, container,
622
+ types with their main flow and each flow's statuses; it opens no session. `apply_planning_kit`
623
+ (`project.kitApply(projectId, { kit, types? })`, POST `connect.project.kit.apply` →
624
+ `{ applied, skipped, warnings }`) refuses a call without `kit` locally, attaches the connector first
625
+ (`ensureSession`: the write is a file operation a local project's connector answers), runs in
626
+ `answering`, and answers what was written, left out and warned. `types` keeps those kit type keys;
627
+ omitted keeps all. Both are offered on every host, in the `planning-kits` capability group. The SDK
628
+ never renders the literals or rebuilds — the platform does, inside the 45-second ceiling.
629
+
630
+ ## `describe_platform` also says what a generated application CARRIES
631
+
632
+ `PLATFORM_CATALOGUE.features` holds facts about the product the runs produce — the landing gate, the
633
+ generated Terms and Privacy pages, the consent-gated Google tag, the look, production builds without
634
+ preview scaffolding — rendered whole on every host under "WHAT A GENERATED APPLICATION CARRIES", with
635
+ only their `tools` narrowed to what the host offers. A parent that is not told the platform generates
636
+ the legal pages writes its own beside them. `describe_capabilities` ends with `GENERATED_SUMMARY`, the
637
+ same facts in one sentence, because it is read at the same moment by a parent that may never call
638
+ `describe_platform`; the two sit side by side in `platform.ts`. The pipelines' `stages` name the steps
639
+ a run can stop at (`landing` and `legal` in init, `landing` in story development).
640
+
641
+ ## A balance, consent, confirmation or plan refusal is phrased for a person, and pushed through `notify`
642
+
643
+ `refusalHelper.personRefusalPhrase(e, retry?)` (`tools/refusal.ts`) phrases the refusals only a PERSON can
644
+ resolve — the three of `@owlmeans/viable-common` `connect/errors.ts` by `instanceof`, and the plan's
645
+ two by MARKER, because `CapabilityRequired` / `LimitExhausted` are declared in `@owlmeans/payment`,
646
+ which this package does not depend on — and answers `null` for anything else:
647
+ `capability-required:<param|param>` → `capabilityRequiredPhrase` (the feature named from
648
+ `CAPABILITY_LABELS`, the plan changed in Billing, nothing the parent does unlocks it) and
649
+ `limit-exhausted:<key>:<used>/<limit>[:<ISO>]` → `limitExhaustedPhrase` (the limit, how full, the
650
+ renewal day). Both also stand in `REFUSALS` for the stored-text path. Both `registerCatalogue`'s catch and the conversion tools' `answering` use it, so a
651
+ conversion's balance refusal reads exactly like any other tool's:
652
+
653
+ - `ConnectOutOfCredits` — rather than the raw `viable-connect:out-of-credits:...` marker, the tool
654
+ result reads as a sentence: what it needed, what the account has, and a link to top up.
655
+ - `ConnectConsentRequired` — the EU spend consent: "Nothing was started", why (credits bought less
656
+ than 14 days ago may only be used once a person expressly asks, the purchase still withdrawable up
657
+ to the last day), the `consentUrl` to open and confirm in the browser, and that the call must NOT
658
+ be retried automatically — only after the user says they confirmed (`refusalHelper.consentRequiredPhrase(url,
659
+ deadline)`, shared with the stored-text entry below).
660
+ - `ConnectConfirmationRequired` — a conversion step that would use the plan's conversion or spend
661
+ credits (`refusalHelper.confirmationRequiredPhrase(fields, retry?)`): "Nothing was started"; for a start, what the
662
+ plan's conversion covers (free up to `cap` credits, the conversion limit; beyond it credit limits
663
+ first, then topped-up credits; every stage quoted and asked first); for a stage, its estimate
664
+ split ("580,000 from the conversion limit, 150,000 from the organization's credit limits, $2.40
665
+ of topped-up credits" — credit limits as credits, topped-up credits as money) and what the limit
666
+ has used and left. Then: tell the user exactly this, and only after they agree make the call it
667
+ prints — never `confirm: true` on the model's own. Every variant names the conversion limit (a
668
+ conversion with no plan unit "has no conversion limit").
669
+
670
+ Their fields travel packed into the message (only `type` and `message` survive the platform's
671
+ internal HTTP hop) and are read back with `finalizeUnmarshal()`. All three are also handed to the
672
+ optional `ToolDeps.notify?('warning', text)`, which a host wires to its own out-of-band channel —
673
+ the stdio `viable-mcp` host sends an MCP `notifications/message`; the platform's stateless `/mcp`
674
+ host has no channel and omits it, so `notify` is always best-effort and optional. Every other
675
+ error returns through `refusalHelper.refusalPhrase` and never calls `notify`.
676
+
677
+ **A conversion's confirmation is answered inside its tool, with the exact call to repeat**
678
+ (`confirming` in `catalogue.ts`): `convert_project` / `proceed_conversion` print
679
+ `<tool> {"projectId":…[,"decision":…,"note":…],"confirm":true}` — the project NAMED, because the
680
+ URL-configured host keeps no attachment between calls and a bare repeat of `convert_project` there
681
+ would file a second conversion (the start is what a confirmation stops; the create before it is
682
+ never repeated). Both take `confirm` (`z.boolean().default(false)`, described), sent only when the
683
+ parent passed `true`, and their descriptions explain the conversion limit (1,000,000 credits today),
684
+ the spend order and that a delegated conversion is never asked — so a parent can say it before it is
685
+ refused. A production body carries only the status: a bare `api:client:status:428` to a call sent
686
+ WITHOUT `confirm` is answered with `refusalHelper.unconfirmedConversionPhrase(retry)` (the confirmation, and the
687
+ consent as what a second refusal of the confirmed call would mean); to a confirmed call it can only
688
+ be the consent and falls to `REFUSALS`. `serverInstructions` says the same in one sentence of the
689
+ workflow paragraph.
690
+
691
+ `proceed_conversion` also takes the person's edits flat — `name`, `description`, `specification`,
692
+ `vision`, `designSystem` — and sends the ones given nested as `update` (`PROCEED_UPDATE_FIELDS`);
693
+ the platform accepts them only with the decision that starts the extraction, and the printed repeat
694
+ call carries them too.
695
+
696
+ The integrations' refusals each have a sentence in `REFUSALS` as well: `github:not-connected`,
697
+ `github:not-published`, `oauth:invalid-or-expired-state` (the GitHub authorization),
698
+ `viable-domain:taken:` (a custom domain held by another project), `iam:group:managed:` (the staff
699
+ group's members, bundles or existence) and `iam-refused:` (the app's own sign-in refusing a console
700
+ change on its terms).
701
+
702
+ The same refusals also arrive where no class survives, and `REFUSALS` phrases them by marker:
703
+ `viable-connect:consent-required:` (a stored run error; the URL and deadline parsed from the
704
+ detail), `viable-connect:confirmation-required:` (`ConnectConfirmationRequired.decode` of the
705
+ detail, the generic "same arguments and confirm: true" repeat), `performance-consent-required` (the web refusal inside a planning `commit-failed:` or a
706
+ stored error — no URL, so "Billing in the OwlMeans web application") — both ABOVE the planning
707
+ markers, because the consent is what the person acts on — and, for a production body that carries
708
+ only an incident id (`@owlmeans/api` `ApiStatusError`), `api:client:status:428` (the consent
709
+ sentence) and `api:client:status:402` (the balance sentence).
710
+
711
+ ## The session loop: a pull loop that never stops, a serial worker, and free to redeliver
712
+
713
+ `openSession` runs two loops. The PULL loop files model tasks and questions for the parent and hands
714
+ every local operation (`SlotCommand`, `Configure`) to the WORKER, which answers them **one at a time**.
715
+ Serial because the thing on the other end of a local operation is a filesystem the platform believes
716
+ it is the only writer of — two concurrent template writes into one tree is not a throughput problem,
717
+ it is a corrupted tree. Separate because a local command can take minutes (`bun install`), and a
718
+ model task the platform is blocked on must still be delivered meanwhile. An operation queued or
719
+ running on the worker is not queued again when it is redelivered. A pull that came back empty in
720
+ under `FAST_PULL_MS` (1 s) is followed by `EMPTY_PULL_PAUSE_MS` (1.5 s) of rest.
721
+
722
+ The task queue hands out the task with the earliest `expiresAt` first (a synchronous check expires
723
+ in seconds, a run's call in minutes). `take(waitMs, signal?)` is cancellable and REMOVES an aborted
724
+ waiter — a leftover one swallows the next item, held as outstanding and shown to nobody — and
725
+ `available(waitMs, signal?)` waits for a queued item without consuming it.
726
+
727
+ Results are remembered by operation id. A connector that reconnects is handed everything still
728
+ outstanding, **including whatever it had already answered when the connection dropped**, and the
729
+ cached result answers it without doing the work again — re-executing a build or a model task because
730
+ an acknowledgement was lost is exactly the cost this avoids. A failed `submitOp` is logged, never
731
+ thrown: the operation stays in the platform's store and is redelivered.
732
+
733
+ Model tasks are **not executed here at all** — they are the parent agent's to run in its own clean
734
+ subagent, and what the loop owes them is delivery. The operation id is kept beside each task and
735
+ never shown to the parent: a parent that could name one could answer an operation it was never
736
+ given.
737
+
738
+ A local operation that fails reports `ConnectOpErrorKind.Refused`, never `Unavailable`. The
739
+ connector ran and could not do the thing; `Unavailable` tells the platform to give up on a run that
740
+ is fine.
741
+
742
+ **The pull long-poll is the only transport.** It works through every proxy and needs no
743
+ reconnection logic, and the URL-configured host opens no session at all, so it never pulls. The
744
+ contract declares no connector socket, and `SessionStats.transport` is `'pull'` or `'none'`;
745
+ presence is refreshed by every pull and every submit.
746
+
747
+ ## An answer is READ in the shapes models produce, not only the one asked for
748
+
749
+ The tool-call instruction asks for a JSON array and models mostly comply — but a single call comes
750
+ back as a bare object often enough, and some wrap the array in `{tool_calls: […]}`, that assuming
751
+ the array is a defect rather than strictness. The reference driver cast straight to an array, so a
752
+ bare object threw `parsed.map is not a function`; the TypeError was handed back AS the answer, the
753
+ model ladder read that as a bad answer and asked again with feedback that said nothing about what
754
+ was wrong, and the story failed with `retry-exceeded` having never been told. `toolCallsOf` accepts
755
+ all three shapes and still throws for an answer carrying no call at all — an empty answer is a bad
756
+ answer, and the retry is the right response to it.
757
+
758
+ The same rule is why `makeTaskEnvelopeModel(task).parseTaskResult` refuses with a described reason rather than a stack trace:
759
+ whatever comes back becomes a model's next prompt, so it has to be readable by one.
760
+
761
+ ## A redelivered model task must NOT be handed over twice
762
+
763
+ The platform redelivers every unanswered operation on each poll — that is what makes a connector
764
+ restart cost a round trip instead of a run, and for a slot command it is free because the result
765
+ cache answers it. A MODEL task is different: it is answered by a parent agent in tens of seconds,
766
+ and every poll during that window used to queue another copy. The parent then ran the same task
767
+ over and over — real model calls, paid for by the user, thrown away — while the job stayed blocked,
768
+ because only the first answer routed to a live operation and every later one reported that no
769
+ operation was waiting.
770
+
771
+ `TaskQueue` therefore remembers every task id it has handed over and ignores a second delivery of
772
+ one, keeping the memory after the task settles so a redelivery that raced the platform's deletion
773
+ is ignored rather than answered again. The operation id IS refreshed on a redelivery, because the
774
+ answer must route to the operation the platform is currently waiting on. `push` returns whether the
775
+ task was accepted, and `tasksDelivered` counts only accepted ones — a counter that grew per poll
776
+ would make a stuck run look busy.
777
+
778
+ ## The task envelope is text for a model to act on
779
+
780
+ The reader is a language model working from a tool result, so it must be able to act without a
781
+ schema in front of it. Everything it must do is stated **in order and before the material** — how to
782
+ run it (in a clean subagent, per harness), what to call afterwards (`submit_task_result`, with the
783
+ id spelled out), and what shape the answer takes — so a model that stops reading early has still
784
+ read the instruction. The task id appears at the top and in the follow-up call because it is the
785
+ only thing that routes an answer back: a parent that paraphrases the rest but copies the id still
786
+ works. A retry says which attempt it is and why the previous answer was refused. Harnesses differ
787
+ only in the isolation mechanism, never in what is asked; `Other` gets the request stated rather than
788
+ mechanised. Record-interaction tasks preserve their complete system instructions and JSON answer
789
+ schema, including host/parent bindings and `selectionComponents`, through every harness. They need
790
+ no navigation-specific MCP arguments: guided story tools and direct free flight share the platform's
791
+ record-placement and wiring capabilities.
792
+
793
+ For Codex, the parent gives a fresh `viable-worker` only the task's system prompt, then its
794
+ conversation, then the requested result shape: the outer handoff instruction and
795
+ `submit_task_result` call are parent-only. It passes that final response through unchanged, but must
796
+ preserve the mode: a `text` task returns the requested source or text and never a JSON tool-call
797
+ array; that array belongs only to `tools` mode.
798
+
799
+ **`makeTaskEnvelopeModel(task).parseTaskResult` refuses a malformed answer locally**, and returns a `problem` rather than
800
+ throwing. The refusal is worth more than the parse: an answer that reaches the platform malformed
801
+ costs a whole retry — another task, another subagent, another wait — while one caught here is a
802
+ sentence the parent can act on immediately, with the subagent's context still open. It unfences,
803
+ rejects an array or a scalar in `Json` mode, and rejects a tool name the task never offered.
804
+
805
+ A task is handed out ONCE, and the platform then waits on it for up to 45 minutes. So the loop has
806
+ to survive a parent that lost the envelope — a compacted conversation, a subagent that died before
807
+ answering. `next_task` takes an optional `taskId` that re-reads a task already handed out, and when
808
+ nothing new is queued but something is unanswered it NAMES the outstanding ids rather than re-handing
809
+ them: re-handing would have a parent whose subagent is still working run the same task twice. A
810
+ refused answer keeps the task outstanding for exactly the same reason.
811
+
812
+ ## Extension tools come from the host, never from this package
813
+
814
+ The catalogue is closed over what the SDK itself implements. A hosting process that offers more —
815
+ the stdio connector adds `describe_harness` and `install_harness`, which write a coding agent's
816
+ instruction, subagent and MCP files — hands them in as `ToolHost.extensions`. `catalogueHelper
817
+ .visibleTools(host)` appends them to the catalogue and filters both by `availability`, so
818
+ `registerCatalogue`, `renderPlatform` and `serverInstructions` treat an extension exactly like a
819
+ built-in tool; a host that passes none offers none, and the platform catalogue's `harness` group
820
+ renders as absent there. A group may name a tool only the catalogue or `EXTENSION_TOOLS` knows —
821
+ `platform.spec.ts` holds that line. This package never imports a host's extension: the SDK is
822
+ public, the agent-setup pair is not.
823
+
824
+ ## The executor is the publisher's job, on somebody's laptop
825
+
826
+ `makeLocalSlotExecutor(dir)` answers the platform's slot commands against a directory here. The
827
+ dispatcher is the publisher's, switch for switch: the platform's remote helpers parse the ANSWERS
828
+ and cannot tell whether a pod or a laptop produced one, so an ordinary failure comes back in the
829
+ same shape ("error text or null"), and only an unknown command — a protocol fault rather than a
830
+ project fault — escapes as an exception. Everything is resolved per call rather than closed over,
831
+ because a re-initialization replaces the tree under a running connector. The integrity verdict is
832
+ forgotten after every command that changes the tree.
833
+
834
+ Rules for the local executor:
835
+
836
+ - **The local browser environment defaults `APP_UNSECURE` to `true`** so its generated client
837
+ calls the HTTP web proxy. The web `.env` can override this default; root backend secrets never
838
+ enter the browser environment. The platform's local configure uses the web port for both
839
+ `FRONTEND_PORT` and `BACKEND_PORT`, with `/api` proxied to the backend port.
840
+
841
+ - **`Bun` forwards `options.env` into the child process**, alongside `options.subproject` for
842
+ its working directory. The spawn helper merges these values over the inherited environment
843
+ without changing the parent process or the project's configuration files. Runtime probes carry
844
+ their input in these variables; dropping them prevents the runner from producing its report.
845
+ `executor-shell.spec.ts` exercises the public slot-command dispatcher with a real Bun child.
846
+
847
+ - **`makeTargetEnvHelper(dir).backendEnv` reads the root `.env` and `.frontendEnv` the web package's own — the file split IS
848
+ the leak boundary.** The publisher has an allow-list (`frontendEnvVars`/`frontendSecrets`); here
849
+ the two files are never merged in either direction, because `FRONTEND_ENV_KEYS` announces
850
+ everything in the frontend set to the bundler and a merge would compile `OIDC_SECRET` and
851
+ `DATABASE_URL` into a bundle every visitor downloads.
852
+ - **`Discard` passes `git clean -fd -e .viable`.** `.viable/connect.json` is untracked until someone
853
+ commits it, and a discard that swept it leaves a tree no later session can recognise as a platform
854
+ project at all.
855
+ - **The always-keep list must not leave empty parent shells.** A wipe keeps `.viable`, `.env`, the
856
+ web package's `.env` and `.git` whatever the caller asked, so a directory holding a kept path is
857
+ walked and pruned rather than removed — and a keep that was not there (the web `.env` every
858
+ project without local branding lacks) must not leave the walked directory behind as a skeleton the
859
+ install then lands in.
860
+ - **An absolute path from elsewhere on the machine is re-rooted inside the project**, not refused —
861
+ the publisher's own behaviour, kept deliberately and pinned by a test. Concatenating it onto the
862
+ root instead builds a shadow tree: the write reports success, a later read of the same name
863
+ resolves to the untouched original, and the change looks silently ignored. `resolveInProject`
864
+ (exported as `confineToProject`) then compares the fully resolved path to the root — confinement,
865
+ not sanitization, and applied on **every** fs call rather than a `..`-to-`.` replace at one caller.
866
+ - **`run/state.ts` sits below both the shell commands and the run**, so the three do not form a
867
+ cycle: `DbSync` bounces the api child, `DbSync` is a shell command, and the shell commands are
868
+ reached through the executor the run is built on. Everything that touches a child process lives
869
+ there, and the run record is a file because the processes outlive the call — and, on a restarted
870
+ connector, the process — that started them.
871
+
872
+ ## Attaching a session is what writes the project marker
873
+
874
+ `.viable/connect.json` is how a connector started in a directory tomorrow knows which project it
875
+ is, and `attach_project` finds a project by its directory through it. It was declared, read at MCP
876
+ startup and documented in `mcp.md` before anything wrote it — a marker nothing writes is a lookup
877
+ that always misses.
878
+
879
+ `openSession` writes it when the session names a project and a directory, which is the moment the
880
+ directory becomes that project's. Best-effort and idempotent: a session that works is worth more
881
+ than a note about it, and it is skipped when the marker already names the same project. It holds
882
+ no secret and is meant to be committed, which is why the API URL and the slug are in it and the
883
+ token never is.
884
+
885
+ ## The managed block yields to every key the user assigned
886
+
887
+ A `.env` gives the LAST assignment of a key and the platform's block is appended, so a platform line
888
+ silently outranks whatever the user wrote above it. That made the documented promise — everything
889
+ outside the block is yours — false for the one value the platform cannot supply: `DATABASE_URL`, the
890
+ line the setup guide tells people to write. The application connected to the placeholder no matter
891
+ what its owner put in the file.
892
+
893
+ `makeProjectEnvHelper(dir).writeEnv` therefore reads the file with the block cut out, and replaces any block line assigning a
894
+ key the user already assigns with a comment saying so. The rule is about VALUES, not lines: a key the
895
+ user has not claimed is still the platform's to set, and a key they later delete goes back to the
896
+ platform on the next push.
897
+
898
+ ## The two services a local project needs are ASKED for, never assumed
899
+
900
+ The platform provisions nothing on somebody's own machine, so a database — and, for a project with a
901
+ worker, a queue store — is the one thing it cannot supply and must not guess. `local_setup_guide`
902
+ reports what the machine already provides and, where something is missing, returns a QUESTION with
903
+ three answers: the user already has one, wants one installed here, or wants a free hosted plan.
904
+ Guessing means either a container on somebody's machine they did not ask for, or a connection string
905
+ invented for a database that does not exist — and the second surfaces minutes later as a boot error
906
+ about a connection, which sends the reader looking for a database rather than for a typo.
907
+
908
+ `set_local_service` records what they answered: it writes the value into the project's own half of
909
+ the `.env`, replacing that key rather than appending a second copy, and PROBES it immediately, so a
910
+ string that does not connect is caught while the user is still in the conversation. A credential
911
+ never reaches the marker, a harness configuration, or the platform, and the guide warns when the
912
+ file is not git-ignored.
913
+
914
+ ## A detached child outlives the connector, so the port is RECLAIMED before it is used
915
+
916
+ Every target process is spawned detached — that is what keeps a build from dying with the tool that
917
+ started it — and detaching is also why a connector killed mid-run (a host restart, a Ctrl-C, a
918
+ crashed test) leaves one alive. What survives holds port 3000, and the next attempt fails with the
919
+ TARGET's own "the api port is already in use", which reads as a defect in the generated app rather
920
+ than as a leftover.
921
+
922
+ Two rules, both ported from the publisher because a laptop needs them more than a pod does:
923
+ - `spawnHelper.killGroupAndWait` never returns early on the child's own exit code. The child is a shell wrapping
924
+ `bun`, so a signal the shell dies from leaves `bun` alive while `exitCode` says gone; the wait
925
+ polls the GROUP instead of the child's exit event.
926
+ - `spawnHelper.reclaimPort` runs before a spawn as well as after one, and matches on the script AND the process
927
+ marker (`--viable-api`, `--viable-worker`, `--viable-boot-check`) — never a port alone, and never
928
+ a marker alone, since reclaiming sends SIGKILL and a marker on its own matches any command line
929
+ that merely mentions it.
930
+
931
+ ## Running the generated app locally
932
+
933
+ `makeLocalRunHelper(dir).runLocal` builds and starts the api, the worker (when the project has one) and a local server that
934
+ holds the web port and proxies `/api` to the api process on the same origin — the generated app's
935
+ configuration assumes one origin, and splitting them across two loopback ports would put its CORS
936
+ setup in the path of a developer's first page load. There is no respawn ladder, no reconciler and no
937
+ health route: a developer watching their own terminal is the supervisor. What IS reproduced is the
938
+ shape — the same ports, the same argv marker, the same environment, and the same "port ownership is
939
+ the only proof" rule about restarts. Nothing from the publisher's public-hostname policy (robots,
940
+ CSP, the preview error reporter, the build-state header) is reproduced: a loopback address has no
941
+ reputation to spend and no crawler to answer.
942
+
943
+ `run_local` refuses to start a project whose `.env` is still missing a required key, because an app
944
+ launched without a database URL fails at boot with a message about a connection, which sends the
945
+ reader looking for a database that was never configured.
946
+
947
+ ## Tests
948
+
949
+ In the `@owlmeans/viable-sdk` package directory, `bun x tsc -p tests/tsconfig.json --noEmit` checks the SDK sources and all test typings.
950
+
951
+ `bun test ./tests` — offline: the envelope and its parser, the tool catalogue and its extension seam,
952
+ the `registerCatalogue` out-of-credits, consent and planning-refusal phrasing and `notify` wiring
953
+ (`mcp-catalogue.spec.ts`; the consent marker, the bare 428/402 statuses and a consent inside a
954
+ commit failure in `catalogue.spec.ts`; the conversion confirmation — schemas and descriptions, the
955
+ start and stage phrasing, the repeat call naming a freshly filed project, the stored marker, the
956
+ bare 428 either side of `confirm`, the confirmed call's balance and consent refusals and the MCP
957
+ boundary — in `conversion-confirmation.spec.ts`; the batch, stop and steer tools — attach order, codes
958
+ resolved to card ids, the batch rendering and each final status's `next:`, cancel-then-stop, no call
959
+ without `confirm`, only the named stop fields, the stopping/steerable status lines and the three
960
+ refusals at the MCP boundary — in `run-control.spec.ts`), the executor's files/git/layout rules, and the marker + managed-`.env`
961
+ block. The story tools run over a REAL `@owlmeans/server-planning` service (memory store, the Viable
962
+ types and flows, one plugin standing in for the platform's format seam) built in `tests/context.ts`,
963
+ the landing mark included; the settings, configuration, credit, organization (`configuration.spec.ts`:
964
+ no backend value printed, the maps sent as lists, the white-label refusal phrased and notified, the
965
+ defaults copy alone, the remote routes and scopes) and planning-kit tools over a recorded `ConnectorApi` (order of
966
+ session and save or apply, the patch or kit body sent, the phrased refusal). `platform.spec.ts` pins that every tool a
967
+ pipeline, feature or group names exists, every tool is in exactly one group, every group has its
968
+ `absent` sentence, and `serverInstructions` names a tool of every group a host offers and none it hides; `planning-wiring.spec.ts` pins
969
+ the planning aliases and paths a context binds, and `remote.spec.ts` drives the remote facade, the
970
+ settings and the kit routes through a captured transport to pin their paths and deadlines, and pins that the remote
971
+ `ConnectorApi` has no member without a connector route (the `files`, `sandbox` and `slot` members by
972
+ name) and every connector alias is bound, and that a
973
+ delegated create keeps the tool deadline and sends its `sessionId`. `call-collect.spec.ts` drives the
974
+ transport through the same captured client (`captureTransport` replaces the context's API CLIENT, so
975
+ the SDK's transport stays in front): the header only in the delegated mode and only on writes, a
976
+ reused id, pending → collect → settled for a connector call and a planning execute, a settled error
977
+ rethrown as its class, `lost` → `ConnectCallLost`, a dropped hop retried, ordinary answers untouched,
978
+ and the handover over a call that went pending. `catalogue.spec.ts` also pins the manual story moves
979
+ over the real planning service (order, the refusal, a late commit), `delete_project` / `unlock_project_agent`
980
+ (no call without `confirm`, the required `projectId`, release → detach → destroy only for the attached
981
+ project), the annotations (declared everywhere, the destructive set, passed through `registerCatalogue`)
982
+ and the plan and integration phrasings, the cloud-only file and preview tools on both hosts with
983
+ `list_slots` everywhere, `list_files`'s kind switch (the category only with `meta`), `write_file`
984
+ attaching before its save and naming a failed rebuild, `delete_file` making no call without
985
+ `confirm`, each `preview_control` action's member, and the protected-file and missing-workload
986
+ phrasings; `conversion-confirmation.spec.ts` the proceed edits nested as
987
+ `update` and repeated in the confirmed call; `remote.spec.ts` the delete and unlock routes. `handover.spec.ts` drives the delegated
988
+ `registerCatalogue` over a real task queue: a task first is the answer and the submit replies with
989
+ the call's result, a call first answers as before, an identical repeat joins the running call, a
990
+ refused call is reported in `settled[]`, `next_task` names a call still running, and a delegated
991
+ create detaches, opens an unattached session, sends its id and attaches the new project (none of it
992
+ in the cloud mode). `task-queue.spec.ts` pins the cancellable take, the non-consuming availability
993
+ and the expiry order; `session-worker.spec.ts` that a model task is delivered while a slow slot
994
+ command runs and a redelivered command runs once.
995
+
996
+ ## Depends On
997
+
998
+ - `@owlmeans/viable-common` — the whole wire contract · `@owlmeans/auth-token` — the carrier guard
999
+ - `@owlmeans/planning` — the protocol tree, the facade contract and its refusals ·
1000
+ `@owlmeans/client-planning` — the remote facade and the long-poll commit wait
1001
+ - `@owlmeans/api`, `@owlmeans/client-context`, `@owlmeans/client-entrypoint`, `@owlmeans/client-config`,
1002
+ `@owlmeans/auth-common`, `@owlmeans/entrypoint`, `@owlmeans/route`, `@owlmeans/socket`,
1003
+ `@owlmeans/config`, `@owlmeans/context`, `@owlmeans/error`, `@owlmeans/basic-ids`
1004
+ - `@modelcontextprotocol/sdk` (types only, structurally), `zod`, `fs-extra`, `globby`, `ajv`
1005
+
1006
+ ## Blueprint discovery before project creation
1007
+
1008
+ `ConnectorApi.account.blueprints()` reads the installed `BlueprintCatalogue` from authenticated
1009
+ `/connect/account/blueprints`, before a project or session exists. `describe_blueprints` renders and
1010
+ returns its full structured catalogue: blueprint ids, stacks, resolved cases, supported application
1011
+ categories, tenancy and planning resource capabilities. Both stdio MCP and platform `/mcp` expose
1012
+ this same SDK tool. The platform bridges to the signed agent registry; never synthesize the deployed
1013
+ catalogue from frontend presets or a closed-package import.
1014
+
1015
+ The remote planning facade hydrates HTTP and collected receipts with empty advisory organization
1016
+ metadata (`entityId: ''`). Native organization IDs are absent from planning wire replies. Use the
1017
+ authenticated organization entity slug and the planning scope lifecycle for client isolation;
1018
+ never resolve permissions or organization identity from a returned planning record.
1019
+
1020
+ Server instructions explicitly describe the stock `owlmeans-fullstack-ts` cases: `web`, `scalable`,
1021
+ `ai-pipeline`, `ai-agent`, `game`, `work-management`, `work-management-tenanted`. Work categories
1022
+ include project tracking, CRM, service desk, inventory, recruiting, field service and process;
1023
+ game categories include casual, online-turn and online-live. The live catalogue is authoritative
1024
+ for installed custom registries. Read it before proposing a project category.
1025
+
1026
+
1027
+ ## Related
1028
+
1029
+ - `@owlmeans/viable-mcp` — the npx stdio server built on this (closed-source; its skill ships in its own package)
1030
+ - [[auth-token]] — the credential and its carrier guard
1031
+ - [[client-planning]] · [[planning]] — the facade the story tools write through
1032
+ - `@owlmeans/llm-delegate` (`internal` monorepo, skill `llm-delegate`) — the other end of a model
1033
+ task, inside the platform