lazyclaw 5.4.4 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/channels/handoff.mjs +36 -0
- package/channels-discord/index.mjs +76 -0
- package/channels-discord/package.json +14 -0
- package/channels-email/index.mjs +109 -0
- package/channels-email/package.json +14 -0
- package/channels-signal/index.mjs +69 -0
- package/channels-signal/package.json +9 -0
- package/channels-voice/index.mjs +81 -0
- package/channels-voice/package.json +9 -0
- package/channels-whatsapp/index.mjs +70 -0
- package/channels-whatsapp/package.json +13 -0
- package/cli.mjs +73 -7399
- package/commands/agents.mjs +669 -0
- package/commands/auth_nodes.mjs +323 -0
- package/commands/automation.mjs +582 -0
- package/commands/channels.mjs +255 -0
- package/commands/chat.mjs +1217 -0
- package/commands/config.mjs +315 -0
- package/commands/daemon.mjs +260 -0
- package/commands/misc.mjs +128 -0
- package/commands/providers.mjs +511 -0
- package/commands/sessions.mjs +343 -0
- package/commands/setup.mjs +741 -0
- package/commands/skills.mjs +218 -0
- package/commands/workflow.mjs +661 -0
- package/daemon/lib/auth.mjs +58 -0
- package/daemon/lib/cost.mjs +30 -0
- package/daemon/lib/provider.mjs +69 -0
- package/daemon/lib/respond.mjs +83 -0
- package/daemon/route_table.mjs +96 -0
- package/daemon/routes/_deps.mjs +36 -0
- package/daemon/routes/config.mjs +99 -0
- package/daemon/routes/conversation.mjs +371 -0
- package/daemon/routes/meta.mjs +239 -0
- package/daemon/routes/ops.mjs +185 -0
- package/daemon/routes/providers.mjs +211 -0
- package/daemon/routes/rates.mjs +90 -0
- package/daemon/routes/registry.mjs +223 -0
- package/daemon/routes/sessions.mjs +213 -0
- package/daemon/routes/skills.mjs +260 -0
- package/daemon/routes/workflows.mjs +224 -0
- package/daemon.mjs +23 -2085
- package/dotenv_min.mjs +23 -0
- package/first_run.mjs +15 -0
- package/goals_cron.mjs +37 -0
- package/lib/args.mjs +216 -0
- package/lib/config.mjs +113 -0
- package/lib/registry_boot.mjs +55 -0
- package/mas/agent_turn.mjs +2 -1
- package/mas/index_db.mjs +82 -0
- package/mas/learning.mjs +17 -1
- package/mas/mention_router.mjs +38 -10
- package/mas/provider_adapters.mjs +28 -4
- package/mas/scrub_env.mjs +34 -0
- package/mas/tool_runner.mjs +23 -7
- package/mas/tools/bash.mjs +10 -5
- package/mas/tools/browser.mjs +18 -0
- package/mas/tools/learning.mjs +24 -14
- package/mas/tools/recall.mjs +5 -1
- package/mas/tools/web.mjs +47 -11
- package/mas/trajectory_store.mjs +7 -4
- package/package.json +16 -2
- package/providers/auth_store.mjs +22 -0
- package/providers/claude_cli.mjs +28 -2
- package/providers/claude_cli_detect.mjs +46 -0
- package/providers/custom_provider.mjs +70 -0
- package/providers/model_catalogue.mjs +86 -0
- package/providers/orchestrator.mjs +30 -9
- package/providers/rates.mjs +12 -2
- package/providers/registry.mjs +10 -7
- package/sandbox/confiners/landlock.mjs +14 -8
- package/sandbox/confiners/seatbelt.mjs +18 -2
- package/scripts/loop-worker.mjs +18 -7
- package/scripts/migrate-v5.mjs +5 -61
- package/secure_write.mjs +46 -0
- package/sessions.mjs +0 -0
- package/tui/modal_filter.mjs +59 -0
- package/tui/modal_picker.mjs +12 -37
- package/tui/pickers.mjs +917 -0
- package/tui/provider_families.mjs +41 -0
- package/tui/repl.mjs +67 -36
- package/tui/slash_commands.mjs +2 -1
- package/tui/slash_dispatcher.mjs +717 -58
- package/tui/splash.mjs +5 -12
- package/tui/subcommands.mjs +17 -0
- package/tui/terminal_approve.mjs +37 -0
- package/web/dashboard.css +275 -0
- package/web/dashboard.html +2 -1685
- package/web/dashboard.js +1406 -0
- package/workflow/persistent.mjs +13 -6
- package/mas/tools/skill_view.mjs +0 -43
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Slack / Telegram / Matrix listener commands, extracted from cli.mjs (D3).
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { configPath, readConfig, _resolveAuthKey } from '../lib/config.mjs';
|
|
4
|
+
import { ensureRegistry, getRegistry } from '../lib/registry_boot.mjs';
|
|
5
|
+
import { loadDotenvIfAny as _loadDotenvShared } from '../dotenv_min.mjs';
|
|
6
|
+
|
|
7
|
+
// Thin .env loader wrapper kept local so the module stays self-contained.
|
|
8
|
+
export function _loadDotenvIfAny(cfgDir) { return _loadDotenvShared(cfgDir); }
|
|
9
|
+
|
|
10
|
+
export async function cmdSlack(sub, positional, flags = {}) {
|
|
11
|
+
if (sub !== 'listen') {
|
|
12
|
+
console.error('Usage: lazyclaw slack listen [--provider X] [--model Y]');
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
await ensureRegistry();
|
|
16
|
+
const cfg = readConfig();
|
|
17
|
+
const cfgDir = path.dirname(configPath());
|
|
18
|
+
|
|
19
|
+
const envInfo = _loadDotenvIfAny(cfgDir);
|
|
20
|
+
process.stderr.write(`[slack] .env: ${envInfo.loaded} keys loaded from ${envInfo.path}\n`);
|
|
21
|
+
|
|
22
|
+
const provName = flags.provider || cfg.provider || 'mock';
|
|
23
|
+
const prov = getRegistry().PROVIDERS[provName];
|
|
24
|
+
if (!prov) { console.error(`unknown provider: ${provName}`); process.exit(2); }
|
|
25
|
+
const model = flags.model || cfg.model;
|
|
26
|
+
|
|
27
|
+
// Per-thread rolling chat history so multi-turn coherence works
|
|
28
|
+
// without committing to on-disk sessions. Capped at MAX_TURNS to
|
|
29
|
+
// bound the prompt size.
|
|
30
|
+
const threadMsgs = new Map();
|
|
31
|
+
const MAX_TURNS = 20;
|
|
32
|
+
|
|
33
|
+
const handler = async ({ threadId, text }) => {
|
|
34
|
+
const cleaned = String(text || '').replace(/<@[A-Z0-9]+>/g, '').trim();
|
|
35
|
+
// Phase 19.2: never post a placeholder ("(empty message)" / "(empty
|
|
36
|
+
// reply)") into the thread — those leaked through as visible noise
|
|
37
|
+
// when listener self-message echoes happened. Return null and let
|
|
38
|
+
// _simulateInbound's guard drop the send. Real provider errors
|
|
39
|
+
// still surface so the operator knows something went wrong.
|
|
40
|
+
if (!cleaned) {
|
|
41
|
+
process.stderr.write('[slack] dropping empty inbound (after mention strip)\n');
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const msgs = threadMsgs.get(threadId) || [];
|
|
45
|
+
msgs.push({ role: 'user', content: cleaned });
|
|
46
|
+
let acc = '';
|
|
47
|
+
try {
|
|
48
|
+
for await (const chunk of prov.sendMessage(msgs, {
|
|
49
|
+
apiKey: _resolveAuthKey(cfg, provName),
|
|
50
|
+
model,
|
|
51
|
+
})) acc += chunk;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
msgs.pop();
|
|
54
|
+
const why = err?.message || String(err);
|
|
55
|
+
process.stderr.write(`[slack] provider error: ${why}\n`);
|
|
56
|
+
return `(provider error: ${why})`;
|
|
57
|
+
}
|
|
58
|
+
msgs.push({ role: 'assistant', content: acc });
|
|
59
|
+
if (msgs.length > MAX_TURNS) msgs.splice(0, msgs.length - MAX_TURNS);
|
|
60
|
+
threadMsgs.set(threadId, msgs);
|
|
61
|
+
if (!acc.trim()) {
|
|
62
|
+
process.stderr.write('[slack] provider returned empty text — not posting\n');
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return acc;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const { SlackChannel } = await import('../channels/slack.mjs');
|
|
69
|
+
const ch = new SlackChannel();
|
|
70
|
+
process.stderr.write(`[slack] provider=${provName} model=${model || '(default)'}\n`);
|
|
71
|
+
try {
|
|
72
|
+
await ch.start(handler);
|
|
73
|
+
await ch._connectSocketMode({ logger: (line) => process.stderr.write(line) });
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err?.code === 'SLACK_MISSING_ENV') {
|
|
76
|
+
console.error(`slack: missing env vars: ${(err.missing || []).join(', ')}`);
|
|
77
|
+
console.error(`hint: set them in ${path.join(cfgDir, '.env')} (uncomment SLACK_APP_TOKEN / SLACK_SIGNING_SECRET)`);
|
|
78
|
+
} else {
|
|
79
|
+
console.error(`slack: ${err?.message || err}`);
|
|
80
|
+
}
|
|
81
|
+
process.exit(2);
|
|
82
|
+
}
|
|
83
|
+
process.stderr.write(`[slack] listening. Ctrl-C to stop.\n`);
|
|
84
|
+
|
|
85
|
+
await new Promise((resolve) => {
|
|
86
|
+
const onSig = async () => {
|
|
87
|
+
process.stderr.write(`\n[slack] shutting down…\n`);
|
|
88
|
+
try { await ch.stop(); } catch { /* best-effort */ }
|
|
89
|
+
resolve();
|
|
90
|
+
};
|
|
91
|
+
process.once('SIGINT', onSig);
|
|
92
|
+
process.once('SIGTERM', onSig);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// `lazyclaw telegram listen` — zero-install mobile control surface.
|
|
97
|
+
// Long-polls the Telegram Bot API (no public URL / webhook needed) and
|
|
98
|
+
// pipes each inbound message through the active provider, replying in
|
|
99
|
+
// the same chat. Mirrors `slack listen`. Access is gated by the existing
|
|
100
|
+
// `pairing` allowlist (Telegram numeric user ids); an empty allowlist
|
|
101
|
+
// means "reply to anyone who can reach the bot".
|
|
102
|
+
export async function cmdTelegram(sub, positional, flags = {}) {
|
|
103
|
+
if (sub !== 'listen') {
|
|
104
|
+
console.error('Usage: lazyclaw telegram listen [--provider X] [--model Y]\n Long-polls the Telegram Bot API. Set TELEGRAM_BOT_TOKEN in ~/.lazyclaw/.env.\n Restrict who can talk to it with `lazyclaw pairing add <telegram-user-id>`.');
|
|
105
|
+
process.exit(2);
|
|
106
|
+
}
|
|
107
|
+
await ensureRegistry();
|
|
108
|
+
const cfg = readConfig();
|
|
109
|
+
const cfgDir = path.dirname(configPath());
|
|
110
|
+
|
|
111
|
+
const envInfo = _loadDotenvIfAny(cfgDir);
|
|
112
|
+
process.stderr.write(`[telegram] .env: ${envInfo.loaded} keys loaded from ${envInfo.path}\n`);
|
|
113
|
+
|
|
114
|
+
const provName = flags.provider || cfg.provider || 'mock';
|
|
115
|
+
const prov = getRegistry().PROVIDERS[provName];
|
|
116
|
+
if (!prov) { console.error(`unknown provider: ${provName}`); process.exit(2); }
|
|
117
|
+
const model = flags.model || cfg.model;
|
|
118
|
+
|
|
119
|
+
const threadMsgs = new Map();
|
|
120
|
+
const MAX_TURNS = 20;
|
|
121
|
+
|
|
122
|
+
const handler = async ({ threadId, text }) => {
|
|
123
|
+
const cleaned = String(text || '').trim();
|
|
124
|
+
if (!cleaned) { process.stderr.write('[telegram] dropping empty inbound\n'); return null; }
|
|
125
|
+
const msgs = threadMsgs.get(threadId) || [];
|
|
126
|
+
msgs.push({ role: 'user', content: cleaned });
|
|
127
|
+
let acc = '';
|
|
128
|
+
try {
|
|
129
|
+
for await (const chunk of prov.sendMessage(msgs, { apiKey: _resolveAuthKey(cfg, provName), model })) acc += chunk;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
msgs.pop();
|
|
132
|
+
const why = err?.message || String(err);
|
|
133
|
+
process.stderr.write(`[telegram] provider error: ${why}\n`);
|
|
134
|
+
return `(provider error: ${why})`;
|
|
135
|
+
}
|
|
136
|
+
msgs.push({ role: 'assistant', content: acc });
|
|
137
|
+
if (msgs.length > MAX_TURNS) msgs.splice(0, msgs.length - MAX_TURNS);
|
|
138
|
+
threadMsgs.set(threadId, msgs);
|
|
139
|
+
if (!acc.trim()) { process.stderr.write('[telegram] provider returned empty text — not posting\n'); return null; }
|
|
140
|
+
return acc;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// The pairing allowlist doubles as the Telegram sender allowlist.
|
|
144
|
+
const allowlist = (cfg.pairing || []).map((p) => String(p.id));
|
|
145
|
+
const { TelegramChannel } = await import('../channels/telegram.mjs');
|
|
146
|
+
let ch;
|
|
147
|
+
try {
|
|
148
|
+
ch = new TelegramChannel({ allowlist: allowlist.length ? allowlist : null });
|
|
149
|
+
} catch (err) {
|
|
150
|
+
console.error(`telegram: ${err?.message || err}`);
|
|
151
|
+
process.exit(2);
|
|
152
|
+
}
|
|
153
|
+
process.stderr.write(`[telegram] provider=${provName} model=${model || '(default)'} allowlist=${allowlist.length || 'open'}\n`);
|
|
154
|
+
try {
|
|
155
|
+
await ch.start(handler, { poll: true, logger: (line) => process.stderr.write(line) });
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err?.code === 'TELEGRAM_MISSING_TOKEN') {
|
|
158
|
+
console.error('telegram: TELEGRAM_BOT_TOKEN not set');
|
|
159
|
+
console.error(`hint: add TELEGRAM_BOT_TOKEN=... to ${path.join(cfgDir, '.env')}`);
|
|
160
|
+
} else {
|
|
161
|
+
console.error(`telegram: ${err?.message || err}`);
|
|
162
|
+
}
|
|
163
|
+
process.exit(2);
|
|
164
|
+
}
|
|
165
|
+
process.stderr.write(`[telegram] listening. Ctrl-C to stop.\n`);
|
|
166
|
+
|
|
167
|
+
await new Promise((resolve) => {
|
|
168
|
+
const onSig = async () => {
|
|
169
|
+
process.stderr.write(`\n[telegram] shutting down…\n`);
|
|
170
|
+
try { await ch.stop(); } catch { /* best-effort */ }
|
|
171
|
+
resolve();
|
|
172
|
+
};
|
|
173
|
+
process.once('SIGINT', onSig);
|
|
174
|
+
process.once('SIGTERM', onSig);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// `lazyclaw matrix listen` — Matrix inbound over the client-server API's
|
|
179
|
+
// long-poll /sync (no SDK). Mirrors `telegram listen`. Set MATRIX_HOMESERVER
|
|
180
|
+
// + MATRIX_ACCESS_TOKEN (+ MATRIX_USER_ID for self-filtering) in ~/.lazyclaw/.env.
|
|
181
|
+
export async function cmdMatrix(sub, positional, flags = {}) {
|
|
182
|
+
if (sub !== 'listen') {
|
|
183
|
+
console.error('Usage: lazyclaw matrix listen [--provider X] [--model Y]\n Long-polls the Matrix /sync API. Set MATRIX_HOMESERVER + MATRIX_ACCESS_TOKEN (+ MATRIX_USER_ID) in ~/.lazyclaw/.env.\n Restrict who can talk to it with `lazyclaw pairing add <@user:server>`.');
|
|
184
|
+
process.exit(2);
|
|
185
|
+
}
|
|
186
|
+
await ensureRegistry();
|
|
187
|
+
const cfg = readConfig();
|
|
188
|
+
const cfgDir = path.dirname(configPath());
|
|
189
|
+
|
|
190
|
+
const envInfo = _loadDotenvIfAny(cfgDir);
|
|
191
|
+
process.stderr.write(`[matrix] .env: ${envInfo.loaded} keys loaded from ${envInfo.path}\n`);
|
|
192
|
+
|
|
193
|
+
const provName = flags.provider || cfg.provider || 'mock';
|
|
194
|
+
const prov = getRegistry().PROVIDERS[provName];
|
|
195
|
+
if (!prov) { console.error(`unknown provider: ${provName}`); process.exit(2); }
|
|
196
|
+
const model = flags.model || cfg.model;
|
|
197
|
+
|
|
198
|
+
const threadMsgs = new Map();
|
|
199
|
+
const MAX_TURNS = 20;
|
|
200
|
+
const handler = async ({ threadId, text }) => {
|
|
201
|
+
const cleaned = String(text || '').trim();
|
|
202
|
+
if (!cleaned) { process.stderr.write('[matrix] dropping empty inbound\n'); return null; }
|
|
203
|
+
const msgs = threadMsgs.get(threadId) || [];
|
|
204
|
+
msgs.push({ role: 'user', content: cleaned });
|
|
205
|
+
let acc = '';
|
|
206
|
+
try {
|
|
207
|
+
for await (const chunk of prov.sendMessage(msgs, { apiKey: _resolveAuthKey(cfg, provName), model })) acc += chunk;
|
|
208
|
+
} catch (err) {
|
|
209
|
+
msgs.pop();
|
|
210
|
+
const why = err?.message || String(err);
|
|
211
|
+
process.stderr.write(`[matrix] provider error: ${why}\n`);
|
|
212
|
+
return `(provider error: ${why})`;
|
|
213
|
+
}
|
|
214
|
+
msgs.push({ role: 'assistant', content: acc });
|
|
215
|
+
if (msgs.length > MAX_TURNS) msgs.splice(0, msgs.length - MAX_TURNS);
|
|
216
|
+
threadMsgs.set(threadId, msgs);
|
|
217
|
+
if (!acc.trim()) { process.stderr.write('[matrix] provider returned empty text — not posting\n'); return null; }
|
|
218
|
+
return acc;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const allowlist = (cfg.pairing || []).map((p) => String(p.id));
|
|
222
|
+
const { MatrixChannel } = await import('../channels/matrix.mjs');
|
|
223
|
+
let ch;
|
|
224
|
+
try {
|
|
225
|
+
ch = new MatrixChannel({ allowlist: allowlist.length ? allowlist : null });
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error(`matrix: ${err?.message || err}`);
|
|
228
|
+
process.exit(2);
|
|
229
|
+
}
|
|
230
|
+
process.stderr.write(`[matrix] provider=${provName} model=${model || '(default)'} allowlist=${allowlist.length || 'open'}\n`);
|
|
231
|
+
try {
|
|
232
|
+
await ch.start(handler, { poll: true, logger: (line) => process.stderr.write(line) });
|
|
233
|
+
} catch (err) {
|
|
234
|
+
if (err?.code === 'MATRIX_MISSING_TOKEN' || err?.code === 'MATRIX_MISSING_HOMESERVER') {
|
|
235
|
+
console.error(`matrix: ${err.message}`);
|
|
236
|
+
console.error(`hint: set MATRIX_HOMESERVER and MATRIX_ACCESS_TOKEN in ${path.join(cfgDir, '.env')}`);
|
|
237
|
+
} else {
|
|
238
|
+
console.error(`matrix: ${err?.message || err}`);
|
|
239
|
+
}
|
|
240
|
+
process.exit(2);
|
|
241
|
+
}
|
|
242
|
+
process.stderr.write(`[matrix] listening. Ctrl-C to stop.\n`);
|
|
243
|
+
|
|
244
|
+
await new Promise((resolve) => {
|
|
245
|
+
const onSig = async () => {
|
|
246
|
+
process.stderr.write(`\n[matrix] shutting down…\n`);
|
|
247
|
+
try { await ch.stop(); } catch { /* best-effort */ }
|
|
248
|
+
resolve();
|
|
249
|
+
};
|
|
250
|
+
process.once('SIGINT', onSig);
|
|
251
|
+
process.once('SIGTERM', onSig);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|