@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,185 +0,0 @@
1
- import type { SymiConfig } from "symi/plugin-sdk";
2
- import { describe, it, expect } from "vitest";
3
- import { buildProviderMap, isLocalProvider, resolveDefaultProvider } from "../detection.js";
4
- import { buildEnhancement } from "../enhancer.js";
5
- import { resolveConfig } from "../types.js";
6
-
7
- describe("Model Equalizer Integration", () => {
8
- // Simulate the full flow that index.ts performs
9
-
10
- function simulateEnhancement(
11
- config: SymiConfig,
12
- pluginConfig: Record<string, unknown> | undefined,
13
- prompt: string,
14
- cachedProvider?: string,
15
- ): string | null {
16
- const cfg = resolveConfig(pluginConfig);
17
- if (!cfg.enabled) return null;
18
-
19
- const providerMap = buildProviderMap(config);
20
- const provider = cachedProvider ?? resolveDefaultProvider(config);
21
- if (!provider) return null;
22
-
23
- const isLocal = providerMap.get(provider.trim().toLowerCase()) ?? isLocalProvider(provider);
24
- if (!isLocal) return null;
25
-
26
- return buildEnhancement(prompt, {
27
- intensity: cfg.intensity,
28
- minPromptLength: cfg.minPromptLength,
29
- });
30
- }
31
-
32
- it("should enhance prompts when default agent uses ollama", () => {
33
- const config: SymiConfig = {
34
- models: {
35
- providers: {
36
- ollama: { baseUrl: "http://localhost:11434", models: [] },
37
- },
38
- },
39
- agents: {
40
- list: [{ id: "main", default: true, model: "ollama/llama3.1" }],
41
- },
42
- };
43
- const result = simulateEnhancement(config, undefined, "Explain how async/await works in JS");
44
- expect(result).not.toBeNull();
45
- expect(result).toContain("<model-equalizer>");
46
- });
47
-
48
- it("should not enhance prompts when default agent uses openai", () => {
49
- const config: SymiConfig = {
50
- models: {
51
- providers: {
52
- openai: { baseUrl: "https://api.openai.com/v1", models: [] },
53
- },
54
- },
55
- agents: {
56
- list: [{ id: "main", default: true, model: "openai/gpt-4o" }],
57
- },
58
- };
59
- const result = simulateEnhancement(config, undefined, "Explain how async/await works in JS");
60
- expect(result).toBeNull();
61
- });
62
-
63
- it("should enhance when cached provider is local", () => {
64
- const config: SymiConfig = {
65
- models: {
66
- providers: {
67
- openai: { baseUrl: "https://api.openai.com/v1", models: [] },
68
- ollama: { baseUrl: "http://localhost:11434", models: [] },
69
- },
70
- },
71
- agents: {
72
- list: [{ id: "main", default: true, model: "openai/gpt-4o" }],
73
- },
74
- };
75
- // Default agent is openai, but cached provider is ollama
76
- const result = simulateEnhancement(
77
- config,
78
- undefined,
79
- "Explain how async/await works in JS",
80
- "ollama",
81
- );
82
- expect(result).not.toBeNull();
83
- });
84
-
85
- it("should not enhance when cached provider is remote", () => {
86
- const config: SymiConfig = {
87
- models: {
88
- providers: {
89
- ollama: { baseUrl: "http://localhost:11434", models: [] },
90
- openai: { baseUrl: "https://api.openai.com/v1", models: [] },
91
- },
92
- },
93
- agents: {
94
- list: [{ id: "main", default: true, model: "ollama/llama3" }],
95
- },
96
- };
97
- // Default agent is ollama, but cached provider override is openai
98
- const result = simulateEnhancement(
99
- config,
100
- undefined,
101
- "Explain how async/await works in JS",
102
- "openai",
103
- );
104
- expect(result).toBeNull();
105
- });
106
-
107
- it("should respect enabled=false in plugin config", () => {
108
- const config: SymiConfig = {
109
- agents: {
110
- list: [{ id: "main", default: true, model: "ollama/llama3.1" }],
111
- },
112
- };
113
- const result = simulateEnhancement(config, { enabled: false }, "Explain something");
114
- expect(result).toBeNull();
115
- });
116
-
117
- it("should detect local provider by baseUrl even with unknown name", () => {
118
- const config: SymiConfig = {
119
- models: {
120
- providers: {
121
- "my-server": { baseUrl: "http://127.0.0.1:5000/v1", models: [] },
122
- },
123
- },
124
- agents: {
125
- list: [{ id: "main", default: true, model: "my-server/custom-model" }],
126
- },
127
- };
128
- const result = simulateEnhancement(config, undefined, "Explain something in detail");
129
- expect(result).not.toBeNull();
130
- });
131
-
132
- it("should skip very short prompts even for local providers", () => {
133
- const config: SymiConfig = {
134
- agents: {
135
- list: [{ id: "main", default: true, model: "ollama/llama3" }],
136
- },
137
- };
138
- const result = simulateEnhancement(config, undefined, "hi");
139
- expect(result).toBeNull();
140
- });
141
-
142
- it("should use aggressive intensity when configured", () => {
143
- const config: SymiConfig = {
144
- agents: {
145
- list: [{ id: "main", default: true, model: "ollama/llama3" }],
146
- },
147
- };
148
- const result = simulateEnhancement(config, { intensity: "aggressive" }, "Simple question?");
149
- expect(result).not.toBeNull();
150
- expect(result).toContain("Decompose");
151
- });
152
-
153
- it("should return null when no agents are configured", () => {
154
- const config: SymiConfig = {};
155
- const result = simulateEnhancement(config, undefined, "Explain something");
156
- expect(result).toBeNull();
157
- });
158
- });
159
-
160
- describe("resolveConfig", () => {
161
- it("should apply defaults for missing values", () => {
162
- const cfg = resolveConfig(undefined);
163
- expect(cfg.enabled).toBe(true);
164
- expect(cfg.intensity).toBe("standard");
165
- expect(cfg.minPromptLength).toBe(10);
166
- });
167
-
168
- it("should preserve explicit values", () => {
169
- const cfg = resolveConfig({
170
- enabled: false,
171
- intensity: "aggressive",
172
- minPromptLength: 50,
173
- });
174
- expect(cfg.enabled).toBe(false);
175
- expect(cfg.intensity).toBe("aggressive");
176
- expect(cfg.minPromptLength).toBe(50);
177
- });
178
-
179
- it("should handle partial config", () => {
180
- const cfg = resolveConfig({ intensity: "light" });
181
- expect(cfg.enabled).toBe(true);
182
- expect(cfg.intensity).toBe("light");
183
- expect(cfg.minPromptLength).toBe(10);
184
- });
185
- });
@@ -1,24 +0,0 @@
1
- export type ModelEqualizerConfig = {
2
- /** Whether the equalizer is active. Default: true. */
3
- enabled?: boolean;
4
- /** Enhancement intensity. Default: "standard". */
5
- intensity?: "light" | "standard" | "aggressive";
6
- /** Skip enhancement for prompts shorter than this. Default: 10. */
7
- minPromptLength?: number;
8
- };
9
-
10
- export type DetectionState = {
11
- /** Cached provider per session key. */
12
- sessionProviders: Map<string, string>;
13
- /** Pre-built provider→isLocal map from config. */
14
- providerMap: Map<string, boolean>;
15
- };
16
-
17
- export function resolveConfig(raw: Record<string, unknown> | undefined): ModelEqualizerConfig {
18
- const cfg = (raw ?? {}) as Partial<ModelEqualizerConfig>;
19
- return {
20
- enabled: cfg.enabled ?? true,
21
- intensity: cfg.intensity ?? "standard",
22
- minPromptLength: cfg.minPromptLength ?? 10,
23
- };
24
- }
@@ -1,12 +0,0 @@
1
- {
2
- "id": "model-equalizer",
3
- "configSchema": {
4
- "type": "object",
5
- "additionalProperties": false,
6
- "properties": {
7
- "enabled": { "type": "boolean" },
8
- "intensity": { "enum": ["light", "standard", "aggressive"] },
9
- "minPromptLength": { "type": "number" }
10
- }
11
- }
12
- }
@@ -1,421 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import type { SymiPluginApi, SymiPluginService } from "symi/plugin-sdk";
4
-
5
- type ArmGroup = "camera" | "screen" | "writes" | "all";
6
-
7
- type ArmStateFileV1 = {
8
- version: 1;
9
- armedAtMs: number;
10
- expiresAtMs: number | null;
11
- removedFromDeny: string[];
12
- };
13
-
14
- type ArmStateFileV2 = {
15
- version: 2;
16
- armedAtMs: number;
17
- expiresAtMs: number | null;
18
- group: ArmGroup;
19
- armedCommands: string[];
20
- addedToAllow: string[];
21
- removedFromDeny: string[];
22
- };
23
-
24
- type ArmStateFile = ArmStateFileV1 | ArmStateFileV2;
25
-
26
- const STATE_VERSION = 2;
27
- const STATE_REL_PATH = ["plugins", "phone-control", "armed.json"] as const;
28
-
29
- const GROUP_COMMANDS: Record<Exclude<ArmGroup, "all">, string[]> = {
30
- camera: ["camera.snap", "camera.clip"],
31
- screen: ["screen.record"],
32
- writes: ["calendar.add", "contacts.add", "reminders.add"],
33
- };
34
-
35
- function uniqSorted(values: string[]): string[] {
36
- return [...new Set(values.map((v) => v.trim()).filter(Boolean))].toSorted();
37
- }
38
-
39
- function resolveCommandsForGroup(group: ArmGroup): string[] {
40
- if (group === "all") {
41
- return uniqSorted(Object.values(GROUP_COMMANDS).flat());
42
- }
43
- return uniqSorted(GROUP_COMMANDS[group]);
44
- }
45
-
46
- function formatGroupList(): string {
47
- return ["camera", "screen", "writes", "all"].join(", ");
48
- }
49
-
50
- function parseDurationMs(input: string | undefined): number | null {
51
- if (!input) {
52
- return null;
53
- }
54
- const raw = input.trim().toLowerCase();
55
- if (!raw) {
56
- return null;
57
- }
58
- const m = raw.match(/^(\d+)(s|m|h|d)$/);
59
- if (!m) {
60
- return null;
61
- }
62
- const n = Number.parseInt(m[1] ?? "", 10);
63
- if (!Number.isFinite(n) || n <= 0) {
64
- return null;
65
- }
66
- const unit = m[2];
67
- const mult = unit === "s" ? 1000 : unit === "m" ? 60_000 : unit === "h" ? 3_600_000 : 86_400_000;
68
- return n * mult;
69
- }
70
-
71
- function formatDuration(ms: number): string {
72
- const s = Math.max(0, Math.floor(ms / 1000));
73
- if (s < 60) {
74
- return `${s}s`;
75
- }
76
- const m = Math.floor(s / 60);
77
- if (m < 60) {
78
- return `${m}m`;
79
- }
80
- const h = Math.floor(m / 60);
81
- if (h < 48) {
82
- return `${h}h`;
83
- }
84
- const d = Math.floor(h / 24);
85
- return `${d}d`;
86
- }
87
-
88
- function resolveStatePath(stateDir: string): string {
89
- return path.join(stateDir, ...STATE_REL_PATH);
90
- }
91
-
92
- async function readArmState(statePath: string): Promise<ArmStateFile | null> {
93
- try {
94
- const raw = await fs.readFile(statePath, "utf8");
95
- // Type as unknown record first to allow property access during validation
96
- const parsed = JSON.parse(raw) as Record<string, unknown>;
97
- if (parsed.version !== 1 && parsed.version !== 2) {
98
- return null;
99
- }
100
- if (typeof parsed.armedAtMs !== "number") {
101
- return null;
102
- }
103
- if (!(parsed.expiresAtMs === null || typeof parsed.expiresAtMs === "number")) {
104
- return null;
105
- }
106
-
107
- if (parsed.version === 1) {
108
- if (
109
- !Array.isArray(parsed.removedFromDeny) ||
110
- !parsed.removedFromDeny.every((v: unknown) => typeof v === "string")
111
- ) {
112
- return null;
113
- }
114
- return parsed as unknown as ArmStateFile;
115
- }
116
-
117
- const group = typeof parsed.group === "string" ? parsed.group : "";
118
- if (group !== "camera" && group !== "screen" && group !== "writes" && group !== "all") {
119
- return null;
120
- }
121
- if (
122
- !Array.isArray(parsed.armedCommands) ||
123
- !parsed.armedCommands.every((v: unknown) => typeof v === "string")
124
- ) {
125
- return null;
126
- }
127
- if (
128
- !Array.isArray(parsed.addedToAllow) ||
129
- !parsed.addedToAllow.every((v: unknown) => typeof v === "string")
130
- ) {
131
- return null;
132
- }
133
- if (
134
- !Array.isArray(parsed.removedFromDeny) ||
135
- !parsed.removedFromDeny.every((v: unknown) => typeof v === "string")
136
- ) {
137
- return null;
138
- }
139
- return parsed as unknown as ArmStateFile;
140
- } catch {
141
- return null;
142
- }
143
- }
144
-
145
- async function writeArmState(statePath: string, state: ArmStateFile | null): Promise<void> {
146
- await fs.mkdir(path.dirname(statePath), { recursive: true });
147
- if (!state) {
148
- try {
149
- await fs.unlink(statePath);
150
- } catch {
151
- // ignore
152
- }
153
- return;
154
- }
155
- await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
156
- }
157
-
158
- function normalizeDenyList(cfg: SymiPluginApi["config"]): string[] {
159
- return uniqSorted([...(cfg.gateway?.nodes?.denyCommands ?? [])]);
160
- }
161
-
162
- function normalizeAllowList(cfg: SymiPluginApi["config"]): string[] {
163
- return uniqSorted([...(cfg.gateway?.nodes?.allowCommands ?? [])]);
164
- }
165
-
166
- function patchConfigNodeLists(
167
- cfg: SymiPluginApi["config"],
168
- next: { allowCommands: string[]; denyCommands: string[] },
169
- ): SymiPluginApi["config"] {
170
- return {
171
- ...cfg,
172
- gateway: {
173
- ...cfg.gateway,
174
- nodes: {
175
- ...cfg.gateway?.nodes,
176
- allowCommands: next.allowCommands,
177
- denyCommands: next.denyCommands,
178
- },
179
- },
180
- };
181
- }
182
-
183
- async function disarmNow(params: {
184
- api: SymiPluginApi;
185
- stateDir: string;
186
- statePath: string;
187
- reason: string;
188
- }): Promise<{ changed: boolean; restored: string[]; removed: string[] }> {
189
- const { api, stateDir, statePath, reason } = params;
190
- const state = await readArmState(statePath);
191
- if (!state) {
192
- return { changed: false, restored: [], removed: [] };
193
- }
194
- const cfg = api.runtime.config.loadConfig();
195
- const allow = new Set(normalizeAllowList(cfg));
196
- const deny = new Set(normalizeDenyList(cfg));
197
- const removed: string[] = [];
198
- const restored: string[] = [];
199
-
200
- if (state.version === 1) {
201
- for (const cmd of state.removedFromDeny) {
202
- if (!deny.has(cmd)) {
203
- deny.add(cmd);
204
- restored.push(cmd);
205
- }
206
- }
207
- } else {
208
- for (const cmd of state.addedToAllow) {
209
- if (allow.delete(cmd)) {
210
- removed.push(cmd);
211
- }
212
- }
213
- for (const cmd of state.removedFromDeny) {
214
- if (!deny.has(cmd)) {
215
- deny.add(cmd);
216
- restored.push(cmd);
217
- }
218
- }
219
- }
220
-
221
- if (removed.length > 0 || restored.length > 0) {
222
- const next = patchConfigNodeLists(cfg, {
223
- allowCommands: uniqSorted([...allow]),
224
- denyCommands: uniqSorted([...deny]),
225
- });
226
- await api.runtime.config.writeConfigFile(next);
227
- }
228
- await writeArmState(statePath, null);
229
- api.logger.info(`phone-control: disarmed (${reason}) stateDir=${stateDir}`);
230
- return {
231
- changed: removed.length > 0 || restored.length > 0,
232
- removed: uniqSorted(removed),
233
- restored: uniqSorted(restored),
234
- };
235
- }
236
-
237
- function formatHelp(): string {
238
- return [
239
- "Phone control commands:",
240
- "",
241
- "/phone status",
242
- "/phone arm <group> [duration]",
243
- "/phone disarm",
244
- "",
245
- "Groups:",
246
- `- ${formatGroupList()}`,
247
- "",
248
- "Duration format: 30s | 10m | 2h | 1d (default: 10m).",
249
- "",
250
- "Notes:",
251
- "- This only toggles what the gateway is allowed to invoke on phone nodes.",
252
- "- iOS will still ask for permissions (camera, photos, contacts, etc.) on first use.",
253
- ].join("\n");
254
- }
255
-
256
- function parseGroup(raw: string | undefined): ArmGroup | null {
257
- const value = (raw ?? "").trim().toLowerCase();
258
- if (!value) {
259
- return null;
260
- }
261
- if (value === "camera" || value === "screen" || value === "writes" || value === "all") {
262
- return value;
263
- }
264
- return null;
265
- }
266
-
267
- function formatStatus(state: ArmStateFile | null): string {
268
- if (!state) {
269
- return "Phone control: disarmed.";
270
- }
271
- const until =
272
- state.expiresAtMs == null
273
- ? "manual disarm required"
274
- : `expires in ${formatDuration(Math.max(0, state.expiresAtMs - Date.now()))}`;
275
- const cmds = uniqSorted(
276
- state.version === 1
277
- ? state.removedFromDeny
278
- : state.armedCommands.length > 0
279
- ? state.armedCommands
280
- : [...state.addedToAllow, ...state.removedFromDeny],
281
- );
282
- const cmdLabel = cmds.length > 0 ? cmds.join(", ") : "none";
283
- return `Phone control: armed (${until}).\nTemporarily allowed: ${cmdLabel}`;
284
- }
285
-
286
- export default function register(api: SymiPluginApi) {
287
- let expiryInterval: ReturnType<typeof setInterval> | null = null;
288
-
289
- const timerService: SymiPluginService = {
290
- id: "phone-control-expiry",
291
- start: async (ctx) => {
292
- const statePath = resolveStatePath(ctx.stateDir);
293
- const tick = async () => {
294
- const state = await readArmState(statePath);
295
- if (!state || state.expiresAtMs == null) {
296
- return;
297
- }
298
- if (Date.now() < state.expiresAtMs) {
299
- return;
300
- }
301
- await disarmNow({
302
- api,
303
- stateDir: ctx.stateDir,
304
- statePath,
305
- reason: "expired",
306
- });
307
- };
308
-
309
- // Best effort; don't crash the gateway if state is corrupt.
310
- await tick().catch(() => {});
311
-
312
- expiryInterval = setInterval(() => {
313
- tick().catch(() => {});
314
- }, 15_000);
315
- expiryInterval.unref?.();
316
-
317
- return;
318
- },
319
- stop: async () => {
320
- if (expiryInterval) {
321
- clearInterval(expiryInterval);
322
- expiryInterval = null;
323
- }
324
- return;
325
- },
326
- };
327
-
328
- api.registerService(timerService);
329
-
330
- api.registerCommand({
331
- name: "phone",
332
- description: "Arm/disarm high-risk phone node commands (camera/screen/writes).",
333
- acceptsArgs: true,
334
- handler: async (ctx) => {
335
- const args = ctx.args?.trim() ?? "";
336
- const tokens = args.split(/\s+/).filter(Boolean);
337
- const action = tokens[0]?.toLowerCase() ?? "";
338
-
339
- const stateDir = api.runtime.state.resolveStateDir();
340
- const statePath = resolveStatePath(stateDir);
341
-
342
- if (!action || action === "help") {
343
- const state = await readArmState(statePath);
344
- return { text: `${formatStatus(state)}\n\n${formatHelp()}` };
345
- }
346
-
347
- if (action === "status") {
348
- const state = await readArmState(statePath);
349
- return { text: formatStatus(state) };
350
- }
351
-
352
- if (action === "disarm") {
353
- const res = await disarmNow({
354
- api,
355
- stateDir,
356
- statePath,
357
- reason: "manual",
358
- });
359
- if (!res.changed) {
360
- return { text: "Phone control: disarmed." };
361
- }
362
- const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none";
363
- const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none";
364
- return {
365
- text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`,
366
- };
367
- }
368
-
369
- if (action === "arm") {
370
- const group = parseGroup(tokens[1]);
371
- if (!group) {
372
- return { text: `Usage: /phone arm <group> [duration]\nGroups: ${formatGroupList()}` };
373
- }
374
- const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000;
375
- const expiresAtMs = Date.now() + durationMs;
376
-
377
- const commands = resolveCommandsForGroup(group);
378
- const cfg = api.runtime.config.loadConfig();
379
- const allowSet = new Set(normalizeAllowList(cfg));
380
- const denySet = new Set(normalizeDenyList(cfg));
381
-
382
- const addedToAllow: string[] = [];
383
- const removedFromDeny: string[] = [];
384
- for (const cmd of commands) {
385
- if (!allowSet.has(cmd)) {
386
- allowSet.add(cmd);
387
- addedToAllow.push(cmd);
388
- }
389
- if (denySet.delete(cmd)) {
390
- removedFromDeny.push(cmd);
391
- }
392
- }
393
- const next = patchConfigNodeLists(cfg, {
394
- allowCommands: uniqSorted([...allowSet]),
395
- denyCommands: uniqSorted([...denySet]),
396
- });
397
- await api.runtime.config.writeConfigFile(next);
398
-
399
- await writeArmState(statePath, {
400
- version: STATE_VERSION,
401
- armedAtMs: Date.now(),
402
- expiresAtMs,
403
- group,
404
- armedCommands: uniqSorted(commands),
405
- addedToAllow: uniqSorted(addedToAllow),
406
- removedFromDeny: uniqSorted(removedFromDeny),
407
- });
408
-
409
- const allowedLabel = uniqSorted(commands).join(", ");
410
- return {
411
- text:
412
- `Phone control: armed for ${formatDuration(durationMs)}.\n` +
413
- `Temporarily allowed: ${allowedLabel}\n` +
414
- `To disarm early: /phone disarm`,
415
- };
416
- }
417
-
418
- return { text: formatHelp() };
419
- },
420
- });
421
- }
@@ -1,10 +0,0 @@
1
- {
2
- "id": "phone-control",
3
- "name": "Phone Control",
4
- "description": "Arm/disarm high-risk phone node commands (camera/screen/writes) with an optional auto-expiry.",
5
- "configSchema": {
6
- "type": "object",
7
- "additionalProperties": false,
8
- "properties": {}
9
- }
10
- }