create-walle 0.9.25 → 0.9.26

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 (179) hide show
  1. package/README.md +8 -0
  2. package/bin/create-walle.js +815 -45
  3. package/package.json +2 -2
  4. package/template/bin/ctm-dev-cleanup.js +90 -4
  5. package/template/bin/ctm-launch.sh +49 -1
  6. package/template/bin/dev.sh +45 -1
  7. package/template/bin/ensure-stable-node.js +132 -0
  8. package/template/bin/install-service.sh +9 -0
  9. package/template/claude-task-manager/api-prompts.js +899 -119
  10. package/template/claude-task-manager/approval-agent.js +360 -40
  11. package/template/claude-task-manager/bin/ctm-disclaim.c +42 -0
  12. package/template/claude-task-manager/bin/ctm-hotkey.swift +67 -81
  13. package/template/claude-task-manager/bin/ctm-screen-auth.swift +37 -0
  14. package/template/claude-task-manager/bin/install-hotkey.sh +97 -49
  15. package/template/claude-task-manager/bin/restart-ctm.sh +14 -0
  16. package/template/claude-task-manager/db.js +399 -48
  17. package/template/claude-task-manager/docs/approval-hook-sandbox.md +84 -0
  18. package/template/claude-task-manager/docs/codex-app-server-approvals.md +72 -0
  19. package/template/claude-task-manager/docs/codex-native-sandbox.md +47 -0
  20. package/template/claude-task-manager/docs/prompt-editing-tree-design.md +18 -1
  21. package/template/claude-task-manager/lib/approval-hook.js +200 -0
  22. package/template/claude-task-manager/lib/approval-self-adapt.js +1 -0
  23. package/template/claude-task-manager/lib/auth-rules.js +11 -0
  24. package/template/claude-task-manager/lib/background-llm.js +32 -4
  25. package/template/claude-task-manager/lib/codesign-identity.js +140 -0
  26. package/template/claude-task-manager/lib/codex-app-server-client.js +119 -0
  27. package/template/claude-task-manager/lib/codex-approval-bridge.js +118 -0
  28. package/template/claude-task-manager/lib/codex-history-terminal-renderer.js +571 -0
  29. package/template/claude-task-manager/lib/codex-paths.js +73 -0
  30. package/template/claude-task-manager/lib/codex-rollout-snapshot.js +164 -0
  31. package/template/claude-task-manager/lib/codex-rollout-tail.js +72 -0
  32. package/template/claude-task-manager/lib/codex-sandbox-args.js +47 -0
  33. package/template/claude-task-manager/lib/coding-agent-models.js +118 -71
  34. package/template/claude-task-manager/lib/command-targets.js +163 -0
  35. package/template/claude-task-manager/lib/conversation-tail-merge.js +61 -19
  36. package/template/claude-task-manager/lib/db-owner-worker-client.js +29 -1
  37. package/template/claude-task-manager/lib/escalation-review.js +80 -3
  38. package/template/claude-task-manager/lib/flow-control.js +52 -0
  39. package/template/claude-task-manager/lib/fs-watcher.js +24 -15
  40. package/template/claude-task-manager/lib/ingest-cooldown.js +68 -0
  41. package/template/claude-task-manager/lib/jsonl-conversation-parser.js +8 -4
  42. package/template/claude-task-manager/lib/launchd-recovery.js +92 -0
  43. package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +207 -52
  44. package/template/claude-task-manager/lib/mobile-push-store.js +7 -0
  45. package/template/claude-task-manager/lib/model-overview-brain-fallback.js +102 -1
  46. package/template/claude-task-manager/lib/model-overview-cache.js +1 -0
  47. package/template/claude-task-manager/lib/oauth-proxy-supervisor.js +2 -1
  48. package/template/claude-task-manager/lib/perf-tracker.js +29 -2
  49. package/template/claude-task-manager/lib/permission-match.js +146 -16
  50. package/template/claude-task-manager/lib/project-slug.js +33 -0
  51. package/template/claude-task-manager/lib/prompt-intent.js +51 -4
  52. package/template/claude-task-manager/lib/read-pool-client.js +48 -3
  53. package/template/claude-task-manager/lib/real-node.js +73 -0
  54. package/template/claude-task-manager/lib/runtime-work-registry.js +131 -14
  55. package/template/claude-task-manager/lib/session-content-backfill.js +24 -5
  56. package/template/claude-task-manager/lib/session-diagnostics-batch.js +87 -0
  57. package/template/claude-task-manager/lib/session-history.js +5 -7
  58. package/template/claude-task-manager/lib/session-host-manager.js +19 -0
  59. package/template/claude-task-manager/lib/session-jobs.js +6 -0
  60. package/template/claude-task-manager/lib/session-message-response-cache.js +89 -0
  61. package/template/claude-task-manager/lib/session-messages-page.js +211 -0
  62. package/template/claude-task-manager/lib/session-messages-projection.js +170 -0
  63. package/template/claude-task-manager/lib/session-standup.js +8 -0
  64. package/template/claude-task-manager/lib/session-timeline-summary.js +16 -2
  65. package/template/claude-task-manager/lib/session-token-usage.js +30 -8
  66. package/template/claude-task-manager/lib/session-workspace-binding.js +29 -15
  67. package/template/claude-task-manager/lib/storage-migration.js +2 -1
  68. package/template/claude-task-manager/lib/transcript-store.js +179 -12
  69. package/template/claude-task-manager/lib/walle-ctm-history.js +298 -11
  70. package/template/claude-task-manager/lib/walle-permission-reply.js +49 -0
  71. package/template/claude-task-manager/lib/walle-session-cache.js +22 -1
  72. package/template/claude-task-manager/lib/walle-supervisor.js +42 -3
  73. package/template/claude-task-manager/package.json +5 -2
  74. package/template/claude-task-manager/prompt-harvest.js +31 -11
  75. package/template/claude-task-manager/providers/claude-code.js +29 -1
  76. package/template/claude-task-manager/providers/codex.js +13 -1
  77. package/template/claude-task-manager/public/css/setup.css +11 -0
  78. package/template/claude-task-manager/public/css/walle-session.css +132 -4
  79. package/template/claude-task-manager/public/css/walle.css +89 -0
  80. package/template/claude-task-manager/public/icon-16.png +0 -0
  81. package/template/claude-task-manager/public/icon-32.png +0 -0
  82. package/template/claude-task-manager/public/icon-512.png +0 -0
  83. package/template/claude-task-manager/public/index.html +2483 -165
  84. package/template/claude-task-manager/public/js/activation-render-check.js +55 -0
  85. package/template/claude-task-manager/public/js/flow-control-policy.js +52 -0
  86. package/template/claude-task-manager/public/js/message-renderer.js +60 -1
  87. package/template/claude-task-manager/public/js/prompts.js +13 -1
  88. package/template/claude-task-manager/public/js/session-status-precedence.js +9 -3
  89. package/template/claude-task-manager/public/js/setup.js +54 -10
  90. package/template/claude-task-manager/public/js/stream-resize-policy.js +80 -0
  91. package/template/claude-task-manager/public/js/stream-view.js +78 -0
  92. package/template/claude-task-manager/public/js/terminal-reconciler.js +52 -2
  93. package/template/claude-task-manager/public/js/tool-state.js +155 -0
  94. package/template/claude-task-manager/public/js/walle-session.js +887 -326
  95. package/template/claude-task-manager/public/js/walle.js +306 -195
  96. package/template/claude-task-manager/public/m/app.css +1 -0
  97. package/template/claude-task-manager/public/m/app.js +33 -3
  98. package/template/claude-task-manager/queue-engine.js +45 -1
  99. package/template/claude-task-manager/server.js +3367 -540
  100. package/template/claude-task-manager/workers/approval-blocklist.js +130 -17
  101. package/template/claude-task-manager/workers/db-owner-worker.js +31 -1
  102. package/template/claude-task-manager/workers/read-pool-worker.js +92 -5
  103. package/template/claude-task-manager/workers/session-host-process.js +10 -0
  104. package/template/claude-task-manager/workers/state-detectors/codex.js +58 -7
  105. package/template/package.json +2 -3
  106. package/template/shared/icons/AppIcon-ctm.icns +0 -0
  107. package/template/shared/icons/AppIcon-walle.icns +0 -0
  108. package/template/wall-e/agent.js +139 -18
  109. package/template/wall-e/api-walle.js +201 -22
  110. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +1981 -0
  111. package/template/wall-e/brain.js +1049 -39
  112. package/template/wall-e/chat.js +427 -86
  113. package/template/wall-e/coding/acceptance-contract.js +26 -1
  114. package/template/wall-e/coding/action-memory-policy.js +353 -0
  115. package/template/wall-e/coding/action-memory-store.js +814 -0
  116. package/template/wall-e/coding/initial-messages.js +197 -0
  117. package/template/wall-e/coding/no-progress-guard.js +327 -0
  118. package/template/wall-e/coding/permission-service.js +88 -22
  119. package/template/wall-e/coding/session-workspaces.js +81 -0
  120. package/template/wall-e/coding/shell-sandbox.js +124 -0
  121. package/template/wall-e/coding/stream-processor.js +63 -2
  122. package/template/wall-e/coding/tool-execution-controller.js +14 -1
  123. package/template/wall-e/coding/tool-registry.js +1 -1
  124. package/template/wall-e/coding/transcript-writer.js +3 -0
  125. package/template/wall-e/coding-orchestrator.js +636 -35
  126. package/template/wall-e/coding-prompts.js +51 -2
  127. package/template/wall-e/docs/model-routing-policy.md +59 -0
  128. package/template/wall-e/docs/walle-shell-sandbox.md +61 -0
  129. package/template/wall-e/extraction/knowledge-extractor.js +76 -23
  130. package/template/wall-e/http/chat-api.js +30 -12
  131. package/template/wall-e/http/model-admin.js +93 -1
  132. package/template/wall-e/lib/background-lanes.js +133 -0
  133. package/template/wall-e/lib/boot-profile.js +11 -0
  134. package/template/wall-e/lib/brain-owner-worker-client.js +324 -0
  135. package/template/wall-e/lib/brain-read-pool-client.js +311 -0
  136. package/template/wall-e/lib/diagnostics-flags.js +87 -0
  137. package/template/wall-e/lib/event-loop-monitor.js +74 -3
  138. package/template/wall-e/lib/mcp-integration.js +7 -1
  139. package/template/wall-e/lib/real-node.js +98 -0
  140. package/template/wall-e/lib/runtime-health.js +206 -0
  141. package/template/wall-e/lib/runtime-worker-pool.js +101 -0
  142. package/template/wall-e/lib/scheduler-worker-jobs.js +231 -0
  143. package/template/wall-e/lib/scheduler.js +446 -17
  144. package/template/wall-e/lib/service-health.js +61 -2
  145. package/template/wall-e/lib/service-readiness.js +258 -0
  146. package/template/wall-e/lib/usage.js +152 -0
  147. package/template/wall-e/lib/worker-thread-pool.js +389 -0
  148. package/template/wall-e/llm/client.js +81 -4
  149. package/template/wall-e/llm/default-fallback.js +54 -8
  150. package/template/wall-e/llm/mlx.js +536 -73
  151. package/template/wall-e/llm/mlx.plugin.json +1 -1
  152. package/template/wall-e/llm/ollama.js +342 -43
  153. package/template/wall-e/llm/provider-error.js +18 -1
  154. package/template/wall-e/llm/provider-health-state.js +176 -0
  155. package/template/wall-e/llm/routing-policy.js +796 -0
  156. package/template/wall-e/llm/supported-models.js +5 -0
  157. package/template/wall-e/loops/tasks.js +60 -14
  158. package/template/wall-e/loops/think.js +89 -24
  159. package/template/wall-e/mcp-server.js +192 -28
  160. package/template/wall-e/server.js +32 -7
  161. package/template/wall-e/skills/script-skill-runner.js +8 -1
  162. package/template/wall-e/skills/skill-planner.js +64 -1
  163. package/template/wall-e/tools/builtin-middleware.js +67 -2
  164. package/template/wall-e/tools/local-tools.js +116 -26
  165. package/template/wall-e/tools/permission-checker.js +52 -4
  166. package/template/wall-e/tools/permission-rules.js +36 -0
  167. package/template/wall-e/tools/shell-analyzer.js +46 -1
  168. package/template/wall-e/training/gemma-e4b-qlora.js +314 -0
  169. package/template/wall-e/training/real-trajectory-miner.js +2617 -0
  170. package/template/wall-e/training/replay-eval-analysis.js +151 -0
  171. package/template/wall-e/training/run-shell-command-selector.js +277 -0
  172. package/template/wall-e/training/tool-sft-dataset.js +312 -0
  173. package/template/wall-e/training/tool-sft-renderers.js +144 -0
  174. package/template/wall-e/training/tool-trace-harvester.js +1440 -0
  175. package/template/wall-e/training/trajectory-action-selector.js +364 -0
  176. package/template/wall-e/weather-runtime.js +232 -0
  177. package/template/wall-e/workers/brain-owner-worker.js +162 -0
  178. package/template/wall-e/workers/brain-read-worker.js +148 -0
  179. package/template/wall-e/workers/runtime-worker.js +145 -0
@@ -0,0 +1,312 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const VALID_SPLITS = new Set(['train', 'valid', 'calibration', 'ab_holdout', 'test']);
8
+
9
+ function stableStringify(value) {
10
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
11
+ if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';
12
+ return '{' + Object.keys(value).sort().map((key) => (
13
+ JSON.stringify(key) + ':' + stableStringify(value[key])
14
+ )).join(',') + '}';
15
+ }
16
+
17
+ function sha256(value) {
18
+ return crypto.createHash('sha256').update(String(value ?? '')).digest('hex');
19
+ }
20
+
21
+ function cloneJson(value) {
22
+ return JSON.parse(JSON.stringify(value));
23
+ }
24
+
25
+ function toolName(tool) {
26
+ if (!tool || typeof tool !== 'object') return null;
27
+ if (tool.type === 'function' && tool.function?.name) return String(tool.function.name);
28
+ if (tool.name) return String(tool.name);
29
+ return null;
30
+ }
31
+
32
+ function toolNameSet(tools = []) {
33
+ return new Set((tools || []).map(toolName).filter(Boolean));
34
+ }
35
+
36
+ function messageToolCalls(message = {}) {
37
+ if (Array.isArray(message.tool_calls)) return message.tool_calls;
38
+ if (Array.isArray(message.toolCalls)) return message.toolCalls;
39
+ return [];
40
+ }
41
+
42
+ function toolCallName(call = {}) {
43
+ if (call.function?.name) return String(call.function.name);
44
+ if (call.name) return String(call.name);
45
+ return null;
46
+ }
47
+
48
+ function validateToolCall(call, knownTools) {
49
+ const issues = [];
50
+ if (!call || typeof call !== 'object') {
51
+ issues.push('tool_call_not_object');
52
+ return issues;
53
+ }
54
+ const name = toolCallName(call);
55
+ if (!name) issues.push('tool_call_missing_name');
56
+ if (name && knownTools.size && !knownTools.has(name)) issues.push(`unknown_tool:${name}`);
57
+ const args = call.function?.arguments ?? call.arguments ?? call.args;
58
+ if (args === undefined) {
59
+ issues.push('tool_call_missing_arguments');
60
+ } else if (typeof args === 'string') {
61
+ try { JSON.parse(args); } catch { issues.push('tool_call_arguments_not_json'); }
62
+ } else if (args === null || typeof args !== 'object' || Array.isArray(args)) {
63
+ issues.push('tool_call_arguments_invalid');
64
+ }
65
+ return issues;
66
+ }
67
+
68
+ function validateCanonicalExample(example) {
69
+ const issues = [];
70
+ if (!example || typeof example !== 'object') return { ok: false, issues: ['example_not_object'] };
71
+
72
+ if (!Array.isArray(example.messages) || example.messages.length === 0) {
73
+ issues.push('missing_messages');
74
+ }
75
+ if (!Array.isArray(example.tools) || example.tools.length === 0) {
76
+ issues.push('missing_tools');
77
+ }
78
+ if (!example.tool_catalog || typeof example.tool_catalog !== 'string') {
79
+ issues.push('missing_tool_catalog');
80
+ }
81
+ if (!VALID_SPLITS.has(example.split)) {
82
+ issues.push('invalid_split');
83
+ }
84
+ if (typeof example.quality !== 'number' || example.quality < 0 || example.quality > 1) {
85
+ issues.push('invalid_quality');
86
+ }
87
+ if (!example.leakage || typeof example.leakage !== 'object') {
88
+ issues.push('missing_leakage');
89
+ }
90
+
91
+ const knownTools = toolNameSet(example.tools || []);
92
+ for (const [idx, message] of (example.messages || []).entries()) {
93
+ if (!message || typeof message !== 'object') {
94
+ issues.push(`message_${idx}_not_object`);
95
+ continue;
96
+ }
97
+ if (!['system', 'user', 'assistant', 'tool'].includes(message.role)) {
98
+ issues.push(`message_${idx}_invalid_role`);
99
+ }
100
+ const calls = messageToolCalls(message);
101
+ if (message.role === 'assistant' && calls.length > 0) {
102
+ for (const call of calls) issues.push(...validateToolCall(call, knownTools));
103
+ }
104
+ if (message.role === 'tool' && !message.tool_call_id && !message.toolCallId) {
105
+ issues.push(`message_${idx}_missing_tool_call_id`);
106
+ }
107
+ if (message.role !== 'assistant' && calls.length > 0) {
108
+ issues.push(`message_${idx}_tool_calls_on_${message.role}`);
109
+ }
110
+ }
111
+
112
+ return { ok: issues.length === 0, issues: [...new Set(issues)] };
113
+ }
114
+
115
+ function normalizeTool(tool) {
116
+ const name = toolName(tool);
117
+ if (!name) return tool;
118
+ if (tool.type === 'function' && tool.function) {
119
+ return {
120
+ type: 'function',
121
+ function: {
122
+ name,
123
+ description: String(tool.function.description || ''),
124
+ parameters: tool.function.parameters || { type: 'object', properties: {} },
125
+ },
126
+ };
127
+ }
128
+ return {
129
+ type: 'function',
130
+ function: {
131
+ name,
132
+ description: String(tool.description || ''),
133
+ parameters: tool.parameters || tool.input_schema || { type: 'object', properties: {} },
134
+ },
135
+ };
136
+ }
137
+
138
+ function normalizeToolCall(call) {
139
+ const name = toolCallName(call);
140
+ const rawArgs = call.function?.arguments ?? call.arguments ?? call.args ?? {};
141
+ let args = rawArgs;
142
+ if (typeof args !== 'string') args = JSON.stringify(args || {});
143
+ return {
144
+ id: call.id || `call_${sha256(`${name}:${args}`).slice(0, 8)}`,
145
+ type: 'function',
146
+ function: { name, arguments: args },
147
+ };
148
+ }
149
+
150
+ function normalizeMessage(message) {
151
+ const out = { role: message.role };
152
+ if (message.content !== undefined) out.content = String(message.content ?? '');
153
+ const calls = messageToolCalls(message);
154
+ if (calls.length) out.tool_calls = calls.map(normalizeToolCall);
155
+ if (message.tool_call_id || message.toolCallId) out.tool_call_id = message.tool_call_id || message.toolCallId;
156
+ return out;
157
+ }
158
+
159
+ function normalizeCanonicalExample(example) {
160
+ const normalized = cloneJson(example || {});
161
+ normalized.id = normalized.id || `tooluse-${sha256(stableStringify(normalized)).slice(0, 12)}`;
162
+ normalized.dataset_version = normalized.dataset_version || normalized.datasetVersion || 'gemma-e4b-tooluse-local';
163
+ normalized.split = normalized.split || 'train';
164
+ normalized.source = normalized.source || 'unknown';
165
+ normalized.task_type = normalized.task_type || normalized.taskType || 'coding-agent';
166
+ normalized.quality = Number.isFinite(normalized.quality) ? normalized.quality : 0.5;
167
+ normalized.tool_catalog = normalized.tool_catalog || normalized.toolCatalog || 'gemma-compact-v1';
168
+ normalized.messages = (normalized.messages || []).map(normalizeMessage);
169
+ normalized.tools = (normalized.tools || []).map(normalizeTool);
170
+ normalized.labels = normalized.labels || {};
171
+ normalized.leakage = {
172
+ holdout: Boolean(normalized.leakage?.holdout || normalized.split === 'ab_holdout'),
173
+ source_session_id: normalized.leakage?.source_session_id || normalized.source_session_id || null,
174
+ source_task_id: normalized.leakage?.source_task_id || normalized.source_task_id || null,
175
+ };
176
+ return normalized;
177
+ }
178
+
179
+ function leakagePayload(example) {
180
+ const normalized = normalizeCanonicalExample(example);
181
+ return {
182
+ source: normalized.source,
183
+ sourceSessionId: normalized.leakage?.source_session_id || null,
184
+ sourceTaskId: normalized.leakage?.source_task_id || null,
185
+ toolCatalog: normalized.tool_catalog,
186
+ messages: normalized.messages.map((message) => ({
187
+ role: message.role,
188
+ content: message.content || '',
189
+ tool_calls: (message.tool_calls || []).map((call) => ({
190
+ name: call.function?.name || '',
191
+ arguments: call.function?.arguments || '',
192
+ })),
193
+ tool_call_id: message.tool_call_id || '',
194
+ })),
195
+ };
196
+ }
197
+
198
+ function hashExampleForLeakage(example) {
199
+ return sha256(stableStringify(leakagePayload(example)));
200
+ }
201
+
202
+ function groupKeyForExample(example) {
203
+ const normalized = normalizeCanonicalExample(example);
204
+ return normalized.leakage?.source_session_id ||
205
+ normalized.leakage?.source_task_id ||
206
+ normalized.source_session_id ||
207
+ normalized.source_task_id ||
208
+ normalized.id;
209
+ }
210
+
211
+ function splitForHash(hash, ratios = {}) {
212
+ const train = ratios.train ?? 0.8;
213
+ const valid = ratios.valid ?? 0.1;
214
+ const calibration = ratios.calibration ?? 0.1;
215
+ const n = parseInt(hash.slice(0, 8), 16) / 0xffffffff;
216
+ if (n < train) return 'train';
217
+ if (n < train + valid) return 'valid';
218
+ if (n < train + valid + calibration) return 'calibration';
219
+ return 'ab_holdout';
220
+ }
221
+
222
+ function splitExamplesBySource(examples = [], ratios = {}, seed = 'tooluse-v1') {
223
+ const out = { train: [], valid: [], calibration: [], ab_holdout: [] };
224
+ const groupToSplit = new Map();
225
+ for (const input of examples) {
226
+ const example = normalizeCanonicalExample(input);
227
+ const groupKey = groupKeyForExample(example);
228
+ if (!groupToSplit.has(groupKey)) {
229
+ groupToSplit.set(groupKey, splitForHash(sha256(`${seed}:${groupKey}`), ratios));
230
+ }
231
+ const split = groupToSplit.get(groupKey);
232
+ example.split = split;
233
+ example.leakage.holdout = split === 'ab_holdout';
234
+ out[split].push(example);
235
+ }
236
+ return out;
237
+ }
238
+
239
+ function asArray(value) {
240
+ if (!value) return [];
241
+ if (Array.isArray(value)) return value;
242
+ return Object.values(value).flat();
243
+ }
244
+
245
+ function checkHoldoutLeakage({ train = [], valid = [], calibration = [], holdout = [], ab_holdout = [] } = {}) {
246
+ const holdoutRows = [...asArray(holdout), ...asArray(ab_holdout)];
247
+ const holdoutHashes = new Set(holdoutRows.map(hashExampleForLeakage));
248
+ const leaked = [];
249
+ for (const [split, rows] of Object.entries({ train, valid, calibration })) {
250
+ for (const row of asArray(rows)) {
251
+ const hash = hashExampleForLeakage(row);
252
+ if (holdoutHashes.has(hash)) leaked.push({ split, id: row.id || null, hash });
253
+ }
254
+ }
255
+ return { ok: leaked.length === 0, leaked };
256
+ }
257
+
258
+ function buildDatasetManifest({ examples = [], splits = null, config = {} } = {}) {
259
+ const all = splits ? asArray(splits) : examples.map(normalizeCanonicalExample);
260
+ const bySplit = {};
261
+ const bySource = {};
262
+ for (const example of all) {
263
+ bySplit[example.split] = (bySplit[example.split] || 0) + 1;
264
+ bySource[example.source] = (bySource[example.source] || 0) + 1;
265
+ }
266
+ const datasetHash = sha256(stableStringify(all.map((example) => ({
267
+ id: example.id,
268
+ split: example.split,
269
+ leakageHash: hashExampleForLeakage(example),
270
+ }))));
271
+ return {
272
+ datasetVersion: config.datasetVersion || 'gemma-e4b-tooluse-v1',
273
+ datasetHash,
274
+ createdAt: config.createdAt || new Date().toISOString(),
275
+ total: all.length,
276
+ bySplit,
277
+ bySource,
278
+ config: { ...config, createdAt: undefined },
279
+ };
280
+ }
281
+
282
+ function writeJsonl(filePath, rows) {
283
+ const lines = rows.map((row) => JSON.stringify(row));
284
+ fs.writeFileSync(filePath, lines.join('\n') + (lines.length ? '\n' : ''), 'utf8');
285
+ }
286
+
287
+ function writeDatasetFiles({ outputDir, examples = [], splits = null, manifest = null } = {}) {
288
+ if (!outputDir) throw new Error('outputDir is required');
289
+ fs.mkdirSync(outputDir, { recursive: true });
290
+ const splitMap = splits || splitExamplesBySource(examples);
291
+ const all = asArray(splitMap).map(normalizeCanonicalExample);
292
+ writeJsonl(path.join(outputDir, 'canonical.jsonl'), all);
293
+ for (const [split, rows] of Object.entries(splitMap)) {
294
+ writeJsonl(path.join(outputDir, `${split}.jsonl`), rows.map(normalizeCanonicalExample));
295
+ }
296
+ const finalManifest = manifest || buildDatasetManifest({ splits: splitMap });
297
+ fs.writeFileSync(path.join(outputDir, 'manifest.json'), JSON.stringify(finalManifest, null, 2) + '\n', 'utf8');
298
+ return { outputDir, manifest: finalManifest, splits: splitMap };
299
+ }
300
+
301
+ module.exports = {
302
+ VALID_SPLITS,
303
+ stableStringify,
304
+ sha256,
305
+ validateCanonicalExample,
306
+ normalizeCanonicalExample,
307
+ hashExampleForLeakage,
308
+ splitExamplesBySource,
309
+ checkHoldoutLeakage,
310
+ buildDatasetManifest,
311
+ writeDatasetFiles,
312
+ };
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { normalizeCanonicalExample } = require('./tool-sft-dataset');
6
+ const { prepareMessagesForMlxProvider } = require('../llm/mlx');
7
+
8
+ const RENDER_FORMATS = new Set(['mlx-tools', 'mlx-provider', 'mlx-provider-bare', 'mlx-provider-state-bare', 'trl-tools', 'text']);
9
+
10
+ function cloneJson(value) {
11
+ return JSON.parse(JSON.stringify(value));
12
+ }
13
+
14
+ function compactTool(tool) {
15
+ const out = cloneJson(tool);
16
+ if (out.function) {
17
+ out.function.description = firstSentence(out.function.description || '');
18
+ } else if (out.description) {
19
+ out.description = firstSentence(out.description);
20
+ }
21
+ return out;
22
+ }
23
+
24
+ function firstSentence(text) {
25
+ const normalized = String(text || '').replace(/\s+/g, ' ').trim();
26
+ if (!normalized) return '';
27
+ const idx = normalized.search(/[.!?](\s|$)/);
28
+ return idx >= 0 ? normalized.slice(0, idx + 1) : normalized;
29
+ }
30
+
31
+ function normalizeArgumentsForBackend(messages, { objectArguments = false } = {}) {
32
+ return (messages || []).map((message) => {
33
+ const out = cloneJson(message);
34
+ if (!Array.isArray(out.tool_calls)) return out;
35
+ out.tool_calls = out.tool_calls.map((call) => {
36
+ const next = cloneJson(call);
37
+ const raw = next.function?.arguments;
38
+ if (objectArguments && typeof raw === 'string') {
39
+ try { next.function.arguments = JSON.parse(raw); } catch { /* leave string */ }
40
+ } else if (!objectArguments && raw !== undefined && typeof raw !== 'string') {
41
+ next.function.arguments = JSON.stringify(raw || {});
42
+ }
43
+ return next;
44
+ });
45
+ return out;
46
+ });
47
+ }
48
+
49
+ function renderToolResultAsMlxProviderMessage(message = {}) {
50
+ if (message.role !== 'tool') return message;
51
+ const id = message.tool_call_id || message.toolCallId || 'tool';
52
+ return {
53
+ role: 'user',
54
+ content: `[Tool result for ${id}]: ${message.content || ''}`,
55
+ };
56
+ }
57
+
58
+ function renderMlxToolsExample(example, opts = {}) {
59
+ const normalized = normalizeCanonicalExample(example);
60
+ return {
61
+ messages: normalizeArgumentsForBackend(
62
+ normalized.messages.map(renderToolResultAsMlxProviderMessage),
63
+ { objectArguments: !!opts.objectArguments }
64
+ ),
65
+ tools: opts.compactTools ? normalized.tools.map(compactTool) : cloneJson(normalized.tools),
66
+ };
67
+ }
68
+
69
+ function renderMlxProviderExample(example, opts = {}) {
70
+ const normalized = normalizeCanonicalExample(example);
71
+ const tools = opts.compactTools ? normalized.tools.map(compactTool) : normalized.tools;
72
+ return {
73
+ messages: prepareMessagesForMlxProvider({
74
+ messages: normalized.messages,
75
+ tools,
76
+ compactDuplicates: true,
77
+ toolCallFormat: opts.toolCallFormat || 'fenced',
78
+ }),
79
+ };
80
+ }
81
+
82
+ function renderTrlToolsExample(example, opts = {}) {
83
+ const normalized = normalizeCanonicalExample(example);
84
+ return {
85
+ messages: normalizeArgumentsForBackend(normalized.messages, { objectArguments: !!opts.objectArguments }),
86
+ tools: opts.compactTools ? normalized.tools.map(compactTool) : cloneJson(normalized.tools),
87
+ };
88
+ }
89
+
90
+ function renderTextFallbackExample(example) {
91
+ const normalized = normalizeCanonicalExample(example);
92
+ const lines = [];
93
+ for (const message of normalized.messages) {
94
+ if (message.role === 'assistant' && Array.isArray(message.tool_calls) && message.tool_calls.length) {
95
+ lines.push('assistant tool_calls:');
96
+ for (const call of message.tool_calls) {
97
+ lines.push(`- ${call.function?.name || ''} ${call.function?.arguments || '{}'}`);
98
+ }
99
+ continue;
100
+ }
101
+ if (message.role === 'tool') {
102
+ lines.push(`tool ${message.tool_call_id || ''}: ${message.content || ''}`);
103
+ continue;
104
+ }
105
+ lines.push(`${message.role}: ${message.content || ''}`);
106
+ }
107
+ return { text: lines.join('\n') };
108
+ }
109
+
110
+ function renderExample(example, format, opts = {}) {
111
+ if (format === 'mlx-tools') return renderMlxToolsExample(example, opts);
112
+ if (format === 'mlx-provider') return renderMlxProviderExample(example, opts);
113
+ if (format === 'mlx-provider-bare') return renderMlxProviderExample(example, { ...opts, toolCallFormat: 'bare-json' });
114
+ if (format === 'mlx-provider-state-bare') return renderMlxProviderExample(example, { ...opts, toolCallFormat: 'bare-json' });
115
+ if (format === 'trl-tools') return renderTrlToolsExample(example, opts);
116
+ if (format === 'text') return renderTextFallbackExample(example, opts);
117
+ throw new Error(`Unsupported render format: ${format}`);
118
+ }
119
+
120
+ function writeJsonl(filePath, rows) {
121
+ const lines = rows.map((row) => JSON.stringify(row));
122
+ fs.writeFileSync(filePath, lines.join('\n') + (lines.length ? '\n' : ''), 'utf8');
123
+ }
124
+
125
+ function writeRenderedSplit({ examples = [], outputDir, format = 'mlx-tools', splitName = 'train', compactTools = true } = {}) {
126
+ if (!RENDER_FORMATS.has(format)) throw new Error(`Unsupported render format: ${format}`);
127
+ if (!outputDir) throw new Error('outputDir is required');
128
+ fs.mkdirSync(outputDir, { recursive: true });
129
+ const filePath = path.join(outputDir, `${splitName}.jsonl`);
130
+ const rows = examples.map((example) => renderExample(example, format, { compactTools }));
131
+ writeJsonl(filePath, rows);
132
+ return { filePath, count: rows.length };
133
+ }
134
+
135
+ module.exports = {
136
+ RENDER_FORMATS,
137
+ renderMlxToolsExample,
138
+ renderMlxProviderExample,
139
+ renderTrlToolsExample,
140
+ renderExample,
141
+ renderTextFallbackExample,
142
+ writeRenderedSplit,
143
+ _private: { compactTool, normalizeArgumentsForBackend, renderToolResultAsMlxProviderMessage },
144
+ };