lazyclaw 6.3.1 → 6.5.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.
Files changed (179) hide show
  1. package/README.ko.md +5 -1
  2. package/README.md +17 -13
  3. package/agents.mjs +54 -4
  4. package/channels-discord/index.mjs +5 -1
  5. package/channels-email/index.mjs +4 -3
  6. package/channels-whatsapp/index.mjs +3 -2
  7. package/chat_window.mjs +11 -2
  8. package/cli.mjs +103 -3
  9. package/commands/agents.mjs +7 -193
  10. package/commands/agents_registry.mjs +205 -0
  11. package/commands/auth_nodes.mjs +19 -1
  12. package/commands/automation.mjs +28 -94
  13. package/commands/automation_loops.mjs +94 -0
  14. package/commands/channels.mjs +46 -3
  15. package/commands/chat.mjs +127 -704
  16. package/commands/chat_hardening.mjs +23 -0
  17. package/commands/chat_legacy_slash.mjs +716 -0
  18. package/commands/config.mjs +36 -2
  19. package/commands/daemon.mjs +99 -1
  20. package/commands/gateway.mjs +123 -4
  21. package/commands/help_text.mjs +78 -0
  22. package/commands/mcp.mjs +150 -0
  23. package/commands/misc.mjs +20 -0
  24. package/commands/sessions.mjs +68 -8
  25. package/commands/setup.mjs +44 -80
  26. package/commands/setup_channels.mjs +219 -47
  27. package/commands/skills.mjs +19 -1
  28. package/commands/workflow_named.mjs +137 -0
  29. package/config-validate.mjs +10 -1
  30. package/config_features.mjs +45 -0
  31. package/cron.mjs +19 -8
  32. package/daemon/lib/auth.mjs +25 -0
  33. package/daemon/lib/cost.mjs +41 -0
  34. package/daemon/lib/respond.mjs +20 -1
  35. package/daemon/lib/team_inbound.mjs +69 -0
  36. package/daemon/route_table.mjs +5 -1
  37. package/daemon/routes/_deps.mjs +2 -2
  38. package/daemon/routes/config.mjs +11 -6
  39. package/daemon/routes/conversation.mjs +103 -40
  40. package/daemon/routes/events.mjs +45 -0
  41. package/daemon/routes/meta.mjs +38 -7
  42. package/daemon/routes/providers.mjs +26 -6
  43. package/daemon/routes/workflows.mjs +30 -0
  44. package/daemon.mjs +27 -3
  45. package/dotenv_min.mjs +15 -3
  46. package/gateway/challenge_registry.mjs +90 -0
  47. package/gateway/device_auth.mjs +71 -97
  48. package/gateway/http_gateway.mjs +29 -3
  49. package/lib/args.mjs +69 -3
  50. package/lib/config.mjs +85 -3
  51. package/lib/render.mjs +43 -0
  52. package/lib/service_install.mjs +15 -1
  53. package/mas/agent_turn.mjs +68 -12
  54. package/mas/confidence.mjs +20 -1
  55. package/mas/embedder.mjs +100 -0
  56. package/mas/events.mjs +57 -0
  57. package/mas/index_db.mjs +103 -10
  58. package/mas/learning.mjs +96 -72
  59. package/mas/mention_router.mjs +48 -90
  60. package/mas/prompt_stack.mjs +60 -3
  61. package/mas/recall_blend.mjs +56 -0
  62. package/mas/redact.mjs +55 -0
  63. package/mas/router_posting.mjs +96 -0
  64. package/mas/router_termination.mjs +63 -0
  65. package/mas/scrub_env.mjs +21 -6
  66. package/mas/skill_synth.mjs +7 -33
  67. package/mas/tool_runner.mjs +3 -2
  68. package/mas/tools/bash.mjs +9 -2
  69. package/mas/tools/coding.mjs +28 -5
  70. package/mas/tools/delegation.mjs +34 -4
  71. package/mas/tools/git.mjs +19 -9
  72. package/mas/tools/ha.mjs +2 -0
  73. package/mas/tools/learning.mjs +55 -0
  74. package/mas/tools/media.mjs +21 -3
  75. package/mas/tools/os.mjs +70 -16
  76. package/mas/tools/recall.mjs +28 -2
  77. package/mcp/client.mjs +8 -1
  78. package/mcp/server_spawn.mjs +5 -1
  79. package/memory.mjs +24 -0
  80. package/package.json +3 -1
  81. package/providers/anthropic.mjs +101 -6
  82. package/providers/cache.mjs +9 -1
  83. package/providers/claude_cli.mjs +104 -22
  84. package/providers/claude_cli_session.mjs +183 -0
  85. package/providers/cli_error.mjs +38 -0
  86. package/providers/cli_login.mjs +179 -0
  87. package/providers/codex_cli.mjs +70 -12
  88. package/providers/gemini.mjs +104 -3
  89. package/providers/gemini_cli.mjs +78 -17
  90. package/providers/model_catalogue.mjs +33 -2
  91. package/providers/ollama.mjs +104 -3
  92. package/providers/openai.mjs +110 -8
  93. package/providers/openai_compat.mjs +97 -6
  94. package/providers/orchestrator.mjs +112 -12
  95. package/providers/registry.mjs +15 -9
  96. package/providers/retry.mjs +14 -2
  97. package/providers/tool_use/anthropic.mjs +17 -3
  98. package/providers/tool_use/claude_cli.mjs +72 -20
  99. package/providers/tool_use/gemini.mjs +29 -2
  100. package/providers/tool_use/openai.mjs +35 -5
  101. package/sandbox/confiners/seatbelt.mjs +37 -12
  102. package/sandbox/index.mjs +49 -0
  103. package/sandbox/local.mjs +7 -1
  104. package/sandbox/spawn.mjs +144 -0
  105. package/sandbox.mjs +5 -1
  106. package/scripts/loop-worker.mjs +25 -1
  107. package/sessions.mjs +0 -0
  108. package/skills/channel-style.md +20 -0
  109. package/skills/code-review.md +33 -0
  110. package/skills/commit-message.md +30 -0
  111. package/skills/concise.md +24 -0
  112. package/skills/debug-coach.md +25 -0
  113. package/skills/explain.md +24 -0
  114. package/skills/korean.md +25 -0
  115. package/skills/summarize.md +33 -0
  116. package/skills.mjs +24 -2
  117. package/skills_curator.mjs +6 -0
  118. package/skills_install.mjs +10 -2
  119. package/tasks.mjs +6 -1
  120. package/teams.mjs +78 -0
  121. package/tui/banner.mjs +72 -0
  122. package/tui/chat_mode_slash.mjs +59 -0
  123. package/tui/config_picker.mjs +1 -0
  124. package/tui/editor.mjs +0 -0
  125. package/tui/editor_anchor.mjs +46 -0
  126. package/tui/editor_keys.mjs +275 -0
  127. package/tui/hud.mjs +111 -0
  128. package/tui/login_flow.mjs +113 -0
  129. package/tui/modal_picker.mjs +10 -1
  130. package/tui/model_pick.mjs +287 -0
  131. package/tui/orchestrator_flow.mjs +164 -0
  132. package/tui/orchestrator_setup.mjs +135 -0
  133. package/tui/pickers.mjs +218 -248
  134. package/tui/repl.mjs +118 -209
  135. package/tui/repl_altbuffer.mjs +63 -0
  136. package/tui/repl_reducers.mjs +114 -0
  137. package/tui/repl_reset.mjs +37 -0
  138. package/tui/run_turn.mjs +228 -26
  139. package/tui/slash_args.mjs +159 -0
  140. package/tui/slash_channels.mjs +208 -0
  141. package/tui/slash_commands.mjs +7 -5
  142. package/tui/slash_dashboard.mjs +220 -0
  143. package/tui/slash_dispatcher.mjs +339 -774
  144. package/tui/slash_helpers.mjs +68 -0
  145. package/tui/slash_popup.mjs +5 -1
  146. package/tui/slash_trainer.mjs +173 -0
  147. package/tui/splash.mjs +15 -2
  148. package/tui/status_bar.mjs +26 -0
  149. package/tui/theme.mjs +28 -0
  150. package/web/avatars/01.png +0 -0
  151. package/web/avatars/02.png +0 -0
  152. package/web/avatars/03.png +0 -0
  153. package/web/avatars/04.png +0 -0
  154. package/web/avatars/05.png +0 -0
  155. package/web/avatars/06.png +0 -0
  156. package/web/avatars/07.png +0 -0
  157. package/web/avatars/08.png +0 -0
  158. package/web/avatars/09.png +0 -0
  159. package/web/avatars/10.png +0 -0
  160. package/web/avatars/11.png +0 -0
  161. package/web/avatars/12.png +0 -0
  162. package/web/avatars/13.png +0 -0
  163. package/web/avatars/14.png +0 -0
  164. package/web/avatars/15.png +0 -0
  165. package/web/avatars/16.png +0 -0
  166. package/web/avatars/17.png +0 -0
  167. package/web/avatars/18.png +0 -0
  168. package/web/avatars/19.png +0 -0
  169. package/web/avatars/20.png +0 -0
  170. package/web/dashboard.css +77 -0
  171. package/web/dashboard.html +29 -2
  172. package/web/dashboard.js +296 -33
  173. package/workflow/builtin_caps.mjs +94 -0
  174. package/workflow/declarative.mjs +101 -0
  175. package/workflow/named.mjs +71 -0
  176. package/workflow/named_cron.mjs +50 -0
  177. package/workflow/nodes.mjs +67 -0
  178. package/workflow/run_request.mjs +74 -0
  179. package/workflow/yaml_min.mjs +97 -0
@@ -0,0 +1,94 @@
1
+ // Detached loop-worker management commands, extracted from
2
+ // commands/automation.mjs to keep that file under the size gate. Owns the
3
+ // _killLog/KILL_ESCALATE_MS loop-kill escalation state, which is private to
4
+ // cmdLoops.
5
+ import path from 'node:path';
6
+ import { configPath } from '../lib/config.mjs';
7
+
8
+ // Kill registry — `lazyclaw loops kill <id>` SIGTERMs once and SIGKILLs
9
+ // on a second invocation within KILL_ESCALATE_MS. Module-scoped so two
10
+ // rapid invocations of `cmd loops kill <id>` from the same process see
11
+ // each other; for separate processes the worker also handles SIGKILL by
12
+ // the OS, so the escalation is a UX nicety rather than a correctness gate.
13
+ const _killLog = new Map();
14
+ const KILL_ESCALATE_MS = 5000;
15
+
16
+ export async function cmdLoops(sub, positional, flags = {}) {
17
+ const loopsMod = await import('../loops.mjs');
18
+ const cfgDir = path.dirname(configPath());
19
+ switch (sub) {
20
+ case undefined:
21
+ case 'list': {
22
+ const items = loopsMod.listLoops(cfgDir).map(loopsMod.reconcileStatus);
23
+ console.log(JSON.stringify(items, null, 2));
24
+ return;
25
+ }
26
+ case 'show': {
27
+ const id = positional[0];
28
+ if (!id) { console.error('Usage: lazyclaw loops show <id>'); process.exit(2); }
29
+ const meta = loopsMod.reconcileStatus(loopsMod.readMeta(id, cfgDir));
30
+ if (!meta) { console.error(`no loop "${id}"`); process.exit(1); }
31
+ const iterations = loopsMod.readIterations(id, cfgDir);
32
+ const result = loopsMod.readResult(id, cfgDir);
33
+ console.log(JSON.stringify({ id, meta, iterations, result }, null, 2));
34
+ return;
35
+ }
36
+ case 'kill': {
37
+ const id = positional[0];
38
+ if (!id) { console.error('Usage: lazyclaw loops kill <id>'); process.exit(2); }
39
+ const meta = loopsMod.readMeta(id, cfgDir);
40
+ if (!meta) { console.error(`no loop "${id}"`); process.exit(1); }
41
+ if (!meta.pid) { console.error(`loop "${id}" has no pid`); process.exit(1); }
42
+ const last = _killLog.get(id) || 0;
43
+ const now = Date.now();
44
+ const escalate = (now - last) < KILL_ESCALATE_MS && last > 0;
45
+ const sig = escalate ? 'SIGKILL' : 'SIGTERM';
46
+ try { process.kill(meta.pid, sig); }
47
+ catch (e) {
48
+ if (e?.code !== 'ESRCH') throw e;
49
+ // Already gone — reconcile and report.
50
+ loopsMod.patchMeta(id, { status: 'killed', finishedAt: new Date().toISOString() }, cfgDir);
51
+ console.log(JSON.stringify({ id, pid: meta.pid, signal: sig, status: 'already_gone' }));
52
+ return;
53
+ }
54
+ _killLog.set(id, now);
55
+ console.log(JSON.stringify({ id, pid: meta.pid, signal: sig, escalated: escalate }));
56
+ return;
57
+ }
58
+ case 'tail': {
59
+ const id = positional[0];
60
+ if (!id) { console.error('Usage: lazyclaw loops tail <id>'); process.exit(2); }
61
+ const dir = loopsMod.loopDir(id, cfgDir);
62
+ const logPath = path.join(dir, 'iterations.log');
63
+ const fs = await import('node:fs');
64
+ if (!fs.existsSync(dir)) { console.error(`no loop "${id}"`); process.exit(1); }
65
+ // Print everything already on disk first, then poll for new lines
66
+ // until the worker exits / status is no longer "running".
67
+ let offset = 0;
68
+ if (fs.existsSync(logPath)) {
69
+ const buf = fs.readFileSync(logPath, 'utf8');
70
+ process.stdout.write(buf);
71
+ offset = buf.length;
72
+ }
73
+ const pollMs = Number(flags['poll-ms']) || 250;
74
+ const maxMs = Number(flags['max-wait-ms']) || 0; // 0 = wait indefinitely
75
+ const startedAt = Date.now();
76
+ while (true) {
77
+ await new Promise(r => setTimeout(r, pollMs));
78
+ let cur = '';
79
+ try { cur = fs.readFileSync(logPath, 'utf8'); } catch { /* file may briefly not exist */ }
80
+ if (cur.length > offset) {
81
+ process.stdout.write(cur.slice(offset));
82
+ offset = cur.length;
83
+ }
84
+ const meta = loopsMod.reconcileStatus(loopsMod.readMeta(id, cfgDir));
85
+ if (!meta || meta.status !== 'running') break;
86
+ if (maxMs > 0 && Date.now() - startedAt > maxMs) break;
87
+ }
88
+ return;
89
+ }
90
+ default:
91
+ console.error('Usage: lazyclaw loops <list|show|kill|tail> ...');
92
+ process.exit(2);
93
+ }
94
+ }
@@ -200,9 +200,40 @@ export async function cmdChannels(sub, positional = [], flags = {}) {
200
200
 
201
201
  if (sub === 'install') {
202
202
  const name = positional[0];
203
- if (!name) { process.stderr.write('usage: lazyclaw channels install <@lazyclaw/channel-name>\n'); process.exit(2); }
204
- const info = await loader.install(name);
205
- process.stdout.write(`installed ${info.name}@${info.version}\n`);
203
+ if (!name) { process.stderr.write('usage: lazyclaw channels install <name> (e.g. discord, email, whatsapp)\n'); process.exit(2); }
204
+ const { isPluginName } = await import('../channels/loader.mjs');
205
+ // Back-compat: an explicit @lazyclaw/channel-* spec still routes through the
206
+ // legacy plugin loader (for anyone shipping a real published package).
207
+ if (isPluginName(name)) {
208
+ const info = await loader.install(name);
209
+ process.stdout.write(`installed ${info.name}@${info.version}\n`);
210
+ return;
211
+ }
212
+ const { channelByName, channelReadiness } = await import('./setup_channels.mjs');
213
+ const spec = channelByName(name.toLowerCase());
214
+ if (!spec) { process.stderr.write(`unknown channel: ${name} (known: ${KNOWN_CHANNELS.join(', ')})\n`); process.exit(2); }
215
+ if (spec.builtin) { process.stdout.write(`channel ${spec.name} is built in — no install needed.\n`); return; }
216
+ if (spec.binary && (!spec.deps || !spec.deps.length)) {
217
+ process.stderr.write(`channel ${spec.name} needs the external "${spec.binary}" binary on your PATH (not an npm package). Install it from its project, then: lazyclaw channels enable ${spec.name}\n`);
218
+ process.exit(2);
219
+ }
220
+ const deps = spec.deps || [];
221
+ if (!deps.length) { process.stdout.write(`channel ${spec.name} needs no runtime package — set creds with \`lazyclaw setup\`, then \`lazyclaw channels enable ${spec.name}\`.\n`); return; }
222
+ // Install the in-tree adapter's runtime deps INTO the config dir; the
223
+ // gateway resolves them from there (commands/gateway.mjs _loadPluginChannel).
224
+ const { spawnSync } = await import('node:child_process');
225
+ process.stdout.write(`installing ${deps.join(', ')} into ${cfgDir} …\n`);
226
+ const res = spawnSync('npm', ['install', '--no-audit', '--no-fund', '--prefix', cfgDir, ...deps], { stdio: 'inherit', env: process.env });
227
+ if (res.status !== 0) { process.stderr.write(`npm install failed (exit ${res.status})\n`); process.exit(1); }
228
+ const r = channelReadiness(spec.name, cfgDir);
229
+ if (r.ready) {
230
+ const cfg = readConfig();
231
+ channelSetEnabled(cfg, spec.name, true);
232
+ writeConfig(cfg);
233
+ process.stdout.write(`installed ${deps.join(', ')} — channel ${spec.name} enabled.\n`);
234
+ } else {
235
+ process.stdout.write(`installed ${deps.join(', ')}. Still missing: ${[...r.missingDeps, r.missingBinary].filter(Boolean).join(', ')}.\n`);
236
+ }
206
237
  return;
207
238
  }
208
239
  if (sub === 'remove' || sub === 'uninstall') {
@@ -212,6 +243,18 @@ export async function cmdChannels(sub, positional = [], flags = {}) {
212
243
  process.stdout.write(`removed ${name}\n`);
213
244
  return;
214
245
  }
246
+ if (sub === 'test') {
247
+ const name = (positional[0] || '').toLowerCase();
248
+ if (!name) { process.stderr.write('usage: lazyclaw channels test <name>\n'); process.exit(2); }
249
+ const { verifyChannel } = await import('./setup_channels.mjs');
250
+ try { (await import('../dotenv_min.mjs')).loadDotenvIfAny(cfgDir); } catch { /* best-effort */ }
251
+ const r = await verifyChannel(name);
252
+ if (flags.json) { process.stdout.write(JSON.stringify({ channel: name, ...r }) + '\n'); return; }
253
+ if (r.ok === true) process.stdout.write(`✓ ${name} verified — ${r.detail}\n`);
254
+ else if (r.ok === null) process.stdout.write(`· ${name}: ${r.detail}\n`);
255
+ else { process.stdout.write(`✗ ${name}: ${r.detail}${r.hint ? `\n fix: ${r.hint}` : ''}\n`); process.exitCode = 1; }
256
+ return;
257
+ }
215
258
  if (sub === 'enable' || sub === 'disable') {
216
259
  const name = (positional[0] || '').toLowerCase();
217
260
  if (!name) { process.stderr.write(`usage: lazyclaw channels ${sub} <name>\n`); process.exit(2); }