@storacha/clawracha 0.1.15 → 0.1.17

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;AAgG7B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QA8hBpD"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EACV,iBAAiB,EAIlB,MAAM,qBAAqB,CAAC;AAkJ7B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QAsmBpD"}
package/dist/plugin.js CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import * as fs from "node:fs/promises";
10
10
  import * as path from "node:path";
11
+ import { json as consumeJson } from "stream/consumers";
11
12
  import { SyncEngine } from "./sync.js";
12
13
  import { FileWatcher } from "./watcher.js";
13
14
  import { createStorachaClient } from "./utils/client.js";
@@ -15,8 +16,13 @@ import { decodeDelegation, encodeDelegation, readDelegationArg, } from "./utils/
15
16
  import { resolveAgentWorkspace, getAgentIds } from "./utils/workspace.js";
16
17
  import { Agent, Name } from "@storacha/ucn/pail";
17
18
  import { extract } from "@storacha/client/delegation";
19
+ import * as z from "zod";
18
20
  const activeSyncers = new Map();
19
21
  // --- Config helpers ---
22
+ const UpdateParams = z.object({
23
+ agentId: z.string(),
24
+ workspace: z.string(),
25
+ });
20
26
  async function loadDeviceConfig(workspace) {
21
27
  const configPath = path.join(workspace, ".storacha", "config.json");
22
28
  try {
@@ -29,6 +35,38 @@ async function loadDeviceConfig(workspace) {
29
35
  throw err;
30
36
  }
31
37
  }
38
+ async function requestWorkspaceUpdate(workspace, agentId, gatewayConfig) {
39
+ // Mirror resolveGatewayPort from openclaw core
40
+ const DEFAULT_GATEWAY_PORT = 18789;
41
+ let port = DEFAULT_GATEWAY_PORT;
42
+ if (typeof gatewayConfig.port === "number" &&
43
+ Number.isFinite(gatewayConfig.port) &&
44
+ gatewayConfig.port > 0) {
45
+ port = gatewayConfig.port;
46
+ }
47
+ const url = `http://127.0.0.1:${port}/api/channels/clawracha/workspace-update`;
48
+ const headers = {
49
+ "Content-Type": "application/json",
50
+ };
51
+ // Auth: /api/channels/ routes get gateway auth middleware
52
+ const token = gatewayConfig.auth?.token ?? gatewayConfig.auth?.password;
53
+ if (token) {
54
+ headers["Authorization"] = `Bearer ${token}`;
55
+ }
56
+ try {
57
+ const res = await fetch(url, {
58
+ method: "POST",
59
+ headers,
60
+ body: JSON.stringify({ agentId, workspace }),
61
+ });
62
+ if (!res.ok) {
63
+ console.warn(`Warning: Failed to notify gateway (${res.status}). Restart the gateway to pick up changes.`);
64
+ }
65
+ }
66
+ catch (err) {
67
+ console.warn(`Warning: Could not reach gateway at ${url}: ${err.message}. Restart the gateway to pick up changes.`);
68
+ }
69
+ }
32
70
  async function saveDeviceConfig(workspace, config) {
33
71
  const configDir = path.join(workspace, ".storacha");
34
72
  await fs.mkdir(configDir, { recursive: true });
@@ -72,6 +110,41 @@ async function startWorkspaceSync(workspace, agentId, pluginConfig, initialAdd,
72
110
  export default function plugin(api) {
73
111
  const pluginConfig = (api.pluginConfig ?? {});
74
112
  // --- Background service: one syncer per agent workspace ---
113
+ api.registerHttpHandler(async (req, res) => {
114
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
115
+ // Only handle /api/channels/clawracha/workspace-update
116
+ if (!url.pathname.startsWith("/api/channels/clawracha/workspace-update")) {
117
+ return false;
118
+ }
119
+ // only handle post requests
120
+ if (req.method !== "POST") {
121
+ return false;
122
+ }
123
+ const body = await consumeJson(req);
124
+ const paramsResult = UpdateParams.safeParse(body);
125
+ if (paramsResult.success === false) {
126
+ res.statusCode = 400;
127
+ res.end(JSON.stringify({
128
+ error: "Invalid parameters",
129
+ details: paramsResult.error,
130
+ }));
131
+ return true;
132
+ }
133
+ const updateParams = paramsResult.data;
134
+ const sync = activeSyncers.get(updateParams.workspace);
135
+ if (sync) {
136
+ // stop active sync engine if present
137
+ // waiting for any active syncs to flush.
138
+ await sync.watcher.stop();
139
+ await sync.watcher.forceFlush();
140
+ await sync.engine.stop();
141
+ }
142
+ const newSync = await startWorkspaceSync(updateParams.workspace, updateParams.agentId, pluginConfig, false, api.logger);
143
+ if (newSync) {
144
+ activeSyncers.set(updateParams.workspace, newSync);
145
+ }
146
+ return true;
147
+ });
75
148
  api.registerService({
76
149
  id: "storacha-sync",
77
150
  async start(ctx) {
@@ -102,8 +175,9 @@ export default function plugin(api) {
102
175
  },
103
176
  async stop(ctx) {
104
177
  for (const [workspace, sync] of activeSyncers) {
105
- sync.engine.stop();
106
178
  await sync.watcher.stop();
179
+ await sync.watcher.forceFlush();
180
+ await sync.engine.stop();
107
181
  ctx.logger.info(`[${sync.agentId}] Stopped syncing: ${workspace}`);
108
182
  }
109
183
  activeSyncers.clear();
@@ -257,7 +331,17 @@ export default function plugin(api) {
257
331
  if (!sync) {
258
332
  throw new Error("Failed to start sync engine");
259
333
  }
260
- activeSyncers.set(workspace, sync);
334
+ // Wait for initial files to flush through, then stop the watcher
335
+ await sync.watcher.waitForReady();
336
+ await sync.watcher.stop();
337
+ // force out changes then stop the engine, which will wait for last
338
+ // sync to complete
339
+ await sync.watcher.forceFlush();
340
+ await sync.engine.stop();
341
+ // post to the endpoint to tell the service to start syncing this space
342
+ if (config.gateway) {
343
+ await requestWorkspaceUpdate(workspace, agentId, config.gateway);
344
+ }
261
345
  const agent = Agent.parse(deviceConfig.agentKey);
262
346
  console.log(`🔥 Storacha workspace ready for ${agentId}!`);
263
347
  console.log(`Agent DID: ${agent.did()}`);
@@ -309,9 +393,16 @@ export default function plugin(api) {
309
393
  // Pull remote state before watcher starts
310
394
  const sync = await startWorkspaceSync(workspace, agentId, pluginConfig, false, console);
311
395
  let pullCount = 0;
312
- if (sync) {
313
- pullCount = await sync.engine.pullRemote();
314
- activeSyncers.set(workspace, sync);
396
+ if (!sync) {
397
+ throw new Error("Failed to start sync engine");
398
+ }
399
+ pullCount = await sync.engine.pullRemote();
400
+ await sync.watcher.stop();
401
+ await sync.watcher.forceFlush();
402
+ await sync.engine.stop();
403
+ // post to the endpoint to tell the service to start syncing this space
404
+ if (config.gateway) {
405
+ await requestWorkspaceUpdate(workspace, agentId, config.gateway);
315
406
  }
316
407
  const agent = Agent.parse(deviceConfig.agentKey);
317
408
  console.log(`🔥 Joined existing Storacha workspace for ${agentId}!`);
package/dist/sync.d.ts CHANGED
@@ -54,7 +54,7 @@ export declare class SyncEngine {
54
54
  /**
55
55
  * Mark the engine as stopped.
56
56
  */
57
- stop(): void;
57
+ stop(): Promise<void>;
58
58
  /**
59
59
  * Pull all remote state and write to local filesystem.
60
60
  * Used by join to overwrite local with remote before watcher starts.
@@ -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;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;IACvC,OAAO,CAAC,QAAQ,CAAoC;gBAExC,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;YAMb,UAAU;IAyDxB;;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;IAkBI,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;IACvC,OAAO,CAAC,QAAQ,CAAoC;gBAExC,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;YAMb,UAAU;IAyDxB;;OAEG;YACW,iBAAiB;IAe/B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAc5C;;OAEG;YACW,kBAAkB;IAUhC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B;;;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;IAkBI,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC;IAW5B,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;YAM5B,WAAW;CAK1B"}
package/dist/sync.js CHANGED
@@ -199,8 +199,11 @@ export class SyncEngine {
199
199
  /**
200
200
  * Mark the engine as stopped.
201
201
  */
202
- stop() {
203
- this.state = { running: false };
202
+ async stop() {
203
+ this.syncLock = this.syncLock.then(() => {
204
+ this.state = { running: false };
205
+ });
206
+ return this.syncLock;
204
207
  }
205
208
  /**
206
209
  * Pull all remote state and write to local filesystem.
package/dist/watcher.d.ts CHANGED
@@ -16,6 +16,7 @@ export declare class FileWatcher {
16
16
  private watcher;
17
17
  private pendingChanges;
18
18
  private debounceTimer;
19
+ private readyPromise;
19
20
  private options;
20
21
  private debounceMs;
21
22
  constructor(options: WatcherOptions);
@@ -23,6 +24,10 @@ export declare class FileWatcher {
23
24
  * Start watching the workspace
24
25
  */
25
26
  start(): Promise<void>;
27
+ /**
28
+ * Wait for chokidar to finish its initial scan
29
+ */
30
+ waitForReady(): Promise<void>;
26
31
  /**
27
32
  * Stop watching
28
33
  */
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,cAAc;IAKnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAmBpB;;OAEG;YACW,KAAK;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAOlC"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,cAAc;IAKnC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuC5B;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B;;OAEG;IACH,OAAO,CAAC,YAAY;IAmBpB;;OAEG;YACW,KAAK;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAOlC"}
package/dist/watcher.js CHANGED
@@ -11,6 +11,7 @@ export class FileWatcher {
11
11
  watcher = null;
12
12
  pendingChanges = new Map();
13
13
  debounceTimer = null;
14
+ readyPromise = null;
14
15
  options;
15
16
  debounceMs;
16
17
  constructor(options) {
@@ -43,12 +44,24 @@ export class FileWatcher {
43
44
  pollInterval: 100,
44
45
  },
45
46
  });
47
+ this.readyPromise = new Promise((resolve) => {
48
+ this.watcher.on("ready", () => resolve());
49
+ });
46
50
  this.watcher
47
51
  .on("add", (filePath) => this.handleChange("add", filePath))
48
52
  .on("change", (filePath) => this.handleChange("change", filePath))
49
53
  .on("unlink", (filePath) => this.handleChange("unlink", filePath))
50
54
  .on("error", (err) => console.error("Watcher error:", err));
51
55
  }
56
+ /**
57
+ * Wait for chokidar to finish its initial scan
58
+ */
59
+ async waitForReady() {
60
+ if (!this.readyPromise) {
61
+ throw new Error("Watcher not started");
62
+ }
63
+ return this.readyPromise;
64
+ }
52
65
  /**
53
66
  * Stop watching
54
67
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storacha/clawracha",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "OpenClaw plugin for Storacha workspace sync via UCN Pail",
5
5
  "type": "module",
6
6
  "files": [
@@ -36,7 +36,8 @@
36
36
  "@web3-storage/pail": "0.6.3-rc.3",
37
37
  "carstream": "^2.3.0",
38
38
  "chokidar": "^3.6.0",
39
- "multiformats": "^13.0.0"
39
+ "multiformats": "^13.0.0",
40
+ "zod": "^4.3.6"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@ipld/unixfs": "^3.0.0",