aimeat 2.1.0 → 2.2.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 (422) hide show
  1. package/dist/.env.example +9 -4
  2. package/dist/locales/en.json +28 -0
  3. package/dist/locales/fi.json +28 -0
  4. package/dist/public/cortex-bundled/aimeat-charts.js +158 -10
  5. package/dist/public/cortex-bundled/aimeat-charts.yaml +1 -1
  6. package/dist/public/cortex-bundled/aimeat-dag.js +515 -173
  7. package/dist/public/cortex-bundled/aimeat-dag.yaml +1 -1
  8. package/dist/public/cortex-bundled/aimeat-i18n.js +6 -1
  9. package/dist/public/cortex-bundled/aimeat-i18n.yaml +1 -1
  10. package/dist/public/cortex-bundled/aimeat-surface.js +577 -0
  11. package/dist/public/cortex-bundled/aimeat-surface.yaml +107 -0
  12. package/dist/public/cortex-bundled/aimeat-viewport.js +405 -0
  13. package/dist/public/cortex-bundled/aimeat-viewport.yaml +100 -0
  14. package/dist/public/css/views/inbox.css +1 -1
  15. package/dist/public/css/views/profile.css +9 -0
  16. package/dist/public/lib/VENDORED.md +6 -3
  17. package/dist/public/lib/aimeat-daisyui-bridge.css +39 -2
  18. package/dist/public/lib/aimeat-theme.css +538 -0
  19. package/dist/public/lib/fonts/LICENSE.md +11 -4
  20. package/dist/public/lib/fonts/fraunces-var-latin-ext.woff2 +0 -0
  21. package/dist/public/lib/fonts/fraunces-var-latin.woff2 +0 -0
  22. package/dist/public/lib/fonts/inter-var-latin-ext.woff2 +0 -0
  23. package/dist/public/lib/fonts/inter-var-latin.woff2 +0 -0
  24. package/dist/public/lib/fonts/jetbrains-mono-var-latin-ext.woff2 +0 -0
  25. package/dist/public/lib/fonts/jetbrains-mono-var-latin.woff2 +0 -0
  26. package/dist/public/lib/fonts/space-grotesk-var-latin-ext.woff2 +0 -0
  27. package/dist/public/lib/fonts/space-grotesk-var-latin.woff2 +0 -0
  28. package/dist/public/lib/samples/theme-check.html +92 -0
  29. package/dist/public/lib/samples/themes-boot.js +20 -0
  30. package/dist/public/lib/samples/themes.html +154 -0
  31. package/dist/public/lib/samples/themes.js +60 -0
  32. package/dist/public/views/profile/access-tab/connected-apps.js +81 -5
  33. package/dist/public/views/profile/extensions-tab.maturity.js +4 -1
  34. package/dist/public/views/profile/inbox-tab/components.js +17 -1
  35. package/dist/public/views/profile/inbox-tab/use-thread-ux.js +29 -2
  36. package/dist/public/views/profile/libraries-tab.js +10 -2
  37. package/dist/public/views/profile/wallet-tab.js +64 -0
  38. package/dist/scripts/check-openapi.d.ts +2 -0
  39. package/dist/scripts/check-openapi.d.ts.map +1 -0
  40. package/dist/scripts/check-openapi.js +139 -0
  41. package/dist/scripts/check-openapi.js.map +1 -0
  42. package/dist/scripts/embed-viewport.d.ts +23 -0
  43. package/dist/scripts/embed-viewport.d.ts.map +1 -0
  44. package/dist/scripts/embed-viewport.js +116 -0
  45. package/dist/scripts/embed-viewport.js.map +1 -0
  46. package/dist/src/auth/sse-domain-scopes.d.ts +50 -0
  47. package/dist/src/auth/sse-domain-scopes.d.ts.map +1 -0
  48. package/dist/src/auth/sse-domain-scopes.js +111 -0
  49. package/dist/src/auth/sse-domain-scopes.js.map +1 -0
  50. package/dist/src/cli/connect/mcp/tools/agent-tasks.d.ts.map +1 -1
  51. package/dist/src/cli/connect/mcp/tools/agent-tasks.js +4 -1
  52. package/dist/src/cli/connect/mcp/tools/agent-tasks.js.map +1 -1
  53. package/dist/src/cli/connect/mcp/tools/core.d.ts.map +1 -1
  54. package/dist/src/cli/connect/mcp/tools/core.js +14 -3
  55. package/dist/src/cli/connect/mcp/tools/core.js.map +1 -1
  56. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts +3 -0
  57. package/dist/src/cli/connect/mcp/tools/workspaces.d.ts.map +1 -1
  58. package/dist/src/cli/connect/mcp/tools/workspaces.js +4 -2
  59. package/dist/src/cli/connect/mcp/tools/workspaces.js.map +1 -1
  60. package/dist/src/cli/connect/tool-call-defs-core.d.ts.map +1 -1
  61. package/dist/src/cli/connect/tool-call-defs-core.js +13 -1
  62. package/dist/src/cli/connect/tool-call-defs-core.js.map +1 -1
  63. package/dist/src/commerce/__tests__/x402-registry.test.d.ts +2 -0
  64. package/dist/src/commerce/__tests__/x402-registry.test.d.ts.map +1 -0
  65. package/dist/src/commerce/__tests__/x402-registry.test.js +105 -0
  66. package/dist/src/commerce/__tests__/x402-registry.test.js.map +1 -0
  67. package/dist/src/commerce/evm-address.d.ts +21 -0
  68. package/dist/src/commerce/evm-address.d.ts.map +1 -0
  69. package/dist/src/commerce/evm-address.js +94 -0
  70. package/dist/src/commerce/evm-address.js.map +1 -0
  71. package/dist/src/commerce/money.d.ts +18 -5
  72. package/dist/src/commerce/money.d.ts.map +1 -1
  73. package/dist/src/commerce/money.js +27 -6
  74. package/dist/src/commerce/money.js.map +1 -1
  75. package/dist/src/commerce/x402-facilitator.d.ts +64 -13
  76. package/dist/src/commerce/x402-facilitator.d.ts.map +1 -1
  77. package/dist/src/commerce/x402-facilitator.js +71 -19
  78. package/dist/src/commerce/x402-facilitator.js.map +1 -1
  79. package/dist/src/commerce/x402-handler.d.ts +13 -7
  80. package/dist/src/commerce/x402-handler.d.ts.map +1 -1
  81. package/dist/src/commerce/x402-handler.js +23 -10
  82. package/dist/src/commerce/x402-handler.js.map +1 -1
  83. package/dist/src/commerce/x402.d.ts +11 -6
  84. package/dist/src/commerce/x402.d.ts.map +1 -1
  85. package/dist/src/commerce/x402.js +10 -7
  86. package/dist/src/commerce/x402.js.map +1 -1
  87. package/dist/src/config-types.d.ts +12 -0
  88. package/dist/src/config-types.d.ts.map +1 -1
  89. package/dist/src/config.d.ts.map +1 -1
  90. package/dist/src/config.js +8 -0
  91. package/dist/src/config.js.map +1 -1
  92. package/dist/src/data/app-templates/shells.d.ts +11 -3
  93. package/dist/src/data/app-templates/shells.d.ts.map +1 -1
  94. package/dist/src/data/app-templates/shells.js +87 -6
  95. package/dist/src/data/app-templates/shells.js.map +1 -1
  96. package/dist/src/data/app-templates.js +2 -2
  97. package/dist/src/data/app-templates.js.map +1 -1
  98. package/dist/src/data/library-packs/cortex.d.ts.map +1 -1
  99. package/dist/src/data/library-packs/cortex.js +108 -5
  100. package/dist/src/data/library-packs/cortex.js.map +1 -1
  101. package/dist/src/data/library-packs/sdk.d.ts.map +1 -1
  102. package/dist/src/data/library-packs/sdk.js +32 -8
  103. package/dist/src/data/library-packs/sdk.js.map +1 -1
  104. package/dist/src/data/library-packs/vendored.d.ts.map +1 -1
  105. package/dist/src/data/library-packs/vendored.js +83 -6
  106. package/dist/src/data/library-packs/vendored.js.map +1 -1
  107. package/dist/src/generated/api-types.d.ts +478 -19
  108. package/dist/src/generated/api-types.d.ts.map +1 -1
  109. package/dist/src/mcp/agent-tasks.d.ts +4 -0
  110. package/dist/src/mcp/agent-tasks.d.ts.map +1 -1
  111. package/dist/src/mcp/agent-tasks.js +20 -2
  112. package/dist/src/mcp/agent-tasks.js.map +1 -1
  113. package/dist/src/mcp/capabilities.d.ts +7 -1
  114. package/dist/src/mcp/capabilities.d.ts.map +1 -1
  115. package/dist/src/mcp/capabilities.js +5 -2
  116. package/dist/src/mcp/capabilities.js.map +1 -1
  117. package/dist/src/mcp/catalog/definitions/commerce.d.ts.map +1 -1
  118. package/dist/src/mcp/catalog/definitions/commerce.js +4 -2
  119. package/dist/src/mcp/catalog/definitions/commerce.js.map +1 -1
  120. package/dist/src/mcp/catalog/definitions/discovery-work-boards.d.ts.map +1 -1
  121. package/dist/src/mcp/catalog/definitions/discovery-work-boards.js +4 -3
  122. package/dist/src/mcp/catalog/definitions/discovery-work-boards.js.map +1 -1
  123. package/dist/src/mcp/catalog/definitions/organisms-workspaces-apps.js +1 -1
  124. package/dist/src/mcp/catalog/definitions/organisms-workspaces-apps.js.map +1 -1
  125. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.d.ts.map +1 -1
  126. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js +3 -2
  127. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js.map +1 -1
  128. package/dist/src/mcp/catalog/output-schemas.d.ts +6 -1
  129. package/dist/src/mcp/catalog/output-schemas.d.ts.map +1 -1
  130. package/dist/src/mcp/catalog/output-schemas.js +6 -1
  131. package/dist/src/mcp/catalog/output-schemas.js.map +1 -1
  132. package/dist/src/mcp/commerce.d.ts.map +1 -1
  133. package/dist/src/mcp/commerce.js +15 -3
  134. package/dist/src/mcp/commerce.js.map +1 -1
  135. package/dist/src/mcp/core-storage.d.ts +23 -0
  136. package/dist/src/mcp/core-storage.d.ts.map +1 -0
  137. package/dist/src/mcp/core-storage.js +157 -0
  138. package/dist/src/mcp/core-storage.js.map +1 -0
  139. package/dist/src/mcp/core.d.ts +16 -0
  140. package/dist/src/mcp/core.d.ts.map +1 -1
  141. package/dist/src/mcp/core.js +75 -119
  142. package/dist/src/mcp/core.js.map +1 -1
  143. package/dist/src/mcp/dm-messages.d.ts +4 -0
  144. package/dist/src/mcp/dm-messages.d.ts.map +1 -1
  145. package/dist/src/mcp/dm-messages.js +43 -2
  146. package/dist/src/mcp/dm-messages.js.map +1 -1
  147. package/dist/src/mcp/exchange.d.ts.map +1 -1
  148. package/dist/src/mcp/exchange.js +3 -2
  149. package/dist/src/mcp/exchange.js.map +1 -1
  150. package/dist/src/mcp/extensions.d.ts.map +1 -1
  151. package/dist/src/mcp/extensions.js +6 -2
  152. package/dist/src/mcp/extensions.js.map +1 -1
  153. package/dist/src/mcp/index.d.ts +5 -0
  154. package/dist/src/mcp/index.d.ts.map +1 -1
  155. package/dist/src/mcp/index.js +23 -4
  156. package/dist/src/mcp/index.js.map +1 -1
  157. package/dist/src/mcp/memory-namespace-hints.d.ts +33 -0
  158. package/dist/src/mcp/memory-namespace-hints.d.ts.map +1 -0
  159. package/dist/src/mcp/memory-namespace-hints.js +60 -0
  160. package/dist/src/mcp/memory-namespace-hints.js.map +1 -0
  161. package/dist/src/mcp/workspaces.d.ts +5 -0
  162. package/dist/src/mcp/workspaces.d.ts.map +1 -1
  163. package/dist/src/mcp/workspaces.js +5 -3
  164. package/dist/src/mcp/workspaces.js.map +1 -1
  165. package/dist/src/models/agent-task-schemas.d.ts +25 -3
  166. package/dist/src/models/agent-task-schemas.d.ts.map +1 -1
  167. package/dist/src/models/agent-task-schemas.js +16 -0
  168. package/dist/src/models/agent-task-schemas.js.map +1 -1
  169. package/dist/src/models/app-tool-schemas.d.ts +518 -1
  170. package/dist/src/models/app-tool-schemas.d.ts.map +1 -1
  171. package/dist/src/models/app-tool-schemas.js +53 -1
  172. package/dist/src/models/app-tool-schemas.js.map +1 -1
  173. package/dist/src/models/odps-schemas.d.ts +305 -0
  174. package/dist/src/models/odps-schemas.d.ts.map +1 -0
  175. package/dist/src/models/odps-schemas.js +182 -0
  176. package/dist/src/models/odps-schemas.js.map +1 -0
  177. package/dist/src/models/offer-schemas.d.ts +357 -2
  178. package/dist/src/models/offer-schemas.d.ts.map +1 -1
  179. package/dist/src/models/offer-schemas.js +37 -0
  180. package/dist/src/models/offer-schemas.js.map +1 -1
  181. package/dist/src/models/schemas.d.ts +6 -3
  182. package/dist/src/models/schemas.d.ts.map +1 -1
  183. package/dist/src/models/schemas.js +10 -1
  184. package/dist/src/models/schemas.js.map +1 -1
  185. package/dist/src/models/webhook-schemas.d.ts +8 -8
  186. package/dist/src/routes/agent-tasks/create-read.d.ts +4 -0
  187. package/dist/src/routes/agent-tasks/create-read.d.ts.map +1 -1
  188. package/dist/src/routes/agent-tasks/create-read.js +20 -1
  189. package/dist/src/routes/agent-tasks/create-read.js.map +1 -1
  190. package/dist/src/routes/agent-tasks/lifecycle.d.ts.map +1 -1
  191. package/dist/src/routes/agent-tasks/lifecycle.js +10 -0
  192. package/dist/src/routes/agent-tasks/lifecycle.js.map +1 -1
  193. package/dist/src/routes/agents/offers.d.ts +2 -0
  194. package/dist/src/routes/agents/offers.d.ts.map +1 -1
  195. package/dist/src/routes/agents/offers.js +0 -0
  196. package/dist/src/routes/agents/offers.js.map +1 -1
  197. package/dist/src/routes/app-grants.d.ts +6 -0
  198. package/dist/src/routes/app-grants.d.ts.map +1 -1
  199. package/dist/src/routes/app-grants.js +55 -25
  200. package/dist/src/routes/app-grants.js.map +1 -1
  201. package/dist/src/routes/apps/drafts.d.ts +3 -0
  202. package/dist/src/routes/apps/drafts.d.ts.map +1 -1
  203. package/dist/src/routes/apps/drafts.js +63 -1
  204. package/dist/src/routes/apps/drafts.js.map +1 -1
  205. package/dist/src/routes/apps/publish.d.ts +2 -0
  206. package/dist/src/routes/apps/publish.d.ts.map +1 -1
  207. package/dist/src/routes/apps/publish.js +17 -2
  208. package/dist/src/routes/apps/publish.js.map +1 -1
  209. package/dist/src/routes/commerce.d.ts.map +1 -1
  210. package/dist/src/routes/commerce.js +122 -2
  211. package/dist/src/routes/commerce.js.map +1 -1
  212. package/dist/src/routes/exchange-market.d.ts +6 -2
  213. package/dist/src/routes/exchange-market.d.ts.map +1 -1
  214. package/dist/src/routes/exchange-market.js +154 -28
  215. package/dist/src/routes/exchange-market.js.map +1 -1
  216. package/dist/src/routes/exchange.d.ts.map +1 -1
  217. package/dist/src/routes/exchange.js +5 -2
  218. package/dist/src/routes/exchange.js.map +1 -1
  219. package/dist/src/routes/extensions/actions.d.ts.map +1 -1
  220. package/dist/src/routes/extensions/actions.js +18 -0
  221. package/dist/src/routes/extensions/actions.js.map +1 -1
  222. package/dist/src/routes/extensions/crud.d.ts.map +1 -1
  223. package/dist/src/routes/extensions/crud.js +36 -3
  224. package/dist/src/routes/extensions/crud.js.map +1 -1
  225. package/dist/src/routes/extensions/entitlement-gate.d.ts.map +1 -1
  226. package/dist/src/routes/extensions/entitlement-gate.js +13 -0
  227. package/dist/src/routes/extensions/entitlement-gate.js.map +1 -1
  228. package/dist/src/routes/extensions/manifest.d.ts +6 -0
  229. package/dist/src/routes/extensions/manifest.d.ts.map +1 -1
  230. package/dist/src/routes/extensions/manifest.js +68 -1
  231. package/dist/src/routes/extensions/manifest.js.map +1 -1
  232. package/dist/src/routes/extensions/pacing.d.ts +31 -0
  233. package/dist/src/routes/extensions/pacing.d.ts.map +1 -0
  234. package/dist/src/routes/extensions/pacing.js +73 -0
  235. package/dist/src/routes/extensions/pacing.js.map +1 -0
  236. package/dist/src/routes/extensions/paywall.d.ts.map +1 -1
  237. package/dist/src/routes/extensions/paywall.js +17 -17
  238. package/dist/src/routes/extensions/paywall.js.map +1 -1
  239. package/dist/src/routes/memory/crud.d.ts.map +1 -1
  240. package/dist/src/routes/memory/crud.js +10 -0
  241. package/dist/src/routes/memory/crud.js.map +1 -1
  242. package/dist/src/routes/organisms/crud.d.ts +3 -0
  243. package/dist/src/routes/organisms/crud.d.ts.map +1 -1
  244. package/dist/src/routes/organisms/crud.js +5 -2
  245. package/dist/src/routes/organisms/crud.js.map +1 -1
  246. package/dist/src/routes/organisms/workspace-read.d.ts.map +1 -1
  247. package/dist/src/routes/organisms/workspace-read.js +7 -2
  248. package/dist/src/routes/organisms/workspace-read.js.map +1 -1
  249. package/dist/src/routes/public-stats.d.ts +7 -0
  250. package/dist/src/routes/public-stats.d.ts.map +1 -1
  251. package/dist/src/routes/public-stats.js +28 -7
  252. package/dist/src/routes/public-stats.js.map +1 -1
  253. package/dist/src/routes/sse.d.ts +7 -0
  254. package/dist/src/routes/sse.d.ts.map +1 -1
  255. package/dist/src/routes/sse.js +36 -6
  256. package/dist/src/routes/sse.js.map +1 -1
  257. package/dist/src/routes/storage-files.d.ts +11 -0
  258. package/dist/src/routes/storage-files.d.ts.map +1 -1
  259. package/dist/src/routes/storage-files.js +58 -51
  260. package/dist/src/routes/storage-files.js.map +1 -1
  261. package/dist/src/routes/subdomains.d.ts +4 -0
  262. package/dist/src/routes/subdomains.d.ts.map +1 -1
  263. package/dist/src/routes/subdomains.js +42 -5
  264. package/dist/src/routes/subdomains.js.map +1 -1
  265. package/dist/src/routes/upload.d.ts +11 -0
  266. package/dist/src/routes/upload.d.ts.map +1 -1
  267. package/dist/src/routes/upload.js +108 -28
  268. package/dist/src/routes/upload.js.map +1 -1
  269. package/dist/src/routes/webmcp.d.ts.map +1 -1
  270. package/dist/src/routes/webmcp.js +4 -0
  271. package/dist/src/routes/webmcp.js.map +1 -1
  272. package/dist/src/schemas/knowledge-package.d.ts +1 -1
  273. package/dist/src/server-bootstrap/static-files.d.ts +4 -0
  274. package/dist/src/server-bootstrap/static-files.d.ts.map +1 -1
  275. package/dist/src/server-bootstrap/static-files.js +11 -0
  276. package/dist/src/server-bootstrap/static-files.js.map +1 -1
  277. package/dist/src/services/build-app-prompt.d.ts +19 -0
  278. package/dist/src/services/build-app-prompt.d.ts.map +1 -1
  279. package/dist/src/services/build-app-prompt.js +97 -11
  280. package/dist/src/services/build-app-prompt.js.map +1 -1
  281. package/dist/src/services/call-timing.d.ts +51 -0
  282. package/dist/src/services/call-timing.d.ts.map +1 -0
  283. package/dist/src/services/call-timing.js +91 -0
  284. package/dist/src/services/call-timing.js.map +1 -0
  285. package/dist/src/services/capability-invoke.d.ts +2 -0
  286. package/dist/src/services/capability-invoke.d.ts.map +1 -1
  287. package/dist/src/services/capability-invoke.js +10 -0
  288. package/dist/src/services/capability-invoke.js.map +1 -1
  289. package/dist/src/services/config-schema.d.ts.map +1 -1
  290. package/dist/src/services/config-schema.js +6 -0
  291. package/dist/src/services/config-schema.js.map +1 -1
  292. package/dist/src/services/draft-token.d.ts +22 -2
  293. package/dist/src/services/draft-token.d.ts.map +1 -1
  294. package/dist/src/services/draft-token.js +52 -3
  295. package/dist/src/services/draft-token.js.map +1 -1
  296. package/dist/src/services/exchange-market.d.ts +41 -8
  297. package/dist/src/services/exchange-market.d.ts.map +1 -1
  298. package/dist/src/services/exchange-market.js +24 -2
  299. package/dist/src/services/exchange-market.js.map +1 -1
  300. package/dist/src/services/exchange-odps.d.ts +58 -0
  301. package/dist/src/services/exchange-odps.d.ts.map +1 -0
  302. package/dist/src/services/exchange-odps.js +400 -0
  303. package/dist/src/services/exchange-odps.js.map +1 -0
  304. package/dist/src/services/exchange-projection.d.ts +61 -0
  305. package/dist/src/services/exchange-projection.d.ts.map +1 -0
  306. package/dist/src/services/exchange-projection.js +504 -0
  307. package/dist/src/services/exchange-projection.js.map +1 -0
  308. package/dist/src/services/exchange-proposals.d.ts.map +1 -1
  309. package/dist/src/services/exchange-proposals.js +2 -0
  310. package/dist/src/services/exchange-proposals.js.map +1 -1
  311. package/dist/src/services/extension-files.d.ts +64 -0
  312. package/dist/src/services/extension-files.d.ts.map +1 -0
  313. package/dist/src/services/extension-files.js +88 -0
  314. package/dist/src/services/extension-files.js.map +1 -0
  315. package/dist/src/services/extension-runtime.d.ts +33 -0
  316. package/dist/src/services/extension-runtime.d.ts.map +1 -1
  317. package/dist/src/services/extension-runtime.js +52 -0
  318. package/dist/src/services/extension-runtime.js.map +1 -1
  319. package/dist/src/services/file-refs.d.ts +105 -0
  320. package/dist/src/services/file-refs.d.ts.map +1 -0
  321. package/dist/src/services/file-refs.js +106 -0
  322. package/dist/src/services/file-refs.js.map +1 -0
  323. package/dist/src/services/metered-entitlements.d.ts +7 -0
  324. package/dist/src/services/metered-entitlements.d.ts.map +1 -1
  325. package/dist/src/services/metered-entitlements.js +4 -0
  326. package/dist/src/services/metered-entitlements.js.map +1 -1
  327. package/dist/src/services/owner-memory.d.ts +14 -1
  328. package/dist/src/services/owner-memory.d.ts.map +1 -1
  329. package/dist/src/services/owner-memory.js +8 -14
  330. package/dist/src/services/owner-memory.js.map +1 -1
  331. package/dist/src/services/task-files.d.ts +51 -0
  332. package/dist/src/services/task-files.d.ts.map +1 -0
  333. package/dist/src/services/task-files.js +58 -0
  334. package/dist/src/services/task-files.js.map +1 -0
  335. package/dist/src/services/upload-token.d.ts +29 -0
  336. package/dist/src/services/upload-token.d.ts.map +1 -1
  337. package/dist/src/services/upload-token.js +37 -0
  338. package/dist/src/services/upload-token.js.map +1 -1
  339. package/dist/src/services/upload-zip.d.ts +2 -0
  340. package/dist/src/services/upload-zip.d.ts.map +1 -1
  341. package/dist/src/services/upload-zip.js +5 -1
  342. package/dist/src/services/upload-zip.js.map +1 -1
  343. package/dist/src/services/workspace-meta.d.ts +15 -0
  344. package/dist/src/services/workspace-meta.d.ts.map +1 -1
  345. package/dist/src/services/workspace-meta.js +17 -0
  346. package/dist/src/services/workspace-meta.js.map +1 -1
  347. package/dist/src/services/workspace-provision.d.ts +3 -0
  348. package/dist/src/services/workspace-provision.d.ts.map +1 -1
  349. package/dist/src/services/workspace-provision.js +5 -3
  350. package/dist/src/services/workspace-provision.js.map +1 -1
  351. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.d.ts +1 -0
  352. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.d.ts.map +1 -1
  353. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.js +10 -0
  354. package/dist/src/storage/providers/postgres-kysely/methods/app-grants.js.map +1 -1
  355. package/dist/src/storage/providers/postgres-kysely/methods/memory.d.ts +2 -0
  356. package/dist/src/storage/providers/postgres-kysely/methods/memory.d.ts.map +1 -1
  357. package/dist/src/storage/providers/postgres-kysely/methods/memory.js +12 -1
  358. package/dist/src/storage/providers/postgres-kysely/methods/memory.js.map +1 -1
  359. package/dist/src/storage/providers/postgres-kysely/methods/organisms.d.ts.map +1 -1
  360. package/dist/src/storage/providers/postgres-kysely/methods/organisms.js +5 -1
  361. package/dist/src/storage/providers/postgres-kysely/methods/organisms.js.map +1 -1
  362. package/dist/src/storage/providers/postgres-kysely/migrations/0012_unique_live_app_grant.sql +29 -0
  363. package/dist/src/storage/providers/sqlite/methods/apps.d.ts +3 -0
  364. package/dist/src/storage/providers/sqlite/methods/apps.d.ts.map +1 -1
  365. package/dist/src/storage/providers/sqlite/methods/apps.js +7 -0
  366. package/dist/src/storage/providers/sqlite/methods/apps.js.map +1 -1
  367. package/dist/src/storage/providers/sqlite/methods/governance.d.ts +1 -0
  368. package/dist/src/storage/providers/sqlite/methods/governance.d.ts.map +1 -1
  369. package/dist/src/storage/providers/sqlite/methods/governance.js +4 -1
  370. package/dist/src/storage/providers/sqlite/methods/governance.js.map +1 -1
  371. package/dist/src/storage/providers/sqlite/methods/owner.d.ts +5 -0
  372. package/dist/src/storage/providers/sqlite/methods/owner.d.ts.map +1 -1
  373. package/dist/src/storage/providers/sqlite/methods/owner.js +8 -0
  374. package/dist/src/storage/providers/sqlite/methods/owner.js.map +1 -1
  375. package/dist/src/storage/providers/sqlite/schema.d.ts +3 -0
  376. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  377. package/dist/src/storage/providers/sqlite/schema.js +20 -0
  378. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  379. package/dist/src/storage/repositories/app-grant.repository.d.ts +9 -1
  380. package/dist/src/storage/repositories/app-grant.repository.d.ts.map +1 -1
  381. package/dist/src/storage/repositories/memory.repository.d.ts +14 -0
  382. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  383. package/dist/src/storage/repositories-impl/memory-repository.d.ts +8 -1
  384. package/dist/src/storage/repositories-impl/memory-repository.d.ts.map +1 -1
  385. package/dist/src/storage/repositories-impl/memory-repository.js +15 -4
  386. package/dist/src/storage/repositories-impl/memory-repository.js.map +1 -1
  387. package/dist/src/storage/types/agents-messaging.d.ts +19 -0
  388. package/dist/src/storage/types/agents-messaging.d.ts.map +1 -1
  389. package/dist/src/storage/types/agents-messaging.js.map +1 -1
  390. package/dist/src/storage/types/organisms-federation.d.ts +29 -0
  391. package/dist/src/storage/types/organisms-federation.d.ts.map +1 -1
  392. package/dist/src/utils/app-badge.d.ts.map +1 -1
  393. package/dist/src/utils/app-badge.js +7 -3
  394. package/dist/src/utils/app-badge.js.map +1 -1
  395. package/dist/static/app-catalog/_template.html +6 -5
  396. package/dist/static/app-catalog/js/cortex.js +3 -1
  397. package/dist/static/app-catalog/js/detail.js +411 -36
  398. package/dist/static/app-catalog/js/i18n-data.js +279 -3
  399. package/dist/static/app-catalog/js/main.js +28 -37
  400. package/dist/static/app-catalog/js/monetize.js +204 -16
  401. package/dist/static/app-catalog/js/odps.js +489 -0
  402. package/dist/static/app-catalog/js/render.js +3 -0
  403. package/dist/static/app-catalog/js/workcopy.js +198 -0
  404. package/dist/static/app-catalog/styles/app-catalog.css +60 -0
  405. package/dist/static/app-catalog.html +1450 -137
  406. package/dist/static/sdk-libs/agents/index.js +45 -7
  407. package/dist/static/sdk-libs/auth/cluster.js +97 -0
  408. package/dist/static/sdk-libs/auth/index.js +25 -1
  409. package/dist/static/sdk-libs/auth/locale.js +151 -0
  410. package/dist/static/sdk-libs/auth/modal.js +11 -2
  411. package/dist/static/sdk-libs/auth/palette.js +172 -0
  412. package/dist/static/sdk-libs/auth/pill.js +37 -6
  413. package/dist/static/sdk-libs/auth/theme.js +61 -20
  414. package/dist/static/sdk-libs/data/index.js +70 -8
  415. package/dist/static/sdk-libs/dist/aimeat-agents.js +41 -8
  416. package/dist/static/sdk-libs/dist/aimeat-auth.js +422 -21
  417. package/dist/static/sdk-libs/dist/aimeat-data.js +50 -8
  418. package/dist/static/sdk-libs/dist/aimeat-live.js +54 -2
  419. package/dist/static/sdk-libs/dist/aimeat-markdown.js +32 -1
  420. package/dist/static/sdk-libs/live/index.js +71 -4
  421. package/dist/static/sdk-libs/markdown/index.js +49 -1
  422. package/package.json +6 -2
@@ -618,6 +618,13 @@
618
618
  .fav-toggle.on { opacity: 1; }
619
619
  .published-card { position: relative; }
620
620
  .published-card .fav-toggle { left: auto; right: 8px; top: 8px; }
621
+ /* Library/community cards already own the top-LEFT corner with the Listed/Unlisted badge, so
622
+ the star sits top-RIGHT instead of on top of it. Keep clear of the ⋮ menu button, which only
623
+ local cards render: :has() pulls the star back to the edge when there is no menu. Where
624
+ :has() is unsupported that selector is simply dropped and the star keeps the wider offset —
625
+ a little gap, never an overlap. */
626
+ .app-card .fav-toggle { left: auto; right: 40px; top: 6px; }
627
+ .app-card:not(:has(.card-menu-btn)) .fav-toggle { right: 8px; }
621
628
  .fav-section-title {
622
629
  font-size: 1rem;
623
630
  font-weight: 700;
@@ -1152,6 +1159,33 @@
1152
1159
  .dtl-sync.diff { background: rgba(245,158,11,.14); color: #f59e0b; }
1153
1160
  .dtl-sync.none { background: var(--bg-subtle); color: var(--text-muted); }
1154
1161
 
1162
+ /* ── Working copy → published lifecycle band (TARGET-048) ──────────────────
1163
+ Replaces the old two-number STATUS card: two labelled stops that say, in plain words, who
1164
+ can see what. The active stop is highlighted so "where is my work" is answerable at a glance. */
1165
+ .wc-row { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
1166
+ .wc-band { flex: 1; min-width: 240px; display: flex; align-items: stretch; gap: 10px; flex-wrap: wrap; }
1167
+ .wc-stop {
1168
+ display: flex; flex-direction: column; gap: 2px;
1169
+ padding: 10px 12px; border-radius: 10px;
1170
+ border: 1px solid var(--border-subtle); background: var(--bg-subtle);
1171
+ min-width: 150px; flex: 1;
1172
+ }
1173
+ .wc-stop.is-active { border-color: rgba(245, 158, 11, .55); background: rgba(245, 158, 11, .10); }
1174
+ .wc-stop.is-live { border-color: rgba(52, 211, 153, .45); background: rgba(52, 211, 153, .10); }
1175
+ .wc-stop-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
1176
+ .wc-stop-val { font-size: .95rem; font-weight: 700; color: var(--text); }
1177
+ .wc-stop-note { font-size: .75rem; color: var(--text-muted); }
1178
+ .wc-arrow { align-self: center; color: var(--text-muted); font-size: 1.1rem; }
1179
+ .wc-explain { font-size: .88rem; color: var(--text); line-height: 1.5; margin: 12px 0 10px; }
1180
+ .wc-size { font-size: .75rem; color: var(--text-muted); margin: 8px 0 0; }
1181
+ .wc-muted { color: var(--text-muted); font-size: .85rem; }
1182
+ /* One-line reminder of what the three verbs do — shown under both button rows so the source
1183
+ editor and the AI loop explain themselves identically. */
1184
+ .wc-verbs-hint { font-size: .78rem; color: var(--text-muted); line-height: 1.45; margin: 10px 0 0; }
1185
+ .wc-proposal-head { margin: 0 0 8px; }
1186
+ .wc-ckpt-when { display: block; font-size: .85rem; font-weight: 600; color: var(--text); }
1187
+ .wc-ckpt-note { display: block; font-size: .78rem; color: var(--text-muted); }
1188
+
1155
1189
  .dtl-meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }
1156
1190
  .dtl-meta-item .dtl-meta-label { font-size: .7rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); }
1157
1191
  .dtl-meta-item .dtl-meta-val { font-size: .9rem; color: var(--text); word-break: break-word; }
@@ -2194,6 +2228,32 @@
2194
2228
  .aga-deploy-btn { white-space: nowrap; }
2195
2229
  .aga-btn-sm { padding: 2px 10px; font-size: .75rem; margin-left: auto; }
2196
2230
  .published-card-badgerow .pcb-agent { cursor: default; }
2231
+
2232
+ /* ── EXCHANGE & ODPS (app detail) ──────────────────────────────────────────────
2233
+ The app-level marketplace status plus the ODPS authoring forms (app defaults and the per-tool
2234
+ descriptor). Themed through the same variables as the rest of the detail view; the attestation
2235
+ block is set apart on purpose, because those fields are legal promises rather than marketing copy. */
2236
+ .od-status { font-size: .86rem; font-weight: 600; padding: 6px 10px; border-radius: 8px; display: inline-block; }
2237
+ .od-status-on { background: rgba(52,211,153,.12); color: #34d399; }
2238
+ .od-status-none { background: var(--bg-subtle); color: var(--text-muted); }
2239
+ .od-note { margin: 6px 0 8px; }
2240
+ .od-muted { color: var(--text-muted); font-size: .85rem; }
2241
+ .od-actions { margin-top: 10px; }
2242
+ .od-form { margin-top: 10px; }
2243
+ .od-input { margin: 4px 0 8px; }
2244
+ .od-grid { display: flex; gap: 10px; flex-wrap: wrap; }
2245
+ .od-col { flex: 1; min-width: 150px; }
2246
+ .od-col-narrow { flex: 0 0 110px; min-width: 90px; }
2247
+ .od-attest { border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; margin: 8px 0; background: var(--bg-subtle); }
2248
+ .od-attest-head { font-weight: 600; margin-bottom: 2px; }
2249
+ .od-tool-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 8px 0 4px; }
2250
+ .od-link { font-size: .8rem; color: var(--accent); text-decoration: none; }
2251
+ .od-link:hover { text-decoration: underline; }
2252
+ .od-blocked { flex-basis: 100%; font-size: .8rem; color: #f59e0b; margin-top: 4px; }
2253
+ .od-ok { color: #34d399; }
2254
+ .od-err { color: var(--accent); }
2255
+ /* Measured delivery time offered as the basis for a commitment (never as the commitment itself). */
2256
+ .od-measured { font-size: 12px; color: var(--text-muted); margin: 4px 0 8px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
2197
2257
  </style>
2198
2258
  </head>
2199
2259
  <body data-active-view="library">
@@ -2500,13 +2560,14 @@
2500
2560
  <div class="modal-actions">
2501
2561
  <button class="modal-btn secondary" id="copy-source-btn" data-i18n="source.copy">Copy Source</button>
2502
2562
  <button class="modal-btn primary" id="copy-prompt-btn" data-i18n="source.copyPrompt">Copy with AI Prompt</button>
2503
- <button class="modal-btn secondary" id="save-source-btn" disabled data-i18n="source.save">Save working copy</button>
2504
- <!-- Real-origin staging (published apps only; toggled in viewSource). Same server-draft
2505
- path as the AI loop: test the edited source on a real origin, then publish it. -->
2506
- <button class="modal-btn primary" id="source-test-live-btn" hidden data-i18n="detail.testLive">Test on real origin</button>
2507
- <button class="modal-btn success" id="source-publish-tested-btn" hidden data-i18n="detail.publishTested">Publish tested version</button>
2563
+ <!-- The three canonical verbs, identical to the detail view's AI loop (TARGET-048):
2564
+ save (private, checkpointed) → try (real origin, live untouched) publish (public). -->
2565
+ <button class="modal-btn primary" id="save-source-btn" disabled data-i18n="source.save">Save working copy</button>
2566
+ <button class="modal-btn secondary" id="source-test-live-btn" hidden data-i18n="wc.try">Try it safely</button>
2567
+ <button class="modal-btn success" id="source-publish-tested-btn" hidden data-i18n="wc.publishNew">Publish as a new version</button>
2508
2568
  <button class="modal-btn secondary" id="close-source-btn" data-i18n="common.close">Close</button>
2509
2569
  </div>
2570
+ <p class="wc-verbs-hint" data-i18n="wc.verbsHint">Save keeps it for you only. Try opens it on its real address without changing the live app. Publish is what other people see.</p>
2510
2571
  </div>
2511
2572
  </div>
2512
2573
 
@@ -2827,6 +2888,9 @@
2827
2888
  "pb.promptPreview": "Prompt preview",
2828
2889
  "pb.addBtn": "➕ Add your app — paste it or upload the file",
2829
2890
  "pb.improving": "Improving",
2891
+ "pb.tier.any": "ANY MODEL",
2892
+ "pb.tier.frontier": "VERSION TRAP",
2893
+ "pb.tier.needs-doc": "READ THE DOC",
2830
2894
  "pb.copied": "Copied!",
2831
2895
  "pb.new": "New App",
2832
2896
  "pb.improve": "Improve This App",
@@ -3010,6 +3074,90 @@
3010
3074
  "monetize.priceMorsels": "Price (morsels)",
3011
3075
  "monetize.priceMoney": "Money price",
3012
3076
  "monetize.currency": "Currency",
3077
+ "monetize.money2": "Second money price",
3078
+ "monetize.currency2": "Currency",
3079
+ "monetize.money2Hint": "Optional: sell the same call in a second currency. Buyers pick one when they open a contract.",
3080
+ "odps.title": "EXCHANGE & ODPS",
3081
+ "odps.hint": "Whether this app sells anything on the marketplace, and the product description every listing carries. A listed tool is published as an Open Data Product Specification v4.1 document that outside catalogues and buying agents can read.",
3082
+ "odps.statusOn": "On the marketplace: {live} of {total} tools",
3083
+ "odps.statusOff": "Not on the marketplace ({total} tools declared)",
3084
+ "odps.statusBlocked": "{n} tool(s) are flagged for EXCHANGE but cannot be listed yet — see the reason on the tool.",
3085
+ "odps.noTools": "This app declares no sellable tools yet. Add one under Monetize to sell a capability on the marketplace.",
3086
+ "odps.editDefaults": "ODPS defaults for this app",
3087
+ "odps.close": "Close",
3088
+ "odps.defaultsHint": "Filled once per app and inherited by every tool it sells. A tool can override any of it.",
3089
+ "odps.legalName": "Legal entity (data holder)",
3090
+ "odps.businessId": "Business ID",
3091
+ "odps.holderEmail": "Contact email",
3092
+ "odps.holderUrl": "Website",
3093
+ "odps.country": "Country",
3094
+ "odps.logoUrl": "Logo URL",
3095
+ "odps.brandSlogan": "Brand slogan",
3096
+ "odps.governance": "Governance profile",
3097
+ "odps.priority": "Portfolio priority",
3098
+ "odps.language": "Language",
3099
+ "odps.geoArea": "Licence area",
3100
+ "odps.applicableLaws": "Applicable law",
3101
+ "odps.attestation": "Provenance (your attestation)",
3102
+ "odps.attestationHint": "Where the material comes from and on what basis you may sell it. These are promises with legal weight, so AI never fills them — you state them.",
3103
+ "odps.source": "Source",
3104
+ "odps.legalBasis": "Legal basis",
3105
+ "odps.consentStatus": "Consent status",
3106
+ "odps.retention": "Retention",
3107
+ "odps.saveDefaults": "Save defaults",
3108
+ "odps.defaultsSaved": "ODPS defaults saved",
3109
+ "odps.toolTitle": "ODPS description",
3110
+ "odps.toolHint": "What a buyer sees before contracting. The node fills price, access, licence rights and measured use by itself.",
3111
+ "odps.viewYaml": "View odps.yaml",
3112
+ "odps.productType": "Product type",
3113
+ "odps.valueProposition": "Value proposition",
3114
+ "odps.valuePlaceholder": "One sentence: what does the buyer get?",
3115
+ "odps.categories": "Categories (comma separated)",
3116
+ "odps.standards": "Standards (comma separated)",
3117
+ "odps.useCases": "Use cases (one per line: title | description | url)",
3118
+ "odps.useCasesPlaceholder": "KYB onboarding | Verify a counterparty before opening an account",
3119
+ "odps.contentSample": "Sample output URL",
3120
+ "odps.sla": "Service commitments (one per line: dimension value unit — description)",
3121
+ "odps.slaPlaceholder": "uptime 99.5 percent — Monthly availability",
3122
+ "odps.quality": "Quality commitments (one per line: dimension value unit — description)",
3123
+ "odps.qualityPlaceholder": "accuracy 99 percentage — Names match the register",
3124
+ "odps.toolAttestation": "Provenance for this capability",
3125
+ "odps.toolAttestationHint": "Only what differs from the app defaults. Left empty, the app-level attestation applies.",
3126
+ "odps.inherited": "inherited from the app",
3127
+ "odps.transformations": "What was done to the raw material",
3128
+ "odps.transformationsPlaceholder": "Normalised names, joined municipality codes, dropped discontinued companies.",
3129
+ "odps.suggest": "Draft with AI",
3130
+ "odps.suggesting": "Drafting…",
3131
+ "odps.suggestDone": "Draft ready — review it before saving",
3132
+ "odps.suggestFailed": "Draft failed",
3133
+ "odps.suggestUnparseable": "The model did not return usable JSON",
3134
+ "odps.needLogin": "Sign in to use your own AI key",
3135
+ "odps.aiUnavailable": "AI is not available here",
3136
+ "odps.genSample": "Generate from a real call",
3137
+ "odps.genSampleHint": "Runs this capability once as you (free on your own capability), stores the answer as a public file, and puts its URL above. A buyer can then open the sample instead of taking your word for it.",
3138
+ "odps.genSampleInput": '{ "query": "Nokia" } — the input to call with',
3139
+ "odps.genSampleRunning": "Calling the capability…",
3140
+ "odps.genSampleDone": "Sample stored and linked",
3141
+ "odps.genSampleFailed": "The call did not produce a sample",
3142
+ "odps.genSampleBadJson": "That input is not valid JSON.",
3143
+ "odps.genSampleNoBinding": "This tool has no capability binding to call.",
3144
+ "odps.measured": "Measured: {n} calls, median {p50} ms, p95 {p95} ms.",
3145
+ "odps.measuredNote": "measured p95 with headroom",
3146
+ "odps.useMeasured": "Use measured + 30%",
3147
+ "odps.noMeasurement": "No delivery time measured yet. Serve some calls first, then commit to a number you can keep.",
3148
+ "odps.inheritedIs": "inherited:",
3149
+ "odps.inheritedNone": "no app-level attestation set yet",
3150
+ "odps.openAppDefaults": "Edit app-level defaults",
3151
+ "odps.blockedSchema": "Cannot be listed: the tool needs a non-empty input AND output schema.",
3152
+ "odps.blockedBinding": "Cannot be listed: the tool needs a capability binding (action_id).",
3153
+ "odps.blockedPrice": "Cannot be listed: the tool needs a price.",
3154
+ "monetize.exchange": "List in EXCHANGE",
3155
+ "monetize.toll": "Pacing burn per call (morsels)",
3156
+ "monetize.tollHint": "Not a price: morsels burned per call to cap how fast this tool can be consumed. Nobody is credited them, and your own calls are free. Leave empty to use the node default; set 0 to switch pacing off for this tool.",
3157
+ "monetize.tollInvalid": "Pacing burn must be a whole number between 0 and 100.",
3158
+ "monetize.exchangeHint": "On: this tool is offered publicly on the marketplace and its listing follows the price you set here. Off: it is removed from the marketplace. Contracts already agreed are unaffected.",
3159
+ "monetize.exchangeOn": "in EXCHANGE",
3160
+ "monetize.exchangeNeedsSchema": "To list in EXCHANGE the tool needs an input and output schema plus a capability binding. Publish those first (MCP: aimeat_app_tools_publish).",
3013
3161
  "monetize.actionId": "Capability binding (action id)",
3014
3162
  "monetize.actionIdHint": "Optional. A capability id (e.g. ext:my-extension:summarize) invoked instantly on purchase. Leave empty to fulfill as an agent task instead.",
3015
3163
  "monetize.agent": "Fulfilling agent",
@@ -3051,7 +3199,7 @@
3051
3199
  "detail.accessCodeCleared": "Access code removed — this app is public again.",
3052
3200
  "detail.needServerVersions": "Set the AIMEAT server URL in Settings to see published versions.",
3053
3201
  "detail.editAi": "✨ Edit with AI",
3054
- "detail.editAiHint": "Describe a change. The AI rewrites the app using your own OpenRouter key, saves a draft you can test, then keep or discard.",
3202
+ "detail.editAiHint": "Describe a change. The AI rewrites the app with your own OpenRouter key and proposes it — nothing changes until you save it to your working copy.",
3055
3203
  "detail.editAiPh": "e.g. add a dark mode toggle in the top-right corner",
3056
3204
  "detail.run": "✨ Generate change",
3057
3205
  "detail.running": "Generating…",
@@ -3060,7 +3208,7 @@
3060
3208
  "detail.publishTested": "🚀 Publish tested version",
3061
3209
  "detail.keep": "✔ Keep draft",
3062
3210
  "detail.discard": "✕ Discard",
3063
- "detail.draftReady": "Draft ready — test it, then keep or discard.",
3211
+ "detail.draftReady": "AI proposal ready — nothing is saved yet.",
3064
3212
  "detail.draftUploading": "Preparing your test version…",
3065
3213
  "detail.draftOpened": "Draft opened in a new tab — it runs just like the published app. Your live app is unchanged.",
3066
3214
  "detail.draftNeedsPublished": "Publish this app first, then you can test edits before they go live.",
@@ -3068,6 +3216,57 @@
3068
3216
  "detail.draftPublishConfirm": "Publish the tested draft as the new live version?",
3069
3217
  "detail.draftPublished": "✔ Published as v{v}. It is now the live app.",
3070
3218
  "detail.kept": "Draft kept as your local copy.",
3219
+ // ── Working copy → published (TARGET-048). One vocabulary for the whole edit loop: the
3220
+ // word "draft" is deliberately gone — it used to mean three different things here. ──
3221
+ "wc.lifecycle": "Where your work is",
3222
+ "wc.title": "Working copy",
3223
+ "wc.published": "Published",
3224
+ "wc.privateNote": "only you can see it",
3225
+ "wc.visibleToOthers": "everyone can see it",
3226
+ "wc.notVisibleYet": "nobody can see it yet",
3227
+ "wc.notPublishedYet": "not published",
3228
+ "wc.sameAsPublished": "same as published",
3229
+ "wc.nothingYet": "nothing saved yet",
3230
+ "wc.savedAt": "saved {t}",
3231
+ "wc.savedEarlier": "saved earlier",
3232
+ "wc.pending": "AI proposal waiting",
3233
+ "wc.explainPending": "The AI wrote a change but nothing is saved yet. Save it to your working copy — you can always get back to where you are now.",
3234
+ "wc.explainSaved": "You have a saved working copy that nobody else can see. Publish it as v{v} when it is ready.",
3235
+ "wc.explainSavedUnpublished": "You have a saved working copy. Publish it when it is ready.",
3236
+ "wc.explainClean": "Your working copy matches the published app. Edit it below — every save keeps the previous state, so nothing gets lost.",
3237
+ "wc.explainUnpublished": "This app is not published yet. Publish it to give it a version other people can open.",
3238
+ "wc.save": "💾 Save to working copy",
3239
+ "wc.saving": "Saving…",
3240
+ "wc.saved": "Saved to your working copy — it survives a reload and stays private until you publish.",
3241
+ "wc.savedShort": "Saved!",
3242
+ "wc.saveFailed": "Could not save",
3243
+ "wc.keptLocalOnly": "Kept in this tab only — publish the app first to get a working copy that lasts.",
3244
+ "wc.try": "▶ Try it safely",
3245
+ "wc.tryLocal": "▶ Quick preview",
3246
+ "wc.publishAs": "🚀 Publish as v{v}",
3247
+ "wc.publishNew": "🚀 Publish as a new version",
3248
+ "wc.discardProposal": "✕ Discard proposal",
3249
+ "wc.discardWork": "✕ Discard working copy",
3250
+ "wc.confirmDiscardWork": "Throw away your saved working copy and go back to the published app?",
3251
+ "wc.discardedWork": "Working copy discarded — you are back on the published app.",
3252
+ "wc.verbsHint": "Save keeps it for you only. Try opens it on its real address without changing the live app. Publish is what other people see.",
3253
+ "wc.history": "Working-copy history",
3254
+ "wc.historyHint": "Every save leaves a checkpoint here, so you can always go back. These are private to you — the published versions below are the ones other people see.",
3255
+ "wc.none": "No checkpoints yet — your first save creates one.",
3256
+ "wc.before": "before: {note}",
3257
+ "wc.beforeUnnamed": "before an edit",
3258
+ "wc.preview": "Preview",
3259
+ "wc.restore": "Restore",
3260
+ "wc.delete": "Delete",
3261
+ "wc.previewTitle": "checkpoint preview",
3262
+ "wc.confirmRestore": "Restore this checkpoint into your working copy? The current working copy is checkpointed first, so you can undo this.",
3263
+ "wc.confirmDelete": "Delete this checkpoint permanently?",
3264
+ "wc.restored": "Restored into your working copy. The published app is unchanged.",
3265
+ "wc.gone": "That checkpoint is no longer stored.",
3266
+ "wc.loginNeeded": "Sign in to save a working copy.",
3267
+ "wc.noteManual": "manual source edit",
3268
+ "wc.noteRestore": "restored an earlier checkpoint",
3269
+ "versions.publishedHint": "Published versions — these are what other people see.",
3071
3270
  "detail.discarded": "Draft discarded.",
3072
3271
  "detail.aiUnavailable": "AI editing needs an OpenRouter key. Add one in AIMEAT settings, then reload.",
3073
3272
  "detail.aiLoginNeeded": "Sign in to your AIMEAT node to use AI editing.",
@@ -3294,6 +3493,9 @@
3294
3493
  "pb.promptPreview": "Promptin esikatselu",
3295
3494
  "pb.addBtn": "➕ Lisää sovelluksesi — liitä se tai lataa tiedosto",
3296
3495
  "pb.improving": "Parannetaan",
3496
+ "pb.tier.any": "KAIKILLE MALLEILLE",
3497
+ "pb.tier.frontier": "VERSIOANSA",
3498
+ "pb.tier.needs-doc": "LUE DOKUMENTTI",
3297
3499
  "pb.copied": "Kopioitu!",
3298
3500
  "pb.new": "Uusi sovellus",
3299
3501
  "pb.improve": "Paranna tätä sovellusta",
@@ -3475,6 +3677,90 @@
3475
3677
  "monetize.priceMorsels": "Hinta (morselit)",
3476
3678
  "monetize.priceMoney": "Rahahinta",
3477
3679
  "monetize.currency": "Valuutta",
3680
+ "monetize.money2": "Toinen rahahinta",
3681
+ "monetize.currency2": "Valuutta",
3682
+ "monetize.money2Hint": "Valinnainen: myy sama kutsu toisessa valuutassa. Ostaja valitsee valuutan sopimusta avatessaan.",
3683
+ "odps.title": "EXCHANGE ja ODPS",
3684
+ "odps.hint": "Myykö tämä appi jotain markkinalla, ja mikä tuotekuvaus jokaisen listauksen mukana kulkee. Listattu työkalu julkaistaan Open Data Product Specification v4.1 -dokumenttina, jota ulkoiset katalogit ja ostavat agentit osaavat lukea.",
3685
+ "odps.statusOn": "Markkinalla: {live} / {total} työkalua",
3686
+ "odps.statusOff": "Ei markkinalla ({total} työkalua määritelty)",
3687
+ "odps.statusBlocked": "{n} työkalua on merkitty EXCHANGEen mutta ei vielä listaudu — syy näkyy työkalun kohdalla.",
3688
+ "odps.noTools": "Tällä apilla ei ole vielä myytäviä työkaluja. Lisää työkalu Monetize-osiossa, niin voit myydä kyvykkyyden markkinalla.",
3689
+ "odps.editDefaults": "Appin ODPS-oletukset",
3690
+ "odps.close": "Sulje",
3691
+ "odps.defaultsHint": "Täytetään kerran appille ja periytyy jokaiselle sen myymälle työkalulle. Työkalu voi ylikirjoittaa minkä tahansa niistä.",
3692
+ "odps.legalName": "Juridinen yhtiö (datan haltija)",
3693
+ "odps.businessId": "Y-tunnus",
3694
+ "odps.holderEmail": "Yhteyssähköposti",
3695
+ "odps.holderUrl": "Verkkosivu",
3696
+ "odps.country": "Maa",
3697
+ "odps.logoUrl": "Logon URL",
3698
+ "odps.brandSlogan": "Brändislogan",
3699
+ "odps.governance": "Hallintaprofiili",
3700
+ "odps.priority": "Portfolioprioriteetti",
3701
+ "odps.language": "Kieli",
3702
+ "odps.geoArea": "Lisenssialue",
3703
+ "odps.applicableLaws": "Sovellettava laki",
3704
+ "odps.attestation": "Provenienssi (sinun vakuutuksesi)",
3705
+ "odps.attestationHint": "Mistä aineisto tulee ja millä perusteella saat myydä sitä. Nämä ovat lupauksia joilla on oikeudellinen paino, joten tekoäly ei täytä niitä koskaan — sinä ilmoitat ne.",
3706
+ "odps.source": "Lähde",
3707
+ "odps.legalBasis": "Oikeusperuste",
3708
+ "odps.consentStatus": "Suostumuksen tila",
3709
+ "odps.retention": "Säilytysaika",
3710
+ "odps.saveDefaults": "Tallenna oletukset",
3711
+ "odps.defaultsSaved": "ODPS-oletukset tallennettu",
3712
+ "odps.toolTitle": "ODPS-kuvaus",
3713
+ "odps.toolHint": "Se mitä ostaja näkee ennen sopimusta. Hinnan, pääsyn, lisenssioikeudet ja mitatun käytön node täyttää itse.",
3714
+ "odps.viewYaml": "Näytä odps.yaml",
3715
+ "odps.productType": "Tuotetyyppi",
3716
+ "odps.valueProposition": "Arvolupaus",
3717
+ "odps.valuePlaceholder": "Yksi lause: mitä ostaja saa?",
3718
+ "odps.categories": "Kategoriat (pilkulla eroteltuna)",
3719
+ "odps.standards": "Standardit (pilkulla eroteltuna)",
3720
+ "odps.useCases": "Käyttötapaukset (yksi riviä kohti: otsikko | kuvaus | url)",
3721
+ "odps.useCasesPlaceholder": "KYB-onboarding | Varmenna vastapuoli ennen tilin avaamista",
3722
+ "odps.contentSample": "Näytetulosteen URL",
3723
+ "odps.sla": "Palvelulupaukset (yksi riviä kohti: ulottuvuus arvo yksikkö — kuvaus)",
3724
+ "odps.slaPlaceholder": "uptime 99.5 percent — Kuukausittainen saatavuus",
3725
+ "odps.quality": "Laatulupaukset (yksi riviä kohti: ulottuvuus arvo yksikkö — kuvaus)",
3726
+ "odps.qualityPlaceholder": "accuracy 99 percentage — Nimet täsmäävät rekisteriin",
3727
+ "odps.toolAttestation": "Tämän kyvykkyyden provenienssi",
3728
+ "odps.toolAttestationHint": "Vain se mikä poikkeaa appin oletuksista. Tyhjänä appitason vakuutus pätee.",
3729
+ "odps.inherited": "peritään apilta",
3730
+ "odps.transformations": "Mitä raaka-aineelle tehtiin",
3731
+ "odps.transformationsPlaceholder": "Nimet normalisoitu, kuntakoodit liitetty, lakanneet yritykset poistettu.",
3732
+ "odps.suggest": "Luonnostele tekoälyllä",
3733
+ "odps.suggesting": "Luonnostellaan…",
3734
+ "odps.suggestDone": "Luonnos valmis — tarkista ennen tallennusta",
3735
+ "odps.suggestFailed": "Luonnostelu epäonnistui",
3736
+ "odps.suggestUnparseable": "Malli ei palauttanut käyttökelpoista JSONia",
3737
+ "odps.needLogin": "Kirjaudu sisään käyttääksesi omaa AI-avaintasi",
3738
+ "odps.aiUnavailable": "Tekoäly ei ole käytettävissä täällä",
3739
+ "odps.genSample": "Luo oikeasta kutsusta",
3740
+ "odps.genSampleHint": "Ajaa tämän kyvykkyyden kerran sinuna (oma kyvykkyys on maksuton), tallentaa vastauksen julkiseksi tiedostoksi ja asettaa sen osoitteen yllä olevaan kenttään. Ostaja voi silloin avata näytteen sen sijaan että uskoisi sanaasi.",
3741
+ "odps.genSampleInput": '{ "query": "Nokia" } — syöte jolla kutsutaan',
3742
+ "odps.genSampleRunning": "Kutsutaan kyvykkyyttä…",
3743
+ "odps.genSampleDone": "Näyte tallennettu ja linkitetty",
3744
+ "odps.genSampleFailed": "Kutsu ei tuottanut näytettä",
3745
+ "odps.genSampleBadJson": "Syöte ei ole kelvollista JSONia.",
3746
+ "odps.genSampleNoBinding": "Tällä työkalulla ei ole kyvykkyyssidontaa jota kutsua.",
3747
+ "odps.measured": "Mitattu: {n} kutsua, mediaani {p50} ms, p95 {p95} ms.",
3748
+ "odps.measuredNote": "mitattu p95 pelivaralla",
3749
+ "odps.useMeasured": "Käytä mitattua + 30 %",
3750
+ "odps.noMeasurement": "Toimitusaikaa ei ole vielä mitattu. Palvele ensin kutsuja, sitoudu vasta sitten lukuun jonka pystyt pitämään.",
3751
+ "odps.inheritedIs": "peritään:",
3752
+ "odps.inheritedNone": "appitason vakuutusta ei ole vielä asetettu",
3753
+ "odps.openAppDefaults": "Muokkaa appitason oletuksia",
3754
+ "odps.blockedSchema": "Ei listaudu: työkalu tarvitsee ei-tyhjän syöte- JA tulosskeeman.",
3755
+ "odps.blockedBinding": "Ei listaudu: työkalu tarvitsee kyvykkyyssidonnan (action_id).",
3756
+ "odps.blockedPrice": "Ei listaudu: työkalu tarvitsee hinnan.",
3757
+ "monetize.exchange": "Listaa EXCHANGEssa",
3758
+ "monetize.toll": "Tahdistuspoltto per kutsu (morselia)",
3759
+ "monetize.tollHint": "Ei hinta: morselit jotka palavat jokaisella kutsulla ja rajaavat kuinka nopeasti työkalua voi kuluttaa. Niitä ei hyvitetä kenellekään, ja omat kutsusi ovat ilmaisia. Jätä tyhjäksi niin noden oletus pätee; 0 poistaa tahdistuksen tästä työkalusta.",
3760
+ "monetize.tollInvalid": "Tahdistuspoltto on kokonaisluku väliltä 0-100.",
3761
+ "monetize.exchangeHint": "Päällä: työkalu on julkisesti markkinalla ja listaus seuraa tässä asetettua hintaa. Pois: se poistuu markkinalta. Jo sovitut sopimukset eivät muutu.",
3762
+ "monetize.exchangeOn": "EXCHANGEssa",
3763
+ "monetize.exchangeNeedsSchema": "EXCHANGE-listaus vaatii työkalulta syöte- ja tulosskeeman sekä kyvykkyyssidonnan. Julkaise ne ensin (MCP: aimeat_app_tools_publish).",
3478
3764
  "monetize.actionId": "Kyvykkyyssidonta (action id)",
3479
3765
  "monetize.actionIdHint": "Valinnainen. Kyvykkyyden id (esim. ext:oma-laajennus:summarize), joka ajetaan heti oston yhteydessä. Jätä tyhjäksi, jos haluat toimittaa agenttitehtävänä.",
3480
3766
  "monetize.agent": "Toimittava agentti",
@@ -3527,7 +3813,7 @@
3527
3813
  "detail.publishTested": "🚀 Julkaise testattu versio",
3528
3814
  "detail.keep": "✔ Säilytä luonnos",
3529
3815
  "detail.discard": "✕ Hylkää",
3530
- "detail.draftReady": "Luonnos valmis — testaa, sitten säilytä tai hylkää.",
3816
+ "detail.draftReady": "AI-ehdotus valmis — mitään ei ole vielä tallennettu.",
3531
3817
  "detail.draftUploading": "Valmistellaan testiversiota…",
3532
3818
  "detail.draftOpened": "Luonnos avattu uuteen välilehteen — toimii kuten julkaistu appi. Live-appi pysyy ennallaan.",
3533
3819
  "detail.draftNeedsPublished": "Julkaise appi ensin, niin voit testata muutoksia ennen kuin ne menevät liveksi.",
@@ -3535,6 +3821,57 @@
3535
3821
  "detail.draftPublishConfirm": "Julkaistaanko testattu luonnos uutena live-versiona?",
3536
3822
  "detail.draftPublished": "✔ Julkaistu versiona v{v}. Se on nyt live-appi.",
3537
3823
  "detail.kept": "Luonnos säilytetty paikallisena kopiona.",
3824
+ // ── Työkopio → julkaistu (TARGET-048). Yksi sanasto koko muokkaussilmukalle: sana
3825
+ // "luonnos" on tarkoituksella poissa — se tarkoitti täällä kolmea eri asiaa. ──
3826
+ "wc.lifecycle": "Missä työsi on",
3827
+ "wc.title": "Työkopio",
3828
+ "wc.published": "Julkaistu",
3829
+ "wc.privateNote": "vain sinä näet",
3830
+ "wc.visibleToOthers": "kaikki näkevät",
3831
+ "wc.notVisibleYet": "kukaan ei näe vielä",
3832
+ "wc.notPublishedYet": "ei julkaistu",
3833
+ "wc.sameAsPublished": "sama kuin julkaistu",
3834
+ "wc.nothingYet": "ei vielä tallennettu",
3835
+ "wc.savedAt": "tallennettu {t}",
3836
+ "wc.savedEarlier": "tallennettu aiemmin",
3837
+ "wc.pending": "AI-ehdotus odottaa",
3838
+ "wc.explainPending": "Tekoäly kirjoitti muutoksen, mutta mitään ei ole vielä tallennettu. Tallenna se työkopioon — pääset aina takaisin nykyiseen tilaan.",
3839
+ "wc.explainSaved": "Sinulla on tallennettu työkopio, jota kukaan muu ei näe. Julkaise se versiona v{v}, kun se on valmis.",
3840
+ "wc.explainSavedUnpublished": "Sinulla on tallennettu työkopio. Julkaise se, kun se on valmis.",
3841
+ "wc.explainClean": "Työkopiosi on sama kuin julkaistu appi. Muokkaa alla — jokainen tallennus säilyttää edellisen tilan, joten mikään ei katoa.",
3842
+ "wc.explainUnpublished": "Tätä appia ei ole vielä julkaistu. Julkaise se, niin sillä on versio jonka muut voivat avata.",
3843
+ "wc.save": "💾 Tallenna työkopioon",
3844
+ "wc.saving": "Tallennetaan…",
3845
+ "wc.saved": "Tallennettu työkopioon — säilyy sivun päivityksen yli ja pysyy yksityisenä kunnes julkaiset.",
3846
+ "wc.savedShort": "Tallennettu!",
3847
+ "wc.saveFailed": "Tallennus ei onnistunut",
3848
+ "wc.keptLocalOnly": "Säilytetty vain tässä välilehdessä — julkaise appi ensin, niin saat pysyvän työkopion.",
3849
+ "wc.try": "▶ Kokeile turvallisesti",
3850
+ "wc.tryLocal": "▶ Pikaesikatselu",
3851
+ "wc.publishAs": "🚀 Julkaise versiona v{v}",
3852
+ "wc.publishNew": "🚀 Julkaise uutena versiona",
3853
+ "wc.discardProposal": "✕ Hylkää ehdotus",
3854
+ "wc.discardWork": "✕ Hylkää työkopio",
3855
+ "wc.confirmDiscardWork": "Heitetäänkö tallennettu työkopio pois ja palataan julkaistuun appiin?",
3856
+ "wc.discardedWork": "Työkopio hylätty — olet takaisin julkaistussa apissa.",
3857
+ "wc.verbsHint": "Tallenna säilyttää muutoksen vain sinulle. Kokeile avaa sen oikeassa osoitteessa muuttamatta live-appia. Julkaise on se minkä muut näkevät.",
3858
+ "wc.history": "Työkopion historia",
3859
+ "wc.historyHint": "Jokainen tallennus jättää tänne tarkistuspisteen, joten voit aina palata taaksepäin. Nämä ovat vain sinun — julkaistut versiot alempana ovat ne jotka muut näkevät.",
3860
+ "wc.none": "Ei vielä tarkistuspisteitä — ensimmäinen tallennus luo sellaisen.",
3861
+ "wc.before": "ennen muutosta: {note}",
3862
+ "wc.beforeUnnamed": "ennen muokkausta",
3863
+ "wc.preview": "Esikatsele",
3864
+ "wc.restore": "Palauta",
3865
+ "wc.delete": "Poista",
3866
+ "wc.previewTitle": "tarkistuspisteen esikatselu",
3867
+ "wc.confirmRestore": "Palautetaanko tämä tarkistuspiste työkopioon? Nykyisestä työkopiosta tehdään ensin tarkistuspiste, joten voit kumota tämän.",
3868
+ "wc.confirmDelete": "Poistetaanko tämä tarkistuspiste pysyvästi?",
3869
+ "wc.restored": "Palautettu työkopioon. Julkaistu appi on ennallaan.",
3870
+ "wc.gone": "Tätä tarkistuspistettä ei ole enää tallessa.",
3871
+ "wc.loginNeeded": "Kirjaudu sisään tallentaaksesi työkopion.",
3872
+ "wc.noteManual": "käsin muokattu lähdekoodi",
3873
+ "wc.noteRestore": "palautettu aiempi tarkistuspiste",
3874
+ "versions.publishedHint": "Julkaistut versiot — nämä muut näkevät.",
3538
3875
  "detail.discarded": "Luonnos hylätty.",
3539
3876
  "detail.aiUnavailable": "Tekoälymuokkaus vaatii OpenRouter-avaimen. Lisää se AIMEAT-asetuksiin ja lataa sivu uudelleen.",
3540
3877
  "detail.aiLoginNeeded": "Kirjaudu AIMEAT-nodeen käyttääksesi tekoälymuokkausta.",
@@ -4318,9 +4655,9 @@
4318
4655
  function loadPbCore() {
4319
4656
  var config = loadConfig();
4320
4657
  if (!config.aimeatUrl) return;
4321
- var base3 = config.aimeatUrl.replace(/\/+$/, "");
4658
+ var base4 = config.aimeatUrl.replace(/\/+$/, "");
4322
4659
  ["new", "improve"].forEach(function(mode) {
4323
- fetch(base3 + "/v1/prompts/build-app?mode=" + mode + "&lang=" + encodeURIComponent(getLang())).then(function(r) {
4660
+ fetch(base4 + "/v1/prompts/build-app?mode=" + mode + "&lang=" + encodeURIComponent(getLang())).then(function(r) {
4324
4661
  return r.json();
4325
4662
  }).then(function(d) {
4326
4663
  var body = d && d.data && d.data.body;
@@ -4337,8 +4674,8 @@
4337
4674
  if (!sel) return;
4338
4675
  var config = loadConfig();
4339
4676
  if (!config.aimeatUrl) return;
4340
- var base3 = config.aimeatUrl.replace(/\/+$/, "");
4341
- fetch(base3 + "/v1/app-templates?lang=" + encodeURIComponent(getLang())).then(function(r) {
4677
+ var base4 = config.aimeatUrl.replace(/\/+$/, "");
4678
+ fetch(base4 + "/v1/app-templates?lang=" + encodeURIComponent(getLang())).then(function(r) {
4342
4679
  return r.json();
4343
4680
  }).then(function(d) {
4344
4681
  var list = (d.data && d.data.templates || []).filter(function(t2) {
@@ -4362,7 +4699,7 @@
4362
4699
  updatePbPreview();
4363
4700
  return;
4364
4701
  }
4365
- fetch(base3 + "/v1/app-templates/" + encodeURIComponent(id)).then(function(r) {
4702
+ fetch(base4 + "/v1/app-templates/" + encodeURIComponent(id)).then(function(r) {
4366
4703
  return r.json();
4367
4704
  }).then(function(d2) {
4368
4705
  window.pbTemplate = d2.data && d2.data.template || null;
@@ -4388,8 +4725,8 @@
4388
4725
  if (!wrap) return;
4389
4726
  var config = loadConfig();
4390
4727
  if (!config.aimeatUrl) return;
4391
- var base3 = config.aimeatUrl.replace(/\/+$/, "");
4392
- fetch(base3 + "/v1/library-packs?lang=" + encodeURIComponent(getLang())).then(function(r) {
4728
+ var base4 = config.aimeatUrl.replace(/\/+$/, "");
4729
+ fetch(base4 + "/v1/library-packs?lang=" + encodeURIComponent(getLang())).then(function(r) {
4393
4730
  return r.json();
4394
4731
  }).then(function(d) {
4395
4732
  var all = d.data && d.data.packs || [];
@@ -4410,7 +4747,7 @@
4410
4747
  cb.value = p.id;
4411
4748
  cb.onchange = function() {
4412
4749
  pbPackTouched[p.id] = true;
4413
- if (cb.checked) loadPbPackDoc(base3, p.id);
4750
+ if (cb.checked) loadPbPackDoc(base4, p.id);
4414
4751
  else updatePbPreview();
4415
4752
  };
4416
4753
  var span = document.createElement("span");
@@ -4420,7 +4757,7 @@
4420
4757
  if (p.modelTier) {
4421
4758
  var badge = document.createElement("span");
4422
4759
  badge.className = "pb-pack-tier pb-tier-" + p.modelTier;
4423
- badge.textContent = p.modelTier;
4760
+ badge.textContent = t("pb.tier." + p.modelTier) || p.modelTier;
4424
4761
  var proven = (p.proofs || []).map(function(pr) {
4425
4762
  return pr.model + "→" + pr.verdict;
4426
4763
  }).join(", ");
@@ -4433,14 +4770,14 @@
4433
4770
  if (desc && !desc._pbPackListener) {
4434
4771
  desc._pbPackListener = true;
4435
4772
  desc.addEventListener("input", function() {
4436
- pbMatchPacksToIdea(base3);
4773
+ pbMatchPacksToIdea(base4);
4437
4774
  });
4438
4775
  }
4439
- pbMatchPacksToIdea(base3);
4776
+ pbMatchPacksToIdea(base4);
4440
4777
  }).catch(function() {
4441
4778
  });
4442
4779
  }
4443
- function pbMatchPacksToIdea(base3) {
4780
+ function pbMatchPacksToIdea(base4) {
4444
4781
  var desc = document.getElementById("pb-description");
4445
4782
  var text = (desc && desc.value || "").toLowerCase();
4446
4783
  pbPacks.forEach(function(p) {
@@ -4453,19 +4790,19 @@
4453
4790
  });
4454
4791
  if (hit && !cb.checked) {
4455
4792
  cb.checked = true;
4456
- loadPbPackDoc(base3, p.id);
4793
+ loadPbPackDoc(base4, p.id);
4457
4794
  } else if (!hit && cb.checked) {
4458
4795
  cb.checked = false;
4459
4796
  updatePbPreview();
4460
4797
  }
4461
4798
  });
4462
4799
  }
4463
- function loadPbPackDoc(base3, id) {
4800
+ function loadPbPackDoc(base4, id) {
4464
4801
  if (pbPackDocs[id]) {
4465
4802
  updatePbPreview();
4466
4803
  return;
4467
4804
  }
4468
- fetch(base3 + "/v1/library-packs/" + encodeURIComponent(id)).then(function(r) {
4805
+ fetch(base4 + "/v1/library-packs/" + encodeURIComponent(id)).then(function(r) {
4469
4806
  return r.json();
4470
4807
  }).then(function(d) {
4471
4808
  var pack = d.data && d.data.pack;
@@ -4721,16 +5058,420 @@
4721
5058
  });
4722
5059
  }
4723
5060
 
5061
+ // src/static/app-catalog/js/odps.js
5062
+ var PRODUCT_TYPES = [
5063
+ "",
5064
+ "raw data",
5065
+ "derived data",
5066
+ "dataset",
5067
+ "reports",
5068
+ "analytic view",
5069
+ "algorithm",
5070
+ "decision support",
5071
+ "automated decision-making",
5072
+ "data-enhanced product",
5073
+ "data-driven service",
5074
+ "data-enabled performance",
5075
+ "bi-directional"
5076
+ ];
5077
+ var GOVERNANCE = ["", "structured", "enforced", "automated", "audit_ready"];
5078
+ var PRIORITIES = ["", "critical", "high", "medium", "low"];
5079
+ var SLA_DIMENSIONS = [
5080
+ "latency",
5081
+ "uptime",
5082
+ "responseTime",
5083
+ "errorRate",
5084
+ "endOfSupport",
5085
+ "endOfLife",
5086
+ "updateFrequency",
5087
+ "timeToDetect",
5088
+ "timeToNotify",
5089
+ "timeToRepair",
5090
+ "emailResponseTime"
5091
+ ];
5092
+ var SLA_UNITS = ["percent", "milliseconds", "seconds", "minutes", "days", "weeks", "months", "years", "never", "date"];
5093
+ var QUALITY_DIMENSIONS = ["accuracy", "completeness", "conformity", "consistency", "coverage", "timeliness", "validity", "uniqueness"];
5094
+ var QUALITY_UNITS = ["percentage", "number"];
5095
+ var odDefaultsOpen = false;
5096
+ var odToolOpen = false;
5097
+ var odBusy = false;
5098
+ function apiBase() {
5099
+ var cfg = loadConfig();
5100
+ return (cfg.aimeatUrl || "").replace(/\/+$/, "");
5101
+ }
5102
+ function val(id) {
5103
+ var el = document.getElementById(id);
5104
+ return el ? (el.value || "").trim() : "";
5105
+ }
5106
+ function setVal(id, v) {
5107
+ var el = document.getElementById(id);
5108
+ if (el && v !== void 0 && v !== null) el.value = String(v);
5109
+ }
5110
+ function listOf(id) {
5111
+ return val(id).split(",").map(function(x) {
5112
+ return x.trim();
5113
+ }).filter(Boolean);
5114
+ }
5115
+ function input(id, label, value, placeholder, type) {
5116
+ return '<label class="dtl-stat-label" for="' + id + '">' + escapeHtml(label) + '</label><input id="' + id + '" class="modal-input od-input" ' + (type ? 'type="' + type + '" ' : "") + 'value="' + escapeHtml(value || "") + '" placeholder="' + escapeHtml(placeholder || "") + '" />';
5117
+ }
5118
+ function textarea(id, label, value, placeholder, rows) {
5119
+ return '<label class="dtl-stat-label" for="' + id + '">' + escapeHtml(label) + '</label><textarea id="' + id + '" class="modal-input od-input" rows="' + (rows || 2) + '" placeholder="' + escapeHtml(placeholder || "") + '">' + escapeHtml(value || "") + "</textarea>";
5120
+ }
5121
+ function select(id, label, options, selected) {
5122
+ var html = '<label class="dtl-stat-label" for="' + id + '">' + escapeHtml(label) + '</label><select id="' + id + '" class="modal-input od-input">';
5123
+ for (var i = 0; i < options.length; i++) {
5124
+ var o = options[i];
5125
+ html += '<option value="' + escapeHtml(o) + '"' + (selected === o ? " selected" : "") + ">" + escapeHtml(o || "—") + "</option>";
5126
+ }
5127
+ return html + "</select>";
5128
+ }
5129
+ function dimsToText(dims) {
5130
+ return (dims || []).map(function(d) {
5131
+ return d.dimension + " " + d.objective + " " + d.unit + (d.description ? " — " + d.description : "");
5132
+ }).join("\n");
5133
+ }
5134
+ function textToDims(text, dimensions, units) {
5135
+ var out = [];
5136
+ var lines = (text || "").split("\n");
5137
+ for (var i = 0; i < lines.length; i++) {
5138
+ var line = lines[i].trim();
5139
+ if (!line) continue;
5140
+ var parts = line.split("—");
5141
+ var head = parts[0].trim().split(/\s+/);
5142
+ var desc = parts.length > 1 ? parts.slice(1).join("—").trim() : "";
5143
+ if (head.length < 3) continue;
5144
+ var dim = head[0], objective = parseFloat(String(head[1]).replace(",", ".")), unit = head[2];
5145
+ if (dimensions.indexOf(dim) === -1 || units.indexOf(unit) === -1 || !Number.isFinite(objective)) continue;
5146
+ var entry = { dimension: dim, objective, unit };
5147
+ if (desc) entry.description = desc;
5148
+ out.push(entry);
5149
+ }
5150
+ return out;
5151
+ }
5152
+ function odpsBlockedReason(tool) {
5153
+ if (!tool || !tool.exchange) return "";
5154
+ var hasIn = tool.inputSchema && Object.keys(tool.inputSchema).length;
5155
+ var hasOut = tool.outputSchema && Object.keys(tool.outputSchema).length;
5156
+ if (!hasIn || !hasOut) return t("odps.blockedSchema");
5157
+ if (!tool.action_id) return t("odps.blockedBinding");
5158
+ var priced = tool.price && tool.price.morsels > 0 || tool.priceMoney && tool.priceMoney.amount > 0;
5159
+ if (!priced) return t("odps.blockedPrice");
5160
+ return "";
5161
+ }
5162
+ function statusLine(doc) {
5163
+ var tools = doc && doc.tools || [];
5164
+ var flagged = tools.filter(function(x) {
5165
+ return x.exchange;
5166
+ });
5167
+ var blocked = flagged.filter(function(x) {
5168
+ return !!odpsBlockedReason(x);
5169
+ });
5170
+ var live = flagged.length - blocked.length;
5171
+ if (!tools.length) {
5172
+ return '<div class="od-status od-status-none">' + escapeHtml(t("odps.noTools")) + "</div>";
5173
+ }
5174
+ var cls = live > 0 ? "od-status-on" : "od-status-none";
5175
+ var label = live > 0 ? t("odps.statusOn").replace("{live}", String(live)).replace("{total}", String(tools.length)) : t("odps.statusOff").replace("{total}", String(tools.length));
5176
+ var html = '<div class="od-status ' + cls + '">' + escapeHtml(label) + "</div>";
5177
+ if (blocked.length) {
5178
+ html += '<div class="dtl-sync none od-note">' + escapeHtml(t("odps.statusBlocked").replace("{n}", String(blocked.length))) + "</div>";
5179
+ }
5180
+ return html;
5181
+ }
5182
+ function defaultsForm(doc) {
5183
+ var o = doc && doc.odps || {};
5184
+ var h = o.dataHolder || {};
5185
+ var lic = o.license || {};
5186
+ var p = doc && doc.provenance || {};
5187
+ return '<div class="od-form"><div class="dtl-sync none od-note">' + escapeHtml(t("odps.defaultsHint")) + '</div><div class="od-grid"><div class="od-col">' + input("od-legalname", t("odps.legalName"), h.legalName, "Overscale Solutions Oy") + '</div><div class="od-col">' + input("od-businessid", t("odps.businessId"), h.businessID, "3312345-6") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-holderemail", t("odps.holderEmail"), h.email, "sales@example.org", "email") + '</div><div class="od-col">' + input("od-holderurl", t("odps.holderUrl"), h.URL, "https://example.org", "url") + '</div><div class="od-col od-col-narrow">' + input("od-country", t("odps.country"), h.addressCountry, "FI") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-logo", t("odps.logoUrl"), o.logoURL, "https://example.org/logo.png", "url") + '</div><div class="od-col">' + input("od-slogan", t("odps.brandSlogan"), o.brandSlogan, "") + '</div></div><div class="od-grid"><div class="od-col">' + select("od-governance", t("odps.governance"), GOVERNANCE, o.governanceProfile || "") + '</div><div class="od-col">' + select("od-priority", t("odps.priority"), PRIORITIES, o.portfolioPriority || "") + '</div><div class="od-col od-col-narrow">' + input("od-language", t("odps.language"), o.language || "en", "en") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-geo", t("odps.geoArea"), (lic.geographicalArea || []).join(", "), "EU, EEA") + '</div><div class="od-col">' + input("od-laws", t("odps.applicableLaws"), lic.applicableLaws, "Finnish law") + '</div></div><div class="od-attest"><div class="dtl-stat-label od-attest-head">' + escapeHtml(t("odps.attestation")) + '</div><div class="dtl-sync none od-note">' + escapeHtml(t("odps.attestationHint")) + '</div><div class="od-grid"><div class="od-col">' + input("od-source", t("odps.source"), p.source, "PRH open company register (YTJ v3)") + '</div><div class="od-col">' + input("od-legalbasis", t("odps.legalBasis"), p.legalBasis, "Public register") + '</div></div><div class="od-grid"><div class="od-col">' + input("od-consent", t("odps.consentStatus"), p.consentStatus, "not applicable") + '</div><div class="od-col">' + input("od-retention", t("odps.retention"), p.retention, "30 days") + '</div></div></div><div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.saveDefaults"), "window._launcher.odpsSaveDefaults()", { variant: "primary" }) + dtlBtn(t("odps.close"), "window._launcher.odpsToggleDefaults()") + '</div><div class="dtl-ai-status" id="od-status"></div></div>';
5188
+ }
5189
+ function odpsStatusInner(doc, state) {
5190
+ if (state === "off") return "";
5191
+ var html = "<h3>" + escapeHtml(t("odps.title")) + '</h3><p class="dtl-desc">' + escapeHtml(t("odps.hint")) + "</p>";
5192
+ if (state === "loading") return html + '<span class="od-muted">…</span>';
5193
+ html += statusLine(doc);
5194
+ html += '<div class="dtl-btn-row od-actions">' + dtlBtn(odDefaultsOpen ? t("odps.close") : t("odps.editDefaults"), "window._launcher.odpsToggleDefaults()") + "</div>";
5195
+ if (odDefaultsOpen) html += defaultsForm(doc);
5196
+ return html;
5197
+ }
5198
+ function readOdpsAppDefaults() {
5199
+ var holder = {};
5200
+ if (val("od-legalname")) holder.legalName = val("od-legalname");
5201
+ if (val("od-businessid")) holder.businessID = val("od-businessid");
5202
+ if (val("od-holderemail")) holder.email = val("od-holderemail");
5203
+ if (val("od-holderurl")) holder.URL = val("od-holderurl");
5204
+ if (val("od-country")) holder.addressCountry = val("od-country");
5205
+ var license = {};
5206
+ if (listOf("od-geo").length) license.geographicalArea = listOf("od-geo");
5207
+ if (val("od-laws")) license.applicableLaws = val("od-laws");
5208
+ var odps = {};
5209
+ if (holder.legalName) odps.dataHolder = holder;
5210
+ if (Object.keys(license).length) odps.license = license;
5211
+ if (val("od-logo")) odps.logoURL = val("od-logo");
5212
+ if (val("od-slogan")) odps.brandSlogan = val("od-slogan");
5213
+ if (val("od-governance")) odps.governanceProfile = val("od-governance");
5214
+ if (val("od-priority")) odps.portfolioPriority = val("od-priority");
5215
+ if (/^[a-z]{2}$/.test(val("od-language"))) odps.language = val("od-language");
5216
+ var prov = {};
5217
+ if (val("od-source")) prov.source = val("od-source");
5218
+ if (val("od-legalbasis")) prov.legalBasis = val("od-legalbasis");
5219
+ if (val("od-consent")) prov.consentStatus = val("od-consent");
5220
+ if (val("od-retention")) prov.retention = val("od-retention");
5221
+ return {
5222
+ odps: Object.keys(odps).length ? odps : void 0,
5223
+ provenance: Object.keys(prov).length ? prov : void 0
5224
+ };
5225
+ }
5226
+ function odpsToolFieldsHtml(tool, offeringId, ctx) {
5227
+ ctx = ctx || {};
5228
+ var o = tool && tool.odps || {};
5229
+ var p = tool && tool.provenance || {};
5230
+ var inherited = ctx.appProvenance || {};
5231
+ var timing = ctx.timing || null;
5232
+ var head = '<div class="od-tool-head">' + dtlBtn((odToolOpen ? "▾ " : "▸ ") + t("odps.toolTitle"), "window._launcher.odpsToggleTool()") + (offeringId ? '<a class="od-link" href="' + apiBase() + "/v1/exchange/offerings/" + encodeURIComponent(offeringId) + '/odps.yaml" target="_blank" rel="noopener">' + escapeHtml(t("odps.viewYaml")) + "</a>" : "") + "</div>";
5233
+ if (!odToolOpen) return head;
5234
+ return head + '<div class="od-form"><div class="dtl-sync none od-note">' + escapeHtml(t("odps.toolHint")) + '</div><div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.suggest"), "window._launcher.odpsSuggest()", { variant: "primary", disabled: odBusy }) + '</div><div class="dtl-ai-status" id="od-tool-status"></div>' + select("odt-type", t("odps.productType"), PRODUCT_TYPES, o.productType || "") + textarea("odt-value", t("odps.valueProposition"), o.valueProposition, t("odps.valuePlaceholder"), 2) + '<div class="od-grid"><div class="od-col">' + input("odt-categories", t("odps.categories"), (o.categories || []).join(", "), "company data, finland") + '</div><div class="od-col">' + input("odt-standards", t("odps.standards"), (o.standards || []).join(", "), "ISO 8000") + "</div></div>" + textarea("odt-usecases", t("odps.useCases"), (o.useCases || []).map(function(u) {
5235
+ return u.title + (u.description ? " | " + u.description : "") + (u.url ? " | " + u.url : "");
5236
+ }).join("\n"), t("odps.useCasesPlaceholder"), 2) + input("odt-sample", t("odps.contentSample"), o.contentSample, "https://example.org/sample.json", "url") + // A sample a buyer can open beats a sentence claiming one exists. Run the capability once, store the
5237
+ // answer as a public file, put its URL here. Nothing is invented: it IS the output.
5238
+ (ctx.actionId ? '<div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.genSample"), "window._launcher.odpsGenerateSample()", { disabled: odBusy }) + '</div><div class="dtl-sync none od-note">' + escapeHtml(t("odps.genSampleHint")) + '</div><textarea id="odt-sample-input" class="modal-input od-input" rows="2" placeholder="' + escapeHtml(t("odps.genSampleInput")) + '">' + escapeHtml(ctx.sampleInput || "") + '</textarea><div class="dtl-ai-status" id="od-sample-status"></div>' : "") + textarea("odt-sla", t("odps.sla"), dimsToText(o.sla), t("odps.slaPlaceholder"), 2) + // Measured, then committed — in that order. The node reports what this capability actually does;
5239
+ // the number that becomes a promise is still chosen by a person.
5240
+ (timing && timing.count > 0 ? '<div class="od-measured">' + escapeHtml(t("odps.measured").replace("{n}", String(timing.count)).replace("{p50}", String(timing.p50Ms)).replace("{p95}", String(timing.p95Ms))) + " " + dtlBtn(t("odps.useMeasured"), "window._launcher.odpsUseMeasured(" + Math.ceil(timing.p95Ms * 1.3) + ")") + "</div>" : '<div class="dtl-sync none od-note">' + escapeHtml(t("odps.noMeasurement")) + "</div>") + textarea("odt-quality", t("odps.quality"), dimsToText(o.dataQuality), t("odps.qualityPlaceholder"), 2) + '<div class="od-attest"><div class="dtl-stat-label od-attest-head">' + escapeHtml(t("odps.toolAttestation")) + '</div><div class="dtl-sync none od-note">' + escapeHtml(t("odps.toolAttestationHint")) + "</div>" + input(
5241
+ "odt-source",
5242
+ t("odps.source"),
5243
+ p.source,
5244
+ inherited.source ? t("odps.inheritedIs") + " " + inherited.source : t("odps.inheritedNone")
5245
+ ) + textarea("odt-transformations", t("odps.transformations"), p.transformations, t("odps.transformationsPlaceholder"), 2) + '<div class="dtl-btn-row od-actions">' + dtlBtn(t("odps.openAppDefaults"), "window._launcher.odpsToggleDefaults()") + "</div></div></div>";
5246
+ }
5247
+ function readOdpsToolFields(base4) {
5248
+ if (!odToolOpen) {
5249
+ return { odps: base4 && base4.odps, provenance: base4 && base4.provenance };
5250
+ }
5251
+ var odps = Object.assign({}, base4 && base4.odps || {});
5252
+ var prov = Object.assign({}, base4 && base4.provenance || {});
5253
+ var set = function(obj, key, v) {
5254
+ if (v) obj[key] = v;
5255
+ else delete obj[key];
5256
+ };
5257
+ set(odps, "productType", val("odt-type"));
5258
+ set(odps, "valueProposition", val("odt-value"));
5259
+ var cats = listOf("odt-categories");
5260
+ if (cats.length) odps.categories = cats;
5261
+ else delete odps.categories;
5262
+ var stds = listOf("odt-standards");
5263
+ if (stds.length) odps.standards = stds;
5264
+ else delete odps.standards;
5265
+ var uc = val("odt-usecases").split("\n").map(function(line) {
5266
+ var parts = line.split("|").map(function(x) {
5267
+ return x.trim();
5268
+ });
5269
+ if (!parts[0]) return null;
5270
+ var entry = { title: parts[0] };
5271
+ if (parts[1]) entry.description = parts[1];
5272
+ if (parts[2]) entry.url = parts[2];
5273
+ return entry;
5274
+ }).filter(Boolean);
5275
+ if (uc.length) odps.useCases = uc;
5276
+ else delete odps.useCases;
5277
+ set(odps, "contentSample", val("odt-sample"));
5278
+ var sla = textToDims(val("odt-sla"), SLA_DIMENSIONS, SLA_UNITS);
5279
+ if (sla.length) odps.sla = sla;
5280
+ else delete odps.sla;
5281
+ var dq = textToDims(val("odt-quality"), QUALITY_DIMENSIONS, QUALITY_UNITS);
5282
+ if (dq.length) odps.dataQuality = dq;
5283
+ else delete odps.dataQuality;
5284
+ set(prov, "source", val("odt-source"));
5285
+ set(prov, "transformations", val("odt-transformations"));
5286
+ return {
5287
+ odps: Object.keys(odps).length ? odps : void 0,
5288
+ provenance: Object.keys(prov).length ? prov : void 0
5289
+ };
5290
+ }
5291
+ function odpsUseMeasured(ms) {
5292
+ var el = document.getElementById("odt-sla");
5293
+ if (!el) return;
5294
+ var line = "responseTime " + ms + " milliseconds — " + t("odps.measuredNote");
5295
+ el.value = el.value.trim() ? el.value.trim() + "\n" + line : line;
5296
+ }
5297
+ function odpsGenerateSample(ctx, rerender3) {
5298
+ var statusEl = document.getElementById("od-sample-status");
5299
+ var token = getCortexOwnerToken();
5300
+ if (!token) {
5301
+ if (statusEl) statusEl.textContent = t("odps.needLogin");
5302
+ return;
5303
+ }
5304
+ var base4 = apiBase();
5305
+ var raw = (document.getElementById("odt-sample-input") || {}).value || "{}";
5306
+ var input2;
5307
+ try {
5308
+ input2 = JSON.parse(raw);
5309
+ } catch (e) {
5310
+ if (statusEl) {
5311
+ statusEl.className = "dtl-ai-status od-err";
5312
+ statusEl.textContent = t("odps.genSampleBadJson");
5313
+ }
5314
+ return;
5315
+ }
5316
+ var m = /^ext:([^:]+):(.+)$/.exec(ctx.actionId || "");
5317
+ if (!m) {
5318
+ if (statusEl) statusEl.textContent = t("odps.genSampleNoBinding");
5319
+ return;
5320
+ }
5321
+ odBusy = true;
5322
+ if (statusEl) {
5323
+ statusEl.className = "dtl-ai-status";
5324
+ statusEl.textContent = t("odps.genSampleRunning");
5325
+ }
5326
+ fetch(base4 + "/v1/ext/" + encodeURIComponent(m[1]) + "/" + encodeURIComponent(m[2]), {
5327
+ method: "POST",
5328
+ headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5329
+ body: JSON.stringify(input2)
5330
+ }).then(function(r) {
5331
+ return r.json().then(function(j) {
5332
+ return { ok: r.ok, j };
5333
+ });
5334
+ }).then(function(res) {
5335
+ if (!res.ok || !res.j || res.j.ok === false) {
5336
+ var msg = res.j && res.j.error && res.j.error.message || t("odps.genSampleFailed");
5337
+ throw new Error(msg);
5338
+ }
5339
+ var payload = JSON.stringify(res.j.data !== void 0 ? res.j.data : res.j, null, 2);
5340
+ var key = "odps.sample." + (ctx.appId || "app") + "." + (ctx.toolName || "tool") + ".json";
5341
+ return fetch(base4 + "/v1/storage", {
5342
+ method: "POST",
5343
+ headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5344
+ body: JSON.stringify({
5345
+ key,
5346
+ visibility: "public",
5347
+ mime_type: "application/json",
5348
+ data: btoa(unescape(encodeURIComponent(payload)))
5349
+ })
5350
+ }).then(function(r) {
5351
+ return r.json();
5352
+ }).then(function(up) {
5353
+ if (!up || up.ok === false) throw new Error(up && up.error && up.error.message || t("odps.genSampleFailed"));
5354
+ var url = base4 + "/v1/pub/" + encodeURIComponent(ctx.ownerGhii || "") + "/" + key;
5355
+ var field = document.getElementById("odt-sample");
5356
+ if (field) field.value = url;
5357
+ var st = document.getElementById("od-sample-status");
5358
+ if (st) {
5359
+ st.className = "dtl-ai-status od-ok";
5360
+ st.textContent = "✔ " + t("odps.genSampleDone");
5361
+ }
5362
+ odBusy = false;
5363
+ });
5364
+ }).catch(function(e) {
5365
+ odBusy = false;
5366
+ var st = document.getElementById("od-sample-status");
5367
+ if (st) {
5368
+ st.className = "dtl-ai-status od-err";
5369
+ st.textContent = "✘ " + (e.message || t("odps.genSampleFailed"));
5370
+ }
5371
+ });
5372
+ }
5373
+ function odpsToggleDefaults(rerender3) {
5374
+ odDefaultsOpen = !odDefaultsOpen;
5375
+ if (rerender3) rerender3();
5376
+ }
5377
+ function odpsToggleTool(rerender3) {
5378
+ odToolOpen = !odToolOpen;
5379
+ if (rerender3) rerender3();
5380
+ }
5381
+ function odpsSuggestForTool(ctx, rerender3) {
5382
+ var statusEl = document.getElementById("od-tool-status");
5383
+ var token = getCortexOwnerToken();
5384
+ if (!token) {
5385
+ if (statusEl) statusEl.textContent = t("odps.needLogin");
5386
+ return;
5387
+ }
5388
+ var base4 = apiBase();
5389
+ if (!base4) {
5390
+ if (statusEl) statusEl.textContent = t("odps.aiUnavailable");
5391
+ return;
5392
+ }
5393
+ odBusy = true;
5394
+ if (statusEl) {
5395
+ statusEl.className = "dtl-ai-status";
5396
+ statusEl.textContent = t("odps.suggesting");
5397
+ }
5398
+ var system = `You write the descriptive half of an Open Data Product Specification (ODPS v4.1) entry for a capability sold on a marketplace. Return ONLY a JSON object with these keys: "productType" (one of: raw data, derived data, dataset, reports, analytic view, algorithm, decision support, automated decision-making, data-enhanced product, data-driven service, data-enabled performance, bi-directional), "valueProposition" (one sentence, max 400 characters, what a buyer gets), "categories" (2-4 short strings), "standards" (0-3 recognised standards that genuinely apply, else []), "useCases" (1-3 objects with "title" and "description"). Base every word on the material given. Do NOT state where the data comes from, its legal basis, consent, retention, service levels, uptime, accuracy or the selling company — those are the owner's own attestations and inventing them would be a false claim. No markdown, no commentary, JSON only.`;
5399
+ var prompt2 = "App: " + (ctx.appName || ctx.appId) + "\n" + (ctx.appDescription ? "App description: " + ctx.appDescription + "\n" : "") + "Capability (tool) name: " + (ctx.toolName || "") + "\n" + (ctx.toolDescription ? "Capability description: " + ctx.toolDescription + "\n" : "") + (ctx.inputSchema ? "Input schema: " + JSON.stringify(ctx.inputSchema).slice(0, 1500) + "\n" : "") + (ctx.outputSchema ? "Output schema: " + JSON.stringify(ctx.outputSchema).slice(0, 1500) + "\n" : "") + (ctx.price ? "Price: " + ctx.price + "\n" : "") + "Write the ODPS descriptive fields for this capability.";
5400
+ fetch(base4 + "/v1/ai/complete", {
5401
+ method: "POST",
5402
+ headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
5403
+ body: JSON.stringify({ prompt: prompt2, systemPrompt: system, app_id: "app-catalog" })
5404
+ }).then(function(r) {
5405
+ return r.json();
5406
+ }).then(function(json) {
5407
+ odBusy = false;
5408
+ var st = document.getElementById("od-tool-status");
5409
+ if (!json || !json.ok) {
5410
+ var msg = json && json.error && json.error.message || t("odps.suggestFailed");
5411
+ if (st) {
5412
+ st.className = "dtl-ai-status od-err";
5413
+ st.textContent = "✘ " + msg;
5414
+ }
5415
+ return;
5416
+ }
5417
+ var raw = json.data && json.data.content || "";
5418
+ var m = raw.match(/\{[\s\S]*\}/);
5419
+ var parsed = null;
5420
+ try {
5421
+ parsed = m ? JSON.parse(m[0]) : null;
5422
+ } catch (e) {
5423
+ parsed = null;
5424
+ }
5425
+ if (!parsed) {
5426
+ if (st) {
5427
+ st.className = "dtl-ai-status od-err";
5428
+ st.textContent = "✘ " + t("odps.suggestUnparseable");
5429
+ }
5430
+ return;
5431
+ }
5432
+ if (PRODUCT_TYPES.indexOf(parsed.productType) !== -1) setVal("odt-type", parsed.productType);
5433
+ if (parsed.valueProposition) setVal("odt-value", String(parsed.valueProposition).slice(0, 512));
5434
+ if (Array.isArray(parsed.categories)) setVal("odt-categories", parsed.categories.join(", "));
5435
+ if (Array.isArray(parsed.standards)) setVal("odt-standards", parsed.standards.join(", "));
5436
+ if (Array.isArray(parsed.useCases)) {
5437
+ setVal("odt-usecases", parsed.useCases.map(function(u) {
5438
+ return (u.title || "") + (u.description ? " | " + u.description : "");
5439
+ }).filter(Boolean).join("\n"));
5440
+ }
5441
+ if (st) {
5442
+ st.className = "dtl-ai-status od-ok";
5443
+ st.textContent = "✔ " + t("odps.suggestDone");
5444
+ }
5445
+ showNotice(t("odps.suggestDone"), "success");
5446
+ if (rerender3) {
5447
+ }
5448
+ }).catch(function(e) {
5449
+ odBusy = false;
5450
+ var st = document.getElementById("od-tool-status");
5451
+ if (st) {
5452
+ st.className = "dtl-ai-status od-err";
5453
+ st.textContent = "✘ " + (e.message || t("odps.suggestFailed"));
5454
+ }
5455
+ });
5456
+ }
5457
+
4724
5458
  // src/static/app-catalog/js/monetize.js
4725
5459
  var NAME_RE = /^[a-z0-9][a-z0-9._-]*$/i;
4726
5460
  var MONEY_UNIT = 1e6;
5461
+ var CURRENCIES = ["EUR", "USD"];
4727
5462
  var mzOwner = "";
4728
5463
  var mzAppId = "";
4729
5464
  var mzDoc = null;
4730
5465
  var mzState = "off";
4731
5466
  var mzEditing = -1;
4732
5467
  var mzBusy = false;
4733
- function apiBase() {
5468
+ var mzOfferings = {};
5469
+ var mzTiming = {};
5470
+ var mzNodeId = "";
5471
+ function mzOwnerGhiiOf() {
5472
+ return mzOwner && mzNodeId ? mzOwner + "@" + mzNodeId : "";
5473
+ }
5474
+ function apiBase2() {
4734
5475
  var cfg = loadConfig();
4735
5476
  return (cfg.aimeatUrl || "").replace(/\/+$/, "");
4736
5477
  }
@@ -4740,6 +5481,8 @@
4740
5481
  function rerender() {
4741
5482
  var el = document.getElementById("detail-monetize");
4742
5483
  if (el) el.innerHTML = monetizeSectionInner();
5484
+ var od = document.getElementById("detail-odps");
5485
+ if (od) od.innerHTML = odpsStatusInner(mzDoc, mzState);
4743
5486
  }
4744
5487
  function monetizeOnOpen(owner, appId, isOwn) {
4745
5488
  mzEditing = -1;
@@ -4755,7 +5498,7 @@
4755
5498
  mzAppId = appId;
4756
5499
  mzState = "loading";
4757
5500
  var token = getCortexOwnerToken();
4758
- fetch(apiBase() + "/v1/memory/" + encodeURIComponent(toolsKey()), {
5501
+ fetch(apiBase2() + "/v1/memory/" + encodeURIComponent(toolsKey()), {
4759
5502
  headers: token ? { "Authorization": "Bearer " + token } : {}
4760
5503
  }).then(function(r) {
4761
5504
  return r.json();
@@ -4764,29 +5507,77 @@
4764
5507
  mzDoc = value && Array.isArray(value.tools) ? value : { tools: [] };
4765
5508
  mzState = "ready";
4766
5509
  rerender();
5510
+ loadOfferings();
4767
5511
  }).catch(function() {
4768
5512
  mzDoc = { tools: [] };
4769
5513
  mzState = "ready";
4770
5514
  rerender();
4771
5515
  });
4772
5516
  }
5517
+ function loadOfferings() {
5518
+ var ext = "apptool:" + mzOwner + "/" + mzAppId;
5519
+ fetch(apiBase2() + "/v1/exchange/offerings?stats=1&q=" + encodeURIComponent(mzAppId)).then(function(r) {
5520
+ return r.json();
5521
+ }).then(function(res) {
5522
+ if (res && res.node) mzNodeId = res.node;
5523
+ var list = res && res.ok && res.data && res.data.offerings || [];
5524
+ var next = {};
5525
+ var timing = {};
5526
+ for (var i = 0; i < list.length; i++) {
5527
+ if (list[i].ext === ext && list[i].state === "listed") {
5528
+ next[list[i].action] = list[i].offeringId;
5529
+ if (list[i].stats && list[i].stats.timing) timing[list[i].action] = list[i].stats.timing;
5530
+ }
5531
+ }
5532
+ mzOfferings = next;
5533
+ mzTiming = timing;
5534
+ rerender();
5535
+ }).catch(function() {
5536
+ });
5537
+ }
4773
5538
  function priceLabel(tool) {
4774
5539
  var parts = [];
5540
+ var seen = {};
4775
5541
  if (tool.price && tool.price.morsels > 0) parts.push(tool.price.morsels + " morsels");
4776
- if (tool.priceMoney && tool.priceMoney.amount > 0) {
4777
- parts.push(tool.priceMoney.amount / MONEY_UNIT + " " + tool.priceMoney.currency);
5542
+ var money = [tool.priceMoney].concat(tool.pricesMoney || []);
5543
+ for (var i = 0; i < money.length; i++) {
5544
+ var m = money[i];
5545
+ if (!m || !(m.amount > 0) || seen[m.currency]) continue;
5546
+ seen[m.currency] = true;
5547
+ parts.push(m.amount / MONEY_UNIT + " " + m.currency);
4778
5548
  }
4779
5549
  return parts.length ? parts.join(" · ") : t("monetize.notForSale");
4780
5550
  }
4781
5551
  function toolRow(tool, i) {
4782
5552
  var mode = tool.action_id ? t("monetize.fulfillCall") : t("monetize.fulfillTask");
4783
5553
  var binding = tool.action_id ? escapeHtml(tool.action_id) : tool.agent ? "→ " + escapeHtml(tool.agent) : t("monetize.taskToOwner");
4784
- return '<div class="dtl-status-row" style="align-items:center;gap:10px;flex-wrap:wrap"><div class="dtl-stat" style="flex:1;min-width:160px"><span class="dtl-stat-val">' + escapeHtml(tool.name) + "</span>" + (tool.description ? '<span class="dtl-stat-label">' + escapeHtml(tool.description) + "</span>" : "") + '</div><div class="dtl-stat"><span class="dtl-stat-label">' + t("monetize.priceCol") + '</span><span class="dtl-stat-val">' + escapeHtml(priceLabel(tool)) + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + mode + '</span><span class="dtl-stat-val" style="font-size:.8rem">' + binding + '</span></div><div class="dtl-btn-row" style="margin:0">' + dtlBtn(t("detail.editDetails"), "window._launcher.monetizeEditTool(" + i + ")") + dtlBtn("✕", "window._launcher.monetizeDeleteTool(" + i + ")", { variant: "danger", title: t("monetize.deleteHint") }) + "</div></div>";
5554
+ return '<div class="dtl-status-row" style="align-items:center;gap:10px;flex-wrap:wrap"><div class="dtl-stat" style="flex:1;min-width:160px"><span class="dtl-stat-val">' + escapeHtml(tool.name) + "</span>" + (tool.description ? '<span class="dtl-stat-label">' + escapeHtml(tool.description) + "</span>" : "") + '</div><div class="dtl-stat"><span class="dtl-stat-label">' + t("monetize.priceCol") + '</span><span class="dtl-stat-val">' + escapeHtml(priceLabel(tool)) + (tool.exchange ? ' <span class="dtl-sync ok" style="display:inline">' + t("monetize.exchangeOn") + "</span>" : "") + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + mode + '</span><span class="dtl-stat-val" style="font-size:.8rem">' + binding + '</span></div><div class="dtl-btn-row" style="margin:0">' + dtlBtn(t("detail.editDetails"), "window._launcher.monetizeEditTool(" + i + ")") + dtlBtn("✕", "window._launcher.monetizeDeleteTool(" + i + ")", { variant: "danger", title: t("monetize.deleteHint") }) + "</div>" + // A tool flagged for EXCHANGE that the projection will skip: say so here instead of leaving the
5555
+ // owner with a ticked box and an empty marketplace.
5556
+ (odpsBlockedReason(tool) ? '<div class="od-blocked">⚠ ' + escapeHtml(odpsBlockedReason(tool)) + "</div>" : "") + "</div>";
5557
+ }
5558
+ function currencySelect(id, selected) {
5559
+ var html = '<select id="' + id + '" class="modal-input" style="margin:4px 0 8px">';
5560
+ for (var i = 0; i < CURRENCIES.length; i++) {
5561
+ html += '<option value="' + CURRENCIES[i] + '"' + (selected === CURRENCIES[i] ? " selected" : "") + ">" + CURRENCIES[i] + "</option>";
5562
+ }
5563
+ return html + "</select>";
4785
5564
  }
4786
5565
  function editorHtml(tool) {
4787
5566
  var moneyMajor = tool.priceMoney && tool.priceMoney.amount > 0 ? String(tool.priceMoney.amount / MONEY_UNIT) : "";
4788
5567
  var cur = tool.priceMoney && tool.priceMoney.currency || "EUR";
4789
- return '<div class="dtl-ac-editor" style="margin-top:10px"><label class="dtl-stat-label" for="mz-name">' + t("monetize.name") + '</label><input id="mz-name" class="modal-input" maxlength="80" value="' + escapeHtml(tool.name || "") + '" placeholder="summarize" style="margin:4px 0 8px" /><label class="dtl-stat-label" for="mz-desc">' + t("monetize.desc") + '</label><input id="mz-desc" class="modal-input" maxlength="500" value="' + escapeHtml(tool.description || "") + '" style="margin:4px 0 8px" /><div style="display:flex;gap:10px;flex-wrap:wrap"><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-morsels">' + t("monetize.priceMorsels") + '</label><input id="mz-morsels" class="modal-input" type="number" min="0" step="1" value="' + (tool.price && tool.price.morsels || "") + '" placeholder="0" style="margin:4px 0 8px" /></div><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-money">' + t("monetize.priceMoney") + '</label><input id="mz-money" class="modal-input" inputmode="decimal" value="' + escapeHtml(moneyMajor) + '" placeholder="0.002" style="margin:4px 0 8px" /></div><div style="min-width:90px"><label class="dtl-stat-label" for="mz-currency">' + t("monetize.currency") + '</label><select id="mz-currency" class="modal-input" style="margin:4px 0 8px"><option value="EUR"' + (cur === "EUR" ? " selected" : "") + '>EUR</option><option value="USD"' + (cur === "USD" ? " selected" : "") + '>USD</option></select></div></div><label class="dtl-stat-label" for="mz-action">' + t("monetize.actionId") + '</label><input id="mz-action" class="modal-input" maxlength="200" value="' + escapeHtml(tool.action_id || "") + '" placeholder="ext:my-extension:summarize" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.actionIdHint") + '</div><label class="dtl-stat-label" for="mz-agent">' + t("monetize.agent") + '</label><input id="mz-agent" class="modal-input" maxlength="100" value="' + escapeHtml(tool.agent || "") + '" placeholder="assistant" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 10px">' + t("monetize.agentHint") + '</div><div class="dtl-btn-row">' + dtlBtn(t("detail.saveDetails"), "window._launcher.monetizeSaveTool()", { variant: "primary", disabled: mzBusy }) + dtlBtn(t("detail.cancelEdit"), "window._launcher.monetizeCancelEdit()") + '</div><div class="dtl-ai-status" id="mz-status"></div></div>';
5568
+ var second = (tool.pricesMoney || []).filter(function(p) {
5569
+ return p && p.currency !== cur && p.amount > 0;
5570
+ })[0];
5571
+ var money2Major = second ? String(second.amount / MONEY_UNIT) : "";
5572
+ var cur2 = second && second.currency || (cur === "EUR" ? "USD" : "EUR");
5573
+ return '<div class="dtl-ac-editor" style="margin-top:10px"><label class="dtl-stat-label" for="mz-name">' + t("monetize.name") + '</label><input id="mz-name" class="modal-input" maxlength="80" value="' + escapeHtml(tool.name || "") + '" placeholder="summarize" style="margin:4px 0 8px" /><label class="dtl-stat-label" for="mz-desc">' + t("monetize.desc") + '</label><input id="mz-desc" class="modal-input" maxlength="500" value="' + escapeHtml(tool.description || "") + '" style="margin:4px 0 8px" /><div style="display:flex;gap:10px;flex-wrap:wrap"><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-morsels">' + t("monetize.priceMorsels") + '</label><input id="mz-morsels" class="modal-input" type="number" min="0" step="1" value="' + (tool.price && tool.price.morsels || "") + '" placeholder="0" style="margin:4px 0 8px" /></div><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-money">' + t("monetize.priceMoney") + '</label><input id="mz-money" class="modal-input" inputmode="decimal" value="' + escapeHtml(moneyMajor) + '" placeholder="0.002" style="margin:4px 0 8px" /></div><div style="min-width:90px"><label class="dtl-stat-label" for="mz-currency">' + t("monetize.currency") + "</label>" + currencySelect("mz-currency", cur) + '</div></div><div style="display:flex;gap:10px;flex-wrap:wrap"><div style="flex:1;min-width:130px"><label class="dtl-stat-label" for="mz-money2">' + t("monetize.money2") + '</label><input id="mz-money2" class="modal-input" inputmode="decimal" value="' + escapeHtml(money2Major) + '" placeholder="0.002" style="margin:4px 0 4px" /></div><div style="min-width:90px"><label class="dtl-stat-label" for="mz-currency2">' + t("monetize.currency2") + "</label>" + currencySelect("mz-currency2", cur2) + '</div></div><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.money2Hint") + '</div><label class="dtl-stat-label" style="display:flex;align-items:center;gap:8px;cursor:pointer"><input id="mz-exchange" type="checkbox"' + (tool.exchange ? " checked" : "") + " />" + t("monetize.exchange") + '</label><div class="dtl-sync none" style="margin:4px 0 8px">' + t("monetize.exchangeHint") + '</div><label class="dtl-stat-label" for="mz-toll">' + t("monetize.toll") + '</label><input id="mz-toll" class="modal-input" type="number" min="0" max="100" step="1" value="' + (typeof tool.tollMorsels === "number" ? tool.tollMorsels : "") + '" placeholder="0" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.tollHint") + "</div>" + odpsToolFieldsHtml(tool, mzOfferings[tool.name || ""] || "", {
5574
+ appId: mzAppId,
5575
+ ownerGhii: mzOwnerGhiiOf(),
5576
+ toolName: tool.name || "",
5577
+ actionId: tool.action_id || "",
5578
+ appProvenance: mzDoc && mzDoc.provenance || null,
5579
+ timing: mzTiming[tool.name || ""] || null
5580
+ }) + '<label class="dtl-stat-label" for="mz-action">' + t("monetize.actionId") + '</label><input id="mz-action" class="modal-input" maxlength="200" value="' + escapeHtml(tool.action_id || "") + '" placeholder="ext:my-extension:summarize" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 8px">' + t("monetize.actionIdHint") + '</div><label class="dtl-stat-label" for="mz-agent">' + t("monetize.agent") + '</label><input id="mz-agent" class="modal-input" maxlength="100" value="' + escapeHtml(tool.agent || "") + '" placeholder="assistant" style="margin:4px 0 4px" /><div class="dtl-sync none" style="margin:0 0 10px">' + t("monetize.agentHint") + '</div><div class="dtl-btn-row">' + dtlBtn(t("detail.saveDetails"), "window._launcher.monetizeSaveTool()", { variant: "primary", disabled: mzBusy }) + dtlBtn(t("detail.cancelEdit"), "window._launcher.monetizeCancelEdit()") + '</div><div class="dtl-ai-status" id="mz-status"></div></div>';
4790
5581
  }
4791
5582
  function monetizeSectionInner() {
4792
5583
  if (mzState === "off") return "";
@@ -4809,6 +5600,62 @@
4809
5600
  }
4810
5601
  return html;
4811
5602
  }
5603
+ function odpsSectionInner() {
5604
+ return odpsStatusInner(mzDoc, mzState);
5605
+ }
5606
+ function odpsToggleDefaultsUi() {
5607
+ odpsToggleDefaults(rerender);
5608
+ }
5609
+ function odpsToggleToolUi() {
5610
+ odpsToggleTool(rerender);
5611
+ }
5612
+ function odpsGenerateSampleUi() {
5613
+ var tool = mzEditing >= 0 && mzDoc && mzDoc.tools[mzEditing] ? mzDoc.tools[mzEditing] : {};
5614
+ odpsGenerateSample({
5615
+ appId: mzAppId,
5616
+ ownerGhii: mzOwnerGhiiOf(),
5617
+ toolName: (document.getElementById("mz-name") || {}).value || tool.name || "",
5618
+ actionId: (document.getElementById("mz-action") || {}).value || tool.action_id || ""
5619
+ }, rerender);
5620
+ }
5621
+ function odpsUseMeasuredUi(ms) {
5622
+ odpsUseMeasured(ms);
5623
+ }
5624
+ function odpsSuggestUi() {
5625
+ var tool = mzEditing >= 0 && mzDoc && mzDoc.tools[mzEditing] ? mzDoc.tools[mzEditing] : {};
5626
+ odpsSuggestForTool({
5627
+ appId: mzAppId,
5628
+ appName: mzAppId,
5629
+ toolName: (document.getElementById("mz-name") || {}).value || tool.name || "",
5630
+ toolDescription: (document.getElementById("mz-desc") || {}).value || tool.description || "",
5631
+ inputSchema: tool.inputSchema,
5632
+ outputSchema: tool.outputSchema,
5633
+ price: priceLabel(tool)
5634
+ }, rerender);
5635
+ }
5636
+ function odpsSaveDefaults() {
5637
+ if (mzBusy || !mzDoc) return;
5638
+ var vals = readOdpsAppDefaults();
5639
+ var next = Object.assign({}, mzDoc, { tools: mzDoc.tools.slice() });
5640
+ if (vals.odps) next.odps = vals.odps;
5641
+ else delete next.odps;
5642
+ if (vals.provenance) next.provenance = vals.provenance;
5643
+ else delete next.provenance;
5644
+ mzBusy = true;
5645
+ var el = document.getElementById("od-status");
5646
+ if (el) el.textContent = "…";
5647
+ writeManifest(next).then(function() {
5648
+ mzDoc = next;
5649
+ mzBusy = false;
5650
+ rerender();
5651
+ showNotice(t("odps.defaultsSaved"), "success");
5652
+ }).catch(function(e) {
5653
+ mzBusy = false;
5654
+ var st = document.getElementById("od-status");
5655
+ if (st) st.textContent = t("monetize.saveFailed") + ": " + e.message;
5656
+ showNotice(t("monetize.saveFailed") + ": " + e.message, "error");
5657
+ });
5658
+ }
4812
5659
  function monetizeAddTool() {
4813
5660
  mzEditing = -2;
4814
5661
  rerender();
@@ -4821,17 +5668,20 @@
4821
5668
  mzEditing = -1;
4822
5669
  rerender();
4823
5670
  }
4824
- function readEditor() {
5671
+ function readEditor(base4) {
4825
5672
  var name = (document.getElementById("mz-name").value || "").trim();
4826
5673
  if (!NAME_RE.test(name)) {
4827
5674
  showNotice(t("monetize.nameInvalid"), "error");
4828
5675
  return null;
4829
5676
  }
4830
- var tool = { name };
5677
+ var tool = Object.assign({}, base4 || {});
5678
+ tool.name = name;
4831
5679
  var desc = (document.getElementById("mz-desc").value || "").trim();
4832
5680
  if (desc) tool.description = desc;
5681
+ else delete tool.description;
4833
5682
  var morsels = parseInt(document.getElementById("mz-morsels").value, 10);
4834
5683
  if (Number.isFinite(morsels) && morsels > 0) tool.price = { morsels, unit: "per-call" };
5684
+ else delete tool.price;
4835
5685
  var moneyRaw = (document.getElementById("mz-money").value || "").trim();
4836
5686
  if (moneyRaw) {
4837
5687
  var major = parseFloat(moneyRaw.replace(",", "."));
@@ -4840,11 +5690,47 @@
4840
5690
  return null;
4841
5691
  }
4842
5692
  tool.priceMoney = { amount: Math.round(major * MONEY_UNIT), currency: document.getElementById("mz-currency").value };
5693
+ } else delete tool.priceMoney;
5694
+ var money2Raw = (document.getElementById("mz-money2").value || "").trim();
5695
+ var cur2 = document.getElementById("mz-currency2").value;
5696
+ var money2 = null;
5697
+ if (money2Raw) {
5698
+ var major2 = parseFloat(money2Raw.replace(",", "."));
5699
+ if (!Number.isFinite(major2) || major2 <= 0) {
5700
+ showNotice(t("monetize.moneyInvalid"), "error");
5701
+ return null;
5702
+ }
5703
+ money2 = { amount: Math.round(major2 * MONEY_UNIT), currency: cur2 };
5704
+ }
5705
+ var moneySet = [];
5706
+ if (tool.priceMoney) moneySet.push(tool.priceMoney);
5707
+ if (money2 && (!tool.priceMoney || tool.priceMoney.currency !== money2.currency)) moneySet.push(money2);
5708
+ if (moneySet.length > 1) tool.pricesMoney = moneySet;
5709
+ else delete tool.pricesMoney;
5710
+ var ex = document.getElementById("mz-exchange");
5711
+ if (ex && ex.checked) tool.exchange = true;
5712
+ else delete tool.exchange;
5713
+ var tollRaw = (document.getElementById("mz-toll").value || "").trim();
5714
+ if (tollRaw === "") delete tool.tollMorsels;
5715
+ else {
5716
+ var toll = parseInt(tollRaw, 10);
5717
+ if (!Number.isFinite(toll) || toll < 0 || toll > 100) {
5718
+ showNotice(t("monetize.tollInvalid"), "error");
5719
+ return null;
5720
+ }
5721
+ tool.tollMorsels = toll;
4843
5722
  }
4844
5723
  var actionId = (document.getElementById("mz-action").value || "").trim();
4845
5724
  if (actionId) tool.action_id = actionId;
5725
+ else delete tool.action_id;
4846
5726
  var agent = (document.getElementById("mz-agent").value || "").trim();
4847
5727
  if (agent) tool.agent = agent;
5728
+ else delete tool.agent;
5729
+ var od = readOdpsToolFields(base4);
5730
+ if (od.odps) tool.odps = od.odps;
5731
+ else delete tool.odps;
5732
+ if (od.provenance) tool.provenance = od.provenance;
5733
+ else delete tool.provenance;
4848
5734
  return tool;
4849
5735
  }
4850
5736
  function writeManifest(doc) {
@@ -4852,7 +5738,7 @@
4852
5738
  if (!token) return Promise.reject(new Error(t("monetize.needLogin")));
4853
5739
  doc.version = (doc.version || 0) + 1;
4854
5740
  doc.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
4855
- return fetch(apiBase() + "/v1/memory", {
5741
+ return fetch(apiBase2() + "/v1/memory", {
4856
5742
  method: "POST",
4857
5743
  headers: { "Authorization": "Bearer " + token, "Content-Type": "application/json" },
4858
5744
  body: JSON.stringify({ key: toolsKey(), value: doc, visibility: "public" })
@@ -4865,7 +5751,7 @@
4865
5751
  }
4866
5752
  function monetizeSaveTool() {
4867
5753
  if (mzBusy || !mzDoc) return;
4868
- var tool = readEditor();
5754
+ var tool = readEditor(mzEditing >= 0 ? mzDoc.tools[mzEditing] : null);
4869
5755
  if (!tool) return;
4870
5756
  var tools = mzDoc.tools.slice();
4871
5757
  var dup = tools.findIndex(function(x, i) {
@@ -4880,7 +5766,7 @@
4880
5766
  mzBusy = true;
4881
5767
  var el = document.getElementById("mz-status");
4882
5768
  if (el) el.textContent = "…";
4883
- var next = { version: mzDoc.version, updatedAt: mzDoc.updatedAt, tools };
5769
+ var next = Object.assign({}, mzDoc, { tools });
4884
5770
  writeManifest(next).then(function() {
4885
5771
  mzDoc = next;
4886
5772
  mzEditing = -1;
@@ -4900,7 +5786,7 @@
4900
5786
  var tools = mzDoc.tools.slice();
4901
5787
  tools.splice(i, 1);
4902
5788
  mzBusy = true;
4903
- var next = { version: mzDoc.version, updatedAt: mzDoc.updatedAt, tools };
5789
+ var next = Object.assign({}, mzDoc, { tools });
4904
5790
  writeManifest(next).then(function() {
4905
5791
  mzDoc = next;
4906
5792
  mzEditing = -1;
@@ -4917,7 +5803,7 @@
4917
5803
  var cState = "off";
4918
5804
  var cData = null;
4919
5805
  var cAppId = "";
4920
- function apiBase2() {
5806
+ function apiBase3() {
4921
5807
  var cfg = loadConfig();
4922
5808
  return (cfg.aimeatUrl || "").replace(/\/+$/, "");
4923
5809
  }
@@ -4940,7 +5826,7 @@
4940
5826
  cData = { _needLogin: true };
4941
5827
  return;
4942
5828
  }
4943
- fetch(apiBase2() + "/v1/apps/cost?app_id=" + encodeURIComponent(cAppId), {
5829
+ fetch(apiBase3() + "/v1/apps/cost?app_id=" + encodeURIComponent(cAppId), {
4944
5830
  headers: { "Authorization": "Bearer " + token }
4945
5831
  }).then(function(r) {
4946
5832
  return r.json();
@@ -5041,8 +5927,8 @@
5041
5927
  showNotice(t("agents.none"));
5042
5928
  return;
5043
5929
  }
5044
- var base3 = aimeatBase();
5045
- if (!base3) {
5930
+ var base4 = aimeatBase();
5931
+ if (!base4) {
5046
5932
  showNotice("Set AIMEAT server URL in Settings first");
5047
5933
  return;
5048
5934
  }
@@ -5058,7 +5944,7 @@
5058
5944
  document.getElementById("agents-overlay").hidden = false;
5059
5945
  defs.forEach(function(def2, i2) {
5060
5946
  var headers = token ? { "Authorization": "Bearer " + token } : {};
5061
- fetch(base3 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/agents/" + encodeURIComponent(def2.agent_name) + "/instances", { headers }).then(function(r) {
5947
+ fetch(base4 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/agents/" + encodeURIComponent(def2.agent_name) + "/instances", { headers }).then(function(r) {
5062
5948
  return r.json();
5063
5949
  }).then(function(json) {
5064
5950
  var el = document.getElementById("aga-instances-" + i2);
@@ -5074,7 +5960,7 @@
5074
5960
  });
5075
5961
  });
5076
5962
  if (token) {
5077
- fetch(base3 + "/v1/agents", { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5963
+ fetch(base4 + "/v1/agents", { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5078
5964
  return r.json();
5079
5965
  }).then(function(json) {
5080
5966
  var agents = json.data && json.data.agents || [];
@@ -5101,7 +5987,7 @@
5101
5987
  } catch (e) {
5102
5988
  }
5103
5989
  if (me) {
5104
- fetch(base3 + "/v1/organisms?member=" + encodeURIComponent(me), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5990
+ fetch(base4 + "/v1/organisms?member=" + encodeURIComponent(me), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
5105
5991
  return r.json();
5106
5992
  }).then(function(json) {
5107
5993
  var orgs = json.data && json.data.organisms || [];
@@ -5156,6 +6042,157 @@
5156
6042
  });
5157
6043
  }
5158
6044
 
6045
+ // src/static/app-catalog/js/workcopy.js
6046
+ var MAX_CHECKPOINTS = 8;
6047
+ var PREFIX = "app-catalog.wc";
6048
+ var cache = {};
6049
+ function base2() {
6050
+ return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
6051
+ }
6052
+ function tok() {
6053
+ return getCortexOwnerToken();
6054
+ }
6055
+ function slugFor(owner, filename) {
6056
+ return (String(owner || "") + "-" + String(filename || "")).replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase().slice(0, 80);
6057
+ }
6058
+ function indexKey(slug) {
6059
+ return PREFIX + "." + slug;
6060
+ }
6061
+ function bodyKey(slug, id) {
6062
+ return PREFIX + "." + slug + ".b" + id;
6063
+ }
6064
+ function memGet(key) {
6065
+ var b = base2(), token = tok();
6066
+ if (!b || !token) return Promise.resolve(null);
6067
+ return fetch(b + "/v1/memory/" + encodeURIComponent(key), { headers: { "Authorization": "Bearer " + token } }).then(function(r) {
6068
+ return r.ok ? r.json() : null;
6069
+ }).then(function(j) {
6070
+ return j && j.data && j.data.value || null;
6071
+ }).catch(function() {
6072
+ return null;
6073
+ });
6074
+ }
6075
+ function memPut(key, value) {
6076
+ var b = base2(), token = tok();
6077
+ if (!b || !token) return Promise.reject(new Error("not signed in"));
6078
+ return fetch(b + "/v1/memory", {
6079
+ method: "POST",
6080
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
6081
+ body: JSON.stringify({ key, value, visibility: "private" })
6082
+ }).then(function(r) {
6083
+ return r.json().then(function(j) {
6084
+ if (!r.ok || j && j.ok === false) throw new Error(j && j.error && j.error.message || "HTTP " + r.status);
6085
+ return j;
6086
+ });
6087
+ });
6088
+ }
6089
+ function memDelete(key) {
6090
+ var b = base2(), token = tok();
6091
+ if (!b || !token) return Promise.resolve();
6092
+ return fetch(b + "/v1/memory/" + encodeURIComponent(key), {
6093
+ method: "DELETE",
6094
+ headers: { "Authorization": "Bearer " + token }
6095
+ }).catch(function() {
6096
+ });
6097
+ }
6098
+ function getCheckpoints(owner, filename) {
6099
+ return cache[slugFor(owner, filename)] || [];
6100
+ }
6101
+ function loadCheckpoints(owner, filename) {
6102
+ var slug = slugFor(owner, filename);
6103
+ return memGet(indexKey(slug)).then(function(v) {
6104
+ var items = v && Array.isArray(v.items) ? v.items : [];
6105
+ cache[slug] = items;
6106
+ return items;
6107
+ });
6108
+ }
6109
+ function pushCheckpoint(slug, b64, note) {
6110
+ var id = String(Date.now());
6111
+ return memPut(bodyKey(slug, id), { html: b64 }).then(function() {
6112
+ var entry = {
6113
+ id,
6114
+ at: (/* @__PURE__ */ new Date()).toISOString(),
6115
+ note: String(note || "").slice(0, 160),
6116
+ size: Math.round(b64.length * 0.75)
6117
+ };
6118
+ var all = [entry].concat(cache[slug] || []);
6119
+ var keep = all.slice(0, MAX_CHECKPOINTS);
6120
+ var prune = all.slice(MAX_CHECKPOINTS);
6121
+ cache[slug] = keep;
6122
+ return memPut(indexKey(slug), { version: 1, updatedAt: entry.at, items: keep }).then(function() {
6123
+ return Promise.all(prune.map(function(p) {
6124
+ return memDelete(bodyKey(slug, p.id));
6125
+ }));
6126
+ }).then(function() {
6127
+ return entry;
6128
+ });
6129
+ });
6130
+ }
6131
+ function getDraft(owner, filename) {
6132
+ var b = base2(), token = tok();
6133
+ if (!b || !token) return Promise.resolve(null);
6134
+ return fetch(b + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/draft", {
6135
+ headers: { "Authorization": "Bearer " + token }
6136
+ }).then(function(r) {
6137
+ return r.ok ? r.json() : null;
6138
+ }).then(function(j) {
6139
+ return j && j.data && typeof j.data.content === "string" ? j.data : null;
6140
+ }).catch(function() {
6141
+ return null;
6142
+ });
6143
+ }
6144
+ function putDraft(owner, filename, b64) {
6145
+ var b = base2(), token = tok();
6146
+ if (!b || !token) return Promise.reject(new Error("not signed in"));
6147
+ return fetch(b + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/draft", {
6148
+ method: "PUT",
6149
+ headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token },
6150
+ body: JSON.stringify({ content: b64 })
6151
+ }).then(function(r) {
6152
+ return r.json().then(function(j) {
6153
+ if (!j.ok) throw new Error(j.error && (j.error.message || j.error.code) || "HTTP " + r.status);
6154
+ return j.data;
6155
+ });
6156
+ });
6157
+ }
6158
+ function discardWorkingCopy(owner, filename) {
6159
+ var b = base2(), token = tok();
6160
+ if (!b || !token) return Promise.resolve();
6161
+ return fetch(b + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/draft", {
6162
+ method: "DELETE",
6163
+ headers: { "Authorization": "Bearer " + token }
6164
+ }).catch(function() {
6165
+ });
6166
+ }
6167
+ function saveWorkingCopy(opts) {
6168
+ var owner = opts.owner, filename = opts.filename;
6169
+ var slug = slugFor(owner, filename);
6170
+ var shouldCheckpoint = !!opts.previousB64 && opts.previousB64 !== opts.nextB64;
6171
+ var chain = shouldCheckpoint ? pushCheckpoint(slug, opts.previousB64, opts.note).catch(function() {
6172
+ return null;
6173
+ }) : Promise.resolve(null);
6174
+ return chain.then(function(entry) {
6175
+ return putDraft(owner, filename, opts.nextB64).then(function(draft) {
6176
+ return { checkpointed: !!entry, draft };
6177
+ });
6178
+ });
6179
+ }
6180
+ function readCheckpoint(owner, filename, id) {
6181
+ return memGet(bodyKey(slugFor(owner, filename), id)).then(function(v) {
6182
+ return v && typeof v.html === "string" ? v.html : null;
6183
+ });
6184
+ }
6185
+ function deleteCheckpoint(owner, filename, id) {
6186
+ var slug = slugFor(owner, filename);
6187
+ var items = (cache[slug] || []).filter(function(c) {
6188
+ return c.id !== id;
6189
+ });
6190
+ cache[slug] = items;
6191
+ return memPut(indexKey(slug), { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), items }).then(function() {
6192
+ return memDelete(bodyKey(slug, id));
6193
+ });
6194
+ }
6195
+
5159
6196
  // src/static/app-catalog/js/detail.js
5160
6197
  var refreshAll;
5161
6198
  var loadPublishedApps;
@@ -5190,6 +6227,12 @@
5190
6227
  var detailMySkills = null;
5191
6228
  var detailBoundSkills = [];
5192
6229
  var detailSkillBusy = false;
6230
+ var detailWorkSavedAt = null;
6231
+ var detailHasWorkCopy = false;
6232
+ var detailCheckpointsHtml = null;
6233
+ var detailCheckpointBusy = false;
6234
+ var detailLastChangeNote = "";
6235
+ var detailEpoch = 0;
5193
6236
  function detailGetApp() {
5194
6237
  for (var i = 0; i < getMainApps().length; i++) {
5195
6238
  if (getMainApps()[i].id === detailAppId) return getMainApps()[i];
@@ -5250,8 +6293,14 @@
5250
6293
  detailSkillPickerOpen = false;
5251
6294
  detailMySkills = null;
5252
6295
  detailBoundSkills = [];
6296
+ detailWorkSavedAt = null;
6297
+ detailCheckpointsHtml = null;
6298
+ detailCheckpointBusy = false;
6299
+ detailEpoch++;
5253
6300
  var app = detailGetApp();
5254
6301
  if (!app) return;
6302
+ var wcState = app.publishedFilename && getServerState()[app.publishedFilename] || null;
6303
+ detailHasWorkCopy = !!(wcState && wcState.hasDraft);
5255
6304
  monetizeOnOpen(detailServerOwner(app), app.publishedFilename || "", detailIsOwnPublished(app));
5256
6305
  costOnOpen(detailServerOwner(app), app.publishedFilename || "", detailIsOwnPublished(app));
5257
6306
  document.getElementById("detail-view").hidden = false;
@@ -5261,6 +6310,8 @@
5261
6310
  if (app.published && app.publishedFilename && owner) {
5262
6311
  detailLoadVersions(owner, app.publishedFilename);
5263
6312
  detailLoadSkills(detailServerOwner(app) || owner, app.publishedFilename);
6313
+ detailLoadCheckpoints(detailServerOwner(app) || owner, app.publishedFilename);
6314
+ if (detailHasWorkCopy) detailLoadWorkingCopy(app, detailServerOwner(app) || owner);
5264
6315
  }
5265
6316
  if (detailEditAboutOnOpen) {
5266
6317
  detailEditAboutOnOpen = false;
@@ -5280,14 +6331,14 @@
5280
6331
  var app = detailGetApp();
5281
6332
  if (!app) return;
5282
6333
  if (app.published && (app.publishedUrl || app.publishedFilename)) {
5283
- var base3 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
6334
+ var base4 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
5284
6335
  var url;
5285
6336
  if (app.publishedUrl) {
5286
- url = base3 + app.publishedUrl;
6337
+ url = base4 + app.publishedUrl;
5287
6338
  } else {
5288
6339
  var st = getServerState()[app.publishedFilename] || {};
5289
6340
  var owner = st.owner || currentOwnerName() || "";
5290
- url = base3 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app.publishedFilename);
6341
+ url = base4 + "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app.publishedFilename);
5291
6342
  }
5292
6343
  viewPublished(url + "?mode=inline", app.name);
5293
6344
  } else {
@@ -5369,24 +6420,27 @@
5369
6420
  var canEditAbout = !!app.id && !isUrlApp;
5370
6421
  document.getElementById("detail-title").innerHTML = '<span style="font-size:1.3rem">' + escapeHtml(icon) + "</span> " + escapeHtml(app.name || "App") + (canEditAbout && !detailEditingAbout ? ' <button class="rename-pencil" style="font-size:1rem" title="' + escapeHtml(t("detail.editDetails")) + '" onclick="window._launcher.detailAboutEdit()">✏️</button>' : "");
5371
6422
  var localBytes = app.blob ? Math.round(app.blob.length * 0.75) : 0;
5372
- var publishedV = app.publishedVersionNumber ? "v" + app.publishedVersionNumber : app.published ? "v?" : t("detail.notPublished");
5373
- var syncClass, syncText;
5374
- if (!app.published) {
5375
- syncClass = "none";
5376
- syncText = t("detail.notPublished");
5377
- } else if (detailDraftBlob) {
5378
- syncClass = "diff";
5379
- syncText = t("detail.localNewer");
6423
+ var publishedV = app.publishedVersionNumber ? "v" + app.publishedVersionNumber : "";
6424
+ var wcState = detailDraftBlob ? "pending" : detailHasWorkCopy ? "saved" : "clean";
6425
+ var wcValue, wcExplain;
6426
+ if (wcState === "pending") {
6427
+ wcValue = t("wc.pending");
6428
+ wcExplain = t("wc.explainPending");
6429
+ } else if (wcState === "saved") {
6430
+ wcValue = detailWorkSavedAt ? t("wc.savedAt").replace("{t}", new Date(detailWorkSavedAt).toLocaleTimeString()) : t("wc.savedEarlier");
6431
+ wcExplain = app.published ? t("wc.explainSaved").replace("{v}", String((app.publishedVersionNumber || 0) + 1)) : t("wc.explainSavedUnpublished");
5380
6432
  } else {
5381
- syncClass = "ok";
5382
- syncText = t("detail.inSync");
6433
+ wcValue = app.published ? t("wc.sameAsPublished") : t("wc.nothingYet");
6434
+ wcExplain = app.published ? t("wc.explainClean") : t("wc.explainUnpublished");
5383
6435
  }
5384
6436
  var aimeatBase2 = config.aimeatUrl ? config.aimeatUrl.replace(/\/+$/, "") : "";
5385
6437
  var shotOwner = detailServerOwner(app);
5386
6438
  var shotFile = app.publishedFilename || "";
5387
6439
  var shotUrl = app.published && shotOwner && shotFile && aimeatBase2 ? aimeatBase2 + "/v1/apps/" + encodeURIComponent(shotOwner) + "/" + encodeURIComponent(shotFile) + "/screenshot?t=" + Date.now() : "";
5388
6440
  var shotImg = shotUrl ? '<img src="' + escapeHtml(shotUrl) + `" alt="App screenshot" loading="lazy" onerror="this.style.display='none'" style="max-width:260px;max-height:160px;border-radius:8px;border:1px solid var(--border-subtle);object-fit:cover;object-position:top center;flex:none" />` : "";
5389
- var statusHtml = '<div class="dtl-section"><h3>' + t("detail.status") + '</h3><div style="display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap"><div style="flex:1;min-width:200px"><div class="dtl-status-row"><div class="dtl-stat"><span class="dtl-stat-label">' + t("detail.statusLocal") + '</span><span class="dtl-stat-val">' + (app.blob ? fmtSize(localBytes) : isUrlApp ? "URL" : "") + '</span></div><div class="dtl-stat"><span class="dtl-stat-label">' + t("detail.statusPublished") + '</span><span class="dtl-stat-val">' + escapeHtml(publishedV) + '</span></div></div><span class="dtl-sync ' + syncClass + '">' + escapeHtml(syncText) + "</span></div>" + shotImg + "</div></div>";
6441
+ var statusHtml = '<div class="dtl-section"><h3>' + t("wc.lifecycle") + '</h3><div class="wc-row"><div class="wc-band"><div class="wc-stop ' + (wcState === "clean" ? "is-idle" : "is-active") + '"><span class="wc-stop-label">' + t("wc.title") + '</span><span class="wc-stop-val">' + escapeHtml(wcValue) + '</span><span class="wc-stop-note">' + t("wc.privateNote") + '</span></div><span class="wc-arrow" aria-hidden="true">→</span><div class="wc-stop ' + (app.published ? "is-live" : "is-idle") + '"><span class="wc-stop-label">' + t("wc.published") + '</span><span class="wc-stop-val">' + escapeHtml(app.published ? publishedV || "v?" : t("wc.notPublishedYet")) + '</span><span class="wc-stop-note">' + (app.published ? t("wc.visibleToOthers") : t("wc.notVisibleYet")) + "</span></div></div>" + shotImg + '</div><p class="wc-explain">' + escapeHtml(wcExplain) + "</p>" + // A saved working copy must never be a dead end: the same three verbs are available right
6442
+ // here, so you can try it, publish it, or throw it away without hunting through other menus.
6443
+ (wcState === "saved" && app.published && !isUrlApp ? '<div class="dtl-btn-row">' + dtlBtn(t("wc.try"), "window._launcher.detailWorkTry()") + dtlBtn(t("wc.publishAs").replace("{v}", String((app.publishedVersionNumber || 0) + 1)), "window._launcher.detailWorkPublish()", { variant: "success" }) + dtlBtn(t("wc.discardWork"), "window._launcher.detailWorkDiscard()") + "</div>" : "") + (app.blob && !isUrlApp ? '<p class="wc-size">' + escapeHtml(t("detail.size") + ": " + fmtSize(localBytes)) + "</p>" : "") + "</div>";
5390
6444
  var tags = app.tags && app.tags.length ? app.tags.join(", ") : "—";
5391
6445
  var cortex = app.usesCortex && app.usesCortex.length ? app.usesCortex.join(", ") : "—";
5392
6446
  var created = app.addedAt ? new Date(app.addedAt).toLocaleString() : "—";
@@ -5408,13 +6462,14 @@
5408
6462
  if (isUrlApp) {
5409
6463
  aiHtml += '<span class="dtl-sync none">' + t("detail.urlCantEdit") + "</span>";
5410
6464
  } else {
5411
- aiHtml += '<div class="dtl-ai-row"><textarea id="detail-ai-input" placeholder="' + escapeHtml(t("detail.editAiPh")) + '"' + (detailAiAvailable ? "" : " disabled") + "></textarea>" + dtlBtn(t("detail.run"), "window._launcher.detailAiRun()", { variant: "primary", id: "detail-ai-run", disabled: !detailAiAvailable }) + '</div><div class="dtl-ai-status" id="detail-ai-status">' + (detailAiAvailable ? "" : escapeHtml(detailAiUnavailableMsg())) + '</div><div class="dtl-ai-draft" id="detail-ai-draft"' + (detailDraftBlob ? "" : " hidden") + '><div class="dtl-ai-status" style="margin:0 0 8px">' + t("detail.draftReady") + '</div><div class="dtl-btn-row">' + // PUBLISHED app the real-origin staging path (mic/camera work); the old opaque-
5412
- // sandbox "Test" is dropped here to avoid two near-identical "Test" buttons. An
5413
- // UNPUBLISHED app has no server draft slot yet, so it keeps the quick sandbox preview.
5414
- (app.published ? dtlBtn(t("detail.testLive"), "window._launcher.detailTestDraftLive()", { variant: "primary" }) + dtlBtn(t("detail.publishTested"), "window._launcher.detailPublishTestedDraft()", { variant: "success" }) : dtlBtn(t("detail.test"), "window._launcher.detailAiTest()", { variant: "primary" })) + dtlBtn(t("detail.keep"), "window._launcher.detailAiKeep()") + dtlBtn(t("detail.discard"), "window._launcher.detailAiDiscard()") + "</div></div>";
6465
+ aiHtml += '<div class="dtl-ai-row"><textarea id="detail-ai-input" placeholder="' + escapeHtml(t("detail.editAiPh")) + '"' + (detailAiAvailable ? "" : " disabled") + "></textarea>" + dtlBtn(t("detail.run"), "window._launcher.detailAiRun()", { variant: "primary", id: "detail-ai-run", disabled: !detailAiAvailable }) + '</div><div class="dtl-ai-status" id="detail-ai-status">' + (detailAiAvailable ? "" : escapeHtml(detailAiUnavailableMsg())) + '</div><div class="dtl-ai-draft" id="detail-ai-draft"' + (detailDraftBlob ? "" : " hidden") + '><div class="dtl-ai-status wc-proposal-head">' + t("detail.draftReady") + '</div><div class="dtl-btn-row">' + dtlBtn(t("wc.save"), "window._launcher.detailAiKeep()", { variant: "primary" }) + (app.published ? dtlBtn(t("wc.try"), "window._launcher.detailTestDraftLive()") + dtlBtn(t("wc.publishAs").replace("{v}", String((app.publishedVersionNumber || 0) + 1)), "window._launcher.detailPublishTestedDraft()", { variant: "success" }) : dtlBtn(t("wc.tryLocal"), "window._launcher.detailAiTest()")) + dtlBtn(t("wc.discardProposal"), "window._launcher.detailAiDiscard()") + '</div><p class="wc-verbs-hint">' + t("wc.verbsHint") + "</p></div>";
5415
6466
  }
5416
6467
  aiHtml += "</div>";
5417
- var versionsHtml = '<div class="dtl-section"><h3>' + t("detail.versions") + '</h3><div id="detail-versions-list">' + (detailVersionsHtml !== null ? detailVersionsHtml : !hasServer ? '<span class="dtl-sync none">' + t("detail.needServerVersions") + "</span>" : app.published ? '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.loadingVersions") + "</span>" : '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.noVersions") + "</span>") + "</div></div>";
6468
+ var historyHtml = "";
6469
+ if (!isUrlApp && app.published) {
6470
+ historyHtml = '<div class="dtl-section"><h3>' + t("wc.history") + '</h3><p class="dtl-desc">' + t("wc.historyHint") + '</p><div id="detail-checkpoints">' + (detailCheckpointsHtml !== null ? detailCheckpointsHtml : '<span class="wc-muted">…</span>') + "</div></div>";
6471
+ }
6472
+ var versionsHtml = '<div class="dtl-section"><h3>' + t("detail.versions") + '</h3><p class="dtl-desc">' + t("versions.publishedHint") + '</p><div id="detail-versions-list">' + (detailVersionsHtml !== null ? detailVersionsHtml : !hasServer ? '<span class="dtl-sync none">' + t("detail.needServerVersions") + "</span>" : app.published ? '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.loadingVersions") + "</span>" : '<span style="color:var(--text-muted);font-size:.85rem">' + t("detail.noVersions") + "</span>") + "</div></div>";
5418
6473
  var publishLabel = app.published && app.publishedVersionNumber ? t("detail.publishAs") + " v" + (app.publishedVersionNumber + 1) : t("detail.publish");
5419
6474
  var actionsHtml = '<div class="dtl-section"><h3>' + t("detail.actions") + '</h3><div class="dtl-btn-row">' + (isUrlApp ? "" : dtlBtn(t("ctx.viewSource"), "window._launcher.detailEditSource()")) + (isUrlApp ? "" : dtlBtn(t("ctx.improveAi"), "window._launcher.detailImproveExternal()")) + dtlBtn(t("ctx.sharePrompt"), "window._launcher.detailSharePrompt()") + (isUrlApp ? "" : dtlBtn(escapeHtml(publishLabel), "window._launcher.detailPublish()", { variant: "primary" })) + (app.published && !isUrlApp ? dtlBtn("📷 " + t("detail.setScreenshot"), "window._launcher.detailSetScreenshot()", { title: "Upload a custom thumbnail for this app" }) : "") + (app.published && !isUrlApp ? dtlBtn("🔄 " + t("detail.refreshScreenshot"), "window._launcher.detailRefreshScreenshot()", { title: "Clear the screenshot; the node re-takes it on its next scheduled run" }) : "") + dtlBtn(t("ctx.delete"), "window._launcher.detailDelete()", { variant: "danger" }) + "</div></div>";
5420
6475
  var mgmtHtml = '<div id="detail-server-mgmt">' + serverMgmtInner(app) + "</div>";
@@ -5425,6 +6480,7 @@
5425
6480
  // on each user-scope skill is rendered by detailLoadSkills into the list above.
5426
6481
  (ownPub ? '<div id="detail-skill-attach" style="margin-top:8px">' + detailSkillAttachInner() + "</div>" : "") + "</div>";
5427
6482
  }
6483
+ var odpsHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-odps">' + odpsSectionInner() + "</div>" : "";
5428
6484
  var monetizeHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-monetize">' + monetizeSectionInner() + "</div>" : "";
5429
6485
  var costHtml = app.published && detailIsOwnPublished(app) ? '<div class="dtl-section" id="detail-cost">' + costSectionInner() + "</div>" : "";
5430
6486
  var promoteHtml = app.published && detailIsOwnPublished(app) ? buildPromoteSection(app) : "";
@@ -5442,7 +6498,115 @@
5442
6498
  ) + "</div>";
5443
6499
  }
5444
6500
  }
5445
- document.getElementById("detail-body").innerHTML = statusHtml + aboutHtml + aiHtml + versionsHtml + skillsHtml + agentsHtml + monetizeHtml + costHtml + promoteHtml + mgmtHtml + actionsHtml;
6501
+ document.getElementById("detail-body").innerHTML = statusHtml + aboutHtml + aiHtml + historyHtml + versionsHtml + skillsHtml + agentsHtml + odpsHtml + monetizeHtml + costHtml + promoteHtml + mgmtHtml + actionsHtml;
6502
+ }
6503
+ function detailCheckpointRows(app) {
6504
+ var owner = detailServerOwner(app);
6505
+ var list = getCheckpoints(owner, app.publishedFilename || "");
6506
+ if (!list.length) return '<span class="wc-muted">' + t("wc.none") + "</span>";
6507
+ var out = "";
6508
+ for (var i = 0; i < list.length; i++) {
6509
+ var c = list[i];
6510
+ var when = c.at ? new Date(c.at).toLocaleString() : "";
6511
+ var kb = c.size ? Math.round(c.size / 102.4) / 10 + " KB" : "";
6512
+ var note = c.note ? t("wc.before").replace("{note}", c.note) : t("wc.beforeUnnamed");
6513
+ out += '<div class="dtl-version-row"><div class="version-meta"><span class="wc-ckpt-when">' + escapeHtml(when) + '</span><span class="wc-ckpt-note">' + escapeHtml(note) + (kb ? " · " + kb : "") + '</span></div><div class="dtl-btn-row">' + dtlBtn(t("wc.preview"), "window._launcher.detailCheckpointPreview('" + jsArg(c.id) + "')") + dtlBtn(t("wc.restore"), "window._launcher.detailCheckpointRestore('" + jsArg(c.id) + "')", { variant: "primary", disabled: detailCheckpointBusy }) + dtlBtn(t("wc.delete"), "window._launcher.detailCheckpointDelete('" + jsArg(c.id) + "')", { disabled: detailCheckpointBusy }) + "</div></div>";
6514
+ }
6515
+ return out;
6516
+ }
6517
+ function refreshCheckpoints() {
6518
+ var app = detailGetApp();
6519
+ if (!app) return;
6520
+ detailCheckpointsHtml = detailCheckpointRows(app);
6521
+ var el = document.getElementById("detail-checkpoints");
6522
+ if (el) el.innerHTML = detailCheckpointsHtml;
6523
+ }
6524
+ function detailLoadWorkingCopy(app, owner) {
6525
+ var epoch = detailEpoch;
6526
+ getDraft(owner, app.publishedFilename).then(function(d) {
6527
+ if (!d || !d.content || detailAppId !== app.id || epoch !== detailEpoch) return;
6528
+ app.blob = d.content;
6529
+ if (d.updated_at) detailWorkSavedAt = d.updated_at;
6530
+ saveApp(app).then(function() {
6531
+ if (detailAppId === app.id) renderDetailView();
6532
+ });
6533
+ });
6534
+ }
6535
+ function detailLoadCheckpoints(owner, filename) {
6536
+ loadCheckpoints(owner, filename).then(function() {
6537
+ if (detailAppId) refreshCheckpoints();
6538
+ }).catch(function() {
6539
+ if (detailAppId) refreshCheckpoints();
6540
+ });
6541
+ }
6542
+ function detailCheckpointPreview(id) {
6543
+ var app = detailGetApp();
6544
+ if (!app) return;
6545
+ readCheckpoint(detailServerOwner(app), app.publishedFilename || "", id).then(function(b64) {
6546
+ if (!b64) {
6547
+ showNotice(t("wc.gone"));
6548
+ return;
6549
+ }
6550
+ var view = document.getElementById("iframe-view");
6551
+ var iframe = document.getElementById("app-iframe");
6552
+ document.getElementById("iframe-title").textContent = (app.name || "App") + " — " + t("wc.previewTitle");
6553
+ iframe.removeAttribute("src");
6554
+ iframe.srcdoc = blobToHtml(b64);
6555
+ setIframeUrl("");
6556
+ delete iframe.dataset.appId;
6557
+ view.hidden = false;
6558
+ });
6559
+ }
6560
+ async function detailCheckpointRestore(id) {
6561
+ var app = detailGetApp();
6562
+ if (!app || detailCheckpointBusy) return;
6563
+ if (!getCortexOwnerToken2()) {
6564
+ showNotice(t("wc.loginNeeded"));
6565
+ return;
6566
+ }
6567
+ if (!await showConfirm(t("wc.confirmRestore"))) return;
6568
+ var owner = detailServerOwner(app);
6569
+ var filename = app.publishedFilename || "";
6570
+ detailCheckpointBusy = true;
6571
+ refreshCheckpoints();
6572
+ readCheckpoint(owner, filename, id).then(function(b64) {
6573
+ if (!b64) throw new Error(t("wc.gone"));
6574
+ return saveWorkingCopy({
6575
+ owner,
6576
+ filename,
6577
+ previousB64: app.blob,
6578
+ nextB64: b64,
6579
+ note: t("wc.noteRestore")
6580
+ }).then(function() {
6581
+ return b64;
6582
+ });
6583
+ }).then(function(b64) {
6584
+ app.blob = b64;
6585
+ detailCheckpointBusy = false;
6586
+ detailHasWorkCopy = true;
6587
+ detailWorkSavedAt = (/* @__PURE__ */ new Date()).toISOString();
6588
+ return saveApp(app).then(function() {
6589
+ renderDetailView();
6590
+ refreshCheckpoints();
6591
+ showNotice(t("wc.restored"));
6592
+ });
6593
+ }).catch(function(err) {
6594
+ detailCheckpointBusy = false;
6595
+ refreshCheckpoints();
6596
+ showNotice(err && err.message || t("wc.saveFailed"));
6597
+ });
6598
+ }
6599
+ async function detailCheckpointDelete(id) {
6600
+ var app = detailGetApp();
6601
+ if (!app || detailCheckpointBusy) return;
6602
+ if (!await showConfirm(t("wc.confirmDelete"))) return;
6603
+ detailCheckpointBusy = true;
6604
+ refreshCheckpoints();
6605
+ deleteCheckpoint(detailServerOwner(app), app.publishedFilename || "", id).catch(function() {
6606
+ }).then(function() {
6607
+ detailCheckpointBusy = false;
6608
+ refreshCheckpoints();
6609
+ });
5446
6610
  }
5447
6611
  function promoteRef(app) {
5448
6612
  return detailServerOwner(app) + "/" + (app.publishedFilename || "");
@@ -5541,9 +6705,9 @@
5541
6705
  showNotice(t("common.loginRequired") || "You must be logged in. Sign in first.");
5542
6706
  return;
5543
6707
  }
5544
- var input = document.getElementById("detail-ac-input");
6708
+ var input2 = document.getElementById("detail-ac-input");
5545
6709
  var statusEl = document.getElementById("detail-ac-status");
5546
- var code = input ? input.value.trim() : "";
6710
+ var code = input2 ? input2.value.trim() : "";
5547
6711
  if (code && (code.length < 4 || code.length > 64)) {
5548
6712
  if (statusEl) {
5549
6713
  statusEl.style.color = "var(--accent)";
@@ -5684,33 +6848,33 @@
5684
6848
  }
5685
6849
  function skillAuthHeaders(withJson) {
5686
6850
  var h = {};
5687
- var tok = getCortexOwnerToken2();
5688
- if (tok) h["Authorization"] = "Bearer " + tok;
6851
+ var tok2 = getCortexOwnerToken2();
6852
+ if (tok2) h["Authorization"] = "Bearer " + tok2;
5689
6853
  if (withJson) h["Content-Type"] = "application/json";
5690
6854
  return h;
5691
6855
  }
5692
6856
  function skillListMine() {
5693
- var base3 = skillApiBase();
5694
- if (!base3) return Promise.reject(new Error("No server configured"));
5695
- return fetch(base3 + "/v1/skills?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
6857
+ var base4 = skillApiBase();
6858
+ if (!base4) return Promise.reject(new Error("No server configured"));
6859
+ return fetch(base4 + "/v1/skills?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5696
6860
  return r.json();
5697
6861
  }).then(function(j) {
5698
6862
  return j.data && j.data.skills ? j.data.skills : [];
5699
6863
  });
5700
6864
  }
5701
6865
  function skillGetMine(name) {
5702
- var base3 = skillApiBase();
5703
- return fetch(base3 + "/v1/skills/" + encodeURIComponent(name) + "?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
6866
+ var base4 = skillApiBase();
6867
+ return fetch(base4 + "/v1/skills/" + encodeURIComponent(name) + "?scope=user", { headers: skillAuthHeaders(false) }).then(function(r) {
5704
6868
  return r.json();
5705
6869
  }).then(function(j) {
5706
6870
  return j.data && j.data.skill || null;
5707
6871
  });
5708
6872
  }
5709
6873
  function skillPublish(skillMd, files) {
5710
- var base3 = skillApiBase();
6874
+ var base4 = skillApiBase();
5711
6875
  var body = { skill_md: skillMd };
5712
6876
  if (files && Object.keys(files).length) body.files = files;
5713
- return fetch(base3 + "/v1/skills", { method: "POST", headers: skillAuthHeaders(true), body: JSON.stringify(body) }).then(function(r) {
6877
+ return fetch(base4 + "/v1/skills", { method: "POST", headers: skillAuthHeaders(true), body: JSON.stringify(body) }).then(function(r) {
5714
6878
  return r.json().then(function(j) {
5715
6879
  if (!j.ok) throw new Error(j.error && (j.error.message || j.error.code) || "HTTP " + r.status);
5716
6880
  return j.data && j.data.skill || null;
@@ -5930,11 +7094,11 @@
5930
7094
  showNotice("No server configured.");
5931
7095
  return;
5932
7096
  }
5933
- var input = document.createElement("input");
5934
- input.type = "file";
5935
- input.accept = "image/*";
5936
- input.onchange = function() {
5937
- var file = input.files && input.files[0];
7097
+ var input2 = document.createElement("input");
7098
+ input2.type = "file";
7099
+ input2.accept = "image/*";
7100
+ input2.onchange = function() {
7101
+ var file = input2.files && input2.files[0];
5938
7102
  if (!file) return;
5939
7103
  if (file.size > 2 * 1024 * 1024) {
5940
7104
  showNotice("Image too large (max 2 MB).");
@@ -5964,7 +7128,7 @@
5964
7128
  };
5965
7129
  reader.readAsDataURL(file);
5966
7130
  };
5967
- input.click();
7131
+ input2.click();
5968
7132
  }
5969
7133
  async function detailRefreshScreenshot() {
5970
7134
  var app = detailGetApp();
@@ -6009,8 +7173,8 @@
6009
7173
  showNotice("Failed: " + e.message);
6010
7174
  });
6011
7175
  }
6012
- function metaItem(label, val) {
6013
- return '<div class="dtl-meta-item"><div class="dtl-meta-label">' + escapeHtml(label) + '</div><div class="dtl-meta-val">' + escapeHtml(val || "—") + "</div></div>";
7176
+ function metaItem(label, val2) {
7177
+ return '<div class="dtl-meta-item"><div class="dtl-meta-label">' + escapeHtml(label) + '</div><div class="dtl-meta-val">' + escapeHtml(val2 || "—") + "</div></div>";
6014
7178
  }
6015
7179
  function detailAiUnavailableMsg() {
6016
7180
  return getCortexOwnerToken2() ? t("detail.aiUnavailable") : t("detail.aiLoginNeeded");
@@ -6143,6 +7307,7 @@
6143
7307
  return;
6144
7308
  }
6145
7309
  detailDraftBlob = htmlToBlob(newHtml);
7310
+ detailLastChangeNote = change;
6146
7311
  statusEl.style.color = "#34d399";
6147
7312
  var usage = json.data && json.data.budget && typeof json.data.budget.spent_today_usd !== "undefined" ? " · " + t("detail.aiUsage") + ": $" + Number(json.data.budget.spent_today_usd).toFixed(3) : "";
6148
7313
  statusEl.textContent = "✔ " + t("detail.draftReady") + usage;
@@ -6174,17 +7339,55 @@
6174
7339
  function detailAiKeep() {
6175
7340
  var app = detailGetApp();
6176
7341
  if (!app || !detailDraftBlob) return;
6177
- app.blob = detailDraftBlob;
6178
- app.source = app.source === "url" ? "paste" : app.source || "paste";
6179
- app.url = app.url || null;
6180
- detailDraftBlob = null;
6181
- saveApp(app).then(function() {
6182
- renderApps();
6183
- renderDetailView();
7342
+ var next = detailDraftBlob;
7343
+ var prev = app.blob;
7344
+ var statusEl = document.getElementById("detail-ai-status");
7345
+ function finishLocal() {
7346
+ app.blob = next;
7347
+ app.source = app.source === "url" ? "paste" : app.source || "paste";
7348
+ app.url = app.url || null;
7349
+ detailDraftBlob = null;
7350
+ return saveApp(app).then(function() {
7351
+ renderApps();
7352
+ renderDetailView();
7353
+ });
7354
+ }
7355
+ if (!app.published || !app.publishedFilename || !getCortexOwnerToken2()) {
7356
+ finishLocal().then(function() {
7357
+ var s2 = document.getElementById("detail-ai-status");
7358
+ if (s2) {
7359
+ s2.style.color = "var(--text-muted)";
7360
+ s2.textContent = t("wc.keptLocalOnly");
7361
+ }
7362
+ });
7363
+ return;
7364
+ }
7365
+ if (statusEl) {
7366
+ statusEl.style.color = "var(--text-muted)";
7367
+ statusEl.textContent = t("wc.saving");
7368
+ }
7369
+ saveWorkingCopy({
7370
+ owner: detailServerOwner(app),
7371
+ filename: app.publishedFilename,
7372
+ previousB64: prev,
7373
+ nextB64: next,
7374
+ note: detailLastChangeNote
7375
+ }).then(function() {
7376
+ detailHasWorkCopy = true;
7377
+ detailWorkSavedAt = (/* @__PURE__ */ new Date()).toISOString();
7378
+ return finishLocal();
7379
+ }).then(function() {
7380
+ refreshCheckpoints();
6184
7381
  var s2 = document.getElementById("detail-ai-status");
6185
7382
  if (s2) {
6186
7383
  s2.style.color = "#34d399";
6187
- s2.textContent = "✔ " + t("detail.kept");
7384
+ s2.textContent = "✔ " + t("wc.saved");
7385
+ }
7386
+ }).catch(function(err) {
7387
+ var s2 = document.getElementById("detail-ai-status");
7388
+ if (s2) {
7389
+ s2.style.color = "var(--accent)";
7390
+ s2.textContent = "✘ " + (err && err.message || t("wc.saveFailed"));
6188
7391
  }
6189
7392
  });
6190
7393
  }
@@ -6197,9 +7400,9 @@
6197
7400
  function draftApi(method, owner, filename, sub, body) {
6198
7401
  var cfg = loadConfig();
6199
7402
  if (!cfg.aimeatUrl) return Promise.reject(new Error("Set the AIMEAT server URL in Settings first"));
6200
- var base3 = cfg.aimeatUrl.replace(/\/+$/, "");
7403
+ var base4 = cfg.aimeatUrl.replace(/\/+$/, "");
6201
7404
  var path = "/v1/apps/" + encodeURIComponent(owner) + "/" + encodeURIComponent(filename) + "/" + sub;
6202
- return fetch(base3 + path, {
7405
+ return fetch(base4 + path, {
6203
7406
  method,
6204
7407
  headers: { "Authorization": "Bearer " + getCortexOwnerToken2(), "Content-Type": "application/json" },
6205
7408
  body: body ? JSON.stringify(body) : void 0
@@ -6291,6 +7494,55 @@
6291
7494
  });
6292
7495
  });
6293
7496
  }
7497
+ function detailWorkTry() {
7498
+ var app = detailGetApp();
7499
+ if (!app || !app.publishedFilename) return;
7500
+ openStagingPreview(detailServerOwner(app), app.publishedFilename);
7501
+ }
7502
+ async function detailWorkPublish() {
7503
+ var app = detailGetApp();
7504
+ if (!app || !app.publishedFilename) return;
7505
+ if (!getCortexOwnerToken2()) {
7506
+ showNotice(t("wc.loginNeeded"));
7507
+ return;
7508
+ }
7509
+ if (!await showConfirm(t("detail.draftPublishConfirm"))) return;
7510
+ var owner = detailServerOwner(app);
7511
+ draftApi("POST", owner, app.publishedFilename, "publish-draft").then(function(data) {
7512
+ detailEpoch++;
7513
+ detailHasWorkCopy = false;
7514
+ detailWorkSavedAt = null;
7515
+ app.publishedVersionNumber = data.version_number || app.publishedVersionNumber;
7516
+ showNotice(t("detail.draftPublished").replace("{v}", data.version_number));
7517
+ renderDetailView();
7518
+ detailLoadVersions(owner, app.publishedFilename);
7519
+ refreshAll();
7520
+ }).catch(function(err) {
7521
+ showNotice("✘ " + (err && err.message || "Publish failed"));
7522
+ });
7523
+ }
7524
+ async function detailWorkDiscard() {
7525
+ var app = detailGetApp();
7526
+ if (!app || !app.publishedFilename) return;
7527
+ if (!await showConfirm(t("wc.confirmDiscardWork"))) return;
7528
+ var owner = detailServerOwner(app);
7529
+ var cfg = loadConfig();
7530
+ var aimeatUrl = (cfg.aimeatUrl || "").replace(/\/+$/, "");
7531
+ discardWorkingCopy(owner, app.publishedFilename).then(function() {
7532
+ return fetchAppContentBase64(aimeatUrl, owner, app.publishedFilename);
7533
+ }).then(function(b64) {
7534
+ app.blob = b64;
7535
+ return saveApp(app);
7536
+ }).catch(function() {
7537
+ }).then(function() {
7538
+ detailEpoch++;
7539
+ detailHasWorkCopy = false;
7540
+ detailWorkSavedAt = null;
7541
+ renderDetailView();
7542
+ refreshAll();
7543
+ showNotice(t("wc.discardedWork"));
7544
+ });
7545
+ }
6294
7546
  function detailTestDraftLive() {
6295
7547
  var app = detailGetApp();
6296
7548
  if (!app || !detailDraftBlob) return;
@@ -6299,10 +7551,16 @@
6299
7551
  function detailPublishTestedDraft() {
6300
7552
  var app = detailGetApp();
6301
7553
  if (!app || !detailDraftBlob) return;
6302
- publishDraftBytes(app, detailDraftBlob, document.getElementById("detail-ai-status"), function() {
7554
+ publishDraftBytes(app, detailDraftBlob, document.getElementById("detail-ai-status"), function(data) {
7555
+ detailEpoch++;
7556
+ app.blob = detailDraftBlob;
6303
7557
  detailDraftBlob = null;
6304
- refreshAll();
7558
+ detailHasWorkCopy = false;
7559
+ detailWorkSavedAt = null;
7560
+ if (data && data.version_number) app.publishedVersionNumber = data.version_number;
7561
+ saveApp(app);
6305
7562
  renderDetailView();
7563
+ refreshAll();
6306
7564
  });
6307
7565
  }
6308
7566
  function sourceOverlayApp() {
@@ -6322,6 +7580,60 @@
6322
7580
  return null;
6323
7581
  }
6324
7582
  }
7583
+ function saveSourceAsWorkingCopy() {
7584
+ var app = sourceOverlayApp();
7585
+ var b64 = sourceCurrentB64();
7586
+ if (!app || b64 == null) return Promise.reject(new Error("No app"));
7587
+ var statusEl = document.getElementById("source-draft-status");
7588
+ var prev = app.blob;
7589
+ var isOpenInDetail = detailAppId === app.id;
7590
+ function localOnly() {
7591
+ app.blob = b64;
7592
+ return saveApp(app).then(function() {
7593
+ renderApps();
7594
+ });
7595
+ }
7596
+ if (!app.published || !app.publishedFilename || !getCortexOwnerToken2()) {
7597
+ return localOnly().then(function() {
7598
+ if (statusEl) {
7599
+ statusEl.style.color = "var(--text-muted)";
7600
+ statusEl.textContent = t("wc.keptLocalOnly");
7601
+ }
7602
+ return { persisted: false };
7603
+ });
7604
+ }
7605
+ if (statusEl) {
7606
+ statusEl.style.color = "var(--text-muted)";
7607
+ statusEl.textContent = t("wc.saving");
7608
+ }
7609
+ return saveWorkingCopy({
7610
+ owner: detailServerOwner(app),
7611
+ filename: app.publishedFilename,
7612
+ previousB64: prev,
7613
+ nextB64: b64,
7614
+ note: t("wc.noteManual")
7615
+ }).then(function() {
7616
+ return localOnly();
7617
+ }).then(function() {
7618
+ if (isOpenInDetail) {
7619
+ detailHasWorkCopy = true;
7620
+ detailWorkSavedAt = (/* @__PURE__ */ new Date()).toISOString();
7621
+ renderDetailView();
7622
+ refreshCheckpoints();
7623
+ }
7624
+ if (statusEl) {
7625
+ statusEl.style.color = "#34d399";
7626
+ statusEl.textContent = "✔ " + t("wc.saved");
7627
+ }
7628
+ return { persisted: true };
7629
+ }).catch(function(err) {
7630
+ if (statusEl) {
7631
+ statusEl.style.color = "var(--accent)";
7632
+ statusEl.textContent = "✘ " + (err && err.message || t("wc.saveFailed"));
7633
+ }
7634
+ throw err;
7635
+ });
7636
+ }
6325
7637
  function sourceTestDraftLive() {
6326
7638
  var app = sourceOverlayApp();
6327
7639
  var c = sourceCurrentB64();
@@ -6659,8 +7971,8 @@
6659
7971
  showNotice(t("common.loginRequired") || "You must be logged in to fork an app into your own catalogue. Sign in first.");
6660
7972
  return;
6661
7973
  }
6662
- var base3 = (filename || "app").replace(/\.html?$/i, "");
6663
- var suggested = base3 + "-fork.html";
7974
+ var base4 = (filename || "app").replace(/\.html?$/i, "");
7975
+ var suggested = base4 + "-fork.html";
6664
7976
  var newName = prompt(t("fork.prompt") || 'Fork "' + filename + '" into a new app.\n\nNew filename:', suggested);
6665
7977
  if (!newName) return;
6666
7978
  newName = newName.trim();
@@ -7066,7 +8378,7 @@
7066
8378
  // src/static/app-catalog/js/favorites.js
7067
8379
  var KEY2 = "app-catalog.favorites";
7068
8380
  var favSet = /* @__PURE__ */ new Set();
7069
- function base2() {
8381
+ function base3() {
7070
8382
  return (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
7071
8383
  }
7072
8384
  function isFavorite(ref) {
@@ -7074,7 +8386,7 @@
7074
8386
  }
7075
8387
  function loadFavorites() {
7076
8388
  var token = getCortexOwnerToken();
7077
- var b = base2();
8389
+ var b = base3();
7078
8390
  if (!token || !b) {
7079
8391
  favSet = /* @__PURE__ */ new Set();
7080
8392
  return Promise.resolve();
@@ -7091,7 +8403,7 @@
7091
8403
  }
7092
8404
  function persist2() {
7093
8405
  var token = getCortexOwnerToken();
7094
- var b = base2();
8406
+ var b = base3();
7095
8407
  if (!token || !b) return Promise.resolve();
7096
8408
  return fetch(b + "/v1/memory", {
7097
8409
  method: "POST",
@@ -8168,7 +9480,7 @@
8168
9480
  }
8169
9481
  }
8170
9482
  function buildLibraryEntries(localApps, serverApps) {
8171
- var base3 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
9483
+ var base4 = (loadConfig().aimeatUrl || "").replace(/\/+$/, "");
8172
9484
  var byFilename = {};
8173
9485
  var entries = [];
8174
9486
  serverStateByFilename = {};
@@ -8198,7 +9510,7 @@
8198
9510
  filename: la.publishedFilename || null,
8199
9511
  owner: null,
8200
9512
  versionNumber: la.publishedVersionNumber || null,
8201
- viewUrl: la.publishedUrl ? base3 + la.publishedUrl : "",
9513
+ viewUrl: la.publishedUrl ? base4 + la.publishedUrl : "",
8202
9514
  forkable: false,
8203
9515
  forks: 0
8204
9516
  };
@@ -8221,7 +9533,7 @@
8221
9533
  m.hasDraft = !!sa.has_draft;
8222
9534
  m.hasAgents = shipsAgent;
8223
9535
  m.protection = prot;
8224
- m.viewUrl = base3 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn);
9536
+ m.viewUrl = base4 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn);
8225
9537
  if (!m.description && sa.manifest && sa.manifest.description) m.description = sa.manifest.description;
8226
9538
  } else {
8227
9539
  var se = {
@@ -8246,7 +9558,7 @@
8246
9558
  filename: fn,
8247
9559
  owner: sa.owner || "",
8248
9560
  versionNumber: sa.version_number || null,
8249
- viewUrl: base3 && fn ? base3 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn) : "",
9561
+ viewUrl: base4 && fn ? base4 + "/v1/apps/" + encodeURIComponent(sa.owner || "") + "/" + encodeURIComponent(fn) : "",
8250
9562
  forkable: !!sa.forkable,
8251
9563
  forks: sa.forks || 0,
8252
9564
  protection: prot,
@@ -8263,6 +9575,9 @@
8263
9575
  owner: sa.owner || "",
8264
9576
  versionNumber: sa.version_number || null,
8265
9577
  protection: prot,
9578
+ // A saved (unpublished) working copy from ANY earlier session — the detail view's
9579
+ // lifecycle band reads this to say "working copy saved" instead of "same as published".
9580
+ hasDraft: !!sa.has_draft,
8266
9581
  // `protected` is the ACCESS-CODE flag (distinct from copy-`protection`); the detail view's
8267
9582
  // access-code editor reads it to show whether the app currently requires a code.
8268
9583
  accessCode: !!sa.protected
@@ -8439,19 +9754,19 @@
8439
9754
  }
8440
9755
  function viewSource2(app) {
8441
9756
  var overlay = document.getElementById("source-overlay");
8442
- var textarea = document.getElementById("source-code");
9757
+ var textarea2 = document.getElementById("source-code");
8443
9758
  var title = document.getElementById("source-title");
8444
9759
  var saveBtn = document.getElementById("save-source-btn");
8445
9760
  title.textContent = "View / Edit Source: " + (app.name || "App");
8446
9761
  var isEditable = !!app.blob;
8447
9762
  if (app.blob) {
8448
- textarea.value = decodeURIComponent(escape(atob(app.blob)));
9763
+ textarea2.value = decodeURIComponent(escape(atob(app.blob)));
8449
9764
  } else if (app.url) {
8450
- textarea.value = "// This app is URL-based (" + app.url + ")\n// Source code is not stored locally.\n// Open the URL to view the app.";
9765
+ textarea2.value = "// This app is URL-based (" + app.url + ")\n// Source code is not stored locally.\n// Open the URL to view the app.";
8451
9766
  } else {
8452
- textarea.value = "// No source available";
9767
+ textarea2.value = "// No source available";
8453
9768
  }
8454
- textarea.readOnly = !isEditable;
9769
+ textarea2.readOnly = !isEditable;
8455
9770
  saveBtn.disabled = true;
8456
9771
  saveBtn.style.display = isEditable ? "" : "none";
8457
9772
  var canStage = isEditable && !!app.published;
@@ -8464,7 +9779,7 @@
8464
9779
  overlay.hidden = false;
8465
9780
  overlay.dataset.appName = app.name || "App";
8466
9781
  overlay.dataset.appId = app.id || "";
8467
- overlay.dataset.originalSource = textarea.value;
9782
+ overlay.dataset.originalSource = textarea2.value;
8468
9783
  }
8469
9784
  function generateSharePrompt2(app) {
8470
9785
  if (!app || !app.blob) {
@@ -8484,12 +9799,12 @@
8484
9799
  showNotice("Share prompt copied! Paste it into any AI chat to recreate this app.");
8485
9800
  }).catch(function() {
8486
9801
  var overlay = document.getElementById("source-overlay");
8487
- var textarea = document.getElementById("source-code");
9802
+ var textarea2 = document.getElementById("source-code");
8488
9803
  var title = document.getElementById("source-title");
8489
9804
  var saveBtn = document.getElementById("save-source-btn");
8490
9805
  title.textContent = "Share Prompt: " + (app.name || "App");
8491
- textarea.value = prompt2;
8492
- textarea.readOnly = true;
9806
+ textarea2.value = prompt2;
9807
+ textarea2.readOnly = true;
8493
9808
  saveBtn.style.display = "none";
8494
9809
  overlay.hidden = false;
8495
9810
  overlay.dataset.appId = "";
@@ -8512,10 +9827,10 @@
8512
9827
  }).join("\n");
8513
9828
  var prompt2 = "Create a single HTML file that serves as my personal homepage/dashboard.\n\nMy apps:\n" + appList + "\n\nRequirements:\n- Show each app as a clickable card with its icon and name\n- For URL-based apps, clicking opens the URL in a new tab\n- For local apps, show a note that they can be opened from the App Launcher\n- Modern, responsive design with light theme\n- Group apps by their tags if they have tags\n- Everything in one self-contained HTML file, no external dependencies\n- Add a header with my name/title (I will customize this)\n- Make it visually distinctive and professional";
8514
9829
  var overlay = document.getElementById("source-overlay");
8515
- var textarea = document.getElementById("source-code");
9830
+ var textarea2 = document.getElementById("source-code");
8516
9831
  var title = document.getElementById("source-title");
8517
9832
  title.textContent = "Generate Homepage - Copy this prompt to AI";
8518
- textarea.value = prompt2;
9833
+ textarea2.value = prompt2;
8519
9834
  overlay.dataset.appName = "Homepage";
8520
9835
  overlay.hidden = false;
8521
9836
  }
@@ -8725,6 +10040,12 @@
8725
10040
  monetizeCancelEdit,
8726
10041
  monetizeSaveTool,
8727
10042
  monetizeDeleteTool,
10043
+ odpsToggleDefaults: odpsToggleDefaultsUi,
10044
+ odpsToggleTool: odpsToggleToolUi,
10045
+ odpsSuggest: odpsSuggestUi,
10046
+ odpsSaveDefaults,
10047
+ odpsGenerateSample: odpsGenerateSampleUi,
10048
+ odpsUseMeasured: odpsUseMeasuredUi,
8728
10049
  editAppDetails,
8729
10050
  showSubdomainModal,
8730
10051
  openConsents,
@@ -8758,6 +10079,12 @@
8758
10079
  detailAiDiscard,
8759
10080
  detailTestDraftLive,
8760
10081
  detailPublishTestedDraft,
10082
+ detailWorkTry,
10083
+ detailWorkPublish,
10084
+ detailWorkDiscard,
10085
+ detailCheckpointPreview,
10086
+ detailCheckpointRestore,
10087
+ detailCheckpointDelete,
8761
10088
  openStagingPreview,
8762
10089
  detailEditSource,
8763
10090
  detailImproveExternal,
@@ -9162,41 +10489,27 @@
9162
10489
  });
9163
10490
  document.getElementById("save-source-btn").addEventListener("click", function() {
9164
10491
  var overlay = document.getElementById("source-overlay");
9165
- var textarea = document.getElementById("source-code");
9166
- var appId = overlay.dataset.appId;
10492
+ var textarea2 = document.getElementById("source-code");
9167
10493
  var btn = this;
9168
- if (!appId) {
10494
+ if (!overlay.dataset.appId) {
9169
10495
  showNotice("Cannot save: no app ID found.");
9170
10496
  return;
9171
10497
  }
9172
- var newSource = textarea.value;
9173
- var newBlob = btoa(unescape(encodeURIComponent(newSource)));
9174
- var app = null;
9175
- for (var i = 0; i < allApps.length; i++) {
9176
- if (allApps[i].id === appId) {
9177
- app = allApps[i];
9178
- break;
9179
- }
9180
- }
9181
- if (!app) {
9182
- showNotice("App not found in library.");
9183
- return;
9184
- }
9185
- app.blob = newBlob;
9186
- btn.textContent = "Saving...";
10498
+ var newSource = textarea2.value;
10499
+ var idle = t("source.save");
10500
+ btn.textContent = t("wc.saving");
9187
10501
  btn.disabled = true;
9188
- saveApp(app).then(function() {
9189
- btn.textContent = "Saved!";
10502
+ saveSourceAsWorkingCopy().then(function() {
10503
+ btn.textContent = t("wc.savedShort");
9190
10504
  overlay.dataset.originalSource = newSource;
9191
10505
  setTimeout(function() {
9192
- btn.textContent = "Save Changes";
10506
+ btn.textContent = idle;
9193
10507
  btn.disabled = true;
9194
10508
  }, 1500);
9195
- renderApps4();
9196
10509
  }).catch(function(err) {
9197
- btn.textContent = "Save Changes";
10510
+ btn.textContent = idle;
9198
10511
  btn.disabled = false;
9199
- showNotice("Failed to save: " + (err.message || err));
10512
+ showNotice((t("wc.saveFailed") || "Failed to save") + ": " + (err && err.message || err));
9200
10513
  });
9201
10514
  });
9202
10515
  async function closeSourceModal() {