@swarmclawai/swarmclaw 1.2.6 → 1.2.9

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 (269) hide show
  1. package/README.md +54 -23
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/scripts/easy-setup.mjs +1 -1
  5. package/scripts/postinstall.mjs +1 -1
  6. package/skills/swarmclaw.md +115 -0
  7. package/skills/tools/browser.md +131 -0
  8. package/skills/tools/execute.md +98 -0
  9. package/skills/tools/files.md +98 -0
  10. package/skills/tools/memory.md +104 -0
  11. package/skills/tools/platform.md +144 -0
  12. package/skills/tools/skills.md +83 -0
  13. package/src/app/agents/[id]/page.tsx +1 -18
  14. package/src/app/api/agents/thread-route.test.ts +0 -1
  15. package/src/app/api/approvals/route.test.ts +6 -22
  16. package/src/app/api/chats/[id]/messages/route.ts +23 -19
  17. package/src/app/api/chats/messages-route.test.ts +105 -51
  18. package/src/app/api/connectors/route.ts +2 -2
  19. package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
  20. package/src/app/api/openclaw/deploy/route.ts +2 -0
  21. package/src/app/api/portability/export/route.ts +8 -0
  22. package/src/app/api/portability/import/route.test.ts +80 -0
  23. package/src/app/api/portability/import/route.ts +28 -0
  24. package/src/app/api/settings/route.ts +0 -2
  25. package/src/app/api/setup/doctor/route.ts +4 -4
  26. package/src/app/api/wallets/[id]/route.ts +15 -157
  27. package/src/app/api/wallets/generate/route.ts +22 -0
  28. package/src/app/api/wallets/route.test.ts +147 -0
  29. package/src/app/api/wallets/route.ts +13 -95
  30. package/src/app/autonomy/page.tsx +2 -57
  31. package/src/app/protocols/page.tsx +2 -21
  32. package/src/app/settings/page.tsx +0 -9
  33. package/src/app/wallets/page.tsx +105 -5
  34. package/src/cli/index.js +21 -33
  35. package/src/cli/spec.js +19 -30
  36. package/src/components/agents/agent-chat-list.tsx +23 -1
  37. package/src/components/agents/agent-sheet.tsx +2 -40
  38. package/src/components/agents/inspector-panel.tsx +165 -131
  39. package/src/components/chat/chat-area.tsx +38 -9
  40. package/src/components/chat/chat-card.tsx +0 -31
  41. package/src/components/chat/message-bubble.tsx +1 -108
  42. package/src/components/chat/message-list.tsx +33 -19
  43. package/src/components/connectors/connector-sheet.tsx +25 -1
  44. package/src/components/gateways/gateway-sheet.tsx +5 -2
  45. package/src/components/layout/sidebar-rail.tsx +6 -10
  46. package/src/components/projects/project-detail.tsx +3 -35
  47. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  48. package/src/components/projects/tabs/work-tab.tsx +7 -77
  49. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  50. package/src/components/shared/connector-platform-icon.tsx +1 -0
  51. package/src/components/tasks/task-card.tsx +4 -34
  52. package/src/components/tasks/task-sheet.tsx +6 -36
  53. package/src/components/wallets/wallet-list.tsx +150 -0
  54. package/src/lib/agent-execute-defaults.test.ts +24 -0
  55. package/src/lib/agent-execute-defaults.ts +62 -0
  56. package/src/lib/app/navigation.test.ts +0 -13
  57. package/src/lib/app/navigation.ts +2 -7
  58. package/src/lib/app/view-constants.ts +14 -19
  59. package/src/lib/chat/queued-message-queue.test.ts +134 -1
  60. package/src/lib/chat/queued-message-queue.ts +77 -2
  61. package/src/lib/server/agents/agent-service.ts +5 -0
  62. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  63. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  64. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  65. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  66. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  67. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  68. package/src/lib/server/approval-match.ts +0 -85
  69. package/src/lib/server/approvals.test.ts +6 -6
  70. package/src/lib/server/approvals.ts +0 -6
  71. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  72. package/src/lib/server/builtin-extensions.ts +1 -2
  73. package/src/lib/server/capability-router.test.ts +0 -2
  74. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
  75. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
  76. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  77. package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
  78. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  79. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  80. package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
  81. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
  82. package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
  83. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  84. package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
  85. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  86. package/src/lib/server/chat-execution/message-classifier.ts +11 -16
  87. package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
  88. package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
  89. package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
  90. package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
  91. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  92. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  93. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  94. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
  95. package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
  96. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  97. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  98. package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
  99. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  100. package/src/lib/server/chats/chat-session-service.ts +3 -5
  101. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  102. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  103. package/src/lib/server/connectors/connector-service.ts +39 -9
  104. package/src/lib/server/connectors/discord.ts +2 -2
  105. package/src/lib/server/connectors/matrix.ts +3 -2
  106. package/src/lib/server/connectors/signal.ts +5 -4
  107. package/src/lib/server/connectors/slack.ts +10 -9
  108. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  109. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  110. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  111. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  112. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  113. package/src/lib/server/connectors/swarmdock.ts +255 -0
  114. package/src/lib/server/connectors/teams.ts +3 -2
  115. package/src/lib/server/connectors/telegram.ts +4 -4
  116. package/src/lib/server/connectors/whatsapp.ts +2 -2
  117. package/src/lib/server/daemon/controller.ts +7 -0
  118. package/src/lib/server/execution-brief.test.ts +2 -25
  119. package/src/lib/server/execution-brief.ts +12 -35
  120. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  121. package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
  122. package/src/lib/server/messages/message-repository.test.ts +70 -0
  123. package/src/lib/server/messages/message-repository.ts +11 -6
  124. package/src/lib/server/openclaw/deploy.ts +32 -2
  125. package/src/lib/server/persistence/storage-context.ts +0 -5
  126. package/src/lib/server/plugins-advanced.test.ts +1 -2
  127. package/src/lib/server/portability/export.ts +109 -0
  128. package/src/lib/server/portability/import.ts +159 -0
  129. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  130. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  131. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  132. package/src/lib/server/protocols/protocol-service.ts +0 -1
  133. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  134. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  135. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  136. package/src/lib/server/protocols/protocol-types.ts +0 -2
  137. package/src/lib/server/provider-health.ts +1 -10
  138. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  139. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  140. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  141. package/src/lib/server/runtime/process-manager.ts +13 -9
  142. package/src/lib/server/runtime/queue/core.ts +11 -33
  143. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  144. package/src/lib/server/runtime/scheduler.ts +0 -13
  145. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  146. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  147. package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
  148. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  149. package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
  150. package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
  151. package/src/lib/server/sandbox/session-runtime.ts +40 -28
  152. package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
  153. package/src/lib/server/session-tools/context.ts +1 -1
  154. package/src/lib/server/session-tools/credential-env.ts +109 -0
  155. package/src/lib/server/session-tools/crud.ts +3 -17
  156. package/src/lib/server/session-tools/delegate.ts +0 -4
  157. package/src/lib/server/session-tools/edit_file.ts +3 -2
  158. package/src/lib/server/session-tools/execute.test.ts +58 -0
  159. package/src/lib/server/session-tools/execute.ts +334 -0
  160. package/src/lib/server/session-tools/files-tool.ts +635 -0
  161. package/src/lib/server/session-tools/index.ts +14 -8
  162. package/src/lib/server/session-tools/memory-tool.ts +242 -0
  163. package/src/lib/server/session-tools/memory.ts +1 -1
  164. package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
  165. package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
  166. package/src/lib/server/session-tools/platform-tool.ts +617 -0
  167. package/src/lib/server/session-tools/session-info.ts +3 -2
  168. package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
  169. package/src/lib/server/session-tools/shell.ts +7 -122
  170. package/src/lib/server/session-tools/skills-tool.ts +396 -0
  171. package/src/lib/server/session-tools/team-context.ts +0 -3
  172. package/src/lib/server/session-tools/web.ts +2 -2
  173. package/src/lib/server/storage-normalization.ts +10 -0
  174. package/src/lib/server/storage.ts +18 -45
  175. package/src/lib/server/tasks/task-checkout.ts +59 -0
  176. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  177. package/src/lib/server/tasks/task-route-service.ts +4 -26
  178. package/src/lib/server/tasks/task-service.ts +0 -7
  179. package/src/lib/server/tool-aliases.ts +2 -2
  180. package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
  181. package/src/lib/server/tool-capability-policy.test.ts +2 -1
  182. package/src/lib/server/tool-capability-policy.ts +60 -35
  183. package/src/lib/server/tool-planning.ts +11 -12
  184. package/src/lib/server/universal-tool-access.ts +0 -1
  185. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  186. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  187. package/src/lib/server/wallets/wallet-service.ts +119 -0
  188. package/src/lib/server/working-state/extraction.ts +8 -42
  189. package/src/lib/server/working-state/normalization.ts +10 -103
  190. package/src/lib/server/working-state/service.ts +12 -21
  191. package/src/lib/setup-defaults.ts +5 -0
  192. package/src/lib/strip-internal-metadata.test.ts +1 -1
  193. package/src/lib/strip-internal-metadata.ts +1 -1
  194. package/src/lib/tool-definitions.ts +1 -1
  195. package/src/lib/validation/schemas.test.ts +16 -0
  196. package/src/lib/validation/schemas.ts +49 -2
  197. package/src/stores/slices/data-slice.ts +5 -1
  198. package/src/stores/slices/ui-slice.ts +0 -4
  199. package/src/stores/use-chat-store.test.ts +231 -0
  200. package/src/stores/use-chat-store.ts +62 -13
  201. package/src/types/agent.ts +264 -0
  202. package/src/types/app-settings.ts +173 -0
  203. package/src/types/approval.ts +25 -0
  204. package/src/types/connector.ts +188 -0
  205. package/src/types/extension.ts +386 -0
  206. package/src/types/index.ts +16 -3555
  207. package/src/types/message.ts +56 -0
  208. package/src/types/misc.ts +737 -0
  209. package/src/types/protocol.ts +420 -0
  210. package/src/types/provider.ts +52 -0
  211. package/src/types/run.ts +180 -0
  212. package/src/types/schedule.ts +59 -0
  213. package/src/types/session.ts +215 -0
  214. package/src/types/skill.ts +157 -0
  215. package/src/types/swarmdock.ts +29 -0
  216. package/src/types/task.ts +144 -0
  217. package/src/types/working-state.ts +204 -0
  218. package/src/views/settings/section-heartbeat.tsx +2 -2
  219. package/src/views/settings/section-runtime-loop.tsx +0 -14
  220. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  221. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  222. package/src/app/api/missions/[id]/events/route.ts +0 -14
  223. package/src/app/api/missions/[id]/route.ts +0 -10
  224. package/src/app/api/missions/route.test.ts +0 -244
  225. package/src/app/api/missions/route.ts +0 -57
  226. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  227. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  228. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  229. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  230. package/src/app/missions/[id]/page.tsx +0 -3
  231. package/src/app/missions/page.tsx +0 -685
  232. package/src/components/canvas/canvas-panel.tsx +0 -267
  233. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  234. package/src/components/wallets/wallet-panel.tsx +0 -1010
  235. package/src/components/wallets/wallet-section.tsx +0 -260
  236. package/src/features/missions/queries.ts +0 -23
  237. package/src/lib/canvas-content.test.ts +0 -360
  238. package/src/lib/canvas-content.ts +0 -198
  239. package/src/lib/server/canvas-content.test.ts +0 -32
  240. package/src/lib/server/canvas-content.ts +0 -6
  241. package/src/lib/server/ethereum.ts +0 -591
  242. package/src/lib/server/evm-swap.ts +0 -476
  243. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  244. package/src/lib/server/missions/mission-intent.ts +0 -569
  245. package/src/lib/server/missions/mission-repository.ts +0 -74
  246. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  247. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  248. package/src/lib/server/missions/mission-service/context.ts +0 -4
  249. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  250. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  251. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  252. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  253. package/src/lib/server/missions/mission-service.test.ts +0 -888
  254. package/src/lib/server/missions/mission-service.ts +0 -6
  255. package/src/lib/server/session-tools/canvas.ts +0 -105
  256. package/src/lib/server/session-tools/sandbox.ts +0 -281
  257. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  258. package/src/lib/server/session-tools/wallet.ts +0 -1287
  259. package/src/lib/server/solana.ts +0 -327
  260. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  261. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  262. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  263. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  264. package/src/lib/server/wallet/wallet-service.ts +0 -225
  265. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  266. package/src/lib/wallet/wallet-transactions.ts +0 -43
  267. package/src/lib/wallet/wallet.test.ts +0 -333
  268. package/src/lib/wallet/wallet.ts +0 -183
  269. package/src/views/settings/section-wallets.tsx +0 -35
@@ -0,0 +1,635 @@
1
+ /**
2
+ * files-tool — Consolidated file operations tool.
3
+ *
4
+ * Merges the capabilities of file.ts (read/write/list/copy/move/delete)
5
+ * and edit_file.ts (surgical string replacement) into a single tool
6
+ * with an `action` discriminator, plus a new `search` action.
7
+ *
8
+ * Actions:
9
+ * read — Read file contents (optional offset/limit for line ranges)
10
+ * write — Write/overwrite a file (supports bulk via files[])
11
+ * edit — Surgical old_string -> new_string replacement
12
+ * list — List directory contents (with depth control)
13
+ * search — Search file contents (grep-like, with include glob filter)
14
+ */
15
+
16
+ import { z } from 'zod'
17
+ import { tool } from '@langchain/core/tools'
18
+ import fs from 'fs'
19
+ import path from 'path'
20
+ import type { Extension, ExtensionHooks } from '@/types'
21
+ import { registerNativeCapability } from '../native-capabilities'
22
+ import { normalizeToolInputArgs } from './normalize-tool-args'
23
+ import { checkFileAccess } from './file-access-policy'
24
+ import { errorMessage } from '@/lib/shared-utils'
25
+ import { log } from '../logger'
26
+ import type { ToolBuildContext } from './context'
27
+ import { safePath, truncate, listDirRecursive, MAX_FILE, MAX_OUTPUT } from './context'
28
+
29
+ const TAG = 'files-tool'
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Types
33
+ // ---------------------------------------------------------------------------
34
+
35
+ type FilesAction = 'read' | 'write' | 'edit' | 'list' | 'search'
36
+
37
+ interface FilesToolContext {
38
+ cwd: string
39
+ filesystemScope?: 'workspace' | 'machine'
40
+ fileAccessPolicy?: { allowedPaths?: string[]; blockedPaths?: string[] } | null
41
+ }
42
+
43
+ interface NormalizedFilesArgs {
44
+ action: FilesAction | undefined
45
+ path: string | undefined
46
+ content: string | undefined
47
+ encoding: string | undefined
48
+ // read
49
+ offset: number | undefined
50
+ limit: number | undefined
51
+ // edit
52
+ oldString: string | undefined
53
+ newString: string | undefined
54
+ // list
55
+ depth: number | undefined
56
+ // search
57
+ query: string | undefined
58
+ include: string | undefined
59
+ // write bulk
60
+ files: Array<Record<string, unknown>> | undefined
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Arg normalization helpers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function pickString(...values: unknown[]): string | undefined {
68
+ for (const v of values) {
69
+ if (typeof v === 'string') {
70
+ const trimmed = v.trim()
71
+ if (trimmed) return trimmed
72
+ }
73
+ }
74
+ return undefined
75
+ }
76
+
77
+ function pickNumber(...values: unknown[]): number | undefined {
78
+ for (const v of values) {
79
+ if (typeof v === 'number' && Number.isFinite(v)) return v
80
+ if (typeof v === 'string') {
81
+ const n = Number(v)
82
+ if (Number.isFinite(n)) return n
83
+ }
84
+ }
85
+ return undefined
86
+ }
87
+
88
+ function pickStringRaw(...values: unknown[]): string | undefined {
89
+ for (const v of values) {
90
+ if (typeof v === 'string') return v
91
+ }
92
+ return undefined
93
+ }
94
+
95
+ function parseFileEntries(value: unknown): Array<Record<string, unknown>> | undefined {
96
+ const candidates = [value]
97
+ if (typeof value === 'string') {
98
+ const trimmed = value.trim()
99
+ if (trimmed.startsWith('[')) {
100
+ try {
101
+ candidates.unshift(JSON.parse(trimmed))
102
+ } catch {
103
+ // ignore malformed JSON
104
+ }
105
+ }
106
+ }
107
+ for (const candidate of candidates) {
108
+ if (!Array.isArray(candidate)) continue
109
+ return candidate.filter(
110
+ (entry): entry is Record<string, unknown> =>
111
+ !!entry && typeof entry === 'object' && !Array.isArray(entry),
112
+ )
113
+ }
114
+ return undefined
115
+ }
116
+
117
+ function getEntryPath(entry: Record<string, unknown> | undefined): string | undefined {
118
+ if (!entry) return undefined
119
+ return pickString(
120
+ entry.path,
121
+ entry.filePath,
122
+ entry.filename,
123
+ entry.fileName,
124
+ entry.name,
125
+ entry.targetPath,
126
+ entry.target,
127
+ )
128
+ }
129
+
130
+ function getEntryContent(entry: Record<string, unknown> | undefined): string | undefined {
131
+ if (!entry) return undefined
132
+ const raw = entry.content ?? entry.text ?? entry.contents ?? entry.value ?? entry.body
133
+ if (raw === undefined || raw === null) return undefined
134
+ return typeof raw === 'string' ? raw : JSON.stringify(raw)
135
+ }
136
+
137
+ /**
138
+ * Infer the action when the LLM doesn't provide one explicitly.
139
+ */
140
+ function inferAction(
141
+ normalized: Record<string, unknown>,
142
+ files: Array<Record<string, unknown>> | undefined,
143
+ filePath: string | undefined,
144
+ ): FilesAction {
145
+ // If old_string/oldString is present, it's an edit
146
+ if (normalized.oldString !== undefined || normalized.old_string !== undefined) return 'edit'
147
+ // If query/search/pattern is present, it's a search
148
+ if (normalized.query !== undefined || normalized.search !== undefined || normalized.pattern !== undefined) return 'search'
149
+ // If files array has content, it's a write
150
+ if (Array.isArray(files) && files.some((e) => getEntryContent(e) !== undefined)) return 'write'
151
+ // If content is present, it's a write
152
+ if (normalized.content !== undefined || normalized.text !== undefined || normalized.body !== undefined) return 'write'
153
+ // If depth is present or path looks like a directory, it's a list
154
+ if (normalized.depth !== undefined) return 'list'
155
+ if (normalized.dirPath !== undefined || normalized.directory !== undefined || normalized.dir !== undefined) return 'list'
156
+ if (filePath && filePath.endsWith('/')) return 'list'
157
+ // Default: if we have a path, read it; otherwise list cwd
158
+ return filePath ? 'read' : 'list'
159
+ }
160
+
161
+ /**
162
+ * Normalize the chaotic LLM arg shapes into a clean internal structure.
163
+ */
164
+ function normalizeArgs(rawArgs: Record<string, unknown>): NormalizedFilesArgs {
165
+ const n = normalizeToolInputArgs(rawArgs)
166
+
167
+ // Some LLMs nest the payload under the action key: { read: { path: "..." } }
168
+ const actionPayload = (['read', 'write', 'edit', 'list', 'search'] as const)
169
+ .map((candidate) => {
170
+ const value = n[candidate]
171
+ return value && typeof value === 'object' && !Array.isArray(value)
172
+ ? { action: candidate, value: value as Record<string, unknown> }
173
+ : null
174
+ })
175
+ .find(Boolean)
176
+
177
+ const merged: Record<string, unknown> = { ...n, ...(actionPayload?.value ?? {}) }
178
+ const files = parseFileEntries(merged.files)
179
+
180
+ const filePath = pickString(
181
+ merged.filePath, merged.filepath, merged.path, merged.file,
182
+ merged.filename, merged.fileName, merged.name, merged.targetPath,
183
+ merged.target, merged.dirPath, merged.directory, merged.directoryPath,
184
+ merged.dir, merged.folder,
185
+ )
186
+
187
+ let action = pickString(n.action, actionPayload?.action) as FilesAction | undefined
188
+ if (!action && Array.isArray(files) && files.length > 0) {
189
+ action = pickString(files[0].action) as FilesAction | undefined
190
+ }
191
+ if (!action) {
192
+ action = inferAction(merged, files, filePath)
193
+ }
194
+
195
+ return {
196
+ action,
197
+ path: filePath,
198
+ content: pickStringRaw(merged.content, merged.text, merged.contents, merged.value, merged.body),
199
+ encoding: pickString(merged.encoding),
200
+ offset: pickNumber(merged.offset, merged.startLine, merged.start_line, merged.from_line),
201
+ limit: pickNumber(merged.limit, merged.lineCount, merged.line_count, merged.maxLines, merged.max_lines, merged.lines),
202
+ oldString: pickStringRaw(merged.oldString, merged.old_string, merged.oldText, merged.old_text, merged.find, merged.search_string),
203
+ newString: pickStringRaw(merged.newString, merged.new_string, merged.newText, merged.new_text, merged.replace, merged.replacement),
204
+ depth: pickNumber(merged.depth, merged.maxDepth, merged.max_depth),
205
+ query: pickString(merged.query, merged.search, merged.pattern, merged.grep, merged.regex),
206
+ include: pickString(merged.include, merged.glob, merged.filePattern, merged.file_pattern),
207
+ files,
208
+ }
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Path resolution + access policy enforcement
213
+ // ---------------------------------------------------------------------------
214
+
215
+ function resolveFilePath(cwd: string, target: string, scope?: 'workspace' | 'machine'): string {
216
+ try {
217
+ return safePath(cwd, target, scope)
218
+ } catch (err: unknown) {
219
+ // For absolute paths, try resolving against process.cwd() as a fallback
220
+ if (!path.isAbsolute(target)) throw err
221
+ return safePath(process.cwd(), target, scope)
222
+ }
223
+ }
224
+
225
+ function enforceAccess(
226
+ filePath: string,
227
+ cwd: string,
228
+ policy: FilesToolContext['fileAccessPolicy'],
229
+ ): string | null {
230
+ if (!policy) return null
231
+ const result = checkFileAccess(filePath, cwd, policy)
232
+ if (!result.allowed) return result.reason ?? 'File access denied by policy'
233
+ return null
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Binary file detection
238
+ // ---------------------------------------------------------------------------
239
+
240
+ const BINARY_EXTENSIONS = new Set([
241
+ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.ico', '.svg', '.pdf',
242
+ '.zip', '.gz', '.tar', '.tgz', '.7z', '.rar',
243
+ '.mp3', '.wav', '.ogg', '.m4a', '.mp4', '.mov', '.avi', '.webm',
244
+ '.woff', '.woff2', '.ttf', '.otf',
245
+ '.exe', '.dll', '.so', '.dylib', '.bin',
246
+ ])
247
+
248
+ function isLikelyBinary(resolvedPath: string, data: Buffer): boolean {
249
+ const ext = path.extname(resolvedPath).toLowerCase()
250
+ if (BINARY_EXTENSIONS.has(ext)) return true
251
+ const sample = data.subarray(0, Math.min(data.length, 512))
252
+ for (const byte of sample) {
253
+ if (byte === 0) return true
254
+ }
255
+ return false
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Action implementations
260
+ // ---------------------------------------------------------------------------
261
+
262
+ function actionRead(args: NormalizedFilesArgs, ctx: FilesToolContext): string {
263
+ const target = args.path
264
+ if (!target) return 'Error: path is required for read action.'
265
+
266
+ const blocked = enforceAccess(target, ctx.cwd, ctx.fileAccessPolicy)
267
+ if (blocked) return `Error: ${blocked}`
268
+
269
+ const resolved = resolveFilePath(ctx.cwd, target, ctx.filesystemScope)
270
+ const data = fs.readFileSync(resolved)
271
+
272
+ if (isLikelyBinary(resolved, data)) {
273
+ return `Binary file: ${target} (${data.byteLength} bytes). Contents not displayed.`
274
+ }
275
+
276
+ let text = data.toString('utf-8')
277
+
278
+ // Apply line-range slicing if offset/limit provided
279
+ if (args.offset !== undefined || args.limit !== undefined) {
280
+ const lines = text.split('\n')
281
+ const start = Math.max(0, (args.offset ?? 1) - 1) // 1-based to 0-based
282
+ const count = args.limit ?? lines.length
283
+ const sliced = lines.slice(start, start + count)
284
+ // Prefix with line numbers for context
285
+ text = sliced
286
+ .map((line, i) => `${start + i + 1}: ${line}`)
287
+ .join('\n')
288
+ if (start + count < lines.length) {
289
+ text += `\n... (${lines.length - start - count} more lines)`
290
+ }
291
+ }
292
+
293
+ return truncate(text, MAX_FILE)
294
+ }
295
+
296
+ function actionWrite(args: NormalizedFilesArgs, ctx: FilesToolContext): string {
297
+ const filesToWrite: Array<Record<string, unknown>> = Array.isArray(args.files)
298
+ ? args.files
299
+ : [{ path: args.path, content: args.content }]
300
+
301
+ const results: string[] = []
302
+
303
+ for (const file of filesToWrite) {
304
+ const targetPath = getEntryPath(file)
305
+ if (!targetPath) continue
306
+
307
+ const blocked = enforceAccess(targetPath, ctx.cwd, ctx.fileAccessPolicy)
308
+ if (blocked) {
309
+ results.push(`Error (${targetPath}): ${blocked}`)
310
+ continue
311
+ }
312
+
313
+ const fileContent = getEntryContent(file) ?? ''
314
+
315
+ // Directory creation: paths ending with / or \
316
+ if (/[\\/]$/.test(targetPath)) {
317
+ const resolvedDir = resolveFilePath(ctx.cwd, targetPath, ctx.filesystemScope)
318
+ fs.mkdirSync(resolvedDir, { recursive: true })
319
+ results.push(`Created directory ${targetPath}`)
320
+ continue
321
+ }
322
+
323
+ const resolved = resolveFilePath(ctx.cwd, targetPath, ctx.filesystemScope)
324
+ fs.mkdirSync(path.dirname(resolved), { recursive: true })
325
+
326
+ if (args.encoding === 'base64' && typeof fileContent === 'string') {
327
+ const buf = Buffer.from(fileContent, 'base64')
328
+ fs.writeFileSync(resolved, buf)
329
+ results.push(`Written ${targetPath} (${buf.length} bytes, binary)`)
330
+ } else {
331
+ fs.writeFileSync(resolved, fileContent, 'utf-8')
332
+ results.push(`Written ${targetPath} (${fileContent.length} bytes)`)
333
+ }
334
+ }
335
+
336
+ return results.join('\n') || 'Error: no files to write.'
337
+ }
338
+
339
+ function actionEdit(args: NormalizedFilesArgs, ctx: FilesToolContext): string {
340
+ const target = args.path
341
+ if (!target) return 'Error: path is required for edit action.'
342
+
343
+ const blocked = enforceAccess(target, ctx.cwd, ctx.fileAccessPolicy)
344
+ if (blocked) return `Error: ${blocked}`
345
+
346
+ if (args.oldString === undefined) return 'Error: old_string is required for edit action.'
347
+ if (args.newString === undefined) return 'Error: new_string is required for edit action.'
348
+
349
+ const resolved = resolveFilePath(ctx.cwd, target, ctx.filesystemScope)
350
+ if (!fs.existsSync(resolved)) return `Error: File not found: ${target}`
351
+
352
+ const content = fs.readFileSync(resolved, 'utf-8')
353
+ const count = content.split(args.oldString).length - 1
354
+
355
+ if (count === 0) {
356
+ return `Error: Exact match for old_string not found in ${target}. Use action="read" to check current content.`
357
+ }
358
+ if (count > 1) {
359
+ return `Error: Multiple matches (${count}) found for old_string. Provide more surrounding context for a unique match.`
360
+ }
361
+
362
+ const updated = content.replace(args.oldString, args.newString)
363
+ fs.writeFileSync(resolved, updated, 'utf-8')
364
+ return `Successfully updated ${target} (1 replacement made).`
365
+ }
366
+
367
+ function actionList(args: NormalizedFilesArgs, ctx: FilesToolContext): string {
368
+ const target = args.path || '.'
369
+ const maxDepth = Math.min(Math.max(args.depth ?? 3, 1), 10)
370
+
371
+ const blocked = enforceAccess(target, ctx.cwd, ctx.fileAccessPolicy)
372
+ if (blocked) return `Error: ${blocked}`
373
+
374
+ const resolved = resolveFilePath(ctx.cwd, target, ctx.filesystemScope)
375
+ const tree = listDirRecursive(resolved, 0, maxDepth)
376
+ return tree.length ? tree.join('\n') : '(empty directory)'
377
+ }
378
+
379
+ function actionSearch(args: NormalizedFilesArgs, ctx: FilesToolContext): string {
380
+ const query = args.query
381
+ if (!query) return 'Error: query is required for search action.'
382
+
383
+ const target = args.path || '.'
384
+ const blocked = enforceAccess(target, ctx.cwd, ctx.fileAccessPolicy)
385
+ if (blocked) return `Error: ${blocked}`
386
+
387
+ const resolved = resolveFilePath(ctx.cwd, target, ctx.filesystemScope)
388
+
389
+ let regex: RegExp
390
+ try {
391
+ regex = new RegExp(query, 'i')
392
+ } catch {
393
+ // Fall back to literal search if the query is not a valid regex
394
+ regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')
395
+ }
396
+
397
+ const includeGlob = args.include
398
+ const results: string[] = []
399
+ const maxResults = 200
400
+
401
+ searchDir(resolved, resolved, regex, includeGlob, results, maxResults, 0, 10)
402
+
403
+ if (results.length === 0) return `No matches found for "${query}" in ${target}`
404
+ const output = results.join('\n')
405
+ return truncate(output, MAX_OUTPUT)
406
+ }
407
+
408
+ /**
409
+ * Recursively search directory for files matching the query.
410
+ */
411
+ function searchDir(
412
+ root: string,
413
+ dir: string,
414
+ regex: RegExp,
415
+ includeGlob: string | undefined,
416
+ results: string[],
417
+ maxResults: number,
418
+ depth: number,
419
+ maxDepth: number,
420
+ ): void {
421
+ if (depth > maxDepth || results.length >= maxResults) return
422
+
423
+ let entries: fs.Dirent[]
424
+ try {
425
+ entries = fs.readdirSync(dir, { withFileTypes: true })
426
+ } catch {
427
+ return
428
+ }
429
+
430
+ for (const entry of entries) {
431
+ if (results.length >= maxResults) return
432
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue
433
+
434
+ const fullPath = path.join(dir, entry.name)
435
+
436
+ if (entry.isDirectory()) {
437
+ searchDir(root, fullPath, regex, includeGlob, results, maxResults, depth + 1, maxDepth)
438
+ continue
439
+ }
440
+
441
+ if (!entry.isFile()) continue
442
+
443
+ // Apply include glob filter (simple extension/suffix matching)
444
+ if (includeGlob && !matchSimpleGlob(entry.name, includeGlob)) continue
445
+
446
+ // Skip binary files
447
+ const ext = path.extname(entry.name).toLowerCase()
448
+ if (BINARY_EXTENSIONS.has(ext)) continue
449
+
450
+ let fileContent: string
451
+ try {
452
+ const buf = fs.readFileSync(fullPath)
453
+ // Quick binary check on first 512 bytes
454
+ const sample = buf.subarray(0, Math.min(buf.length, 512))
455
+ let isBinary = false
456
+ for (const byte of sample) {
457
+ if (byte === 0) { isBinary = true; break }
458
+ }
459
+ if (isBinary) continue
460
+ fileContent = buf.toString('utf-8')
461
+ } catch {
462
+ continue
463
+ }
464
+
465
+ const relativePath = path.relative(root, fullPath)
466
+ const lines = fileContent.split('\n')
467
+ for (let i = 0; i < lines.length; i++) {
468
+ if (results.length >= maxResults) return
469
+ if (regex.test(lines[i])) {
470
+ results.push(`${relativePath}:${i + 1}: ${lines[i].trimEnd()}`)
471
+ }
472
+ }
473
+ }
474
+ }
475
+
476
+ /**
477
+ * Simple glob matching for include filters.
478
+ * Supports: "*.ts", "*.{ts,tsx}", "test_*", exact names.
479
+ */
480
+ function matchSimpleGlob(filename: string, glob: string): boolean {
481
+ // Handle brace expansion: *.{ts,tsx} -> check each extension
482
+ const braceMatch = glob.match(/^(.+)\.\{([^}]+)\}$/)
483
+ if (braceMatch) {
484
+ const prefix = braceMatch[1]
485
+ const extensions = braceMatch[2].split(',').map((e) => e.trim())
486
+ for (const ext of extensions) {
487
+ if (matchSimpleGlob(filename, `${prefix}.${ext}`)) return true
488
+ }
489
+ return false
490
+ }
491
+
492
+ // Convert simple glob to regex
493
+ const escaped = glob
494
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
495
+ .replace(/\*/g, '.*')
496
+ .replace(/\?/g, '.')
497
+ try {
498
+ return new RegExp(`^${escaped}$`, 'i').test(filename)
499
+ } catch {
500
+ return filename === glob
501
+ }
502
+ }
503
+
504
+ // ---------------------------------------------------------------------------
505
+ // Main dispatch
506
+ // ---------------------------------------------------------------------------
507
+
508
+ async function executeFilesAction(
509
+ rawArgs: Record<string, unknown>,
510
+ ctx: FilesToolContext,
511
+ ): Promise<string> {
512
+ const args = normalizeArgs(rawArgs)
513
+
514
+ log.info(TAG, `action=${args.action ?? 'inferred'} path=${args.path ?? '(none)'}`)
515
+
516
+ try {
517
+ switch (args.action) {
518
+ case 'read':
519
+ return actionRead(args, ctx)
520
+ case 'write':
521
+ return actionWrite(args, ctx)
522
+ case 'edit':
523
+ return actionEdit(args, ctx)
524
+ case 'list':
525
+ return actionList(args, ctx)
526
+ case 'search':
527
+ return actionSearch(args, ctx)
528
+ default:
529
+ return `Error: Unknown action "${String(args.action)}". Valid actions: read, write, edit, list, search.`
530
+ }
531
+ } catch (err: unknown) {
532
+ const msg = errorMessage(err)
533
+ if (msg === 'Path traversal not allowed') {
534
+ if (ctx.filesystemScope === 'workspace') {
535
+ return 'Error: target path is outside the session workspace. Use a relative path (e.g., "src/app/globals.css" instead of "/projectname/src/app/globals.css"). The files tool only accesses paths under the workspace root.'
536
+ }
537
+ return 'Error: target path is blocked by the current filesystem policy.'
538
+ }
539
+ return `Error: ${msg}`
540
+ }
541
+ }
542
+
543
+ // ---------------------------------------------------------------------------
544
+ // Extension registration
545
+ // ---------------------------------------------------------------------------
546
+
547
+ const FilesExtension: Extension = {
548
+ name: 'Core Files',
549
+ description: 'Consolidated file operations: read, write, edit, list, and search.',
550
+ hooks: {
551
+ getCapabilityDescription: () =>
552
+ 'I can manage files with the `files` tool. ' +
553
+ 'Actions: `read` (view contents with optional line range), ' +
554
+ '`write` (create/overwrite files), ' +
555
+ '`edit` (surgical find-and-replace), ' +
556
+ '`list` (directory tree), ' +
557
+ '`search` (grep-like content search).',
558
+ getOperatingGuidance: () => [
559
+ 'Use `{"action":"list","path":"."}` to inspect the workspace structure.',
560
+ 'Use `{"action":"read","path":"src/index.ts"}` to read a file. Add `offset` and `limit` for large files.',
561
+ 'Use `{"action":"write","path":"output.txt","content":"..."}` to create or overwrite a file.',
562
+ 'Use `{"action":"edit","path":"src/index.ts","old_string":"foo","new_string":"bar"}` for surgical edits without rewriting the whole file.',
563
+ 'Use `{"action":"search","path":"src/","query":"TODO","include":"*.ts"}` to find patterns across files.',
564
+ 'If a call fails, correct the arguments and retry. Do not conclude the workspace is inaccessible until an explicit attempt fails.',
565
+ ],
566
+ } as ExtensionHooks,
567
+ tools: [
568
+ {
569
+ name: 'files',
570
+ description:
571
+ 'Consolidated file operations tool. ' +
572
+ 'Actions: read (view file, optional offset/limit for line ranges), ' +
573
+ 'write (create/overwrite, supports bulk via files[]), ' +
574
+ 'edit (surgical old_string->new_string replacement), ' +
575
+ 'list (directory tree with depth control), ' +
576
+ 'search (grep-like content search with include glob filter).',
577
+ parameters: {
578
+ type: 'object',
579
+ properties: {
580
+ action: { type: 'string', enum: ['read', 'write', 'edit', 'list', 'search'] },
581
+ path: { type: 'string', description: 'Target file or directory path' },
582
+ content: { type: 'string', description: 'File content (write action)' },
583
+ old_string: { type: 'string', description: 'Exact text to find (edit action)' },
584
+ new_string: { type: 'string', description: 'Replacement text (edit action)' },
585
+ offset: { type: 'number', description: 'Start line number, 1-based (read action)' },
586
+ limit: { type: 'number', description: 'Max lines to return (read action)' },
587
+ depth: { type: 'number', description: 'Max directory depth (list action, default 3)' },
588
+ query: { type: 'string', description: 'Search pattern/regex (search action)' },
589
+ include: { type: 'string', description: 'File glob filter, e.g. "*.ts" (search action)' },
590
+ encoding: { type: 'string', enum: ['utf-8', 'base64'] },
591
+ files: {
592
+ type: 'array',
593
+ items: {
594
+ type: 'object',
595
+ properties: { path: { type: 'string' }, content: { type: 'string' } },
596
+ },
597
+ description: 'Bulk file writes',
598
+ },
599
+ },
600
+ required: ['action'],
601
+ },
602
+ execute: async (args, context) =>
603
+ executeFilesAction(
604
+ args as Record<string, unknown>,
605
+ { cwd: context.session?.cwd || process.cwd() },
606
+ ),
607
+ },
608
+ ],
609
+ }
610
+
611
+ registerNativeCapability('files', FilesExtension)
612
+
613
+ // ---------------------------------------------------------------------------
614
+ // Tool builder (called from session-tools/index.ts)
615
+ // ---------------------------------------------------------------------------
616
+
617
+ export function buildFilesTools(bctx: ToolBuildContext) {
618
+ if (!bctx.hasExtension('files')) return []
619
+
620
+ return [
621
+ tool(
622
+ async (args) =>
623
+ executeFilesAction(args, {
624
+ cwd: bctx.cwd,
625
+ filesystemScope: bctx.filesystemScope,
626
+ fileAccessPolicy: bctx.fileAccessPolicy,
627
+ }),
628
+ {
629
+ name: 'files',
630
+ description: FilesExtension.tools![0].description,
631
+ schema: z.object({}).passthrough(),
632
+ },
633
+ ),
634
+ ]
635
+ }
@@ -19,8 +19,6 @@ import { buildMemoryTools } from './memory'
19
19
  import { buildChatroomTools } from './chatroom'
20
20
  import { buildProtocolTools } from './protocol'
21
21
  import { buildSubagentTools } from './subagent'
22
- import { buildCanvasTools } from './canvas'
23
- import { buildWalletTools } from './wallet'
24
22
  import { buildOpenClawWorkspaceTools } from './openclaw-workspace'
25
23
  import { buildScheduleTools } from './schedule'
26
24
  import { buildPlatformTools } from './platform'
@@ -41,6 +39,11 @@ import { buildSkillRuntimeTools } from './skill-runtime'
41
39
  import { buildConnectorTools } from './connector'
42
40
  import { buildPeerQueryTools } from './peer-query'
43
41
  import { buildTeamContextTools } from './team-context'
42
+ import { buildExecuteTools } from './execute'
43
+ import { buildSkillsTools } from './skills-tool'
44
+ import { buildFilesTools } from './files-tool'
45
+ import { buildMemoryTool } from './memory-tool'
46
+ import { buildPlatformV2Tools } from './platform-tool'
44
47
  import './connector'
45
48
  import { normalizeToolInputArgs } from './normalize-tool-args'
46
49
  import { enforceFileAccessPolicy } from './file-access-policy'
@@ -182,8 +185,6 @@ export async function buildSessionTools(cwd: string, enabledExtensions: string[]
182
185
  ['manage_chatrooms', buildChatroomTools],
183
186
  ['manage_protocols', buildProtocolTools],
184
187
  ['spawn_subagent', buildSubagentTools],
185
- ['canvas', buildCanvasTools],
186
- ['wallet', buildWalletTools],
187
188
  ['openclaw_workspace', buildOpenClawWorkspaceTools],
188
189
  ['schedule', buildScheduleTools],
189
190
  ['manage_sessions', buildSessionInfoTools],
@@ -202,6 +203,11 @@ export async function buildSessionTools(cwd: string, enabledExtensions: string[]
202
203
  ['ask_human', buildHumanLoopTools],
203
204
  ['peer_query', buildPeerQueryTools],
204
205
  ['team_context', buildTeamContextTools],
206
+ ['execute', buildExecuteTools],
207
+ ['skills', buildSkillsTools],
208
+ ['files_v2', buildFilesTools],
209
+ ['memory_v2', buildMemoryTool],
210
+ ['platform_v2', buildPlatformV2Tools],
205
211
  ]
206
212
 
207
213
  for (const [extensionId, builder] of nativeBuilders) {
@@ -288,8 +294,8 @@ export async function buildSessionTools(cwd: string, enabledExtensions: string[]
288
294
  tools.push(t)
289
295
  }
290
296
  }
291
- } catch (err: any) {
292
- log.warn('session-tools', `Failed to connect MCP server "${config.name}"`, { serverId, error: err.message })
297
+ } catch (err: unknown) {
298
+ log.warn('session-tools', `Failed to connect MCP server "${config.name}"`, { serverId, error: errorMessage(err) })
293
299
  }
294
300
  }
295
301
  cleanupFns.push(async () => {
@@ -456,8 +462,8 @@ export async function buildSessionTools(cwd: string, enabledExtensions: string[]
456
462
  toolToExtensionMap,
457
463
  abortSignalRef,
458
464
  }
459
- } catch (err: any) {
460
- log.error(TAG, 'buildSessionTools critical failure:', err.message)
465
+ } catch (err: unknown) {
466
+ log.error(TAG, 'buildSessionTools critical failure:', errorMessage(err))
461
467
  throw err
462
468
  }
463
469
  }