velaclaw-dev 0.2.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.
Files changed (63) hide show
  1. package/.gitignore +14 -0
  2. package/ARCHITECTURE.md +143 -0
  3. package/README.dev.md +208 -0
  4. package/README.local-before-remote-sync.md +224 -0
  5. package/README.md +211 -0
  6. package/README.public.md +115 -0
  7. package/RELEASING.md +162 -0
  8. package/TESTING.md +195 -0
  9. package/dist/cli.js +213 -0
  10. package/dist/data.js +2988 -0
  11. package/dist/server.js +1020 -0
  12. package/dist/ui.js +1486 -0
  13. package/members/LAUNCH_CHECKLIST.md +13 -0
  14. package/members/README.md +17 -0
  15. package/members/member-template/README.md +9 -0
  16. package/members/member-template/private-docs/README.md +3 -0
  17. package/members/member-template/private-memory/README.md +3 -0
  18. package/members/member-template/private-skills/README.md +4 -0
  19. package/members/member-template/private-tools/README.md +4 -0
  20. package/members/member-template/runtime/config/README.md +3 -0
  21. package/members/member-template/runtime/config/local-plugins/member-quota-guard/index.js +123 -0
  22. package/members/member-template/runtime/config/local-plugins/member-quota-guard/openclaw.plugin.json +19 -0
  23. package/members/member-template/runtime/config/local-plugins/member-quota-guard/package.json +10 -0
  24. package/members/member-template/runtime/config/local-plugins/member-runtime-upgrader/index.js +97 -0
  25. package/members/member-template/runtime/config/local-plugins/member-runtime-upgrader/openclaw.plugin.json +21 -0
  26. package/members/member-template/runtime/config/local-plugins/member-runtime-upgrader/package.json +10 -0
  27. package/members/member-template/runtime/config/local-plugins/shared-asset-injector/index.js +548 -0
  28. package/members/member-template/runtime/config/local-plugins/shared-asset-injector/openclaw.plugin.json +33 -0
  29. package/members/member-template/runtime/config/local-plugins/shared-asset-injector/package.json +10 -0
  30. package/members/member-template/runtime/config/openclaw.json +104 -0
  31. package/members/member-template/runtime/docker-compose.yml +53 -0
  32. package/members/member-template/runtime/logs/README.md +3 -0
  33. package/members/member-template/runtime/secrets/.gitkeep +1 -0
  34. package/members/member-template/runtime/secrets/README.md +3 -0
  35. package/members/member-template/runtime/workspace/.gitkeep +1 -0
  36. package/members/member-template/runtime/workspace/README.md +3 -0
  37. package/package.json +57 -0
  38. package/pic/banner.jpg +0 -0
  39. package/provision-member.md +87 -0
  40. package/scripts/shared-asset-stack-test.mjs +369 -0
  41. package/scripts/shared-skill-combo-test.mjs +282 -0
  42. package/scripts/team-load-test.mjs +358 -0
  43. package/scripts/verify-install.mjs +44 -0
  44. package/services/litellm/config.yaml +35 -0
  45. package/services/litellm/docker-compose.yml +36 -0
  46. package/services/litellm/litellm.env.example +13 -0
  47. package/shared-snapshots/README.md +16 -0
  48. package/shared-snapshots/docs/README.md +3 -0
  49. package/shared-snapshots/memory/README.md +3 -0
  50. package/shared-snapshots/skills/README.md +3 -0
  51. package/shared-snapshots/tools/README.md +4 -0
  52. package/shared-snapshots/workflows/README.md +3 -0
  53. package/team-assets/README.md +11 -0
  54. package/team-assets/policies/README.md +7 -0
  55. package/team-assets/policies/asset-visibility.md +24 -0
  56. package/team-assets/policies/high-risk-action-approval.md +18 -0
  57. package/team-assets/policies/promotion-rules.md +25 -0
  58. package/team-assets/policies/tool-binding-rules.md +26 -0
  59. package/team-assets/shared-docs/README.md +3 -0
  60. package/team-assets/shared-memory/README.md +8 -0
  61. package/team-assets/shared-skills/README.md +8 -0
  62. package/team-assets/shared-tools/README.md +8 -0
  63. package/team-assets/shared-workflows/README.md +9 -0
@@ -0,0 +1,548 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+
6
+ let definePluginEntry = (entry) => entry;
7
+ try {
8
+ const runtimeRequire = createRequire("/app/package.json");
9
+ ({ definePluginEntry } = runtimeRequire("openclaw/plugin-sdk/plugin-entry"));
10
+ } catch {}
11
+
12
+ const DEFAULT_WORKSPACE_ROOT = "/home/node/.openclaw/workspace";
13
+ const DEFAULT_STATE_PATH = "/home/node/.openclaw/shared-assets-state.json";
14
+ const DEFAULT_SYNC_TTL_MS = 30000;
15
+ const DEFAULT_RESOLVE_LIMIT_PER_KIND = 2;
16
+ const ASSET_KINDS = ["skills", "memory", "workflows", "docs"];
17
+
18
+ function asTrimmedString(value) {
19
+ return typeof value === "string" ? value.trim() : "";
20
+ }
21
+
22
+ async function safeReadJson(filePath, fallback) {
23
+ try {
24
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
25
+ } catch {
26
+ return fallback;
27
+ }
28
+ }
29
+
30
+ async function safeWriteJson(filePath, value) {
31
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
32
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
33
+ }
34
+
35
+ function slugify(value) {
36
+ return String(value || "")
37
+ .trim()
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9]+/g, "-")
40
+ .replace(/^-+|-+$/g, "")
41
+ .slice(0, 80) || "asset";
42
+ }
43
+
44
+ function wrapSkillDoc(name, description, content) {
45
+ const trimmed = String(content || "").trim();
46
+ if (trimmed.startsWith("---")) {
47
+ return `${trimmed}\n`;
48
+ }
49
+ return `---\nname: ${name}\ndescription: ${description}\n---\n\n${trimmed}\n`;
50
+ }
51
+
52
+ function summarize(content, fallback) {
53
+ const line = String(content || "")
54
+ .split("\n")
55
+ .map((entry) => entry.trim())
56
+ .find(Boolean);
57
+ return (line || fallback || "").replace(/^#+\s*/, "").slice(0, 160);
58
+ }
59
+
60
+ function isCommandOnly(text) {
61
+ return String(text || "").trim().startsWith("/");
62
+ }
63
+
64
+ function flattenMessageText(messageLike) {
65
+ if (!messageLike) {
66
+ return "";
67
+ }
68
+ if (typeof messageLike === "string") {
69
+ return messageLike.trim();
70
+ }
71
+
72
+ const nestedMessage = flattenMessageText(messageLike.message);
73
+ if (nestedMessage) {
74
+ return nestedMessage;
75
+ }
76
+
77
+ const directText = asTrimmedString(messageLike.text || messageLike.body || messageLike.content);
78
+ if (directText) {
79
+ return directText;
80
+ }
81
+
82
+ const content = Array.isArray(messageLike.content) ? messageLike.content : [];
83
+ const parts = content
84
+ .filter((entry) => entry && entry.type === "text" && typeof entry.text === "string")
85
+ .map((entry) => entry.text.trim())
86
+ .filter(Boolean);
87
+ if (parts.length) {
88
+ return parts.join("\n");
89
+ }
90
+
91
+ return "";
92
+ }
93
+
94
+ function extractEventText(event) {
95
+ const direct = flattenMessageText(event);
96
+ if (direct) {
97
+ return direct;
98
+ }
99
+
100
+ const message = flattenMessageText(event?.message);
101
+ if (message) {
102
+ return message;
103
+ }
104
+
105
+ const messages = Array.isArray(event?.messages) ? event.messages : [];
106
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
107
+ const candidate = messages[index];
108
+ const role = candidate?.role || candidate?.message?.role;
109
+ if (role && role !== "user") {
110
+ continue;
111
+ }
112
+ const text = flattenMessageText(candidate);
113
+ if (text) {
114
+ return text;
115
+ }
116
+ }
117
+
118
+ return "";
119
+ }
120
+
121
+ function selectionHash(manifestHash, query, matches) {
122
+ return crypto
123
+ .createHash("sha256")
124
+ .update(
125
+ JSON.stringify({
126
+ manifestHash,
127
+ query,
128
+ matches: Object.fromEntries(
129
+ ASSET_KINDS.map((kind) => [kind, Array.isArray(matches?.[kind]) ? matches[kind].map((item) => item.id) : []])
130
+ )
131
+ })
132
+ )
133
+ .digest("hex");
134
+ }
135
+
136
+ async function fetchJson(url, token, init = {}) {
137
+ const response = await fetch(url, {
138
+ method: init.method || "GET",
139
+ headers: {
140
+ Authorization: `Bearer ${token}`,
141
+ Accept: "application/json",
142
+ ...(init.body ? { "Content-Type": "application/json" } : {}),
143
+ ...(init.headers || {})
144
+ },
145
+ body: init.body,
146
+ signal: init.signal
147
+ });
148
+
149
+ const text = await response.text();
150
+ let payload;
151
+ try {
152
+ payload = JSON.parse(text);
153
+ } catch {
154
+ payload = text;
155
+ }
156
+ if (!response.ok) {
157
+ throw new Error(typeof payload === "string" ? payload : JSON.stringify(payload));
158
+ }
159
+ return payload;
160
+ }
161
+
162
+ async function ensureDir(dirPath) {
163
+ await fs.mkdir(dirPath, { recursive: true });
164
+ }
165
+
166
+ async function resetPath(dirPath) {
167
+ await fs.rm(dirPath, { recursive: true, force: true });
168
+ await ensureDir(dirPath);
169
+ }
170
+
171
+ function normalizeManifest(manifest) {
172
+ return {
173
+ team: manifest?.team || { slug: "unknown", name: "Unknown Team" },
174
+ generatedAt: manifest?.generatedAt || new Date().toISOString(),
175
+ manifestHash: String(manifest?.manifestHash || ""),
176
+ counts: manifest?.counts || {},
177
+ items: Array.isArray(manifest?.items) ? manifest.items : []
178
+ };
179
+ }
180
+
181
+ async function writeCatalog(manifest, workspaceRoot) {
182
+ const normalized = normalizeManifest(manifest);
183
+ const catalogRoot = path.join(workspaceRoot, "docs", "team-shared", "catalog");
184
+ const catalogSkillRoot = path.join(workspaceRoot, "skills", "team-shared-catalog");
185
+
186
+ await resetPath(catalogRoot);
187
+ await resetPath(catalogSkillRoot);
188
+
189
+ await safeWriteJson(path.join(catalogRoot, "manifest.json"), normalized);
190
+
191
+ const catalogIndexLines = [
192
+ `# Team Shared Asset Catalog`,
193
+ ``,
194
+ `Team: ${normalized.team.name} (${normalized.team.slug})`,
195
+ `Generated: ${normalized.generatedAt}`,
196
+ `Published assets: ${normalized.items.length}`,
197
+ ``
198
+ ];
199
+
200
+ const skillLines = [
201
+ "---",
202
+ "name: team-shared-catalog",
203
+ "description: Browse the shared team asset catalog. Relevant assets for the current task will be materialized into active shared directories.",
204
+ "---",
205
+ "",
206
+ `Team: ${normalized.team.name} (${normalized.team.slug})`,
207
+ "",
208
+ "Use this catalog to discover team-shared assets without loading every asset into context.",
209
+ "Relevant assets for the current task, when selected by the injector, are placed under:",
210
+ "- /home/node/.openclaw/workspace/skills/team-shared-active-<skill>",
211
+ "- /home/node/.openclaw/workspace/docs/team-shared/active",
212
+ "",
213
+ "Published counts:"
214
+ ];
215
+
216
+ for (const kind of ASSET_KINDS) {
217
+ const count = Number(normalized.counts?.[kind] || 0);
218
+ skillLines.push(`- ${kind}: ${count}`);
219
+ }
220
+ skillLines.push("");
221
+
222
+ for (const kind of ASSET_KINDS) {
223
+ const items = normalized.items.filter((item) => item.kind === kind);
224
+ if (!items.length) {
225
+ continue;
226
+ }
227
+ catalogIndexLines.push(`## ${kind}`);
228
+ skillLines.push(`${kind.toUpperCase()}:`);
229
+ for (const item of items) {
230
+ const keywords = Array.isArray(item.keywords) && item.keywords.length ? ` | keywords: ${item.keywords.slice(0, 6).join(", ")}` : "";
231
+ catalogIndexLines.push(`- ${item.title}: ${item.summary}${keywords}`);
232
+ skillLines.push(`- ${item.title}: ${item.summary}`);
233
+ }
234
+ catalogIndexLines.push("");
235
+ skillLines.push("");
236
+ }
237
+
238
+ await fs.writeFile(path.join(catalogRoot, "INDEX.md"), `${catalogIndexLines.join("\n")}\n`, "utf8");
239
+ await fs.writeFile(path.join(catalogSkillRoot, "SKILL.md"), `${skillLines.join("\n")}\n`, "utf8");
240
+ }
241
+
242
+ async function clearActiveSelection(workspaceRoot) {
243
+ const skillsRoot = path.join(workspaceRoot, "skills");
244
+ await ensureDir(skillsRoot);
245
+ const entries = await fs.readdir(skillsRoot, { withFileTypes: true }).catch(() => []);
246
+ for (const entry of entries) {
247
+ if (entry.isDirectory() && entry.name.startsWith("team-shared-active-")) {
248
+ await fs.rm(path.join(skillsRoot, entry.name), { recursive: true, force: true });
249
+ }
250
+ }
251
+ await resetPath(path.join(workspaceRoot, "docs", "team-shared", "active"));
252
+ }
253
+
254
+ async function writeBootstrapContext(workspaceRoot, content) {
255
+ const bootstrapPath = path.join(workspaceRoot, "BOOTSTRAP.md");
256
+ const text = String(content || "").trim();
257
+ if (!text) {
258
+ await fs.rm(bootstrapPath, { force: true });
259
+ return;
260
+ }
261
+ await fs.writeFile(bootstrapPath, `${text}\n`, "utf8");
262
+ }
263
+
264
+ async function writeActiveSelection(manifest, query, matches, itemsById, workspaceRoot) {
265
+ const skillsRoot = path.join(workspaceRoot, "skills");
266
+ const sharedDocsRoot = path.join(workspaceRoot, "docs", "team-shared", "active");
267
+
268
+ await clearActiveSelection(workspaceRoot);
269
+ await ensureDir(skillsRoot);
270
+
271
+ const selectionSummary = {
272
+ team: manifest.team,
273
+ manifestHash: manifest.manifestHash,
274
+ query,
275
+ generatedAt: new Date().toISOString(),
276
+ matches: Object.fromEntries(
277
+ ASSET_KINDS.map((kind) => [
278
+ kind,
279
+ (matches?.[kind] || []).map((item) => ({
280
+ id: item.id,
281
+ title: item.title,
282
+ score: item.score,
283
+ matchedTerms: item.matchedTerms
284
+ }))
285
+ ])
286
+ )
287
+ };
288
+
289
+ const overviewLines = [
290
+ `# Active Team Shared Assets`,
291
+ ``,
292
+ `Team: ${manifest.team.name} (${manifest.team.slug})`,
293
+ `Query: ${query}`,
294
+ `Generated: ${selectionSummary.generatedAt}`,
295
+ ``
296
+ ];
297
+
298
+ for (const kind of ASSET_KINDS) {
299
+ const targetDir = kind === "skills" ? skillsRoot : path.join(sharedDocsRoot, kind);
300
+ await ensureDir(targetDir);
301
+ const activeItems = Array.isArray(matches?.[kind]) ? matches[kind] : [];
302
+ if (!activeItems.length) {
303
+ continue;
304
+ }
305
+
306
+ overviewLines.push(`## ${kind}`);
307
+ for (const match of activeItems) {
308
+ const item = itemsById.get(match.id);
309
+ if (!item) {
310
+ continue;
311
+ }
312
+ overviewLines.push(`- ${item.title}: ${item.summary}`);
313
+ if (kind === "skills") {
314
+ const skillDir = path.join(skillsRoot, `team-shared-active-${slugify(item.title)}`);
315
+ await ensureDir(skillDir);
316
+ await fs.writeFile(
317
+ path.join(skillDir, "SKILL.md"),
318
+ wrapSkillDoc(slugify(item.title), item.summary || item.title, item.content),
319
+ "utf8"
320
+ );
321
+ } else {
322
+ await fs.writeFile(path.join(targetDir, item.filename), `${String(item.content || "").trim()}\n`, "utf8");
323
+ }
324
+ }
325
+ overviewLines.push("");
326
+ }
327
+
328
+ await safeWriteJson(path.join(sharedDocsRoot, "selection.json"), selectionSummary);
329
+ await fs.writeFile(path.join(sharedDocsRoot, "README.md"), `${overviewLines.join("\n")}\n`, "utf8");
330
+ }
331
+
332
+ function buildPromptAdditionFromSelection(selection, itemsById) {
333
+ if (!selection?.query) {
334
+ return "";
335
+ }
336
+
337
+ const lines = [
338
+ "<team_shared_active_context>",
339
+ "Use the following shared team context only when it is relevant to the user's current task.",
340
+ "If a workflow block matches the task, follow its required structure exactly.",
341
+ "If a memory block contains a team preference, required phrase, or standing rule that matches the task, honor it exactly.",
342
+ `Current task query: ${selection.query}`,
343
+ ""
344
+ ];
345
+
346
+ const orderedKinds = ["memory", "workflows", "docs"];
347
+ for (const kind of orderedKinds) {
348
+ const items = Array.isArray(selection?.matches?.[kind]) ? selection.matches[kind] : [];
349
+ if (!items.length) {
350
+ continue;
351
+ }
352
+ lines.push(`${kind.toUpperCase()}:`);
353
+ for (const match of items) {
354
+ const item = itemsById.get(match.id);
355
+ if (!item) {
356
+ continue;
357
+ }
358
+ lines.push(`- ${item.title}: ${item.summary}`);
359
+ if (Array.isArray(item.capability?.activationHints) && item.capability.activationHints.length) {
360
+ lines.push(` Activation: ${item.capability.activationHints.join(" ")}`);
361
+ }
362
+ if (kind === "memory" || kind === "workflows") {
363
+ const excerpt = String(item.content || "").trim().split("\n").slice(0, 24).join("\n");
364
+ if (excerpt) {
365
+ lines.push(kind === "memory" ? " Required memory content:" : " Required workflow content:");
366
+ lines.push(...excerpt.split("\n").map((line) => ` ${line}`));
367
+ }
368
+ }
369
+ }
370
+ lines.push("");
371
+ }
372
+
373
+ if (lines.length <= 3) {
374
+ return "";
375
+ }
376
+ lines.push("</team_shared_active_context>");
377
+ return `${lines.join("\n").trim()}\n`;
378
+ }
379
+
380
+ async function ensureManifestSynced(config) {
381
+ const assetServerBaseUrl = config?.assetServerBaseUrl;
382
+ const assetServerToken = config?.assetServerToken;
383
+ if (!assetServerBaseUrl || !assetServerToken) {
384
+ return null;
385
+ }
386
+
387
+ const workspaceRoot = config?.workspaceRoot || DEFAULT_WORKSPACE_ROOT;
388
+ const statePath = config?.statePath || DEFAULT_STATE_PATH;
389
+ const syncTtlMs = Number(config?.syncTtlMs || DEFAULT_SYNC_TTL_MS);
390
+ const now = Date.now();
391
+ const state = await safeReadJson(statePath, {
392
+ manifestHash: null,
393
+ manifest: null,
394
+ lastCheckedAt: 0,
395
+ lastAppliedAt: null,
396
+ activeSelectionHash: null,
397
+ activeSelectionQuery: null,
398
+ activeSelectionAt: null
399
+ });
400
+
401
+ if (state.manifest && Number(state.lastCheckedAt || 0) > 0 && now - Number(state.lastCheckedAt) < syncTtlMs) {
402
+ return {
403
+ workspaceRoot,
404
+ statePath,
405
+ state,
406
+ manifest: normalizeManifest(state.manifest)
407
+ };
408
+ }
409
+
410
+ const manifest = normalizeManifest(await fetchJson(`${assetServerBaseUrl}/manifest`, assetServerToken));
411
+ state.lastCheckedAt = now;
412
+
413
+ if (state.manifestHash !== manifest.manifestHash) {
414
+ await writeCatalog(manifest, workspaceRoot);
415
+ state.manifestHash = manifest.manifestHash;
416
+ state.manifest = manifest;
417
+ state.lastAppliedAt = new Date().toISOString();
418
+ state.activeSelectionHash = null;
419
+ } else if (!state.manifest) {
420
+ state.manifest = manifest;
421
+ }
422
+
423
+ await safeWriteJson(statePath, state);
424
+ return {
425
+ workspaceRoot,
426
+ statePath,
427
+ state,
428
+ manifest
429
+ };
430
+ }
431
+
432
+ export async function syncSharedAssetsForEvent(config, eventText) {
433
+ const synced = await ensureManifestSynced(config);
434
+ if (!synced) {
435
+ return null;
436
+ }
437
+
438
+ const { workspaceRoot, statePath, state, manifest } = synced;
439
+ const assetServerBaseUrl = config?.assetServerBaseUrl;
440
+ const assetServerToken = config?.assetServerToken;
441
+ const query = String(eventText || "").trim();
442
+
443
+ if (!query || isCommandOnly(query)) {
444
+ await writeBootstrapContext(workspaceRoot, "");
445
+ return { manifest, selection: null };
446
+ }
447
+
448
+ const resolveLimitPerKind = Math.max(1, Math.min(6, Number(config?.resolveLimitPerKind || DEFAULT_RESOLVE_LIMIT_PER_KIND)));
449
+ const resolveResult = await fetchJson(`${assetServerBaseUrl}/resolve`, assetServerToken, {
450
+ method: "POST",
451
+ body: JSON.stringify({
452
+ query,
453
+ limitPerKind: resolveLimitPerKind
454
+ })
455
+ });
456
+
457
+ const nextSelectionHash = selectionHash(manifest.manifestHash, query, resolveResult.matches);
458
+ if (state.activeSelectionHash === nextSelectionHash) {
459
+ return {
460
+ manifest,
461
+ selection: resolveResult
462
+ };
463
+ }
464
+
465
+ const selectedIds = Array.from(
466
+ new Set(
467
+ ASSET_KINDS.flatMap((kind) =>
468
+ Array.isArray(resolveResult?.matches?.[kind]) ? resolveResult.matches[kind].map((item) => item.id) : []
469
+ )
470
+ )
471
+ );
472
+
473
+ const itemsById = new Map();
474
+ for (const itemId of selectedIds) {
475
+ const item = await fetchJson(`${assetServerBaseUrl}/items/${encodeURIComponent(itemId)}`, assetServerToken);
476
+ itemsById.set(itemId, item);
477
+ }
478
+
479
+ await writeActiveSelection(manifest, query, resolveResult.matches, itemsById, workspaceRoot);
480
+ const bootstrapContext = buildPromptAdditionFromSelection(
481
+ {
482
+ query,
483
+ matches: resolveResult.matches
484
+ },
485
+ itemsById
486
+ );
487
+ await writeBootstrapContext(workspaceRoot, bootstrapContext);
488
+ state.activeSelectionHash = nextSelectionHash;
489
+ state.activeSelectionQuery = query;
490
+ state.activeSelectionAt = new Date().toISOString();
491
+ await safeWriteJson(statePath, state);
492
+
493
+ return {
494
+ manifest,
495
+ selection: resolveResult,
496
+ itemsById
497
+ };
498
+ }
499
+
500
+ export async function syncSharedAssetCatalog(config) {
501
+ return ensureManifestSynced(config);
502
+ }
503
+
504
+ export default definePluginEntry({
505
+ id: "shared-asset-injector",
506
+ name: "Shared Asset Injector",
507
+ description: "Sync the shared team asset catalog and materialize task-relevant assets into the member workspace.",
508
+ register(api) {
509
+ const register = typeof api.on === "function" ? api.on.bind(api) : api.registerHook.bind(api);
510
+
511
+ async function syncCatalogOnly() {
512
+ try {
513
+ await syncSharedAssetCatalog(api.pluginConfig || {});
514
+ } catch (error) {
515
+ console.error("[shared-asset-injector] catalog sync failed:", error instanceof Error ? error.message : String(error));
516
+ }
517
+ }
518
+
519
+ async function syncForEvent(event) {
520
+ try {
521
+ const text = extractEventText(event);
522
+ const result = await syncSharedAssetsForEvent(api.pluginConfig || {}, text);
523
+ if (result?.selection && result?.itemsById) {
524
+ const systemPromptAddition = buildPromptAdditionFromSelection(result.selection, result.itemsById);
525
+ if (systemPromptAddition) {
526
+ return {
527
+ prependSystemContext: systemPromptAddition
528
+ };
529
+ }
530
+ }
531
+ } catch (error) {
532
+ console.error("[shared-asset-injector] active sync failed:", error instanceof Error ? error.message : String(error));
533
+ }
534
+ }
535
+
536
+ register("before_prompt_build", async (event) => {
537
+ return syncForEvent(event);
538
+ });
539
+
540
+ register("before_dispatch", async (event) => {
541
+ await syncForEvent(event);
542
+ });
543
+
544
+ register("before_tool_call", async () => {
545
+ await syncCatalogOnly();
546
+ });
547
+ }
548
+ });
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "shared-asset-injector",
3
+ "name": "Shared Asset Injector",
4
+ "description": "Pulls published shared team assets from the Velaclaw asset server into the member runtime workspace.",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "assetServerBaseUrl": {
10
+ "type": "string"
11
+ },
12
+ "assetServerToken": {
13
+ "type": "string"
14
+ },
15
+ "workspaceRoot": {
16
+ "type": "string",
17
+ "default": "/home/node/.openclaw/workspace"
18
+ },
19
+ "statePath": {
20
+ "type": "string",
21
+ "default": "/home/node/.openclaw/shared-assets-state.json"
22
+ },
23
+ "syncTtlMs": {
24
+ "type": "integer",
25
+ "default": 30000
26
+ },
27
+ "resolveLimitPerKind": {
28
+ "type": "integer",
29
+ "default": 2
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@local/shared-asset-injector",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "openclaw": {
6
+ "extensions": [
7
+ "./index.js"
8
+ ]
9
+ }
10
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "agents": {
3
+ "defaults": {
4
+ "workspace": "/home/node/.openclaw/workspace",
5
+ "model": {
6
+ "primary": "saymycode/gpt-5.1-codex-mini"
7
+ },
8
+ "compaction": {
9
+ "mode": "safeguard"
10
+ },
11
+ "sandbox": {
12
+ "mode": "off",
13
+ "scope": "agent"
14
+ }
15
+ },
16
+ "list": [
17
+ {
18
+ "id": "main",
19
+ "default": true,
20
+ "workspace": "/home/node/.openclaw/workspace",
21
+ "model": "saymycode/gpt-5.4",
22
+ "thinkingDefault": "low",
23
+ "identity": {
24
+ "name": "小虾-member",
25
+ "emoji": "🦐",
26
+ "theme": "member runtime"
27
+ }
28
+ }
29
+ ]
30
+ },
31
+ "gateway": {
32
+ "mode": "local",
33
+ "auth": {
34
+ "mode": "token",
35
+ "token": "REPLACE_WITH_RANDOM_GATEWAY_TOKEN"
36
+ },
37
+ "port": 18789,
38
+ "bind": "loopback"
39
+ },
40
+ "tools": {
41
+ "profile": "minimal",
42
+ "alsoAllow": [
43
+ "read"
44
+ ],
45
+ "deny": [
46
+ "cron",
47
+ "sessions_spawn",
48
+ "subagents",
49
+ "lobster"
50
+ ],
51
+ "fs": {
52
+ "workspaceOnly": true
53
+ },
54
+ "exec": {
55
+ "host": "gateway",
56
+ "security": "allowlist",
57
+ "ask": "always"
58
+ },
59
+ "elevated": {
60
+ "enabled": false
61
+ }
62
+ },
63
+ "messages": {
64
+ "queue": {
65
+ "mode": "collect",
66
+ "debounceMs": 1000,
67
+ "cap": 20,
68
+ "drop": "summarize"
69
+ },
70
+ "inbound": {
71
+ "debounceMs": 0
72
+ }
73
+ },
74
+ "session": {
75
+ "dmScope": "per-channel-peer"
76
+ },
77
+ "channels": {
78
+ "telegram": {
79
+ "enabled": true,
80
+ "tokenFile": "/home/node/.openclaw/secrets/telegram-bot-token",
81
+ "dmPolicy": "allowlist",
82
+ "allowFrom": [
83
+ "REPLACE_WITH_USER_ID"
84
+ ],
85
+ "groupPolicy": "disabled"
86
+ }
87
+ },
88
+ "plugins": {
89
+ "entries": {
90
+ "member-quota-guard": {
91
+ "enabled": true,
92
+ "config": {
93
+ "policyPath": "/home/node/.openclaw/team-policy.json",
94
+ "usagePath": "/home/node/.openclaw/team-usage.json"
95
+ }
96
+ }
97
+ },
98
+ "load": {
99
+ "paths": [
100
+ "/home/node/.openclaw/local-plugins/member-quota-guard"
101
+ ]
102
+ }
103
+ }
104
+ }