Commit Graph

1290 Commits

Author SHA1 Message Date
exe.dev user 1d73b2986a feat: add migrate-v2.sh — standalone v1 → v2 migration script
New entry point: `bash migrate-v2.sh` from the v2 checkout.
Replaces the old setup-embedded migration flow with a standalone
4-phase script + rewritten Claude skill for the interactive parts.

Phase 0: Bootstrap (Node/pnpm/deps via setup.sh) + find v1
Phase 1: Core state (env, DB, groups, sessions, tasks)
Phase 2: Channels (clack multiselect, auth copy, code install)
Phase 3: Infrastructure (OneCLI, auth, Docker, skills, container build)
Service switchover: stop v1 → start v2 → test → keep or revert
Phase 4: Handoff → exec claude "/migrate-from-v1"

The skill handles: owner seeding, access policy, CLAUDE.local.md
cleanup, container config validation, fork customization porting.

Key fixes found during testing:
- triggerToEngage: requires_trigger=0 must override non-empty pattern
- unknown_sender_policy defaults to 'public' (strict drops all msgs
  before owner is seeded)
- Service revert must stop v2 (parse unit name from step log, not
  early tsx one-liner that can fail)
- Session continuity: copy JSONL from -workspace-group/ to
  -workspace-agent/ and write continuation:claude into outbound.db
- container_config.additionalMounts written directly to container.json
  (same shape in v1 and v2)
- EXIT trap writes handoff.json; explicit write_handoff before exec

Includes migrate-v2-reset.sh for dev iteration and docs/migration-dev.md
for testing/debugging reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 20:13:38 +00:00
gabi-simons 36e731c02d Merge branch 'main' into feat/migrate-from-v1
Resolve import conflict in setup/auto.ts — keep runMigrateV1 import,
deduplicate runWindowedStep and getLaunchdLabel/getSystemdUnit imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-01 04:52:41 +00:00
github-actions[bot] 8c962d3f73 chore: bump version to 2.0.23 2026-04-30 23:00:24 +00:00
exe.dev user 28c38ae28b fix(container): pin vercel to 52.2.1 to dodge broken 53.0.1 publish
vercel@53.0.1 declares a dep on @vercel/static-build@2.9.22 which is not
published on npm (only 2.9.21 exists), breaking every fresh container
build that resolves vercel@latest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:00:02 +00:00
github-actions[bot] 7ac8dd0f6d docs: update token count to 139k tokens · 69% of context window 2026-04-30 22:28:25 +00:00
gavrielc 7814e45570 Merge pull request #2001 from Hinotoi-agent/fix/outbox-path-confinement
[security] fix(container): prevent host file read/delete via container-controlled outbox paths
2026-05-01 01:28:07 +03:00
gavrielc fc3c11b6b9 fix(session-manager): apply outbox path-confinement to inbound attachments
Mirrors the four defenses on the outbound side onto extractAttachmentFiles:

  1. Reject unsafe messageId via isSafeAttachmentName before any inbox path
     is built. WhatsApp passes msg.key.id through raw and that field is
     client generated, so a peer can craft it; future end to end encrypted
     adapters will have the same property.
  2. lstatSync on the inbox dir refuses a pre placed symlink before
     mkdirSync would silently follow it.
  3. realpathSync + isPathInside contains the resolved dir under the
     session inbox root.
  4. writeFileSync uses the wx flag so a pre placed symlink at the file
     path is refused atomically by the kernel; EEXIST surfaces as a
     logged skip.

Threat: the session dir is mounted writable into the container at
/workspace, so a compromised agent can pre place inbox/<future msgId>/
as a symlink and wait for a chat message with a matching id to redirect
the host write. The four guards together close that window.

Consolidates with the existing isSafeAttachmentName helper from
attachment-safety.ts rather than introducing a duplicate basename
validator inside session-manager.

Co-Authored-By: Daisuke Tsuji <dim0627@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 01:27:09 +03:00
hinotoi-agent 852009dcb1 fix(container): confine outbound attachment paths 2026-05-01 01:27:09 +03:00
gavrielc 212281ba8e Merge pull request #2055 from dooha333/pr/setup-local-bin-path
fix(setup): inject ~/.local/bin into PATH so post-install onecli is reachable
2026-05-01 01:20:07 +03:00
gavrielc 6db6bf9c40 Merge branch 'main' into pr/setup-local-bin-path 2026-05-01 01:19:58 +03:00
github-actions[bot] 8977f0d0be chore: bump version to 2.0.22 2026-04-30 21:57:45 +00:00
gavrielc d13f338af9 Merge pull request #2114 from robbyczgw-cla/fix/poll-loop-prescripts-on-followups
fix(poll-loop): apply pre-task scripts to follow-up injections too
2026-05-01 00:57:34 +03:00
gavrielc 5ab1a2733c review: catch follow-up poll errors + re-check done before push
Two fixes on top of the follow-up pre-task-script work:

1. The void async IIFE inside the interval handler had no catch, so a
   throw from the dynamic import or applyPreTaskScripts escaped as an
   unhandled rejection — terminating the container. The initial-batch
   path is wrapped by processQuery's outer try/catch; the follow-up
   path needs its own. Now logs the error and lets the next tick retry.

2. Re-check `done` immediately before query.push. The flag can flip
   true while applyPreTaskScripts is awaited (outer stream finishes
   during the script execution); without the re-check we'd push into a
   closed query. Claimed messages get released by the host's
   processing-claim sweep — same recovery posture as the rest of the
   poller.

Co-Authored-By: Michael Zazon <mzazon@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:55:46 +03:00
gavrielc 7d29888e59 Merge branch 'main' into fix/poll-loop-prescripts-on-followups 2026-05-01 00:34:45 +03:00
github-actions[bot] 58d875b3c3 chore: bump version to 2.0.21 2026-04-30 21:31:18 +00:00
gavrielc 3e7fea0fde Merge pull request #2142 from mnolet/fix/schedule-task-routing
fix(scheduling): include routing in schedule_task content JSON
2026-05-01 00:31:04 +03:00
gavrielc d418f830db Merge branch 'main' into fix/schedule-task-routing 2026-05-01 00:30:11 +03:00
Mohamed Khedr 32daf607c1 Merge branch 'main' into pr/setup-local-bin-path 2026-04-30 21:57:55 +01:00
gavrielc 524ac221e1 Merge pull request #2111 from qwibitai/setup-scratch-agent-cleanup
feat(setup): delete scratch agent after ping-pong, simplify flow
2026-04-30 23:20:54 +03:00
gavrielc 69b4225916 Merge branch 'main' into setup-scratch-agent-cleanup 2026-04-30 23:20:32 +03:00
gavrielc 3d6a9b74f3 review: surface ping-test cleanup failures + restore copy
Routes the post-ping `_ping-test` cleanup through `spawnQuiet` +
`setupLog.step` so a non-zero exit from `delete-cli-agent.ts` lands
in `logs/setup-steps/cleanup-cli-agent.log` and the progression log,
and prints a one-line warn to the user. Previously the spawnSync was
fire-and-forget with `stdio: 'ignore'`, leaving an orphan agent group
silently if cleanup failed.

Restores the original copy on the cli-agent step labels, the ping
explainer paragraph, and the post-ping spinner stop line — those
copy changes are out of scope for this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:16:34 +03:00
gavrielc dcc625f2b8 Merge pull request #2155 from qwibitai/setup-root-warning-v2
Add root user warning gate to Linux setup
2026-04-30 23:09:36 +03:00
gavrielc 99a8559b14 Merge remote-tracking branch 'origin/main' into setup-root-warning-v2
# Conflicts:
#	setup/auto.ts
2026-04-30 23:07:38 +03:00
gavrielc 3dc772cca0 Merge branch 'main' into setup-scratch-agent-cleanup 2026-04-30 23:05:09 +03:00
gavrielc 5ebad280ce Merge pull request #1502 from Koshkoshinsk/docs/pr-hygiene-check
Add PR hygiene check to CLAUDE.md and contributing guidelines
2026-04-30 23:00:43 +03:00
gavrielc d73b9e14ad Merge branch 'main' into docs/pr-hygiene-check 2026-04-30 23:00:10 +03:00
gavrielc 681a5b51c8 Merge pull request #2157 from qwibitai/setup-lazy-env-reuse
refactor(setup): per-step env var reuse instead of upfront all-or-nothing
2026-04-30 22:59:03 +03:00
gavrielc 8e45f4e964 Merge branch 'main' into setup-lazy-env-reuse 2026-04-30 22:58:53 +03:00
gavrielc eb9a5d706d Merge branch 'main' into setup-scratch-agent-cleanup 2026-04-30 22:54:48 +03:00
github-actions[bot] 46cd91c306 docs: update token count to 138k tokens · 69% of context window 2026-04-30 19:54:27 +00:00
github-actions[bot] 0218159ef0 chore: bump version to 2.0.20 2026-04-30 19:54:21 +00:00
gavrielc 3ee07effea Merge pull request #2105 from qwibitai/feat/channel-approval-flow
feat: richer channel-approval flow with agent selection and free-text naming
2026-04-30 22:54:08 +03:00
gavrielc 462b9581b2 Merge branch 'main' into feat/channel-approval-flow 2026-04-30 22:54:00 +03:00
gavrielc a359f2555f Merge pull request #2158 from alipgoldberg/setup-splash-screen
feat(setup): show under-the-sea lobster splash at boot
2026-04-30 22:51:35 +03:00
gavrielc 6525926ca9 Merge branch 'main' into setup-splash-screen 2026-04-30 22:51:01 +03:00
gavrielc 35d35fefc3 Merge pull request #2154 from alipgoldberg/setup-fallback-url-in-prompt
feat(setup): move URL fallback into the open-browser prompt
2026-04-30 22:50:44 +03:00
gavrielc eab9110232 Merge branch 'main' into setup-fallback-url-in-prompt 2026-04-30 22:48:47 +03:00
gavrielc 2c0d0e9d44 Merge pull request #2146 from alipgoldberg/setup-headless-link-copy
feat(setup): label headless URL fallback with "Get started:"
2026-04-30 22:48:26 +03:00
gavrielc 17823dffae Merge branch 'main' into setup-headless-link-copy 2026-04-30 17:14:25 +03:00
gavrielc 941a75f65d Merge pull request #2145 from alipgoldberg/setup-headless-skip-browser
feat(setup): skip browser-open prompts on headless devices
2026-04-30 17:13:57 +03:00
gavrielc c2ee2b7c91 Merge branch 'main' into setup-headless-skip-browser 2026-04-30 17:11:35 +03:00
gavrielc ef62f57326 Merge pull request #2108 from alipgoldberg/setup-fmt-duration
feat(setup): switch elapsed-time suffixes to "Xm Ys" past 60s
2026-04-30 17:10:40 +03:00
exe.dev user e51f6e0c41 feat(setup): show under-the-sea lobster splash at boot
Replaces the single-line `NanoClaw` wordmark printed by
nanoclaw.sh with a multi-line splash frame: the lobster mascot
rendered as truecolor braille, drifting bubbles on either side,
the figlet wordmark below (Nano in bold, Claw in cyan bold),
three taglines — "Small.", "Runs on your machine.", "Yours to
modify." — and a navy seafloor line.

The frame is pre-rendered into `assets/setup-splash.txt` (built
from `assets/nanoclaw-icon.png` via chafa for the lobster +
figlet for the wordmark). nanoclaw.sh just streams the literal
bytes — no runtime dependency on chafa, figlet, or
ImageMagick.

Total height: 30 lines. Visible width: ~40 columns (fits any
terminal). Truecolor ANSI codes are used directly; terminals
without truecolor support will see a degraded but still
readable frame.

Also removes the standalone "Small. Runs on your machine.
Yours to modify." tagline line that nanoclaw.sh used to print
above the bootstrap spinner — those taglines now appear inside
the splash, so showing them again would duplicate.

The wordmark-suppression flow downstream (`setup:auto` honoring
`NANOCLAW_BOOTSTRAPPED=1`) is unchanged: the splash prints once
in nanoclaw.sh, setup:auto's `printIntro()` sees the flag and
keeps the clack `p.intro` line clean ("Let's get you set up.").
2026-04-30 16:46:43 +03:00
exe.dev user cb15e606c3 feat(setup): move URL fallback into the open-browser prompt
On GUI devices the URL was previously rendered dim inside the
instructional `note(...)` card, then `confirmThenOpen` printed
its prompt below: read the card, see the URL, then a separate
"Press Enter to open the X" prompt with no link near it. Two
visual moments for what's really one decision.

This PR pulls the URL out of the card on GUI devices and
relocates it directly under the action line of the confirm
prompt, separated only by a dim "If browser does not appear,
please visit: <url>" line:

    │
    ◆  Press Enter to open the Developer Portal
    │  If browser does not appear, please visit: …  (dim)
    │  ● Yes / ○ No
    │

Action and fallback live as one prompt block — the user sees
both at the same time, no need to scroll back up to grab the
URL if the auto-open misses.

Headless behavior is unchanged: `formatNoteLink` still emits
"Get started: <url>" inside the card on headless devices (per
#2146), and `confirmThenOpen` still no-ops on headless (per
#2145). The only thing that changed for headless is the leading
`\n` in the helper output, which acts as a visual separator from
the steps above.

Five call sites adjusted (Discord ×3, Slack ×1, Telegram ×1) to
use `.filter((line) => line !== null)` so the now-nullable
`formatNoteLink` cleanly drops out of GUI-rendered cards.
2026-04-30 16:46:29 +03:00
exe.dev user 6863e0f63b feat(setup): label headless URL fallback with "Get started:"
When a card's auto-open is gated on `confirmThenOpen`, the URL also
appears inside the surrounding `note(...)` as a copy-paste fallback —
rendered dim because on a GUI device the auto-open is doing the
heavy lifting and the printed URL is just an incidental backup.

On headless devices the auto-open doesn't run (per #2145), so the
URL inside the note is the user's *only* path forward. A dim URL
reads as "incidental reference" exactly when it should be reading
as "this is the action."

Adds `formatNoteLink(url)` to setup/lib/browser.ts:
  - GUI device → `k.dim(url)` (unchanged from today)
  - Headless device → `Get started: <url>` at full strength

Replaces five call sites (Discord ×3, Slack ×1, Telegram ×1).
Single helper, atomic switch via the same `isHeadless()` plumbing
introduced in #2145, so the headless behavior across all five
flows stays in sync.
2026-04-30 16:46:16 +03:00
exe.dev user 4d42bb95fb feat(setup): skip browser-open prompts on headless devices
Wires the existing `isHeadless()` from setup/platform.ts into
`confirmThenOpen`. When the helper detects a headless device
(Linux without `DISPLAY`/`WAYLAND_DISPLAY`), both the
"Press Enter to open your browser" prompt and the actual
`openUrl(...)` call are skipped — there's no browser to launch
and the user can't usefully press Enter to summon one.

Why this is enough — the surrounding flow already supports the
headless path implicitly:

  - Every `confirmThenOpen` call site sits beneath a `note(...)`
    that prints the URL and the steps the user needs to take.
    The URL is already visible to copy-paste onto another
    device.

  - Every site is followed by an explicit confirmation prompt
    ("Got your bot token?", "Done with the X?", etc.) that
    naturally serves as the headless user's "I finished the
    thing on my other device" signal.

So the headless branch becomes: read the note, do the thing,
answer the next prompt — without a useless "Press Enter to
open your browser" detour in between.

Coverage rationale (~95% accurate for the cases that actually
cause user confusion today):

  - Linux + no `DISPLAY`/`WAYLAND_DISPLAY` → headless. Catches:
      • Raspberry Pi headless installs
      • Bare-metal Linux servers
      • SSH'd into Linux without X11 forwarding
      • CI environments on Linux
      • Linux containers (which have no display)
  - macOS → never headless. Even SSH'd Macs can usually still
    open URLs through the local user's session, so treating
    them as GUI-capable is the right default.
  - Windows → never headless (effectively always GUI in
    practice).

The remaining ~5% are edge cases (someone manually unset
`DISPLAY` on a desktop Linux session, etc.) that almost never
happen accidentally and recover gracefully — the URL is still
visible in the surrounding note.

Six call sites in channel adapters (Discord ×3, Slack ×1,
Telegram ×1, Teams ×1) all change behavior atomically through
the single helper. No per-site copy changes needed; consistency
is enforced by the central wiring.
2026-04-30 16:45:59 +03:00
exe.dev user a66cd545d5 feat(setup): switch elapsed-time suffixes to "Xm Ys" past 60s
Adds a `fmtDuration(ms)` helper in `setup/lib/theme.ts` that returns
`47s` under a minute and `1m 34s` from 60s onward, then routes every
elapsed-time spinner suffix in the setup flow through it. Replaces
the inline `Math.round((Date.now() - start) / 1000)` + `(${elapsed}s)`
pattern at every site.

Format is consistent past 60s — `1m 0s` over `1m` — so the live
spinner doesn't change shape at every whole-minute crossing.

Sites updated: setup/auto.ts, setup/lib/{runner,tz-from-claude,
claude-assist}.ts, and setup/channels/{signal,whatsapp,telegram,
discord,slack}.ts. Pre-allocated suffix budgets in `fitToWidth`
calls bumped from `' (999s)'` to `' (99m 59s)'` so long-running
steps don't blow past the reserved width.
2026-04-30 16:45:21 +03:00
Gabi Simons cfb737d681 Merge branch 'main' into feat/channel-approval-flow 2026-04-30 15:54:55 +03:00
Gabi 1db98ee614 refactor(setup): check env vars per-step instead of upfront all-or-nothing
Remove the grouped detectExistingEnv() block that asked "reuse all or
start fresh" at the top of setup. Each channel step now reads credentials
directly from .env on disk via readEnvKey() and offers to reuse them
individually at the point of use.

- Add readEnvKey() helper in setup/environment.ts
- Remove ENV_KEY_GROUPS, ExistingEnvGroup, detectExistingEnv from auto.ts
- Move detectRegisteredGroups skip to right before cli-agent step
- Switch all channel files (telegram, discord, slack, teams, imessage)
  from process.env to readEnvKey()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 12:36:25 +00:00
gavrielc bb1b41800c Merge pull request #2156 from qwibitai/fix/telegram-spinner-overflow
fix: prevent telegram pairing spinner from flooding terminal
2026-04-30 15:30:54 +03:00