@storacha/clawracha 0.0.11-rc.0 → 0.1.0
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.
- package/README.md +16 -14
- package/dist/plugin.d.ts +2 -5
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +292 -389
- package/dist/utils/delegation.d.ts +5 -0
- package/dist/utils/delegation.d.ts.map +1 -1
- package/dist/utils/delegation.js +22 -0
- package/dist/utils/workspace.d.ts +20 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +72 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,12 +21,14 @@ openclaw plugins install @storacha/clawracha
|
|
|
21
21
|
|
|
22
22
|
## Setup
|
|
23
23
|
|
|
24
|
-
Setup is done via
|
|
24
|
+
Setup is done via CLI commands on the host (not slash commands in chat).
|
|
25
|
+
|
|
26
|
+
All commands require `--agent <id>` to specify which agent workspace to configure.
|
|
25
27
|
|
|
26
28
|
### Step 1: Initialize the agent
|
|
27
29
|
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
+
```bash
|
|
31
|
+
openclaw clawracha init --agent <id>
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
Generates an agent identity and displays the Agent DID. You'll need this DID to create delegations.
|
|
@@ -35,32 +37,32 @@ Generates an agent identity and displays the Agent DID. You'll need this DID to
|
|
|
35
37
|
|
|
36
38
|
**New workspace (first device):**
|
|
37
39
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
+
```bash
|
|
41
|
+
openclaw clawracha setup <delegation> --agent <id>
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
Have the space owner create an upload delegation for your Agent DID, then import it.
|
|
44
|
+
`<delegation>` can be a file path (raw CAR) or a base64 CID string. Have the space owner create an upload delegation for your Agent DID, then import it.
|
|
43
45
|
|
|
44
46
|
**Join an existing workspace (additional devices):**
|
|
45
47
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
+
```bash
|
|
49
|
+
openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
Get both delegations by running
|
|
52
|
+
Get both delegations by running `openclaw clawracha grant` on the existing device. Arguments can be file paths or base64 CID strings. The join command pulls all remote files before the watcher starts.
|
|
51
53
|
|
|
52
54
|
### Grant access to another device
|
|
53
55
|
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
+
```bash
|
|
57
|
+
openclaw clawracha grant <target-agent-DID> --agent <id>
|
|
56
58
|
```
|
|
57
59
|
|
|
58
|
-
Generates upload and name delegations for the target device.
|
|
60
|
+
Generates upload and name delegations for the target device.
|
|
59
61
|
|
|
60
62
|
### Check status
|
|
61
63
|
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
+
```bash
|
|
65
|
+
openclaw clawracha status --agent <id>
|
|
64
66
|
```
|
|
65
67
|
|
|
66
68
|
After setup, restart the gateway to start syncing:
|
package/dist/plugin.d.ts
CHANGED
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
* OpenClaw Plugin Entry Point
|
|
3
3
|
*
|
|
4
4
|
* Registers:
|
|
5
|
-
* - Background service
|
|
5
|
+
* - Background service that syncs ALL agent workspaces with .storacha configs
|
|
6
|
+
* - CLI commands for setup and management (openclaw clawracha ...)
|
|
6
7
|
* - Agent tools for manual sync control
|
|
7
|
-
* - Slash commands for setup
|
|
8
8
|
*/
|
|
9
9
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
10
|
-
/**
|
|
11
|
-
* Plugin entry — called by OpenClaw when the plugin is loaded.
|
|
12
|
-
*/
|
|
13
10
|
export default function plugin(api: OpenClawPluginApi): void;
|
|
14
11
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
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;AAiG7B,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,GAAG,EAAE,iBAAiB,QAoepD"}
|
package/dist/plugin.js
CHANGED
|
@@ -2,24 +2,19 @@
|
|
|
2
2
|
* OpenClaw Plugin Entry Point
|
|
3
3
|
*
|
|
4
4
|
* Registers:
|
|
5
|
-
* - Background service
|
|
5
|
+
* - Background service that syncs ALL agent workspaces with .storacha configs
|
|
6
|
+
* - CLI commands for setup and management (openclaw clawracha ...)
|
|
6
7
|
* - Agent tools for manual sync control
|
|
7
|
-
* - Slash commands for setup
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs/promises";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
-
import { extract as extractDelegation } from "@storacha/client/delegation";
|
|
12
11
|
import { SyncEngine } from "./sync.js";
|
|
13
|
-
import { decodeDelegation, encodeDelegation } from "./utils/delegation.js";
|
|
14
12
|
import { FileWatcher } from "./watcher.js";
|
|
15
13
|
import { createStorachaClient } from "./utils/client.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Load device config from .storacha/config.json
|
|
22
|
-
*/
|
|
14
|
+
import { decodeDelegation, encodeDelegation, readDelegationArg, } from "./utils/delegation.js";
|
|
15
|
+
import { resolveAgentWorkspace, getAgentIds, } from "./utils/workspace.js";
|
|
16
|
+
const activeSyncers = new Map();
|
|
17
|
+
// --- Config helpers ---
|
|
23
18
|
async function loadDeviceConfig(workspace) {
|
|
24
19
|
const configPath = path.join(workspace, ".storacha", "config.json");
|
|
25
20
|
try {
|
|
@@ -32,22 +27,49 @@ async function loadDeviceConfig(workspace) {
|
|
|
32
27
|
throw err;
|
|
33
28
|
}
|
|
34
29
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Save device config
|
|
37
|
-
*/
|
|
38
30
|
async function saveDeviceConfig(workspace, config) {
|
|
39
31
|
const configDir = path.join(workspace, ".storacha");
|
|
40
32
|
await fs.mkdir(configDir, { recursive: true });
|
|
41
33
|
const configPath = path.join(configDir, "config.json");
|
|
42
34
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
43
35
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
// --- Service helpers ---
|
|
37
|
+
async function startWorkspaceSync(workspace, agentId, pluginConfig, logger) {
|
|
38
|
+
const deviceConfig = await loadDeviceConfig(workspace);
|
|
39
|
+
if (!deviceConfig || !deviceConfig.setupComplete) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const storachaClient = await createStorachaClient(deviceConfig);
|
|
43
|
+
const engine = new SyncEngine(storachaClient, workspace);
|
|
44
|
+
await engine.init(deviceConfig);
|
|
45
|
+
const watcher = new FileWatcher({
|
|
46
|
+
workspace,
|
|
47
|
+
config: {
|
|
48
|
+
enabled: true,
|
|
49
|
+
watchPatterns: pluginConfig.watchPatterns ?? ["**/*"],
|
|
50
|
+
ignorePatterns: pluginConfig.ignorePatterns ?? [
|
|
51
|
+
".storacha/**",
|
|
52
|
+
"node_modules/**",
|
|
53
|
+
".git/**",
|
|
54
|
+
"dist/**",
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
onChanges: async (changes) => {
|
|
58
|
+
await engine.processChanges(changes);
|
|
59
|
+
await engine.sync();
|
|
60
|
+
const nameArchive = await engine.exportNameArchive();
|
|
61
|
+
const updatedConfig = { ...deviceConfig, nameArchive };
|
|
62
|
+
await saveDeviceConfig(workspace, updatedConfig);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
await watcher.start();
|
|
66
|
+
logger.info(`[${agentId}] Started syncing workspace: ${workspace}`);
|
|
67
|
+
return { engine, watcher, workspace, agentId };
|
|
68
|
+
}
|
|
69
|
+
// --- Plugin entry ---
|
|
47
70
|
export default function plugin(api) {
|
|
48
|
-
// Capture plugin-specific config at registration time
|
|
49
71
|
const pluginConfig = (api.pluginConfig ?? {});
|
|
50
|
-
//
|
|
72
|
+
// --- Background service: one syncer per agent workspace ---
|
|
51
73
|
api.registerService({
|
|
52
74
|
id: "storacha-sync",
|
|
53
75
|
async start(ctx) {
|
|
@@ -55,73 +77,55 @@ export default function plugin(api) {
|
|
|
55
77
|
ctx.logger.info("Storacha sync disabled via config.");
|
|
56
78
|
return;
|
|
57
79
|
}
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
const agentIds = getAgentIds(ctx.config);
|
|
81
|
+
for (const agentId of agentIds) {
|
|
82
|
+
const workspace = resolveAgentWorkspace(ctx.config, agentId);
|
|
83
|
+
try {
|
|
84
|
+
const sync = await startWorkspaceSync(workspace, agentId, pluginConfig, ctx.logger);
|
|
85
|
+
if (sync) {
|
|
86
|
+
activeSyncers.set(workspace, sync);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
ctx.logger.warn(`[${agentId}] Failed to start sync: ${err.message}`);
|
|
91
|
+
}
|
|
63
92
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
if (activeSyncers.size === 0) {
|
|
94
|
+
ctx.logger.info("No agent workspaces configured for Storacha sync. Use `openclaw clawracha init --agent <id>` to set up.");
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
ctx.logger.info(`Storacha sync active for ${activeSyncers.size} workspace(s).`);
|
|
68
98
|
}
|
|
69
|
-
const storachaClient = await createStorachaClient(deviceConfig);
|
|
70
|
-
syncEngine = new SyncEngine(storachaClient, workspace);
|
|
71
|
-
await syncEngine.init(deviceConfig);
|
|
72
|
-
fileWatcher = new FileWatcher({
|
|
73
|
-
workspace,
|
|
74
|
-
config: {
|
|
75
|
-
enabled: true,
|
|
76
|
-
watchPatterns: pluginConfig.watchPatterns ?? ["**/*"],
|
|
77
|
-
ignorePatterns: pluginConfig.ignorePatterns ?? [
|
|
78
|
-
".storacha/**",
|
|
79
|
-
"node_modules/**",
|
|
80
|
-
".git/**",
|
|
81
|
-
"dist/**",
|
|
82
|
-
],
|
|
83
|
-
},
|
|
84
|
-
onChanges: async (changes) => {
|
|
85
|
-
if (!syncEngine)
|
|
86
|
-
return;
|
|
87
|
-
await syncEngine.processChanges(changes);
|
|
88
|
-
await syncEngine.sync();
|
|
89
|
-
const nameArchive = await syncEngine.exportNameArchive();
|
|
90
|
-
const updatedConfig = { ...deviceConfig, nameArchive };
|
|
91
|
-
await saveDeviceConfig(workspace, updatedConfig);
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
await fileWatcher.start();
|
|
95
|
-
ctx.logger.info("Started watching workspace");
|
|
96
99
|
},
|
|
97
100
|
async stop(ctx) {
|
|
98
|
-
|
|
99
|
-
await
|
|
100
|
-
|
|
101
|
+
for (const [workspace, sync] of activeSyncers) {
|
|
102
|
+
await sync.watcher.stop();
|
|
103
|
+
ctx.logger.info(`[${sync.agentId}] Stopped syncing: ${workspace}`);
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
ctx.logger.info("Stopped");
|
|
105
|
+
activeSyncers.clear();
|
|
104
106
|
},
|
|
105
107
|
});
|
|
106
|
-
//
|
|
108
|
+
// --- Agent tools (keyed by workspace dir) ---
|
|
107
109
|
api.registerTool({
|
|
108
110
|
name: "storacha_sync_status",
|
|
109
111
|
label: "Storacha Sync Status",
|
|
110
112
|
description: "Get the current Storacha workspace sync status",
|
|
111
113
|
parameters: { type: "object", properties: {} },
|
|
112
|
-
execute: async () => {
|
|
113
|
-
|
|
114
|
+
execute: async (_params, ctx) => {
|
|
115
|
+
const workspace = ctx?.workspaceDir;
|
|
116
|
+
const sync = workspace ? activeSyncers.get(workspace) : undefined;
|
|
117
|
+
if (!sync) {
|
|
114
118
|
return {
|
|
115
119
|
content: [
|
|
116
120
|
{
|
|
117
121
|
type: "text",
|
|
118
|
-
text: "Sync not
|
|
122
|
+
text: "Sync not active for this workspace. Set up with `openclaw clawracha init --agent <id>`.",
|
|
119
123
|
},
|
|
120
124
|
],
|
|
121
125
|
details: null,
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
|
-
const status = await
|
|
128
|
+
const status = await sync.engine.status();
|
|
125
129
|
return {
|
|
126
130
|
content: [
|
|
127
131
|
{ type: "text", text: JSON.stringify(status, null, 2) },
|
|
@@ -135,20 +139,22 @@ export default function plugin(api) {
|
|
|
135
139
|
label: "Storacha Sync Now",
|
|
136
140
|
description: "Trigger an immediate workspace sync to Storacha",
|
|
137
141
|
parameters: { type: "object", properties: {} },
|
|
138
|
-
execute: async () => {
|
|
139
|
-
|
|
142
|
+
execute: async (_params, ctx) => {
|
|
143
|
+
const workspace = ctx?.workspaceDir;
|
|
144
|
+
const sync = workspace ? activeSyncers.get(workspace) : undefined;
|
|
145
|
+
if (!sync) {
|
|
140
146
|
return {
|
|
141
147
|
content: [
|
|
142
148
|
{
|
|
143
149
|
type: "text",
|
|
144
|
-
text: "Sync not
|
|
150
|
+
text: "Sync not active for this workspace. Set up with `openclaw clawracha init --agent <id>`.",
|
|
145
151
|
},
|
|
146
152
|
],
|
|
147
153
|
details: null,
|
|
148
154
|
};
|
|
149
155
|
}
|
|
150
|
-
await
|
|
151
|
-
const status = await
|
|
156
|
+
await sync.engine.sync();
|
|
157
|
+
const status = await sync.engine.status();
|
|
152
158
|
return {
|
|
153
159
|
content: [
|
|
154
160
|
{
|
|
@@ -160,375 +166,272 @@ export default function plugin(api) {
|
|
|
160
166
|
};
|
|
161
167
|
},
|
|
162
168
|
});
|
|
163
|
-
// ---
|
|
164
|
-
api.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
// --- CLI commands: openclaw clawracha <subcommand> ---
|
|
170
|
+
api.registerCli(({ program, config }) => {
|
|
171
|
+
const clawracha = program
|
|
172
|
+
.command("clawracha")
|
|
173
|
+
.description("Storacha workspace sync commands");
|
|
174
|
+
// Helper to resolve workspace from --agent
|
|
175
|
+
function requireAgent(agentId) {
|
|
176
|
+
if (!agentId) {
|
|
177
|
+
console.error("Error: --agent <id> is required. Specify which agent workspace to configure.");
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
agentId,
|
|
182
|
+
workspace: resolveAgentWorkspace(config, agentId),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// --- init ---
|
|
186
|
+
clawracha
|
|
187
|
+
.command("init")
|
|
188
|
+
.description("Generate an agent identity for Storacha sync")
|
|
189
|
+
.requiredOption("--agent <id>", "Agent ID")
|
|
190
|
+
.action(async (opts) => {
|
|
168
191
|
try {
|
|
169
|
-
const workspace =
|
|
170
|
-
if (!workspace)
|
|
171
|
-
return { text: "No workspace configured." };
|
|
172
|
-
// Check if already initialized
|
|
192
|
+
const { agentId, workspace } = requireAgent(opts.agent);
|
|
173
193
|
const existing = await loadDeviceConfig(workspace);
|
|
174
194
|
if (existing?.agentKey) {
|
|
175
195
|
const { Agent } = await import("@storacha/ucn/pail");
|
|
176
196
|
const agent = Agent.parse(existing.agentKey);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"- **Join existing:** Have the other device run `/storacha-grant <this-DID>`, then run `/storacha-join <upload-b64> <name-b64>`",
|
|
189
|
-
]
|
|
190
|
-
: []),
|
|
191
|
-
].join("\n"),
|
|
192
|
-
};
|
|
197
|
+
console.log(`Agent already initialized for ${agentId}.`);
|
|
198
|
+
console.log(`Agent DID: ${agent.did()}`);
|
|
199
|
+
if (existing.setupComplete) {
|
|
200
|
+
console.log(`\nSetup is complete. Use \`openclaw clawracha status --agent ${agentId}\` to check sync state.`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log("\nNext step — choose one:");
|
|
204
|
+
console.log(` New workspace: openclaw clawracha setup <delegation> --agent ${agentId}`);
|
|
205
|
+
console.log(` Join existing: openclaw clawracha join <upload> <name> --agent ${agentId}`);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
193
208
|
}
|
|
194
209
|
const { Agent } = await import("@storacha/ucn/pail");
|
|
195
210
|
const agent = await Agent.generate();
|
|
196
211
|
const agentKey = Agent.format(agent);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
"",
|
|
204
|
-
"**Next step \u2014 choose one:**",
|
|
205
|
-
"- **New workspace:** Have the space owner create an upload delegation for this DID, then run `/storacha-setup <upload-b64>`",
|
|
206
|
-
"- **Join existing workspace:** Have the other device run `/storacha-grant <this-DID>`, then run `/storacha-join <upload-b64> <name-b64>`",
|
|
207
|
-
].join("\n"),
|
|
208
|
-
};
|
|
212
|
+
await saveDeviceConfig(workspace, { agentKey });
|
|
213
|
+
console.log(`🔥 Agent initialized for ${agentId}!`);
|
|
214
|
+
console.log(`Agent DID: ${agent.did()}`);
|
|
215
|
+
console.log("\nNext step — choose one:");
|
|
216
|
+
console.log(` New workspace: openclaw clawracha setup <delegation> --agent ${agentId}`);
|
|
217
|
+
console.log(` Join existing: openclaw clawracha join <upload> <name> --agent ${agentId}`);
|
|
209
218
|
}
|
|
210
219
|
catch (err) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
};
|
|
220
|
+
console.error(`Error: ${err.message}`);
|
|
221
|
+
process.exit(1);
|
|
214
222
|
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
223
|
+
});
|
|
224
|
+
// --- setup ---
|
|
225
|
+
clawracha
|
|
226
|
+
.command("setup <delegation>")
|
|
227
|
+
.description("Set up a NEW workspace (first device). <delegation> is a file path or base64 CID string.")
|
|
228
|
+
.requiredOption("--agent <id>", "Agent ID")
|
|
229
|
+
.action(async (delegationArg, opts) => {
|
|
222
230
|
try {
|
|
223
|
-
const workspace =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
text: "Run `/storacha-init` first to generate an agent identity.",
|
|
230
|
-
};
|
|
231
|
+
const { agentId, workspace } = requireAgent(opts.agent);
|
|
232
|
+
const deviceConfig = await loadDeviceConfig(workspace);
|
|
233
|
+
if (!deviceConfig?.agentKey) {
|
|
234
|
+
console.error(`Run \`openclaw clawracha init --agent ${agentId}\` first.`);
|
|
235
|
+
process.exit(1);
|
|
231
236
|
}
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
};
|
|
237
|
+
if (deviceConfig.setupComplete) {
|
|
238
|
+
console.log("Setup already complete.");
|
|
239
|
+
return;
|
|
236
240
|
}
|
|
237
|
-
const
|
|
238
|
-
if (!b64) {
|
|
239
|
-
return {
|
|
240
|
-
text: [
|
|
241
|
-
"Usage: `/storacha-setup <upload-delegation-b64>`",
|
|
242
|
-
"",
|
|
243
|
-
"This creates a **new** workspace. If you're joining an existing workspace, use `/storacha-join` instead.",
|
|
244
|
-
].join("\n"),
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
return {
|
|
248
|
-
text: [
|
|
249
|
-
"You ran '/storacha-setup' without the following command argument:",
|
|
250
|
-
"",
|
|
251
|
-
b64,
|
|
252
|
-
].join("\n"),
|
|
253
|
-
};
|
|
254
|
-
/*
|
|
255
|
-
// Validate delegation
|
|
256
|
-
const bytes = decodeDelegation(b64);
|
|
257
|
-
const { ok: delegation, error } = await extractDelegation(bytes);
|
|
258
|
-
if (!delegation) {
|
|
259
|
-
return { text: `Invalid delegation: ${error}` };
|
|
260
|
-
}
|
|
261
|
-
|
|
241
|
+
const delegation = await readDelegationArg(delegationArg);
|
|
262
242
|
const spaceDID = delegation.capabilities[0]?.with;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
243
|
+
const { ok: archiveBytes } = await delegation.archive();
|
|
244
|
+
if (!archiveBytes) {
|
|
245
|
+
throw new Error("Failed to archive delegation");
|
|
246
|
+
}
|
|
247
|
+
deviceConfig.uploadDelegation = encodeDelegation(archiveBytes);
|
|
248
|
+
deviceConfig.spaceDID = spaceDID ?? undefined;
|
|
249
|
+
deviceConfig.setupComplete = true;
|
|
250
|
+
await saveDeviceConfig(workspace, deviceConfig);
|
|
269
251
|
const { Agent } = await import("@storacha/ucn/pail");
|
|
270
|
-
const agent = Agent.parse(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
"",
|
|
278
|
-
"Restart the gateway to start syncing.",
|
|
279
|
-
"",
|
|
280
|
-
"To add another device, run `/storacha-grant <their-agent-DID>` here,",
|
|
281
|
-
"then `/storacha-join <upload-b64> <name-b64>` on the other device.",
|
|
282
|
-
].join("\n"),
|
|
283
|
-
};*/
|
|
252
|
+
const agent = Agent.parse(deviceConfig.agentKey);
|
|
253
|
+
console.log(`🔥 Storacha workspace ready for ${agentId}!`);
|
|
254
|
+
console.log(`Agent DID: ${agent.did()}`);
|
|
255
|
+
console.log(`Space: ${spaceDID ?? "unknown"}`);
|
|
256
|
+
console.log("\nRestart the gateway to start syncing: `openclaw gateway restart`");
|
|
257
|
+
console.log(`\nTo add another device, run \`openclaw clawracha grant <their-DID> --agent ${agentId}\` here,`);
|
|
258
|
+
console.log(`then \`openclaw clawracha join <upload> <name> --agent <id>\` on the other device.`);
|
|
284
259
|
}
|
|
285
260
|
catch (err) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
};
|
|
261
|
+
console.error(`Error: ${err.message}`);
|
|
262
|
+
process.exit(1);
|
|
289
263
|
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
264
|
+
});
|
|
265
|
+
// --- join ---
|
|
266
|
+
clawracha
|
|
267
|
+
.command("join <upload-delegation> <name-delegation>")
|
|
268
|
+
.description("Join an existing workspace from another device. Arguments are file paths or base64 CID strings.")
|
|
269
|
+
.requiredOption("--agent <id>", "Agent ID")
|
|
270
|
+
.action(async (uploadArg, nameArg, opts) => {
|
|
297
271
|
try {
|
|
298
|
-
const workspace =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
text: [
|
|
305
|
-
"Usage: `/storacha-join <upload-delegation-b64> <name-delegation-b64>`",
|
|
306
|
-
"",
|
|
307
|
-
"Get both delegations by running `/storacha-grant` on the existing device.",
|
|
308
|
-
"If you're setting up a **new** workspace, use `/storacha-setup` instead.",
|
|
309
|
-
].join("\n"),
|
|
310
|
-
};
|
|
272
|
+
const { agentId, workspace } = requireAgent(opts.agent);
|
|
273
|
+
const deviceConfig = await loadDeviceConfig(workspace);
|
|
274
|
+
if (!deviceConfig?.agentKey) {
|
|
275
|
+
console.error(`Run \`openclaw clawracha init --agent ${agentId}\` first.`);
|
|
276
|
+
process.exit(1);
|
|
311
277
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return
|
|
315
|
-
text: "Two arguments required: `/storacha-join <upload-b64> <name-b64>`",
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
const uploadB64 = args.slice(0, spaceIdx).trim();
|
|
319
|
-
const nameB64 = args.slice(spaceIdx + 1).trim();
|
|
320
|
-
if (!uploadB64 || !nameB64) {
|
|
321
|
-
return {
|
|
322
|
-
text: "Two arguments required: `/storacha-join <upload-b64> <name-b64>`",
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
// Validate upload delegation
|
|
326
|
-
const uploadBytes = decodeDelegation(uploadB64);
|
|
327
|
-
const { ok: uploadDelegation, error: uploadErr } = await extractDelegation(uploadBytes);
|
|
328
|
-
if (!uploadDelegation) {
|
|
329
|
-
return { text: `Invalid upload delegation: ${uploadErr}` };
|
|
330
|
-
}
|
|
331
|
-
// Validate name delegation
|
|
332
|
-
const nameBytes = decodeDelegation(nameB64);
|
|
333
|
-
const { ok: nameDelegation, error: nameErr } = await extractDelegation(nameBytes);
|
|
334
|
-
if (!nameDelegation) {
|
|
335
|
-
return { text: `Invalid name delegation: ${nameErr}` };
|
|
336
|
-
}
|
|
337
|
-
const config = await loadDeviceConfig(workspace);
|
|
338
|
-
if (!config?.agentKey) {
|
|
339
|
-
return {
|
|
340
|
-
text: "Run `/storacha-init` first to generate an agent identity.",
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
if (config.setupComplete) {
|
|
344
|
-
return {
|
|
345
|
-
text: "Setup already complete. Use `/storacha-status` to check sync state.",
|
|
346
|
-
};
|
|
278
|
+
if (deviceConfig.setupComplete) {
|
|
279
|
+
console.log("Setup already complete.");
|
|
280
|
+
return;
|
|
347
281
|
}
|
|
348
|
-
const
|
|
349
|
-
const
|
|
282
|
+
const uploadDelegation = await readDelegationArg(uploadArg);
|
|
283
|
+
const nameDelegation = await readDelegationArg(nameArg);
|
|
350
284
|
const spaceDID = uploadDelegation.capabilities[0]?.with;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
285
|
+
const { ok: uploadArchive } = await uploadDelegation.archive();
|
|
286
|
+
if (!uploadArchive)
|
|
287
|
+
throw new Error("Failed to archive upload delegation");
|
|
288
|
+
const { ok: nameArchiveBytes } = await nameDelegation.archive();
|
|
289
|
+
if (!nameArchiveBytes)
|
|
290
|
+
throw new Error("Failed to archive name delegation");
|
|
291
|
+
deviceConfig.uploadDelegation = encodeDelegation(uploadArchive);
|
|
292
|
+
deviceConfig.nameDelegation = encodeDelegation(nameArchiveBytes);
|
|
293
|
+
deviceConfig.spaceDID = spaceDID ?? undefined;
|
|
294
|
+
deviceConfig.setupComplete = true;
|
|
295
|
+
await saveDeviceConfig(workspace, deviceConfig);
|
|
296
|
+
// Pull remote state before watcher starts
|
|
357
297
|
let pullCount = 0;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
"",
|
|
374
|
-
"Restart the gateway to retry.",
|
|
375
|
-
].join("\n"),
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
return {
|
|
379
|
-
text: [
|
|
380
|
-
"\u{1f525} Joined existing Storacha workspace!",
|
|
381
|
-
`Agent DID: \`${agent.did()}\``,
|
|
382
|
-
`Space: \`${spaceDID ?? "unknown"}\``,
|
|
383
|
-
`Pulled ${pullCount} files from remote.`,
|
|
384
|
-
"",
|
|
385
|
-
"Restart the gateway to start syncing.",
|
|
386
|
-
].join("\n"),
|
|
387
|
-
};
|
|
298
|
+
const storachaClient = await createStorachaClient(deviceConfig);
|
|
299
|
+
const engine = new SyncEngine(storachaClient, workspace);
|
|
300
|
+
await engine.init(deviceConfig);
|
|
301
|
+
pullCount = await engine.pullRemote();
|
|
302
|
+
// Save name archive after pull
|
|
303
|
+
const exportedArchive = await engine.exportNameArchive();
|
|
304
|
+
deviceConfig.nameArchive = exportedArchive;
|
|
305
|
+
await saveDeviceConfig(workspace, deviceConfig);
|
|
306
|
+
const { Agent } = await import("@storacha/ucn/pail");
|
|
307
|
+
const agent = Agent.parse(deviceConfig.agentKey);
|
|
308
|
+
console.log(`🔥 Joined existing Storacha workspace for ${agentId}!`);
|
|
309
|
+
console.log(`Agent DID: ${agent.did()}`);
|
|
310
|
+
console.log(`Space: ${spaceDID ?? "unknown"}`);
|
|
311
|
+
console.log(`Pulled ${pullCount} files from remote.`);
|
|
312
|
+
console.log("\nRestart the gateway to start syncing: `openclaw gateway restart`");
|
|
388
313
|
}
|
|
389
314
|
catch (err) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
};
|
|
315
|
+
console.error(`Error: ${err.message}`);
|
|
316
|
+
process.exit(1);
|
|
393
317
|
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
318
|
+
});
|
|
319
|
+
// --- grant ---
|
|
320
|
+
clawracha
|
|
321
|
+
.command("grant <target-DID>")
|
|
322
|
+
.description("Grant another device access to this workspace")
|
|
323
|
+
.requiredOption("--agent <id>", "Agent ID")
|
|
324
|
+
.action(async (targetDID, opts) => {
|
|
401
325
|
try {
|
|
402
|
-
const workspace =
|
|
403
|
-
if (!
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (!targetDID || !targetDID.startsWith("did:")) {
|
|
407
|
-
return { text: "Usage: `/storacha-grant <did:key:z...>`" };
|
|
326
|
+
const { agentId, workspace } = requireAgent(opts.agent);
|
|
327
|
+
if (!targetDID.startsWith("did:")) {
|
|
328
|
+
console.error("Error: target must be a DID (did:key:z...)");
|
|
329
|
+
process.exit(1);
|
|
408
330
|
}
|
|
409
|
-
const
|
|
410
|
-
if (!
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
};
|
|
331
|
+
const deviceConfig = await loadDeviceConfig(workspace);
|
|
332
|
+
if (!deviceConfig) {
|
|
333
|
+
console.error(`Not initialized. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
|
|
334
|
+
process.exit(1);
|
|
414
335
|
}
|
|
415
336
|
const results = [];
|
|
416
337
|
// Re-delegate upload capability
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
catch (err) {
|
|
434
|
-
results.push(`\u274c Failed to create upload delegation: ${err.message}`);
|
|
338
|
+
if (deviceConfig.uploadDelegation) {
|
|
339
|
+
const storachaClient = await createStorachaClient(deviceConfig);
|
|
340
|
+
const audience = {
|
|
341
|
+
did: () => targetDID,
|
|
342
|
+
};
|
|
343
|
+
const uploadDel = await storachaClient.createDelegation(audience, [
|
|
344
|
+
"space/blob/add",
|
|
345
|
+
"space/index/add",
|
|
346
|
+
"upload/add",
|
|
347
|
+
"filecoin/offer",
|
|
348
|
+
]);
|
|
349
|
+
const { ok: archiveBytes } = await uploadDel.archive();
|
|
350
|
+
if (archiveBytes) {
|
|
351
|
+
results.push(`Upload delegation:\n${encodeDelegation(archiveBytes)}`);
|
|
435
352
|
}
|
|
436
353
|
}
|
|
437
354
|
else {
|
|
438
|
-
results.push("
|
|
355
|
+
results.push("⚠️ No upload delegation to re-delegate.");
|
|
439
356
|
}
|
|
440
|
-
// Re-delegate name
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
name = Name.from(agent, [nameDel]);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
if (name) {
|
|
461
|
-
const nameDel = await name.grant(targetDID);
|
|
462
|
-
const { ok: archiveBytes } = await nameDel.archive();
|
|
463
|
-
if (archiveBytes) {
|
|
464
|
-
const b64 = encodeDelegation(archiveBytes);
|
|
465
|
-
results.push("**Name delegation:**\n```\n" + b64 + "\n```");
|
|
466
|
-
}
|
|
357
|
+
// Re-delegate name capability
|
|
358
|
+
if (deviceConfig.nameDelegation) {
|
|
359
|
+
const { Agent, Name } = await import("@storacha/ucn/pail");
|
|
360
|
+
const { extract } = await import("@storacha/client/delegation");
|
|
361
|
+
const agent = Agent.parse(deviceConfig.agentKey);
|
|
362
|
+
let name;
|
|
363
|
+
if (deviceConfig.nameArchive) {
|
|
364
|
+
const archiveBytes = decodeDelegation(deviceConfig.nameArchive);
|
|
365
|
+
name = await Name.extract(agent, archiveBytes);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
const nameBytes = decodeDelegation(deviceConfig.nameDelegation);
|
|
369
|
+
const { ok: nameDel } = await extract(nameBytes);
|
|
370
|
+
if (nameDel) {
|
|
371
|
+
name = Name.from(agent, [nameDel]);
|
|
467
372
|
}
|
|
468
373
|
}
|
|
469
|
-
|
|
470
|
-
|
|
374
|
+
if (name) {
|
|
375
|
+
const nameDel = await name.grant(targetDID);
|
|
376
|
+
const { ok: archiveBytes } = await nameDel.archive();
|
|
377
|
+
if (archiveBytes) {
|
|
378
|
+
results.push(`Name delegation:\n${encodeDelegation(archiveBytes)}`);
|
|
379
|
+
}
|
|
471
380
|
}
|
|
472
381
|
}
|
|
473
382
|
else {
|
|
474
|
-
results.push("
|
|
383
|
+
results.push("⚠️ No name delegation to re-delegate.");
|
|
475
384
|
}
|
|
476
|
-
|
|
477
|
-
|
|
385
|
+
console.log(`🔥 Delegations for ${targetDID}:\n`);
|
|
386
|
+
for (const r of results) {
|
|
387
|
+
console.log(r);
|
|
388
|
+
console.log();
|
|
478
389
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
"",
|
|
483
|
-
...results,
|
|
484
|
-
"",
|
|
485
|
-
"The target device should run:",
|
|
486
|
-
"`/storacha-join <upload-b64> <name-b64>`",
|
|
487
|
-
].join("\n"),
|
|
488
|
-
};
|
|
390
|
+
console.log("The target device should run:");
|
|
391
|
+
console.log(` openclaw clawracha join <upload-delegation> <name-delegation> --agent <id>`);
|
|
392
|
+
console.log("\nThen restart the gateway: `openclaw gateway restart`");
|
|
489
393
|
}
|
|
490
394
|
catch (err) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
};
|
|
395
|
+
console.error(`Error: ${err.message}`);
|
|
396
|
+
process.exit(1);
|
|
494
397
|
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
398
|
+
});
|
|
399
|
+
// --- status ---
|
|
400
|
+
clawracha
|
|
401
|
+
.command("status")
|
|
402
|
+
.description("Show Storacha sync status for an agent workspace")
|
|
403
|
+
.requiredOption("--agent <id>", "Agent ID")
|
|
404
|
+
.action(async (opts) => {
|
|
501
405
|
try {
|
|
502
|
-
const workspace =
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
406
|
+
const { agentId, workspace } = requireAgent(opts.agent);
|
|
407
|
+
const deviceConfig = await loadDeviceConfig(workspace);
|
|
408
|
+
if (!deviceConfig) {
|
|
409
|
+
console.log(`Not initialized. Run \`openclaw clawracha init --agent ${agentId}\` first.`);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
console.log(`🔥 Storacha Sync Status [${agentId}]`);
|
|
413
|
+
console.log(`Workspace: ${workspace}`);
|
|
414
|
+
console.log(`Upload delegation: ${deviceConfig.uploadDelegation ? "✅" : "❌ not set"}`);
|
|
415
|
+
console.log(`Name delegation: ${deviceConfig.nameDelegation ? "✅" : "❌ not set"}`);
|
|
416
|
+
console.log(`Space DID: ${deviceConfig.spaceDID ?? "unknown"}`);
|
|
417
|
+
console.log(`Name Archive: ${deviceConfig.nameArchive ? "saved" : "not created"}`);
|
|
418
|
+
console.log(`Setup complete: ${deviceConfig.setupComplete ? "✅" : "❌"}`);
|
|
419
|
+
const sync = activeSyncers.get(workspace);
|
|
420
|
+
if (sync) {
|
|
421
|
+
const status = await sync.engine.status();
|
|
422
|
+
console.log(`Running: true`);
|
|
423
|
+
console.log(`Last Sync: ${status.lastSync ? new Date(status.lastSync).toISOString() : "never"}`);
|
|
424
|
+
console.log(`Entries: ${status.entryCount}`);
|
|
425
|
+
console.log(`Pending: ${status.pendingChanges}`);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
console.log(`Running: false`);
|
|
524
429
|
}
|
|
525
|
-
return { text: lines.join("\n") };
|
|
526
430
|
}
|
|
527
431
|
catch (err) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
};
|
|
432
|
+
console.error(`Error: ${err.message}`);
|
|
433
|
+
process.exit(1);
|
|
531
434
|
}
|
|
532
|
-
}
|
|
533
|
-
});
|
|
435
|
+
});
|
|
436
|
+
}, { commands: ["clawracha"] });
|
|
534
437
|
}
|
|
@@ -15,4 +15,9 @@ export declare function encodeDelegation(archiveBytes: Uint8Array): string;
|
|
|
15
15
|
* base64 string → CID → identity multihash digest → bytes
|
|
16
16
|
*/
|
|
17
17
|
export declare function decodeDelegation(encoded: string): Uint8Array;
|
|
18
|
+
/**
|
|
19
|
+
* Read a delegation from either a file path (raw CAR) or a CID string (base64).
|
|
20
|
+
* Returns the extracted delegation.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readDelegationArg(input: string): Promise<import("@ucanto/interface").Delegation<import("@ipld/dag-ucan").Capabilities>>;
|
|
18
23
|
//# sourceMappingURL=delegation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../../src/utils/delegation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../../src/utils/delegation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,UAAU,GAAG,MAAM,CAGjE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ5D;AAGD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,0FAcpD"}
|
package/dist/utils/delegation.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* and identity multihash, encoded as base64. This matches the format
|
|
6
6
|
* used by the Storacha CLI (`storacha delegation create --base64`).
|
|
7
7
|
*/
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
8
9
|
import { CID } from "multiformats/cid";
|
|
10
|
+
import { extract } from "@storacha/client/delegation";
|
|
9
11
|
import { base64 } from "multiformats/bases/base64";
|
|
10
12
|
import { identity } from "multiformats/hashes/identity";
|
|
11
13
|
/** CAR codec code (multicodec 0x0202) */
|
|
@@ -29,3 +31,23 @@ export function decodeDelegation(encoded) {
|
|
|
29
31
|
}
|
|
30
32
|
return cid.multihash.digest;
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Read a delegation from either a file path (raw CAR) or a CID string (base64).
|
|
36
|
+
* Returns the extracted delegation.
|
|
37
|
+
*/
|
|
38
|
+
export async function readDelegationArg(input) {
|
|
39
|
+
let bytes;
|
|
40
|
+
try {
|
|
41
|
+
// Try reading as file first
|
|
42
|
+
bytes = await fs.readFile(input);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Not a file — treat as CID string
|
|
46
|
+
bytes = decodeDelegation(input);
|
|
47
|
+
}
|
|
48
|
+
const { ok: delegation, error } = await extract(bytes);
|
|
49
|
+
if (!delegation) {
|
|
50
|
+
throw new Error(`Invalid delegation: ${error}`);
|
|
51
|
+
}
|
|
52
|
+
return delegation;
|
|
53
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent workspace resolution.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors OpenClaw's resolveAgentWorkspaceDir logic from
|
|
5
|
+
* src/agents/agent-scope.ts — resolves workspace dir for a given agent ID.
|
|
6
|
+
*/
|
|
7
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the workspace directory for an agent by ID.
|
|
10
|
+
* Matches OpenClaw's resolution order:
|
|
11
|
+
* 1. Agent-specific workspace from config
|
|
12
|
+
* 2. For default agent: agents.defaults.workspace or ~/.openclaw/workspace
|
|
13
|
+
* 3. For other agents: ~/.openclaw/workspace-{agentId}
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveAgentWorkspace(config: OpenClawConfig, agentId: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Get all agent IDs from config.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getAgentIds(config: OpenClawConfig): string[];
|
|
20
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/utils/workspace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,MAAM,GACd,MAAM,CAsBR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,CAQ5D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent workspace resolution.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors OpenClaw's resolveAgentWorkspaceDir logic from
|
|
5
|
+
* src/agents/agent-scope.ts — resolves workspace dir for a given agent ID.
|
|
6
|
+
*/
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the workspace directory for an agent by ID.
|
|
11
|
+
* Matches OpenClaw's resolution order:
|
|
12
|
+
* 1. Agent-specific workspace from config
|
|
13
|
+
* 2. For default agent: agents.defaults.workspace or ~/.openclaw/workspace
|
|
14
|
+
* 3. For other agents: ~/.openclaw/workspace-{agentId}
|
|
15
|
+
*/
|
|
16
|
+
export function resolveAgentWorkspace(config, agentId) {
|
|
17
|
+
const agent = config.agents?.list?.find((a) => a.id.toLowerCase() === agentId.toLowerCase());
|
|
18
|
+
// Agent-specific workspace
|
|
19
|
+
if (agent?.workspace?.trim()) {
|
|
20
|
+
return resolveUserPath(agent.workspace.trim());
|
|
21
|
+
}
|
|
22
|
+
// Default agent gets the default workspace
|
|
23
|
+
const defaultId = resolveDefaultAgentId(config);
|
|
24
|
+
if (agentId.toLowerCase() === defaultId.toLowerCase()) {
|
|
25
|
+
const fallback = config.agents?.defaults?.workspace?.trim();
|
|
26
|
+
if (fallback) {
|
|
27
|
+
return resolveUserPath(fallback);
|
|
28
|
+
}
|
|
29
|
+
return path.join(resolveStateDir(), "workspace");
|
|
30
|
+
}
|
|
31
|
+
// Other agents: workspace-{id}
|
|
32
|
+
return path.join(resolveStateDir(), `workspace-${agentId}`);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all agent IDs from config.
|
|
36
|
+
*/
|
|
37
|
+
export function getAgentIds(config) {
|
|
38
|
+
const list = config.agents?.list;
|
|
39
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
40
|
+
return ["default"];
|
|
41
|
+
}
|
|
42
|
+
return list
|
|
43
|
+
.filter((a) => a && typeof a === "object" && a.id)
|
|
44
|
+
.map((a) => a.id);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the default agent ID from config.
|
|
48
|
+
*/
|
|
49
|
+
function resolveDefaultAgentId(config) {
|
|
50
|
+
const list = config.agents?.list;
|
|
51
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
52
|
+
return "default";
|
|
53
|
+
}
|
|
54
|
+
const defaultAgent = list.find((a) => a.default);
|
|
55
|
+
return (defaultAgent ?? list[0])?.id?.trim() || "default";
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve OpenClaw state directory.
|
|
59
|
+
*/
|
|
60
|
+
function resolveStateDir() {
|
|
61
|
+
return (process.env.OPENCLAW_STATE_DIR ||
|
|
62
|
+
path.join(os.homedir(), ".openclaw"));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Expand ~ in paths.
|
|
66
|
+
*/
|
|
67
|
+
function resolveUserPath(p) {
|
|
68
|
+
if (p.startsWith("~/")) {
|
|
69
|
+
return path.join(os.homedir(), p.slice(2));
|
|
70
|
+
}
|
|
71
|
+
return p;
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storacha/clawracha",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "OpenClaw plugin for Storacha workspace sync via UCN Pail",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -36,7 +36,7 @@
|
|
|
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.
|
|
39
|
+
"multiformats": "^13.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@ipld/unixfs": "^3.0.0",
|