lazyclaw 6.3.1 → 6.4.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 (165) 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 +6 -1
  10. package/commands/auth_nodes.mjs +19 -1
  11. package/commands/automation.mjs +20 -5
  12. package/commands/channels.mjs +46 -3
  13. package/commands/chat.mjs +90 -33
  14. package/commands/chat_hardening.mjs +23 -0
  15. package/commands/daemon.mjs +99 -1
  16. package/commands/gateway.mjs +123 -4
  17. package/commands/mcp.mjs +150 -0
  18. package/commands/misc.mjs +20 -0
  19. package/commands/sessions.mjs +68 -8
  20. package/commands/setup.mjs +44 -8
  21. package/commands/setup_channels.mjs +219 -47
  22. package/commands/skills.mjs +19 -1
  23. package/commands/workflow_named.mjs +137 -0
  24. package/config-validate.mjs +10 -1
  25. package/config_features.mjs +45 -0
  26. package/cron.mjs +19 -8
  27. package/daemon/lib/auth.mjs +25 -0
  28. package/daemon/lib/cost.mjs +36 -0
  29. package/daemon/lib/respond.mjs +20 -1
  30. package/daemon/lib/team_inbound.mjs +69 -0
  31. package/daemon/route_table.mjs +5 -1
  32. package/daemon/routes/_deps.mjs +2 -2
  33. package/daemon/routes/config.mjs +11 -6
  34. package/daemon/routes/conversation.mjs +103 -40
  35. package/daemon/routes/events.mjs +45 -0
  36. package/daemon/routes/meta.mjs +38 -7
  37. package/daemon/routes/providers.mjs +26 -6
  38. package/daemon/routes/workflows.mjs +30 -0
  39. package/daemon.mjs +26 -2
  40. package/dotenv_min.mjs +15 -3
  41. package/gateway/challenge_registry.mjs +90 -0
  42. package/gateway/device_auth.mjs +71 -97
  43. package/gateway/http_gateway.mjs +29 -3
  44. package/lib/args.mjs +69 -3
  45. package/lib/config.mjs +85 -3
  46. package/lib/render.mjs +43 -0
  47. package/lib/service_install.mjs +15 -1
  48. package/mas/agent_turn.mjs +61 -12
  49. package/mas/confidence.mjs +20 -1
  50. package/mas/embedder.mjs +100 -0
  51. package/mas/events.mjs +57 -0
  52. package/mas/index_db.mjs +103 -10
  53. package/mas/learning.mjs +96 -72
  54. package/mas/mention_router.mjs +48 -90
  55. package/mas/prompt_stack.mjs +60 -3
  56. package/mas/recall_blend.mjs +56 -0
  57. package/mas/redact.mjs +55 -0
  58. package/mas/router_posting.mjs +96 -0
  59. package/mas/router_termination.mjs +63 -0
  60. package/mas/scrub_env.mjs +21 -6
  61. package/mas/skill_synth.mjs +7 -33
  62. package/mas/tool_runner.mjs +3 -2
  63. package/mas/tools/bash.mjs +9 -2
  64. package/mas/tools/coding.mjs +28 -5
  65. package/mas/tools/delegation.mjs +34 -4
  66. package/mas/tools/git.mjs +19 -9
  67. package/mas/tools/ha.mjs +2 -0
  68. package/mas/tools/learning.mjs +55 -0
  69. package/mas/tools/media.mjs +21 -3
  70. package/mas/tools/os.mjs +70 -16
  71. package/mas/tools/recall.mjs +28 -2
  72. package/mcp/client.mjs +8 -1
  73. package/mcp/server_spawn.mjs +5 -1
  74. package/memory.mjs +24 -0
  75. package/package.json +3 -1
  76. package/providers/anthropic.mjs +101 -6
  77. package/providers/cache.mjs +9 -1
  78. package/providers/claude_cli.mjs +104 -22
  79. package/providers/claude_cli_session.mjs +166 -0
  80. package/providers/cli_error.mjs +38 -0
  81. package/providers/cli_login.mjs +179 -0
  82. package/providers/codex_cli.mjs +66 -11
  83. package/providers/gemini.mjs +101 -3
  84. package/providers/gemini_cli.mjs +62 -15
  85. package/providers/model_catalogue.mjs +33 -2
  86. package/providers/ollama.mjs +104 -3
  87. package/providers/openai.mjs +110 -8
  88. package/providers/openai_compat.mjs +97 -6
  89. package/providers/orchestrator.mjs +112 -12
  90. package/providers/registry.mjs +15 -9
  91. package/providers/retry.mjs +14 -2
  92. package/providers/tool_use/anthropic.mjs +17 -3
  93. package/providers/tool_use/claude_cli.mjs +72 -20
  94. package/providers/tool_use/gemini.mjs +18 -2
  95. package/providers/tool_use/openai.mjs +28 -5
  96. package/sandbox/confiners/seatbelt.mjs +37 -12
  97. package/sandbox/index.mjs +49 -0
  98. package/sandbox/local.mjs +7 -1
  99. package/sandbox/spawn.mjs +144 -0
  100. package/sandbox.mjs +5 -1
  101. package/scripts/loop-worker.mjs +25 -1
  102. package/sessions.mjs +0 -0
  103. package/skills/channel-style.md +20 -0
  104. package/skills/code-review.md +33 -0
  105. package/skills/commit-message.md +30 -0
  106. package/skills/concise.md +24 -0
  107. package/skills/debug-coach.md +25 -0
  108. package/skills/explain.md +24 -0
  109. package/skills/korean.md +25 -0
  110. package/skills/summarize.md +33 -0
  111. package/skills.mjs +24 -2
  112. package/skills_curator.mjs +6 -0
  113. package/skills_install.mjs +10 -2
  114. package/tasks.mjs +6 -1
  115. package/teams.mjs +78 -0
  116. package/tui/chat_mode_slash.mjs +59 -0
  117. package/tui/config_picker.mjs +1 -0
  118. package/tui/editor.mjs +178 -151
  119. package/tui/editor_keys.mjs +275 -0
  120. package/tui/hud.mjs +111 -0
  121. package/tui/login_flow.mjs +113 -0
  122. package/tui/modal_picker.mjs +10 -1
  123. package/tui/model_pick.mjs +287 -0
  124. package/tui/orchestrator_flow.mjs +164 -0
  125. package/tui/pickers.mjs +196 -80
  126. package/tui/repl.mjs +106 -44
  127. package/tui/repl_reset.mjs +37 -0
  128. package/tui/run_turn.mjs +228 -26
  129. package/tui/slash_args.mjs +159 -0
  130. package/tui/slash_commands.mjs +7 -5
  131. package/tui/slash_dispatcher.mjs +537 -344
  132. package/tui/slash_popup.mjs +5 -1
  133. package/tui/splash.mjs +15 -2
  134. package/tui/status_bar.mjs +26 -0
  135. package/tui/theme.mjs +28 -0
  136. package/web/avatars/01.png +0 -0
  137. package/web/avatars/02.png +0 -0
  138. package/web/avatars/03.png +0 -0
  139. package/web/avatars/04.png +0 -0
  140. package/web/avatars/05.png +0 -0
  141. package/web/avatars/06.png +0 -0
  142. package/web/avatars/07.png +0 -0
  143. package/web/avatars/08.png +0 -0
  144. package/web/avatars/09.png +0 -0
  145. package/web/avatars/10.png +0 -0
  146. package/web/avatars/11.png +0 -0
  147. package/web/avatars/12.png +0 -0
  148. package/web/avatars/13.png +0 -0
  149. package/web/avatars/14.png +0 -0
  150. package/web/avatars/15.png +0 -0
  151. package/web/avatars/16.png +0 -0
  152. package/web/avatars/17.png +0 -0
  153. package/web/avatars/18.png +0 -0
  154. package/web/avatars/19.png +0 -0
  155. package/web/avatars/20.png +0 -0
  156. package/web/dashboard.css +77 -0
  157. package/web/dashboard.html +29 -2
  158. package/web/dashboard.js +296 -33
  159. package/workflow/builtin_caps.mjs +94 -0
  160. package/workflow/declarative.mjs +101 -0
  161. package/workflow/named.mjs +71 -0
  162. package/workflow/named_cron.mjs +50 -0
  163. package/workflow/nodes.mjs +67 -0
  164. package/workflow/run_request.mjs +74 -0
  165. package/workflow/yaml_min.mjs +97 -0
@@ -86,6 +86,10 @@ export function SlashPopup({
86
86
  selectedIndex = 0,
87
87
  maxRows = 8,
88
88
  columns,
89
+ // When true, always render the selectable chooser (used by the arg popup,
90
+ // whose buffer has a space — which would otherwise collapse a single
91
+ // candidate into the non-selectable inline hint).
92
+ forceChooser = false,
89
93
  }) {
90
94
  if (!commands || commands.length === 0) return null;
91
95
  const cols = columns
@@ -97,7 +101,7 @@ export function SlashPopup({
97
101
 
98
102
  // Inline-hint mode: buffer already has args + a single match. Render
99
103
  // the help text on one dimmed line. No border, no chooser.
100
- const isInlineHint = buffer.includes(' ') && commands.length === 1;
104
+ const isInlineHint = !forceChooser && buffer.includes(' ') && commands.length === 1;
101
105
  if (isInlineHint) {
102
106
  const c = commands[0];
103
107
  return React.createElement(
package/tui/splash.mjs CHANGED
@@ -49,6 +49,19 @@ function shortCwd(cwd) {
49
49
  return cwd;
50
50
  }
51
51
 
52
+ // Provider-aware splash tip. The $0 "learning loop" pitch only applies when
53
+ // the trainer runs on claude-cli (Claude Pro/Max subscription is keyless and
54
+ // free); for any other provider the loop costs API tokens, so showing the
55
+ // Claude pitch is misleading. The trainer provider wins because the pitch is
56
+ // about the learning loop, not the chat provider (see splash info line).
57
+ export function pickSplashTip({ provider, trainer = {} } = {}) {
58
+ const trainerProvider = trainer.provider || provider;
59
+ if (trainerProvider === 'claude-cli') {
60
+ return 'Tip: trainer learns from your Claude Pro subscription at $0.';
61
+ }
62
+ return 'Tip: /help lists every command, tool, and skill.';
63
+ }
64
+
52
65
  function toolRow({ category, sensitive, verbs }) {
53
66
  const label = sensitive ? `${category}*` : category;
54
67
  const tail = verbs.slice(0, 6).join(' · ');
@@ -153,7 +166,7 @@ function renderWide(props, cols) {
153
166
  if (sessionId) lines.push(`${LMARGIN}Session: ${sessionId}`);
154
167
  lines.push('');
155
168
  lines.push(`${LMARGIN}Welcome to lazyclaw. Type your message or /help for commands.`);
156
- lines.push(`${LMARGIN}+ Tip: trainer learns from your Claude Pro subscription at $0.`);
169
+ lines.push(`${LMARGIN}+ ${pickSplashTip({ provider, trainer })}`);
157
170
  // v5.4.3 — the baked-in status row that used to live here duplicated
158
171
  // ReplApp's real <StatusBar/> (tui/repl.mjs:476). Removing it cuts 4
159
172
  // rows from the splash AND eliminates the visible overlap the user
@@ -237,7 +250,7 @@ function renderMedium(props, cols) {
237
250
  if (sessionId) lines.push(`${LMARGIN}Session: ${sessionId}`);
238
251
  lines.push('');
239
252
  lines.push(`${LMARGIN}Welcome to lazyclaw. Type your message or /help for commands.`);
240
- lines.push(`${LMARGIN}+ Tip: trainer learns from your Claude Pro subscription at $0.`);
253
+ lines.push(`${LMARGIN}+ ${pickSplashTip({ provider, trainer })}`);
241
254
  // v5.4.3 — baked-in status row removed; ReplApp renders the real one.
242
255
 
243
256
  return lines;
@@ -0,0 +1,26 @@
1
+ // tui/status_bar.mjs — the sticky single/double status row above the chat
2
+ // input. Row 1: streaming indicator · provider · model · ctx gauge. Row 2
3
+ // (only when the HUD is enabled): real-time usage, cost, trainer, orchestrator.
4
+ // Extracted from repl.mjs so the HUD can grow without pushing repl.mjs over the
5
+ // file-size ratchet.
6
+
7
+ import React from 'react';
8
+ import { Box, Text } from 'ink';
9
+ import { theme } from './theme.mjs';
10
+ import { formatHudRow, formatGauge } from './hud.mjs';
11
+
12
+ export function StatusBar({ provider, model, streaming, ctxUsed, ctxTotal, hud }) {
13
+ // Numbers are computed upstream (chat-history budget, not provider self-report);
14
+ // formatGauge only changes the RENDERING — adds percent + bar + warn marker.
15
+ const ctx = (ctxUsed != null && ctxTotal != null) ? formatGauge(ctxUsed, ctxTotal) : '--';
16
+ const indicator = streaming ? theme.accent('● streaming') : theme.dim('○ idle');
17
+ const prov = provider || '?';
18
+ const mdl = model || '?';
19
+ const hudRow = hud ? formatHudRow(hud) : '';
20
+ return React.createElement(
21
+ Box,
22
+ { flexShrink: 0, flexDirection: 'column', paddingX: 1 },
23
+ React.createElement(Text, null, `${indicator} ${prov} · ${mdl} ctx ${ctx}`),
24
+ hudRow ? React.createElement(Text, null, theme.dim(hudRow)) : null,
25
+ );
26
+ }
package/tui/theme.mjs CHANGED
@@ -10,6 +10,34 @@ import chalk from 'chalk';
10
10
  const AMBER_HEX = '#FFB347';
11
11
  const BORDER_HEX = '#5A5A5A';
12
12
 
13
+ // Central color gate. Color is OFF when the NO_COLOR standard applies
14
+ // (https://no-color.org — any non-empty NO_COLOR), the terminal is `dumb`,
15
+ // or the target stream isn't a TTY (piped / screen-reader / CI). Evaluated
16
+ // per call so tests and runtime env changes are honored without re-importing.
17
+ // `stream` defaults to stdout; tests pass a fake `{ isTTY }` to probe the
18
+ // gate without mutating the real process streams. chalk self-detects the same
19
+ // conditions (level 0), so its tokens degrade automatically; this gate is for
20
+ // callers that emit raw ANSI.
21
+ export function colorEnabled(stream = process.stdout) {
22
+ if (process.env.NO_COLOR) return false;
23
+ if (process.env.TERM === 'dumb') return false;
24
+ if (!stream || !stream.isTTY) return false;
25
+ return true;
26
+ }
27
+
28
+ // Wrap `str` in a raw SGR escape (`\x1b[<code>m … \x1b[0m`) when color is on,
29
+ // otherwise return it unchanged. Keeps inline-ANSI call sites accessible
30
+ // without each one re-implementing the NO_COLOR check.
31
+ export function paint(code, str) {
32
+ if (!colorEnabled()) return str;
33
+ return `\x1b[${code}m${str}\x1b[0m`;
34
+ }
35
+
36
+ // Align chalk's own level with our gate so theme tokens (amber/dim/accent)
37
+ // and raw-ANSI emitters agree under NO_COLOR / dumb / non-TTY. chalk reads
38
+ // this at import; our gate stays the runtime source of truth for raw ANSI.
39
+ if (!colorEnabled()) chalk.level = 0;
40
+
13
41
  function amber(text) {
14
42
  return chalk.hex(AMBER_HEX)(text);
15
43
  }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/web/dashboard.css CHANGED
@@ -267,3 +267,80 @@
267
267
  display: flex;
268
268
  justify-content: space-between;
269
269
  }
270
+
271
+ /* ── Team Live tab ─────────────────────────────────────────── */
272
+ .team-live { display: flex; gap: 16px; align-items: stretch; flex-wrap: wrap; }
273
+ .team-canvas {
274
+ flex: 2 1 420px; min-width: 0; min-height: 260px;
275
+ background: var(--card); border: 1px solid var(--border); border-radius: 12px;
276
+ padding: 20px; display: flex; flex-direction: column; align-items: center; gap: 18px;
277
+ }
278
+ .team-detail {
279
+ flex: 1 1 280px; min-width: 0;
280
+ background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 16px;
281
+ font-size: 13px;
282
+ }
283
+ /* tree: a lead row on top, a wrapping children row below, with connectors */
284
+ .team-row { display: flex; gap: 18px; flex-wrap: wrap; justify-content: center; }
285
+ .team-children { position: relative; padding-top: 18px; }
286
+ .team-children::before {
287
+ content: ''; position: absolute; top: 0; left: 50%; width: 1px; height: 18px;
288
+ background: var(--border); transform: translateX(-50%);
289
+ }
290
+ .tagent {
291
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
292
+ width: 104px; cursor: pointer; background: none; border: 0; color: inherit; font: inherit;
293
+ padding: 6px; border-radius: 10px;
294
+ }
295
+ .tagent:hover { background: rgba(217,179,90,0.06); }
296
+ .tagent:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
297
+ .tagent[aria-selected="true"] { background: rgba(217,179,90,0.12); }
298
+ .tagent-avatar {
299
+ width: 56px; height: 56px; border-radius: 50%;
300
+ position: relative; overflow: hidden;
301
+ display: flex; align-items: center; justify-content: center; font-size: 24px;
302
+ background: #1e2130; border: 3px solid #475569;
303
+ }
304
+ .tagent-avatar img {
305
+ position: absolute; inset: 0; width: 100%; height: 100%;
306
+ object-fit: cover; object-position: center top;
307
+ image-rendering: pixelated;
308
+ }
309
+ .detail-avatar {
310
+ width: 30px; height: 30px; border-radius: 50%; object-fit: cover;
311
+ object-position: center top; image-rendering: pixelated; vertical-align: middle;
312
+ margin-right: 8px; background: #1e2130; border: 2px solid #475569;
313
+ }
314
+ .tagent.working .tagent-avatar { border-color: #34d399; box-shadow: 0 0 0 4px rgba(52,211,153,0.16); }
315
+ .tagent.idle .tagent-avatar { border-color: #475569; }
316
+ .tagent.delegating .tagent-avatar { animation: tdelegate 900ms ease-out; }
317
+ @keyframes tdelegate {
318
+ 0% { box-shadow: 0 0 0 0 rgba(217,179,90,0.55); }
319
+ 100% { box-shadow: 0 0 0 14px rgba(217,179,90,0); }
320
+ }
321
+ .tagent-name { font-size: 12px; font-weight: 600; max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
322
+ .tagent-status { font-size: 10px; }
323
+ .tagent.working .tagent-status { color: #34d399; }
324
+ .tagent.idle .tagent-status { color: var(--dim); }
325
+ .harness-badge {
326
+ font-size: 9px; background: #312e81; color: #c7d2fe; border-radius: 5px; padding: 1px 6px;
327
+ max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
328
+ }
329
+ .team-detail h3 { margin: 0 0 6px; font-size: 15px; }
330
+ .team-detail .kv { margin: 8px 0; }
331
+ .team-detail .kv .label { color: var(--dim); font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; }
332
+ .team-detail .activity { margin: 3px 0; color: #cbd5e1; line-height: 1.6; }
333
+ .team-feed-wrap { margin-top: 16px; }
334
+ .team-feed { list-style: none; margin: 6px 0 0; padding: 0; max-height: 180px; overflow-y: auto; font-size: 12px; }
335
+ .team-feed li { padding: 4px 0; border-top: 1px solid var(--border); color: var(--dim); }
336
+ .team-feed li .who { color: var(--fg, #e8e8f0); font-weight: 600; }
337
+ .team-feed li .arrow { color: #34d399; }
338
+
339
+ @media (max-width: 760px) {
340
+ .team-live { flex-direction: column; }
341
+ .team-canvas, .team-detail { flex: 1 1 auto; }
342
+ }
343
+ @media (prefers-reduced-motion: reduce) {
344
+ .tagent.working .tagent-avatar { box-shadow: none; }
345
+ .tagent.delegating .tagent-avatar { animation: none; }
346
+ }
@@ -14,7 +14,11 @@
14
14
  <meta charset="utf-8">
15
15
  <meta name="viewport" content="width=device-width, initial-scale=1">
16
16
  <title>LazyClaw</title>
17
- <link rel="stylesheet" href="dashboard.css">
17
+ <!-- Absolute paths: the daemon serves this page at /dashboard AND /dashboard/
18
+ (and /), so a relative href would resolve to /dashboard/dashboard.css and
19
+ 404 — leaving the page unstyled with every tab stuck on "Loading…". -->
20
+ <link rel="stylesheet" href="/dashboard.css">
21
+ <link rel="icon" href="data:,">
18
22
  </head>
19
23
  <body>
20
24
  <header>
@@ -38,6 +42,7 @@
38
42
  <button data-tab="agents">Agents</button>
39
43
  <button data-tab="teams">Teams</button>
40
44
  <button data-tab="tasks">Tasks</button>
45
+ <button data-tab="team">Team Live</button>
41
46
  <button data-tab="trainer">Trainer</button>
42
47
  <button data-tab="recall">Recall</button>
43
48
  <button data-tab="sandbox">Sandbox</button>
@@ -182,6 +187,28 @@
182
187
  <div id="tasks-list"><div class="empty">Loading…</div></div>
183
188
  </section>
184
189
 
190
+ <section id="tab-team">
191
+ <h2>Team Live</h2>
192
+ <div class="toolbar">
193
+ <label for="team-select" class="dim">Team</label>
194
+ <select id="team-select" aria-label="Select a team to watch"></select>
195
+ <button class="btn btn-secondary" onclick="LOADERS.team()">Refresh</button>
196
+ <span class="dim" id="team-conn" role="status" aria-live="polite">○ connecting…</span>
197
+ </div>
198
+ <div id="team-live" class="team-live">
199
+ <div id="team-canvas" class="team-canvas" role="tree" aria-label="Agent team">
200
+ <div class="empty">Loading…</div>
201
+ </div>
202
+ <aside id="team-detail" class="team-detail" aria-live="polite">
203
+ <div class="empty">Click an agent to see its harness and what it's working on.</div>
204
+ </aside>
205
+ </div>
206
+ <div class="team-feed-wrap">
207
+ <div class="label">Live activity</div>
208
+ <ul id="team-feed" class="team-feed" aria-live="polite"></ul>
209
+ </div>
210
+ </section>
211
+
185
212
  <section id="tab-trainer">
186
213
  <h2>Trainer</h2>
187
214
  <div class="toolbar">
@@ -244,6 +271,6 @@
244
271
  </div>
245
272
  </div>
246
273
 
247
- <script src="dashboard.js"></script>
274
+ <script src="/dashboard.js"></script>
248
275
  </body>
249
276
  </html>