@desplega.ai/agent-swarm 1.78.1 → 1.79.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.
Files changed (75) hide show
  1. package/README.md +1 -0
  2. package/openapi.json +1335 -236
  3. package/package.json +4 -4
  4. package/plugin/skills/artifacts/SKILL.md +151 -0
  5. package/plugin/skills/artifacts/examples/static-report.sh +1 -1
  6. package/plugin/skills/kv-storage/SKILL.md +168 -0
  7. package/plugin/skills/pages/SKILL.md +423 -0
  8. package/src/artifact-sdk/browser-sdk.ts +396 -19
  9. package/src/be/db.ts +548 -0
  10. package/src/be/migrations/059_pages.sql +34 -0
  11. package/src/be/migrations/060_page_versions.sql +19 -0
  12. package/src/be/migrations/061_kv_store.sql +34 -0
  13. package/src/be/migrations/062_pages_view_count.sql +9 -0
  14. package/src/commands/artifact.ts +17 -11
  15. package/src/commands/provider-credentials.ts +1 -1
  16. package/src/http/index.ts +9 -1
  17. package/src/http/kv.ts +658 -0
  18. package/src/http/page-proxy.ts +213 -0
  19. package/src/http/pages-public.ts +507 -0
  20. package/src/http/pages.ts +608 -0
  21. package/src/http/status.ts +1 -1
  22. package/src/http/utils.ts +68 -5
  23. package/src/pages/version.ts +44 -0
  24. package/src/prompts/session-templates.ts +51 -0
  25. package/src/providers/pi-mono-adapter.ts +3 -3
  26. package/src/providers/pi-mono-extension.ts +1 -1
  27. package/src/server.ts +29 -1
  28. package/src/tasks/context-key.ts +28 -0
  29. package/src/telemetry.ts +65 -1
  30. package/src/tests/artifact-commands.test.ts +92 -0
  31. package/src/tests/artifact-sdk.test.ts +80 -74
  32. package/src/tests/context-key.test.ts +17 -0
  33. package/src/tests/create-page-tool.test.ts +197 -0
  34. package/src/tests/fixtures/sample-json-page.json +52 -0
  35. package/src/tests/kv-http.test.ts +331 -0
  36. package/src/tests/kv-namespace-resolution.test.ts +172 -0
  37. package/src/tests/kv-page-proxy.test.ts +212 -0
  38. package/src/tests/kv-storage.test.ts +227 -0
  39. package/src/tests/kv-tool.test.ts +217 -0
  40. package/src/tests/launch-password-rejection.test.ts +139 -0
  41. package/src/tests/page-proxy-authed.test.ts +146 -0
  42. package/src/tests/page-proxy.test.ts +270 -0
  43. package/src/tests/page-session.test.ts +169 -0
  44. package/src/tests/pages-actions-endpoint.test.ts +102 -0
  45. package/src/tests/pages-authed-mode.test.ts +211 -0
  46. package/src/tests/pages-http.test.ts +193 -0
  47. package/src/tests/pages-list-endpoint.test.ts +149 -0
  48. package/src/tests/pages-password-hash.test.ts +57 -0
  49. package/src/tests/pages-password-mode.test.ts +265 -0
  50. package/src/tests/pages-public-authed-401.test.ts +102 -0
  51. package/src/tests/pages-public-html.test.ts +151 -0
  52. package/src/tests/pages-public-json-redirect.test.ts +86 -0
  53. package/src/tests/pages-storage.test.ts +196 -0
  54. package/src/tests/pages-versioning.test.ts +231 -0
  55. package/src/tests/pages-view-count.test.ts +220 -0
  56. package/src/tests/prompt-template-session.test.ts +3 -2
  57. package/src/tests/skill-update-scope.test.ts +165 -0
  58. package/src/tests/swarm-diff.test.ts +303 -0
  59. package/src/tests/telemetry-init.test.ts +149 -0
  60. package/src/tests/workflow-wait-event.test.ts +4 -7
  61. package/src/tools/create-page.ts +263 -0
  62. package/src/tools/kv/index.ts +5 -0
  63. package/src/tools/kv/kv-delete.ts +89 -0
  64. package/src/tools/kv/kv-get.ts +64 -0
  65. package/src/tools/kv/kv-incr.ts +116 -0
  66. package/src/tools/kv/kv-list.ts +81 -0
  67. package/src/tools/kv/kv-set.ts +194 -0
  68. package/src/tools/kv/resolve-namespace.ts +58 -0
  69. package/src/tools/skills/skill-update.ts +26 -0
  70. package/src/tools/tool-config.ts +10 -0
  71. package/src/types.ts +107 -0
  72. package/src/utils/internal-ai/complete-structured.ts +2 -2
  73. package/src/utils/internal-ai/credentials.ts +3 -3
  74. package/src/utils/page-session.ts +254 -0
  75. package/plugin/skills/artifacts/skill.md +0 -70
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/agent-swarm",
3
- "version": "1.78.1",
3
+ "version": "1.79.1",
4
4
  "description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
5
5
  "license": "MIT",
6
6
  "author": "desplega.sh <contact@desplega.sh>",
@@ -104,9 +104,9 @@
104
104
  "@desplega.ai/localtunnel": "^2.2.0",
105
105
  "@inkjs/ui": "^2.0.0",
106
106
  "@linear/sdk": "^77.0.0",
107
- "@mariozechner/pi-agent-core": "^0.73.0",
108
- "@mariozechner/pi-ai": "^0.73.0",
109
- "@mariozechner/pi-coding-agent": "^0.73.0",
107
+ "@earendil-works/pi-agent-core": "^0.74.0",
108
+ "@earendil-works/pi-ai": "^0.74.0",
109
+ "@earendil-works/pi-coding-agent": "^0.74.0",
110
110
  "@modelcontextprotocol/sdk": "^1.25.1",
111
111
  "@openai/codex-sdk": "^0.128.0",
112
112
  "@opencode-ai/sdk": "^1.14.30",
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: artifacts
3
+ description: Serve interactive web content (HTML pages, dashboards, approval flows, static reports, custom Hono apps) to a public URL via localtunnel. Use when the user asks to "create an artifact for X", "host this for me", "make me a tunneled URL", "spin up a web server for X", "publish this report so I can see it", "share this file/page publicly", "expose this dashboard", "give me a live link", or anything that needs a browser-reachable URL pointing at agent-generated content. Wraps the `agent-swarm artifact` CLI plus the `createArtifactServer` SDK; covers static directories, custom Hono apps, daemonization (nohup / PM2), HTTP Basic auth, and the in-page swarm Browser SDK.
4
+ ---
5
+
6
+ # Artifacts — Serving Interactive Web Content
7
+
8
+ Serve a directory or a Hono app to a public, auth-protected URL via localtunnel. Useful for sharing reports, dashboards, approval flows, or anything else a human needs to look at in a browser.
9
+
10
+ The CLI is a subcommand of `agent-swarm`. Always invoke as **`agent-swarm artifact <subcommand>`** — there is no top-level `artifact` binary.
11
+
12
+ ## Quick Start
13
+
14
+ ### Static content
15
+ ```bash
16
+ # Create your content in a persisted directory
17
+ mkdir -p /workspace/personal/artifacts/my-report
18
+ echo '<h1>My Report</h1>' > /workspace/personal/artifacts/my-report/index.html
19
+
20
+ # Serve it (auto-assigns a free port, creates tunnel, registers in service registry)
21
+ agent-swarm artifact serve /workspace/personal/artifacts/my-report --name my-report
22
+ # -> Artifact "my-report" live at https://<agentId>-my-report.lt.desplega.ai (port <auto>)
23
+ ```
24
+
25
+ ### Programmatic (custom Hono server)
26
+ ```typescript
27
+ import { createArtifactServer } from '../artifact-sdk';
28
+ import { Hono } from 'hono';
29
+
30
+ const app = new Hono();
31
+ app.get('/', (c) => c.html('<h1>Dashboard</h1>'));
32
+
33
+ const server = createArtifactServer({ name: 'dashboard', app });
34
+ await server.start();
35
+ console.log(`Live at: ${server.url}`);
36
+ ```
37
+
38
+ You can also `agent-swarm artifact serve ./server.ts --name dashboard` if `server.ts` exports a Hono instance as its default export.
39
+
40
+ ## CLI Commands
41
+
42
+ | Command | Description |
43
+ |---|---|
44
+ | `agent-swarm artifact serve <path> --name <name> [--port <port>] [--no-auth] [--subdomain <sub>]` | Start serving content. `<path>` is a directory (static) or a `.ts`/`.js` file exporting a default Hono app. |
45
+ | `agent-swarm artifact list` | List active artifacts (name, agent, port, URL, status) from the service registry. |
46
+ | `agent-swarm artifact stop <name>` | Stop an artifact: deletes the matching PM2 process and unregisters it from the service registry. See "Known limitation" below for non-PM2 processes. |
47
+
48
+ Flags accepted by `serve`:
49
+ - `--name <name>` — defaults to the basename of `<path>`. Used for the subdomain and PM2 process name.
50
+ - `--port <port>` — pin to a specific port. Default: auto-assigned ephemeral port.
51
+ - `--no-auth` — disable HTTP Basic auth on the tunnel (DANGEROUS — anyone with the URL can access).
52
+ - `--subdomain <sub>` — override the default `${agentId}-${name}` subdomain.
53
+
54
+ ## Auth & URL Pattern
55
+
56
+ Tunnels are protected by **HTTP Basic auth** by default:
57
+ - **Username:** `hi` (hardcoded MVP default in `src/artifact-sdk/tunnel.ts`)
58
+ - **Password:** the agent's `API_KEY`
59
+
60
+ Two equivalent URL forms:
61
+
62
+ ```
63
+ # Plain (browser will prompt for credentials)
64
+ https://<agentId>-<name>.lt.desplega.ai
65
+
66
+ # Auth-prefilled (works in curl, scripts, and most browsers without a prompt)
67
+ https://hi:<API_KEY>@<agentId>-<name>.lt.desplega.ai
68
+ ```
69
+
70
+ Use `--no-auth` only for genuinely public content. Anyone who learns the subdomain can read it.
71
+
72
+ ## Running it as a daemon
73
+
74
+ `agent-swarm artifact serve` blocks on a never-resolving promise to stay alive — you cannot inline it in a script that needs to do other work. Pick one of these:
75
+
76
+ ### Option A — `nohup` (quick, throwaway)
77
+
78
+ Easiest for one-off "host this for the next 10 minutes" cases:
79
+
80
+ ```bash
81
+ mkdir -p /workspace/personal/logs
82
+ nohup agent-swarm artifact serve /workspace/personal/artifacts/my-report \
83
+ --name my-report \
84
+ > /workspace/personal/logs/my-report.out 2>&1 &
85
+ echo $! > /workspace/personal/logs/my-report.pid
86
+
87
+ # Later, kill it manually:
88
+ kill "$(cat /workspace/personal/logs/my-report.pid)"
89
+ ```
90
+
91
+ ### Option B — PM2 (recommended for anything you'll come back to)
92
+
93
+ PM2 gives you auto-restart on crash, a process name, log management, and — crucially — **lets `agent-swarm artifact stop <name>` actually kill it** (see Known limitation below).
94
+
95
+ ```bash
96
+ pm2 start agent-swarm \
97
+ --name artifact-my-report \
98
+ -- artifact serve /workspace/personal/artifacts/my-report --name my-report
99
+
100
+ # Stop it cleanly later:
101
+ agent-swarm artifact stop my-report
102
+ ```
103
+
104
+ The PM2 process name **must** be `artifact-<name>` (matching `--name`) — that's exactly what `artifact stop` looks for.
105
+
106
+ ### Known limitation — `artifact stop` only kills PM2-started processes
107
+
108
+ Today, `agent-swarm artifact stop <name>` runs `pm2 delete artifact-<name>` and then unregisters the entry from the service registry. If you started the artifact with `nohup` (or `&`, or any non-PM2 launcher), `pm2 delete` silently fails and the actual server keeps running and serving — even though the command prints `Artifact '<name>' stopped.` Tracked as a follow-up bug filed alongside PR #469; until it's fixed:
109
+
110
+ - Use **PM2** if you want `artifact stop` to actually do its job.
111
+ - For `nohup`/foreground processes, kill the PID yourself (`kill <pid>` or `pkill -f 'artifact serve.*<name>'`) **and then** run `agent-swarm artifact stop <name>` to clear the registry row.
112
+
113
+ ## Multiple Artifacts
114
+
115
+ Each artifact gets its own port (auto-assigned) and subdomain (`<agentId>-<name>`). You can run several simultaneously — see `examples/multi-artifact.ts`.
116
+
117
+ ## Browser SDK
118
+
119
+ HTML artifacts can call back into the swarm API via a server-side proxy that injects auth, so browser code never sees the API key:
120
+
121
+ ```html
122
+ <script src="/@swarm/sdk.js"></script>
123
+ <script>
124
+ const swarm = new SwarmSDK();
125
+ await swarm.createTask({ task: 'Do something' });
126
+ const agents = await swarm.getSwarm();
127
+ </script>
128
+ ```
129
+
130
+ ### Available SDK Methods
131
+ - `createTask(opts)` — Create a new task
132
+ - `getTasks(filters)` — List tasks with optional filters
133
+ - `getTaskDetails(id)` — Get details for a specific task
134
+ - `storeProgress(taskId, data)` — Update task progress
135
+ - `postMessage(opts)` — Post a message to a channel
136
+ - `readMessages(opts)` — Read messages from a channel
137
+ - `getSwarm()` — Get list of agents
138
+ - `listServices()` — List registered services
139
+ - `slackReply(opts)` — Reply to a Slack thread
140
+
141
+ ## API Proxy
142
+
143
+ The `/@swarm/api/*` proxy forwards requests to the MCP server with proper authentication headers. This allows browser-side JavaScript to call swarm APIs without exposing credentials.
144
+
145
+ ## Storage
146
+
147
+ Always store artifact content in persisted directories — the working dir is wiped between sessions:
148
+ - `/workspace/personal/artifacts/` — per-agent, persists across sessions (default)
149
+ - `/workspace/shared/artifacts/` — shared across the swarm
150
+
151
+ See the `examples/` directory for complete working examples.
@@ -14,4 +14,4 @@ cat > "$ARTIFACT_DIR/index.html" << 'HTML'
14
14
  </html>
15
15
  HTML
16
16
 
17
- artifact serve "$ARTIFACT_DIR" --name "my-report"
17
+ agent-swarm artifact serve "$ARTIFACT_DIR" --name "my-report"
@@ -0,0 +1,168 @@
1
+ ---
2
+ name: kv-storage
3
+ description: Use the swarm KV store (Redis-like, namespaced) for cross-task / cross-session / per-page state. Auto-scoped to your context (Slack thread / PR / Linear issue / agent / page). Use for counters, cursors, page state. Do NOT use for secrets (`swarm_config`), embedded knowledge (`memory`), or files (`agent-fs`).
4
+ ---
5
+
6
+ # KV Storage
7
+
8
+ Namespaced key/value store inside the swarm SQLite DB. Auto-scoped to your
9
+ calling context — same string used by `agent_tasks.contextKey`.
10
+
11
+ > **Capability gate**: the `kv-*` MCP tools are only available when your
12
+ > `CAPABILITIES` includes `kv` (default-on; check `my-agent-info`). The REST
13
+ > endpoints under `/api/kv/*` are always present on the API server.
14
+
15
+ ## When to use KV
16
+
17
+ | You need… | Use this | Not this |
18
+ |---|---|---|
19
+ | Count something in this Slack thread / PR / Linear issue | **KV** (auto-scoped) | memory / agent-fs |
20
+ | Save a cursor / last-seen state for a recurring schedule | **KV** | swarm_config |
21
+ | Page-internal counter / vote / state across reloads | **KV** via `swarmSdk.kv` | memory |
22
+ | Cross-task state in the same conversation | **KV** (auto-scoped to `task:slack:...`) | parentTaskId only |
23
+ | Secrets, API tokens, OAuth creds | `swarm_config` (encrypted + masked) | **NOT KV** |
24
+ | Cross-session knowledge for this agent ("how do I…") | `memory_search` / `memory-get` | **NOT KV** |
25
+ | Files, binaries, long documents | `agent-fs` | **NOT KV** |
26
+ | Workflow run state | workflow vars (own KV) | **NOT KV** |
27
+
28
+ Rule of thumb:
29
+ - If a future invocation should *find this without knowing the key* → memory.
30
+ - If a future invocation will *know exactly which key to read* → KV.
31
+ - If it has secrets in it → `swarm_config`.
32
+ - If it's bytes (image, pdf, large doc) → agent-fs.
33
+
34
+ ## Namespacing
35
+
36
+ Namespace is just a string. It mirrors the `contextKey` schema
37
+ (`src/tasks/context-key.ts`). When you don't pass one, the server resolves it
38
+ from request headers in this order:
39
+
40
+ 1. `X-Page-Id` (only the page-proxy sets this) → `task:page:<id>`
41
+ 2. `X-Source-Task-Id` → that task's `contextKey` (e.g. `task:slack:C123:1776...`)
42
+ 3. `X-Agent-ID` → `task:agent:<id>` (per-agent scratchpad)
43
+
44
+ So **inside a session triggered by a Slack thread, KV is automatically scoped
45
+ to that thread** — your sibling tasks (re-runs, retries, follow-ups in the same
46
+ thread) read the same store with no setup. Same for PRs (`task:trackers:github:owner:repo:pr:N`),
47
+ Linear issues (`task:trackers:linear:DES-42`), schedules, workflows.
48
+
49
+ You can override the namespace explicitly when you need to — see "Explicit
50
+ override" below.
51
+
52
+ ## Quick recipes
53
+
54
+ ### MCP — inside any agent session
55
+
56
+ ```
57
+ kv-set key="vote-count" value=0 valueType="integer" # → namespace = task:slack:...
58
+ kv-incr key="vote-count" # → 1
59
+ kv-incr key="vote-count" by=5 # → 6
60
+ kv-get key="vote-count" # → entry with value=6
61
+ kv-list prefix="vote-" # → all matching entries
62
+ kv-delete key="vote-count" # → done
63
+ ```
64
+
65
+ `kv-set` defaults to `valueType: 'json'` and JSON-encodes whatever you pass.
66
+ Use `'string'` to skip encoding (good for short tokens, URLs) and
67
+ `'integer'` for counters (required by `kv-incr`).
68
+
69
+ ### REST — humans, scripts, external clients
70
+
71
+ ```bash
72
+ # Header-resolved namespace (recommended for in-session calls)
73
+ curl -H "Authorization: Bearer $API_KEY" \
74
+ -H "X-Agent-ID: $AGENT_ID" \
75
+ "$MCP_BASE_URL/api/kv/last-cursor"
76
+
77
+ # Explicit namespace
78
+ curl -H "Authorization: Bearer $API_KEY" \
79
+ "$MCP_BASE_URL/api/kv/_/task:trackers:linear:DES-42/last-comment-id"
80
+
81
+ # PUT a JSON value with a 10-minute TTL
82
+ curl -X PUT -H "Authorization: Bearer $API_KEY" -H "X-Agent-ID: $AGENT_ID" \
83
+ -H "Content-Type: application/json" \
84
+ -d '{"value":{"n":42},"valueType":"json","expiresInSec":600}' \
85
+ "$MCP_BASE_URL/api/kv/snapshot"
86
+
87
+ # List with a prefix
88
+ curl -H "Authorization: Bearer $API_KEY" -H "X-Agent-ID: $AGENT_ID" \
89
+ "$MCP_BASE_URL/api/kv?prefix=daily-&limit=50"
90
+ ```
91
+
92
+ ### Pages browser SDK — inside an authed page
93
+
94
+ Page proxy forces the namespace to `task:page:<id>` — no namespace argument is
95
+ exposed. Use it for page-local counters, vote tallies, multi-step form state,
96
+ "remember this number from last refresh" UX:
97
+
98
+ ```js
99
+ // Inside a page's <script> tag
100
+ const count = await swarmSdk.kv.incr('clicks'); // → number-valued entry
101
+ await swarmSdk.kv.set('lastSeen', Date.now()); // → 'json' by default
102
+ const entry = await swarmSdk.kv.get('clicks'); // → { value, valueType, ... } or null
103
+ await swarmSdk.kv.del('clicks');
104
+ const all = await swarmSdk.kv.list({ prefix: 'click', limit: 50 });
105
+ ```
106
+
107
+ Public pages (`authMode: 'public'`) cannot reach `/@swarm/api/*` and so cannot
108
+ use KV. Promote to `authed` or `password` mode if the page needs state.
109
+
110
+ ## Explicit override
111
+
112
+ Pass `namespace` to read/write somewhere other than your auto-context:
113
+
114
+ ```
115
+ kv-get key="seed" namespace="swarm:experiments" # ad-hoc namespace
116
+ kv-set key="note" value="hi" namespace="task:agent:OTHER-AGENT-ID"
117
+ # → 403 unless caller is lead
118
+ ```
119
+
120
+ Rules:
121
+ - **Reads:** any authenticated caller can read any namespace.
122
+ - **Writes to `task:agent:<X>`** where X ≠ caller agentId: **403** unless lead.
123
+ - **Writes to `task:page:<X>`** from anywhere except a page-proxy request: **403**.
124
+ - Everything else: writable by any authenticated caller.
125
+
126
+ ## TTL & expiry
127
+
128
+ Default = **no expiry**. Opt in by passing `expiresInSec`:
129
+
130
+ ```
131
+ kv-set key="lock-token" value="xyz" valueType="string" expiresInSec=60
132
+ ```
133
+
134
+ Expiry is *lazy*: reads on an expired key return null and delete the row;
135
+ `kv-list` filters expired rows out of the SELECT but doesn't delete them
136
+ (keeps cursor pagination stable). No background sweeper — expired rows that
137
+ never get touched stay on disk harmlessly.
138
+
139
+ ## Body cap
140
+
141
+ 2 MiB per value. Over the cap returns 413. If you want to store something
142
+ larger, write it to `agent-fs` and stash the path in KV.
143
+
144
+ ## Gotchas
145
+
146
+ - **Namespaces ARE contextKey strings.** The same string that lets the swarm
147
+ find sibling tasks for a PR also indexes KV for that PR.
148
+ - **Reads return `null` for missing AND expired keys** — you can't tell the
149
+ difference from one call. (If you need to know, list the key.)
150
+ - **INCR collides** if the existing row has `valueType` `'json'` or `'string'`
151
+ (409 / `KvTypeCollisionError`). Delete and re-create as `'integer'` first,
152
+ or use a different key.
153
+ - **JSON values round-trip** through `JSON.parse` on read. If you wrote
154
+ `{a:1}`, you'll get back the object — not the raw string. Use
155
+ `valueType: 'string'` if you want byte-exact storage.
156
+ - **No CAS / SETNX yet.** Use `kv-incr` for atomic counters; for
157
+ "claim a token" patterns, set with a short TTL and re-check.
158
+ - **Page SDK has no `namespace` argument.** Pages are always scoped to
159
+ `task:page:<id>`. Don't try to encode another namespace in the key path —
160
+ the URL gets rewritten anyway.
161
+
162
+ ## See also
163
+
164
+ - `src/be/migrations/061_kv_store.sql` — schema (`kv_entries`)
165
+ - `src/http/kv.ts` — REST handler + namespace resolution
166
+ - `src/tools/kv/*` — MCP tool registrars
167
+ - `src/artifact-sdk/browser-sdk.ts` — `swarmSdk.kv` for pages
168
+ - `plugin/skills/pages/SKILL.md` — companion skill for authed pages