@reefclaw/connect 0.1.15 → 0.1.16

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.
@@ -280,7 +280,15 @@ export class GatewayWsClient {
280
280
  mode: 'backend',
281
281
  },
282
282
  role: 'operator',
283
- scopes: ['operator.read', 'operator.write'],
283
+ // operator.admin: cron management (the boot-time heartbeat-cron ensure
284
+ // calls cron.list/cron.add) moved behind the admin scope on OpenClaw
285
+ // 2026.7.x — observed live on the first real npx onboarding
286
+ // ("Heartbeat cron: skipped (missing scope: operator.admin)",
287
+ // 2026-07-22, openclaw 2026.7.1-2). The gateway is localhost-bound and
288
+ // this connection already holds operator.write (orders, chat), so the
289
+ // marginal grant is small; without it fresh 2026.7.x installs never
290
+ // get a heartbeat cron.
291
+ scopes: ['operator.read', 'operator.write', 'operator.admin'],
284
292
  auth: {
285
293
  token: this.gatewayToken,
286
294
  ...(this.deviceToken && { deviceToken: this.deviceToken }),
@@ -58,20 +58,35 @@ export async function ensureHeartbeatCron(opts) {
58
58
  log(`Heartbeat cron: already exists (${String(existing.name)})`);
59
59
  return 'found_existing';
60
60
  }
61
- // Parity with the params the CLI built for the old setup command
61
+ // First attempt mirrors the params the CLI built for the old setup command
62
62
  // (`openclaw cron add --name reefclaw-heartbeat --every 15m --session isolated --message ...`):
63
- // isolated agentTurn defaults to delivery announce on the 'last' channel.
64
- await rpc.sendRpc('cron.add', {
63
+ // isolated agentTurn defaults to delivery announce on the 'last' channel
64
+ // right for boxes with a chat channel (the heartbeat's summary reaches the
65
+ // operator). On a FRESH box with no channels configured, cron.add REJECTS
66
+ // announce/'last' (no resolvable recipient — observed live on a first
67
+ // real npx onboarding, 2026-07-22; the old setup-time CLI path failed the
68
+ // same way, silently). Fall back to delivery mode 'none': a heartbeat that
69
+ // runs without announcing beats no heartbeat at all, and the operator sees
70
+ // the results on the dashboard anyway.
71
+ const baseJob = {
65
72
  name: HEARTBEAT_CRON_NAME,
66
73
  enabled: true,
67
74
  schedule: { kind: 'every', everyMs: HEARTBEAT_EVERY_MS },
68
75
  sessionTarget: 'isolated',
69
76
  wakeMode: 'now',
70
77
  payload: { kind: 'agentTurn', message: HEARTBEAT_MESSAGE },
71
- delivery: { mode: 'announce', channel: 'last' },
72
- });
73
- writeMarker(markerPath, { ensuredAt: new Date().toISOString(), created: HEARTBEAT_CRON_NAME });
74
- log(`Heartbeat cron: created (${HEARTBEAT_CRON_NAME}, every 15m)`);
78
+ };
79
+ let delivery = 'announce';
80
+ try {
81
+ await rpc.sendRpc('cron.add', { ...baseJob, delivery: { mode: 'announce', channel: 'last' } });
82
+ }
83
+ catch (addErr) {
84
+ log(`Heartbeat cron: announce delivery rejected (${addErr instanceof Error ? addErr.message.split('\n')[0] : String(addErr)}) — retrying without delivery`);
85
+ await rpc.sendRpc('cron.add', { ...baseJob, delivery: { mode: 'none' } });
86
+ delivery = 'none';
87
+ }
88
+ writeMarker(markerPath, { ensuredAt: new Date().toISOString(), created: HEARTBEAT_CRON_NAME, delivery });
89
+ log(`Heartbeat cron: created (${HEARTBEAT_CRON_NAME}, every 15m, delivery=${delivery})`);
75
90
  return 'created';
76
91
  }
77
92
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reefclaw/connect",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "One-command installer that connects your OpenClaw agent to ReefClaw (paper trading, no exchange keys).",
5
5
  "type": "module",
6
6
  "bin": {