@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.
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +720 -0
- package/dist/index.js.map +1 -0
- package/dist/src/agent-card.d.ts +4 -0
- package/dist/src/agent-card.d.ts.map +1 -0
- package/dist/src/agent-card.js +61 -0
- package/dist/src/agent-card.js.map +1 -0
- package/dist/src/audit.d.ts +36 -0
- package/dist/src/audit.d.ts.map +1 -0
- package/dist/src/audit.js +88 -0
- package/dist/src/audit.js.map +1 -0
- package/dist/src/client.d.ts +53 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +322 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/executor.d.ts +34 -0
- package/dist/src/executor.d.ts.map +1 -0
- package/dist/src/executor.js +994 -0
- package/dist/src/executor.js.map +1 -0
- package/dist/src/file-security.d.ts +63 -0
- package/dist/src/file-security.d.ts.map +1 -0
- package/dist/src/file-security.js +350 -0
- package/dist/src/file-security.js.map +1 -0
- package/dist/src/hub-match.d.ts +73 -0
- package/dist/src/hub-match.d.ts.map +1 -0
- package/dist/src/hub-match.js +120 -0
- package/dist/src/hub-match.js.map +1 -0
- package/dist/src/hub-registration.d.ts +24 -0
- package/dist/src/hub-registration.d.ts.map +1 -0
- package/dist/src/hub-registration.js +242 -0
- package/dist/src/hub-registration.js.map +1 -0
- package/dist/src/internal/envelope.d.ts +33 -0
- package/dist/src/internal/envelope.d.ts.map +1 -0
- package/dist/src/internal/envelope.js +152 -0
- package/dist/src/internal/envelope.js.map +1 -0
- package/dist/src/internal/idempotency.d.ts +48 -0
- package/dist/src/internal/idempotency.d.ts.map +1 -0
- package/dist/src/internal/idempotency.js +82 -0
- package/dist/src/internal/idempotency.js.map +1 -0
- package/dist/src/internal/metrics.d.ts +38 -0
- package/dist/src/internal/metrics.d.ts.map +1 -0
- package/dist/src/internal/metrics.js +83 -0
- package/dist/src/internal/metrics.js.map +1 -0
- package/dist/src/internal/outbox.d.ts +49 -0
- package/dist/src/internal/outbox.d.ts.map +1 -0
- package/dist/src/internal/outbox.js +149 -0
- package/dist/src/internal/outbox.js.map +1 -0
- package/dist/src/internal/routing.d.ts +28 -0
- package/dist/src/internal/routing.d.ts.map +1 -0
- package/dist/src/internal/routing.js +57 -0
- package/dist/src/internal/routing.js.map +1 -0
- package/dist/src/internal/security.d.ts +53 -0
- package/dist/src/internal/security.d.ts.map +1 -0
- package/dist/src/internal/security.js +122 -0
- package/dist/src/internal/security.js.map +1 -0
- package/dist/src/internal/transport.d.ts +49 -0
- package/dist/src/internal/transport.d.ts.map +1 -0
- package/dist/src/internal/transport.js +207 -0
- package/dist/src/internal/transport.js.map +1 -0
- package/dist/src/internal/types-internal.d.ts +95 -0
- package/dist/src/internal/types-internal.d.ts.map +1 -0
- package/dist/src/internal/types-internal.js +9 -0
- package/dist/src/internal/types-internal.js.map +1 -0
- package/dist/src/peer-health.d.ts +47 -0
- package/dist/src/peer-health.d.ts.map +1 -0
- package/dist/src/peer-health.js +169 -0
- package/dist/src/peer-health.js.map +1 -0
- package/dist/src/peer-retry.d.ts +16 -0
- package/dist/src/peer-retry.d.ts.map +1 -0
- package/dist/src/peer-retry.js +75 -0
- package/dist/src/peer-retry.js.map +1 -0
- package/dist/src/queueing-executor.d.ts +23 -0
- package/dist/src/queueing-executor.d.ts.map +1 -0
- package/dist/src/queueing-executor.js +179 -0
- package/dist/src/queueing-executor.js.map +1 -0
- package/dist/src/routing-rules.d.ts +53 -0
- package/dist/src/routing-rules.d.ts.map +1 -0
- package/dist/src/routing-rules.js +130 -0
- package/dist/src/routing-rules.js.map +1 -0
- package/dist/src/task-cleanup.d.ts +21 -0
- package/dist/src/task-cleanup.d.ts.map +1 -0
- package/dist/src/task-cleanup.js +77 -0
- package/dist/src/task-cleanup.js.map +1 -0
- package/dist/src/task-store.d.ts +16 -0
- package/dist/src/task-store.d.ts.map +1 -0
- package/dist/src/task-store.js +80 -0
- package/dist/src/task-store.js.map +1 -0
- package/dist/src/telemetry.d.ts +88 -0
- package/dist/src/telemetry.d.ts.map +1 -0
- package/dist/src/telemetry.js +235 -0
- package/dist/src/telemetry.js.map +1 -0
- package/dist/src/transport-fallback.d.ts +29 -0
- package/dist/src/transport-fallback.d.ts.map +1 -0
- package/dist/src/transport-fallback.js +81 -0
- package/dist/src/transport-fallback.js.map +1 -0
- package/dist/src/types.d.ts +160 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +7 -0
- package/dist/src/types.js.map +1 -0
- package/openclaw.plugin.json +272 -0
- package/package.json +56 -0
- package/skill/SKILL.md +230 -0
- package/skill/references/tools-md-template.md +57 -0
- 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
|
+
});
|