Adds a skill that installs the mnemon CLI into agent containers, giving each
agent group a persistent, queryable knowledge graph across sessions.
Mnemon stores facts (insights) with categories, importance scores, and entity
tags, and connects them with typed edges (causal, semantic, temporal, entity).
The agent can remember, recall, search, link, and forget facts — surviving
container restarts and context compaction.
Installation: drops the mnemon binary from the channels branch, creates the
per-agent-group data directory, and configures the agent's CLAUDE.md to load
the skill on every spawn.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace "full E.164, e.g. +15551234567" with plain-language guidance
mirroring the WhatsApp setup card: "start with + and your country code,
no spaces or dashes" plus a worked example. "E.164" is the technical
name for the format and means nothing to non-telecom users; the
explanation it stands in for is one sentence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After picking "Other…" from the channel picker, today's flow drops the
user straight into a free-text prompt with no way back. Replace it with
a brightSelect that offers either "Type the channel name" (existing
behavior) or "← Back to channel selection" — same back-affording pattern
the channel sub-flows already use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Teams setup is 6+ Azure steps over 30+ minutes. Today, every
"Done / Stuck / Show again" gate forces continuation; the only escape
is Ctrl-C, which kills setup entirely. Add a fourth option at each gate
that returns to the channel picker so a stuck operator can pick a
different channel without losing the rest of setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first-keyword check (`WITH` → SELECT path) was wrong for CTEs that
precede mutations (e.g. `WITH stale AS (...) DELETE FROM t WHERE ...`).
These would be routed through `db.prepare().all()` instead of executing
the mutation.
Use better-sqlite3's `stmt.reader` property, which asks SQLite's own
parser whether the statement returns data. Single mutations go through
`stmt.run()`; compound statements (which `prepare()` rejects) fall back
to `db.exec()`.
Add a regression test for WITH...DELETE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Setup deliberately avoids the sqlite3 CLI (`setup/verify.ts:5` calls
this out: "Uses better-sqlite3 directly (no sqlite3 CLI)") and never
installs or probes for the binary. Despite that, 13 skills shelled out
to `sqlite3 ...` directly, breaking on hosts where the CLI isn't
preinstalled — the same root cause as #2191 but spread across the
skill surface.
Add `scripts/q.ts`, a ~30-LOC wrapper over the `better-sqlite3` dep
that setup already installs and verifies. Default output matches
`sqlite3 -list` (pipe-separated, no header) so existing skill text
reads identically — only the binary changes. SELECT/WITH queries go
through `db.prepare().all()`; everything else (INSERT/UPDATE/DELETE,
including compound statements) goes through `db.exec()`.
Migrate every in-tree caller:
- 17 hardcoded invocations across 8 SKILL.md files (init-first-agent,
add-deltachat, add-signal, add-emacs, add-whatsapp, add-ollama-provider,
debug, add-parallel) plus add-deltachat/VERIFY.md.
- `manage-channels/SKILL.md` shows canonical SQL but never prescribed
a tool, so the assistant defaulted to `sqlite3` and silently failed.
Add a one-line wrapper hint above the SQL block.
- `migrate-v2.sh` schema/count probes (was the original #2191 case).
Replace `.tables` with `SELECT name FROM sqlite_master`.
- Document the wrapper convention in root `CLAUDE.md` under "Central DB".
Add `scripts/q.test.ts` with 6 vitest cases covering both modes,
NULL rendering, empty-result, compound mutations, and arg validation.
Wire `scripts/**/*.test.ts` into `vitest.config.ts`.
Out of scope (flagged for follow-up):
- `debug` and `add-parallel` still reference the v1-only path
`store/messages.db`. Routing through the wrapper now produces a
cleaner "no such file" error, but the surrounding sections are
v1-era throughout — a v1-content cleanup is its own PR.
- `cleanup-sessions.sh` is being addressed in #1889 (different style,
hard-fail rather than wrap); left untouched here to avoid stepping
on that author's work.
Closes#2191.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slack's profile button is in the bottom-left of the desktop sidebar (not
the top-right), and the "More" overflow icon next to "Copy member ID" is
the vertical kebab `⋮`, not the horizontal `⋯`. Match what users actually
see in Slack.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move the "Get started: …" URL above the numbered instructions and
render it in bright white so it pops against the brand-cyan body.
(Headless-only — interactive runs still auto-open the URL in a
browser, no card line.)
- Group the OAuth scope list vertically by family (im, channels,
groups, chat, users, reactions) instead of one comma-run wall.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The skill's "Assess Current State" step said only "query agent_groups,
messaging_groups, ..." without specifying columns. The `register` CLI
takes `--assistant-name "<name>"` (mentioned three times in the same
SKILL.md), but the schema column is `name`, not `assistant_name` — and
the SKILL.md never linked the two.
When the agent had to compose a SELECT against `agent_groups` from the
SKILL.md vocabulary alone, it extrapolated `--assistant-name` into a
column name and produced:
SELECT id, folder, assistant_name FROM agent_groups;
-> Error: in prepare, no such column: assistant_name
Replace the prose pointer with canonical SQL queries that match the
real schema. The `name AS assistant_name` alias preserves the familiar
term in the agent's output.
Verified locally as a drop-in: `/manage-channels` runs clean from end
to end with this version, no further inference needed.
Closes#2289
SQLite TIMESTAMP columns store UTC without a zone marker. `Date.parse`
treats timezoneless ISO strings as local time, so on any non-UTC host
every claim and processAfter looks (TZ offset) hours stale. That makes
fresh claims trip the kill-claim path on the first sweep tick — every
container gets killed within seconds of spawn.
Two affected sites in host-sweep.ts:
- decideStuckAction reads claim.status_changed and computes claimAge.
On a TZ=Europe/Madrid host (UTC+2), a claim made 5s ago looks
7205s old and exceeds CLAIM_STUCK_MS (60s).
- The orphan retry loop reads msg.processAfter and skips messages
rescheduled into the future. On the same host, future timestamps
look (TZ offset) hours in the past, so the skip is missed and
tries gets bumped on every tick.
Fix: introduce parseSqliteUtc(s) which appends "Z" only when no zone
marker is present, then call it from both sites. Behavior under
TZ=UTC is unchanged.
Verified on a production v2 install on TZ=Europe/Madrid: with the
patch applied, an idle container survived 30+ minutes without being
killed (previously: killed within 60s of spawn).
Tests: 5 new cases covering the bare/Z/+offset/invalid input matrix
and a TZ-independence check. All 19 host-sweep tests pass and tsc
clears against main.
migrate-v2.sh probes ${ONECLI_URL_CHECK}/health (with ONECLI_URL_CHECK
defaulting to http://127.0.0.1:10254, the OneCLI web port). That path
returns 404, so the detection branch never matches an already-running
OneCLI instance and the script falls through to the install path.
The web app's health endpoint is /api/health
(apps/web/src/app/api/health/route.ts) and has been since the OneCLI
repo was made public. /health was never exposed by the web on :10254
nor by the gateway on :10255 (the gateway uses /healthz).
Verified against a running OneCLI v1.21.0:
GET :10254/api/health -> 200 {"status":"ok","version":"1.21.0",...}
GET :10254/health -> 404 (Next.js fallback HTML)
GET :10255/healthz -> 200
GET :10255/health -> 400 (gateway parses non-/healthz as CONNECT)
Closes#2285
Resource-first CLI: `nc groups list`, `nc wirings get <id>`, etc.
Seven resources defined (groups, messaging-groups, wirings, users,
roles, members, sessions) with full column documentation that serves
as the single source of truth for help output and arg validation.
- CRUD helper auto-registers list/get/create/update/delete from
declarative resource definitions with generic SQL
- Custom operations for composite-PK resources (roles grant/revoke,
members add/remove)
- Access model: open (reads) / approval (writes) / hidden
- `nc help` lists resources; `nc <resource> help` shows fields
- Positional target IDs: `nc groups get <id>`
- Removed unused priority column from wirings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drop the nc MCP tool in favor of a standalone Bun CLI script at
container/agent-runner/src/cli/nc.ts. Same interface as host-side
bin/nc — all three callers (operator, Claude on host, agent in
container) now use the same nc CLI.
Container transport: writes cli_request to outbound.db (BEGIN
IMMEDIATE for seq safety), polls inbound.db for response, acks via
processing_ack. Dockerfile adds a /usr/local/bin/nc wrapper that
execs the mounted source.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add delivery action handler (cli_request) so the host dispatches CLI
commands arriving from container agents via outbound.db and writes
responses back to inbound.db. Add nc MCP tool in the agent-runner
following the ask_user_question blocking pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR #2259 (Baileys v6→v7) was merged into the channels branch instead of
main. PR #2260 was merged into main 28s later assuming v7 was already
in place. The v6 pin survived in three sites while the WhatsApp adapter
copied from origin/channels at install time was already on the v7 LID
API, breaking every fresh migrate-v2.sh run at 2c-install-whatsapp with
TS errors on remoteJidAlt/participantAlt/lid-mapping.update.
Bumps the pin to 7.0.0-rc.9 (the version v1 has been running on for
months) in:
- setup/install-whatsapp.sh
- setup/add-whatsapp.sh
- .claude/skills/add-whatsapp/SKILL.md (install instruction)
package.json + pnpm-lock.yaml are not touched here — install-whatsapp.sh
mutates them at runtime via pnpm install with the corrected pin.
Closes#2283
Today's copy says "Check that signal-cli is installed (we'll guide
you if not)" but the auto-install PR (#2281) makes that misleading —
we don't guide, we just install. Update the intro list to match what
will actually happen, and add a "no input needed for any of it" lead
so users know to expect a hands-off run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a user picks Signal in setup and signal-cli isn't on PATH, today
NanoClaw bails with a GitHub releases link and tells them to re-run.
That's a hard wall for non-technical users — GitHub releases pages
are intimidating, and the Linux native build / Java decision isn't
obvious.
Replace the bail-out with a real install: a new install-signal-cli.sh
script that does `brew install signal-cli` on macOS or downloads the
native Linux release into ~/.local/bin (no Java, no sudo). Wired into
ensureSignalCli with a spinner; probe again after, fall back to the
original manual-install copy if anything fails.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve conflict in src/index.ts shutdown sequence — keep both
stopCliServer() from nc-cli and try/finally + resetCircuitBreaker()
from main.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>