@desplega.ai/agent-swarm 1.157.0 → 1.158.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/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
- package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +29 -15
- package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
- package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
- package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +2 -1
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/dist/cli-2daksqps.js +0 -378
- package/dist/rbac-roles-3gxmnb20.js +0 -37
package/src/http/sessions.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { countSessions, getRootTaskChain, getTaskById, listRecentSessions } from "../be/db";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { getTaskSteeringFieldsForTasks } from "../be/steering";
|
|
5
|
+
import { getTaskCitationsForTasks, TaskCitationSchema } from "../be/task-citations";
|
|
6
6
|
import { mintSessionToken, revokeSessionToken } from "../be/users";
|
|
7
|
-
import { AgentTaskSchema, AgentTaskStatusSchema, SteerModeSchema } from "../types";
|
|
7
|
+
import { type AgentTask, AgentTaskSchema, AgentTaskStatusSchema, SteerModeSchema } from "../types";
|
|
8
8
|
import { getRequestAuth } from "../utils/request-auth-context";
|
|
9
9
|
import { route } from "./route-def";
|
|
10
10
|
import { jsonError } from "./utils";
|
|
@@ -144,6 +144,12 @@ const listSessions = route({
|
|
|
144
144
|
* excluded. Omit to return every session (legacy / non-UI callers).
|
|
145
145
|
*/
|
|
146
146
|
requestedByUserId: z.string().min(1).optional(),
|
|
147
|
+
/**
|
|
148
|
+
* When present, restrict results to root tasks whose `contextKey` starts
|
|
149
|
+
* with this literal prefix. The UI contextual session panel passes its page
|
|
150
|
+
* key plus a trailing `:` (e.g. `task:ui:workflow:abc:`).
|
|
151
|
+
*/
|
|
152
|
+
contextKeyPrefix: z.string().min(1).optional(),
|
|
147
153
|
/** `full` restores the legacy shape (full root `AgentTask`); default is slim. */
|
|
148
154
|
fields: z.enum(["full", "slim"]).optional(),
|
|
149
155
|
}),
|
|
@@ -235,18 +241,20 @@ export async function handleSessions(
|
|
|
235
241
|
source: sources,
|
|
236
242
|
q: parsed.query.q,
|
|
237
243
|
requestedByUserId: parsed.query.requestedByUserId,
|
|
244
|
+
contextKeyPrefix: parsed.query.contextKeyPrefix,
|
|
238
245
|
};
|
|
239
246
|
// List responses default to slim (root is a task summary); `?fields=full` restores it.
|
|
240
247
|
const sessions =
|
|
241
248
|
parsed.query.fields === "full"
|
|
242
249
|
? await listRecentSessions(baseOpts)
|
|
243
250
|
: await listRecentSessions({ ...baseOpts, slim: true });
|
|
244
|
-
// Filter-aware total: same
|
|
245
|
-
//
|
|
251
|
+
// Filter-aware total: same WHERE as the list query, so the UI pager
|
|
252
|
+
// reflects the filtered result set.
|
|
246
253
|
const total = await countSessions({
|
|
247
254
|
source: sources,
|
|
248
255
|
q: parsed.query.q,
|
|
249
256
|
requestedByUserId: parsed.query.requestedByUserId,
|
|
257
|
+
contextKeyPrefix: parsed.query.contextKeyPrefix,
|
|
250
258
|
});
|
|
251
259
|
listSessions.respond(res, 200, {
|
|
252
260
|
sessions,
|
|
@@ -266,20 +274,19 @@ export async function handleSessions(
|
|
|
266
274
|
return true;
|
|
267
275
|
}
|
|
268
276
|
const chain = await getRootTaskChain(parsed.params.rootTaskId);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
})),
|
|
281
|
-
),
|
|
277
|
+
// Fixed query count regardless of chain length: one agents lookup and one
|
|
278
|
+
// citations lookup for the whole chain (chunked past 500 ids).
|
|
279
|
+
const tasks = [root, ...chain];
|
|
280
|
+
const [steering, citations] = await Promise.all([
|
|
281
|
+
getTaskSteeringFieldsForTasks(tasks),
|
|
282
|
+
getTaskCitationsForTasks(tasks.map((task) => task.id)),
|
|
283
|
+
]);
|
|
284
|
+
const decorate = (task: AgentTask) => ({
|
|
285
|
+
...task,
|
|
286
|
+
...steering.get(task.id)!,
|
|
287
|
+
citations: citations.get(task.id)!,
|
|
282
288
|
});
|
|
289
|
+
getSession.respond(res, 200, { root: decorate(root), chain: chain.map(decorate) });
|
|
283
290
|
return true;
|
|
284
291
|
}
|
|
285
292
|
|
package/src/http/stats.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { AgentLogSchema, ScheduledTaskSchema, ServiceSchema } from "../types";
|
|
|
18
18
|
import { isMultiRuntimeEnabled } from "../utils/multi-runtime";
|
|
19
19
|
import { resolveHttpFavoriteOwner } from "./favorite-owner";
|
|
20
20
|
import { route } from "./route-def";
|
|
21
|
+
import { scheduleSummaryWithFavoriteSchema } from "./schedules";
|
|
21
22
|
|
|
22
23
|
// ─── Response schemas ────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -183,6 +184,8 @@ const listScheduledTasks = route({
|
|
|
183
184
|
path: "/api/scheduled-tasks",
|
|
184
185
|
pattern: ["api", "scheduled-tasks"],
|
|
185
186
|
summary: "List scheduled tasks",
|
|
187
|
+
description:
|
|
188
|
+
"Returns full schedules by default. Pass `fields=slim` for the list-view shape, which swaps the full `taskTemplate` for a bounded `taskTemplatePreview`. Fetch one schedule in full via `GET /api/schedules/{id}`.",
|
|
186
189
|
tags: ["Stats"],
|
|
187
190
|
query: z.object({
|
|
188
191
|
enabled: z.enum(["true", "false"]).optional(),
|
|
@@ -192,11 +195,18 @@ const listScheduledTasks = route({
|
|
|
192
195
|
targetType: z.enum(["agent-task", "workflow", "script"]).optional(),
|
|
193
196
|
workflowId: z.string().uuid().optional(),
|
|
194
197
|
scriptName: z.string().optional(),
|
|
198
|
+
/** `slim` is the list-view shape (no full `taskTemplate`); default is full. */
|
|
199
|
+
fields: z.enum(["full", "slim"]).optional(),
|
|
195
200
|
}),
|
|
196
201
|
responses: {
|
|
197
202
|
200: {
|
|
198
203
|
description: "Scheduled tasks list",
|
|
199
|
-
schema: z.object({
|
|
204
|
+
schema: z.object({
|
|
205
|
+
scheduledTasks: z.union([
|
|
206
|
+
z.array(ScheduledTaskSchema),
|
|
207
|
+
z.array(scheduleSummaryWithFavoriteSchema),
|
|
208
|
+
]),
|
|
209
|
+
}),
|
|
200
210
|
},
|
|
201
211
|
},
|
|
202
212
|
});
|
|
@@ -301,7 +311,7 @@ export async function handleStats(
|
|
|
301
311
|
if (listScheduledTasks.match(req.method, pathSegments)) {
|
|
302
312
|
const parsed = await listScheduledTasks.parse(req, res, pathSegments, queryParams);
|
|
303
313
|
if (!parsed) return true;
|
|
304
|
-
const
|
|
314
|
+
const filters = {
|
|
305
315
|
enabled: parsed.query.enabled !== undefined ? parsed.query.enabled === "true" : undefined,
|
|
306
316
|
name: parsed.query.name || undefined,
|
|
307
317
|
scheduleType: (parsed.query.scheduleType as "recurring" | "one_time") || undefined,
|
|
@@ -312,13 +322,15 @@ export async function handleStats(
|
|
|
312
322
|
targetType: parsed.query.targetType,
|
|
313
323
|
workflowId: parsed.query.workflowId,
|
|
314
324
|
scriptName: parsed.query.scriptName,
|
|
315
|
-
}
|
|
325
|
+
};
|
|
316
326
|
const favoriteScope = (await resolveHttpFavoriteOwner(req, myAgentId))?.scope;
|
|
327
|
+
const favoriteOpts = { favoriteScope, itemType: "schedule" as const };
|
|
328
|
+
// Opt-in: API, MCP and script callers that don't ask keep the full rows.
|
|
317
329
|
listScheduledTasks.respond(res, 200, {
|
|
318
|
-
scheduledTasks:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
330
|
+
scheduledTasks:
|
|
331
|
+
parsed.query.fields === "slim"
|
|
332
|
+
? await withFavoriteFlags(await getScheduledTasks(filters, { slim: true }), favoriteOpts)
|
|
333
|
+
: await withFavoriteFlags(await getScheduledTasks(filters), favoriteOpts),
|
|
322
334
|
});
|
|
323
335
|
return true;
|
|
324
336
|
}
|
package/src/http/status.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { getEmbeddingProvider } from "../be/memory";
|
|
37
37
|
import { getFileStorageProvider } from "../fs/registry";
|
|
38
38
|
import { getSlackConfiguration } from "../slack/config";
|
|
39
|
+
import { getSlackConnectionState } from "../slack/connection-state";
|
|
39
40
|
import { type AgentCredStatus, AutomationIntegrationIdSchema, ProviderNameSchema } from "../types";
|
|
40
41
|
import { route } from "./route-def";
|
|
41
42
|
import { json, jsonError } from "./utils";
|
|
@@ -438,6 +439,15 @@ function slackMilestone(state: AutomationSetupStates["slack"]): SetupMilestone {
|
|
|
438
439
|
};
|
|
439
440
|
}
|
|
440
441
|
|
|
442
|
+
if (config.mode === "socket" && getSlackConnectionState() === "connected") {
|
|
443
|
+
return {
|
|
444
|
+
id: "slack",
|
|
445
|
+
label: "Slack configured",
|
|
446
|
+
state: "verified",
|
|
447
|
+
action_url: automationIntegrationFixUrl("slack"),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
441
451
|
return {
|
|
442
452
|
id: "slack",
|
|
443
453
|
label: "Slack configured",
|
|
@@ -445,7 +455,7 @@ function slackMilestone(state: AutomationSetupStates["slack"]): SetupMilestone {
|
|
|
445
455
|
hint:
|
|
446
456
|
config.mode === "http"
|
|
447
457
|
? "HTTP credentials are configured, but HTTP ingress is unavailable until the receiver is installed."
|
|
448
|
-
: "Socket Mode credentials are configured
|
|
458
|
+
: "Socket Mode credentials are configured, but the bot is not connected to Slack.",
|
|
449
459
|
action_url: automationIntegrationFixUrl("slack"),
|
|
450
460
|
};
|
|
451
461
|
}
|
package/src/http/tasks.ts
CHANGED
|
@@ -113,6 +113,21 @@ const AgentTaskSummarySchema = AgentTaskSchema.pick({
|
|
|
113
113
|
totalCostUsd: true,
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
+
/** `/api/tasks?fields=timeline` item — mirrors `AgentTaskTimelineItem` in ../types. */
|
|
117
|
+
const AgentTaskTimelineItemSchema = AgentTaskSchema.pick({
|
|
118
|
+
id: true,
|
|
119
|
+
agentId: true,
|
|
120
|
+
parentTaskId: true,
|
|
121
|
+
task: true,
|
|
122
|
+
title: true,
|
|
123
|
+
status: true,
|
|
124
|
+
createdAt: true,
|
|
125
|
+
lastUpdatedAt: true,
|
|
126
|
+
finishedAt: true,
|
|
127
|
+
peakContextTokens: true,
|
|
128
|
+
totalCostUsd: true,
|
|
129
|
+
});
|
|
130
|
+
|
|
116
131
|
/** Shared by cancel/pause/resume — each sends `{ success: true, task }` on success. */
|
|
117
132
|
const TaskActionResultSchema = z.object({
|
|
118
133
|
success: z.literal(true),
|
|
@@ -173,7 +188,7 @@ const listTasks = route({
|
|
|
173
188
|
pattern: ["api", "tasks"],
|
|
174
189
|
summary: "List tasks with filters",
|
|
175
190
|
description:
|
|
176
|
-
"Returns tasks with the full `task` text replaced by a bounded `taskPreview` and completion/integration blobs dropped by default — list views only need the preview. Pass `fields=full` to restore the full `AgentTask
|
|
191
|
+
"Returns tasks with the full `task` text replaced by a bounded `taskPreview` and completion/integration blobs dropped by default — list views only need the preview. Pass `fields=full` to restore the full `AgentTask`, or `fields=timeline` for the narrow shape the dashboard timeline draws. Fetch a single task in full via `GET /api/tasks/{id}`. `total` (the filtered row count, ignoring limit/offset) is computed only with `includeTotal=true`.",
|
|
177
192
|
tags: ["Tasks"],
|
|
178
193
|
query: z.object({
|
|
179
194
|
/** Single status, or comma-separated list (e.g. "failed,cancelled"). */
|
|
@@ -200,15 +215,25 @@ const listTasks = route({
|
|
|
200
215
|
orderBy: z.enum(["lastUpdatedAt", "createdAt"]).optional(),
|
|
201
216
|
limit: z.coerce.number().int().optional(),
|
|
202
217
|
offset: z.coerce.number().int().optional(),
|
|
203
|
-
/**
|
|
204
|
-
|
|
218
|
+
/**
|
|
219
|
+
* `full` restores the legacy shape (full `task` text + all fields);
|
|
220
|
+
* `timeline` is the dashboard timeline's narrow shape; default is slim.
|
|
221
|
+
*/
|
|
222
|
+
fields: z.enum(["full", "slim", "timeline"]).optional(),
|
|
223
|
+
/** `true` adds `total`, a filtered COUNT(*) that pagers need. Omitted by default. */
|
|
224
|
+
includeTotal: z.enum(["true", "false"]).optional(),
|
|
205
225
|
}),
|
|
206
226
|
responses: {
|
|
207
227
|
200: {
|
|
208
228
|
description: "Paginated task list",
|
|
209
229
|
schema: z.object({
|
|
210
|
-
tasks: z.union([
|
|
211
|
-
|
|
230
|
+
tasks: z.union([
|
|
231
|
+
z.array(AgentTaskSchema),
|
|
232
|
+
z.array(AgentTaskSummarySchema),
|
|
233
|
+
z.array(AgentTaskTimelineItemSchema),
|
|
234
|
+
]),
|
|
235
|
+
/** Present only when the request passed `includeTotal=true`. */
|
|
236
|
+
total: z.number().int().optional(),
|
|
212
237
|
}),
|
|
213
238
|
},
|
|
214
239
|
400: { description: "Validation error (e.g. unknown status token)" },
|
|
@@ -785,9 +810,16 @@ export async function handleTasks(
|
|
|
785
810
|
const tasks =
|
|
786
811
|
parsed.query.fields === "full"
|
|
787
812
|
? await getAllTasks(filters)
|
|
788
|
-
:
|
|
789
|
-
|
|
790
|
-
|
|
813
|
+
: parsed.query.fields === "timeline"
|
|
814
|
+
? await getAllTasks(filters, { fields: "timeline" })
|
|
815
|
+
: await getAllTasks(filters, { slim: true });
|
|
816
|
+
// The COUNT(*) re-scans every matching row, so only pagers pay for it.
|
|
817
|
+
if (parsed.query.includeTotal === "true") {
|
|
818
|
+
const total = await getTasksCount(filters);
|
|
819
|
+
listTasks.respond(res, 200, { tasks, total });
|
|
820
|
+
} else {
|
|
821
|
+
listTasks.respond(res, 200, { tasks });
|
|
822
|
+
}
|
|
791
823
|
return true;
|
|
792
824
|
}
|
|
793
825
|
|
package/src/http/webhooks.ts
CHANGED
|
@@ -52,6 +52,7 @@ import type { KapsoConfig } from "../integrations/kapso/config";
|
|
|
52
52
|
import { getKapsoConfig } from "../integrations/kapso/config";
|
|
53
53
|
import type { KapsoWebhookPayload } from "../integrations/kapso/inbound";
|
|
54
54
|
import { resolveKapsoRequestedByUserId, routeKapsoInbound } from "../integrations/kapso/inbound";
|
|
55
|
+
import { TaskCreationBlockedError } from "../tasks/errors";
|
|
55
56
|
import { getExecutorRegistry } from "../workflows";
|
|
56
57
|
import { workflowEventBus } from "../workflows/event-bus";
|
|
57
58
|
import { handleWebhookTrigger, verifyHmacSignature, WebhookError } from "../workflows/triggers";
|
|
@@ -67,8 +68,34 @@ import { route } from "./route-def";
|
|
|
67
68
|
const WebhookDispatchResultSchema = z.object({
|
|
68
69
|
created: z.boolean(),
|
|
69
70
|
taskId: z.string().optional(),
|
|
71
|
+
skipped: z.boolean().optional(),
|
|
72
|
+
reason: z.string().optional(),
|
|
73
|
+
extension: z.object({ id: z.string(), name: z.string() }).optional(),
|
|
70
74
|
});
|
|
71
75
|
|
|
76
|
+
type WebhookDispatchResult = z.infer<typeof WebhookDispatchResultSchema>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Runs a webhook's task-creating handler. A `pre.task.create` extension block
|
|
80
|
+
* is an expected outcome, not a server error: the delivery is acked with a
|
|
81
|
+
* skipped result so the provider does not retry and the caller still emits the
|
|
82
|
+
* delivery's workflow events. Any other error propagates.
|
|
83
|
+
*/
|
|
84
|
+
async function runBlockableHandler(
|
|
85
|
+
source: string,
|
|
86
|
+
handler: () => Promise<WebhookDispatchResult>,
|
|
87
|
+
): Promise<WebhookDispatchResult> {
|
|
88
|
+
try {
|
|
89
|
+
return await handler();
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (!(err instanceof TaskCreationBlockedError)) throw err;
|
|
92
|
+
console.log(
|
|
93
|
+
`[${source}] Task creation blocked by extension ${err.extension.name}: ${err.reason}`,
|
|
94
|
+
);
|
|
95
|
+
return { created: false, skipped: true, reason: err.reason, extension: err.extension };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
72
99
|
const githubWebhook = route({
|
|
73
100
|
method: "post",
|
|
74
101
|
path: "/api/github/webhook",
|
|
@@ -249,37 +276,32 @@ export async function handleWebhooks(
|
|
|
249
276
|
|
|
250
277
|
console.log(`[GitHub] Received ${eventType} event`);
|
|
251
278
|
|
|
252
|
-
let result:
|
|
279
|
+
let result: WebhookDispatchResult = { created: false };
|
|
253
280
|
|
|
254
281
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
result = await handleWorkflowRun(body as WorkflowRunEvent);
|
|
279
|
-
break;
|
|
280
|
-
default:
|
|
281
|
-
console.log(`[GitHub] Ignoring unsupported event type: ${eventType}`);
|
|
282
|
-
}
|
|
282
|
+
result = await runBlockableHandler("GitHub", async () => {
|
|
283
|
+
switch (eventType) {
|
|
284
|
+
case "pull_request":
|
|
285
|
+
return await handlePullRequest(body as PullRequestEvent);
|
|
286
|
+
case "issues":
|
|
287
|
+
return await handleIssue(body as IssueEvent);
|
|
288
|
+
case "issue_comment":
|
|
289
|
+
return await handleComment(body as CommentEvent, "issue_comment");
|
|
290
|
+
case "pull_request_review_comment":
|
|
291
|
+
return await handleComment(body as CommentEvent, "pull_request_review_comment");
|
|
292
|
+
case "pull_request_review":
|
|
293
|
+
return await handlePullRequestReview(body as PullRequestReviewEvent);
|
|
294
|
+
case "check_run":
|
|
295
|
+
return await handleCheckRun(body as CheckRunEvent);
|
|
296
|
+
case "check_suite":
|
|
297
|
+
return await handleCheckSuite(body as CheckSuiteEvent);
|
|
298
|
+
case "workflow_run":
|
|
299
|
+
return await handleWorkflowRun(body as WorkflowRunEvent);
|
|
300
|
+
default:
|
|
301
|
+
console.log(`[GitHub] Ignoring unsupported event type: ${eventType}`);
|
|
302
|
+
return { created: false };
|
|
303
|
+
}
|
|
304
|
+
});
|
|
283
305
|
|
|
284
306
|
// Emit workflow trigger event for matching event types
|
|
285
307
|
switch (eventType) {
|
|
@@ -376,25 +398,24 @@ export async function handleWebhooks(
|
|
|
376
398
|
const objectKind = body.object_kind as string | undefined;
|
|
377
399
|
console.log(`[GitLab] Received ${objectKind} event`);
|
|
378
400
|
|
|
379
|
-
let result:
|
|
401
|
+
let result: WebhookDispatchResult = { created: false };
|
|
380
402
|
|
|
381
403
|
try {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
404
|
+
result = await runBlockableHandler("GitLab", async () => {
|
|
405
|
+
switch (objectKind) {
|
|
406
|
+
case "merge_request":
|
|
407
|
+
return await handleMergeRequest(body as unknown as MergeRequestEvent);
|
|
408
|
+
case "issue":
|
|
409
|
+
return await handleGitLabIssue(body as unknown as GitLabIssueEvent);
|
|
410
|
+
case "note":
|
|
411
|
+
return await handleNote(body as unknown as NoteEvent);
|
|
412
|
+
case "pipeline":
|
|
413
|
+
return await handlePipeline(body as unknown as PipelineEvent);
|
|
414
|
+
default:
|
|
415
|
+
console.log(`[GitLab] Ignoring unsupported event type: ${objectKind}`);
|
|
416
|
+
return { created: false };
|
|
417
|
+
}
|
|
418
|
+
});
|
|
398
419
|
|
|
399
420
|
// Emit workflow trigger events for GitLab
|
|
400
421
|
switch (objectKind) {
|
package/src/http/workflows.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
getWorkflowRunStepsByRunId,
|
|
11
11
|
getWorkflowVersion,
|
|
12
12
|
getWorkflowVersions,
|
|
13
|
-
listWorkflowRuns,
|
|
14
13
|
listWorkflowRunsPage,
|
|
15
14
|
listWorkflows,
|
|
16
15
|
type updateWorkflow,
|
|
@@ -29,6 +28,7 @@ import {
|
|
|
29
28
|
WorkflowRunSchema,
|
|
30
29
|
WorkflowRunStatusSchema,
|
|
31
30
|
WorkflowRunStepSchema,
|
|
31
|
+
WorkflowRunSummarySchema,
|
|
32
32
|
WorkflowSchema,
|
|
33
33
|
WorkflowVersionSchema,
|
|
34
34
|
} from "../types";
|
|
@@ -68,9 +68,12 @@ const ExecutorTypeInfoSchema = z.object({
|
|
|
68
68
|
outputSchema: z.record(z.string(), z.unknown()),
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
/** Page size when a caller sends no `limit`. */
|
|
72
|
+
const DEFAULT_WORKFLOW_RUNS_LIMIT = 50;
|
|
73
|
+
|
|
71
74
|
/** Mirrors `WorkflowRunPage` in src/be/db.ts. */
|
|
72
75
|
const WorkflowRunPageSchema = z.object({
|
|
73
|
-
runs: z.array(
|
|
76
|
+
runs: z.array(WorkflowRunSummarySchema),
|
|
74
77
|
page: z.object({
|
|
75
78
|
limit: z.number().int(),
|
|
76
79
|
offset: z.number().int(),
|
|
@@ -273,7 +276,8 @@ const listWorkflowRunsRoute = route({
|
|
|
273
276
|
method: "get",
|
|
274
277
|
path: "/api/workflows/{id}/runs",
|
|
275
278
|
pattern: ["api", "workflows", null, "runs"],
|
|
276
|
-
summary: "List runs for a workflow",
|
|
279
|
+
summary: "List runs for a workflow, newest first",
|
|
280
|
+
description: `Returns one page of runs without \`context\` (fetch it with GET /api/workflow-runs/{id}). \`limit\` defaults to ${DEFAULT_WORKFLOW_RUNS_LIMIT} and is capped at 100; follow \`page.nextOffset\` while \`page.hasMore\` is true.`,
|
|
277
281
|
tags: ["Workflows"],
|
|
278
282
|
params: z.object({ id: z.string() }),
|
|
279
283
|
query: z.object({
|
|
@@ -283,8 +287,8 @@ const listWorkflowRunsRoute = route({
|
|
|
283
287
|
}),
|
|
284
288
|
responses: {
|
|
285
289
|
200: {
|
|
286
|
-
description: "
|
|
287
|
-
schema:
|
|
290
|
+
description: "One page of workflow runs",
|
|
291
|
+
schema: WorkflowRunPageSchema,
|
|
288
292
|
},
|
|
289
293
|
},
|
|
290
294
|
});
|
|
@@ -847,21 +851,12 @@ export async function handleWorkflows(
|
|
|
847
851
|
if (listWorkflowRunsRoute.match(req.method, pathSegments)) {
|
|
848
852
|
const parsed = await listWorkflowRunsRoute.parse(req, res, pathSegments, queryParams);
|
|
849
853
|
if (!parsed) return true;
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
offset: parsed.query?.offset ?? 0,
|
|
857
|
-
});
|
|
858
|
-
listWorkflowRunsRoute.respond(res, 200, page);
|
|
859
|
-
return true;
|
|
860
|
-
}
|
|
861
|
-
// Preserve the pre-pagination response for the UI when limit/offset are
|
|
862
|
-
// omitted: a bare array containing every matching run.
|
|
863
|
-
const runs = await listWorkflowRuns(parsed.params.id, { status: parsed.query?.status });
|
|
864
|
-
listWorkflowRunsRoute.respond(res, 200, runs);
|
|
854
|
+
const page = await listWorkflowRunsPage(parsed.params.id, {
|
|
855
|
+
status: parsed.query?.status,
|
|
856
|
+
limit: parsed.query?.limit ?? DEFAULT_WORKFLOW_RUNS_LIMIT,
|
|
857
|
+
offset: parsed.query?.offset ?? 0,
|
|
858
|
+
});
|
|
859
|
+
listWorkflowRunsRoute.respond(res, 200, page);
|
|
865
860
|
return true;
|
|
866
861
|
}
|
|
867
862
|
|
package/src/linear/outbound.ts
CHANGED
|
@@ -102,6 +102,31 @@ async function handleTaskProgress(data: unknown): Promise<void> {
|
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// Linear documents no size limit for a comment or an AgentActivity body. 30k chars stays under
|
|
106
|
+
// the 32,767 figure other Linear integrations use, and fits any realistic task output.
|
|
107
|
+
export const LINEAR_BODY_MAX = 30_000;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build the body of the single Linear comment that carries a task's outcome.
|
|
111
|
+
*
|
|
112
|
+
* The text goes in as-is: no `+++ Title … +++` wrapper (Linear renders that as a
|
|
113
|
+
* collapsed section) and no boilerplate. A body over `LINEAR_BODY_MAX` is cut and ends
|
|
114
|
+
* with a visible note that points at the swarm task for the full text.
|
|
115
|
+
*/
|
|
116
|
+
export function formatLinearOutcomeBody(text: string, taskId: string): string {
|
|
117
|
+
if (text.length <= LINEAR_BODY_MAX) return text;
|
|
118
|
+
|
|
119
|
+
const dashboardUrl = (process.env.SWARM_DASHBOARD_URL || process.env.APP_URL)?.replace(
|
|
120
|
+
/\/+$/,
|
|
121
|
+
"",
|
|
122
|
+
);
|
|
123
|
+
const where = dashboardUrl ? `${dashboardUrl}/tasks/${taskId}` : `swarm task \`${taskId}\``;
|
|
124
|
+
const note = (shown: number) =>
|
|
125
|
+
`\n\n---\n_Truncated: showing ${shown} of ${text.length} characters. Full text: ${where}_`;
|
|
126
|
+
const shown = LINEAR_BODY_MAX - note(LINEAR_BODY_MAX).length;
|
|
127
|
+
return `${text.slice(0, shown).trimEnd()}${note(shown)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
105
130
|
async function handleTaskCompleted(data: unknown): Promise<void> {
|
|
106
131
|
const { taskId, output } = data as { taskId: string; output?: string };
|
|
107
132
|
if (!taskId) return;
|
|
@@ -112,11 +137,11 @@ async function handleTaskCompleted(data: unknown): Promise<void> {
|
|
|
112
137
|
if (shouldSkipForLoopPrevention(sync)) return;
|
|
113
138
|
|
|
114
139
|
const sessionId = taskSessionMap.get(taskId);
|
|
115
|
-
const body = output
|
|
116
|
-
? `Task completed.\n\n+++ Output\n${output.slice(0, 2000)}\n+++`
|
|
117
|
-
: "Task completed.";
|
|
140
|
+
const body = output?.trim() ? formatLinearOutcomeBody(output, taskId) : "Task completed.";
|
|
118
141
|
|
|
119
|
-
//
|
|
142
|
+
// One comment only. A `response` activity ends the AgentSession and Linear renders it as a
|
|
143
|
+
// comment on the issue, so it carries the output itself. The plain comment below runs only
|
|
144
|
+
// when there is no session to end.
|
|
120
145
|
if (sessionId) {
|
|
121
146
|
endAgentSession(sessionId, body, "response").catch((err) => {
|
|
122
147
|
console.error(`[Linear Outbound] Failed to end AgentSession for task ${taskId}:`, err);
|
|
@@ -133,10 +158,7 @@ async function handleTaskCompleted(data: unknown): Promise<void> {
|
|
|
133
158
|
console.log("[Linear Outbound] No Linear client available, skipping sync for", taskId);
|
|
134
159
|
return;
|
|
135
160
|
}
|
|
136
|
-
|
|
137
|
-
? `Task completed by swarm agent.\n\n+++ Output\n${output.slice(0, 2000)}\n+++`
|
|
138
|
-
: "Task completed by swarm agent.";
|
|
139
|
-
await client.createComment({ issueId: sync.externalId, body: comment });
|
|
161
|
+
await client.createComment({ issueId: sync.externalId, body });
|
|
140
162
|
console.log(`[Linear Outbound] Posted completion comment for task ${taskId}`);
|
|
141
163
|
} catch (error) {
|
|
142
164
|
console.error(
|
|
@@ -162,11 +184,14 @@ async function handleTaskFailed(data: unknown): Promise<void> {
|
|
|
162
184
|
if (shouldSkipForLoopPrevention(sync)) return;
|
|
163
185
|
|
|
164
186
|
const sessionId = taskSessionMap.get(taskId);
|
|
165
|
-
|
|
166
|
-
|
|
187
|
+
// Keep the one-line "Task failed." lead: without it a plain fallback comment reads like
|
|
188
|
+
// a normal answer.
|
|
189
|
+
const body = failureReason?.trim()
|
|
190
|
+
? formatLinearOutcomeBody(`Task failed.\n\n${failureReason}`, taskId)
|
|
167
191
|
: "Task failed.";
|
|
168
192
|
|
|
169
|
-
//
|
|
193
|
+
// One comment only: the `error` activity ends the AgentSession and carries the reason.
|
|
194
|
+
// The plain comment below runs only when there is no session to end.
|
|
170
195
|
if (sessionId) {
|
|
171
196
|
endAgentSession(sessionId, body, "error").catch((err) => {
|
|
172
197
|
console.error(`[Linear Outbound] Failed to end AgentSession for task ${taskId}:`, err);
|
|
@@ -183,10 +208,7 @@ async function handleTaskFailed(data: unknown): Promise<void> {
|
|
|
183
208
|
console.log("[Linear Outbound] No Linear client available, skipping sync for", taskId);
|
|
184
209
|
return;
|
|
185
210
|
}
|
|
186
|
-
|
|
187
|
-
? `Task failed.\n\n+++ Error Details\n${failureReason.slice(0, 2000)}\n+++`
|
|
188
|
-
: "Task failed.";
|
|
189
|
-
await client.createComment({ issueId: sync.externalId, body: comment });
|
|
211
|
+
await client.createComment({ issueId: sync.externalId, body });
|
|
190
212
|
console.log(`[Linear Outbound] Posted failure comment for task ${taskId}`);
|
|
191
213
|
} catch (error) {
|
|
192
214
|
console.error(
|