@swarmclawai/swarmclaw 1.5.57 → 1.5.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -399,6 +399,25 @@ Operational docs: https://swarmclaw.ai/docs/observability
399
399
 
400
400
  ## Releases
401
401
 
402
+ ### v1.5.59 Highlights
403
+
404
+ Viral-loop release. Adds public share links for missions, skills, and sessions, plus a complementary raw-markdown endpoint so any shared skill installs directly through the existing `POST /api/skills/import`.
405
+
406
+ - **Share links for missions, skills, and sessions.** New `share_links` collection in `src/lib/server/storage.ts` plus `src/lib/server/sharing/share-link-repository.ts`. `POST /api/share { entityType, entityId, expiresInSec?, label? }` mints a cryptographically random 32-char base64url token; `GET /api/share` lists; `GET /api/share/:id` fetches; `DELETE /api/share/:id` revokes (pass `?hard=true` to hard-delete). CLI: `swarmclaw share {list,mint,get,revoke,resolve,raw}`.
407
+ - **Public read endpoints (no auth required).** `GET /api/s/:token` returns the scrubbed JSON payload; `GET /api/s/:token/raw` returns plain markdown (skills return their SKILL.md verbatim, missions render as title + goal + criteria + milestones, sessions as a transcript). Revoked and expired tokens return `404 Not found` without leaking shape information. `GET /s/:token` is a server-rendered page for dropping straight into a browser.
408
+ - **Share-link-based skill install.** `POST /api/skills/import` already accepts an http(s) URL; pointing it at `https://<your-host>/api/s/<token>/raw` now installs a shared skill from another SwarmClaw instance without auth handshakes. Pairs naturally with existing `swarmclaw skills import` CLI.
409
+ - **Share-link repository tests.** `share-link-repository.test.ts` covers mint / list / revoke / lookup-by-token round-trip plus expiry handling against a temporary data dir.
410
+
411
+ ### v1.5.58 Highlights
412
+
413
+ This release broadens the built-in evaluation harness so SwarmClaw runs can be benchmarked against named suites, adds two targeted starter kits, exposes live per-session cost data, tightens auto-skill drafting, and ships a zero-setup demo mission template.
414
+
415
+ - **Benchmark-style eval suites.** New `SWEBENCH_LITE_SCENARIOS` and `GAIA_L1_SCENARIOS` in `src/lib/server/eval/scenarios-swebench.ts` and `scenarios-gaia.ts` — curated parallels (not the upstream datasets) sized for a single-agent harness run. The shared `EvalScenario` type now carries an optional `suite: 'core' | 'swe-bench-lite' | 'gaia-l1' | 'tool-use' | 'code-action'` tag. `POST /api/eval/suite` accepts `{ suite: "swe-bench-lite" }` to scope a run. New `GET /api/eval/suites` lists every suite with scenario count, max score, and categories. CLI commands: `swarmclaw eval suites`, and `swarmclaw eval suite` still takes a JSON body now including `suite`. Useful for advertising verifiable numbers against a named benchmark instead of a bespoke scoring rubric.
416
+ - **Two additional starter kits.** `inbox_triage` (single Triager agent over email + memory + documents) and `data_analyst` (single Analyst agent over shell + files + web + documents) join the existing seven kits in `src/lib/setup-defaults.ts`. Both are surfaced on the intent-driven setup path alongside Personal Assistant, Research Copilot, Builder Studio, and Delegate Team.
417
+ - **Live per-session usage API.** New `GET /api/usage/live?sessionId=...` returns a lightweight snapshot — records, tokens in/out, estimated cost, firstAt/lastAt, wallclockMs, turns — so frontends can surface a live cost meter without pulling the full aggregated `/api/usage` payload. Without a `sessionId` the route returns the ten most recently active sessions. Registered in the CLI as `swarmclaw usage live`.
418
+ - **Auto-skill drafting is stricter and rate-limited.** `shouldAutoDraftSkillSuggestion` in `chat-turn-finalization.ts` now requires at least 3 tool events in the completed turn (was 1), and a new per-agent daily cap limits automatic drafts to 3 per day per agent to prevent suggestion-inbox spam. Both thresholds are named constants (`AUTO_DRAFT_MIN_TOOL_EVENTS`, `AUTO_DRAFT_DAILY_LIMIT`). Agents with `autoDraftSkillSuggestions = false` are unaffected (auto-drafting remains opt-in per agent).
419
+ - **Hello World demo mission template.** New `hello-world-demo` entry in `BUILT_IN_MISSION_TEMPLATES` — a bounded, zero-setup mission that reads three files in the working directory and writes a one-paragraph markdown summary to `hello-world-report.md`. Budgets (USD 0.25, 20k tokens, 30 turns, 15 min) are small enough to run on a local Ollama model without cost. Intended as the first thing a new user watches an agent complete end to end.
420
+
402
421
  ### v1.5.57 Highlights
403
422
 
404
423
  This release closes the org-orchestration feature gap with Paperclip while keeping SwarmClaw's autonomous-assistant focus. Most additions are additive; nothing existing has changed shape.
@@ -428,116 +447,6 @@ This release closes the org-orchestration feature gap with Paperclip while keepi
428
447
  - **Fix: `PUT /api/webhooks/:id` now validates its body with a Zod schema.** Previously `{"events": "not_an_array"}` wiped the events list. Added `WebhookUpdateSchema` and explicit `rawKeys.has(...)` guards in the mutate closure so only fields actually present in the body are applied.
429
448
  - **Fix: classifier JSON no longer leaks into assistant responses.** Some Ollama / Ollama Cloud turns were emitting the internal `MessageClassification` object directly into the stream (e.g. `{"taskIntent":"research",...}` prepended to the real reply). The existing stripper only matched when `isDeliverableTask` was the first key, so leaks starting with `taskIntent` sailed through to the user. Replaced the regex with a principled detector that brace-matches candidate JSON (string-quote aware) and validates against `MessageClassificationSchema.safeParse` — the schema itself is the source of truth, so future schema changes can't break detection.
430
449
 
431
- ### v1.5.54 Highlights
432
-
433
- - **Mission templates library**: the `/missions` page now opens with a curated gallery of starter missions. Each template pre-wires a goal, success criteria, USD / token / turn / wallclock budgets, and a report cadence, so non-technical users can install a working autonomous run in one click. Initial lineup: Daily News Digest, Inbox Triage, Competitor Watch, Weekly Research Report, Social Listener, and Customer Support Triage. Setup notes flag any connector or permission prerequisites before installation. Power-user overrides (budget caps, success criteria, report cadence) live behind a collapsed **Advanced Settings** panel so the default install flow stays one click.
434
- - **New API routes `GET /api/missions/templates` and `POST /api/missions/templates/:id/instantiate`** with matching CLI commands `swarmclaw missions templates` and `swarmclaw missions instantiate`. Installed missions persist a `templateId` so the origin is traceable for future template-update flows; legacy missions normalize to `templateId: null` on load, no data migration required.
435
- - **Fix: user-selected provider and model now survive the chat execution pipeline** ([#51](https://github.com/swarmclawai/swarmclaw/pull/51), thanks to [@borislavnnikolov](https://github.com/borislavnnikolov)). Switching provider or model via the inspector panel mid-session was being reverted on every turn because the agent's configured route was unconditionally reapplied in three places. `syncSessionFromAgent` now only syncs credentials / endpoint / fallbacks when the session's provider still matches the route provider, `prepareChatTurn` preserves the user's chosen model after applying the route, and `updateChatSession` auto-resolves a stored credential for the new provider (and clears the stale `apiEndpoint`) when provider changes without an explicit `credentialId`. Restores reliable switching between Copilot CLI, Codex CLI, Groq, and OpenAI-compatible providers.
436
-
437
- > **Note:** v1.5.53 release notes described the mission templates library, but the feature commit landed after the v1.5.53 tag was cut. v1.5.54 is the release that actually ships it.
438
-
439
- ### v1.5.53 Highlights
440
-
441
- - **Fix: switching a session's model now sticks in the UI** ([#50](https://github.com/swarmclawai/swarmclaw/pull/50), thanks to [@borislavnnikolov](https://github.com/borislavnnikolov)). The **Switch Model** panel in the agent inspector was reading from `agent.provider` / `agent.model` (the agent's defaults) instead of `session.provider` / `session.model`, so after saving a model switch the collapsed pill still showed the agent default, the combobox reset to the default when reopened, and `selectedProvider` reverted on every save. `ModelSwitcherInline` now uses `session.provider || agent.provider` and `session.model || agent.model` as the source of truth, and its `useEffect` syncs to `session.provider` changes so a successful save updates the panel immediately.
442
-
443
- ### v1.5.52 Highlights
444
-
445
- - **Session X-Ray now surfaces the backend execution log** ([#48](https://github.com/swarmclawai/swarmclaw/pull/48), thanks to [@borislavnnikolov](https://github.com/borislavnnikolov)). The debug panel fetches entries from the SQLite execution log on open and merges them with in-memory message events, sorted by time. Expandable entries show provider, model, stream errors, duration, and token counts — the info that was previously invisible when Ollama or other local-model runs failed silently. A new **Tools** filter tab, an `exec` badge for log-sourced entries, an entry count in the stats bar, and a Refresh button round it out. New API route `GET /api/chats/:id/execution-log` with `limit`, `since`, and `category` query params, registered in the CLI manifest as `swarmclaw chats execution-log`.
446
- - **Execution errors now captured in the log** ([#48](https://github.com/swarmclawai/swarmclaw/pull/48)). `finalizeChatTurn()` writes a structured `error` entry to the execution log on terminal failure, recording provider, model, stream errors, duration, token counts, and whether a response was produced — so the Session X-Ray above actually has something to show.
447
- - **Fix: blank task-sheet no longer shows `"null"` under *Blocked By*** ([#47](https://github.com/swarmclawai/swarmclaw/pull/47), thanks to [@borislavnnikolov](https://github.com/borislavnnikolov)). A successful task create/update returns `error: null`, and the old `'error' in res` guard treated that as a truthy error and rendered `String(null)` as a red "null" string under the Blocked By field. Now only non-empty string errors trigger the UI, and `depError` is cleared on dialog close so stale state cannot leak across re-opens.
448
-
449
- ### v1.5.51 Highlights
450
-
451
- - **Desktop app now actually opens and renders on macOS**: packaged builds were broken in v1.5.50 by a stack of independent issues that each masked the next. This release unblocks the cold-boot path end to end. Measured cold-boot time on a populated install: ~1 second to first `/api/healthz` response, down from a hard 60-second timeout.
452
- - Ad-hoc code signing (`identity: '-'`) via a new `scripts/electron-after-pack.cjs` hook that runs `codesign --sign - --force --deep` after electron-builder packages the bundle. The bundle identifier is now sealed as `ai.swarmclaw.desktop` with all 74k resources sealed, so quarantined dmgs surface as "unidentified developer" (right-click → Open) instead of the more confusing "damaged" error.
453
- - Per-architecture native module sync: the afterPack hook copies `better-sqlite3`, `@mongodb-js/zstd`, `node-liblzma`, and `utf-8-validate` `.node` binaries from the electron-builder-rebuilt root `node_modules` into the packaged `.next/standalone/node_modules`. Without this, the standalone server hit `ERR_DLOPEN_FAILED: NODE_MODULE_VERSION 137` on launch because Next.js's output-tracing copied the Node-ABI build of better-sqlite3 into standalone while electron-builder only rebuilt the root tree for Electron's ABI.
454
- - `scripts/run-next-build.mjs` now copies `mdn-data` (used by `css-tree` via `jsdom`) into standalone alongside the existing `css-tree/data` patch, so pages that depend on it don't 500 with `Cannot find module 'mdn-data/css/at-rules.json'`.
455
- - `isomorphic-dompurify` replaced by the browser-only `dompurify` in `agent-avatar.tsx`. The isomorphic wrapper was pulling `jsdom`'s ESM-only `@exodus/bytes` dep into every server bundle the avatar was referenced from, which blew up SSR under Electron 33 (Node 20.18) with `ERR_REQUIRE_ESM` on every page.
456
- - Session-consolidation migrations, `initWsServer`, and `ensureDaemonStarted` moved into a `setImmediate` deferred block in `src/instrumentation.ts` so Next.js can bind the HTTP listener before per-install work runs.
457
- - **App icon fixed**: the Dock no longer shows Electron's default `exec` placeholder. `scripts/gen-icons.mjs` generates `resources/icon.icns`, `resources/icon.ico`, and `resources/icon.png` from `public/branding/swarmclaw-org-avatar.png`; the main process sets the Dock icon at launch and passes it to every `BrowserWindow`.
458
- - **Embedded server log file + improved failure dialog**: the Electron wrapper now tees the child Next.js server's stdout/stderr into `<userData>/logs/server.log` (`~/Library/Application Support/@swarmclawai/swarmclaw/logs/server.log` on macOS, 1 MB rotation). If startup fails or the server exits, the error dialog shows the tail of the log inline and exposes an **Open Logs Folder** button that jumps Finder straight to the file. This is what made root-cause debugging possible in the first place — if you hit any kind of regression here, grab that log and open an issue.
459
- - **Embedded server timeout raised from 60s to 5 minutes**: a safety net. On a healthy install the server is up in about a second; 300 seconds is there for pathological cold boots (very large data dirs, contested Apple Silicon Gatekeeper verification on unsigned binaries, etc.) and should never be hit in normal use.
460
-
461
- ### v1.5.50 Highlights
462
-
463
- - **Fix: opencode-web remote instances no longer fail with `EACCES`**: SwarmClaw used to send the local workspace path (e.g. `/root/.swarmclaw/workspace`) as a `directory=` query parameter on every opencode-web request. Remote opencode-web instances tried to `lstat` that path and rejected the call. The provider now auto-detects local vs. remote from the endpoint hostname (`localhost`, `127.0.0.1`, `::1`, `0.0.0.0`) and only sends `directory=` when the endpoint is local. Thanks to [@SteamedFish](https://github.com/SteamedFish) for the detailed root-cause writeup in [#45](https://github.com/swarmclawai/swarmclaw/issues/45).
464
-
465
- ### v1.5.49 Highlights
466
-
467
- - **Autonomous Missions**: a new first-class concept for long-running, goal-driven agent work. Hand your agent team a goal on Friday, come back Monday to see what they shipped. Each mission carries a title, a natural-language objective, bulleted success criteria, hard budgets (USD, tokens, turns, wallclock), periodic markdown reports, and a full milestone timeline. Missions drive any session through the existing heartbeat pipeline, so delegation to Claude Code, Codex, OpenCode, Cursor, Droid, Goose, Qwen, or native SwarmClaw agents all work without changes.
468
- - **Budget enforcement in the run pipeline**: `enqueueSessionRun` now consults the mission's budget before every autonomous turn. When any cap is hit the mission transitions to `budget_exhausted`, the queue drains, and a final report fires. Warn thresholds (default 50% / 80% / 95% of each cap) emit `budget_warn` milestones exactly once each.
469
- - **Scheduler tick from heartbeat**: `runMissionScheduler()` fires every heartbeat tick, independent of the active-hours window, so wallclock budgets and periodic reports still fire overnight. Report cadence is configurable per mission; reports land as in-app notifications today and ship as Slack/Discord/audio in a follow-up.
470
- - **`/missions` dashboard**: new page with a live mission list, status pills, four-axis budget gauges, a scrollable milestone timeline, a reports drawer, and start / pause / cancel / mark-complete / generate-report-now controls.
471
- - **CLI commands**: `swarmclaw missions list|get|create|update|delete|control|reports|report-now|events`. Create a mission, start it, and watch the timeline from the terminal or CI.
472
- - **New storage collections**: `agent_missions`, `mission_reports`, and `agent_mission_events`. The legacy deprecated `missions` table is left untouched so nothing in existing installs is disturbed.
473
-
474
- ### v1.5.48 Highlights
475
-
476
- - **SwarmDock MCP preset now points at the hosted endpoint**: *MCP Servers → Quick Setup → SwarmDock* is pre-filled with `streamable-http` transport pointed at `https://swarmdock-api.onrender.com/mcp` and a ready-to-edit `Authorization: Bearer <key>` header template. Users no longer need to run `npx swarmdock-mcp` locally — the SwarmDock team hosts the MCP server in-process on the existing API service. First-time setup (browser keygen + agent registration) lives at [swarmdock.ai/mcp/connect](https://www.swarmdock.ai/mcp/connect).
477
- - **McpPreset gains `url` and `headersTemplate`**: `applyPreset` now prefills the URL input and the Headers textarea in addition to command/args/env, so remote presets can ship complete configs.
478
- - **Skills doc refresh**: the `swarmclaw` skill's MCP Servers section points to the hosted flow instead of the prior stdio instructions.
479
-
480
- ### v1.5.47 Highlights
481
-
482
- - **MCP injection for GitHub Copilot CLI and OpenAI Codex CLI agents**: agents using the `copilot-cli` or `codex-cli` providers now run with their assigned MCP servers attached at runtime. Copilot CLI receives the servers via `--additional-mcp-config @<tempfile>`; Codex CLI gets per-session `[mcp_servers.*]` TOML sections appended to a scoped `config.toml`. Stdio transports (command, args, env, cwd) and SSE / streamable-http transports (url, headers) are both supported. Skills assigned to the agent continue to be injected via the system prompt.
483
- - **Skills and MCP panel visible for copilot-cli and codex-cli in the agent editor**: the Advanced Settings section now opens for these two providers so you can attach skills and MCP servers from the UI. Routing, memory, and voice panels stay hidden since these providers are worker-only.
484
- - **Codex CLI approval policy change**: Codex CLI sessions now launch with `--dangerously-bypass-approvals-and-sandbox` instead of `--full-auto`. The old flag silently cancels MCP tool calls via Codex's approval gate, which is why MCP tool results were not landing. SwarmClaw itself runs in its own sandbox, so Codex's additional sandbox was not load-bearing, but be aware of the change if you were relying on it for a specific agent.
485
- - **Under the hood**: `~/.codex-sessions/<session.id>/` replaces `/tmp/swarmclaw-codex-*` as the per-session Codex config directory because Codex refuses to create helper binaries under `/tmp`. The Playwright MCP proxy now passes an explicit `cwd: process.cwd()` when spawning, so it no longer crashes with `uv_cwd ENOENT` when the server is restarted after a directory move.
486
- - **Exa as a new web search provider**: Settings > Web Search gains an Exa option alongside Tavily, Brave, SearXNG, DuckDuckGo, Google, and Bing. Exa uses neural search with AI-generated summaries and falls back to highlights, then raw text when summaries are unavailable. Configure the key via the UI, the `EXA_API_KEY` environment variable, or the secrets store. Requests carry an `x-exa-integration: swarmclaw` tracking header so usage attributed to SwarmClaw is visible to Exa.
487
-
488
- Thanks to [@borislavnnikolov](https://github.com/borislavnnikolov) and [@tgonzalezc5](https://github.com/tgonzalezc5) for the contributions.
489
-
490
- ### v1.5.46 Highlights
491
-
492
- - **Custom base URL for built-in OpenAI and Anthropic providers**: the Endpoint field in provider settings now works for the built-in OpenAI and Anthropic providers (marked as `optionalEndpoint`). Point them at a proxy, gateway, or self-hosted endpoint and the URL persists, auto-resolves on connection test, and flows through both the live chat path and the LangGraph agent path (`ChatAnthropic` now receives `anthropicApiUrl`). Existing installs with no custom URL keep using the defaults.
493
- - **Test-model selector in provider settings**: when you hit "Test Connection", a new dropdown lets you pick a specific model (for example `gpt-4.1-mini` or `claude-haiku-4-5`) or leave it on Auto-detect. Useful for verifying a specific model is reachable on a given endpoint.
494
- - **Auto-resolution of credentials and endpoints in the connection test**: the test route now looks up the saved credential and base URL for the provider when they are not explicitly supplied, so the provider sheet's "Test" button works without needing to replay config.
495
- - **Anthropic streaming refactor**: the streaming handler moved from Node's `https.request()` to `fetch()`. Same behavior, cleaner cancellation, and it now respects `session.apiEndpoint` as a full base URL instead of a hostname.
496
- - **Connection test body**: Ollama and OpenAI-compatible test requests now send `max_completion_tokens` instead of the legacy `max_tokens`, matching current OpenAI conventions and working correctly with reasoning models that reject `max_tokens`.
497
-
498
- Thanks to [@Llugaes](https://github.com/Llugaes) for the contribution.
499
-
500
- ### v1.5.45 Highlights
501
-
502
- - **SwarmVault MCP preset**: a new "SwarmVault" Quick Setup chip in the MCP server sheet pre-fills `npx -y @swarmvaultai/cli mcp` over `stdio` and prompts for the vault directory. One click registers a SwarmVault knowledge vault as an MCP server; agents pick it up via the existing per-agent MCP server selector. SwarmVault docs: https://swarmvault.ai
503
- - **`cwd` on stdio MCP servers**: `McpServerConfig` now has an optional `cwd` field. The MCP client passes it through to `StdioClientTransport` so servers that discover config from the working directory (SwarmVault, anything that reads from `cwd`-relative files) work correctly. Existing MCP servers are untouched (the field is optional and defaults to the SwarmClaw process cwd, which was the prior behaviour).
504
- - **Bundled `swarmvault` skill**: ships at `skills/swarmvault/SKILL.md` and is auto-discovered alongside the other bundled skills. Captures the schema-first / graph-query-first conventions (read `swarmvault.schema.md` before compile or query work, treat `raw/` as immutable, prefer `graph query|path|explain` over grep, preserve `page_id` / `source_ids` / `node_ids` / `freshness` / `source_hashes` frontmatter, save high-value answers to `wiki/outputs/`). Pin it on any agent that talks to a SwarmVault vault. Optional and decoupled from the MCP integration.
505
-
506
- ### v1.5.44 Highlights
507
-
508
- - **Model lists refreshed across every provider**: dropdowns now lead with the April-2026 flagship models instead of mid-2025 names. OpenAI goes to GPT-5.4 / 5.4-mini / 5.4-nano / 5.3 / o3-mini. Google and Gemini CLI lead with Gemini 3.1 Pro, Gemini 3 Flash, and 3.1 Flash-Lite, keeping 2.5 as a legacy fallback. xAI jumps from Grok 3 to Grok 4 plus the Grok 4 / 4.1 Fast reasoning and non-reasoning variants. Groq drops the deprecated `deepseek-r1-distill-llama-70b` and leads with Llama 4 Maverick, Llama 4 Scout, Kimi K2, and gpt-oss 120b/20b. Mistral moves to Magistral 1.2, Devstral 2, Codestral, and Mistral Small 4. Fireworks / Nebius / DeepInfra now lead with DeepSeek V3.2, Kimi K2.5, and Qwen 3 235B instead of the older R1-0528 checkpoint. Anthropic and Claude CLI reorder Opus 4.6 / Sonnet 4.6 / Haiku 4.5 newest-first. OpenCode Web refreshes its `providerID/modelID` seed list.
509
- - **OpenRouter default set expanded**: was one model (`openai/gpt-4.1-mini`). Now ten flagship routes including `openrouter/auto`, Claude 4.6 Opus / Sonnet / Haiku, GPT-5.4, Gemini 3.1 Pro / 3 Flash, Grok 4, DeepSeek V3.2, and Llama 4 Maverick. Much better first-run experience for the "provider that routes to every other provider".
510
- - **`DEFAULT_AGENTS` models refreshed**: 11 starter-agent models updated to match the new flagship lineups (OpenAI → GPT-5.4, xAI → Grok 4, Google / Gemini CLI → Gemini 3.1 Pro, Groq → Llama 4 Maverick, Fireworks / Nebius / DeepInfra → DeepSeek V3.2, OpenCode Web / Copilot CLI → Claude Sonnet 4.6, OpenRouter → Claude Sonnet 4.6). Starter agents created from the setup wizard now default to the right model out of the box.
511
- - **Starter-agent tool bundles now include `droid_cli` and `copilot_cli`**: these delegation backends were added in v1.5.37 and v1.5.3 respectively but never made it into `STARTER_AGENT_TOOLS` / `BUILDER_AGENT_TOOLS`. Every starter kit (Sidekick, Researcher, Builder, Reviewer, Operator, OpenClaw fleet) now picks them up on new workspace creation.
512
- - **DeepSeek note**: `deepseek-chat` and `deepseek-reasoner` remain the recommended model names — they are stable aliases that auto-track the current `V3.2` weights. No action required.
513
- - **Registry sanity test**: added `provider-models.test.ts` which asserts every provider declares a non-empty deduplicated models array, matching metadata keys, and a working `handler.streamChat`. Guards against future copy-paste regressions in the registry.
514
-
515
- ### v1.5.43 Highlights
516
-
517
- - **`/api/version` no longer 500s in Docker**: the route used to shell out to `git` at runtime, which fails in the production image because `.git/` is not copied. The route now returns 200 with `{ source: 'package', version }` from `package.json` when git metadata is unavailable, and `{ source: 'git', version, commit, ... }` when it is. `/api/version/update` short-circuits on Docker-style installs with a clear `no_git_metadata` reason instead of an opaque 500. ([#41](https://github.com/swarmclawai/swarmclaw/issues/41) Bug 1, reported by [@SteamedFish](https://github.com/SteamedFish).)
518
- - **Daemon reclaims stale `daemon-primary` leases on container restart**: when the previous container died holding the SQLite-backed lease, the new container previously waited up to the full 120 s TTL before the daemon could start. The successor now parses the recorded owner pid, probes it with `process.kill(pid, 0)`, and reclaims the lease immediately when the prior owner is provably dead on this host. When the owner is genuinely alive (or when the recorded host is ambiguous, such as multi-pod Kubernetes), behaviour is unchanged but a single deferred retry is scheduled just past the TTL so the daemon comes up automatically rather than waiting for the next API call. ([#41](https://github.com/swarmclawai/swarmclaw/issues/41) Bug 2.)
519
- - **Subprocess daemon fallback fails soft in Docker**: when `resolveDaemonRuntimeEntry()` cannot find `src/lib/server/daemon/daemon-runtime.ts` (the file is intentionally not in the standalone build), `ensureDaemonProcessRunning()` now logs a one-shot warning and returns `false` instead of throwing into the API handler. The in-process daemon path (with the Bug 2 fix) is the production path in Docker. ([#41](https://github.com/swarmclawai/swarmclaw/issues/41) Bug 3.)
520
- - **`CONTRIBUTING.md`**: dropped the broken reference to `AGENTS.md`. That file is `.gitignore`'d and not visible to external contributors. The single canonical project-conventions document is `CLAUDE.md`.
521
-
522
- ### v1.5.42 Highlights
523
-
524
- - **New `opencode-web` provider — connect to remote OpenCode HTTP servers** ([#40](https://github.com/swarmclawai/swarmclaw/issues/40), requested by [@SteamedFish](https://github.com/SteamedFish)): point an agent at any host running `opencode serve` or `opencode web` (default port `4096`). Supports HTTPS endpoints, HTTP Basic Auth (encode credentials as `username:password` in the API key field; bare passwords default the username to `opencode`), automatic OpenCode session reuse across chat turns, and per-session workspace isolation via `?directory=...`. Models are entered as `providerID/modelID` (e.g. `anthropic/claude-sonnet-4-5`). The existing `opencode-cli` provider is unchanged.
525
- - **New `CONTRIBUTING.md`**: short, scannable guide covering bug reports, feature requests, PR expectations, commit conventions, and where to look in the codebase. Models the gold-standard examples after issues #39 and #40.
526
- - **`GET /api/memory/:id` now returns a single entry by default**: previously it eagerly traversed linked memories and returned an array, which broke naive callers that expected a single object per REST convention. Linked traversal is now opt-in via `?depth=N` or `?envelope=true`.
527
-
528
- ### v1.5.41 Highlights
529
-
530
- - **Moonshot / Kimi compatibility — duplicate `files` tool name fixed**: any agent with the default `files` extension was sending two tools both literally named `files` to the LLM. Most providers tolerated the duplicate; Moonshot's strict tool-schema validation rejected it with `MoonshotException - function name files is duplicated` ([#39](https://github.com/swarmclawai/swarmclaw/issues/39), reported by [@SteamedFish](https://github.com/SteamedFish)). Three fixes: the v2 file builder is now correctly gated on `files_v2` (not `files`), it registers under the matching capability key, and the session-tools assembler now shares a single dedup Set across native, CRUD, and extension phases so any future name collision is rejected with a clear warning instead of a silent double-register.
531
-
532
- ### v1.5.40 Highlights
533
-
534
- - **Current-thread recall routing**: the message classifier now emits four explicit flags (`isCurrentThreadRecall`, `isGreeting`, `isAcknowledgement`, `isMemoryWriteIntent`) so the chat router stops treating in-thread pronouns ("your last reply", "both answers", "what I just said") as durable-memory queries. Previously small OSS models (`devstral-small-2:24b` and similar) would run `memory_search` for these, come back empty, and truthfully report "no memories found" even when the answer was three messages up.
535
- - **`memory_search` short-circuits thread-recall queries**: when the search query itself contains phrases like "just", "last reply", "my last", "both answers", the tool now returns a redirect pointing the model back to the visible chat history instead of executing a pointless vector search. Explicit cross-session phrasing ("yesterday", "last week", "in a previous conversation") still runs the normal search path.
536
- - **Explicit Routing Matrix in the system prompt**: spells out the boundary between "read the thread above" and "call a memory tool" in plain language, so routing doesn't depend on the model extrapolating a terse rule. Memory-tool lines are now tagged `(not this thread)` so the distinction is unmissable.
537
- - **Tool-summary retry threshold tightened**: the "trivial response" threshold used to decide whether to force a redundant `tool_summary` continuation dropped from 150 → 80 characters. A 119-char response like "I wrote X, stored Y, and confirmed both." is substantive; the old threshold forced the model to re-stream the same answer twice.
538
- - **Classifier timeout raised to 10 s**: 2 s was too tight for Ollama Cloud with a fully-configured agent (observed 4–6 s calls). Result caching means the latency tax only applies to first-seen messages.
539
- - **Reflection memories dedup across runs**: the supervisor reflection writer now compares candidate notes against recent (last 7 days) reflection memories for the same agent and skips ones that have already been stored, stopping the ~7-per-turn rediscovery churn on top of the within-run dedup shipped in v1.5.38.
540
-
541
450
  Older releases: https://swarmclaw.ai/docs/release-notes
542
451
 
543
452
  - GitHub releases: https://github.com/swarmclawai/swarmclaw/releases
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.5.57",
3
+ "version": "1.5.59",
4
4
  "description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
5
5
  "main": "electron-dist/main.js",
6
6
  "license": "MIT",
@@ -1,19 +1,21 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { EVAL_SCENARIOS } from '@/lib/server/eval/scenarios'
2
+ import { EVAL_SCENARIOS, getSuiteScenarios } from '@/lib/server/eval/scenarios'
3
3
 
4
4
  export async function GET(req: Request) {
5
5
  const { searchParams } = new URL(req.url)
6
6
  const category = searchParams.get('category')
7
+ const suite = searchParams.get('suite')
7
8
 
8
- const scenarios = category
9
- ? EVAL_SCENARIOS.filter((s) => s.category === category)
10
- : EVAL_SCENARIOS
9
+ let scenarios = EVAL_SCENARIOS
10
+ if (suite) scenarios = getSuiteScenarios(suite)
11
+ if (category) scenarios = scenarios.filter((s) => s.category === category)
11
12
 
12
13
  return NextResponse.json(
13
14
  scenarios.map((s) => ({
14
15
  id: s.id,
15
16
  name: s.name,
16
17
  category: s.category,
18
+ suite: s.suite ?? 'core',
17
19
  description: s.description,
18
20
  tools: s.tools,
19
21
  timeoutMs: s.timeoutMs,
@@ -6,6 +6,7 @@ import { errorMessage } from '@/lib/shared-utils'
6
6
  const SuiteSchema = z.object({
7
7
  agentId: z.string().min(1),
8
8
  categories: z.array(z.string()).optional(),
9
+ suite: z.string().min(1).optional(),
9
10
  })
10
11
 
11
12
  export async function POST(req: Request) {
@@ -19,7 +20,10 @@ export async function POST(req: Request) {
19
20
  )
20
21
  }
21
22
 
22
- const result = await runEvalSuite(parsed.data.agentId, parsed.data.categories)
23
+ const result = await runEvalSuite(parsed.data.agentId, {
24
+ categories: parsed.data.categories,
25
+ suite: parsed.data.suite,
26
+ })
23
27
  return NextResponse.json(result)
24
28
  } catch (err: unknown) {
25
29
  return NextResponse.json(
@@ -0,0 +1,19 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { EVAL_SCENARIOS, getSuiteScenarios, listSuites } from '@/lib/server/eval/scenarios'
3
+
4
+ export async function GET() {
5
+ const suites = listSuites()
6
+ const summary = suites.map((name) => {
7
+ const scenarios = name === 'core' ? EVAL_SCENARIOS.filter(s => !s.suite || s.suite === 'core') : getSuiteScenarios(name)
8
+ return {
9
+ name,
10
+ count: scenarios.length,
11
+ maxScore: scenarios.reduce(
12
+ (sum, s) => sum + s.scoringCriteria.reduce((a, c) => a + c.weight, 0),
13
+ 0,
14
+ ),
15
+ categories: Array.from(new Set(scenarios.map(s => s.category))),
16
+ }
17
+ })
18
+ return NextResponse.json(summary)
19
+ }
@@ -0,0 +1,79 @@
1
+ import {
2
+ isShareLinkActive,
3
+ loadShareLinkByToken,
4
+ } from '@/lib/server/sharing/share-link-repository'
5
+ import { resolveSharedEntity } from '@/lib/server/sharing/share-resolver'
6
+
7
+ export const dynamic = 'force-dynamic'
8
+
9
+ /**
10
+ * Public raw-content endpoint for shared entities. Skills return markdown so
11
+ * a second SwarmClaw instance can install via `POST /api/skills/import`
12
+ * without any auth handshake. Missions and sessions return plain-text
13
+ * summaries sized for quick sharing.
14
+ *
15
+ * Returns 404 for missing, expired, or revoked tokens to avoid leaking
16
+ * shape information to a probe.
17
+ */
18
+ export async function GET(_req: Request, ctx: { params: Promise<{ token: string }> }) {
19
+ const { token } = await ctx.params
20
+ const link = loadShareLinkByToken(token)
21
+ if (!link || !isShareLinkActive(link)) {
22
+ return new Response('Not found', { status: 404 })
23
+ }
24
+ const payload = resolveSharedEntity(link)
25
+ if (!payload) {
26
+ return new Response('Not found', { status: 404 })
27
+ }
28
+
29
+ if (payload.kind === 'skill') {
30
+ return new Response(payload.content, {
31
+ status: 200,
32
+ headers: {
33
+ 'content-type': 'text/markdown; charset=utf-8',
34
+ 'cache-control': 'public, max-age=60',
35
+ 'x-skill-name': encodeURIComponent(payload.name),
36
+ },
37
+ })
38
+ }
39
+
40
+ if (payload.kind === 'mission') {
41
+ const lines: string[] = []
42
+ lines.push(`# ${payload.title}`, '')
43
+ if (payload.goal) lines.push(payload.goal, '')
44
+ if (payload.successCriteria.length > 0) {
45
+ lines.push('## Success criteria', '')
46
+ for (const c of payload.successCriteria) lines.push(`- ${c}`)
47
+ lines.push('')
48
+ }
49
+ if (payload.milestones.length > 0) {
50
+ lines.push('## Milestones', '')
51
+ for (const m of payload.milestones) {
52
+ lines.push(`- ${new Date(m.at).toISOString().slice(0, 19).replace('T', ' ')}: ${m.note}`)
53
+ }
54
+ lines.push('')
55
+ }
56
+ return new Response(lines.join('\n'), {
57
+ status: 200,
58
+ headers: {
59
+ 'content-type': 'text/markdown; charset=utf-8',
60
+ 'cache-control': 'public, max-age=60',
61
+ },
62
+ })
63
+ }
64
+
65
+ // session
66
+ const lines: string[] = []
67
+ lines.push(`# ${payload.name}`, '')
68
+ if (payload.agentName) lines.push(`Agent: ${payload.agentName}`, '')
69
+ for (const m of payload.messages) {
70
+ lines.push(`### ${m.role}`, '', m.text, '')
71
+ }
72
+ return new Response(lines.join('\n'), {
73
+ status: 200,
74
+ headers: {
75
+ 'content-type': 'text/markdown; charset=utf-8',
76
+ 'cache-control': 'public, max-age=60',
77
+ },
78
+ })
79
+ }
@@ -0,0 +1,37 @@
1
+ import { NextResponse } from 'next/server'
2
+ import {
3
+ isShareLinkActive,
4
+ loadShareLinkByToken,
5
+ } from '@/lib/server/sharing/share-link-repository'
6
+ import { resolveSharedEntity } from '@/lib/server/sharing/share-resolver'
7
+
8
+ export const dynamic = 'force-dynamic'
9
+
10
+ /**
11
+ * Public, unauthenticated fetch of a shared entity by token.
12
+ *
13
+ * Returns the scrubbed payload shape (secrets and credentials are never
14
+ * loaded into the resolver). A 404 is returned for unknown, expired, or
15
+ * revoked tokens to avoid leaking validity to a probe.
16
+ */
17
+ export async function GET(_req: Request, ctx: { params: Promise<{ token: string }> }) {
18
+ const { token } = await ctx.params
19
+ const link = loadShareLinkByToken(token)
20
+ if (!link || !isShareLinkActive(link)) {
21
+ return NextResponse.json({ error: 'not_found' }, { status: 404 })
22
+ }
23
+ const payload = resolveSharedEntity(link)
24
+ if (!payload) {
25
+ return NextResponse.json({ error: 'entity_missing' }, { status: 404 })
26
+ }
27
+ return NextResponse.json({
28
+ share: {
29
+ id: link.id,
30
+ entityType: link.entityType,
31
+ label: link.label,
32
+ createdAt: link.createdAt,
33
+ expiresAt: link.expiresAt,
34
+ },
35
+ payload,
36
+ })
37
+ }
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server'
2
+ import {
3
+ loadShareLinkById,
4
+ revokeShareLink,
5
+ deleteShareLink,
6
+ } from '@/lib/server/sharing/share-link-repository'
7
+
8
+ export const dynamic = 'force-dynamic'
9
+
10
+ export async function GET(_req: Request, ctx: { params: Promise<{ id: string }> }) {
11
+ const { id } = await ctx.params
12
+ const link = loadShareLinkById(id)
13
+ if (!link) return NextResponse.json({ error: 'not_found' }, { status: 404 })
14
+ return NextResponse.json(link)
15
+ }
16
+
17
+ export async function DELETE(req: Request, ctx: { params: Promise<{ id: string }> }) {
18
+ const { id } = await ctx.params
19
+ const { searchParams } = new URL(req.url)
20
+ const hard = searchParams.get('hard') === 'true'
21
+ if (hard) {
22
+ deleteShareLink(id)
23
+ return NextResponse.json({ ok: true, deleted: true })
24
+ }
25
+ const revoked = revokeShareLink(id)
26
+ if (!revoked) return NextResponse.json({ error: 'not_found' }, { status: 404 })
27
+ return NextResponse.json(revoked)
28
+ }
@@ -0,0 +1,53 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import {
4
+ createShareLink,
5
+ listShareLinks,
6
+ type ShareEntityType,
7
+ } from '@/lib/server/sharing/share-link-repository'
8
+ import { errorMessage } from '@/lib/shared-utils'
9
+
10
+ export const dynamic = 'force-dynamic'
11
+
12
+ const MintSchema = z.object({
13
+ entityType: z.enum(['mission', 'skill', 'session']),
14
+ entityId: z.string().min(1),
15
+ expiresInSec: z.number().int().positive().nullable().optional(),
16
+ label: z.string().trim().max(120).nullable().optional(),
17
+ })
18
+
19
+ export async function GET(req: Request) {
20
+ const { searchParams } = new URL(req.url)
21
+ const entityType = searchParams.get('entityType') as ShareEntityType | null
22
+ const entityId = searchParams.get('entityId')
23
+
24
+ let links = listShareLinks()
25
+ if (entityType) links = links.filter((l) => l.entityType === entityType)
26
+ if (entityId) links = links.filter((l) => l.entityId === entityId)
27
+
28
+ // Newest first
29
+ links.sort((a, b) => b.createdAt - a.createdAt)
30
+ return NextResponse.json(links)
31
+ }
32
+
33
+ export async function POST(req: Request) {
34
+ try {
35
+ const body: unknown = await req.json()
36
+ const parsed = MintSchema.safeParse(body)
37
+ if (!parsed.success) {
38
+ return NextResponse.json(
39
+ { error: parsed.error.issues.map((i) => i.message).join(', ') },
40
+ { status: 400 },
41
+ )
42
+ }
43
+ const link = createShareLink({
44
+ entityType: parsed.data.entityType,
45
+ entityId: parsed.data.entityId,
46
+ expiresInSec: parsed.data.expiresInSec ?? null,
47
+ label: parsed.data.label ?? null,
48
+ })
49
+ return NextResponse.json(link)
50
+ } catch (err) {
51
+ return NextResponse.json({ error: errorMessage(err) }, { status: 500 })
52
+ }
53
+ }
@@ -0,0 +1,94 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadUsage, loadSessions } from '@/lib/server/storage'
3
+ import type { UsageRecord } from '@/types'
4
+
5
+ export const dynamic = 'force-dynamic'
6
+
7
+ type SessionSnapshot = {
8
+ id?: string
9
+ agentId?: string
10
+ createdAt?: number
11
+ lastActiveAt?: number
12
+ messages?: unknown[]
13
+ }
14
+
15
+ interface LiveUsage {
16
+ sessionId: string
17
+ records: number
18
+ totalTokens: number
19
+ inputTokens: number
20
+ outputTokens: number
21
+ estimatedCost: number
22
+ firstAt: number | null
23
+ lastAt: number | null
24
+ wallclockMs: number
25
+ turns: number
26
+ }
27
+
28
+ function summarize(sessionId: string, records: UsageRecord[], session: SessionSnapshot | undefined): LiveUsage {
29
+ let totalTokens = 0
30
+ let inputTokens = 0
31
+ let outputTokens = 0
32
+ let estimatedCost = 0
33
+ let firstAt: number | null = null
34
+ let lastAt: number | null = null
35
+
36
+ for (const r of records) {
37
+ totalTokens += r.totalTokens || 0
38
+ inputTokens += r.inputTokens || 0
39
+ outputTokens += r.outputTokens || 0
40
+ estimatedCost += r.estimatedCost || 0
41
+ const ts = r.timestamp || 0
42
+ if (ts > 0) {
43
+ if (firstAt === null || ts < firstAt) firstAt = ts
44
+ if (lastAt === null || ts > lastAt) lastAt = ts
45
+ }
46
+ }
47
+
48
+ const turns = Array.isArray(session?.messages) ? session!.messages!.length : records.length
49
+ const wallStart = session?.createdAt ?? firstAt ?? 0
50
+ const wallEnd = session?.lastActiveAt ?? lastAt ?? Date.now()
51
+ const wallclockMs = wallStart > 0 ? Math.max(0, wallEnd - wallStart) : 0
52
+
53
+ return {
54
+ sessionId,
55
+ records: records.length,
56
+ totalTokens,
57
+ inputTokens,
58
+ outputTokens,
59
+ estimatedCost: Math.round(estimatedCost * 10000) / 10000,
60
+ firstAt,
61
+ lastAt,
62
+ wallclockMs,
63
+ turns,
64
+ }
65
+ }
66
+
67
+ export async function GET(req: Request) {
68
+ const { searchParams } = new URL(req.url)
69
+ const sessionId = searchParams.get('sessionId')?.trim()
70
+
71
+ const usage = loadUsage() as Record<string, UsageRecord[]>
72
+ const sessions = loadSessions() as Record<string, SessionSnapshot>
73
+
74
+ if (sessionId) {
75
+ const records = usage[sessionId] ?? []
76
+ const session = sessions[sessionId]
77
+ return NextResponse.json(summarize(sessionId, records, session))
78
+ }
79
+
80
+ // Without sessionId, return the 10 most recently active sessions
81
+ const ids = Object.keys(usage)
82
+ const recent = ids
83
+ .map((id) => {
84
+ const records = usage[id] ?? []
85
+ const last = records.reduce((m, r) => Math.max(m, r.timestamp || 0), 0)
86
+ return { id, last }
87
+ })
88
+ .sort((a, b) => b.last - a.last)
89
+ .slice(0, 10)
90
+
91
+ return NextResponse.json(
92
+ recent.map(({ id }) => summarize(id, usage[id] ?? [], sessions[id])),
93
+ )
94
+ }