lazyclaw 5.4.3 → 6.0.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 (46) hide show
  1. package/channels/handoff.mjs +36 -0
  2. package/cli.mjs +73 -7399
  3. package/daemon.mjs +23 -2085
  4. package/mas/agent_turn.mjs +2 -1
  5. package/mas/index_db.mjs +82 -0
  6. package/mas/learning.mjs +17 -1
  7. package/mas/mention_router.mjs +38 -10
  8. package/mas/provider_adapters.mjs +28 -4
  9. package/mas/scrub_env.mjs +34 -0
  10. package/mas/tool_runner.mjs +23 -7
  11. package/mas/tools/bash.mjs +10 -5
  12. package/mas/tools/browser.mjs +18 -0
  13. package/mas/tools/learning.mjs +24 -14
  14. package/mas/tools/recall.mjs +5 -1
  15. package/mas/tools/web.mjs +47 -11
  16. package/mas/trajectory_store.mjs +7 -4
  17. package/package.json +3 -2
  18. package/providers/auth_store.mjs +22 -0
  19. package/providers/claude_cli.mjs +28 -2
  20. package/providers/claude_cli_detect.mjs +46 -0
  21. package/providers/custom_provider.mjs +70 -0
  22. package/providers/model_catalogue.mjs +86 -0
  23. package/providers/orchestrator.mjs +30 -9
  24. package/providers/rates.mjs +12 -2
  25. package/providers/registry.mjs +10 -7
  26. package/sandbox/confiners/landlock.mjs +14 -8
  27. package/sandbox/confiners/seatbelt.mjs +18 -2
  28. package/scripts/loop-worker.mjs +18 -7
  29. package/scripts/migrate-v5.mjs +5 -61
  30. package/sessions.mjs +0 -0
  31. package/tui/editor.mjs +44 -0
  32. package/tui/modal_filter.mjs +59 -0
  33. package/tui/modal_picker.mjs +12 -37
  34. package/tui/pickers.mjs +917 -0
  35. package/tui/provider_families.mjs +41 -0
  36. package/tui/repl.mjs +67 -36
  37. package/tui/slash_commands.mjs +8 -7
  38. package/tui/slash_dispatcher.mjs +923 -118
  39. package/tui/splash.mjs +5 -12
  40. package/tui/subcommands.mjs +17 -0
  41. package/tui/terminal_approve.mjs +37 -0
  42. package/web/dashboard.css +275 -0
  43. package/web/dashboard.html +2 -1685
  44. package/web/dashboard.js +1406 -0
  45. package/workflow/persistent.mjs +13 -6
  46. package/mas/tools/skill_view.mjs +0 -43
package/tui/splash.mjs CHANGED
@@ -26,18 +26,11 @@ const WORDMARK_BREAKPOINT = 140; // drop wordmark below this
26
26
  const MEDIUM_BREAKPOINT = 90; // side-by-side sloth+panel above this; stacked below
27
27
  const NARROW_BREAKPOINT = 45; // headline-only fallback below this
28
28
 
29
- // Subcommand catalog — grouped for the splash so a new user sees the
30
- // surface area at a glance. Mirrors SUBCOMMANDS in cli.mjs.
31
- export const SUBCOMMAND_GROUPS = [
32
- ['core', ['chat', 'agent', 'orchestrator', 'dashboard', 'menu']],
33
- ['workflow', ['run', 'resume', 'inspect', 'clear', 'validate', 'graph']],
34
- ['config', ['config', 'auth', 'rates', 'providers', 'setup', 'onboard']],
35
- ['state', ['sessions', 'skills', 'workspace', 'memory', 'status', 'doctor']],
36
- ['runtime', ['daemon', 'cron', 'loop', 'loops', 'goal']],
37
- ['channels', ['slack', 'telegram', 'matrix', 'channels', 'message', 'pairing']],
38
- ['v5', ['sandbox', 'personality', 'migrate', 'hermes', 'openclaw', 'trajectories']],
39
- ['utility', ['browse', 'version', 'completion', 'help', 'export', 'import', 'nodes']],
40
- ];
29
+ // Subcommand catalog — grouped for the splash so a new user sees the surface
30
+ // area at a glance. Single source of truth lives in the react-free
31
+ // tui/subcommands.mjs so the in-chat /menu palette can share it.
32
+ export { SUBCOMMAND_GROUPS } from './subcommands.mjs';
33
+ import { SUBCOMMAND_GROUPS } from './subcommands.mjs';
41
34
 
42
35
  function fit(text, max) {
43
36
  if (stringWidth(text) <= max) return text.padEnd(max);
@@ -0,0 +1,17 @@
1
+ // tui/subcommands.mjs — the lazyclaw subcommand catalog, grouped. Pure data
2
+ // (no react/ink) so both the splash (tui/splash.mjs) and the in-chat command
3
+ // palette (/menu in tui/slash_dispatcher.mjs) can share it. The no-arg
4
+ // launcher menu used to be the home screen; defaulting to chat hid it behind
5
+ // `lazyclaw menu`, so /menu brings the discoverable subcommand list back into
6
+ // the chat.
7
+
8
+ export const SUBCOMMAND_GROUPS = [
9
+ ['core', ['chat', 'agent', 'orchestrator', 'dashboard', 'menu']],
10
+ ['workflow', ['run', 'resume', 'inspect', 'clear', 'validate', 'graph']],
11
+ ['config', ['config', 'auth', 'rates', 'providers', 'setup', 'onboard']],
12
+ ['state', ['sessions', 'skills', 'workspace', 'memory', 'status', 'doctor']],
13
+ ['runtime', ['daemon', 'cron', 'loop', 'loops', 'goal']],
14
+ ['channels', ['slack', 'telegram', 'matrix', 'channels', 'message', 'pairing']],
15
+ ['v5', ['sandbox', 'personality', 'migrate', 'hermes', 'openclaw', 'trajectories']],
16
+ ['utility', ['browse', 'version', 'completion', 'help', 'export', 'import', 'nodes']],
17
+ ];
@@ -0,0 +1,37 @@
1
+ // terminal_approve.mjs — default human-in-the-loop approval hook for the
2
+ // bare (non-Ink) CLI path, e.g. `lazyclaw task tick` on a TTY.
3
+ //
4
+ // The tool runner is fail-closed: a sensitive tool (bash / write / web /
5
+ // browser / delegate) runs only behind an approve({tool,args,agent}) →
6
+ // {approved,reason} hook, or an explicit security.allowUnattendedSensitive
7
+ // opt-in. This module supplies the default hook: a y/N prompt on the
8
+ // controlling terminal. Default (bare Enter), timeout, EOF, and a
9
+ // non-TTY stdin ALL deny — approval is never granted by omission.
10
+
11
+ import readline from 'node:readline';
12
+ import { redactSecrets } from '../mas/redact.mjs';
13
+
14
+ export function makeReadlineApprove({ input = process.stdin, output = process.stderr, timeoutMs = 120_000 } = {}) {
15
+ return async function approve({ tool, args, agent }) {
16
+ if (!input.isTTY) return { approved: false, reason: 'no TTY for approval (fail-closed)' };
17
+ const raw = typeof args === 'object' ? JSON.stringify(args) : String(args ?? '');
18
+ const summary = redactSecrets(raw).slice(0, 400);
19
+ const rl = readline.createInterface({ input, output });
20
+ try {
21
+ const answer = await new Promise((resolve) => {
22
+ let settled = false;
23
+ const finish = (v) => { if (!settled) { settled = true; resolve(v); } };
24
+ const timer = setTimeout(() => finish(''), timeoutMs);
25
+ rl.on('close', () => { clearTimeout(timer); finish(''); });
26
+ rl.question(`\n⚠ agent "${agent}" wants to run sensitive tool "${tool}": ${summary}\n approve? [y/N] `, (a) => {
27
+ clearTimeout(timer);
28
+ finish(a);
29
+ });
30
+ });
31
+ const approved = /^\s*y(es)?\s*$/i.test(answer);
32
+ return { approved, reason: approved ? 'approved at terminal' : 'denied at terminal (default)' };
33
+ } finally {
34
+ rl.close();
35
+ }
36
+ };
37
+ }
@@ -0,0 +1,275 @@
1
+ :root {
2
+ --bg: #0a0a0a;
3
+ --card: #14141c;
4
+ --border: #2a2a36;
5
+ --text: #e8e8ea;
6
+ --dim: #a8a8b8;
7
+ --accent: #d97757;
8
+ --ok: #4ade80;
9
+ --warn: #f59e0b;
10
+ --err: #ef4444;
11
+ }
12
+ * { box-sizing: border-box; }
13
+ body {
14
+ margin: 0;
15
+ font: 14px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
16
+ background: var(--bg);
17
+ color: var(--text);
18
+ min-height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ }
22
+ header {
23
+ padding: 14px 22px;
24
+ border-bottom: 1px solid var(--border);
25
+ display: flex;
26
+ align-items: center;
27
+ gap: 14px;
28
+ }
29
+ .logo { font-weight: 700; font-size: 16px; color: var(--accent); display: flex; align-items: center; gap: 10px; }
30
+ .logo .mascot { width: 44px; height: 44px; flex: none; image-rendering: pixelated; image-rendering: crisp-edges; }
31
+ .ver { color: var(--dim); font-size: 11px; }
32
+ /* lazyclaw 16x16 pixel mascot — Claude Design handoff (mascot sheet
33
+ v0.1, "claude original" palette). Claude's asterisk star (#d97757)
34
+ worn under a crustacean helmet (#c33d2a) with two antenna-claws.
35
+ Idle pose (sleepy slits). Hover gently brightens the helmet. */
36
+ .mascot { transition: filter 0.2s ease; }
37
+ .logo:hover .mascot { filter: drop-shadow(0 0 6px rgba(217, 119, 87, 0.45)); }
38
+ nav.tabs {
39
+ display: flex;
40
+ flex-wrap: wrap;
41
+ gap: 2px;
42
+ padding: 0 14px;
43
+ border-bottom: 1px solid var(--border);
44
+ }
45
+ nav.tabs button {
46
+ display: inline-flex;
47
+ align-items: center;
48
+ min-height: 44px;
49
+ background: none;
50
+ border: 0;
51
+ color: var(--dim);
52
+ padding: 10px 16px;
53
+ cursor: pointer;
54
+ font-size: 13px;
55
+ border-bottom: 2px solid transparent;
56
+ white-space: nowrap;
57
+ }
58
+ nav.tabs button:hover { color: var(--text); }
59
+ nav.tabs button.active {
60
+ color: var(--accent);
61
+ border-bottom-color: var(--accent);
62
+ }
63
+ main {
64
+ flex: 1;
65
+ padding: 22px;
66
+ overflow-y: auto;
67
+ }
68
+ section { display: none; }
69
+ section.active { display: block; }
70
+ h2 { margin: 0 0 14px; font-size: 18px; }
71
+ .card {
72
+ background: var(--card);
73
+ border: 1px solid var(--border);
74
+ border-radius: 8px;
75
+ padding: 14px 16px;
76
+ margin-bottom: 12px;
77
+ }
78
+ .row { display: flex; align-items: center; gap: 12px; padding: 6px 0; }
79
+ .row + .row { border-top: 1px solid var(--border); }
80
+ .name { font-weight: 600; }
81
+ .dim { color: var(--dim); font-size: 12px; }
82
+ button.btn {
83
+ background: var(--accent);
84
+ color: #fff;
85
+ border: 0;
86
+ border-radius: 6px;
87
+ padding: 8px 14px;
88
+ cursor: pointer;
89
+ font-size: 13px;
90
+ font-weight: 500;
91
+ }
92
+ button.btn:hover { filter: brightness(1.1); }
93
+ button.btn-secondary {
94
+ background: transparent;
95
+ border: 1px solid var(--border);
96
+ color: var(--text);
97
+ }
98
+ button.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
99
+ .empty { color: var(--dim); padding: 24px; text-align: center; font-style: italic; }
100
+ pre {
101
+ background: #06060c;
102
+ padding: 12px 14px;
103
+ border-radius: 6px;
104
+ overflow-x: auto;
105
+ font-size: 12px;
106
+ color: #cfcfd6;
107
+ border: 1px solid var(--border);
108
+ }
109
+ /* Chat */
110
+ #chat-stream {
111
+ background: var(--card);
112
+ border: 1px solid var(--border);
113
+ border-radius: 8px;
114
+ padding: 12px;
115
+ height: 50vh;
116
+ overflow-y: auto;
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: 14px;
120
+ }
121
+ .msg { padding: 8px 12px; border-radius: 6px; max-width: 90%; white-space: pre-wrap; word-wrap: break-word; }
122
+ .msg.user { align-self: flex-end; background: rgba(217, 119, 87, 0.15); border: 1px solid rgba(217, 119, 87, 0.3); }
123
+ .msg.assistant { align-self: flex-start; background: rgba(74, 222, 128, 0.06); border: 1px solid rgba(74, 222, 128, 0.18); }
124
+ .msg.error { align-self: stretch; background: rgba(239, 68, 68, 0.12); border: 1px solid rgba(239, 68, 68, 0.3); color: #ffd3d3; }
125
+ .input-row {
126
+ display: flex;
127
+ gap: 8px;
128
+ margin-top: 12px;
129
+ }
130
+ .input-row textarea {
131
+ flex: 1;
132
+ background: var(--card);
133
+ border: 1px solid var(--border);
134
+ border-radius: 6px;
135
+ padding: 10px 12px;
136
+ color: var(--text);
137
+ resize: vertical;
138
+ min-height: 60px;
139
+ font: inherit;
140
+ }
141
+ .pill {
142
+ display: inline-block;
143
+ padding: 2px 8px;
144
+ border-radius: 999px;
145
+ font-size: 10px;
146
+ background: var(--border);
147
+ color: var(--dim);
148
+ margin-left: 6px;
149
+ }
150
+ .pill.ok { background: rgba(74, 222, 128, 0.15); color: var(--ok); }
151
+ .pill.warn { background: rgba(245, 158, 11, 0.15); color: var(--warn); }
152
+ .pill.err { background: rgba(239, 68, 68, 0.15); color: var(--err); }
153
+ .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
154
+ .toolbar select {
155
+ background: var(--card);
156
+ border: 1px solid var(--border);
157
+ color: var(--text);
158
+ padding: 6px 10px;
159
+ border-radius: 6px;
160
+ font: inherit;
161
+ }
162
+ .toolbar input[type="text"], .toolbar input[type="search"] {
163
+ background: var(--card);
164
+ border: 1px solid var(--border);
165
+ color: var(--text);
166
+ padding: 6px 10px;
167
+ border-radius: 6px;
168
+ font: inherit;
169
+ min-width: 0;
170
+ }
171
+ .grid {
172
+ display: grid;
173
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
174
+ gap: 10px;
175
+ margin-bottom: 14px;
176
+ }
177
+ .stat {
178
+ background: var(--card);
179
+ border: 1px solid var(--border);
180
+ border-radius: 8px;
181
+ padding: 12px 14px;
182
+ }
183
+ .stat .label { color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
184
+ .stat .value { font-size: 22px; font-weight: 600; margin-top: 4px; word-break: break-all; }
185
+ .stat .sub { color: var(--dim); font-size: 11px; margin-top: 4px; }
186
+ table.tbl {
187
+ width: 100%;
188
+ border-collapse: collapse;
189
+ background: var(--card);
190
+ border: 1px solid var(--border);
191
+ border-radius: 8px;
192
+ overflow: hidden;
193
+ font-size: 13px;
194
+ }
195
+ table.tbl th, table.tbl td {
196
+ padding: 8px 12px;
197
+ text-align: left;
198
+ border-bottom: 1px solid var(--border);
199
+ vertical-align: top;
200
+ }
201
+ table.tbl th { color: var(--dim); font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; background: rgba(255,255,255,0.02); }
202
+ table.tbl tr:last-child td { border-bottom: 0; }
203
+ table.tbl td.num { text-align: right; font-variant-numeric: tabular-nums; }
204
+ .banner {
205
+ padding: 10px 14px;
206
+ border-radius: 8px;
207
+ margin-bottom: 12px;
208
+ border: 1px solid var(--border);
209
+ background: var(--card);
210
+ display: flex;
211
+ flex-wrap: wrap;
212
+ align-items: center;
213
+ gap: 10px;
214
+ }
215
+ .banner.ok { border-color: rgba(74, 222, 128, 0.4); background: rgba(74, 222, 128, 0.06); }
216
+ .banner.warn { border-color: rgba(245, 158, 11, 0.4); background: rgba(245, 158, 11, 0.06); }
217
+ .banner.err { border-color: rgba(239, 68, 68, 0.4); background: rgba(239, 68, 68, 0.06); }
218
+ .banner ul { margin: 6px 0 0 18px; padding: 0; }
219
+ .banner li { font-size: 12px; }
220
+ /* Modal — used by session/workflow/skill detail views, the rate-card
221
+ editor, and the custom-provider form. One stacking layer; only one
222
+ open at a time. */
223
+ .modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.65); display: none; align-items: center; justify-content: center; z-index: 100; padding: 20px; }
224
+ .modal-backdrop.open { display: flex; }
225
+ .modal { background: var(--card); border: 1px solid var(--border); border-radius: 10px; max-width: min(720px, 96vw); width: 100%; max-height: 86vh; display: flex; flex-direction: column; box-shadow: 0 20px 60px rgba(0,0,0,0.5); }
226
+ .modal-head { padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
227
+ .modal-head h3 { margin: 0; font-size: 16px; font-weight: 600; flex: 1; }
228
+ .modal-close { background: none; border: 0; color: var(--dim); cursor: pointer; font-size: 20px; line-height: 1; padding: 4px 8px; }
229
+ .modal-close:hover { color: var(--text); }
230
+ .modal-body { padding: 16px 18px; overflow-y: auto; flex: 1; }
231
+ .modal-foot { padding: 12px 18px; border-top: 1px solid var(--border); display: flex; gap: 8px; justify-content: flex-end; }
232
+ .clickable { cursor: pointer; }
233
+ .clickable:hover { background: rgba(217, 119, 87, 0.05); }
234
+ .turn { padding: 8px 12px; border-radius: 6px; margin-bottom: 8px; white-space: pre-wrap; word-wrap: break-word; font-size: 13px; }
235
+ .turn.user { background: rgba(217, 119, 87, 0.10); border: 1px solid rgba(217, 119, 87, 0.25); }
236
+ .turn.assistant { background: rgba(74, 222, 128, 0.06); border: 1px solid rgba(74, 222, 128, 0.18); }
237
+ .turn.system { background: rgba(245, 158, 11, 0.06); border: 1px solid rgba(245, 158, 11, 0.20); }
238
+ .turn .role-tag { display: block; color: var(--dim); font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
239
+ .form-row { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
240
+ .form-row label { color: var(--dim); font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; }
241
+ .form-row input, .form-row textarea { background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: 8px 10px; border-radius: 6px; font: inherit; font-size: 13px; }
242
+ .form-row textarea { resize: vertical; min-height: 80px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
243
+ .row-actions { display: flex; gap: 6px; align-items: center; margin-left: auto; }
244
+ button.btn-sm { font-size: 12px; padding: 4px 10px; }
245
+ button.btn-danger { background: rgba(239,68,68,0.15); color: #ffb4b4; border: 1px solid rgba(239,68,68,0.3); }
246
+ button.btn-danger:hover { background: rgba(239,68,68,0.25); }
247
+ .markdown { font-size: 13px; line-height: 1.55; }
248
+ .markdown pre { font-size: 12px; }
249
+ /* a11y: a visible keyboard focus ring on every interactive control.
250
+ The styled buttons/inputs otherwise relied on the UA default ring,
251
+ which is easy to lose against the dark theme. */
252
+ a:focus-visible, button:focus-visible, input:focus-visible,
253
+ select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
254
+ outline: 2px solid var(--accent);
255
+ outline-offset: 2px;
256
+ border-radius: 4px;
257
+ }
258
+ @media (prefers-reduced-motion: reduce) {
259
+ * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
260
+ }
261
+ @media (max-width: 480px) {
262
+ main { padding: 14px; }
263
+ header { padding: 12px 16px; }
264
+ .grid { grid-template-columns: 1fr; }
265
+ table.tbl { font-size: 12px; }
266
+ table.tbl th, table.tbl td { padding: 6px 8px; }
267
+ }
268
+ footer {
269
+ padding: 10px 22px;
270
+ border-top: 1px solid var(--border);
271
+ color: var(--dim);
272
+ font-size: 11px;
273
+ display: flex;
274
+ justify-content: space-between;
275
+ }