@desplega.ai/agent-swarm 1.67.5 → 1.68.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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Agent Swarm API",
5
- "version": "1.67.5",
5
+ "version": "1.68.0",
6
6
  "description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
7
7
  },
8
8
  "servers": [
@@ -995,6 +995,69 @@
995
995
  }
996
996
  }
997
997
  },
998
+ "/api/config/env-presence": {
999
+ "get": {
1000
+ "summary": "Check which of the given env var keys are currently set in process.env (presence only, no values)",
1001
+ "tags": [
1002
+ "Config"
1003
+ ],
1004
+ "security": [
1005
+ {
1006
+ "bearerAuth": []
1007
+ }
1008
+ ],
1009
+ "parameters": [
1010
+ {
1011
+ "schema": {
1012
+ "type": "string",
1013
+ "minLength": 1
1014
+ },
1015
+ "required": true,
1016
+ "name": "keys",
1017
+ "in": "query"
1018
+ }
1019
+ ],
1020
+ "responses": {
1021
+ "200": {
1022
+ "description": "Map of key -> boolean (true iff set in process.env)"
1023
+ },
1024
+ "400": {
1025
+ "description": "Validation error"
1026
+ }
1027
+ }
1028
+ }
1029
+ },
1030
+ "/api/config/reload": {
1031
+ "post": {
1032
+ "summary": "Reload global swarm_config into process.env (override=true) and re-init integrations (Slack, GitHub, Linear, AgentMail)",
1033
+ "tags": [
1034
+ "Config"
1035
+ ],
1036
+ "security": [
1037
+ {
1038
+ "bearerAuth": []
1039
+ }
1040
+ ],
1041
+ "requestBody": {
1042
+ "content": {
1043
+ "application/json": {
1044
+ "schema": {
1045
+ "type": "object",
1046
+ "properties": {}
1047
+ }
1048
+ }
1049
+ }
1050
+ },
1051
+ "responses": {
1052
+ "200": {
1053
+ "description": "Reload result"
1054
+ },
1055
+ "500": {
1056
+ "description": "Reload failed"
1057
+ }
1058
+ }
1059
+ }
1060
+ },
998
1061
  "/api/config/{id}": {
999
1062
  "get": {
1000
1063
  "summary": "Get a single config entry by ID",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.67.5",
3
+ "version": "1.68.0",
4
4
  "description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
5
5
  "license": "MIT",
6
6
  "author": "desplega.sh <contact@desplega.sh>",
@@ -10,9 +10,12 @@ import {
10
10
  upsertSwarmConfig,
11
11
  } from "../be/db";
12
12
  import { isReservedConfigKey, reservedKeyError } from "../be/swarm-config-guard";
13
+ import { reloadGlobalConfigsAndIntegrations } from "./core";
13
14
  import { route } from "./route-def";
14
15
  import { json, jsonError } from "./utils";
15
16
 
17
+ const MAX_ENV_PRESENCE_KEYS = 200;
18
+
16
19
  // ─── Route Definitions ───────────────────────────────────────────────────────
17
20
 
18
21
  const getResolvedConfigRoute = route({
@@ -31,6 +34,36 @@ const getResolvedConfigRoute = route({
31
34
  },
32
35
  });
33
36
 
37
+ const envPresence = route({
38
+ method: "get",
39
+ path: "/api/config/env-presence",
40
+ pattern: ["api", "config", "env-presence"],
41
+ summary:
42
+ "Check which of the given env var keys are currently set in process.env (presence only, no values)",
43
+ tags: ["Config"],
44
+ query: z.object({
45
+ keys: z.string().min(1),
46
+ }),
47
+ responses: {
48
+ 200: { description: "Map of key -> boolean (true iff set in process.env)" },
49
+ 400: { description: "Validation error" },
50
+ },
51
+ });
52
+
53
+ const reloadConfigRoute = route({
54
+ method: "post",
55
+ path: "/api/config/reload",
56
+ pattern: ["api", "config", "reload"],
57
+ summary:
58
+ "Reload global swarm_config into process.env (override=true) and re-init integrations (Slack, GitHub, Linear, AgentMail)",
59
+ tags: ["Config"],
60
+ body: z.object({}).optional(),
61
+ responses: {
62
+ 200: { description: "Reload result" },
63
+ 500: { description: "Reload failed" },
64
+ },
65
+ });
66
+
34
67
  const getConfigById = route({
35
68
  method: "get",
36
69
  path: "/api/config/{id}",
@@ -117,6 +150,40 @@ export async function handleConfig(
117
150
  return true;
118
151
  }
119
152
 
153
+ if (envPresence.match(req.method, pathSegments)) {
154
+ const parsed = await envPresence.parse(req, res, pathSegments, queryParams);
155
+ if (!parsed) return true;
156
+ const keys = parsed.query.keys
157
+ .split(",")
158
+ .map((k) => k.trim())
159
+ .filter(Boolean);
160
+ if (keys.length > MAX_ENV_PRESENCE_KEYS) {
161
+ jsonError(res, `Too many keys (max ${MAX_ENV_PRESENCE_KEYS})`, 400);
162
+ return true;
163
+ }
164
+ const presence: Record<string, boolean> = {};
165
+ for (const key of keys) {
166
+ presence[key] = process.env[key] !== undefined && process.env[key] !== "";
167
+ }
168
+ json(res, { presence });
169
+ return true;
170
+ }
171
+
172
+ if (reloadConfigRoute.match(req.method, pathSegments)) {
173
+ try {
174
+ const result = await reloadGlobalConfigsAndIntegrations();
175
+ console.log(
176
+ `[reload-config] Loaded ${result.configsLoaded} config(s), re-initialized: ${result.integrationsReinitialized.join(", ") || "none"}`,
177
+ );
178
+ json(res, { success: true, ...result });
179
+ } catch (e) {
180
+ const message = e instanceof Error ? e.message : String(e);
181
+ console.error("[reload-config] Failed:", message);
182
+ jsonError(res, `Failed to reload config: ${message}`, 500);
183
+ }
184
+ return true;
185
+ }
186
+
120
187
  if (getConfigById.match(req.method, pathSegments)) {
121
188
  const parsed = await getConfigById.parse(req, res, pathSegments, queryParams);
122
189
  if (!parsed) return true;
package/src/http/core.ts CHANGED
@@ -43,6 +43,42 @@ export function loadGlobalConfigsIntoEnv(override = false): string[] {
43
43
  return updated;
44
44
  }
45
45
 
46
+ export type ReloadConfigResult = {
47
+ configsLoaded: number;
48
+ keysUpdated: string[];
49
+ integrationsReinitialized: string[];
50
+ };
51
+
52
+ /**
53
+ * Re-read swarm_config into process.env with override=true, then reset and
54
+ * re-init each integration so long-lived clients (Slack socket mode, etc.)
55
+ * pick up the new values without requiring a process restart.
56
+ */
57
+ export async function reloadGlobalConfigsAndIntegrations(): Promise<ReloadConfigResult> {
58
+ const updated = loadGlobalConfigsIntoEnv(true);
59
+
60
+ const integrations: string[] = [];
61
+
62
+ resetAgentMail();
63
+ if (initAgentMail()) integrations.push("agentmail");
64
+
65
+ resetGitHub();
66
+ if (initGitHub()) integrations.push("github");
67
+
68
+ resetLinear();
69
+ if (initLinear()) integrations.push("linear");
70
+
71
+ await stopSlackApp();
72
+ await startSlackApp();
73
+ integrations.push("slack");
74
+
75
+ return {
76
+ configsLoaded: updated.length,
77
+ keysUpdated: updated,
78
+ integrationsReinitialized: integrations,
79
+ };
80
+ }
81
+
46
82
  export async function handleCore(
47
83
  req: IncomingMessage,
48
84
  res: ServerResponse,
@@ -116,38 +152,12 @@ export async function handleCore(
116
152
  // POST /internal/reload-config — re-read swarm_config into process.env and re-init integrations
117
153
  if (req.method === "POST" && req.url === "/internal/reload-config") {
118
154
  try {
119
- const updated = loadGlobalConfigsIntoEnv(true);
120
-
121
- // Re-initialize integrations so they pick up new secrets
122
- const integrations: string[] = [];
123
-
124
- resetAgentMail();
125
- if (initAgentMail()) integrations.push("agentmail");
126
-
127
- resetGitHub();
128
- if (initGitHub()) integrations.push("github");
129
-
130
- resetLinear();
131
- if (initLinear()) integrations.push("linear");
132
-
133
- // Slack: stop and restart to pick up new token
134
- await stopSlackApp();
135
- await startSlackApp();
136
- integrations.push("slack");
137
-
155
+ const result = await reloadGlobalConfigsAndIntegrations();
138
156
  console.log(
139
- `[reload-config] Loaded ${updated.length} config(s), re-initialized: ${integrations.join(", ") || "none"}`,
157
+ `[reload-config] Loaded ${result.configsLoaded} config(s), re-initialized: ${result.integrationsReinitialized.join(", ") || "none"}`,
140
158
  );
141
-
142
159
  res.writeHead(200, { "Content-Type": "application/json" });
143
- res.end(
144
- JSON.stringify({
145
- success: true,
146
- configsLoaded: updated.length,
147
- keysUpdated: updated,
148
- integrationsReinitialized: integrations,
149
- }),
150
- );
160
+ res.end(JSON.stringify({ success: true, ...result }));
151
161
  } catch (e) {
152
162
  const message = e instanceof Error ? e.message : String(e);
153
163
  console.error("[reload-config] Failed:", message);