lazyclaw 6.0.1 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +88 -25
- package/README.md +121 -190
- package/channels/handoff.mjs +18 -5
- package/channels/matrix.mjs +23 -5
- package/channels/slack.mjs +83 -50
- package/channels/slack_env.mjs +45 -0
- package/channels/telegram.mjs +49 -6
- package/cli.mjs +10 -21
- package/commands/channels.mjs +106 -107
- package/commands/chat.mjs +81 -45
- package/commands/daemon.mjs +15 -0
- package/commands/gateway.mjs +194 -0
- package/commands/providers.mjs +17 -38
- package/commands/service.mjs +113 -0
- package/commands/setup.mjs +55 -54
- package/commands/setup_channels.mjs +207 -0
- package/config_features.mjs +106 -0
- package/daemon/lib/inbound_dedup.mjs +108 -0
- package/daemon/lib/learn_queue.mjs +46 -0
- package/daemon/routes/_deps.mjs +6 -0
- package/daemon/routes/conversation.mjs +68 -4
- package/daemon/routes/ops.mjs +9 -29
- package/dotenv_min.mjs +28 -0
- package/first_run.mjs +9 -0
- package/lib/gateway_guard.mjs +100 -0
- package/lib/inbound_client.mjs +108 -0
- package/lib/service_install.mjs +207 -0
- package/package.json +2 -3
- package/providers/probe.mjs +28 -0
- package/tui/editor.mjs +18 -2
- package/tui/repl.mjs +25 -4
- package/tui/slash_commands.mjs +4 -0
- package/tui/slash_dispatcher.mjs +118 -0
- package/tui/splash_props.mjs +52 -0
- package/web/dashboard.css +6 -12
- package/web/dashboard.html +2 -34
- package/web/dashboard.js +3 -3
package/README.ko.md
CHANGED
|
@@ -1,44 +1,107 @@
|
|
|
1
1
|
# lazyclaw (한국어 안내)
|
|
2
2
|
|
|
3
|
-
> 영문 정본은 `README.md
|
|
3
|
+
> 영문 정본은 [`README.md`](./README.md). 본 문서는 핵심만 한국어로.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**터미널 에이전트 — 학습은 네 Claude 구독으로 $0, 응답은 모든 채널로.**
|
|
6
|
+
|
|
7
|
+
터미널에서 대화하면 백그라운드 학습 루프가 대화를 재사용 가능한 스킬로 증류(claude-cli 구독 = API 청구 없음). Slack·Telegram·Discord·Matrix·Email·Signal·WhatsApp·Voice 연결. 어려운 작업은 planner + workers로 분산. 작고 읽을 수 있는 Node 코어.
|
|
8
|
+
|
|
9
|
+
## 빠른 시작
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g lazyclaw # 또는: npx lazyclaw
|
|
13
|
+
lazyclaw # 새 설치 → 가이드 셋업 후 chat
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**첫 실행 = phased 위저드** (Hermes식 — 깔끔한 대화 1개 먼저, 그 다음 레이어링):
|
|
17
|
+
1. **Provider + model** — 화살표 picker (`claude-cli`는 키 불요)
|
|
18
|
+
2. **Verify** — 1-토큰 ping으로 응답 확인
|
|
19
|
+
3. **Context window** — 턴당 유지할 히스토리 (선택)
|
|
20
|
+
4. **Channel** — Slack/Telegram/Matrix/HTTP 빌트인, 나머지는 플러그인 (선택)
|
|
21
|
+
5. **Workspace / skills / webhook** (선택)
|
|
22
|
+
6. **Orchestration** — planner + workers 켜기 (선택)
|
|
23
|
+
|
|
24
|
+
언제든 `lazyclaw setup` 또는 chat에서 `/config`로 재실행.
|
|
25
|
+
|
|
26
|
+
## $0 자가 학습
|
|
27
|
+
|
|
28
|
+
`provider`(chat)와 `trainer`(학습 루프)를 분리. Claude Pro/Max 구독이 학습을 돌리는 동안 chat은 아무 backend나. 매 턴 후 fire-and-forget 루프가 trajectory 기록 + 스킬 증류(`trained_by` 태그). `trainer: { provider: "auto" }`면 claude-cli 세션 자동 감지 → **$0**.
|
|
29
|
+
|
|
30
|
+
```jsonc
|
|
31
|
+
{ "provider": "openai", "model": "gpt-4.1",
|
|
32
|
+
"trainer": { "provider": "auto" } } // 학습은 네 Claude 구독으로 $0
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 어디서나 대화
|
|
36
|
+
|
|
37
|
+
모든 리스너는 상시 데몬의 **공유 세션 스토어**로 forward → chat·대시보드·전 채널이 메모리 하나를 쓰는 단일 에이전트(컨텍스트가 채널 간 따라옴).
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
lazyclaw service install # 1) 상시 데몬(공유 brain)
|
|
41
|
+
|
|
42
|
+
# 2) 리스너를 데몬에 연결 (Slack Socket Mode: .env에 SLACK_BOT_TOKEN + SLACK_APP_TOKEN)
|
|
43
|
+
lazyclaw slack listen # inbound를 데몬으로 forward, 스레드 응답
|
|
44
|
+
lazyclaw slack listen --provider orchestrator # …오케스트레이션 응답
|
|
45
|
+
lazyclaw slack listen --daemon-url http://127.0.0.1:19600 # 비기본 데몬
|
|
46
|
+
lazyclaw channels # 설정된 채널 보기
|
|
47
|
+
lazyclaw channels enable|disable slack
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
리스너는 thin forwarder: 채널 소켓을 소유(Slack Socket Mode는 공개 URL 불요, app-level `xapp-` 토큰)하고 각 메시지를 데몬 `/inbound`로 POST → 데몬이 영속 세션에 바인딩 후 provider 실행. **데몬이 떠 있어야 함**(`lazyclaw service install` 또는 `lazyclaw daemon`); 타깃은 `--daemon-url`/`LAZYCLAW_DAEMON_URL`로 override. 위저드 채널 단계나 chat `/channels`로 설정.
|
|
51
|
+
|
|
52
|
+
## 상시 가동 (always-on)
|
|
53
|
+
|
|
54
|
+
**한 프로세스, 모든 채널:** `lazyclaw gateway` = 데몬 코어 + 설정된 채널 transport(Slack Socket Mode/Telegram long-poll/Matrix sync)를 단일 프로세스로. 채널이 in-process라 `/handoff`가 타깃 채널에 resume 마커 통지 가능(통지 실패 시 handoff 롤백).
|
|
6
55
|
|
|
7
56
|
```bash
|
|
8
|
-
|
|
57
|
+
lazyclaw gateway # 데몬 코어 + 활성 채널 전부, 한 프로세스
|
|
58
|
+
lazyclaw gateway --channels slack # 채널 명시
|
|
59
|
+
lazyclaw service install gateway # 재부팅에도 살아있게
|
|
9
60
|
```
|
|
10
61
|
|
|
11
|
-
|
|
12
|
-
|
|
62
|
+
gateway는 **기본 인증** — 첫 실행 시 bearer 토큰을 만들어 `~/.lazyclaw/gateway.token`(0600, 로그 안 남김)에 영속. 자기 채널은 자동 사용, 외부 호출자는 파일에서 읽음(`--auth-token`/`--no-auth`로 override).
|
|
63
|
+
|
|
64
|
+
분리 실행도 가능: 데몬만(`lazyclaw service install`) + 채널별 `* listen` forwarder.
|
|
13
65
|
|
|
14
|
-
|
|
66
|
+
인바운드 메시지는 **idempotent** — 채널 native id(Slack `channel:ts`, Telegram `chat:message_id`, Matrix `event_id`)로 dedup, 재전송/리스너 재시작 replay는 기록된 응답 반환(provider 재실행 없음). 세션 바인딩된 채널 턴은 chat REPL과 동일한 post-task **학습 루프**에 공급(trainer `auto` → Claude 구독 $0).
|
|
67
|
+
|
|
68
|
+
> ⚠️ `security.allowUnattendedSensitive=true`이면 채널 리스너/데몬이 **부팅 거부** — 이 플래그는 모든 inbound에 대해 fail-closed tool 승인 게이트를 우회하므로 상시 표면 + 이 플래그 = RCE 경로. 민감 tool 승인은 인터랙티브로 유지.
|
|
69
|
+
|
|
70
|
+
## 멀티 에이전트 오케스트레이션
|
|
71
|
+
|
|
72
|
+
provider를 `orchestrator`로 → **Plan → Delegate → Synthesise**. planner가 분해, workers가 병렬 실행, planner가 병합.
|
|
15
73
|
|
|
16
74
|
```bash
|
|
17
|
-
lazyclaw
|
|
18
|
-
lazyclaw
|
|
75
|
+
lazyclaw orchestrator set-planner claude-cli:claude-sonnet-4-6
|
|
76
|
+
lazyclaw orchestrator workers add claude-cli:claude-sonnet-4-6
|
|
77
|
+
lazyclaw orchestrator on
|
|
19
78
|
```
|
|
20
79
|
|
|
21
|
-
|
|
80
|
+
chat에서 `/orchestrator` (빈 입력 = on/off picker) 또는 `/orchestrator on|off|planner <spec>|worker add <spec>`.
|
|
22
81
|
|
|
23
|
-
|
|
24
|
-
(skill synthesis) 를 독립 설정. Claude Pro/Max 사용자는 학습 비용
|
|
25
|
-
$0 (`docs/trainer-recipes.md`).
|
|
26
|
-
- **FTS5 recall** — `~/.lazyclaw/index.db` 단일 SQLite + FTS5 corpus.
|
|
27
|
-
cross-CLI trajectory recall 제공 (`lazyclaw recall <query>`).
|
|
28
|
-
- **Persona 7-layer compose** — workspace 별 SOUL.md, Hermes skin
|
|
29
|
-
import (`docs/persona-cookbook.md`).
|
|
30
|
-
- **Sandbox 6-backend** — local / docker / ssh / singularity / modal
|
|
31
|
-
/ daytona.
|
|
82
|
+
## chat에서 직접 조작 (슬래시)
|
|
32
83
|
|
|
33
|
-
|
|
84
|
+
| 슬래시 | 기능 |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `/config` | chat 나가고 setup 위저드 재실행 |
|
|
87
|
+
| `/provider` · `/model` | 검색 picker로 provider/model 선택 |
|
|
88
|
+
| `/channels [<name> on\|off]` | 채널 보기 / 토글 |
|
|
89
|
+
| `/orchestrator [on\|off\|…]` | 멀티에이전트 보기 / 토글 (빈 입력=picker) |
|
|
90
|
+
| `/context [turns N\|tokens N]` | 히스토리 윈도우 조절 |
|
|
91
|
+
| `/skill` · `/personality` · `/memory` · `/loop` · `/goal` | 스킬·페르소나·메모리·루프·목표 |
|
|
34
92
|
|
|
35
|
-
|
|
93
|
+
`/help`로 전체 목록. 고스트 자동완성, 한글 IME는 박스 안에서 조합.
|
|
36
94
|
|
|
37
|
-
##
|
|
95
|
+
## 대시보드
|
|
38
96
|
|
|
39
97
|
```bash
|
|
40
|
-
lazyclaw
|
|
41
|
-
lazyclaw rates --trainer-only # trainer 비용 추적
|
|
42
|
-
lazyclaw persona use <name> # persona 활성화
|
|
43
|
-
lazyclaw-export --format openai-ft # trajectory export
|
|
98
|
+
lazyclaw dashboard # http://127.0.0.1:19600 로컬 웹 UI (17 탭, 다크 앰버)
|
|
44
99
|
```
|
|
100
|
+
|
|
101
|
+
## 설정 / 보안
|
|
102
|
+
|
|
103
|
+
설정은 `~/.lazyclaw/config.json`(plain JSON), 시크릿은 `~/.lazyclaw/.env`(0600, 로그 안 남김). 디렉터리 이동 `LAZYCLAW_CONFIG_DIR=/path`.
|
|
104
|
+
|
|
105
|
+
> ⚠️ `config.json`은 shell rc처럼 신뢰 코드 — `$(...)` 값은 로드 시 실행됨. 신뢰 못 할 스니펫 붙여넣기 금지. 민감 tool(shell/write/network)은 기본 fail-closed(승인 hook 필요).
|
|
106
|
+
|
|
107
|
+
Node 18+ (Slack Socket Mode는 22+). macOS / Linux / WSL 1급.
|
package/README.md
CHANGED
|
@@ -1,268 +1,199 @@
|
|
|
1
1
|
# lazyclaw
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**A terminal agent that learns on your Claude subscription — for $0 — and reaches you on every channel.**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Chat with any provider. Train the skill bank, user model, and reflection pass on `claude-cli` — $0. One SQLite + FTS5 store remembers every session. Hand the same conversation off between TUI, Slack, Discord, Telegram, Matrix, Email, and Voice.
|
|
5
|
+
Chat in the terminal. Let the background learning loop distil your conversations into reusable skills on `claude-cli` (your Pro/Max subscription — no API bill). Wire it to Slack, Telegram, Discord, Matrix, Email, Signal, WhatsApp, or Voice. Fan a hard task out to a planner + workers. One small, auditable Node core — no daemon you can't read.
|
|
8
6
|
|
|
9
7
|
[](https://www.npmjs.com/package/lazyclaw)
|
|
10
8
|
[](https://nodejs.org/)
|
|
11
9
|
[](./LICENSE)
|
|
12
10
|
|
|
13
11
|
```bash
|
|
14
|
-
npx lazyclaw
|
|
12
|
+
npx lazyclaw # first run walks you through setup, then drops into chat
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
<img
|
|
15
|
+
<img width="1871" height="1146" alt="image" src="https://github.com/user-attachments/assets/365d05ac-cd24-4451-96b1-01fe82582a2b" />
|
|
18
16
|
|
|
19
17
|
*한국어: [README.ko.md](./README.ko.md)*
|
|
20
18
|
|
|
21
19
|
---
|
|
22
20
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
Four things no other terminal agent CLI does together:
|
|
21
|
+
## What it is
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
2. **Recall across every CLI.** One FTS5 index over sessions, skills, trajectories, and memory.
|
|
29
|
-
3. **Cross-channel handoff.** `/handoff slack <channel-id>` moves the live conversation; context follows.
|
|
30
|
-
4. **Six sandbox backends, one API.** `local` / `docker` / `ssh` / `singularity` / `modal` / `daytona`.
|
|
23
|
+
lazyclaw is a single-binary-feel Node CLI in the "claw" family (Hermes → OpenClaw → nanoclaw). It is **TUI-first**: `lazyclaw` with no arguments opens a chat REPL with a sloth splash, slash commands, and ghost-text autocomplete. Underneath, every turn feeds a learning loop, and the same agent answers from any messaging channel you connect.
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
You can read the whole thing. No hosted service, no telemetry, config in plain JSON at `~/.lazyclaw/`, secrets in `~/.lazyclaw/.env` (0600).
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
## Quick start
|
|
35
28
|
|
|
36
|
-
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g lazyclaw # or: npx lazyclaw
|
|
31
|
+
lazyclaw # fresh install → guided setup, then chat
|
|
32
|
+
```
|
|
37
33
|
|
|
38
|
-
|
|
34
|
+
The first run is a **phased wizard** (Hermes-style — get one clean chat working first, then layer the rest):
|
|
39
35
|
|
|
40
|
-
|
|
36
|
+
1. **Provider + model** — arrow-key picker (`claude-cli` is keyless; gemini/openai/anthropic take an API key).
|
|
37
|
+
2. **Verify** — a one-token ping confirms the provider answers.
|
|
38
|
+
3. **Context window** — how much history to keep per turn (optional).
|
|
39
|
+
4. **Channel** — Slack / Telegram / Matrix / HTTP built in; Discord / Email / Signal / Voice / WhatsApp via plugins (optional).
|
|
40
|
+
5. **Workspace / skills / webhook** (optional).
|
|
41
|
+
6. **Orchestration** — turn on the planner + workers pipeline (optional).
|
|
41
42
|
|
|
42
|
-
-
|
|
43
|
-
- `lazyclaw sandbox` exposes `list | test | add | use`; the `sandbox run --backend ...` shape lands in v5.1.
|
|
44
|
-
- `codex-cli` and `gemini-cli` provider modules are tracked but not yet registered in the main runtime.
|
|
45
|
-
- The E2E matrix in `tests/e2e/phaseH-e2e-matrix.spec.ts` still has a number of flows marked `test.skip` pending v5.1 wiring; the min-green-set is documented at the top of that file.
|
|
43
|
+
Re-run it any time with `lazyclaw setup`, or `/config` from inside chat.
|
|
46
44
|
|
|
47
|
-
|
|
45
|
+
<img src="docs/screenshots/onboard.png" alt="lazyclaw provider picker" width="680">
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
# Try it (no install)
|
|
51
|
-
npx lazyclaw onboard && lazyclaw chat
|
|
47
|
+
## $0 self-learning
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
npm install -g lazyclaw
|
|
49
|
+
lazyclaw splits two provider slots: **`provider`** (chat — the hot path) and **`trainer`** (the learning loop — skill synthesis, reflection, the user model). They are independent, so a Claude Pro/Max subscription can power the learning while chat runs anywhere — or both run on one backend.
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
git clone https://github.com/cmblir/lazyclaw && cd lazyclaw
|
|
58
|
-
npm install && npm link
|
|
59
|
-
```
|
|
51
|
+
After every turn, a fire-and-forget loop records the trajectory and distils reusable skills, tagged `trained_by`. With `trainer: { provider: "auto" }` it auto-detects your `claude-cli` session and runs the loop for free; otherwise it mirrors the chat provider.
|
|
60
52
|
|
|
61
|
-
```
|
|
62
|
-
lazyclaw
|
|
53
|
+
```jsonc
|
|
54
|
+
// ~/.lazyclaw/config.json
|
|
55
|
+
{
|
|
56
|
+
"provider": "openai", // chat: pay-per-token (or claude-cli, ollama, …)
|
|
57
|
+
"model": "gpt-4.1",
|
|
58
|
+
"trainer": { "provider": "auto" } // learning: $0 on your Claude subscription
|
|
59
|
+
}
|
|
63
60
|
```
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
| Setup | `provider` | `trainer` | Cost |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| Subscription only | `claude-cli` | `auto` | **$0** |
|
|
65
|
+
| Hybrid (recommended) | `openai` / any | `auto` | chat only |
|
|
66
|
+
| Pure API | `openai` / any | `openai` | both metered |
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
## Talk to it anywhere
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
Connect a channel and **the same agent** answers there — every listener forwards into the always-on daemon's shared session store, so chat, the dashboard, and all channels are one agent with one memory (and context follows across channels). Slack, Telegram, Matrix, and HTTP are built in; Discord, Email, Signal, Voice, and WhatsApp install as `@lazyclaw/channel-*` plugins.
|
|
70
71
|
|
|
71
72
|
```bash
|
|
72
|
-
lazyclaw
|
|
73
|
-
# → writes ~/.lazyclaw/config.json
|
|
74
|
-
```
|
|
73
|
+
lazyclaw service install # 1) the always-on daemon (the shared brain)
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
# 2) point a listener at it (Slack Socket Mode: SLACK_BOT_TOKEN + SLACK_APP_TOKEN in ~/.lazyclaw/.env)
|
|
76
|
+
lazyclaw slack listen # forwards inbound to the daemon, replies in-thread
|
|
77
|
+
lazyclaw slack listen --provider orchestrator # …and orchestrate the reply
|
|
78
|
+
lazyclaw slack listen --daemon-url http://127.0.0.1:19600 # non-default daemon
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
lazyclaw
|
|
80
|
-
lazyclaw
|
|
81
|
-
lazyclaw status # → { provider, model, hasApiKey }
|
|
82
|
-
lazyclaw doctor # validates config + provider registry + index.db
|
|
80
|
+
lazyclaw channels # view configured channels
|
|
81
|
+
lazyclaw channels enable|disable slack
|
|
82
|
+
lazyclaw channels install @lazyclaw/channel-discord
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
A listener is a thin forwarder: it owns the channel socket (Slack Socket Mode needs no public URL, just an app-level `xapp-` token) and POSTs each message to the daemon's `/inbound`, which binds the conversation to a persistent session and runs the provider. It needs a reachable daemon (`lazyclaw service install`, or `lazyclaw daemon`); override the target with `--daemon-url` / `LAZYCLAW_DAEMON_URL`. Set it all up from the wizard's channel step, or `/channels` in chat.
|
|
86
86
|
|
|
87
|
-
##
|
|
87
|
+
## Run it always-on
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
**One process, every channel:** `lazyclaw gateway` runs the daemon core *and* your configured channel transports (Slack Socket Mode / Telegram long-poll / Matrix sync) in a single process — and because the channels live in-process, `/handoff` can notify the target channel with a resume marker (a failed notify rolls the handoff back).
|
|
90
90
|
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"model": "gpt-4.1",
|
|
96
|
-
"trainer": {
|
|
97
|
-
"provider": "claude-cli", // trainer: $0 on Pro/Max
|
|
98
|
-
"model": "claude-haiku-4-5",
|
|
99
|
-
"schedule": "nightly",
|
|
100
|
-
"budget": { "maxCallsPerDay": 200, "usdPerDay": 0.50 }
|
|
101
|
-
}
|
|
102
|
-
}
|
|
91
|
+
```bash
|
|
92
|
+
lazyclaw gateway # daemon core + every enabled channel, one process
|
|
93
|
+
lazyclaw gateway --channels slack # explicit channel set
|
|
94
|
+
lazyclaw service install gateway # …and keep it alive across reboots
|
|
103
95
|
```
|
|
104
96
|
|
|
105
|
-
The
|
|
97
|
+
The gateway is **authenticated by default**: it mints a bearer token on first run and persists it to `~/.lazyclaw/gateway.token` (0600, never logged). Its own channels use it automatically; external callers read it from the file (`--auth-token`/`--no-auth` to override).
|
|
106
98
|
|
|
107
|
-
|
|
99
|
+
Or run pieces separately: the daemon is the agent core — one provider path and one session/memory store on `127.0.0.1` — and `* listen` commands are standalone single-channel forwarders.
|
|
108
100
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
```bash
|
|
102
|
+
lazyclaw service install # daemon only: launchd (macOS) · systemd user unit (Linux) · pidfile fallback
|
|
103
|
+
lazyclaw service status
|
|
104
|
+
lazyclaw service uninstall
|
|
105
|
+
```
|
|
114
106
|
|
|
115
|
-
|
|
107
|
+
The backend is auto-detected (override with `--backend launchd|systemd|fallback`); flags pass through, e.g. `lazyclaw service install gateway --port 19600 --auth-token "$TOK" --channels slack`.
|
|
116
108
|
|
|
117
|
-
|
|
109
|
+
Inbound channel messages are **idempotent**: each message's native id (Slack `channel:ts`, Telegram `chat:message_id`, Matrix `event_id`) is deduplicated by the daemon — a redelivery or listener-restart replay returns the recorded reply instead of running the provider twice. And every session-bound channel turn feeds the same post-task **learning loop** as the chat REPL (trainer `auto` → your Claude subscription, $0).
|
|
118
110
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
| `claude-cli` | `claude-cli` | subscription (Pro/Max) |
|
|
122
|
-
| `anthropic` | `anthropic` | API key |
|
|
123
|
-
| `openai` | `openai` | API key |
|
|
124
|
-
| `gemini` | `gemini` | API key |
|
|
125
|
-
| `ollama` | `ollama` | local (no key) |
|
|
126
|
-
| `nim` / `openrouter` / `groq` / `together` / `xai` / `deepseek` / `mistral` / `fireworks` | — | API key (OpenAI-compatible) |
|
|
111
|
+
> [!NOTE]
|
|
112
|
+
> A channel listener or the daemon refuses to start while `security.allowUnattendedSensitive=true` — that flag bypasses the fail-closed tool-approval gate for *every* inbound message, so an always-on surface plus that flag is a remote-code-execution path. Keep sensitive-tool approval interactive.
|
|
127
113
|
|
|
128
|
-
|
|
129
|
-
- `orchestrator` — composes any of the above into a multi-agent pipeline ([docs/multi-agent.md](./docs/multi-agent.md))
|
|
130
|
-
- `custom` — any OpenAI-compatible v1 endpoint with your own base URL + key
|
|
114
|
+
## Multi-agent orchestration
|
|
131
115
|
|
|
132
|
-
|
|
116
|
+
Set the provider to `orchestrator` and a hard request becomes **Plan → Delegate → Synthesise**: a planner decomposes the task, workers run the subtasks in parallel, then the planner merges the results. Workers are real agents with the tool registry.
|
|
133
117
|
|
|
134
|
-
|
|
118
|
+
```bash
|
|
119
|
+
lazyclaw orchestrator set-planner claude-cli:claude-sonnet-4-6
|
|
120
|
+
lazyclaw orchestrator workers add claude-cli:claude-sonnet-4-6
|
|
121
|
+
lazyclaw orchestrator on # route chats through the pipeline
|
|
122
|
+
```
|
|
135
123
|
|
|
136
|
-
|
|
124
|
+
From chat: `/orchestrator` opens an on/off picker, or `/orchestrator on|off|planner <spec>|worker add <spec>`. Details: [docs/multi-agent.md](./docs/multi-agent.md).
|
|
137
125
|
|
|
138
|
-
|
|
126
|
+
## Drive it from chat
|
|
139
127
|
|
|
140
|
-
|
|
128
|
+
The REPL has slash commands for everything you'd otherwise edit config for — point-and-pick, no JSON:
|
|
141
129
|
|
|
142
|
-
|
|
130
|
+
| Slash | Does |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `/config` | leave chat and re-run the setup wizard |
|
|
133
|
+
| `/provider` · `/model` | pick provider / model from a searchable list |
|
|
134
|
+
| `/channels [<name> on\|off]` | view / toggle channels |
|
|
135
|
+
| `/orchestrator [on\|off\|…]` | view / toggle multi-agent (picker on bare call) |
|
|
136
|
+
| `/context [turns N\|tokens N]` | resize the chat history window |
|
|
137
|
+
| `/skill` · `/personality` · `/memory` · `/loop` · `/goal` | skills, personas, memory, loops, goals |
|
|
143
138
|
|
|
144
|
-
|
|
139
|
+
`/help` lists them all. Ghost-text autocomplete completes commands as you type; CJK/Hangul input composes inside the box.
|
|
145
140
|
|
|
146
|
-
|
|
141
|
+
## The dashboard
|
|
147
142
|
|
|
148
143
|
```bash
|
|
149
|
-
lazyclaw
|
|
150
|
-
# inside Slack: /handoff discord <channel-id>
|
|
151
|
-
# inside Discord: /handoff tui <thread-id>
|
|
144
|
+
lazyclaw dashboard # local web UI on http://127.0.0.1:19600
|
|
152
145
|
```
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
### Personas that compose
|
|
157
|
-
|
|
158
|
-
Swap personality per channel without losing session memory. Layers compose top-down: global SOUL → workspace SOUL → active personality → agent role → user model (USER.md) → skill bank → memory core → recent trajectory tail. See [docs/persona-cookbook.md](./docs/persona-cookbook.md).
|
|
159
|
-
|
|
160
|
-
### Loops and scheduled goals
|
|
161
|
-
|
|
162
|
-
Durable foreground or `--detach` loops; cron-scheduled goals with channel fan-out. State lives in `~/.lazyclaw/loops/<id>/` and survives restart. See [docs/loop-goal-preflight.md](./docs/loop-goal-preflight.md).
|
|
147
|
+
A framework-free SPA over the daemon's JSON API: Chat, Sessions, Workflows, Skills, Providers, Rates, Metrics, Doctor, Config, Status, Agents, Teams, Tasks, Trainer, Recall, Sandbox, Channels — 17 tabs, dark amber theme.
|
|
163
148
|
|
|
164
|
-
|
|
149
|
+
## Providers
|
|
165
150
|
|
|
166
|
-
|
|
151
|
+
| Chat / trainer | Auth |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `claude-cli` | subscription (Pro/Max) — keyless |
|
|
154
|
+
| `anthropic` · `openai` · `gemini` | API key |
|
|
155
|
+
| `ollama` | local, no key |
|
|
156
|
+
| `nim` · `openrouter` · `groq` · `together` · `xai` · `deepseek` · `mistral` · `fireworks` | OpenAI-compatible API key |
|
|
157
|
+
| `custom` | any OpenAI-compatible v1 endpoint (your base URL + key) |
|
|
158
|
+
| `orchestrator` | meta-provider — planner + workers over any of the above |
|
|
167
159
|
|
|
168
|
-
|
|
160
|
+
<img src="docs/screenshots/providers.png" alt="lazyclaw providers info" width="680">
|
|
169
161
|
|
|
170
|
-
|
|
171
|
-
|---|---|
|
|
172
|
-
| `lazyclaw` | No-arg → drops into chat (since v5.0.6) |
|
|
173
|
-
| `lazyclaw menu` | Arrow-key launcher (formerly the no-arg default) |
|
|
174
|
-
| `lazyclaw onboard` | Arrow-key setup; writes `~/.lazyclaw/config.json` |
|
|
175
|
-
| `lazyclaw status` | Print active provider / model / masked key |
|
|
176
|
-
| `lazyclaw doctor` | Validate config, provider registry, FTS5 index |
|
|
177
|
-
| `lazyclaw chat` | Interactive REPL with ghost autocomplete + slash commands |
|
|
178
|
-
| `lazyclaw agent "<prompt>"` | One-shot generate; supports stdin |
|
|
179
|
-
| `lazyclaw run \| resume \| inspect` | DAG / sequential / persistent workflow jobs (`--dir <state-dir>`) |
|
|
180
|
-
| `lazyclaw config get\|set\|list\|edit\|validate` | Dotted-key config access |
|
|
181
|
-
| `lazyclaw sandbox list\|test\|add\|use` | Manage sandbox backends |
|
|
182
|
-
| `lazyclaw channels install\|list\|remove` | Channel plugin lifecycle (`@lazyclaw/channel-<name>`) |
|
|
183
|
-
| `lazyclaw trajectories export --format ...` | Export to atropos / axolotl / openai-ft / jsonl |
|
|
184
|
-
| `lazyclaw personality use\|list\|show` | Activate / inspect personas |
|
|
185
|
-
| `lazyclaw dashboard` | Local web UI on `127.0.0.1:19600` |
|
|
186
|
-
| `lazyclaw migrate v5` | v4 → v5 with backup |
|
|
187
|
-
| `lazyclaw recall` | (v5.1) Top-level recall over the FTS5 index — today, use `loop`/`goal` with `--recall "<query>"` |
|
|
188
|
-
| `lazyclaw version` / `lazyclaw help` | Version + subcommand help |
|
|
189
|
-
|
|
190
|
-
In-REPL slash commands include `/help`, `/status`, `/provider`, `/model`, `/skill`, `/loop`, `/goal`, `/memory`, `/agent`, `/team`, `/handoff`, `/personality`, `/exit`. Depth lives in `lazyclaw <cmd> --help` and the docs below.
|
|
191
|
-
|
|
192
|
-
## Migrating from v4
|
|
193
|
-
|
|
194
|
-
> [!IMPORTANT]
|
|
195
|
-
> `lazyclaw migrate v5` backs up your existing `~/.lazyclaw/` to `backup-v4-<ts>/` before rewriting anything. Don't skip the backup — the SQLite schema and skill frontmatter both change shape.
|
|
196
|
-
|
|
197
|
-
- **What changed**: split `trainer` provider block, new `~/.lazyclaw/index.db` (SQLite + FTS5), per-day JSONL trajectory sink, persona file directory, kebab-case provider IDs (`claude-cli`, `gemini-cli`, ...), additive SKILL.md frontmatter (`group`, `trained_by`, `confidence`, `cross_cli_tested`).
|
|
198
|
-
- **Migrate**:
|
|
199
|
-
```bash
|
|
200
|
-
npm install -g lazyclaw@5
|
|
201
|
-
lazyclaw migrate v5
|
|
202
|
-
```
|
|
203
|
-
- **Rollback**: restore `~/.lazyclaw/backup-v4-<ts>/` and `npm install -g lazyclaw@4`.
|
|
204
|
-
|
|
205
|
-
Full guide: [docs/migration-v4-to-v5.md](./docs/migration-v4-to-v5.md).
|
|
206
|
-
|
|
207
|
-
## CLI vs Channels — quick reference
|
|
208
|
-
|
|
209
|
-
| Capability | TUI | Slack / Discord / Telegram |
|
|
210
|
-
|---|---|---|
|
|
211
|
-
| Start a chat | `lazyclaw chat` | `@lazyclaw <message>` |
|
|
212
|
-
| Hand off to another surface | `/handoff slack <channel-id>` | `/handoff tui <thread-id>` |
|
|
213
|
-
| Recall across history | `lazyclaw loop --recall "<query>" ...` (slash + top-level v5.1) | `lazyclaw loop --recall "<query>" ...` (channel-side `/recall` v5.1) |
|
|
214
|
-
| Switch persona | `/personality use terse` | `/personality use terse` |
|
|
215
|
-
| Switch model | `/model <name>` | `/model <name>` |
|
|
216
|
-
| Show status | `lazyclaw status` | `@lazyclaw status` |
|
|
217
|
-
|
|
218
|
-
Channel plugins implement a shared `channels/base.mjs` contract, so the surface you address them through is interchangeable.
|
|
219
|
-
|
|
220
|
-
## Configuration
|
|
162
|
+
## What else it ships
|
|
221
163
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
"trainer": {
|
|
228
|
-
"provider": "auto", // "auto" | claude-cli | anthropic | openai | gemini | ollama
|
|
229
|
-
"model": "claude-haiku-4-5",
|
|
230
|
-
"schedule": "nightly",
|
|
231
|
-
"budget": { "maxCallsPerDay": 200, "usdPerDay": 0.50 }
|
|
232
|
-
},
|
|
233
|
-
"sandbox": { "backend": "local" }, // local | docker | ssh | singularity | modal | daytona
|
|
234
|
-
"channels": { "slack": { "enabled": true } },
|
|
235
|
-
"persona": { "active": "default" },
|
|
236
|
-
"orchestra": { "learning": { "crossCliDampenFactor": 0.85 } }
|
|
237
|
-
}
|
|
238
|
-
```
|
|
164
|
+
- **Tool registry** — 12 categories (`agents`, `browser`, `coding`, `exec`, `fs`, `git`, `iot`, `learning`, `media`, `net`, `os`, `scheduling`) plus stdio MCP. Sensitive tools (shell, write, network) are **fail-closed** behind an approval hook by default.
|
|
165
|
+
- **Durable recall** — one SQLite + FTS5 index over sessions, skills, trajectories, and memory; rebuildable from the corpus.
|
|
166
|
+
- **Loops & goals** — durable foreground/`--detach` loops and cron-scheduled goals that survive restart.
|
|
167
|
+
- **Personas** — layered SOUL / workspace / personality / role / user-model / skills compose into the system prompt.
|
|
168
|
+
- **Sandboxes** — `local` / `docker` / `ssh` / `singularity` / `modal` / `daytona` behind one API.
|
|
239
169
|
|
|
240
|
-
|
|
170
|
+
## Configuration & security
|
|
241
171
|
|
|
242
|
-
|
|
172
|
+
Config is plain JSON at `~/.lazyclaw/config.json`; channel + provider secrets live in `~/.lazyclaw/.env` (written 0600, never logged). Move the dir with `LAZYCLAW_CONFIG_DIR=/path`.
|
|
243
173
|
|
|
244
174
|
> [!WARNING]
|
|
245
|
-
>
|
|
175
|
+
> Treat `~/.lazyclaw/config.json` like a shell rc — values resolved with `$(...)` execute at load. Don't paste an untrusted snippet without reading it.
|
|
176
|
+
|
|
177
|
+
Sensitive tools deny by default unless an approval hook grants them; `config.json` and workflow state are written owner-only; secrets are scrubbed from the `bash` tool's child env and redacted from trajectories and synthesised skills.
|
|
246
178
|
|
|
247
|
-
|
|
248
|
-
- **Sandbox backends enforce different blast radii.** `local` runs as you; `docker` / `singularity` / `daytona` isolate; `ssh` / `modal` move execution off-host. Pick per task.
|
|
249
|
-
- **Secrets are redacted** from trajectories and synthesised skills (`sk-...`, `ghp_...`, `AKIA...`, bearer tokens, `*_KEY=...`, PEM blocks). Channel tokens live in `~/.lazyclaw/.env` and are never logged.
|
|
179
|
+
## Install / hack
|
|
250
180
|
|
|
251
|
-
|
|
181
|
+
```bash
|
|
182
|
+
npm install -g lazyclaw # install
|
|
183
|
+
git clone https://github.com/cmblir/lazyclaw && cd lazyclaw && npm install && npm link # hack
|
|
184
|
+
node --test tests/*.test.mjs # run the suite
|
|
185
|
+
```
|
|
252
186
|
|
|
253
|
-
|
|
254
|
-
- [docs/trainer-recipes.md](./docs/trainer-recipes.md) — $0, hybrid, offline, and `auto` trainer configs
|
|
255
|
-
- [docs/persona-cookbook.md](./docs/persona-cookbook.md) — layered persona compose stack + skin import
|
|
256
|
-
- [docs/multi-agent.md](./docs/multi-agent.md) — orchestrator pipeline + Slack team data model
|
|
257
|
-
- [docs/loop-goal-preflight.md](./docs/loop-goal-preflight.md) — durable loops and cron-scheduled goals
|
|
258
|
-
- [docs/agent-memory.md](./docs/agent-memory.md) — per-agent memory, reflection, and skill synthesis
|
|
259
|
-
- [CHANGELOG.md](./CHANGELOG.md) — release notes (Keep a Changelog)
|
|
260
|
-
- [README.ko.md](./README.ko.md) — Korean companion
|
|
187
|
+
Requires **Node 18+** (Node 22+ for Slack Socket Mode). macOS / Linux / WSL are first-class.
|
|
261
188
|
|
|
262
|
-
##
|
|
189
|
+
## Docs
|
|
263
190
|
|
|
264
|
-
|
|
191
|
+
- [docs/multi-agent.md](./docs/multi-agent.md) — orchestrator pipeline + Slack teams
|
|
192
|
+
- [docs/trainer-recipes.md](./docs/trainer-recipes.md) — $0, hybrid, and `auto` trainer configs
|
|
193
|
+
- [docs/persona-cookbook.md](./docs/persona-cookbook.md) — persona compose stack
|
|
194
|
+
- [docs/loop-goal-preflight.md](./docs/loop-goal-preflight.md) — loops + scheduled goals
|
|
195
|
+
- [CHANGELOG.md](./CHANGELOG.md) — release notes · [README.ko.md](./README.ko.md) — Korean
|
|
265
196
|
|
|
266
197
|
## License
|
|
267
198
|
|
|
268
|
-
[MIT](./LICENSE)
|
|
199
|
+
[MIT](./LICENSE) · Source & issues: [cmblir/lazyclaw](https://github.com/cmblir/lazyclaw)
|
package/channels/handoff.mjs
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
// Pure function over (threads store, live channel map) — the CLI slash
|
|
5
5
|
// and the daemon HTTP route both call this.
|
|
6
6
|
|
|
7
|
+
// The note rides inside a message posted INTO a channel, and /handoff callers
|
|
8
|
+
// control it — strip control characters (no ANSI/newline injection into
|
|
9
|
+
// channel messages) and bound the length. Session ids are internal state and
|
|
10
|
+
// never belong in user-visible text.
|
|
11
|
+
function sanitizeNote(note) {
|
|
12
|
+
// eslint-disable-next-line no-control-regex
|
|
13
|
+
const cleaned = String(note || '').replace(/[\x00-\x1f\x7f]/g, ' ').trim();
|
|
14
|
+
return cleaned.length > 200 ? cleaned.slice(0, 200) + '…' : cleaned;
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
export async function runHandoff({ threads, channels, threadId, target, externalId, note = '' }) {
|
|
8
18
|
const cur = threads.findByThread(threadId);
|
|
9
19
|
if (!cur) {
|
|
@@ -23,7 +33,8 @@ export async function runHandoff({ threads, channels, threadId, target, external
|
|
|
23
33
|
const next = threads.handoff(threadId, { channel: target, externalId });
|
|
24
34
|
|
|
25
35
|
// 2. Notify source (best-effort) so the human knows where the convo went.
|
|
26
|
-
const
|
|
36
|
+
const cleanNote = sanitizeNote(note);
|
|
37
|
+
const tail = cleanNote ? ` — ${cleanNote}` : '';
|
|
27
38
|
if (channels[srcChannel] && typeof channels[srcChannel].send === 'function') {
|
|
28
39
|
try {
|
|
29
40
|
await channels[srcChannel].send(srcExternal,
|
|
@@ -33,9 +44,10 @@ export async function runHandoff({ threads, channels, threadId, target, external
|
|
|
33
44
|
}
|
|
34
45
|
}
|
|
35
46
|
|
|
36
|
-
// 3. Notify target with a resume marker.
|
|
47
|
+
// 3. Notify target with a resume marker. (No session id — internal state
|
|
48
|
+
// never belongs in user-visible channel text.)
|
|
37
49
|
await channels[target].send(externalId,
|
|
38
|
-
`resumed from ${srcChannel}
|
|
50
|
+
`resumed from ${srcChannel}${tail}`);
|
|
39
51
|
|
|
40
52
|
return next;
|
|
41
53
|
}
|
|
@@ -62,8 +74,9 @@ export async function handoffWithRollback({ threads, threadId, target, externalI
|
|
|
62
74
|
const next = threads.handoff(threadId, { channel: target, externalId: String(externalId) });
|
|
63
75
|
if (typeof send === 'function') {
|
|
64
76
|
try {
|
|
65
|
-
const
|
|
66
|
-
|
|
77
|
+
const cleanNote = sanitizeNote(note);
|
|
78
|
+
const tail = cleanNote ? ` — ${cleanNote}` : '';
|
|
79
|
+
await send(String(externalId), `resumed from ${prior.channel}${tail}`);
|
|
67
80
|
} catch (e) {
|
|
68
81
|
// Target never got the resume marker — roll the binding back so the
|
|
69
82
|
// session isn't stranded on a channel that can't be reached.
|