@clawcipes/recipes 0.1.1 → 0.1.3
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/index.ts +110 -16
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -200,8 +200,46 @@ function upsertAgentInConfig(cfgObj: any, snippet: AgentConfigSnippet) {
|
|
|
200
200
|
tools: snippet.tools ? { ...snippet.tools } : prev?.tools,
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
-
if (idx >= 0)
|
|
204
|
-
|
|
203
|
+
if (idx >= 0) {
|
|
204
|
+
list[idx] = nextAgent;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// New agent: append to end of list.
|
|
209
|
+
// (We still separately enforce that main exists and stays first/default.)
|
|
210
|
+
list.push(nextAgent);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function ensureMainFirstInAgentsList(cfgObj: any, api: OpenClawPluginApi) {
|
|
214
|
+
if (!cfgObj.agents) cfgObj.agents = {};
|
|
215
|
+
if (!Array.isArray(cfgObj.agents.list)) cfgObj.agents.list = [];
|
|
216
|
+
|
|
217
|
+
const list: any[] = cfgObj.agents.list;
|
|
218
|
+
|
|
219
|
+
const workspaceRoot =
|
|
220
|
+
cfgObj.agents?.defaults?.workspace ??
|
|
221
|
+
api.config.agents?.defaults?.workspace ??
|
|
222
|
+
"~/.openclaw/workspace";
|
|
223
|
+
|
|
224
|
+
const idx = list.findIndex((a) => a?.id === "main");
|
|
225
|
+
const prevMain = idx >= 0 ? list[idx] : {};
|
|
226
|
+
|
|
227
|
+
// Enforce: main exists, is first, and is the default.
|
|
228
|
+
const main = {
|
|
229
|
+
...prevMain,
|
|
230
|
+
id: "main",
|
|
231
|
+
default: true,
|
|
232
|
+
workspace: prevMain?.workspace ?? workspaceRoot,
|
|
233
|
+
sandbox: prevMain?.sandbox ?? { mode: "off" },
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Ensure only one default.
|
|
237
|
+
for (const a of list) {
|
|
238
|
+
if (a?.id !== "main" && a?.default) a.default = false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
242
|
+
list.unshift(main);
|
|
205
243
|
}
|
|
206
244
|
|
|
207
245
|
async function applyAgentSnippetsToOpenClawConfig(api: OpenClawPluginApi, snippets: AgentConfigSnippet[]) {
|
|
@@ -211,22 +249,36 @@ async function applyAgentSnippetsToOpenClawConfig(api: OpenClawPluginApi, snippe
|
|
|
211
249
|
|
|
212
250
|
// Some loaders return { cfg, ... }. If so, normalize.
|
|
213
251
|
const cfgObj = (current.cfg ?? current) as any;
|
|
252
|
+
|
|
253
|
+
// Always keep main first/default when multi-agent workflows are in play.
|
|
254
|
+
ensureMainFirstInAgentsList(cfgObj, api);
|
|
255
|
+
|
|
214
256
|
for (const s of snippets) upsertAgentInConfig(cfgObj, s);
|
|
215
257
|
|
|
258
|
+
// Re-assert ordering/default after upserts.
|
|
259
|
+
ensureMainFirstInAgentsList(cfgObj, api);
|
|
260
|
+
|
|
216
261
|
await (api.runtime as any).config?.writeConfigFile?.(cfgObj);
|
|
217
262
|
return { updatedAgents: snippets.map((s) => s.id) };
|
|
218
263
|
}
|
|
219
264
|
|
|
220
|
-
async function scaffoldAgentFromRecipe(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
265
|
+
async function scaffoldAgentFromRecipe(
|
|
266
|
+
api: OpenClawPluginApi,
|
|
267
|
+
recipe: RecipeFrontmatter,
|
|
268
|
+
opts: {
|
|
269
|
+
agentId: string;
|
|
270
|
+
agentName?: string;
|
|
271
|
+
update?: boolean;
|
|
272
|
+
vars?: Record<string, string>;
|
|
227
273
|
|
|
228
|
-
|
|
229
|
-
|
|
274
|
+
// Where to write the scaffolded files (may be a shared team workspace role folder)
|
|
275
|
+
filesRootDir: string;
|
|
276
|
+
|
|
277
|
+
// What to set in agents.list[].workspace (may be shared team workspace root)
|
|
278
|
+
workspaceRootDir: string;
|
|
279
|
+
},
|
|
280
|
+
) {
|
|
281
|
+
await ensureDir(opts.filesRootDir);
|
|
230
282
|
|
|
231
283
|
const templates = recipe.templates ?? {};
|
|
232
284
|
const files = recipe.files ?? [];
|
|
@@ -237,7 +289,7 @@ async function scaffoldAgentFromRecipe(api: OpenClawPluginApi, recipe: RecipeFro
|
|
|
237
289
|
const raw = templates[f.template];
|
|
238
290
|
if (typeof raw !== "string") throw new Error(`Missing template: ${f.template}`);
|
|
239
291
|
const rendered = renderTemplate(raw, vars);
|
|
240
|
-
const target = path.join(
|
|
292
|
+
const target = path.join(opts.filesRootDir, f.path);
|
|
241
293
|
const mode = opts.update ? (f.mode ?? "overwrite") : (f.mode ?? "createOnly");
|
|
242
294
|
const r = await writeFileSafely(target, rendered, mode);
|
|
243
295
|
fileResults.push({ path: target, wrote: r.wrote, reason: r.reason });
|
|
@@ -245,13 +297,14 @@ async function scaffoldAgentFromRecipe(api: OpenClawPluginApi, recipe: RecipeFro
|
|
|
245
297
|
|
|
246
298
|
const configSnippet: AgentConfigSnippet = {
|
|
247
299
|
id: opts.agentId,
|
|
248
|
-
workspace:
|
|
300
|
+
workspace: opts.workspaceRootDir,
|
|
249
301
|
identity: { name: opts.agentName ?? recipe.name ?? opts.agentId },
|
|
250
302
|
tools: recipe.tools ?? {},
|
|
251
303
|
};
|
|
252
304
|
|
|
253
305
|
return {
|
|
254
|
-
|
|
306
|
+
filesRootDir: opts.filesRootDir,
|
|
307
|
+
workspaceRootDir: opts.workspaceRootDir,
|
|
255
308
|
fileResults,
|
|
256
309
|
next: {
|
|
257
310
|
configSnippet,
|
|
@@ -269,6 +322,27 @@ const recipesPlugin = {
|
|
|
269
322
|
properties: {},
|
|
270
323
|
},
|
|
271
324
|
register(api: OpenClawPluginApi) {
|
|
325
|
+
// On plugin load, ensure multi-agent config has an explicit agents.list with main at top.
|
|
326
|
+
// This is idempotent and only writes if a change is required.
|
|
327
|
+
(async () => {
|
|
328
|
+
try {
|
|
329
|
+
const current = (api.runtime as any).config?.loadConfig?.();
|
|
330
|
+
if (!current) return;
|
|
331
|
+
const cfgObj = (current.cfg ?? current) as any;
|
|
332
|
+
|
|
333
|
+
const before = JSON.stringify(cfgObj.agents?.list ?? null);
|
|
334
|
+
ensureMainFirstInAgentsList(cfgObj, api);
|
|
335
|
+
const after = JSON.stringify(cfgObj.agents?.list ?? null);
|
|
336
|
+
|
|
337
|
+
if (before !== after) {
|
|
338
|
+
await (api.runtime as any).config?.writeConfigFile?.(cfgObj);
|
|
339
|
+
console.error("[recipes] ensured agents.list includes main as first/default");
|
|
340
|
+
}
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.error(`[recipes] warning: failed to ensure main agent in agents.list: ${(e as Error).message}`);
|
|
343
|
+
}
|
|
344
|
+
})();
|
|
345
|
+
|
|
272
346
|
api.registerCli(
|
|
273
347
|
({ program }) => {
|
|
274
348
|
const cmd = program.command("recipes").description("Manage markdown recipes (scaffold agents/teams)");
|
|
@@ -443,7 +517,8 @@ const recipesPlugin = {
|
|
|
443
517
|
if (!workspaceRoot) throw new Error("agents.defaults.workspace is not set in config");
|
|
444
518
|
|
|
445
519
|
const teamId = String(options.teamId);
|
|
446
|
-
|
|
520
|
+
// Team workspace root (shared by all role agents): ~/.openclaw/workspace-<teamId>
|
|
521
|
+
const teamDir = path.resolve(workspaceRoot, "..", `workspace-${teamId}`);
|
|
447
522
|
|
|
448
523
|
const inboxDir = path.join(teamDir, "inbox");
|
|
449
524
|
const backlogDir = path.join(teamDir, "work", "backlog");
|
|
@@ -592,10 +667,16 @@ const recipesPlugin = {
|
|
|
592
667
|
return;
|
|
593
668
|
}
|
|
594
669
|
|
|
670
|
+
const baseWorkspace = api.config.agents?.defaults?.workspace ?? "~/.openclaw/workspace";
|
|
671
|
+
// Put standalone agent workspaces alongside the default workspace (same parent dir).
|
|
672
|
+
const resolvedWorkspaceRoot = path.resolve(baseWorkspace, "..", `workspace-${options.agentId}`);
|
|
673
|
+
|
|
595
674
|
const result = await scaffoldAgentFromRecipe(api, recipe, {
|
|
596
675
|
agentId: options.agentId,
|
|
597
676
|
agentName: options.name,
|
|
598
677
|
update: !!options.overwrite,
|
|
678
|
+
filesRootDir: resolvedWorkspaceRoot,
|
|
679
|
+
workspaceRootDir: resolvedWorkspaceRoot,
|
|
599
680
|
vars: {
|
|
600
681
|
agentId: options.agentId,
|
|
601
682
|
agentName: options.name ?? recipe.name ?? options.agentId,
|
|
@@ -636,8 +717,15 @@ const recipesPlugin = {
|
|
|
636
717
|
return;
|
|
637
718
|
}
|
|
638
719
|
|
|
639
|
-
const
|
|
720
|
+
const baseWorkspace = api.config.agents?.defaults?.workspace;
|
|
721
|
+
if (!baseWorkspace) throw new Error("agents.defaults.workspace is not set in config");
|
|
722
|
+
|
|
723
|
+
// Team workspace root (shared by all role agents): ~/.openclaw/workspace-<teamId>
|
|
724
|
+
const teamDir = path.resolve(baseWorkspace, "..", `workspace-${teamId}`);
|
|
640
725
|
await ensureDir(teamDir);
|
|
726
|
+
|
|
727
|
+
const rolesDir = path.join(teamDir, "roles");
|
|
728
|
+
await ensureDir(rolesDir);
|
|
641
729
|
const notesDir = path.join(teamDir, "notes");
|
|
642
730
|
const workDir = path.join(teamDir, "work");
|
|
643
731
|
const backlogDir = path.join(workDir, "backlog");
|
|
@@ -695,16 +783,22 @@ const recipesPlugin = {
|
|
|
695
783
|
tools: a.tools ?? recipe.tools,
|
|
696
784
|
};
|
|
697
785
|
|
|
786
|
+
const roleDir = path.join(rolesDir, role);
|
|
698
787
|
const r = await scaffoldAgentFromRecipe(api, scopedRecipe, {
|
|
699
788
|
agentId,
|
|
700
789
|
agentName,
|
|
701
790
|
update: !!options.overwrite,
|
|
791
|
+
// Write role-specific files under roles/<role>/
|
|
792
|
+
filesRootDir: roleDir,
|
|
793
|
+
// But set the agent workspace root to the shared team workspace
|
|
794
|
+
workspaceRootDir: teamDir,
|
|
702
795
|
vars: {
|
|
703
796
|
teamId,
|
|
704
797
|
teamDir,
|
|
705
798
|
role,
|
|
706
799
|
agentId,
|
|
707
800
|
agentName,
|
|
801
|
+
roleDir,
|
|
708
802
|
},
|
|
709
803
|
});
|
|
710
804
|
results.push({ role, agentId, ...r });
|