@desplega.ai/agent-swarm 1.51.2 → 1.52.1
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 +131 -0
- package/openapi.json +767 -4
- package/package.json +3 -1
- package/src/be/db.ts +669 -0
- package/src/be/migrations/019_skills.sql +65 -0
- package/src/be/migrations/020_approval_requests.sql +41 -0
- package/src/be/migrations/runner.ts +4 -4
- package/src/be/skill-parser.ts +70 -0
- package/src/be/skill-sync.ts +106 -0
- package/src/commands/runner.ts +299 -52
- package/src/http/agents.ts +29 -0
- package/src/http/approval-requests.ts +310 -0
- package/src/http/config.ts +3 -3
- package/src/http/index.ts +26 -2
- package/src/http/poll.ts +15 -0
- package/src/http/skills.ts +479 -0
- package/src/http/tasks.ts +94 -0
- package/src/linear/outbound.ts +12 -12
- package/src/prompts/base-prompt.ts +8 -0
- package/src/providers/claude-adapter.ts +19 -3
- package/src/scheduler/scheduler.ts +24 -1
- package/src/server.ts +29 -0
- package/src/slack/blocks.ts +1 -1
- package/src/tests/approval-requests.test.ts +948 -0
- package/src/tests/skill-parser.test.ts +178 -0
- package/src/tests/skill-sync.test.ts +171 -0
- package/src/tests/slack-blocks.test.ts +3 -2
- package/src/tests/structured-output.test.ts +1 -0
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/tool-call-progress.test.ts +207 -0
- package/src/tests/tool-registrar-no-input.test.ts +114 -0
- package/src/tests/update-profile-auth.test.ts +1 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tools/request-human-input.ts +117 -0
- package/src/tools/skills/index.ts +11 -0
- package/src/tools/skills/skill-create.ts +105 -0
- package/src/tools/skills/skill-delete.ts +67 -0
- package/src/tools/skills/skill-get.ts +75 -0
- package/src/tools/skills/skill-install-remote.ts +152 -0
- package/src/tools/skills/skill-install.ts +101 -0
- package/src/tools/skills/skill-list.ts +77 -0
- package/src/tools/skills/skill-publish.ts +123 -0
- package/src/tools/skills/skill-search.ts +43 -0
- package/src/tools/skills/skill-sync-remote.ts +128 -0
- package/src/tools/skills/skill-uninstall.ts +60 -0
- package/src/tools/skills/skill-update.ts +128 -0
- package/src/tools/store-progress.ts +31 -0
- package/src/tools/templates.ts +28 -0
- package/src/tools/tool-config.ts +16 -0
- package/src/tools/utils.ts +9 -7
- package/src/types.ts +54 -0
- package/src/workflows/executors/human-in-the-loop.ts +273 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/recovery.ts +72 -0
- package/src/workflows/resume.ts +65 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
createApprovalRequest,
|
|
5
|
+
createTaskExtended,
|
|
6
|
+
getApprovalRequestById,
|
|
7
|
+
getTaskById,
|
|
8
|
+
listApprovalRequests,
|
|
9
|
+
resolveApprovalRequest,
|
|
10
|
+
} from "../be/db";
|
|
11
|
+
import { resolveTemplate } from "../prompts/resolver";
|
|
12
|
+
import { workflowEventBus } from "../workflows/event-bus";
|
|
13
|
+
import { route } from "./route-def";
|
|
14
|
+
import { json, jsonError } from "./utils";
|
|
15
|
+
|
|
16
|
+
// ─── Route Definitions ───────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const QuestionSchema = z.object({
|
|
19
|
+
id: z.string(),
|
|
20
|
+
type: z.enum(["approval", "text", "single-select", "multi-select", "boolean"]),
|
|
21
|
+
label: z.string(),
|
|
22
|
+
required: z.boolean().optional(),
|
|
23
|
+
description: z.string().optional(),
|
|
24
|
+
placeholder: z.string().optional(),
|
|
25
|
+
multiline: z.boolean().optional(),
|
|
26
|
+
options: z
|
|
27
|
+
.array(
|
|
28
|
+
z.object({
|
|
29
|
+
value: z.string(),
|
|
30
|
+
label: z.string(),
|
|
31
|
+
description: z.string().optional(),
|
|
32
|
+
}),
|
|
33
|
+
)
|
|
34
|
+
.optional(),
|
|
35
|
+
minSelections: z.number().int().min(0).optional(),
|
|
36
|
+
maxSelections: z.number().int().min(1).optional(),
|
|
37
|
+
defaultValue: z.boolean().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const createRoute = route({
|
|
41
|
+
method: "post",
|
|
42
|
+
path: "/api/approval-requests",
|
|
43
|
+
pattern: ["api", "approval-requests"],
|
|
44
|
+
summary: "Create a new approval request",
|
|
45
|
+
tags: ["ApprovalRequests"],
|
|
46
|
+
body: z.object({
|
|
47
|
+
title: z.string().min(1),
|
|
48
|
+
questions: z.array(QuestionSchema).min(1),
|
|
49
|
+
approvers: z.object({
|
|
50
|
+
users: z.array(z.string()).optional(),
|
|
51
|
+
roles: z.array(z.string()).optional(),
|
|
52
|
+
policy: z.union([
|
|
53
|
+
z.literal("any"),
|
|
54
|
+
z.literal("all"),
|
|
55
|
+
z.object({ min: z.number().int().min(1) }),
|
|
56
|
+
]),
|
|
57
|
+
}),
|
|
58
|
+
workflowRunId: z.string().uuid().optional(),
|
|
59
|
+
workflowRunStepId: z.string().uuid().optional(),
|
|
60
|
+
sourceTaskId: z.string().uuid().optional(),
|
|
61
|
+
timeoutSeconds: z.number().int().min(1).optional(),
|
|
62
|
+
notifications: z
|
|
63
|
+
.array(
|
|
64
|
+
z.object({
|
|
65
|
+
channel: z.enum(["slack", "email"]),
|
|
66
|
+
target: z.string(),
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
.optional(),
|
|
70
|
+
}),
|
|
71
|
+
responses: {
|
|
72
|
+
201: { description: "Approval request created" },
|
|
73
|
+
400: { description: "Validation error" },
|
|
74
|
+
},
|
|
75
|
+
auth: { apiKey: true },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const getByIdRoute = route({
|
|
79
|
+
method: "get",
|
|
80
|
+
path: "/api/approval-requests/{id}",
|
|
81
|
+
pattern: ["api", "approval-requests", null],
|
|
82
|
+
summary: "Get approval request details",
|
|
83
|
+
tags: ["ApprovalRequests"],
|
|
84
|
+
params: z.object({ id: z.string().uuid() }),
|
|
85
|
+
responses: {
|
|
86
|
+
200: { description: "Approval request details" },
|
|
87
|
+
404: { description: "Not found" },
|
|
88
|
+
},
|
|
89
|
+
auth: { apiKey: true },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const respondRoute = route({
|
|
93
|
+
method: "post",
|
|
94
|
+
path: "/api/approval-requests/{id}/respond",
|
|
95
|
+
pattern: ["api", "approval-requests", null, "respond"],
|
|
96
|
+
summary: "Submit a response to an approval request",
|
|
97
|
+
tags: ["ApprovalRequests"],
|
|
98
|
+
params: z.object({ id: z.string().uuid() }),
|
|
99
|
+
body: z.object({
|
|
100
|
+
responses: z.record(z.string(), z.unknown()),
|
|
101
|
+
respondedBy: z.string().optional(),
|
|
102
|
+
}),
|
|
103
|
+
responses: {
|
|
104
|
+
200: { description: "Response recorded" },
|
|
105
|
+
400: { description: "Validation error" },
|
|
106
|
+
404: { description: "Not found" },
|
|
107
|
+
409: { description: "Already resolved" },
|
|
108
|
+
},
|
|
109
|
+
auth: { apiKey: true },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const listRoute = route({
|
|
113
|
+
method: "get",
|
|
114
|
+
path: "/api/approval-requests",
|
|
115
|
+
pattern: ["api", "approval-requests"],
|
|
116
|
+
summary: "List approval requests with optional filters",
|
|
117
|
+
tags: ["ApprovalRequests"],
|
|
118
|
+
query: z.object({
|
|
119
|
+
status: z.string().optional(),
|
|
120
|
+
workflowRunId: z.string().optional(),
|
|
121
|
+
limit: z.coerce.number().optional(),
|
|
122
|
+
}),
|
|
123
|
+
responses: {
|
|
124
|
+
200: { description: "List of approval requests" },
|
|
125
|
+
},
|
|
126
|
+
auth: { apiKey: true },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ─── Handler ─────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export async function handleApprovalRequests(
|
|
132
|
+
req: IncomingMessage,
|
|
133
|
+
res: ServerResponse,
|
|
134
|
+
pathSegments: string[],
|
|
135
|
+
queryParams: URLSearchParams,
|
|
136
|
+
): Promise<boolean> {
|
|
137
|
+
// 4-segment: POST /api/approval-requests/{id}/respond
|
|
138
|
+
if (respondRoute.match(req.method, pathSegments)) {
|
|
139
|
+
const parsed = await respondRoute.parse(req, res, pathSegments, queryParams);
|
|
140
|
+
if (!parsed) return true;
|
|
141
|
+
|
|
142
|
+
const existing = getApprovalRequestById(parsed.params.id);
|
|
143
|
+
if (!existing) {
|
|
144
|
+
jsonError(res, "Approval request not found", 404);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (existing.status !== "pending") {
|
|
149
|
+
jsonError(res, `Approval request already resolved with status: ${existing.status}`, 409);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Determine status from responses: if any approval question has approved: false → rejected
|
|
154
|
+
const questions = existing.questions as Array<{ id: string; type: string }>;
|
|
155
|
+
let status: "approved" | "rejected" = "approved";
|
|
156
|
+
for (const q of questions) {
|
|
157
|
+
if (q.type === "approval") {
|
|
158
|
+
const answer = parsed.body.responses[q.id] as { approved?: boolean } | undefined;
|
|
159
|
+
if (answer && answer.approved === false) {
|
|
160
|
+
status = "rejected";
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const updated = resolveApprovalRequest(parsed.params.id, {
|
|
167
|
+
status,
|
|
168
|
+
responses: parsed.body.responses,
|
|
169
|
+
resolvedBy: parsed.body.respondedBy,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!updated) {
|
|
173
|
+
jsonError(
|
|
174
|
+
res,
|
|
175
|
+
"Failed to resolve approval request (may have been resolved concurrently)",
|
|
176
|
+
409,
|
|
177
|
+
);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Emit event for workflow resume
|
|
182
|
+
if (updated.workflowRunId && updated.workflowRunStepId) {
|
|
183
|
+
workflowEventBus.emit("approval.resolved", {
|
|
184
|
+
requestId: updated.id,
|
|
185
|
+
status: updated.status,
|
|
186
|
+
responses: updated.responses,
|
|
187
|
+
workflowRunId: updated.workflowRunId,
|
|
188
|
+
workflowRunStepId: updated.workflowRunStepId,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// For standalone (non-workflow) requests, create a follow-up task
|
|
193
|
+
// so the requesting agent is notified of the human's response
|
|
194
|
+
if (!updated.workflowRunId && updated.sourceTaskId) {
|
|
195
|
+
const sourceTask = getTaskById(updated.sourceTaskId);
|
|
196
|
+
if (sourceTask) {
|
|
197
|
+
// Format responses for the template
|
|
198
|
+
const formattedResponses = formatResponses(
|
|
199
|
+
updated.questions as Array<{ id: string; type: string; label: string }>,
|
|
200
|
+
updated.responses as Record<string, unknown>,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const { text: taskText } = resolveTemplate("hitl.follow_up", {
|
|
204
|
+
request_id: updated.id,
|
|
205
|
+
title: updated.title,
|
|
206
|
+
status: updated.status,
|
|
207
|
+
responses: formattedResponses,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
createTaskExtended(taskText, {
|
|
211
|
+
agentId: sourceTask.agentId,
|
|
212
|
+
parentTaskId: updated.sourceTaskId,
|
|
213
|
+
source: "system",
|
|
214
|
+
taskType: "hitl-follow-up",
|
|
215
|
+
tags: ["hitl", "follow-up"],
|
|
216
|
+
// Explicit Slack metadata — parentTaskId auto-inherits too,
|
|
217
|
+
// but being explicit ensures the follow-up task always gets
|
|
218
|
+
// the right thread context even if inheritance logic changes.
|
|
219
|
+
slackChannelId: sourceTask.slackChannelId ?? undefined,
|
|
220
|
+
slackThreadTs: sourceTask.slackThreadTs ?? undefined,
|
|
221
|
+
slackUserId: sourceTask.slackUserId ?? undefined,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
json(res, { approvalRequest: updated });
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 3-segment with param: GET /api/approval-requests/{id}
|
|
231
|
+
if (getByIdRoute.match(req.method, pathSegments)) {
|
|
232
|
+
const parsed = await getByIdRoute.parse(req, res, pathSegments, queryParams);
|
|
233
|
+
if (!parsed) return true;
|
|
234
|
+
|
|
235
|
+
const request = getApprovalRequestById(parsed.params.id);
|
|
236
|
+
if (!request) {
|
|
237
|
+
jsonError(res, "Approval request not found", 404);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
json(res, { approvalRequest: request });
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 2-segment: POST /api/approval-requests (create)
|
|
246
|
+
if (createRoute.match(req.method, pathSegments)) {
|
|
247
|
+
const parsed = await createRoute.parse(req, res, pathSegments, queryParams);
|
|
248
|
+
if (!parsed) return true;
|
|
249
|
+
|
|
250
|
+
const id = crypto.randomUUID();
|
|
251
|
+
const request = createApprovalRequest({
|
|
252
|
+
id,
|
|
253
|
+
title: parsed.body.title,
|
|
254
|
+
questions: parsed.body.questions,
|
|
255
|
+
approvers: parsed.body.approvers,
|
|
256
|
+
workflowRunId: parsed.body.workflowRunId,
|
|
257
|
+
workflowRunStepId: parsed.body.workflowRunStepId,
|
|
258
|
+
sourceTaskId: parsed.body.sourceTaskId,
|
|
259
|
+
timeoutSeconds: parsed.body.timeoutSeconds,
|
|
260
|
+
notificationChannels: parsed.body.notifications,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
res.writeHead(201, { "Content-Type": "application/json" });
|
|
264
|
+
res.end(JSON.stringify({ approvalRequest: request }));
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 2-segment: GET /api/approval-requests (list)
|
|
269
|
+
if (listRoute.match(req.method, pathSegments)) {
|
|
270
|
+
const parsed = await listRoute.parse(req, res, pathSegments, queryParams);
|
|
271
|
+
if (!parsed) return true;
|
|
272
|
+
|
|
273
|
+
const requests = listApprovalRequests({
|
|
274
|
+
status: parsed.query.status || undefined,
|
|
275
|
+
workflowRunId: parsed.query.workflowRunId || undefined,
|
|
276
|
+
limit: parsed.query.limit || undefined,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
json(res, { approvalRequests: requests });
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
function formatResponses(
|
|
289
|
+
questions: Array<{ id: string; type: string; label: string }>,
|
|
290
|
+
responses: Record<string, unknown>,
|
|
291
|
+
): string {
|
|
292
|
+
return questions
|
|
293
|
+
.map((q) => {
|
|
294
|
+
const answer = responses[q.id];
|
|
295
|
+
let answerText: string;
|
|
296
|
+
if (answer == null) {
|
|
297
|
+
answerText = "(no answer)";
|
|
298
|
+
} else if (q.type === "approval") {
|
|
299
|
+
const a = answer as { approved?: boolean; comment?: string };
|
|
300
|
+
answerText = a.approved ? "Approved" : "Rejected";
|
|
301
|
+
if (a.comment) answerText += ` — ${a.comment}`;
|
|
302
|
+
} else if (typeof answer === "object") {
|
|
303
|
+
answerText = JSON.stringify(answer);
|
|
304
|
+
} else {
|
|
305
|
+
answerText = String(answer);
|
|
306
|
+
}
|
|
307
|
+
return `- ${q.label}: ${answerText}`;
|
|
308
|
+
})
|
|
309
|
+
.join("\n");
|
|
310
|
+
}
|
package/src/http/config.ts
CHANGED
|
@@ -69,12 +69,12 @@ const upsertConfig = route({
|
|
|
69
69
|
tags: ["Config"],
|
|
70
70
|
body: z.object({
|
|
71
71
|
scope: z.enum(["global", "agent", "repo"]),
|
|
72
|
-
scopeId: z.string().
|
|
72
|
+
scopeId: z.string().nullish(),
|
|
73
73
|
key: z.string().min(1),
|
|
74
74
|
value: z.unknown(),
|
|
75
75
|
isSecret: z.boolean().optional(),
|
|
76
|
-
envPath: z.string().
|
|
77
|
-
description: z.string().
|
|
76
|
+
envPath: z.string().nullish(),
|
|
77
|
+
description: z.string().nullish(),
|
|
78
78
|
}),
|
|
79
79
|
responses: {
|
|
80
80
|
200: { description: "Config entry upserted" },
|
package/src/http/index.ts
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
type Server,
|
|
5
5
|
type ServerResponse,
|
|
6
6
|
} from "node:http";
|
|
7
|
+
import { assert, initialize } from "@desplega.ai/business-use";
|
|
7
8
|
import type { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
8
|
-
import { hasCapability } from "@/server";
|
|
9
|
+
import { getEnabledCapabilities, hasCapability } from "@/server";
|
|
9
10
|
import { initAgentMail } from "../agentmail";
|
|
10
11
|
import { closeDb } from "../be/db";
|
|
11
12
|
import { initGitHub } from "../github";
|
|
@@ -16,6 +17,7 @@ import { startSlackApp, stopSlackApp } from "../slack";
|
|
|
16
17
|
import { initWorkflows } from "../workflows";
|
|
17
18
|
import { handleActiveSessions } from "./active-sessions";
|
|
18
19
|
import { handleAgentRegister, handleAgentsRest } from "./agents";
|
|
20
|
+
import { handleApprovalRequests } from "./approval-requests";
|
|
19
21
|
import { handleConfig } from "./config";
|
|
20
22
|
import { handleCore, loadGlobalConfigsIntoEnv } from "./core";
|
|
21
23
|
import { handleDbQuery } from "./db-query";
|
|
@@ -28,6 +30,7 @@ import { handlePromptTemplates } from "./prompt-templates";
|
|
|
28
30
|
import { handleRepos } from "./repos";
|
|
29
31
|
import { handleSchedules } from "./schedules";
|
|
30
32
|
import { handleSessionData } from "./session-data";
|
|
33
|
+
import { handleSkills } from "./skills";
|
|
31
34
|
import { handleStats } from "./stats";
|
|
32
35
|
import { handleTasks } from "./tasks";
|
|
33
36
|
import { handleTrackers } from "./trackers";
|
|
@@ -43,6 +46,7 @@ const globalState = globalThis as typeof globalThis & {
|
|
|
43
46
|
__httpServer?: Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
44
47
|
__transports?: Record<string, StreamableHTTPServerTransport>;
|
|
45
48
|
__sigintRegistered?: boolean;
|
|
49
|
+
__runId?: string;
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
// Clean up previous server on hot reload
|
|
@@ -104,10 +108,12 @@ const httpServer = createHttpServer(async (req, res) => {
|
|
|
104
108
|
() => handleEpics(req, res, pathSegments, queryParams, myAgentId),
|
|
105
109
|
() => handleSchedules(req, res, pathSegments, queryParams, myAgentId),
|
|
106
110
|
() => handleWorkflows(req, res, pathSegments, queryParams, myAgentId),
|
|
111
|
+
() => handleApprovalRequests(req, res, pathSegments, queryParams),
|
|
107
112
|
() => handleConfig(req, res, pathSegments, queryParams),
|
|
108
113
|
() => handlePromptTemplates(req, res, pathSegments, queryParams),
|
|
109
114
|
() => handleDbQuery(req, res, pathSegments, queryParams),
|
|
110
115
|
() => handleRepos(req, res, pathSegments, queryParams),
|
|
116
|
+
() => handleSkills(req, res, pathSegments, queryParams, myAgentId),
|
|
111
117
|
() => handleMemory(req, res, pathSegments, myAgentId),
|
|
112
118
|
() => handleMcp(req, res, transports),
|
|
113
119
|
];
|
|
@@ -163,10 +169,26 @@ if (!globalState.__sigintRegistered) {
|
|
|
163
169
|
process.on("SIGTERM", shutdown);
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
if (!globalState.__runId) {
|
|
173
|
+
globalState.__runId = `run_${Date.now()}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// business-use initialization (no-op if envs not set)
|
|
177
|
+
initialize();
|
|
178
|
+
|
|
166
179
|
httpServer
|
|
167
180
|
.listen(port, async () => {
|
|
168
181
|
console.log(`MCP HTTP server running on http://localhost:${port}/mcp`);
|
|
169
182
|
|
|
183
|
+
assert({
|
|
184
|
+
id: "listen",
|
|
185
|
+
flow: "api",
|
|
186
|
+
runId: globalState.__runId!,
|
|
187
|
+
data: {
|
|
188
|
+
capabilities: getEnabledCapabilities(),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
170
192
|
// Load global swarm configs into process.env (so integrations can read them)
|
|
171
193
|
// Infrastructure-level env vars take precedence — only missing keys are filled.
|
|
172
194
|
try {
|
|
@@ -201,7 +223,9 @@ httpServer
|
|
|
201
223
|
const { startScheduler } = await import("../scheduler");
|
|
202
224
|
const { getExecutorRegistry } = await import("../workflows");
|
|
203
225
|
const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 10000;
|
|
204
|
-
startScheduler(getExecutorRegistry(), intervalMs
|
|
226
|
+
startScheduler(getExecutorRegistry(), intervalMs, {
|
|
227
|
+
runId: globalState.__runId!,
|
|
228
|
+
});
|
|
205
229
|
}
|
|
206
230
|
|
|
207
231
|
// Start heartbeat triage (unless disabled)
|
package/src/http/poll.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import { ensure } from "@desplega.ai/business-use";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import {
|
|
4
5
|
claimMentions,
|
|
@@ -128,6 +129,20 @@ export async function handlePoll(
|
|
|
128
129
|
if (pendingTask) {
|
|
129
130
|
// Mark task as in_progress immediately to prevent duplicate polling
|
|
130
131
|
startTask(pendingTask.id);
|
|
132
|
+
|
|
133
|
+
ensure({
|
|
134
|
+
id: "started",
|
|
135
|
+
flow: "task",
|
|
136
|
+
runId: pendingTask.id,
|
|
137
|
+
depIds: ["created"],
|
|
138
|
+
data: {
|
|
139
|
+
taskId: pendingTask.id,
|
|
140
|
+
agentId: myAgentId,
|
|
141
|
+
previousStatus: pendingTask.status,
|
|
142
|
+
},
|
|
143
|
+
validator: (data) => data.previousStatus === "pending",
|
|
144
|
+
});
|
|
145
|
+
|
|
131
146
|
return {
|
|
132
147
|
trigger: {
|
|
133
148
|
type: "task_assigned",
|