@desplega.ai/agent-swarm 2.5.1 → 2.5.3
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-7g6bpwbk.js → acp-adapter-gr2fcyyj.js} +3 -3
- package/dist/{actions-w8xd014k.js → actions-wv766ghf.js} +18 -18
- package/dist/{amp-adapter-z4qvhacj.js → amp-adapter-speb906t.js} +3 -3
- package/dist/{app-nndcvftq.js → app-ag29n14f.js} +3 -3
- package/dist/{assistant-hjmqyscx.js → assistant-3x8bz3eg.js} +19 -19
- package/dist/{boot-reembed-r8rhf9h8.js → boot-reembed-5b3d2eea.js} +1 -1
- package/dist/{boot-reembed-vwcj2y5d.js → boot-reembed-jsmfrpzf.js} +1 -1
- package/dist/{boot-scrub-sweep-fr9q36ne.js → boot-scrub-sweep-s73gerxy.js} +2 -2
- package/dist/{boot-seeds-9jqwnegq.js → boot-seeds-5zmax2wr.js} +4 -4
- package/dist/{claude-adapter-2f8b85df.js → claude-adapter-hswdnm3t.js} +3 -3
- package/dist/{claude-managed-adapter-gt6pa35d.js → claude-managed-adapter-cvbzj1h6.js} +2 -2
- package/dist/{claude-managed-setup-c3hp36ph.js → claude-managed-setup-3k8m3hhc.js} +2 -2
- package/dist/{claude-sdk-session-05kat8rv.js → claude-sdk-session-fkbyk66x.js} +6905 -6855
- package/dist/{cli-y3zyaee5.js → cli-1fc85mvt.js} +5 -5
- package/dist/{cli-v2g93k4v.js → cli-210xd1f4.js} +4 -4
- package/dist/{cli-sn1vbtg2.js → cli-25j01dpj.js} +3 -3
- package/dist/{cli-n6kmqngw.js → cli-35gtk1p6.js} +1 -1
- package/dist/{cli-r9fy0ddc.js → cli-3ngdk5te.js} +1 -1
- package/dist/{cli-z63a269p.js → cli-40jsnrjm.js} +2 -2
- package/dist/{cli-ba8ne1es.js → cli-4rvr7zfx.js} +10 -0
- package/dist/{cli-qfm6my86.js → cli-502eqv93.js} +1 -1
- package/dist/{cli-xd4eqtr2.js → cli-6n8axjv1.js} +1 -1
- package/dist/{cli-hadv0725.js → cli-8s5wx3nz.js} +2 -2
- package/dist/{cli-z2dv25mf.js → cli-99xgw26r.js} +116 -5
- package/dist/{cli-jpxsrxcb.js → cli-9s05422w.js} +1 -1
- package/dist/{cli-kcsd1ez8.js → cli-aecdcr77.js} +343 -325
- package/dist/{cli-w9etnnxk.js → cli-bf8ma3f1.js} +120 -50
- package/dist/{cli-e9btqw4y.js → cli-ce0ak9ms.js} +18 -13
- package/dist/{cli-nj12tn2c.js → cli-e2ar4bp1.js} +71 -20
- package/dist/{cli-gwcf52gz.js → cli-e8jyq7d8.js} +11 -7
- package/dist/{cli-rngde2jv.js → cli-egjggehm.js} +1 -1
- package/dist/{cli-qfjm4gvt.js → cli-ewm3pwbq.js} +2 -2
- package/dist/{cli-9mm5ade4.js → cli-fsdprwrg.js} +1 -1
- package/dist/{cli-3ee5z0q0.js → cli-fzcn3d87.js} +1 -1
- package/dist/{cli-eym3jg7w.js → cli-gpsz7g88.js} +6 -6
- package/dist/{cli-cwyafhw1.js → cli-hcpj2xqz.js} +1 -1
- package/dist/{cli-j1eghqfm.js → cli-hhxjnjzs.js} +1 -1
- package/dist/{cli-pyy4c9ch.js → cli-jjznk3f7.js} +881 -232
- package/dist/{cli-q9pzwxvk.js → cli-kxawjrxg.js} +222 -37
- package/dist/{cli-rh5smt8t.js → cli-mz9afxxf.js} +14 -3
- package/dist/{cli-mz2ygeyq.js → cli-n45mr7zh.js} +2 -2
- package/dist/{cli-fes51ya2.js → cli-p0rzjkya.js} +2 -2
- package/dist/{cli-3qv8naq8.js → cli-p6zs9h4y.js} +827 -107
- package/dist/{cli-mq7kg1b1.js → cli-p9rafdrb.js} +41 -6
- package/dist/{cli-cah5detr.js → cli-qgf8emrn.js} +5 -5
- package/dist/{cli-2wba1ac9.js → cli-qzme5949.js} +26 -26
- package/dist/{cli-znvq0k3t.js → cli-s49hpn37.js} +2 -2
- package/dist/{cli-xc65x52q.js → cli-s53vp709.js} +1 -1
- package/dist/{cli-0jy85zz4.js → cli-svrj71pg.js} +18 -16
- package/dist/{cli-1dp1zj04.js → cli-vwn81jbs.js} +3 -3
- package/dist/{cli-ajqhxtnz.js → cli-w1zwctj2.js} +1 -1
- package/dist/{cli-22rmcjkg.js → cli-y561h5a9.js} +7 -6
- package/dist/{cli-2jg61wab.js → cli-z21whrrg.js} +15 -15
- package/dist/cli.js +19 -18
- package/dist/{codex-adapter-p4ptwar5.js → codex-adapter-n8ka66vf.js} +2 -2
- package/dist/{codex-session-runner-964c7dnk.js → codex-session-runner-fsebts37.js} +1 -1
- package/dist/{commands-mbvhbrcy.js → commands-t04rt2pc.js} +2 -2
- package/dist/{controller-gnq1k1mf.js → controller-rgna04r2.js} +2 -2
- package/dist/{controllers-connect-6dkm71tg.js → controllers-connect-bqm1rtft.js} +20 -20
- package/dist/{cursor-adapter-yq5xgfcg.js → cursor-adapter-swr2k95y.js} +1 -1
- package/dist/{db-88wcymxt.js → db-24ck09xy.js} +1 -1
- package/dist/{db-af9y8nkf.js → db-a4m7haw0.js} +3 -3
- package/dist/{dispatcher-606ydx5v.js → dispatcher-nwf9ss0q.js} +17 -17
- package/dist/{dsh-adapter-tvhc4k2d.js → dsh-adapter-kpdnhjst.js} +2 -2
- package/dist/{external-channel-y8q5z8z2.js → external-channel-j1e2p34p.js} +2 -2
- package/dist/{grok-adapter-39aha52t.js → grok-adapter-qtjz0whk.js} +4 -4
- package/dist/{handlers-rk9dqech.js → handlers-19xr6dnz.js} +20 -20
- package/dist/{heartbeat-jfe9zfjr.js → heartbeat-vtesda5d.js} +20 -20
- package/dist/{hook-b1mez1m8.js → hook-8wqaszda.js} +1 -1
- package/dist/{hook-ffevzed1.js → hook-b2hhj6rd.js} +1 -1
- package/dist/{http-d5b44f1r.js → http-mxcqhhcs.js} +3020 -2463
- package/dist/{index-5en23s5z.js → index-1r8wfpzs.js} +1 -1
- package/dist/{index-cbxkmfc5.js → index-9pn1pfkf.js} +1 -1
- package/dist/{index-mj46rk9k.js → index-kw2mmat7.js} +20 -20
- package/dist/{index-nq8hhv28.js → index-n1vbvta9.js} +19 -10
- package/dist/{index-6srwvd5b.js → index-p4xfz37z.js} +1 -1
- package/dist/{index-d2qeem97.js → index-spwxjgay.js} +17 -17
- package/dist/{index-4q3vj1yr.js → index-srg522m6.js} +17 -17
- package/dist/{keepalive-wsby1btq.js → keepalive-dsd8rtgx.js} +3 -3
- package/dist/{lead-paa141j6.js → lead-zyerk1x2.js} +30 -30
- package/dist/{maintenance-vnshcyv9.js → maintenance-jqmqwnet.js} +1 -1
- package/dist/{oauth-refresh-sweep-d6sqn5np.js → oauth-refresh-sweep-q2c3bnvm.js} +2 -2
- package/dist/{onboard-nr4xhpzy.js → onboard-w4yd7s8t.js} +2 -2
- package/dist/{opencode-adapter-e56086m1.js → opencode-adapter-9tppvxar.js} +2 -2
- package/dist/{otel-impl-ytn9wyv9.js → otel-impl-f36byzbd.js} +1 -1
- package/dist/{pricing-refresh-pva04zee.js → pricing-refresh-xvak5p4v.js} +3 -3
- package/dist/{rbac-roles-7rc1q3g4.js → rbac-roles-682k2mt5.js} +12 -2
- package/dist/rbac-roles-gfn30fb8.js +26 -0
- package/dist/{render-v2-pyfsg9zk.js → render-v2-hgytz8nn.js} +3 -3
- package/dist/{resume-phj8xhqr.js → resume-2dvq348g.js} +7 -5
- package/dist/{session-access-jzx2z9h5.js → session-access-7f21ekq9.js} +4 -4
- package/dist/{setup-rfg0f0sg.js → setup-epffxtbk.js} +1 -1
- package/dist/{work-objects-44z2yjg8.js → work-objects-xz1a5rqk.js} +20 -20
- package/dist/{worker-zsfqzxhm.js → worker-42t8emjt.js} +30 -30
- package/openapi.json +3163 -2114
- package/package.json +7 -6
- package/src/be/approval-config-question.ts +389 -0
- package/src/be/automation-preflight.ts +37 -3
- package/src/be/db/session-tree.ts +158 -13
- package/src/be/db/session-visibility.ts +31 -0
- package/src/be/db/skill-search.ts +87 -0
- package/src/be/db/tasks/read.ts +47 -7
- package/src/be/db/workflow-runs.ts +54 -7
- package/src/be/db.ts +27 -5
- package/src/be/migrations/233_script_run_session_tokens.sql +8 -0
- package/src/be/migrations/235_chat_relay_defer.sql +86 -0
- package/src/be/migrations/237_session_inbox_cleanup.sql +75 -0
- package/src/be/migrations/239_skills_fts.sql +40 -0
- package/src/be/migrations/240_workflow_run_parent_step_attempt.sql +37 -0
- package/src/be/migrations/241_skills_meta_fts.sql +39 -0
- package/src/be/rbac-roles.ts +138 -0
- package/src/be/scripts/run-credential.ts +64 -0
- package/src/be/scripts/run-saved.ts +3 -2
- package/src/be/swarm-config-guard.ts +23 -0
- package/src/be/users.ts +28 -4
- package/src/extensions/catalog.generated.json +4 -4
- package/src/http/all-routes.ts +1 -0
- package/src/http/approval-requests.ts +254 -43
- package/src/http/config.ts +22 -37
- package/src/http/index.ts +2 -0
- package/src/http/schedules.ts +26 -0
- package/src/http/scripts.ts +2 -2
- package/src/http/sessions.ts +118 -2
- package/src/http/user-roles.ts +173 -0
- package/src/http/users.ts +9 -0
- package/src/http/x.ts +2 -2
- package/src/integrations/chat-sdk/commands.ts +14 -1
- package/src/integrations/chat-sdk/inbound.ts +42 -6
- package/src/integrations/chat-sdk/outbound.ts +35 -10
- package/src/integrations/chat-sdk/reactions.ts +107 -0
- package/src/integrations/chat-sdk/relay.ts +75 -7
- package/src/integrations/chat-sdk/thread-buffer.ts +2 -0
- package/src/integrations/reaction-events.ts +20 -0
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/rbac/legacy-policy.ts +3 -0
- package/src/rbac/permissions.ts +8 -0
- package/src/scheduler/schedule-task.ts +44 -3
- package/src/scheduler/scheduler.ts +4 -4
- package/src/scripts-runtime/loader.ts +8 -1
- package/src/slack/ack.ts +2 -1
- package/src/slack/reaction-shortcode.ts +4 -7
- package/src/tasks/worker-follow-up.ts +30 -0
- package/src/tests/approval-config-question.test.ts +702 -0
- package/src/tests/approval-requests-notifications.test.ts +189 -0
- package/src/tests/chat-sdk-commands.test.ts +12 -0
- package/src/tests/chat-sdk-inbound.test.ts +183 -9
- package/src/tests/chat-sdk-outbound-files.test.ts +9 -2
- package/src/tests/chat-sdk-relay.test.ts +361 -10
- package/src/tests/chat-sdk-tools.test.ts +104 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/entrypoint-gitlab-credential-helper.test.ts +273 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +321 -12
- package/src/tests/http-users.test.ts +38 -0
- package/src/tests/list-endpoint-slimming.test.ts +33 -0
- package/src/tests/migration-237-session-inbox-cleanup.test.ts +136 -0
- package/src/tests/migration-240-workflow-run-parent-step-attempt.test.ts +55 -0
- package/src/tests/rbac-engine.test.ts +2 -0
- package/src/tests/rbac-roles.test.ts +10 -0
- package/src/tests/schedule-http-triage-tools.test.ts +44 -0
- package/src/tests/script-api-credential.test.ts +173 -0
- package/src/tests/script-connections.test.ts +10 -0
- package/src/tests/script-run-config-scope.test.ts +120 -0
- package/src/tests/script-run-token.test.ts +75 -0
- package/src/tests/scripts-http.test.ts +19 -0
- package/src/tests/sessions-tree.test.ts +410 -1
- package/src/tests/skill-search-long-content.test.ts +193 -0
- package/src/tests/skill-search.test.ts +173 -22
- package/src/tests/user-roles-http.test.ts +204 -0
- package/src/tests/workflow-hitl-dynamic-questions.test.ts +1 -1
- package/src/tests/workflow-sub-workflow.test.ts +538 -1
- package/src/tools/chat-reply.ts +2 -0
- package/src/tools/request-human-input.ts +27 -5
- package/src/tools/schedules/patch-schedule.ts +18 -0
- package/src/tools/schedules/update-schedule.ts +18 -0
- package/src/types.ts +23 -0
- package/src/workflows/approval-notifications.ts +221 -0
- package/src/workflows/checkpoint.ts +69 -35
- package/src/workflows/engine.ts +234 -87
- package/src/workflows/executors/base.ts +6 -0
- package/src/workflows/executors/human-in-the-loop.ts +36 -173
- package/src/workflows/executors/sub-workflow.ts +45 -16
- package/src/workflows/executors/swarm-script.ts +2 -2
- package/src/workflows/recovery.ts +63 -1
- package/src/workflows/resume.ts +60 -21
- package/src/workflows/retry-poller.ts +25 -4
- package/templates/extensions/comms-adherence-gate/README.md +38 -13
- package/templates/extensions/comms-adherence-gate/hooks.ts +272 -69
- package/templates/extensions/comms-adherence-gate/manifest.json +2 -2
- package/templates/schedules/weekly-harness-upgrade-check/content.md +1 -1
- package/templates/skills/code-quality/content.md +1 -1
- package/templates/skills/turso-interaction/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +8 -1
- package/dist/rbac-roles-ff1ffmec.js +0 -12
- /package/dist/{cli-stxdrqs2.js → cli-avdvgcay.js} +0 -0
- /package/dist/{pi-mono-adapter-tzps8f38.js → pi-mono-adapter-tqsge1ay.js} +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getKv } from "@/be/db";
|
|
2
|
+
import type { ReactionEvent } from "@/integrations/reaction-events";
|
|
3
|
+
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
4
|
+
import { errorMessage } from "./logger";
|
|
5
|
+
import { chatChannelKey } from "./router";
|
|
6
|
+
import { getChatInstance } from "./runtime";
|
|
7
|
+
import { assertTrustedChatTarget } from "./teams-origin";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Telegram reactions per lifecycle event. A bot may set only the emoji in the
|
|
11
|
+
* Bot API `ReactionTypeEmoji` list (https://core.telegram.org/bots/api#reactiontypeemoji),
|
|
12
|
+
* which has no ✅ or ❌. `✍` is U+270D with no variation selector, as listed.
|
|
13
|
+
* A bot sets one reaction per message, so each call replaces the previous one.
|
|
14
|
+
*/
|
|
15
|
+
export const TELEGRAM_REACTIONS: Record<ReactionEvent, string> = {
|
|
16
|
+
accepted: "👀",
|
|
17
|
+
buffered: "👀",
|
|
18
|
+
now: "⚡",
|
|
19
|
+
steered: "✍",
|
|
20
|
+
completed: "👍",
|
|
21
|
+
failed: "👎",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Adapters that show the task lifecycle as a reaction on the user's message. */
|
|
25
|
+
const CHAT_REACTIONS: Readonly<Record<string, Record<ReactionEvent, string>>> = {
|
|
26
|
+
telegram: TELEGRAM_REACTIONS,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** True when `adapter` shows the lifecycle as reactions instead of text acknowledgements. */
|
|
30
|
+
export function chatAdapterReacts(adapter: string): boolean {
|
|
31
|
+
return adapter in CHAT_REACTIONS;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set the reaction for `event` on one message. Best effort: an adapter without
|
|
36
|
+
* a reaction map is a no-op, and a platform error (Telegram answers
|
|
37
|
+
* `400 REACTION_INVALID` for an emoji a group admin disallowed) is logged and
|
|
38
|
+
* never thrown, so it cannot block delivery or fail the task.
|
|
39
|
+
*/
|
|
40
|
+
export async function setChatMessageReaction(
|
|
41
|
+
adapter: string,
|
|
42
|
+
threadId: string,
|
|
43
|
+
messageId: string,
|
|
44
|
+
event: ReactionEvent,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
const emoji = CHAT_REACTIONS[adapter]?.[event];
|
|
47
|
+
if (!emoji) return;
|
|
48
|
+
try {
|
|
49
|
+
const target = getChatInstance(adapter)?.getAdapter(adapter);
|
|
50
|
+
if (!target) return;
|
|
51
|
+
assertTrustedChatTarget(threadId);
|
|
52
|
+
await target.addReaction(threadId, messageId, emoji);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.warn(
|
|
55
|
+
scrubSecrets(
|
|
56
|
+
`[chat-sdk] Could not set the ${event} reaction on ${messageId} in ${threadId}: ${errorMessage(error)}`,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Remove the bot's reaction from each message. Best effort, like {@link setChatMessageReaction}. */
|
|
63
|
+
export async function clearChatMessageReactions(
|
|
64
|
+
adapter: string,
|
|
65
|
+
threadId: string,
|
|
66
|
+
messageIds: readonly string[],
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
if (!chatAdapterReacts(adapter) || messageIds.length === 0) return;
|
|
69
|
+
const target = getChatInstance(adapter)?.getAdapter(adapter);
|
|
70
|
+
if (!target) return;
|
|
71
|
+
for (const messageId of messageIds) {
|
|
72
|
+
try {
|
|
73
|
+
// Telegram clears the bot's whole reaction whatever emoji is named.
|
|
74
|
+
await target.removeReaction(threadId, messageId, TELEGRAM_REACTIONS.accepted);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.warn(
|
|
77
|
+
scrubSecrets(
|
|
78
|
+
`[chat-sdk] Could not clear the reaction on ${messageId} in ${threadId}: ${errorMessage(error)}`,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** KV namespace of per-channel Chat SDK settings, keyed like the channel mappings. */
|
|
86
|
+
export const CHAT_CHANNEL_SETTINGS_NAMESPACE = "integrations:chat:channel-settings";
|
|
87
|
+
|
|
88
|
+
export interface ChatChannelSettings {
|
|
89
|
+
/** Also post the text steering acknowledgement on an adapter that reacts. */
|
|
90
|
+
textAcks?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Whether a steering message gets a text acknowledgement. Adapters without
|
|
95
|
+
* reactions always get one; a reacting adapter only when its channel opts in.
|
|
96
|
+
*/
|
|
97
|
+
export async function wantsChatTextAck(adapter: string, channelId: string): Promise<boolean> {
|
|
98
|
+
if (!chatAdapterReacts(adapter)) return true;
|
|
99
|
+
try {
|
|
100
|
+
const row = await getKv(CHAT_CHANNEL_SETTINGS_NAMESPACE, chatChannelKey(adapter, channelId));
|
|
101
|
+
const settings = row?.value as ChatChannelSettings | null | undefined;
|
|
102
|
+
return typeof settings === "object" && settings !== null && settings.textAcks === true;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.warn(`[chat-sdk] Could not read the channel settings: ${errorMessage(error)}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
getAgentById,
|
|
2
3
|
getPendingChatRelayTasks,
|
|
3
4
|
giveUpStaleChatRelays,
|
|
4
5
|
markChatRelayAttempted,
|
|
@@ -6,13 +7,17 @@ import {
|
|
|
6
7
|
markChatRelayGivenUp,
|
|
7
8
|
recordChatRelayError,
|
|
8
9
|
} from "@/be/db";
|
|
10
|
+
import { terminalReactionEvent } from "@/integrations/reaction-events";
|
|
9
11
|
import { chatThreadOfContextKey } from "@/tasks/context-key";
|
|
10
12
|
import type { AgentTask } from "@/types";
|
|
11
13
|
import { scrubSecrets } from "@/utils/secret-scrubber";
|
|
12
14
|
import { workflowEventBus } from "@/workflows/event-bus";
|
|
15
|
+
import { shortId } from "./commands";
|
|
16
|
+
import { chatMessageLimit, splitChatMessage } from "./limits";
|
|
13
17
|
import { errorMessage } from "./logger";
|
|
14
18
|
import { postChatMessage } from "./outbound";
|
|
15
19
|
import { taskOutputChatFiles } from "./outbound-files";
|
|
20
|
+
import { setChatMessageReaction } from "./reactions";
|
|
16
21
|
import { getChatInstance, getChatSdkHealth } from "./runtime";
|
|
17
22
|
import { sweepExpired } from "./state";
|
|
18
23
|
import { stopTypingKeepalive } from "./typing";
|
|
@@ -33,14 +38,64 @@ const SWEEP_EVERY_N_TICKS = 20;
|
|
|
33
38
|
/** Coalesce bursts of terminal-task events into one early tick. */
|
|
34
39
|
const EARLY_TICK_DEBOUNCE_MS = 250;
|
|
35
40
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The first line of every relayed answer: the short task id `/status` shows
|
|
43
|
+
* and the agent's name, e.g. `#a3f2b1c9 · Coder`. Two answers in one chat stay
|
|
44
|
+
* apart even when the platform cannot show what they reply to.
|
|
45
|
+
*/
|
|
46
|
+
export function formatChatTaskTag(task: Pick<AgentTask, "id">, agentName?: string): string {
|
|
47
|
+
return agentName ? `#${shortId(task)} · ${agentName}` : `#${shortId(task)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The task tag, an optional status line, and the answer itself. */
|
|
51
|
+
function relayHeadAndBody(task: AgentTask, agentName?: string): { head: string; body: string } {
|
|
52
|
+
const tag = formatChatTaskTag(task, agentName);
|
|
38
53
|
const body = (task.output || task.failureReason || "").trim();
|
|
39
|
-
if (task.status === "failed") return
|
|
40
|
-
if (task.status === "cancelled") {
|
|
41
|
-
|
|
54
|
+
if (task.status === "failed") return { head: `${tag}\n**Task failed.**`, body };
|
|
55
|
+
if (task.status === "cancelled") return { head: `${tag}\n**Task cancelled.**`, body };
|
|
56
|
+
return { head: tag, body: body || "Task completed." };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The text posted for a terminal task. Failed and cancelled tasks get a status line after the tag. */
|
|
60
|
+
export function formatChatRelayText(task: AgentTask, agentName?: string): string {
|
|
61
|
+
const { head, body } = relayHeadAndBody(task, agentName);
|
|
62
|
+
return body ? `${head}\n\n${body}` : head;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** On Telegram, an answer body longer than this is posted collapsed. */
|
|
66
|
+
export const TELEGRAM_FOLD_CHARS = 1000;
|
|
67
|
+
/** A first paragraph up to this long stays visible above the collapsed rest. */
|
|
68
|
+
const FOLD_LEAD_MAX_CHARS = 280;
|
|
69
|
+
/** Room for the `<details>` markup and separators around one folded piece. */
|
|
70
|
+
const FOLD_MARKUP_CHARS = 80;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The messages posted for a terminal task. On Telegram a long body goes into
|
|
74
|
+
* collapsed `<details>` blocks: Bot API 10.1 rich messages render them folded
|
|
75
|
+
* and parse the markdown inside. Each message gets its own block, so the
|
|
76
|
+
* length split never cuts the markup in two.
|
|
77
|
+
*/
|
|
78
|
+
export function formatChatRelayMessages(
|
|
79
|
+
task: AgentTask,
|
|
80
|
+
adapter: string,
|
|
81
|
+
agentName?: string,
|
|
82
|
+
): string[] {
|
|
83
|
+
const { head, body } = relayHeadAndBody(task, agentName);
|
|
84
|
+
if (adapter !== "telegram" || body.length <= TELEGRAM_FOLD_CHARS) {
|
|
85
|
+
return [formatChatRelayText(task, agentName)];
|
|
42
86
|
}
|
|
43
|
-
|
|
87
|
+
const breakAt = body.indexOf("\n\n");
|
|
88
|
+
const lead = breakAt > 0 && breakAt <= FOLD_LEAD_MAX_CHARS ? body.slice(0, breakAt) : "";
|
|
89
|
+
const rest = lead ? body.slice(breakAt).trim() : body;
|
|
90
|
+
const top = lead ? `${head}\n\n${lead}` : head;
|
|
91
|
+
const room = chatMessageLimit(adapter) - top.length - FOLD_MARKUP_CHARS;
|
|
92
|
+
const pieces = splitChatMessage(rest, room);
|
|
93
|
+
return pieces.map((piece, index) => {
|
|
94
|
+
const label = lead ? "Rest of the answer" : "Full answer";
|
|
95
|
+
const summary = pieces.length > 1 ? `${label} (${index + 1}/${pieces.length})` : label;
|
|
96
|
+
const block = `<details><summary>${summary}</summary>\n\n${piece}\n\n</details>`;
|
|
97
|
+
return index === 0 ? `${top}\n\n${block}` : block;
|
|
98
|
+
});
|
|
44
99
|
}
|
|
45
100
|
|
|
46
101
|
/**
|
|
@@ -96,16 +151,29 @@ export async function processPendingChatRelays(
|
|
|
96
151
|
|
|
97
152
|
if (!(await markChatRelayAttempted(task.id, now))) continue;
|
|
98
153
|
try {
|
|
154
|
+
const agent = task.agentId ? await getAgentById(task.agentId) : null;
|
|
99
155
|
await postChatMessage({
|
|
100
156
|
adapter: target.adapter,
|
|
101
157
|
threadId: target.threadId,
|
|
102
|
-
text:
|
|
158
|
+
text: formatChatRelayMessages(task, target.adapter, agent?.name),
|
|
103
159
|
taskId: task.id,
|
|
104
160
|
files: await taskOutputChatFiles(task.id),
|
|
161
|
+
// The inbound message this task answers. A lead follow-up, resume or
|
|
162
|
+
// defer wake-up inherits it from its parent, so it answers the same ask.
|
|
163
|
+
replyToMessageId: task.mentionMessageId,
|
|
105
164
|
});
|
|
106
165
|
stopTypingKeepalive(target.threadId);
|
|
107
166
|
await markChatRelayDelivered(task.id, now);
|
|
108
167
|
console.log(`[chat-sdk] Relayed ${task.status} task ${task.id} to ${target.adapter}`);
|
|
168
|
+
// The ask's reaction ends with the answer. Never throws: delivery already landed.
|
|
169
|
+
if (task.mentionMessageId) {
|
|
170
|
+
await setChatMessageReaction(
|
|
171
|
+
target.adapter,
|
|
172
|
+
target.threadId,
|
|
173
|
+
task.mentionMessageId,
|
|
174
|
+
terminalReactionEvent(task.status),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
109
177
|
} catch (error) {
|
|
110
178
|
const message = errorMessage(error);
|
|
111
179
|
await recordChatRelayError(task.id, message, now);
|
|
@@ -17,6 +17,8 @@ export interface ChatBufferedMessage {
|
|
|
17
17
|
isDirectMessage: boolean;
|
|
18
18
|
/** Why the message was taken; `none` when it is unaddressed. */
|
|
19
19
|
addressed: ChatAddressedReason;
|
|
20
|
+
/** The message got the `accepted` reaction when it was buffered. */
|
|
21
|
+
reacted?: boolean;
|
|
20
22
|
facts: ChatMessageFacts;
|
|
21
23
|
attachments: Attachment[];
|
|
22
24
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentTaskStatus } from "@/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The task lifecycle a chat channel shows as a reaction on the user's message.
|
|
5
|
+
* Each channel maps these events to its own emoji: Slack in
|
|
6
|
+
* `src/slack/reaction-shortcode.ts`, Chat SDK adapters in
|
|
7
|
+
* `src/integrations/chat-sdk/reactions.ts`.
|
|
8
|
+
*
|
|
9
|
+
* - `accepted`: the swarm took the message.
|
|
10
|
+
* - `buffered`: the message joined a batch that is still waiting.
|
|
11
|
+
* - `now`: the message skipped the buffer (`!now`).
|
|
12
|
+
* - `steered`: the message went to a running task, or was queued for it.
|
|
13
|
+
* - `completed` / `failed`: the task that answers the message ended.
|
|
14
|
+
*/
|
|
15
|
+
export type ReactionEvent = "accepted" | "buffered" | "now" | "steered" | "completed" | "failed";
|
|
16
|
+
|
|
17
|
+
/** The terminal event for an ended task: anything but `completed` shows as `failed`. */
|
|
18
|
+
export function terminalReactionEvent(status: AgentTaskStatus): "completed" | "failed" {
|
|
19
|
+
return status === "completed" ? "completed" : "failed";
|
|
20
|
+
}
|
|
@@ -44,7 +44,7 @@ import type {
|
|
|
44
44
|
* default; the image installs exactly this version and the per-task settings
|
|
45
45
|
* set `amp.updates.mode: "disabled"`. Bump it deliberately, with a live run.
|
|
46
46
|
*/
|
|
47
|
-
export const AMP_PACKAGE = "@ampcode/cli@0.0.
|
|
47
|
+
export const AMP_PACKAGE = "@ampcode/cli@0.0.1792756861-g6187a2";
|
|
48
48
|
|
|
49
49
|
/** MCP server name. Amp exposes its tools as `agent_swarm` / `store_progress` (dashes become underscores). */
|
|
50
50
|
const AMP_MCP_SERVER_NAME = "agent-swarm";
|
|
@@ -37,7 +37,7 @@ import type {
|
|
|
37
37
|
} from "./types";
|
|
38
38
|
|
|
39
39
|
// npm's `latest` is older and lacks the JSON/stdin contract used here.
|
|
40
|
-
export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.
|
|
40
|
+
export const DSH_PACKAGE = "@deepseek-ai/dsh@0.2.1-rc.3";
|
|
41
41
|
|
|
42
42
|
export function checkDshCredentials(env: Record<string, string | undefined>): CredStatus {
|
|
43
43
|
return env.DEEPSEEK_API_KEY?.trim() || env.OPENROUTER_API_KEY?.trim()
|
|
@@ -351,6 +351,7 @@ export const LEGACY_POLICY = {
|
|
|
351
351
|
"script.api.update": leadOnly,
|
|
352
352
|
"script.api.rotate": leadOnly,
|
|
353
353
|
"script.api.delete": leadOnly,
|
|
354
|
+
"schedule.deferral.update.any": leadOrResourceOwner,
|
|
354
355
|
// Pool writes pick the image that receives a runtime token, so only humans
|
|
355
356
|
// may make them. The lead may only place agents into operator-made pools.
|
|
356
357
|
"runtime_pool.write": operatorOrUser,
|
|
@@ -358,6 +359,8 @@ export const LEGACY_POLICY = {
|
|
|
358
359
|
"runtime_controller.write": operatorOrUser,
|
|
359
360
|
// A worker token stands in for the swarm API key on a worker, so only humans mint it.
|
|
360
361
|
"agent.token.manage": operatorOrUser,
|
|
362
|
+
// Agents never change human roles.
|
|
363
|
+
"user.role.manage": operatorOrUser,
|
|
361
364
|
"extension.write": extensionWrite,
|
|
362
365
|
"extension.activate": extensionActivation,
|
|
363
366
|
"extension.install.inline": leadOrOperatorOrUser,
|
package/src/rbac/permissions.ts
CHANGED
|
@@ -37,6 +37,10 @@ export const PERMISSIONS = {
|
|
|
37
37
|
description: "Mint, rotate, or revoke an agent-bound worker token.",
|
|
38
38
|
namespace: "agent",
|
|
39
39
|
},
|
|
40
|
+
"user.role.manage": {
|
|
41
|
+
description: "Assign or remove a person's RBAC roles.",
|
|
42
|
+
namespace: "user",
|
|
43
|
+
},
|
|
40
44
|
"task.cancel.any": {
|
|
41
45
|
description: "Cancel any task (beyond tasks the caller created).",
|
|
42
46
|
namespace: "task",
|
|
@@ -354,6 +358,10 @@ export const PERMISSIONS = {
|
|
|
354
358
|
description: "Delete an external script API endpoint.",
|
|
355
359
|
namespace: "script",
|
|
356
360
|
},
|
|
361
|
+
"schedule.deferral.update.any": {
|
|
362
|
+
description: "Change a defer-task schedule the caller did not create.",
|
|
363
|
+
namespace: "schedule",
|
|
364
|
+
},
|
|
357
365
|
"runtime_pool.write": {
|
|
358
366
|
description: "Create, update, or delete runtime pools (image, provider kind, placement).",
|
|
359
367
|
namespace: "runtime_pool",
|
|
@@ -1,12 +1,53 @@
|
|
|
1
|
-
import { createTaskExtended, getDbClient } from "@/be/db";
|
|
1
|
+
import { createTaskExtended, getAgentById, getDbClient, getTaskById } from "@/be/db";
|
|
2
2
|
import { carryUnhandledSteering } from "@/be/steering";
|
|
3
|
-
import { scheduleContextKey } from "@/tasks/context-key";
|
|
3
|
+
import { chatThreadOfContextKey, scheduleContextKey } from "@/tasks/context-key";
|
|
4
4
|
import {
|
|
5
5
|
type PreparedTaskCreate,
|
|
6
6
|
prepareTaskWithSiblingAwareness,
|
|
7
7
|
} from "@/tasks/sibling-awareness";
|
|
8
8
|
import type { AgentTask, ScheduledTask } from "@/types";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Whether the task's own answer is posted to its chat thread. Mirrors the
|
|
12
|
+
* relay triggers (migration 235): a delegated worker task, or its resume,
|
|
13
|
+
* reports through the Lead follow-up instead.
|
|
14
|
+
*/
|
|
15
|
+
async function answersInChat(task: AgentTask): Promise<boolean> {
|
|
16
|
+
if (task.source === "chat") return true;
|
|
17
|
+
if (task.source === "system" && task.taskType === "follow-up") return true;
|
|
18
|
+
// Only a wake-up that passed `scheduleTaskContextKey` has a chat key.
|
|
19
|
+
if (task.source === "schedule" && task.taskType === "deferred") return true;
|
|
20
|
+
if (task.source === "system" && task.taskType === "resume" && task.agentId) {
|
|
21
|
+
return (await getAgentById(task.agentId))?.isLead === true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* A defer-task wake-up of a chat task stays in its chat thread, so the chat
|
|
28
|
+
* relay posts the real answer there (and `/status` and `/cancel` see it).
|
|
29
|
+
* Only the deferring agent continuing its own task gets the thread: a schedule
|
|
30
|
+
* re-targeted or re-owned after the deferral, or the deferral of a task that
|
|
31
|
+
* never answered in chat, fires like any other schedule. Every other firing
|
|
32
|
+
* gets the schedule's own key.
|
|
33
|
+
*/
|
|
34
|
+
async function scheduleTaskContextKey(schedule: ScheduledTask): Promise<string> {
|
|
35
|
+
if (schedule.taskType === "deferred" && schedule.parentTaskId) {
|
|
36
|
+
const parent = await getTaskById(schedule.parentTaskId);
|
|
37
|
+
if (
|
|
38
|
+
parent?.contextKey &&
|
|
39
|
+
chatThreadOfContextKey(parent.contextKey) &&
|
|
40
|
+
parent.agentId &&
|
|
41
|
+
schedule.targetAgentId === parent.agentId &&
|
|
42
|
+
schedule.createdByAgentId === parent.agentId &&
|
|
43
|
+
(await answersInChat(parent))
|
|
44
|
+
) {
|
|
45
|
+
return parent.contextKey;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return scheduleContextKey({ scheduleId: schedule.id });
|
|
49
|
+
}
|
|
50
|
+
|
|
10
51
|
/**
|
|
11
52
|
* Run the `pre.task.create` extension hooks for a schedule firing. Call this OUTSIDE
|
|
12
53
|
* the transaction that creates the task: the dispatcher skips hooks while a
|
|
@@ -35,7 +76,7 @@ export async function prepareStandaloneScheduleTask(
|
|
|
35
76
|
scheduleId: schedule.id,
|
|
36
77
|
source: "schedule",
|
|
37
78
|
requestedByUserId: schedule.createdBy,
|
|
38
|
-
contextKey:
|
|
79
|
+
contextKey: await scheduleTaskContextKey(schedule),
|
|
39
80
|
// Set only by `defer-task`. An explicit parent wins over the sibling-awareness
|
|
40
81
|
// auto-wiring (see withSiblingAwareness in src/tasks/sibling-awareness.ts), so
|
|
41
82
|
// the wake-up run continues the deferred task rather than a random sibling.
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "@/be/script-connections";
|
|
26
26
|
import { buildScriptCredentialBindingsWithFailures } from "@/be/script-credential-broker";
|
|
27
27
|
import { getScript } from "@/be/scripts/db";
|
|
28
|
-
import {
|
|
28
|
+
import { runScriptWithRunCredential } from "@/be/scripts/run-credential";
|
|
29
29
|
import { telemetry } from "@/telemetry";
|
|
30
30
|
import type { AgentTask, ScheduledTask } from "@/types";
|
|
31
31
|
import { getExecutorRegistry as getWorkflowExecutorRegistry } from "@/workflows";
|
|
@@ -72,7 +72,7 @@ function resolveExecutorRegistry(): ExecutorRegistry | null {
|
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Execute a schedule's `targetType='script'` target directly via the
|
|
75
|
-
* scripts-runtime — no agent/LLM in the loop. Reuses the same `
|
|
75
|
+
* scripts-runtime — no agent/LLM in the loop. Reuses the same `runScriptWithRunCredential()`
|
|
76
76
|
* path as the `swarm-script` workflow executor.
|
|
77
77
|
*/
|
|
78
78
|
async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
@@ -98,10 +98,10 @@ async function executeScheduleScript(schedule: ScheduledTask): Promise<void> {
|
|
|
98
98
|
startedAt: new Date().toISOString(),
|
|
99
99
|
};
|
|
100
100
|
|
|
101
|
-
let output: Awaited<ReturnType<typeof
|
|
101
|
+
let output: Awaited<ReturnType<typeof runScriptWithRunCredential>>;
|
|
102
102
|
try {
|
|
103
103
|
const credentials = await buildScriptCredentialBindingsWithFailures({ agentId });
|
|
104
|
-
output = await
|
|
104
|
+
output = await runScriptWithRunCredential({
|
|
105
105
|
source: script.source,
|
|
106
106
|
args,
|
|
107
107
|
fsMode: "none",
|
|
@@ -40,6 +40,13 @@ export type RunScriptInput = {
|
|
|
40
40
|
args?: unknown;
|
|
41
41
|
fsMode?: ScriptFsMode;
|
|
42
42
|
agentId: string;
|
|
43
|
+
/**
|
|
44
|
+
* Bearer for the run's `ctx.swarm` SDK calls. Api-side callers pass a
|
|
45
|
+
* per-run token bound to the executing agent (`runScriptWithRunCredential`
|
|
46
|
+
* in `src/be/scripts/run-credential.ts`, which also revokes it). Omitted, the
|
|
47
|
+
* run presents the swarm API key.
|
|
48
|
+
*/
|
|
49
|
+
apiKey?: string;
|
|
43
50
|
/** Per-boot runtime identity of the invoking worker; system context, never script input. */
|
|
44
51
|
runtimeInstanceId?: string;
|
|
45
52
|
signal?: AbortSignal;
|
|
@@ -74,7 +81,7 @@ export function scriptEgressPolicy(env: NodeJS.ProcessEnv = process.env): Egress
|
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
async function buildConfigPayload(input: RunScriptInput): Promise<SwarmConfigPayload> {
|
|
77
|
-
const apiKey = getApiKey();
|
|
84
|
+
const apiKey = input.apiKey ?? getApiKey();
|
|
78
85
|
if (!apiKey) throw new Error("Swarm API key is required to run scripts");
|
|
79
86
|
|
|
80
87
|
return {
|
package/src/slack/ack.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { WebClient } from "@slack/web-api";
|
|
2
2
|
import { getLogsByTaskIdChronological, getSlackTasksInThread, getTaskById } from "../be/db";
|
|
3
|
+
import { terminalReactionEvent } from "../integrations/reaction-events";
|
|
3
4
|
import { recordSlackReactionInvalidName } from "../otel";
|
|
4
5
|
import { type AgentTask, isTerminalTaskStatus } from "../types";
|
|
5
6
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
@@ -143,7 +144,7 @@ export type SlackReactionChoice = { name: string; event?: SlackReactionEvent };
|
|
|
143
144
|
|
|
144
145
|
/** The reaction choice for a task's terminal outcome, honouring operator config. */
|
|
145
146
|
export function terminalReactionChoice(task: AgentTask): SlackReactionChoice {
|
|
146
|
-
const event
|
|
147
|
+
const event = terminalReactionEvent(task.status);
|
|
147
148
|
return { name: reactionName(event), event };
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
| "steered"
|
|
6
|
-
| "completed"
|
|
7
|
-
| "failed";
|
|
1
|
+
import type { ReactionEvent } from "@/integrations/reaction-events";
|
|
2
|
+
|
|
3
|
+
/** The shared lifecycle events; Slack maps each one to a shortcode below. */
|
|
4
|
+
export type SlackReactionEvent = ReactionEvent;
|
|
8
5
|
|
|
9
6
|
export const SLACK_REACTION_DEFAULTS: Record<SlackReactionEvent, string> = {
|
|
10
7
|
accepted: "eyes",
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from "../types";
|
|
34
34
|
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
35
35
|
import { taskAttachmentDisplayUrl } from "../utils/task-attachment-links";
|
|
36
|
+
import { chatThreadOfContextKey } from "./context-key";
|
|
36
37
|
import { taskModelPin } from "./model-pin";
|
|
37
38
|
import { createTaskWithSiblingAwareness } from "./sibling-awareness";
|
|
38
39
|
// Side-effect import: registers task lifecycle templates in the in-memory registry.
|
|
@@ -386,6 +387,7 @@ export async function createWorkerTaskFollowUp(args: {
|
|
|
386
387
|
taskType: "follow-up",
|
|
387
388
|
priority: changes.priority,
|
|
388
389
|
parentTaskId: task.id,
|
|
390
|
+
contextKey: await deferredWorkChatContextKey(task),
|
|
389
391
|
slackChannelId: task.slackChannelId,
|
|
390
392
|
slackThreadTs: task.slackThreadTs,
|
|
391
393
|
slackUserId: task.slackUserId,
|
|
@@ -398,6 +400,34 @@ export async function createWorkerTaskFollowUp(args: {
|
|
|
398
400
|
);
|
|
399
401
|
}
|
|
400
402
|
|
|
403
|
+
/** Hop cap for `deferredWorkChatContextKey`; a defer/resume chain is short. */
|
|
404
|
+
const MAX_CONTINUATION_HOPS = 32;
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* The chat thread a worker's deferred work answers in, for its Lead follow-up.
|
|
408
|
+
* A worker's defer-task wake-up (and any resume of it) runs on its schedule's
|
|
409
|
+
* contextKey, so the worker stays out of the chat (`scheduleTaskContextKey`).
|
|
410
|
+
* The Lead follow-up still answers the original ask: walk back through the
|
|
411
|
+
* wake-up and resume chain to the deferred task and report on its chat thread.
|
|
412
|
+
* Undefined when the task already has a chat key or the chain leads to none,
|
|
413
|
+
* so the follow-up inherits the task's own key as before.
|
|
414
|
+
*/
|
|
415
|
+
async function deferredWorkChatContextKey(task: AgentTask): Promise<string | undefined> {
|
|
416
|
+
if (chatThreadOfContextKey(task.contextKey)) return undefined;
|
|
417
|
+
const seen = new Set<string>();
|
|
418
|
+
let current: AgentTask | null = task;
|
|
419
|
+
while (current && !seen.has(current.id) && seen.size < MAX_CONTINUATION_HOPS) {
|
|
420
|
+
if (chatThreadOfContextKey(current.contextKey)) return current.contextKey;
|
|
421
|
+
const continuation =
|
|
422
|
+
(current.source === "schedule" && current.taskType === "deferred") ||
|
|
423
|
+
(current.source === "system" && current.taskType === "resume");
|
|
424
|
+
if (!continuation || !current.parentTaskId) return undefined;
|
|
425
|
+
seen.add(current.id);
|
|
426
|
+
current = await getTaskById(current.parentTaskId);
|
|
427
|
+
}
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
401
431
|
/** Result of `createResumeFollowUp`. */
|
|
402
432
|
export type CreateResumeFollowUpResult =
|
|
403
433
|
| { kind: "created"; task: AgentTask }
|