@desplega.ai/agent-swarm 1.151.0 → 1.152.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 -1
- package/dist/{acp-adapter-ex8hzjfc.js → acp-adapter-4s7q25ft.js} +1 -1
- package/dist/{actions-2bgpyhww.js → actions-cs6rhmvc.js} +13 -13
- package/dist/{app-1706zhr1.js → app-j54aq0dj.js} +3 -3
- package/dist/{assistant-bavke5p7.js → assistant-w1pgn7vf.js} +14 -14
- package/dist/{boot-reembed-3cmrrmpa.js → boot-reembed-42vgkrrj.js} +4 -4
- package/dist/{boot-reembed-nafcmmmc.js → boot-reembed-smy30z32.js} +3 -3
- package/dist/{boot-scrub-logs-cd6a2sq0.js → boot-scrub-logs-b6s72fsw.js} +2 -2
- package/dist/{claude-adapter-wfp2gzvz.js → claude-adapter-fh7rmcj9.js} +3 -3
- package/dist/{claude-sdk-session-bht9jjak.js → claude-sdk-session-xhynatn9.js} +3 -3
- package/dist/{cli-f1wky6zp.js → cli-3g61md6v.js} +1 -1
- package/dist/{cli-a1z3h107.js → cli-3pft2z6r.js} +4 -4
- package/dist/{cli-5tpgq60f.js → cli-72tgtzr1.js} +152 -23
- package/dist/{cli-0qb1z6s2.js → cli-7bwaerjt.js} +1 -1
- package/dist/{cli-czjtgfys.js → cli-b4dyetqt.js} +2 -2
- package/dist/{cli-s14agm5s.js → cli-bdt7w021.js} +12 -3
- package/dist/{cli-sjaae6ff.js → cli-dsyg66vt.js} +3 -3
- package/dist/{cli-2tbvf1z1.js → cli-ftdjz1mk.js} +8 -8
- package/dist/{cli-zkxymas5.js → cli-gfez0qmk.js} +1 -1
- package/dist/{cli-q88st9aj.js → cli-gxf2ebyz.js} +2 -2
- package/dist/{cli-ppm4975n.js → cli-jtvs5m8q.js} +1 -1
- package/dist/{cli-7tgcstn2.js → cli-jytf5mw9.js} +2 -2
- package/dist/{cli-g69fdc9g.js → cli-kf8qp2vf.js} +2 -2
- package/dist/{cli-5jd2z7av.js → cli-ks8a1cgq.js} +6 -2
- package/dist/{cli-gjyvhmmh.js → cli-ng4q6a9t.js} +5 -5
- package/dist/{cli-3v9xhjcn.js → cli-sakxd2rg.js} +2 -2
- package/dist/{cli-frcta2w4.js → cli-sqmh7jj1.js} +1 -1
- package/dist/{cli-vwm416m6.js → cli-v9sk4dzw.js} +1 -1
- package/dist/{cli-j9xczhaq.js → cli-vbk025fm.js} +1 -1
- package/dist/{cli-r2q6pevn.js → cli-wfve107r.js} +43 -21
- package/dist/{cli-9004vfdf.js → cli-y8tayczk.js} +2 -2
- package/dist/{cli-gbfwcd3k.js → cli-zfzznn7c.js} +7 -6
- package/dist/cli.js +11 -11
- package/dist/{codex-adapter-jjjk8w4m.js → codex-adapter-k6yq95sn.js} +2 -2
- package/dist/{codex-session-runner-nd6ayxkx.js → codex-session-runner-3rp84pk8.js} +2 -2
- package/dist/{commands-9v4psyq1.js → commands-pbr5jzy5.js} +2 -2
- package/dist/{db-wnt58tpe.js → db-z0xaa59a.js} +2 -2
- package/dist/{dispatcher-05jh1aqk.js → dispatcher-pwpgsj0x.js} +12 -12
- package/dist/{handlers-tw90pkh5.js → handlers-5mzk4mbg.js} +15 -15
- package/dist/{hook-sxe3r349.js → hook-8bz33zee.js} +1 -1
- package/dist/{hook-123e2scb.js → hook-kbh84zb1.js} +2 -2
- package/dist/{http-ecsar8ez.js → http-pppxg5vr.js} +100 -36
- package/dist/{index-hw26vj40.js → index-68bd9b5f.js} +12 -12
- package/dist/{index-16js521z.js → index-k1hq1qt6.js} +8 -8
- package/dist/{index-zj518gf9.js → index-mjpxsr6k.js} +13 -13
- package/dist/{index-ymn7em0p.js → index-y0te09pn.js} +3 -3
- package/dist/{index-nnrbpcjp.js → index-z0zwjtkn.js} +12 -12
- package/dist/{keepalive-asqtcm4y.js → keepalive-xvmwnh1d.js} +4 -4
- package/dist/{lead-xbfr7fvt.js → lead-pf8nvxt8.js} +16 -16
- package/dist/{maintenance-qma55qvw.js → maintenance-jk4q0wgg.js} +4 -4
- package/dist/{oauth-refresh-sweep-3s04c6qw.js → oauth-refresh-sweep-6207x9sj.js} +3 -3
- package/dist/{onboard-drskd386.js → onboard-b0csgm0q.js} +2 -2
- package/dist/{otel-impl-943c96er.js → otel-impl-b6rfehth.js} +1 -1
- package/dist/{pricing-refresh-mx51az05.js → pricing-refresh-4e201wtd.js} +4 -4
- package/dist/{rbac-roles-wcmrg6sv.js → rbac-roles-mkc5n5qa.js} +4 -4
- package/dist/{rbac-roles-78baecqd.js → rbac-roles-zp6e4geh.js} +3 -3
- package/dist/{render-v2-8zjntgf5.js → render-v2-4fazz9tf.js} +3 -3
- package/dist/{seed-pricing-jqjs6cws.js → seed-pricing-thxpn9vn.js} +3 -3
- package/dist/{setup-4pks0vfk.js → setup-wb0z8cat.js} +2 -2
- package/dist/{work-objects-wangt792.js → work-objects-h3498kwe.js} +18 -17
- package/dist/{worker-p2mr422x.js → worker-2m2tzxp3.js} +16 -16
- package/openapi.json +5 -5
- package/package.json +1 -1
- package/src/agentmail/inbound-archive.ts +71 -0
- package/src/agentmail/types.ts +2 -0
- package/src/be/db.ts +1 -1
- package/src/be/scripts/typecheck.ts +4 -0
- package/src/be/swarm-config-guard.ts +7 -1
- package/src/extensions/lifecycle.ts +26 -4
- package/src/http/extensions.ts +8 -3
- package/src/http/webhooks.ts +13 -5
- package/src/rbac/legacy-policy.ts +13 -1
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/types/stdlib.d.ts +4 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +4 -0
- package/src/server.ts +12 -0
- package/src/slack/render-v2.ts +10 -4
- package/src/slack/responses.ts +7 -4
- package/src/slack/task-output.ts +21 -0
- package/src/slack/watcher.ts +3 -2
- package/src/slack/work-objects.ts +2 -1
- package/src/tests/agentmail-inbound-archive.test.ts +418 -0
- package/src/tests/extensions-example-mcp-install.test.ts +2 -2
- package/src/tests/extensions-http.test.ts +84 -3
- package/src/tests/extensions-lead-equivalence.test.ts +21 -0
- package/src/tests/extensions-mcp-tools.test.ts +111 -3
- package/src/tests/fixtures/store-progress-malformed-output.json +4 -0
- package/src/tests/rbac-admission-e2e.test.ts +35 -4
- package/src/tests/rbac-e2e-helpers.ts +18 -0
- package/src/tests/rbac-engine.test.ts +2 -2
- package/src/tests/rbac-mcp-admission-e2e.test.ts +22 -4
- package/src/tests/reload-config.test.ts +17 -0
- package/src/tests/scripts-only-gating.test.ts +4 -0
- package/src/tests/slack-render-v2.test.ts +24 -2
- package/src/tests/slack-task-output.test.ts +36 -0
- package/src/tests/store-progress-attachments-handler.test.ts +100 -1
- package/src/tests/swarm-config-reserved-keys.test.ts +6 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tools/extension-activate-version.ts +38 -0
- package/src/tools/extension-common.ts +2 -1
- package/src/tools/extension-delete.ts +29 -0
- package/src/tools/extension-disable.ts +36 -0
- package/src/tools/extension-enable.ts +36 -0
- package/src/tools/extension-install.ts +2 -2
- package/src/tools/store-progress.ts +30 -0
- package/src/tools/tool-config.ts +5 -1
- package/templates/skills/agent-browser/SKILL.md +84 -0
- package/templates/skills/agent-browser/content.md +21 -0
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
getExtensionById,
|
|
4
4
|
getExtensionFiles,
|
|
5
5
|
getExtensionVersion,
|
|
6
|
+
insertExtensionRun,
|
|
6
7
|
listExtensions,
|
|
7
8
|
pruneExtensionRuns,
|
|
8
9
|
setExtensionState,
|
|
@@ -95,14 +96,33 @@ async function removeRegistered(id: string): Promise<void> {
|
|
|
95
96
|
if (previous) await previous.dispose();
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
// Record the request before importing code, including attempts whose load fails.
|
|
100
|
+
async function recordLifecycleRequest(
|
|
101
|
+
record: Extension,
|
|
102
|
+
operation: string,
|
|
103
|
+
agentId: string | undefined,
|
|
104
|
+
version = record.activeVersion,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
if (!agentId) return;
|
|
107
|
+
await insertExtensionRun({
|
|
108
|
+
extensionId: record.id,
|
|
109
|
+
version,
|
|
110
|
+
event: `lifecycle.${operation}.requested`,
|
|
111
|
+
action: "continue",
|
|
112
|
+
agentId,
|
|
113
|
+
subject: record.name,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
export async function enableExtension(
|
|
99
118
|
id: string,
|
|
100
|
-
opts: { by?: string | null } = {},
|
|
119
|
+
opts: { by?: string | null; agentId?: string } = {},
|
|
101
120
|
): Promise<Extension> {
|
|
102
121
|
initExtensionPostBridge();
|
|
103
122
|
let record = await getExtensionById(id);
|
|
104
123
|
if (!record) throw new ExtensionLifecycleError("Extension not found", 404);
|
|
105
124
|
|
|
125
|
+
await recordLifecycleRequest(record, "enable", opts.agentId);
|
|
106
126
|
const agentId = await ensureExtensionAgent(record.name);
|
|
107
127
|
const identified = await setExtensionState(id, { agentId, updatedBy: opts.by });
|
|
108
128
|
if (!identified) throw new ExtensionLifecycleError("Extension not found", 404);
|
|
@@ -133,10 +153,11 @@ export async function enableExtension(
|
|
|
133
153
|
|
|
134
154
|
export async function disableExtension(
|
|
135
155
|
id: string,
|
|
136
|
-
opts: { by?: string | null } = {},
|
|
156
|
+
opts: { by?: string | null; agentId?: string } = {},
|
|
137
157
|
): Promise<Extension> {
|
|
138
158
|
const record = await getExtensionById(id);
|
|
139
159
|
if (!record) throw new ExtensionLifecycleError("Extension not found", 404);
|
|
160
|
+
await recordLifecycleRequest(record, "disable", opts.agentId);
|
|
140
161
|
await removeRegistered(id);
|
|
141
162
|
attemptedFingerprints.delete(id);
|
|
142
163
|
await deactivateExtensionAgent(record.name);
|
|
@@ -152,7 +173,7 @@ export async function disableExtension(
|
|
|
152
173
|
export async function activateVersion(
|
|
153
174
|
id: string,
|
|
154
175
|
version: number,
|
|
155
|
-
opts: { by?: string | null } = {},
|
|
176
|
+
opts: { by?: string | null; agentId?: string } = {},
|
|
156
177
|
): Promise<Extension> {
|
|
157
178
|
const current = await getExtensionById(id);
|
|
158
179
|
if (!current) throw new ExtensionLifecycleError("Extension not found", 404);
|
|
@@ -160,6 +181,7 @@ export async function activateVersion(
|
|
|
160
181
|
throw new ExtensionLifecycleError(`Extension version ${version} was not found`, 404);
|
|
161
182
|
}
|
|
162
183
|
|
|
184
|
+
await recordLifecycleRequest(current, "activate-version", opts.agentId, version);
|
|
163
185
|
const wasEnabled = current.enabled;
|
|
164
186
|
if (wasEnabled) await disableExtension(id, opts);
|
|
165
187
|
const activated = await activateExtensionVersionSnapshot(id, version, opts.by);
|
|
@@ -170,7 +192,7 @@ export async function activateVersion(
|
|
|
170
192
|
|
|
171
193
|
export async function reloadExtension(
|
|
172
194
|
id: string,
|
|
173
|
-
opts: { by?: string | null } = {},
|
|
195
|
+
opts: { by?: string | null; agentId?: string } = {},
|
|
174
196
|
): Promise<Extension> {
|
|
175
197
|
const record = await getExtensionById(id);
|
|
176
198
|
if (!record) throw new ExtensionLifecycleError("Extension not found", 404);
|
package/src/http/extensions.ts
CHANGED
|
@@ -197,7 +197,8 @@ const enableRoute = route({
|
|
|
197
197
|
pattern: ["api", "extensions", null, "enable"],
|
|
198
198
|
operationId: "extensions_enable",
|
|
199
199
|
summary: "Enable an extension",
|
|
200
|
-
description:
|
|
200
|
+
description:
|
|
201
|
+
"Loads and enables an extension. Available to leads, operators, and dashboard users.",
|
|
201
202
|
tags: ["Extensions"],
|
|
202
203
|
params: idParamsSchema,
|
|
203
204
|
responses: {
|
|
@@ -215,7 +216,8 @@ const disableRoute = route({
|
|
|
215
216
|
pattern: ["api", "extensions", null, "disable"],
|
|
216
217
|
operationId: "extensions_disable",
|
|
217
218
|
summary: "Disable an extension",
|
|
218
|
-
description:
|
|
219
|
+
description:
|
|
220
|
+
"Unloads and disables an extension. Available to leads, operators, and dashboard users.",
|
|
219
221
|
tags: ["Extensions"],
|
|
220
222
|
params: idParamsSchema,
|
|
221
223
|
responses: {
|
|
@@ -232,7 +234,7 @@ const activateVersionRoute = route({
|
|
|
232
234
|
pattern: ["api", "extensions", null, "activate-version"],
|
|
233
235
|
operationId: "extensions_activate_version",
|
|
234
236
|
summary: "Activate an extension version",
|
|
235
|
-
description: "Activates a stored version.
|
|
237
|
+
description: "Activates a stored version. Available to leads, operators, and dashboard users.",
|
|
236
238
|
tags: ["Extensions"],
|
|
237
239
|
params: idParamsSchema,
|
|
238
240
|
body: z.object({ version: z.number().int().min(1) }).strict(),
|
|
@@ -501,6 +503,7 @@ export async function handleExtensions(
|
|
|
501
503
|
try {
|
|
502
504
|
const extension = await enableExtension(parsed.params.id, {
|
|
503
505
|
by: await resolveHttpAuditUserId(req, writerAgentId),
|
|
506
|
+
agentId: writerAgentId,
|
|
504
507
|
});
|
|
505
508
|
enableRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
506
509
|
} catch (error) {
|
|
@@ -518,6 +521,7 @@ export async function handleExtensions(
|
|
|
518
521
|
try {
|
|
519
522
|
const extension = await disableExtension(parsed.params.id, {
|
|
520
523
|
by: await resolveHttpAuditUserId(req, writerAgentId),
|
|
524
|
+
agentId: writerAgentId,
|
|
521
525
|
});
|
|
522
526
|
disableRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
523
527
|
} catch (error) {
|
|
@@ -535,6 +539,7 @@ export async function handleExtensions(
|
|
|
535
539
|
try {
|
|
536
540
|
const extension = await activateVersion(parsed.params.id, parsed.body.version, {
|
|
537
541
|
by: await resolveHttpAuditUserId(req, writerAgentId),
|
|
542
|
+
agentId: writerAgentId,
|
|
538
543
|
});
|
|
539
544
|
activateVersionRoute.respond(res, 200, { extension: redactExtension(extension) });
|
|
540
545
|
} catch (error) {
|
package/src/http/webhooks.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isSenderAllowed,
|
|
9
9
|
verifyAgentMailWebhook,
|
|
10
10
|
} from "../agentmail";
|
|
11
|
+
import { archiveInboundMessage } from "../agentmail/inbound-archive";
|
|
11
12
|
import type {
|
|
12
13
|
CheckRunEvent,
|
|
13
14
|
CheckSuiteEvent,
|
|
@@ -109,7 +110,7 @@ const agentmailWebhook = route({
|
|
|
109
110
|
responses: {
|
|
110
111
|
200: { description: "Event received", schema: z.object({ received: z.literal(true) }) },
|
|
111
112
|
401: { description: "Invalid signature" },
|
|
112
|
-
503: { description: "AgentMail integration not configured" },
|
|
113
|
+
503: { description: "AgentMail integration not configured or inbound archive unavailable" },
|
|
113
114
|
},
|
|
114
115
|
});
|
|
115
116
|
|
|
@@ -486,11 +487,18 @@ export async function handleWebhooks(
|
|
|
486
487
|
return true;
|
|
487
488
|
}
|
|
488
489
|
|
|
489
|
-
// Return 200 immediately — Svix best practice to avoid retries.
|
|
490
|
-
// Processing happens asynchronously below; dedup is handled in handlers.ts.
|
|
491
|
-
agentmailWebhook.respond(res, 200, { received: true });
|
|
492
|
-
|
|
493
490
|
const payload = verified as AgentMailWebhookPayload;
|
|
491
|
+
try {
|
|
492
|
+
await archiveInboundMessage(payload);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.error("[AgentMail] Inbound archive failed; requesting webhook retry", err);
|
|
495
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
496
|
+
res.end(JSON.stringify({ error: "Inbound archive unavailable" }));
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
// Only acknowledge received events after durable capture. Task routing below
|
|
500
|
+
// remains independent: rejected senders and routing failures stay discoverable.
|
|
501
|
+
agentmailWebhook.respond(res, 200, { received: true });
|
|
494
502
|
|
|
495
503
|
if (
|
|
496
504
|
payload.message &&
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* `PermissionVerb` (enforced via `satisfies`).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
14
15
|
import { hasLeadEquivalence } from "./elevated-agents";
|
|
15
16
|
import type { PermissionVerb } from "./permissions";
|
|
16
17
|
import type { RbacPrincipal, RbacResource } from "./types";
|
|
@@ -55,6 +56,17 @@ const leadOrOperatorOrUser: LegacyRule = {
|
|
|
55
56
|
principal.kind === "operator" || principal.kind === "user" || actsAsLead(principal),
|
|
56
57
|
};
|
|
57
58
|
|
|
59
|
+
// Deployment-only: agents cannot reset this through swarm_config.
|
|
60
|
+
const extensionActivation: LegacyRule = {
|
|
61
|
+
name: "extension-activation",
|
|
62
|
+
denyReason:
|
|
63
|
+
"requires operator/user authentication, or a lead with EXTENSION_ALLOW_LEAD_ACTIVATION enabled",
|
|
64
|
+
evaluate: (principal) =>
|
|
65
|
+
principal.kind === "operator" ||
|
|
66
|
+
principal.kind === "user" ||
|
|
67
|
+
(actsAsLead(principal) && isEnvFlagEnabled("EXTENSION_ALLOW_LEAD_ACTIVATION", true)),
|
|
68
|
+
};
|
|
69
|
+
|
|
58
70
|
const leadOrTaskCreator: LegacyRule = {
|
|
59
71
|
name: "lead-or-task-creator",
|
|
60
72
|
denyReason: "requires lead agent or task creator",
|
|
@@ -231,5 +243,5 @@ export const LEGACY_POLICY = {
|
|
|
231
243
|
"script.api.rotate": leadOnly,
|
|
232
244
|
"script.api.delete": leadOnly,
|
|
233
245
|
"extension.write": leadOrOperatorOrUser,
|
|
234
|
-
"extension.activate":
|
|
246
|
+
"extension.activate": extensionActivation,
|
|
235
247
|
} as const satisfies Record<PermissionVerb, LegacyRule>;
|
|
@@ -57,6 +57,10 @@ export const SDK_TOOL_NAME_MAP = {
|
|
|
57
57
|
script_listRuns: "list-script-runs",
|
|
58
58
|
|
|
59
59
|
// ── extensions ──
|
|
60
|
+
extension_delete: "extension-delete",
|
|
61
|
+
extension_enable: "extension-enable",
|
|
62
|
+
extension_disable: "extension-disable",
|
|
63
|
+
extension_activate_version: "extension-activate-version",
|
|
60
64
|
extension_install: "extension-install",
|
|
61
65
|
extension_list: "extension-list",
|
|
62
66
|
|
|
@@ -525,6 +525,10 @@ declare module "swarm-sdk" {
|
|
|
525
525
|
config?: Record<string, unknown>;
|
|
526
526
|
}): Promise<unknown>;
|
|
527
527
|
extension_list(args?: { enabledOnly?: boolean }): Promise<unknown>;
|
|
528
|
+
extension_delete(args: { id: string }): Promise<unknown>;
|
|
529
|
+
extension_enable(args: { id: string }): Promise<unknown>;
|
|
530
|
+
extension_disable(args: { id: string }): Promise<unknown>;
|
|
531
|
+
extension_activate_version(args: { id: string; version: number }): Promise<unknown>;
|
|
528
532
|
|
|
529
533
|
// --- write: repos ---
|
|
530
534
|
repo_update(args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -507,6 +507,10 @@ declare module "swarm-sdk" {
|
|
|
507
507
|
config?: Record<string, unknown>;
|
|
508
508
|
}): Promise<unknown>;
|
|
509
509
|
extension_list(args?: { enabledOnly?: boolean }): Promise<unknown>;
|
|
510
|
+
extension_delete(args: { id: string }): Promise<unknown>;
|
|
511
|
+
extension_enable(args: { id: string }): Promise<unknown>;
|
|
512
|
+
extension_disable(args: { id: string }): Promise<unknown>;
|
|
513
|
+
extension_activate_version(args: { id: string; version: number }): Promise<unknown>;
|
|
510
514
|
|
|
511
515
|
// --- write: repos ---
|
|
512
516
|
repo_update(args: Record<string, unknown>): Promise<unknown>;
|
package/src/server.ts
CHANGED
|
@@ -30,6 +30,10 @@ import { registerDbQueryTool } from "./tools/db-query";
|
|
|
30
30
|
import { registerDeferTaskTool } from "./tools/defer-task";
|
|
31
31
|
import { registerDeleteChannelTool } from "./tools/delete-channel";
|
|
32
32
|
import { registerDeletePageTool } from "./tools/delete-page";
|
|
33
|
+
import { registerExtensionActivateVersionTool } from "./tools/extension-activate-version";
|
|
34
|
+
import { registerExtensionDeleteTool } from "./tools/extension-delete";
|
|
35
|
+
import { registerExtensionDisableTool } from "./tools/extension-disable";
|
|
36
|
+
import { registerExtensionEnableTool } from "./tools/extension-enable";
|
|
33
37
|
import { registerExtensionInstallTool } from "./tools/extension-install";
|
|
34
38
|
import { registerExtensionListTool } from "./tools/extension-list";
|
|
35
39
|
import { registerGetMetricsTool } from "./tools/get-metrics";
|
|
@@ -340,6 +344,10 @@ export async function createServer(
|
|
|
340
344
|
registerScriptSearchTool(server);
|
|
341
345
|
registerScriptRunTool(server);
|
|
342
346
|
registerScriptUpsertTool(server);
|
|
347
|
+
registerExtensionDeleteTool(server);
|
|
348
|
+
registerExtensionEnableTool(server);
|
|
349
|
+
registerExtensionDisableTool(server);
|
|
350
|
+
registerExtensionActivateVersionTool(server);
|
|
343
351
|
registerExtensionInstallTool(server);
|
|
344
352
|
registerExtensionListTool(server);
|
|
345
353
|
registerScriptDeleteTool(server);
|
|
@@ -402,6 +410,10 @@ export async function createServer(
|
|
|
402
410
|
registerScriptApisTool(server);
|
|
403
411
|
registerScriptRunTool(server);
|
|
404
412
|
registerScriptUpsertTool(server);
|
|
413
|
+
registerExtensionDeleteTool(server);
|
|
414
|
+
registerExtensionEnableTool(server);
|
|
415
|
+
registerExtensionDisableTool(server);
|
|
416
|
+
registerExtensionActivateVersionTool(server);
|
|
405
417
|
registerExtensionInstallTool(server);
|
|
406
418
|
registerExtensionListTool(server);
|
|
407
419
|
registerScriptDeleteTool(server);
|
package/src/slack/render-v2.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
import { buildAskClosure, type ClosureState, closureState } from "./closure";
|
|
45
45
|
import { reactionName, type SlackReactionEvent } from "./reaction-shortcode";
|
|
46
46
|
import { getAgentDisplayName, getAgentEmoji } from "./responses";
|
|
47
|
+
import { isSlackEtaOnlyNotice, slackTaskOutput } from "./task-output";
|
|
47
48
|
|
|
48
49
|
const TREE_UPDATE_DEBOUNCE_MS = 500;
|
|
49
50
|
const TREE_UPDATE_MIN_INTERVAL_MS = 3_000;
|
|
@@ -757,7 +758,7 @@ async function outcomeContent(task: AgentTask, slackReplySent: boolean): Promise
|
|
|
757
758
|
: "Agent";
|
|
758
759
|
return `✅ ${agentName} completed`;
|
|
759
760
|
}
|
|
760
|
-
return `✅\n\n${outcomeText(task
|
|
761
|
+
return `✅\n\n${outcomeText(slackTaskOutput(task), "Task completed.")}`;
|
|
761
762
|
}
|
|
762
763
|
|
|
763
764
|
async function agentDisplayNameFor(task: AgentTask): Promise<string> {
|
|
@@ -786,7 +787,7 @@ export async function childOutcomeContent(
|
|
|
786
787
|
if (slackReplySent && !task.tags?.includes("deferred")) {
|
|
787
788
|
return `↳ ✅ ${agentName} completed`;
|
|
788
789
|
}
|
|
789
|
-
return `↳ ✅ ${agentName} — result\n\n${outcomeText(task
|
|
790
|
+
return `↳ ✅ ${agentName} — result\n\n${outcomeText(slackTaskOutput(task), "Task completed.")}`;
|
|
790
791
|
}
|
|
791
792
|
|
|
792
793
|
// A deferred wake continues a human conversation even though the scheduler
|
|
@@ -808,6 +809,7 @@ function isSlackContinuation(task: AgentTask): boolean {
|
|
|
808
809
|
*/
|
|
809
810
|
function isChildCardCandidate(task: AgentTask, delegationActivatedAt: string): boolean {
|
|
810
811
|
return (
|
|
812
|
+
!isSlackEtaOnlyNotice(task) &&
|
|
811
813
|
task.source !== "slack" &&
|
|
812
814
|
!isSlackContinuation(task) &&
|
|
813
815
|
!!task.slackChannelId &&
|
|
@@ -821,6 +823,7 @@ function isChildCardCandidate(task: AgentTask, delegationActivatedAt: string): b
|
|
|
821
823
|
|
|
822
824
|
function taskNeedsDirectOutcome(task: AgentTask, activatedAt: string): boolean {
|
|
823
825
|
return (
|
|
826
|
+
!isSlackEtaOnlyNotice(task) &&
|
|
824
827
|
((task.source === "slack" && isAskOutcomeStatus(task.status)) ||
|
|
825
828
|
(isSlackContinuation(task) && isOutcomeStatus(task.status))) &&
|
|
826
829
|
task.createdAt >= activatedAt
|
|
@@ -839,7 +842,7 @@ async function conclusionResultsLines(closure: AgentTask[]): Promise<string[]> {
|
|
|
839
842
|
const lines: string[] = [];
|
|
840
843
|
for (const member of closure) {
|
|
841
844
|
if (member.taskType === "follow-up" || member.taskType === "reroute-decision") continue;
|
|
842
|
-
if (!isOutcomeStatus(member.status)) continue;
|
|
845
|
+
if (!isOutcomeStatus(member.status) || isSlackEtaOnlyNotice(member)) continue;
|
|
843
846
|
const glyph = await taskStateGlyph(member, new Date());
|
|
844
847
|
const agentName = await agentDisplayNameFor(member);
|
|
845
848
|
const card = await getSlackOutcomeMessage(member.id);
|
|
@@ -847,7 +850,10 @@ async function conclusionResultsLines(closure: AgentTask[]): Promise<string[]> {
|
|
|
847
850
|
lines.push(`↳ ${glyph} ${agentName} — ${card.permalink}`);
|
|
848
851
|
continue;
|
|
849
852
|
}
|
|
850
|
-
const raw = outcomeText(
|
|
853
|
+
const raw = outcomeText(
|
|
854
|
+
member.status === "failed" ? member.failureReason : slackTaskOutput(member),
|
|
855
|
+
"",
|
|
856
|
+
);
|
|
851
857
|
const digest =
|
|
852
858
|
raw.length > CONCLUSION_DIGEST_LENGTH
|
|
853
859
|
? `${raw.slice(0, CONCLUSION_DIGEST_LENGTH).trimEnd()}…`
|
package/src/slack/responses.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
markdownToSlack,
|
|
16
16
|
splitSlackSectionText,
|
|
17
17
|
} from "./blocks";
|
|
18
|
+
import { isSlackEtaOnlyNotice, slackTaskOutput } from "./task-output";
|
|
18
19
|
|
|
19
20
|
// Re-export for backward compatibility
|
|
20
21
|
export { markdownToSlack } from "./blocks";
|
|
@@ -56,7 +57,7 @@ export function shouldPostInlineCompletionOutput(task: AgentTask): boolean {
|
|
|
56
57
|
if (!task.slackChannelId || !task.slackThreadTs) return false;
|
|
57
58
|
if (task.slackReplySent) return false;
|
|
58
59
|
|
|
59
|
-
const output = task
|
|
60
|
+
const output = slackTaskOutput(task)?.trim();
|
|
60
61
|
return !!output && isTreeOutputTruncated(markdownToSlack(output));
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -85,7 +86,7 @@ export async function sendInlineTaskOutput(task: AgentTask): Promise<boolean> {
|
|
|
85
86
|
const chunks = formatInlineCompletionOutputChunks({
|
|
86
87
|
agentName: agent.name,
|
|
87
88
|
taskId: task.id,
|
|
88
|
-
output: task
|
|
89
|
+
output: slackTaskOutput(task) ?? "",
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
try {
|
|
@@ -115,6 +116,7 @@ export async function sendInlineTaskOutput(task: AgentTask): Promise<boolean> {
|
|
|
115
116
|
* Send a task completion message to Slack with the agent's persona.
|
|
116
117
|
*/
|
|
117
118
|
export async function sendTaskResponse(task: AgentTask): Promise<boolean> {
|
|
119
|
+
if (isSlackEtaOnlyNotice(task)) return true;
|
|
118
120
|
const app = getSlackApp();
|
|
119
121
|
if (!app || !task.slackChannelId || !task.slackThreadTs) {
|
|
120
122
|
return false;
|
|
@@ -136,7 +138,7 @@ export async function sendTaskResponse(task: AgentTask): Promise<boolean> {
|
|
|
136
138
|
|
|
137
139
|
try {
|
|
138
140
|
if (task.status === "completed") {
|
|
139
|
-
const output = task
|
|
141
|
+
const output = slackTaskOutput(task) || "Task completed.";
|
|
140
142
|
const slackOutput = markdownToSlack(output);
|
|
141
143
|
const attachmentsBlock = formatAttachmentsBlockForSlack(await getTaskAttachments(task.id));
|
|
142
144
|
const body = slackOutput + attachmentsBlock;
|
|
@@ -285,6 +287,7 @@ export async function updateToFinal(
|
|
|
285
287
|
task: AgentTask,
|
|
286
288
|
messageTs: string,
|
|
287
289
|
): Promise<SlackUpdateResult> {
|
|
290
|
+
if (isSlackEtaOnlyNotice(task)) return "ok";
|
|
288
291
|
const app = getSlackApp();
|
|
289
292
|
if (!app || !task.slackChannelId || !task.agentId) return "failed";
|
|
290
293
|
|
|
@@ -297,7 +300,7 @@ export async function updateToFinal(
|
|
|
297
300
|
let completionBlockBatches: unknown[][] | undefined;
|
|
298
301
|
|
|
299
302
|
if (task.status === "completed") {
|
|
300
|
-
const output = task
|
|
303
|
+
const output = slackTaskOutput(task) || "Task completed.";
|
|
301
304
|
const slackOutput = markdownToSlack(output);
|
|
302
305
|
const attachmentsBlock = formatAttachmentsBlockForSlack(await getTaskAttachments(task.id));
|
|
303
306
|
const body = slackOutput + attachmentsBlock;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentTask } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Keep the stored deferral ETA for task details, but omit it from Slack. */
|
|
4
|
+
export function slackTaskOutput(
|
|
5
|
+
task: Pick<AgentTask, "output" | "tags" | "outputSchema" | "status">,
|
|
6
|
+
): string | undefined {
|
|
7
|
+
const output = task.output ?? undefined;
|
|
8
|
+
if (task.status !== "completed" || task.outputSchema || !task.tags?.includes("deferred")) {
|
|
9
|
+
return output;
|
|
10
|
+
}
|
|
11
|
+
// Only match the engine-authored line from defer-task, not continuation results.
|
|
12
|
+
const notice = output?.match(/^Deferred until [^\n]+? \([^\n]+?\) -> (.*)$/);
|
|
13
|
+
if (!notice) return output;
|
|
14
|
+
const pending = notice[1]?.trim();
|
|
15
|
+
return pending ? `Pending: ${pending}` : "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** An ETA-only notice has no useful content left to post. */
|
|
19
|
+
export function isSlackEtaOnlyNotice(task: AgentTask): boolean {
|
|
20
|
+
return !!task.output && slackTaskOutput(task) === "";
|
|
21
|
+
}
|
package/src/slack/watcher.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
updateToFinal,
|
|
29
29
|
updateTreeMessage,
|
|
30
30
|
} from "./responses";
|
|
31
|
+
import { slackTaskOutput } from "./task-output";
|
|
31
32
|
|
|
32
33
|
let watcherInterval: ReturnType<typeof setInterval> | null = null;
|
|
33
34
|
let isProcessing = false;
|
|
@@ -169,7 +170,7 @@ export async function buildTreeNodes(tree: TreeMessageState): Promise<TreeNode[]
|
|
|
169
170
|
progress: child.progress ?? undefined,
|
|
170
171
|
duration: childDuration,
|
|
171
172
|
slackReplySent: child.slackReplySent,
|
|
172
|
-
output: child
|
|
173
|
+
output: slackTaskOutput(child),
|
|
173
174
|
failureReason: child.failureReason ?? undefined,
|
|
174
175
|
attachments: childAttachments,
|
|
175
176
|
steered: childSteered,
|
|
@@ -192,7 +193,7 @@ export async function buildTreeNodes(tree: TreeMessageState): Promise<TreeNode[]
|
|
|
192
193
|
progress: task.progress ?? undefined,
|
|
193
194
|
duration,
|
|
194
195
|
slackReplySent: task.slackReplySent,
|
|
195
|
-
output: task
|
|
196
|
+
output: slackTaskOutput(task),
|
|
196
197
|
failureReason: task.failureReason ?? undefined,
|
|
197
198
|
attachments: rootAttachments,
|
|
198
199
|
steered: rootSteered,
|
|
@@ -4,6 +4,7 @@ import { getAgentById, getTaskById } from "../be/db";
|
|
|
4
4
|
import { isEnvFlagEnabled } from "../utils/env-flag";
|
|
5
5
|
import { getTaskUrl } from "./blocks";
|
|
6
6
|
import { isUserAllowed } from "./handlers";
|
|
7
|
+
import { slackTaskOutput } from "./task-output";
|
|
7
8
|
|
|
8
9
|
type DetailsEvent = SlackEventMiddlewareArgs<"entity_details_requested">["event"];
|
|
9
10
|
type Details = Omit<EntityPresentDetailsArguments, "trigger_id" | "token">;
|
|
@@ -36,7 +37,7 @@ async function getDetails(event: DetailsEvent, client: WebClient): Promise<Detai
|
|
|
36
37
|
if (task.slackChannelId !== event.channel) return { error: { status: "restricted" } };
|
|
37
38
|
|
|
38
39
|
const agent = task.agentId ? await getAgentById(task.agentId) : null;
|
|
39
|
-
const result = task
|
|
40
|
+
const result = slackTaskOutput(task) || task.failureReason || task.progress;
|
|
40
41
|
return {
|
|
41
42
|
metadata: {
|
|
42
43
|
entity_type: "slack#/entities/task",
|