@desplega.ai/agent-swarm 1.171.0 → 1.172.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-1z9p3fvx.js → acp-adapter-fpecx1g1.js} +23 -5
- package/dist/{actions-rc2yjk8a.js → actions-pw87a1a8.js} +24 -25
- package/dist/{amp-adapter-ypyf5s1x.js → amp-adapter-8jem2bc0.js} +1 -1
- package/dist/{anthropic-messages-etkbxhhy.js → anthropic-messages-8ee2f22q.js} +55 -22
- package/dist/{app-04ab2s0z.js → app-wmf0m843.js} +7 -8
- package/dist/{assistant-vhmjd8wf.js → assistant-emp1e108.js} +26 -27
- package/dist/{azure-openai-responses-xap57x4b.js → azure-openai-responses-ap1gwxgr.js} +3 -3
- package/dist/{boot-reembed-3p0m259x.js → boot-reembed-1de4f4aj.js} +6 -7
- package/dist/{boot-reembed-wew3gwk8.js → boot-reembed-4ymn9atn.js} +6 -7
- package/dist/{boot-scrub-sweep-5ytwtq24.js → boot-scrub-sweep-adhk1s8c.js} +7 -8
- package/dist/{boot-seeds-s8p2xry8.js → boot-seeds-8q637aew.js} +9 -10
- package/dist/{claude-adapter-akrzg6jh.js → claude-adapter-t26cv8a4.js} +16 -17
- package/dist/{claude-managed-adapter-kqqqhp2q.js → claude-managed-adapter-xgxhqrt2.js} +2 -2
- package/dist/{claude-sdk-session-exq1jadx.js → claude-sdk-session-gsfsh662.js} +16 -17
- package/dist/{cli-n8ybnv1k.js → cli-04emfe2q.js} +1 -1
- package/dist/{cli-w0bwte75.js → cli-104zsxba.js} +2 -1
- package/dist/{cli-x6z90nq9.js → cli-19njxg5g.js} +2 -4
- package/dist/{cli-8mr695sb.js → cli-1hgat6pa.js} +5 -5
- package/dist/{cli-1qtgd1h1.js → cli-1m2xxxe2.js} +2 -2
- package/dist/{cli-231av4y6.js → cli-1x7da0de.js} +2 -4
- package/dist/{cli-x3fy2tgp.js → cli-388egyf6.js} +1 -1
- package/dist/{cli-j0w59p15.js → cli-4fnb6884.js} +4 -4
- package/dist/{cli-y1j1ggbf.js → cli-4nz8p1p5.js} +1372 -588
- package/dist/{cli-s88my3br.js → cli-5rv1j9ga.js} +6 -6
- package/dist/{cli-mn8qkd8v.js → cli-61625k3x.js} +1 -1
- package/dist/{cli-mzj3aqac.js → cli-6jb2rf77.js} +3 -3
- package/dist/{cli-ec7hrgqp.js → cli-8hzfb6xz.js} +8 -8
- package/dist/{cli-n805bb98.js → cli-8vs9trd4.js} +1 -1
- package/dist/{cli-xf713zz0.js → cli-9fxpr83v.js} +1 -1
- package/dist/{cli-q1vnz8wv.js → cli-9s17naqf.js} +1 -1
- package/dist/{cli-nkjwe4z7.js → cli-9xsmdpdt.js} +59 -54
- package/dist/{cli-6xxxb8qt.js → cli-aspcaef3.js} +5 -5
- package/dist/cli-bf7485pm.js +514 -0
- package/dist/{cli-s9y81s7j.js → cli-bt8k5h3s.js} +625 -150
- package/dist/{cli-jxsgrnmq.js → cli-c2pq2rjb.js} +2 -2
- package/dist/{cli-13xwcqvj.js → cli-c5km5znr.js} +2 -2
- package/dist/{cli-5y67yrt6.js → cli-cceskcvt.js} +112 -176
- package/dist/{cli-ph3145b7.js → cli-cf69s3ew.js} +4 -4
- package/dist/{cli-k2v5x13t.js → cli-d603qf86.js} +1 -1
- package/dist/{cli-r455yd58.js → cli-e0a9sk00.js} +1 -1
- package/dist/{cli-cgj4hc12.js → cli-eh1ytktb.js} +1872 -190
- package/dist/{cli-erbp6b4g.js → cli-esnpehf5.js} +7 -8
- package/dist/cli-ev91t6xp.js +148 -0
- package/dist/{cli-e2prcx5m.js → cli-fdvbjtbw.js} +235 -5
- package/dist/{cli-62bwwc2q.js → cli-g4f5pj26.js} +3 -3
- package/dist/{cli-tg8q0b5y.js → cli-g6t9texm.js} +5 -5
- package/dist/{cli-p2pjfq1r.js → cli-gj81e47t.js} +1 -1
- package/dist/{cli-srmej5tp.js → cli-gp1rtyvk.js} +2 -2
- package/dist/{cli-rb9m6rpe.js → cli-h6vtvt5c.js} +139 -1
- package/dist/cli-hd27d2md.js +1057 -0
- package/dist/{cli-hsk5gd15.js → cli-hwwvy36w.js} +3 -3
- package/dist/{cli-2rbt7at8.js → cli-hz99wfxp.js} +1 -1
- package/dist/cli-j1pmc07z.js +706 -0
- package/dist/{cli-01qndrvw.js → cli-j6kpj9az.js} +14 -8
- package/dist/{cli-2qbs2tvf.js → cli-k46r9hmk.js} +6 -6
- package/dist/{cli-wfvedq5w.js → cli-kb4ehzx5.js} +5 -5
- package/dist/{cli-agamgm0p.js → cli-mvzyxwg3.js} +1 -1
- package/dist/{cli-280gnz4v.js → cli-nv4zd0b9.js} +1 -1
- package/dist/{cli-rrbctenq.js → cli-nyhb1r6x.js} +3 -5
- package/dist/{cli-g7k7zbcz.js → cli-p7z6q2m6.js} +2 -1
- package/dist/{cli-yn0a417a.js → cli-phre6gz0.js} +22 -4
- package/dist/{cli-m4csbs67.js → cli-pj6x9qce.js} +1 -1
- package/dist/{cli-w0zx9ya1.js → cli-prrt9fa0.js} +4 -4
- package/dist/{cli-ctret352.js → cli-pstq9888.js} +33 -33
- package/dist/{cli-y195hv1a.js → cli-pxeeyc53.js} +1 -1
- package/dist/{cli-4bm25xtw.js → cli-qsztpfry.js} +1 -1
- package/dist/{cli-5t5my0h8.js → cli-qybkr9n3.js} +278 -1267
- package/dist/{cli-xezn9nkh.js → cli-r44t9wqc.js} +29 -472
- package/dist/{cli-x207g4w1.js → cli-r8smj8b8.js} +1 -1
- package/dist/{cli-bxqez1p0.js → cli-rcp5aqdw.js} +9 -39
- package/dist/{cli-vr5avem2.js → cli-smcrrmhy.js} +14 -18
- package/dist/{cli-0d2rqhvq.js → cli-tgpfnm6r.js} +15 -17
- package/dist/cli-vb8g5x02.js +549 -0
- package/dist/{cli-gt4hxb8p.js → cli-vv0qtfpx.js} +1 -2
- package/dist/{cli-95zs84qw.js → cli-wd0hz6ah.js} +671 -388
- package/dist/{cli-nkqjj42q.js → cli-x4m6feqt.js} +1 -1
- package/dist/{cli-x61srfpp.js → cli-xczpdp9e.js} +2 -2
- package/dist/cli-z1kd834t.js +36 -0
- package/dist/{cli-p8f7dqgt.js → cli-ztrv9y21.js} +48 -33
- package/dist/cli.js +24 -25
- package/dist/{cloudflare-workers-ai-system-one-sta45jf9.js → cloudflare-workers-ai-system-one-vnpyfr7e.js} +3 -3
- package/dist/{codex-adapter-9ag75whm.js → codex-adapter-m4tgdysy.js} +12 -12
- package/dist/{codex-hook-7ba15hjd.js → codex-hook-xch6z5wt.js} +2 -3
- package/dist/{codex-session-runner-9fp7y4cf.js → codex-session-runner-ymwzm6ea.js} +12 -12
- package/dist/{commands-d7eah0y9.js → commands-n0hshcvs.js} +6 -7
- package/dist/{controllers-connect-8t0k65x2.js → controllers-connect-y07cqcvy.js} +24 -25
- package/dist/{cursor-adapter-t4pr1m3c.js → cursor-adapter-q8pgv5qa.js} +2 -3
- package/dist/{db-sp39qv72.js → db-4w7tmt1g.js} +6 -7
- package/dist/{db-2rmd23wq.js → db-hkqvrdcb.js} +7 -8
- package/dist/{dispatcher-g7fsybsd.js → dispatcher-5sd69y14.js} +23 -24
- package/dist/{easter-egg-3d-66zxervv.js → easter-egg-3d-m8rvz40s.js} +4 -3
- package/dist/execute-107xtsp1.js +3745 -0
- package/dist/{google-generative-ai-pvhm0dq1.js → google-generative-ai-7pavrbv7.js} +3 -3
- package/dist/{google-vertex-tek9sqe2.js → google-vertex-3f1jk41f.js} +3 -3
- package/dist/{grok-adapter-1err1me7.js → grok-adapter-8m9yqzbq.js} +24 -6
- package/dist/{handlers-c7yggm37.js → handlers-s20xr10r.js} +26 -27
- package/dist/{heartbeat-dkm924fk.js → heartbeat-3k31q4g2.js} +25 -26
- package/dist/{hook-wgx4exw6.js → hook-gdn6a2fn.js} +13 -14
- package/dist/{hook-g8e7sfnf.js → hook-ygwerrdh.js} +14 -15
- package/dist/{http-1d23k928.js → http-jdteeg81.js} +53 -55
- package/dist/{index-y5vvczrs.js → index-6ksewd5y.js} +20 -18
- package/dist/{index-kkeftv5h.js → index-82rk11v7.js} +4 -4
- package/dist/{index-gm10tmye.js → index-e0snym4t.js} +23 -24
- package/dist/{index-vertaxs1.js → index-ffqzqgwb.js} +6 -7
- package/dist/{index-q7cj9bn2.js → index-tsszt7g9.js} +25 -26
- package/dist/{index-w097eg0x.js → index-vccv018g.js} +1 -1
- package/dist/{index-va4zbvzp.js → index-vm3bm9t7.js} +24 -25
- package/dist/{keepalive-1jc5e7ct.js → keepalive-jekc896t.js} +8 -9
- package/dist/{lead-yxw2f1sg.js → lead-4k9s9szx.js} +40 -41
- package/dist/{maintenance-h7w2m10y.js → maintenance-shazch4n.js} +6 -7
- package/dist/{mistral-conversations-rjxa9s1j.js → mistral-conversations-vjc1ggpd.js} +29 -15
- package/dist/{oauth-refresh-sweep-wh9ypd1b.js → oauth-refresh-sweep-5gb36kn8.js} +7 -8
- package/dist/{onboard-dfr9j762.js → onboard-ydrbnvww.js} +2 -2
- package/dist/{openai-codex-responses-pgvtddvv.js → openai-codex-responses-33qhkkdy.js} +7 -39
- package/dist/{openai-completions-44gb0zsk.js → openai-completions-s88xm3ns.js} +2 -2
- package/dist/{openai-decisions-vj9nkpf8.js → openai-decisions-fctnj8xd.js} +2 -2
- package/dist/{openai-responses-9dyzwzkr.js → openai-responses-7xd2f2e2.js} +3 -3
- package/dist/opencode-adapter-5zh0he20.js +55 -0
- package/dist/{otel-impl-and31m5s.js → otel-impl-6pf5cv86.js} +1 -1
- package/dist/{pi-mono-adapter-fx6k5wwh.js → pi-mono-adapter-ef9wwx9m.js} +25 -500
- package/dist/{pricing-refresh-23zr6j3w.js → pricing-refresh-s52nsa5q.js} +8 -9
- package/dist/{rbac-roles-wnx35x4x.js → rbac-roles-aq3msh4e.js} +6 -7
- package/dist/{rbac-roles-nc7k9d0t.js → rbac-roles-q5gb5e8v.js} +7 -8
- package/dist/{render-v2-81cg1y52.js → render-v2-pd1rg54q.js} +7 -8
- package/dist/{resume-d4b6j2n2.js → resume-gw4a2c6v.js} +14 -15
- package/dist/{runtime-h9ha1mq6.js → runtime-qfry88v2.js} +4 -7
- package/dist/{setup-g68r1w28.js → setup-jda1ksdh.js} +2 -2
- package/dist/{typesafe-system-one-e988vty7.js → typesafe-system-one-zzzcx4s3.js} +3 -3
- package/dist/{virtual-modules-rdjmcg7q.js → virtual-modules-b3ga2m65.js} +13 -12
- package/dist/{work-objects-0zzdnbt9.js → work-objects-tb7ycye8.js} +26 -27
- package/dist/{worker-p1g8qm6h.js → worker-njjccz5d.js} +40 -41
- package/openapi.json +1 -1
- package/package.json +7 -8
- package/src/be/scripts/extract-schema.ts +4 -0
- package/src/github/handlers.ts +61 -18
- package/src/github/types.ts +2 -1
- package/src/prompts/goal-reminder.ts +30 -0
- package/src/prompts/session-templates.ts +22 -0
- package/src/providers/acp-targets.ts +3 -4
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/harness-version.ts +0 -1
- package/src/providers/opencode-adapter.ts +442 -723
- package/src/providers/opencode-swarm-plugin.ts +88 -0
- package/src/providers/pi-mono-extension.ts +58 -37
- package/src/scripts-runtime/executors/native.ts +6 -5
- package/src/scripts-runtime/executors/quickjs-bundle.ts +18 -11
- package/src/scripts-runtime/extract-args-schema.ts +22 -4
- package/src/scripts-runtime/import-allowlist.ts +1 -1
- package/src/scripts-runtime/zod-mini-bundle.ts +5 -0
- package/src/slack/render-v2.ts +2 -2
- package/src/tests/entrypoint-acp-target-binary.test.ts +19 -1
- package/src/tests/github-handlers-inline-comments.test.ts +131 -2
- package/src/tests/github-handlers-reactions.test.ts +9 -0
- package/src/tests/harness-version.test.ts +0 -20
- package/src/tests/opencode-adapter.test.ts +536 -1389
- package/src/tests/opencode-mcp-admission-sdk.test.ts +106 -0
- package/src/tests/opencode-swarm-plugin.test.ts +130 -0
- package/src/tests/pi-mono-extension.test.ts +59 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/provider-registration.test.ts +3 -0
- package/src/tests/script-executor-quickjs.test.ts +17 -12
- package/src/tests/scripts-http.test.ts +24 -16
- package/src/tests/scripts-import-allowlist.test.ts +1 -1
- package/src/tests/scripts-runtime.test.ts +78 -40
- package/src/tests/seed-scripts.test.ts +13 -0
- package/src/tests/slack-render-v2.test.ts +4 -2
- package/src/tests/ui-logs-parser.test.ts +28 -0
- package/src/tools/script-upsert.ts +1 -1
- package/src/utils/memory-raters.ts +7 -3
- package/src/utils/openrouter-base-url.ts +0 -2
- package/templates/schedules/weekly-harness-upgrade-check/content.md +4 -6
- package/templates/skills/heartbeat-runbook/content.md +2 -0
- package/templates/skills/swarm-scripts/SKILL.md +1 -0
- package/templates/skills/swarm-scripts/content.md +1 -0
- package/dist/cli-5bfdm1ah.js +0 -462
- package/dist/cli-5c2j5yvx.js +0 -222
- package/dist/cli-rhy7z1rp.js +0 -532
- package/dist/cli-ywh2a87k.js +0 -2359
- package/dist/execute-h4jgevyw.js +0 -1387
- package/dist/opencode-adapter-f1zby94p.js +0 -34
- package/plugin/opencode-plugins/lib/memory-raters.ts +0 -7
- package/src/tests/opencode-plugin.test.ts +0 -580
- package/src/tests/providers/opencode-cost.test.ts +0 -74
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Swarm plugin for OpenCode 2.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode 2 runs in-process (`@opencode/sdk`), so this plugin is a plain
|
|
5
|
+
* object passed to `OpenCode.create({ plugins })`. It closes over the task's
|
|
6
|
+
* swarm config and reuses the pi extension's swarm-hook helpers; no env vars,
|
|
7
|
+
* no plugin file on disk, no vendored copies.
|
|
8
|
+
*
|
|
9
|
+
* Session-end work (identity sync, session summary, session close) runs from
|
|
10
|
+
* the adapter's event loop, not from here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Plugin } from "@opencode/plugin";
|
|
14
|
+
import { renderGoalReminder } from "../prompts/goal-reminder";
|
|
15
|
+
import {
|
|
16
|
+
apiHeaders,
|
|
17
|
+
checkShouldBlockPolling,
|
|
18
|
+
fetchConcurrentContext,
|
|
19
|
+
fetchTaskDetails,
|
|
20
|
+
fireAndForget,
|
|
21
|
+
isTaskCancelled,
|
|
22
|
+
type SwarmHooksConfig,
|
|
23
|
+
} from "./pi-mono-extension";
|
|
24
|
+
|
|
25
|
+
/** The host's registered tools (MCP tools included), keyed by effective name. */
|
|
26
|
+
export type OpencodeToolLister = () => Promise<ReadonlyArray<{ readonly id: string }>>;
|
|
27
|
+
|
|
28
|
+
export interface OpencodeSwarmPluginOptions {
|
|
29
|
+
/** Receives the tool lister once OpenCode sets the plugin up. */
|
|
30
|
+
onToolRegistry?: (listTools: OpencodeToolLister) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createOpencodeSwarmPlugin(
|
|
34
|
+
config: SwarmHooksConfig,
|
|
35
|
+
options: OpencodeSwarmPluginOptions = {},
|
|
36
|
+
): Plugin.Plugin {
|
|
37
|
+
return {
|
|
38
|
+
id: "agent-swarm",
|
|
39
|
+
async setup(ctx) {
|
|
40
|
+
options.onToolRegistry?.(() => ctx.tool.list());
|
|
41
|
+
|
|
42
|
+
// A throw blocks the tool; OpenCode hands the message back to the model.
|
|
43
|
+
await ctx.tool.hook("execute.before", async (event) => {
|
|
44
|
+
if (!config.isLead && config.taskId) {
|
|
45
|
+
const { cancelled, reason } = await isTaskCancelled(config);
|
|
46
|
+
if (cancelled) {
|
|
47
|
+
const cancelReason = reason || "Task cancelled by lead or creator";
|
|
48
|
+
throw new Error(
|
|
49
|
+
`🛑 TASK CANCELLED: Your current task (${config.taskId.slice(0, 8)}) has been cancelled. Reason: "${cancelReason}". ` +
|
|
50
|
+
"Stop working on this task immediately. Do NOT continue making tool calls. " +
|
|
51
|
+
"Use store-progress to acknowledge the cancellation and mark the task as failed, then wait for new tasks.",
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (event.tool.includes("poll-task") && (await checkShouldBlockPolling(config))) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"🛑 POLLING LIMIT REACHED: You have exceeded the maximum empty poll attempts. " +
|
|
58
|
+
"EXIT NOW - do not make any more tool calls.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await ctx.tool.hook("execute.after", () => {
|
|
64
|
+
if (config.isLead || !config.taskId) return;
|
|
65
|
+
fireAndForget(`${config.apiUrl}/api/agents/${config.agentId}/activity`, {
|
|
66
|
+
method: "PUT",
|
|
67
|
+
headers: apiHeaders(config),
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Lead only: tell the model what other sessions are already handling.
|
|
72
|
+
await ctx.session.hook("context", async (event) => {
|
|
73
|
+
if (!config.isLead) return;
|
|
74
|
+
const concurrent = await fetchConcurrentContext(config);
|
|
75
|
+
if (concurrent) event.system.push({ type: "text", text: concurrent });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// PreCompact parity: keep the task goal in the checkpoint summary.
|
|
79
|
+
await ctx.session.hook("compaction", async (event) => {
|
|
80
|
+
if (!config.taskId) return;
|
|
81
|
+
const task = await fetchTaskDetails(config);
|
|
82
|
+
if (!task) return;
|
|
83
|
+
const reminder = await renderGoalReminder(task);
|
|
84
|
+
if (reminder) event.system.push({ type: "text", text: reminder });
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { ExtensionFactory, McpServerConfig } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { buildRatingsFromLlm, fetchRetrievalsForTask, postRatings } from "../be/memory/raters/llm";
|
|
11
11
|
import { checkToolLoop, clearToolHistory } from "../hooks/tool-loop-detection";
|
|
12
|
+
import { renderGoalReminder } from "../prompts/goal-reminder";
|
|
12
13
|
import { summarizeSession as runSummarize } from "../utils/internal-ai";
|
|
13
14
|
import { getMemoryRaterNames } from "../utils/memory-raters";
|
|
14
15
|
import { getOpenRouterAttributionHeaders } from "../utils/openrouter-base-url";
|
|
@@ -34,7 +35,7 @@ export interface SwarmHooksConfig {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/** Standard headers for swarm API requests */
|
|
37
|
-
function apiHeaders(config: SwarmHooksConfig): Record<string, string> {
|
|
38
|
+
export function apiHeaders(config: SwarmHooksConfig): Record<string, string> {
|
|
38
39
|
return {
|
|
39
40
|
"Content-Type": "application/json",
|
|
40
41
|
...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
|
|
@@ -43,12 +44,12 @@ function apiHeaders(config: SwarmHooksConfig): Record<string, string> {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/** Fire-and-forget fetch — logs nothing, swallows errors */
|
|
46
|
-
function fireAndForget(url: string, init: RequestInit): void {
|
|
47
|
+
export function fireAndForget(url: string, init: RequestInit): void {
|
|
47
48
|
void fetch(url, init).catch(() => {});
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/** Check if a task has been cancelled */
|
|
51
|
-
async function isTaskCancelled(
|
|
52
|
+
export async function isTaskCancelled(
|
|
52
53
|
config: SwarmHooksConfig,
|
|
53
54
|
): Promise<{ cancelled: boolean; reason?: string }> {
|
|
54
55
|
try {
|
|
@@ -68,7 +69,7 @@ async function isTaskCancelled(
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/** Check if agent should stop polling */
|
|
71
|
-
async function checkShouldBlockPolling(config: SwarmHooksConfig): Promise<boolean> {
|
|
72
|
+
export async function checkShouldBlockPolling(config: SwarmHooksConfig): Promise<boolean> {
|
|
72
73
|
try {
|
|
73
74
|
const resp = await fetch(`${config.apiUrl}/me`, {
|
|
74
75
|
method: "GET",
|
|
@@ -83,7 +84,7 @@ async function checkShouldBlockPolling(config: SwarmHooksConfig): Promise<boolea
|
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
/** Fetch task details for goal reminder */
|
|
86
|
-
async function fetchTaskDetails(
|
|
87
|
+
export async function fetchTaskDetails(
|
|
87
88
|
config: SwarmHooksConfig,
|
|
88
89
|
): Promise<{ id: string; task: string; progress?: string } | null> {
|
|
89
90
|
try {
|
|
@@ -99,9 +100,10 @@ async function fetchTaskDetails(
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
/** Sync identity files (SOUL.md, IDENTITY.md, TOOLS.md) to server */
|
|
102
|
-
async function syncIdentityFilesToServer(
|
|
103
|
+
export async function syncIdentityFilesToServer(
|
|
103
104
|
config: SwarmHooksConfig,
|
|
104
105
|
changeSource: "self_edit" | "session_sync" = "session_sync",
|
|
106
|
+
signal?: AbortSignal,
|
|
105
107
|
): Promise<void> {
|
|
106
108
|
const updates: Record<string, string> = {};
|
|
107
109
|
const paths: Record<string, string> = {
|
|
@@ -131,6 +133,7 @@ async function syncIdentityFilesToServer(
|
|
|
131
133
|
method: "PUT",
|
|
132
134
|
headers: apiHeaders(config),
|
|
133
135
|
body: JSON.stringify({ ...updates, changeSource }),
|
|
136
|
+
signal,
|
|
134
137
|
});
|
|
135
138
|
} catch {
|
|
136
139
|
/* silently fail */
|
|
@@ -174,7 +177,10 @@ async function syncSetupScriptToServer(
|
|
|
174
177
|
}
|
|
175
178
|
|
|
176
179
|
/** Auto-index a file written to memory directory */
|
|
177
|
-
async function autoIndexMemoryFile(
|
|
180
|
+
export async function autoIndexMemoryFile(
|
|
181
|
+
config: SwarmHooksConfig,
|
|
182
|
+
editedPath: string,
|
|
183
|
+
): Promise<void> {
|
|
178
184
|
try {
|
|
179
185
|
const fileContent = await Bun.file(editedPath).text();
|
|
180
186
|
const isShared = editedPath.startsWith("/workspace/shared/");
|
|
@@ -198,7 +204,7 @@ async function autoIndexMemoryFile(config: SwarmHooksConfig, editedPath: string)
|
|
|
198
204
|
}
|
|
199
205
|
|
|
200
206
|
/** Fetch concurrent context for lead agents */
|
|
201
|
-
async function fetchConcurrentContext(config: SwarmHooksConfig): Promise<string | null> {
|
|
207
|
+
export async function fetchConcurrentContext(config: SwarmHooksConfig): Promise<string | null> {
|
|
202
208
|
try {
|
|
203
209
|
const resp = await fetch(`${config.apiUrl}/api/concurrent-context`, {
|
|
204
210
|
method: "GET",
|
|
@@ -313,26 +319,38 @@ export async function summarizeSessionForPi(
|
|
|
313
319
|
return;
|
|
314
320
|
}
|
|
315
321
|
|
|
322
|
+
let transcript = "";
|
|
323
|
+
try {
|
|
324
|
+
const fullTranscript = await Bun.file(sessionFile).text();
|
|
325
|
+
transcript = fullTranscript.length > 20000 ? fullTranscript.slice(-20000) : fullTranscript;
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.warn(
|
|
328
|
+
scrubSecrets(`session_summary skipped (pi): cannot read ${sessionFile}: ${String(err)}`),
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
await summarizeTranscript(config, "pi", transcript, deps);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Summarize an already-loaded session transcript and index the result. Shared
|
|
337
|
+
* by pi (transcript file) and opencode (transcript read from its session API).
|
|
338
|
+
*/
|
|
339
|
+
export async function summarizeTranscript(
|
|
340
|
+
config: SwarmHooksConfig,
|
|
341
|
+
harness: "pi" | "opencode",
|
|
342
|
+
transcript: string,
|
|
343
|
+
deps: SummarizeSessionForPiDeps = {},
|
|
344
|
+
): Promise<void> {
|
|
316
345
|
const _runSummarize = deps.runSummarize ?? runSummarize;
|
|
317
346
|
const _fetchRetrievals = deps.fetchRetrievalsForTask ?? fetchRetrievalsForTask;
|
|
318
347
|
const _postRatings = deps.postRatings ?? postRatings;
|
|
319
348
|
const _buildRatings = deps.buildRatingsFromLlm ?? buildRatingsFromLlm;
|
|
320
349
|
|
|
321
350
|
try {
|
|
322
|
-
let transcript = "";
|
|
323
|
-
try {
|
|
324
|
-
const fullTranscript = await Bun.file(sessionFile).text();
|
|
325
|
-
transcript = fullTranscript.length > 20000 ? fullTranscript.slice(-20000) : fullTranscript;
|
|
326
|
-
} catch (err) {
|
|
327
|
-
console.warn(
|
|
328
|
-
scrubSecrets(`session_summary skipped (pi): cannot read ${sessionFile}: ${String(err)}`),
|
|
329
|
-
);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
351
|
if (transcript.length <= 100) {
|
|
334
352
|
console.warn(
|
|
335
|
-
`session_summary skipped (
|
|
353
|
+
`session_summary skipped (${harness}): transcript too short (${transcript.length} chars)`,
|
|
336
354
|
);
|
|
337
355
|
return;
|
|
338
356
|
}
|
|
@@ -341,7 +359,7 @@ export async function summarizeSessionForPi(
|
|
|
341
359
|
const agentId = config.agentId;
|
|
342
360
|
if (!sourceTaskId || !agentId) {
|
|
343
361
|
console.warn(
|
|
344
|
-
`session_summary skipped (
|
|
362
|
+
`session_summary skipped (${harness}): ${!sourceTaskId ? "task id unavailable" : "agent id unavailable"}`,
|
|
345
363
|
);
|
|
346
364
|
return;
|
|
347
365
|
}
|
|
@@ -359,7 +377,7 @@ export async function summarizeSessionForPi(
|
|
|
359
377
|
: [];
|
|
360
378
|
|
|
361
379
|
const result = await _runSummarize({
|
|
362
|
-
harness
|
|
380
|
+
harness,
|
|
363
381
|
transcript,
|
|
364
382
|
retrievals,
|
|
365
383
|
taskContext: {
|
|
@@ -375,14 +393,14 @@ export async function summarizeSessionForPi(
|
|
|
375
393
|
});
|
|
376
394
|
// null = no auth resolved or wrapper exhausted retries (already logged inside)
|
|
377
395
|
if (!result) {
|
|
378
|
-
console.warn(
|
|
396
|
+
console.warn(`session_summary skipped (${harness}): summarizer returned no result`);
|
|
379
397
|
return;
|
|
380
398
|
}
|
|
381
399
|
|
|
382
400
|
const summary = result.summary.trim();
|
|
383
401
|
if (summary.length <= 20 || summary.toLowerCase().includes("no significant learnings")) {
|
|
384
402
|
console.debug(
|
|
385
|
-
`session_summary skipped (
|
|
403
|
+
`session_summary skipped (${harness}): summary failed quality gate (${summary.length} chars)`,
|
|
386
404
|
);
|
|
387
405
|
return;
|
|
388
406
|
}
|
|
@@ -401,7 +419,11 @@ export async function summarizeSessionForPi(
|
|
|
401
419
|
});
|
|
402
420
|
if (!indexResp.ok) {
|
|
403
421
|
const text = await indexResp.text().catch(() => "");
|
|
404
|
-
console.error(
|
|
422
|
+
console.error(
|
|
423
|
+
`session_summary: /api/memory/index POST failed (${harness}):`,
|
|
424
|
+
indexResp.status,
|
|
425
|
+
scrubSecrets(text),
|
|
426
|
+
);
|
|
405
427
|
return;
|
|
406
428
|
}
|
|
407
429
|
|
|
@@ -414,14 +436,21 @@ export async function summarizeSessionForPi(
|
|
|
414
436
|
agentId,
|
|
415
437
|
taskId: sourceTaskId,
|
|
416
438
|
events: ratingEvents,
|
|
417
|
-
}).catch((err) =>
|
|
439
|
+
}).catch((err) =>
|
|
440
|
+
console.error(`session_summary: postRatings failed (${harness}):`, scrubError(err)),
|
|
441
|
+
);
|
|
418
442
|
}
|
|
419
443
|
}
|
|
420
444
|
} catch (err) {
|
|
421
|
-
console.error(
|
|
445
|
+
console.error(`session_summary failed (${harness}):`, scrubError(err));
|
|
422
446
|
}
|
|
423
447
|
}
|
|
424
448
|
|
|
449
|
+
/** An error as scrubbed text: the session-summary diagnostics go to stderr. */
|
|
450
|
+
function scrubError(err: unknown): string {
|
|
451
|
+
return scrubSecrets(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
452
|
+
}
|
|
453
|
+
|
|
425
454
|
/** `customType` of the advisory messages this extension sends to the model. */
|
|
426
455
|
export const SWARM_NUDGE_CUSTOM_TYPE = "swarm-nudge";
|
|
427
456
|
|
|
@@ -629,16 +658,8 @@ export function createSwarmHooksExtension(config: SwarmHooksConfig): ExtensionFa
|
|
|
629
658
|
|
|
630
659
|
const taskDetails = await fetchTaskDetails(config);
|
|
631
660
|
if (taskDetails) {
|
|
632
|
-
const reminder =
|
|
633
|
-
|
|
634
|
-
`Task ID: ${taskDetails.id}`,
|
|
635
|
-
`Task: ${taskDetails.task}`,
|
|
636
|
-
];
|
|
637
|
-
if (taskDetails.progress) {
|
|
638
|
-
reminder.push(`Current Progress: ${taskDetails.progress}`);
|
|
639
|
-
}
|
|
640
|
-
reminder.push("=== Continue working on this task after compaction ===");
|
|
641
|
-
sendNudge(pi, reminder.join("\n"));
|
|
661
|
+
const reminder = await renderGoalReminder(taskDetails);
|
|
662
|
+
if (reminder) sendNudge(pi, reminder);
|
|
642
663
|
}
|
|
643
664
|
});
|
|
644
665
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
2
|
import {
|
|
3
3
|
detachedProcessGroup,
|
|
4
4
|
registerProcessGroup,
|
|
@@ -112,6 +112,7 @@ async function writeBareImportShims(tmpdir: string): Promise<void> {
|
|
|
112
112
|
["stdlib", `${runtimeDir}/stdlib.bundle.js`],
|
|
113
113
|
["swarm-sdk", `${runtimeDir}/swarm-sdk.bundle.js`],
|
|
114
114
|
["zod", `${runtimeDir}/zod.bundle.js`],
|
|
115
|
+
["zod/mini", `${runtimeDir}/zod-mini.bundle.js`],
|
|
115
116
|
];
|
|
116
117
|
for (const [name, bundlePath] of shims) {
|
|
117
118
|
const dir = `${tmpdir}/node_modules/${name}`;
|
|
@@ -119,16 +120,16 @@ async function writeBareImportShims(tmpdir: string): Promise<void> {
|
|
|
119
120
|
await Bun.write(`${dir}/package.json`, JSON.stringify({ type: "module", main: "index.js" }));
|
|
120
121
|
await Bun.write(
|
|
121
122
|
`${dir}/index.js`,
|
|
122
|
-
`export * from ${JSON.stringify(
|
|
123
|
+
`export * from ${JSON.stringify(pathToFileURL(bundlePath).href)};\n`,
|
|
123
124
|
);
|
|
124
125
|
}
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
127
128
|
await writeBareImportShim(tmpdir, "stdlib", new URL("../stdlib/index.ts", import.meta.url));
|
|
128
129
|
await writeBareImportShim(tmpdir, "swarm-sdk", new URL("../swarm-sdk.ts", import.meta.url));
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
for (const name of ["zod", "zod/mini"]) {
|
|
131
|
+
await writeBareImportShim(tmpdir, name, pathToFileURL(Bun.resolveSync(name, import.meta.dir)));
|
|
132
|
+
}
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
function harnessCommand(
|
|
@@ -37,33 +37,38 @@ export type QuickJSBundle =
|
|
|
37
37
|
|
|
38
38
|
type BuildConfigWithFiles = Parameters<typeof Bun.build>[0] & { files: Record<string, string> };
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const zodEntryCache = new Map<string, string | null>();
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Prefer zod's real ESM sources, which tree-shake well. The prebuilt
|
|
44
44
|
* `zod.bundle.js` of the API image barely tree-shakes (~340 KB against ~87 KB
|
|
45
45
|
* for z.object + z.string().email()), so it is only the last fallback.
|
|
46
46
|
*/
|
|
47
|
-
export async function resolveZodEntry(
|
|
48
|
-
|
|
47
|
+
export async function resolveZodEntry(
|
|
48
|
+
moduleName: "zod" | "zod/mini" = "zod",
|
|
49
|
+
): Promise<string | null> {
|
|
49
50
|
const runtimeDir = process.env.SCRIPT_RUNTIME_DIR;
|
|
51
|
+
const cacheKey = `${runtimeDir ?? ""}:${moduleName}`;
|
|
52
|
+
if (zodEntryCache.has(cacheKey)) return zodEntryCache.get(cacheKey)!;
|
|
53
|
+
const esmPath = moduleName === "zod" ? "index.js" : "mini/index.js";
|
|
54
|
+
const bundleName = moduleName === "zod" ? "zod" : "zod-mini";
|
|
50
55
|
const candidates: Array<() => string | undefined> = [
|
|
51
|
-
() => (runtimeDir ? `${runtimeDir}/zod-esm
|
|
52
|
-
() => Bun.resolveSync(
|
|
53
|
-
() => (runtimeDir ? `${runtimeDir}
|
|
56
|
+
() => (runtimeDir ? `${runtimeDir}/zod-esm/${esmPath}` : undefined),
|
|
57
|
+
() => Bun.resolveSync(moduleName, import.meta.dir),
|
|
58
|
+
() => (runtimeDir ? `${runtimeDir}/${bundleName}.bundle.js` : undefined),
|
|
54
59
|
];
|
|
55
60
|
for (const candidate of candidates) {
|
|
56
61
|
try {
|
|
57
62
|
const path = candidate();
|
|
58
63
|
if (path && (await Bun.file(path).exists())) {
|
|
59
|
-
zodEntryCache
|
|
64
|
+
zodEntryCache.set(cacheKey, path);
|
|
60
65
|
return path;
|
|
61
66
|
}
|
|
62
67
|
} catch {
|
|
63
68
|
// Try the next candidate. Bun.resolveSync throws inside the compiled binary.
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
|
-
zodEntryCache
|
|
71
|
+
zodEntryCache.set(cacheKey, null);
|
|
67
72
|
return null;
|
|
68
73
|
}
|
|
69
74
|
|
|
@@ -89,7 +94,6 @@ export async function bundleForQuickJS(source: string): Promise<QuickJSBundle> {
|
|
|
89
94
|
return cached;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
const zodEntry = await resolveZodEntry();
|
|
93
97
|
let result: Awaited<ReturnType<typeof Bun.build>>;
|
|
94
98
|
try {
|
|
95
99
|
result = await Bun.build({
|
|
@@ -117,9 +121,12 @@ export async function bundleForQuickJS(source: string): Promise<QuickJSBundle> {
|
|
|
117
121
|
contents: args.path === "stdlib" ? STDLIB_MODULE : SWARM_SDK_MODULE,
|
|
118
122
|
loader: "js",
|
|
119
123
|
}));
|
|
120
|
-
build.onResolve({ filter: /^zod
|
|
124
|
+
build.onResolve({ filter: /^zod(?:\/mini)?$/ }, async (args) => {
|
|
125
|
+
const zodEntry = await resolveZodEntry(args.path as "zod" | "zod/mini");
|
|
121
126
|
if (!zodEntry) {
|
|
122
|
-
throw new Error(
|
|
127
|
+
throw new Error(
|
|
128
|
+
`${args.path} is not available to the quickjs executor in this deployment`,
|
|
129
|
+
);
|
|
123
130
|
}
|
|
124
131
|
return { path: zodEntry };
|
|
125
132
|
});
|
|
@@ -7,14 +7,32 @@
|
|
|
7
7
|
* SWARM_SCHEMA_RESULT_FILE path where JSON Schema (or "null") is written
|
|
8
8
|
* SWARM_SCHEMA_TMPDIR tmpdir used for shims + the user module
|
|
9
9
|
*/
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
10
11
|
import { toJSONSchema } from "zod";
|
|
11
12
|
|
|
12
13
|
async function createShims(tmpdir: string): Promise<void> {
|
|
13
|
-
const
|
|
14
|
+
const runtimeDir = process.env.SCRIPT_RUNTIME_DIR;
|
|
14
15
|
const shims: [string, URL][] = [
|
|
15
|
-
[
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
[
|
|
17
|
+
"stdlib",
|
|
18
|
+
runtimeDir
|
|
19
|
+
? pathToFileURL(`${runtimeDir}/stdlib.bundle.js`)
|
|
20
|
+
: new URL("./stdlib/index.ts", import.meta.url),
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
"swarm-sdk",
|
|
24
|
+
runtimeDir
|
|
25
|
+
? pathToFileURL(`${runtimeDir}/swarm-sdk.bundle.js`)
|
|
26
|
+
: new URL("./swarm-sdk.ts", import.meta.url),
|
|
27
|
+
],
|
|
28
|
+
...["zod", "zod/mini"].map((name): [string, URL] => [
|
|
29
|
+
name,
|
|
30
|
+
pathToFileURL(
|
|
31
|
+
runtimeDir
|
|
32
|
+
? `${runtimeDir}/${name === "zod" ? "zod" : "zod-mini"}.bundle.js`
|
|
33
|
+
: Bun.resolveSync(name, import.meta.dir),
|
|
34
|
+
),
|
|
35
|
+
]),
|
|
18
36
|
];
|
|
19
37
|
for (const [name, url] of shims) {
|
|
20
38
|
const dir = `${tmpdir}/node_modules/${name}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
|
|
3
|
-
const SCRIPT_ALLOWED_BARE_IMPORTS = ["swarm-sdk", "stdlib", "zod"] as const;
|
|
3
|
+
const SCRIPT_ALLOWED_BARE_IMPORTS = ["swarm-sdk", "stdlib", "zod", "zod/mini"] as const;
|
|
4
4
|
const FORBIDDEN_HINTS = new Set(["node:", "bun:", "fs", "child_process", "crypto", "bun:sqlite"]);
|
|
5
5
|
|
|
6
6
|
export type ImportAllowlistOptions = {
|
package/src/slack/render-v2.ts
CHANGED
|
@@ -1016,7 +1016,7 @@ async function conclusionResultsLines(closure: AgentTask[]): Promise<string[]> {
|
|
|
1016
1016
|
const agentName = await agentDisplayNameFor(member);
|
|
1017
1017
|
const card = await getSlackOutcomeMessage(member.id);
|
|
1018
1018
|
if (card?.permalink) {
|
|
1019
|
-
lines.push(`↳ ${glyph} ${agentName} — ${card.permalink}`);
|
|
1019
|
+
lines.push(`↳ ${glyph} ${agentName} — [result](${card.permalink})`);
|
|
1020
1020
|
continue;
|
|
1021
1021
|
}
|
|
1022
1022
|
const raw = outcomeText(
|
|
@@ -1452,7 +1452,7 @@ async function resolvedDeferralContent(
|
|
|
1452
1452
|
return { text: presentation, blocks: [{ type: "markdown", text: presentation }, ...captions] };
|
|
1453
1453
|
}
|
|
1454
1454
|
const card = await getSlackOutcomeMessage(wake.id);
|
|
1455
|
-
const pointer = card?.permalink ? ` —
|
|
1455
|
+
const pointer = card?.permalink ? ` — <${card.permalink}|next update>` : ".";
|
|
1456
1456
|
return { text: `↪️ Resumed by ${await agentDisplayNameFor(wake)} and deferred again${pointer}` };
|
|
1457
1457
|
}
|
|
1458
1458
|
|
|
@@ -393,7 +393,6 @@ describe("docker-entrypoint.sh: provider binary chain contract", () => {
|
|
|
393
393
|
test.each([
|
|
394
394
|
["claude", "Claude CLI:"],
|
|
395
395
|
["codex", "Codex CLI:"],
|
|
396
|
-
["opencode", "opencode CLI:"],
|
|
397
396
|
["dsh", "dsh CLI:"],
|
|
398
397
|
["claude-managed", "no local CLI required"],
|
|
399
398
|
["devin", "cloud API"],
|
|
@@ -408,6 +407,25 @@ describe("docker-entrypoint.sh: provider binary chain contract", () => {
|
|
|
408
407
|
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
409
408
|
);
|
|
410
409
|
|
|
410
|
+
test(
|
|
411
|
+
"checks the opencode branch for the in-process SDK, not a CLI",
|
|
412
|
+
async () => {
|
|
413
|
+
const sdkPath = join(getStubDir(), "opencode-sdk-index.js");
|
|
414
|
+
writeFileSync(sdkPath, "");
|
|
415
|
+
const found = await runChain({ HARNESS_PROVIDER: "opencode", OPENCODE_SDK_PATH: sdkPath });
|
|
416
|
+
expect(found.exitCode).toBe(0);
|
|
417
|
+
expect(found.stdout).toContain(`OpenCode SDK: ${sdkPath}`);
|
|
418
|
+
|
|
419
|
+
const missing = await runChain({
|
|
420
|
+
HARNESS_PROVIDER: "opencode",
|
|
421
|
+
OPENCODE_SDK_PATH: join(getStubDir(), "no-such-sdk.js"),
|
|
422
|
+
});
|
|
423
|
+
expect(missing.exitCode).not.toBe(0);
|
|
424
|
+
expect(missing.stdout).toContain("FATAL: @opencode/sdk not found");
|
|
425
|
+
},
|
|
426
|
+
CHILD_PROCESS_TEST_BUDGET_MS,
|
|
427
|
+
);
|
|
428
|
+
|
|
411
429
|
test(
|
|
412
430
|
"leaves the pi provider unchecked",
|
|
413
431
|
async () => {
|