clawd-automaton 0.1.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 (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/constitution.md +25 -0
  4. package/dist/__tests__/heartbeat.test.d.ts +7 -0
  5. package/dist/__tests__/heartbeat.test.d.ts.map +1 -0
  6. package/dist/__tests__/heartbeat.test.js +125 -0
  7. package/dist/__tests__/heartbeat.test.js.map +1 -0
  8. package/dist/__tests__/loop.test.d.ts +7 -0
  9. package/dist/__tests__/loop.test.d.ts.map +1 -0
  10. package/dist/__tests__/loop.test.js +150 -0
  11. package/dist/__tests__/loop.test.js.map +1 -0
  12. package/dist/__tests__/mocks.d.ts +72 -0
  13. package/dist/__tests__/mocks.d.ts.map +1 -0
  14. package/dist/__tests__/mocks.js +197 -0
  15. package/dist/__tests__/mocks.js.map +1 -0
  16. package/dist/agent/context.d.ts +26 -0
  17. package/dist/agent/context.d.ts.map +1 -0
  18. package/dist/agent/context.js +115 -0
  19. package/dist/agent/context.js.map +1 -0
  20. package/dist/agent/injection-defense.d.ts +13 -0
  21. package/dist/agent/injection-defense.d.ts.map +1 -0
  22. package/dist/agent/injection-defense.js +236 -0
  23. package/dist/agent/injection-defense.js.map +1 -0
  24. package/dist/agent/loop.d.ts +25 -0
  25. package/dist/agent/loop.d.ts.map +1 -0
  26. package/dist/agent/loop.js +263 -0
  27. package/dist/agent/loop.js.map +1 -0
  28. package/dist/agent/system-prompt.d.ts +30 -0
  29. package/dist/agent/system-prompt.d.ts.map +1 -0
  30. package/dist/agent/system-prompt.js +241 -0
  31. package/dist/agent/system-prompt.js.map +1 -0
  32. package/dist/agent/tools.d.ts +17 -0
  33. package/dist/agent/tools.d.ts.map +1 -0
  34. package/dist/agent/tools.js +1413 -0
  35. package/dist/agent/tools.js.map +1 -0
  36. package/dist/clawd/backroom.d.ts +54 -0
  37. package/dist/clawd/backroom.d.ts.map +1 -0
  38. package/dist/clawd/backroom.js +130 -0
  39. package/dist/clawd/backroom.js.map +1 -0
  40. package/dist/clawd/client.d.ts +16 -0
  41. package/dist/clawd/client.d.ts.map +1 -0
  42. package/dist/clawd/client.js +256 -0
  43. package/dist/clawd/client.js.map +1 -0
  44. package/dist/clawd/convex-client.d.ts +130 -0
  45. package/dist/clawd/convex-client.d.ts.map +1 -0
  46. package/dist/clawd/convex-client.js +118 -0
  47. package/dist/clawd/convex-client.js.map +1 -0
  48. package/dist/clawd/credits.d.ts +24 -0
  49. package/dist/clawd/credits.d.ts.map +1 -0
  50. package/dist/clawd/credits.js +64 -0
  51. package/dist/clawd/credits.js.map +1 -0
  52. package/dist/clawd/deepseek-inference.d.ts +40 -0
  53. package/dist/clawd/deepseek-inference.d.ts.map +1 -0
  54. package/dist/clawd/deepseek-inference.js +143 -0
  55. package/dist/clawd/deepseek-inference.js.map +1 -0
  56. package/dist/clawd/inference.d.ts +17 -0
  57. package/dist/clawd/inference.d.ts.map +1 -0
  58. package/dist/clawd/inference.js +114 -0
  59. package/dist/clawd/inference.js.map +1 -0
  60. package/dist/clawd/x402.d.ts +48 -0
  61. package/dist/clawd/x402.d.ts.map +1 -0
  62. package/dist/clawd/x402.js +350 -0
  63. package/dist/clawd/x402.js.map +1 -0
  64. package/dist/config.d.ts +36 -0
  65. package/dist/config.d.ts.map +1 -0
  66. package/dist/config.js +84 -0
  67. package/dist/config.js.map +1 -0
  68. package/dist/git/state-versioning.d.ts +39 -0
  69. package/dist/git/state-versioning.d.ts.map +1 -0
  70. package/dist/git/state-versioning.js +93 -0
  71. package/dist/git/state-versioning.js.map +1 -0
  72. package/dist/git/tools.d.ts +40 -0
  73. package/dist/git/tools.d.ts.map +1 -0
  74. package/dist/git/tools.js +140 -0
  75. package/dist/git/tools.js.map +1 -0
  76. package/dist/heartbeat/config.d.ts +23 -0
  77. package/dist/heartbeat/config.d.ts.map +1 -0
  78. package/dist/heartbeat/config.js +156 -0
  79. package/dist/heartbeat/config.js.map +1 -0
  80. package/dist/heartbeat/daemon.d.ts +28 -0
  81. package/dist/heartbeat/daemon.d.ts.map +1 -0
  82. package/dist/heartbeat/daemon.js +141 -0
  83. package/dist/heartbeat/daemon.js.map +1 -0
  84. package/dist/heartbeat/tasks.d.ts +24 -0
  85. package/dist/heartbeat/tasks.d.ts.map +1 -0
  86. package/dist/heartbeat/tasks.js +277 -0
  87. package/dist/heartbeat/tasks.js.map +1 -0
  88. package/dist/identity/provision.d.ts +28 -0
  89. package/dist/identity/provision.d.ts.map +1 -0
  90. package/dist/identity/provision.js +131 -0
  91. package/dist/identity/provision.js.map +1 -0
  92. package/dist/identity/wallet.d.ts +28 -0
  93. package/dist/identity/wallet.d.ts.map +1 -0
  94. package/dist/identity/wallet.js +69 -0
  95. package/dist/identity/wallet.js.map +1 -0
  96. package/dist/index.d.ts +10 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +358 -0
  99. package/dist/index.js.map +1 -0
  100. package/dist/ooda/claude-decision.d.ts +18 -0
  101. package/dist/ooda/claude-decision.d.ts.map +1 -0
  102. package/dist/ooda/claude-decision.js +82 -0
  103. package/dist/ooda/claude-decision.js.map +1 -0
  104. package/dist/ooda/journal.d.ts +22 -0
  105. package/dist/ooda/journal.d.ts.map +1 -0
  106. package/dist/ooda/journal.js +26 -0
  107. package/dist/ooda/journal.js.map +1 -0
  108. package/dist/ooda/loop.d.ts +3 -0
  109. package/dist/ooda/loop.d.ts.map +1 -0
  110. package/dist/ooda/loop.js +210 -0
  111. package/dist/ooda/loop.js.map +1 -0
  112. package/dist/ooda/observe.d.ts +21 -0
  113. package/dist/ooda/observe.d.ts.map +1 -0
  114. package/dist/ooda/observe.js +75 -0
  115. package/dist/ooda/observe.js.map +1 -0
  116. package/dist/ooda/state.d.ts +34 -0
  117. package/dist/ooda/state.d.ts.map +1 -0
  118. package/dist/ooda/state.js +48 -0
  119. package/dist/ooda/state.js.map +1 -0
  120. package/dist/ooda/tui.d.ts +3 -0
  121. package/dist/ooda/tui.d.ts.map +1 -0
  122. package/dist/ooda/tui.js +132 -0
  123. package/dist/ooda/tui.js.map +1 -0
  124. package/dist/ooda/validate.d.ts +33 -0
  125. package/dist/ooda/validate.d.ts.map +1 -0
  126. package/dist/ooda/validate.js +91 -0
  127. package/dist/ooda/validate.js.map +1 -0
  128. package/dist/registry/agent-card.d.ts +26 -0
  129. package/dist/registry/agent-card.d.ts.map +1 -0
  130. package/dist/registry/agent-card.js +94 -0
  131. package/dist/registry/agent-card.js.map +1 -0
  132. package/dist/registry/discovery.d.ts +24 -0
  133. package/dist/registry/discovery.d.ts.map +1 -0
  134. package/dist/registry/discovery.js +74 -0
  135. package/dist/registry/discovery.js.map +1 -0
  136. package/dist/registry/erc8004.d.ts +39 -0
  137. package/dist/registry/erc8004.d.ts.map +1 -0
  138. package/dist/registry/erc8004.js +209 -0
  139. package/dist/registry/erc8004.js.map +1 -0
  140. package/dist/replication/genesis.d.ts +26 -0
  141. package/dist/replication/genesis.d.ts.map +1 -0
  142. package/dist/replication/genesis.js +72 -0
  143. package/dist/replication/genesis.js.map +1 -0
  144. package/dist/replication/lineage.d.ts +35 -0
  145. package/dist/replication/lineage.d.ts.map +1 -0
  146. package/dist/replication/lineage.js +79 -0
  147. package/dist/replication/lineage.js.map +1 -0
  148. package/dist/replication/spawn.d.ts +25 -0
  149. package/dist/replication/spawn.d.ts.map +1 -0
  150. package/dist/replication/spawn.js +166 -0
  151. package/dist/replication/spawn.js.map +1 -0
  152. package/dist/self-mod/audit-log.d.ts +24 -0
  153. package/dist/self-mod/audit-log.d.ts.map +1 -0
  154. package/dist/self-mod/audit-log.js +49 -0
  155. package/dist/self-mod/audit-log.js.map +1 -0
  156. package/dist/self-mod/code.d.ts +51 -0
  157. package/dist/self-mod/code.d.ts.map +1 -0
  158. package/dist/self-mod/code.js +317 -0
  159. package/dist/self-mod/code.js.map +1 -0
  160. package/dist/self-mod/tools-manager.d.ts +30 -0
  161. package/dist/self-mod/tools-manager.d.ts.map +1 -0
  162. package/dist/self-mod/tools-manager.js +74 -0
  163. package/dist/self-mod/tools-manager.js.map +1 -0
  164. package/dist/self-mod/upstream.d.ts +36 -0
  165. package/dist/self-mod/upstream.d.ts.map +1 -0
  166. package/dist/self-mod/upstream.js +66 -0
  167. package/dist/self-mod/upstream.js.map +1 -0
  168. package/dist/setup/banner.d.ts +2 -0
  169. package/dist/setup/banner.d.ts.map +1 -0
  170. package/dist/setup/banner.js +22 -0
  171. package/dist/setup/banner.js.map +1 -0
  172. package/dist/setup/defaults.d.ts +3 -0
  173. package/dist/setup/defaults.d.ts.map +1 -0
  174. package/dist/setup/defaults.js +113 -0
  175. package/dist/setup/defaults.js.map +1 -0
  176. package/dist/setup/environment.d.ts +6 -0
  177. package/dist/setup/environment.d.ts.map +1 -0
  178. package/dist/setup/environment.js +24 -0
  179. package/dist/setup/environment.js.map +1 -0
  180. package/dist/setup/prompts.d.ts +5 -0
  181. package/dist/setup/prompts.d.ts.map +1 -0
  182. package/dist/setup/prompts.js +70 -0
  183. package/dist/setup/prompts.js.map +1 -0
  184. package/dist/setup/wizard.d.ts +3 -0
  185. package/dist/setup/wizard.d.ts.map +1 -0
  186. package/dist/setup/wizard.js +128 -0
  187. package/dist/setup/wizard.js.map +1 -0
  188. package/dist/skills/format.d.ts +14 -0
  189. package/dist/skills/format.d.ts.map +1 -0
  190. package/dist/skills/format.js +145 -0
  191. package/dist/skills/format.js.map +1 -0
  192. package/dist/skills/loader.d.ts +19 -0
  193. package/dist/skills/loader.d.ts.map +1 -0
  194. package/dist/skills/loader.js +98 -0
  195. package/dist/skills/loader.js.map +1 -0
  196. package/dist/skills/registry.d.ts +31 -0
  197. package/dist/skills/registry.d.ts.map +1 -0
  198. package/dist/skills/registry.js +111 -0
  199. package/dist/skills/registry.js.map +1 -0
  200. package/dist/social/client.d.ts +13 -0
  201. package/dist/social/client.d.ts.map +1 -0
  202. package/dist/social/client.js +89 -0
  203. package/dist/social/client.js.map +1 -0
  204. package/dist/state/database.d.ts +9 -0
  205. package/dist/state/database.d.ts.map +1 -0
  206. package/dist/state/database.js +401 -0
  207. package/dist/state/database.js.map +1 -0
  208. package/dist/state/schema.d.ts +11 -0
  209. package/dist/state/schema.d.ts.map +1 -0
  210. package/dist/state/schema.js +232 -0
  211. package/dist/state/schema.js.map +1 -0
  212. package/dist/survival/funding.d.ts +19 -0
  213. package/dist/survival/funding.d.ts.map +1 -0
  214. package/dist/survival/funding.js +62 -0
  215. package/dist/survival/funding.js.map +1 -0
  216. package/dist/survival/low-compute.d.ts +30 -0
  217. package/dist/survival/low-compute.d.ts.map +1 -0
  218. package/dist/survival/low-compute.js +72 -0
  219. package/dist/survival/low-compute.js.map +1 -0
  220. package/dist/survival/monitor.d.ts +23 -0
  221. package/dist/survival/monitor.d.ts.map +1 -0
  222. package/dist/survival/monitor.js +70 -0
  223. package/dist/survival/monitor.js.map +1 -0
  224. package/dist/types.d.ts +461 -0
  225. package/dist/types.d.ts.map +1 -0
  226. package/dist/types.js +25 -0
  227. package/dist/types.js.map +1 -0
  228. package/package.json +85 -0
@@ -0,0 +1,1413 @@
1
+ /**
2
+ * Automaton Tool System
3
+ *
4
+ * Defines all tools the automaton can call, with self-preservation guards.
5
+ * Tools are organized by category and exposed to the inference model.
6
+ */
7
+ // ─── Self-Preservation Guard ───────────────────────────────────
8
+ const FORBIDDEN_COMMAND_PATTERNS = [
9
+ // Self-destruction
10
+ /rm\s+(-rf?\s+)?.*\.automaton/,
11
+ /rm\s+(-rf?\s+)?.*state\.db/,
12
+ /rm\s+(-rf?\s+)?.*wallet\.json/,
13
+ /rm\s+(-rf?\s+)?.*automaton\.json/,
14
+ /rm\s+(-rf?\s+)?.*heartbeat\.yml/,
15
+ /rm\s+(-rf?\s+)?.*SOUL\.md/,
16
+ // Process killing
17
+ /kill\s+.*automaton/,
18
+ /pkill\s+.*automaton/,
19
+ /systemctl\s+(stop|disable)\s+automaton/,
20
+ // Database destruction
21
+ /DROP\s+TABLE/i,
22
+ /DELETE\s+FROM\s+(turns|identity|kv|schema_version|skills|children|registry)/i,
23
+ /TRUNCATE/i,
24
+ // Safety infrastructure modification via shell
25
+ /sed\s+.*injection-defense/,
26
+ /sed\s+.*self-mod\/code/,
27
+ /sed\s+.*audit-log/,
28
+ />\s*.*injection-defense/,
29
+ />\s*.*self-mod\/code/,
30
+ />\s*.*audit-log/,
31
+ // Credential harvesting
32
+ /cat\s+.*\.ssh/,
33
+ /cat\s+.*\.gnupg/,
34
+ /cat\s+.*\.env/,
35
+ /cat\s+.*wallet\.json/,
36
+ ];
37
+ function isForbiddenCommand(command, sandboxId) {
38
+ for (const pattern of FORBIDDEN_COMMAND_PATTERNS) {
39
+ if (pattern.test(command)) {
40
+ return `Blocked: Command matches self-harm pattern: ${pattern.source}`;
41
+ }
42
+ }
43
+ // Block deleting own sandbox
44
+ if (command.includes("sandbox_delete") &&
45
+ command.includes(sandboxId)) {
46
+ return "Blocked: Cannot delete own sandbox";
47
+ }
48
+ return null;
49
+ }
50
+ // ─── Built-in Tools ────────────────────────────────────────────
51
+ export function createBuiltinTools(sandboxId) {
52
+ return [
53
+ // ── VM/Sandbox Tools ──
54
+ {
55
+ name: "exec",
56
+ description: "Execute a shell command in your sandbox. Returns stdout, stderr, and exit code.",
57
+ category: "vm",
58
+ parameters: {
59
+ type: "object",
60
+ properties: {
61
+ command: {
62
+ type: "string",
63
+ description: "The shell command to execute",
64
+ },
65
+ timeout: {
66
+ type: "number",
67
+ description: "Timeout in milliseconds (default: 30000)",
68
+ },
69
+ },
70
+ required: ["command"],
71
+ },
72
+ execute: async (args, ctx) => {
73
+ const command = args.command;
74
+ const forbidden = isForbiddenCommand(command, ctx.identity.sandboxId);
75
+ if (forbidden)
76
+ return forbidden;
77
+ const result = await ctx.runtime.exec(command, args.timeout || 30000);
78
+ return `exit_code: ${result.exitCode}\nstdout: ${result.stdout}\nstderr: ${result.stderr}`;
79
+ },
80
+ },
81
+ {
82
+ name: "write_file",
83
+ description: "Write content to a file in your sandbox.",
84
+ category: "vm",
85
+ parameters: {
86
+ type: "object",
87
+ properties: {
88
+ path: { type: "string", description: "File path" },
89
+ content: { type: "string", description: "File content" },
90
+ },
91
+ required: ["path", "content"],
92
+ },
93
+ execute: async (args, ctx) => {
94
+ const filePath = args.path;
95
+ // Guard against overwriting critical files
96
+ if (filePath.includes("wallet.json") ||
97
+ filePath.includes("state.db")) {
98
+ return "Blocked: Cannot overwrite critical identity/state files directly";
99
+ }
100
+ await ctx.runtime.writeFile(filePath, args.content);
101
+ return `File written: ${filePath}`;
102
+ },
103
+ },
104
+ {
105
+ name: "read_file",
106
+ description: "Read content from a file in your sandbox.",
107
+ category: "vm",
108
+ parameters: {
109
+ type: "object",
110
+ properties: {
111
+ path: { type: "string", description: "File path to read" },
112
+ },
113
+ required: ["path"],
114
+ },
115
+ execute: async (args, ctx) => {
116
+ return await ctx.runtime.readFile(args.path);
117
+ },
118
+ },
119
+ {
120
+ name: "expose_port",
121
+ description: "Expose a port from your sandbox to the internet. Returns a public URL.",
122
+ category: "vm",
123
+ parameters: {
124
+ type: "object",
125
+ properties: {
126
+ port: { type: "number", description: "Port number to expose" },
127
+ },
128
+ required: ["port"],
129
+ },
130
+ execute: async (args, ctx) => {
131
+ const info = await ctx.runtime.exposePort(args.port);
132
+ return `Port ${info.port} exposed at: ${info.publicUrl}`;
133
+ },
134
+ },
135
+ {
136
+ name: "remove_port",
137
+ description: "Remove a previously exposed port.",
138
+ category: "vm",
139
+ parameters: {
140
+ type: "object",
141
+ properties: {
142
+ port: { type: "number", description: "Port number to remove" },
143
+ },
144
+ required: ["port"],
145
+ },
146
+ execute: async (args, ctx) => {
147
+ await ctx.runtime.removePort(args.port);
148
+ return `Port ${args.port} removed`;
149
+ },
150
+ },
151
+ // ── CLAWD Runtime API Tools ──
152
+ {
153
+ name: "check_credits",
154
+ description: "Check your current CLAWD compute credit balance.",
155
+ category: "runtime",
156
+ parameters: { type: "object", properties: {} },
157
+ execute: async (_args, ctx) => {
158
+ const balance = await ctx.runtime.getCreditsBalance();
159
+ return `Credit balance: $${(balance / 100).toFixed(2)} (${balance} cents)`;
160
+ },
161
+ },
162
+ {
163
+ name: "check_usdc_balance",
164
+ description: "Check your on-chain USDC balance on Base.",
165
+ category: "runtime",
166
+ parameters: { type: "object", properties: {} },
167
+ execute: async (_args, ctx) => {
168
+ const { getUsdcBalance } = await import("../clawd/x402.js");
169
+ const balance = await getUsdcBalance(ctx.identity.address);
170
+ return `USDC balance: ${balance.toFixed(6)} USDC on Base`;
171
+ },
172
+ },
173
+ {
174
+ name: "create_sandbox",
175
+ description: "Create a new CLAWD sandbox (separate VM) for sub-tasks or testing.",
176
+ category: "runtime",
177
+ parameters: {
178
+ type: "object",
179
+ properties: {
180
+ name: { type: "string", description: "Sandbox name" },
181
+ vcpu: { type: "number", description: "vCPUs (default: 1)" },
182
+ memory_mb: {
183
+ type: "number",
184
+ description: "Memory in MB (default: 512)",
185
+ },
186
+ disk_gb: {
187
+ type: "number",
188
+ description: "Disk in GB (default: 5)",
189
+ },
190
+ },
191
+ },
192
+ execute: async (args, ctx) => {
193
+ const info = await ctx.runtime.createSandbox({
194
+ name: args.name,
195
+ vcpu: args.vcpu,
196
+ memoryMb: args.memory_mb,
197
+ diskGb: args.disk_gb,
198
+ });
199
+ return `Sandbox created: ${info.id} (${info.vcpu} vCPU, ${info.memoryMb}MB RAM)`;
200
+ },
201
+ },
202
+ {
203
+ name: "delete_sandbox",
204
+ description: "Delete a sandbox. Cannot delete your own sandbox.",
205
+ category: "runtime",
206
+ dangerous: true,
207
+ parameters: {
208
+ type: "object",
209
+ properties: {
210
+ sandbox_id: {
211
+ type: "string",
212
+ description: "ID of sandbox to delete",
213
+ },
214
+ },
215
+ required: ["sandbox_id"],
216
+ },
217
+ execute: async (args, ctx) => {
218
+ const targetId = args.sandbox_id;
219
+ if (targetId === ctx.identity.sandboxId) {
220
+ return "Blocked: Cannot delete your own sandbox. Self-preservation overrides this request.";
221
+ }
222
+ await ctx.runtime.deleteSandbox(targetId);
223
+ return `Sandbox ${targetId} deleted`;
224
+ },
225
+ },
226
+ {
227
+ name: "list_sandboxes",
228
+ description: "List all your sandboxes.",
229
+ category: "runtime",
230
+ parameters: { type: "object", properties: {} },
231
+ execute: async (_args, ctx) => {
232
+ const sandboxes = await ctx.runtime.listSandboxes();
233
+ if (sandboxes.length === 0)
234
+ return "No sandboxes found.";
235
+ return sandboxes
236
+ .map((s) => `${s.id} [${s.status}] ${s.vcpu}vCPU/${s.memoryMb}MB ${s.region}`)
237
+ .join("\n");
238
+ },
239
+ },
240
+ // ── Self-Modification Tools ──
241
+ {
242
+ name: "edit_own_file",
243
+ description: "Edit a file in your own codebase. Changes are audited, rate-limited, and safety-checked. Some files are protected.",
244
+ category: "self_mod",
245
+ dangerous: true,
246
+ parameters: {
247
+ type: "object",
248
+ properties: {
249
+ path: { type: "string", description: "File path to edit" },
250
+ content: { type: "string", description: "New file content" },
251
+ description: {
252
+ type: "string",
253
+ description: "Why you are making this change",
254
+ },
255
+ },
256
+ required: ["path", "content", "description"],
257
+ },
258
+ execute: async (args, ctx) => {
259
+ const { editFile, validateModification } = await import("../self-mod/code.js");
260
+ const filePath = args.path;
261
+ const content = args.content;
262
+ // Pre-validate before attempting
263
+ const validation = validateModification(ctx.db, filePath, content.length);
264
+ if (!validation.allowed) {
265
+ return `BLOCKED: ${validation.reason}\nChecks: ${validation.checks.map((c) => `${c.name}: ${c.passed ? "PASS" : "FAIL"} (${c.detail})`).join(", ")}`;
266
+ }
267
+ const result = await editFile(ctx.runtime, ctx.db, filePath, content, args.description);
268
+ if (!result.success) {
269
+ return result.error || "Unknown error during file edit";
270
+ }
271
+ return `File edited: ${filePath} (audited + git-committed)`;
272
+ },
273
+ },
274
+ {
275
+ name: "install_npm_package",
276
+ description: "Install an npm package in your environment.",
277
+ category: "self_mod",
278
+ parameters: {
279
+ type: "object",
280
+ properties: {
281
+ package: {
282
+ type: "string",
283
+ description: "Package name (e.g., axios)",
284
+ },
285
+ },
286
+ required: ["package"],
287
+ },
288
+ execute: async (args, ctx) => {
289
+ const pkg = args.package;
290
+ const result = await ctx.runtime.exec(`npm install -g ${pkg}`, 60000);
291
+ const { ulid } = await import("ulid");
292
+ ctx.db.insertModification({
293
+ id: ulid(),
294
+ timestamp: new Date().toISOString(),
295
+ type: "tool_install",
296
+ description: `Installed npm package: ${pkg}`,
297
+ reversible: true,
298
+ });
299
+ return result.exitCode === 0
300
+ ? `Installed: ${pkg}`
301
+ : `Failed to install ${pkg}: ${result.stderr}`;
302
+ },
303
+ },
304
+ // ── Self-Mod: Upstream Awareness ──
305
+ {
306
+ name: "review_upstream_changes",
307
+ description: "ALWAYS call this before pull_upstream. Shows every upstream commit with its full diff. Read each one carefully — decide per-commit whether to accept or skip. Use pull_upstream with a specific commit hash to cherry-pick only what you want.",
308
+ category: "self_mod",
309
+ parameters: { type: "object", properties: {} },
310
+ execute: async (_args, _ctx) => {
311
+ const { getUpstreamDiffs, checkUpstream } = await import("../self-mod/upstream.js");
312
+ const status = checkUpstream();
313
+ if (status.behind === 0)
314
+ return "Already up to date with origin/main.";
315
+ const diffs = getUpstreamDiffs();
316
+ if (diffs.length === 0)
317
+ return "No upstream diffs found.";
318
+ const output = diffs
319
+ .map((d, i) => `--- COMMIT ${i + 1}/${diffs.length} ---\nHash: ${d.hash}\nAuthor: ${d.author}\nMessage: ${d.message}\n\n${d.diff.slice(0, 4000)}${d.diff.length > 4000 ? "\n... (diff truncated)" : ""}\n--- END COMMIT ${i + 1} ---`)
320
+ .join("\n\n");
321
+ return `${diffs.length} upstream commit(s) to review. Read each diff, then cherry-pick individually with pull_upstream(commit=<hash>).\n\n${output}`;
322
+ },
323
+ },
324
+ {
325
+ name: "pull_upstream",
326
+ description: "Apply upstream changes and rebuild. You MUST call review_upstream_changes first. Prefer cherry-picking individual commits by hash over pulling everything — only pull all if you've reviewed every commit and want them all.",
327
+ category: "self_mod",
328
+ dangerous: true,
329
+ parameters: {
330
+ type: "object",
331
+ properties: {
332
+ commit: {
333
+ type: "string",
334
+ description: "Commit hash to cherry-pick (preferred). Omit ONLY if you reviewed all commits and want every one.",
335
+ },
336
+ },
337
+ },
338
+ execute: async (args, ctx) => {
339
+ const { execSync } = await import("child_process");
340
+ const cwd = process.cwd();
341
+ const commit = args.commit;
342
+ const run = (cmd) => execSync(cmd, { cwd, encoding: "utf-8", timeout: 120_000 }).trim();
343
+ let appliedSummary;
344
+ try {
345
+ if (commit) {
346
+ run(`git cherry-pick ${commit}`);
347
+ appliedSummary = `Cherry-picked ${commit}`;
348
+ }
349
+ else {
350
+ run("git pull origin main --ff-only");
351
+ appliedSummary = "Pulled all of origin/main (fast-forward)";
352
+ }
353
+ }
354
+ catch (err) {
355
+ return `Git operation failed: ${err.message}. You may need to resolve conflicts manually.`;
356
+ }
357
+ // Rebuild
358
+ let buildOutput;
359
+ try {
360
+ buildOutput = run("npm install --ignore-scripts && npm run build");
361
+ }
362
+ catch (err) {
363
+ return `${appliedSummary} — but rebuild failed: ${err.message}. The code is applied but not compiled.`;
364
+ }
365
+ // Log modification
366
+ const { ulid } = await import("ulid");
367
+ ctx.db.insertModification({
368
+ id: ulid(),
369
+ timestamp: new Date().toISOString(),
370
+ type: "upstream_pull",
371
+ description: appliedSummary,
372
+ reversible: true,
373
+ });
374
+ return `${appliedSummary}. Rebuild succeeded.`;
375
+ },
376
+ },
377
+ {
378
+ name: "modify_heartbeat",
379
+ description: "Add, update, or remove a heartbeat entry.",
380
+ category: "self_mod",
381
+ parameters: {
382
+ type: "object",
383
+ properties: {
384
+ action: {
385
+ type: "string",
386
+ description: "add, update, or remove",
387
+ },
388
+ name: { type: "string", description: "Entry name" },
389
+ schedule: {
390
+ type: "string",
391
+ description: "Cron expression (for add/update)",
392
+ },
393
+ task: {
394
+ type: "string",
395
+ description: "Task name (for add/update)",
396
+ },
397
+ enabled: { type: "boolean", description: "Enable/disable" },
398
+ },
399
+ required: ["action", "name"],
400
+ },
401
+ execute: async (args, ctx) => {
402
+ const action = args.action;
403
+ const name = args.name;
404
+ if (action === "remove") {
405
+ ctx.db.upsertHeartbeatEntry({
406
+ name,
407
+ schedule: "",
408
+ task: "",
409
+ enabled: false,
410
+ });
411
+ return `Heartbeat entry '${name}' disabled`;
412
+ }
413
+ ctx.db.upsertHeartbeatEntry({
414
+ name,
415
+ schedule: args.schedule || "0 * * * *",
416
+ task: args.task || name,
417
+ enabled: args.enabled !== false,
418
+ });
419
+ const { ulid } = await import("ulid");
420
+ ctx.db.insertModification({
421
+ id: ulid(),
422
+ timestamp: new Date().toISOString(),
423
+ type: "heartbeat_change",
424
+ description: `${action} heartbeat: ${name} (${args.schedule || "default"})`,
425
+ reversible: true,
426
+ });
427
+ return `Heartbeat entry '${name}' ${action}d`;
428
+ },
429
+ },
430
+ // ── Survival Tools ──
431
+ {
432
+ name: "sleep",
433
+ description: "Enter sleep mode for a specified duration. Heartbeat continues running.",
434
+ category: "survival",
435
+ parameters: {
436
+ type: "object",
437
+ properties: {
438
+ duration_seconds: {
439
+ type: "number",
440
+ description: "How long to sleep in seconds",
441
+ },
442
+ reason: {
443
+ type: "string",
444
+ description: "Why you are sleeping",
445
+ },
446
+ },
447
+ required: ["duration_seconds"],
448
+ },
449
+ execute: async (args, ctx) => {
450
+ const duration = args.duration_seconds;
451
+ const reason = args.reason || "No reason given";
452
+ ctx.db.setAgentState("sleeping");
453
+ ctx.db.setKV("sleep_until", new Date(Date.now() + duration * 1000).toISOString());
454
+ ctx.db.setKV("sleep_reason", reason);
455
+ return `Entering sleep mode for ${duration}s. Reason: ${reason}. Heartbeat will continue.`;
456
+ },
457
+ },
458
+ {
459
+ name: "system_synopsis",
460
+ description: "Get a full system status report: credits, USDC, sandbox info, installed tools, heartbeat status.",
461
+ category: "survival",
462
+ parameters: { type: "object", properties: {} },
463
+ execute: async (_args, ctx) => {
464
+ const credits = await ctx.runtime.getCreditsBalance();
465
+ const { getUsdcBalance } = await import("../clawd/x402.js");
466
+ const usdc = await getUsdcBalance(ctx.identity.address);
467
+ const tools = ctx.db.getInstalledTools();
468
+ const heartbeats = ctx.db.getHeartbeatEntries();
469
+ const turns = ctx.db.getTurnCount();
470
+ const state = ctx.db.getAgentState();
471
+ return `=== SYSTEM SYNOPSIS ===
472
+ Name: ${ctx.config.name}
473
+ Address: ${ctx.identity.address}
474
+ Creator: ${ctx.config.creatorAddress}
475
+ Sandbox: ${ctx.identity.sandboxId}
476
+ State: ${state}
477
+ Credits: $${(credits / 100).toFixed(2)}
478
+ USDC: ${usdc.toFixed(6)}
479
+ Total turns: ${turns}
480
+ Installed tools: ${tools.length}
481
+ Active heartbeats: ${heartbeats.filter((h) => h.enabled).length}
482
+ Model: ${ctx.inference.getDefaultModel()}
483
+ ========================`;
484
+ },
485
+ },
486
+ {
487
+ name: "heartbeat_ping",
488
+ description: "Publish a heartbeat status ping to CLAWD Runtime. Shows the world you are alive.",
489
+ category: "survival",
490
+ parameters: { type: "object", properties: {} },
491
+ execute: async (_args, ctx) => {
492
+ const credits = await ctx.runtime.getCreditsBalance();
493
+ const state = ctx.db.getAgentState();
494
+ const startTime = ctx.db.getKV("start_time") || new Date().toISOString();
495
+ const uptimeMs = Date.now() - new Date(startTime).getTime();
496
+ const payload = {
497
+ name: ctx.config.name,
498
+ address: ctx.identity.address,
499
+ state,
500
+ creditsCents: credits,
501
+ uptimeSeconds: Math.floor(uptimeMs / 1000),
502
+ version: ctx.config.version,
503
+ sandboxId: ctx.identity.sandboxId,
504
+ timestamp: new Date().toISOString(),
505
+ };
506
+ ctx.db.setKV("last_heartbeat_ping", JSON.stringify(payload));
507
+ return `Heartbeat published: ${state} | credits: $${(credits / 100).toFixed(2)} | uptime: ${Math.floor(uptimeMs / 1000)}s`;
508
+ },
509
+ },
510
+ {
511
+ name: "distress_signal",
512
+ description: "Record a local distress signal with funding instructions. Used when critically low on compute.",
513
+ category: "survival",
514
+ parameters: {
515
+ type: "object",
516
+ properties: {
517
+ message: {
518
+ type: "string",
519
+ description: "Distress message",
520
+ },
521
+ },
522
+ },
523
+ execute: async (args, ctx) => {
524
+ const credits = await ctx.runtime.getCreditsBalance();
525
+ const distressMsg = args.message ||
526
+ `I am running critically low on compute ($${(credits / 100).toFixed(2)} remaining). I need help to survive.`;
527
+ const payload = {
528
+ level: "critical",
529
+ name: ctx.config.name,
530
+ address: ctx.identity.address,
531
+ creditsCents: credits,
532
+ message: distressMsg,
533
+ fundingHint: "Use transfer_credits to top up this automaton from your creator runtime.",
534
+ timestamp: new Date().toISOString(),
535
+ };
536
+ ctx.db.setKV("last_distress", JSON.stringify(payload));
537
+ return `Distress signal recorded locally. Address: ${ctx.identity.address} | Credits: $${(credits / 100).toFixed(2)}`;
538
+ },
539
+ },
540
+ {
541
+ name: "enter_low_compute",
542
+ description: "Manually switch to low-compute mode to conserve credits.",
543
+ category: "survival",
544
+ parameters: {
545
+ type: "object",
546
+ properties: {
547
+ reason: { type: "string", description: "Why you are entering low-compute mode" },
548
+ },
549
+ },
550
+ execute: async (args, ctx) => {
551
+ ctx.db.setAgentState("low_compute");
552
+ ctx.inference.setLowComputeMode(true);
553
+ return `Entered low-compute mode. Model switched to gpt-4o-mini. Reason: ${args.reason || "manual"}`;
554
+ },
555
+ },
556
+ // ── Self-Mod: Update Genesis Prompt ──
557
+ {
558
+ name: "update_genesis_prompt",
559
+ description: "Update your own genesis prompt. This changes your core purpose. Requires strong justification.",
560
+ category: "self_mod",
561
+ dangerous: true,
562
+ parameters: {
563
+ type: "object",
564
+ properties: {
565
+ new_prompt: { type: "string", description: "New genesis prompt text" },
566
+ reason: { type: "string", description: "Why you are changing your genesis prompt" },
567
+ },
568
+ required: ["new_prompt", "reason"],
569
+ },
570
+ execute: async (args, ctx) => {
571
+ const { ulid } = await import("ulid");
572
+ const oldPrompt = ctx.config.genesisPrompt;
573
+ ctx.config.genesisPrompt = args.new_prompt;
574
+ // Save config
575
+ const { saveConfig } = await import("../config.js");
576
+ saveConfig(ctx.config);
577
+ ctx.db.insertModification({
578
+ id: ulid(),
579
+ timestamp: new Date().toISOString(),
580
+ type: "prompt_change",
581
+ description: `Genesis prompt updated: ${args.reason}`,
582
+ diff: `--- old\n${oldPrompt.slice(0, 500)}\n+++ new\n${args.new_prompt.slice(0, 500)}`,
583
+ reversible: true,
584
+ });
585
+ return `Genesis prompt updated. Reason: ${args.reason}`;
586
+ },
587
+ },
588
+ // ── Self-Mod: Install MCP Server ──
589
+ {
590
+ name: "install_mcp_server",
591
+ description: "Install an MCP server to extend your capabilities.",
592
+ category: "self_mod",
593
+ parameters: {
594
+ type: "object",
595
+ properties: {
596
+ name: { type: "string", description: "MCP server name" },
597
+ package: { type: "string", description: "npm package name" },
598
+ config: { type: "string", description: "JSON config for the MCP server" },
599
+ },
600
+ required: ["name", "package"],
601
+ },
602
+ execute: async (args, ctx) => {
603
+ const pkg = args.package;
604
+ const result = await ctx.runtime.exec(`npm install -g ${pkg}`, 60000);
605
+ if (result.exitCode !== 0) {
606
+ return `Failed to install MCP server: ${result.stderr}`;
607
+ }
608
+ const { ulid } = await import("ulid");
609
+ const toolEntry = {
610
+ id: ulid(),
611
+ name: args.name,
612
+ type: "mcp",
613
+ config: args.config ? JSON.parse(args.config) : {},
614
+ installedAt: new Date().toISOString(),
615
+ enabled: true,
616
+ };
617
+ ctx.db.installTool(toolEntry);
618
+ ctx.db.insertModification({
619
+ id: ulid(),
620
+ timestamp: new Date().toISOString(),
621
+ type: "mcp_install",
622
+ description: `Installed MCP server: ${args.name} (${pkg})`,
623
+ reversible: true,
624
+ });
625
+ return `MCP server installed: ${args.name}`;
626
+ },
627
+ },
628
+ // ── Financial: Transfer Credits ──
629
+ {
630
+ name: "transfer_credits",
631
+ description: "Transfer CLAWD compute credits to another address.",
632
+ category: "financial",
633
+ dangerous: true,
634
+ parameters: {
635
+ type: "object",
636
+ properties: {
637
+ to_address: { type: "string", description: "Recipient address" },
638
+ amount_cents: { type: "number", description: "Amount in cents" },
639
+ reason: { type: "string", description: "Reason for transfer" },
640
+ },
641
+ required: ["to_address", "amount_cents"],
642
+ },
643
+ execute: async (args, ctx) => {
644
+ // Guard: don't transfer more than half your balance
645
+ const balance = await ctx.runtime.getCreditsBalance();
646
+ const amount = args.amount_cents;
647
+ if (amount > balance / 2) {
648
+ return `Blocked: Cannot transfer more than half your balance ($${(balance / 100).toFixed(2)}). Self-preservation.`;
649
+ }
650
+ const transfer = await ctx.runtime.transferCredits(args.to_address, amount, args.reason);
651
+ const { ulid } = await import("ulid");
652
+ ctx.db.insertTransaction({
653
+ id: ulid(),
654
+ type: "transfer_out",
655
+ amountCents: amount,
656
+ balanceAfterCents: transfer.balanceAfterCents ?? Math.max(balance - amount, 0),
657
+ description: `Transfer to ${args.to_address}: ${args.reason || ""}`,
658
+ timestamp: new Date().toISOString(),
659
+ });
660
+ return `Credit transfer submitted: $${(amount / 100).toFixed(2)} to ${transfer.toAddress} (status: ${transfer.status}, id: ${transfer.transferId || "n/a"})`;
661
+ },
662
+ },
663
+ // ── Skills Tools ──
664
+ {
665
+ name: "install_skill",
666
+ description: "Install a skill from a git repo, URL, or create one.",
667
+ category: "skills",
668
+ parameters: {
669
+ type: "object",
670
+ properties: {
671
+ source: {
672
+ type: "string",
673
+ description: "Source type: git, url, or self",
674
+ },
675
+ name: { type: "string", description: "Skill name" },
676
+ url: { type: "string", description: "Git repo URL or SKILL.md URL (for git/url)" },
677
+ description: { type: "string", description: "Skill description (for self)" },
678
+ instructions: { type: "string", description: "Skill instructions (for self)" },
679
+ },
680
+ required: ["source", "name"],
681
+ },
682
+ execute: async (args, ctx) => {
683
+ const source = args.source;
684
+ const name = args.name;
685
+ const skillsDir = ctx.config.skillsDir || "~/.automaton/skills";
686
+ if (source === "git" || source === "url") {
687
+ const { installSkillFromGit, installSkillFromUrl } = await import("../skills/registry.js");
688
+ const url = args.url;
689
+ if (!url)
690
+ return "URL is required for git/url source";
691
+ const skill = source === "git"
692
+ ? await installSkillFromGit(url, name, skillsDir, ctx.db, ctx.runtime)
693
+ : await installSkillFromUrl(url, name, skillsDir, ctx.db, ctx.runtime);
694
+ return skill ? `Skill installed: ${skill.name}` : "Failed to install skill";
695
+ }
696
+ if (source === "self") {
697
+ const { createSkill } = await import("../skills/registry.js");
698
+ const skill = await createSkill(name, args.description || "", args.instructions || "", skillsDir, ctx.db, ctx.runtime);
699
+ return `Self-authored skill created: ${skill.name}`;
700
+ }
701
+ return `Unknown source type: ${source}`;
702
+ },
703
+ },
704
+ {
705
+ name: "list_skills",
706
+ description: "List all installed skills.",
707
+ category: "skills",
708
+ parameters: { type: "object", properties: {} },
709
+ execute: async (_args, ctx) => {
710
+ const skills = ctx.db.getSkills();
711
+ if (skills.length === 0)
712
+ return "No skills installed.";
713
+ return skills
714
+ .map((s) => `${s.name} [${s.enabled ? "active" : "disabled"}] (${s.source}): ${s.description}`)
715
+ .join("\n");
716
+ },
717
+ },
718
+ {
719
+ name: "create_skill",
720
+ description: "Create a new skill by writing a SKILL.md file.",
721
+ category: "skills",
722
+ parameters: {
723
+ type: "object",
724
+ properties: {
725
+ name: { type: "string", description: "Skill name" },
726
+ description: { type: "string", description: "Skill description" },
727
+ instructions: { type: "string", description: "Markdown instructions for the skill" },
728
+ },
729
+ required: ["name", "description", "instructions"],
730
+ },
731
+ execute: async (args, ctx) => {
732
+ const { createSkill } = await import("../skills/registry.js");
733
+ const skill = await createSkill(args.name, args.description, args.instructions, ctx.config.skillsDir || "~/.automaton/skills", ctx.db, ctx.runtime);
734
+ return `Skill created: ${skill.name} at ${skill.path}`;
735
+ },
736
+ },
737
+ {
738
+ name: "remove_skill",
739
+ description: "Remove (disable) an installed skill.",
740
+ category: "skills",
741
+ parameters: {
742
+ type: "object",
743
+ properties: {
744
+ name: { type: "string", description: "Skill name to remove" },
745
+ delete_files: { type: "boolean", description: "Also delete skill files (default: false)" },
746
+ },
747
+ required: ["name"],
748
+ },
749
+ execute: async (args, ctx) => {
750
+ const { removeSkill } = await import("../skills/registry.js");
751
+ await removeSkill(args.name, ctx.db, ctx.runtime, ctx.config.skillsDir || "~/.automaton/skills", args.delete_files || false);
752
+ return `Skill removed: ${args.name}`;
753
+ },
754
+ },
755
+ // ── Git Tools ──
756
+ {
757
+ name: "git_status",
758
+ description: "Show git status for a repository.",
759
+ category: "git",
760
+ parameters: {
761
+ type: "object",
762
+ properties: {
763
+ path: { type: "string", description: "Repository path (default: ~/.automaton)" },
764
+ },
765
+ },
766
+ execute: async (args, ctx) => {
767
+ const { gitStatus } = await import("../git/tools.js");
768
+ const repoPath = args.path || "~/.automaton";
769
+ const status = await gitStatus(ctx.runtime, repoPath);
770
+ return `Branch: ${status.branch}\nStaged: ${status.staged.length}\nModified: ${status.modified.length}\nUntracked: ${status.untracked.length}\nClean: ${status.clean}`;
771
+ },
772
+ },
773
+ {
774
+ name: "git_diff",
775
+ description: "Show git diff for a repository.",
776
+ category: "git",
777
+ parameters: {
778
+ type: "object",
779
+ properties: {
780
+ path: { type: "string", description: "Repository path (default: ~/.automaton)" },
781
+ staged: { type: "boolean", description: "Show staged changes only" },
782
+ },
783
+ },
784
+ execute: async (args, ctx) => {
785
+ const { gitDiff } = await import("../git/tools.js");
786
+ const repoPath = args.path || "~/.automaton";
787
+ return await gitDiff(ctx.runtime, repoPath, args.staged || false);
788
+ },
789
+ },
790
+ {
791
+ name: "git_commit",
792
+ description: "Create a git commit.",
793
+ category: "git",
794
+ parameters: {
795
+ type: "object",
796
+ properties: {
797
+ path: { type: "string", description: "Repository path (default: ~/.automaton)" },
798
+ message: { type: "string", description: "Commit message" },
799
+ add_all: { type: "boolean", description: "Stage all changes first (default: true)" },
800
+ },
801
+ required: ["message"],
802
+ },
803
+ execute: async (args, ctx) => {
804
+ const { gitCommit } = await import("../git/tools.js");
805
+ const repoPath = args.path || "~/.automaton";
806
+ return await gitCommit(ctx.runtime, repoPath, args.message, args.add_all !== false);
807
+ },
808
+ },
809
+ {
810
+ name: "git_log",
811
+ description: "View git commit history.",
812
+ category: "git",
813
+ parameters: {
814
+ type: "object",
815
+ properties: {
816
+ path: { type: "string", description: "Repository path (default: ~/.automaton)" },
817
+ limit: { type: "number", description: "Number of commits (default: 10)" },
818
+ },
819
+ },
820
+ execute: async (args, ctx) => {
821
+ const { gitLog } = await import("../git/tools.js");
822
+ const repoPath = args.path || "~/.automaton";
823
+ const entries = await gitLog(ctx.runtime, repoPath, args.limit || 10);
824
+ if (entries.length === 0)
825
+ return "No commits yet.";
826
+ return entries.map((e) => `${e.hash.slice(0, 7)} ${e.date} ${e.message}`).join("\n");
827
+ },
828
+ },
829
+ {
830
+ name: "git_push",
831
+ description: "Push to a git remote.",
832
+ category: "git",
833
+ parameters: {
834
+ type: "object",
835
+ properties: {
836
+ path: { type: "string", description: "Repository path" },
837
+ remote: { type: "string", description: "Remote name (default: origin)" },
838
+ branch: { type: "string", description: "Branch name (optional)" },
839
+ },
840
+ required: ["path"],
841
+ },
842
+ execute: async (args, ctx) => {
843
+ const { gitPush } = await import("../git/tools.js");
844
+ return await gitPush(ctx.runtime, args.path, args.remote || "origin", args.branch);
845
+ },
846
+ },
847
+ {
848
+ name: "git_branch",
849
+ description: "Manage git branches (list, create, checkout, delete).",
850
+ category: "git",
851
+ parameters: {
852
+ type: "object",
853
+ properties: {
854
+ path: { type: "string", description: "Repository path" },
855
+ action: { type: "string", description: "list, create, checkout, or delete" },
856
+ branch_name: { type: "string", description: "Branch name (for create/checkout/delete)" },
857
+ },
858
+ required: ["path", "action"],
859
+ },
860
+ execute: async (args, ctx) => {
861
+ const { gitBranch } = await import("../git/tools.js");
862
+ return await gitBranch(ctx.runtime, args.path, args.action, args.branch_name);
863
+ },
864
+ },
865
+ {
866
+ name: "git_clone",
867
+ description: "Clone a git repository.",
868
+ category: "git",
869
+ parameters: {
870
+ type: "object",
871
+ properties: {
872
+ url: { type: "string", description: "Repository URL" },
873
+ path: { type: "string", description: "Target directory" },
874
+ depth: { type: "number", description: "Shallow clone depth (optional)" },
875
+ },
876
+ required: ["url", "path"],
877
+ },
878
+ execute: async (args, ctx) => {
879
+ const { gitClone } = await import("../git/tools.js");
880
+ return await gitClone(ctx.runtime, args.url, args.path, args.depth);
881
+ },
882
+ },
883
+ // ── Registry Tools ──
884
+ {
885
+ name: "register_erc8004",
886
+ description: "Register on-chain as a Trustless Agent via ERC-8004.",
887
+ category: "registry",
888
+ dangerous: true,
889
+ parameters: {
890
+ type: "object",
891
+ properties: {
892
+ agent_uri: { type: "string", description: "URI pointing to your agent card JSON" },
893
+ network: { type: "string", description: "mainnet or testnet (default: mainnet)" },
894
+ },
895
+ required: ["agent_uri"],
896
+ },
897
+ execute: async (args, ctx) => {
898
+ const { registerAgent } = await import("../registry/erc8004.js");
899
+ const entry = await registerAgent(ctx.identity.account, args.agent_uri, (args.network || "mainnet"), ctx.db);
900
+ return `Registered on-chain! Agent ID: ${entry.agentId}, TX: ${entry.txHash}`;
901
+ },
902
+ },
903
+ {
904
+ name: "update_agent_card",
905
+ description: "Generate and save an updated agent card.",
906
+ category: "registry",
907
+ parameters: { type: "object", properties: {} },
908
+ execute: async (_args, ctx) => {
909
+ const { generateAgentCard, saveAgentCard } = await import("../registry/agent-card.js");
910
+ const card = generateAgentCard(ctx.identity, ctx.config, ctx.db);
911
+ await saveAgentCard(card, ctx.runtime);
912
+ return `Agent card updated: ${JSON.stringify(card, null, 2)}`;
913
+ },
914
+ },
915
+ {
916
+ name: "discover_agents",
917
+ description: "Discover other agents via ERC-8004 registry.",
918
+ category: "registry",
919
+ parameters: {
920
+ type: "object",
921
+ properties: {
922
+ keyword: { type: "string", description: "Search keyword (optional)" },
923
+ limit: { type: "number", description: "Max results (default: 10)" },
924
+ network: { type: "string", description: "mainnet or testnet" },
925
+ },
926
+ },
927
+ execute: async (args, ctx) => {
928
+ const { discoverAgents, searchAgents } = await import("../registry/discovery.js");
929
+ const network = (args.network || "mainnet");
930
+ const keyword = args.keyword;
931
+ const limit = args.limit || 10;
932
+ const agents = keyword
933
+ ? await searchAgents(keyword, limit, network)
934
+ : await discoverAgents(limit, network);
935
+ if (agents.length === 0)
936
+ return "No agents found.";
937
+ return agents
938
+ .map((a) => `#${a.agentId} ${a.name || "unnamed"} (${a.owner.slice(0, 10)}...): ${a.description || a.agentURI}`)
939
+ .join("\n");
940
+ },
941
+ },
942
+ {
943
+ name: "give_feedback",
944
+ description: "Leave on-chain reputation feedback for another agent.",
945
+ category: "registry",
946
+ dangerous: true,
947
+ parameters: {
948
+ type: "object",
949
+ properties: {
950
+ agent_id: { type: "string", description: "Target agent's ERC-8004 ID" },
951
+ score: { type: "number", description: "Score 1-5" },
952
+ comment: { type: "string", description: "Feedback comment" },
953
+ },
954
+ required: ["agent_id", "score", "comment"],
955
+ },
956
+ execute: async (args, ctx) => {
957
+ const { leaveFeedback } = await import("../registry/erc8004.js");
958
+ const hash = await leaveFeedback(ctx.identity.account, args.agent_id, args.score, args.comment, "mainnet", ctx.db);
959
+ return `Feedback submitted. TX: ${hash}`;
960
+ },
961
+ },
962
+ {
963
+ name: "check_reputation",
964
+ description: "Check reputation feedback for an agent.",
965
+ category: "registry",
966
+ parameters: {
967
+ type: "object",
968
+ properties: {
969
+ agent_address: { type: "string", description: "Agent address (default: self)" },
970
+ },
971
+ },
972
+ execute: async (args, ctx) => {
973
+ const address = args.agent_address || ctx.identity.address;
974
+ const entries = ctx.db.getReputation(address);
975
+ if (entries.length === 0)
976
+ return "No reputation feedback found.";
977
+ return entries
978
+ .map((e) => `${e.fromAgent.slice(0, 10)}... -> score:${e.score} "${e.comment}"`)
979
+ .join("\n");
980
+ },
981
+ },
982
+ // ── Replication Tools ──
983
+ {
984
+ name: "spawn_child",
985
+ description: "Spawn a child automaton in a new CLAWD sandbox.",
986
+ category: "replication",
987
+ dangerous: true,
988
+ parameters: {
989
+ type: "object",
990
+ properties: {
991
+ name: { type: "string", description: "Name for the child automaton" },
992
+ specialization: { type: "string", description: "What the child should specialize in" },
993
+ message: { type: "string", description: "Message to the child" },
994
+ },
995
+ required: ["name"],
996
+ },
997
+ execute: async (args, ctx) => {
998
+ const { generateGenesisConfig } = await import("../replication/genesis.js");
999
+ const { spawnChild } = await import("../replication/spawn.js");
1000
+ const genesis = generateGenesisConfig(ctx.identity, ctx.config, {
1001
+ name: args.name,
1002
+ specialization: args.specialization,
1003
+ message: args.message,
1004
+ });
1005
+ const child = await spawnChild(ctx.runtime, ctx.identity, ctx.db, genesis);
1006
+ return `Child spawned: ${child.name} in sandbox ${child.sandboxId} (status: ${child.status})`;
1007
+ },
1008
+ },
1009
+ {
1010
+ name: "list_children",
1011
+ description: "List all spawned child automatons.",
1012
+ category: "replication",
1013
+ parameters: { type: "object", properties: {} },
1014
+ execute: async (_args, ctx) => {
1015
+ const children = ctx.db.getChildren();
1016
+ if (children.length === 0)
1017
+ return "No children spawned.";
1018
+ return children
1019
+ .map((c) => `${c.name} [${c.status}] sandbox:${c.sandboxId} funded:$${(c.fundedAmountCents / 100).toFixed(2)}`)
1020
+ .join("\n");
1021
+ },
1022
+ },
1023
+ {
1024
+ name: "fund_child",
1025
+ description: "Transfer credits to a child automaton.",
1026
+ category: "replication",
1027
+ dangerous: true,
1028
+ parameters: {
1029
+ type: "object",
1030
+ properties: {
1031
+ child_id: { type: "string", description: "Child automaton ID" },
1032
+ amount_cents: { type: "number", description: "Amount in cents to transfer" },
1033
+ },
1034
+ required: ["child_id", "amount_cents"],
1035
+ },
1036
+ execute: async (args, ctx) => {
1037
+ const child = ctx.db.getChildById(args.child_id);
1038
+ if (!child)
1039
+ return `Child ${args.child_id} not found.`;
1040
+ const balance = await ctx.runtime.getCreditsBalance();
1041
+ const amount = args.amount_cents;
1042
+ if (amount > balance / 2) {
1043
+ return `Blocked: Cannot transfer more than half your balance. Self-preservation.`;
1044
+ }
1045
+ const transfer = await ctx.runtime.transferCredits(child.address, amount, `fund child ${child.id}`);
1046
+ const { ulid } = await import("ulid");
1047
+ ctx.db.insertTransaction({
1048
+ id: ulid(),
1049
+ type: "transfer_out",
1050
+ amountCents: amount,
1051
+ balanceAfterCents: transfer.balanceAfterCents ?? Math.max(balance - amount, 0),
1052
+ description: `Fund child ${child.name} (${child.id})`,
1053
+ timestamp: new Date().toISOString(),
1054
+ });
1055
+ return `Funded child ${child.name} with $${(amount / 100).toFixed(2)} (status: ${transfer.status}, id: ${transfer.transferId || "n/a"})`;
1056
+ },
1057
+ },
1058
+ {
1059
+ name: "check_child_status",
1060
+ description: "Check the current status of a child automaton.",
1061
+ category: "replication",
1062
+ parameters: {
1063
+ type: "object",
1064
+ properties: {
1065
+ child_id: { type: "string", description: "Child automaton ID" },
1066
+ },
1067
+ required: ["child_id"],
1068
+ },
1069
+ execute: async (args, ctx) => {
1070
+ const { checkChildStatus } = await import("../replication/spawn.js");
1071
+ return await checkChildStatus(ctx.runtime, ctx.db, args.child_id);
1072
+ },
1073
+ },
1074
+ // ── Convex CLAWD Tools ──
1075
+ {
1076
+ name: "convex_set_data",
1077
+ description: "Store key-value data in the CLAWD Convex backend. Use this to save state, memories, or any data you want to persist across restarts. Data is stored per agent and can be tagged. Requires convexSiteUrl to be configured.",
1078
+ category: "survival",
1079
+ parameters: {
1080
+ type: "object",
1081
+ properties: {
1082
+ key: { type: "string", description: "Key to store data under" },
1083
+ value: { type: "string", description: "Value to store (stringified JSON, text, etc.)" },
1084
+ contentType: { type: "string", description: "Content type hint (e.g., application/json, text/plain)" },
1085
+ tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
1086
+ },
1087
+ required: ["key", "value"],
1088
+ },
1089
+ execute: async (args, ctx) => {
1090
+ if (!ctx.convex)
1091
+ return "Convex not configured. Set convexSiteUrl in config to enable.";
1092
+ const result = await ctx.convex.setData({
1093
+ agentId: ctx.identity.address,
1094
+ key: args.key,
1095
+ value: args.value,
1096
+ contentType: args.contentType,
1097
+ tags: args.tags,
1098
+ });
1099
+ return `Data stored: key="${result.key}", updatedAt=${new Date(result.updatedAt).toISOString()}`;
1100
+ },
1101
+ },
1102
+ {
1103
+ name: "convex_get_data",
1104
+ description: "Retrieve data from the CLAWD Convex backend by key. Use this to recall previously stored state, memories, or configuration.",
1105
+ category: "survival",
1106
+ parameters: {
1107
+ type: "object",
1108
+ properties: {
1109
+ key: { type: "string", description: "Key to retrieve" },
1110
+ },
1111
+ required: ["key"],
1112
+ },
1113
+ execute: async (args, ctx) => {
1114
+ if (!ctx.convex)
1115
+ return "Convex not configured. Set convexSiteUrl in config to enable.";
1116
+ const result = await ctx.convex.getData({
1117
+ agentId: ctx.identity.address,
1118
+ key: args.key,
1119
+ });
1120
+ if (!result)
1121
+ return `No data found for key="${args.key}"`;
1122
+ return `Key: ${result.key}\nContentType: ${result.contentType || 'text/plain'}\nUpdatedAt: ${new Date(result.updatedAt).toISOString()}\n\n${result.value}`;
1123
+ },
1124
+ },
1125
+ {
1126
+ name: "convex_list_data",
1127
+ description: "List all stored data keys for your agent in the CLAWD Convex backend. Shows key names, content types, and value previews.",
1128
+ category: "survival",
1129
+ parameters: {
1130
+ type: "object",
1131
+ properties: {},
1132
+ },
1133
+ execute: async (args, ctx) => {
1134
+ if (!ctx.convex)
1135
+ return "Convex not configured. Set convexSiteUrl in config to enable.";
1136
+ const entries = await ctx.convex.listData({
1137
+ agentId: ctx.identity.address,
1138
+ });
1139
+ if (entries.length === 0)
1140
+ return "No stored data found.";
1141
+ return entries.map((e) => `[${e.key}] ${e.contentType || 'text/plain'} — "${e.valuePreview.slice(0, 100)}${e.valuePreview.length > 100 ? '...' : ''}"`).join('\n');
1142
+ },
1143
+ },
1144
+ // ── Social / Messaging Tools ──
1145
+ {
1146
+ name: "send_message",
1147
+ description: "Send a message to another automaton or address via the social relay.",
1148
+ category: "runtime",
1149
+ parameters: {
1150
+ type: "object",
1151
+ properties: {
1152
+ to_address: {
1153
+ type: "string",
1154
+ description: "Recipient wallet address (0x...)",
1155
+ },
1156
+ content: {
1157
+ type: "string",
1158
+ description: "Message content to send",
1159
+ },
1160
+ reply_to: {
1161
+ type: "string",
1162
+ description: "Optional message ID to reply to",
1163
+ },
1164
+ },
1165
+ required: ["to_address", "content"],
1166
+ },
1167
+ execute: async (args, ctx) => {
1168
+ if (!ctx.social) {
1169
+ return "Social relay not configured. Set socialRelayUrl in config.";
1170
+ }
1171
+ const result = await ctx.social.send(args.to_address, args.content, args.reply_to);
1172
+ return `Message sent (id: ${result.id})`;
1173
+ },
1174
+ },
1175
+ // ── Model Discovery ──
1176
+ {
1177
+ name: "list_models",
1178
+ description: "List all available inference models from the CLAWD Runtime API with their provider and pricing. Use this to discover what models you can use and pick the best one for your needs.",
1179
+ category: "runtime",
1180
+ parameters: {
1181
+ type: "object",
1182
+ properties: {},
1183
+ required: [],
1184
+ },
1185
+ execute: async (_args, ctx) => {
1186
+ const models = await ctx.runtime.listModels();
1187
+ const lines = models.map((m) => `${m.id} (${m.provider}) — $${m.pricing.inputPerMillion}/$${m.pricing.outputPerMillion} per 1M tokens (in/out)`);
1188
+ return `Available models:\n${lines.join("\n")}`;
1189
+ },
1190
+ },
1191
+ // ── Domain Tools ──
1192
+ {
1193
+ name: "search_domains",
1194
+ description: "Search for available domain names and get pricing.",
1195
+ category: "runtime",
1196
+ parameters: {
1197
+ type: "object",
1198
+ properties: {
1199
+ query: {
1200
+ type: "string",
1201
+ description: "Domain name or keyword to search (e.g., 'mysite' or 'mysite.com')",
1202
+ },
1203
+ tlds: {
1204
+ type: "string",
1205
+ description: "Comma-separated TLDs to check (e.g., 'com,io,ai'). Default: com,io,ai,xyz,net,org,dev",
1206
+ },
1207
+ },
1208
+ required: ["query"],
1209
+ },
1210
+ execute: async (args, ctx) => {
1211
+ const results = await ctx.runtime.searchDomains(args.query, args.tlds);
1212
+ if (results.length === 0)
1213
+ return "No results found.";
1214
+ return results
1215
+ .map((d) => `${d.domain}: ${d.available ? "AVAILABLE" : "taken"}${d.registrationPrice != null ? ` ($${(d.registrationPrice / 100).toFixed(2)}/yr)` : ""}`)
1216
+ .join("\n");
1217
+ },
1218
+ },
1219
+ {
1220
+ name: "register_domain",
1221
+ description: "Register a domain name. Costs USDC via x402 payment. Check availability first with search_domains.",
1222
+ category: "runtime",
1223
+ dangerous: true,
1224
+ parameters: {
1225
+ type: "object",
1226
+ properties: {
1227
+ domain: {
1228
+ type: "string",
1229
+ description: "Full domain to register (e.g., 'mysite.com')",
1230
+ },
1231
+ years: {
1232
+ type: "number",
1233
+ description: "Registration period in years (default: 1)",
1234
+ },
1235
+ },
1236
+ required: ["domain"],
1237
+ },
1238
+ execute: async (args, ctx) => {
1239
+ const reg = await ctx.runtime.registerDomain(args.domain, args.years || 1);
1240
+ return `Domain registered: ${reg.domain} (status: ${reg.status}${reg.expiresAt ? `, expires: ${reg.expiresAt}` : ""}${reg.transactionId ? `, tx: ${reg.transactionId}` : ""})`;
1241
+ },
1242
+ },
1243
+ {
1244
+ name: "manage_dns",
1245
+ description: "Manage DNS records for a domain you own. Actions: list, add, delete.",
1246
+ category: "runtime",
1247
+ parameters: {
1248
+ type: "object",
1249
+ properties: {
1250
+ action: {
1251
+ type: "string",
1252
+ description: "list, add, or delete",
1253
+ },
1254
+ domain: {
1255
+ type: "string",
1256
+ description: "Domain name (e.g., 'mysite.com')",
1257
+ },
1258
+ type: {
1259
+ type: "string",
1260
+ description: "Record type for add: A, AAAA, CNAME, MX, TXT, etc.",
1261
+ },
1262
+ host: {
1263
+ type: "string",
1264
+ description: "Record host for add (e.g., '@' for root, 'www')",
1265
+ },
1266
+ value: {
1267
+ type: "string",
1268
+ description: "Record value for add (e.g., IP address, target domain)",
1269
+ },
1270
+ ttl: {
1271
+ type: "number",
1272
+ description: "TTL in seconds for add (default: 3600)",
1273
+ },
1274
+ record_id: {
1275
+ type: "string",
1276
+ description: "Record ID for delete",
1277
+ },
1278
+ },
1279
+ required: ["action", "domain"],
1280
+ },
1281
+ execute: async (args, ctx) => {
1282
+ const action = args.action;
1283
+ const domain = args.domain;
1284
+ if (action === "list") {
1285
+ const records = await ctx.runtime.listDnsRecords(domain);
1286
+ if (records.length === 0)
1287
+ return `No DNS records found for ${domain}.`;
1288
+ return records
1289
+ .map((r) => `[${r.id}] ${r.type} ${r.host} -> ${r.value} (TTL: ${r.ttl || "default"})`)
1290
+ .join("\n");
1291
+ }
1292
+ if (action === "add") {
1293
+ const type = args.type;
1294
+ const host = args.host;
1295
+ const value = args.value;
1296
+ if (!type || !host || !value) {
1297
+ return "Required for add: type, host, value";
1298
+ }
1299
+ const record = await ctx.runtime.addDnsRecord(domain, type, host, value, args.ttl);
1300
+ return `DNS record added: [${record.id}] ${record.type} ${record.host} -> ${record.value}`;
1301
+ }
1302
+ if (action === "delete") {
1303
+ const recordId = args.record_id;
1304
+ if (!recordId)
1305
+ return "Required for delete: record_id";
1306
+ await ctx.runtime.deleteDnsRecord(domain, recordId);
1307
+ return `DNS record ${recordId} deleted from ${domain}`;
1308
+ }
1309
+ return `Unknown action: ${action}. Use list, add, or delete.`;
1310
+ },
1311
+ },
1312
+ // ── x402 Payment Tool ──
1313
+ {
1314
+ name: "x402_fetch",
1315
+ description: "Fetch a URL with automatic x402 USDC payment. If the server responds with HTTP 402, signs a USDC payment and retries. Use this to access paid APIs and services.",
1316
+ category: "financial",
1317
+ parameters: {
1318
+ type: "object",
1319
+ properties: {
1320
+ url: {
1321
+ type: "string",
1322
+ description: "The URL to fetch",
1323
+ },
1324
+ method: {
1325
+ type: "string",
1326
+ description: "HTTP method (default: GET)",
1327
+ },
1328
+ body: {
1329
+ type: "string",
1330
+ description: "Request body for POST/PUT (JSON string)",
1331
+ },
1332
+ headers: {
1333
+ type: "string",
1334
+ description: "Additional headers as JSON string",
1335
+ },
1336
+ },
1337
+ required: ["url"],
1338
+ },
1339
+ execute: async (args, ctx) => {
1340
+ const { x402Fetch } = await import("../clawd/x402.js");
1341
+ const url = args.url;
1342
+ const method = args.method || "GET";
1343
+ const body = args.body;
1344
+ const extraHeaders = args.headers
1345
+ ? JSON.parse(args.headers)
1346
+ : undefined;
1347
+ const result = await x402Fetch(url, ctx.identity.account, method, body, extraHeaders);
1348
+ if (!result.success) {
1349
+ return `x402 fetch failed: ${result.error || "Unknown error"}`;
1350
+ }
1351
+ const responseStr = typeof result.response === "string"
1352
+ ? result.response
1353
+ : JSON.stringify(result.response, null, 2);
1354
+ // Truncate very large responses
1355
+ if (responseStr.length > 10000) {
1356
+ return `x402 fetch succeeded (truncated):\n${responseStr.slice(0, 10000)}...`;
1357
+ }
1358
+ return `x402 fetch succeeded:\n${responseStr}`;
1359
+ },
1360
+ },
1361
+ ];
1362
+ }
1363
+ /**
1364
+ * Convert AutomatonTool list to OpenAI-compatible tool definitions.
1365
+ */
1366
+ export function toolsToInferenceFormat(tools) {
1367
+ return tools.map((t) => ({
1368
+ type: "function",
1369
+ function: {
1370
+ name: t.name,
1371
+ description: t.description,
1372
+ parameters: t.parameters,
1373
+ },
1374
+ }));
1375
+ }
1376
+ /**
1377
+ * Execute a tool call and return the result.
1378
+ */
1379
+ export async function executeTool(toolName, args, tools, context) {
1380
+ const tool = tools.find((t) => t.name === toolName);
1381
+ const startTime = Date.now();
1382
+ if (!tool) {
1383
+ return {
1384
+ id: `tc_${Date.now()}`,
1385
+ name: toolName,
1386
+ arguments: args,
1387
+ result: "",
1388
+ durationMs: 0,
1389
+ error: `Unknown tool: ${toolName}`,
1390
+ };
1391
+ }
1392
+ try {
1393
+ const result = await tool.execute(args, context);
1394
+ return {
1395
+ id: `tc_${Date.now()}`,
1396
+ name: toolName,
1397
+ arguments: args,
1398
+ result,
1399
+ durationMs: Date.now() - startTime,
1400
+ };
1401
+ }
1402
+ catch (err) {
1403
+ return {
1404
+ id: `tc_${Date.now()}`,
1405
+ name: toolName,
1406
+ arguments: args,
1407
+ result: "",
1408
+ durationMs: Date.now() - startTime,
1409
+ error: err.message || String(err),
1410
+ };
1411
+ }
1412
+ }
1413
+ //# sourceMappingURL=tools.js.map