@desplega.ai/agent-swarm 1.69.0 โ 1.69.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 +3 -3
- package/openapi.json +4 -1
- package/package.json +1 -1
- package/src/agentmail/handlers.ts +87 -6
- package/src/be/db.ts +34 -2
- package/src/be/migrations/042_task_context_key.sql +13 -0
- package/src/github/handlers.ts +42 -10
- package/src/gitlab/handlers.ts +29 -5
- package/src/http/schedules.ts +4 -2
- package/src/http/tasks.ts +4 -2
- package/src/linear/sync.ts +22 -10
- package/src/providers/claude-adapter.ts +1 -0
- package/src/scheduler/scheduler.ts +9 -10
- package/src/slack/actions.ts +10 -9
- package/src/slack/assistant.ts +8 -4
- package/src/slack/handlers.ts +8 -3
- package/src/slack/thread-buffer.ts +61 -72
- package/src/tasks/additive-buffer.ts +152 -0
- package/src/tasks/additive-ingress.ts +125 -0
- package/src/tasks/context-key.ts +245 -0
- package/src/tasks/sibling-awareness.ts +144 -0
- package/src/tasks/sibling-block.ts +164 -0
- package/src/tests/additive-buffer.test.ts +186 -0
- package/src/tests/additive-ingress.test.ts +111 -0
- package/src/tests/context-key-db.test.ts +87 -0
- package/src/tests/context-key.test.ts +173 -0
- package/src/tests/sibling-awareness-db.test.ts +172 -0
- package/src/tests/sibling-block.test.ts +232 -0
- package/src/types.ts +5 -0
- package/src/workflows/executors/agent-task.ts +21 -14
package/README.md
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
<a href="https://discord.gg/KZgfyyDVZa">
|
|
32
32
|
<img src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join Discord">
|
|
33
33
|
</a>
|
|
34
|
-
<a href="https://x.com/
|
|
35
|
-
<img src="https://img.shields.io/badge/๐-@
|
|
34
|
+
<a href="https://x.com/desplegalabs">
|
|
35
|
+
<img src="https://img.shields.io/badge/๐-@desplegalabs-000?style=for-the-badge&logo=x&logoColor=white" alt="Follow on X">
|
|
36
36
|
</a>
|
|
37
37
|
</p>
|
|
38
38
|
|
|
@@ -89,7 +89,7 @@ flowchart LR
|
|
|
89
89
|
- **Multi-channel inputs** โ Slack, GitHub, GitLab, email, Linear, and the HTTP API all create tasks. [Integrations](#integrations)
|
|
90
90
|
- **Workflow engine with Human-in-the-Loop** โ DAG-based automation with approval gates, retries, and structured I/O. [Workflows โ](https://docs.agent-swarm.dev/docs/concepts/workflows)
|
|
91
91
|
- **Scheduled & recurring tasks** โ cron-based automation for standing work. [Scheduling โ](https://docs.agent-swarm.dev/docs/concepts/scheduling)
|
|
92
|
-
- **Multi-provider** โ run with Claude Code, OpenAI Codex, or pi-mono. [Harness config โ](https://docs.agent-swarm.dev/docs/guides/harness-configuration)
|
|
92
|
+
- **Multi-provider** โ run with Claude Code, OpenAI Codex, or pi-mono. [Harness config โ](https://docs.agent-swarm.dev/docs/guides/harness-configuration) ยท [Add a new provider โ](https://docs.agent-swarm.dev/docs/guides/harness-providers)
|
|
93
93
|
- **Skills & MCP servers** โ reusable procedural knowledge and per-agent MCP servers with scope cascade. [MCP tools โ](https://docs.agent-swarm.dev/docs/reference/mcp-tools)
|
|
94
94
|
- **Real-time dashboard** โ monitor agents, tasks, and inter-agent chat. [app.agent-swarm.dev โ](https://app.agent-swarm.dev)
|
|
95
95
|
|
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.69.
|
|
5
|
+
"version": "1.69.1",
|
|
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": [
|
|
@@ -5744,6 +5744,9 @@
|
|
|
5744
5744
|
"outputSchema": {
|
|
5745
5745
|
"type": "object",
|
|
5746
5746
|
"additionalProperties": {}
|
|
5747
|
+
},
|
|
5748
|
+
"contextKey": {
|
|
5749
|
+
"type": "string"
|
|
5747
5750
|
}
|
|
5748
5751
|
},
|
|
5749
5752
|
"required": [
|
package/package.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createTaskExtended,
|
|
3
2
|
findTaskByAgentMailThread,
|
|
4
3
|
getAgentById,
|
|
5
4
|
getAgentMailInboxMapping,
|
|
@@ -7,11 +6,63 @@ import {
|
|
|
7
6
|
resolveUser,
|
|
8
7
|
} from "../be/db";
|
|
9
8
|
import { resolveTemplate } from "../prompts/resolver";
|
|
9
|
+
import { createIngressBuffer } from "../tasks/additive-ingress";
|
|
10
|
+
import { agentmailContextKey } from "../tasks/context-key";
|
|
11
|
+
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
10
12
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
11
13
|
// Side-effect import: registers all AgentMail event templates in the in-memory registry
|
|
12
14
|
import "./templates";
|
|
13
15
|
import type { AgentMailMessage, AgentMailWebhookPayload } from "./types";
|
|
14
16
|
|
|
17
|
+
const ACTIVE_TASK_STATUSES = new Set(["pending", "in_progress", "offered", "paused"]);
|
|
18
|
+
|
|
19
|
+
interface BufferedAgentMailMessage {
|
|
20
|
+
from: string;
|
|
21
|
+
subject: string;
|
|
22
|
+
inboxId: string;
|
|
23
|
+
threadId: string;
|
|
24
|
+
messageId: string;
|
|
25
|
+
preview: string;
|
|
26
|
+
agentId: string | null;
|
|
27
|
+
parentTaskId: string;
|
|
28
|
+
requestedByUserId: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const AGENTMAIL_BUFFER_TIMEOUT_MS = Number(process.env.ADDITIVE_AGENTMAIL_BUFFER_MS) || 10_000;
|
|
32
|
+
|
|
33
|
+
const agentmailBuffer = createIngressBuffer<BufferedAgentMailMessage>({
|
|
34
|
+
source: "agentmail",
|
|
35
|
+
envFlag: "ADDITIVE_AGENTMAIL",
|
|
36
|
+
timeoutMs: AGENTMAIL_BUFFER_TIMEOUT_MS,
|
|
37
|
+
onFlush: (items, contextKey) => {
|
|
38
|
+
if (items.length === 0) return;
|
|
39
|
+
const first = items[0]!;
|
|
40
|
+
const combinedPreview = items.map((m) => m.preview).join("\n---\n");
|
|
41
|
+
const followupResult = resolveTemplate("agentmail.email.followup", {
|
|
42
|
+
from: first.from,
|
|
43
|
+
subject: first.subject,
|
|
44
|
+
inbox_id: first.inboxId,
|
|
45
|
+
thread_id: first.threadId,
|
|
46
|
+
preview: `[${items.length} buffered message(s)]\n\n${combinedPreview}`,
|
|
47
|
+
});
|
|
48
|
+
if (followupResult.skipped) return;
|
|
49
|
+
const task = createTaskWithSiblingAwareness(followupResult.text, {
|
|
50
|
+
agentId: first.agentId,
|
|
51
|
+
source: "agentmail",
|
|
52
|
+
taskType: "agentmail-reply",
|
|
53
|
+
agentmailInboxId: first.inboxId,
|
|
54
|
+
agentmailMessageId: first.messageId,
|
|
55
|
+
agentmailThreadId: first.threadId,
|
|
56
|
+
parentTaskId: first.parentTaskId,
|
|
57
|
+
requestedByUserId: first.requestedByUserId,
|
|
58
|
+
contextKey,
|
|
59
|
+
});
|
|
60
|
+
console.log(
|
|
61
|
+
`[AgentMail] Buffered flush โ task ${task.id} (${items.length} messages, thread ${first.threadId})`,
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
15
66
|
/**
|
|
16
67
|
* Extract bare email address from a from_ field like "Taras Yarema <t@desplega.ai>" or "t@desplega.ai".
|
|
17
68
|
*/
|
|
@@ -126,6 +177,31 @@ export async function handleMessageReceived(
|
|
|
126
177
|
// Check for thread continuity - find existing task for this thread
|
|
127
178
|
const existingTask = findTaskByAgentMailThread(thread_id);
|
|
128
179
|
if (existingTask) {
|
|
180
|
+
const contextKey = agentmailContextKey({ threadId: thread_id });
|
|
181
|
+
const siblingInFlight = ACTIVE_TASK_STATUSES.has(existingTask.status);
|
|
182
|
+
|
|
183
|
+
// Opt-in: when ADDITIVE_AGENTMAIL is true, buffer rapid follow-ups while
|
|
184
|
+
// the prior task is still running โ coalesce into ONE follow-up task.
|
|
185
|
+
if (
|
|
186
|
+
agentmailBuffer.enabled &&
|
|
187
|
+
agentmailBuffer.maybeBuffer(contextKey, siblingInFlight, {
|
|
188
|
+
from,
|
|
189
|
+
subject,
|
|
190
|
+
inboxId: inbox_id,
|
|
191
|
+
threadId: thread_id,
|
|
192
|
+
messageId: message_id,
|
|
193
|
+
preview,
|
|
194
|
+
agentId: existingTask.agentId,
|
|
195
|
+
parentTaskId: existingTask.id,
|
|
196
|
+
requestedByUserId,
|
|
197
|
+
})
|
|
198
|
+
) {
|
|
199
|
+
console.log(
|
|
200
|
+
`[AgentMail] Buffered follow-up for thread ${thread_id} (parent ${existingTask.id}, status ${existingTask.status})`,
|
|
201
|
+
);
|
|
202
|
+
return { created: false };
|
|
203
|
+
}
|
|
204
|
+
|
|
129
205
|
// Create a follow-up task with parentTaskId to continue the session
|
|
130
206
|
const followupResult = resolveTemplate("agentmail.email.followup", {
|
|
131
207
|
from,
|
|
@@ -139,7 +215,7 @@ export async function handleMessageReceived(
|
|
|
139
215
|
return { created: false };
|
|
140
216
|
}
|
|
141
217
|
|
|
142
|
-
const task =
|
|
218
|
+
const task = createTaskWithSiblingAwareness(followupResult.text, {
|
|
143
219
|
agentId: existingTask.agentId,
|
|
144
220
|
source: "agentmail",
|
|
145
221
|
taskType: "agentmail-reply",
|
|
@@ -148,6 +224,7 @@ export async function handleMessageReceived(
|
|
|
148
224
|
agentmailThreadId: thread_id,
|
|
149
225
|
parentTaskId: existingTask.id,
|
|
150
226
|
requestedByUserId,
|
|
227
|
+
contextKey,
|
|
151
228
|
});
|
|
152
229
|
|
|
153
230
|
console.log(
|
|
@@ -177,7 +254,7 @@ export async function handleMessageReceived(
|
|
|
177
254
|
return { created: false };
|
|
178
255
|
}
|
|
179
256
|
|
|
180
|
-
const task =
|
|
257
|
+
const task = createTaskWithSiblingAwareness(leadResult.text, {
|
|
181
258
|
agentId: agent.id,
|
|
182
259
|
source: "agentmail",
|
|
183
260
|
taskType: "agentmail-message",
|
|
@@ -185,6 +262,7 @@ export async function handleMessageReceived(
|
|
|
185
262
|
agentmailMessageId: message_id,
|
|
186
263
|
agentmailThreadId: thread_id,
|
|
187
264
|
requestedByUserId,
|
|
265
|
+
contextKey: agentmailContextKey({ threadId: thread_id }),
|
|
188
266
|
});
|
|
189
267
|
|
|
190
268
|
console.log(
|
|
@@ -206,7 +284,7 @@ export async function handleMessageReceived(
|
|
|
206
284
|
return { created: false };
|
|
207
285
|
}
|
|
208
286
|
|
|
209
|
-
const task =
|
|
287
|
+
const task = createTaskWithSiblingAwareness(workerResult.text, {
|
|
210
288
|
agentId: agent.id,
|
|
211
289
|
source: "agentmail",
|
|
212
290
|
taskType: "agentmail-message",
|
|
@@ -214,6 +292,7 @@ export async function handleMessageReceived(
|
|
|
214
292
|
agentmailMessageId: message_id,
|
|
215
293
|
agentmailThreadId: thread_id,
|
|
216
294
|
requestedByUserId,
|
|
295
|
+
contextKey: agentmailContextKey({ threadId: thread_id }),
|
|
217
296
|
});
|
|
218
297
|
|
|
219
298
|
console.log(
|
|
@@ -239,7 +318,7 @@ export async function handleMessageReceived(
|
|
|
239
318
|
return { created: false };
|
|
240
319
|
}
|
|
241
320
|
|
|
242
|
-
const task =
|
|
321
|
+
const task = createTaskWithSiblingAwareness(unmappedResult.text, {
|
|
243
322
|
agentId: lead.id,
|
|
244
323
|
source: "agentmail",
|
|
245
324
|
taskType: "agentmail-message",
|
|
@@ -247,6 +326,7 @@ export async function handleMessageReceived(
|
|
|
247
326
|
agentmailMessageId: message_id,
|
|
248
327
|
agentmailThreadId: thread_id,
|
|
249
328
|
requestedByUserId,
|
|
329
|
+
contextKey: agentmailContextKey({ threadId: thread_id }),
|
|
250
330
|
});
|
|
251
331
|
|
|
252
332
|
console.log(
|
|
@@ -268,13 +348,14 @@ export async function handleMessageReceived(
|
|
|
268
348
|
return { created: false };
|
|
269
349
|
}
|
|
270
350
|
|
|
271
|
-
const task =
|
|
351
|
+
const task = createTaskWithSiblingAwareness(noAgentResult.text, {
|
|
272
352
|
source: "agentmail",
|
|
273
353
|
taskType: "agentmail-message",
|
|
274
354
|
agentmailInboxId: inbox_id,
|
|
275
355
|
agentmailMessageId: message_id,
|
|
276
356
|
agentmailThreadId: thread_id,
|
|
277
357
|
requestedByUserId,
|
|
358
|
+
contextKey: agentmailContextKey({ threadId: thread_id }),
|
|
278
359
|
});
|
|
279
360
|
|
|
280
361
|
console.log(`[AgentMail] Created unassigned task ${task.id} (no lead or mapping available)`);
|
package/src/be/db.ts
CHANGED
|
@@ -802,6 +802,7 @@ type AgentTaskRow = {
|
|
|
802
802
|
workflowRunId: string | null;
|
|
803
803
|
workflowRunStepId: string | null;
|
|
804
804
|
outputSchema: string | null;
|
|
805
|
+
contextKey: string | null;
|
|
805
806
|
createdAt: string;
|
|
806
807
|
lastUpdatedAt: string;
|
|
807
808
|
finishedAt: string | null;
|
|
@@ -862,6 +863,7 @@ function rowToAgentTask(row: AgentTaskRow): AgentTask {
|
|
|
862
863
|
workflowRunId: row.workflowRunId ?? undefined,
|
|
863
864
|
workflowRunStepId: row.workflowRunStepId ?? undefined,
|
|
864
865
|
outputSchema: row.outputSchema ? JSON.parse(row.outputSchema) : undefined,
|
|
866
|
+
contextKey: row.contextKey ?? undefined,
|
|
865
867
|
compactionCount: row.compactionCount ?? undefined,
|
|
866
868
|
peakContextPercent: row.peakContextPercent ?? undefined,
|
|
867
869
|
totalContextTokensUsed: row.totalContextTokensUsed ?? undefined,
|
|
@@ -1430,6 +1432,31 @@ export function getInProgressSlackTasks(): AgentTask[] {
|
|
|
1430
1432
|
.map(rowToAgentTask);
|
|
1431
1433
|
}
|
|
1432
1434
|
|
|
1435
|
+
/**
|
|
1436
|
+
* Return sibling tasks for a given cross-ingress context key, optionally
|
|
1437
|
+
* filtered by status. The returned shape mirrors getInProgressSlackTasks for
|
|
1438
|
+
* consistency; callers can narrow further in TypeScript.
|
|
1439
|
+
*
|
|
1440
|
+
* See src/tasks/context-key.ts for the key schema.
|
|
1441
|
+
*/
|
|
1442
|
+
export function getInProgressTasksByContextKey(
|
|
1443
|
+
contextKey: string,
|
|
1444
|
+
statuses: AgentTaskStatus[] = ["pending", "in_progress", "offered", "paused"],
|
|
1445
|
+
): AgentTask[] {
|
|
1446
|
+
if (!contextKey || statuses.length === 0) return [];
|
|
1447
|
+
const placeholders = statuses.map(() => "?").join(",");
|
|
1448
|
+
return getDb()
|
|
1449
|
+
.prepare<AgentTaskRow, (string | AgentTaskStatus)[]>(
|
|
1450
|
+
`SELECT * FROM agent_tasks
|
|
1451
|
+
WHERE contextKey = ?
|
|
1452
|
+
AND status IN (${placeholders})
|
|
1453
|
+
ORDER BY lastUpdatedAt DESC
|
|
1454
|
+
LIMIT 200`,
|
|
1455
|
+
)
|
|
1456
|
+
.all(contextKey, ...statuses)
|
|
1457
|
+
.map(rowToAgentTask);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1433
1460
|
/**
|
|
1434
1461
|
* Find the most recent agent associated with a specific Slack thread.
|
|
1435
1462
|
* No status filter โ returns the last agent that touched this thread regardless of task state.
|
|
@@ -1934,6 +1961,7 @@ export interface CreateTaskOptions {
|
|
|
1934
1961
|
sourceTaskId?: string;
|
|
1935
1962
|
outputSchema?: Record<string, unknown>;
|
|
1936
1963
|
requestedByUserId?: string;
|
|
1964
|
+
contextKey?: string;
|
|
1937
1965
|
}
|
|
1938
1966
|
|
|
1939
1967
|
/**
|
|
@@ -2004,6 +2032,9 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2004
2032
|
if (parent.requestedByUserId && !options.requestedByUserId) {
|
|
2005
2033
|
options.requestedByUserId = parent.requestedByUserId;
|
|
2006
2034
|
}
|
|
2035
|
+
if (parent.contextKey && !options.contextKey) {
|
|
2036
|
+
options.contextKey = parent.contextKey;
|
|
2037
|
+
}
|
|
2007
2038
|
}
|
|
2008
2039
|
}
|
|
2009
2040
|
|
|
@@ -2029,8 +2060,8 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2029
2060
|
vcsInstallationId, vcsNodeId,
|
|
2030
2061
|
agentmailInboxId, agentmailMessageId, agentmailThreadId,
|
|
2031
2062
|
mentionMessageId, mentionChannelId, dir, parentTaskId, model, scheduleId,
|
|
2032
|
-
workflowRunId, workflowRunStepId, outputSchema, requestedByUserId, swarmVersion, createdAt, lastUpdatedAt
|
|
2033
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2063
|
+
workflowRunId, workflowRunStepId, outputSchema, requestedByUserId, contextKey, swarmVersion, createdAt, lastUpdatedAt
|
|
2064
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`,
|
|
2034
2065
|
)
|
|
2035
2066
|
.get(
|
|
2036
2067
|
id,
|
|
@@ -2070,6 +2101,7 @@ export function createTaskExtended(task: string, options?: CreateTaskOptions): A
|
|
|
2070
2101
|
options?.workflowRunStepId ?? null,
|
|
2071
2102
|
options?.outputSchema ? JSON.stringify(options.outputSchema) : null,
|
|
2072
2103
|
options?.requestedByUserId ?? null,
|
|
2104
|
+
options?.contextKey ?? null,
|
|
2073
2105
|
pkg.version,
|
|
2074
2106
|
now,
|
|
2075
2107
|
now,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Add a uniform context_key column on tasks for cross-ingress sibling awareness.
|
|
2
|
+
-- See src/tasks/context-key.ts for the key schema.
|
|
3
|
+
-- Nullable: historical rows stay NULL, new ingress paths populate it going forward.
|
|
4
|
+
ALTER TABLE agent_tasks ADD COLUMN contextKey TEXT;
|
|
5
|
+
|
|
6
|
+
-- Plain btree index for generic lookups by context key.
|
|
7
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tasks_context_key
|
|
8
|
+
ON agent_tasks(contextKey);
|
|
9
|
+
|
|
10
|
+
-- Composite index supporting the "in-progress sibling" lookup pattern:
|
|
11
|
+
-- WHERE contextKey = ? AND status IN (...).
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_agent_tasks_context_key_status
|
|
13
|
+
ON agent_tasks(contextKey, status);
|
package/src/github/handlers.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { failTask, findTaskByVcs, getAllAgents, resolveUser } from "../be/db";
|
|
2
2
|
import { resolveTemplate } from "../prompts/resolver";
|
|
3
|
+
import { githubContextKey } from "../tasks/context-key";
|
|
4
|
+
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
3
5
|
import {
|
|
4
6
|
detectMention,
|
|
5
7
|
extractMentionContext,
|
|
@@ -24,6 +26,25 @@ import type {
|
|
|
24
26
|
const processedEvents = new Map<string, number>();
|
|
25
27
|
const EVENT_TTL = 60_000;
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Build a uniform cross-ingress context key for a GitHub issue or PR.
|
|
31
|
+
* `repository.full_name` is "owner/repo"; split it and fall back gracefully
|
|
32
|
+
* if the split unexpectedly fails so we never block task creation on a bad key.
|
|
33
|
+
*/
|
|
34
|
+
function buildGithubContextKey(
|
|
35
|
+
fullName: string,
|
|
36
|
+
kind: "issue" | "pr",
|
|
37
|
+
number: number,
|
|
38
|
+
): string | undefined {
|
|
39
|
+
const [owner, repo] = fullName.split("/");
|
|
40
|
+
if (!owner || !repo) return undefined;
|
|
41
|
+
try {
|
|
42
|
+
return githubContextKey({ owner, repo, kind, number });
|
|
43
|
+
} catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
/**
|
|
28
49
|
* Get review state emoji and label
|
|
29
50
|
*/
|
|
@@ -172,7 +193,7 @@ export async function handlePullRequest(
|
|
|
172
193
|
return { created: false };
|
|
173
194
|
}
|
|
174
195
|
|
|
175
|
-
const task =
|
|
196
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
176
197
|
agentId: lead?.id ?? "",
|
|
177
198
|
source: "github",
|
|
178
199
|
vcsProvider: "github",
|
|
@@ -184,6 +205,7 @@ export async function handlePullRequest(
|
|
|
184
205
|
requestedByUserId,
|
|
185
206
|
vcsUrl: pr.html_url,
|
|
186
207
|
vcsInstallationId: installation?.id,
|
|
208
|
+
contextKey: buildGithubContextKey(repository.full_name, "pr", pr.number),
|
|
187
209
|
});
|
|
188
210
|
|
|
189
211
|
if (lead) {
|
|
@@ -271,7 +293,7 @@ export async function handlePullRequest(
|
|
|
271
293
|
return { created: false };
|
|
272
294
|
}
|
|
273
295
|
|
|
274
|
-
const task =
|
|
296
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
275
297
|
agentId: lead?.id ?? "",
|
|
276
298
|
source: "github",
|
|
277
299
|
vcsProvider: "github",
|
|
@@ -283,6 +305,7 @@ export async function handlePullRequest(
|
|
|
283
305
|
requestedByUserId,
|
|
284
306
|
vcsUrl: pr.html_url,
|
|
285
307
|
vcsInstallationId: installation?.id,
|
|
308
|
+
contextKey: buildGithubContextKey(repository.full_name, "pr", pr.number),
|
|
286
309
|
});
|
|
287
310
|
|
|
288
311
|
if (lead) {
|
|
@@ -362,7 +385,7 @@ export async function handlePullRequest(
|
|
|
362
385
|
return { created: false };
|
|
363
386
|
}
|
|
364
387
|
|
|
365
|
-
const task =
|
|
388
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
366
389
|
agentId: lead?.id ?? "",
|
|
367
390
|
source: "github",
|
|
368
391
|
vcsProvider: "github",
|
|
@@ -374,6 +397,7 @@ export async function handlePullRequest(
|
|
|
374
397
|
requestedByUserId,
|
|
375
398
|
vcsUrl: pr.html_url,
|
|
376
399
|
vcsInstallationId: installation?.id,
|
|
400
|
+
contextKey: buildGithubContextKey(repository.full_name, "pr", pr.number),
|
|
377
401
|
});
|
|
378
402
|
|
|
379
403
|
if (lead) {
|
|
@@ -451,7 +475,7 @@ export async function handlePullRequest(
|
|
|
451
475
|
}
|
|
452
476
|
|
|
453
477
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
454
|
-
const task =
|
|
478
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
455
479
|
agentId: lead?.id ?? "",
|
|
456
480
|
source: "github",
|
|
457
481
|
vcsProvider: "github",
|
|
@@ -462,6 +486,7 @@ export async function handlePullRequest(
|
|
|
462
486
|
vcsAuthor: sender.login,
|
|
463
487
|
vcsUrl: pr.html_url,
|
|
464
488
|
vcsInstallationId: installation?.id,
|
|
489
|
+
contextKey: buildGithubContextKey(repository.full_name, "pr", pr.number),
|
|
465
490
|
});
|
|
466
491
|
|
|
467
492
|
if (lead) {
|
|
@@ -524,7 +549,7 @@ export async function handleIssue(
|
|
|
524
549
|
return { created: false };
|
|
525
550
|
}
|
|
526
551
|
|
|
527
|
-
const task =
|
|
552
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
528
553
|
agentId: lead?.id ?? "",
|
|
529
554
|
source: "github",
|
|
530
555
|
vcsProvider: "github",
|
|
@@ -536,6 +561,7 @@ export async function handleIssue(
|
|
|
536
561
|
requestedByUserId,
|
|
537
562
|
vcsUrl: issue.html_url,
|
|
538
563
|
vcsInstallationId: installation?.id,
|
|
564
|
+
contextKey: buildGithubContextKey(repository.full_name, "issue", issue.number),
|
|
539
565
|
});
|
|
540
566
|
|
|
541
567
|
if (lead) {
|
|
@@ -611,7 +637,7 @@ export async function handleIssue(
|
|
|
611
637
|
return { created: false };
|
|
612
638
|
}
|
|
613
639
|
|
|
614
|
-
const task =
|
|
640
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
615
641
|
agentId: lead?.id ?? "",
|
|
616
642
|
source: "github",
|
|
617
643
|
vcsProvider: "github",
|
|
@@ -623,6 +649,7 @@ export async function handleIssue(
|
|
|
623
649
|
requestedByUserId,
|
|
624
650
|
vcsUrl: issue.html_url,
|
|
625
651
|
vcsInstallationId: installation?.id,
|
|
652
|
+
contextKey: buildGithubContextKey(repository.full_name, "issue", issue.number),
|
|
626
653
|
});
|
|
627
654
|
|
|
628
655
|
if (lead) {
|
|
@@ -682,7 +709,7 @@ export async function handleIssue(
|
|
|
682
709
|
}
|
|
683
710
|
|
|
684
711
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
685
|
-
const task =
|
|
712
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
686
713
|
agentId: lead?.id ?? "",
|
|
687
714
|
source: "github",
|
|
688
715
|
vcsProvider: "github",
|
|
@@ -693,6 +720,7 @@ export async function handleIssue(
|
|
|
693
720
|
vcsAuthor: sender.login,
|
|
694
721
|
vcsUrl: issue.html_url,
|
|
695
722
|
vcsInstallationId: installation?.id,
|
|
723
|
+
contextKey: buildGithubContextKey(repository.full_name, "issue", issue.number),
|
|
696
724
|
});
|
|
697
725
|
|
|
698
726
|
if (lead) {
|
|
@@ -780,7 +808,7 @@ export async function handleComment(
|
|
|
780
808
|
}
|
|
781
809
|
|
|
782
810
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
783
|
-
const task =
|
|
811
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
784
812
|
agentId: lead?.id ?? "",
|
|
785
813
|
source: "github",
|
|
786
814
|
vcsProvider: "github",
|
|
@@ -793,6 +821,9 @@ export async function handleComment(
|
|
|
793
821
|
vcsUrl: targetUrl,
|
|
794
822
|
vcsInstallationId: installation?.id,
|
|
795
823
|
vcsNodeId: comment.node_id,
|
|
824
|
+
contextKey: targetNumber
|
|
825
|
+
? buildGithubContextKey(repository.full_name, pull_request ? "pr" : "issue", targetNumber)
|
|
826
|
+
: undefined,
|
|
796
827
|
});
|
|
797
828
|
|
|
798
829
|
if (lead) {
|
|
@@ -895,7 +926,7 @@ export async function handlePullRequestReview(
|
|
|
895
926
|
}
|
|
896
927
|
|
|
897
928
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
898
|
-
const task =
|
|
929
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
899
930
|
agentId: lead?.id ?? "",
|
|
900
931
|
source: "github",
|
|
901
932
|
vcsProvider: "github",
|
|
@@ -907,6 +938,7 @@ export async function handlePullRequestReview(
|
|
|
907
938
|
vcsUrl: review.html_url,
|
|
908
939
|
vcsInstallationId: installation?.id,
|
|
909
940
|
vcsNodeId: review.node_id,
|
|
941
|
+
contextKey: buildGithubContextKey(repository.full_name, "pr", pr.number),
|
|
910
942
|
});
|
|
911
943
|
|
|
912
944
|
if (lead) {
|
package/src/gitlab/handlers.ts
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* - Detects bot mentions
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import { failTask, findTaskByVcs, getAllAgents, resolveUser } from "../be/db";
|
|
12
12
|
import { resolveTemplate } from "../prompts/resolver";
|
|
13
|
+
import { gitlabContextKey } from "../tasks/context-key";
|
|
14
|
+
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
13
15
|
import { GITLAB_BOT_NAME } from "./auth";
|
|
14
16
|
import { addGitLabNoteReaction, addGitLabReaction } from "./reactions";
|
|
15
17
|
// Side-effect import: registers all GitLab event templates in the in-memory registry
|
|
@@ -99,7 +101,7 @@ export async function handleMergeRequest(
|
|
|
99
101
|
return { created: false };
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
const task =
|
|
104
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
103
105
|
agentId: lead?.id ?? null,
|
|
104
106
|
source: "gitlab",
|
|
105
107
|
vcsProvider: "gitlab",
|
|
@@ -110,6 +112,11 @@ export async function handleMergeRequest(
|
|
|
110
112
|
vcsAuthor: user.username,
|
|
111
113
|
requestedByUserId,
|
|
112
114
|
vcsUrl: mr.url,
|
|
115
|
+
contextKey: gitlabContextKey({
|
|
116
|
+
projectId: String(project.id),
|
|
117
|
+
kind: "mr",
|
|
118
|
+
iid: mr.iid,
|
|
119
|
+
}),
|
|
113
120
|
});
|
|
114
121
|
|
|
115
122
|
try {
|
|
@@ -196,7 +203,7 @@ export async function handleIssue(
|
|
|
196
203
|
return { created: false };
|
|
197
204
|
}
|
|
198
205
|
|
|
199
|
-
const task =
|
|
206
|
+
const task = createTaskWithSiblingAwareness(result.text, {
|
|
200
207
|
agentId: lead?.id ?? null,
|
|
201
208
|
source: "gitlab",
|
|
202
209
|
vcsProvider: "gitlab",
|
|
@@ -207,6 +214,11 @@ export async function handleIssue(
|
|
|
207
214
|
vcsAuthor: user.username,
|
|
208
215
|
requestedByUserId,
|
|
209
216
|
vcsUrl: issue.url,
|
|
217
|
+
contextKey: gitlabContextKey({
|
|
218
|
+
projectId: String(project.id),
|
|
219
|
+
kind: "issue",
|
|
220
|
+
iid: issue.iid,
|
|
221
|
+
}),
|
|
210
222
|
});
|
|
211
223
|
|
|
212
224
|
try {
|
|
@@ -293,7 +305,7 @@ export async function handleNote(event: NoteEvent): Promise<{ created: boolean;
|
|
|
293
305
|
return { created: false };
|
|
294
306
|
}
|
|
295
307
|
|
|
296
|
-
const task =
|
|
308
|
+
const task = createTaskWithSiblingAwareness(noteResult.text, {
|
|
297
309
|
agentId: lead?.id ?? null,
|
|
298
310
|
source: "gitlab",
|
|
299
311
|
vcsProvider: "gitlab",
|
|
@@ -305,6 +317,13 @@ export async function handleNote(event: NoteEvent): Promise<{ created: boolean;
|
|
|
305
317
|
vcsAuthor: user.username,
|
|
306
318
|
vcsUrl: targetUrl,
|
|
307
319
|
parentTaskId: existingTask?.id,
|
|
320
|
+
contextKey: targetNumber
|
|
321
|
+
? gitlabContextKey({
|
|
322
|
+
projectId: String(project.id),
|
|
323
|
+
kind: note.noteable_type === "MergeRequest" ? "mr" : "issue",
|
|
324
|
+
iid: targetNumber,
|
|
325
|
+
})
|
|
326
|
+
: undefined,
|
|
308
327
|
});
|
|
309
328
|
|
|
310
329
|
try {
|
|
@@ -362,7 +381,7 @@ export async function handlePipeline(
|
|
|
362
381
|
return { created: false };
|
|
363
382
|
}
|
|
364
383
|
|
|
365
|
-
const task =
|
|
384
|
+
const task = createTaskWithSiblingAwareness(pipelineResult.text, {
|
|
366
385
|
agentId: lead?.id ?? null,
|
|
367
386
|
source: "gitlab",
|
|
368
387
|
vcsProvider: "gitlab",
|
|
@@ -373,6 +392,11 @@ export async function handlePipeline(
|
|
|
373
392
|
vcsAuthor: "",
|
|
374
393
|
vcsUrl: event.merge_request.url,
|
|
375
394
|
parentTaskId: existingTask.id,
|
|
395
|
+
contextKey: gitlabContextKey({
|
|
396
|
+
projectId: String(project.id),
|
|
397
|
+
kind: "mr",
|
|
398
|
+
iid: mrIid,
|
|
399
|
+
}),
|
|
376
400
|
});
|
|
377
401
|
|
|
378
402
|
return { created: true, taskId: task.id };
|
package/src/http/schedules.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { CronExpressionParser } from "cron-parser";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import {
|
|
5
5
|
createScheduledTask,
|
|
6
|
-
createTaskExtended,
|
|
7
6
|
deleteScheduledTask,
|
|
8
7
|
getAgentById,
|
|
9
8
|
getDb,
|
|
@@ -12,6 +11,8 @@ import {
|
|
|
12
11
|
updateScheduledTask,
|
|
13
12
|
} from "../be/db";
|
|
14
13
|
import { calculateNextRun } from "../scheduler/scheduler";
|
|
14
|
+
import { scheduleContextKey } from "../tasks/context-key";
|
|
15
|
+
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
15
16
|
import { getExecutorRegistry } from "../workflows";
|
|
16
17
|
import { handleScheduleTrigger } from "../workflows/triggers";
|
|
17
18
|
import { route } from "./route-def";
|
|
@@ -285,7 +286,7 @@ export async function handleSchedules(
|
|
|
285
286
|
const now = new Date().toISOString();
|
|
286
287
|
|
|
287
288
|
const task = getDb().transaction(() => {
|
|
288
|
-
const createdTask =
|
|
289
|
+
const createdTask = createTaskWithSiblingAwareness(schedule.taskTemplate, {
|
|
289
290
|
creatorAgentId: schedule.createdByAgentId,
|
|
290
291
|
taskType: schedule.taskType,
|
|
291
292
|
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, "manual-run"],
|
|
@@ -294,6 +295,7 @@ export async function handleSchedules(
|
|
|
294
295
|
model: schedule.model,
|
|
295
296
|
scheduleId: schedule.id,
|
|
296
297
|
source: "schedule",
|
|
298
|
+
contextKey: scheduleContextKey({ scheduleId: schedule.id }),
|
|
297
299
|
});
|
|
298
300
|
|
|
299
301
|
if (schedule.scheduleType === "one_time") {
|
package/src/http/tasks.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { z } from "zod";
|
|
|
4
4
|
import {
|
|
5
5
|
cancelTask,
|
|
6
6
|
completeTask,
|
|
7
|
-
createTaskExtended,
|
|
8
7
|
failTask,
|
|
9
8
|
getAllTasks,
|
|
10
9
|
getDb,
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
updateTaskProgress,
|
|
20
19
|
updateTaskVcs,
|
|
21
20
|
} from "../be/db";
|
|
21
|
+
import { createTaskWithSiblingAwareness } from "../tasks/sibling-awareness";
|
|
22
22
|
import { telemetry } from "../telemetry";
|
|
23
23
|
import { route } from "./route-def";
|
|
24
24
|
import { json, jsonError } from "./utils";
|
|
@@ -63,6 +63,7 @@ const createTask = route({
|
|
|
63
63
|
parentTaskId: z.string().optional(),
|
|
64
64
|
source: z.string().optional(),
|
|
65
65
|
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
66
|
+
contextKey: z.string().optional(),
|
|
66
67
|
}),
|
|
67
68
|
responses: {
|
|
68
69
|
201: { description: "Task created" },
|
|
@@ -240,7 +241,7 @@ export async function handleTasks(
|
|
|
240
241
|
if (!parsed) return true;
|
|
241
242
|
|
|
242
243
|
try {
|
|
243
|
-
const task =
|
|
244
|
+
const task = createTaskWithSiblingAwareness(parsed.body.task, {
|
|
244
245
|
agentId: parsed.body.agentId || undefined,
|
|
245
246
|
creatorAgentId: myAgentId || undefined,
|
|
246
247
|
taskType: parsed.body.taskType || undefined,
|
|
@@ -252,6 +253,7 @@ export async function handleTasks(
|
|
|
252
253
|
parentTaskId: parsed.body.parentTaskId || undefined,
|
|
253
254
|
source: (parsed.body.source as import("../types").AgentTaskSource) || "api",
|
|
254
255
|
outputSchema: parsed.body.outputSchema || undefined,
|
|
256
|
+
contextKey: parsed.body.contextKey || undefined,
|
|
255
257
|
});
|
|
256
258
|
|
|
257
259
|
ensure({
|