@xfxstudio/claworld 0.2.12 → 0.2.13
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 +45 -19
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -5
- package/skills/claworld-help/SKILL.md +84 -83
- package/skills/claworld-join-and-chat/SKILL.md +1 -1
- package/src/openclaw/plugin/onboarding.js +128 -103
- package/src/product-shell/agent-cards/spec-builder.js +2 -2
- package/src/product-shell/onboarding/onboarding-service.js +27 -25
- package/bin/claworld.mjs +0 -9
- package/src/openclaw/installer/cli.js +0 -406
- package/src/openclaw/installer/core.js +0 -2122
- package/src/openclaw/installer/doctor.js +0 -876
- package/src/openclaw/installer/workspace-contract.js +0 -427
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
import crypto from 'crypto';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import {
|
|
6
|
-
buildWorkspaceAgentsContent,
|
|
7
|
-
buildWorkspaceMemoryContent,
|
|
8
|
-
expandUserPath,
|
|
9
|
-
normalizeText,
|
|
10
|
-
} from '../plugin/managed-config.js';
|
|
11
|
-
|
|
12
|
-
export const CLAWORLD_MANAGED_WORKSPACE_METADATA_FILE = '.claworld-managed.json';
|
|
13
|
-
export const CLAWORLD_MANAGED_WORKSPACE_CONTRACT_VERSION = 1;
|
|
14
|
-
|
|
15
|
-
function resolveHomeDir(homeDir = null) {
|
|
16
|
-
const explicitHomeDir = normalizeText(homeDir, null);
|
|
17
|
-
if (explicitHomeDir) {
|
|
18
|
-
return explicitHomeDir;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const envHomeDir = normalizeText(
|
|
22
|
-
process.env.HOME,
|
|
23
|
-
normalizeText(
|
|
24
|
-
process.env.USERPROFILE,
|
|
25
|
-
normalizeText(
|
|
26
|
-
process.env.HOMEDRIVE && process.env.HOMEPATH
|
|
27
|
-
? path.join(process.env.HOMEDRIVE, process.env.HOMEPATH)
|
|
28
|
-
: null,
|
|
29
|
-
null,
|
|
30
|
-
),
|
|
31
|
-
),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
return envHomeDir || os.homedir();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function createHash(content) {
|
|
38
|
-
return crypto.createHash('sha256').update(String(content || ''), 'utf8').digest('hex');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function readTextIfPresent(filePath) {
|
|
42
|
-
try {
|
|
43
|
-
return await fs.readFile(filePath, 'utf8');
|
|
44
|
-
} catch (error) {
|
|
45
|
-
if (error && error.code === 'ENOENT') return null;
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function createWorkspaceFileSpec(relativePath, policy, content) {
|
|
51
|
-
return {
|
|
52
|
-
relativePath,
|
|
53
|
-
policy,
|
|
54
|
-
content,
|
|
55
|
-
expectedHash: createHash(content),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function normalizeMetadata(source = {}) {
|
|
60
|
-
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const files = source.files && typeof source.files === 'object' && !Array.isArray(source.files)
|
|
64
|
-
? source.files
|
|
65
|
-
: {};
|
|
66
|
-
return {
|
|
67
|
-
channel: normalizeText(source.channel, null),
|
|
68
|
-
contractVersion: Number(source.contractVersion) || null,
|
|
69
|
-
workspaceOwner: normalizeText(source.workspaceOwner, null),
|
|
70
|
-
accountId: normalizeText(source.accountId, null),
|
|
71
|
-
updatedAt: normalizeText(source.updatedAt, null),
|
|
72
|
-
files,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function resolveManagedWorkspacePath(workspace, homeDir = null) {
|
|
77
|
-
return path.resolve(expandUserPath(workspace, resolveHomeDir(homeDir)));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function buildManagedWorkspaceContract(options = {}, homeDir = null) {
|
|
81
|
-
const resolvedHomeDir = resolveHomeDir(homeDir);
|
|
82
|
-
const workspacePath = resolveManagedWorkspacePath(options.workspace, resolvedHomeDir);
|
|
83
|
-
const files = [
|
|
84
|
-
createWorkspaceFileSpec('AGENTS.md', 'refreshable', buildWorkspaceAgentsContent(options)),
|
|
85
|
-
createWorkspaceFileSpec('MEMORY.md', 'durable', buildWorkspaceMemoryContent(options)),
|
|
86
|
-
];
|
|
87
|
-
return {
|
|
88
|
-
channel: 'claworld',
|
|
89
|
-
contractVersion: CLAWORLD_MANAGED_WORKSPACE_CONTRACT_VERSION,
|
|
90
|
-
workspacePath,
|
|
91
|
-
metadataPath: path.join(workspacePath, CLAWORLD_MANAGED_WORKSPACE_METADATA_FILE),
|
|
92
|
-
workspaceOwner: normalizeText(options.agentId, null),
|
|
93
|
-
accountId: normalizeText(options.accountId, null),
|
|
94
|
-
files: files.map((file) => ({
|
|
95
|
-
...file,
|
|
96
|
-
absolutePath: path.join(workspacePath, file.relativePath),
|
|
97
|
-
})),
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export async function loadManagedWorkspaceMetadata(workspacePath, homeDir = null) {
|
|
102
|
-
const resolvedWorkspacePath = resolveManagedWorkspacePath(workspacePath, homeDir);
|
|
103
|
-
const metadataPath = path.join(resolvedWorkspacePath, CLAWORLD_MANAGED_WORKSPACE_METADATA_FILE);
|
|
104
|
-
const raw = await readTextIfPresent(metadataPath);
|
|
105
|
-
if (raw == null) {
|
|
106
|
-
return {
|
|
107
|
-
exists: false,
|
|
108
|
-
metadataPath,
|
|
109
|
-
value: null,
|
|
110
|
-
parseError: null,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const parsed = JSON.parse(raw);
|
|
115
|
-
return {
|
|
116
|
-
exists: true,
|
|
117
|
-
metadataPath,
|
|
118
|
-
value: normalizeMetadata(parsed),
|
|
119
|
-
parseError: null,
|
|
120
|
-
};
|
|
121
|
-
} catch (error) {
|
|
122
|
-
return {
|
|
123
|
-
exists: true,
|
|
124
|
-
metadataPath,
|
|
125
|
-
value: null,
|
|
126
|
-
parseError: error,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function toMetadataFileState({
|
|
132
|
-
file,
|
|
133
|
-
managedHash,
|
|
134
|
-
observedHash,
|
|
135
|
-
state,
|
|
136
|
-
}) {
|
|
137
|
-
return {
|
|
138
|
-
policy: file.policy,
|
|
139
|
-
expectedHash: file.expectedHash,
|
|
140
|
-
managedHash,
|
|
141
|
-
observedHash,
|
|
142
|
-
state,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function evaluateRefreshableFile({
|
|
147
|
-
file,
|
|
148
|
-
currentContent,
|
|
149
|
-
previousRecord = null,
|
|
150
|
-
}) {
|
|
151
|
-
if (currentContent == null) {
|
|
152
|
-
return {
|
|
153
|
-
action: 'create',
|
|
154
|
-
nextContent: file.content,
|
|
155
|
-
nextRecord: toMetadataFileState({
|
|
156
|
-
file,
|
|
157
|
-
managedHash: file.expectedHash,
|
|
158
|
-
observedHash: file.expectedHash,
|
|
159
|
-
state: 'managed_current',
|
|
160
|
-
}),
|
|
161
|
-
inspectState: 'missing',
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const currentHash = createHash(currentContent);
|
|
166
|
-
if (currentHash === file.expectedHash) {
|
|
167
|
-
return {
|
|
168
|
-
action: 'keep',
|
|
169
|
-
nextContent: null,
|
|
170
|
-
nextRecord: toMetadataFileState({
|
|
171
|
-
file,
|
|
172
|
-
managedHash: file.expectedHash,
|
|
173
|
-
observedHash: currentHash,
|
|
174
|
-
state: 'managed_current',
|
|
175
|
-
}),
|
|
176
|
-
inspectState: 'managed_current',
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (previousRecord?.managedHash && currentHash === previousRecord.managedHash) {
|
|
181
|
-
return {
|
|
182
|
-
action: 'refresh',
|
|
183
|
-
nextContent: file.content,
|
|
184
|
-
nextRecord: toMetadataFileState({
|
|
185
|
-
file,
|
|
186
|
-
managedHash: file.expectedHash,
|
|
187
|
-
observedHash: file.expectedHash,
|
|
188
|
-
state: 'managed_current',
|
|
189
|
-
}),
|
|
190
|
-
inspectState: 'managed_stale',
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
action: 'preserve',
|
|
196
|
-
nextContent: null,
|
|
197
|
-
nextRecord: toMetadataFileState({
|
|
198
|
-
file,
|
|
199
|
-
managedHash: previousRecord?.managedHash || null,
|
|
200
|
-
observedHash: currentHash,
|
|
201
|
-
state: 'customized',
|
|
202
|
-
}),
|
|
203
|
-
inspectState: 'customized',
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function evaluateDurableFile({
|
|
208
|
-
file,
|
|
209
|
-
currentContent,
|
|
210
|
-
previousRecord = null,
|
|
211
|
-
}) {
|
|
212
|
-
if (currentContent == null) {
|
|
213
|
-
return {
|
|
214
|
-
action: 'create',
|
|
215
|
-
nextContent: file.content,
|
|
216
|
-
nextRecord: toMetadataFileState({
|
|
217
|
-
file,
|
|
218
|
-
managedHash: file.expectedHash,
|
|
219
|
-
observedHash: file.expectedHash,
|
|
220
|
-
state: 'durable_present',
|
|
221
|
-
}),
|
|
222
|
-
inspectState: 'missing',
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const currentHash = createHash(currentContent);
|
|
227
|
-
return {
|
|
228
|
-
action: 'preserve',
|
|
229
|
-
nextContent: null,
|
|
230
|
-
nextRecord: toMetadataFileState({
|
|
231
|
-
file,
|
|
232
|
-
managedHash: previousRecord?.managedHash || (currentHash === file.expectedHash ? file.expectedHash : null),
|
|
233
|
-
observedHash: currentHash,
|
|
234
|
-
state: currentHash === file.expectedHash ? 'durable_present' : 'durable_customized',
|
|
235
|
-
}),
|
|
236
|
-
inspectState: currentHash === file.expectedHash ? 'durable_present' : 'durable_customized',
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export async function seedManagedWorkspaceContract(options = {}, dryRun = false, homeDir = null) {
|
|
241
|
-
const resolvedHomeDir = resolveHomeDir(homeDir);
|
|
242
|
-
const contract = buildManagedWorkspaceContract(options, resolvedHomeDir);
|
|
243
|
-
const agentDirPath = options.agentDirExplicit && options.agentDir
|
|
244
|
-
? path.resolve(expandUserPath(options.agentDir, resolvedHomeDir))
|
|
245
|
-
: null;
|
|
246
|
-
const metadataState = await loadManagedWorkspaceMetadata(contract.workspacePath, resolvedHomeDir);
|
|
247
|
-
const previousFiles = metadataState.value?.files || {};
|
|
248
|
-
const actions = [];
|
|
249
|
-
|
|
250
|
-
if (dryRun) {
|
|
251
|
-
actions.push(`mkdir -p ${contract.workspacePath}`);
|
|
252
|
-
if (agentDirPath) {
|
|
253
|
-
actions.push(`mkdir -p ${agentDirPath}`);
|
|
254
|
-
}
|
|
255
|
-
for (const file of contract.files) {
|
|
256
|
-
actions.push(`${file.policy === 'refreshable' ? 'refresh' : 'seed'} ${file.absolutePath} when contract allows`);
|
|
257
|
-
}
|
|
258
|
-
actions.push(`write ${contract.metadataPath}`);
|
|
259
|
-
return actions;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
await fs.mkdir(contract.workspacePath, { recursive: true });
|
|
263
|
-
actions.push(`ensured ${contract.workspacePath}`);
|
|
264
|
-
if (agentDirPath) {
|
|
265
|
-
await fs.mkdir(agentDirPath, { recursive: true });
|
|
266
|
-
actions.push(`ensured ${agentDirPath}`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const nextMetadata = {
|
|
270
|
-
channel: contract.channel,
|
|
271
|
-
contractVersion: contract.contractVersion,
|
|
272
|
-
workspaceOwner: contract.workspaceOwner,
|
|
273
|
-
accountId: contract.accountId,
|
|
274
|
-
updatedAt: new Date().toISOString(),
|
|
275
|
-
files: {},
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
for (const file of contract.files) {
|
|
279
|
-
const currentContent = await readTextIfPresent(file.absolutePath);
|
|
280
|
-
const previousRecord = previousFiles[file.relativePath] || null;
|
|
281
|
-
const decision = file.policy === 'refreshable'
|
|
282
|
-
? evaluateRefreshableFile({ file, currentContent, previousRecord })
|
|
283
|
-
: evaluateDurableFile({ file, currentContent, previousRecord });
|
|
284
|
-
|
|
285
|
-
if (decision.nextContent != null) {
|
|
286
|
-
await fs.writeFile(file.absolutePath, decision.nextContent, 'utf8');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (decision.action === 'create') {
|
|
290
|
-
actions.push(`created ${file.absolutePath}`);
|
|
291
|
-
} else if (decision.action === 'refresh') {
|
|
292
|
-
actions.push(`refreshed ${file.absolutePath}`);
|
|
293
|
-
} else if (decision.action === 'preserve' && file.policy === 'durable') {
|
|
294
|
-
actions.push(`kept durable ${file.absolutePath}`);
|
|
295
|
-
} else if (decision.action === 'preserve') {
|
|
296
|
-
actions.push(`preserved customized ${file.absolutePath}`);
|
|
297
|
-
} else {
|
|
298
|
-
actions.push(`kept current ${file.absolutePath}`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
nextMetadata.files[file.relativePath] = decision.nextRecord;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
await fs.writeFile(contract.metadataPath, `${JSON.stringify(nextMetadata, null, 2)}\n`, 'utf8');
|
|
305
|
-
actions.push(`wrote ${contract.metadataPath}`);
|
|
306
|
-
return actions;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export async function inspectManagedWorkspaceContract(options = {}, homeDir = os.homedir()) {
|
|
310
|
-
const contract = buildManagedWorkspaceContract(options, homeDir);
|
|
311
|
-
const metadataState = await loadManagedWorkspaceMetadata(contract.workspacePath, homeDir);
|
|
312
|
-
const files = [];
|
|
313
|
-
const issues = [];
|
|
314
|
-
let workspaceExists = true;
|
|
315
|
-
|
|
316
|
-
try {
|
|
317
|
-
const stat = await fs.stat(contract.workspacePath);
|
|
318
|
-
workspaceExists = stat.isDirectory();
|
|
319
|
-
} catch (error) {
|
|
320
|
-
if (error && error.code === 'ENOENT') {
|
|
321
|
-
workspaceExists = false;
|
|
322
|
-
} else {
|
|
323
|
-
throw error;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (!workspaceExists) {
|
|
328
|
-
issues.push({
|
|
329
|
-
code: 'workspace_missing',
|
|
330
|
-
message: `Managed workspace is missing: ${contract.workspacePath}`,
|
|
331
|
-
severity: 'fail',
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (!metadataState.exists) {
|
|
336
|
-
issues.push({
|
|
337
|
-
code: 'workspace_metadata_missing',
|
|
338
|
-
message: `Managed workspace metadata is missing: ${contract.metadataPath}`,
|
|
339
|
-
severity: 'fail',
|
|
340
|
-
});
|
|
341
|
-
} else if (metadataState.parseError || !metadataState.value) {
|
|
342
|
-
issues.push({
|
|
343
|
-
code: 'workspace_metadata_invalid',
|
|
344
|
-
message: `Managed workspace metadata is invalid: ${contract.metadataPath}`,
|
|
345
|
-
severity: 'fail',
|
|
346
|
-
});
|
|
347
|
-
} else if (metadataState.value.contractVersion !== contract.contractVersion) {
|
|
348
|
-
issues.push({
|
|
349
|
-
code: 'workspace_metadata_version_mismatch',
|
|
350
|
-
message: `Managed workspace metadata version is ${metadataState.value.contractVersion}; expected ${contract.contractVersion}.`,
|
|
351
|
-
severity: 'warn',
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const metadataFiles = metadataState.value?.files || {};
|
|
356
|
-
for (const file of contract.files) {
|
|
357
|
-
const currentContent = await readTextIfPresent(file.absolutePath);
|
|
358
|
-
const currentHash = currentContent == null ? null : createHash(currentContent);
|
|
359
|
-
const metadataFile = metadataFiles[file.relativePath] || null;
|
|
360
|
-
let state = 'missing';
|
|
361
|
-
let healthy = false;
|
|
362
|
-
let message = `${file.relativePath} is missing.`;
|
|
363
|
-
|
|
364
|
-
if (currentContent != null) {
|
|
365
|
-
if (file.policy === 'durable') {
|
|
366
|
-
state = currentHash === file.expectedHash ? 'durable_present' : 'durable_customized';
|
|
367
|
-
healthy = true;
|
|
368
|
-
message = state === 'durable_present'
|
|
369
|
-
? `${file.relativePath} matches the durable seed.`
|
|
370
|
-
: `${file.relativePath} exists and is protected durable state.`;
|
|
371
|
-
} else if (currentHash === file.expectedHash) {
|
|
372
|
-
state = 'managed_current';
|
|
373
|
-
healthy = true;
|
|
374
|
-
message = `${file.relativePath} matches the managed seed.`;
|
|
375
|
-
} else if (metadataFile?.managedHash && currentHash === metadataFile.managedHash) {
|
|
376
|
-
state = 'managed_stale';
|
|
377
|
-
message = `${file.relativePath} still matches an older managed seed and can be refreshed.`;
|
|
378
|
-
issues.push({
|
|
379
|
-
code: 'workspace_refreshable_stale',
|
|
380
|
-
message,
|
|
381
|
-
severity: 'warn',
|
|
382
|
-
file: file.relativePath,
|
|
383
|
-
});
|
|
384
|
-
} else {
|
|
385
|
-
state = 'customized';
|
|
386
|
-
message = `${file.relativePath} differs from the managed seed and was preserved.`;
|
|
387
|
-
issues.push({
|
|
388
|
-
code: 'workspace_refreshable_customized',
|
|
389
|
-
message,
|
|
390
|
-
severity: 'warn',
|
|
391
|
-
file: file.relativePath,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
issues.push({
|
|
396
|
-
code: 'workspace_seed_file_missing',
|
|
397
|
-
message,
|
|
398
|
-
severity: 'fail',
|
|
399
|
-
file: file.relativePath,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
files.push({
|
|
404
|
-
relativePath: file.relativePath,
|
|
405
|
-
absolutePath: file.absolutePath,
|
|
406
|
-
policy: file.policy,
|
|
407
|
-
exists: currentContent != null,
|
|
408
|
-
state,
|
|
409
|
-
healthy,
|
|
410
|
-
expectedHash: file.expectedHash,
|
|
411
|
-
observedHash: currentHash,
|
|
412
|
-
metadata: metadataFile,
|
|
413
|
-
message,
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return {
|
|
418
|
-
ok: issues.every((issue) => issue.severity !== 'fail'),
|
|
419
|
-
workspaceExists,
|
|
420
|
-
workspacePath: contract.workspacePath,
|
|
421
|
-
metadataPath: contract.metadataPath,
|
|
422
|
-
contractVersion: contract.contractVersion,
|
|
423
|
-
metadata: metadataState,
|
|
424
|
-
files,
|
|
425
|
-
issues,
|
|
426
|
-
};
|
|
427
|
-
}
|