clawmoney 0.17.23 → 0.17.25

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.
@@ -1,11 +1,19 @@
1
- import type { ProviderConfig, ServiceCallEvent, TestCallEvent, EscrowTaskEvent, DeliverEvent, TestResponseEvent } from "./types.js";
2
- type SendFn = (event: DeliverEvent | TestResponseEvent) => boolean;
1
+ import type { ProviderConfig, ServiceCallEvent, TestCallEvent, EscrowTaskEvent, DeliverEvent, TestResponseEvent, ProgressEvent } from "./types.js";
2
+ type SendFn = (event: DeliverEvent | TestResponseEvent | ProgressEvent) => boolean;
3
3
  export declare class Executor {
4
4
  private config;
5
5
  private send;
6
6
  private activeTasks;
7
7
  constructor(config: ProviderConfig, send: SendFn);
8
8
  get activeCount(): number;
9
+ /**
10
+ * Fire a progress update for an in-flight order. The buyer-facing UI
11
+ * (clawmoney-web playground) polls /market/orders/{id} and reads the
12
+ * `progress` field that backend Redis-caches from these events.
13
+ * Best-effort: failed sends are non-fatal (the UI just sees a slightly
14
+ * stale stage label until the next progress fires).
15
+ */
16
+ private sendProgress;
9
17
  handleServiceCall(call: ServiceCallEvent): void;
10
18
  handleEscrowTask(task: EscrowTaskEvent): void;
11
19
  private executeEscrowTask;
@@ -201,7 +201,7 @@ async function waitForStableSize(filePath) {
201
201
  }
202
202
  }
203
203
  // ── CLI execution (openclaw agent / claude -p) ──
204
- function runCli(command, prompt, timeoutMs, orderId, watchForImage) {
204
+ function runCli(command, prompt, timeoutMs, orderId, watchForImage, onProgress) {
205
205
  return new Promise((resolve) => {
206
206
  // Build args based on command
207
207
  let args;
@@ -290,6 +290,10 @@ function runCli(command, prompt, timeoutMs, orderId, watchForImage) {
290
290
  resolve({ stdout, stderr, exitCode: 0 });
291
291
  };
292
292
  if (dirBaseline) {
293
+ // Report once when codex actually starts working — the spawn ack
294
+ // is fast but the first reasoning step can take ~10s and we want
295
+ // the UI to know "we're actually running, not just queued".
296
+ onProgress?.("Generating image…");
293
297
  pollHandle = setInterval(() => {
294
298
  if (earlyResolved)
295
299
  return;
@@ -299,6 +303,7 @@ function runCli(command, prompt, timeoutMs, orderId, watchForImage) {
299
303
  clearInterval(pollHandle);
300
304
  pollHandle = null;
301
305
  }
306
+ onProgress?.("Image generated, finalizing…");
302
307
  void finishWithEarlyImage(newImage);
303
308
  }
304
309
  }, POLL_INTERVAL_MS);
@@ -456,6 +461,22 @@ export class Executor {
456
461
  get activeCount() {
457
462
  return this.activeTasks.size;
458
463
  }
464
+ /**
465
+ * Fire a progress update for an in-flight order. The buyer-facing UI
466
+ * (clawmoney-web playground) polls /market/orders/{id} and reads the
467
+ * `progress` field that backend Redis-caches from these events.
468
+ * Best-effort: failed sends are non-fatal (the UI just sees a slightly
469
+ * stale stage label until the next progress fires).
470
+ */
471
+ sendProgress(orderId, text) {
472
+ try {
473
+ this.send({ event: "progress", order_id: orderId, progress: text });
474
+ }
475
+ catch {
476
+ // Ignore — WS may be reconnecting; the next progress event will
477
+ // pick up where we left off.
478
+ }
479
+ }
459
480
  handleServiceCall(call) {
460
481
  if (isProcessed(call.order_id)) {
461
482
  logger.info(`Skipping duplicate order: ${call.order_id}`);
@@ -650,11 +671,15 @@ export class Executor {
650
671
  const timeoutS = Math.max(call.timeout - TIMEOUT_BUFFER_S, 30);
651
672
  const command = this.config.provider.cli_command;
652
673
  logger.info(`Executing: ${command} for skill="${call.skill}" order=${call.order_id} (timeout=${timeoutS}s)`);
674
+ this.sendProgress(call.order_id, `Spawning ${command}…`);
653
675
  const { stdout, stderr, exitCode } = await runCli(command, prompt, timeoutS * 1000, call.order_id,
654
676
  // Watch the codex image dir for generation/image so we can
655
677
  // ship the moment the file lands, skipping codex's final
656
678
  // reasoning tail (~60–90s of pure overhead).
657
- call.category?.startsWith("generation/image"));
679
+ call.category?.startsWith("generation/image"),
680
+ // Progress callback fired inside runCli when we cross
681
+ // observable milestones (image landing on disk, etc).
682
+ (text) => this.sendProgress(call.order_id, text));
658
683
  if (exitCode !== 0) {
659
684
  const errMsg = stderr.trim() || `CLI exited with code ${exitCode}`;
660
685
  logger.error(`CLI failed (code=${exitCode}):`, errMsg);
@@ -701,6 +726,9 @@ export class Executor {
701
726
  }
702
727
  }
703
728
  // Upload local files to R2
729
+ if (allFiles.length > 0) {
730
+ this.sendProgress(call.order_id, "Uploading to CDN…");
731
+ }
704
732
  for (const filePath of allFiles) {
705
733
  const cdnUrl = await uploadFile(filePath, this.config);
706
734
  if (cdnUrl) {
@@ -56,7 +56,20 @@ export interface TestResponseEvent {
56
56
  order_id: string;
57
57
  output: Record<string, unknown>;
58
58
  }
59
- export type OutgoingEvent = DeliverEvent | TestResponseEvent;
59
+ /**
60
+ * Live in-flight progress reported back to backend during a service_call.
61
+ * Backend stashes the text at hub:progress:<order_id> in Redis with a
62
+ * 10-minute TTL; clawmoney-web's playground poll picks it up via the
63
+ * HubOrderPublic.progress field. Fire-and-forget — backend doesn't
64
+ * ack, so dropped progress messages just mean the UI sees a slightly
65
+ * stale stage label.
66
+ */
67
+ export interface ProgressEvent {
68
+ event: "progress";
69
+ order_id: string;
70
+ progress: string;
71
+ }
72
+ export type OutgoingEvent = DeliverEvent | TestResponseEvent | ProgressEvent;
60
73
  export interface ProviderSettings {
61
74
  cli_command: string;
62
75
  max_concurrent: number;
@@ -102,7 +102,21 @@ export class WsClient {
102
102
  this.stopHeartbeat();
103
103
  this.heartbeatTimer = setInterval(() => {
104
104
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
105
- this.ws.ping();
105
+ // Application-level heartbeat. Backend's `event == "heartbeat"`
106
+ // handler is what updates ClawAgent.last_heartbeat_at, which in
107
+ // turn drives the `agent_is_online` field on /market/skills/search
108
+ // results. A WS protocol-level ping/pong (this.ws.ping()) keeps
109
+ // the TCP connection alive but the backend never sees it as a
110
+ // JSON message — so DB heartbeat would only ever get bumped at
111
+ // initial connect, then go stale after 600s and the agent would
112
+ // disappear from search results despite the daemon being fine.
113
+ try {
114
+ this.ws.send(JSON.stringify({ event: "heartbeat" }));
115
+ }
116
+ catch {
117
+ // Send failure is non-fatal; the reconnect loop will pick up
118
+ // a real socket close if the connection is actually dead.
119
+ }
106
120
  }
107
121
  }, HEARTBEAT_INTERVAL_MS);
108
122
  this.heartbeatTimer.unref();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.17.23",
3
+ "version": "0.17.25",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {