@storacha/clawracha 0.1.2 → 0.1.4

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 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,qBAAqB,CAAC;AAiH7B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QAgjBpD"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EACV,iBAAiB,EAGlB,MAAM,qBAAqB,CAAC;AAgH7B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QA6jBpD"}
package/dist/plugin.js CHANGED
@@ -40,8 +40,7 @@ async function startWorkspaceSync(workspace, agentId, pluginConfig, logger) {
40
40
  if (!deviceConfig || !deviceConfig.setupComplete) {
41
41
  return null;
42
42
  }
43
- const storachaClient = await createStorachaClient(deviceConfig);
44
- const engine = new SyncEngine(storachaClient, workspace);
43
+ const engine = new SyncEngine(workspace);
45
44
  await engine.init(deviceConfig);
46
45
  const watcher = new FileWatcher({
47
46
  workspace,
@@ -241,6 +240,7 @@ export default function plugin(api) {
241
240
  .description("Set up a NEW workspace (first device). <delegation> is a file path or base64 CID string.")
242
241
  .requiredOption("--agent <id>", "Agent ID")
243
242
  .action(async (delegationArg, opts) => {
243
+ let engine = null;
244
244
  try {
245
245
  const { agentId, workspace } = requireAgent(opts.agent);
246
246
  const deviceConfig = await loadDeviceConfig(workspace);
@@ -263,8 +263,7 @@ export default function plugin(api) {
263
263
  deviceConfig.setupComplete = true;
264
264
  await saveDeviceConfig(workspace, deviceConfig);
265
265
  // Initial upload: scan all existing workspace files and sync to Storacha
266
- const storachaClient = await createStorachaClient(deviceConfig);
267
- const engine = new SyncEngine(storachaClient, workspace);
266
+ engine = new SyncEngine(workspace);
268
267
  await engine.init(deviceConfig);
269
268
  const userIgnored = await readIgnoreFile(workspace);
270
269
  const ignorePatterns = [
@@ -293,11 +292,19 @@ export default function plugin(api) {
293
292
  console.log(`🔥 Storacha workspace ready for ${agentId}!`);
294
293
  console.log(`Agent DID: ${agent.did()}`);
295
294
  console.log(`Space: ${spaceDID ?? "unknown"}`);
296
- console.log("\nRestart the gateway to start syncing: `openclaw gateway restart`");
297
295
  console.log(`\nTo add another device, run \`openclaw clawracha grant <their-DID> --agent ${agentId}\` here,`);
298
296
  console.log(`then \`openclaw clawracha join <upload> <name> --agent <id>\` on the other device.`);
297
+ console.log("\nSync is now active (no gateway restart needed).");
299
298
  }
300
299
  catch (err) {
300
+ if (engine) {
301
+ try {
302
+ const state = await engine.inspect();
303
+ console.error("\nEngine state at failure:");
304
+ console.error(JSON.stringify(state, null, 2));
305
+ }
306
+ catch { }
307
+ }
301
308
  console.error(`Error: ${err.message}`);
302
309
  process.exit(1);
303
310
  }
@@ -308,6 +315,7 @@ export default function plugin(api) {
308
315
  .description("Join an existing workspace from another device. Arguments are file paths or base64 CID strings.")
309
316
  .requiredOption("--agent <id>", "Agent ID")
310
317
  .action(async (uploadArg, nameArg, opts) => {
318
+ let engine = null;
311
319
  try {
312
320
  const { agentId, workspace } = requireAgent(opts.agent);
313
321
  const deviceConfig = await loadDeviceConfig(workspace);
@@ -335,8 +343,7 @@ export default function plugin(api) {
335
343
  await saveDeviceConfig(workspace, deviceConfig);
336
344
  // Pull remote state before watcher starts
337
345
  let pullCount = 0;
338
- const storachaClient = await createStorachaClient(deviceConfig);
339
- const engine = new SyncEngine(storachaClient, workspace);
346
+ engine = new SyncEngine(workspace);
340
347
  await engine.init(deviceConfig);
341
348
  pullCount = await engine.pullRemote();
342
349
  // Save name archive after pull
@@ -349,9 +356,17 @@ export default function plugin(api) {
349
356
  console.log(`Agent DID: ${agent.did()}`);
350
357
  console.log(`Space: ${spaceDID ?? "unknown"}`);
351
358
  console.log(`Pulled ${pullCount} files from remote.`);
352
- console.log("\nRestart the gateway to start syncing: `openclaw gateway restart`");
359
+ console.log("\nSync is now active (no gateway restart needed).");
353
360
  }
354
361
  catch (err) {
362
+ if (engine) {
363
+ try {
364
+ const state = await engine.inspect();
365
+ console.error("\nEngine state at failure:");
366
+ console.error(JSON.stringify(state, null, 2));
367
+ }
368
+ catch { }
369
+ }
355
370
  console.error(`Error: ${err.message}`);
356
371
  process.exit(1);
357
372
  }
@@ -429,7 +444,6 @@ export default function plugin(api) {
429
444
  }
430
445
  console.log("The target device should run:");
431
446
  console.log(` openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>`);
432
- console.log("\nThen restart the gateway: `openclaw gateway restart`");
433
447
  }
434
448
  catch (err) {
435
449
  console.error(`Error: ${err.message}`);
@@ -486,10 +500,16 @@ export default function plugin(api) {
486
500
  console.log(`Not set up. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
487
501
  return;
488
502
  }
489
- // Spin up a temporary engine to inspect state
490
- const storachaClient = await createStorachaClient(deviceConfig);
491
- const engine = new SyncEngine(storachaClient, workspace);
492
- await engine.init(deviceConfig);
503
+ // Use active syncer if available, otherwise spin up temporary engine
504
+ let engine;
505
+ const activeSync = activeSyncers.get(workspace);
506
+ if (activeSync) {
507
+ engine = activeSync.engine;
508
+ }
509
+ else {
510
+ engine = new SyncEngine(workspace);
511
+ await engine.init(deviceConfig);
512
+ }
493
513
  const state = await engine.inspect();
494
514
  console.log(`🔥 Storacha Inspect [${agentId}]`);
495
515
  console.log(`Workspace: ${workspace}`);
package/dist/sync.d.ts CHANGED
@@ -10,40 +10,43 @@
10
10
  */
11
11
  import type { SyncState, FileChange, DeviceConfig } from "./types/index.js";
12
12
  import { type PailEntries } from "./utils/differ.js";
13
- import { Client } from "@storacha/client";
14
13
  export declare class SyncEngine {
15
14
  private workspace;
16
15
  private blocks;
17
- private name;
16
+ private state;
18
17
  private current;
19
18
  private pendingOps;
20
19
  private carFile;
21
- private running;
22
20
  private lastSync;
23
- private storachaClient;
24
- constructor(storachaClient: Client, workspace: string);
21
+ constructor(workspace: string);
25
22
  /**
26
- * Initialize sync engine with device config
23
+ * Initialize sync engine with device config.
24
+ * Creates the Storacha client and resolves the UCN name.
27
25
  */
28
26
  init(config: DeviceConfig): Promise<void>;
29
27
  /**
30
- * Process a batch of file changes
28
+ * Require running state or throw.
29
+ */
30
+ private requireRunning;
31
+ /**
32
+ * Process a batch of file changes.
33
+ * Can be called even when not running (accumulates pending ops).
31
34
  */
32
35
  processChanges(changes: FileChange[]): Promise<void>;
33
36
  /**
34
- * Execute sync: generate revision, publish, upload, apply remote changes
37
+ * Execute sync: generate revision, publish, upload, apply remote changes.
35
38
  */
36
39
  sync(): Promise<void>;
37
40
  /**
38
- * Create CAR and upload to Storacha
41
+ * Create CAR and upload to Storacha.
39
42
  */
40
43
  private possiblyUploadCAR;
41
44
  /**
42
- * Get current pail entries as map
45
+ * Get current pail entries as map.
43
46
  */
44
47
  getPailEntries(): Promise<PailEntries>;
45
48
  /**
46
- * Apply remote changes to local filesystem
49
+ * Apply remote changes to local filesystem.
47
50
  */
48
51
  private applyRemoteChanges;
49
52
  /**
@@ -52,7 +55,7 @@ export declare class SyncEngine {
52
55
  stop(): void;
53
56
  /**
54
57
  * Pull all remote state and write to local filesystem.
55
- * Used by /storacha-join to overwrite local with remote before watcher starts.
58
+ * Used by join to overwrite local with remote before watcher starts.
56
59
  */
57
60
  pullRemote(): Promise<number>;
58
61
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EAEV,YAAY,EACb,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAG1C,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,IAAI,CAAyB;IACrC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,cAAc,CAAS;gBAEnB,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAMrD;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAkC/C;;OAEG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD3B;;OAEG;YACW,iBAAiB;IAc/B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAc5C;;OAEG;YACW,kBAAkB;IAWhC;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IA0BnC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC;QACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC5D,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAiBI,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAW5B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YAM5B,WAAW;CAK1B"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EAEV,YAAY,EACb,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAWxE,qBAAa,UAAU;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,QAAQ,CAAuB;gBAE3B,SAAS,EAAE,MAAM;IAK7B;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC/C;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgD3B;;OAEG;YACW,iBAAiB;IAe/B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAc5C;;OAEG;YACW,kBAAkB;IAUhC;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IA0BnC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC;QACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,UAAU,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC5D,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IAiBI,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAW5B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YAM5B,WAAW;CAK1B"}
package/dist/sync.js CHANGED
@@ -16,31 +16,32 @@ import { applyRemoteChanges } from "./handlers/remote.js";
16
16
  import { processChanges } from "./handlers/process.js";
17
17
  import { diffRemoteChanges } from "./utils/differ.js";
18
18
  import { makeTempCar } from "./utils/tempcar.js";
19
+ import { createStorachaClient } from "./utils/client.js";
19
20
  import { decodeDelegation, encodeDelegation } from "./utils/delegation.js";
20
21
  export class SyncEngine {
21
22
  workspace;
22
23
  blocks;
23
- name = null;
24
+ state = { running: false };
24
25
  current = null;
25
26
  pendingOps = [];
26
27
  carFile = null;
27
- running = false;
28
28
  lastSync = null;
29
- storachaClient;
30
- constructor(storachaClient, workspace) {
31
- this.storachaClient = storachaClient;
29
+ constructor(workspace) {
32
30
  this.workspace = workspace;
33
31
  this.blocks = createWorkspaceBlockstore(workspace);
34
32
  }
35
33
  /**
36
- * Initialize sync engine with device config
34
+ * Initialize sync engine with device config.
35
+ * Creates the Storacha client and resolves the UCN name.
37
36
  */
38
37
  async init(config) {
38
+ const storachaClient = await createStorachaClient(config);
39
39
  const agent = Agent.parse(config.agentKey);
40
+ let name;
40
41
  if (config.nameArchive) {
41
42
  // Restore from previously saved archive (has full state)
42
43
  const archiveBytes = decodeDelegation(config.nameArchive);
43
- this.name = await Name.extract(agent, archiveBytes);
44
+ name = await Name.extract(agent, archiveBytes);
44
45
  }
45
46
  else if (config.nameDelegation) {
46
47
  // Reconstruct from delegation (granted by another device)
@@ -49,14 +50,15 @@ export class SyncEngine {
49
50
  const { ok: delegation } = await extract(nameBytes);
50
51
  if (!delegation)
51
52
  throw new Error("Failed to extract name delegation");
52
- this.name = Name.from(agent, [delegation]);
53
+ name = Name.from(agent, [delegation]);
53
54
  }
54
55
  else {
55
56
  // First device — create a new name
56
- this.name = await Name.create(agent);
57
+ name = await Name.create(agent);
57
58
  }
59
+ this.state = { running: true, name, storachaClient };
58
60
  try {
59
- const result = await Revision.resolve(this.blocks, this.name);
61
+ const result = await Revision.resolve(this.blocks, name);
60
62
  this.current = result.value;
61
63
  await this.storeBlocks(result.additions);
62
64
  }
@@ -68,10 +70,19 @@ export class SyncEngine {
68
70
  throw err;
69
71
  }
70
72
  }
71
- this.running = true;
72
73
  }
73
74
  /**
74
- * Process a batch of file changes
75
+ * Require running state or throw.
76
+ */
77
+ requireRunning() {
78
+ if (!this.state.running) {
79
+ throw new Error("Sync engine not initialized");
80
+ }
81
+ return this.state;
82
+ }
83
+ /**
84
+ * Process a batch of file changes.
85
+ * Can be called even when not running (accumulates pending ops).
75
86
  */
76
87
  async processChanges(changes) {
77
88
  if (!this.carFile) {
@@ -81,18 +92,16 @@ export class SyncEngine {
81
92
  this.pendingOps.push(...pendingOps);
82
93
  }
83
94
  /**
84
- * Execute sync: generate revision, publish, upload, apply remote changes
95
+ * Execute sync: generate revision, publish, upload, apply remote changes.
85
96
  */
86
97
  async sync() {
87
- if (!this.name) {
88
- throw new Error("Sync engine not initialized");
89
- }
98
+ const { name } = this.requireRunning();
90
99
  const beforeEntries = await this.getPailEntries();
91
100
  if (this.pendingOps.length > 0) {
92
101
  if (!this.carFile) {
93
102
  throw new Error("CAR file not initialized");
94
103
  }
95
- const result = await applyPendingOps(this.blocks, this.name, this.current, this.pendingOps);
104
+ const result = await applyPendingOps(this.blocks, name, this.current, this.pendingOps);
96
105
  this.current = result.current;
97
106
  for (const block of result.revisionBlocks) {
98
107
  await this.carFile.put(block);
@@ -102,7 +111,7 @@ export class SyncEngine {
102
111
  else {
103
112
  // No pending ops — just pull remote
104
113
  try {
105
- const result = await Revision.resolve(this.blocks, this.name, {
114
+ const result = await Revision.resolve(this.blocks, name, {
106
115
  base: this.current ?? undefined,
107
116
  });
108
117
  await this.storeBlocks(result.additions);
@@ -125,15 +134,16 @@ export class SyncEngine {
125
134
  this.lastSync = Date.now();
126
135
  }
127
136
  /**
128
- * Create CAR and upload to Storacha
137
+ * Create CAR and upload to Storacha.
129
138
  */
130
139
  async possiblyUploadCAR() {
131
140
  if (this.carFile) {
141
+ const { storachaClient } = this.requireRunning();
132
142
  const readableCar = await this.carFile.switchToReadable();
133
143
  this.carFile = null;
134
144
  if (readableCar) {
135
145
  try {
136
- await this.storachaClient.uploadCAR(readableCar.readable);
146
+ await storachaClient.uploadCAR(readableCar.readable);
137
147
  }
138
148
  finally {
139
149
  await readableCar.cleanup();
@@ -142,7 +152,7 @@ export class SyncEngine {
142
152
  }
143
153
  }
144
154
  /**
145
- * Get current pail entries as map
155
+ * Get current pail entries as map.
146
156
  */
147
157
  async getPailEntries() {
148
158
  const entries = new Map();
@@ -156,7 +166,7 @@ export class SyncEngine {
156
166
  return entries;
157
167
  }
158
168
  /**
159
- * Apply remote changes to local filesystem
169
+ * Apply remote changes to local filesystem.
160
170
  */
161
171
  async applyRemoteChanges(changedPaths, entries) {
162
172
  await applyRemoteChanges(changedPaths, entries, this.workspace, {
@@ -168,17 +178,16 @@ export class SyncEngine {
168
178
  * Mark the engine as stopped.
169
179
  */
170
180
  stop() {
171
- this.running = false;
181
+ this.state = { running: false };
172
182
  }
173
183
  /**
174
184
  * Pull all remote state and write to local filesystem.
175
- * Used by /storacha-join to overwrite local with remote before watcher starts.
185
+ * Used by join to overwrite local with remote before watcher starts.
176
186
  */
177
187
  async pullRemote() {
178
- if (!this.name)
179
- throw new Error("Sync engine not initialized");
188
+ const { name } = this.requireRunning();
180
189
  try {
181
- const result = await Revision.resolve(this.blocks, this.name, {
190
+ const result = await Revision.resolve(this.blocks, name, {
182
191
  base: this.current ?? undefined,
183
192
  });
184
193
  await this.storeBlocks(result.additions);
@@ -215,13 +224,13 @@ export class SyncEngine {
215
224
  key: op.key,
216
225
  value: op.value?.toString(),
217
226
  })),
218
- running: this.running,
227
+ running: this.state.running,
219
228
  };
220
229
  }
221
230
  async status() {
222
231
  const entries = await this.getPailEntries();
223
232
  return {
224
- running: this.running,
233
+ running: this.state.running,
225
234
  lastSync: this.lastSync,
226
235
  root: this.current?.root ?? null,
227
236
  entryCount: entries.size,
@@ -229,9 +238,8 @@ export class SyncEngine {
229
238
  };
230
239
  }
231
240
  async exportNameArchive() {
232
- if (!this.name)
233
- throw new Error("Sync engine not initialized");
234
- const bytes = await this.name.archive();
241
+ const { name } = this.requireRunning();
242
+ const bytes = await name.archive();
235
243
  return encodeDelegation(bytes);
236
244
  }
237
245
  async storeBlocks(blocks) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storacha/clawracha",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "OpenClaw plugin for Storacha workspace sync via UCN Pail",
5
5
  "type": "module",
6
6
  "files": [