create-walle 0.9.11 → 0.9.13
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 +3 -3
- package/package.json +2 -2
- package/template/bin/dev.sh +7 -1
- package/template/bin/setup.js +53 -9
- package/template/bin/sync-images.js +53 -0
- package/template/builder-journal.md +17 -0
- package/template/claude-task-manager/api-prompts.js +98 -13
- package/template/claude-task-manager/api-reviews.js +82 -5
- package/template/claude-task-manager/db.js +32 -5
- package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
- package/template/claude-task-manager/lib/session-capture.js +421 -0
- package/template/claude-task-manager/lib/session-history.js +135 -15
- package/template/claude-task-manager/lib/session-jobs.js +10 -5
- package/template/claude-task-manager/lib/session-stream.js +87 -19
- package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
- package/template/claude-task-manager/lib/walle-session-context.js +61 -0
- package/template/claude-task-manager/lib/walle-transcript.js +176 -0
- package/template/claude-task-manager/public/css/setup.css +35 -8
- package/template/claude-task-manager/public/css/walle-session.css +56 -0
- package/template/claude-task-manager/public/css/walle.css +120 -0
- package/template/claude-task-manager/public/index.html +814 -181
- package/template/claude-task-manager/public/js/message-renderer.js +148 -19
- package/template/claude-task-manager/public/js/reviews.js +120 -62
- package/template/claude-task-manager/public/js/setup.js +75 -31
- package/template/claude-task-manager/public/js/stream-view.js +115 -55
- package/template/claude-task-manager/public/js/walle-session.js +84 -2
- package/template/claude-task-manager/public/js/walle.js +308 -54
- package/template/claude-task-manager/server.js +1092 -146
- package/template/claude-task-manager/session-integrity.js +181 -54
- package/template/claude-task-manager/session-utils.js +123 -41
- package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
- package/template/package.json +1 -1
- package/template/wall-e/adapters/ctm.js +39 -18
- package/template/wall-e/agent-runners/contract.js +17 -0
- package/template/wall-e/agent-runners/index.js +22 -0
- package/template/wall-e/agent-runtime/harness.js +212 -0
- package/template/wall-e/agent-runtime/index.js +8 -0
- package/template/wall-e/agent-runtime/registry.js +67 -0
- package/template/wall-e/agent-runtime/session-store.js +179 -0
- package/template/wall-e/agent-runtime/spawn.js +208 -0
- package/template/wall-e/api-walle.js +174 -7
- package/template/wall-e/brain.js +266 -28
- package/template/wall-e/channels/policy.js +88 -0
- package/template/wall-e/channels/registry.js +15 -1
- package/template/wall-e/channels/reply-dispatcher.js +70 -0
- package/template/wall-e/channels/session-bindings.js +51 -0
- package/template/wall-e/chat/code-review-context.js +29 -0
- package/template/wall-e/chat.js +188 -42
- package/template/wall-e/coding/acp-adapter.js +188 -0
- package/template/wall-e/coding/agent-catalog.js +129 -0
- package/template/wall-e/coding/compaction-service.js +247 -0
- package/template/wall-e/coding/execution-trace.js +3 -0
- package/template/wall-e/coding/instruction-service.js +224 -0
- package/template/wall-e/coding/model-message.js +67 -0
- package/template/wall-e/coding/permission-rules-store.js +111 -0
- package/template/wall-e/coding/permission-service.js +266 -0
- package/template/wall-e/coding/prompt-bundle.js +67 -0
- package/template/wall-e/coding/prompt-runtime.js +243 -0
- package/template/wall-e/coding/provider-transform.js +188 -0
- package/template/wall-e/coding/runtime-mode.js +132 -0
- package/template/wall-e/coding/snapshot-service.js +155 -0
- package/template/wall-e/coding/stream-processor.js +268 -0
- package/template/wall-e/coding/task-tool.js +255 -0
- package/template/wall-e/coding/tool-registry.js +361 -0
- package/template/wall-e/coding/transcript-writer.js +143 -0
- package/template/wall-e/coding/workspace-replay.js +324 -0
- package/template/wall-e/coding-context.js +4 -22
- package/template/wall-e/coding-orchestrator.js +307 -18
- package/template/wall-e/coding-prompts.js +44 -3
- package/template/wall-e/context/context-builder.js +43 -1
- package/template/wall-e/context/topic-matcher.js +1 -1
- package/template/wall-e/eval/agent-runner.js +59 -13
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
- package/template/wall-e/eval/benchmarks.js +100 -16
- package/template/wall-e/eval/eval-orchestrator.js +218 -8
- package/template/wall-e/eval/harvester.js +62 -5
- package/template/wall-e/eval/head-to-head.js +23 -2
- package/template/wall-e/eval/humaneval-adapter.js +30 -5
- package/template/wall-e/eval/livecodebench-adapter.js +29 -5
- package/template/wall-e/eval/manifest.js +186 -0
- package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
- package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
- package/template/wall-e/eval/session-transcripts.js +57 -4
- package/template/wall-e/eval/swebench-adapter.js +109 -3
- package/template/wall-e/evaluation/agent-router.js +53 -1
- package/template/wall-e/evaluation/coding-quorum.js +48 -1
- package/template/wall-e/evaluation/router.js +4 -2
- package/template/wall-e/evaluation/tier-selector.js +11 -1
- package/template/wall-e/extraction/contradiction.js +2 -2
- package/template/wall-e/extraction/indexer.js +2 -1
- package/template/wall-e/extraction/knowledge-extractor.js +2 -2
- package/template/wall-e/hooks/cli.js +92 -0
- package/template/wall-e/hooks/discovery.js +119 -0
- package/template/wall-e/hooks/index.js +7 -0
- package/template/wall-e/hooks/manifest.js +55 -0
- package/template/wall-e/hooks/runtime.js +84 -0
- package/template/wall-e/hooks/session-memory.js +225 -0
- package/template/wall-e/http/auth.js +6 -2
- package/template/wall-e/http/chat-api.js +54 -8
- package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
- package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
- package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
- package/template/wall-e/listening/calendar.js +3 -1
- package/template/wall-e/llm/client.js +64 -10
- package/template/wall-e/llm/google.js +39 -5
- package/template/wall-e/llm/ollama.js +1 -1
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/provider-availability.js +10 -0
- package/template/wall-e/llm/provider-error.js +269 -0
- package/template/wall-e/llm/tool-adapter.js +48 -12
- package/template/wall-e/loops/boot.js +2 -1
- package/template/wall-e/loops/initiative.js +2 -2
- package/template/wall-e/loops/tasks.js +8 -47
- package/template/wall-e/loops/workspace-prompts.js +20 -0
- package/template/wall-e/mcp-server.js +442 -1
- package/template/wall-e/memory/session-ingest-service.js +159 -0
- package/template/wall-e/memory/source-indexer.js +289 -0
- package/template/wall-e/plugins/discovery.js +83 -0
- package/template/wall-e/plugins/manifest-loader.js +50 -10
- package/template/wall-e/plugins/manifest-schema.js +69 -0
- package/template/wall-e/plugins/model-catalog.js +55 -0
- package/template/wall-e/prompts/coding/base.txt +2 -0
- package/template/wall-e/prompts/coding/deepseek.txt +1 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
- package/template/wall-e/prompts/coding/plan.txt +1 -0
- package/template/wall-e/runtime/execution-trace.js +220 -0
- package/template/wall-e/security/audit.js +266 -0
- package/template/wall-e/security/ssrf.js +236 -0
- package/template/wall-e/session-files.js +303 -0
- package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
- package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
- package/template/wall-e/skills/internal-skill-registry.js +2 -2
- package/template/wall-e/skills/script-skill-runner.js +143 -0
- package/template/wall-e/skills/skill-executor.js +5 -6
- package/template/wall-e/skills/skill-fallback.js +3 -1
- package/template/wall-e/skills/skill-harness-registry.js +7 -8
- package/template/wall-e/skills/skill-planner.js +52 -4
- package/template/wall-e/skills/slack-ingest.js +11 -3
- package/template/wall-e/sources/base.js +90 -0
- package/template/wall-e/sources/builtin.js +33 -0
- package/template/wall-e/sources/claude-code-jsonl.js +78 -0
- package/template/wall-e/sources/codex-jsonl.js +125 -0
- package/template/wall-e/sources/coding-session-utils.js +117 -0
- package/template/wall-e/sources/contract-suite.js +59 -0
- package/template/wall-e/sources/gemini-jsonl.js +85 -0
- package/template/wall-e/sources/index.js +9 -0
- package/template/wall-e/sources/jsonl-utils.js +181 -0
- package/template/wall-e/sources/record-types.js +252 -0
- package/template/wall-e/sources/registry.js +92 -0
- package/template/wall-e/sources/transforms.js +100 -0
- package/template/wall-e/sources/walle-jsonl.js +108 -0
- package/template/wall-e/tools/coding-middleware.js +31 -1
- package/template/wall-e/tools/file-tracker.js +25 -1
- package/template/wall-e/tools/local-tools.js +75 -47
- package/template/wall-e/tools/session-sharing.js +68 -1
- package/template/wall-e/tools/shell-analyzer.js +1 -1
- package/template/wall-e/tools/shell-policy.js +47 -0
- package/template/wall-e/tools/snapshot.js +42 -0
- package/template/wall-e/training/harvester.js +62 -5
- package/template/wall-e/utils/repair.js +253 -1
- package/template/website/index.html +3 -3
- package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
|
@@ -5,49 +5,12 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
const execAsync = promisify(exec);
|
|
8
|
+
const { SHELL_ALLOWLIST, SHELL_DENYLIST } = require('./shell-policy');
|
|
8
9
|
|
|
9
10
|
const HOME = process.env.HOME || '/tmp';
|
|
10
11
|
|
|
11
12
|
// ── Shell execution (expanded allowlist) ──
|
|
12
13
|
|
|
13
|
-
const SHELL_ALLOWLIST = new Set([
|
|
14
|
-
// File ops
|
|
15
|
-
'ls', 'cat', 'head', 'tail', 'wc', 'find', 'file', 'stat', 'du', 'df',
|
|
16
|
-
'mkdir', 'cp', 'mv', 'touch', 'tee', 'readlink', 'realpath',
|
|
17
|
-
// Path utilities
|
|
18
|
-
'cd', 'pwd', 'basename', 'dirname', 'which', 'type', 'command',
|
|
19
|
-
// Search
|
|
20
|
-
'grep', 'rg', 'ag', 'fzf',
|
|
21
|
-
// Text processing
|
|
22
|
-
'sort', 'uniq', 'cut', 'tr', 'awk', 'sed', 'jq', 'yq', 'diff', 'comm',
|
|
23
|
-
'xargs', 'tee', 'fmt', 'column',
|
|
24
|
-
// Network
|
|
25
|
-
'curl', 'wget', 'ping', 'dig', 'host', 'nslookup',
|
|
26
|
-
// Dev tools
|
|
27
|
-
'git', 'node', 'npm', 'npx', 'python3', 'pip3', 'bun', 'deno',
|
|
28
|
-
'make', 'cargo', 'go', 'ruby', 'perl',
|
|
29
|
-
// Cloud / infra
|
|
30
|
-
'fly', 'docker', 'kubectl',
|
|
31
|
-
// System info
|
|
32
|
-
'date', 'echo', 'env', 'whoami', 'hostname', 'uname', 'uptime', 'ps', 'top',
|
|
33
|
-
'id', 'groups', 'printenv', 'locale', 'lsof',
|
|
34
|
-
// macOS specific
|
|
35
|
-
'open', 'pbcopy', 'pbpaste', 'say', 'defaults', 'mdfind', 'mdls',
|
|
36
|
-
'osascript', 'screencapture', 'sw_vers', 'system_profiler',
|
|
37
|
-
// Archive
|
|
38
|
-
'tar', 'zip', 'unzip', 'gzip', 'gunzip', 'xz', 'bzip2',
|
|
39
|
-
// Misc utilities
|
|
40
|
-
'less', 'more', 'true', 'false', 'yes', 'test', 'expr', 'seq', 'sleep',
|
|
41
|
-
'md5', 'shasum', 'base64', 'xxd',
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
// Commands that are NEVER allowed regardless of context
|
|
45
|
-
const SHELL_DENYLIST = new Set([
|
|
46
|
-
'rm', 'rmdir', 'kill', 'killall', 'shutdown', 'reboot',
|
|
47
|
-
'sudo', 'su', 'chmod', 'chown', 'chgrp',
|
|
48
|
-
'dd', 'mkfs', 'fdisk', 'diskutil',
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
14
|
/**
|
|
52
15
|
* Shell escape a single argument for safe interpolation.
|
|
53
16
|
*/
|
|
@@ -375,20 +338,41 @@ async function readFile(filePath, { max_bytes, offset, limit, sessionId, project
|
|
|
375
338
|
content: numbered.join('\n') + '\n\n' + footer,
|
|
376
339
|
truncated: more || byteCut,
|
|
377
340
|
total_size: stat.size,
|
|
341
|
+
...loadReadInstructionMetadata(resolved, { sessionId, projectRoot }),
|
|
378
342
|
};
|
|
379
343
|
}
|
|
380
344
|
|
|
345
|
+
function loadReadInstructionMetadata(filePath, { sessionId, projectRoot } = {}) {
|
|
346
|
+
if (!sessionId || !projectRoot) return {};
|
|
347
|
+
try {
|
|
348
|
+
const { InstructionService } = require('../coding/instruction-service');
|
|
349
|
+
const result = new InstructionService().discoverForRead(filePath, {
|
|
350
|
+
projectRoot,
|
|
351
|
+
sessionId,
|
|
352
|
+
});
|
|
353
|
+
if (!result.instructions.length) return {};
|
|
354
|
+
return {
|
|
355
|
+
instruction_paths: result.loadedPaths,
|
|
356
|
+
instructions: result.instructions,
|
|
357
|
+
};
|
|
358
|
+
} catch {
|
|
359
|
+
return {};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
381
363
|
async function writeFile(filePath, content, { sessionId, projectRoot } = {}) {
|
|
382
364
|
const resolved = resolveToolPath(filePath, projectRoot);
|
|
383
365
|
// Assert file unchanged since last read (stale edit prevention)
|
|
366
|
+
let FileTracker = null;
|
|
384
367
|
if (sessionId) {
|
|
385
|
-
|
|
368
|
+
FileTracker = require('./file-tracker');
|
|
386
369
|
FileTracker.assertUnchanged(resolved, sessionId);
|
|
387
370
|
}
|
|
388
371
|
// Create parent dirs if needed
|
|
389
372
|
const dir = path.dirname(resolved);
|
|
390
373
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
391
374
|
fs.writeFileSync(resolved, content, 'utf8');
|
|
375
|
+
if (FileTracker) FileTracker.recordCurrent(resolved, sessionId);
|
|
392
376
|
return { written: true, path: resolved, bytes: Buffer.byteLength(content) };
|
|
393
377
|
}
|
|
394
378
|
|
|
@@ -1376,8 +1360,8 @@ async function executeLocalTool(name, input) {
|
|
|
1376
1360
|
case 'list_directory': return listDirectory(input);
|
|
1377
1361
|
case 'claude_code': return runClaudeCode(input);
|
|
1378
1362
|
case 'edit_file': return editFile(input.file_path, input.old_string, input.new_string, input.replace_all, { sessionId: input.sessionId, projectRoot: input.projectRoot });
|
|
1379
|
-
case 'apply_patch': return applyPatchTool(input.patch_text, { projectRoot: input.projectRoot });
|
|
1380
|
-
case 'multi_edit': return multiEditTool(input.file_path, input.edits, { projectRoot: input.projectRoot });
|
|
1363
|
+
case 'apply_patch': return applyPatchTool(input.patch_text, { sessionId: input.sessionId, projectRoot: input.projectRoot });
|
|
1364
|
+
case 'multi_edit': return multiEditTool(input.file_path, input.edits, { sessionId: input.sessionId, projectRoot: input.projectRoot });
|
|
1381
1365
|
default: return null; // not a local tool
|
|
1382
1366
|
}
|
|
1383
1367
|
}
|
|
@@ -1393,34 +1377,78 @@ async function editFile(filePath, oldString, newString, replaceAll = false, { se
|
|
|
1393
1377
|
if (oldString === newString) throw new Error('old_string and new_string must differ');
|
|
1394
1378
|
const resolved = resolveToolPath(filePath, projectRoot);
|
|
1395
1379
|
// Assert file unchanged since last read (stale edit prevention)
|
|
1380
|
+
let FileTracker = null;
|
|
1396
1381
|
if (sessionId) {
|
|
1397
|
-
|
|
1382
|
+
FileTracker = require('./file-tracker');
|
|
1398
1383
|
FileTracker.assertUnchanged(resolved, sessionId);
|
|
1399
1384
|
}
|
|
1400
1385
|
const content = fs.readFileSync(resolved, 'utf8');
|
|
1401
|
-
|
|
1386
|
+
let updated;
|
|
1387
|
+
try {
|
|
1388
|
+
updated = fuzzyReplace(content, oldString, newString, replaceAll);
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
if (isAlreadyAppliedEdit(content, oldString, newString)) {
|
|
1391
|
+
if (FileTracker) FileTracker.recordCurrent(resolved, sessionId);
|
|
1392
|
+
return { edited: false, unchanged: true, already_applied: true, path: resolved, bytes: Buffer.byteLength(content) };
|
|
1393
|
+
}
|
|
1394
|
+
throw err;
|
|
1395
|
+
}
|
|
1402
1396
|
// Defense in depth: replace() should throw on no-match, but if any future
|
|
1403
1397
|
// code path returns content unchanged, refuse to silently "succeed" with
|
|
1404
1398
|
// a no-op. The agent must see the failure and try again rather than
|
|
1405
1399
|
// believing the file was edited when it wasn't.
|
|
1406
1400
|
if (updated === content) {
|
|
1401
|
+
if (isAlreadyAppliedEdit(content, oldString, newString)) {
|
|
1402
|
+
if (FileTracker) FileTracker.recordCurrent(resolved, sessionId);
|
|
1403
|
+
return { edited: false, unchanged: true, already_applied: true, path: resolved, bytes: Buffer.byteLength(content) };
|
|
1404
|
+
}
|
|
1407
1405
|
throw new Error(
|
|
1408
1406
|
'edit_file no-op: applying old_string -> new_string left the file byte-identical. The match likely missed; provide more unique old_string context.'
|
|
1409
1407
|
);
|
|
1410
1408
|
}
|
|
1411
1409
|
fs.writeFileSync(resolved, updated, 'utf8');
|
|
1410
|
+
if (FileTracker) FileTracker.recordCurrent(resolved, sessionId);
|
|
1412
1411
|
return { edited: true, path: resolved, bytes: Buffer.byteLength(updated) };
|
|
1413
1412
|
}
|
|
1414
1413
|
|
|
1415
|
-
|
|
1414
|
+
function isAlreadyAppliedEdit(content, oldString, newString) {
|
|
1415
|
+
if (!newString || typeof newString !== 'string') return false;
|
|
1416
|
+
if (content.includes(oldString)) return false;
|
|
1417
|
+
return content.includes(newString);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
async function applyPatchTool(patchText, { sessionId, projectRoot } = {}) {
|
|
1416
1421
|
if (!patchText) throw new Error('patch_text is required');
|
|
1417
|
-
|
|
1422
|
+
const result = await applyPatch(patchText, { baseDir: projectRoot });
|
|
1423
|
+
if (sessionId) {
|
|
1424
|
+
const FileTracker = require('./file-tracker');
|
|
1425
|
+
const trackingWarnings = [];
|
|
1426
|
+
for (const filePath of [...(result.added || []), ...(result.modified || [])]) {
|
|
1427
|
+
try {
|
|
1428
|
+
FileTracker.recordCurrent(filePath, sessionId);
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
trackingWarnings.push(`Failed to refresh file tracker for ${filePath}: ${err.message}`);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
for (const filePath of result.deleted || []) {
|
|
1434
|
+
FileTracker.forgetFile(filePath, sessionId);
|
|
1435
|
+
}
|
|
1436
|
+
if (trackingWarnings.length > 0) result.tracking_warnings = trackingWarnings;
|
|
1437
|
+
}
|
|
1438
|
+
return result;
|
|
1418
1439
|
}
|
|
1419
1440
|
|
|
1420
|
-
async function multiEditTool(filePath, edits, { projectRoot } = {}) {
|
|
1441
|
+
async function multiEditTool(filePath, edits, { sessionId, projectRoot } = {}) {
|
|
1421
1442
|
if (!filePath || !edits || !Array.isArray(edits)) throw new Error('file_path and edits array are required');
|
|
1422
1443
|
const resolved = resolveToolPath(filePath, projectRoot);
|
|
1423
|
-
|
|
1444
|
+
let FileTracker = null;
|
|
1445
|
+
if (sessionId) {
|
|
1446
|
+
FileTracker = require('./file-tracker');
|
|
1447
|
+
FileTracker.assertUnchanged(resolved, sessionId);
|
|
1448
|
+
}
|
|
1449
|
+
const result = multiEdit(resolved, edits);
|
|
1450
|
+
if (FileTracker && result.edits_applied > 0) FileTracker.recordCurrent(resolved, sessionId);
|
|
1451
|
+
return result;
|
|
1424
1452
|
}
|
|
1425
1453
|
|
|
1426
1454
|
// ── Enhanced Glob (Phase 9b) ──
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const os = require('node:os');
|
|
10
|
+
const path = require('node:path');
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Session sharing — export coding sessions as JSON and import them back.
|
|
@@ -88,6 +91,8 @@ function exportSession(sessionId, brain) {
|
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
const transcript = _readWalleTranscriptMetadata(sessionId);
|
|
95
|
+
|
|
91
96
|
// Store the share token in kv_store for later verification
|
|
92
97
|
if (typeof brain.setKv === 'function') {
|
|
93
98
|
brain.setKv(`share:${sessionId}`, JSON.stringify({
|
|
@@ -103,6 +108,10 @@ function exportSession(sessionId, brain) {
|
|
|
103
108
|
plan,
|
|
104
109
|
subtasks,
|
|
105
110
|
events,
|
|
111
|
+
parts: transcript.parts,
|
|
112
|
+
compactions: transcript.compactions,
|
|
113
|
+
snapshots: transcript.snapshots,
|
|
114
|
+
transcriptPath: transcript.transcriptPath,
|
|
106
115
|
diffs,
|
|
107
116
|
createdAt,
|
|
108
117
|
exportedAt: new Date().toISOString(),
|
|
@@ -228,4 +237,62 @@ function _tableExists(db, tableName) {
|
|
|
228
237
|
}
|
|
229
238
|
}
|
|
230
239
|
|
|
231
|
-
|
|
240
|
+
function _readWalleTranscriptMetadata(sessionId) {
|
|
241
|
+
const transcriptPath = _findWalleTranscriptPath(sessionId);
|
|
242
|
+
if (!transcriptPath) return { transcriptPath: '', parts: [], compactions: [], snapshots: [] };
|
|
243
|
+
|
|
244
|
+
const parts = [];
|
|
245
|
+
let raw = '';
|
|
246
|
+
try { raw = fs.readFileSync(transcriptPath, 'utf8'); } catch { raw = ''; }
|
|
247
|
+
for (const line of raw.split('\n')) {
|
|
248
|
+
if (!line.trim()) continue;
|
|
249
|
+
let row;
|
|
250
|
+
try { row = JSON.parse(line); } catch { continue; }
|
|
251
|
+
if (row?.type !== 'walle_part') continue;
|
|
252
|
+
const part = {
|
|
253
|
+
uuid: row.uuid || '',
|
|
254
|
+
partType: row.partType || 'event',
|
|
255
|
+
timestamp: row.timestamp || '',
|
|
256
|
+
data: row.data || {},
|
|
257
|
+
};
|
|
258
|
+
parts.push(part);
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
transcriptPath,
|
|
262
|
+
parts,
|
|
263
|
+
compactions: parts.filter((part) => part.partType === 'compaction'),
|
|
264
|
+
snapshots: parts.filter((part) => part.partType === 'snapshot'),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _findWalleTranscriptPath(sessionId) {
|
|
269
|
+
if (!sessionId) return '';
|
|
270
|
+
for (const root of _candidateWalleSessionRoots()) {
|
|
271
|
+
const candidate = path.join(root, `${sessionId}.jsonl`);
|
|
272
|
+
try {
|
|
273
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
274
|
+
} catch { /* ignore inaccessible roots */ }
|
|
275
|
+
}
|
|
276
|
+
return '';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function _candidateWalleSessionRoots() {
|
|
280
|
+
const roots = [
|
|
281
|
+
process.env.WALLE_SESSIONS_DIR,
|
|
282
|
+
process.env.WALL_E_SESSIONS_DIR,
|
|
283
|
+
process.env.WALLE_DEV_DIR ? path.join(process.env.WALLE_DEV_DIR, 'sessions') : '',
|
|
284
|
+
process.env.WALL_E_DATA_DIR ? path.join(process.env.WALL_E_DATA_DIR, 'sessions') : '',
|
|
285
|
+
process.env.CTM_DATA_DIR ? path.join(process.env.CTM_DATA_DIR, 'sessions') : '',
|
|
286
|
+
path.join(os.homedir(), '.walle', 'sessions'),
|
|
287
|
+
].filter(Boolean);
|
|
288
|
+
return [...new Set(roots)];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = {
|
|
292
|
+
exportSession,
|
|
293
|
+
importSession,
|
|
294
|
+
verifyShareToken,
|
|
295
|
+
EXPORT_VERSION,
|
|
296
|
+
_readWalleTranscriptMetadata,
|
|
297
|
+
_findWalleTranscriptPath,
|
|
298
|
+
};
|
|
@@ -365,7 +365,7 @@ async function analyzeShellCommand(commandStr, cwd) {
|
|
|
365
365
|
const tree = Parser.parse(commandStr);
|
|
366
366
|
if (!tree) return result;
|
|
367
367
|
|
|
368
|
-
const { SHELL_ALLOWLIST, SHELL_DENYLIST } = require('./
|
|
368
|
+
const { SHELL_ALLOWLIST, SHELL_DENYLIST } = require('./shell-policy');
|
|
369
369
|
const commandNodes = collectCommandNodes(tree.rootNode);
|
|
370
370
|
|
|
371
371
|
for (const node of commandNodes) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Shell execution policy shared by the local tool runner and static analyzer.
|
|
4
|
+
// Keep this module dependency-free so shell analysis never imports local-tools.
|
|
5
|
+
|
|
6
|
+
const SHELL_ALLOWLIST = new Set([
|
|
7
|
+
// File ops
|
|
8
|
+
'ls', 'cat', 'head', 'tail', 'wc', 'find', 'file', 'stat', 'du', 'df',
|
|
9
|
+
'mkdir', 'cp', 'mv', 'touch', 'tee', 'readlink', 'realpath',
|
|
10
|
+
// Path utilities
|
|
11
|
+
'cd', 'pwd', 'basename', 'dirname', 'which', 'type', 'command',
|
|
12
|
+
// Search
|
|
13
|
+
'grep', 'rg', 'ag', 'fzf',
|
|
14
|
+
// Text processing
|
|
15
|
+
'sort', 'uniq', 'cut', 'tr', 'awk', 'sed', 'jq', 'yq', 'diff', 'comm',
|
|
16
|
+
'xargs', 'tee', 'fmt', 'column',
|
|
17
|
+
// Network
|
|
18
|
+
'curl', 'wget', 'ping', 'dig', 'host', 'nslookup',
|
|
19
|
+
// Dev tools
|
|
20
|
+
'git', 'node', 'npm', 'npx', 'python3', 'pip3', 'bun', 'deno',
|
|
21
|
+
'make', 'cargo', 'go', 'ruby', 'perl', 'tsc',
|
|
22
|
+
// Cloud / infra
|
|
23
|
+
'fly', 'docker', 'kubectl',
|
|
24
|
+
// System info
|
|
25
|
+
'date', 'echo', 'env', 'whoami', 'hostname', 'uname', 'uptime', 'ps', 'top',
|
|
26
|
+
'id', 'groups', 'printenv', 'locale', 'lsof',
|
|
27
|
+
// macOS specific
|
|
28
|
+
'open', 'pbcopy', 'pbpaste', 'say', 'defaults', 'mdfind', 'mdls',
|
|
29
|
+
'osascript', 'screencapture', 'sw_vers', 'system_profiler',
|
|
30
|
+
// Archive
|
|
31
|
+
'tar', 'zip', 'unzip', 'gzip', 'gunzip', 'xz', 'bzip2',
|
|
32
|
+
// Misc utilities
|
|
33
|
+
'less', 'more', 'true', 'false', 'yes', 'test', 'expr', 'seq', 'sleep',
|
|
34
|
+
'md5', 'shasum', 'base64', 'xxd',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// Commands that are NEVER allowed regardless of context.
|
|
38
|
+
const SHELL_DENYLIST = new Set([
|
|
39
|
+
'rm', 'rmdir', 'kill', 'killall', 'shutdown', 'reboot',
|
|
40
|
+
'sudo', 'su', 'chmod', 'chown', 'chgrp',
|
|
41
|
+
'dd', 'mkfs', 'fdisk', 'diskutil',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
SHELL_ALLOWLIST,
|
|
46
|
+
SHELL_DENYLIST,
|
|
47
|
+
};
|
|
@@ -171,6 +171,48 @@ class SnapshotManager {
|
|
|
171
171
|
return [...this.snapshots.keys()];
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Return current snapshot stack depths by file. SnapshotService uses this as
|
|
176
|
+
* a cheap boundary marker before a coding step starts.
|
|
177
|
+
*
|
|
178
|
+
* @returns {Record<string, number>}
|
|
179
|
+
*/
|
|
180
|
+
getStackDepths() {
|
|
181
|
+
const depths = {};
|
|
182
|
+
for (const [filePath, stack] of this.snapshots) {
|
|
183
|
+
depths[filePath] = stack.length;
|
|
184
|
+
}
|
|
185
|
+
return depths;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Restore files to the state captured immediately after the provided stack
|
|
190
|
+
* depths. If a file was first captured after the boundary, depth 0 restores
|
|
191
|
+
* the pre-boundary file content (or deletes it when it was new).
|
|
192
|
+
*
|
|
193
|
+
* @param {Record<string, number>} depths
|
|
194
|
+
* @returns {string[]} restored file paths
|
|
195
|
+
*/
|
|
196
|
+
restoreToStackDepths(depths = {}) {
|
|
197
|
+
const restored = [];
|
|
198
|
+
for (const [filePath, stack] of this.snapshots) {
|
|
199
|
+
const depth = Number.isFinite(depths[filePath]) ? depths[filePath] : 0;
|
|
200
|
+
if (stack.length <= depth) continue;
|
|
201
|
+
const targetIndex = Math.min(depth, stack.length - 1);
|
|
202
|
+
const { content } = stack[targetIndex];
|
|
203
|
+
if (content === null) {
|
|
204
|
+
try { fs.unlinkSync(filePath); } catch { /* already gone */ }
|
|
205
|
+
} else if (Buffer.isBuffer(content)) {
|
|
206
|
+
fs.writeFileSync(filePath, content);
|
|
207
|
+
} else {
|
|
208
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
209
|
+
}
|
|
210
|
+
stack.length = targetIndex + 1;
|
|
211
|
+
restored.push(filePath);
|
|
212
|
+
}
|
|
213
|
+
return restored;
|
|
214
|
+
}
|
|
215
|
+
|
|
174
216
|
/**
|
|
175
217
|
* Return a unified diff string between the last snapshot and the current file content.
|
|
176
218
|
* Uses a simple line-by-line diff algorithm (no external dependencies).
|
|
@@ -5,6 +5,17 @@ const path = require('path');
|
|
|
5
5
|
const { createHash } = require('crypto');
|
|
6
6
|
const { execSync } = require('child_process');
|
|
7
7
|
|
|
8
|
+
let claudeDesktopSessions = null;
|
|
9
|
+
function getClaudeDesktopSessions() {
|
|
10
|
+
if (claudeDesktopSessions) return claudeDesktopSessions;
|
|
11
|
+
try {
|
|
12
|
+
claudeDesktopSessions = require('../../claude-task-manager/lib/claude-desktop-sessions');
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return claudeDesktopSessions;
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
// --- Task type classification ---
|
|
9
20
|
|
|
10
21
|
function classifyTaskType(content) {
|
|
@@ -86,6 +97,46 @@ async function harvestClaudeCodeSessions(since) {
|
|
|
86
97
|
return samples;
|
|
87
98
|
}
|
|
88
99
|
|
|
100
|
+
// --- Claude Desktop Session Harvesting ---
|
|
101
|
+
|
|
102
|
+
async function harvestClaudeDesktopSessions(since) {
|
|
103
|
+
const reader = getClaudeDesktopSessions();
|
|
104
|
+
if (!reader) return [];
|
|
105
|
+
|
|
106
|
+
const sessions = reader.listSessions();
|
|
107
|
+
const samples = [];
|
|
108
|
+
|
|
109
|
+
for (const session of sessions) {
|
|
110
|
+
if (since && session.updatedAt && session.updatedAt <= since) continue;
|
|
111
|
+
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
112
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
113
|
+
const userMsg = messages[i];
|
|
114
|
+
const assistantMsg = messages[i + 1];
|
|
115
|
+
if (userMsg.role !== 'user' || assistantMsg.role !== 'assistant') continue;
|
|
116
|
+
const userContent = userMsg.text || '';
|
|
117
|
+
const assistantContent = assistantMsg.text || '';
|
|
118
|
+
if (!userContent || userContent.length < 20) continue;
|
|
119
|
+
if (!assistantContent || assistantContent.length < 20) continue;
|
|
120
|
+
|
|
121
|
+
samples.push({
|
|
122
|
+
id: contentHash('claude-desktop', `${session.uuid}:${i}:${userContent}`),
|
|
123
|
+
source: 'claude-desktop',
|
|
124
|
+
session_id: session.uuid,
|
|
125
|
+
timestamp: userMsg.timestamp || session.updatedAt || session.createdAt || new Date().toISOString(),
|
|
126
|
+
task_type: classifyTaskType(userContent),
|
|
127
|
+
prompt: userContent,
|
|
128
|
+
response: assistantContent,
|
|
129
|
+
tool_calls: [],
|
|
130
|
+
outcome: 'unknown',
|
|
131
|
+
outcome_signal: { git_committed: false, git_diff: null, task_status: null, user_corrected: false },
|
|
132
|
+
model: session.model || 'unknown',
|
|
133
|
+
quality_label: 0.5,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return samples;
|
|
138
|
+
}
|
|
139
|
+
|
|
89
140
|
// --- Codex Session Harvesting ---
|
|
90
141
|
|
|
91
142
|
async function harvestCodexSessions(since) {
|
|
@@ -144,12 +195,13 @@ async function harvestCodexSessions(since) {
|
|
|
144
195
|
|
|
145
196
|
// --- CTM Session Harvesting ---
|
|
146
197
|
|
|
147
|
-
async function harvestCtmSessions(since) {
|
|
148
|
-
const dataDir = process.env.WALL_E_DATA_DIR || path.join(process.env.HOME, '.walle', 'data');
|
|
198
|
+
async function harvestCtmSessions(since, dataDirOverride = null) {
|
|
199
|
+
const dataDir = dataDirOverride || process.env.WALL_E_DATA_DIR || path.join(process.env.HOME, '.walle', 'data');
|
|
149
200
|
const ctmDbPath = path.join(dataDir, 'task-manager.db');
|
|
150
201
|
if (!fs.existsSync(ctmDbPath)) return [];
|
|
151
202
|
|
|
152
|
-
|
|
203
|
+
let Database;
|
|
204
|
+
try { Database = require('better-sqlite3'); } catch { return []; }
|
|
153
205
|
let ctmDb;
|
|
154
206
|
try {
|
|
155
207
|
ctmDb = new Database(ctmDbPath, { readonly: true, fileMustExist: true });
|
|
@@ -405,12 +457,13 @@ async function runHarvest({ incremental = true, brain, dataDir } = {}) {
|
|
|
405
457
|
|
|
406
458
|
// Harvest from each source
|
|
407
459
|
const claudeSamples = await harvestClaudeCodeSessions(getSince('claude-code'));
|
|
460
|
+
const claudeDesktopSamples = await harvestClaudeDesktopSessions(getSince('claude-desktop'));
|
|
408
461
|
const codexSamples = await harvestCodexSessions(getSince('codex'));
|
|
409
462
|
const chatSamples = await harvestWalleChat(brain, getSince('walle-chat'));
|
|
410
463
|
const taskSamples = await harvestWalleTasks(brain, getSince('walle-task'));
|
|
411
|
-
const ctmSamples = await harvestCtmSessions(getSince('ctm-sessions'));
|
|
464
|
+
const ctmSamples = await harvestCtmSessions(getSince('ctm-sessions'), dataDir);
|
|
412
465
|
|
|
413
|
-
allSamples.push(...claudeSamples, ...codexSamples, ...chatSamples, ...taskSamples, ...ctmSamples);
|
|
466
|
+
allSamples.push(...claudeSamples, ...claudeDesktopSamples, ...codexSamples, ...chatSamples, ...taskSamples, ...ctmSamples);
|
|
414
467
|
|
|
415
468
|
// Deduplicate by content hash
|
|
416
469
|
const seen = new Set();
|
|
@@ -440,6 +493,9 @@ async function runHarvest({ incremental = true, brain, dataDir } = {}) {
|
|
|
440
493
|
if (claudeSamples.length > 0) {
|
|
441
494
|
brain.updateHarvestState('claude-code', { lastProcessedAt: now, totalHarvested: claudeSamples.length });
|
|
442
495
|
}
|
|
496
|
+
if (claudeDesktopSamples.length > 0) {
|
|
497
|
+
brain.updateHarvestState('claude-desktop', { lastProcessedAt: now, totalHarvested: claudeDesktopSamples.length });
|
|
498
|
+
}
|
|
443
499
|
if (codexSamples.length > 0) {
|
|
444
500
|
brain.updateHarvestState('codex', { lastProcessedAt: now, totalHarvested: codexSamples.length });
|
|
445
501
|
}
|
|
@@ -460,6 +516,7 @@ module.exports = {
|
|
|
460
516
|
classifyTaskType,
|
|
461
517
|
contentHash,
|
|
462
518
|
harvestClaudeCodeSessions,
|
|
519
|
+
harvestClaudeDesktopSessions,
|
|
463
520
|
harvestCodexSessions,
|
|
464
521
|
harvestCtmSessions,
|
|
465
522
|
harvestWalleChat,
|