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.
Files changed (167) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/template/bin/dev.sh +7 -1
  4. package/template/bin/setup.js +53 -9
  5. package/template/bin/sync-images.js +53 -0
  6. package/template/builder-journal.md +17 -0
  7. package/template/claude-task-manager/api-prompts.js +98 -13
  8. package/template/claude-task-manager/api-reviews.js +82 -5
  9. package/template/claude-task-manager/db.js +32 -5
  10. package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
  11. package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
  12. package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
  13. package/template/claude-task-manager/lib/session-capture.js +421 -0
  14. package/template/claude-task-manager/lib/session-history.js +135 -15
  15. package/template/claude-task-manager/lib/session-jobs.js +10 -5
  16. package/template/claude-task-manager/lib/session-stream.js +87 -19
  17. package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
  18. package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
  19. package/template/claude-task-manager/lib/walle-session-context.js +61 -0
  20. package/template/claude-task-manager/lib/walle-transcript.js +176 -0
  21. package/template/claude-task-manager/public/css/setup.css +35 -8
  22. package/template/claude-task-manager/public/css/walle-session.css +56 -0
  23. package/template/claude-task-manager/public/css/walle.css +120 -0
  24. package/template/claude-task-manager/public/index.html +814 -181
  25. package/template/claude-task-manager/public/js/message-renderer.js +148 -19
  26. package/template/claude-task-manager/public/js/reviews.js +120 -62
  27. package/template/claude-task-manager/public/js/setup.js +75 -31
  28. package/template/claude-task-manager/public/js/stream-view.js +115 -55
  29. package/template/claude-task-manager/public/js/walle-session.js +84 -2
  30. package/template/claude-task-manager/public/js/walle.js +308 -54
  31. package/template/claude-task-manager/server.js +1092 -146
  32. package/template/claude-task-manager/session-integrity.js +181 -54
  33. package/template/claude-task-manager/session-utils.js +123 -41
  34. package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
  35. package/template/package.json +1 -1
  36. package/template/wall-e/adapters/ctm.js +39 -18
  37. package/template/wall-e/agent-runners/contract.js +17 -0
  38. package/template/wall-e/agent-runners/index.js +22 -0
  39. package/template/wall-e/agent-runtime/harness.js +212 -0
  40. package/template/wall-e/agent-runtime/index.js +8 -0
  41. package/template/wall-e/agent-runtime/registry.js +67 -0
  42. package/template/wall-e/agent-runtime/session-store.js +179 -0
  43. package/template/wall-e/agent-runtime/spawn.js +208 -0
  44. package/template/wall-e/api-walle.js +174 -7
  45. package/template/wall-e/brain.js +266 -28
  46. package/template/wall-e/channels/policy.js +88 -0
  47. package/template/wall-e/channels/registry.js +15 -1
  48. package/template/wall-e/channels/reply-dispatcher.js +70 -0
  49. package/template/wall-e/channels/session-bindings.js +51 -0
  50. package/template/wall-e/chat/code-review-context.js +29 -0
  51. package/template/wall-e/chat.js +188 -42
  52. package/template/wall-e/coding/acp-adapter.js +188 -0
  53. package/template/wall-e/coding/agent-catalog.js +129 -0
  54. package/template/wall-e/coding/compaction-service.js +247 -0
  55. package/template/wall-e/coding/execution-trace.js +3 -0
  56. package/template/wall-e/coding/instruction-service.js +224 -0
  57. package/template/wall-e/coding/model-message.js +67 -0
  58. package/template/wall-e/coding/permission-rules-store.js +111 -0
  59. package/template/wall-e/coding/permission-service.js +266 -0
  60. package/template/wall-e/coding/prompt-bundle.js +67 -0
  61. package/template/wall-e/coding/prompt-runtime.js +243 -0
  62. package/template/wall-e/coding/provider-transform.js +188 -0
  63. package/template/wall-e/coding/runtime-mode.js +132 -0
  64. package/template/wall-e/coding/snapshot-service.js +155 -0
  65. package/template/wall-e/coding/stream-processor.js +268 -0
  66. package/template/wall-e/coding/task-tool.js +255 -0
  67. package/template/wall-e/coding/tool-registry.js +361 -0
  68. package/template/wall-e/coding/transcript-writer.js +143 -0
  69. package/template/wall-e/coding/workspace-replay.js +324 -0
  70. package/template/wall-e/coding-context.js +4 -22
  71. package/template/wall-e/coding-orchestrator.js +307 -18
  72. package/template/wall-e/coding-prompts.js +44 -3
  73. package/template/wall-e/context/context-builder.js +43 -1
  74. package/template/wall-e/context/topic-matcher.js +1 -1
  75. package/template/wall-e/eval/agent-runner.js +59 -13
  76. package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
  77. package/template/wall-e/eval/benchmarks.js +100 -16
  78. package/template/wall-e/eval/eval-orchestrator.js +218 -8
  79. package/template/wall-e/eval/harvester.js +62 -5
  80. package/template/wall-e/eval/head-to-head.js +23 -2
  81. package/template/wall-e/eval/humaneval-adapter.js +30 -5
  82. package/template/wall-e/eval/livecodebench-adapter.js +29 -5
  83. package/template/wall-e/eval/manifest.js +186 -0
  84. package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
  85. package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
  86. package/template/wall-e/eval/session-transcripts.js +57 -4
  87. package/template/wall-e/eval/swebench-adapter.js +109 -3
  88. package/template/wall-e/evaluation/agent-router.js +53 -1
  89. package/template/wall-e/evaluation/coding-quorum.js +48 -1
  90. package/template/wall-e/evaluation/router.js +4 -2
  91. package/template/wall-e/evaluation/tier-selector.js +11 -1
  92. package/template/wall-e/extraction/contradiction.js +2 -2
  93. package/template/wall-e/extraction/indexer.js +2 -1
  94. package/template/wall-e/extraction/knowledge-extractor.js +2 -2
  95. package/template/wall-e/hooks/cli.js +92 -0
  96. package/template/wall-e/hooks/discovery.js +119 -0
  97. package/template/wall-e/hooks/index.js +7 -0
  98. package/template/wall-e/hooks/manifest.js +55 -0
  99. package/template/wall-e/hooks/runtime.js +84 -0
  100. package/template/wall-e/hooks/session-memory.js +225 -0
  101. package/template/wall-e/http/auth.js +6 -2
  102. package/template/wall-e/http/chat-api.js +54 -8
  103. package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
  104. package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
  105. package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
  106. package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
  107. package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
  108. package/template/wall-e/listening/calendar.js +3 -1
  109. package/template/wall-e/llm/client.js +64 -10
  110. package/template/wall-e/llm/google.js +39 -5
  111. package/template/wall-e/llm/ollama.js +1 -1
  112. package/template/wall-e/llm/ollama.plugin.json +1 -1
  113. package/template/wall-e/llm/provider-availability.js +10 -0
  114. package/template/wall-e/llm/provider-error.js +269 -0
  115. package/template/wall-e/llm/tool-adapter.js +48 -12
  116. package/template/wall-e/loops/boot.js +2 -1
  117. package/template/wall-e/loops/initiative.js +2 -2
  118. package/template/wall-e/loops/tasks.js +8 -47
  119. package/template/wall-e/loops/workspace-prompts.js +20 -0
  120. package/template/wall-e/mcp-server.js +442 -1
  121. package/template/wall-e/memory/session-ingest-service.js +159 -0
  122. package/template/wall-e/memory/source-indexer.js +289 -0
  123. package/template/wall-e/plugins/discovery.js +83 -0
  124. package/template/wall-e/plugins/manifest-loader.js +50 -10
  125. package/template/wall-e/plugins/manifest-schema.js +69 -0
  126. package/template/wall-e/plugins/model-catalog.js +55 -0
  127. package/template/wall-e/prompts/coding/base.txt +2 -0
  128. package/template/wall-e/prompts/coding/deepseek.txt +1 -0
  129. package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
  130. package/template/wall-e/prompts/coding/plan.txt +1 -0
  131. package/template/wall-e/runtime/execution-trace.js +220 -0
  132. package/template/wall-e/security/audit.js +266 -0
  133. package/template/wall-e/security/ssrf.js +236 -0
  134. package/template/wall-e/session-files.js +303 -0
  135. package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
  136. package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
  137. package/template/wall-e/skills/internal-skill-registry.js +2 -2
  138. package/template/wall-e/skills/script-skill-runner.js +143 -0
  139. package/template/wall-e/skills/skill-executor.js +5 -6
  140. package/template/wall-e/skills/skill-fallback.js +3 -1
  141. package/template/wall-e/skills/skill-harness-registry.js +7 -8
  142. package/template/wall-e/skills/skill-planner.js +52 -4
  143. package/template/wall-e/skills/slack-ingest.js +11 -3
  144. package/template/wall-e/sources/base.js +90 -0
  145. package/template/wall-e/sources/builtin.js +33 -0
  146. package/template/wall-e/sources/claude-code-jsonl.js +78 -0
  147. package/template/wall-e/sources/codex-jsonl.js +125 -0
  148. package/template/wall-e/sources/coding-session-utils.js +117 -0
  149. package/template/wall-e/sources/contract-suite.js +59 -0
  150. package/template/wall-e/sources/gemini-jsonl.js +85 -0
  151. package/template/wall-e/sources/index.js +9 -0
  152. package/template/wall-e/sources/jsonl-utils.js +181 -0
  153. package/template/wall-e/sources/record-types.js +252 -0
  154. package/template/wall-e/sources/registry.js +92 -0
  155. package/template/wall-e/sources/transforms.js +100 -0
  156. package/template/wall-e/sources/walle-jsonl.js +108 -0
  157. package/template/wall-e/tools/coding-middleware.js +31 -1
  158. package/template/wall-e/tools/file-tracker.js +25 -1
  159. package/template/wall-e/tools/local-tools.js +75 -47
  160. package/template/wall-e/tools/session-sharing.js +68 -1
  161. package/template/wall-e/tools/shell-analyzer.js +1 -1
  162. package/template/wall-e/tools/shell-policy.js +47 -0
  163. package/template/wall-e/tools/snapshot.js +42 -0
  164. package/template/wall-e/training/harvester.js +62 -5
  165. package/template/wall-e/utils/repair.js +253 -1
  166. package/template/website/index.html +3 -3
  167. 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
+ };