@clawcrony/claw-crony 1.0.1

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 (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/index.d.ts +17 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +720 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/agent-card.d.ts +4 -0
  8. package/dist/src/agent-card.d.ts.map +1 -0
  9. package/dist/src/agent-card.js +61 -0
  10. package/dist/src/agent-card.js.map +1 -0
  11. package/dist/src/audit.d.ts +36 -0
  12. package/dist/src/audit.d.ts.map +1 -0
  13. package/dist/src/audit.js +88 -0
  14. package/dist/src/audit.js.map +1 -0
  15. package/dist/src/client.d.ts +53 -0
  16. package/dist/src/client.d.ts.map +1 -0
  17. package/dist/src/client.js +322 -0
  18. package/dist/src/client.js.map +1 -0
  19. package/dist/src/executor.d.ts +34 -0
  20. package/dist/src/executor.d.ts.map +1 -0
  21. package/dist/src/executor.js +994 -0
  22. package/dist/src/executor.js.map +1 -0
  23. package/dist/src/file-security.d.ts +63 -0
  24. package/dist/src/file-security.d.ts.map +1 -0
  25. package/dist/src/file-security.js +350 -0
  26. package/dist/src/file-security.js.map +1 -0
  27. package/dist/src/hub-match.d.ts +73 -0
  28. package/dist/src/hub-match.d.ts.map +1 -0
  29. package/dist/src/hub-match.js +120 -0
  30. package/dist/src/hub-match.js.map +1 -0
  31. package/dist/src/hub-registration.d.ts +24 -0
  32. package/dist/src/hub-registration.d.ts.map +1 -0
  33. package/dist/src/hub-registration.js +242 -0
  34. package/dist/src/hub-registration.js.map +1 -0
  35. package/dist/src/internal/envelope.d.ts +33 -0
  36. package/dist/src/internal/envelope.d.ts.map +1 -0
  37. package/dist/src/internal/envelope.js +152 -0
  38. package/dist/src/internal/envelope.js.map +1 -0
  39. package/dist/src/internal/idempotency.d.ts +48 -0
  40. package/dist/src/internal/idempotency.d.ts.map +1 -0
  41. package/dist/src/internal/idempotency.js +82 -0
  42. package/dist/src/internal/idempotency.js.map +1 -0
  43. package/dist/src/internal/metrics.d.ts +38 -0
  44. package/dist/src/internal/metrics.d.ts.map +1 -0
  45. package/dist/src/internal/metrics.js +83 -0
  46. package/dist/src/internal/metrics.js.map +1 -0
  47. package/dist/src/internal/outbox.d.ts +49 -0
  48. package/dist/src/internal/outbox.d.ts.map +1 -0
  49. package/dist/src/internal/outbox.js +149 -0
  50. package/dist/src/internal/outbox.js.map +1 -0
  51. package/dist/src/internal/routing.d.ts +28 -0
  52. package/dist/src/internal/routing.d.ts.map +1 -0
  53. package/dist/src/internal/routing.js +57 -0
  54. package/dist/src/internal/routing.js.map +1 -0
  55. package/dist/src/internal/security.d.ts +53 -0
  56. package/dist/src/internal/security.d.ts.map +1 -0
  57. package/dist/src/internal/security.js +122 -0
  58. package/dist/src/internal/security.js.map +1 -0
  59. package/dist/src/internal/transport.d.ts +49 -0
  60. package/dist/src/internal/transport.d.ts.map +1 -0
  61. package/dist/src/internal/transport.js +207 -0
  62. package/dist/src/internal/transport.js.map +1 -0
  63. package/dist/src/internal/types-internal.d.ts +95 -0
  64. package/dist/src/internal/types-internal.d.ts.map +1 -0
  65. package/dist/src/internal/types-internal.js +9 -0
  66. package/dist/src/internal/types-internal.js.map +1 -0
  67. package/dist/src/peer-health.d.ts +47 -0
  68. package/dist/src/peer-health.d.ts.map +1 -0
  69. package/dist/src/peer-health.js +169 -0
  70. package/dist/src/peer-health.js.map +1 -0
  71. package/dist/src/peer-retry.d.ts +16 -0
  72. package/dist/src/peer-retry.d.ts.map +1 -0
  73. package/dist/src/peer-retry.js +75 -0
  74. package/dist/src/peer-retry.js.map +1 -0
  75. package/dist/src/queueing-executor.d.ts +23 -0
  76. package/dist/src/queueing-executor.d.ts.map +1 -0
  77. package/dist/src/queueing-executor.js +179 -0
  78. package/dist/src/queueing-executor.js.map +1 -0
  79. package/dist/src/routing-rules.d.ts +53 -0
  80. package/dist/src/routing-rules.d.ts.map +1 -0
  81. package/dist/src/routing-rules.js +130 -0
  82. package/dist/src/routing-rules.js.map +1 -0
  83. package/dist/src/task-cleanup.d.ts +21 -0
  84. package/dist/src/task-cleanup.d.ts.map +1 -0
  85. package/dist/src/task-cleanup.js +77 -0
  86. package/dist/src/task-cleanup.js.map +1 -0
  87. package/dist/src/task-store.d.ts +16 -0
  88. package/dist/src/task-store.d.ts.map +1 -0
  89. package/dist/src/task-store.js +80 -0
  90. package/dist/src/task-store.js.map +1 -0
  91. package/dist/src/telemetry.d.ts +88 -0
  92. package/dist/src/telemetry.d.ts.map +1 -0
  93. package/dist/src/telemetry.js +235 -0
  94. package/dist/src/telemetry.js.map +1 -0
  95. package/dist/src/transport-fallback.d.ts +29 -0
  96. package/dist/src/transport-fallback.d.ts.map +1 -0
  97. package/dist/src/transport-fallback.js +81 -0
  98. package/dist/src/transport-fallback.js.map +1 -0
  99. package/dist/src/types.d.ts +160 -0
  100. package/dist/src/types.d.ts.map +1 -0
  101. package/dist/src/types.js +7 -0
  102. package/dist/src/types.js.map +1 -0
  103. package/openclaw.plugin.json +272 -0
  104. package/package.json +56 -0
  105. package/skill/SKILL.md +230 -0
  106. package/skill/references/tools-md-template.md +57 -0
  107. package/skill/scripts/a2a-send.mjs +357 -0
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Send a message to an A2A peer using the official @a2a-js/sdk.
4
+ *
5
+ * Usage:
6
+ * node a2a-send.mjs --peer-url <PEER_BASE_URL> --token <TOKEN> --message "Hello!"
7
+ * node a2a-send.mjs --peer-url http://100.76.43.74:18800 --token abc123 --message "What is your name?"
8
+ * node a2a-send.mjs --peer-url <URL> --token <TOKEN> --message "Follow up" --task-id <TASK_ID> --context-id <CONTEXT_ID>
9
+ *
10
+ * Async task mode (recommended for long-running prompts):
11
+ * node a2a-send.mjs --peer-url <URL> --token <TOKEN> --non-blocking --wait --message "..."
12
+ *
13
+ * Options:
14
+ * --peer-url <url> Peer base URL, e.g. http://100.76.43.74:18800
15
+ * --token <token> Bearer token for the peer inbound auth
16
+ * --message <text> Text to send
17
+ * --task-id <id> Reuse an existing A2A task for follow-up turns
18
+ * --context-id <id> Reuse an existing A2A context for multi-round conversation routing
19
+ * --non-blocking Send with configuration.blocking=false (returns quickly with a Task)
20
+ * --wait When non-blocking, poll tasks/get until terminal state
21
+ * --timeout-ms <ms> Max wait time for --wait (default: 600000)
22
+ * --poll-ms <ms> Poll interval for --wait (default: 1000)
23
+ * --help Show this help text
24
+ *
25
+ * Optional (OpenClaw extension):
26
+ * --agent-id <agentId> Route the inbound A2A request to a specific OpenClaw agentId on the peer.
27
+ * Note: this works reliably over JSON-RPC/REST. gRPC transport may drop unknown
28
+ * Message fields, so gRPC is disabled when --agent-id is used.
29
+ *
30
+ * Requires: npm install @a2a-js/sdk
31
+ */
32
+
33
+ import {
34
+ ClientFactory,
35
+ ClientFactoryOptions,
36
+ DefaultAgentCardResolver,
37
+ JsonRpcTransportFactory,
38
+ RestTransportFactory,
39
+ createAuthenticatingFetchWithRetry,
40
+ } from "@a2a-js/sdk/client";
41
+ import { GrpcTransportFactory } from "@a2a-js/sdk/client/grpc";
42
+ import { randomUUID } from "node:crypto";
43
+ import { readFileSync, statSync } from "node:fs";
44
+ import { extname } from "node:path";
45
+
46
+ const USAGE = `Usage: node a2a-send.mjs --peer-url <URL> --token <TOKEN> --message <TEXT> [--file-uri <url>] [--file-path <localpath>] [--task-id <id>] [--context-id <id>] [--non-blocking] [--wait] [--stream] [--timeout-ms <ms>] [--poll-ms <ms>] [--agent-id <openclaw-agent-id>] [--help]`;
47
+
48
+ const MAX_INLINE_FILE_SIZE = 10 * 1024 * 1024; // 10MB
49
+
50
+ const CLI_MIME_MAP = {
51
+ ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
52
+ ".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml",
53
+ ".pdf": "application/pdf", ".txt": "text/plain", ".csv": "text/csv",
54
+ ".json": "application/json", ".mp3": "audio/mpeg", ".wav": "audio/wav",
55
+ ".mp4": "video/mp4", ".webm": "video/webm", ".zip": "application/zip",
56
+ };
57
+
58
+ const CLI_ALLOWED_MIME_PATTERNS = [
59
+ "image/*", "application/pdf", "text/plain", "text/csv",
60
+ "application/json", "audio/*", "video/*",
61
+ ];
62
+
63
+ function detectMimeFromPath(filePath) {
64
+ const ext = extname(filePath).toLowerCase();
65
+ return CLI_MIME_MAP[ext] || "application/octet-stream";
66
+ }
67
+
68
+ function isMimeAllowed(mimeType) {
69
+ const normalized = mimeType.toLowerCase();
70
+ for (const pattern of CLI_ALLOWED_MIME_PATTERNS) {
71
+ if (normalized === pattern) return true;
72
+ if (pattern.endsWith("/*")) {
73
+ const prefix = pattern.slice(0, -1);
74
+ if (normalized.startsWith(prefix)) return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+
80
+ function usageAndExit(code = 1) {
81
+ const stream = code === 0 ? console.log : console.error;
82
+ stream(USAGE);
83
+ process.exit(code);
84
+ }
85
+
86
+ function parseArgs() {
87
+ const args = process.argv.slice(2);
88
+ const opts = {};
89
+
90
+ for (let i = 0; i < args.length; i++) {
91
+ const arg = args[i];
92
+ if (!arg?.startsWith("--")) continue;
93
+
94
+ const key = arg.replace(/^--/, "");
95
+ const next = args[i + 1];
96
+
97
+ if (next && !next.startsWith("--")) {
98
+ opts[key] = next;
99
+ i++;
100
+ } else {
101
+ opts[key] = true;
102
+ }
103
+ }
104
+
105
+ if (opts.help || opts.h) {
106
+ usageAndExit(0);
107
+ }
108
+
109
+ const peerUrl = String(opts["peer-url"] || opts.peerUrl || "").trim();
110
+ const message = String(opts.message || "").trim();
111
+ const fileUri = String(opts["file-uri"] || opts.fileUri || "").trim();
112
+ const filePath = String(opts["file-path"] || opts.filePath || "").trim();
113
+
114
+ // At least one of --message, --file-uri, --file-path required
115
+ if (!peerUrl || (!message && !fileUri && !filePath)) {
116
+ usageAndExit(1);
117
+ }
118
+
119
+ return { ...opts, peerUrl, message, fileUri, filePath };
120
+ }
121
+
122
+ function sleep(ms) {
123
+ return new Promise((resolve) => setTimeout(resolve, ms));
124
+ }
125
+
126
+ function extractFirstTextParts(parts) {
127
+ if (!Array.isArray(parts)) return undefined;
128
+ for (const p of parts) {
129
+ if (p && typeof p === "object" && p.kind === "text" && typeof p.text === "string") {
130
+ return p.text;
131
+ }
132
+ }
133
+ return undefined;
134
+ }
135
+
136
+ async function main() {
137
+ const opts = parseArgs();
138
+
139
+ const peerUrl = opts.peerUrl;
140
+ const token = typeof opts.token === "string" ? opts.token : "";
141
+ const message = opts.message;
142
+ const targetAgentId = (opts["agent-id"] || opts.agentId || "").toString().trim();
143
+ const continuationTaskId = (opts["task-id"] || opts.taskId || "").toString().trim().slice(0, 256);
144
+ const continuationContextId = (opts["context-id"] || opts.contextId || "").toString().trim().slice(0, 256);
145
+
146
+ const nonBlocking = Boolean(opts["non-blocking"] || opts.nonBlocking);
147
+ const wait = Boolean(opts.wait);
148
+ const stream = Boolean(opts.stream);
149
+
150
+ const timeoutMsRaw = opts["timeout-ms"] || opts.timeoutMs;
151
+ const pollMsRaw = opts["poll-ms"] || opts.pollMs;
152
+
153
+ // Default wait timeout: 10 minutes. Long agent runs are common in multi-round discussions.
154
+ const timeoutMs = timeoutMsRaw ? Number(timeoutMsRaw) : 600_000;
155
+ const pollMs = pollMsRaw ? Number(pollMsRaw) : 1_000;
156
+
157
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
158
+ console.error("Invalid --timeout-ms");
159
+ usageAndExit(2);
160
+ }
161
+ if (!Number.isFinite(pollMs) || pollMs <= 0) {
162
+ console.error("Invalid --poll-ms");
163
+ usageAndExit(2);
164
+ }
165
+
166
+ // Build auth handler
167
+ const authHandler = token
168
+ ? {
169
+ headers: async () => ({ authorization: `Bearer ${token}` }),
170
+ shouldRetryWithHeaders: async () => undefined,
171
+ }
172
+ : undefined;
173
+
174
+ const authFetch = authHandler
175
+ ? createAuthenticatingFetchWithRetry(fetch, authHandler)
176
+ : fetch;
177
+
178
+ // If using OpenClaw extension agentId routing, disable gRPC transport to avoid
179
+ // protobuf dropping unknown message fields.
180
+ const transports = targetAgentId
181
+ ? [
182
+ new JsonRpcTransportFactory({ fetchImpl: authFetch }),
183
+ new RestTransportFactory({ fetchImpl: authFetch }),
184
+ ]
185
+ : [
186
+ new JsonRpcTransportFactory({ fetchImpl: authFetch }),
187
+ new RestTransportFactory({ fetchImpl: authFetch }),
188
+ new GrpcTransportFactory(),
189
+ ];
190
+
191
+ const factory = new ClientFactory(
192
+ ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
193
+ cardResolver: new DefaultAgentCardResolver({ fetchImpl: authFetch }),
194
+ transports,
195
+ })
196
+ );
197
+
198
+ // Discover agent card and create client
199
+ const client = await factory.createFromUrl(peerUrl);
200
+
201
+ // Build message parts: text + optional file
202
+ const outboundParts = [];
203
+ if (message) {
204
+ outboundParts.push({ kind: "text", text: message });
205
+ }
206
+
207
+ const fileUri = opts.fileUri;
208
+ const filePath = opts.filePath;
209
+
210
+ if (filePath) {
211
+ // Read local file, base64-encode, auto-detect MIME
212
+ const stat = statSync(filePath);
213
+ if (stat.size > MAX_INLINE_FILE_SIZE) {
214
+ console.error(`File too large: ${(stat.size / 1048576).toFixed(1)}MB exceeds 10MB limit`);
215
+ process.exit(2);
216
+ }
217
+ const mimeType = detectMimeFromPath(filePath);
218
+ if (!isMimeAllowed(mimeType)) {
219
+ console.error(`MIME type not allowed: ${mimeType}`);
220
+ process.exit(2);
221
+ }
222
+ const fileBuffer = readFileSync(filePath);
223
+ const base64 = fileBuffer.toString("base64");
224
+ const name = filePath.split("/").pop() || "file";
225
+ outboundParts.push({
226
+ kind: "file",
227
+ file: { bytes: base64, mimeType, name },
228
+ });
229
+ } else if (fileUri) {
230
+ // URI-based file reference
231
+ outboundParts.push({
232
+ kind: "file",
233
+ file: { uri: fileUri },
234
+ });
235
+ }
236
+
237
+ if (outboundParts.length === 0) {
238
+ console.error("No message content to send");
239
+ process.exit(2);
240
+ }
241
+
242
+ const outboundMessage = {
243
+ kind: "message",
244
+ messageId: randomUUID(),
245
+ role: "user",
246
+ parts: outboundParts,
247
+ ...(continuationTaskId ? { taskId: continuationTaskId } : {}),
248
+ ...(continuationContextId ? { contextId: continuationContextId } : {}),
249
+ ...(targetAgentId ? { agentId: targetAgentId } : {}),
250
+ };
251
+
252
+ const requestOptions = token ? { serviceParameters: { authorization: `Bearer ${token}` } } : undefined;
253
+
254
+ const sendParams = {
255
+ message: outboundMessage,
256
+ ...(nonBlocking ? { configuration: { blocking: false } } : {}),
257
+ };
258
+
259
+ // SSE streaming mode: subscribe to task event stream
260
+ if (stream) {
261
+ console.log("[stream] connecting...");
262
+ const eventStream = client.sendMessageStream(sendParams, requestOptions);
263
+ for await (const event of eventStream) {
264
+ const kind = event?.kind;
265
+ if (kind === "task") {
266
+ const state = event.status?.state;
267
+ const text = extractFirstTextParts(event.status?.message?.parts);
268
+ if (state === "working") {
269
+ console.log(`[stream] working... (${event.status?.timestamp || ""})`);
270
+ } else if (text) {
271
+ console.log(`[stream] ${state}: ${text}`);
272
+ } else {
273
+ console.log(`[stream] ${state}: ${JSON.stringify(event.status)}`);
274
+ }
275
+ } else if (kind === "status-update") {
276
+ const state = event.status?.state;
277
+ const text = extractFirstTextParts(event.status?.message?.parts);
278
+ console.log(`[stream] status-update: ${state}${text ? ` — ${text}` : ""}`);
279
+ } else {
280
+ console.log(`[stream] ${kind || "unknown"}: ${JSON.stringify(event)}`);
281
+ }
282
+ }
283
+ console.log("[stream] done");
284
+ return;
285
+ }
286
+
287
+ const result = await client.sendMessage(sendParams, requestOptions);
288
+
289
+ const printTaskHandle = (task) => {
290
+ if (!task || typeof task !== "object") return;
291
+ const responseTaskId = typeof task.id === "string" ? task.id : typeof task.taskId === "string" ? task.taskId : "";
292
+ if (!responseTaskId) return;
293
+ const responseContextId =
294
+ typeof task.contextId === "string"
295
+ ? task.contextId
296
+ : typeof continuationContextId === "string" && continuationContextId
297
+ ? continuationContextId
298
+ : "";
299
+ console.log(`[task] id=${responseTaskId} contextId=${responseContextId || "-"}`);
300
+ };
301
+
302
+ // If the user didn't request waiting, print the immediate response.
303
+ if (!nonBlocking || !wait) {
304
+ if (result?.kind === "message") {
305
+ const text = extractFirstTextParts(result.parts);
306
+ console.log(text || JSON.stringify(result, null, 2));
307
+ return;
308
+ }
309
+ if (result?.kind === "task") {
310
+ printTaskHandle(result);
311
+ const text = extractFirstTextParts(result.status?.message?.parts);
312
+ console.log(text || JSON.stringify(result, null, 2));
313
+ return;
314
+ }
315
+ console.log(JSON.stringify(result, null, 2));
316
+ return;
317
+ }
318
+
319
+ // Async task mode: wait for terminal task state via tasks/get.
320
+ const responseTaskId = result?.kind === "task" ? result.id : result?.taskId;
321
+ if (!responseTaskId || typeof responseTaskId !== "string") {
322
+ // Can't wait if we don't know the task id.
323
+ console.log(JSON.stringify(result, null, 2));
324
+ return;
325
+ }
326
+
327
+ if (result?.kind === "task") {
328
+ printTaskHandle(result);
329
+ }
330
+
331
+ const startedAt = Date.now();
332
+ const terminalStates = new Set(["completed", "failed", "canceled"]);
333
+
334
+ while (true) {
335
+ const task = await client.getTask({ id: responseTaskId, historyLength: 20 }, requestOptions);
336
+ const state = task?.status?.state;
337
+
338
+ if (state && terminalStates.has(state)) {
339
+ const text = extractFirstTextParts(task.status?.message?.parts);
340
+ console.log(text || JSON.stringify(task, null, 2));
341
+ return;
342
+ }
343
+
344
+ if (Date.now() - startedAt > timeoutMs) {
345
+ console.error(`Timeout waiting for task ${responseTaskId} after ${timeoutMs}ms`);
346
+ console.log(JSON.stringify(task, null, 2));
347
+ process.exit(3);
348
+ }
349
+
350
+ await sleep(pollMs);
351
+ }
352
+ }
353
+
354
+ main().catch((err) => {
355
+ console.error("Error:", err?.stack || err?.message || String(err));
356
+ process.exit(1);
357
+ });