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,67 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const {
6
+ buildChildScopeSection,
7
+ buildPromptEnvelope,
8
+ buildReviewerEnvelope,
9
+ createPromptSection,
10
+ redactPromptContent,
11
+ renderPromptEnvelope,
12
+ } = require('./prompt-runtime');
13
+
14
+ const PROMPT_DIR = path.join(__dirname, '..', 'prompts', 'coding');
15
+
16
+ function loadPromptText(name) {
17
+ try {
18
+ return fs.readFileSync(path.join(PROMPT_DIR, name), 'utf8').trim();
19
+ } catch {
20
+ return '';
21
+ }
22
+ }
23
+
24
+ function selectPromptFiles({ mode = 'build', provider = '', model = '' } = {}) {
25
+ const files = ['base.txt'];
26
+ if (mode === 'plan') files.push('plan.txt');
27
+ if (/deepseek/i.test(`${provider} ${model}`)) files.push('deepseek.txt');
28
+ return files;
29
+ }
30
+
31
+ function buildPromptBundle({ body = '', mode = 'build', provider = '', model = '', extraInstructions = [], returnEnvelope = false } = {}) {
32
+ const bundleText = selectPromptFiles({ mode, provider, model })
33
+ .map(loadPromptText)
34
+ .filter(Boolean)
35
+ .join('\n\n');
36
+ const extras = (extraInstructions || [])
37
+ .filter((item) => item?.content)
38
+ .map((item) => createPromptSection({
39
+ id: `instruction:${item.path || 'inline'}`,
40
+ source: item.path || 'extra-instructions',
41
+ content: `## Instruction: ${item.path}\n${item.content}`,
42
+ cacheable: true,
43
+ redactionPolicy: 'secrets',
44
+ }));
45
+
46
+ const envelope = buildPromptEnvelope({
47
+ stablePolicy: bundleText,
48
+ toolPolicy: body,
49
+ sections: extras,
50
+ metadata: { mode, provider, model },
51
+ });
52
+
53
+ return returnEnvelope ? envelope : renderPromptEnvelope(envelope);
54
+ }
55
+
56
+ module.exports = {
57
+ PROMPT_DIR,
58
+ loadPromptText,
59
+ selectPromptFiles,
60
+ buildPromptBundle,
61
+ buildChildScopeSection,
62
+ buildPromptEnvelope,
63
+ buildReviewerEnvelope,
64
+ createPromptSection,
65
+ redactPromptContent,
66
+ renderPromptEnvelope,
67
+ };
@@ -0,0 +1,243 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_RECIPIENT = 'agent';
4
+
5
+ function estimateTokens(content) {
6
+ const text = String(content || '');
7
+ if (!text) return 0;
8
+ return Math.ceil(text.length / 4);
9
+ }
10
+
11
+ function createPromptSection({
12
+ id,
13
+ source = 'runtime',
14
+ content = '',
15
+ recipient = DEFAULT_RECIPIENT,
16
+ cacheable = true,
17
+ visibility = 'llm',
18
+ mutableByHooks = true,
19
+ tokenEstimate = null,
20
+ redactionPolicy = 'none',
21
+ metadata = {},
22
+ } = {}) {
23
+ if (!id) throw new Error('Prompt section id is required');
24
+ const text = String(content || '');
25
+ return {
26
+ id,
27
+ source,
28
+ content: text,
29
+ recipient,
30
+ cacheable: Boolean(cacheable),
31
+ visibility,
32
+ mutableByHooks: Boolean(mutableByHooks),
33
+ tokenEstimate: tokenEstimate != null && Number.isFinite(Number(tokenEstimate))
34
+ ? Number(tokenEstimate)
35
+ : estimateTokens(text),
36
+ redactionPolicy,
37
+ metadata: { ...metadata },
38
+ };
39
+ }
40
+
41
+ function redactPromptContent(content, policy = 'none') {
42
+ const text = String(content || '');
43
+ if (policy === 'none') return text;
44
+ let redacted = text;
45
+ if (policy === 'secrets' || policy === 'standard') {
46
+ redacted = redacted
47
+ .replace(/\b(sk-[A-Za-z0-9_-]{8,})\b/g, '[REDACTED_API_KEY]')
48
+ .replace(/\b(AKIA[0-9A-Z]{16})\b/g, '[REDACTED_AWS_KEY]')
49
+ .replace(/(token|secret|password)\s*[:=]\s*["'][^"']+["']/gi, '$1="[REDACTED]"');
50
+ }
51
+ if (policy === 'standard') {
52
+ redacted = redacted.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[REDACTED_EMAIL]');
53
+ }
54
+ return redacted;
55
+ }
56
+
57
+ function normalizePromptSection(section) {
58
+ if (!section) return null;
59
+ if (typeof section === 'string') {
60
+ return createPromptSection({ id: `inline-${hashString(section)}`, content: section });
61
+ }
62
+ return createPromptSection(section);
63
+ }
64
+
65
+ function buildPromptEnvelope({
66
+ recipient = DEFAULT_RECIPIENT,
67
+ stablePolicy = '',
68
+ workspacePersona = '',
69
+ projectContext = '',
70
+ toolPolicy = '',
71
+ providerContribution = '',
72
+ channelContext = '',
73
+ parentRunContext = '',
74
+ childScope = '',
75
+ runtimeOnlyContext = '',
76
+ nextTurnContext = '',
77
+ userTask = '',
78
+ sections = [],
79
+ metadata = {},
80
+ } = {}) {
81
+ const built = [];
82
+ addSection(built, {
83
+ id: 'stable-policy',
84
+ source: 'prompt-runtime',
85
+ content: stablePolicy,
86
+ recipient,
87
+ cacheable: true,
88
+ mutableByHooks: false,
89
+ });
90
+ addSection(built, {
91
+ id: 'workspace-persona',
92
+ source: 'workspace',
93
+ content: workspacePersona,
94
+ recipient,
95
+ cacheable: true,
96
+ redactionPolicy: 'standard',
97
+ });
98
+ addSection(built, {
99
+ id: 'project-context',
100
+ source: 'project',
101
+ content: projectContext,
102
+ recipient,
103
+ cacheable: true,
104
+ });
105
+ addSection(built, {
106
+ id: 'tool-policy',
107
+ source: 'tools',
108
+ content: toolPolicy,
109
+ recipient,
110
+ cacheable: true,
111
+ });
112
+ addSection(built, {
113
+ id: 'provider-contribution',
114
+ source: 'provider',
115
+ content: providerContribution,
116
+ recipient,
117
+ cacheable: true,
118
+ });
119
+ addSection(built, {
120
+ id: 'channel-context',
121
+ source: 'channel',
122
+ content: channelContext,
123
+ recipient,
124
+ cacheable: false,
125
+ redactionPolicy: 'standard',
126
+ });
127
+ addSection(built, {
128
+ id: 'parent-run-context',
129
+ source: 'runtime',
130
+ content: parentRunContext,
131
+ recipient,
132
+ cacheable: false,
133
+ });
134
+ addSection(built, {
135
+ id: 'child-scope',
136
+ source: 'runtime',
137
+ content: childScope,
138
+ recipient,
139
+ cacheable: false,
140
+ });
141
+ addSection(built, {
142
+ id: 'runtime-only-context',
143
+ source: 'runtime',
144
+ content: runtimeOnlyContext,
145
+ recipient,
146
+ cacheable: false,
147
+ visibility: 'runtime-only',
148
+ });
149
+ addSection(built, {
150
+ id: 'next-turn-context',
151
+ source: 'runtime',
152
+ content: nextTurnContext,
153
+ recipient,
154
+ cacheable: false,
155
+ });
156
+ addSection(built, {
157
+ id: 'user-task',
158
+ source: 'user',
159
+ content: userTask,
160
+ recipient,
161
+ cacheable: false,
162
+ mutableByHooks: false,
163
+ redactionPolicy: 'standard',
164
+ });
165
+ for (const section of sections || []) {
166
+ const normalized = normalizePromptSection(section);
167
+ if (normalized && normalized.content) built.push(normalized);
168
+ }
169
+ return {
170
+ version: 1,
171
+ recipient,
172
+ sections: built,
173
+ metadata: { ...metadata },
174
+ tokenEstimate: built.reduce((sum, section) => sum + section.tokenEstimate, 0),
175
+ };
176
+ }
177
+
178
+ function addSection(target, section) {
179
+ const normalized = normalizePromptSection(section);
180
+ if (normalized && normalized.content) target.push(normalized);
181
+ }
182
+
183
+ function renderPromptEnvelope(envelope, { includeMetadata = false, includeRuntimeOnly = true } = {}) {
184
+ const sections = (envelope?.sections || [])
185
+ .filter(section => includeRuntimeOnly || section.visibility !== 'runtime-only')
186
+ .map((section) => {
187
+ const content = redactPromptContent(section.content, section.redactionPolicy);
188
+ if (!includeMetadata) return content;
189
+ const cache = section.cacheable ? 'cacheable' : 'dynamic';
190
+ return `<!-- prompt-section:${section.id} source:${section.source} ${cache} -->\n${content}`;
191
+ })
192
+ .filter(Boolean);
193
+ return sections.join('\n\n');
194
+ }
195
+
196
+ function buildChildScopeSection({ writeScope = [], mode = 'build', parentRunId = '', instructions = '' } = {}) {
197
+ const scopes = Array.isArray(writeScope) ? writeScope : (writeScope ? [writeScope] : []);
198
+ return createPromptSection({
199
+ id: 'child-scope',
200
+ source: 'agent-runtime',
201
+ cacheable: false,
202
+ content: [
203
+ `Child mode: ${mode}`,
204
+ parentRunId ? `Parent run: ${parentRunId}` : '',
205
+ scopes.length ? `Write scope: ${scopes.join(', ')}` : '',
206
+ instructions,
207
+ ].filter(Boolean).join('\n'),
208
+ });
209
+ }
210
+
211
+ function buildReviewerEnvelope({ diff = '', tests = '', context = '', builder = '', instructions = '' } = {}) {
212
+ return buildPromptEnvelope({
213
+ recipient: 'reviewer',
214
+ stablePolicy: 'You are reviewing coding work. Identify correctness, safety, and maintainability issues. Prefer actionable findings over style comments.',
215
+ parentRunContext: builder ? `Builder: ${builder}` : '',
216
+ runtimeOnlyContext: context,
217
+ userTask: [
218
+ instructions,
219
+ diff ? `Diff:\n${diff}` : '',
220
+ tests ? `Tests:\n${tests}` : '',
221
+ ].filter(Boolean).join('\n\n'),
222
+ });
223
+ }
224
+
225
+ function hashString(text) {
226
+ let hash = 0;
227
+ const input = String(text || '');
228
+ for (let i = 0; i < input.length; i += 1) {
229
+ hash = ((hash << 5) - hash + input.charCodeAt(i)) | 0;
230
+ }
231
+ return Math.abs(hash).toString(36);
232
+ }
233
+
234
+ module.exports = {
235
+ buildChildScopeSection,
236
+ buildPromptEnvelope,
237
+ buildReviewerEnvelope,
238
+ createPromptSection,
239
+ estimateTokens,
240
+ normalizePromptSection,
241
+ redactPromptContent,
242
+ renderPromptEnvelope,
243
+ };
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ const { textFromContent, normalizeToolCall } = require('./model-message');
4
+
5
+ function providerId(provider) {
6
+ if (!provider) return 'unknown';
7
+ if (typeof provider === 'string') return provider;
8
+ return provider.type || provider.provider || provider.id || 'unknown';
9
+ }
10
+
11
+ function modelId(model, provider) {
12
+ return model || provider?.model || '';
13
+ }
14
+
15
+ function isOpenAIStyle(provider) {
16
+ return ['openai', 'deepseek', 'lmstudio', 'ollama', 'mlx'].includes(providerId(provider));
17
+ }
18
+
19
+ function isAnthropicStyle(provider) {
20
+ return ['anthropic', 'claude-cli'].includes(providerId(provider));
21
+ }
22
+
23
+ function normalizeMessagesForProvider(messages = [], { provider, model } = {}) {
24
+ const pid = providerId(provider);
25
+ const out = [];
26
+ let lastRole = null;
27
+
28
+ for (const raw of messages || []) {
29
+ if (!raw || !raw.role) continue;
30
+ const msg = { ...raw };
31
+ if (msg.content == null) msg.content = '';
32
+
33
+ // Some OpenAI-compatible providers reject empty text messages.
34
+ if (isOpenAIStyle(pid) && textFromContent(msg.content).trim() === '' && !hasToolPayload(msg)) {
35
+ msg.content = '[empty]';
36
+ }
37
+
38
+ // Anthropic rejects consecutive user/tool-result formatting less often than
39
+ // OpenAI-style providers, but merging same-role plain text keeps history
40
+ // smaller and deterministic for every provider.
41
+ if (lastRole === msg.role && canMergeMessages(out[out.length - 1], msg)) {
42
+ out[out.length - 1].content = mergeContent(out[out.length - 1].content, msg.content);
43
+ continue;
44
+ }
45
+
46
+ // DeepSeek thinking mode cannot currently continue a tool loop with
47
+ // reasoning_content carried in prior assistant messages. Strip reasoning
48
+ // blocks when the history already contains tools; the final visible answer
49
+ // stays intact.
50
+ if (pid === 'deepseek' && hasToolHistory(messages) && hasToolPayload(msg) && Array.isArray(msg.content)) {
51
+ msg.content = msg.content.filter((part) => part?.type !== 'reasoning');
52
+ }
53
+
54
+ out.push(msg);
55
+ lastRole = msg.role;
56
+ }
57
+
58
+ if (isOpenAIStyle(pid)) {
59
+ ensureToolResultOrdering(out);
60
+ }
61
+
62
+ return out;
63
+ }
64
+
65
+ function normalizeToolsForProvider(tools = [], { provider, allowSyntheticNoop = false } = {}) {
66
+ const pid = providerId(provider);
67
+ const normalized = [];
68
+ const seen = new Set();
69
+ for (const tool of tools || []) {
70
+ if (!tool || !tool.name || seen.has(tool.name)) continue;
71
+ seen.add(tool.name);
72
+ const copy = { ...tool };
73
+ if (pid === 'anthropic' && copy.parameters && !copy.input_schema) {
74
+ copy.input_schema = copy.parameters;
75
+ delete copy.parameters;
76
+ }
77
+ if (isOpenAIStyle(pid) && copy.input_schema && !copy.parameters) {
78
+ copy.parameters = copy.input_schema;
79
+ }
80
+ normalized.push(copy);
81
+ }
82
+
83
+ // OpenAI-compatible histories with prior tool calls can error if the next
84
+ // request has no tools. A harmless no-op tool preserves the tool-capable shape.
85
+ if (isOpenAIStyle(pid) && normalized.length === 0 && allowSyntheticNoop) {
86
+ normalized.push({
87
+ name: 'noop',
88
+ description: 'No operation. Use only when no tool action is required.',
89
+ input_schema: { type: 'object', properties: {} },
90
+ parameters: { type: 'object', properties: {} },
91
+ execute: async () => ({ ok: true }),
92
+ _synthetic: true,
93
+ });
94
+ }
95
+ return normalized;
96
+ }
97
+
98
+ function normalizeResponse(response = {}, { provider, model } = {}) {
99
+ const toolCalls = (response.toolCalls || []).map(normalizeToolCall);
100
+ return {
101
+ content: response.content || '',
102
+ reasoningContent: response.reasoningContent || '',
103
+ toolCalls,
104
+ stopReason: response.stopReason || (toolCalls.length > 0 ? 'tool_use' : 'end_turn'),
105
+ usage: response.usage || { input: 0, output: 0 },
106
+ model: response.model || modelId(model, provider),
107
+ provider: response.provider || providerId(provider),
108
+ latencyMs: response.latencyMs || 0,
109
+ raw: response.raw,
110
+ };
111
+ }
112
+
113
+ function transformRequest({ provider, model, messages, tools, system, ...rest } = {}) {
114
+ const normalizedMessages = normalizeMessagesForProvider(messages, { provider, model });
115
+ return {
116
+ ...rest,
117
+ provider: providerId(provider),
118
+ model,
119
+ system,
120
+ messages: normalizedMessages,
121
+ tools: normalizeToolsForProvider(tools, {
122
+ provider,
123
+ model,
124
+ allowSyntheticNoop: hasToolHistory(normalizedMessages),
125
+ }),
126
+ };
127
+ }
128
+
129
+ function hasToolPayload(msg) {
130
+ if (Array.isArray(msg?.tool_calls) && msg.tool_calls.length > 0) return true;
131
+ if (!Array.isArray(msg?.content)) return false;
132
+ return msg.content.some((part) => part?.type === 'tool_use' || part?.type === 'tool_result');
133
+ }
134
+
135
+ function hasToolHistory(messages = []) {
136
+ return messages.some((msg) => msg?.role === 'tool' || hasToolPayload(msg));
137
+ }
138
+
139
+ function canMergeMessages(left, right) {
140
+ if (!left || !right) return false;
141
+ return typeof left.content === 'string' && typeof right.content === 'string';
142
+ }
143
+
144
+ function mergeContent(left, right) {
145
+ const a = textFromContent(left);
146
+ const b = textFromContent(right);
147
+ if (!a) return b;
148
+ if (!b) return a;
149
+ return `${a}\n\n${b}`;
150
+ }
151
+
152
+ function ensureToolResultOrdering(messages) {
153
+ // Convert Anthropic-style tool_result user content into OpenAI-style tool
154
+ // messages only when the provider adapter has not already done it. This is a
155
+ // conservative transform used by tests and future stream paths; existing
156
+ // provider adapters may still do richer conversion.
157
+ for (let i = 0; i < messages.length; i++) {
158
+ const msg = messages[i];
159
+ if (msg.role !== 'user' || !Array.isArray(msg.content)) continue;
160
+ const toolResults = msg.content.filter((part) => part?.type === 'tool_result');
161
+ if (toolResults.length === 0) continue;
162
+ const textParts = msg.content.filter((part) => part?.type !== 'tool_result');
163
+ const replacements = toolResults.map((part) => ({
164
+ role: 'tool',
165
+ tool_call_id: part.tool_use_id,
166
+ content: textFromContent(part.content),
167
+ }));
168
+ if (textParts.length > 0) {
169
+ msg.content = textParts;
170
+ messages.splice(i + 1, 0, ...replacements);
171
+ i += replacements.length;
172
+ } else {
173
+ messages.splice(i, 1, ...replacements);
174
+ i += replacements.length - 1;
175
+ }
176
+ }
177
+ }
178
+
179
+ module.exports = {
180
+ providerId,
181
+ isOpenAIStyle,
182
+ isAnthropicStyle,
183
+ normalizeMessagesForProvider,
184
+ normalizeToolsForProvider,
185
+ normalizeResponse,
186
+ transformRequest,
187
+ hasToolHistory,
188
+ };
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ const READ_ONLY_TOOL_NAMES = new Set([
4
+ 'read_file',
5
+ 'glob',
6
+ 'grep_files',
7
+ 'list_directory',
8
+ 'lsp_symbols',
9
+ 'lsp_definition',
10
+ 'lsp_references',
11
+ 'lsp_diagnostics',
12
+ 'lsp_hover',
13
+ 'lsp_implementation',
14
+ ]);
15
+
16
+ const COORDINATION_TOOL_NAMES = new Set([
17
+ 'task',
18
+ 'update_todos',
19
+ 'ask_user',
20
+ 'run_shell',
21
+ 'mcp_call',
22
+ 'skill',
23
+ ]);
24
+
25
+ const WRITE_TOOL_NAMES = new Set([
26
+ 'write_file',
27
+ 'edit_file',
28
+ 'apply_patch',
29
+ 'multi_edit',
30
+ ]);
31
+
32
+ const MODES = {
33
+ agent: {
34
+ id: 'agent',
35
+ canEdit: true,
36
+ canDelegate: false,
37
+ description: 'Act as one coding agent. Edit and verify directly; do not delegate through task unless explicitly reconfigured.',
38
+ },
39
+ master: {
40
+ id: 'master',
41
+ canEdit: false,
42
+ canDelegate: true,
43
+ description: 'Act as a master of coding agents. Inspect, plan, verify, and delegate implementation to child agents.',
44
+ },
45
+ mixed: {
46
+ id: 'mixed',
47
+ canEdit: true,
48
+ canDelegate: true,
49
+ description: 'Act as both coding agent and master. Edit directly for tight work and delegate separable work to child agents.',
50
+ },
51
+ };
52
+
53
+ function resolveRuntimeMode(opts = {}, env = process.env) {
54
+ const raw = String(
55
+ opts.runtimeMode
56
+ || opts.codingRole
57
+ || opts.agentRole
58
+ || opts.role
59
+ || (['agent', 'master', 'mixed'].includes(opts.mode) ? opts.mode : '')
60
+ || env.WALLE_CODING_RUNTIME_MODE
61
+ || ''
62
+ ).trim().toLowerCase();
63
+ const id = normalizeRuntimeMode(raw || 'mixed');
64
+ return { ...MODES[id] };
65
+ }
66
+
67
+ function normalizeRuntimeMode(value) {
68
+ const v = String(value || '').trim().toLowerCase();
69
+ if (v === 'delegate' || v === 'delegator' || v === 'orchestrator' || v === 'supervisor') return 'master';
70
+ if (v === 'builder' || v === 'coder' || v === 'single') return 'agent';
71
+ if (v === 'hybrid') return 'mixed';
72
+ if (MODES[v]) return v;
73
+ return 'mixed';
74
+ }
75
+
76
+ function filterToolsForRuntimeMode(tools = [], runtimeMode = resolveRuntimeMode()) {
77
+ const mode = typeof runtimeMode === 'string' ? resolveRuntimeMode({ runtimeMode }) : runtimeMode;
78
+ const list = Array.isArray(tools) ? tools : [];
79
+ if (mode.id === 'mixed') return [...list];
80
+ if (mode.id === 'agent') return list.filter((tool) => tool?.name !== 'task');
81
+ if (mode.id === 'master') {
82
+ return list.filter((tool) => {
83
+ const name = tool?.name || '';
84
+ if (WRITE_TOOL_NAMES.has(name)) return false;
85
+ return READ_ONLY_TOOL_NAMES.has(name) || COORDINATION_TOOL_NAMES.has(name);
86
+ });
87
+ }
88
+ return [...list];
89
+ }
90
+
91
+ function runtimeModeInstructions(runtimeMode = resolveRuntimeMode()) {
92
+ const mode = typeof runtimeMode === 'string' ? resolveRuntimeMode({ runtimeMode }) : runtimeMode;
93
+ if (mode.id === 'master') {
94
+ return [
95
+ 'Runtime role: master of coding agents.',
96
+ '- Use task for implementation/review/exploration work that can be delegated.',
97
+ '- You may inspect files and run verification commands, but direct file-editing tools are intentionally unavailable.',
98
+ '- Split work into the fewest independent child tasks; integrate their results and decide final status.',
99
+ ].join('\n');
100
+ }
101
+ if (mode.id === 'agent') {
102
+ return [
103
+ 'Runtime role: coding agent.',
104
+ '- Do the coding work directly in this session.',
105
+ '- Delegation is unavailable in this role; use local tools to inspect, edit, and verify.',
106
+ ].join('\n');
107
+ }
108
+ return [
109
+ 'Runtime role: mixed coding agent and coordinator.',
110
+ '- Do compact/local edits directly.',
111
+ '- Use task to delegate independent, parallelizable, review, or exploration work.',
112
+ '- Keep the parent session responsible for integration, verification, and final status.',
113
+ ].join('\n');
114
+ }
115
+
116
+ function shouldUseStreamProcessor(opts = {}, env = process.env) {
117
+ if (opts.useStreamProcessor === false) return false;
118
+ if (String(env.WALLE_CODING_STREAM_PROCESSOR || '').trim() === '0') return false;
119
+ return true;
120
+ }
121
+
122
+ module.exports = {
123
+ READ_ONLY_TOOL_NAMES,
124
+ COORDINATION_TOOL_NAMES,
125
+ WRITE_TOOL_NAMES,
126
+ MODES,
127
+ normalizeRuntimeMode,
128
+ resolveRuntimeMode,
129
+ filterToolsForRuntimeMode,
130
+ runtimeModeInstructions,
131
+ shouldUseStreamProcessor,
132
+ };