@desplega.ai/agent-swarm 1.94.0 → 1.95.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/openapi.json +1 -1
- package/package.json +4 -3
- package/src/be/db.ts +11 -2
- package/src/be/migrations/094_mcp_extra_authorize_params.sql +4 -0
- package/src/be/skill-sync.ts +4 -4
- package/src/be/swarm-config-guard.ts +8 -0
- package/src/commands/provider-credentials.ts +14 -8
- package/src/commands/runner.ts +1 -0
- package/src/http/mcp-oauth.ts +14 -0
- package/src/oauth/mcp-wrapper.ts +14 -0
- package/src/providers/codex-skill-resolver.ts +22 -8
- package/src/providers/opencode-adapter.ts +20 -2
- package/src/providers/pi-mono-adapter.ts +65 -20
- package/src/providers/types.ts +12 -0
- package/src/tests/credential-check.test.ts +185 -46
- package/src/tests/harness-provider-resolution.test.ts +23 -0
- package/src/tests/mcp-oauth-queries.test.ts +71 -1
- package/src/tests/mcp-oauth-wrapper.test.ts +109 -0
- package/src/tests/opencode-adapter.test.ts +29 -1
- package/src/tests/provider-command-format.test.ts +12 -0
- package/src/tests/skill-fs-writer.test.ts +7 -1
- package/src/tests/skill-sync.test.ts +15 -3
- package/src/tools/mcp-servers/mcp-server-create.ts +7 -0
- package/src/tools/mcp-servers/mcp-server-update.ts +8 -0
- package/src/types.ts +1 -0
- package/src/utils/skill-fs-writer.ts +11 -3
|
@@ -41,6 +41,8 @@ describe("writeSkillsToFilesystem", () => {
|
|
|
41
41
|
rmSync(join(FAKE_HOME, ".claude"), { recursive: true, force: true });
|
|
42
42
|
rmSync(join(FAKE_HOME, ".pi"), { recursive: true, force: true });
|
|
43
43
|
rmSync(join(FAKE_HOME, ".codex"), { recursive: true, force: true });
|
|
44
|
+
rmSync(join(FAKE_HOME, ".opencode"), { recursive: true, force: true });
|
|
45
|
+
rmSync(join(FAKE_HOME, ".agents"), { recursive: true, force: true });
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
afterAll(() => {
|
|
@@ -71,12 +73,16 @@ describe("writeSkillsToFilesystem", () => {
|
|
|
71
73
|
const entries = [skillEntry({ name: "multi-skill", content: "# Multi" })];
|
|
72
74
|
const result = writeSkillsToFilesystem(entries, "all", FAKE_HOME);
|
|
73
75
|
|
|
74
|
-
expect(result.synced).toBe(
|
|
76
|
+
expect(result.synced).toBe(5); // claude + pi + codex + opencode + .agents
|
|
75
77
|
expect(existsSync(join(FAKE_HOME, ".claude", "skills", "multi-skill", "SKILL.md"))).toBe(true);
|
|
76
78
|
expect(existsSync(join(FAKE_HOME, ".pi", "agent", "skills", "multi-skill", "SKILL.md"))).toBe(
|
|
77
79
|
true,
|
|
78
80
|
);
|
|
79
81
|
expect(existsSync(join(FAKE_HOME, ".codex", "skills", "multi-skill", "SKILL.md"))).toBe(true);
|
|
82
|
+
expect(existsSync(join(FAKE_HOME, ".opencode", "skills", "multi-skill", "SKILL.md"))).toBe(
|
|
83
|
+
true,
|
|
84
|
+
);
|
|
85
|
+
expect(existsSync(join(FAKE_HOME, ".agents", "skills", "multi-skill", "SKILL.md"))).toBe(true);
|
|
80
86
|
});
|
|
81
87
|
|
|
82
88
|
test("writes complex skill SKILL.md plus non-binary bundled files", () => {
|
|
@@ -121,23 +121,29 @@ describe("syncSkillsToFilesystem", () => {
|
|
|
121
121
|
expect(existsSync(piOnlyFile)).toBe(false);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
test("syncs to
|
|
124
|
+
test("syncs to all local harness skill trees when harnessType is 'all'", () => {
|
|
125
125
|
// Clean up first to get accurate count
|
|
126
126
|
rmSync(join(FAKE_HOME, ".claude"), { recursive: true, force: true });
|
|
127
127
|
rmSync(join(FAKE_HOME, ".pi"), { recursive: true, force: true });
|
|
128
128
|
rmSync(join(FAKE_HOME, ".codex"), { recursive: true, force: true });
|
|
129
|
+
rmSync(join(FAKE_HOME, ".opencode"), { recursive: true, force: true });
|
|
130
|
+
rmSync(join(FAKE_HOME, ".agents"), { recursive: true, force: true });
|
|
129
131
|
|
|
130
132
|
const result = syncSkillsToFilesystem(agentId, "all", FAKE_HOME);
|
|
131
133
|
|
|
132
134
|
expect(result.errors).toHaveLength(0);
|
|
133
|
-
expect(result.synced).toBe(
|
|
135
|
+
expect(result.synced).toBe(10); // 2 DB-backed skills × 5 dirs
|
|
134
136
|
|
|
135
137
|
const claudeFile = join(FAKE_HOME, ".claude", "skills", "test-skill", "SKILL.md");
|
|
136
138
|
const piFile = join(FAKE_HOME, ".pi", "agent", "skills", "test-skill", "SKILL.md");
|
|
137
139
|
const codexFile = join(FAKE_HOME, ".codex", "skills", "test-skill", "SKILL.md");
|
|
140
|
+
const opencodeFile = join(FAKE_HOME, ".opencode", "skills", "test-skill", "SKILL.md");
|
|
141
|
+
const agentsFile = join(FAKE_HOME, ".agents", "skills", "test-skill", "SKILL.md");
|
|
138
142
|
expect(existsSync(claudeFile)).toBe(true);
|
|
139
143
|
expect(existsSync(piFile)).toBe(true);
|
|
140
144
|
expect(existsSync(codexFile)).toBe(true);
|
|
145
|
+
expect(existsSync(opencodeFile)).toBe(true);
|
|
146
|
+
expect(existsSync(agentsFile)).toBe(true);
|
|
141
147
|
});
|
|
142
148
|
|
|
143
149
|
test("syncs DB-backed complex skill files and skips binary placeholders", () => {
|
|
@@ -299,19 +305,25 @@ describe("syncSkillsToFilesystem", () => {
|
|
|
299
305
|
rmSync(join(FAKE_HOME, ".claude"), { recursive: true, force: true });
|
|
300
306
|
rmSync(join(FAKE_HOME, ".pi"), { recursive: true, force: true });
|
|
301
307
|
rmSync(join(FAKE_HOME, ".codex"), { recursive: true, force: true });
|
|
308
|
+
rmSync(join(FAKE_HOME, ".opencode"), { recursive: true, force: true });
|
|
309
|
+
rmSync(join(FAKE_HOME, ".agents"), { recursive: true, force: true });
|
|
302
310
|
|
|
303
311
|
// Use 'all' explicitly with homeOverride (default harnessType would use real home)
|
|
304
312
|
const result = syncSkillsToFilesystem(agentId, "all", FAKE_HOME);
|
|
305
313
|
|
|
306
314
|
expect(result.errors).toHaveLength(0);
|
|
307
|
-
expect(result.synced).toBeGreaterThanOrEqual(
|
|
315
|
+
expect(result.synced).toBeGreaterThanOrEqual(10);
|
|
308
316
|
|
|
309
317
|
const claudeFile = join(FAKE_HOME, ".claude", "skills", "test-skill", "SKILL.md");
|
|
310
318
|
const piFile = join(FAKE_HOME, ".pi", "agent", "skills", "test-skill", "SKILL.md");
|
|
311
319
|
const codexFile = join(FAKE_HOME, ".codex", "skills", "test-skill", "SKILL.md");
|
|
320
|
+
const opencodeFile = join(FAKE_HOME, ".opencode", "skills", "test-skill", "SKILL.md");
|
|
321
|
+
const agentsFile = join(FAKE_HOME, ".agents", "skills", "test-skill", "SKILL.md");
|
|
312
322
|
expect(existsSync(claudeFile)).toBe(true);
|
|
313
323
|
expect(existsSync(piFile)).toBe(true);
|
|
314
324
|
expect(existsSync(codexFile)).toBe(true);
|
|
325
|
+
expect(existsSync(opencodeFile)).toBe(true);
|
|
326
|
+
expect(existsSync(agentsFile)).toBe(true);
|
|
315
327
|
});
|
|
316
328
|
|
|
317
329
|
test("returns empty result for agent with no skills", () => {
|
|
@@ -35,6 +35,12 @@ export const registerMcpServerCreateTool = (server: McpServer) => {
|
|
|
35
35
|
.string()
|
|
36
36
|
.optional()
|
|
37
37
|
.describe("JSON object mapping header names to config key paths for secret headers"),
|
|
38
|
+
extraAuthorizeParams: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe(
|
|
42
|
+
'JSON object string of extra OAuth authorize-request params, e.g. {"access_type":"offline","prompt":"consent"}',
|
|
43
|
+
),
|
|
38
44
|
}),
|
|
39
45
|
outputSchema: z.object({
|
|
40
46
|
yourAgentId: z.string().uuid().optional(),
|
|
@@ -108,6 +114,7 @@ export const registerMcpServerCreateTool = (server: McpServer) => {
|
|
|
108
114
|
headers: args.headers,
|
|
109
115
|
envConfigKeys: args.envConfigKeys,
|
|
110
116
|
headerConfigKeys: args.headerConfigKeys,
|
|
117
|
+
extraAuthorizeParams: args.extraAuthorizeParams,
|
|
111
118
|
});
|
|
112
119
|
|
|
113
120
|
// Auto-install for the creating agent
|
|
@@ -21,6 +21,12 @@ export const registerMcpServerUpdateTool = (server: McpServer) => {
|
|
|
21
21
|
headers: z.string().optional().describe("New JSON object of non-secret headers"),
|
|
22
22
|
envConfigKeys: z.string().optional().describe("New env config key mappings"),
|
|
23
23
|
headerConfigKeys: z.string().optional().describe("New header config key mappings"),
|
|
24
|
+
extraAuthorizeParams: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe(
|
|
28
|
+
'JSON object string of extra OAuth authorize-request params, e.g. {"access_type":"offline","prompt":"consent"}',
|
|
29
|
+
),
|
|
24
30
|
isEnabled: z.boolean().optional().describe("Toggle enabled/disabled"),
|
|
25
31
|
}),
|
|
26
32
|
outputSchema: z.object({
|
|
@@ -76,6 +82,8 @@ export const registerMcpServerUpdateTool = (server: McpServer) => {
|
|
|
76
82
|
if (args.headers !== undefined) updates.headers = args.headers;
|
|
77
83
|
if (args.envConfigKeys !== undefined) updates.envConfigKeys = args.envConfigKeys;
|
|
78
84
|
if (args.headerConfigKeys !== undefined) updates.headerConfigKeys = args.headerConfigKeys;
|
|
85
|
+
if (args.extraAuthorizeParams !== undefined)
|
|
86
|
+
updates.extraAuthorizeParams = args.extraAuthorizeParams;
|
|
79
87
|
if (args.isEnabled !== undefined) updates.isEnabled = args.isEnabled;
|
|
80
88
|
|
|
81
89
|
const updated = updateMcpServer(args.id, updates);
|
package/src/types.ts
CHANGED
|
@@ -1878,6 +1878,7 @@ export const McpServerSchema = z.object({
|
|
|
1878
1878
|
headers: z.string().nullable(),
|
|
1879
1879
|
envConfigKeys: z.string().nullable(),
|
|
1880
1880
|
headerConfigKeys: z.string().nullable(),
|
|
1881
|
+
extraAuthorizeParams: z.string().nullable(),
|
|
1881
1882
|
authMethod: McpAuthMethodSchema.default("static"),
|
|
1882
1883
|
isEnabled: z.boolean(),
|
|
1883
1884
|
version: z.number(),
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* SkillFsEntry data from the DB then delegates here.
|
|
9
9
|
* - Worker-side: refreshSkillsIfChanged (src/utils/skills-refresh.ts) which
|
|
10
10
|
* fetches SkillFsEntry data over HTTP then calls writeSkillsToFilesystem
|
|
11
|
-
* with the worker's own homedir(), writing SKILL.md files to
|
|
12
|
-
*
|
|
11
|
+
* with the worker's own homedir(), writing SKILL.md files to every local
|
|
12
|
+
* harness tree instead of the API box.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { Dirent } from "node:fs";
|
|
@@ -32,6 +32,8 @@ export interface SkillFsEntry {
|
|
|
32
32
|
files: { path: string; content: string; isBinary: boolean }[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export type SkillHarnessTarget = "claude" | "pi" | "codex" | "opencode" | "agents" | "all";
|
|
36
|
+
|
|
35
37
|
/**
|
|
36
38
|
* Marker file written into every swarm-managed skill directory. Cleanup
|
|
37
39
|
* only ever removes directories that contain this marker, so unrelated
|
|
@@ -109,7 +111,7 @@ function reconcileManagedSkillFiles(skillDir: string, currentRelativeFiles: Set<
|
|
|
109
111
|
*/
|
|
110
112
|
export function writeSkillsToFilesystem(
|
|
111
113
|
entries: SkillFsEntry[],
|
|
112
|
-
harnessType:
|
|
114
|
+
harnessType: SkillHarnessTarget = "all",
|
|
113
115
|
home: string,
|
|
114
116
|
): SkillSyncResult {
|
|
115
117
|
const errors: string[] = [];
|
|
@@ -127,6 +129,12 @@ export function writeSkillsToFilesystem(
|
|
|
127
129
|
if (harnessType === "codex" || harnessType === "all") {
|
|
128
130
|
skillDirs.push(join(home, ".codex", "skills"));
|
|
129
131
|
}
|
|
132
|
+
if (harnessType === "opencode" || harnessType === "all") {
|
|
133
|
+
skillDirs.push(join(home, ".opencode", "skills"));
|
|
134
|
+
}
|
|
135
|
+
if (harnessType === "agents" || harnessType === "all") {
|
|
136
|
+
skillDirs.push(join(home, ".agents", "skills"));
|
|
137
|
+
}
|
|
130
138
|
|
|
131
139
|
// Ensure base dirs exist
|
|
132
140
|
for (const dir of skillDirs) {
|