@desplega.ai/agent-swarm 1.157.0 → 1.158.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-adapter-qhqrk4q0.js → acp-adapter-xaq4n6pc.js} +1 -1
- package/dist/{actions-9rrg0zxm.js → actions-p9hz5yat.js} +15 -15
- package/dist/{app-enysy1zd.js → app-g26wdcz6.js} +4 -4
- package/dist/{artifact-td4q4dch.js → artifact-3v5t9jtg.js} +1 -1
- package/dist/{assistant-zegamy48.js → assistant-wydfqkb3.js} +16 -16
- package/dist/{boot-reembed-r6shx5qx.js → boot-reembed-g5ckg9yb.js} +6 -6
- package/dist/{boot-reembed-zfxb937k.js → boot-reembed-se8zmdcp.js} +5 -5
- package/dist/{boot-scrub-logs-k5y0se6r.js → boot-scrub-logs-wfxqan61.js} +3 -3
- package/dist/{seed-pricing-vqxa06qf.js → boot-seeds-s3q01xj3.js} +10 -10
- package/dist/{claude-adapter-5yc7hxwv.js → claude-adapter-tv2x4f36.js} +7 -7
- package/dist/{claude-managed-adapter-ea2aga6w.js → claude-managed-adapter-41b2s7fd.js} +2 -2
- package/dist/{claude-sdk-session-j4ywn3xf.js → claude-sdk-session-t442qz6f.js} +8 -8
- package/dist/{cli-e781c6f9.js → cli-0xjkj1j7.js} +1 -0
- package/dist/{cli-58rgvkt4.js → cli-1hkrch3k.js} +2 -2
- package/dist/{cli-yscm6cv8.js → cli-20cj37vt.js} +1866 -9
- package/dist/{cli-h0wdadsx.js → cli-4198qc9v.js} +7 -3
- package/dist/{cli-awwphhcs.js → cli-47m6dkd3.js} +362 -126
- package/dist/{cli-wb2zvbfg.js → cli-53sz8gkz.js} +1 -1
- package/dist/{cli-005pze6s.js → cli-5d10vd2m.js} +1 -1
- package/dist/{cli-v53rbqp3.js → cli-65hjmev5.js} +57 -17
- package/dist/{cli-scwq51gn.js → cli-68w09sjw.js} +7 -1
- package/dist/{cli-s98skd5r.js → cli-7gtvvpbh.js} +4 -4
- package/dist/{cli-y3nxwx0t.js → cli-7s3syk22.js} +18 -11
- package/dist/{cli-74h6f2bp.js → cli-8qmme6vd.js} +2 -2
- package/dist/{cli-wsed24mk.js → cli-987hg0aa.js} +2 -2
- package/dist/{cli-9n76jszt.js → cli-9httdpyq.js} +1 -1
- package/dist/{cli-f3va018d.js → cli-aky8tctm.js} +26 -93
- package/dist/{cli-sgyqyyxe.js → cli-c07r9a2m.js} +2 -1
- package/dist/{cli-c41wjx0r.js → cli-fgqxwrpv.js} +291 -12
- package/dist/{cli-01vfmq8c.js → cli-ftfvggzx.js} +607 -164
- package/dist/{cli-ytxs7mn1.js → cli-g8djp6qe.js} +17 -0
- package/dist/{cli-avkd6mqv.js → cli-h967nb6r.js} +551 -0
- package/dist/{cli-r9ee9c4w.js → cli-j55n64b6.js} +23 -22
- package/dist/{cli-9p33cz9h.js → cli-j57qkrpq.js} +86 -28
- package/dist/{cli-tjmghmm5.js → cli-q67y4wzm.js} +6 -3
- package/dist/{cli-n6zq3kbd.js → cli-rj19hc34.js} +1 -1
- package/dist/{cli-jytrw2na.js → cli-rjehvrts.js} +4 -4
- package/dist/{cli-tpv97m71.js → cli-rqz8qbw7.js} +245 -75
- package/dist/{cli-p37sg0a9.js → cli-s8eet254.js} +1 -1
- package/dist/cli-t1ze64h3.js +811 -0
- package/dist/{cli-6jjzxv3b.js → cli-ws6rqycm.js} +3 -2
- package/dist/{cli-xf5b7y1f.js → cli-wscdvcqz.js} +13 -5
- package/dist/{cli-5j7bm4tb.js → cli-x24pshtq.js} +5 -5
- package/dist/{cli-nbp6m6gp.js → cli-xberxa1r.js} +51 -26
- package/dist/{cli-vx9p3qdt.js → cli-xhaeg2nc.js} +1 -1
- package/dist/cli.js +29 -15
- package/dist/{codex-adapter-gns3s6c3.js → codex-adapter-cnm8wk11.js} +5 -5
- package/dist/{codex-session-runner-gqnxys5w.js → codex-session-runner-gfg547zj.js} +5 -5
- package/dist/{commands-hxy2y2sp.js → commands-e3bn0p75.js} +3 -3
- package/dist/{db-w8ghhsgf.js → db-03v9kzr2.js} +17 -3
- package/dist/{dispatcher-cp1rknjz.js → dispatcher-nrmg8yht.js} +14 -14
- package/dist/{dsh-adapter-1dwg97zv.js → dsh-adapter-k03nfqa6.js} +2 -2
- package/dist/{handlers-egfhr351.js → handlers-aqcedbyb.js} +17 -17
- package/dist/{hook-x4kftwzt.js → hook-7vkskkre.js} +19 -11
- package/dist/{hook-fbxjqfjz.js → hook-ybvmrmhq.js} +4 -4
- package/dist/{http-c7jfx990.js → http-2eqa8t24.js} +432 -195
- package/dist/{index-4k9hct21.js → index-1yks3qcr.js} +14 -14
- package/dist/{index-jtvpgb8f.js → index-a3vn96n8.js} +30 -27
- package/dist/{index-dc2ek6bw.js → index-c6zsv1bd.js} +5 -5
- package/dist/{index-yy07ef2s.js → index-chgdkq36.js} +14 -14
- package/dist/{index-r6bty6tb.js → index-x71bvq0p.js} +15 -15
- package/dist/{keepalive-f7ekefyc.js → keepalive-vtkf5n99.js} +5 -5
- package/dist/{lead-h053syts.js → lead-h3sp357x.js} +26 -26
- package/dist/{maintenance-a3w6zs54.js → maintenance-jzeqtk3q.js} +6 -6
- package/dist/{oauth-refresh-sweep-vra1dhkd.js → oauth-refresh-sweep-qp3dzmdp.js} +4 -4
- package/dist/{onboard-mfgzeqxs.js → onboard-tmsp0qbz.js} +278 -81
- package/dist/{opencode-adapter-z64xjzec.js → opencode-adapter-x6x2da1z.js} +3 -3
- package/dist/{otel-impl-2ez6xk57.js → otel-impl-9mz73q65.js} +416 -301
- package/dist/{pi-mono-adapter-2b7mw16r.js → pi-mono-adapter-m6dvv0dz.js} +2 -2
- package/dist/{pricing-refresh-6sw65tck.js → pricing-refresh-v1cbr9te.js} +5 -5
- package/dist/{rbac-roles-nm6924sy.js → rbac-roles-c9wqz83x.js} +4 -4
- package/dist/{render-v2-f026j6c4.js → render-v2-gryrh9h8.js} +4 -4
- package/dist/{setup-w606fr1q.js → setup-q9720bt6.js} +2 -2
- package/dist/{virtual-modules-8qheft18.js → virtual-modules-3htpgn8x.js} +1 -1
- package/dist/{work-objects-ezmggdne.js → work-objects-b76x7deb.js} +17 -17
- package/dist/{worker-1mbyv29k.js → worker-6vrxbv41.js} +26 -26
- package/openapi.json +1626 -629
- package/package.json +2 -1
- package/src/apps/catalog.generated.json +229 -0
- package/src/apps/page-validator.ts +71 -16
- package/src/apps/row-store.ts +66 -11
- package/src/apps/sync.ts +59 -17
- package/src/artifact-sdk/browser-sdk.ts +436 -2
- package/src/be/approval-sweeps.ts +109 -0
- package/src/be/boot-seeds.ts +40 -0
- package/src/be/chunking.ts +1 -1
- package/src/be/db/agents.ts +20 -0
- package/src/be/db/approvals.ts +78 -0
- package/src/be/db/tasks/read.ts +197 -39
- package/src/be/db/tasks/write.ts +41 -9
- package/src/be/db-client.ts +107 -48
- package/src/be/db.ts +219 -76
- package/src/be/events.ts +33 -1
- package/src/be/memory/graph-expansion.ts +10 -3
- package/src/be/memory/providers/sqlite-store.ts +55 -13
- package/src/be/memory/raters/retrieval.ts +5 -2
- package/src/be/memory/reranker.ts +26 -2
- package/src/be/memory/types.ts +2 -0
- package/src/be/migrations/166_approval_request_auto_cancellation.sql +9 -0
- package/src/be/migrations/171_memory_retrieval_relevance.sql +8 -0
- package/src/be/migrations/175_workflow_runs_list_index.sql +11 -0
- package/src/be/migrations/177_events_event_agent_created_index.sql +11 -0
- package/src/be/models-catalog.ts +2 -0
- package/src/be/modelsdev-cache.json +1857 -0
- package/src/be/modelsdev-cache.ts +14 -1
- package/src/be/pricing-normalize.ts +2 -0
- package/src/be/scripts/typecheck.ts +1 -0
- package/src/be/seed-pricing.ts +3 -1
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/steering.ts +53 -3
- package/src/be/swarm-config-guard.ts +3 -0
- package/src/be/task-citations.ts +29 -0
- package/src/cli.tsx +14 -1
- package/src/commands/onboard/compose-generator.ts +8 -3
- package/src/commands/onboard/container-engine.ts +220 -0
- package/src/commands/onboard/manifest.ts +1 -0
- package/src/commands/onboard/steps/health-check.tsx +4 -1
- package/src/commands/onboard/steps/prereq-check.tsx +50 -54
- package/src/commands/onboard/steps/start.tsx +31 -16
- package/src/commands/onboard/types.ts +7 -2
- package/src/commands/onboard.tsx +39 -4
- package/src/commands/profile-sync.ts +79 -26
- package/src/commands/runner.ts +8 -1
- package/src/extensions/catalog.generated.json +17 -0
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/heartbeat/heartbeat.ts +263 -103
- package/src/hooks/hook.ts +21 -12
- package/src/http/agents.ts +35 -0
- package/src/http/approval-requests.ts +136 -64
- package/src/http/apps.ts +14 -3
- package/src/http/config.ts +15 -1
- package/src/http/events.ts +62 -2
- package/src/http/index.ts +6 -4
- package/src/http/memory.ts +21 -7
- package/src/http/schedules.ts +1 -1
- package/src/http/session-data.ts +9 -4
- package/src/http/sessions.ts +25 -18
- package/src/http/stats.ts +19 -7
- package/src/http/status.ts +11 -1
- package/src/http/tasks.ts +40 -8
- package/src/http/webhooks.ts +67 -46
- package/src/http/workflows.ts +15 -20
- package/src/linear/outbound.ts +37 -15
- package/src/otel-impl.ts +151 -1
- package/src/otel.ts +14 -3
- package/src/prompts/memories.ts +19 -2
- package/src/providers/claude-managed-models.ts +8 -0
- package/src/providers/modelsdev-reasoning.json +551 -0
- package/src/rbac/legacy-policy.ts +16 -0
- package/src/rbac/permissions.ts +4 -0
- package/src/scheduler/schedule-task.ts +3 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +9 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +12 -7
- package/src/slack/app.ts +35 -4
- package/src/slack/connection-state.ts +17 -0
- package/src/tasks/context-key.ts +1 -0
- package/src/tasks/worker-follow-up.ts +6 -2
- package/src/tests/agent-task-activity.test.ts +74 -0
- package/src/tests/agents-list-model-display.test.ts +6 -1
- package/src/tests/approval-auto-cancellation-migration.test.ts +104 -0
- package/src/tests/approval-requests.test.ts +395 -0
- package/src/tests/approval-sweeps-config.test.ts +47 -0
- package/src/tests/approval-sweeps.test.ts +508 -0
- package/src/tests/apps-kibo-blocks.test.ts +77 -0
- package/src/tests/apps-spike.test.ts +56 -1
- package/src/tests/apps-sync-engine.test.ts +20 -9
- package/src/tests/boot-seeds.test.ts +170 -0
- package/src/tests/cancel-approval-request-tool.test.ts +115 -0
- package/src/tests/claude-managed-adapter.test.ts +6 -0
- package/src/tests/config-key-filter.test.ts +80 -0
- package/src/tests/context-window.test.ts +1 -0
- package/src/tests/db-client-busy-retry.test.ts +96 -0
- package/src/tests/db-client.test.ts +60 -1
- package/src/tests/db-task-read-contract.test.ts +10 -0
- package/src/tests/db-task-write-contract.test.ts +9 -0
- package/src/tests/docs-index-footprint.test.js +3 -3
- package/src/tests/events-http.test.ts +103 -0
- package/src/tests/extensions-catalog.test.ts +22 -17
- package/src/tests/extensions-pre-task.test.ts +107 -0
- package/src/tests/fixtures/kibo-blocks-definition.json.txt +105 -0
- package/src/tests/follow-up-config-inheritance.test.ts +260 -0
- package/src/tests/heartbeat-orphan-resume.test.ts +84 -0
- package/src/tests/heartbeat-reboot-live.test.ts +95 -0
- package/src/tests/heartbeat-stall-cas.test.ts +80 -0
- package/src/tests/heartbeat.test.ts +99 -3
- package/src/tests/hook-mcp-config.test.ts +29 -0
- package/src/tests/http-api-integration.test.ts +16 -2
- package/src/tests/kv-storage.test.ts +62 -0
- package/src/tests/linear-outbound-sync.test.ts +120 -5
- package/src/tests/list-endpoint-slimming.test.ts +91 -0
- package/src/tests/memory-graph-organic-route.test.ts +155 -0
- package/src/tests/memory-http-recall-gating.test.ts +14 -4
- package/src/tests/memory-hybrid.test.ts +161 -1
- package/src/tests/memory-prompt-recall-crowdout.test.ts +297 -0
- package/src/tests/memory.test.ts +22 -0
- package/src/tests/onboard-container-engine.test.ts +240 -0
- package/src/tests/otel-event-loop-and-logs.test.ts +99 -0
- package/src/tests/pricing-routes.test.ts +14 -0
- package/src/tests/profile-sync-warning.test.ts +8 -4
- package/src/tests/promote-draft-task-route.test.ts +19 -0
- package/src/tests/provider-registration.test.ts +296 -42
- package/src/tests/rbac-engine.test.ts +30 -0
- package/src/tests/reasoning-effort.test.ts +8 -0
- package/src/tests/script-connections.test.ts +7 -0
- package/src/tests/sdk-allowlist.test.ts +39 -0
- package/src/tests/session-costs-model-key-normalize.test.ts +1 -0
- package/src/tests/sessions-detail-batch.test.ts +250 -0
- package/src/tests/sessions.test.ts +118 -0
- package/src/tests/slack-config.test.ts +22 -0
- package/src/tests/sonnet-5-5-registry.test.ts +44 -0
- package/src/tests/status.test.ts +36 -1
- package/src/tests/swarm-diff.test.ts +316 -10
- package/src/tests/task-completion-idempotency.test.ts +2 -1
- package/src/tests/tasks-requested-by-filter.test.ts +3 -1
- package/src/tests/tool-annotations.test.ts +1 -1
- package/src/tests/ui-list-slim-projections.test.ts +158 -0
- package/src/tests/webhooks-task-blocked.test.ts +127 -0
- package/src/tests/workflow-approval-recovery.test.ts +203 -0
- package/src/tests/workflow-hitl-routing.test.ts +22 -1
- package/src/tests/workflow-hitl-slack-card.test.ts +52 -0
- package/src/tests/workflow-http-v2.test.ts +99 -8
- package/src/tests/workflow-retry-validation.test.ts +56 -0
- package/src/tests/workflow-tla-races.test.ts +389 -0
- package/src/tests/workflow-validation-port-routing.test.ts +113 -1
- package/src/tools/cancel-approval-request.ts +57 -0
- package/src/tools/memory-search.ts +1 -0
- package/src/tools/request-human-input.ts +4 -2
- package/src/tools/send-task.ts +4 -0
- package/src/tools/templates.ts +19 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/workflows/list-workflow-runs.ts +1 -0
- package/src/types.ts +38 -0
- package/src/utils/context-window.ts +1 -0
- package/src/workflows/approval-cancel.ts +152 -0
- package/src/workflows/approval-notifications.ts +84 -0
- package/src/workflows/checkpoint.ts +10 -5
- package/src/workflows/definition.ts +21 -0
- package/src/workflows/engine.ts +152 -21
- package/src/workflows/executors/human-in-the-loop.ts +1 -1
- package/src/workflows/recovery.ts +100 -45
- package/src/workflows/resume.ts +49 -18
- package/src/workflows/retry-poller.ts +71 -21
- package/templates/extensions/README.md +1 -0
- package/templates/extensions/github-sender-allowlist/README.md +18 -0
- package/templates/extensions/github-sender-allowlist/hooks.ts +55 -0
- package/templates/extensions/github-sender-allowlist/manifest.json +8 -0
- package/templates/skills/apps/content.md +20 -1
- package/templates/skills/code-quality/content.md +26 -1
- package/templates/skills/pages/content.md +16 -4
- package/templates/skills/pages/files/examples/multiplayer-board.html +3 -12
- package/dist/cli-2daksqps.js +0 -378
- package/dist/rbac-roles-3gxmnb20.js +0 -37
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { Select, Spinner } from "@inkjs/ui";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
CONTAINER_ENGINE_LABELS,
|
|
6
|
+
ENGINE_INSTALL_HINTS,
|
|
7
|
+
type EngineCheck,
|
|
8
|
+
type EngineResolution,
|
|
9
|
+
resolveContainerEngine,
|
|
10
|
+
} from "../container-engine.ts";
|
|
4
11
|
import type { StepProps } from "../types.ts";
|
|
5
12
|
|
|
6
13
|
type CheckStatus = "checking" | "passed" | "failed";
|
|
7
14
|
|
|
8
15
|
interface CheckResult {
|
|
9
|
-
|
|
10
|
-
compose: { ok: boolean; version?: string; error?: string };
|
|
16
|
+
engine: EngineResolution;
|
|
11
17
|
ports: { ok: boolean; conflicts?: string[] };
|
|
12
18
|
}
|
|
13
19
|
|
|
@@ -22,35 +28,10 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
|
|
|
22
28
|
|
|
23
29
|
const run = async () => {
|
|
24
30
|
const res: CheckResult = {
|
|
25
|
-
|
|
26
|
-
compose: { ok: false },
|
|
31
|
+
engine: await resolveContainerEngine(state.containerEngine),
|
|
27
32
|
ports: { ok: true },
|
|
28
33
|
};
|
|
29
34
|
|
|
30
|
-
// Check Docker
|
|
31
|
-
try {
|
|
32
|
-
const out = await Bun.$`docker --version`.quiet();
|
|
33
|
-
if (out.exitCode === 0) {
|
|
34
|
-
res.docker = { ok: true, version: out.text().trim() };
|
|
35
|
-
} else {
|
|
36
|
-
res.docker = { ok: false, error: "Docker exited with non-zero status" };
|
|
37
|
-
}
|
|
38
|
-
} catch {
|
|
39
|
-
res.docker = { ok: false, error: "Docker not found" };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check Docker Compose
|
|
43
|
-
try {
|
|
44
|
-
const out = await Bun.$`docker compose version`.quiet();
|
|
45
|
-
if (out.exitCode === 0) {
|
|
46
|
-
res.compose = { ok: true, version: out.text().trim() };
|
|
47
|
-
} else {
|
|
48
|
-
res.compose = { ok: false, error: "Docker Compose not found" };
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
res.compose = { ok: false, error: "Docker Compose v2 not found" };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
35
|
// Check ports
|
|
55
36
|
const portsToCheck = [3013];
|
|
56
37
|
const agentCount = state.services.reduce((sum, s) => sum + s.count, 0);
|
|
@@ -75,7 +56,8 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
|
|
|
75
56
|
|
|
76
57
|
setResult(res);
|
|
77
58
|
|
|
78
|
-
if (res.
|
|
59
|
+
if (res.engine.ok && res.ports.ok) {
|
|
60
|
+
addLog(`Container engine: ${CONTAINER_ENGINE_LABELS[res.engine.engine]}`);
|
|
79
61
|
addLog("All prerequisites met");
|
|
80
62
|
setStatus("passed");
|
|
81
63
|
} else {
|
|
@@ -84,15 +66,16 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
|
|
|
84
66
|
};
|
|
85
67
|
|
|
86
68
|
run().catch((err) => goToError(err.message));
|
|
87
|
-
}, [state.services, addLog, goToError]);
|
|
69
|
+
}, [state.services, state.containerEngine, addLog, goToError]);
|
|
88
70
|
|
|
89
71
|
// Auto-advance on pass
|
|
90
72
|
useEffect(() => {
|
|
91
|
-
if (status === "passed") {
|
|
92
|
-
const
|
|
73
|
+
if (status === "passed" && result?.engine.ok) {
|
|
74
|
+
const engine = result.engine.engine;
|
|
75
|
+
const timer = setTimeout(() => goToNext({ containerEngine: engine }), 500);
|
|
93
76
|
return () => clearTimeout(timer);
|
|
94
77
|
}
|
|
95
|
-
}, [status, goToNext]);
|
|
78
|
+
}, [status, result, goToNext]);
|
|
96
79
|
|
|
97
80
|
if (status === "checking") {
|
|
98
81
|
return (
|
|
@@ -102,46 +85,41 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
|
|
|
102
85
|
);
|
|
103
86
|
}
|
|
104
87
|
|
|
105
|
-
if (status === "passed" && result) {
|
|
88
|
+
if (status === "passed" && result?.engine.ok) {
|
|
106
89
|
return (
|
|
107
90
|
<Box flexDirection="column" padding={1}>
|
|
108
91
|
<Text color="green">
|
|
109
|
-
{"✓"} {result.
|
|
92
|
+
{"✓"} {result.engine.probe.binary.version}
|
|
110
93
|
</Text>
|
|
111
94
|
<Text color="green">
|
|
112
|
-
{"✓"} {result.compose.version}
|
|
95
|
+
{"✓"} {result.engine.probe.compose.version}
|
|
113
96
|
</Text>
|
|
114
97
|
<Text color="green">{"✓"} All ports available</Text>
|
|
115
98
|
</Box>
|
|
116
99
|
);
|
|
117
100
|
}
|
|
118
101
|
|
|
102
|
+
const probe = result?.engine.probe ?? null;
|
|
103
|
+
const engineLabel = probe ? CONTAINER_ENGINE_LABELS[probe.engine] : "Container engine";
|
|
104
|
+
|
|
119
105
|
// Failed
|
|
120
106
|
return (
|
|
121
107
|
<Box flexDirection="column" padding={1}>
|
|
122
|
-
{result
|
|
123
|
-
<Text color="green">
|
|
124
|
-
{"✓"} {result.docker.version}
|
|
125
|
-
</Text>
|
|
126
|
-
) : (
|
|
108
|
+
{result && !result.engine.ok && !result.engine.engine ? (
|
|
127
109
|
<Box flexDirection="column">
|
|
128
110
|
<Text color="red">
|
|
129
|
-
{"✗"}
|
|
130
|
-
</Text>
|
|
131
|
-
<Text dimColor>
|
|
132
|
-
{" "}
|
|
133
|
-
Install: brew install --cask docker (macOS) or https://docs.docker.com/get-docker/
|
|
111
|
+
{"✗"} {result.engine.error}
|
|
134
112
|
</Text>
|
|
113
|
+
<Text dimColor> {ENGINE_INSTALL_HINTS.docker}</Text>
|
|
114
|
+
<Text dimColor> or {ENGINE_INSTALL_HINTS.podman}</Text>
|
|
135
115
|
</Box>
|
|
136
|
-
)}
|
|
137
|
-
{result?.compose.ok ? (
|
|
138
|
-
<Text color="green">
|
|
139
|
-
{"✓"} {result.compose.version}
|
|
140
|
-
</Text>
|
|
141
116
|
) : (
|
|
142
|
-
|
|
143
|
-
{
|
|
144
|
-
|
|
117
|
+
<>
|
|
118
|
+
<CheckLine label={engineLabel} check={probe?.binary} />
|
|
119
|
+
{probe?.binary.ok ? (
|
|
120
|
+
<CheckLine label={`${engineLabel} Compose`} check={probe.compose} />
|
|
121
|
+
) : null}
|
|
122
|
+
</>
|
|
145
123
|
)}
|
|
146
124
|
{result?.ports.ok ? (
|
|
147
125
|
<Text color="green">{"✓"} All ports available</Text>
|
|
@@ -176,3 +154,21 @@ export function PrereqCheckStep({ state, addLog, goToNext, goToStep, goToError }
|
|
|
176
154
|
</Box>
|
|
177
155
|
);
|
|
178
156
|
}
|
|
157
|
+
|
|
158
|
+
function CheckLine({ label, check }: { label: string; check: EngineCheck | undefined }) {
|
|
159
|
+
if (check?.ok) {
|
|
160
|
+
return (
|
|
161
|
+
<Text color="green">
|
|
162
|
+
{"✓"} {check.version}
|
|
163
|
+
</Text>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return (
|
|
167
|
+
<Box flexDirection="column">
|
|
168
|
+
<Text color="red">
|
|
169
|
+
{"✗"} {label}: {check?.error}
|
|
170
|
+
</Text>
|
|
171
|
+
{check?.hint ? <Text dimColor> {check.hint}</Text> : null}
|
|
172
|
+
</Box>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { Select, Spinner } from "@inkjs/ui";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
CONTAINER_ENGINE_LABELS,
|
|
6
|
+
composeArgv,
|
|
7
|
+
composeCommandText,
|
|
8
|
+
defaultCommandRunner,
|
|
9
|
+
engineForCommands,
|
|
10
|
+
} from "../container-engine.ts";
|
|
4
11
|
import type { StepProps } from "../types.ts";
|
|
5
12
|
|
|
6
13
|
type StartStatus = "starting" | "success" | "failed";
|
|
@@ -9,24 +16,27 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
|
|
|
9
16
|
const [status, setStatus] = useState<StartStatus>("starting");
|
|
10
17
|
const [errorMsg, setErrorMsg] = useState("");
|
|
11
18
|
const executed = useRef(false);
|
|
19
|
+
const engine = engineForCommands(state.containerEngine);
|
|
20
|
+
const engineLabel = CONTAINER_ENGINE_LABELS[engine];
|
|
12
21
|
|
|
13
22
|
useEffect(() => {
|
|
14
23
|
if (executed.current) return;
|
|
15
24
|
executed.current = true;
|
|
16
25
|
|
|
17
26
|
const run = async () => {
|
|
18
|
-
addLog(
|
|
27
|
+
addLog(`Running ${composeCommandText(engine, "--env-file .env up -d")}...`);
|
|
19
28
|
try {
|
|
20
|
-
const result = await
|
|
21
|
-
|
|
22
|
-
.
|
|
29
|
+
const result = await defaultCommandRunner(
|
|
30
|
+
composeArgv(engine, ["--env-file", ".env", "up", "-d"]),
|
|
31
|
+
{ cwd: state.outputDir },
|
|
32
|
+
);
|
|
23
33
|
|
|
24
34
|
if (result.exitCode === 0) {
|
|
25
|
-
addLog(
|
|
35
|
+
addLog(`${engineLabel} stack started successfully`);
|
|
26
36
|
setStatus("success");
|
|
27
37
|
} else {
|
|
28
|
-
const stderr = result.stderr.
|
|
29
|
-
setErrorMsg(stderr || "
|
|
38
|
+
const stderr = result.stderr.trim();
|
|
39
|
+
setErrorMsg(stderr || `${composeCommandText(engine, "up")} failed`);
|
|
30
40
|
setStatus("failed");
|
|
31
41
|
}
|
|
32
42
|
} catch (err) {
|
|
@@ -36,7 +46,7 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
|
|
|
36
46
|
};
|
|
37
47
|
|
|
38
48
|
run().catch((err) => addLog(`Start failed: ${err}`));
|
|
39
|
-
}, [state.outputDir, addLog]);
|
|
49
|
+
}, [state.outputDir, engine, engineLabel, addLog]);
|
|
40
50
|
|
|
41
51
|
useEffect(() => {
|
|
42
52
|
if (status === "success") {
|
|
@@ -48,7 +58,7 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
|
|
|
48
58
|
if (status === "starting") {
|
|
49
59
|
return (
|
|
50
60
|
<Box padding={1}>
|
|
51
|
-
<Spinner label=
|
|
61
|
+
<Spinner label={`Starting ${engineLabel} stack...`} />
|
|
52
62
|
</Box>
|
|
53
63
|
);
|
|
54
64
|
}
|
|
@@ -56,20 +66,24 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
|
|
|
56
66
|
if (status === "success") {
|
|
57
67
|
return (
|
|
58
68
|
<Box padding={1}>
|
|
59
|
-
<Text color="green">
|
|
69
|
+
<Text color="green">
|
|
70
|
+
{"✓"} {engineLabel} stack started
|
|
71
|
+
</Text>
|
|
60
72
|
</Box>
|
|
61
73
|
);
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
return (
|
|
65
77
|
<Box flexDirection="column" padding={1}>
|
|
66
|
-
<Text color="red">
|
|
78
|
+
<Text color="red">
|
|
79
|
+
{"✗"} Failed to start {engineLabel} stack
|
|
80
|
+
</Text>
|
|
67
81
|
<Text dimColor>{errorMsg}</Text>
|
|
68
82
|
<Box marginTop={1}>
|
|
69
83
|
<Select
|
|
70
84
|
options={[
|
|
71
85
|
{ label: "Retry", value: "retry" },
|
|
72
|
-
{ label:
|
|
86
|
+
{ label: `View logs (${composeCommandText(engine, "logs")})`, value: "logs" },
|
|
73
87
|
{ label: "Skip", value: "skip" },
|
|
74
88
|
]}
|
|
75
89
|
onChange={async (value) => {
|
|
@@ -79,10 +93,11 @@ export function StartStep({ state, addLog, goToNext }: StepProps) {
|
|
|
79
93
|
setErrorMsg("");
|
|
80
94
|
} else if (value === "logs") {
|
|
81
95
|
try {
|
|
82
|
-
const logs = await
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
96
|
+
const logs = await defaultCommandRunner(
|
|
97
|
+
composeArgv(engine, ["logs", "--tail", "30"]),
|
|
98
|
+
{ cwd: state.outputDir },
|
|
99
|
+
);
|
|
100
|
+
addLog(logs.stdout || logs.stderr);
|
|
86
101
|
} catch {
|
|
87
102
|
addLog("Failed to fetch logs");
|
|
88
103
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SlackMode } from "../../slack/config";
|
|
2
|
+
import type { ContainerEnginePreference } from "./container-engine.ts";
|
|
2
3
|
|
|
3
4
|
export type OnboardStep =
|
|
4
5
|
| "welcome"
|
|
@@ -90,6 +91,8 @@ export interface OnboardState {
|
|
|
90
91
|
apiPort: number;
|
|
91
92
|
maxConcurrentTasks: number | null;
|
|
92
93
|
pullPolicy: PullPolicy;
|
|
94
|
+
/** `auto` until the prereq check resolves it to the engine that passed. */
|
|
95
|
+
containerEngine: ContainerEnginePreference;
|
|
93
96
|
outputDir: string;
|
|
94
97
|
nonInteractive: boolean;
|
|
95
98
|
error: string | null;
|
|
@@ -110,6 +113,7 @@ export interface OnboardProps {
|
|
|
110
113
|
preset?: string;
|
|
111
114
|
maxConcurrentTasks?: string;
|
|
112
115
|
pullPolicy?: string;
|
|
116
|
+
containerEngine?: string;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
export type PullPolicy = "always" | "missing" | "never";
|
|
@@ -137,7 +141,7 @@ export const STEP_LABELS: { step: OnboardStep; label: string }[] = [
|
|
|
137
141
|
{ step: "integration_menu", label: "Integrations" },
|
|
138
142
|
{ step: "review", label: "Review" },
|
|
139
143
|
{ step: "generate", label: "Generate" },
|
|
140
|
-
{ step: "prereq_check", label: "
|
|
144
|
+
{ step: "prereq_check", label: "Engine" },
|
|
141
145
|
{ step: "start", label: "Start" },
|
|
142
146
|
{ step: "health_check", label: "Health" },
|
|
143
147
|
{ step: "post_connect", label: "Connect" },
|
|
@@ -205,6 +209,7 @@ export const INITIAL_STATE: OnboardState = {
|
|
|
205
209
|
apiPort: 0,
|
|
206
210
|
maxConcurrentTasks: null,
|
|
207
211
|
pullPolicy: "always",
|
|
212
|
+
containerEngine: "auto",
|
|
208
213
|
outputDir: process.cwd(),
|
|
209
214
|
nonInteractive: false,
|
|
210
215
|
error: null,
|
|
@@ -293,7 +298,7 @@ export function nextStep(current: OnboardStep, state: OnboardState): OnboardStep
|
|
|
293
298
|
return "generate";
|
|
294
299
|
|
|
295
300
|
case "prereq_check":
|
|
296
|
-
// If
|
|
301
|
+
// If the container engine is missing and user chose "files only", skip to done
|
|
297
302
|
// Default: proceed to start
|
|
298
303
|
return "start";
|
|
299
304
|
|
package/src/commands/onboard.tsx
CHANGED
|
@@ -5,6 +5,13 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
5
5
|
import pkg from "../../package.json";
|
|
6
6
|
import { getSlackConfiguration, isSlackConfigured } from "../slack/config.ts";
|
|
7
7
|
import { getApiKey } from "../utils/api-key.ts";
|
|
8
|
+
import {
|
|
9
|
+
CONTAINER_ENGINE_LABELS,
|
|
10
|
+
composeCommandText,
|
|
11
|
+
engineForCommands,
|
|
12
|
+
isContainerEnginePreference,
|
|
13
|
+
resolveContainerEnginePreference,
|
|
14
|
+
} from "./onboard/container-engine.ts";
|
|
8
15
|
import { buildOnboardDashboardUrl } from "./onboard/dashboard-url.ts";
|
|
9
16
|
import {
|
|
10
17
|
parseMaxConcurrentTasks,
|
|
@@ -53,6 +60,7 @@ export function Onboard({
|
|
|
53
60
|
preset,
|
|
54
61
|
maxConcurrentTasks,
|
|
55
62
|
pullPolicy,
|
|
63
|
+
containerEngine,
|
|
56
64
|
}: OnboardProps) {
|
|
57
65
|
const { exit } = useApp();
|
|
58
66
|
const [state, setState] = useState<OnboardState>(() => {
|
|
@@ -75,6 +83,13 @@ export function Onboard({
|
|
|
75
83
|
}
|
|
76
84
|
initial.pullPolicy = pullPolicy;
|
|
77
85
|
}
|
|
86
|
+
const engine = resolveContainerEnginePreference(containerEngine, process.env);
|
|
87
|
+
if (!engine.ok) {
|
|
88
|
+
initial.step = "error";
|
|
89
|
+
initial.error = engine.error;
|
|
90
|
+
return initial;
|
|
91
|
+
}
|
|
92
|
+
initial.containerEngine = engine.value;
|
|
78
93
|
return initial;
|
|
79
94
|
});
|
|
80
95
|
|
|
@@ -311,8 +326,14 @@ export function Onboard({
|
|
|
311
326
|
</Box>
|
|
312
327
|
<Box marginTop={1} flexDirection="column">
|
|
313
328
|
<Text bold>Useful commands:</Text>
|
|
314
|
-
<Text dimColor>
|
|
315
|
-
|
|
329
|
+
<Text dimColor>
|
|
330
|
+
{" "}
|
|
331
|
+
{composeCommandText(engineForCommands(state.containerEngine), "logs -f")}
|
|
332
|
+
</Text>
|
|
333
|
+
<Text dimColor>
|
|
334
|
+
{" "}
|
|
335
|
+
{composeCommandText(engineForCommands(state.containerEngine), "down")}
|
|
336
|
+
</Text>
|
|
316
337
|
<Text dimColor> agent-swarm setup</Text>
|
|
317
338
|
</Box>
|
|
318
339
|
</Box>
|
|
@@ -341,8 +362,22 @@ function renderStep(step: OnboardStep, props: StepProps) {
|
|
|
341
362
|
<Text bold>How would you like to deploy your swarm?</Text>
|
|
342
363
|
<Box marginTop={1}>
|
|
343
364
|
<Select
|
|
344
|
-
|
|
345
|
-
|
|
365
|
+
defaultValue={props.state.containerEngine}
|
|
366
|
+
options={[
|
|
367
|
+
{
|
|
368
|
+
label: "Local, auto-detect engine (Docker if installed, else Podman)",
|
|
369
|
+
value: "auto",
|
|
370
|
+
},
|
|
371
|
+
{ label: `Local (${CONTAINER_ENGINE_LABELS.docker} Compose)`, value: "docker" },
|
|
372
|
+
{
|
|
373
|
+
label: `Local (${CONTAINER_ENGINE_LABELS.podman} Compose, rootless)`,
|
|
374
|
+
value: "podman",
|
|
375
|
+
},
|
|
376
|
+
]}
|
|
377
|
+
onChange={(value) => {
|
|
378
|
+
if (!isContainerEnginePreference(value)) return;
|
|
379
|
+
props.goToNext({ deployType: "local", containerEngine: value });
|
|
380
|
+
}}
|
|
346
381
|
/>
|
|
347
382
|
</Box>
|
|
348
383
|
<Text dimColor>Remote (SSH) — Coming soon</Text>
|
|
@@ -189,6 +189,82 @@ export async function renderProfileSyncRejectionBanner(event: SwarmEvent): Promi
|
|
|
189
189
|
return result.skipped ? null : result.text;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
type LatestProfileSyncEvents = {
|
|
193
|
+
rejection: SwarmEvent | null;
|
|
194
|
+
reconciliation: SwarmEvent | null;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const PROFILE_SYNC_REJECTED = "system.profile_sync_rejected";
|
|
198
|
+
const PROFILE_SYNC_RECONCILED = "system.profile_sync_reconciled";
|
|
199
|
+
|
|
200
|
+
function profileSyncHeaders(config: Pick<ProfileSyncOptions, "agentId" | "apiKey">) {
|
|
201
|
+
return { Authorization: `Bearer ${config.apiKey}`, "X-Agent-ID": config.agentId };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* One request for every field's latest rejection and reconciliation. Returns
|
|
206
|
+
* null when the API does not confirm `latestPerDataField` (an API that predates
|
|
207
|
+
* it ignores the parameters), so the caller falls back to per-field lookups.
|
|
208
|
+
*/
|
|
209
|
+
async function fetchLatestProfileSyncEventsBatched(
|
|
210
|
+
config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey">,
|
|
211
|
+
fetchImpl: typeof fetch,
|
|
212
|
+
): Promise<LatestProfileSyncEvents[] | null> {
|
|
213
|
+
const query = new URLSearchParams({
|
|
214
|
+
events: `${PROFILE_SYNC_REJECTED},${PROFILE_SYNC_RECONCILED}`,
|
|
215
|
+
dataFields: Object.keys(IDENTITY_FIELD_BUDGETS).join(","),
|
|
216
|
+
agentId: config.agentId,
|
|
217
|
+
latestPerDataField: "true",
|
|
218
|
+
});
|
|
219
|
+
const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
|
|
220
|
+
headers: profileSyncHeaders(config),
|
|
221
|
+
});
|
|
222
|
+
if (!response.ok) return null;
|
|
223
|
+
const payload = (await response.json()) as {
|
|
224
|
+
events?: SwarmEvent[];
|
|
225
|
+
latestPerDataField?: boolean;
|
|
226
|
+
};
|
|
227
|
+
if (payload.latestPerDataField !== true || !Array.isArray(payload.events)) return null;
|
|
228
|
+
const events = payload.events;
|
|
229
|
+
const latest = (event: string, field: string) =>
|
|
230
|
+
events.find((candidate) => candidate.event === event && candidate.data?.field === field) ??
|
|
231
|
+
null;
|
|
232
|
+
return (Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map((field) => ({
|
|
233
|
+
rejection: latest(PROFILE_SYNC_REJECTED, field),
|
|
234
|
+
reconciliation: latest(PROFILE_SYNC_RECONCILED, field),
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Legacy path: one `limit=1` lookup per (field, event), for APIs without batching. */
|
|
239
|
+
async function fetchLatestProfileSyncEventsPerField(
|
|
240
|
+
config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey">,
|
|
241
|
+
fetchImpl: typeof fetch,
|
|
242
|
+
): Promise<LatestProfileSyncEvents[]> {
|
|
243
|
+
return await Promise.all(
|
|
244
|
+
(Object.keys(IDENTITY_FIELD_BUDGETS) as BudgetedIdentityField[]).map(async (field) => {
|
|
245
|
+
const fetchLatestEvent = async (event: SwarmEvent["event"]): Promise<SwarmEvent | null> => {
|
|
246
|
+
const query = new URLSearchParams({
|
|
247
|
+
event,
|
|
248
|
+
agentId: config.agentId,
|
|
249
|
+
dataField: field,
|
|
250
|
+
limit: "1",
|
|
251
|
+
});
|
|
252
|
+
const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
|
|
253
|
+
headers: profileSyncHeaders(config),
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok) return null;
|
|
256
|
+
const payload = (await response.json()) as { events?: SwarmEvent[] };
|
|
257
|
+
return payload.events?.[0] ?? null;
|
|
258
|
+
};
|
|
259
|
+
const [rejection, reconciliation] = await Promise.all([
|
|
260
|
+
fetchLatestEvent(PROFILE_SYNC_REJECTED),
|
|
261
|
+
fetchLatestEvent(PROFILE_SYNC_RECONCILED),
|
|
262
|
+
]);
|
|
263
|
+
return { rejection, reconciliation };
|
|
264
|
+
}),
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
192
268
|
/** Fetch every field's latest unresolved rejection before a provider session. */
|
|
193
269
|
export async function fetchProfileSyncRejectionBanner(
|
|
194
270
|
config: Pick<ProfileSyncOptions, "agentId" | "apiUrl" | "apiKey" | "claudeMdPath">,
|
|
@@ -196,32 +272,9 @@ export async function fetchProfileSyncRejectionBanner(
|
|
|
196
272
|
readFile: FileReader = readFileIfExists,
|
|
197
273
|
): Promise<string> {
|
|
198
274
|
try {
|
|
199
|
-
const responses =
|
|
200
|
-
(
|
|
201
|
-
|
|
202
|
-
const query = new URLSearchParams({
|
|
203
|
-
event,
|
|
204
|
-
agentId: config.agentId,
|
|
205
|
-
dataField: field,
|
|
206
|
-
limit: "1",
|
|
207
|
-
});
|
|
208
|
-
const response = await fetchImpl(`${config.apiUrl}/api/events?${query}`, {
|
|
209
|
-
headers: {
|
|
210
|
-
Authorization: `Bearer ${config.apiKey}`,
|
|
211
|
-
"X-Agent-ID": config.agentId,
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
if (!response.ok) return null;
|
|
215
|
-
const payload = (await response.json()) as { events?: SwarmEvent[] };
|
|
216
|
-
return payload.events?.[0] ?? null;
|
|
217
|
-
};
|
|
218
|
-
const [rejection, reconciliation] = await Promise.all([
|
|
219
|
-
fetchLatestEvent("system.profile_sync_rejected"),
|
|
220
|
-
fetchLatestEvent("system.profile_sync_reconciled"),
|
|
221
|
-
]);
|
|
222
|
-
return { rejection, reconciliation };
|
|
223
|
-
}),
|
|
224
|
-
);
|
|
275
|
+
const responses =
|
|
276
|
+
(await fetchLatestProfileSyncEventsBatched(config, fetchImpl)) ??
|
|
277
|
+
(await fetchLatestProfileSyncEventsPerField(config, fetchImpl));
|
|
225
278
|
const latestEvents = responses.flatMap(({ rejection, reconciliation }) =>
|
|
226
279
|
rejection ? [{ rejection, reconciliation }] : [],
|
|
227
280
|
);
|
package/src/commands/runner.ts
CHANGED
|
@@ -1156,6 +1156,7 @@ const SWARM_TOOL_LABELS: Record<string, string | null> = {
|
|
|
1156
1156
|
"post-message": "💬 Sending message",
|
|
1157
1157
|
"read-messages": "💬 Reading messages",
|
|
1158
1158
|
"request-human-input": "🙋 Requesting human input",
|
|
1159
|
+
"cancel-approval-request": "🚫 Cancelling approval request",
|
|
1159
1160
|
"cancel-task": "🚫 Cancelling task",
|
|
1160
1161
|
"db-query": "🗃️ Querying database",
|
|
1161
1162
|
"inject-learning": "🧠 Storing learning",
|
|
@@ -3253,7 +3254,13 @@ async function fetchRelevantMemories(
|
|
|
3253
3254
|
if (!response.ok) return null;
|
|
3254
3255
|
|
|
3255
3256
|
const data = (await response.json()) as {
|
|
3256
|
-
results: Array<{
|
|
3257
|
+
results: Array<{
|
|
3258
|
+
id: string;
|
|
3259
|
+
name: string;
|
|
3260
|
+
content: string;
|
|
3261
|
+
similarity: number;
|
|
3262
|
+
rawSimilarity?: number;
|
|
3263
|
+
}>;
|
|
3257
3264
|
};
|
|
3258
3265
|
|
|
3259
3266
|
return renderMemoriesPrompt(data.results || []);
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
+
"github-sender-allowlist": {
|
|
3
|
+
"manifestFile": "manifest.json",
|
|
4
|
+
"manifest": {
|
|
5
|
+
"$schema": "../manifest.schema.json",
|
|
6
|
+
"name": "github-sender-allowlist",
|
|
7
|
+
"description": "Blocks GitHub webhook tasks from senders outside configured allowlists",
|
|
8
|
+
"version": "1.0.0",
|
|
9
|
+
"runtime": "api",
|
|
10
|
+
"assets": {
|
|
11
|
+
"hooks": "hooks.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": {
|
|
15
|
+
"hooks.ts": "import { block, type SwarmExtension } from \"swarm-extension\";\nimport { z } from \"zod\";\n\nexport const config = z.object({\n internal: z.array(z.string()).default([]),\n external: z.array(z.string()).default([]),\n reviewBots: z.array(z.string()).default([]),\n exemptTaskTypes: z.array(z.string()).default([]),\n});\n\nconst manifest = {\n name: \"github-sender-allowlist\",\n description: \"Blocks GitHub webhook tasks from senders outside configured allowlists\",\n version: \"1.0.0\",\n runtime: \"api\",\n assets: { hooks: \"hooks.ts\" },\n config,\n} as const;\n\nfunction includesLogin(logins: string[], login: string): boolean {\n return logins.some((allowed) => allowed.toLowerCase() === login.toLowerCase());\n}\n\nconst extension: SwarmExtension<typeof manifest> = (api) => {\n api.on(\"pre.task.create\", (event, ctx) => {\n const { options } = event;\n if (options.source !== \"github\") return;\n\n const taskType = options.taskType ?? \"unknown\";\n const login = options.vcsAuthor ?? \"\";\n if (includesLogin(ctx.config.internal, login)) return;\n\n const isReviewBot = includesLogin(ctx.config.reviewBots, login);\n if (isReviewBot && taskType === \"github-review\") return;\n if (!isReviewBot && ctx.config.exemptTaskTypes.includes(taskType)) return;\n\n const isPullRequest = options.vcsUrl?.includes(\"/pull/\") ?? false;\n const isExternalPullRequestTask =\n (taskType === \"github-comment\" || taskType === \"github-pr\" || taskType === \"github-review\") &&\n isPullRequest;\n const isExternalSender = includesLogin(ctx.config.external, login);\n if (!isReviewBot && isExternalSender && isExternalPullRequestTask) return;\n\n const rule = isReviewBot\n ? \"review bots are allowed only for github-review tasks\"\n : isExternalSender\n ? \"external senders are allowed only for PR comments, reviews, and github-pr tasks on pull requests\"\n : \"sender is not on the internal, external, or reviewBots allowlist\";\n const reason = `GitHub sender \"${login || \"unknown\"}\" is blocked: ${rule}.`;\n ctx.log.warn(reason, { login: login || null, taskType, vcsUrl: options.vcsUrl ?? null });\n return block(reason);\n });\n};\n\nexport default extension;\n"
|
|
16
|
+
},
|
|
17
|
+
"readme": "# github-sender-allowlist\n\nBlocks GitHub webhook tasks from senders outside the configured allowlists. Login matching is case-insensitive. Tasks from other sources pass through. No task type is exempt by default.\n\nConfig:\n\n```json\n{\n \"internal\": [\"tarasyarema\", \"harlequinetcie\"],\n \"external\": [\"fuvidani\", \"capchase-bot\"],\n \"reviewBots\": [],\n \"exemptTaskTypes\": []\n}\n```\n\nInternal senders can create any GitHub task, including reviews. Logins in `reviewBots` can create `github-review` tasks only. External senders can create `github-comment` or `github-pr` tasks on pull requests, and `github-review` tasks when `vcsUrl` contains `/pull/`. Choose any review-bot logins from the sender census in the [security audit](https://live.agent-fs.dev/file/~/648a5f3c-35c8-4f11-8673-b89de52cd6bd/2faf73ba-4eee-4472-8b3b-359c4ed6bfbb/thoughts/a09d19a4-bd35-4593-9b6f-c2ccafccead8/reviews/2026-09-28-github-sender-authorization-audit.md) when installing the extension; the template intentionally defaults to an empty list.\n\n`exemptTaskTypes` is an optional operator override for explicitly exempting task types. It defaults to an empty list. The GitHub handler does not pass the action for `github-pr` tasks, so external `github-pr` access also permits assignment and label events on pull requests.\n"
|
|
18
|
+
},
|
|
2
19
|
"notify-on-complete": {
|
|
3
20
|
"manifestFile": "manifest.json",
|
|
4
21
|
"manifest": {
|