create-walle 0.9.3 → 0.9.4
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.md +2 -1
- package/package.json +1 -1
- package/template/claude-task-manager/db.js +5 -1
- package/template/claude-task-manager/public/css/walle.css +317 -0
- package/template/claude-task-manager/public/index.html +404 -101
- package/template/claude-task-manager/public/js/walle.js +1256 -86
- package/template/claude-task-manager/server.js +189 -14
- package/template/docs/site/api/README.md +146 -0
- package/template/docs/site/skills/README.md +99 -5
- package/template/package.json +1 -1
- package/template/wall-e/agent.js +54 -0
- package/template/wall-e/api-walle.js +452 -3
- package/template/wall-e/brain.js +45 -1
- package/template/wall-e/channels/telegram-channel.js +96 -0
- package/template/wall-e/chat.js +61 -2
- package/template/wall-e/coding-context.js +252 -0
- package/template/wall-e/coding-orchestrator.js +625 -0
- package/template/wall-e/coding-review.js +189 -0
- package/template/wall-e/core-tasks.js +12 -3
- package/template/wall-e/deploy.sh +4 -4
- package/template/wall-e/fly.toml +2 -2
- package/template/wall-e/package.json +4 -1
- package/template/wall-e/skills/_bundled/coding-agent/SKILL.md +17 -0
- package/template/wall-e/skills/_bundled/coding-agent/run.js +142 -0
- package/template/wall-e/skills/_bundled/email-sync/SKILL.md +12 -7
- package/template/wall-e/skills/_bundled/email-sync/mail-reader.jxa +76 -46
- package/template/wall-e/skills/_bundled/email-sync/run.js +42 -2
- package/template/wall-e/skills/_bundled/glean-team-sync/SKILL.md +57 -0
- package/template/wall-e/skills/_bundled/glean-team-sync/run.js +254 -0
- package/template/wall-e/skills/_bundled/slack-mentions/SKILL.md +1 -1
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +268 -121
- package/template/wall-e/skills/_templates/data-fetcher.md +27 -0
- package/template/wall-e/skills/_templates/manual-action.md +19 -0
- package/template/wall-e/skills/_templates/periodic-checker.md +29 -0
- package/template/wall-e/skills/_templates/script-runner.md +21 -0
- package/template/wall-e/skills/claude-code-reader.js +16 -4
- package/template/wall-e/skills/skill-executor.js +23 -1
- package/template/wall-e/skills/skill-validator.js +73 -0
- package/template/wall-e/tests/brain.test.js +3 -3
- package/template/wall-e/tests/coding-agent-integration.test.js +240 -0
- package/template/wall-e/tests/coding-context.test.js +212 -0
- package/template/wall-e/tests/coding-orchestrator.test.js +303 -0
- package/template/wall-e/tests/coding-review.test.js +141 -0
- package/template/claude-task-manager/package-lock.json +0 -1607
- package/template/claude-task-manager/tests/test-ai-search.js +0 -61
- package/template/claude-task-manager/tests/test-editor-ux.js +0 -76
- package/template/claude-task-manager/tests/test-editor-ux2.js +0 -51
- package/template/claude-task-manager/tests/test-features-v2.js +0 -127
- package/template/claude-task-manager/tests/test-insights-cached.js +0 -78
- package/template/claude-task-manager/tests/test-insights.js +0 -124
- package/template/claude-task-manager/tests/test-permissions-v2.js +0 -127
- package/template/claude-task-manager/tests/test-permissions.js +0 -122
- package/template/claude-task-manager/tests/test-pin.js +0 -51
- package/template/claude-task-manager/tests/test-prompts.js +0 -164
- package/template/claude-task-manager/tests/test-recent-sessions.js +0 -96
- package/template/claude-task-manager/tests/test-review.js +0 -104
- package/template/claude-task-manager/tests/test-send-dropdown.js +0 -76
- package/template/claude-task-manager/tests/test-send-final.js +0 -30
- package/template/claude-task-manager/tests/test-send-fixes.js +0 -76
- package/template/claude-task-manager/tests/test-send-integration.js +0 -107
- package/template/claude-task-manager/tests/test-send-visual.js +0 -34
- package/template/claude-task-manager/tests/test-session-create.js +0 -147
- package/template/claude-task-manager/tests/test-sidebar-ux.js +0 -83
- package/template/claude-task-manager/tests/test-url-hash.js +0 -68
- package/template/claude-task-manager/tests/test-ux-crop.js +0 -34
- package/template/claude-task-manager/tests/test-ux-review.js +0 -130
- package/template/claude-task-manager/tests/test-zoom-card.js +0 -76
- package/template/claude-task-manager/tests/test-zoom.js +0 -92
- package/template/claude-task-manager/tests/test-zoom2.js +0 -67
- package/template/docs/openclaw-vs-walle-comparison.md +0 -103
- package/template/docs/ux-improvement-plan.md +0 -84
- package/template/wall-e/docs/specs/2026-04-01-publish-plan.md +0 -112
- package/template/wall-e/docs/specs/SKILL-FORMAT.md +0 -326
- package/template/wall-e/package-lock.json +0 -533
- package/template/wall-e/skills/_bundled/slack-mentions/.watermark.json +0 -4
package/template/wall-e/chat.js
CHANGED
|
@@ -18,6 +18,16 @@ function ensureBrainInit() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function getCodingDefaultCwd() {
|
|
22
|
+
try {
|
|
23
|
+
const cfgPath = require('path').join(__dirname, 'wall-e-config.json');
|
|
24
|
+
const cfg = JSON.parse(require('fs').readFileSync(cfgPath, 'utf8'));
|
|
25
|
+
return (cfg.coding_agent && cfg.coding_agent.default_cwd) || process.cwd();
|
|
26
|
+
} catch {
|
|
27
|
+
return process.cwd();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
async function chat(message, opts = {}) {
|
|
22
32
|
ensureBrainInit();
|
|
23
33
|
const channel = opts.channel || 'ctm';
|
|
@@ -72,7 +82,7 @@ async function chat(message, opts = {}) {
|
|
|
72
82
|
},
|
|
73
83
|
{
|
|
74
84
|
name: 'run_skill',
|
|
75
|
-
description:
|
|
85
|
+
description: `Run one of my skills to fetch data or perform an action. PREFER skills over raw mcp_call when a matching skill exists — skills parse data correctly and store results in brain. Available skills: ${(() => { try { const { loadAllSkills } = require('./skills/skill-loader'); const bundled = loadAllSkills().map(s => s.name + ': ' + (s.description || '').slice(0, 60)); const db = brain.listSkills({ enabled: 1 }).map(s => s.name + ': ' + (s.description || '').slice(0, 60)); const all = [...new Set([...bundled, ...db])]; return all.join('; ') || 'none'; } catch { return 'unknown'; } })()}`,
|
|
76
86
|
input_schema: { type: 'object', properties: { skill_name: { type: 'string', description: 'Name of the skill to run' } }, required: ['skill_name'] },
|
|
77
87
|
},
|
|
78
88
|
{
|
|
@@ -176,6 +186,19 @@ async function chat(message, opts = {}) {
|
|
|
176
186
|
required: ['title', 'description'],
|
|
177
187
|
},
|
|
178
188
|
},
|
|
189
|
+
{
|
|
190
|
+
name: 'start_coding',
|
|
191
|
+
description: 'Start an autonomous coding task. Wall-E will plan the implementation, execute subtasks via Claude Code, run tests, review code, and commit. Use when the user asks you to build, fix, add, or refactor something in a codebase.',
|
|
192
|
+
input_schema: {
|
|
193
|
+
type: 'object',
|
|
194
|
+
properties: {
|
|
195
|
+
request: { type: 'string', description: 'What to build, fix, or change — the user\'s request in their own words' },
|
|
196
|
+
cwd: { type: 'string', description: 'Project directory path. Defaults to the current workspace.' },
|
|
197
|
+
delivery: { type: 'string', enum: ['commit', 'push', 'pr'], description: 'What to do after completion. Default: commit (local only)' },
|
|
198
|
+
},
|
|
199
|
+
required: ['request'],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
179
202
|
{
|
|
180
203
|
name: 'list_tasks',
|
|
181
204
|
description: 'List current tasks and their status.',
|
|
@@ -199,7 +222,20 @@ async function chat(message, opts = {}) {
|
|
|
199
222
|
return { acknowledged: true };
|
|
200
223
|
}
|
|
201
224
|
if (name === 'run_skill') {
|
|
202
|
-
|
|
225
|
+
// Check bundled/user skills first (script-based), then DB skills (agent-based)
|
|
226
|
+
const { findSkill } = require('./skills/skill-loader');
|
|
227
|
+
const bundledSkill = findSkill(input.skill_name);
|
|
228
|
+
if (bundledSkill && bundledSkill.execution === 'script') {
|
|
229
|
+
try {
|
|
230
|
+
const { executeSkill } = require('./loops/tasks');
|
|
231
|
+
const fakeTaskId = `chat-${Date.now()}`;
|
|
232
|
+
const result = await executeSkill(fakeTaskId, { id: fakeTaskId, skill: input.skill_name, title: bundledSkill.name });
|
|
233
|
+
return { success: true, output: result.slice(0, 5000) };
|
|
234
|
+
} catch (err) {
|
|
235
|
+
return { error: err.message };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const skill = bundledSkill || brain.getSkillByName(input.skill_name);
|
|
203
239
|
if (!skill) return { error: `Skill "${input.skill_name}" not found` };
|
|
204
240
|
try {
|
|
205
241
|
const { runSkill } = require('./skills/skill-executor');
|
|
@@ -409,6 +445,29 @@ async function chat(message, opts = {}) {
|
|
|
409
445
|
});
|
|
410
446
|
return { created: true, id: result.id, title: input.title, status: 'pending', due_at: input.due_at || 'immediate' };
|
|
411
447
|
}
|
|
448
|
+
if (name === 'start_coding') {
|
|
449
|
+
ensureBrainInit();
|
|
450
|
+
const result = brain.insertTask({
|
|
451
|
+
title: input.request.slice(0, 100),
|
|
452
|
+
description: `Coding request: ${input.request}`,
|
|
453
|
+
priority: 'normal',
|
|
454
|
+
type: 'once',
|
|
455
|
+
execution: 'skill',
|
|
456
|
+
skill: 'coding-agent',
|
|
457
|
+
skill_config: JSON.stringify({
|
|
458
|
+
request: input.request,
|
|
459
|
+
cwd: input.cwd || getCodingDefaultCwd(),
|
|
460
|
+
options: { delivery: input.delivery || 'commit' },
|
|
461
|
+
}),
|
|
462
|
+
source: 'chat',
|
|
463
|
+
source_ref: opts.session_id || '',
|
|
464
|
+
});
|
|
465
|
+
return {
|
|
466
|
+
created: true,
|
|
467
|
+
task_id: result.id,
|
|
468
|
+
message: 'Coding task created. Wall-E will plan and execute autonomously. Track progress in the Tasks tab.',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
412
471
|
if (name === 'list_tasks') {
|
|
413
472
|
const tasks = brain.listTasks({ status: input.status, limit: 20 });
|
|
414
473
|
return { count: tasks.length, tasks: tasks.map(t => ({ id: t.id, title: t.title, status: t.status, priority: t.priority, due_at: t.due_at, created_at: t.created_at, result: t.result?.slice(0, 200) })) };
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const fsp = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { execFile } = require('node:child_process');
|
|
6
|
+
const { promisify } = require('node:util');
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
const IGNORED_DIRS = new Set([
|
|
11
|
+
'node_modules', '.git', '.next', 'dist', 'build', '.superpowers', '__pycache__',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const MAX_FILE_CONTENT = 50 * 1024; // 50KB
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns file tree as newline-separated relative paths.
|
|
18
|
+
* Directories have trailing `/`. Excludes ignored dirs and dotfiles.
|
|
19
|
+
*/
|
|
20
|
+
async function getFileTree(cwd, maxDepth = 5) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
|
|
23
|
+
async function walk(dir, depth) {
|
|
24
|
+
if (depth > maxDepth) return;
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Sort for deterministic output
|
|
32
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const name = entry.name;
|
|
35
|
+
// Skip dotfiles and ignored dirs
|
|
36
|
+
if (name.startsWith('.')) continue;
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
if (IGNORED_DIRS.has(name)) continue;
|
|
39
|
+
const rel = path.relative(cwd, path.join(dir, name)) + '/';
|
|
40
|
+
lines.push(rel);
|
|
41
|
+
await walk(path.join(dir, name), depth + 1);
|
|
42
|
+
} else {
|
|
43
|
+
const rel = path.relative(cwd, path.join(dir, name));
|
|
44
|
+
lines.push(rel);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await walk(cwd, 1);
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reads CLAUDE.md from project dir. Returns content or empty string.
|
|
55
|
+
*/
|
|
56
|
+
async function readClaudeMd(cwd) {
|
|
57
|
+
try {
|
|
58
|
+
return await fsp.readFile(path.join(cwd, 'CLAUDE.md'), 'utf8');
|
|
59
|
+
} catch {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Runs `git log --oneline -N`. Returns string or empty.
|
|
66
|
+
*/
|
|
67
|
+
async function getGitLog(cwd, count = 20) {
|
|
68
|
+
try {
|
|
69
|
+
const { stdout } = await execFileAsync('git', ['log', '--oneline', `-${count}`], { cwd });
|
|
70
|
+
return stdout.trim();
|
|
71
|
+
} catch {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Detects test command from package.json or Makefile.
|
|
78
|
+
*/
|
|
79
|
+
async function detectTestCommand(cwd) {
|
|
80
|
+
// Check package.json
|
|
81
|
+
try {
|
|
82
|
+
const pkgRaw = await fsp.readFile(path.join(cwd, 'package.json'), 'utf8');
|
|
83
|
+
const pkg = JSON.parse(pkgRaw);
|
|
84
|
+
if (pkg.scripts && pkg.scripts.test) {
|
|
85
|
+
return 'npm test';
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
// no package.json or invalid
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check Makefile
|
|
92
|
+
try {
|
|
93
|
+
const makefile = await fsp.readFile(path.join(cwd, 'Makefile'), 'utf8');
|
|
94
|
+
if (/^test:/m.test(makefile)) {
|
|
95
|
+
return 'make test';
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// no Makefile
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Splits request into keywords, scores files by keyword matches in filename,
|
|
106
|
+
* reads top matches. Returns { filename: content } map.
|
|
107
|
+
*/
|
|
108
|
+
async function findRelevantFiles(cwd, request, maxFiles = 10, existingTree = null) {
|
|
109
|
+
const keywords = request
|
|
110
|
+
.toLowerCase()
|
|
111
|
+
.split(/[\s,.\-_/]+/)
|
|
112
|
+
.filter(w => w.length > 2);
|
|
113
|
+
|
|
114
|
+
if (keywords.length === 0) return {};
|
|
115
|
+
|
|
116
|
+
// Reuse existing tree if provided, otherwise fetch
|
|
117
|
+
const tree = existingTree || await getFileTree(cwd);
|
|
118
|
+
const files = tree.split('\n').filter(f => f && !f.endsWith('/'));
|
|
119
|
+
|
|
120
|
+
// Score each file
|
|
121
|
+
const scored = files.map(f => {
|
|
122
|
+
const lower = f.toLowerCase();
|
|
123
|
+
let score = 0;
|
|
124
|
+
for (const kw of keywords) {
|
|
125
|
+
if (lower.includes(kw)) score++;
|
|
126
|
+
}
|
|
127
|
+
return { file: f, score };
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Filter and sort
|
|
131
|
+
const matches = scored
|
|
132
|
+
.filter(s => s.score > 0)
|
|
133
|
+
.sort((a, b) => b.score - a.score)
|
|
134
|
+
.slice(0, maxFiles);
|
|
135
|
+
|
|
136
|
+
const result = {};
|
|
137
|
+
for (const { file } of matches) {
|
|
138
|
+
try {
|
|
139
|
+
let content = await fsp.readFile(path.join(cwd, file), 'utf8');
|
|
140
|
+
if (content.length > MAX_FILE_CONTENT) {
|
|
141
|
+
content = content.slice(0, MAX_FILE_CONTENT);
|
|
142
|
+
}
|
|
143
|
+
result[file] = content;
|
|
144
|
+
} catch {
|
|
145
|
+
// skip unreadable files
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Search brain knowledge base for relevant context.
|
|
153
|
+
* Uses findKnowledge (SQL LIKE) since there's no FTS on memories.
|
|
154
|
+
*/
|
|
155
|
+
function searchBrain(brain, request, limit = 10) {
|
|
156
|
+
if (!brain || typeof brain.findKnowledge !== 'function') return [];
|
|
157
|
+
try {
|
|
158
|
+
// Extract meaningful keywords from the request
|
|
159
|
+
const keywords = request
|
|
160
|
+
.toLowerCase()
|
|
161
|
+
.split(/[\s,.\-_/]+/)
|
|
162
|
+
.filter(w => w.length > 3);
|
|
163
|
+
if (keywords.length === 0) return [];
|
|
164
|
+
|
|
165
|
+
// Search knowledge by subject for each keyword, deduplicate
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
const results = [];
|
|
168
|
+
for (const kw of keywords.slice(0, 5)) {
|
|
169
|
+
const found = brain.findKnowledge({ subject: kw, status: 'active' });
|
|
170
|
+
for (const item of found) {
|
|
171
|
+
if (!seen.has(item.id) && results.length < limit) {
|
|
172
|
+
seen.add(item.id);
|
|
173
|
+
results.push(item);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return results;
|
|
178
|
+
} catch {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* If mcpClient provided, list tools, find search-type tools, call up to 3.
|
|
185
|
+
* Return [{ tool, result }]. Otherwise return [].
|
|
186
|
+
*/
|
|
187
|
+
async function queryMcp(mcpClient, request) {
|
|
188
|
+
if (!mcpClient || typeof mcpClient.listTools !== 'function') return [];
|
|
189
|
+
try {
|
|
190
|
+
const tools = await mcpClient.listTools();
|
|
191
|
+
const searchTools = tools.filter(t => {
|
|
192
|
+
const name = (t.name || '').toLowerCase();
|
|
193
|
+
const desc = (t.description || '').toLowerCase();
|
|
194
|
+
return name.includes('search') || name.includes('query') || name.includes('find')
|
|
195
|
+
|| desc.includes('search') || desc.includes('query') || desc.includes('find');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const selected = searchTools.slice(0, 3);
|
|
199
|
+
const settled = await Promise.allSettled(
|
|
200
|
+
selected.map(tool =>
|
|
201
|
+
mcpClient.callTool(tool.name, { query: request })
|
|
202
|
+
.then(result => ({ tool: tool.name, result }))
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
return settled
|
|
206
|
+
.filter(s => s.status === 'fulfilled')
|
|
207
|
+
.map(s => s.value);
|
|
208
|
+
} catch {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Calls all context-gathering functions in parallel.
|
|
215
|
+
* Returns combined context object.
|
|
216
|
+
*/
|
|
217
|
+
async function assembleContext(request, cwd, { brain, mcpClient } = {}) {
|
|
218
|
+
// Gather independent data in parallel (file tree fetched once, reused for relevantFiles)
|
|
219
|
+
const [fileTree, claudeMd, gitLog, testCommand, brainMemories, mcpResults] =
|
|
220
|
+
await Promise.all([
|
|
221
|
+
getFileTree(cwd),
|
|
222
|
+
readClaudeMd(cwd),
|
|
223
|
+
getGitLog(cwd),
|
|
224
|
+
detectTestCommand(cwd),
|
|
225
|
+
Promise.resolve(searchBrain(brain, request)),
|
|
226
|
+
queryMcp(mcpClient, request),
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
// Pass fileTree to avoid re-traversal
|
|
230
|
+
const relevantFiles = await findRelevantFiles(cwd, request, 10, fileTree);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
fileTree,
|
|
234
|
+
claudeMd,
|
|
235
|
+
gitLog,
|
|
236
|
+
testCommand,
|
|
237
|
+
relevantFiles,
|
|
238
|
+
brainMemories,
|
|
239
|
+
mcpResults,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
getFileTree,
|
|
245
|
+
readClaudeMd,
|
|
246
|
+
getGitLog,
|
|
247
|
+
detectTestCommand,
|
|
248
|
+
findRelevantFiles,
|
|
249
|
+
searchBrain,
|
|
250
|
+
queryMcp,
|
|
251
|
+
assembleContext,
|
|
252
|
+
};
|