freddie 0.0.48 → 0.0.50

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 (230) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +2 -1
  3. package/plugins/ansi_strip/handler.js +7 -0
  4. package/plugins/ansi_strip/plugin.js +2 -0
  5. package/plugins/approval/handler.js +13 -0
  6. package/plugins/approval/plugin.js +2 -0
  7. package/plugins/bash/handler.js +33 -0
  8. package/plugins/bash/plugin.js +2 -0
  9. package/plugins/binary_extensions/handler.js +20 -0
  10. package/plugins/binary_extensions/plugin.js +2 -0
  11. package/plugins/browser/handler.js +46 -0
  12. package/plugins/browser/plugin.js +2 -0
  13. package/plugins/budget_config/handler.js +12 -0
  14. package/plugins/budget_config/plugin.js +2 -0
  15. package/plugins/checkpoint/handler.js +27 -0
  16. package/plugins/checkpoint/plugin.js +2 -0
  17. package/plugins/clarify/handler.js +13 -0
  18. package/plugins/clarify/plugin.js +2 -0
  19. package/plugins/code_execution/handler.js +25 -0
  20. package/plugins/code_execution/plugin.js +2 -0
  21. package/plugins/core-agent-machine/plugin.js +8 -0
  22. package/plugins/core-cli/plugin.js +83 -0
  23. package/plugins/core-commands/plugin.js +7 -0
  24. package/plugins/core-compressor/plugin.js +15 -0
  25. package/plugins/core-context-engine/plugin.js +7 -0
  26. package/plugins/core-cron/plugin.js +7 -0
  27. package/plugins/core-skills/plugin.js +7 -0
  28. package/plugins/credential_files/handler.js +14 -0
  29. package/plugins/credential_files/plugin.js +2 -0
  30. package/plugins/cronjob/handler.js +14 -0
  31. package/plugins/cronjob/plugin.js +2 -0
  32. package/plugins/debug_helpers/handler.js +8 -0
  33. package/plugins/debug_helpers/plugin.js +2 -0
  34. package/plugins/delegate/handler.js +27 -0
  35. package/plugins/delegate/plugin.js +2 -0
  36. package/plugins/discord_tool/handler.js +12 -0
  37. package/plugins/discord_tool/plugin.js +2 -0
  38. package/plugins/edit/handler.js +29 -0
  39. package/plugins/edit/plugin.js +2 -0
  40. package/plugins/env_passthrough/handler.js +14 -0
  41. package/plugins/env_passthrough/plugin.js +2 -0
  42. package/plugins/feishu_doc/handler.js +14 -0
  43. package/plugins/feishu_doc/plugin.js +2 -0
  44. package/plugins/feishu_drive/handler.js +13 -0
  45. package/plugins/feishu_drive/plugin.js +2 -0
  46. package/plugins/file_operations/handler.js +15 -0
  47. package/plugins/file_operations/plugin.js +2 -0
  48. package/plugins/file_state/handler.js +14 -0
  49. package/plugins/file_state/plugin.js +2 -0
  50. package/plugins/file_tools/handler.js +21 -0
  51. package/plugins/file_tools/plugin.js +2 -0
  52. package/plugins/fuzzy_match/handler.js +7 -0
  53. package/plugins/fuzzy_match/plugin.js +2 -0
  54. package/plugins/gm-cc/plugin.js +28 -0
  55. package/plugins/grep/handler.js +49 -0
  56. package/plugins/grep/plugin.js +2 -0
  57. package/plugins/gui-agents/plugin.js +26 -0
  58. package/plugins/gui-batch/plugin.js +11 -0
  59. package/plugins/gui-chat/plugin.js +22 -0
  60. package/plugins/gui-config/plugin.js +12 -0
  61. package/plugins/gui-cron/plugin.js +13 -0
  62. package/plugins/gui-debug/plugin.js +24 -0
  63. package/plugins/gui-env/plugin.js +7 -0
  64. package/plugins/gui-gateway/plugin.js +9 -0
  65. package/plugins/gui-profiles-commands-health/plugin.js +11 -0
  66. package/plugins/gui-sessions/plugin.js +9 -0
  67. package/plugins/gui-skills/plugin.js +8 -0
  68. package/plugins/gui-tools/plugin.js +7 -0
  69. package/plugins/homeassistant_tool/handler.js +14 -0
  70. package/plugins/homeassistant_tool/plugin.js +2 -0
  71. package/plugins/image_gen/handler.js +31 -0
  72. package/plugins/image_gen/plugin.js +2 -0
  73. package/plugins/interrupt/handler.js +16 -0
  74. package/plugins/interrupt/plugin.js +2 -0
  75. package/plugins/managed_tool_gateway/handler.js +9 -0
  76. package/plugins/managed_tool_gateway/plugin.js +2 -0
  77. package/plugins/mcp_oauth/handler.js +20 -0
  78. package/plugins/mcp_oauth/plugin.js +2 -0
  79. package/plugins/mcp_oauth_manager/handler.js +18 -0
  80. package/plugins/mcp_oauth_manager/plugin.js +2 -0
  81. package/plugins/mcp_tool/handler.js +34 -0
  82. package/plugins/mcp_tool/plugin.js +2 -0
  83. package/plugins/memory/handler.js +66 -0
  84. package/plugins/memory/plugin.js +2 -0
  85. package/plugins/memory-byterover/handler.js +25 -0
  86. package/plugins/memory-byterover/plugin.js +2 -0
  87. package/plugins/memory-hindsight/handler.js +25 -0
  88. package/plugins/memory-hindsight/plugin.js +2 -0
  89. package/plugins/memory-holographic/handler.js +31 -0
  90. package/plugins/memory-holographic/plugin.js +2 -0
  91. package/plugins/memory-honcho/handler.js +25 -0
  92. package/plugins/memory-honcho/plugin.js +2 -0
  93. package/plugins/memory-mem0/handler.js +25 -0
  94. package/plugins/memory-mem0/plugin.js +2 -0
  95. package/plugins/memory-openviking/handler.js +25 -0
  96. package/plugins/memory-openviking/plugin.js +2 -0
  97. package/plugins/memory-retaindb/handler.js +25 -0
  98. package/plugins/memory-retaindb/plugin.js +2 -0
  99. package/plugins/memory-supermemory/handler.js +25 -0
  100. package/plugins/memory-supermemory/plugin.js +2 -0
  101. package/plugins/mixture_of_agents/handler.js +13 -0
  102. package/plugins/mixture_of_agents/plugin.js +2 -0
  103. package/plugins/neutts_synth/handler.js +12 -0
  104. package/plugins/neutts_synth/plugin.js +2 -0
  105. package/plugins/openrouter_client/handler.js +12 -0
  106. package/plugins/openrouter_client/plugin.js +2 -0
  107. package/plugins/osv_check/handler.js +10 -0
  108. package/plugins/osv_check/plugin.js +2 -0
  109. package/plugins/patch_parser/handler.js +40 -0
  110. package/plugins/patch_parser/plugin.js +2 -0
  111. package/plugins/path_security/handler.js +14 -0
  112. package/plugins/path_security/plugin.js +2 -0
  113. package/plugins/platform-api_server/handler.js +21 -0
  114. package/plugins/platform-api_server/plugin.js +2 -0
  115. package/plugins/platform-bluebubbles/handler.js +32 -0
  116. package/plugins/platform-bluebubbles/plugin.js +2 -0
  117. package/plugins/platform-dingtalk/handler.js +32 -0
  118. package/plugins/platform-dingtalk/plugin.js +2 -0
  119. package/plugins/platform-discord/handler.js +24 -0
  120. package/plugins/platform-discord/plugin.js +2 -0
  121. package/plugins/platform-email/handler.js +51 -0
  122. package/plugins/platform-email/plugin.js +2 -0
  123. package/plugins/platform-feishu/handler.js +32 -0
  124. package/plugins/platform-feishu/plugin.js +2 -0
  125. package/plugins/platform-feishu_comment/handler.js +12 -0
  126. package/plugins/platform-feishu_comment/plugin.js +2 -0
  127. package/plugins/platform-feishu_comment_rules/handler.js +11 -0
  128. package/plugins/platform-feishu_comment_rules/plugin.js +2 -0
  129. package/plugins/platform-homeassistant/handler.js +32 -0
  130. package/plugins/platform-homeassistant/plugin.js +2 -0
  131. package/plugins/platform-matrix/handler.js +40 -0
  132. package/plugins/platform-matrix/plugin.js +2 -0
  133. package/plugins/platform-mattermost/handler.js +29 -0
  134. package/plugins/platform-mattermost/plugin.js +2 -0
  135. package/plugins/platform-qqbot/handler.js +32 -0
  136. package/plugins/platform-qqbot/plugin.js +2 -0
  137. package/plugins/platform-signal/handler.js +33 -0
  138. package/plugins/platform-signal/plugin.js +2 -0
  139. package/plugins/platform-slack/handler.js +34 -0
  140. package/plugins/platform-slack/plugin.js +2 -0
  141. package/plugins/platform-sms/handler.js +34 -0
  142. package/plugins/platform-sms/plugin.js +2 -0
  143. package/plugins/platform-telegram/handler.js +38 -0
  144. package/plugins/platform-telegram/plugin.js +2 -0
  145. package/plugins/platform-telegram_network/handler.js +17 -0
  146. package/plugins/platform-telegram_network/plugin.js +2 -0
  147. package/plugins/platform-webhook/handler.js +19 -0
  148. package/plugins/platform-webhook/plugin.js +2 -0
  149. package/plugins/platform-wecom/handler.js +32 -0
  150. package/plugins/platform-wecom/plugin.js +2 -0
  151. package/plugins/platform-wecom_callback/handler.js +15 -0
  152. package/plugins/platform-wecom_callback/plugin.js +2 -0
  153. package/plugins/platform-wecom_crypto/handler.js +16 -0
  154. package/plugins/platform-wecom_crypto/plugin.js +2 -0
  155. package/plugins/platform-weixin/handler.js +32 -0
  156. package/plugins/platform-weixin/plugin.js +2 -0
  157. package/plugins/platform-whatsapp/handler.js +40 -0
  158. package/plugins/platform-whatsapp/plugin.js +2 -0
  159. package/plugins/platform-yuanbao/handler.js +9 -0
  160. package/plugins/platform-yuanbao/plugin.js +2 -0
  161. package/plugins/platform-yuanbao_media/handler.js +5 -0
  162. package/plugins/platform-yuanbao_media/plugin.js +2 -0
  163. package/plugins/platform-yuanbao_proto/handler.js +9 -0
  164. package/plugins/platform-yuanbao_proto/plugin.js +2 -0
  165. package/plugins/platform-yuanbao_sticker/handler.js +6 -0
  166. package/plugins/platform-yuanbao_sticker/plugin.js +2 -0
  167. package/plugins/process_registry/handler.js +15 -0
  168. package/plugins/process_registry/plugin.js +2 -0
  169. package/plugins/read/handler.js +24 -0
  170. package/plugins/read/plugin.js +2 -0
  171. package/plugins/rl_training/handler.js +12 -0
  172. package/plugins/rl_training/plugin.js +2 -0
  173. package/plugins/schema_sanitizer/handler.js +17 -0
  174. package/plugins/schema_sanitizer/plugin.js +2 -0
  175. package/plugins/send_message/handler.js +30 -0
  176. package/plugins/send_message/plugin.js +2 -0
  177. package/plugins/session_search/handler.js +21 -0
  178. package/plugins/session_search/plugin.js +2 -0
  179. package/plugins/skill_manager/handler.js +16 -0
  180. package/plugins/skill_manager/plugin.js +2 -0
  181. package/plugins/skill_usage/handler.js +18 -0
  182. package/plugins/skill_usage/plugin.js +2 -0
  183. package/plugins/skills_guard/handler.js +16 -0
  184. package/plugins/skills_guard/plugin.js +2 -0
  185. package/plugins/skills_hub/handler.js +29 -0
  186. package/plugins/skills_hub/plugin.js +2 -0
  187. package/plugins/skills_index/handler.js +12 -0
  188. package/plugins/skills_index/plugin.js +2 -0
  189. package/plugins/skills_sync/handler.js +17 -0
  190. package/plugins/skills_sync/plugin.js +2 -0
  191. package/plugins/skills_tool/handler.js +9 -0
  192. package/plugins/skills_tool/plugin.js +2 -0
  193. package/plugins/slash_confirm/handler.js +14 -0
  194. package/plugins/slash_confirm/plugin.js +2 -0
  195. package/plugins/terminal/handler.js +27 -0
  196. package/plugins/terminal/plugin.js +2 -0
  197. package/plugins/tirith_security/handler.js +23 -0
  198. package/plugins/tirith_security/plugin.js +2 -0
  199. package/plugins/todo/handler.js +52 -0
  200. package/plugins/todo/plugin.js +2 -0
  201. package/plugins/tool_backend_helpers/handler.js +24 -0
  202. package/plugins/tool_backend_helpers/plugin.js +2 -0
  203. package/plugins/tool_output_limits/handler.js +14 -0
  204. package/plugins/tool_output_limits/plugin.js +2 -0
  205. package/plugins/tool_result_storage/handler.js +18 -0
  206. package/plugins/tool_result_storage/plugin.js +2 -0
  207. package/plugins/transcription/handler.js +18 -0
  208. package/plugins/transcription/plugin.js +2 -0
  209. package/plugins/tts/handler.js +18 -0
  210. package/plugins/tts/plugin.js +2 -0
  211. package/plugins/url_safety/handler.js +14 -0
  212. package/plugins/url_safety/plugin.js +2 -0
  213. package/plugins/vision/handler.js +17 -0
  214. package/plugins/vision/plugin.js +2 -0
  215. package/plugins/voice_mode/handler.js +9 -0
  216. package/plugins/voice_mode/plugin.js +2 -0
  217. package/plugins/web_search/handler.js +35 -0
  218. package/plugins/web_search/plugin.js +2 -0
  219. package/plugins/web_tools/handler.js +17 -0
  220. package/plugins/web_tools/plugin.js +2 -0
  221. package/plugins/website_policy/handler.js +13 -0
  222. package/plugins/website_policy/plugin.js +2 -0
  223. package/plugins/write/handler.js +23 -0
  224. package/plugins/write/plugin.js +2 -0
  225. package/plugins/xai_http/handler.js +12 -0
  226. package/plugins/xai_http/plugin.js +2 -0
  227. package/plugins/yuanbao_tools/handler.js +12 -0
  228. package/plugins/yuanbao_tools/plugin.js +2 -0
  229. package/src/agent/llm_resolver.js +2 -1
  230. package/src/agent/pi-bridge.js +3 -1
@@ -0,0 +1,25 @@
1
+ export class ByteroverMemory {
2
+ constructor(opts = {}) {
3
+ this.name = 'byterover'
4
+ this.apiKey = opts.apiKey || process.env.BYTEROVER_API_KEY
5
+ this.base = opts.base || "https://api.byterover.com"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["BYTEROVER_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('ByteroverMemory: BYTEROVER_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-byterover', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'byterover', module: mod }) } }
@@ -0,0 +1,25 @@
1
+ export class HindsightMemory {
2
+ constructor(opts = {}) {
3
+ this.name = 'hindsight'
4
+ this.apiKey = opts.apiKey || process.env.HINDSIGHT_API_KEY
5
+ this.base = opts.base || "https://api.hindsightai.com"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["HINDSIGHT_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('HindsightMemory: HINDSIGHT_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-hindsight', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'hindsight', module: mod }) } }
@@ -0,0 +1,31 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFreddieHome } from '../../src/plugins/../home.js'
4
+
5
+ export class HolographicMemory {
6
+ constructor(opts = {}) {
7
+ this.name = 'holographic'
8
+ this.dir = opts.dir || path.join(getFreddieHome(), 'memory', 'holographic')
9
+ fs.mkdirSync(this.dir, { recursive: true })
10
+ }
11
+ getRequiredEnv() { return [] }
12
+ async syncTurn(messages) {
13
+ const file = path.join(this.dir, Date.now() + '.json')
14
+ fs.writeFileSync(file, JSON.stringify({ ts: Date.now(), messages }), 'utf8')
15
+ return { stored: file }
16
+ }
17
+ async prefetch(query) {
18
+ const files = fs.readdirSync(this.dir).filter(f => f.endsWith('.json')).sort().slice(-20)
19
+ const items = []
20
+ for (const f of files) {
21
+ try {
22
+ const data = JSON.parse(fs.readFileSync(path.join(this.dir, f), 'utf8'))
23
+ const last = data.messages?.slice(-2).map(m => m.content).join(' ') || ''
24
+ if (!query || last.toLowerCase().includes(String(query).toLowerCase())) items.push({ file: f, summary: last.slice(0, 200) })
25
+ } catch {}
26
+ }
27
+ return { items }
28
+ }
29
+ async shutdown() {}
30
+ async postSetup() {}
31
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-holographic', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'holographic', module: mod }) } }
@@ -0,0 +1,25 @@
1
+ export class HonchoMemory {
2
+ constructor(opts = {}) {
3
+ this.name = 'honcho'
4
+ this.apiKey = opts.apiKey || process.env.HONCHO_API_KEY
5
+ this.base = opts.base || "https://api.honcho.dev"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["HONCHO_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('HonchoMemory: HONCHO_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-honcho', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'honcho', module: mod }) } }
@@ -0,0 +1,25 @@
1
+ export class Mem0Memory {
2
+ constructor(opts = {}) {
3
+ this.name = 'mem0'
4
+ this.apiKey = opts.apiKey || process.env.MEM0_API_KEY
5
+ this.base = opts.base || "https://api.mem0.ai/v1"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["MEM0_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('Mem0Memory: MEM0_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-mem0', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'mem0', module: mod }) } }
@@ -0,0 +1,25 @@
1
+ export class OpenvikingMemory {
2
+ constructor(opts = {}) {
3
+ this.name = 'openviking'
4
+ this.apiKey = opts.apiKey || process.env.OPENVIKING_API_KEY
5
+ this.base = opts.base || "https://api.openviking.com"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["OPENVIKING_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('OpenvikingMemory: OPENVIKING_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-openviking', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'openviking', module: mod }) } }
@@ -0,0 +1,25 @@
1
+ export class RetaindbMemory {
2
+ constructor(opts = {}) {
3
+ this.name = 'retaindb'
4
+ this.apiKey = opts.apiKey || process.env.RETAINDB_API_KEY
5
+ this.base = opts.base || "https://api.retaindb.com"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["RETAINDB_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('RetaindbMemory: RETAINDB_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-retaindb', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'retaindb', module: mod }) } }
@@ -0,0 +1,25 @@
1
+ export class SupermemoryMemory {
2
+ constructor(opts = {}) {
3
+ this.name = 'supermemory'
4
+ this.apiKey = opts.apiKey || process.env.SUPERMEMORY_API_KEY
5
+ this.base = opts.base || "https://api.supermemory.ai/v3"
6
+ this.userId = opts.userId || 'default'
7
+ }
8
+ getRequiredEnv() { return ["SUPERMEMORY_API_KEY"] }
9
+ _headers() {
10
+ if (!this.apiKey) throw new Error('SupermemoryMemory: SUPERMEMORY_API_KEY required')
11
+ return { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' }
12
+ }
13
+ async syncTurn(messages) {
14
+ const res = await fetch(`${this.base}/memories`, { method: 'POST', headers: this._headers(), body: JSON.stringify({ user_id: this.userId, messages }) })
15
+ return { status: res.status, ok: res.ok }
16
+ }
17
+ async prefetch(query) {
18
+ const url = `${this.base}/memories/search?query=${encodeURIComponent(query || '')}&user_id=${encodeURIComponent(this.userId)}`
19
+ const res = await fetch(url, { headers: this._headers() })
20
+ if (!res.ok) return { items: [], status: res.status }
21
+ return { items: await res.json() }
22
+ }
23
+ async shutdown() {}
24
+ async postSetup() {}
25
+ }
@@ -0,0 +1,2 @@
1
+ import * as mod from './handler.js'
2
+ export default { name: 'memory-supermemory', surfaces: 'pi', register({ pi }) { pi.memory.register({ name: 'supermemory', module: mod }) } }
@@ -0,0 +1,13 @@
1
+ import { runTurn } from '../../src/agent/machine.js'
2
+
3
+ export const _tool = ({
4
+ name: 'mixture_of_agents',
5
+ toolset: 'core',
6
+ schema: { name: 'mixture_of_agents', description: 'Run the same prompt through N sub-agents (different models or seeds), then synthesize the results. Reduces variance.', parameters: { type: 'object', properties: { prompt: { type: 'string' }, models: { type: 'array', items: { type: 'string' } }, callLLM: {} }, required: ['prompt'] } },
7
+ handler: async ({ prompt, models = ['default'] }, ctx = {}) => {
8
+ const llm = ctx.callLLM || null
9
+ const runs = await Promise.all(models.map(m => runTurn({ prompt, model: m, callLLM: llm, timeoutMs: 30000 }).catch(e => ({ error: String(e.message || e) }))))
10
+ const synthesized = runs.map(r => r.result || r.error || '').join('\n---\n')
11
+ return { runs: runs.length, synthesized }
12
+ },
13
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-mixture_of_agents', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,12 @@
1
+ export const _tool = ({
2
+ name: 'neutts_synth',
3
+ toolset: 'creative',
4
+ schema: { name: 'neutts_synth', description: 'Local NeuTTS synth (alternate TTS backend).', parameters: { type: 'object', properties: { text: { type: 'string' }, voice: { type: 'string', default: 'default' } }, required: ['text'] } },
5
+ requiresEnv: ['NEUTTS_URL'],
6
+ checkFn: () => Boolean(process.env.NEUTTS_URL),
7
+ handler: async ({ text, voice = 'default' }) => {
8
+ if (!process.env.NEUTTS_URL) return { error: 'NEUTTS_URL required' }
9
+ const r = await fetch(process.env.NEUTTS_URL + '/synthesize', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ text, voice }) })
10
+ return { status: r.status, bytes: (await r.arrayBuffer()).byteLength }
11
+ },
12
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-neutts_synth', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,12 @@
1
+ export const _tool = ({
2
+ name: 'openrouter',
3
+ toolset: 'core',
4
+ schema: { name: 'openrouter', description: 'Chat completion via OpenRouter (any model).', parameters: { type: 'object', properties: { prompt: { type: 'string' }, model: { type: 'string', default: 'anthropic/claude-sonnet-4' } }, required: ['prompt'] } },
5
+ requiresEnv: ['OPENROUTER_API_KEY'],
6
+ checkFn: () => Boolean(process.env.OPENROUTER_API_KEY),
7
+ handler: async ({ prompt, model = 'anthropic/claude-sonnet-4' }) => {
8
+ if (!process.env.OPENROUTER_API_KEY) return { error: 'OPENROUTER_API_KEY required' }
9
+ const r = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }] }) })
10
+ return await r.json()
11
+ },
12
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-openrouter_client', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,10 @@
1
+ export const _tool = ({
2
+ name: 'osv_check',
3
+ toolset: 'core',
4
+ schema: { name: 'osv_check', description: 'Query osv.dev for known vulnerabilities. Pass either {package, version, ecosystem} or {commit_sha, repo}.', parameters: { type: 'object', properties: { package: { type: 'string' }, version: { type: 'string' }, ecosystem: { type: 'string' }, commit_sha: { type: 'string' } } } },
5
+ handler: async (args) => {
6
+ const body = args.commit_sha ? { commit: args.commit_sha } : { package: { name: args.package, ecosystem: args.ecosystem || 'npm' }, version: args.version }
7
+ const r = await fetch('https://api.osv.dev/v1/query', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
8
+ return await r.json()
9
+ },
10
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-osv_check', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ export function applyUnifiedDiff(diff, { cwd = process.cwd() } = {}) {
4
+ const lines = diff.split('\n')
5
+ const results = []
6
+ let curFile = null, curHunks = [], curHunk = null
7
+ const flush = () => {
8
+ if (!curFile) return
9
+ const file = path.join(cwd, curFile)
10
+ if (!fs.existsSync(file)) { results.push({ file: curFile, error: 'not found' }); curFile = null; curHunks = []; return }
11
+ let src = fs.readFileSync(file, 'utf8').split('\n')
12
+ for (const h of curHunks) {
13
+ const before = src.slice(0, h.start)
14
+ const after = src.slice(h.start + h.removed)
15
+ src = [...before, ...h.added, ...after]
16
+ }
17
+ fs.writeFileSync(file, src.join('\n'), 'utf8')
18
+ results.push({ file: curFile, applied: curHunks.length })
19
+ curFile = null; curHunks = []
20
+ }
21
+ for (const l of lines) {
22
+ if (l.startsWith('--- ')) { flush(); curFile = l.slice(6).trim() }
23
+ else if (l.startsWith('+++ ')) {}
24
+ else if (l.startsWith('@@ ')) {
25
+ const m = l.match(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/)
26
+ if (m) { curHunk = { start: Number(m[1]) - 1, removed: Number(m[2]), added: [] }; curHunks.push(curHunk) }
27
+ } else if (curHunk) {
28
+ if (l.startsWith('+')) curHunk.added.push(l.slice(1))
29
+ else if (l.startsWith(' ')) curHunk.added.push(l.slice(1))
30
+ }
31
+ }
32
+ flush()
33
+ return results
34
+ }
35
+ export const _tool = ({
36
+ name: 'patch_parser',
37
+ toolset: 'core',
38
+ schema: { name: 'patch_parser', description: 'Apply a unified diff to files in cwd. Returns per-file results.', parameters: { type: 'object', properties: { diff: { type: 'string' }, cwd: { type: 'string' } }, required: ['diff'] } },
39
+ handler: async ({ diff, cwd }) => ({ results: applyUnifiedDiff(diff, { cwd }) }),
40
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-patch_parser', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,14 @@
1
+ import path from 'node:path'
2
+ const FORBIDDEN = ['/etc/passwd', '/etc/shadow', '/.ssh/', '/.aws/', 'C:\\Windows\\System32']
3
+ export function isPathSafe(p, { cwd = process.cwd() } = {}) {
4
+ const abs = path.resolve(cwd, p)
5
+ for (const bad of FORBIDDEN) if (abs.includes(bad)) return { safe: false, reason: 'forbidden: ' + bad }
6
+ if (abs.includes('..')) return { safe: false, reason: 'parent reference in resolved path' }
7
+ return { safe: true, abs }
8
+ }
9
+ export const _tool = ({
10
+ name: 'path_security',
11
+ toolset: 'core',
12
+ schema: { name: 'path_security', description: 'Check whether a path is allowed (no /etc/passwd, no .ssh/, etc).', parameters: { type: 'object', properties: { path: { type: 'string' }, cwd: { type: 'string' } }, required: ['path'] } },
13
+ handler: async ({ path: p, cwd }) => isPathSafe(p, { cwd }),
14
+ })
@@ -0,0 +1,2 @@
1
+ import { _tool } from './handler.js'
2
+ export default { name: 'tool-path_security', surfaces: 'pi', register({ pi }) { pi.tools.register(_tool) } }
@@ -0,0 +1,21 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class ApiServerAdapter extends EventEmitter {
5
+ constructor({ port = 0 } = {}) { super(); this.port = port; this._server = null; this._messages = [] }
6
+ async start() {
7
+ const app = express()
8
+ app.use(express.json())
9
+ app.post('/messages', (req, res) => {
10
+ const m = { from: req.body?.from || 'api', text: req.body?.text || '', raw: req.body }
11
+ this.emit('message', m)
12
+ res.json({ ok: true })
13
+ })
14
+ app.get('/messages', (_, res) => res.json(this._messages))
15
+ await new Promise(resolve => { this._server = app.listen(this.port, () => resolve()) })
16
+ this.port = this._server.address().port
17
+ }
18
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
19
+ async send(reply) { this._messages.push(reply) }
20
+ drain() { const out = [...this._messages]; this._messages.length = 0; return out }
21
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-api_server', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'api_server', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class BluebubblesAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'bluebubbles'
8
+ this.token = opts.token || process.env.BLUEBUBBLES_PASSWORD
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "http://localhost:1234/api/v1/message/text"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["BLUEBUBBLES_PASSWORD"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('BluebubblesAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('BluebubblesAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-bluebubbles', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'bluebubbles', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class DingtalkAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'dingtalk'
8
+ this.token = opts.token || process.env.DINGTALK_ACCESS_TOKEN
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "https://oapi.dingtalk.com/robot/send"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["DINGTALK_ACCESS_TOKEN"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('DingtalkAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('DingtalkAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-dingtalk', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'dingtalk', module: adapter }) } }
@@ -0,0 +1,24 @@
1
+ import { EventEmitter } from 'node:events'
2
+
3
+ export class DiscordAdapter extends EventEmitter {
4
+ constructor(opts = {}) {
5
+ super()
6
+ this.platform = 'discord'
7
+ this.token = opts.token || process.env.DISCORD_BOT_TOKEN
8
+ this.api = opts.api || 'https://discord.com/api/v10'
9
+ this._ws = null
10
+ }
11
+ getRequiredEnv() { return ['DISCORD_BOT_TOKEN'] }
12
+ async start() {
13
+ if (!this.token) throw new Error('DiscordAdapter: DISCORD_BOT_TOKEN required')
14
+ const gw = await fetch(`${this.api}/gateway/bot`, { headers: { authorization: `Bot ${this.token}` } }).then(r => r.json())
15
+ if (!gw.url) throw new Error('DiscordAdapter: gateway lookup failed: ' + JSON.stringify(gw))
16
+ this.gatewayUrl = gw.url + '/?v=10&encoding=json'
17
+ }
18
+ async stop() { try { this._ws?.close?.() } catch {} }
19
+ async send(reply) {
20
+ if (!this.token) throw new Error('DiscordAdapter: token required')
21
+ const url = `${this.api}/channels/${reply.to}/messages`
22
+ return fetch(url, { method: 'POST', headers: { authorization: `Bot ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ content: reply.text }) }).then(r => r.json())
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-discord', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'discord', module: adapter }) } }
@@ -0,0 +1,51 @@
1
+ import { EventEmitter } from 'node:events'
2
+ import net from 'node:net'
3
+
4
+ export class EmailAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'email'
8
+ this.smtpHost = opts.smtpHost || process.env.SMTP_HOST
9
+ this.smtpPort = opts.smtpPort || Number(process.env.SMTP_PORT || 587)
10
+ this.smtpUser = opts.smtpUser || process.env.SMTP_USER
11
+ this.smtpPass = opts.smtpPass || process.env.SMTP_PASS
12
+ this.imapHost = opts.imapHost || process.env.IMAP_HOST
13
+ this._running = false
14
+ }
15
+ getRequiredEnv() { return ['SMTP_HOST', 'SMTP_USER', 'SMTP_PASS'] }
16
+ async start() {
17
+ if (!this.smtpHost || !this.smtpUser || !this.smtpPass) throw new Error('EmailAdapter: SMTP_HOST/USER/PASS required')
18
+ this._running = true
19
+ }
20
+ async stop() { this._running = false }
21
+ async send(reply) {
22
+ return new Promise((resolve, reject) => {
23
+ const sock = net.createConnection(this.smtpPort, this.smtpHost)
24
+ const lines = []
25
+ const send = s => sock.write(s + '\r\n')
26
+ sock.on('data', d => {
27
+ const text = d.toString()
28
+ lines.push(text)
29
+ const code = parseInt(text.slice(0, 3), 10)
30
+ if (code >= 400) { sock.end(); return reject(new Error('SMTP error: ' + text)) }
31
+ })
32
+ sock.on('error', reject)
33
+ sock.on('connect', () => {
34
+ send('EHLO freddie')
35
+ send('AUTH LOGIN')
36
+ send(Buffer.from(this.smtpUser).toString('base64'))
37
+ send(Buffer.from(this.smtpPass).toString('base64'))
38
+ send('MAIL FROM:<' + this.smtpUser + '>')
39
+ send('RCPT TO:<' + reply.to + '>')
40
+ send('DATA')
41
+ send('Subject: ' + (reply.subject || 'freddie'))
42
+ send('To: ' + reply.to)
43
+ send('')
44
+ send(reply.text)
45
+ send('.')
46
+ send('QUIT')
47
+ setTimeout(() => { sock.end(); resolve({ ok: true, log: lines.join('') }) }, 500)
48
+ })
49
+ })
50
+ }
51
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-email', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'email', module: adapter }) } }
@@ -0,0 +1,32 @@
1
+ import express from 'express'
2
+ import { EventEmitter } from 'node:events'
3
+
4
+ export class FeishuAdapter extends EventEmitter {
5
+ constructor(opts = {}) {
6
+ super()
7
+ this.platform = 'feishu'
8
+ this.token = opts.token || process.env.FEISHU_APP_TOKEN
9
+ this.port = opts.port || 0
10
+ this.api = opts.api || "https://open.feishu.cn/open-apis/im/v1/messages"
11
+ this._server = null
12
+ }
13
+ getRequiredEnv() { return ["FEISHU_APP_TOKEN"] }
14
+ async start() {
15
+ if (!this.token) throw new Error('FeishuAdapter: ' + this.getRequiredEnv().join(', ') + ' required')
16
+ const app = express()
17
+ app.use(express.json())
18
+ app.post('/webhook', (req, res) => {
19
+ const text = req.body?.text || req.body?.message?.text || req.body?.content || ''
20
+ const from = req.body?.from || req.body?.user_id || req.body?.sender_id || ''
21
+ this.emit('message', { from: String(from), text, raw: req.body })
22
+ res.json({ ok: true })
23
+ })
24
+ await new Promise(r => { this._server = app.listen(this.port, () => r()) })
25
+ this.port = this._server.address().port
26
+ }
27
+ async stop() { if (this._server) await new Promise(r => this._server.close(() => r())) }
28
+ async send(reply) {
29
+ if (!this.token) throw new Error('FeishuAdapter: token required')
30
+ return fetch(this.api, { method: 'POST', headers: { authorization: `Bearer ${this.token}`, 'content-type': 'application/json' }, body: JSON.stringify({ to: reply.to, text: reply.text }) }).then(r => r.json())
31
+ }
32
+ }
@@ -0,0 +1,2 @@
1
+ import * as adapter from './handler.js'
2
+ export default { name: 'platform-feishu', surfaces: 'pi', register({ pi }) { pi.platforms.register({ name: 'feishu', module: adapter }) } }
@@ -0,0 +1,12 @@
1
+ export async function listComments({ token, docToken }) {
2
+ const r = await fetch('https://open.feishu.cn/open-apis/comments/v1/files/' + docToken + '/comments', { headers: { authorization: 'Bearer ' + token } })
3
+ return await r.json()
4
+ }
5
+ export async function postComment({ token, docToken, content }) {
6
+ const r = await fetch('https://open.feishu.cn/open-apis/comments/v1/files/' + docToken + '/comments', { method: 'POST', headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, body: JSON.stringify({ comment: { content } }) })
7
+ return await r.json()
8
+ }
9
+ export async function resolveComment({ token, docToken, commentId }) {
10
+ const r = await fetch('https://open.feishu.cn/open-apis/comments/v1/files/' + docToken + '/comments/' + commentId + '/patch', { method: 'PATCH', headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, body: JSON.stringify({ is_solved: true }) })
11
+ return await r.json()
12
+ }