@jack200714/mafw 4.5.1 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +5 -2
  2. package/bin/mafw.js +16 -14
  3. package/gateway/dist/index.js +52 -38
  4. package/gateway/dist/media/media-plugin-loader.js +0 -12
  5. package/gateway/dist/plugins/hub.js +153 -19
  6. package/gateway/dist/routes/plugins.js +15 -5
  7. package/gateway/dist/runtime/event-broadcast.js +33 -0
  8. package/gateway/dist/runtime/loader.js +23 -13
  9. package/gateway/dist/usage/builtin-plugins/gateway.js +25 -3
  10. package/gateway/dist/usage/plugin-loader.js +0 -11
  11. package/gateway/package.json +2 -2
  12. package/package.json +1 -7
  13. package/packages/tui/dist/cli.js +8 -3
  14. package/.opencode/mafw/.backup-v61/parametric/base-skill-manifest.yaml +0 -2
  15. package/.opencode/mafw/STATUS.md +0 -6
  16. package/.opencode/mafw/ledger.md +0 -20
  17. package/.opencode/mafw/memory/.harmonic_index.json +0 -28
  18. package/.opencode/mafw/memory/.review_queue.json +0 -1
  19. package/.opencode/mafw/memory/tier3/parametric.json +0 -13
  20. package/.opencode/mafw/memory/tier3/test-goal.json +0 -19
  21. package/.opencode/mafw/memory-index.json +0 -6
  22. package/.opencode/mafw/parametric/base-skill-manifest.yaml +0 -2
  23. package/.opencode/mafw/waves.json +0 -4
  24. package/.opencode/opencode.json +0 -3
  25. package/.opencode/plugins/mafw-plugin.ts +0 -8
  26. package/.opencode/skills/banner-design/SKILL.md +0 -196
  27. package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +0 -118
  28. package/.opencode/skills/brand/SKILL.md +0 -97
  29. package/.opencode/skills/brand/references/approval-checklist.md +0 -169
  30. package/.opencode/skills/brand/references/asset-organization.md +0 -157
  31. package/.opencode/skills/brand/references/brand-guideline-template.md +0 -140
  32. package/.opencode/skills/brand/references/color-palette-management.md +0 -186
  33. package/.opencode/skills/brand/references/consistency-checklist.md +0 -94
  34. package/.opencode/skills/brand/references/logo-usage-rules.md +0 -185
  35. package/.opencode/skills/brand/references/messaging-framework.md +0 -85
  36. package/.opencode/skills/brand/references/typography-specifications.md +0 -214
  37. package/.opencode/skills/brand/references/update.md +0 -118
  38. package/.opencode/skills/brand/references/visual-identity.md +0 -96
  39. package/.opencode/skills/brand/references/voice-framework.md +0 -88
  40. package/.opencode/skills/brand/scripts/extract-colors.cjs +0 -341
  41. package/.opencode/skills/brand/scripts/inject-brand-context.cjs +0 -349
  42. package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +0 -248
  43. package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +0 -52
  44. package/.opencode/skills/brand/scripts/validate-asset.cjs +0 -387
  45. package/.opencode/skills/brand/templates/brand-guidelines-starter.md +0 -275
  46. package/.opencode/skills/design/SKILL.md +0 -313
  47. package/.opencode/skills/design/data/cip/deliverables.csv +0 -51
  48. package/.opencode/skills/design/data/cip/industries.csv +0 -21
  49. package/.opencode/skills/design/data/cip/mockup-contexts.csv +0 -21
  50. package/.opencode/skills/design/data/cip/styles.csv +0 -21
  51. package/.opencode/skills/design/data/icon/styles.csv +0 -16
  52. package/.opencode/skills/design/data/logo/colors.csv +0 -56
  53. package/.opencode/skills/design/data/logo/industries.csv +0 -56
  54. package/.opencode/skills/design/data/logo/styles.csv +0 -56
  55. package/.opencode/skills/design/references/banner-sizes-and-styles.md +0 -118
  56. package/.opencode/skills/design/references/cip-deliverable-guide.md +0 -95
  57. package/.opencode/skills/design/references/cip-design.md +0 -121
  58. package/.opencode/skills/design/references/cip-prompt-engineering.md +0 -84
  59. package/.opencode/skills/design/references/cip-style-guide.md +0 -68
  60. package/.opencode/skills/design/references/design-routing.md +0 -207
  61. package/.opencode/skills/design/references/icon-design.md +0 -122
  62. package/.opencode/skills/design/references/logo-color-psychology.md +0 -101
  63. package/.opencode/skills/design/references/logo-design.md +0 -92
  64. package/.opencode/skills/design/references/logo-prompt-engineering.md +0 -158
  65. package/.opencode/skills/design/references/logo-style-guide.md +0 -109
  66. package/.opencode/skills/design/references/slides-copywriting-formulas.md +0 -84
  67. package/.opencode/skills/design/references/slides-create.md +0 -4
  68. package/.opencode/skills/design/references/slides-html-template.md +0 -295
  69. package/.opencode/skills/design/references/slides-layout-patterns.md +0 -137
  70. package/.opencode/skills/design/references/slides-strategies.md +0 -94
  71. package/.opencode/skills/design/references/slides.md +0 -42
  72. package/.opencode/skills/design/references/social-photos-design.md +0 -329
  73. package/.opencode/skills/design/scripts/cip/core.py +0 -215
  74. package/.opencode/skills/design/scripts/cip/generate.py +0 -484
  75. package/.opencode/skills/design/scripts/cip/render-html.py +0 -424
  76. package/.opencode/skills/design/scripts/cip/search.py +0 -127
  77. package/.opencode/skills/design/scripts/icon/generate.py +0 -487
  78. package/.opencode/skills/design/scripts/logo/core.py +0 -175
  79. package/.opencode/skills/design/scripts/logo/generate.py +0 -362
  80. package/.opencode/skills/design/scripts/logo/search.py +0 -114
  81. package/.opencode/skills/design-system/SKILL.md +0 -244
  82. package/.opencode/skills/design-system/data/slide-backgrounds.csv +0 -11
  83. package/.opencode/skills/design-system/data/slide-charts.csv +0 -26
  84. package/.opencode/skills/design-system/data/slide-color-logic.csv +0 -14
  85. package/.opencode/skills/design-system/data/slide-copy.csv +0 -26
  86. package/.opencode/skills/design-system/data/slide-layout-logic.csv +0 -16
  87. package/.opencode/skills/design-system/data/slide-layouts.csv +0 -26
  88. package/.opencode/skills/design-system/data/slide-strategies.csv +0 -16
  89. package/.opencode/skills/design-system/data/slide-typography.csv +0 -15
  90. package/.opencode/skills/design-system/references/component-specs.md +0 -236
  91. package/.opencode/skills/design-system/references/component-tokens.md +0 -214
  92. package/.opencode/skills/design-system/references/primitive-tokens.md +0 -203
  93. package/.opencode/skills/design-system/references/semantic-tokens.md +0 -215
  94. package/.opencode/skills/design-system/references/states-and-variants.md +0 -241
  95. package/.opencode/skills/design-system/references/tailwind-integration.md +0 -251
  96. package/.opencode/skills/design-system/references/token-architecture.md +0 -224
  97. package/.opencode/skills/design-system/scripts/embed-tokens.cjs +0 -99
  98. package/.opencode/skills/design-system/scripts/fetch-background.py +0 -317
  99. package/.opencode/skills/design-system/scripts/generate-slide.py +0 -770
  100. package/.opencode/skills/design-system/scripts/generate-tokens.cjs +0 -205
  101. package/.opencode/skills/design-system/scripts/html-token-validator.py +0 -327
  102. package/.opencode/skills/design-system/scripts/search-slides.py +0 -218
  103. package/.opencode/skills/design-system/scripts/slide-token-validator.py +0 -35
  104. package/.opencode/skills/design-system/scripts/slide_search_core.py +0 -453
  105. package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +0 -48
  106. package/.opencode/skills/design-system/scripts/validate-tokens.cjs +0 -246
  107. package/.opencode/skills/design-system/templates/design-tokens-starter.json +0 -143
  108. package/.opencode/skills/frontend-design/LICENSE.txt +0 -21
  109. package/.opencode/skills/frontend-design/SKILL.md +0 -115
  110. package/.opencode/skills/mafw-automation-interview/SKILL.md +0 -43
  111. package/.opencode/skills/mafw-cli-scanner/SKILL.md +0 -37
  112. package/.opencode/skills/mafw-compression-verifier/SKILL.md +0 -52
  113. package/.opencode/skills/mafw-desktop-inspector/SKILL.md +0 -170
  114. package/.opencode/skills/mafw-execute/SKILL.md +0 -62
  115. package/.opencode/skills/mafw-gateway-restart/SKILL.md +0 -87
  116. package/.opencode/skills/mafw-github-scanner/SKILL.md +0 -38
  117. package/.opencode/skills/mafw-goal/SKILL.md +0 -76
  118. package/.opencode/skills/mafw-interview/SKILL.md +0 -68
  119. package/.opencode/skills/mafw-memory-extractor/SKILL.md +0 -65
  120. package/.opencode/skills/mafw-plan/SKILL.md +0 -58
  121. package/.opencode/skills/mafw-review/SKILL.md +0 -85
  122. package/.opencode/skills/runtime-plugin-authoring/SKILL.md +0 -791
  123. package/.opencode/skills/slides/SKILL.md +0 -40
  124. package/.opencode/skills/slides/references/copywriting-formulas.md +0 -84
  125. package/.opencode/skills/slides/references/create.md +0 -4
  126. package/.opencode/skills/slides/references/html-template.md +0 -295
  127. package/.opencode/skills/slides/references/layout-patterns.md +0 -137
  128. package/.opencode/skills/slides/references/slide-strategies.md +0 -94
  129. package/.opencode/skills/ui-styling/LICENSE.txt +0 -202
  130. package/.opencode/skills/ui-styling/SKILL.md +0 -324
  131. package/.opencode/skills/ui-styling/references/canvas-design-system.md +0 -320
  132. package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +0 -471
  133. package/.opencode/skills/ui-styling/references/shadcn-components.md +0 -424
  134. package/.opencode/skills/ui-styling/references/shadcn-theming.md +0 -373
  135. package/.opencode/skills/ui-styling/references/tailwind-customization.md +0 -483
  136. package/.opencode/skills/ui-styling/references/tailwind-responsive.md +0 -382
  137. package/.opencode/skills/ui-styling/references/tailwind-utilities.md +0 -455
  138. package/.opencode/skills/ui-styling/scripts/requirements.txt +0 -17
  139. package/.opencode/skills/ui-styling/scripts/shadcn_add.py +0 -308
  140. package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +0 -473
  141. package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +0 -1
  142. package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +0 -3
  143. package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +0 -266
  144. package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +0 -394
  145. package/.opencode/skills/ui-ux-pro-max/SKILL.md +0 -388
  146. package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +0 -31
  147. package/.opencode/skills/ui-ux-pro-max/data/charts.csv +0 -26
  148. package/.opencode/skills/ui-ux-pro-max/data/colors.csv +0 -193
  149. package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +0 -1924
  150. package/.opencode/skills/ui-ux-pro-max/data/icons.csv +0 -106
  151. package/.opencode/skills/ui-ux-pro-max/data/landing.csv +0 -35
  152. package/.opencode/skills/ui-ux-pro-max/data/motion.csv +0 -17
  153. package/.opencode/skills/ui-ux-pro-max/data/products.csv +0 -193
  154. package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +0 -45
  155. package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +0 -51
  156. package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +0 -54
  157. package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +0 -57
  158. package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  159. package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  160. package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +0 -76
  161. package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  162. package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +0 -51
  163. package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  164. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -71
  165. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  166. package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  167. package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +0 -54
  168. package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  169. package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  170. package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  171. package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +0 -54
  172. package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +0 -60
  173. package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +0 -56
  174. package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  175. package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +0 -60
  176. package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +0 -57
  177. package/.opencode/skills/ui-ux-pro-max/data/styles.csv +0 -85
  178. package/.opencode/skills/ui-ux-pro-max/data/typography.csv +0 -75
  179. package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +0 -162
  180. package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  181. package/.opencode/skills/ui-ux-pro-max/scripts/core.py +0 -464
  182. package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +0 -1479
  183. package/.opencode/skills/ui-ux-pro-max/scripts/search.py +0 -162
  184. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_core.py +0 -134
  185. package/.opencode/skills/ui-ux-pro-max/scripts/tests/test_design_system_mode.py +0 -159
  186. package/.opencode/skills/ui-ux-pro-max/scripts/validate_data.py +0 -114
  187. package/dist/hooks/bash-python-guide.d.ts +0 -4
  188. package/dist/hooks/bash-python-guide.d.ts.map +0 -1
  189. package/dist/hooks/bash-python-guide.js +0 -67
  190. package/dist/hooks/bash-python-guide.js.map +0 -1
  191. package/dist/hooks/handoff.d.ts +0 -9
  192. package/dist/hooks/handoff.d.ts.map +0 -1
  193. package/dist/hooks/handoff.js +0 -8
  194. package/dist/hooks/handoff.js.map +0 -1
  195. package/dist/hooks/hook-manager.d.ts +0 -32
  196. package/dist/hooks/hook-manager.d.ts.map +0 -1
  197. package/dist/hooks/hook-manager.js +0 -91
  198. package/dist/hooks/hook-manager.js.map +0 -1
  199. package/dist/hooks/llm-after.d.ts +0 -8
  200. package/dist/hooks/llm-after.d.ts.map +0 -1
  201. package/dist/hooks/llm-after.js +0 -8
  202. package/dist/hooks/llm-after.js.map +0 -1
  203. package/dist/hooks/media-ingest.d.ts +0 -43
  204. package/dist/hooks/media-ingest.d.ts.map +0 -1
  205. package/dist/hooks/media-ingest.js +0 -269
  206. package/dist/hooks/media-ingest.js.map +0 -1
  207. package/dist/hooks/memory-guide.d.ts +0 -3
  208. package/dist/hooks/memory-guide.d.ts.map +0 -1
  209. package/dist/hooks/memory-guide.js +0 -47
  210. package/dist/hooks/memory-guide.js.map +0 -1
  211. package/dist/hooks/observation-capture.d.ts +0 -17
  212. package/dist/hooks/observation-capture.d.ts.map +0 -1
  213. package/dist/hooks/observation-capture.js +0 -20
  214. package/dist/hooks/observation-capture.js.map +0 -1
  215. package/dist/hooks/session-compacting.d.ts +0 -6
  216. package/dist/hooks/session-compacting.d.ts.map +0 -1
  217. package/dist/hooks/session-compacting.js +0 -6
  218. package/dist/hooks/session-compacting.js.map +0 -1
  219. package/dist/hooks/session-ending.d.ts +0 -20
  220. package/dist/hooks/session-ending.d.ts.map +0 -1
  221. package/dist/hooks/session-ending.js +0 -113
  222. package/dist/hooks/session-ending.js.map +0 -1
  223. package/dist/hooks/session-recall.d.ts +0 -2
  224. package/dist/hooks/session-recall.d.ts.map +0 -1
  225. package/dist/hooks/session-recall.js +0 -103
  226. package/dist/hooks/session-recall.js.map +0 -1
  227. package/dist/hooks/session-start.d.ts +0 -9
  228. package/dist/hooks/session-start.d.ts.map +0 -1
  229. package/dist/hooks/session-start.js +0 -42
  230. package/dist/hooks/session-start.js.map +0 -1
  231. package/dist/hooks/tool-before.d.ts +0 -10
  232. package/dist/hooks/tool-before.d.ts.map +0 -1
  233. package/dist/hooks/tool-before.js +0 -6
  234. package/dist/hooks/tool-before.js.map +0 -1
  235. package/dist/hooks/tool-executed.d.ts +0 -19
  236. package/dist/hooks/tool-executed.d.ts.map +0 -1
  237. package/dist/hooks/tool-executed.js +0 -12
  238. package/dist/hooks/tool-executed.js.map +0 -1
  239. package/dist/hooks/user-profile.d.ts +0 -2
  240. package/dist/hooks/user-profile.d.ts.map +0 -1
  241. package/dist/hooks/user-profile.js +0 -30
  242. package/dist/hooks/user-profile.js.map +0 -1
  243. package/dist/hooks/user-prompt.d.ts +0 -9
  244. package/dist/hooks/user-prompt.d.ts.map +0 -1
  245. package/dist/hooks/user-prompt.js +0 -8
  246. package/dist/hooks/user-prompt.js.map +0 -1
  247. package/dist/hooks/voice-guide.d.ts +0 -8
  248. package/dist/hooks/voice-guide.d.ts.map +0 -1
  249. package/dist/hooks/voice-guide.js +0 -101
  250. package/dist/hooks/voice-guide.js.map +0 -1
  251. package/dist/plugin.d.ts +0 -55
  252. package/dist/plugin.d.ts.map +0 -1
  253. package/dist/plugin.js +0 -342
  254. package/dist/plugin.js.map +0 -1
  255. package/dist/tools/add-memory.d.ts +0 -17
  256. package/dist/tools/add-memory.d.ts.map +0 -1
  257. package/dist/tools/add-memory.js +0 -60
  258. package/dist/tools/add-memory.js.map +0 -1
  259. package/dist/tools/media-ask.d.ts +0 -14
  260. package/dist/tools/media-ask.d.ts.map +0 -1
  261. package/dist/tools/media-ask.js +0 -224
  262. package/dist/tools/media-ask.js.map +0 -1
  263. package/dist/tools/media-speak.d.ts +0 -16
  264. package/dist/tools/media-speak.d.ts.map +0 -1
  265. package/dist/tools/media-speak.js +0 -78
  266. package/dist/tools/media-speak.js.map +0 -1
  267. package/dist/tools/media-upload.d.ts +0 -14
  268. package/dist/tools/media-upload.d.ts.map +0 -1
  269. package/dist/tools/media-upload.js +0 -155
  270. package/dist/tools/media-upload.js.map +0 -1
  271. package/dist/tools/python-exec.d.ts +0 -13
  272. package/dist/tools/python-exec.d.ts.map +0 -1
  273. package/dist/tools/python-exec.js +0 -99
  274. package/dist/tools/python-exec.js.map +0 -1
  275. package/dist/tools/python-restart.d.ts +0 -11
  276. package/dist/tools/python-restart.d.ts.map +0 -1
  277. package/dist/tools/python-restart.js +0 -33
  278. package/dist/tools/python-restart.js.map +0 -1
  279. package/dist/utils/circuit-breaker.d.ts +0 -23
  280. package/dist/utils/circuit-breaker.d.ts.map +0 -1
  281. package/dist/utils/circuit-breaker.js +0 -75
  282. package/dist/utils/circuit-breaker.js.map +0 -1
  283. package/dist/utils/config-loader.d.ts +0 -20
  284. package/dist/utils/config-loader.d.ts.map +0 -1
  285. package/dist/utils/config-loader.js +0 -194
  286. package/dist/utils/config-loader.js.map +0 -1
  287. package/dist/utils/fallback.d.ts +0 -7
  288. package/dist/utils/fallback.d.ts.map +0 -1
  289. package/dist/utils/fallback.js +0 -20
  290. package/dist/utils/fallback.js.map +0 -1
  291. package/dist/utils/git.d.ts +0 -24
  292. package/dist/utils/git.d.ts.map +0 -1
  293. package/dist/utils/git.js +0 -71
  294. package/dist/utils/git.js.map +0 -1
  295. package/dist/utils/github.d.ts +0 -37
  296. package/dist/utils/github.d.ts.map +0 -1
  297. package/dist/utils/github.js +0 -53
  298. package/dist/utils/github.js.map +0 -1
  299. package/dist/utils/global-path.d.ts +0 -2
  300. package/dist/utils/global-path.d.ts.map +0 -1
  301. package/dist/utils/global-path.js +0 -32
  302. package/dist/utils/global-path.js.map +0 -1
  303. package/dist/utils/logger.d.ts +0 -6
  304. package/dist/utils/logger.d.ts.map +0 -1
  305. package/dist/utils/logger.js +0 -69
  306. package/dist/utils/logger.js.map +0 -1
  307. package/dist/utils/obs-capture.d.ts +0 -7
  308. package/dist/utils/obs-capture.d.ts.map +0 -1
  309. package/dist/utils/obs-capture.js +0 -49
  310. package/dist/utils/obs-capture.js.map +0 -1
  311. package/dist/utils/retry.d.ts +0 -11
  312. package/dist/utils/retry.d.ts.map +0 -1
  313. package/dist/utils/retry.js +0 -45
  314. package/dist/utils/retry.js.map +0 -1
  315. package/dist/utils/self-wiring.d.ts +0 -13
  316. package/dist/utils/self-wiring.d.ts.map +0 -1
  317. package/dist/utils/self-wiring.js +0 -86
  318. package/dist/utils/self-wiring.js.map +0 -1
  319. package/dist/utils/state.d.ts +0 -106
  320. package/dist/utils/state.d.ts.map +0 -1
  321. package/dist/utils/state.js +0 -166
  322. package/dist/utils/state.js.map +0 -1
  323. package/dist/utils/status.d.ts +0 -47
  324. package/dist/utils/status.d.ts.map +0 -1
  325. package/dist/utils/status.js +0 -147
  326. package/dist/utils/status.js.map +0 -1
  327. package/dist/utils/ttl-map.d.ts +0 -17
  328. package/dist/utils/ttl-map.d.ts.map +0 -1
  329. package/dist/utils/ttl-map.js +0 -67
  330. package/dist/utils/ttl-map.js.map +0 -1
@@ -1,791 +0,0 @@
1
- ---
2
- name: runtime-plugin-authoring
3
- description: 为 MAFW Gateway 编写 Runtime 插件的完整指南。覆盖能力契约(Tier 0/1/2)、CJS 插件格式、事件归一化、可选接口、激活与测试。当需要接入新的 agent runtime(如 pi-coding-agent、claude、自定义 LLM 服务)时使用此 skill。
4
- ---
5
-
6
- # MAFW Runtime Plugin 编写指南
7
-
8
- ## 概述
9
-
10
- MAFW Gateway 通过**能力契约**(Runtime Capability Contract)与 agent runtime 解耦。插件是一个 CJS `.js` 文件,放在 `~/.mafw/runtime-plugins/` 目录下,声明自己支持的能力等级,gateway 按能力集自动开关功能。
11
-
12
- **核心原则:**
13
- - **能力自声明**:插件声明能力,gateway 按能力降级(缺能力 → 503 或跳过,永不崩溃)
14
- - **Fail-open**:插件加载/运行失败 → 自动回退到内置 opencode runtime
15
- - **运行时热切换**:`POST /api/runtime/switch` 可在进程内热切换 runtime(无需重启);插件文件修改后需 `POST /api/runtime/reload` 重扫或重启 gateway
16
-
17
- ## 第一步:理解能力分级
18
-
19
- ### Tier 0(基线,所有插件自动获得)
20
- | 能力 | 说明 |
21
- |------|------|
22
- | `sessionApi` | 会话 CRUD(create/prompt/messages/get/delete/abort/list) |
23
- | `promptWhileBusy` | 会话忙碌时仍可追加输入(promptAsync) |
24
-
25
- Tier 0 是 `minimalCapabilities()` 默认值,插件无需声明即可获得。
26
-
27
- ### Tier 1(自治执行)
28
- | 能力 | 说明 | 缺省行为 |
29
- |------|------|----------|
30
- | `eventStream` | SSE 事件流订阅 | 跳过事件订阅,无自治触发 |
31
- | `nativeApprovals` | 原生审批 UI | 4 个审批端点返回 503 |
32
- | `providerConfigApi` | Provider 配置管理 | 4 个 provider 端点返回 503 |
33
- | `perLlmCallTransform` | 每次 LLM 调用的 transform | 跳过 transform 注入 |
34
-
35
- ### Tier 2(桌面完整)
36
- | 能力 | 说明 | 缺省行为 |
37
- |------|------|----------|
38
- | `sessionStorageApi` | 直读 runtime 私有存储列出会话 | 回退 `session.list` + 客户端过滤 |
39
- | `agentConfigApi` | Agent 定义安装 | Manager agent 安装跳过(warn 日志) |
40
-
41
- **选择指南:**
42
- - 仅协作对话 → Tier 0 即可
43
- - 需要自动化/事件驱动 → 加 `eventStream`(Tier 1)
44
- - 桌面聊天完整体验 → 加 Tier 2 能力
45
-
46
- ## 第二步:编写插件文件
47
-
48
- ### 文件位置
49
- ```
50
- ~/.mafw/runtime-plugins/my-runtime.js
51
- ```
52
-
53
- ### CJS module.exports 形状
54
-
55
- ```javascript
56
- // ~/.mafw/runtime-plugins/my-runtime.js
57
- module.exports = {
58
- // 必需:唯一标识符(用于 config.yaml 激活)
59
- name: "my-runtime",
60
-
61
- // 可选:声明超出 Tier-0 基线的能力(与 minimalCapabilities() 合并)
62
- capabilities: {
63
- eventStream: true, // Tier 1:需要事件流
64
- nativeApprovals: false, // 不需要原生审批
65
- providerConfigApi: false, // 不需要 provider 管理
66
- perLlmCallTransform: false,
67
- // 可选能力(不在 Tier 分级内):
68
- sessionStorageApi: false, // 无直读存储
69
- agentConfigApi: false, // 无 agent 安装
70
- },
71
-
72
- // 可选:默认 true(gateway 不 spawn 进程)
73
- // 设为 false 仅当你需要 gateway 启动/监管 runtime 进程
74
- external: true,
75
-
76
- // 必需:工厂函数,接收 RuntimePluginContext,返回 AgentRuntime
77
- async createRuntime(ctx) {
78
- // ctx 提供的工具:
79
- // - ctx.fetch(url, opts) — 带 60s 默认超时的 fetch
80
- // - ctx.log — gateway 日志器
81
- // - ctx.pluginConfig(name) — 读取 config.yaml 的 pluginConfig 段
82
-
83
- return {
84
- name: "my-runtime",
85
- capabilities: { /* 同上 */ },
86
-
87
- // ─── 必需:会话 API(Tier 0)──────────────────────
88
- session: {
89
- async create(opts) {
90
- // opts: { directory?: string }
91
- // 返回: { id: string, ... }
92
- const res = await ctx.fetch('http://localhost:8080/sessions', {
93
- method: 'POST',
94
- headers: { 'Content-Type': 'application/json' },
95
- body: JSON.stringify(opts),
96
- });
97
- return res.json();
98
- },
99
-
100
- async promptAsync(opts) {
101
- // opts: { sessionID, parts?, message?, agent?, model?, variant?, system?, noReply? }
102
- // 返回: void 或 { error?, response? }
103
- await ctx.fetch(`http://localhost:8080/sessions/${opts.sessionID}/prompt`, {
104
- method: 'POST',
105
- headers: { 'Content-Type': 'application/json' },
106
- body: JSON.stringify(opts),
107
- });
108
- },
109
-
110
- async prompt(opts) {
111
- // 同步等待回复
112
- // 返回: { parts: any[], ... }
113
- const res = await ctx.fetch(`http://localhost:8080/sessions/${opts.sessionID}/prompt`, {
114
- method: 'POST',
115
- headers: { 'Content-Type': 'application/json' },
116
- body: JSON.stringify(opts),
117
- });
118
- return res.json();
119
- },
120
-
121
- async messages(opts) {
122
- // opts: { sessionID, limit?, before? }
123
- // 返回: { data: any[], nextCursor?: string }
124
- const res = await ctx.fetch(
125
- `http://localhost:8080/sessions/${opts.sessionID}/messages?limit=${opts.limit || 50}`
126
- );
127
- return res.json();
128
- },
129
-
130
- async get({ sessionID }) {
131
- const res = await ctx.fetch(`http://localhost:8080/sessions/${sessionID}`);
132
- return res.json();
133
- },
134
-
135
- async delete({ sessionID }) {
136
- await ctx.fetch(`http://localhost:8080/sessions/${sessionID}`, { method: 'DELETE' });
137
- },
138
-
139
- async abort({ sessionID }) {
140
- await ctx.fetch(`http://localhost:8080/sessions/${sessionID}/abort`, { method: 'POST' });
141
- },
142
-
143
- async list(opts) {
144
- const res = await ctx.fetch('http://localhost:8080/sessions');
145
- return res.json();
146
- },
147
-
148
- async todo({ sessionID }) {
149
- // 返回: any[](待办事项列表)
150
- return [];
151
- },
152
-
153
- async children({ sessionID }) {
154
- // 返回: any[](子会话列表)
155
- return [];
156
- },
157
-
158
- async summarize(opts) {
159
- // opts: { sessionID, providerID?, modelID? }
160
- // 返回: 压缩后的会话摘要
161
- const res = await ctx.fetch(
162
- `http://localhost:8080/sessions/${opts.sessionID}/summarize`,
163
- { method: 'POST' }
164
- );
165
- return res.json();
166
- },
167
-
168
- // 可选:需要 sessionStorageApi 能力
169
- // async listByDirectory(directory, limit) { return []; },
170
- },
171
-
172
- // ─── 必需:事件流(Tier 1,若声明 eventStream)───
173
- global: {
174
- async event() {
175
- // 返回: { stream: AsyncIterable<RawRuntimeEvent> }
176
- // 见下方"事件归一化"章节
177
- return { stream: createEventStream() };
178
- },
179
- },
180
-
181
- // ─── 必需:Provider 与配置 ────────────────────────
182
- provider: {
183
- async list() {
184
- return { all: [], connected: [], default: {} };
185
- },
186
- },
187
-
188
- app: {
189
- async agents() {
190
- return [];
191
- },
192
- },
193
-
194
- config: {
195
- async get() { return {}; },
196
- async update(c) { return c; },
197
- },
198
-
199
- // ─── 必需:Base URL ──────────────────────────────
200
- getBaseUrl() {
201
- return "http://127.0.0.1:8080";
202
- },
203
-
204
- // ─── 可选:健康检查 ──────────────────────────────
205
- async healthCheck() {
206
- try {
207
- const res = await ctx.fetch('http://localhost:8080/health', {
208
- signal: AbortSignal.timeout(3000),
209
- });
210
- return res.ok;
211
- } catch {
212
- return false;
213
- }
214
- },
215
-
216
- // ─── 可选:凭据获取 ──────────────────────────────
217
- // credentials: {
218
- // getApiKey(provider) { return process.env[`${provider.toUpperCase()}_API_KEY`] || null; }
219
- // },
220
-
221
- // ─── 可选:Agent 定义安装(需 agentConfigApi 能力)
222
- // agents: {
223
- // async install(name, definition) { /* 写入配置文件 */ },
224
- // async remove(name) { /* 删除配置 */ },
225
- // },
226
- };
227
- },
228
- };
229
- ```
230
-
231
- ## 第三步:事件归一化
232
-
233
- ### 事件形状
234
-
235
- Gateway 的事件归一化器 `normalizeOpencodeEvent()` 接受两种形状:
236
-
237
- ```typescript
238
- // 信封形状(GlobalEvent wrapper)
239
- { payload: { type: "string", properties: {...}, sessionID: "..." } }
240
-
241
- // 扁平形状
242
- { type: "string", properties: {...}, sessionID: "..." }
243
- ```
244
-
245
- **推荐:** 让你的 runtime 事件尽可能接近 opencode 事件形状,这样 `normalizeOpencodeEvent()` 可直接使用,无需写新归一化器。
246
-
247
- ### 关键事件类型(opencode 参考)
248
-
249
- | 事件类型 | EventFacets 映射 | 说明 |
250
- |----------|-----------------|------|
251
- | `message.part.updated` | `step`(settled step)+ `chatSignal: 'delta'` | 流式文本输出 |
252
- | `message.updated` | `step`(completed message)+ `chatSignal: 'complete'` | 消息完成 |
253
- | `session.idle` | `chatSignal: 'complete'` + `broadcast: 'idle'` | 会话空闲 |
254
- | `session.error` | `chatSignal: 'error'` + `broadcast: 'error'` | 会话错误 |
255
- | `session.next.step.ended` | `step`(legacy 兜底) | 步骤结束(旧版) |
256
-
257
- ### EventFacets 正交切面
258
-
259
- ```typescript
260
- interface EventFacets {
261
- type: string; // 原始类型(透传)
262
- properties: any; // 原始属性(透传)
263
- sessionID?: string;
264
- directory?: string;
265
- step: StepEndedProps | null; // 已结算的 LLM step
266
- chatSignal: 'delta' | 'complete' | 'error' | null; // chat 信号
267
- deltaText?: string;
268
- chatError?: unknown;
269
- broadcast: 'idle' | 'error' | 'passthrough'; // 全局广播
270
- toolCommand?: string; // shell 命令(自更新定位用)
271
- }
272
- ```
273
-
274
- ### 自定义事件流
275
-
276
- 若你的 runtime 事件形状与 opencode 差异大,需要:
277
- 1. 写新归一化函数(如 `normalizeMyRuntimeEvent(evt): EventFacets`)
278
- 2. 修改 `gateway/src/index.ts` 的事件分发逻辑,根据 `runtimeName` 选择归一化器
279
-
280
- **简单路径:** 让你的 runtime 发出 opencode 兼容事件,无需改 gateway 代码。
281
-
282
- ## 第四步:激活与测试
283
-
284
- ### 激活方式
285
-
286
- **方式 A:config.yaml**
287
- ```yaml
288
- # ~/.mafw/config.yaml
289
- runtime:
290
- plugin: my-runtime # 匹配 module.exports.name
291
- pluginConfig:
292
- my-runtime:
293
- baseUrl: "http://localhost:8080"
294
- apiKey: "xxx" # 通过 ctx.pluginConfig("my-runtime") 读取
295
- ```
296
-
297
- **方式 B:环境变量**
298
- ```bash
299
- MAFW_RUNTIME_PLUGIN=my-runtime
300
- ```
301
-
302
- ### 测试流程
303
-
304
- 1. **编写插件** → 保存为 `~/.mafw/runtime-plugins/my-runtime.js`
305
- 2. **重启 gateway** → `mafw restart` 或前台 `mafw start`
306
- 3. **检查加载状态** → `GET http://localhost:3000/api/runtime`
307
- ```json
308
- {
309
- "active": { "name": "my-runtime", "capabilities": {...} },
310
- "plugins": [
311
- { "file": "my-runtime.js", "name": "my-runtime", "status": "ok", "capabilities": {...} }
312
- ]
313
- }
314
- ```
315
- 4. **测试功能** → 创建会话、发送消息、验证事件流
316
-
317
- ### 常见错误
318
-
319
- | 现象 | 原因 | 解决 |
320
- |------|------|------|
321
- | `status: "error", error: "missing name"` | 未导出 `name` 字段 | 添加 `name: "my-runtime"` |
322
- | `status: "error", error: "missing createRuntime(ctx)"` | 未导出工厂函数 | 添加 `async createRuntime(ctx) {...}` |
323
- | `status: "error", error: "duplicate name"` | 多个文件导出相同 `name` | 检查重复插件 |
324
- | Gateway 仍用 opencode | 插件加载失败 / 未配置 | 检查 `/api/runtime` 返回;确认 `config.yaml` 的 `runtime.plugin` |
325
-
326
- ## 第五步:参考实现
327
-
328
- ### 内置 opencode runtime
329
-
330
- `gateway/src/runtime/opencode-runtime.ts` 是完整的 Tier 2 参考实现:
331
- - **能力声明**:`fullCapabilities()`(全满)
332
- - **凭据**:`credentials.getApiKey()` 从 opencode auth.json 读取
333
- - **sessionStorageApi**:`session.listByDirectory()` 直读 SQLite
334
- - **agentConfigApi**:`agents.install()` 写 frontmatter markdown
335
- - **事件流**:`global.event()` 返回 SSE stream
336
- - **健康检查**:`healthCheck()` 探测 `/global/health`
337
-
338
- ### 最小可用插件(Tier 0)
339
-
340
- ```javascript
341
- // ~/.mafw/runtime-plugins/minimal.js
342
- module.exports = {
343
- name: "minimal",
344
- // 不声明额外能力 → 仅 Tier 0(sessionApi + promptWhileBusy)
345
- async createRuntime(ctx) {
346
- const baseUrl = ctx.pluginConfig("minimal").baseUrl || "http://localhost:8080";
347
- return {
348
- name: "minimal",
349
- capabilities: {}, // Tier 0 only
350
- session: {
351
- async create(opts) {
352
- const res = await ctx.fetch(`${baseUrl}/sessions`, { method: 'POST', body: JSON.stringify(opts) });
353
- return res.json();
354
- },
355
- async promptAsync(opts) {
356
- await ctx.fetch(`${baseUrl}/sessions/${opts.sessionID}/prompt`, {
357
- method: 'POST', body: JSON.stringify(opts)
358
- });
359
- },
360
- async prompt(opts) {
361
- const res = await ctx.fetch(`${baseUrl}/sessions/${opts.sessionID}/prompt`, {
362
- method: 'POST', body: JSON.stringify(opts)
363
- });
364
- return res.json();
365
- },
366
- async messages(opts) {
367
- const res = await ctx.fetch(`${baseUrl}/sessions/${opts.sessionID}/messages?limit=${opts.limit || 50}`);
368
- return res.json();
369
- },
370
- async get({ sessionID }) {
371
- const res = await ctx.fetch(`${baseUrl}/sessions/${sessionID}`);
372
- return res.json();
373
- },
374
- async delete({ sessionID }) {
375
- await ctx.fetch(`${baseUrl}/sessions/${sessionID}`, { method: 'DELETE' });
376
- },
377
- async abort({ sessionID }) {
378
- await ctx.fetch(`${baseUrl}/sessions/${sessionID}/abort`, { method: 'POST' });
379
- },
380
- async list() { return []; },
381
- async todo({ sessionID }) { return []; },
382
- async children({ sessionID }) { return []; },
383
- async summarize(opts) { return {}; },
384
- },
385
- global: { async event() { return { stream: (async function*(){})() }; } },
386
- provider: { async list() { return { all: [], connected: [], default: {} }; } },
387
- app: { async agents() { return []; } },
388
- config: { async get() { return {}; }, async update(c) { return c; } },
389
- getBaseUrl() { return baseUrl; },
390
- };
391
- },
392
- };
393
- ```
394
-
395
- ## 可选接口详解
396
-
397
- ### credentials(凭据获取)
398
-
399
- ```typescript
400
- interface RuntimeCredentials {
401
- getApiKey(provider: string): string | null;
402
- }
403
- ```
404
-
405
- **用途:** Media Agent 等服务优先从 runtime credentials 获取 API key,回退到直读 opencode auth.json。
406
-
407
- **示例:**
408
- ```javascript
409
- credentials: {
410
- getApiKey(provider) {
411
- // 从环境变量、配置文件或密钥管理器读取
412
- return process.env[`${provider.toUpperCase()}_API_KEY`] || null;
413
- }
414
- }
415
- ```
416
-
417
- ### agents(Agent 定义安装)
418
-
419
- ```typescript
420
- interface AgentInstaller {
421
- install(name: string, definition: AgentDefinition): Promise<void>;
422
- remove?(name: string): Promise<void>;
423
- }
424
- ```
425
-
426
- **用途:** Manager agent 通过此接口安装自定义 agent 定义到 runtime。
427
-
428
- **AgentDefinition 形状:**
429
- ```typescript
430
- interface AgentDefinition {
431
- description: string;
432
- mode?: 'primary' | 'subagent' | 'all';
433
- model?: string;
434
- temperature?: number;
435
- color?: string;
436
- systemPrompt: string;
437
- permissions: AgentPermissions;
438
- }
439
- ```
440
-
441
- **示例:**
442
- ```javascript
443
- agents: {
444
- async install(name, definition) {
445
- const configDir = path.join(os.homedir(), '.config', 'my-runtime', 'agents');
446
- fs.mkdirSync(configDir, { recursive: true });
447
- const filePath = path.join(configDir, `${name}.yaml`);
448
- fs.writeFileSync(filePath, serializeToYaml(definition));
449
- ctx.log.info(`Installed agent ${name} to ${filePath}`);
450
- },
451
- async remove(name) {
452
- const filePath = path.join(os.homedir(), '.config', 'my-runtime', 'agents', `${name}.yaml`);
453
- fs.unlinkSync(filePath);
454
- }
455
- }
456
- ```
457
-
458
- ### session.listByDirectory(按目录列出会话)
459
-
460
- ```typescript
461
- listByDirectory?(directory: string, limit?: number): Promise<SessionInfo[]>;
462
- ```
463
-
464
- **用途:** 直读 runtime 私有存储(如 SQLite),按项目目录列出会话。解决 `session.list` 按 `project_id` 过滤时隐藏 worktree 会话的问题。
465
-
466
- **SessionInfo 形状:**
467
- ```typescript
468
- interface SessionInfo {
469
- id: string;
470
- projectID: string;
471
- directory: string;
472
- title: string;
473
- metadata?: Record<string, unknown>;
474
- time: { created: number; updated: number };
475
- }
476
- ```
477
-
478
- **示例:**
479
- ```javascript
480
- session: {
481
- // ...其他方法...
482
- async listByDirectory(directory, limit = 200) {
483
- // 直读 SQLite 或文件系统
484
- const db = new DatabaseSync(dbPath, { readOnly: true });
485
- const rows = db.prepare(
486
- `SELECT id, project_id, directory, title, metadata, time_created, time_updated
487
- FROM session WHERE directory LIKE ? ORDER BY time_updated DESC LIMIT ?`
488
- ).all(`${directory}%`, limit);
489
- return rows.map(row => ({
490
- id: row.id,
491
- projectID: row.project_id,
492
- directory: row.directory,
493
- title: row.title,
494
- metadata: row.metadata ?? undefined,
495
- time: { created: row.time_created, updated: row.time_updated },
496
- }));
497
- }
498
- }
499
- ```
500
-
501
- ## 常见陷阱
502
-
503
- ### 1. 忘记刷新插件
504
- 修改 `.js` 文件后需要重扫或重启 gateway:
505
- ```bash
506
- # 方式 A:热重扫(推荐,不中断服务)
507
- curl -X POST http://localhost:3000/api/runtime/reload
508
- # 然后切换到新插件
509
- curl -X POST http://localhost:3000/api/runtime/switch -H 'Content-Type: application/json' -d '{"plugin":"my-runtime"}'
510
-
511
- # 方式 B:重启 gateway
512
- mafw restart
513
- ```
514
-
515
- ### 2. 能力声明与实际实现不匹配
516
- 声明了 `eventStream: true` 但 `global.event()` 未实现 → 事件订阅失败。
517
-
518
- **规则:** 声明的能力必须有对应实现;未实现的能力声明为 `false`。
519
-
520
- ### 3. 事件形状不兼容
521
- 自定义事件形状与 `normalizeOpencodeEvent()` 不兼容 → 归一化失败。
522
-
523
- **解决:**
524
- - 优先让事件形状接近 opencode(见"事件归一化"章节)
525
- - 或写新归一化器并修改 `index.ts` 的事件分发
526
-
527
- ### 4. 忽略 external 字段
528
- `external: true`(默认)→ gateway 不 spawn 进程,仅做健康探测。
529
- `external: false` → gateway 尝试 spawn/kill 进程(仅内置 opencode 使用)。
530
-
531
- **规则:** 自定义插件保持 `external: true`(或不声明)。
532
-
533
- ### 5. pluginConfig 路径错误
534
- `ctx.pluginConfig("my-runtime")` 读取 `config.yaml` 的 `runtime.pluginConfig.my-runtime` 段。
535
-
536
- **正确配置:**
537
- ```yaml
538
- runtime:
539
- plugin: my-runtime
540
- pluginConfig:
541
- my-runtime: # 键名必须与 name 匹配
542
- key: value
543
- ```
544
-
545
- ## 调试技巧
546
-
547
- ### 查看插件扫描状态
548
- ```bash
549
- curl http://localhost:3000/api/runtime
550
- ```
551
-
552
- 返回示例:
553
- ```json
554
- {
555
- "active": {
556
- "name": "my-runtime",
557
- "capabilities": {
558
- "sessionApi": true,
559
- "promptWhileBusy": true,
560
- "eventStream": true,
561
- "nativeApprovals": false,
562
- "providerConfigApi": false,
563
- "perLlmCallTransform": false,
564
- "sessionStorageApi": false,
565
- "agentConfigApi": false
566
- }
567
- },
568
- "plugins": [
569
- {
570
- "file": "my-runtime.js",
571
- "name": "my-runtime",
572
- "status": "ok",
573
- "capabilities": {...}
574
- }
575
- ]
576
- }
577
- ```
578
-
579
- ### 查看 gateway 日志
580
- ```bash
581
- mafw logs
582
- ```
583
-
584
- 关注:
585
- - `[RuntimePluginLoader] Loaded my-runtime.js (my-runtime)` — 加载成功
586
- - `[Runtime] using plugin runtime 'my-runtime'` — 激活成功
587
- - `[Runtime] plugin 'my-runtime' createRuntime failed: ...` — 工厂函数异常
588
-
589
- ### 健康检查
590
- ```bash
591
- curl http://localhost:3000/health
592
- ```
593
-
594
- 返回 `{"status":"ok"}` 表示 gateway 正常运行。若插件的 `healthCheck()` 返回 `false`,gateway 会记录警告日志。
595
-
596
- ## 完整示例:接入自定义 LLM 服务
597
-
598
- ```javascript
599
- // ~/.mafw/runtime-plugins/custom-llm.js
600
- const http = require('http');
601
-
602
- module.exports = {
603
- name: "custom-llm",
604
- capabilities: {
605
- eventStream: false, // 无实时事件流
606
- nativeApprovals: false,
607
- providerConfigApi: false,
608
- perLlmCallTransform: false,
609
- sessionStorageApi: false,
610
- agentConfigApi: false,
611
- },
612
- external: true,
613
-
614
- async createRuntime(ctx) {
615
- const cfg = ctx.pluginConfig("custom-llm");
616
- const baseUrl = cfg.baseUrl || "http://localhost:9000";
617
- const apiKey = cfg.apiKey || process.env.CUSTOM_LLM_API_KEY;
618
-
619
- const headers = apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {};
620
-
621
- // 内存会话存储(生产环境应持久化)
622
- const sessions = new Map();
623
-
624
- return {
625
- name: "custom-llm",
626
- capabilities: {
627
- eventStream: false,
628
- nativeApprovals: false,
629
- providerConfigApi: false,
630
- perLlmCallTransform: false,
631
- sessionStorageApi: false,
632
- agentConfigApi: false,
633
- },
634
-
635
- session: {
636
- async create(opts) {
637
- const id = `sess_${Date.now()}_${Math.random().toString(36).slice(2)}`;
638
- sessions.set(id, { id, directory: opts.directory, messages: [], created: Date.now() });
639
- return { id };
640
- },
641
-
642
- async promptAsync(opts) {
643
- const sess = sessions.get(opts.sessionID);
644
- if (!sess) return { error: "session not found" };
645
-
646
- const message = opts.message || opts.parts?.[0]?.text;
647
- sess.messages.push({ role: 'user', content: message });
648
-
649
- try {
650
- const res = await ctx.fetch(`${baseUrl}/v1/chat/completions`, {
651
- method: 'POST',
652
- headers: { ...headers, 'Content-Type': 'application/json' },
653
- body: JSON.stringify({
654
- model: cfg.model || 'custom-model',
655
- messages: sess.messages,
656
- }),
657
- });
658
- const data = await res.json();
659
- const reply = data.choices?.[0]?.message?.content || '';
660
- sess.messages.push({ role: 'assistant', content: reply });
661
- } catch (err) {
662
- ctx.log.error(`[custom-llm] prompt failed: ${err.message}`);
663
- }
664
- },
665
-
666
- async prompt(opts) {
667
- await this.promptAsync(opts);
668
- const sess = sessions.get(opts.sessionID);
669
- const lastMsg = sess?.messages[sess.messages.length - 1];
670
- return { parts: [{ type: 'text', text: lastMsg?.content || '' }] };
671
- },
672
-
673
- async messages(opts) {
674
- const sess = sessions.get(opts.sessionID);
675
- if (!sess) return { data: [] };
676
- return {
677
- data: sess.messages.map((m, i) => ({
678
- id: `${opts.sessionID}_${i}`,
679
- role: m.role,
680
- parts: [{ type: 'text', text: m.content }],
681
- })),
682
- };
683
- },
684
-
685
- async get({ sessionID }) {
686
- return sessions.get(sessionID) || null;
687
- },
688
-
689
- async delete({ sessionID }) {
690
- sessions.delete(sessionID);
691
- },
692
-
693
- async abort({ sessionID }) {
694
- // 无长时间任务,忽略
695
- },
696
-
697
- async list() {
698
- return [...sessions.values()].map(s => ({
699
- id: s.id,
700
- title: s.messages[0]?.content?.slice(0, 50) || 'New session',
701
- time: { created: s.created, updated: Date.now() },
702
- }));
703
- },
704
-
705
- async todo({ sessionID }) { return []; },
706
- async children({ sessionID }) { return []; },
707
- async summarize(opts) { return {}; },
708
- },
709
-
710
- global: {
711
- async event() {
712
- // 无事件流,返回空流
713
- return { stream: (async function*(){})() };
714
- },
715
- },
716
-
717
- provider: {
718
- async list() {
719
- return { all: ['custom-llm'], connected: ['custom-llm'], default: { chat: 'custom-llm' } };
720
- },
721
- },
722
-
723
- app: { async agents() { return []; } },
724
- config: { async get() { return {}; }, async update(c) { return c; } },
725
-
726
- getBaseUrl() { return baseUrl; },
727
-
728
- async healthCheck() {
729
- try {
730
- const res = await ctx.fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(3000) });
731
- return res.ok;
732
- } catch {
733
- return false;
734
- }
735
- },
736
-
737
- credentials: {
738
- getApiKey(provider) {
739
- if (provider === 'custom-llm') return apiKey;
740
- return null;
741
- },
742
- },
743
- };
744
- },
745
- };
746
- ```
747
-
748
- **激活:**
749
- ```yaml
750
- # ~/.mafw/config.yaml
751
- runtime:
752
- plugin: custom-llm
753
- pluginConfig:
754
- custom-llm:
755
- baseUrl: "http://localhost:9000"
756
- apiKey: "sk-xxx"
757
- model: "custom-model-v1"
758
- ```
759
-
760
- ```bash
761
- mafw restart
762
- curl http://localhost:3000/api/runtime
763
- ```
764
-
765
- ## 架构文档
766
-
767
- - **契约定义**:`gateway/src/runtime/contract.ts`
768
- - **插件加载器**:`gateway/src/runtime/loader.ts`
769
- - **事件归一化**:`gateway/src/runtime/normalize.ts`
770
- - **参考实现**:`gateway/src/runtime/opencode-runtime.ts`
771
- - **Agent 定义模型**:`gateway/src/runtime/agent-definition.ts`
772
- - **Gateway 激活逻辑**:`gateway/src/index.ts:814`(`createRuntime()` 方法)
773
- - **能力守卫**:`gateway/src/index.ts:800`(`capGuard()` 方法)
774
-
775
- ## 总结
776
-
777
- 编写 MAFW runtime 插件的核心步骤:
778
-
779
- 1. **理解能力分级**(Tier 0/1/2),选择需要的能力
780
- 2. **编写 CJS 插件**(`module.exports`),声明能力 + 实现 `createRuntime(ctx)`
781
- 3. **处理事件归一化**(优先兼容 opencode 事件形状)
782
- 4. **激活与测试**(config.yaml 或环境变量,热切换或重启 gateway,检查 `/api/runtime`)
783
- 5. **参考内置实现**(`opencode-runtime.ts` 是完整的 Tier 2 参考)
784
-
785
- **关键原则:**
786
- - 能力自声明 + fail-open 降级
787
- - 插件文件修改后需 `POST /api/runtime/reload` 重扫或重启 gateway;运行时切换可热切换
788
- - 插件失败自动回退 opencode
789
- - 事件形状尽量兼容 opencode 归一化器
790
-
791
- 遵循这些原则,你的 runtime 插件可以无缝接入 MAFW gateway,享受记忆系统、自动化、桌面 UI 等全套功能。