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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { SnapshotManager } = require('../tools/snapshot');
|
|
5
|
+
const { newId } = require('./model-message');
|
|
6
|
+
|
|
7
|
+
const MUTATING_TOOLS = new Set(['write_file', 'edit_file', 'multi_edit', 'apply_patch']);
|
|
8
|
+
|
|
9
|
+
class SnapshotService {
|
|
10
|
+
constructor({ cwd = process.cwd(), snapshot = null, now } = {}) {
|
|
11
|
+
this.cwd = path.resolve(cwd);
|
|
12
|
+
this.snapshot = snapshot || new SnapshotManager(this.cwd);
|
|
13
|
+
this.now = now || (() => new Date().toISOString());
|
|
14
|
+
this.boundaries = new Map();
|
|
15
|
+
this.activeBoundaryId = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async captureStepStart({ sessionId = '', cwd = this.cwd, messageId = '' } = {}) {
|
|
19
|
+
this.cwd = path.resolve(cwd || this.cwd);
|
|
20
|
+
const boundaryId = newId('boundary');
|
|
21
|
+
const record = {
|
|
22
|
+
snapshotId: newId('snapshot'),
|
|
23
|
+
boundaryId,
|
|
24
|
+
kind: 'step_start',
|
|
25
|
+
sessionId,
|
|
26
|
+
cwd: this.cwd,
|
|
27
|
+
messageId,
|
|
28
|
+
timestamp: this.now(),
|
|
29
|
+
stackDepths: this.snapshot.getStackDepths ? this.snapshot.getStackDepths() : {},
|
|
30
|
+
capturedFiles: [],
|
|
31
|
+
};
|
|
32
|
+
this.boundaries.set(boundaryId, record);
|
|
33
|
+
this.activeBoundaryId = boundaryId;
|
|
34
|
+
return publicBoundary(record);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async captureToolInput({ toolName, input = {}, sessionId = '', cwd = this.cwd, messageId = '' } = {}) {
|
|
38
|
+
if (!MUTATING_TOOLS.has(toolName)) return null;
|
|
39
|
+
this.cwd = path.resolve(cwd || this.cwd);
|
|
40
|
+
const files = inferMutatingPaths(toolName, input, this.cwd);
|
|
41
|
+
if (files.length === 0) return null;
|
|
42
|
+
|
|
43
|
+
const captured = [];
|
|
44
|
+
for (const filePath of files) {
|
|
45
|
+
try {
|
|
46
|
+
if (this.snapshot.capture(filePath)) captured.push(filePath);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
captured.push({ filePath, error: err.message });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const boundary = this.boundaries.get(this.activeBoundaryId);
|
|
52
|
+
if (boundary) {
|
|
53
|
+
for (const file of captured) {
|
|
54
|
+
const filePath = typeof file === 'string' ? file : file.filePath;
|
|
55
|
+
if (filePath && !boundary.capturedFiles.includes(filePath)) boundary.capturedFiles.push(filePath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
snapshotId: newId('snapshot'),
|
|
61
|
+
boundaryId: this.activeBoundaryId,
|
|
62
|
+
kind: 'tool_pre_mutation',
|
|
63
|
+
sessionId,
|
|
64
|
+
cwd: this.cwd,
|
|
65
|
+
messageId,
|
|
66
|
+
toolName,
|
|
67
|
+
files: captured,
|
|
68
|
+
timestamp: this.now(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async captureStepFinish({ sessionId = '', cwd = this.cwd, messageId = '' } = {}) {
|
|
73
|
+
this.cwd = path.resolve(cwd || this.cwd);
|
|
74
|
+
const summary = this.snapshot.summary();
|
|
75
|
+
return {
|
|
76
|
+
snapshotId: newId('snapshot'),
|
|
77
|
+
boundaryId: this.activeBoundaryId,
|
|
78
|
+
kind: 'step_finish',
|
|
79
|
+
sessionId,
|
|
80
|
+
cwd: this.cwd,
|
|
81
|
+
messageId,
|
|
82
|
+
timestamp: this.now(),
|
|
83
|
+
files: summary.files,
|
|
84
|
+
totalAdditions: summary.totalAdditions,
|
|
85
|
+
totalDeletions: summary.totalDeletions,
|
|
86
|
+
diffs: summary.diffs,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
revertToBoundary(boundaryId = this.activeBoundaryId) {
|
|
91
|
+
const boundary = this.boundaries.get(boundaryId);
|
|
92
|
+
if (!boundary) throw new Error(`No snapshot boundary: ${boundaryId}`);
|
|
93
|
+
const restoredFiles = this.snapshot.restoreToStackDepths
|
|
94
|
+
? this.snapshot.restoreToStackDepths(boundary.stackDepths)
|
|
95
|
+
: fallbackRestoreAll(this.snapshot);
|
|
96
|
+
return {
|
|
97
|
+
boundaryId,
|
|
98
|
+
restoredFiles,
|
|
99
|
+
timestamp: this.now(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getBoundary(boundaryId = this.activeBoundaryId) {
|
|
104
|
+
const boundary = this.boundaries.get(boundaryId);
|
|
105
|
+
return boundary ? publicBoundary(boundary) : null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function inferMutatingPaths(toolName, input = {}, cwd = process.cwd()) {
|
|
110
|
+
if (!MUTATING_TOOLS.has(toolName)) return [];
|
|
111
|
+
if (toolName === 'apply_patch') return inferPatchPaths(input.patch_text || input.patch || '', cwd);
|
|
112
|
+
const raw = input.file_path || input.path;
|
|
113
|
+
if (!raw) return [];
|
|
114
|
+
return [resolveToolPath(raw, input.projectRoot || cwd)];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function inferPatchPaths(patchText, cwd = process.cwd()) {
|
|
118
|
+
const files = [];
|
|
119
|
+
for (const line of String(patchText || '').split('\n')) {
|
|
120
|
+
const match = line.match(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/);
|
|
121
|
+
if (!match) continue;
|
|
122
|
+
files.push(resolveToolPath(match[1].trim(), cwd));
|
|
123
|
+
}
|
|
124
|
+
return [...new Set(files)];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolveToolPath(filePath, root) {
|
|
128
|
+
if (path.isAbsolute(filePath)) return path.resolve(filePath);
|
|
129
|
+
return path.resolve(root || process.cwd(), filePath);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function publicBoundary(record) {
|
|
133
|
+
return {
|
|
134
|
+
snapshotId: record.snapshotId,
|
|
135
|
+
boundaryId: record.boundaryId,
|
|
136
|
+
kind: record.kind,
|
|
137
|
+
sessionId: record.sessionId,
|
|
138
|
+
cwd: record.cwd,
|
|
139
|
+
messageId: record.messageId,
|
|
140
|
+
timestamp: record.timestamp,
|
|
141
|
+
capturedFiles: [...record.capturedFiles],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function fallbackRestoreAll(snapshot) {
|
|
146
|
+
snapshot.revertAll();
|
|
147
|
+
return snapshot.files ? snapshot.files() : [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
SnapshotService,
|
|
152
|
+
inferMutatingPaths,
|
|
153
|
+
inferPatchPaths,
|
|
154
|
+
MUTATING_TOOLS,
|
|
155
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { EventEmitter } = require('node:events');
|
|
4
|
+
const {
|
|
5
|
+
newId,
|
|
6
|
+
textFromContent,
|
|
7
|
+
normalizeToolCall,
|
|
8
|
+
assistantMessageFromResponse,
|
|
9
|
+
toolResultMessage,
|
|
10
|
+
} = require('./model-message');
|
|
11
|
+
const { normalizeResponse, transformRequest, providerId } = require('./provider-transform');
|
|
12
|
+
|
|
13
|
+
async function* streamFromChat(provider, request) {
|
|
14
|
+
const response = normalizeResponse(await provider.chat(request), {
|
|
15
|
+
provider,
|
|
16
|
+
model: request.model,
|
|
17
|
+
});
|
|
18
|
+
const messageId = newId('assistant');
|
|
19
|
+
if (response.reasoningContent) {
|
|
20
|
+
yield { type: 'reasoning_delta', messageId, partId: newId('reasoning'), data: { text: response.reasoningContent } };
|
|
21
|
+
}
|
|
22
|
+
if (response.content) {
|
|
23
|
+
yield { type: 'text_delta', messageId, partId: newId('text'), data: { text: textFromContent(response.content) } };
|
|
24
|
+
}
|
|
25
|
+
for (const call of response.toolCalls || []) {
|
|
26
|
+
yield { type: 'tool_call', messageId, partId: newId('tool'), toolCallId: call.id, data: call };
|
|
27
|
+
}
|
|
28
|
+
yield {
|
|
29
|
+
type: 'finish',
|
|
30
|
+
messageId,
|
|
31
|
+
data: {
|
|
32
|
+
stopReason: response.stopReason,
|
|
33
|
+
usage: response.usage,
|
|
34
|
+
model: response.model,
|
|
35
|
+
provider: response.provider,
|
|
36
|
+
latencyMs: response.latencyMs,
|
|
37
|
+
response,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class StreamProcessor extends EventEmitter {
|
|
43
|
+
constructor({ provider, model, transcript, toolExecutor, snapshotService, permissionService, now } = {}) {
|
|
44
|
+
super();
|
|
45
|
+
if (!provider) throw new Error('provider is required');
|
|
46
|
+
this.provider = provider;
|
|
47
|
+
this.model = model;
|
|
48
|
+
this.transcript = transcript || null;
|
|
49
|
+
this.toolExecutor = toolExecutor || null;
|
|
50
|
+
this.snapshotService = snapshotService || null;
|
|
51
|
+
this.permissionService = permissionService || null;
|
|
52
|
+
this.now = now || (() => new Date().toISOString());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async runTurn({ sessionId, cwd, system, messages = [], tools = [], signal, maxTokens, temperature, ...requestOptions } = {}) {
|
|
56
|
+
const request = transformRequest({
|
|
57
|
+
...requestOptions,
|
|
58
|
+
provider: this.provider,
|
|
59
|
+
model: this.model,
|
|
60
|
+
system,
|
|
61
|
+
messages,
|
|
62
|
+
tools,
|
|
63
|
+
cwd,
|
|
64
|
+
maxTokens,
|
|
65
|
+
temperature,
|
|
66
|
+
signal,
|
|
67
|
+
});
|
|
68
|
+
const stream = typeof this.provider.chatStream === 'function'
|
|
69
|
+
? this.provider.chatStream(request)
|
|
70
|
+
: streamFromChat(this.provider, request);
|
|
71
|
+
|
|
72
|
+
const assistantMessageId = newId('assistant');
|
|
73
|
+
const state = {
|
|
74
|
+
sessionId,
|
|
75
|
+
cwd,
|
|
76
|
+
messageId: assistantMessageId,
|
|
77
|
+
provider: providerId(this.provider),
|
|
78
|
+
model: this.model,
|
|
79
|
+
text: '',
|
|
80
|
+
reasoning: '',
|
|
81
|
+
toolCalls: [],
|
|
82
|
+
toolResults: [],
|
|
83
|
+
usage: { input: 0, output: 0 },
|
|
84
|
+
stopReason: '',
|
|
85
|
+
status: 'running',
|
|
86
|
+
errors: [],
|
|
87
|
+
events: [],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await this._record(sessionId, cwd, 'step_start', {
|
|
91
|
+
messageId: assistantMessageId,
|
|
92
|
+
provider: state.provider,
|
|
93
|
+
model: state.model,
|
|
94
|
+
});
|
|
95
|
+
this._emit({ type: 'step_start', sessionId, messageId: assistantMessageId });
|
|
96
|
+
if (this.snapshotService?.captureStepStart) {
|
|
97
|
+
const snapshot = await this.snapshotService.captureStepStart({ sessionId, cwd, messageId: assistantMessageId });
|
|
98
|
+
if (snapshot) await this._record(sessionId, cwd, 'snapshot', snapshot);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
for await (const event of stream) {
|
|
103
|
+
await this._handleEvent(event, state, { sessionId, cwd, tools });
|
|
104
|
+
}
|
|
105
|
+
if (state.toolCalls.length > 0 && this.toolExecutor) {
|
|
106
|
+
for (const call of state.toolCalls) {
|
|
107
|
+
await this._runTool(call, state, { sessionId, cwd });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (this.snapshotService?.captureStepFinish) {
|
|
111
|
+
const snapshot = await this.snapshotService.captureStepFinish({ sessionId, cwd, messageId: assistantMessageId });
|
|
112
|
+
if (snapshot) await this._record(sessionId, cwd, 'snapshot', snapshot);
|
|
113
|
+
}
|
|
114
|
+
state.status = state.errors.length > 0 ? 'error' : 'finished';
|
|
115
|
+
} catch (err) {
|
|
116
|
+
state.status = 'error';
|
|
117
|
+
state.errors.push(err.message);
|
|
118
|
+
await this._record(sessionId, cwd, 'error', { message: err.message });
|
|
119
|
+
this._emit({ type: 'error', sessionId, messageId: assistantMessageId, error: err.message });
|
|
120
|
+
} finally {
|
|
121
|
+
await this._record(sessionId, cwd, 'step_finish', {
|
|
122
|
+
messageId: assistantMessageId,
|
|
123
|
+
status: state.status,
|
|
124
|
+
stopReason: state.stopReason,
|
|
125
|
+
usage: state.usage,
|
|
126
|
+
});
|
|
127
|
+
this._emit({ type: 'step_finish', sessionId, messageId: assistantMessageId, status: state.status });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
assistantMessage: assistantMessageFromResponse({
|
|
133
|
+
content: state.text,
|
|
134
|
+
reasoningContent: state.reasoning,
|
|
135
|
+
toolCalls: state.toolCalls,
|
|
136
|
+
}),
|
|
137
|
+
toolResultMessage: state.toolResults.length > 0 ? toolResultMessage(state.toolResults) : null,
|
|
138
|
+
next: state.status === 'error' ? 'stop' : state.toolResults.length > 0 ? 'continue' : 'stop',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async _handleEvent(event, state, ctx) {
|
|
143
|
+
if (!event || !event.type) return;
|
|
144
|
+
state.events.push(event);
|
|
145
|
+
this._emit({ ...event, sessionId: ctx.sessionId });
|
|
146
|
+
if (event.type === 'text_delta') {
|
|
147
|
+
const text = textFromContent(event.data?.text ?? event.text ?? event.delta ?? '');
|
|
148
|
+
if (!text) return;
|
|
149
|
+
state.text += text;
|
|
150
|
+
await this._record(ctx.sessionId, ctx.cwd, 'text', { text, messageId: state.messageId, partId: event.partId || newId('text') });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (event.type === 'reasoning_delta') {
|
|
154
|
+
const text = textFromContent(event.data?.text ?? event.text ?? event.delta ?? '');
|
|
155
|
+
if (!text) return;
|
|
156
|
+
state.reasoning += text;
|
|
157
|
+
await this._record(ctx.sessionId, ctx.cwd, 'reasoning', { text, messageId: state.messageId, partId: event.partId || newId('reasoning') });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (event.type === 'tool_input_delta') {
|
|
161
|
+
await this._record(ctx.sessionId, ctx.cwd, 'tool', {
|
|
162
|
+
state: 'input_delta',
|
|
163
|
+
toolCallId: event.toolCallId,
|
|
164
|
+
delta: event.data?.delta ?? event.delta ?? '',
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (event.type === 'tool_call') {
|
|
169
|
+
const call = normalizeToolCall(event.data || event);
|
|
170
|
+
state.toolCalls.push(call);
|
|
171
|
+
await this._record(ctx.sessionId, ctx.cwd, 'tool', {
|
|
172
|
+
state: 'pending',
|
|
173
|
+
toolCallId: call.id,
|
|
174
|
+
name: call.name,
|
|
175
|
+
input: call.input,
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (event.type === 'finish') {
|
|
180
|
+
const data = event.data || {};
|
|
181
|
+
state.stopReason = data.stopReason || data.response?.stopReason || '';
|
|
182
|
+
state.usage = data.usage || data.response?.usage || state.usage;
|
|
183
|
+
if (data.model) state.model = data.model;
|
|
184
|
+
if (data.provider) state.provider = data.provider;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async _runTool(call, state, { sessionId, cwd }) {
|
|
189
|
+
try {
|
|
190
|
+
if (this.permissionService?.authorize) {
|
|
191
|
+
await this._record(sessionId, cwd, 'tool', {
|
|
192
|
+
state: 'permission_check',
|
|
193
|
+
toolCallId: call.id,
|
|
194
|
+
name: call.name,
|
|
195
|
+
input: call.input,
|
|
196
|
+
});
|
|
197
|
+
const permission = await this.permissionService.authorize({
|
|
198
|
+
sessionId,
|
|
199
|
+
tool: call.name,
|
|
200
|
+
input: call.input,
|
|
201
|
+
cwd,
|
|
202
|
+
projectRoot: cwd,
|
|
203
|
+
metadata: { toolCallId: call.id },
|
|
204
|
+
});
|
|
205
|
+
if (permission.decision !== 'allow') {
|
|
206
|
+
throw new Error(`Permission denied: ${permission.reason || permission.message || permission.decision}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (this.snapshotService?.captureToolInput) {
|
|
210
|
+
const snapshot = await this.snapshotService.captureToolInput({
|
|
211
|
+
toolName: call.name,
|
|
212
|
+
input: call.input,
|
|
213
|
+
sessionId,
|
|
214
|
+
cwd,
|
|
215
|
+
messageId: state.messageId,
|
|
216
|
+
});
|
|
217
|
+
if (snapshot) await this._record(sessionId, cwd, 'snapshot', snapshot);
|
|
218
|
+
}
|
|
219
|
+
await this._record(sessionId, cwd, 'tool', {
|
|
220
|
+
state: 'running',
|
|
221
|
+
toolCallId: call.id,
|
|
222
|
+
name: call.name,
|
|
223
|
+
input: call.input,
|
|
224
|
+
});
|
|
225
|
+
const result = await this.toolExecutor(call, { sessionId, cwd, model: state.model, provider: state.provider });
|
|
226
|
+
state.toolResults.push({ toolCallId: call.id, name: call.name, result });
|
|
227
|
+
await this._record(sessionId, cwd, 'tool', {
|
|
228
|
+
state: 'completed',
|
|
229
|
+
toolCallId: call.id,
|
|
230
|
+
name: call.name,
|
|
231
|
+
result,
|
|
232
|
+
});
|
|
233
|
+
} catch (err) {
|
|
234
|
+
state.errors.push(err.message);
|
|
235
|
+
state.toolResults.push({ toolCallId: call.id, name: call.name, error: err.message });
|
|
236
|
+
await this._record(sessionId, cwd, 'tool', {
|
|
237
|
+
state: 'error',
|
|
238
|
+
toolCallId: call.id,
|
|
239
|
+
name: call.name,
|
|
240
|
+
error: err.message,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async _record(sessionId, cwd, partType, data) {
|
|
246
|
+
const payload = {
|
|
247
|
+
type: 'walle_part',
|
|
248
|
+
provider: 'walle',
|
|
249
|
+
sessionId,
|
|
250
|
+
cwd,
|
|
251
|
+
timestamp: this.now(),
|
|
252
|
+
partType,
|
|
253
|
+
data,
|
|
254
|
+
};
|
|
255
|
+
if (this.transcript?.appendPart) return this.transcript.appendPart(payload);
|
|
256
|
+
if (this.transcript?.append) return this.transcript.append(payload);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_emit(event) {
|
|
261
|
+
this.emit('event', event);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
StreamProcessor,
|
|
267
|
+
streamFromChat,
|
|
268
|
+
};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const { AgentCatalog } = require('./agent-catalog');
|
|
5
|
+
|
|
6
|
+
class TaskTool {
|
|
7
|
+
constructor({ agentCatalog = null, runner = null, agentRuntime = null, brain = null, transcript = null } = {}) {
|
|
8
|
+
this.agentCatalog = agentCatalog || new AgentCatalog();
|
|
9
|
+
this.runner = runner || null;
|
|
10
|
+
this.agentRuntime = agentRuntime || null;
|
|
11
|
+
this.brain = brain || null;
|
|
12
|
+
this.transcript = transcript || null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
definition() {
|
|
16
|
+
return {
|
|
17
|
+
name: 'task',
|
|
18
|
+
description: `Create or resume a child Wall-E coding task with a selected agent.\nAvailable agents:\n${this.agentCatalog.describeForTool()}`,
|
|
19
|
+
input_schema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
prompt: { type: 'string', description: 'Task prompt for the child agent' },
|
|
23
|
+
agent: { type: 'string', description: 'Agent id, such as build, plan, review, or explore' },
|
|
24
|
+
task_id: { type: 'string', description: 'Existing task id to resume' },
|
|
25
|
+
action: { type: 'string', enum: ['run', 'create', 'status', 'resume', 'cancel'], description: 'Lifecycle action. run is default; create/defer queues without running.' },
|
|
26
|
+
defer: { type: 'boolean', description: 'Create the child task but do not run it yet.' },
|
|
27
|
+
async: { type: 'boolean', description: 'Alias for defer.' },
|
|
28
|
+
resume: { type: 'boolean', description: 'Resume task_id instead of creating a new task' },
|
|
29
|
+
},
|
|
30
|
+
required: [],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async execute(args = {}, ctx = {}) {
|
|
36
|
+
const taskConfig = args.task_id ? this._loadTaskConfig(args.task_id) : {};
|
|
37
|
+
const agentId = args.agent || taskConfig.agent || 'build';
|
|
38
|
+
const agent = this.agentCatalog.get(agentId);
|
|
39
|
+
if (!agent) return { error: `Unknown agent: ${agentId}`, invalid: true };
|
|
40
|
+
const action = normalizeAction(args);
|
|
41
|
+
if (!VALID_ACTIONS.has(action)) return { error: `Unknown task action: ${action}`, invalid: true };
|
|
42
|
+
|
|
43
|
+
if (action === 'status') {
|
|
44
|
+
if (!args.task_id) return { error: 'task_id is required for status', invalid: true };
|
|
45
|
+
const task = this._loadTask(args.task_id);
|
|
46
|
+
await this._appendPart(ctx, {
|
|
47
|
+
task_id: args.task_id,
|
|
48
|
+
parent_id: ctx.sessionId || '',
|
|
49
|
+
agent: agentId,
|
|
50
|
+
state: 'status',
|
|
51
|
+
status: task?.status || 'unknown',
|
|
52
|
+
});
|
|
53
|
+
return { task_id: args.task_id, agent: agentId, status: task?.status || 'unknown', task: publicTask(task) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (action === 'cancel') {
|
|
57
|
+
if (!args.task_id) return { error: 'task_id is required for cancel', invalid: true };
|
|
58
|
+
this._cancelTask(args.task_id, args.reason || 'Canceled by parent agent');
|
|
59
|
+
const task = this._loadTask(args.task_id);
|
|
60
|
+
await this._appendPart(ctx, {
|
|
61
|
+
task_id: args.task_id,
|
|
62
|
+
parent_id: ctx.sessionId || '',
|
|
63
|
+
agent: agentId,
|
|
64
|
+
state: 'canceled',
|
|
65
|
+
reason: args.reason || '',
|
|
66
|
+
});
|
|
67
|
+
return { task_id: args.task_id, agent: agentId, status: task?.status || 'canceled', task: publicTask(task) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const taskId = args.task_id || this._createTask(args, agent, ctx);
|
|
71
|
+
const prompt = args.prompt || taskConfig.prompt || this._loadPrompt(taskId) || '';
|
|
72
|
+
if (!prompt) return { error: 'prompt is required for new task', task_id: taskId };
|
|
73
|
+
|
|
74
|
+
await this._appendPart(ctx, {
|
|
75
|
+
task_id: taskId,
|
|
76
|
+
parent_id: ctx.sessionId || '',
|
|
77
|
+
agent: agentId,
|
|
78
|
+
state: args.task_id || args.resume ? 'resumed' : 'created',
|
|
79
|
+
prompt,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (action === 'create' || args.defer || args.async) {
|
|
83
|
+
await this._appendPart(ctx, {
|
|
84
|
+
task_id: taskId,
|
|
85
|
+
parent_id: ctx.sessionId || '',
|
|
86
|
+
agent: agentId,
|
|
87
|
+
state: 'deferred',
|
|
88
|
+
prompt,
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
task_id: taskId,
|
|
92
|
+
agent: agentId,
|
|
93
|
+
status: this._loadTask(taskId)?.status || 'pending',
|
|
94
|
+
result: { success: true, deferred: true, output: 'Task created and deferred.' },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this._markRunning(taskId);
|
|
99
|
+
let result;
|
|
100
|
+
try {
|
|
101
|
+
if (this.agentRuntime) {
|
|
102
|
+
result = await this.agentRuntime.spawnAgentSession({
|
|
103
|
+
parentRunId: ctx.runId || ctx.sessionId || '',
|
|
104
|
+
runnerId: agent.runnerId || agent.id,
|
|
105
|
+
providerType: agent.providerType,
|
|
106
|
+
mode: agent.mode,
|
|
107
|
+
task: prompt,
|
|
108
|
+
cwd: ctx.cwd || '',
|
|
109
|
+
sessionId: args.session_id || args.agent_session_id,
|
|
110
|
+
persistent: Boolean(args.persistent),
|
|
111
|
+
cleanupPolicy: args.cleanup_policy,
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
result = this.runner
|
|
115
|
+
? await this.runner({ prompt, agent, taskId, ctx })
|
|
116
|
+
: { success: true, output: 'Task created but no runner is configured.', deferred: true };
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
result = { success: false, error: err.message };
|
|
120
|
+
}
|
|
121
|
+
if (!result || typeof result !== 'object') result = { success: true, output: String(result ?? '') };
|
|
122
|
+
|
|
123
|
+
this._updateTask(taskId, result);
|
|
124
|
+
await this._appendPart(ctx, {
|
|
125
|
+
task_id: taskId,
|
|
126
|
+
parent_id: ctx.sessionId || '',
|
|
127
|
+
agent: agentId,
|
|
128
|
+
state: result.success === false ? 'error' : 'completed',
|
|
129
|
+
result,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return { task_id: taskId, agent: agentId, result };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
_createTask(args, agent, ctx) {
|
|
136
|
+
if (!this.brain?.insertTask) return `task_${crypto.randomUUID().slice(0, 8)}`;
|
|
137
|
+
const inserted = this.brain.insertTask({
|
|
138
|
+
title: args.prompt ? args.prompt.slice(0, 80) : `Child task: ${agent.id}`,
|
|
139
|
+
description: args.prompt || '',
|
|
140
|
+
priority: 'normal',
|
|
141
|
+
type: 'once',
|
|
142
|
+
execution: 'skill',
|
|
143
|
+
skill: 'coding-agent',
|
|
144
|
+
skill_config: JSON.stringify({
|
|
145
|
+
parent_id: ctx.sessionId || '',
|
|
146
|
+
agent: agent.id,
|
|
147
|
+
mode: agent.mode,
|
|
148
|
+
prompt: args.prompt || '',
|
|
149
|
+
cwd: ctx.cwd || '',
|
|
150
|
+
}),
|
|
151
|
+
source: 'walle-task-tool',
|
|
152
|
+
source_ref: ctx.sessionId || '',
|
|
153
|
+
});
|
|
154
|
+
return inserted.id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_loadTaskConfig(taskId) {
|
|
158
|
+
if (!taskId || !this.brain?.getTask) return {};
|
|
159
|
+
const task = this.brain.getTask(taskId);
|
|
160
|
+
if (!task?.skill_config) return {};
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(task.skill_config) || {};
|
|
163
|
+
} catch {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_loadTask(taskId) {
|
|
169
|
+
if (!taskId || !this.brain?.getTask) return null;
|
|
170
|
+
return this.brain.getTask(taskId);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
_loadPrompt(taskId) {
|
|
174
|
+
if (!taskId || !this.brain?.getTask) return '';
|
|
175
|
+
const task = this.brain.getTask(taskId);
|
|
176
|
+
if (!task?.skill_config) return task?.description || '';
|
|
177
|
+
try {
|
|
178
|
+
return JSON.parse(task.skill_config).prompt || task.description || '';
|
|
179
|
+
} catch {
|
|
180
|
+
return task.description || '';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_markRunning(taskId) {
|
|
185
|
+
if (!taskId || !this.brain?.updateTask) return;
|
|
186
|
+
const updates = {
|
|
187
|
+
status: 'running',
|
|
188
|
+
started_at: new Date().toISOString(),
|
|
189
|
+
error: null,
|
|
190
|
+
};
|
|
191
|
+
this.brain.updateTask(taskId, updates);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_updateTask(taskId, result) {
|
|
195
|
+
if (!taskId || !this.brain?.updateTask) return;
|
|
196
|
+
const deferred = Boolean(result.deferred);
|
|
197
|
+
const failed = result.success === false || Boolean(result.error);
|
|
198
|
+
this.brain.updateTask(taskId, {
|
|
199
|
+
status: deferred ? 'pending' : failed ? 'failed' : 'completed',
|
|
200
|
+
result: JSON.stringify(result),
|
|
201
|
+
error: failed ? String(result.error || 'Task failed') : null,
|
|
202
|
+
completed_at: deferred ? null : new Date().toISOString(),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
_cancelTask(taskId, reason) {
|
|
207
|
+
if (!taskId || !this.brain?.updateTask) return;
|
|
208
|
+
this.brain.updateTask(taskId, {
|
|
209
|
+
status: 'canceled',
|
|
210
|
+
error: reason || null,
|
|
211
|
+
completed_at: new Date().toISOString(),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async _appendPart(ctx, data) {
|
|
216
|
+
const target = ctx.transcript || this.transcript;
|
|
217
|
+
if (target?.appendPart) {
|
|
218
|
+
await target.appendPart({ sessionId: ctx.sessionId || '', cwd: ctx.cwd || '', partType: 'subtask', data });
|
|
219
|
+
} else if (target?.append) {
|
|
220
|
+
await target.append({ type: 'walle_part', sessionId: ctx.sessionId || '', cwd: ctx.cwd || '', partType: 'subtask', data });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function normalizeAction(args = {}) {
|
|
226
|
+
if (args.action) return String(args.action).toLowerCase();
|
|
227
|
+
if (args.resume) return 'resume';
|
|
228
|
+
if (args.defer || args.async) return 'create';
|
|
229
|
+
return 'run';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const VALID_ACTIONS = new Set(['run', 'create', 'status', 'resume', 'cancel']);
|
|
233
|
+
|
|
234
|
+
function publicTask(task) {
|
|
235
|
+
if (!task) return null;
|
|
236
|
+
let result = task.result || null;
|
|
237
|
+
if (typeof result === 'string' && result.trim()) {
|
|
238
|
+
try {
|
|
239
|
+
result = JSON.parse(result);
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
id: task.id,
|
|
244
|
+
title: task.title || '',
|
|
245
|
+
status: task.status || '',
|
|
246
|
+
error: task.error || null,
|
|
247
|
+
started_at: task.started_at || null,
|
|
248
|
+
completed_at: task.completed_at || null,
|
|
249
|
+
result,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
TaskTool,
|
|
255
|
+
};
|