@swarmclawai/swarmclaw 0.7.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/README.md +12 -14
  2. package/next.config.ts +13 -2
  3. package/package.json +4 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +9 -0
  5. package/src/app/api/agents/route.ts +4 -0
  6. package/src/app/api/agents/thread-route.test.ts +133 -0
  7. package/src/app/api/approvals/route.test.ts +148 -0
  8. package/src/app/api/canvas/[sessionId]/route.ts +3 -1
  9. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
  10. package/src/app/api/chats/[id]/devserver/route.ts +48 -7
  11. package/src/app/api/chats/[id]/messages/route.ts +42 -18
  12. package/src/app/api/chats/[id]/route.ts +1 -1
  13. package/src/app/api/chats/[id]/stop/route.ts +5 -4
  14. package/src/app/api/chats/route.ts +23 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +46 -3
  17. package/src/app/api/connectors/route.ts +12 -8
  18. package/src/app/api/external-agents/route.test.ts +165 -0
  19. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  20. package/src/app/api/gateways/[id]/route.ts +2 -0
  21. package/src/app/api/gateways/health-route.test.ts +135 -0
  22. package/src/app/api/gateways/route.ts +2 -0
  23. package/src/app/api/mcp-servers/route.test.ts +130 -0
  24. package/src/app/api/openclaw/deploy/route.ts +38 -5
  25. package/src/app/api/plugins/install/route.ts +46 -6
  26. package/src/app/api/plugins/marketplace/route.ts +48 -15
  27. package/src/app/api/preview-server/route.ts +26 -11
  28. package/src/app/api/projects/[id]/route.ts +6 -2
  29. package/src/app/api/projects/route.ts +4 -3
  30. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  31. package/src/app/api/schedules/route.test.ts +86 -0
  32. package/src/app/api/schedules/route.ts +6 -1
  33. package/src/app/api/secrets/[id]/route.ts +1 -0
  34. package/src/app/api/secrets/route.ts +2 -1
  35. package/src/app/api/settings/route.ts +2 -0
  36. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  37. package/src/app/api/setup/check-provider/route.ts +40 -10
  38. package/src/app/api/skills/[id]/route.ts +12 -0
  39. package/src/app/api/skills/import/route.ts +14 -12
  40. package/src/app/api/skills/route.ts +13 -1
  41. package/src/app/api/tasks/[id]/route.ts +10 -1
  42. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  43. package/src/app/api/tasks/import/github/route.ts +337 -0
  44. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  45. package/src/app/api/wallets/[id]/route.ts +79 -33
  46. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  47. package/src/app/api/wallets/route.ts +78 -61
  48. package/src/app/api/webhooks/[id]/route.ts +33 -6
  49. package/src/app/api/webhooks/route.test.ts +272 -0
  50. package/src/cli/index.js +1 -0
  51. package/src/cli/spec.js +1 -0
  52. package/src/components/agents/agent-card.tsx +9 -2
  53. package/src/components/agents/agent-chat-list.tsx +18 -2
  54. package/src/components/agents/agent-list.tsx +1 -0
  55. package/src/components/agents/agent-sheet.tsx +257 -38
  56. package/src/components/agents/inspector-panel.tsx +41 -0
  57. package/src/components/canvas/canvas-panel.tsx +236 -65
  58. package/src/components/chat/chat-area.tsx +36 -19
  59. package/src/components/chat/chat-card.tsx +36 -13
  60. package/src/components/chat/chat-header.tsx +48 -16
  61. package/src/components/chat/chat-list.tsx +28 -4
  62. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  63. package/src/components/chat/delegation-banner.test.ts +14 -1
  64. package/src/components/chat/delegation-banner.tsx +1 -1
  65. package/src/components/chat/message-bubble.tsx +208 -145
  66. package/src/components/chat/message-list.tsx +48 -19
  67. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  68. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  69. package/src/components/connectors/connector-health.tsx +1 -1
  70. package/src/components/connectors/connector-list.tsx +7 -2
  71. package/src/components/connectors/connector-sheet.tsx +337 -148
  72. package/src/components/gateways/gateway-sheet.tsx +2 -2
  73. package/src/components/layout/app-layout.tsx +40 -23
  74. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  75. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  76. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  77. package/src/components/plugins/plugin-list.tsx +45 -9
  78. package/src/components/plugins/plugin-sheet.tsx +55 -7
  79. package/src/components/projects/project-detail.tsx +217 -0
  80. package/src/components/projects/project-sheet.tsx +176 -4
  81. package/src/components/providers/provider-list.tsx +2 -1
  82. package/src/components/providers/provider-sheet.tsx +21 -2
  83. package/src/components/schedules/schedule-card.tsx +25 -1
  84. package/src/components/schedules/schedule-sheet.tsx +44 -2
  85. package/src/components/secrets/secret-sheet.tsx +21 -2
  86. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  87. package/src/components/shared/bottom-sheet.tsx +13 -3
  88. package/src/components/shared/command-palette.tsx +8 -1
  89. package/src/components/shared/confirm-dialog.tsx +19 -4
  90. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  91. package/src/components/shared/connector-platform-icon.tsx +39 -6
  92. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  93. package/src/components/shared/settings/section-capability-policy.tsx +45 -3
  94. package/src/components/shared/settings/section-voice.tsx +11 -3
  95. package/src/components/skills/skill-list.tsx +25 -0
  96. package/src/components/skills/skill-sheet.tsx +84 -12
  97. package/src/components/tasks/approvals-panel.tsx +289 -34
  98. package/src/components/tasks/task-board.tsx +410 -25
  99. package/src/components/tasks/task-card.tsx +66 -8
  100. package/src/components/tasks/task-sheet.tsx +16 -4
  101. package/src/components/ui/dialog.tsx +2 -2
  102. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  103. package/src/components/wallets/wallet-panel.tsx +435 -90
  104. package/src/components/wallets/wallet-section.tsx +198 -48
  105. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  106. package/src/lib/approval-display.ts +20 -0
  107. package/src/lib/canvas-content.ts +198 -0
  108. package/src/lib/chat-artifact-summary.ts +165 -0
  109. package/src/lib/chat-display.test.ts +91 -0
  110. package/src/lib/chat-display.ts +58 -0
  111. package/src/lib/chat-streaming-state.test.ts +47 -1
  112. package/src/lib/chat-streaming-state.ts +42 -0
  113. package/src/lib/ollama-model.ts +10 -0
  114. package/src/lib/openclaw-endpoint.test.ts +8 -0
  115. package/src/lib/openclaw-endpoint.ts +6 -1
  116. package/src/lib/plugin-install-cors.ts +46 -0
  117. package/src/lib/plugin-sources.test.ts +43 -0
  118. package/src/lib/plugin-sources.ts +77 -0
  119. package/src/lib/providers/ollama.ts +16 -6
  120. package/src/lib/providers/openclaw.test.ts +54 -0
  121. package/src/lib/providers/openclaw.ts +127 -11
  122. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  123. package/src/lib/schedule-dedupe.test.ts +66 -1
  124. package/src/lib/schedule-dedupe.ts +169 -12
  125. package/src/lib/schedule-origin.test.ts +20 -0
  126. package/src/lib/schedule-origin.ts +15 -0
  127. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  128. package/src/lib/server/agent-availability.ts +16 -0
  129. package/src/lib/server/agent-runtime-config.ts +12 -4
  130. package/src/lib/server/agent-thread-session.test.ts +51 -0
  131. package/src/lib/server/agent-thread-session.ts +7 -0
  132. package/src/lib/server/approval-match.ts +205 -0
  133. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  134. package/src/lib/server/approvals.ts +214 -1
  135. package/src/lib/server/assistant-control.test.ts +29 -0
  136. package/src/lib/server/assistant-control.ts +23 -0
  137. package/src/lib/server/build-llm.test.ts +79 -0
  138. package/src/lib/server/build-llm.ts +14 -4
  139. package/src/lib/server/canvas-content.test.ts +32 -0
  140. package/src/lib/server/canvas-content.ts +6 -0
  141. package/src/lib/server/capability-router.test.ts +33 -0
  142. package/src/lib/server/capability-router.ts +80 -19
  143. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  144. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  145. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  146. package/src/lib/server/chat-execution.ts +378 -73
  147. package/src/lib/server/clawhub-client.test.ts +14 -8
  148. package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
  149. package/src/lib/server/connectors/manager.test.ts +1147 -0
  150. package/src/lib/server/connectors/manager.ts +461 -137
  151. package/src/lib/server/connectors/pairing.ts +26 -5
  152. package/src/lib/server/connectors/types.ts +2 -0
  153. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  154. package/src/lib/server/connectors/whatsapp.ts +271 -47
  155. package/src/lib/server/context-manager.ts +6 -1
  156. package/src/lib/server/daemon-state.ts +84 -47
  157. package/src/lib/server/data-dir.test.ts +37 -0
  158. package/src/lib/server/data-dir.ts +20 -1
  159. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  160. package/src/lib/server/devserver-launch.test.ts +60 -0
  161. package/src/lib/server/devserver-launch.ts +85 -0
  162. package/src/lib/server/elevenlabs.test.ts +247 -1
  163. package/src/lib/server/elevenlabs.ts +147 -43
  164. package/src/lib/server/ethereum.ts +590 -0
  165. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  166. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  167. package/src/lib/server/eval/agent-regression.ts +383 -11
  168. package/src/lib/server/evm-swap.ts +475 -0
  169. package/src/lib/server/execution-log.ts +1 -0
  170. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  171. package/src/lib/server/heartbeat-service.ts +20 -11
  172. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  173. package/src/lib/server/heartbeat-wake.ts +338 -57
  174. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  175. package/src/lib/server/main-agent-loop.test.ts +260 -0
  176. package/src/lib/server/main-agent-loop.ts +559 -14
  177. package/src/lib/server/mcp-client.test.ts +16 -0
  178. package/src/lib/server/mcp-client.ts +25 -0
  179. package/src/lib/server/memory-integration.test.ts +719 -0
  180. package/src/lib/server/memory-policy.test.ts +43 -0
  181. package/src/lib/server/memory-policy.ts +132 -0
  182. package/src/lib/server/memory-tiers.test.ts +60 -0
  183. package/src/lib/server/memory-tiers.ts +16 -0
  184. package/src/lib/server/ollama-runtime.ts +58 -0
  185. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  186. package/src/lib/server/openclaw-deploy.ts +557 -81
  187. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  188. package/src/lib/server/openclaw-gateway.ts +10 -4
  189. package/src/lib/server/openclaw-health.test.ts +35 -0
  190. package/src/lib/server/openclaw-health.ts +215 -47
  191. package/src/lib/server/orchestrator-lg.ts +3 -2
  192. package/src/lib/server/orchestrator.ts +2 -0
  193. package/src/lib/server/plugins-advanced.test.ts +351 -0
  194. package/src/lib/server/plugins.ts +211 -6
  195. package/src/lib/server/project-context.ts +162 -0
  196. package/src/lib/server/project-utils.ts +150 -0
  197. package/src/lib/server/queue-advanced.test.ts +528 -0
  198. package/src/lib/server/queue-followups.test.ts +409 -2
  199. package/src/lib/server/queue-reconcile.test.ts +128 -0
  200. package/src/lib/server/queue.ts +527 -68
  201. package/src/lib/server/scheduler.ts +29 -1
  202. package/src/lib/server/session-note.test.ts +36 -0
  203. package/src/lib/server/session-note.ts +42 -0
  204. package/src/lib/server/session-run-manager.ts +83 -4
  205. package/src/lib/server/session-tools/canvas.ts +14 -12
  206. package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
  207. package/src/lib/server/session-tools/connector.test.ts +138 -0
  208. package/src/lib/server/session-tools/connector.ts +366 -54
  209. package/src/lib/server/session-tools/context.ts +17 -3
  210. package/src/lib/server/session-tools/crud.ts +484 -84
  211. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  212. package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
  213. package/src/lib/server/session-tools/delegate.ts +102 -10
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  215. package/src/lib/server/session-tools/discovery.ts +80 -12
  216. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  217. package/src/lib/server/session-tools/file.ts +43 -4
  218. package/src/lib/server/session-tools/human-loop.ts +35 -5
  219. package/src/lib/server/session-tools/index.ts +44 -9
  220. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  221. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  222. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  223. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  224. package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
  225. package/src/lib/server/session-tools/memory.test.ts +93 -0
  226. package/src/lib/server/session-tools/memory.ts +554 -75
  227. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  228. package/src/lib/server/session-tools/platform-access.test.ts +58 -0
  229. package/src/lib/server/session-tools/platform.ts +60 -19
  230. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  231. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  232. package/src/lib/server/session-tools/schedule.ts +6 -1
  233. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  234. package/src/lib/server/session-tools/shell.ts +22 -3
  235. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  236. package/src/lib/server/session-tools/wallet.ts +1374 -139
  237. package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
  238. package/src/lib/server/session-tools/web.ts +621 -70
  239. package/src/lib/server/skill-discovery.ts +128 -0
  240. package/src/lib/server/skill-eligibility.test.ts +84 -0
  241. package/src/lib/server/skill-eligibility.ts +95 -0
  242. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  243. package/src/lib/server/skill-prompt-budget.ts +125 -0
  244. package/src/lib/server/skills-normalize.test.ts +54 -0
  245. package/src/lib/server/skills-normalize.ts +372 -26
  246. package/src/lib/server/solana.ts +214 -29
  247. package/src/lib/server/storage.ts +65 -36
  248. package/src/lib/server/stream-agent-chat.test.ts +437 -2
  249. package/src/lib/server/stream-agent-chat.ts +957 -79
  250. package/src/lib/server/system-events.ts +1 -1
  251. package/src/lib/server/tool-aliases.ts +2 -0
  252. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  253. package/src/lib/server/tool-capability-policy.test.ts +24 -0
  254. package/src/lib/server/tool-capability-policy.ts +29 -1
  255. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  256. package/src/lib/server/tool-loop-detection.ts +260 -0
  257. package/src/lib/server/tool-planning.test.ts +44 -0
  258. package/src/lib/server/tool-planning.ts +271 -0
  259. package/src/lib/server/wallet-execution.test.ts +198 -0
  260. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  261. package/src/lib/server/wallet-portfolio.ts +724 -0
  262. package/src/lib/server/wallet-service.test.ts +57 -0
  263. package/src/lib/server/wallet-service.ts +213 -0
  264. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  265. package/src/lib/server/watch-jobs.ts +17 -2
  266. package/src/lib/server/workspace-context.ts +111 -0
  267. package/src/lib/skill-save-payload.test.ts +39 -0
  268. package/src/lib/skill-save-payload.ts +37 -0
  269. package/src/lib/tasks.ts +28 -0
  270. package/src/lib/tool-definitions.ts +2 -1
  271. package/src/lib/tool-event-summary.test.ts +30 -0
  272. package/src/lib/tool-event-summary.ts +37 -0
  273. package/src/lib/validation/schemas.ts +1 -0
  274. package/src/lib/wallet-transactions.test.ts +75 -0
  275. package/src/lib/wallet-transactions.ts +43 -0
  276. package/src/lib/wallet.test.ts +17 -0
  277. package/src/lib/wallet.ts +183 -0
  278. package/src/proxy.test.ts +31 -0
  279. package/src/proxy.ts +34 -2
  280. package/src/stores/use-chat-store.ts +15 -1
  281. package/src/types/index.ts +249 -14
@@ -33,6 +33,57 @@ function runWithTempDataDir(script: string) {
33
33
  }
34
34
 
35
35
  describe('approval auto-approve', () => {
36
+ it('defaults new installs to approvals auto-run', () => {
37
+ const output = runWithTempDataDir(`
38
+ const storageMod = await import('./src/lib/server/storage.ts')
39
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
40
+ const storage = storageMod.default || storageMod
41
+ const approvals = approvalsMod.default || approvalsMod
42
+
43
+ const now = Date.now()
44
+ storage.saveSessions({
45
+ session_default: {
46
+ id: 'session_default',
47
+ name: 'Default Approval Policy Test',
48
+ cwd: process.cwd(),
49
+ user: 'tester',
50
+ provider: 'openai',
51
+ model: 'gpt-test',
52
+ credentialId: null,
53
+ apiEndpoint: null,
54
+ claudeSessionId: null,
55
+ codexThreadId: null,
56
+ opencodeSessionId: null,
57
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
58
+ messages: [],
59
+ createdAt: now,
60
+ lastActiveAt: now,
61
+ sessionType: 'human',
62
+ agentId: 'default',
63
+ plugins: [],
64
+ },
65
+ })
66
+
67
+ const settings = storage.loadSettings()
68
+ const approval = await approvals.requestApprovalMaybeAutoApprove({
69
+ category: 'tool_access',
70
+ title: 'Enable Plugin: shell',
71
+ description: 'Need shell access for a task.',
72
+ data: { toolId: 'shell', pluginId: 'shell' },
73
+ sessionId: 'session_default',
74
+ agentId: 'default',
75
+ })
76
+
77
+ console.log(JSON.stringify({
78
+ approvalsEnabled: settings.approvalsEnabled,
79
+ approvalStatus: approval.status,
80
+ }))
81
+ `)
82
+
83
+ assert.equal(output.approvalsEnabled, false)
84
+ assert.equal(output.approvalStatus, 'approved')
85
+ })
86
+
36
87
  it('auto-approves tool access and plugin scaffolds when configured', () => {
37
88
  const output = runWithTempDataDir(`
38
89
  const storageMod = await import('./src/lib/server/storage.ts')
@@ -115,8 +166,34 @@ describe('approval auto-approve', () => {
115
166
  const output = runWithTempDataDir(`
116
167
  const storageMod = await import('./src/lib/server/storage.ts')
117
168
  const approvalsMod = await import('./src/lib/server/approvals.ts')
169
+ const sessionRunsMod = await import('./src/lib/server/session-run-manager.ts')
118
170
  const storage = storageMod.default || storageMod
119
171
  const approvals = approvalsMod.default || approvalsMod
172
+ const sessionRuns = sessionRunsMod.default || sessionRunsMod
173
+
174
+ const now = Date.now()
175
+ storage.saveSessions({
176
+ session_disabled: {
177
+ id: 'session_disabled',
178
+ name: 'Disabled Approval Session',
179
+ cwd: process.cwd(),
180
+ user: 'tester',
181
+ provider: 'openai',
182
+ model: 'gpt-test',
183
+ credentialId: null,
184
+ apiEndpoint: null,
185
+ claudeSessionId: null,
186
+ codexThreadId: null,
187
+ opencodeSessionId: null,
188
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
189
+ messages: [],
190
+ createdAt: now,
191
+ lastActiveAt: now,
192
+ sessionType: 'human',
193
+ agentId: 'default',
194
+ plugins: [],
195
+ },
196
+ })
120
197
 
121
198
  storage.saveSettings({
122
199
  approvalsEnabled: false,
@@ -128,18 +205,22 @@ describe('approval auto-approve', () => {
128
205
  description: 'Should be auto-approved because approvals are disabled platform-wide.',
129
206
  data: { question: 'Proceed?' },
130
207
  agentId: 'default',
131
- sessionId: null,
208
+ sessionId: 'session_disabled',
132
209
  })
133
210
 
211
+ await new Promise((resolve) => setTimeout(resolve, 500))
212
+
134
213
  const stored = storage.loadApprovals()[approval.id]
135
214
  console.log(JSON.stringify({
136
215
  approvalStatus: approval.status,
137
216
  storedStatus: stored?.status || null,
217
+ runCount: sessionRuns.listRuns({ sessionId: 'session_disabled', limit: 10 }).length,
138
218
  }))
139
219
  `)
140
220
 
141
221
  assert.equal(output.approvalStatus, 'approved')
142
222
  assert.equal(output.storedStatus, 'approved')
223
+ assert.equal(output.runCount, 0)
143
224
  })
144
225
 
145
226
  it('adds a pending approval request message to the chat session when approvals are enabled', () => {
@@ -153,6 +234,7 @@ describe('approval auto-approve', () => {
153
234
  storage.saveSettings({
154
235
  approvalsEnabled: true,
155
236
  approvalAutoApproveCategories: [],
237
+ approvalConnectorNotifyDelaySec: 30,
156
238
  })
157
239
  storage.saveSessions({
158
240
  session_chat: {
@@ -203,8 +285,270 @@ describe('approval auto-approve', () => {
203
285
  assert.match(output.lastMessage.text, /\"pluginId\":\"shell\"/)
204
286
  })
205
287
 
288
+ it('injects approval guidance only from enabled plugins', () => {
289
+ const output = runWithTempDataDir(`
290
+ process.on('unhandledRejection', () => {})
291
+ await import('./src/lib/server/session-tools/wallet.ts')
292
+ const storageMod = await import('./src/lib/server/storage.ts')
293
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
294
+ const storage = storageMod.default || storageMod
295
+ const approvals = approvalsMod.default || approvalsMod
296
+
297
+ const now = Date.now()
298
+ storage.saveAgents({
299
+ agent_wallet: {
300
+ id: 'agent_wallet',
301
+ name: 'Wallet Agent',
302
+ description: 'Tests plugin approval guidance',
303
+ systemPrompt: 'test',
304
+ provider: 'openai',
305
+ model: 'gpt-test',
306
+ plugins: ['wallet'],
307
+ createdAt: now,
308
+ updatedAt: now,
309
+ },
310
+ agent_plain: {
311
+ id: 'agent_plain',
312
+ name: 'Plain Agent',
313
+ description: 'No wallet plugin',
314
+ systemPrompt: 'test',
315
+ provider: 'openai',
316
+ model: 'gpt-test',
317
+ plugins: [],
318
+ createdAt: now,
319
+ updatedAt: now,
320
+ },
321
+ })
322
+ storage.saveSettings({
323
+ approvalsEnabled: true,
324
+ approvalAutoApproveCategories: [],
325
+ })
326
+ storage.saveSessions({
327
+ session_wallet_guidance: {
328
+ id: 'session_wallet_guidance',
329
+ name: 'Wallet Guidance Session',
330
+ cwd: process.cwd(),
331
+ user: 'tester',
332
+ provider: 'openai',
333
+ model: 'gpt-test',
334
+ credentialId: null,
335
+ apiEndpoint: null,
336
+ claudeSessionId: null,
337
+ codexThreadId: null,
338
+ opencodeSessionId: null,
339
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
340
+ messages: [],
341
+ createdAt: now,
342
+ lastActiveAt: now,
343
+ sessionType: 'human',
344
+ agentId: 'agent_wallet',
345
+ plugins: ['wallet'],
346
+ connectorContext: {
347
+ connectorId: 'connector_wallet',
348
+ channelId: 'chan_wallet',
349
+ },
350
+ },
351
+ session_plain_guidance: {
352
+ id: 'session_plain_guidance',
353
+ name: 'Plain Guidance Session',
354
+ cwd: process.cwd(),
355
+ user: 'tester',
356
+ provider: 'openai',
357
+ model: 'gpt-test',
358
+ credentialId: null,
359
+ apiEndpoint: null,
360
+ claudeSessionId: null,
361
+ codexThreadId: null,
362
+ opencodeSessionId: null,
363
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
364
+ messages: [],
365
+ createdAt: now,
366
+ lastActiveAt: now,
367
+ sessionType: 'human',
368
+ agentId: 'agent_plain',
369
+ plugins: [],
370
+ },
371
+ })
372
+
373
+ const walletApproval = await approvals.requestApprovalMaybeAutoApprove({
374
+ category: 'wallet_action',
375
+ title: 'Wallet action: send transaction',
376
+ description: 'Broadcast an Ethereum transaction on Arbitrum.',
377
+ data: {
378
+ action: 'send_transaction',
379
+ chain: 'ethereum',
380
+ network: 'arbitrum',
381
+ summary: 'swap 1 USDC',
382
+ transaction: {
383
+ to: '0x000000000000000000000000000000000000dEaD',
384
+ data: '0x1234',
385
+ value: '0',
386
+ },
387
+ },
388
+ sessionId: 'session_wallet_guidance',
389
+ agentId: 'agent_wallet',
390
+ })
391
+
392
+ const plainApproval = await approvals.requestApprovalMaybeAutoApprove({
393
+ category: 'wallet_action',
394
+ title: 'Wallet action: send transaction',
395
+ description: 'Broadcast an Ethereum transaction on Arbitrum.',
396
+ data: {
397
+ action: 'send_transaction',
398
+ chain: 'ethereum',
399
+ network: 'arbitrum',
400
+ summary: 'swap 1 USDC',
401
+ transaction: {
402
+ to: '0x000000000000000000000000000000000000dEaD',
403
+ data: '0x1234',
404
+ value: '0',
405
+ },
406
+ },
407
+ sessionId: 'session_plain_guidance',
408
+ agentId: 'agent_plain',
409
+ })
410
+
411
+ const walletMessage = JSON.parse(storage.loadSessions().session_wallet_guidance.messages.at(-1).text)
412
+ const plainMessage = JSON.parse(storage.loadSessions().session_plain_guidance.messages.at(-1).text)
413
+ console.log(JSON.stringify({
414
+ walletGuidance: walletMessage.guidance || [],
415
+ plainGuidance: plainMessage.guidance || [],
416
+ }))
417
+ `)
418
+
419
+ assert.equal(Array.isArray(output.walletGuidance), true)
420
+ assert.equal(output.walletGuidance.some((line: string) => /wallet_tool/.test(line)), true)
421
+ assert.deepEqual(output.plainGuidance, [])
422
+ })
423
+
424
+ it('derives tool-access approval guidance from the requested plugin metadata', () => {
425
+ const output = runWithTempDataDir(`
426
+ process.on('unhandledRejection', () => {})
427
+ await import('./src/lib/server/session-tools/http.ts')
428
+ const storageMod = await import('./src/lib/server/storage.ts')
429
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
430
+ const storage = storageMod.default || storageMod
431
+ const approvals = approvalsMod.default || approvalsMod
432
+
433
+ const now = Date.now()
434
+ storage.saveSettings({
435
+ approvalsEnabled: true,
436
+ approvalAutoApproveCategories: [],
437
+ })
438
+ storage.saveSessions({
439
+ session_http_guidance: {
440
+ id: 'session_http_guidance',
441
+ name: 'HTTP Guidance Session',
442
+ cwd: process.cwd(),
443
+ user: 'tester',
444
+ provider: 'openai',
445
+ model: 'gpt-test',
446
+ credentialId: null,
447
+ apiEndpoint: null,
448
+ claudeSessionId: null,
449
+ codexThreadId: null,
450
+ opencodeSessionId: null,
451
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
452
+ messages: [],
453
+ createdAt: now,
454
+ lastActiveAt: now,
455
+ sessionType: 'human',
456
+ agentId: 'agent_http',
457
+ plugins: [],
458
+ },
459
+ })
460
+
461
+ await approvals.requestApprovalMaybeAutoApprove({
462
+ category: 'tool_access',
463
+ title: 'Enable Plugin: http',
464
+ description: 'Need HTTP access for an API task.',
465
+ data: { toolId: 'http', pluginId: 'http' },
466
+ sessionId: 'session_http_guidance',
467
+ agentId: 'agent_http',
468
+ })
469
+
470
+ const message = JSON.parse(storage.loadSessions().session_http_guidance.messages.at(-1).text)
471
+ console.log(JSON.stringify({ guidance: message.guidance || [] }))
472
+ `)
473
+
474
+ assert.equal(Array.isArray(output.guidance), true)
475
+ assert.equal(output.guidance.some((line: string) => /http_request/.test(line)), true)
476
+ })
477
+
478
+ it('injects plugin-owned scaffold guidance for plugin creator approvals', () => {
479
+ const output = runWithTempDataDir(`
480
+ process.on('unhandledRejection', () => {})
481
+ await import('./src/lib/server/session-tools/plugin-creator.ts')
482
+ const storageMod = await import('./src/lib/server/storage.ts')
483
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
484
+ const storage = storageMod.default || storageMod
485
+ const approvals = approvalsMod.default || approvalsMod
486
+
487
+ const now = Date.now()
488
+ storage.saveAgents({
489
+ agent_plugins: {
490
+ id: 'agent_plugins',
491
+ name: 'Plugin Agent',
492
+ description: 'Tests plugin creator approval guidance',
493
+ systemPrompt: 'test',
494
+ provider: 'openai',
495
+ model: 'gpt-test',
496
+ plugins: ['plugin_creator'],
497
+ createdAt: now,
498
+ updatedAt: now,
499
+ },
500
+ })
501
+ storage.saveSettings({
502
+ approvalsEnabled: true,
503
+ approvalAutoApproveCategories: [],
504
+ })
505
+ storage.saveSessions({
506
+ session_plugin_creator_guidance: {
507
+ id: 'session_plugin_creator_guidance',
508
+ name: 'Plugin Creator Guidance Session',
509
+ cwd: process.cwd(),
510
+ user: 'tester',
511
+ provider: 'openai',
512
+ model: 'gpt-test',
513
+ credentialId: null,
514
+ apiEndpoint: null,
515
+ claudeSessionId: null,
516
+ codexThreadId: null,
517
+ opencodeSessionId: null,
518
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
519
+ messages: [],
520
+ createdAt: now,
521
+ lastActiveAt: now,
522
+ sessionType: 'human',
523
+ agentId: 'agent_plugins',
524
+ plugins: ['plugin_creator'],
525
+ },
526
+ })
527
+
528
+ await approvals.requestApprovalMaybeAutoApprove({
529
+ category: 'plugin_scaffold',
530
+ title: 'Scaffold Plugin: test-plugin.js',
531
+ description: 'Create a test plugin file.',
532
+ data: {
533
+ filename: 'test-plugin.js',
534
+ code: 'module.exports = { name: "Test Plugin" }',
535
+ packageManager: 'npm',
536
+ },
537
+ sessionId: 'session_plugin_creator_guidance',
538
+ agentId: 'agent_plugins',
539
+ })
540
+
541
+ const message = JSON.parse(storage.loadSessions().session_plugin_creator_guidance.messages.at(-1).text)
542
+ console.log(JSON.stringify({ guidance: message.guidance || [] }))
543
+ `)
544
+
545
+ assert.equal(Array.isArray(output.guidance), true)
546
+ assert.equal(output.guidance.some((line: string) => /plugin_creator_tool/.test(line)), true)
547
+ })
548
+
206
549
  it('applies tool access after a manual approval decision', () => {
207
550
  const output = runWithTempDataDir(`
551
+ process.on('unhandledRejection', () => {})
208
552
  const storageMod = await import('./src/lib/server/storage.ts')
209
553
  const approvalsMod = await import('./src/lib/server/approvals.ts')
210
554
  const storage = storageMod.default || storageMod
@@ -261,4 +605,197 @@ describe('approval auto-approve', () => {
261
605
  assert.equal(output.finalStatus, 'approved')
262
606
  assert.equal(output.plugins.includes('shell'), true)
263
607
  })
608
+
609
+ it('wakes the blocked session after a manual approval decision', () => {
610
+ const output = runWithTempDataDir(`
611
+ process.on('unhandledRejection', () => {})
612
+ const storageMod = await import('./src/lib/server/storage.ts')
613
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
614
+ const sessionRunsMod = await import('./src/lib/server/session-run-manager.ts')
615
+ const storage = storageMod.default || storageMod
616
+ const approvals = approvalsMod.default || approvalsMod
617
+ const sessionRuns = sessionRunsMod.default || sessionRunsMod
618
+
619
+ const now = Date.now()
620
+ storage.saveSettings({
621
+ approvalsEnabled: true,
622
+ approvalAutoApproveCategories: [],
623
+ })
624
+ storage.saveSessions({
625
+ session_resume: {
626
+ id: 'session_resume',
627
+ name: 'Resume Approval Test',
628
+ cwd: process.cwd(),
629
+ user: 'tester',
630
+ provider: 'openai',
631
+ model: 'gpt-test',
632
+ credentialId: null,
633
+ apiEndpoint: null,
634
+ claudeSessionId: null,
635
+ codexThreadId: null,
636
+ opencodeSessionId: null,
637
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
638
+ messages: [],
639
+ createdAt: now,
640
+ lastActiveAt: now,
641
+ sessionType: 'human',
642
+ agentId: 'default',
643
+ plugins: [],
644
+ },
645
+ })
646
+
647
+ const approval = await approvals.requestApprovalMaybeAutoApprove({
648
+ category: 'wallet_action',
649
+ title: 'Wallet action: sign transaction',
650
+ description: 'Sign an Ethereum transaction on Arbitrum.',
651
+ data: {
652
+ action: 'sign_transaction',
653
+ chain: 'ethereum',
654
+ network: 'arbitrum',
655
+ },
656
+ sessionId: 'session_resume',
657
+ agentId: 'default',
658
+ })
659
+
660
+ await approvals.submitDecision(approval.id, true)
661
+ await approvals.submitDecision(approval.id, true)
662
+ await new Promise((resolve) => setTimeout(resolve, 600))
663
+
664
+ const runs = sessionRuns.listRuns({ sessionId: 'session_resume', limit: 10 })
665
+ console.log(JSON.stringify({
666
+ finalStatus: storage.loadApprovals()[approval.id]?.status || null,
667
+ runCount: runs.length,
668
+ runSources: runs.map((run) => run.source),
669
+ runStatuses: runs.map((run) => run.status),
670
+ }))
671
+ `)
672
+
673
+ assert.equal(output.finalStatus, 'approved')
674
+ assert.equal(output.runCount >= 1, true)
675
+ assert.equal(output.runSources.filter((source) => source === 'approval-decision').length, 1)
676
+ })
677
+
678
+ it('reuses equivalent wallet approvals instead of creating duplicates', () => {
679
+ const output = runWithTempDataDir(`
680
+ process.on('unhandledRejection', () => {})
681
+ const storageMod = await import('./src/lib/server/storage.ts')
682
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
683
+ const storage = storageMod.default || storageMod
684
+ const approvals = approvalsMod.default || approvalsMod
685
+
686
+ const now = Date.now()
687
+ storage.saveSettings({
688
+ approvalsEnabled: true,
689
+ approvalAutoApproveCategories: [],
690
+ })
691
+ storage.saveSessions({
692
+ session_wallet: {
693
+ id: 'session_wallet',
694
+ name: 'Wallet Approval Dedupe Test',
695
+ cwd: process.cwd(),
696
+ user: 'tester',
697
+ provider: 'openai',
698
+ model: 'gpt-test',
699
+ credentialId: null,
700
+ apiEndpoint: null,
701
+ claudeSessionId: null,
702
+ codexThreadId: null,
703
+ opencodeSessionId: null,
704
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
705
+ messages: [],
706
+ createdAt: now,
707
+ lastActiveAt: now,
708
+ sessionType: 'human',
709
+ agentId: 'default',
710
+ plugins: [],
711
+ },
712
+ })
713
+
714
+ const requestParams = {
715
+ category: 'wallet_action',
716
+ title: 'Wallet action: sign transaction',
717
+ description: 'Sign an Ethereum transaction on Arbitrum.',
718
+ data: {
719
+ action: 'sign_transaction',
720
+ chain: 'ethereum',
721
+ network: 'arbitrum',
722
+ transaction: {
723
+ to: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
724
+ data: '0x095ea7b3000000000000000000000000216b4b4ba9f3e719726886d34a177484278bfcae00000000000000000000000000000000000000000000000000000000000f4240',
725
+ value: '0',
726
+ },
727
+ },
728
+ sessionId: 'session_wallet',
729
+ agentId: 'default',
730
+ }
731
+
732
+ const first = await approvals.requestApprovalMaybeAutoApprove(requestParams)
733
+ const second = await approvals.requestApprovalMaybeAutoApprove(requestParams)
734
+ await approvals.submitDecision(first.id, true)
735
+ const third = await approvals.requestApprovalMaybeAutoApprove(requestParams)
736
+
737
+ const allApprovals = storage.loadApprovals()
738
+ console.log(JSON.stringify({
739
+ firstId: first.id,
740
+ secondId: second.id,
741
+ thirdId: third.id,
742
+ secondStatus: second.status,
743
+ thirdStatus: third.status,
744
+ approvalCount: Object.keys(allApprovals).length,
745
+ }))
746
+ `)
747
+
748
+ assert.equal(output.firstId, output.secondId)
749
+ assert.equal(output.firstId, output.thirdId)
750
+ assert.equal(output.secondStatus, 'pending')
751
+ assert.equal(output.thirdStatus, 'approved')
752
+ assert.equal(output.approvalCount, 1)
753
+ })
754
+
755
+ it('reuses approved tool-access decisions across sessions for the same agent', () => {
756
+ const output = runWithTempDataDir(`
757
+ const storageMod = await import('./src/lib/server/storage.ts')
758
+ const approvalsMod = await import('./src/lib/server/approvals.ts')
759
+ const storage = storageMod.default || storageMod
760
+ const approvals = approvalsMod.default || approvalsMod
761
+
762
+ const now = Date.now()
763
+ storage.saveSettings({
764
+ approvalsEnabled: true,
765
+ approvalAutoApproveCategories: [],
766
+ })
767
+
768
+ const first = await approvals.requestApprovalMaybeAutoApprove({
769
+ category: 'tool_access',
770
+ title: 'Enable connector tool',
771
+ description: 'Grant connector messaging',
772
+ data: { toolId: 'connector_message_tool', pluginId: 'connector_message_tool' },
773
+ agentId: 'agent_1',
774
+ })
775
+ await approvals.submitDecision(first.id, true)
776
+
777
+ const approvalsStore = storage.loadApprovals()
778
+ approvalsStore[first.id].updatedAt = now - (24 * 60 * 60 * 1000)
779
+ storage.upsertApproval(first.id, approvalsStore[first.id])
780
+
781
+ const second = await approvals.requestApprovalMaybeAutoApprove({
782
+ category: 'tool_access',
783
+ title: 'Enable connector tool',
784
+ description: 'Grant connector messaging again',
785
+ data: { toolId: 'connector_message_tool', pluginId: 'connector_message_tool' },
786
+ agentId: 'agent_1',
787
+ })
788
+
789
+ console.log(JSON.stringify({
790
+ firstId: first.id,
791
+ secondId: second.id,
792
+ secondStatus: second.status,
793
+ approvalCount: Object.keys(storage.loadApprovals()).length,
794
+ }))
795
+ `)
796
+
797
+ assert.equal(output.firstId, output.secondId)
798
+ assert.equal(output.secondStatus, 'approved')
799
+ assert.equal(output.approvalCount, 1)
800
+ })
264
801
  })