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,188 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('node:events');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const crypto = require('node:crypto');
7
+ const { WorkspaceReplay } = require('./workspace-replay');
8
+
9
+ class AcpAdapter {
10
+ constructor({
11
+ replay = null,
12
+ permissionService = null,
13
+ eventBus = null,
14
+ runner = null,
15
+ runAgentLoop = null,
16
+ } = {}) {
17
+ this.replay = replay || new WorkspaceReplay();
18
+ this.permissionService = permissionService || null;
19
+ this.eventBus = eventBus || new EventEmitter();
20
+ this.runner = runner || null;
21
+ this.runAgentLoop = runAgentLoop || null;
22
+ this.sessions = new Map();
23
+ this.subscribers = new Map();
24
+ this.history = new Map();
25
+
26
+ this.eventBus.on('permission.asked', (entry) => {
27
+ this._emit(entry.sessionId || '', 'permission', { state: 'asked', request: entry });
28
+ });
29
+ this.eventBus.on('permission.replied', (entry) => {
30
+ this._emit(entry.sessionId || '', 'permission', { state: 'replied', request: entry });
31
+ });
32
+ }
33
+
34
+ async createSession({ sessionId = crypto.randomUUID(), cwd = process.cwd(), prompt = '', model = '', agent = 'build', mode = 'build', run = false } = {}) {
35
+ this.sessions.set(sessionId, { sessionId, cwd, model, agent, mode, status: run ? 'running' : 'created' });
36
+ this.replay.appendRecord(sessionId, {
37
+ type: 'session_meta',
38
+ cwd,
39
+ modelId: model,
40
+ label: prompt.slice(0, 80),
41
+ agent,
42
+ mode,
43
+ });
44
+ if (prompt) {
45
+ this.replay.appendRecord(sessionId, {
46
+ type: 'user',
47
+ cwd,
48
+ message: { role: 'user', content: [{ type: 'text', text: prompt }] },
49
+ });
50
+ }
51
+ this._emit(sessionId, 'session.created', { sessionId, cwd, model, agent, mode });
52
+
53
+ if (run) {
54
+ const result = await this._runSession({ sessionId, cwd, prompt, model, agent, mode });
55
+ this.sessions.set(sessionId, { ...this.sessions.get(sessionId), status: result.success === false ? 'failed' : 'completed' });
56
+ return { sessionId, cwd, model, agent, mode, result };
57
+ }
58
+ return { sessionId, cwd, model, agent, mode };
59
+ }
60
+
61
+ loadSession(sessionId, opts = {}) {
62
+ const state = this.replay.rebuildState(sessionId, opts);
63
+ const existing = this.sessions.get(sessionId) || {};
64
+ this.sessions.set(sessionId, {
65
+ sessionId,
66
+ cwd: opts.cwd || state.cwd || existing.cwd || '',
67
+ model: state.meta.modelId || existing.model || '',
68
+ agent: state.meta.agent || existing.agent || '',
69
+ mode: state.meta.mode || existing.mode || '',
70
+ status: existing.status || 'loaded',
71
+ });
72
+ return state;
73
+ }
74
+
75
+ setSessionOptions(sessionId, updates = {}) {
76
+ const previous = this.sessions.get(sessionId) || { sessionId };
77
+ const next = { ...previous, ...updates, sessionId };
78
+ this.sessions.set(sessionId, next);
79
+ this.replay.appendPart(sessionId, 'session_options', updates, { cwd: next.cwd || '' });
80
+ this._emit(sessionId, 'session.options', next);
81
+ return next;
82
+ }
83
+
84
+ cancelSession(sessionId, reason = 'cancelled') {
85
+ const previous = this.sessions.get(sessionId) || { sessionId };
86
+ const next = { ...previous, status: 'cancelled', cancelReason: reason };
87
+ this.sessions.set(sessionId, next);
88
+ this.replay.appendPart(sessionId, 'cancelled', { reason }, { cwd: next.cwd || '' });
89
+ this._emit(sessionId, 'session.cancelled', { sessionId, reason });
90
+ return next;
91
+ }
92
+
93
+ subscribe(sessionId, { offset = 0, sequence = 0, onEvent } = {}) {
94
+ if (typeof onEvent !== 'function') throw new Error('onEvent callback is required');
95
+ const replay = this.replay.replayRange(sessionId, { offset, sequence });
96
+ for (const entry of replay.records) {
97
+ onEvent({ type: 'transcript', sessionId, sequence: entry.sequence, offset: entry.offset, record: entry.record });
98
+ }
99
+ for (const evt of this.history.get(sessionId) || []) {
100
+ onEvent(evt);
101
+ }
102
+ for (const request of this.listPermissions({ sessionId })) {
103
+ onEvent({ type: 'permission', sessionId, data: { state: 'pending', request } });
104
+ }
105
+
106
+ const set = this.subscribers.get(sessionId) || new Set();
107
+ set.add(onEvent);
108
+ this.subscribers.set(sessionId, set);
109
+ return () => {
110
+ const current = this.subscribers.get(sessionId);
111
+ if (!current) return;
112
+ current.delete(onEvent);
113
+ if (current.size === 0) this.subscribers.delete(sessionId);
114
+ };
115
+ }
116
+
117
+ listPermissions({ sessionId = '' } = {}) {
118
+ if (!this.permissionService?.list) return [];
119
+ return this.permissionService.list({ sessionId });
120
+ }
121
+
122
+ replyPermission(requestId, reply = 'once', message = '') {
123
+ if (!this.permissionService?.reply) return false;
124
+ return this.permissionService.reply({ requestId, reply, message });
125
+ }
126
+
127
+ writeFilesFromAcceptedDiff({ sessionId = '', workspaceRoot, files = [] } = {}) {
128
+ const root = realpathBestEffort(workspaceRoot);
129
+ if (!root) throw new Error('workspaceRoot is required');
130
+ const written = [];
131
+ for (const file of files) {
132
+ const target = resolveInside(root, file.path || file.filePath || file.name || '');
133
+ fs.mkdirSync(path.dirname(target), { recursive: true });
134
+ fs.writeFileSync(target, String(file.content ?? ''), 'utf8');
135
+ const rel = path.relative(root, target);
136
+ written.push(rel);
137
+ }
138
+ if (sessionId) {
139
+ this.replay.appendPart(sessionId, 'accepted_diff', { files: written }, { cwd: root });
140
+ this._emit(sessionId, 'files.written', { files: written, workspaceRoot: root });
141
+ }
142
+ return { workspaceRoot: root, files: written };
143
+ }
144
+
145
+ async _runSession({ sessionId, cwd, prompt, model, agent, mode }) {
146
+ if (this.runner) return this.runner({ sessionId, cwd, prompt, model, agent, mode, adapter: this });
147
+ if (!this.runAgentLoop) return { success: true, deferred: true };
148
+ return this.runAgentLoop(prompt, {
149
+ cwd,
150
+ model,
151
+ mode,
152
+ _resumeSessionId: sessionId,
153
+ });
154
+ }
155
+
156
+ _emit(sessionId, type, data = {}) {
157
+ if (!sessionId) return;
158
+ const event = { type, sessionId, data, timestamp: new Date().toISOString() };
159
+ const history = this.history.get(sessionId) || [];
160
+ history.push(event);
161
+ if (history.length > 200) history.shift();
162
+ this.history.set(sessionId, history);
163
+ const subscribers = this.subscribers.get(sessionId);
164
+ if (subscribers) {
165
+ for (const send of subscribers) send(event);
166
+ }
167
+ }
168
+ }
169
+
170
+ function resolveInside(root, requestedPath) {
171
+ if (!requestedPath) throw new Error('file path is required');
172
+ const target = path.resolve(root, requestedPath);
173
+ const rel = path.relative(root, target);
174
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
175
+ throw new Error(`Path escapes workspace: ${requestedPath}`);
176
+ }
177
+ return target;
178
+ }
179
+
180
+ function realpathBestEffort(p) {
181
+ if (!p) return '';
182
+ try { return fs.realpathSync(p); } catch { return path.resolve(p); }
183
+ }
184
+
185
+ module.exports = {
186
+ AcpAdapter,
187
+ resolveInside,
188
+ };
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+
6
+ const READ_ONLY_TOOL_NAMES = new Set([
7
+ 'read_file',
8
+ 'glob',
9
+ 'grep_files',
10
+ 'list_directory',
11
+ 'lsp_symbols',
12
+ 'lsp_definition',
13
+ 'lsp_references',
14
+ ]);
15
+
16
+ const DELEGATE_TOOL_NAMES = new Set([
17
+ ...READ_ONLY_TOOL_NAMES,
18
+ 'run_shell',
19
+ 'update_todos',
20
+ 'ask_user',
21
+ 'task',
22
+ 'mcp_call',
23
+ 'skill',
24
+ ]);
25
+
26
+ const BUILTIN_AGENTS = {
27
+ build: { id: 'build', mode: 'build', description: 'Implement code changes and verify them.', tools: 'all' },
28
+ plan: { id: 'plan', mode: 'plan', description: 'Plan changes without editing files.', tools: 'read-only' },
29
+ review: { id: 'review', mode: 'review', description: 'Review code and diffs without editing files.', tools: 'read-only' },
30
+ explore: { id: 'explore', mode: 'plan', description: 'Explore the repository and report findings.', tools: 'read-only' },
31
+ master: { id: 'master', mode: 'master', description: 'Coordinate child coding agents and verify their work.', tools: 'delegate' },
32
+ mixed: { id: 'mixed', mode: 'mixed', description: 'Implement directly while delegating separable work to child agents.', tools: 'all' },
33
+ general: { id: 'general', mode: 'build', description: 'General coding assistant.', tools: 'all' },
34
+ compaction: { id: 'compaction', mode: 'summary', description: 'Summarize long coding histories.', hidden: true, tools: [] },
35
+ summary: { id: 'summary', mode: 'summary', description: 'Produce concise session summaries.', hidden: true, tools: [] },
36
+ title: { id: 'title', mode: 'summary', description: 'Generate session titles.', hidden: true, tools: [] },
37
+ };
38
+
39
+ class AgentCatalog {
40
+ constructor({ projectRoot = process.cwd(), overrides = null } = {}) {
41
+ this.projectRoot = projectRoot;
42
+ this.overrides = overrides;
43
+ this.agents = new Map();
44
+ this.reload();
45
+ }
46
+
47
+ reload() {
48
+ this.agents.clear();
49
+ for (const [id, agent] of Object.entries(BUILTIN_AGENTS)) {
50
+ this.agents.set(id, normalizeAgent({ ...agent, id }));
51
+ }
52
+ const overrides = this.overrides || loadAgentOverrides(this.projectRoot);
53
+ for (const [id, override] of Object.entries(overrides || {})) {
54
+ const base = this.agents.get(id) || { id };
55
+ this.agents.set(id, normalizeAgent({ ...base, ...override, id }));
56
+ }
57
+ }
58
+
59
+ get(id = 'build') {
60
+ return this.agents.get(id) || null;
61
+ }
62
+
63
+ list({ includeHidden = false } = {}) {
64
+ return [...this.agents.values()]
65
+ .filter((agent) => includeHidden || !agent.hidden)
66
+ .map((agent) => ({ ...agent }));
67
+ }
68
+
69
+ describeForTool() {
70
+ return this.list()
71
+ .map((agent) => `${agent.id}: ${agent.description}`)
72
+ .join('\n');
73
+ }
74
+
75
+ toolsForAgent(toolDefinitions = [], agentOrId = 'build') {
76
+ const agent = typeof agentOrId === 'string' ? this.get(agentOrId) : agentOrId;
77
+ if (!agent) return [];
78
+ const policy = agent.tools ?? 'all';
79
+ if (policy === 'all') return [...toolDefinitions];
80
+ if (policy === 'none' || (Array.isArray(policy) && policy.length === 0)) return [];
81
+ if (policy === 'delegate' || agent.mode === 'master') {
82
+ return toolDefinitions.filter((tool) => DELEGATE_TOOL_NAMES.has(tool.name));
83
+ }
84
+ if (policy === 'read-only' || agent.mode === 'plan' || agent.mode === 'review') {
85
+ return toolDefinitions.filter((tool) => READ_ONLY_TOOL_NAMES.has(tool.name));
86
+ }
87
+ if (Array.isArray(policy)) {
88
+ const allowed = new Set(policy);
89
+ return toolDefinitions.filter((tool) => allowed.has(tool.name));
90
+ }
91
+ return [...toolDefinitions];
92
+ }
93
+ }
94
+
95
+ function loadAgentOverrides(projectRoot = process.cwd()) {
96
+ const filePath = path.join(projectRoot, '.walle', 'agents.json');
97
+ try {
98
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
99
+ return parsed && typeof parsed === 'object' ? parsed : {};
100
+ } catch {
101
+ return {};
102
+ }
103
+ }
104
+
105
+ function normalizeAgent(agent = {}) {
106
+ const mode = String(agent.mode || 'build');
107
+ return {
108
+ id: String(agent.id || ''),
109
+ mode,
110
+ description: String(agent.description || `${agent.id || 'Agent'} coding agent.`),
111
+ prompt: typeof agent.prompt === 'string' ? agent.prompt : '',
112
+ model: typeof agent.model === 'string' ? agent.model : '',
113
+ temperature: agent.temperature,
114
+ topP: agent.topP ?? agent.top_p,
115
+ maxSteps: Number.isFinite(agent.maxSteps) ? agent.maxSteps : agent.max_steps,
116
+ permissionRules: agent.permissionRules || agent.permission_rules || null,
117
+ hidden: Boolean(agent.hidden),
118
+ tools: agent.tools ?? (mode === 'plan' || mode === 'review' ? 'read-only' : 'all'),
119
+ };
120
+ }
121
+
122
+ module.exports = {
123
+ AgentCatalog,
124
+ BUILTIN_AGENTS,
125
+ READ_ONLY_TOOL_NAMES,
126
+ DELEGATE_TOOL_NAMES,
127
+ loadAgentOverrides,
128
+ normalizeAgent,
129
+ };
@@ -0,0 +1,247 @@
1
+ 'use strict';
2
+
3
+ const { estimateTokens, estimateMessagesTokens } = require('../context/token-counter');
4
+ const { recordContextCompaction } = require('./execution-trace');
5
+ const { newId, textFromContent } = require('./model-message');
6
+
7
+ const DEFAULT_CONTEXT_WINDOW = 200000;
8
+ const DEFAULT_THRESHOLD = 0.75;
9
+ const DEFAULT_TAIL_TOKEN_BUDGET = 40000;
10
+ const DEFAULT_KEEP_RECENT_USER_TURNS = 4;
11
+
12
+ class CompactionService {
13
+ constructor({
14
+ provider,
15
+ model,
16
+ contextWindow = DEFAULT_CONTEXT_WINDOW,
17
+ threshold = DEFAULT_THRESHOLD,
18
+ tailTokenBudget = DEFAULT_TAIL_TOKEN_BUDGET,
19
+ keepRecentUserTurns = DEFAULT_KEEP_RECENT_USER_TURNS,
20
+ now,
21
+ } = {}) {
22
+ this.provider = provider || null;
23
+ this.model = model || provider?.model || '';
24
+ this.contextWindow = contextWindow;
25
+ this.threshold = threshold;
26
+ this.tailTokenBudget = tailTokenBudget;
27
+ this.keepRecentUserTurns = keepRecentUserTurns;
28
+ this.now = now || (() => new Date().toISOString());
29
+ }
30
+
31
+ shouldCompact({ messages = [], systemTokens = 0, usage = null, contextWindow = this.contextWindow } = {}) {
32
+ const actualInput = Number.isFinite(usage?.input) ? usage.input
33
+ : Number.isFinite(usage?.inputTokens) ? usage.inputTokens
34
+ : null;
35
+ const tokens = actualInput == null
36
+ ? systemTokens + estimateMessagesTokens(messages)
37
+ : actualInput;
38
+ return tokens >= contextWindow * this.threshold;
39
+ }
40
+
41
+ selectTail(messages = [], {
42
+ tailTokenBudget = this.tailTokenBudget,
43
+ keepRecentUserTurns = this.keepRecentUserTurns,
44
+ } = {}) {
45
+ if (!Array.isArray(messages) || messages.length === 0) {
46
+ return { head: [], tail: [], tailStartIndex: -1, tailStartId: null, tailTokens: 0, headTokens: 0 };
47
+ }
48
+
49
+ let tailStartIndex = messages.length;
50
+ let tailTokens = 0;
51
+ let userTurns = 0;
52
+
53
+ for (let i = messages.length - 1; i >= 0; i--) {
54
+ const msg = messages[i];
55
+ const msgTokens = estimateMessagesTokens([msg]);
56
+ const stillProtectingTurns = userTurns < keepRecentUserTurns;
57
+ if (!stillProtectingTurns && tailStartIndex < messages.length && tailTokens + msgTokens > tailTokenBudget) {
58
+ break;
59
+ }
60
+
61
+ tailStartIndex = i;
62
+ tailTokens += msgTokens;
63
+ if (msg?.role === 'user') userTurns++;
64
+
65
+ if (!stillProtectingTurns && tailTokens >= tailTokenBudget) break;
66
+ }
67
+
68
+ if (tailStartIndex <= 0) {
69
+ return {
70
+ head: [],
71
+ tail: cloneMessages(messages),
72
+ tailStartIndex: 0,
73
+ tailStartId: getMessageId(messages[0], 0),
74
+ tailTokens,
75
+ headTokens: 0,
76
+ };
77
+ }
78
+
79
+ const head = messages.slice(0, tailStartIndex);
80
+ const tail = messages.slice(tailStartIndex);
81
+ return {
82
+ head: cloneMessages(head),
83
+ tail: cloneMessages(tail),
84
+ tailStartIndex,
85
+ tailStartId: getMessageId(messages[tailStartIndex], tailStartIndex),
86
+ tailTokens,
87
+ headTokens: estimateMessagesTokens(head),
88
+ };
89
+ }
90
+
91
+ async compact(messages = [], {
92
+ sessionId = '',
93
+ cwd = '',
94
+ reason = 'context_overflow',
95
+ transcript = null,
96
+ trace = null,
97
+ sessionMemory = null,
98
+ tailTokenBudget,
99
+ keepRecentUserTurns,
100
+ } = {}) {
101
+ if (sessionMemory && typeof sessionMemory.precompact === 'function') {
102
+ await sessionMemory.precompact({ messages, sessionId, cwd, reason });
103
+ }
104
+
105
+ const selection = this.selectTail(messages, { tailTokenBudget, keepRecentUserTurns });
106
+ const tokensBefore = estimateMessagesTokens(messages);
107
+ if (selection.head.length === 0) {
108
+ const noCompaction = {
109
+ compacted: false,
110
+ reason: 'no_compactable_head',
111
+ messages: cloneMessages(messages),
112
+ tokensBeforeCompaction: tokensBefore,
113
+ tokensAfterCompaction: tokensBefore,
114
+ };
115
+ recordContextCompaction(trace, noCompaction);
116
+ return noCompaction;
117
+ }
118
+
119
+ const compactionId = newId('compaction');
120
+ const summary = await this._summarize(selection.head, selection);
121
+ const timestamp = this.now();
122
+ const userMessage = {
123
+ role: 'user',
124
+ content: `[Compaction:${compactionId}] Earlier Wall-E context was compacted. Recent tail starts at ${selection.tailStartId}.`,
125
+ };
126
+ const assistantMessage = {
127
+ role: 'assistant',
128
+ content: `Compaction summary:\n${summary}`,
129
+ };
130
+ const compactedMessages = [userMessage, assistantMessage, ...selection.tail];
131
+ const tokensAfter = estimateMessagesTokens(compactedMessages);
132
+ const metadata = {
133
+ compactionId,
134
+ reason,
135
+ sessionId,
136
+ cwd,
137
+ timestamp,
138
+ compaction_user_id: newId('compaction_user'),
139
+ compaction_assistant_id: newId('compaction_assistant'),
140
+ tail_start_id: selection.tailStartId,
141
+ tail_start_index: selection.tailStartIndex,
142
+ tail_message_ids: selection.tail.map((msg, index) => getMessageId(msg, selection.tailStartIndex + index)),
143
+ compacted_message_count: selection.head.length,
144
+ retained_message_count: selection.tail.length,
145
+ tokens_before: tokensBefore,
146
+ tokens_after: tokensAfter,
147
+ summary,
148
+ };
149
+
150
+ if (transcript?.appendPart) {
151
+ await transcript.appendPart({ sessionId, cwd, partType: 'compaction', data: metadata });
152
+ } else if (transcript?.append) {
153
+ await transcript.append({ type: 'walle_part', sessionId, cwd, partType: 'compaction', data: metadata });
154
+ }
155
+
156
+ const result = {
157
+ compacted: true,
158
+ messages: compactedMessages,
159
+ summary,
160
+ userMessage,
161
+ assistantMessage,
162
+ tailStartId: selection.tailStartId,
163
+ metadata,
164
+ tokensBeforeCompaction: tokensBefore,
165
+ tokensAfterCompaction: tokensAfter,
166
+ };
167
+ recordContextCompaction(trace, result);
168
+ return result;
169
+ }
170
+
171
+ remapTailStartId(compaction, idMap = {}, forkMessages = []) {
172
+ return remapTailStartId(compaction, idMap, forkMessages);
173
+ }
174
+
175
+ async _summarize(messages, selection) {
176
+ const historyText = messages.map(formatMessageForSummary).join('\n\n');
177
+ const fallback = fallbackSummary(historyText, selection);
178
+ if (!this.provider || typeof this.provider.chat !== 'function') return fallback;
179
+
180
+ const prompt = `Summarize the compacted Wall-E coding history. Preserve decisions, file paths, commands, tool findings, failures, and unfinished work. Do not include a preamble.\n\nCOMPACTED HISTORY:\n${historyText.slice(0, 60000)}`;
181
+ try {
182
+ const response = await this.provider.chat({
183
+ model: this.model,
184
+ messages: [{ role: 'user', content: prompt }],
185
+ maxTokens: 1200,
186
+ metadata: { purpose: 'compaction' },
187
+ });
188
+ const content = textFromContent(response?.content || '');
189
+ return content.trim() || fallback;
190
+ } catch {
191
+ return fallback;
192
+ }
193
+ }
194
+ }
195
+
196
+ function remapTailStartId(compaction, idMap = {}, forkMessages = []) {
197
+ const oldTailId = compaction?.tailStartId || compaction?.tail_start_id || compaction?.metadata?.tail_start_id || null;
198
+ if (!oldTailId) return null;
199
+ if (idMap[oldTailId]) return idMap[oldTailId];
200
+
201
+ const forkIds = new Set((forkMessages || []).map((msg, index) => getMessageId(msg, index)).filter(Boolean));
202
+ if (forkIds.has(oldTailId)) return oldTailId;
203
+
204
+ const tailIds = compaction?.tailMessageIds || compaction?.tail_message_ids || compaction?.metadata?.tail_message_ids || [];
205
+ for (const id of tailIds) {
206
+ if (idMap[id]) return idMap[id];
207
+ if (forkIds.has(id)) return id;
208
+ }
209
+ return forkMessages.length > 0 ? getMessageId(forkMessages[0], 0) : null;
210
+ }
211
+
212
+ function getMessageId(message, index = 0) {
213
+ return message?.id || message?.uuid || message?.messageId || message?.message_id || `msg_${index}`;
214
+ }
215
+
216
+ function cloneMessages(messages) {
217
+ return (messages || []).map((msg) => cloneValue(msg));
218
+ }
219
+
220
+ function cloneValue(value) {
221
+ if (value == null) return value;
222
+ return JSON.parse(JSON.stringify(value));
223
+ }
224
+
225
+ function formatMessageForSummary(message, index) {
226
+ const role = message?.role || 'unknown';
227
+ const id = getMessageId(message, index);
228
+ return `${role} (${id}): ${textFromContent(message?.content || '').slice(0, 12000)}`;
229
+ }
230
+
231
+ function fallbackSummary(historyText, selection) {
232
+ const line = `Compacted ${selection.head.length} older messages before tail ${selection.tailStartId}.`;
233
+ if (!historyText) return line;
234
+ const max = 4000;
235
+ return `${line}\n\n${historyText.length > max ? historyText.slice(-max) : historyText}`;
236
+ }
237
+
238
+ module.exports = {
239
+ CompactionService,
240
+ remapTailStartId,
241
+ getMessageId,
242
+ DEFAULT_CONTEXT_WINDOW,
243
+ DEFAULT_THRESHOLD,
244
+ DEFAULT_TAIL_TOKEN_BUDGET,
245
+ DEFAULT_KEEP_RECENT_USER_TURNS,
246
+ estimateTokens,
247
+ };
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = require('../runtime/execution-trace');