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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { spawn } = require('node:child_process');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build a prompt for reviewing a single subtask's diff against its intent.
|
|
6
|
+
* @param {{ title: string, prompt: string }} subtask
|
|
7
|
+
* @param {string} diff
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
function buildSubtaskReviewPrompt(subtask, diff) {
|
|
11
|
+
return `You are a code reviewer. Review the following diff for a subtask and check whether it correctly implements the stated intent. Look for bugs, security issues, and missing pieces.
|
|
12
|
+
|
|
13
|
+
## Subtask Title
|
|
14
|
+
${subtask.title}
|
|
15
|
+
|
|
16
|
+
## Subtask Intent
|
|
17
|
+
${subtask.prompt}
|
|
18
|
+
|
|
19
|
+
## Diff
|
|
20
|
+
\`\`\`
|
|
21
|
+
${diff}
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
Reply with JSON in exactly this format (no other text outside the JSON):
|
|
25
|
+
{"pass": true/false, "issues": ["description of each issue"], "severity": "minor|major|blocker"}
|
|
26
|
+
|
|
27
|
+
- "pass": true if the diff correctly implements the intent with no significant issues
|
|
28
|
+
- "issues": array of strings describing any problems found (empty array if none)
|
|
29
|
+
- "severity": "minor" for style/nitpicks, "major" for bugs or missing functionality, "blocker" for security issues or fundamentally broken logic`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build a prompt for final review of the entire diff against the original request.
|
|
34
|
+
* @param {string} request - The original user request
|
|
35
|
+
* @param {string} fullDiff - Combined diff of all changes
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
function buildFinalReviewPrompt(request, fullDiff) {
|
|
39
|
+
return `You are a senior code reviewer performing a final review of an entire changeset. Check that the diff fully implements the original request. Evaluate:
|
|
40
|
+
|
|
41
|
+
1. Cross-file consistency - do imports, exports, and interfaces line up?
|
|
42
|
+
2. Missing pieces - is anything from the request left unimplemented?
|
|
43
|
+
3. Architectural issues - are there design problems that span multiple files?
|
|
44
|
+
4. Bugs and security issues
|
|
45
|
+
|
|
46
|
+
## Original Request
|
|
47
|
+
${request}
|
|
48
|
+
|
|
49
|
+
## Full Diff
|
|
50
|
+
\`\`\`
|
|
51
|
+
${fullDiff}
|
|
52
|
+
\`\`\`
|
|
53
|
+
|
|
54
|
+
Reply with JSON in exactly this format (no other text outside the JSON):
|
|
55
|
+
{"pass": true/false, "issues": ["description of each issue"], "severity": "minor|major|blocker", "summary": "one-line summary of the review"}
|
|
56
|
+
|
|
57
|
+
- "pass": true if the changeset correctly and completely implements the request
|
|
58
|
+
- "issues": array of strings describing any problems found (empty array if none)
|
|
59
|
+
- "severity": "minor" for style/nitpicks, "major" for bugs or missing functionality, "blocker" for security issues or fundamentally broken logic
|
|
60
|
+
- "summary": a brief one-line summary of the overall review`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extract a JSON verdict from Claude's response text.
|
|
65
|
+
* Finds the first JSON object containing a "pass" field.
|
|
66
|
+
* @param {string} text
|
|
67
|
+
* @returns {{ pass: boolean, issues: string[], severity: string, summary: string }}
|
|
68
|
+
*/
|
|
69
|
+
function parseVerdict(text) {
|
|
70
|
+
const failResult = {
|
|
71
|
+
pass: false,
|
|
72
|
+
issues: ['Failed to parse review verdict'],
|
|
73
|
+
severity: 'blocker',
|
|
74
|
+
summary: '',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (!text) return failResult;
|
|
78
|
+
|
|
79
|
+
// Match all JSON-like objects in the text
|
|
80
|
+
const jsonPattern = /\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/g;
|
|
81
|
+
let match;
|
|
82
|
+
while ((match = jsonPattern.exec(text)) !== null) {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(match[0]);
|
|
85
|
+
if ('pass' in parsed) {
|
|
86
|
+
return {
|
|
87
|
+
pass: Boolean(parsed.pass),
|
|
88
|
+
issues: Array.isArray(parsed.issues) ? parsed.issues : [],
|
|
89
|
+
severity: parsed.severity || 'minor',
|
|
90
|
+
summary: parsed.summary || '',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Not valid JSON, try next match
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return failResult;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Spawn `claude` CLI to run a review prompt and return the parsed verdict.
|
|
103
|
+
* Uses spawn with argument array (no shell injection risk).
|
|
104
|
+
* @param {string} prompt
|
|
105
|
+
* @param {{ cwd?: string, timeoutMs?: number, budgetUsd?: number }} opts
|
|
106
|
+
* @returns {Promise<{ pass: boolean, issues: string[], severity: string, summary: string }>}
|
|
107
|
+
*/
|
|
108
|
+
function runReview(prompt, opts = {}) {
|
|
109
|
+
const { cwd, timeoutMs = 120_000 } = opts;
|
|
110
|
+
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const blockerVerdict = {
|
|
113
|
+
pass: false,
|
|
114
|
+
issues: ['Review process failed'],
|
|
115
|
+
severity: 'blocker',
|
|
116
|
+
summary: '',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
let proc;
|
|
120
|
+
try {
|
|
121
|
+
const args = ['-p', prompt, '--output-format', 'text', '--max-turns', '1'];
|
|
122
|
+
proc = spawn('claude', args, {
|
|
123
|
+
cwd: cwd || process.cwd(),
|
|
124
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
125
|
+
timeout: timeoutMs,
|
|
126
|
+
});
|
|
127
|
+
} catch (err) {
|
|
128
|
+
blockerVerdict.issues = [`Spawn error: ${err.message}`];
|
|
129
|
+
return resolve(blockerVerdict);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const MAX_OUTPUT = 256 * 1024; // 256KB cap
|
|
133
|
+
let stdout = '';
|
|
134
|
+
let stderr = '';
|
|
135
|
+
|
|
136
|
+
proc.stdout.on('data', (chunk) => {
|
|
137
|
+
if (stdout.length < MAX_OUTPUT) stdout += chunk;
|
|
138
|
+
});
|
|
139
|
+
proc.stderr.on('data', (chunk) => {
|
|
140
|
+
if (stderr.length < MAX_OUTPUT) stderr += chunk;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
proc.on('error', (err) => {
|
|
144
|
+
blockerVerdict.issues = [`Spawn error: ${err.message}`];
|
|
145
|
+
resolve(blockerVerdict);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
proc.on('close', (code) => {
|
|
149
|
+
if (code !== 0 && !stdout.trim()) {
|
|
150
|
+
blockerVerdict.issues = [`claude exited with code ${code}: ${stderr.trim() || 'no output'}`];
|
|
151
|
+
return resolve(blockerVerdict);
|
|
152
|
+
}
|
|
153
|
+
resolve(parseVerdict(stdout));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Review a single subtask's diff.
|
|
160
|
+
* @param {{ title: string, prompt: string }} subtask
|
|
161
|
+
* @param {string} diff
|
|
162
|
+
* @param {{ cwd?: string, timeoutMs?: number, budgetUsd?: number }} opts
|
|
163
|
+
* @returns {Promise<{ pass: boolean, issues: string[], severity: string, summary: string }>}
|
|
164
|
+
*/
|
|
165
|
+
function reviewSubtask(subtask, diff, opts = {}) {
|
|
166
|
+
const prompt = buildSubtaskReviewPrompt(subtask, diff);
|
|
167
|
+
return runReview(prompt, opts);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Final review of the entire diff against the original request.
|
|
172
|
+
* @param {string} request
|
|
173
|
+
* @param {string} fullDiff
|
|
174
|
+
* @param {{ cwd?: string, timeoutMs?: number, budgetUsd?: number }} opts
|
|
175
|
+
* @returns {Promise<{ pass: boolean, issues: string[], severity: string, summary: string }>}
|
|
176
|
+
*/
|
|
177
|
+
function reviewFinal(request, fullDiff, opts = {}) {
|
|
178
|
+
const prompt = buildFinalReviewPrompt(request, fullDiff);
|
|
179
|
+
return runReview(prompt, opts);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
buildSubtaskReviewPrompt,
|
|
184
|
+
buildFinalReviewPrompt,
|
|
185
|
+
parseVerdict,
|
|
186
|
+
runReview,
|
|
187
|
+
reviewSubtask,
|
|
188
|
+
reviewFinal,
|
|
189
|
+
};
|
|
@@ -44,10 +44,11 @@ module.exports = [
|
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
title: 'Email: Full Send History Pull',
|
|
47
|
-
description: '
|
|
48
|
-
type: '
|
|
47
|
+
description: 'Full sync of sent email history from macOS Mail. First run pulls 10 years; subsequent runs sync incrementally from last email.',
|
|
48
|
+
type: 'recurring',
|
|
49
|
+
schedule: 'every 12h',
|
|
49
50
|
skill: 'email-sync',
|
|
50
|
-
skill_config: JSON.stringify({ days_back: 3650, sync_inbox: true }),
|
|
51
|
+
skill_config: JSON.stringify({ days_back: 3650, sync_inbox: true, incremental: true }),
|
|
51
52
|
priority: 'normal',
|
|
52
53
|
},
|
|
53
54
|
{
|
|
@@ -66,6 +67,14 @@ module.exports = [
|
|
|
66
67
|
skill: 'slack-mentions',
|
|
67
68
|
priority: 'normal',
|
|
68
69
|
},
|
|
70
|
+
{
|
|
71
|
+
title: 'Glean: Team Structure Sync',
|
|
72
|
+
description: 'Sync team structure and contacts from Glean people directory into Wall-E brain.',
|
|
73
|
+
type: 'recurring',
|
|
74
|
+
schedule: 'weekly Monday at 6am',
|
|
75
|
+
skill: 'glean-team-sync',
|
|
76
|
+
priority: 'normal',
|
|
77
|
+
},
|
|
69
78
|
{
|
|
70
79
|
title: 'Weekly Reflection',
|
|
71
80
|
description: 'Generate a weekly reflection analyzing patterns, decisions, and insights from the past 7 days.',
|
|
@@ -11,9 +11,9 @@ if ! command -v fly &> /dev/null; then
|
|
|
11
11
|
fi
|
|
12
12
|
|
|
13
13
|
# Check if app exists
|
|
14
|
-
if ! fly apps list 2>/dev/null | grep -q wall-e
|
|
14
|
+
if ! fly apps list 2>/dev/null | grep -q my-wall-e; then
|
|
15
15
|
echo "Creating Fly.io app..."
|
|
16
|
-
fly apps create wall-e
|
|
16
|
+
fly apps create my-wall-e
|
|
17
17
|
fly volumes create wall_e_data --region sjc --size 1
|
|
18
18
|
fi
|
|
19
19
|
|
|
@@ -31,5 +31,5 @@ fly deploy
|
|
|
31
31
|
|
|
32
32
|
echo ""
|
|
33
33
|
echo "=== Deployment complete ==="
|
|
34
|
-
echo "WALL-E is running at: https://wall-e
|
|
35
|
-
echo "Health check: https://wall-e
|
|
34
|
+
echo "WALL-E is running at: https://my-wall-e.fly.dev"
|
|
35
|
+
echo "Health check: https://my-wall-e.fly.dev/api/wall-e/health"
|
package/template/wall-e/fly.toml
CHANGED
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
"test": "node --test tests/*.test.js",
|
|
10
10
|
"deploy": "./deploy.sh"
|
|
11
11
|
},
|
|
12
|
-
"engines": {
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
13
15
|
"dependencies": {
|
|
14
16
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
15
17
|
"better-sqlite3": "^12.8.0",
|
|
18
|
+
"grammy": "^1.42.0",
|
|
16
19
|
"uuid": "^11.1.0"
|
|
17
20
|
}
|
|
18
21
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coding-agent
|
|
3
|
+
description: Autonomous coding agent — decomposes requests into subtasks, executes via Claude Code, verifies, and delivers
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
execution: script
|
|
6
|
+
entry: run.js
|
|
7
|
+
trigger:
|
|
8
|
+
type: manual
|
|
9
|
+
permissions: [brain:read, brain:write, shell:exec, mcp:call]
|
|
10
|
+
tags: [coding, orchestration, automation]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Coding Agent
|
|
14
|
+
|
|
15
|
+
Accepts a coding request, breaks it into subtasks, executes each via headless Claude Code sessions, verifies with tests and code review, then commits and reports back.
|
|
16
|
+
|
|
17
|
+
Triggered manually via chat, Slack, API, or task UI.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Coding Agent Skill — entry point.
|
|
5
|
+
*
|
|
6
|
+
* Receives config via WALL_E_SKILL_CONFIG env var.
|
|
7
|
+
* Resumes from WALL_E_CHECKPOINT if present.
|
|
8
|
+
* Outputs CHECKPOINT: lines for crash-safe resumption.
|
|
9
|
+
* Outputs final report to stdout.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
// Parse config
|
|
17
|
+
const configStr = process.env.WALL_E_SKILL_CONFIG;
|
|
18
|
+
if (!configStr) {
|
|
19
|
+
console.error('Missing WALL_E_SKILL_CONFIG');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let config;
|
|
24
|
+
try {
|
|
25
|
+
config = JSON.parse(configStr);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error('Invalid WALL_E_SKILL_CONFIG:', e.message);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const request = config.request;
|
|
32
|
+
const cwd = config.cwd || process.cwd();
|
|
33
|
+
const options = config.options || {};
|
|
34
|
+
|
|
35
|
+
if (!request) {
|
|
36
|
+
console.error('Missing request in config');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Load orchestrator (resolve relative to wall-e root, not skill dir)
|
|
41
|
+
const walleRoot = path.resolve(__dirname, '..', '..', '..');
|
|
42
|
+
const orchestrator = require(path.join(walleRoot, 'coding-orchestrator.js'));
|
|
43
|
+
|
|
44
|
+
// Load brain if available
|
|
45
|
+
let brain;
|
|
46
|
+
try {
|
|
47
|
+
brain = require(path.join(walleRoot, 'brain.js'));
|
|
48
|
+
} catch {}
|
|
49
|
+
|
|
50
|
+
// Check for checkpoint (resume case)
|
|
51
|
+
let resumeState = null;
|
|
52
|
+
const checkpoint = process.env.WALL_E_CHECKPOINT;
|
|
53
|
+
if (checkpoint && fs.existsSync(checkpoint)) {
|
|
54
|
+
try {
|
|
55
|
+
resumeState = orchestrator.readCheckpoint(checkpoint);
|
|
56
|
+
console.log(`Resuming from checkpoint: ${resumeState.completed.length} subtasks completed`);
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error('Failed to read checkpoint:', e.message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Progress callback — logs to stdout
|
|
63
|
+
function onProgress(event) {
|
|
64
|
+
switch (event.type) {
|
|
65
|
+
case 'subtask_start':
|
|
66
|
+
console.log(`\n--- Subtask ${event.subtask.id}: ${event.subtask.title} ---`);
|
|
67
|
+
break;
|
|
68
|
+
case 'subtask_done':
|
|
69
|
+
console.log(` [OK] Subtask ${event.subtask.id} completed`);
|
|
70
|
+
break;
|
|
71
|
+
case 'subtask_retry':
|
|
72
|
+
console.log(` [RETRY ${event.attempt}] ${(event.error || '').slice(0, 200)}`);
|
|
73
|
+
break;
|
|
74
|
+
case 'review_concern':
|
|
75
|
+
console.log(` [CONCERN] ${(event.issues || []).join('; ')}`);
|
|
76
|
+
break;
|
|
77
|
+
case 'escalate':
|
|
78
|
+
console.log(` [ESCALATE] Stuck on subtask ${event.subtask.id}: ${(event.error || '').slice(0, 200)}`);
|
|
79
|
+
break;
|
|
80
|
+
case 'budget_exceeded':
|
|
81
|
+
console.log(` [BUDGET] Exceeded: ~$${event.spent.toFixed(2)}`);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Plan (skip if resuming with existing plan)
|
|
88
|
+
let planData;
|
|
89
|
+
if (resumeState && resumeState.plan) {
|
|
90
|
+
console.log('Using plan from checkpoint');
|
|
91
|
+
const mergedConfig = { ...orchestrator.getDefaultConfig(), ...options };
|
|
92
|
+
const { assembleContext } = require(path.join(walleRoot, 'coding-context.js'));
|
|
93
|
+
const context = await assembleContext(request, cwd, { brain });
|
|
94
|
+
planData = { plan: resumeState.plan, context, config: mergedConfig };
|
|
95
|
+
} else {
|
|
96
|
+
console.log('Planning...');
|
|
97
|
+
planData = await orchestrator.plan(request, cwd, { ...options, brain });
|
|
98
|
+
console.log(`Plan: ${planData.plan.subtasks.length} subtasks, branch: ${planData.plan.branch_name}`);
|
|
99
|
+
|
|
100
|
+
// Guardrail check
|
|
101
|
+
if (planData.plan.subtasks.length > planData.config.guardrail_threshold) {
|
|
102
|
+
console.log(`\n[GUARDRAIL] Plan has ${planData.plan.subtasks.length} subtasks (threshold: ${planData.config.guardrail_threshold})`);
|
|
103
|
+
console.log('Subtasks:');
|
|
104
|
+
planData.plan.subtasks.forEach(s => console.log(` ${s.id}. ${s.title}`));
|
|
105
|
+
console.log('\nPausing for approval. Re-run to continue.');
|
|
106
|
+
const statePath = path.join(cwd, '.wall-e-coding-state.json');
|
|
107
|
+
orchestrator.writeCheckpoint(statePath, {
|
|
108
|
+
completed: [], plan: planData.plan,
|
|
109
|
+
cumulative_context: '', branch: planData.plan.branch_name, session_ids: {},
|
|
110
|
+
});
|
|
111
|
+
console.log(`CHECKPOINT: ${statePath}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Execute
|
|
117
|
+
console.log('\nExecuting...');
|
|
118
|
+
const executeResult = await orchestrator.execute(planData, {
|
|
119
|
+
cwd,
|
|
120
|
+
onProgress,
|
|
121
|
+
startFrom: resumeState,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!executeResult.success) {
|
|
125
|
+
console.error(`\nExecution failed: ${executeResult.error}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Complete
|
|
130
|
+
console.log('\nCompleting...');
|
|
131
|
+
const report = await orchestrator.complete(request, planData, executeResult, {
|
|
132
|
+
cwd, brain, onProgress,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
console.log('\n' + orchestrator.formatReport(report));
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.error(`Coding agent error: ${e.message}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main();
|
|
@@ -5,7 +5,7 @@ description: >
|
|
|
5
5
|
the owner's communication style. Always syncs all sent mail with body
|
|
6
6
|
content. Inbox sync is optional and filters to only emails addressed
|
|
7
7
|
directly to the owner (on To/Cc line). Uses JXA to read Mail.app.
|
|
8
|
-
version: 1.
|
|
8
|
+
version: 1.2.0
|
|
9
9
|
author: wall-e
|
|
10
10
|
execution: script
|
|
11
11
|
entry: run.js
|
|
@@ -16,11 +16,15 @@ config:
|
|
|
16
16
|
days_back:
|
|
17
17
|
type: number
|
|
18
18
|
default: 3
|
|
19
|
-
description: "How many days back to sync"
|
|
19
|
+
description: "How many days back to sync (used as fallback when incremental has no prior data)"
|
|
20
20
|
sync_inbox:
|
|
21
21
|
type: boolean
|
|
22
22
|
default: false
|
|
23
23
|
description: "Also sync inbox emails addressed directly to owner"
|
|
24
|
+
incremental:
|
|
25
|
+
type: boolean
|
|
26
|
+
default: false
|
|
27
|
+
description: "Query brain for latest email and only fetch since then (days_back used as fallback for first run)"
|
|
24
28
|
tags: [email, gmail, mail, sent, sync, learning]
|
|
25
29
|
permissions:
|
|
26
30
|
- mail:read
|
|
@@ -36,11 +40,12 @@ Optionally syncs inbox emails that are addressed directly to the owner.
|
|
|
36
40
|
|
|
37
41
|
## How It Works
|
|
38
42
|
|
|
39
|
-
1.
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
43
|
+
1. **Incremental mode** (`incremental: true`): queries brain for the latest email timestamp, computes `days_back` dynamically (+1 day overlap). Falls back to configured `days_back` on first run (no prior data).
|
|
44
|
+
2. Run JXA script via `osascript` to read sent messages from all accounts
|
|
45
|
+
3. Sent mail always includes body content (first 2000 chars) for learning
|
|
46
|
+
4. Inbox is opt-in (`--sync-inbox`), filtered to emails where owner is on To/Cc
|
|
47
|
+
5. Dedup by `source_id` = `mail:{messageId}` — skip unchanged, update modified
|
|
48
|
+
6. Store as memories with `source: 'email'`, `source_channel: <account name>`
|
|
44
49
|
|
|
45
50
|
## Memory Format
|
|
46
51
|
|
|
@@ -17,6 +17,24 @@ function run(argv) {
|
|
|
17
17
|
var cutoff = new Date();
|
|
18
18
|
cutoff.setDate(cutoff.getDate() - daysBack);
|
|
19
19
|
|
|
20
|
+
// For large date ranges, chunk into 90-day windows to avoid AppleEvent timeout (-1712).
|
|
21
|
+
// The .whose() query is a single Apple Event and Mail can't respond in time for huge ranges.
|
|
22
|
+
var CHUNK_DAYS = 90;
|
|
23
|
+
var chunks = [];
|
|
24
|
+
if (daysBack > CHUNK_DAYS) {
|
|
25
|
+
var now = new Date();
|
|
26
|
+
var chunkEnd = new Date(now);
|
|
27
|
+
while (chunkEnd > cutoff) {
|
|
28
|
+
var chunkStart = new Date(chunkEnd);
|
|
29
|
+
chunkStart.setDate(chunkStart.getDate() - CHUNK_DAYS);
|
|
30
|
+
if (chunkStart < cutoff) chunkStart = cutoff;
|
|
31
|
+
chunks.push({ start: chunkStart, end: chunkEnd });
|
|
32
|
+
chunkEnd = new Date(chunkStart);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
chunks.push({ start: cutoff, end: new Date() });
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
var results = { accounts: [], inbox: [], sent: [] };
|
|
21
39
|
var accounts = mail.accounts();
|
|
22
40
|
|
|
@@ -33,27 +51,33 @@ function run(argv) {
|
|
|
33
51
|
if (readInbox) {
|
|
34
52
|
try {
|
|
35
53
|
var inbox = acct.mailboxes.byName('INBOX');
|
|
36
|
-
var
|
|
37
|
-
for (var m = 0; m < inboxMsgs.length; m++) {
|
|
54
|
+
for (var ci = 0; ci < chunks.length; ci++) {
|
|
38
55
|
try {
|
|
39
|
-
var
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
var
|
|
43
|
-
|
|
56
|
+
var inboxMsgs = inbox.messages.whose({
|
|
57
|
+
_and: [{ dateReceived: { _greaterThan: chunks[ci].start } }, { dateReceived: { _lessThan: chunks[ci].end } }]
|
|
58
|
+
})();
|
|
59
|
+
for (var m = 0; m < inboxMsgs.length; m++) {
|
|
60
|
+
try {
|
|
61
|
+
var msg = inboxMsgs[m];
|
|
62
|
+
var toRecips = [];
|
|
63
|
+
try { toRecips = msg.toRecipients().map(function(r) { return { name: r.name(), email: r.address() }; }); } catch(e) {}
|
|
64
|
+
var ccRecips = [];
|
|
65
|
+
try { ccRecips = msg.ccRecipients().map(function(r) { return { name: r.name(), email: r.address() }; }); } catch(e) {}
|
|
44
66
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
results.inbox.push({
|
|
68
|
+
messageId: msg.messageId(),
|
|
69
|
+
subject: msg.subject() || '(no subject)',
|
|
70
|
+
sender: msg.sender(),
|
|
71
|
+
to: toRecips,
|
|
72
|
+
cc: ccRecips,
|
|
73
|
+
date: msg.dateReceived().toISOString(),
|
|
74
|
+
account: acctName,
|
|
75
|
+
mailbox: 'INBOX',
|
|
76
|
+
readStatus: msg.readStatus()
|
|
77
|
+
});
|
|
78
|
+
} catch(e) { /* skip individual message errors */ }
|
|
79
|
+
}
|
|
80
|
+
} catch(e) { /* chunk query failed, continue */ }
|
|
57
81
|
}
|
|
58
82
|
} catch(e) { /* account may not have INBOX */ }
|
|
59
83
|
}
|
|
@@ -63,37 +87,43 @@ function run(argv) {
|
|
|
63
87
|
for (var s = 0; s < sentNames.length; s++) {
|
|
64
88
|
try {
|
|
65
89
|
var sentBox = acct.mailboxes.byName(sentNames[s]);
|
|
66
|
-
var
|
|
67
|
-
for (var m = 0; m < sentMsgs.length; m++) {
|
|
90
|
+
for (var ci = 0; ci < chunks.length; ci++) {
|
|
68
91
|
try {
|
|
69
|
-
var
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
var
|
|
73
|
-
|
|
92
|
+
var sentMsgs = sentBox.messages.whose({
|
|
93
|
+
_and: [{ dateSent: { _greaterThan: chunks[ci].start } }, { dateSent: { _lessThan: chunks[ci].end } }]
|
|
94
|
+
})();
|
|
95
|
+
for (var m = 0; m < sentMsgs.length; m++) {
|
|
96
|
+
try {
|
|
97
|
+
var msg = sentMsgs[m];
|
|
98
|
+
var toRecips = [];
|
|
99
|
+
try { toRecips = msg.toRecipients().map(function(r) { return { name: r.name(), email: r.address() }; }); } catch(e) {}
|
|
100
|
+
var ccRecips = [];
|
|
101
|
+
try { ccRecips = msg.ccRecipients().map(function(r) { return { name: r.name(), email: r.address() }; }); } catch(e) {}
|
|
74
102
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
var entry = {
|
|
104
|
+
messageId: msg.messageId(),
|
|
105
|
+
subject: msg.subject() || '(no subject)',
|
|
106
|
+
sender: msg.sender(),
|
|
107
|
+
to: toRecips,
|
|
108
|
+
cc: ccRecips,
|
|
109
|
+
date: msg.dateSent().toISOString(),
|
|
110
|
+
account: acctName,
|
|
111
|
+
mailbox: sentNames[s],
|
|
112
|
+
readStatus: true
|
|
113
|
+
};
|
|
86
114
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
115
|
+
// Always fetch content for sent messages
|
|
116
|
+
try {
|
|
117
|
+
var body = msg.content();
|
|
118
|
+
if (body && body.length > 0) {
|
|
119
|
+
entry.content = body.substring(0, 2000);
|
|
120
|
+
}
|
|
121
|
+
} catch(e) { /* content extraction failed */ }
|
|
94
122
|
|
|
95
|
-
|
|
96
|
-
|
|
123
|
+
results.sent.push(entry);
|
|
124
|
+
} catch(e) { /* skip individual message errors */ }
|
|
125
|
+
}
|
|
126
|
+
} catch(e) { /* chunk query failed, continue */ }
|
|
97
127
|
}
|
|
98
128
|
break; // found the sent mailbox for this account
|
|
99
129
|
} catch(e) { /* try next sent mailbox name */ }
|