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,252 @@
1
+ 'use strict';
2
+
3
+ const RECORD_TYPES = Object.freeze({
4
+ SOURCE_ITEM: 'source_item',
5
+ MEMORY_RECORD: 'memory_record',
6
+ SOURCE_SUMMARY: 'source_summary',
7
+ });
8
+
9
+ const INGEST_MODES = Object.freeze({
10
+ CHUNKED_CONTENT: 'chunked_content',
11
+ WHOLE_RECORD: 'whole_record',
12
+ METADATA_ONLY: 'metadata_only',
13
+ });
14
+
15
+ const PRIVACY_CLASSES = Object.freeze({
16
+ PUBLIC: 'public',
17
+ INTERNAL: 'internal',
18
+ PII_POTENTIAL: 'pii_potential',
19
+ SENSITIVE: 'sensitive',
20
+ SECRETS_POSSIBLE: 'secrets_possible',
21
+ });
22
+
23
+ const PRIVACY_CLASS_ORDER = Object.freeze([
24
+ PRIVACY_CLASSES.PUBLIC,
25
+ PRIVACY_CLASSES.INTERNAL,
26
+ PRIVACY_CLASSES.PII_POTENTIAL,
27
+ PRIVACY_CLASSES.SENSITIVE,
28
+ PRIVACY_CLASSES.SECRETS_POSSIBLE,
29
+ ]);
30
+
31
+ class SourceValidationError extends Error {
32
+ constructor(message, { errors = [], record = null } = {}) {
33
+ super(message);
34
+ this.name = 'SourceValidationError';
35
+ this.errors = errors;
36
+ this.record = record;
37
+ }
38
+ }
39
+
40
+ function validateSourceRef(ref = {}) {
41
+ const errors = [];
42
+ if (!ref || typeof ref !== 'object') errors.push('source ref must be an object');
43
+ if (!nonEmpty(ref.adapterId)) errors.push('adapterId is required');
44
+ if (!nonEmpty(ref.uri) && !nonEmpty(ref.sourceFile)) errors.push('uri or sourceFile is required');
45
+ if (ref.sourceId != null && !nonEmpty(ref.sourceId)) errors.push('sourceId must be a non-empty string when provided');
46
+ if (ref.privacyClass != null && !isPrivacyClass(ref.privacyClass)) {
47
+ errors.push(`privacyClass must be one of: ${PRIVACY_CLASS_ORDER.join(', ')}`);
48
+ }
49
+ if (ref.metadata != null && !plainObject(ref.metadata)) errors.push('metadata must be an object when provided');
50
+ return result(errors, normalizeSourceRef(ref));
51
+ }
52
+
53
+ function normalizeSourceRef(ref = {}) {
54
+ const uri = ref.uri || ref.sourceFile || '';
55
+ return {
56
+ adapterId: String(ref.adapterId || ''),
57
+ uri: String(uri || ''),
58
+ sourceFile: ref.sourceFile || uri || '',
59
+ sourceId: ref.sourceId || `${ref.adapterId || 'source'}:${uri || 'unknown'}`,
60
+ cwd: ref.cwd || '',
61
+ privacyClass: ref.privacyClass || PRIVACY_CLASSES.PII_POTENTIAL,
62
+ metadata: plainObject(ref.metadata) ? { ...ref.metadata } : {},
63
+ };
64
+ }
65
+
66
+ function validateAdapterSchema(schema = {}) {
67
+ const errors = [];
68
+ if (!schema || typeof schema !== 'object') errors.push('adapter schema must be an object');
69
+ if (!nonEmpty(schema.adapterId)) errors.push('adapterId is required');
70
+ if (!nonEmpty(schema.version)) errors.push('version is required');
71
+ if (schema.defaultPrivacyClass != null && !isPrivacyClass(schema.defaultPrivacyClass)) {
72
+ errors.push(`defaultPrivacyClass must be one of: ${PRIVACY_CLASS_ORDER.join(', ')}`);
73
+ }
74
+ if (schema.declaredTransformations != null && !Array.isArray(schema.declaredTransformations)) {
75
+ errors.push('declaredTransformations must be an array');
76
+ }
77
+ if (schema.fields != null && !plainObject(schema.fields)) errors.push('fields must be an object when provided');
78
+ return result(errors, {
79
+ adapterId: schema.adapterId || '',
80
+ version: schema.version || '',
81
+ supportsIncremental: Boolean(schema.supportsIncremental),
82
+ defaultPrivacyClass: schema.defaultPrivacyClass || PRIVACY_CLASSES.PII_POTENTIAL,
83
+ declaredTransformations: normalizeTransformList(schema.declaredTransformations || []),
84
+ fields: plainObject(schema.fields) ? { ...schema.fields } : {},
85
+ capabilities: plainObject(schema.capabilities) ? { ...schema.capabilities } : {},
86
+ });
87
+ }
88
+
89
+ function validateSourceItemMetadata(record = {}) {
90
+ const errors = [];
91
+ if (!plainObject(record)) errors.push('source item must be an object');
92
+ if (record.type !== RECORD_TYPES.SOURCE_ITEM) errors.push(`type must be ${RECORD_TYPES.SOURCE_ITEM}`);
93
+ validateCommonSourceRecord(record, errors);
94
+ if (!nonEmpty(record.version)) errors.push('version is required');
95
+ if (record.ingestMode != null && !isIngestMode(record.ingestMode)) {
96
+ errors.push(`ingestMode must be one of: ${Object.values(INGEST_MODES).join(', ')}`);
97
+ }
98
+ return result(errors, {
99
+ ...record,
100
+ ingestMode: record.ingestMode || INGEST_MODES.WHOLE_RECORD,
101
+ privacyClass: record.privacyClass || PRIVACY_CLASSES.PII_POTENTIAL,
102
+ metadata: normalizeMetadata(record.metadata),
103
+ });
104
+ }
105
+
106
+ function validateMemoryRecord(record = {}, schema = null) {
107
+ const errors = [];
108
+ if (!plainObject(record)) errors.push('memory record must be an object');
109
+ if (record.type !== RECORD_TYPES.MEMORY_RECORD) errors.push(`type must be ${RECORD_TYPES.MEMORY_RECORD}`);
110
+ validateCommonSourceRecord(record, errors);
111
+ if (!nonEmpty(record.memoryType)) errors.push('memoryType is required');
112
+ if (!nonEmpty(record.content) && !nonEmpty(record.contentRaw)) errors.push('content or contentRaw is required');
113
+ if (record.content != null && typeof record.content !== 'string') errors.push('content must be a string');
114
+ if (record.contentRaw != null && typeof record.contentRaw !== 'string') errors.push('contentRaw must be a string');
115
+ if (record.transforms != null && !Array.isArray(record.transforms)) errors.push('transforms must be an array');
116
+ if (record.ingestMode != null && !isIngestMode(record.ingestMode)) {
117
+ errors.push(`ingestMode must be one of: ${Object.values(INGEST_MODES).join(', ')}`);
118
+ }
119
+ if (schema) errors.push(...undeclaredTransformErrors(record, schema));
120
+ return result(errors, {
121
+ ...record,
122
+ content: record.content == null ? record.contentRaw : record.content,
123
+ contentRaw: record.contentRaw == null ? record.content : record.contentRaw,
124
+ transforms: normalizeTransformList(record.transforms || []),
125
+ ingestMode: record.ingestMode || INGEST_MODES.WHOLE_RECORD,
126
+ privacyClass: record.privacyClass || PRIVACY_CLASSES.PII_POTENTIAL,
127
+ metadata: normalizeMetadata(record.metadata),
128
+ });
129
+ }
130
+
131
+ function validateSourceSummary(record = {}) {
132
+ const errors = [];
133
+ if (!plainObject(record)) errors.push('source summary must be an object');
134
+ if (record.type !== RECORD_TYPES.SOURCE_SUMMARY) errors.push(`type must be ${RECORD_TYPES.SOURCE_SUMMARY}`);
135
+ validateCommonSourceRecord(record, errors, { itemIdRequired: false });
136
+ if (!nonEmpty(record.summary)) errors.push('summary is required');
137
+ return result(errors, {
138
+ ...record,
139
+ privacyClass: record.privacyClass || PRIVACY_CLASSES.PII_POTENTIAL,
140
+ metadata: normalizeMetadata(record.metadata),
141
+ });
142
+ }
143
+
144
+ function validateIngestRecord(record = {}, schema = null) {
145
+ if (record?.type === RECORD_TYPES.SOURCE_ITEM) return validateSourceItemMetadata(record);
146
+ if (record?.type === RECORD_TYPES.MEMORY_RECORD) return validateMemoryRecord(record, schema);
147
+ if (record?.type === RECORD_TYPES.SOURCE_SUMMARY) return validateSourceSummary(record);
148
+ return result([`unknown source record type: ${record?.type || '(missing)'}`], record);
149
+ }
150
+
151
+ function assertValid(validation, message = 'invalid source record') {
152
+ if (!validation.ok) {
153
+ throw new SourceValidationError(`${message}: ${validation.errors.join('; ')}`, {
154
+ errors: validation.errors,
155
+ record: validation.value,
156
+ });
157
+ }
158
+ return validation.value;
159
+ }
160
+
161
+ function assertIngestRecord(record, schema = null) {
162
+ return assertValid(validateIngestRecord(record, schema), 'invalid ingest record');
163
+ }
164
+
165
+ function assertAdapterSchema(schema) {
166
+ return assertValid(validateAdapterSchema(schema), 'invalid adapter schema');
167
+ }
168
+
169
+ function assertSourceRef(ref) {
170
+ return assertValid(validateSourceRef(ref), 'invalid source ref');
171
+ }
172
+
173
+ function undeclaredTransformErrors(record = {}, schema = {}) {
174
+ const declared = new Set(normalizeTransformList(schema.declaredTransformations || []));
175
+ const transforms = normalizeTransformList(record.transforms || []);
176
+ return transforms
177
+ .filter((name) => !declared.has(name))
178
+ .map((name) => `transform is not declared by adapter ${schema.adapterId || '(unknown)'}: ${name}`);
179
+ }
180
+
181
+ function validateCommonSourceRecord(record, errors, { itemIdRequired = true } = {}) {
182
+ if (!nonEmpty(record.sourceId)) errors.push('sourceId is required');
183
+ if (!nonEmpty(record.sourceFile)) errors.push('sourceFile is required');
184
+ if (itemIdRequired && !nonEmpty(record.itemId)) errors.push('itemId is required');
185
+ if (record.timestamp != null && !validDate(record.timestamp)) errors.push('timestamp must be parseable when provided');
186
+ if (record.privacyClass != null && !isPrivacyClass(record.privacyClass)) {
187
+ errors.push(`privacyClass must be one of: ${PRIVACY_CLASS_ORDER.join(', ')}`);
188
+ }
189
+ if (record.metadata != null && !plainObject(record.metadata)) errors.push('metadata must be an object when provided');
190
+ }
191
+
192
+ function normalizeTransformList(list) {
193
+ return [...new Set((list || [])
194
+ .filter((name) => typeof name === 'string')
195
+ .map((name) => name.trim())
196
+ .filter(Boolean))];
197
+ }
198
+
199
+ function normalizeMetadata(metadata) {
200
+ return plainObject(metadata) ? { ...metadata } : {};
201
+ }
202
+
203
+ function privacyClassAllows({ recordClass, floor }) {
204
+ if (!floor) return true;
205
+ if (!isPrivacyClass(recordClass) || !isPrivacyClass(floor)) return false;
206
+ return PRIVACY_CLASS_ORDER.indexOf(recordClass) <= PRIVACY_CLASS_ORDER.indexOf(floor);
207
+ }
208
+
209
+ function result(errors, value) {
210
+ return { ok: errors.length === 0, errors, value };
211
+ }
212
+
213
+ function isIngestMode(value) {
214
+ return Object.values(INGEST_MODES).includes(value);
215
+ }
216
+
217
+ function isPrivacyClass(value) {
218
+ return PRIVACY_CLASS_ORDER.includes(value);
219
+ }
220
+
221
+ function nonEmpty(value) {
222
+ return typeof value === 'string' && value.trim().length > 0;
223
+ }
224
+
225
+ function plainObject(value) {
226
+ return !!value && typeof value === 'object' && !Array.isArray(value);
227
+ }
228
+
229
+ function validDate(value) {
230
+ return typeof value === 'string' && !Number.isNaN(new Date(value).getTime());
231
+ }
232
+
233
+ module.exports = {
234
+ INGEST_MODES,
235
+ PRIVACY_CLASSES,
236
+ PRIVACY_CLASS_ORDER,
237
+ RECORD_TYPES,
238
+ SourceValidationError,
239
+ assertAdapterSchema,
240
+ assertIngestRecord,
241
+ assertSourceRef,
242
+ assertValid,
243
+ normalizeSourceRef,
244
+ normalizeTransformList,
245
+ privacyClassAllows,
246
+ validateAdapterSchema,
247
+ validateIngestRecord,
248
+ validateMemoryRecord,
249
+ validateSourceItemMetadata,
250
+ validateSourceRef,
251
+ validateSourceSummary,
252
+ };
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ const { Registry } = require('../plugins/registry-base');
4
+ const { SourceAdapterBase } = require('./base');
5
+ const { assertAdapterSchema } = require('./record-types');
6
+
7
+ const _registry = new Registry('source-adapter');
8
+
9
+ function register(entry, opts = {}) {
10
+ const normalized = normalizeEntry(entry);
11
+ return _registry.register(normalized, opts);
12
+ }
13
+
14
+ function normalizeEntry(entry) {
15
+ if (typeof entry === 'function') {
16
+ const instance = new entry();
17
+ return {
18
+ id: instance.schema.adapterId,
19
+ adapterClass: entry,
20
+ schema: instance.schema,
21
+ source: 'class',
22
+ };
23
+ }
24
+ if (entry instanceof SourceAdapterBase) {
25
+ return {
26
+ id: entry.schema.adapterId,
27
+ adapter: entry,
28
+ schema: entry.schema,
29
+ source: 'instance',
30
+ };
31
+ }
32
+ if (entry && typeof entry === 'object') {
33
+ const schema = assertAdapterSchema(entry.schema || entry.manifest || entry);
34
+ return {
35
+ ...entry,
36
+ id: entry.id || schema.adapterId,
37
+ schema,
38
+ };
39
+ }
40
+ throw new Error('source adapter registry entry must be a class, instance, or object');
41
+ }
42
+
43
+ function instantiate(id, opts = {}) {
44
+ const entry = _registry.get(id);
45
+ if (!entry) return null;
46
+ if (entry.adapter) return entry.adapter;
47
+ if (entry.adapterClass) return new entry.adapterClass(opts);
48
+ if (typeof entry.factory === 'function') return entry.factory(opts);
49
+ return null;
50
+ }
51
+
52
+ function get(id) {
53
+ return _registry.get(id) || null;
54
+ }
55
+
56
+ function has(id) {
57
+ return _registry.has(id);
58
+ }
59
+
60
+ function list(filter = {}) {
61
+ let entries = _registry.list();
62
+ if (filter.privacyClass) {
63
+ entries = entries.filter((entry) => entry.schema.defaultPrivacyClass === filter.privacyClass);
64
+ }
65
+ if (filter.incremental != null) {
66
+ entries = entries.filter((entry) => Boolean(entry.schema.supportsIncremental) === Boolean(filter.incremental));
67
+ }
68
+ return entries;
69
+ }
70
+
71
+ function ids(filter = {}) {
72
+ return list(filter).map((entry) => entry.id);
73
+ }
74
+
75
+ function size() {
76
+ return _registry.size();
77
+ }
78
+
79
+ function clear() {
80
+ _registry.clear();
81
+ }
82
+
83
+ module.exports = {
84
+ clear,
85
+ get,
86
+ has,
87
+ ids,
88
+ instantiate,
89
+ list,
90
+ register,
91
+ size,
92
+ };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ let stripNoise;
6
+ try {
7
+ stripNoise = require('../extraction/noise-stripper').stripNoise;
8
+ } catch {
9
+ stripNoise = (value) => value;
10
+ }
11
+
12
+ const BASE64_BLOCK_RE = /\b[A-Za-z0-9+/]{512,}={0,2}\b/g;
13
+
14
+ const RESERVED_TRANSFORMATIONS = Object.freeze({
15
+ utf8_replace_invalid: {
16
+ name: 'utf8_replace_invalid',
17
+ description: 'Decode bytes as UTF-8 and replace invalid sequences.',
18
+ apply(value) {
19
+ if (Buffer.isBuffer(value)) return value.toString('utf8');
20
+ return String(value ?? '');
21
+ },
22
+ },
23
+ newline_normalize: {
24
+ name: 'newline_normalize',
25
+ description: 'Normalize CRLF/CR newlines to LF.',
26
+ apply(value) {
27
+ return String(value ?? '').replace(/\r\n?/g, '\n');
28
+ },
29
+ },
30
+ whitespace_trim: {
31
+ name: 'whitespace_trim',
32
+ description: 'Trim leading and trailing whitespace.',
33
+ apply(value) {
34
+ return String(value ?? '').trim();
35
+ },
36
+ },
37
+ strip_tool_chrome: {
38
+ name: 'strip_tool_chrome',
39
+ description: 'Remove Claude/Codex/Wall-E tool wrapper chrome from searchable text.',
40
+ apply(value) {
41
+ return stripNoise(String(value ?? ''));
42
+ },
43
+ },
44
+ tool_result_summarize: {
45
+ name: 'tool_result_summarize',
46
+ description: 'Bound very large tool results while keeping their existence visible.',
47
+ apply(value, { maxToolResultChars = 4000 } = {}) {
48
+ const text = String(value ?? '');
49
+ if (text.length <= maxToolResultChars) return text;
50
+ return `${text.slice(0, maxToolResultChars)}\n[tool result truncated: ${text.length - maxToolResultChars} chars omitted]`;
51
+ },
52
+ },
53
+ base64_payload_omit: {
54
+ name: 'base64_payload_omit',
55
+ description: 'Replace large inline base64 payloads with deterministic hashes.',
56
+ apply(value) {
57
+ return String(value ?? '').replace(BASE64_BLOCK_RE, (match) => {
58
+ const hash = crypto.createHash('sha256').update(match).digest('hex').slice(0, 16);
59
+ return `[base64 omitted sha256:${hash} bytes:${Buffer.byteLength(match, 'utf8')}]`;
60
+ });
61
+ },
62
+ },
63
+ exchange_pair_chunk: {
64
+ name: 'exchange_pair_chunk',
65
+ description: 'Content was formed by pairing adjacent user and assistant turns.',
66
+ apply(value) {
67
+ return String(value ?? '');
68
+ },
69
+ },
70
+ });
71
+
72
+ function getTransformation(name) {
73
+ return RESERVED_TRANSFORMATIONS[name] || null;
74
+ }
75
+
76
+ function hasTransformation(name) {
77
+ return !!getTransformation(name);
78
+ }
79
+
80
+ function listReservedTransformations() {
81
+ return Object.keys(RESERVED_TRANSFORMATIONS);
82
+ }
83
+
84
+ function applyTransforms(value, transforms = [], context = {}) {
85
+ let current = value;
86
+ for (const name of transforms || []) {
87
+ const transform = getTransformation(name);
88
+ if (!transform) throw new Error(`Unknown reserved transformation: ${name}`);
89
+ current = transform.apply(current, context);
90
+ }
91
+ return current;
92
+ }
93
+
94
+ module.exports = {
95
+ RESERVED_TRANSFORMATIONS,
96
+ applyTransforms,
97
+ getTransformation,
98
+ hasTransformation,
99
+ listReservedTransformations,
100
+ };
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const { SourceAdapterBase } = require('./base');
4
+ const {
5
+ cleanUserText,
6
+ extractMessageText,
7
+ fileVersion,
8
+ looksLikeToolResult,
9
+ readJsonlFile,
10
+ sourceIdFromFile,
11
+ summarizeToolInput,
12
+ toIso,
13
+ } = require('./jsonl-utils');
14
+ const { buildExchangeRecords, memoryRecord, messageToRecords, sourceItem } = require('./coding-session-utils');
15
+
16
+ class WalleJsonlAdapter extends SourceAdapterBase {
17
+ static schema = {
18
+ adapterId: 'walle-jsonl',
19
+ version: '1.0.0',
20
+ supportsIncremental: true,
21
+ defaultPrivacyClass: 'pii_potential',
22
+ declaredTransformations: [
23
+ 'newline_normalize',
24
+ 'strip_tool_chrome',
25
+ 'base64_payload_omit',
26
+ 'tool_result_summarize',
27
+ 'whitespace_trim',
28
+ 'exchange_pair_chunk',
29
+ ],
30
+ fields: {
31
+ sourceId: { type: 'string', required: true },
32
+ sourceFile: { type: 'string', required: true },
33
+ cwd: { type: 'string', required: false },
34
+ gitBranch: { type: 'string', required: false },
35
+ },
36
+ };
37
+
38
+ validateSource(source) {
39
+ const normalized = super.validateSource(source);
40
+ if (!source.sourceId) normalized.sourceId = sourceIdFromFile('walle', normalized.sourceFile || normalized.uri);
41
+ return normalized;
42
+ }
43
+
44
+ async *ingest({ source, context }) {
45
+ const filePath = source.sourceFile || source.uri;
46
+ const version = fileVersion(filePath);
47
+ const { events, diagnostics } = readJsonlFile(filePath);
48
+ for (const diagnostic of diagnostics) context?.warn?.(diagnostic.message, diagnostic);
49
+
50
+ let cwd = source.cwd || '';
51
+ let gitBranch = source.metadata?.gitBranch || '';
52
+ const messages = [];
53
+
54
+ for (const { line, event } of events) {
55
+ if (event.type === 'session_meta' || event.type === 'session_start') {
56
+ cwd = event.cwd || event.data?.cwd || cwd;
57
+ gitBranch = event.gitBranch || gitBranch;
58
+ continue;
59
+ }
60
+ if (event.type === 'user' || event.type === 'assistant') {
61
+ const role = event.type;
62
+ const content = role === 'user'
63
+ ? cleanUserText(extractMessageText(event.message || event))
64
+ : extractMessageText(event.message || event);
65
+ if (!content || looksLikeToolResult(content)) continue;
66
+ const message = {
67
+ itemId: event.uuid || `line-${line}`,
68
+ role,
69
+ content,
70
+ timestamp: toIso(event.timestamp),
71
+ cwd: event.cwd || cwd,
72
+ gitBranch: event.gitBranch || gitBranch,
73
+ rawType: event.type,
74
+ harness: 'walle',
75
+ toolCalls: [],
76
+ };
77
+ messages.push(message);
78
+ for (const record of messageToRecords({ source, message, version })) yield record;
79
+ continue;
80
+ }
81
+ if (event.type === 'walle_part' && (event.partType === 'tool_call' || event.partType === 'tool')) {
82
+ const data = event.data || {};
83
+ const name = data.name || data.tool || 'tool';
84
+ const itemId = event.uuid || `tool-${line}`;
85
+ yield sourceItem({
86
+ source,
87
+ itemId,
88
+ version,
89
+ timestamp: event.timestamp,
90
+ metadata: { role: 'tool', cwd, gitBranch, name, input: data.input || {} },
91
+ });
92
+ yield memoryRecord({
93
+ source,
94
+ itemId: `${itemId}:tool`,
95
+ memoryType: 'coding_session_tool_call',
96
+ role: 'tool',
97
+ contentRaw: `[Tool: ${name}] ${summarizeToolInput(name, data.input || {})}`,
98
+ timestamp: event.timestamp,
99
+ metadata: { cwd, gitBranch, name, input: data.input || {} },
100
+ });
101
+ }
102
+ }
103
+
104
+ for (const record of buildExchangeRecords(source, messages)) yield record;
105
+ }
106
+ }
107
+
108
+ module.exports = WalleJsonlAdapter;
@@ -25,9 +25,10 @@ const HOOK_TYPES = new Set([
25
25
  ]);
26
26
 
27
27
  class CodingMiddleware {
28
- constructor() {
28
+ constructor({ hookRuntime = null } = {}) {
29
29
  this._hooks = {};
30
30
  for (const type of HOOK_TYPES) this._hooks[type] = [];
31
+ this.hookRuntime = hookRuntime;
31
32
 
32
33
  // Config notification
33
34
  // Ported from OpenCode plugin/index.ts — hook.config?.() pattern (lines 237-245)
@@ -43,6 +44,10 @@ class CodingMiddleware {
43
44
  this._hooks[hookName].push(fn);
44
45
  }
45
46
 
47
+ attachHookRuntime(hookRuntime) {
48
+ this.hookRuntime = hookRuntime;
49
+ }
50
+
46
51
  /**
47
52
  * Run all hooks for a given type.
48
53
  * - For context-mutating hooks (llm.before, llm.after): hooks mutate ctx in place
@@ -75,6 +80,31 @@ class CodingMiddleware {
75
80
  console.error(`[middleware] Error in ${hookName} hook:`, err.message);
76
81
  }
77
82
  }
83
+ return this._runExternalHook(hookName, args, result);
84
+ }
85
+
86
+ async _runExternalHook(hookName, args, result) {
87
+ if (!this.hookRuntime?.trigger) return result;
88
+ if (hookName === 'llm.before') {
89
+ const payload = await this.hookRuntime.trigger('llm_input', { context: args[0] || {} }, { hookName });
90
+ return payload?.context || result;
91
+ }
92
+ if (hookName === 'llm.after') {
93
+ const payload = await this.hookRuntime.trigger('llm_output', {
94
+ context: args[0] || {},
95
+ response: result,
96
+ }, { hookName });
97
+ return payload?.response || result;
98
+ }
99
+ if (hookName === 'tool.after') {
100
+ const payload = await this.hookRuntime.trigger('tool_result', {
101
+ context: args[0] || {},
102
+ toolName: args[1],
103
+ args: args[2],
104
+ result,
105
+ }, { hookName });
106
+ return payload?.result ?? result;
107
+ }
78
108
  return result;
79
109
  }
80
110
 
@@ -17,6 +17,18 @@ const sessions = new Map();
17
17
  * @param {string} sessionId - Coding session ID
18
18
  */
19
19
  function recordRead(filePath, sessionId) {
20
+ recordCurrent(filePath, sessionId);
21
+ }
22
+
23
+ /**
24
+ * Record the current mtime and size of a file after this session writes it.
25
+ * This keeps stale-edit protection focused on external races, not on the
26
+ * agent's own prior successful edits.
27
+ * @param {string} filePath - Absolute path
28
+ * @param {string} sessionId - Coding session ID
29
+ */
30
+ function recordCurrent(filePath, sessionId) {
31
+ if (!sessionId) return;
20
32
  const resolved = path.resolve(filePath);
21
33
  const stat = fs.statSync(resolved);
22
34
  if (!sessions.has(sessionId)) sessions.set(sessionId, new Map());
@@ -26,6 +38,18 @@ function recordRead(filePath, sessionId) {
26
38
  });
27
39
  }
28
40
 
41
+ /**
42
+ * Forget a tracked file after this session deletes it.
43
+ * @param {string} filePath - Absolute path
44
+ * @param {string} sessionId - Coding session ID
45
+ */
46
+ function forgetFile(filePath, sessionId) {
47
+ if (!sessionId) return;
48
+ const resolved = path.resolve(filePath);
49
+ const sessionMap = sessions.get(sessionId);
50
+ if (sessionMap) sessionMap.delete(resolved);
51
+ }
52
+
29
53
  /**
30
54
  * Assert a file has not changed since the last recordRead().
31
55
  * Called from editFile()/writeFile() in local-tools.js before writing.
@@ -69,4 +93,4 @@ function clearSession(sessionId) {
69
93
  sessions.delete(sessionId);
70
94
  }
71
95
 
72
- module.exports = { recordRead, assertUnchanged, clearSession };
96
+ module.exports = { recordRead, recordCurrent, forgetFile, assertUnchanged, clearSession };