@storacha/clawracha 0.1.2 → 0.1.3

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,QAojBpD"}
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,
@@ -263,8 +262,7 @@ export default function plugin(api) {
263
262
  deviceConfig.setupComplete = true;
264
263
  await saveDeviceConfig(workspace, deviceConfig);
265
264
  // Initial upload: scan all existing workspace files and sync to Storacha
266
- const storachaClient = await createStorachaClient(deviceConfig);
267
- const engine = new SyncEngine(storachaClient, workspace);
265
+ const engine = new SyncEngine(workspace);
268
266
  await engine.init(deviceConfig);
269
267
  const userIgnored = await readIgnoreFile(workspace);
270
268
  const ignorePatterns = [
@@ -296,6 +294,7 @@ export default function plugin(api) {
296
294
  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) {
301
300
  console.error(`Error: ${err.message}`);
@@ -335,8 +334,7 @@ export default function plugin(api) {
335
334
  await saveDeviceConfig(workspace, deviceConfig);
336
335
  // Pull remote state before watcher starts
337
336
  let pullCount = 0;
338
- const storachaClient = await createStorachaClient(deviceConfig);
339
- const engine = new SyncEngine(storachaClient, workspace);
337
+ const engine = new SyncEngine(workspace);
340
338
  await engine.init(deviceConfig);
341
339
  pullCount = await engine.pullRemote();
342
340
  // Save name archive after pull
@@ -486,10 +484,16 @@ export default function plugin(api) {
486
484
  console.log(`Not set up. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
487
485
  return;
488
486
  }
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);
487
+ // Use active syncer if available, otherwise spin up temporary engine
488
+ let engine;
489
+ const activeSync = activeSyncers.get(workspace);
490
+ if (activeSync) {
491
+ engine = activeSync.engine;
492
+ }
493
+ else {
494
+ engine = new SyncEngine(workspace);
495
+ await engine.init(deviceConfig);
496
+ }
493
497
  const state = await engine.inspect();
494
498
  console.log(`🔥 Storacha Inspect [${agentId}]`);
495
499
  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.3",
4
4
  "description": "OpenClaw plugin for Storacha workspace sync via UCN Pail",
5
5
  "type": "module",
6
6
  "files": [