@agent-native/dispatch 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/README.md +38 -1
  2. package/dist/actions/apply-dream-proposal.d.ts +3 -0
  3. package/dist/actions/apply-dream-proposal.d.ts.map +1 -0
  4. package/dist/actions/apply-dream-proposal.js +11 -0
  5. package/dist/actions/apply-dream-proposal.js.map +1 -0
  6. package/dist/actions/create-dream-report.d.ts +3 -0
  7. package/dist/actions/create-dream-report.d.ts.map +1 -0
  8. package/dist/actions/create-dream-report.js +67 -0
  9. package/dist/actions/create-dream-report.js.map +1 -0
  10. package/dist/actions/create-pylon-ticket.d.ts +3 -0
  11. package/dist/actions/create-pylon-ticket.d.ts.map +1 -0
  12. package/dist/actions/create-pylon-ticket.js +94 -0
  13. package/dist/actions/create-pylon-ticket.js.map +1 -0
  14. package/dist/actions/create-vault-grant.js +1 -1
  15. package/dist/actions/create-vault-grant.js.map +1 -1
  16. package/dist/actions/create-vault-secret.d.ts.map +1 -1
  17. package/dist/actions/create-vault-secret.js +4 -3
  18. package/dist/actions/create-vault-secret.js.map +1 -1
  19. package/dist/actions/create-workspace-resource.js +3 -3
  20. package/dist/actions/create-workspace-resource.js.map +1 -1
  21. package/dist/actions/delete-workspace-resource.js +1 -1
  22. package/dist/actions/delete-workspace-resource.js.map +1 -1
  23. package/dist/actions/ensure-dream-job.d.ts +3 -0
  24. package/dist/actions/ensure-dream-job.d.ts.map +1 -0
  25. package/dist/actions/ensure-dream-job.js +73 -0
  26. package/dist/actions/ensure-dream-job.js.map +1 -0
  27. package/dist/actions/get-dream-settings.d.ts +3 -0
  28. package/dist/actions/get-dream-settings.d.ts.map +1 -0
  29. package/dist/actions/get-dream-settings.js +11 -0
  30. package/dist/actions/get-dream-settings.js.map +1 -0
  31. package/dist/actions/get-dream.d.ts +3 -0
  32. package/dist/actions/get-dream.d.ts.map +1 -0
  33. package/dist/actions/get-dream.js +13 -0
  34. package/dist/actions/get-dream.js.map +1 -0
  35. package/dist/actions/get-vault-access-settings.d.ts +3 -0
  36. package/dist/actions/get-vault-access-settings.d.ts.map +1 -0
  37. package/dist/actions/get-vault-access-settings.js +10 -0
  38. package/dist/actions/get-vault-access-settings.js.map +1 -0
  39. package/dist/actions/get-workspace-resource-effective-context.d.ts +3 -0
  40. package/dist/actions/get-workspace-resource-effective-context.d.ts.map +1 -0
  41. package/dist/actions/get-workspace-resource-effective-context.js +27 -0
  42. package/dist/actions/get-workspace-resource-effective-context.js.map +1 -0
  43. package/dist/actions/grant-vault-secrets-to-app.js +1 -1
  44. package/dist/actions/grant-vault-secrets-to-app.js.map +1 -1
  45. package/dist/actions/index.d.ts.map +1 -1
  46. package/dist/actions/index.js +38 -4
  47. package/dist/actions/index.js.map +1 -1
  48. package/dist/actions/list-dream-candidates.d.ts +3 -0
  49. package/dist/actions/list-dream-candidates.d.ts.map +1 -0
  50. package/dist/actions/list-dream-candidates.js +68 -0
  51. package/dist/actions/list-dream-candidates.js.map +1 -0
  52. package/dist/actions/list-dreams.d.ts +3 -0
  53. package/dist/actions/list-dreams.d.ts.map +1 -0
  54. package/dist/actions/list-dreams.js +17 -0
  55. package/dist/actions/list-dreams.js.map +1 -0
  56. package/dist/actions/list-integrations-catalog.js +1 -1
  57. package/dist/actions/list-integrations-catalog.js.map +1 -1
  58. package/dist/actions/list-vault-grants.js +1 -1
  59. package/dist/actions/list-vault-grants.js.map +1 -1
  60. package/dist/actions/list-workspace-apps.d.ts.map +1 -1
  61. package/dist/actions/list-workspace-apps.js +5 -1
  62. package/dist/actions/list-workspace-apps.js.map +1 -1
  63. package/dist/actions/list-workspace-resources-for-app.d.ts +3 -0
  64. package/dist/actions/list-workspace-resources-for-app.d.ts.map +1 -0
  65. package/dist/actions/list-workspace-resources-for-app.js +12 -0
  66. package/dist/actions/list-workspace-resources-for-app.js.map +1 -0
  67. package/dist/actions/list-workspace-resources.js +1 -1
  68. package/dist/actions/list-workspace-resources.js.map +1 -1
  69. package/dist/actions/navigate.d.ts +1 -0
  70. package/dist/actions/navigate.d.ts.map +1 -1
  71. package/dist/actions/navigate.js +2 -1
  72. package/dist/actions/navigate.js.map +1 -1
  73. package/dist/actions/preview-dream-proposal.d.ts +3 -0
  74. package/dist/actions/preview-dream-proposal.d.ts.map +1 -0
  75. package/dist/actions/preview-dream-proposal.js +13 -0
  76. package/dist/actions/preview-dream-proposal.js.map +1 -0
  77. package/dist/actions/preview-workspace-resource-change.d.ts +3 -0
  78. package/dist/actions/preview-workspace-resource-change.d.ts.map +1 -0
  79. package/dist/actions/preview-workspace-resource-change.js +24 -0
  80. package/dist/actions/preview-workspace-resource-change.js.map +1 -0
  81. package/dist/actions/reject-dream-proposal.d.ts +3 -0
  82. package/dist/actions/reject-dream-proposal.d.ts.map +1 -0
  83. package/dist/actions/reject-dream-proposal.js +12 -0
  84. package/dist/actions/reject-dream-proposal.js.map +1 -0
  85. package/dist/actions/restore-starter-workspace-resources.d.ts +3 -0
  86. package/dist/actions/restore-starter-workspace-resources.d.ts.map +1 -0
  87. package/dist/actions/restore-starter-workspace-resources.js +14 -0
  88. package/dist/actions/restore-starter-workspace-resources.js.map +1 -0
  89. package/dist/actions/send-code-agent-remote-command.d.ts +3 -0
  90. package/dist/actions/send-code-agent-remote-command.d.ts.map +1 -0
  91. package/dist/actions/send-code-agent-remote-command.js +53 -0
  92. package/dist/actions/send-code-agent-remote-command.js.map +1 -0
  93. package/dist/actions/set-dream-settings.d.ts +3 -0
  94. package/dist/actions/set-dream-settings.d.ts.map +1 -0
  95. package/dist/actions/set-dream-settings.js +41 -0
  96. package/dist/actions/set-dream-settings.js.map +1 -0
  97. package/dist/actions/set-vault-access-settings.d.ts +3 -0
  98. package/dist/actions/set-vault-access-settings.d.ts.map +1 -0
  99. package/dist/actions/set-vault-access-settings.js +13 -0
  100. package/dist/actions/set-vault-access-settings.js.map +1 -0
  101. package/dist/actions/start-workspace-app-creation.d.ts.map +1 -1
  102. package/dist/actions/start-workspace-app-creation.js +6 -0
  103. package/dist/actions/start-workspace-app-creation.js.map +1 -1
  104. package/dist/actions/sync-vault-to-app.js +1 -1
  105. package/dist/actions/sync-vault-to-app.js.map +1 -1
  106. package/dist/actions/update-workspace-app-metadata.d.ts +3 -0
  107. package/dist/actions/update-workspace-app-metadata.d.ts.map +1 -0
  108. package/dist/actions/update-workspace-app-metadata.js +30 -0
  109. package/dist/actions/update-workspace-app-metadata.js.map +1 -0
  110. package/dist/actions/update-workspace-resource.js +1 -1
  111. package/dist/actions/update-workspace-resource.js.map +1 -1
  112. package/dist/actions/view-screen.d.ts.map +1 -1
  113. package/dist/actions/view-screen.js +77 -4
  114. package/dist/actions/view-screen.js.map +1 -1
  115. package/dist/components/app-keys-popover.js +16 -5
  116. package/dist/components/app-keys-popover.js.map +1 -1
  117. package/dist/components/approval-value-block.d.ts +7 -0
  118. package/dist/components/approval-value-block.d.ts.map +1 -0
  119. package/dist/components/approval-value-block.js +22 -0
  120. package/dist/components/approval-value-block.js.map +1 -0
  121. package/dist/components/create-app-popover.d.ts.map +1 -1
  122. package/dist/components/create-app-popover.js +41 -16
  123. package/dist/components/create-app-popover.js.map +1 -1
  124. package/dist/components/dispatch-shell.d.ts +4 -4
  125. package/dist/components/dispatch-shell.d.ts.map +1 -1
  126. package/dist/components/dispatch-shell.js +6 -6
  127. package/dist/components/dispatch-shell.js.map +1 -1
  128. package/dist/components/layout/Layout.d.ts.map +1 -1
  129. package/dist/components/layout/Layout.js +18 -4
  130. package/dist/components/layout/Layout.js.map +1 -1
  131. package/dist/components/messaging-setup-panel.d.ts.map +1 -1
  132. package/dist/components/messaging-setup-panel.js +2 -2
  133. package/dist/components/messaging-setup-panel.js.map +1 -1
  134. package/dist/components/ui/chart.d.ts +1 -1
  135. package/dist/components/workspace-app-card.d.ts.map +1 -1
  136. package/dist/components/workspace-app-card.js +63 -3
  137. package/dist/components/workspace-app-card.js.map +1 -1
  138. package/dist/components/workspace-resource-effective-stack.d.ts +11 -0
  139. package/dist/components/workspace-resource-effective-stack.d.ts.map +1 -0
  140. package/dist/components/workspace-resource-effective-stack.js +59 -0
  141. package/dist/components/workspace-resource-effective-stack.js.map +1 -0
  142. package/dist/components/workspace-resource-impact-preview.d.ts +9 -0
  143. package/dist/components/workspace-resource-impact-preview.d.ts.map +1 -0
  144. package/dist/components/workspace-resource-impact-preview.js +39 -0
  145. package/dist/components/workspace-resource-impact-preview.js.map +1 -0
  146. package/dist/db/migrations.d.ts.map +1 -1
  147. package/dist/db/migrations.js +59 -0
  148. package/dist/db/migrations.js.map +1 -1
  149. package/dist/db/schema.d.ts +714 -0
  150. package/dist/db/schema.d.ts.map +1 -1
  151. package/dist/db/schema.js +44 -2
  152. package/dist/db/schema.js.map +1 -1
  153. package/dist/hooks/use-navigation-state.d.ts +3 -0
  154. package/dist/hooks/use-navigation-state.d.ts.map +1 -1
  155. package/dist/hooks/use-navigation-state.js +35 -8
  156. package/dist/hooks/use-navigation-state.js.map +1 -1
  157. package/dist/lib/catch-all-target.d.ts +2 -0
  158. package/dist/lib/catch-all-target.d.ts.map +1 -0
  159. package/dist/lib/catch-all-target.js +95 -0
  160. package/dist/lib/catch-all-target.js.map +1 -0
  161. package/dist/lib/utils.d.ts +2 -1
  162. package/dist/lib/utils.d.ts.map +1 -1
  163. package/dist/lib/utils.js +5 -1
  164. package/dist/lib/utils.js.map +1 -1
  165. package/dist/lib/workspace-apps.d.ts +9 -0
  166. package/dist/lib/workspace-apps.d.ts.map +1 -1
  167. package/dist/lib/workspace-apps.js.map +1 -1
  168. package/dist/routes/index.d.ts.map +1 -1
  169. package/dist/routes/index.js +1 -0
  170. package/dist/routes/index.js.map +1 -1
  171. package/dist/routes/pages/$appId.d.ts +2 -2
  172. package/dist/routes/pages/$appId.d.ts.map +1 -1
  173. package/dist/routes/pages/$appId.js +17 -8
  174. package/dist/routes/pages/$appId.js.map +1 -1
  175. package/dist/routes/pages/approval.d.ts.map +1 -1
  176. package/dist/routes/pages/approval.js +4 -1
  177. package/dist/routes/pages/approval.js.map +1 -1
  178. package/dist/routes/pages/approvals.js +1 -1
  179. package/dist/routes/pages/approvals.js.map +1 -1
  180. package/dist/routes/pages/dream-settings.d.ts +34 -0
  181. package/dist/routes/pages/dream-settings.d.ts.map +1 -0
  182. package/dist/routes/pages/dream-settings.js +68 -0
  183. package/dist/routes/pages/dream-settings.js.map +1 -0
  184. package/dist/routes/pages/dreams.d.ts +5 -0
  185. package/dist/routes/pages/dreams.d.ts.map +1 -0
  186. package/dist/routes/pages/dreams.js +435 -0
  187. package/dist/routes/pages/dreams.js.map +1 -0
  188. package/dist/routes/pages/integrations.d.ts.map +1 -1
  189. package/dist/routes/pages/integrations.js +20 -15
  190. package/dist/routes/pages/integrations.js.map +1 -1
  191. package/dist/routes/pages/new-app.js +1 -1
  192. package/dist/routes/pages/new-app.js.map +1 -1
  193. package/dist/routes/pages/overview.d.ts.map +1 -1
  194. package/dist/routes/pages/overview.js +5 -1
  195. package/dist/routes/pages/overview.js.map +1 -1
  196. package/dist/routes/pages/vault.d.ts.map +1 -1
  197. package/dist/routes/pages/vault.js +23 -5
  198. package/dist/routes/pages/vault.js.map +1 -1
  199. package/dist/routes/pages/workspace.d.ts.map +1 -1
  200. package/dist/routes/pages/workspace.js +187 -35
  201. package/dist/routes/pages/workspace.js.map +1 -1
  202. package/dist/server/lib/app-creation-store.d.ts +13 -0
  203. package/dist/server/lib/app-creation-store.d.ts.map +1 -1
  204. package/dist/server/lib/app-creation-store.js +298 -11
  205. package/dist/server/lib/app-creation-store.js.map +1 -1
  206. package/dist/server/lib/dispatch-integrations.d.ts +1 -1
  207. package/dist/server/lib/dispatch-integrations.d.ts.map +1 -1
  208. package/dist/server/lib/dispatch-integrations.js +9 -4
  209. package/dist/server/lib/dispatch-integrations.js.map +1 -1
  210. package/dist/server/lib/dispatch-remote-commands.d.ts +83 -0
  211. package/dist/server/lib/dispatch-remote-commands.d.ts.map +1 -0
  212. package/dist/server/lib/dispatch-remote-commands.js +256 -0
  213. package/dist/server/lib/dispatch-remote-commands.js.map +1 -0
  214. package/dist/server/lib/dispatch-store.d.ts +26 -0
  215. package/dist/server/lib/dispatch-store.d.ts.map +1 -1
  216. package/dist/server/lib/dispatch-store.js +17 -1
  217. package/dist/server/lib/dispatch-store.js.map +1 -1
  218. package/dist/server/lib/dreams-store.d.ts +398 -0
  219. package/dist/server/lib/dreams-store.d.ts.map +1 -0
  220. package/dist/server/lib/dreams-store.js +2330 -0
  221. package/dist/server/lib/dreams-store.js.map +1 -0
  222. package/dist/server/lib/env-config.d.ts.map +1 -1
  223. package/dist/server/lib/env-config.js +5 -0
  224. package/dist/server/lib/env-config.js.map +1 -1
  225. package/dist/server/lib/onboarding-steps.d.ts +12 -0
  226. package/dist/server/lib/onboarding-steps.d.ts.map +1 -0
  227. package/dist/server/lib/onboarding-steps.js +47 -0
  228. package/dist/server/lib/onboarding-steps.js.map +1 -0
  229. package/dist/server/lib/thread-debug-store.d.ts +2 -2
  230. package/dist/server/lib/vault-store.d.ts +55 -0
  231. package/dist/server/lib/vault-store.d.ts.map +1 -1
  232. package/dist/server/lib/vault-store.js +210 -41
  233. package/dist/server/lib/vault-store.js.map +1 -1
  234. package/dist/server/lib/workspace-resources-store.d.ts +181 -17
  235. package/dist/server/lib/workspace-resources-store.d.ts.map +1 -1
  236. package/dist/server/lib/workspace-resources-store.js +737 -108
  237. package/dist/server/lib/workspace-resources-store.js.map +1 -1
  238. package/dist/server/plugins/agent-chat.d.ts.map +1 -1
  239. package/dist/server/plugins/agent-chat.js +2 -1
  240. package/dist/server/plugins/agent-chat.js.map +1 -1
  241. package/dist/server/plugins/core-routes.d.ts.map +1 -1
  242. package/dist/server/plugins/core-routes.js +4 -0
  243. package/dist/server/plugins/core-routes.js.map +1 -1
  244. package/dist/server/plugins/integrations.js +2 -2
  245. package/dist/server/plugins/integrations.js.map +1 -1
  246. package/package.json +15 -11
  247. package/src/actions/apply-dream-proposal.ts +12 -0
  248. package/src/actions/create-dream-report.ts +76 -0
  249. package/src/actions/create-pylon-ticket.ts +109 -0
  250. package/src/actions/create-vault-grant.ts +1 -1
  251. package/src/actions/create-vault-secret.ts +4 -3
  252. package/src/actions/create-workspace-resource.ts +3 -3
  253. package/src/actions/delete-workspace-resource.ts +1 -1
  254. package/src/actions/ensure-dream-job.ts +76 -0
  255. package/src/actions/get-dream-settings.ts +12 -0
  256. package/src/actions/get-dream.ts +14 -0
  257. package/src/actions/get-vault-access-settings.ts +11 -0
  258. package/src/actions/get-workspace-resource-effective-context.ts +34 -0
  259. package/src/actions/grant-vault-secrets-to-app.ts +1 -1
  260. package/src/actions/index.spec.ts +26 -0
  261. package/src/actions/index.ts +39 -4
  262. package/src/actions/list-dream-candidates.ts +77 -0
  263. package/src/actions/list-dreams.ts +17 -0
  264. package/src/actions/list-integrations-catalog.ts +1 -1
  265. package/src/actions/list-vault-grants.ts +1 -1
  266. package/src/actions/list-workspace-apps.ts +5 -1
  267. package/src/actions/list-workspace-resources-for-app.ts +13 -0
  268. package/src/actions/list-workspace-resources.ts +1 -1
  269. package/src/actions/navigate.ts +2 -1
  270. package/src/actions/preview-dream-proposal.ts +14 -0
  271. package/src/actions/preview-workspace-resource-change.ts +25 -0
  272. package/src/actions/reject-dream-proposal.ts +12 -0
  273. package/src/actions/restore-starter-workspace-resources.ts +17 -0
  274. package/src/actions/send-code-agent-remote-command.ts +59 -0
  275. package/src/actions/set-dream-settings.spec.ts +81 -0
  276. package/src/actions/set-dream-settings.ts +44 -0
  277. package/src/actions/set-vault-access-settings.ts +16 -0
  278. package/src/actions/start-workspace-app-creation.ts +8 -0
  279. package/src/actions/sync-vault-to-app.ts +1 -1
  280. package/src/actions/update-workspace-app-metadata.ts +32 -0
  281. package/src/actions/update-workspace-resource.ts +1 -1
  282. package/src/actions/view-screen.ts +94 -3
  283. package/src/components/app-keys-popover.tsx +23 -7
  284. package/src/components/approval-value-block.spec.tsx +59 -0
  285. package/src/components/approval-value-block.tsx +33 -0
  286. package/src/components/create-app-popover.tsx +50 -16
  287. package/src/components/dispatch-shell.tsx +16 -15
  288. package/src/components/layout/Layout.tsx +19 -5
  289. package/src/components/messaging-setup-panel.tsx +54 -39
  290. package/src/components/workspace-app-card.tsx +268 -1
  291. package/src/components/workspace-resource-effective-stack.spec.tsx +125 -0
  292. package/src/components/workspace-resource-effective-stack.tsx +141 -0
  293. package/src/components/workspace-resource-impact-preview.spec.tsx +147 -0
  294. package/src/components/workspace-resource-impact-preview.tsx +116 -0
  295. package/src/db/migrations.ts +59 -0
  296. package/src/db/schema.ts +46 -2
  297. package/src/hooks/use-navigation-state.ts +34 -9
  298. package/src/lib/catch-all-target.spec.ts +218 -0
  299. package/src/lib/catch-all-target.ts +99 -0
  300. package/src/lib/utils.ts +6 -1
  301. package/src/lib/workspace-apps.ts +9 -0
  302. package/src/routes/index.ts +1 -0
  303. package/src/routes/pages/$appId.tsx +21 -8
  304. package/src/routes/pages/approval.tsx +14 -1
  305. package/src/routes/pages/approvals.tsx +1 -1
  306. package/src/routes/pages/dream-settings.spec.ts +130 -0
  307. package/src/routes/pages/dream-settings.ts +103 -0
  308. package/src/routes/pages/dreams.tsx +1828 -0
  309. package/src/routes/pages/integrations.tsx +57 -18
  310. package/src/routes/pages/new-app.tsx +1 -1
  311. package/src/routes/pages/overview.tsx +11 -3
  312. package/src/routes/pages/vault.tsx +76 -9
  313. package/src/routes/pages/workspace.tsx +577 -97
  314. package/src/server/lib/app-creation-store.spec.ts +61 -2
  315. package/src/server/lib/app-creation-store.ts +389 -13
  316. package/src/server/lib/dispatch-integrations.ts +10 -3
  317. package/src/server/lib/dispatch-remote-commands.spec.ts +167 -0
  318. package/src/server/lib/dispatch-remote-commands.ts +375 -0
  319. package/src/server/lib/dispatch-store.ts +37 -1
  320. package/src/server/lib/dreams-store.spec.ts +1492 -0
  321. package/src/server/lib/dreams-store.ts +3168 -0
  322. package/src/server/lib/env-config.ts +5 -0
  323. package/src/server/lib/onboarding-steps.ts +49 -0
  324. package/src/server/lib/vault-store.spec.ts +69 -0
  325. package/src/server/lib/vault-store.ts +266 -49
  326. package/src/server/lib/workspace-resource-approval-lifecycle.spec.ts +236 -0
  327. package/src/server/lib/workspace-resources-store.spec.ts +1106 -0
  328. package/src/server/lib/workspace-resources-store.ts +1001 -134
  329. package/src/server/plugins/agent-chat.ts +2 -1
  330. package/src/server/plugins/core-routes.ts +5 -0
  331. package/src/server/plugins/integrations.ts +2 -2
  332. package/dist/actions/sync-workspace-resources-to-all.d.ts +0 -3
  333. package/dist/actions/sync-workspace-resources-to-all.d.ts.map +0 -1
  334. package/dist/actions/sync-workspace-resources-to-all.js +0 -9
  335. package/dist/actions/sync-workspace-resources-to-all.js.map +0 -1
  336. package/dist/actions/sync-workspace-resources-to-app.d.ts +0 -3
  337. package/dist/actions/sync-workspace-resources-to-app.d.ts.map +0 -1
  338. package/dist/actions/sync-workspace-resources-to-app.js +0 -11
  339. package/dist/actions/sync-workspace-resources-to-app.js.map +0 -1
  340. package/src/actions/sync-workspace-resources-to-all.ts +0 -10
  341. package/src/actions/sync-workspace-resources-to-app.ts +0 -12
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { getSetting, putSetting } from "@agent-native/core/settings";
6
+ import { assertValidWorkspaceAppId } from "@agent-native/core/shared";
6
7
  import {
7
8
  getBuilderBranchProjectId,
8
9
  getRequestContext,
@@ -12,7 +13,6 @@ import {
12
13
  runBuilderAgent,
13
14
  } from "@agent-native/core/server";
14
15
  import { getDbExec } from "@agent-native/core/db";
15
- import { assertValidWorkspaceAppId } from "@agent-native/core/shared";
16
16
  import {
17
17
  currentOrgId,
18
18
  currentOwnerEmail,
@@ -28,6 +28,7 @@ import {
28
28
  } from "./workspace-resources-store.js";
29
29
 
30
30
  const SETTINGS_KEY = "dispatch-app-creation-settings";
31
+ const WORKSPACE_APP_METADATA_SETTINGS_KEY = "workspace-app-metadata";
31
32
  const WORKSPACE_APPS_ENV_KEY = "AGENT_NATIVE_WORKSPACE_APPS_JSON";
32
33
  const WORKSPACE_APPS_MANIFEST_FILE = "workspace-apps.json";
33
34
  const WORKSPACE_APPS_GATEWAY_PATH = "/_workspace/apps";
@@ -35,6 +36,9 @@ const WORKSPACE_APPS_GATEWAY_TIMEOUT_MS = 1_000;
35
36
  const MAX_PENDING_APPS = 50;
36
37
  const AGENT_CARD_PATH = "/.well-known/agent-card.json";
37
38
  const AGENT_CARD_FETCH_TIMEOUT_MS = 1_500;
39
+ const DEFAULT_WORKSPACE_APP_AUDIENCE = "internal";
40
+
41
+ type WorkspaceAppAudience = "internal" | "public";
38
42
 
39
43
  export interface WorkspaceAppSummary {
40
44
  id: string;
@@ -43,6 +47,9 @@ export interface WorkspaceAppSummary {
43
47
  path: string;
44
48
  url: string | null;
45
49
  isDispatch: boolean;
50
+ audience: WorkspaceAppAudience;
51
+ publicPaths: string[];
52
+ protectedPaths: string[];
46
53
  status?: "ready" | "pending";
47
54
  statusLabel?: string;
48
55
  builderUrl?: string | null;
@@ -64,6 +71,7 @@ export interface ListWorkspaceAppsOptions {
64
71
  * when rendering the "Hidden apps" expander.
65
72
  */
66
73
  includeArchived?: boolean;
74
+ audience?: WorkspaceAppAudience | "all";
67
75
  }
68
76
 
69
77
  export interface AvailableWorkspaceTemplate {
@@ -103,10 +111,24 @@ interface PendingWorkspaceApp {
103
111
  builderUrl: string | null;
104
112
  branchName: string | null;
105
113
  projectId: string | null;
114
+ audience?: WorkspaceAppAudience;
106
115
  createdAt: string;
107
116
  updatedAt: string;
108
117
  }
109
118
 
119
+ interface WorkspaceAppMetadataOverride {
120
+ name?: string;
121
+ description?: string;
122
+ generated?: boolean;
123
+ sourcePrompt?: string;
124
+ updatedAt?: string;
125
+ updatedBy?: string;
126
+ }
127
+
128
+ interface WorkspaceAppMetadataSettings {
129
+ apps: Record<string, WorkspaceAppMetadataOverride>;
130
+ }
131
+
110
132
  function readJson(file: string): any {
111
133
  try {
112
134
  return JSON.parse(fs.readFileSync(file, "utf8"));
@@ -137,12 +159,56 @@ function titleCase(value: string): string {
137
159
  .join(" ");
138
160
  }
139
161
 
162
+ function normalizeWhitespace(value: string): string {
163
+ return value.replace(/\s+/g, " ").trim();
164
+ }
165
+
166
+ function ensureSentence(value: string): string {
167
+ if (!value) return value;
168
+ const capitalized = value.charAt(0).toUpperCase() + value.slice(1);
169
+ return /[.!?]$/.test(capitalized) ? capitalized : `${capitalized}.`;
170
+ }
171
+
172
+ function clipSentence(value: string, max = 180): string {
173
+ if (value.length <= max) return value;
174
+ const clipped = value
175
+ .slice(0, max - 1)
176
+ .replace(/\s+\S*$/, "")
177
+ .trim();
178
+ return `${clipped || value.slice(0, max - 1).trim()}…`;
179
+ }
180
+
181
+ export function generateWorkspaceAppDescription(
182
+ prompt: string,
183
+ appId: string,
184
+ ): string {
185
+ const cleaned = normalizeWhitespace(prompt)
186
+ .replace(
187
+ /^(please\s+)?(build|create|make|generate|scaffold)\s+(me\s+|us\s+)?/i,
188
+ "",
189
+ )
190
+ .replace(
191
+ /^(an?\s+)?(workspace\s+)?(agent-native\s+)?(app|tool)\s+(that|to|for)\s+/i,
192
+ "",
193
+ )
194
+ .replace(/^(an?\s+)?(dashboard|workspace|agent)\s+(that|to|for)\s+/i, "");
195
+
196
+ if (!cleaned) return `Workspace app for ${titleCase(appId)}.`;
197
+ return clipSentence(ensureSentence(cleaned));
198
+ }
199
+
140
200
  function scopedSettingsKey(): string {
141
201
  const orgId = currentOrgId();
142
202
  if (orgId) return `${SETTINGS_KEY}:org:${orgId}`;
143
203
  return `${SETTINGS_KEY}:user:${currentOwnerEmail()}`;
144
204
  }
145
205
 
206
+ function workspaceAppMetadataSettingsKey(): string {
207
+ const orgId = currentOrgId();
208
+ if (orgId) return `${WORKSPACE_APP_METADATA_SETTINGS_KEY}:org:${orgId}`;
209
+ return `${WORKSPACE_APP_METADATA_SETTINGS_KEY}:user:${currentOwnerEmail()}`;
210
+ }
211
+
146
212
  async function readSettingsRecord(): Promise<Record<string, any>> {
147
213
  const raw = await getSetting(scopedSettingsKey()).catch(() => null);
148
214
  return raw && typeof raw === "object" && !Array.isArray(raw)
@@ -150,6 +216,114 @@ async function readSettingsRecord(): Promise<Record<string, any>> {
150
216
  : {};
151
217
  }
152
218
 
219
+ function cleanOptionalText(value: unknown): string | undefined {
220
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
221
+ }
222
+
223
+ function parseWorkspaceAppMetadataSettings(
224
+ raw: unknown,
225
+ ): WorkspaceAppMetadataSettings {
226
+ const record =
227
+ raw && typeof raw === "object" && !Array.isArray(raw)
228
+ ? (raw as Record<string, unknown>)
229
+ : {};
230
+ const rawApps =
231
+ record.apps &&
232
+ typeof record.apps === "object" &&
233
+ !Array.isArray(record.apps)
234
+ ? (record.apps as Record<string, unknown>)
235
+ : {};
236
+ const apps: Record<string, WorkspaceAppMetadataOverride> = {};
237
+
238
+ for (const [id, value] of Object.entries(rawApps)) {
239
+ if (!id.trim() || !value || typeof value !== "object") continue;
240
+ const item = value as Record<string, unknown>;
241
+ const override: WorkspaceAppMetadataOverride = {};
242
+ const name = cleanOptionalText(item.name);
243
+ const description = cleanOptionalText(item.description);
244
+ const sourcePrompt = cleanOptionalText(item.sourcePrompt);
245
+ const updatedAt = cleanOptionalText(item.updatedAt);
246
+ const updatedBy = cleanOptionalText(item.updatedBy);
247
+
248
+ if (name) override.name = name;
249
+ if (description) override.description = description;
250
+ if (item.generated === true) override.generated = true;
251
+ if (sourcePrompt) override.sourcePrompt = sourcePrompt;
252
+ if (updatedAt) override.updatedAt = updatedAt;
253
+ if (updatedBy) override.updatedBy = updatedBy;
254
+
255
+ if (Object.keys(override).length > 0) apps[id.trim()] = override;
256
+ }
257
+
258
+ return { apps };
259
+ }
260
+
261
+ async function readWorkspaceAppMetadataSettings(): Promise<WorkspaceAppMetadataSettings> {
262
+ const raw = await getSetting(workspaceAppMetadataSettingsKey()).catch(
263
+ () => null,
264
+ );
265
+ return parseWorkspaceAppMetadataSettings(raw);
266
+ }
267
+
268
+ async function writeWorkspaceAppMetadataOverride(input: {
269
+ appId: string;
270
+ name?: string | null;
271
+ description?: string | null;
272
+ generated?: boolean;
273
+ sourcePrompt?: string | null;
274
+ updatedBy?: string | null;
275
+ }): Promise<WorkspaceAppMetadataSettings> {
276
+ const key = workspaceAppMetadataSettingsKey();
277
+ const current = parseWorkspaceAppMetadataSettings(
278
+ await getSetting(key).catch(() => null),
279
+ );
280
+ const appId = input.appId.trim();
281
+ const existing = current.apps[appId] ?? {};
282
+ const next: WorkspaceAppMetadataOverride = {
283
+ ...existing,
284
+ updatedAt: new Date().toISOString(),
285
+ };
286
+ const name = cleanOptionalText(input.name);
287
+ const description = cleanOptionalText(input.description);
288
+ const sourcePrompt = cleanOptionalText(input.sourcePrompt);
289
+ const updatedBy = cleanOptionalText(input.updatedBy);
290
+
291
+ if (name) next.name = name;
292
+ else delete next.name;
293
+ if (description) next.description = description;
294
+ else delete next.description;
295
+ if (input.generated === true) next.generated = true;
296
+ else if (input.generated === false) delete next.generated;
297
+ if (sourcePrompt) next.sourcePrompt = sourcePrompt;
298
+ if (updatedBy) next.updatedBy = updatedBy;
299
+
300
+ current.apps[appId] = next;
301
+ await putSetting(key, { apps: current.apps });
302
+ return current;
303
+ }
304
+
305
+ function applyWorkspaceAppMetadataOverride(
306
+ app: WorkspaceAppSummary,
307
+ settings: WorkspaceAppMetadataSettings,
308
+ ): WorkspaceAppSummary {
309
+ const override = settings.apps[app.id];
310
+ if (!override) return app;
311
+
312
+ const name = cleanOptionalText(override.name);
313
+ const description = cleanOptionalText(override.description);
314
+ const generated = override.generated === true;
315
+ const shouldApplyName = !!name && !generated;
316
+ const shouldApplyDescription =
317
+ !!description && (!generated || !cleanOptionalText(app.description));
318
+ if (!shouldApplyName && !shouldApplyDescription) return app;
319
+
320
+ return {
321
+ ...app,
322
+ ...(shouldApplyName ? { name } : {}),
323
+ ...(shouldApplyDescription ? { description } : {}),
324
+ };
325
+ }
326
+
153
327
  function workspaceAppUrl(appPath: string): string | null {
154
328
  const base =
155
329
  process.env.WORKSPACE_GATEWAY_URL ||
@@ -180,6 +354,85 @@ function workspaceAppLink(
180
354
  }
181
355
  }
182
356
 
357
+ function normalizeWorkspaceAppAudience(value: unknown): WorkspaceAppAudience {
358
+ return value === "public" ? "public" : DEFAULT_WORKSPACE_APP_AUDIENCE;
359
+ }
360
+
361
+ function normalizeWorkspaceAppPathList(value: unknown): string[] {
362
+ let rawPaths: unknown[] = [];
363
+ if (Array.isArray(value)) {
364
+ rawPaths = value;
365
+ } else if (typeof value === "string") {
366
+ const trimmed = value.trim();
367
+ if (!trimmed) return [];
368
+ try {
369
+ const parsed = JSON.parse(trimmed);
370
+ rawPaths = Array.isArray(parsed) ? parsed : [trimmed];
371
+ } catch {
372
+ rawPaths = trimmed.split(",");
373
+ }
374
+ }
375
+
376
+ const paths = rawPaths
377
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
378
+ .filter((entry) => entry.startsWith("/"))
379
+ .map((entry) =>
380
+ entry.length > 1 && entry.endsWith("/") ? entry.slice(0, -1) : entry,
381
+ );
382
+ return Array.from(new Set(paths));
383
+ }
384
+
385
+ function workspaceAppAudienceFromPackageJson(
386
+ pkg: unknown,
387
+ ): WorkspaceAppAudience | undefined {
388
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) return undefined;
389
+ const record = pkg as Record<string, any>;
390
+ const config = record["agent-native"] ?? record.agentNative;
391
+ const nested =
392
+ config && typeof config === "object" && !Array.isArray(config)
393
+ ? (config as Record<string, any>)
394
+ : {};
395
+ const raw =
396
+ nested.workspaceApp?.audience ??
397
+ nested.workspace?.audience ??
398
+ nested.audience ??
399
+ record.workspaceAppAudience;
400
+ if (raw === undefined) return undefined;
401
+ return normalizeWorkspaceAppAudience(raw);
402
+ }
403
+
404
+ function workspaceAppRouteAccessFromPackageJson(pkg: unknown): {
405
+ publicPaths: string[];
406
+ protectedPaths: string[];
407
+ } {
408
+ if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) {
409
+ return { publicPaths: [], protectedPaths: [] };
410
+ }
411
+ const record = pkg as Record<string, any>;
412
+ const config = record["agent-native"] ?? record.agentNative;
413
+ const nested =
414
+ config && typeof config === "object" && !Array.isArray(config)
415
+ ? (config as Record<string, any>)
416
+ : {};
417
+ return {
418
+ publicPaths: normalizeWorkspaceAppPathList(
419
+ nested.workspaceApp?.publicPaths ??
420
+ nested.workspaceApp?.publicPagePaths ??
421
+ nested.workspace?.publicPaths ??
422
+ nested.publicPaths ??
423
+ record.workspaceAppPublicPaths,
424
+ ),
425
+ protectedPaths: normalizeWorkspaceAppPathList(
426
+ nested.workspaceApp?.protectedPaths ??
427
+ nested.workspaceApp?.privatePaths ??
428
+ nested.workspaceApp?.authRequiredPaths ??
429
+ nested.workspace?.protectedPaths ??
430
+ nested.protectedPaths ??
431
+ record.workspaceAppProtectedPaths,
432
+ ),
433
+ };
434
+ }
435
+
183
436
  function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
184
437
  const rawApps = Array.isArray(parsed?.apps)
185
438
  ? parsed.apps
@@ -208,6 +461,12 @@ function parseWorkspaceAppsManifest(parsed: any): WorkspaceAppSummary[] | null {
208
461
  typeof entry.isDispatch === "boolean"
209
462
  ? entry.isDispatch
210
463
  : id === "dispatch",
464
+ audience:
465
+ entry.audience === undefined
466
+ ? DEFAULT_WORKSPACE_APP_AUDIENCE
467
+ : normalizeWorkspaceAppAudience(entry.audience),
468
+ publicPaths: normalizeWorkspaceAppPathList(entry.publicPaths),
469
+ protectedPaths: normalizeWorkspaceAppPathList(entry.protectedPaths),
211
470
  status: "ready",
212
471
  } satisfies WorkspaceAppSummary;
213
472
  })
@@ -228,7 +487,7 @@ function sortWorkspaceApps(a: WorkspaceAppSummary, b: WorkspaceAppSummary) {
228
487
  function parsePendingWorkspaceApps(value: unknown): PendingWorkspaceApp[] {
229
488
  if (!Array.isArray(value)) return [];
230
489
  return value
231
- .map((entry) => {
490
+ .map((entry): PendingWorkspaceApp | null => {
232
491
  if (!entry || typeof entry !== "object") return null;
233
492
  const record = entry as Record<string, unknown>;
234
493
  const id = typeof record.id === "string" ? record.id.trim() : "";
@@ -259,6 +518,9 @@ function parsePendingWorkspaceApps(value: unknown): PendingWorkspaceApp[] {
259
518
  typeof record.projectId === "string" && record.projectId.trim()
260
519
  ? record.projectId.trim()
261
520
  : null,
521
+ ...(record.audience === undefined
522
+ ? {}
523
+ : { audience: normalizeWorkspaceAppAudience(record.audience) }),
262
524
  createdAt:
263
525
  typeof record.createdAt === "string" && record.createdAt.trim()
264
526
  ? record.createdAt.trim()
@@ -356,6 +618,9 @@ function pendingAppToSummary(app: PendingWorkspaceApp): WorkspaceAppSummary {
356
618
  path: app.path,
357
619
  url: app.builderUrl,
358
620
  isDispatch: false,
621
+ audience: app.audience ?? DEFAULT_WORKSPACE_APP_AUDIENCE,
622
+ publicPaths: [],
623
+ protectedPaths: [],
359
624
  status: "pending",
360
625
  statusLabel: "Building in Builder",
361
626
  builderUrl: app.builderUrl,
@@ -494,6 +759,8 @@ async function maybeIncludeAgentCards(
494
759
  async function recordPendingWorkspaceApp(input: {
495
760
  appId: string;
496
761
  projectId: string | null;
762
+ description: string;
763
+ sourcePrompt: string;
497
764
  branchName?: string | null;
498
765
  builderUrl?: string | null;
499
766
  }) {
@@ -505,6 +772,7 @@ async function recordPendingWorkspaceApp(input: {
505
772
  id: input.appId,
506
773
  name: titleCase(input.appId),
507
774
  description:
775
+ input.description ||
508
776
  "Builder is creating this app. The workspace path becomes live after the branch is merged and deployed.",
509
777
  path: `/${input.appId}`,
510
778
  builderUrl: input.builderUrl?.trim() || null,
@@ -522,6 +790,14 @@ async function recordPendingWorkspaceApp(input: {
522
790
  ].slice(0, MAX_PENDING_APPS),
523
791
  });
524
792
 
793
+ await writeWorkspaceAppMetadataOverride({
794
+ appId: input.appId,
795
+ description: input.description,
796
+ generated: true,
797
+ sourcePrompt: input.sourcePrompt,
798
+ updatedBy: currentOwnerEmail(),
799
+ });
800
+
525
801
  await recordAudit({
526
802
  action: "workspace-app.pending",
527
803
  targetType: "workspace-app",
@@ -614,11 +890,12 @@ function readWorkspaceAppsFromFilesystem(
614
890
 
615
891
  const apps = fs
616
892
  .readdirSync(appsDir, { withFileTypes: true })
617
- .filter((entry) => entry.isDirectory())
893
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
618
894
  .map((entry): WorkspaceAppSummary | null => {
619
895
  const appDir = path.join(appsDir, entry.name);
620
896
  const pkg = readJson(path.join(appDir, "package.json"));
621
897
  if (!pkg) return null;
898
+ const routeAccess = workspaceAppRouteAccessFromPackageJson(pkg);
622
899
  return {
623
900
  id: entry.name,
624
901
  name: pkg.displayName || titleCase(entry.name),
@@ -626,6 +903,11 @@ function readWorkspaceAppsFromFilesystem(
626
903
  path: `/${entry.name}`,
627
904
  url: workspaceAppUrl(`/${entry.name}`),
628
905
  isDispatch: entry.name === "dispatch",
906
+ audience:
907
+ workspaceAppAudienceFromPackageJson(pkg) ??
908
+ DEFAULT_WORKSPACE_APP_AUDIENCE,
909
+ publicPaths: routeAccess.publicPaths,
910
+ protectedPaths: routeAccess.protectedPaths,
629
911
  status: "ready",
630
912
  } satisfies WorkspaceAppSummary;
631
913
  })
@@ -689,17 +971,92 @@ async function applyArchivedAndPending(
689
971
  apps: WorkspaceAppSummary[],
690
972
  options: ListWorkspaceAppsOptions,
691
973
  ): Promise<WorkspaceAppSummary[]> {
692
- const [withPending, archivedIds] = await Promise.all([
974
+ const [withPending, archivedIds, metadataSettings] = await Promise.all([
693
975
  appendPendingWorkspaceApps(apps),
694
976
  listArchivedAppIds(),
977
+ readWorkspaceAppMetadataSettings(),
695
978
  ]);
696
979
  const archivedSet = new Set(archivedIds);
697
- const annotated = withPending.map((app) =>
698
- archivedSet.has(app.id) ? { ...app, archived: true } : app,
699
- );
980
+ const annotated = withPending.map((app) => {
981
+ const withMetadata = applyWorkspaceAppMetadataOverride(
982
+ app,
983
+ metadataSettings,
984
+ );
985
+ return archivedSet.has(app.id)
986
+ ? { ...withMetadata, archived: true }
987
+ : withMetadata;
988
+ });
700
989
  return options.includeArchived
701
- ? annotated
702
- : annotated.filter((app) => !app.archived);
990
+ ? filterAppsByAudience(annotated, options.audience)
991
+ : filterAppsByAudience(
992
+ annotated.filter((app) => !app.archived),
993
+ options.audience,
994
+ );
995
+ }
996
+
997
+ function filterAppsByAudience(
998
+ apps: WorkspaceAppSummary[],
999
+ audience: ListWorkspaceAppsOptions["audience"],
1000
+ ): WorkspaceAppSummary[] {
1001
+ if (!audience || audience === "all") return apps;
1002
+ return apps.filter(
1003
+ (app) =>
1004
+ (app.audience ?? DEFAULT_WORKSPACE_APP_AUDIENCE) ===
1005
+ normalizeWorkspaceAppAudience(audience),
1006
+ );
1007
+ }
1008
+
1009
+ export async function updateWorkspaceAppMetadata(input: {
1010
+ appId: string;
1011
+ name?: string | null;
1012
+ description?: string | null;
1013
+ }): Promise<WorkspaceAppSummary> {
1014
+ await assertCanManageAppCreationSettings();
1015
+ const appId = input.appId.trim();
1016
+ assertValidWorkspaceAppId(appId);
1017
+
1018
+ const apps = await listWorkspaceApps({
1019
+ includeAgentCards: false,
1020
+ includeArchived: true,
1021
+ });
1022
+ const app = apps.find((candidate) => candidate.id === appId);
1023
+ if (!app) throw new Error(`Workspace app "${appId}" was not found.`);
1024
+
1025
+ // Treat undefined/null as "field omitted, leave existing value alone"; an
1026
+ // explicit empty string clears the override (the app reverts to its
1027
+ // built-in name / no description). Without this, a partial update that
1028
+ // only touches one field silently wipes the other.
1029
+ const name = input.name == null ? app.name : input.name.trim();
1030
+ const description =
1031
+ input.description == null
1032
+ ? (app.description ?? undefined)
1033
+ : input.description.trim();
1034
+ await writeWorkspaceAppMetadataOverride({
1035
+ appId,
1036
+ name,
1037
+ description,
1038
+ generated: false,
1039
+ updatedBy: currentOwnerEmail(),
1040
+ });
1041
+
1042
+ await recordAudit({
1043
+ action: "workspace-app.metadata-updated",
1044
+ targetType: "workspace-app",
1045
+ targetId: appId,
1046
+ summary: `Updated workspace app details for ${name}`,
1047
+ metadata: {
1048
+ name,
1049
+ descriptionConfigured: !!description,
1050
+ },
1051
+ });
1052
+
1053
+ const updated = (
1054
+ await listWorkspaceApps({
1055
+ includeAgentCards: false,
1056
+ includeArchived: true,
1057
+ })
1058
+ ).find((candidate) => candidate.id === appId);
1059
+ return updated ?? { ...app, name, description };
703
1060
  }
704
1061
 
705
1062
  export async function listWorkspaceApps(
@@ -745,6 +1102,9 @@ export async function listWorkspaceApps(
745
1102
  path: "/dispatch",
746
1103
  url: workspaceAppUrl("/dispatch"),
747
1104
  isDispatch: true,
1105
+ audience: DEFAULT_WORKSPACE_APP_AUDIENCE,
1106
+ publicPaths: [],
1107
+ protectedPaths: [],
748
1108
  status: "ready",
749
1109
  },
750
1110
  ],
@@ -1166,6 +1526,7 @@ async function remoteAppCreationAuthorization(): Promise<
1166
1526
  function buildWorkspaceAppPrompt(input: {
1167
1527
  prompt: string;
1168
1528
  appId?: string | null;
1529
+ description?: string | null;
1169
1530
  template?: string | null;
1170
1531
  selectedKeys?: string[];
1171
1532
  selectedResources?: WorkspaceResourceOption[];
@@ -1176,6 +1537,9 @@ function buildWorkspaceAppPrompt(input: {
1176
1537
  input.prompt.replace(/\b(build|create|make|an?|the|app|tool)\b/gi, " "),
1177
1538
  ) ||
1178
1539
  "new-app";
1540
+ const appDescription =
1541
+ input.description?.trim() ||
1542
+ generateWorkspaceAppDescription(input.prompt, appId);
1179
1543
  const selectedKeys = input.selectedKeys || [];
1180
1544
  const selectedResources = input.selectedResources || [];
1181
1545
  const resourceList = selectedResources.length
@@ -1192,6 +1556,7 @@ function buildWorkspaceAppPrompt(input: {
1192
1556
  "Create a new agent-native app in this workspace.",
1193
1557
  "",
1194
1558
  `App name: ${appId}`,
1559
+ `App description: ${appDescription}`,
1195
1560
  `Template to start from: ${input.template || "starter"}`,
1196
1561
  `User prompt: ${input.prompt.trim()}`,
1197
1562
  "If the user mentions a product or company such as Granola, Loom, Superhuman, Linear, or Notion, treat it as product inspiration unless they explicitly ask to connect to that service. Do not invent or require third-party API keys like GRANOLA_API_KEY just because a product is named.",
@@ -1199,6 +1564,7 @@ function buildWorkspaceAppPrompt(input: {
1199
1564
  ? `Dispatch vault keys selected for this app: ${selectedKeys.join(", ")}`
1200
1565
  : "Dispatch vault keys selected for this app: none",
1201
1566
  `Dispatch workspace resources selected for this app:\n${resourceList}`,
1567
+ `Dispatch workspace resources with scope=all are global. After the app exists, sync workspace resources to appId "${appId}" so global skills, guardrail instructions, and reference resources reach the new app even when no per-app resources were selected.`,
1202
1568
  "",
1203
1569
  `Use the workspace app layout: create it under apps/${appId}, mount it at /${appId}, keep it on the shared workspace database/hosting model, and avoid table-name collisions by namespacing any new domain tables to the app.`,
1204
1570
  `Important routing rule: from outside the app, link to /${appId}; inside apps/${appId}, React Router routes are app-local. Use <Link to="/review"> and navigate("/review"), not "/${appId}/review"; APP_BASE_PATH supplies the mounted prefix, and hardcoding it causes doubled URLs like /${appId}/${appId}/review.`,
@@ -1209,8 +1575,8 @@ function buildWorkspaceAppPrompt(input: {
1209
1575
  ? `Dispatch will create pending vault requests for the selected keys for appId "${appId}" after this app creation request is accepted. Do not grant or sync vault keys directly from the app-creation branch.`
1210
1576
  : "Do not grant or request any Dispatch vault keys unless the user asks later.",
1211
1577
  selectedResources.length
1212
- ? `Dispatch will create workspace resource grants for the selected resources for appId "${appId}". After the app exists, sync workspace resources so the app receives those shared resources. Add a short note to apps/${appId}/AGENTS.md telling the app agent to read relevant shared resources under context/ or the selected resource paths before doing GTM/domain work.`
1213
- : "Do not grant any Dispatch workspace resources unless the user asks later.",
1578
+ ? `Dispatch will create workspace resource grants for the selected resources for appId "${appId}". After the app exists, sync workspace resources so the app receives both global and selected shared resources.`
1579
+ : "Do not grant any selected-only Dispatch workspace resources unless the user asks later.",
1214
1580
  "",
1215
1581
  "Agent-native rules (these are the framework's contract — not optional):",
1216
1582
  `- Persist ALL data in SQL via Drizzle. Add tables to apps/${appId}/server/db/schema.ts and migrations to apps/${appId}/server/plugins/db.ts. NEVER use localStorage, sessionStorage, IndexedDB, or in-memory state for anything the user expects to persist — agent and UI must read the same source of truth.`,
@@ -1222,11 +1588,13 @@ function buildWorkspaceAppPrompt(input: {
1222
1588
  "",
1223
1589
  "Branch readiness requirements before handing off:",
1224
1590
  "- The CLI auto-fills package.json name and displayName from the app id; only edit the description / scripts / dependencies if the app actually needs more than the template provides.",
1591
+ `- Save a concise, human-readable app description in apps/${appId}/package.json "description" so Dispatch, A2A discovery, and connected agents can describe what this app does. Use the description above or improve it based on the prompt.`,
1225
1592
  "- Do not add or update workspace-apps.json or .agent-native/workspace-apps.json unless the app needs an explicit external URL override; the root deploy generates the workspace app registry from apps/* and deploy metadata.",
1226
1593
  "- Update pnpm-lock.yaml when adding or changing dependencies so Netlify can install the branch reliably.",
1227
1594
  "- Update the app manifest/package/deploy metadata needed by the existing workspace deployment model; do not leave the branch relying only on uncommitted local state.",
1228
- "- Verify the app's agent card/A2A metadata is ready so Dispatch can discover and delegate to the app after deployment.",
1229
- "- Include a final verification note covering the registry entry, manifest/deploy metadata, and agent-card readiness.",
1595
+ "- Verify the app's agent card/A2A metadata is ready so Dispatch can discover and delegate to the app after deployment. Every sibling workspace app should be usable over A2A by default through call-agent.",
1596
+ "- Give the app agent context that sibling workspace apps are available over A2A with names and descriptions from the workspace app registry; do not hardcode a stale app list.",
1597
+ "- Include a final verification note covering the registry entry, manifest/deploy metadata, relative same-origin routing, and agent-card readiness.",
1230
1598
  `When it is ready, start or update the workspace dev server and navigate the user to /${appId}.`,
1231
1599
  ].join("\n"),
1232
1600
  };
@@ -1270,6 +1638,7 @@ async function grantSelectedWorkspaceResources(input: {
1270
1638
  export async function startWorkspaceAppCreation(input: {
1271
1639
  prompt: string;
1272
1640
  appId?: string | null;
1641
+ description?: string | null;
1273
1642
  template?: string | null;
1274
1643
  secretIds?: string[];
1275
1644
  resourceIds?: string[];
@@ -1277,6 +1646,7 @@ export async function startWorkspaceAppCreation(input: {
1277
1646
  const initial = buildWorkspaceAppPrompt({
1278
1647
  prompt: input.prompt,
1279
1648
  appId: input.appId,
1649
+ description: input.description,
1280
1650
  template: input.template,
1281
1651
  });
1282
1652
  assertValidWorkspaceAppId(initial.appId);
@@ -1304,11 +1674,15 @@ export async function startWorkspaceAppCreation(input: {
1304
1674
  const built = buildWorkspaceAppPrompt({
1305
1675
  prompt: input.prompt,
1306
1676
  appId: input.appId,
1677
+ description: input.description,
1307
1678
  template: input.template,
1308
1679
  selectedKeys,
1309
1680
  selectedResources,
1310
1681
  });
1311
1682
  const prompt = built.prompt;
1683
+ const appDescription =
1684
+ input.description?.trim() ||
1685
+ generateWorkspaceAppDescription(input.prompt, built.appId);
1312
1686
 
1313
1687
  if (isLocal) {
1314
1688
  await requestSelectedVaultKeys({
@@ -1375,6 +1749,8 @@ export async function startWorkspaceAppCreation(input: {
1375
1749
  await recordPendingWorkspaceApp({
1376
1750
  appId: built.appId,
1377
1751
  projectId: settings.builderProjectId,
1752
+ description: appDescription,
1753
+ sourcePrompt: input.prompt,
1378
1754
  branchName: result.branchName,
1379
1755
  builderUrl: result.url,
1380
1756
  });
@@ -5,6 +5,7 @@ import type {
5
5
  import { resolveOrgIdForEmail } from "@agent-native/core/org";
6
6
  import crypto from "node:crypto";
7
7
  import { consumeLinkToken, resolveLinkedOwner } from "./dispatch-store.js";
8
+ import { handleRemoteCodeCommand } from "./dispatch-remote-commands.js";
8
9
 
9
10
  type SlackSenderProfile = {
10
11
  email: string | null;
@@ -195,11 +196,17 @@ export async function resolveDispatchOwner(
195
196
 
196
197
  export async function beforeDispatchProcess(
197
198
  incoming: IncomingMessage,
198
- _adapter: PlatformAdapter,
199
+ adapter: PlatformAdapter,
199
200
  ): Promise<{ handled: true; responseText?: string } | { handled: false }> {
200
201
  const trimmed = incoming.text.trim();
201
- const match = trimmed.match(/^\/link\s+([a-zA-Z0-9_-]+)$/);
202
- if (!match) return { handled: false };
202
+ const commandText =
203
+ contextString(incoming.platformContext.rawText) || trimmed;
204
+ const match = commandText.match(/^\/link(?:@\w+)?\s+([a-zA-Z0-9_-]+)$/);
205
+ if (!match) {
206
+ return handleRemoteCodeCommand(incoming, adapter, {
207
+ resolveOwner: () => resolveDispatchOwner(incoming),
208
+ });
209
+ }
203
210
 
204
211
  try {
205
212
  const owner = await consumeLinkToken({