@storacha/clawracha 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.
@@ -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;AAwJ7B,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,45 @@ 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
+ const envRaw = process.env.OPENCLAW_GATEWAY_PORT?.trim() ||
43
+ process.env.CLAWDBOT_GATEWAY_PORT?.trim();
44
+ if (envRaw) {
45
+ const parsed = Number.parseInt(envRaw, 10);
46
+ if (Number.isFinite(parsed) && parsed > 0)
47
+ port = parsed;
48
+ }
49
+ else if (typeof gatewayConfig.port === "number" &&
50
+ Number.isFinite(gatewayConfig.port) &&
51
+ gatewayConfig.port > 0) {
52
+ port = gatewayConfig.port;
53
+ }
54
+ const url = `http://127.0.0.1:${port}/api/channels/clawracha/workspace-update`;
55
+ const headers = {
56
+ "Content-Type": "application/json",
57
+ };
58
+ // Auth: /api/channels/ routes get gateway auth middleware
59
+ const token = gatewayConfig.auth?.token ?? gatewayConfig.auth?.password;
60
+ if (token) {
61
+ headers["Authorization"] = `Bearer ${token}`;
62
+ }
63
+ try {
64
+ const res = await fetch(url, {
65
+ method: "POST",
66
+ headers,
67
+ body: JSON.stringify({ agentId, workspace }),
68
+ });
69
+ if (!res.ok) {
70
+ console.warn(`Warning: Failed to notify gateway (${res.status}). Restart the gateway to pick up changes.`);
71
+ }
72
+ }
73
+ catch (err) {
74
+ console.warn(`Warning: Could not reach gateway at ${url}: ${err.message}. Restart the gateway to pick up changes.`);
75
+ }
76
+ }
32
77
  async function saveDeviceConfig(workspace, config) {
33
78
  const configDir = path.join(workspace, ".storacha");
34
79
  await fs.mkdir(configDir, { recursive: true });
@@ -72,6 +117,41 @@ async function startWorkspaceSync(workspace, agentId, pluginConfig, initialAdd,
72
117
  export default function plugin(api) {
73
118
  const pluginConfig = (api.pluginConfig ?? {});
74
119
  // --- Background service: one syncer per agent workspace ---
120
+ api.registerHttpHandler(async (req, res) => {
121
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
122
+ // Only handle /api/channels/clawracha/workspace-update
123
+ if (!url.pathname.startsWith("/api/channels/clawracha/workspace-update")) {
124
+ return false;
125
+ }
126
+ // only handle post requests
127
+ if (req.method !== "POST") {
128
+ return false;
129
+ }
130
+ const body = await consumeJson(req);
131
+ const paramsResult = UpdateParams.safeParse(body);
132
+ if (paramsResult.success === false) {
133
+ res.statusCode = 400;
134
+ res.end(JSON.stringify({
135
+ error: "Invalid parameters",
136
+ details: paramsResult.error,
137
+ }));
138
+ return true;
139
+ }
140
+ const updateParams = paramsResult.data;
141
+ const sync = activeSyncers.get(updateParams.workspace);
142
+ if (sync) {
143
+ // stop active sync engine if present
144
+ // waiting for any active syncs to flush.
145
+ await sync.watcher.stop();
146
+ await sync.watcher.forceFlush();
147
+ await sync.engine.stop();
148
+ }
149
+ const newSync = await startWorkspaceSync(updateParams.workspace, updateParams.agentId, pluginConfig, false, api.logger);
150
+ if (newSync) {
151
+ activeSyncers.set(updateParams.workspace, newSync);
152
+ }
153
+ return true;
154
+ });
75
155
  api.registerService({
76
156
  id: "storacha-sync",
77
157
  async start(ctx) {
@@ -102,8 +182,9 @@ export default function plugin(api) {
102
182
  },
103
183
  async stop(ctx) {
104
184
  for (const [workspace, sync] of activeSyncers) {
105
- sync.engine.stop();
106
185
  await sync.watcher.stop();
186
+ await sync.watcher.forceFlush();
187
+ await sync.engine.stop();
107
188
  ctx.logger.info(`[${sync.agentId}] Stopped syncing: ${workspace}`);
108
189
  }
109
190
  activeSyncers.clear();
@@ -257,7 +338,17 @@ export default function plugin(api) {
257
338
  if (!sync) {
258
339
  throw new Error("Failed to start sync engine");
259
340
  }
260
- activeSyncers.set(workspace, sync);
341
+ // Wait for initial files to flush through, then stop the watcher
342
+ await sync.watcher.waitForReady();
343
+ await sync.watcher.stop();
344
+ // force out changes then stop the engine, which will wait for last
345
+ // sync to complete
346
+ await sync.watcher.forceFlush();
347
+ await sync.engine.stop();
348
+ // post to the endpoint to tell the service to start syncing this space
349
+ if (config.gateway) {
350
+ await requestWorkspaceUpdate(workspace, agentId, config.gateway);
351
+ }
261
352
  const agent = Agent.parse(deviceConfig.agentKey);
262
353
  console.log(`🔥 Storacha workspace ready for ${agentId}!`);
263
354
  console.log(`Agent DID: ${agent.did()}`);
@@ -309,9 +400,16 @@ export default function plugin(api) {
309
400
  // Pull remote state before watcher starts
310
401
  const sync = await startWorkspaceSync(workspace, agentId, pluginConfig, false, console);
311
402
  let pullCount = 0;
312
- if (sync) {
313
- pullCount = await sync.engine.pullRemote();
314
- activeSyncers.set(workspace, sync);
403
+ if (!sync) {
404
+ throw new Error("Failed to start sync engine");
405
+ }
406
+ pullCount = await sync.engine.pullRemote();
407
+ await sync.watcher.stop();
408
+ await sync.watcher.forceFlush();
409
+ await sync.engine.stop();
410
+ // post to the endpoint to tell the service to start syncing this space
411
+ if (config.gateway) {
412
+ await requestWorkspaceUpdate(workspace, agentId, config.gateway);
315
413
  }
316
414
  const agent = Agent.parse(deviceConfig.agentKey);
317
415
  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.16",
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",