@symerian/symi 3.0.17 → 3.0.19

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 (259) hide show
  1. package/dist/{audio-preflight-CBDFctZN.js → audio-preflight-BfmZbg4Y.js} +4 -4
  2. package/dist/{audio-preflight-gsZSpG-6.js → audio-preflight-DcuC-liM.js} +4 -4
  3. package/dist/build-info.json +3 -3
  4. package/dist/bundled/boot-md/handler.js +8 -8
  5. package/dist/bundled/session-memory/handler.js +7 -7
  6. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  7. package/dist/{chrome-nPMY1XTJ.js → chrome-Bo7cbvFK.js} +5 -5
  8. package/dist/{chrome-BjVab8gM.js → chrome-DYp18Q0t.js} +5 -5
  9. package/dist/{deliver-D-QFqm31.js → deliver-ChSIbiMM.js} +1 -1
  10. package/dist/{deliver-B4-bcot9.js → deliver-DEgRQM4J.js} +1 -1
  11. package/dist/extensionAPI.js +7 -7
  12. package/dist/{image-CDwtQjmt.js → image-Bx-hvoNJ.js} +1 -1
  13. package/dist/{image-CcS-vzTA.js → image-CQl_mjWk.js} +1 -1
  14. package/dist/llm-slug-generator.js +7 -7
  15. package/dist/{manager-BnEdHzmO.js → manager-D_pn0urG.js} +1 -1
  16. package/dist/{manager-09r0qPze.js → manager-YQxK2t0C.js} +1 -1
  17. package/dist/{pi-embedded-CWsY69-4.js → pi-embedded-CLw_ZzEZ.js} +16 -16
  18. package/dist/{pi-embedded-helpers-BBMy-lqr.js → pi-embedded-helpers-B5I53aw6.js} +4 -4
  19. package/dist/{pi-embedded-helpers-ChEYbgVj.js → pi-embedded-helpers-sUAEIC9X.js} +4 -4
  20. package/dist/plugin-sdk/{accounts-BfyWsC_i.js → accounts-CWFytwbR.js} +3 -3
  21. package/dist/plugin-sdk/{active-listener-DcJW7xAT.js → active-listener-BkZ4jHrL.js} +2 -2
  22. package/dist/plugin-sdk/{agent-scope-ChbGV6of.js → agent-scope-C9gfY_Gk.js} +2 -2
  23. package/dist/plugin-sdk/{audio-preflight-D3GtNLqW.js → audio-preflight-HKbdzXLZ.js} +21 -21
  24. package/dist/plugin-sdk/{bindings-CN2Qmefj.js → bindings-BaKIqPPy.js} +2 -2
  25. package/dist/plugin-sdk/{channel-web-DTyqujjA.js → channel-web-D5nWiTH1.js} +18 -18
  26. package/dist/plugin-sdk/{chrome-BKzAKr3K.js → chrome-klTSnz-9.js} +3 -3
  27. package/dist/plugin-sdk/{chunk-DhDkBujV.js → chunk-BbrYSny_.js} +1 -1
  28. package/dist/plugin-sdk/{command-format-CVrYFyZS.js → command-format-BN6tyZt6.js} +1 -1
  29. package/dist/plugin-sdk/{commands-registry-17yfZkHZ.js → commands-registry-CTzKKtY6.js} +4 -4
  30. package/dist/plugin-sdk/{config-7wk65zKC.js → config-Crv2qEdJ.js} +9 -9
  31. package/dist/plugin-sdk/{consolidate-exbAW0ml.js → consolidate-DT1QH65Q.js} +2 -2
  32. package/dist/plugin-sdk/{deliver-TxAcw7J5.js → deliver-7rOvAlrc.js} +12 -12
  33. package/dist/plugin-sdk/{diagnostic-Debx4frd.js → diagnostic-0nsxhWp7.js} +1 -1
  34. package/dist/plugin-sdk/{fs-safe-wBYbAkJF.js → fs-safe-DfWYBeWF.js} +1 -1
  35. package/dist/plugin-sdk/{gemini-auth-7U2pm2Ky.js → gemini-auth-C0N0_u49.js} +1 -1
  36. package/dist/plugin-sdk/{image-BtDVmYA5.js → image-WOSl2apK.js} +4 -4
  37. package/dist/plugin-sdk/index.js +43 -43
  38. package/dist/plugin-sdk/{ir-CKMvRrGW.js → ir-9J84MTls.js} +4 -4
  39. package/dist/plugin-sdk/{local-roots-c_gaPs01.js → local-roots-OLRDbvyY.js} +3 -3
  40. package/dist/plugin-sdk/{login-DUym1Jy0.js → login-C7x4q0i2.js} +7 -7
  41. package/dist/plugin-sdk/{login-qr-B-WBdvrX.js → login-qr-Dv5_MoAW.js} +9 -9
  42. package/dist/plugin-sdk/{manager-B71SCzos.js → manager-C83tK17x.js} +8 -8
  43. package/dist/plugin-sdk/{manifest-registry-Dnic6Chh.js → manifest-registry-CJMV-PI7.js} +1 -1
  44. package/dist/plugin-sdk/{markdown-tables-Dur7OTlM.js → markdown-tables-DXNKz5y_.js} +1 -1
  45. package/dist/plugin-sdk/{message-channel-BrAhJJV_.js → message-channel-aGy1HbQQ.js} +1 -1
  46. package/dist/plugin-sdk/{model-selection-B9qaVQSJ.js → model-selection-C-3-tpe7.js} +4 -4
  47. package/dist/plugin-sdk/{outbound-DB1wDM8b.js → outbound-DquCeSy5.js} +6 -6
  48. package/dist/plugin-sdk/{pi-auth-json-ZO118hoy.js → pi-auth-json-D9PDCXGn.js} +1 -1
  49. package/dist/plugin-sdk/{pi-embedded-helpers-s_U0Un7j.js → pi-embedded-helpers-D3ygfH7l.js} +16 -16
  50. package/dist/plugin-sdk/{plugins-DF81oSaI.js → plugins-DOwnSg9D.js} +4 -4
  51. package/dist/plugin-sdk/{pw-ai-CTwP02uv.js → pw-ai-rlengLjb.js} +8 -8
  52. package/dist/plugin-sdk/{qmd-manager-CBaSGant.js → qmd-manager-BzxFjRFa.js} +4 -4
  53. package/dist/plugin-sdk/{registry-CZVURNhF.js → registry-5iFfixlB.js} +2 -2
  54. package/dist/plugin-sdk/{replies-hwRbkU3z.js → replies-BXOzO_H5.js} +7 -7
  55. package/dist/plugin-sdk/{reply-prefix-CaXmzZlx.js → reply-prefix-INAKTqCU.js} +1 -1
  56. package/dist/plugin-sdk/{resolve-outbound-target-fxVSOBmk.js → resolve-outbound-target-DvbxHtqp.js} +2 -2
  57. package/dist/plugin-sdk/{resolve-route-ClCyiOeu.js → resolve-route-URXlY3AK.js} +3 -3
  58. package/dist/plugin-sdk/{runner-Cq5jvwQ7.js → runner-Bv0_DWoH.js} +9 -9
  59. package/dist/plugin-sdk/{session-B_TkB65Y.js → session-C3r8l7ou.js} +4 -4
  60. package/dist/plugin-sdk/{skill-commands-0LF9HTGr.js → skill-commands-KjLUGIdZ.js} +5 -5
  61. package/dist/plugin-sdk/{skills-BIT_O7J0.js → skills-BrsD4L5c.js} +7 -7
  62. package/dist/plugin-sdk/{sqlite-Bx5Y5U5X.js → sqlite-CjW7ME1H.js} +1 -1
  63. package/dist/plugin-sdk/{subsystem-CXqYeDy-.js → subsystem-DcOg1xJr.js} +1 -1
  64. package/dist/plugin-sdk/{synthesis-DtsYAj1E.js → synthesis-CY7YAasV.js} +38 -38
  65. package/dist/plugin-sdk/{target-errors-B8mokOeH.js → target-errors-BVWJGWFq.js} +2 -2
  66. package/dist/plugin-sdk/{thinking-Ca0DhqzO.js → thinking-CtsTDPOi.js} +3 -3
  67. package/dist/plugin-sdk/{tokens-CvlONEqh.js → tokens-8lqOTZCB.js} +1 -1
  68. package/dist/plugin-sdk/{tool-images-DpBaWEHT.js → tool-images-Cl_rGIUZ.js} +2 -2
  69. package/dist/plugin-sdk/{tool-loop-detection-BOvUFa0f.js → tool-loop-detection-Da4WUT_P.js} +2 -2
  70. package/dist/plugin-sdk/{unified-runner-CnM7lyNd.js → unified-runner-nwMnsZyj.js} +60 -60
  71. package/dist/plugin-sdk/web-BlweOZDp.js +54 -0
  72. package/dist/plugin-sdk/{whatsapp-actions-CvnfsFJm.js → whatsapp-actions-DpfaGYs7.js} +21 -21
  73. package/dist/{pw-ai-BW8_KeDf.js → pw-ai-BqxJG-Wh.js} +1 -1
  74. package/dist/{pw-ai-j9IE1K0-.js → pw-ai-C-NSGye0.js} +1 -1
  75. package/dist/{runner-8ALr2UII.js → runner-COGFTeDw.js} +1 -1
  76. package/dist/{runner-C4-9kFdR.js → runner-DhCi2lT1.js} +1 -1
  77. package/dist/{synthesis-Cph3LhA1.js → synthesis-CXZu24Vx.js} +7 -7
  78. package/dist/{synthesis-Cus0A2dL.js → synthesis-DrPxcMlQ.js} +7 -7
  79. package/dist/{unified-runner-CX80YMTk.js → unified-runner-iByUazvW.js} +16 -16
  80. package/dist/{web-ChozvJ7I.js → web-EsMQBIYf.js} +7 -7
  81. package/dist/{web-DFlsbXmQ.js → web-PPg5y6xI.js} +7 -7
  82. package/package.json +1 -1
  83. package/dist/plugin-sdk/web-CIPJBHAU.js +0 -54
  84. package/extensions/copilot-proxy/README.md +0 -24
  85. package/extensions/copilot-proxy/index.ts +0 -154
  86. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  87. package/extensions/copilot-proxy/package.json +0 -15
  88. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  89. package/extensions/device-pair/index.ts +0 -642
  90. package/extensions/device-pair/symi.plugin.json +0 -20
  91. package/extensions/diagnostics-otel/index.ts +0 -15
  92. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  93. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  94. package/extensions/diagnostics-otel/package.json +0 -27
  95. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  96. package/extensions/diagnostics-otel/src/service.ts +0 -666
  97. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  98. package/extensions/google-antigravity-auth/README.md +0 -24
  99. package/extensions/google-antigravity-auth/index.ts +0 -424
  100. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  101. package/extensions/google-antigravity-auth/package.json +0 -15
  102. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  103. package/extensions/google-gemini-cli-auth/README.md +0 -35
  104. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  105. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  106. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  107. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  108. package/extensions/google-gemini-cli-auth/package.json +0 -15
  109. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  110. package/extensions/learning-loop/index.ts +0 -159
  111. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  112. package/extensions/learning-loop/package.json +0 -18
  113. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  114. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  115. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  116. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  117. package/extensions/learning-loop/src/db.ts +0 -583
  118. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  119. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  120. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  121. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  122. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  123. package/extensions/learning-loop/src/hooks.ts +0 -244
  124. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  125. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  126. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  127. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  128. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  129. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  130. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  131. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  132. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  133. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  134. package/extensions/learning-loop/src/schema.ts +0 -176
  135. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  136. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  137. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  138. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  139. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  140. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  141. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  142. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  143. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  144. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  145. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  146. package/extensions/learning-loop/src/types.ts +0 -281
  147. package/extensions/learning-loop/symi.plugin.json +0 -46
  148. package/extensions/llm-task/README.md +0 -97
  149. package/extensions/llm-task/index.ts +0 -6
  150. package/extensions/llm-task/package.json +0 -12
  151. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  152. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  153. package/extensions/llm-task/symi.plugin.json +0 -21
  154. package/extensions/memory-lancedb/config.ts +0 -161
  155. package/extensions/memory-lancedb/index.test.ts +0 -330
  156. package/extensions/memory-lancedb/index.ts +0 -670
  157. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  158. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  159. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  160. package/extensions/memory-lancedb/package.json +0 -20
  161. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  162. package/extensions/minimax-portal-auth/README.md +0 -33
  163. package/extensions/minimax-portal-auth/index.ts +0 -161
  164. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  165. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  166. package/extensions/minimax-portal-auth/package.json +0 -15
  167. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  168. package/extensions/model-equalizer/index.ts +0 -80
  169. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  170. package/extensions/model-equalizer/src/detection.ts +0 -62
  171. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  172. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  173. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  174. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  175. package/extensions/model-equalizer/src/types.ts +0 -24
  176. package/extensions/model-equalizer/symi.plugin.json +0 -12
  177. package/extensions/phone-control/index.ts +0 -421
  178. package/extensions/phone-control/symi.plugin.json +0 -10
  179. package/extensions/pipeline/README.md +0 -75
  180. package/extensions/pipeline/SKILL.md +0 -97
  181. package/extensions/pipeline/index.ts +0 -18
  182. package/extensions/pipeline/package.json +0 -11
  183. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  184. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  185. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  186. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  187. package/extensions/pipeline/symi.plugin.json +0 -10
  188. package/extensions/qwen-portal-auth/README.md +0 -24
  189. package/extensions/qwen-portal-auth/index.ts +0 -134
  190. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  191. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  192. package/extensions/talk-voice/index.ts +0 -150
  193. package/extensions/talk-voice/symi.plugin.json +0 -10
  194. package/extensions/thread-ownership/index.test.ts +0 -180
  195. package/extensions/thread-ownership/index.ts +0 -133
  196. package/extensions/thread-ownership/symi.plugin.json +0 -28
  197. package/skills/1password/SKILL.md +0 -71
  198. package/skills/1password/references/cli-examples.md +0 -29
  199. package/skills/1password/references/get-started.md +0 -17
  200. package/skills/apple-notes/SKILL.md +0 -78
  201. package/skills/apple-reminders/SKILL.md +0 -119
  202. package/skills/bear-notes/SKILL.md +0 -108
  203. package/skills/blogwatcher/SKILL.md +0 -70
  204. package/skills/blucli/SKILL.md +0 -48
  205. package/skills/bluebubbles/SKILL.md +0 -132
  206. package/skills/camsnap/SKILL.md +0 -46
  207. package/skills/canvas/SKILL.md +0 -204
  208. package/skills/connect-email/SKILL.md +0 -142
  209. package/skills/document-generation/SKILL.md +0 -83
  210. package/skills/eightctl/SKILL.md +0 -51
  211. package/skills/food-order/SKILL.md +0 -49
  212. package/skills/gemini/SKILL.md +0 -44
  213. package/skills/gh-issues/SKILL.md +0 -865
  214. package/skills/gifgrep/SKILL.md +0 -80
  215. package/skills/github/SKILL.md +0 -164
  216. package/skills/gog/SKILL.md +0 -117
  217. package/skills/goplaces/SKILL.md +0 -53
  218. package/skills/healthcheck/SKILL.md +0 -246
  219. package/skills/himalaya/SKILL.md +0 -258
  220. package/skills/himalaya/references/configuration.md +0 -184
  221. package/skills/himalaya/references/message-composition.md +0 -199
  222. package/skills/imsg/SKILL.md +0 -122
  223. package/skills/long-task/SKILL.md +0 -58
  224. package/skills/long-task/scripts/detach-task.sh +0 -187
  225. package/skills/nano-banana-pro/SKILL.md +0 -59
  226. package/skills/nano-banana-pro/scripts/generate_image.py +0 -184
  227. package/skills/nano-pdf/SKILL.md +0 -39
  228. package/skills/notion/SKILL.md +0 -173
  229. package/skills/obsidian/SKILL.md +0 -82
  230. package/skills/openai-image-gen/SKILL.md +0 -90
  231. package/skills/openai-image-gen/scripts/gen.py +0 -240
  232. package/skills/openai-whisper/SKILL.md +0 -39
  233. package/skills/openai-whisper-api/SKILL.md +0 -53
  234. package/skills/openai-whisper-api/scripts/transcribe.sh +0 -85
  235. package/skills/openhue/SKILL.md +0 -113
  236. package/skills/oracle/SKILL.md +0 -126
  237. package/skills/ordercli/SKILL.md +0 -79
  238. package/skills/peekaboo/SKILL.md +0 -191
  239. package/skills/reactions-extensive/SKILL.md +0 -30
  240. package/skills/reactions-minimal/SKILL.md +0 -31
  241. package/skills/safe-edit/SKILL.md +0 -51
  242. package/skills/sag/SKILL.md +0 -88
  243. package/skills/sherpa-onnx-tts/SKILL.md +0 -104
  244. package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +0 -178
  245. package/skills/songsee/SKILL.md +0 -50
  246. package/skills/sonoscli/SKILL.md +0 -66
  247. package/skills/spotify-player/SKILL.md +0 -65
  248. package/skills/symihub/SKILL.md +0 -78
  249. package/skills/things-mac/SKILL.md +0 -87
  250. package/skills/tmux/SKILL.md +0 -153
  251. package/skills/tmux/scripts/find-sessions.sh +0 -112
  252. package/skills/tmux/scripts/wait-for-text.sh +0 -83
  253. package/skills/trello/SKILL.md +0 -96
  254. package/skills/video-frames/SKILL.md +0 -47
  255. package/skills/video-frames/scripts/frame.sh +0 -81
  256. package/skills/voice-call/SKILL.md +0 -46
  257. package/skills/wacli/SKILL.md +0 -73
  258. package/skills/weather/SKILL.md +0 -113
  259. package/skills/xurl/SKILL.md +0 -462
@@ -1,134 +0,0 @@
1
- import {
2
- emptyPluginConfigSchema,
3
- type SymiPluginApi,
4
- type ProviderAuthContext,
5
- } from "symi/plugin-sdk";
6
- import { loginQwenPortalOAuth } from "./oauth.js";
7
-
8
- const PROVIDER_ID = "qwen-portal";
9
- const PROVIDER_LABEL = "Qwen";
10
- const DEFAULT_MODEL = "qwen-portal/coder-model";
11
- const DEFAULT_BASE_URL = "https://portal.qwen.ai/v1";
12
- const DEFAULT_CONTEXT_WINDOW = 128000;
13
- const DEFAULT_MAX_TOKENS = 8192;
14
- const OAUTH_PLACEHOLDER = "qwen-oauth";
15
-
16
- function normalizeBaseUrl(value: string | undefined): string {
17
- const raw = value?.trim() || DEFAULT_BASE_URL;
18
- const withProtocol = raw.startsWith("http") ? raw : `https://${raw}`;
19
- return withProtocol.endsWith("/v1") ? withProtocol : `${withProtocol.replace(/\/+$/, "")}/v1`;
20
- }
21
-
22
- function buildModelDefinition(params: {
23
- id: string;
24
- name: string;
25
- input: Array<"text" | "image">;
26
- }) {
27
- return {
28
- id: params.id,
29
- name: params.name,
30
- reasoning: false,
31
- input: params.input,
32
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
33
- contextWindow: DEFAULT_CONTEXT_WINDOW,
34
- maxTokens: DEFAULT_MAX_TOKENS,
35
- };
36
- }
37
-
38
- const qwenPortalPlugin = {
39
- id: "qwen-portal-auth",
40
- name: "Qwen OAuth",
41
- description: "OAuth flow for Qwen (free-tier) models",
42
- configSchema: emptyPluginConfigSchema(),
43
- register(api: SymiPluginApi) {
44
- api.registerProvider({
45
- id: PROVIDER_ID,
46
- label: PROVIDER_LABEL,
47
- docsPath: "/providers/qwen",
48
- aliases: ["qwen"],
49
- auth: [
50
- {
51
- id: "device",
52
- label: "Qwen OAuth",
53
- hint: "Device code login",
54
- kind: "device_code",
55
- run: async (ctx: ProviderAuthContext) => {
56
- const progress = ctx.prompter.progress("Starting Qwen OAuth…");
57
- try {
58
- const result = await loginQwenPortalOAuth({
59
- openUrl: ctx.openUrl,
60
- note: ctx.prompter.note,
61
- progress,
62
- });
63
-
64
- progress.stop("Qwen OAuth complete");
65
-
66
- const profileId = `${PROVIDER_ID}:default`;
67
- const baseUrl = normalizeBaseUrl(result.resourceUrl);
68
-
69
- return {
70
- profiles: [
71
- {
72
- profileId,
73
- credential: {
74
- type: "oauth",
75
- provider: PROVIDER_ID,
76
- access: result.access,
77
- refresh: result.refresh,
78
- expires: result.expires,
79
- },
80
- },
81
- ],
82
- configPatch: {
83
- models: {
84
- providers: {
85
- [PROVIDER_ID]: {
86
- baseUrl,
87
- apiKey: OAUTH_PLACEHOLDER,
88
- api: "openai-completions",
89
- models: [
90
- buildModelDefinition({
91
- id: "coder-model",
92
- name: "Qwen Coder",
93
- input: ["text"],
94
- }),
95
- buildModelDefinition({
96
- id: "vision-model",
97
- name: "Qwen Vision",
98
- input: ["text", "image"],
99
- }),
100
- ],
101
- },
102
- },
103
- },
104
- agents: {
105
- defaults: {
106
- models: {
107
- "qwen-portal/coder-model": { alias: "qwen" },
108
- "qwen-portal/vision-model": {},
109
- },
110
- },
111
- },
112
- },
113
- defaultModel: DEFAULT_MODEL,
114
- notes: [
115
- "Qwen OAuth tokens auto-refresh. Re-run login if refresh fails or access is revoked.",
116
- `Base URL defaults to ${DEFAULT_BASE_URL}. Override models.providers.${PROVIDER_ID}.baseUrl if needed.`,
117
- ],
118
- };
119
- } catch (err) {
120
- progress.stop("Qwen OAuth failed");
121
- await ctx.prompter.note(
122
- "If OAuth fails, verify your Qwen account has portal access and try again.",
123
- "Qwen OAuth",
124
- );
125
- throw err;
126
- }
127
- },
128
- },
129
- ],
130
- });
131
- },
132
- };
133
-
134
- export default qwenPortalPlugin;
@@ -1,190 +0,0 @@
1
- import { createHash, randomBytes, randomUUID } from "node:crypto";
2
-
3
- const QWEN_OAUTH_BASE_URL = "https://chat.qwen.ai";
4
- const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/device/code`;
5
- const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`;
6
- const QWEN_OAUTH_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
7
- const QWEN_OAUTH_SCOPE = "openid profile email model.completion";
8
- const QWEN_OAUTH_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
9
-
10
- export type QwenDeviceAuthorization = {
11
- device_code: string;
12
- user_code: string;
13
- verification_uri: string;
14
- verification_uri_complete?: string;
15
- expires_in: number;
16
- interval?: number;
17
- };
18
-
19
- export type QwenOAuthToken = {
20
- access: string;
21
- refresh: string;
22
- expires: number;
23
- resourceUrl?: string;
24
- };
25
-
26
- type TokenPending = { status: "pending"; slowDown?: boolean };
27
-
28
- type DeviceTokenResult =
29
- | { status: "success"; token: QwenOAuthToken }
30
- | TokenPending
31
- | { status: "error"; message: string };
32
-
33
- function toFormUrlEncoded(data: Record<string, string>): string {
34
- return Object.entries(data)
35
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
36
- .join("&");
37
- }
38
-
39
- function generatePkce(): { verifier: string; challenge: string } {
40
- const verifier = randomBytes(32).toString("base64url");
41
- const challenge = createHash("sha256").update(verifier).digest("base64url");
42
- return { verifier, challenge };
43
- }
44
-
45
- async function requestDeviceCode(params: { challenge: string }): Promise<QwenDeviceAuthorization> {
46
- const response = await fetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, {
47
- method: "POST",
48
- headers: {
49
- "Content-Type": "application/x-www-form-urlencoded",
50
- Accept: "application/json",
51
- "x-request-id": randomUUID(),
52
- },
53
- body: toFormUrlEncoded({
54
- client_id: QWEN_OAUTH_CLIENT_ID,
55
- scope: QWEN_OAUTH_SCOPE,
56
- code_challenge: params.challenge,
57
- code_challenge_method: "S256",
58
- }),
59
- });
60
-
61
- if (!response.ok) {
62
- const text = await response.text();
63
- throw new Error(`Qwen device authorization failed: ${text || response.statusText}`);
64
- }
65
-
66
- const payload = (await response.json()) as QwenDeviceAuthorization & { error?: string };
67
- if (!payload.device_code || !payload.user_code || !payload.verification_uri) {
68
- throw new Error(
69
- payload.error ??
70
- "Qwen device authorization returned an incomplete payload (missing user_code or verification_uri).",
71
- );
72
- }
73
- return payload;
74
- }
75
-
76
- async function pollDeviceToken(params: {
77
- deviceCode: string;
78
- verifier: string;
79
- }): Promise<DeviceTokenResult> {
80
- const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, {
81
- method: "POST",
82
- headers: {
83
- "Content-Type": "application/x-www-form-urlencoded",
84
- Accept: "application/json",
85
- },
86
- body: toFormUrlEncoded({
87
- grant_type: QWEN_OAUTH_GRANT_TYPE,
88
- client_id: QWEN_OAUTH_CLIENT_ID,
89
- device_code: params.deviceCode,
90
- code_verifier: params.verifier,
91
- }),
92
- });
93
-
94
- if (!response.ok) {
95
- let payload: { error?: string; error_description?: string } | undefined;
96
- try {
97
- payload = (await response.json()) as { error?: string; error_description?: string };
98
- } catch {
99
- const text = await response.text();
100
- return { status: "error", message: text || response.statusText };
101
- }
102
-
103
- if (payload?.error === "authorization_pending") {
104
- return { status: "pending" };
105
- }
106
-
107
- if (payload?.error === "slow_down") {
108
- return { status: "pending", slowDown: true };
109
- }
110
-
111
- return {
112
- status: "error",
113
- message: payload?.error_description || payload?.error || response.statusText,
114
- };
115
- }
116
-
117
- const tokenPayload = (await response.json()) as {
118
- access_token?: string | null;
119
- refresh_token?: string | null;
120
- expires_in?: number | null;
121
- token_type?: string;
122
- resource_url?: string;
123
- };
124
-
125
- if (!tokenPayload.access_token || !tokenPayload.refresh_token || !tokenPayload.expires_in) {
126
- return { status: "error", message: "Qwen OAuth returned incomplete token payload." };
127
- }
128
-
129
- return {
130
- status: "success",
131
- token: {
132
- access: tokenPayload.access_token,
133
- refresh: tokenPayload.refresh_token,
134
- expires: Date.now() + tokenPayload.expires_in * 1000,
135
- resourceUrl: tokenPayload.resource_url,
136
- },
137
- };
138
- }
139
-
140
- export async function loginQwenPortalOAuth(params: {
141
- openUrl: (url: string) => Promise<void>;
142
- note: (message: string, title?: string) => Promise<void>;
143
- progress: { update: (message: string) => void; stop: (message?: string) => void };
144
- }): Promise<QwenOAuthToken> {
145
- const { verifier, challenge } = generatePkce();
146
- const device = await requestDeviceCode({ challenge });
147
- const verificationUrl = device.verification_uri_complete || device.verification_uri;
148
-
149
- await params.note(
150
- [
151
- `Open ${verificationUrl} to approve access.`,
152
- `If prompted, enter the code ${device.user_code}.`,
153
- ].join("\n"),
154
- "Qwen OAuth",
155
- );
156
-
157
- try {
158
- await params.openUrl(verificationUrl);
159
- } catch {
160
- // Fall back to manual copy/paste if browser open fails.
161
- }
162
-
163
- const start = Date.now();
164
- let pollIntervalMs = device.interval ? device.interval * 1000 : 2000;
165
- const timeoutMs = device.expires_in * 1000;
166
-
167
- while (Date.now() - start < timeoutMs) {
168
- params.progress.update("Waiting for Qwen OAuth approval…");
169
- const result = await pollDeviceToken({
170
- deviceCode: device.device_code,
171
- verifier,
172
- });
173
-
174
- if (result.status === "success") {
175
- return result.token;
176
- }
177
-
178
- if (result.status === "error") {
179
- throw new Error(`Qwen OAuth failed: ${result.message}`);
180
- }
181
-
182
- if (result.status === "pending" && result.slowDown) {
183
- pollIntervalMs = Math.min(pollIntervalMs * 1.5, 10000);
184
- }
185
-
186
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
187
- }
188
-
189
- throw new Error("Qwen OAuth timed out waiting for authorization.");
190
- }
@@ -1,9 +0,0 @@
1
- {
2
- "id": "qwen-portal-auth",
3
- "providers": ["qwen-portal"],
4
- "configSchema": {
5
- "type": "object",
6
- "additionalProperties": false,
7
- "properties": {}
8
- }
9
- }
@@ -1,150 +0,0 @@
1
- import type { SymiPluginApi } from "symi/plugin-sdk";
2
-
3
- type ElevenLabsVoice = {
4
- voice_id: string;
5
- name?: string;
6
- category?: string;
7
- description?: string;
8
- };
9
-
10
- function mask(s: string, keep: number = 6): string {
11
- const trimmed = s.trim();
12
- if (trimmed.length <= keep) {
13
- return "***";
14
- }
15
- return `${trimmed.slice(0, keep)}…`;
16
- }
17
-
18
- function isLikelyVoiceId(value: string): boolean {
19
- const v = value.trim();
20
- if (v.length < 10 || v.length > 64) {
21
- return false;
22
- }
23
- return /^[a-zA-Z0-9_-]+$/.test(v);
24
- }
25
-
26
- async function listVoices(apiKey: string): Promise<ElevenLabsVoice[]> {
27
- const res = await fetch("https://api.elevenlabs.io/v1/voices", {
28
- headers: {
29
- "xi-api-key": apiKey,
30
- },
31
- });
32
- if (!res.ok) {
33
- throw new Error(`ElevenLabs voices API error (${res.status})`);
34
- }
35
- const json = (await res.json()) as { voices?: ElevenLabsVoice[] };
36
- return Array.isArray(json.voices) ? json.voices : [];
37
- }
38
-
39
- function formatVoiceList(voices: ElevenLabsVoice[], limit: number): string {
40
- const sliced = voices.slice(0, Math.max(1, Math.min(limit, 50)));
41
- const lines: string[] = [];
42
- lines.push(`Voices: ${voices.length}`);
43
- lines.push("");
44
- for (const v of sliced) {
45
- const name = (v.name ?? "").trim() || "(unnamed)";
46
- const category = (v.category ?? "").trim();
47
- const meta = category ? ` · ${category}` : "";
48
- lines.push(`- ${name}${meta}`);
49
- lines.push(` id: ${v.voice_id}`);
50
- }
51
- if (voices.length > sliced.length) {
52
- lines.push("");
53
- lines.push(`(showing first ${sliced.length})`);
54
- }
55
- return lines.join("\n");
56
- }
57
-
58
- function findVoice(voices: ElevenLabsVoice[], query: string): ElevenLabsVoice | null {
59
- const q = query.trim();
60
- if (!q) {
61
- return null;
62
- }
63
- const lower = q.toLowerCase();
64
- const byId = voices.find((v) => v.voice_id === q);
65
- if (byId) {
66
- return byId;
67
- }
68
- const exactName = voices.find((v) => (v.name ?? "").trim().toLowerCase() === lower);
69
- if (exactName) {
70
- return exactName;
71
- }
72
- const partial = voices.find((v) => (v.name ?? "").trim().toLowerCase().includes(lower));
73
- return partial ?? null;
74
- }
75
-
76
- export default function register(api: SymiPluginApi) {
77
- api.registerCommand({
78
- name: "voice",
79
- description: "List/set ElevenLabs Talk voice (affects iOS Talk playback).",
80
- acceptsArgs: true,
81
- handler: async (ctx) => {
82
- const args = ctx.args?.trim() ?? "";
83
- const tokens = args.split(/\s+/).filter(Boolean);
84
- const action = (tokens[0] ?? "status").toLowerCase();
85
-
86
- const cfg = api.runtime.config.loadConfig();
87
- const apiKey = (cfg.talk?.apiKey ?? "").trim();
88
- if (!apiKey) {
89
- return {
90
- text:
91
- "Talk voice is not configured.\n\n" +
92
- "Missing: talk.apiKey (ElevenLabs API key).\n" +
93
- "Set it on the gateway, then retry.",
94
- };
95
- }
96
-
97
- const currentVoiceId = (cfg.talk?.voiceId ?? "").trim();
98
-
99
- if (action === "status") {
100
- return {
101
- text:
102
- "Talk voice status:\n" +
103
- `- talk.voiceId: ${currentVoiceId ? currentVoiceId : "(unset)"}\n` +
104
- `- talk.apiKey: ${mask(apiKey)}`,
105
- };
106
- }
107
-
108
- if (action === "list") {
109
- const limit = Number.parseInt(tokens[1] ?? "12", 10);
110
- const voices = await listVoices(apiKey);
111
- return { text: formatVoiceList(voices, Number.isFinite(limit) ? limit : 12) };
112
- }
113
-
114
- if (action === "set") {
115
- const query = tokens.slice(1).join(" ").trim();
116
- if (!query) {
117
- return { text: "Usage: /voice set <voiceId|name>" };
118
- }
119
- const voices = await listVoices(apiKey);
120
- const chosen = findVoice(voices, query);
121
- if (!chosen) {
122
- const hint = isLikelyVoiceId(query) ? query : `"${query}"`;
123
- return { text: `No voice found for ${hint}. Try: /voice list` };
124
- }
125
-
126
- const nextConfig = {
127
- ...cfg,
128
- talk: {
129
- ...cfg.talk,
130
- voiceId: chosen.voice_id,
131
- },
132
- };
133
- await api.runtime.config.writeConfigFile(nextConfig);
134
-
135
- const name = (chosen.name ?? "").trim() || "(unnamed)";
136
- return { text: `✅ Talk voice set to ${name}\n${chosen.voice_id}` };
137
- }
138
-
139
- return {
140
- text: [
141
- "Voice commands:",
142
- "",
143
- "/voice status",
144
- "/voice list [limit]",
145
- "/voice set <voiceId|name>",
146
- ].join("\n"),
147
- };
148
- },
149
- });
150
- }
@@ -1,10 +0,0 @@
1
- {
2
- "id": "talk-voice",
3
- "name": "Talk Voice",
4
- "description": "Manage Talk voice selection (list/set).",
5
- "configSchema": {
6
- "type": "object",
7
- "additionalProperties": false,
8
- "properties": {}
9
- }
10
- }
@@ -1,180 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import register from "./index.js";
3
-
4
- describe("thread-ownership plugin", () => {
5
- const hooks: Record<string, Function> = {};
6
- const api = {
7
- pluginConfig: {},
8
- config: {
9
- agents: {
10
- list: [{ id: "test-agent", default: true, identity: { name: "TestBot" } }],
11
- },
12
- },
13
- id: "thread-ownership",
14
- name: "Thread Ownership",
15
- logger: { info: vi.fn(), warn: vi.fn(), debug: vi.fn() },
16
- on: vi.fn((hookName: string, handler: Function) => {
17
- hooks[hookName] = handler;
18
- }),
19
- };
20
-
21
- let originalFetch: typeof globalThis.fetch;
22
-
23
- beforeEach(() => {
24
- vi.clearAllMocks();
25
- for (const key of Object.keys(hooks)) delete hooks[key];
26
-
27
- process.env.SLACK_FORWARDER_URL = "http://localhost:8750";
28
- process.env.SLACK_BOT_USER_ID = "U999";
29
-
30
- originalFetch = globalThis.fetch;
31
- globalThis.fetch = vi.fn() as unknown as typeof globalThis.fetch;
32
- });
33
-
34
- afterEach(() => {
35
- globalThis.fetch = originalFetch;
36
- delete process.env.SLACK_FORWARDER_URL;
37
- delete process.env.SLACK_BOT_USER_ID;
38
- vi.restoreAllMocks();
39
- });
40
-
41
- it("registers message_received and message_sending hooks", () => {
42
- register(api as any);
43
-
44
- expect(api.on).toHaveBeenCalledTimes(2);
45
- expect(api.on).toHaveBeenCalledWith("message_received", expect.any(Function));
46
- expect(api.on).toHaveBeenCalledWith("message_sending", expect.any(Function));
47
- });
48
-
49
- describe("message_sending", () => {
50
- beforeEach(() => {
51
- register(api as any);
52
- });
53
-
54
- it("allows non-slack channels", async () => {
55
- const result = await hooks.message_sending(
56
- { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" },
57
- { channelId: "telegram", conversationId: "C123" },
58
- );
59
-
60
- expect(result).toBeUndefined();
61
- expect(globalThis.fetch).not.toHaveBeenCalled();
62
- });
63
-
64
- it("allows top-level messages (no threadTs)", async () => {
65
- const result = await hooks.message_sending(
66
- { content: "hello", metadata: {}, to: "C123" },
67
- { channelId: "slack", conversationId: "C123" },
68
- );
69
-
70
- expect(result).toBeUndefined();
71
- expect(globalThis.fetch).not.toHaveBeenCalled();
72
- });
73
-
74
- it("claims ownership successfully", async () => {
75
- vi.mocked(globalThis.fetch).mockResolvedValue(
76
- new Response(JSON.stringify({ owner: "test-agent" }), { status: 200 }),
77
- );
78
-
79
- const result = await hooks.message_sending(
80
- { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" },
81
- { channelId: "slack", conversationId: "C123" },
82
- );
83
-
84
- expect(result).toBeUndefined();
85
- expect(globalThis.fetch).toHaveBeenCalledWith(
86
- "http://localhost:8750/api/v1/ownership/C123/1234.5678",
87
- expect.objectContaining({
88
- method: "POST",
89
- body: JSON.stringify({ agent_id: "test-agent" }),
90
- }),
91
- );
92
- });
93
-
94
- it("cancels when thread owned by another agent", async () => {
95
- vi.mocked(globalThis.fetch).mockResolvedValue(
96
- new Response(JSON.stringify({ owner: "other-agent" }), { status: 409 }),
97
- );
98
-
99
- const result = await hooks.message_sending(
100
- { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" },
101
- { channelId: "slack", conversationId: "C123" },
102
- );
103
-
104
- expect(result).toEqual({ cancel: true });
105
- expect(api.logger.info).toHaveBeenCalledWith(expect.stringContaining("cancelled send"));
106
- });
107
-
108
- it("fails open on network error", async () => {
109
- vi.mocked(globalThis.fetch).mockRejectedValue(new Error("ECONNREFUSED"));
110
-
111
- const result = await hooks.message_sending(
112
- { content: "hello", metadata: { threadTs: "1234.5678", channelId: "C123" }, to: "C123" },
113
- { channelId: "slack", conversationId: "C123" },
114
- );
115
-
116
- expect(result).toBeUndefined();
117
- expect(api.logger.warn).toHaveBeenCalledWith(
118
- expect.stringContaining("ownership check failed"),
119
- );
120
- });
121
- });
122
-
123
- describe("message_received @-mention tracking", () => {
124
- beforeEach(() => {
125
- register(api as any);
126
- });
127
-
128
- it("tracks @-mentions and skips ownership check for mentioned threads", async () => {
129
- // Simulate receiving a message that @-mentions the agent.
130
- await hooks.message_received(
131
- { content: "Hey @TestBot help me", metadata: { threadTs: "9999.0001", channelId: "C456" } },
132
- { channelId: "slack", conversationId: "C456" },
133
- );
134
-
135
- // Now send in the same thread -- should skip the ownership HTTP call.
136
- const result = await hooks.message_sending(
137
- { content: "Sure!", metadata: { threadTs: "9999.0001", channelId: "C456" }, to: "C456" },
138
- { channelId: "slack", conversationId: "C456" },
139
- );
140
-
141
- expect(result).toBeUndefined();
142
- expect(globalThis.fetch).not.toHaveBeenCalled();
143
- });
144
-
145
- it("ignores @-mentions on non-slack channels", async () => {
146
- // Use a unique thread key so module-level state from other tests doesn't interfere.
147
- await hooks.message_received(
148
- { content: "Hey @TestBot", metadata: { threadTs: "7777.0001", channelId: "C999" } },
149
- { channelId: "telegram", conversationId: "C999" },
150
- );
151
-
152
- // The mention should not have been tracked, so sending should still call fetch.
153
- vi.mocked(globalThis.fetch).mockResolvedValue(
154
- new Response(JSON.stringify({ owner: "test-agent" }), { status: 200 }),
155
- );
156
-
157
- await hooks.message_sending(
158
- { content: "Sure!", metadata: { threadTs: "7777.0001", channelId: "C999" }, to: "C999" },
159
- { channelId: "slack", conversationId: "C999" },
160
- );
161
-
162
- expect(globalThis.fetch).toHaveBeenCalled();
163
- });
164
-
165
- it("tracks bot user ID mentions via <@U999> syntax", async () => {
166
- await hooks.message_received(
167
- { content: "Hey <@U999> help", metadata: { threadTs: "8888.0001", channelId: "C789" } },
168
- { channelId: "slack", conversationId: "C789" },
169
- );
170
-
171
- const result = await hooks.message_sending(
172
- { content: "On it!", metadata: { threadTs: "8888.0001", channelId: "C789" }, to: "C789" },
173
- { channelId: "slack", conversationId: "C789" },
174
- );
175
-
176
- expect(result).toBeUndefined();
177
- expect(globalThis.fetch).not.toHaveBeenCalled();
178
- });
179
- });
180
- });