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.
- package/README.md +3 -3
- package/package.json +2 -2
- package/template/bin/dev.sh +7 -1
- package/template/bin/setup.js +53 -9
- package/template/bin/sync-images.js +53 -0
- package/template/builder-journal.md +17 -0
- package/template/claude-task-manager/api-prompts.js +98 -13
- package/template/claude-task-manager/api-reviews.js +82 -5
- package/template/claude-task-manager/db.js +32 -5
- package/template/claude-task-manager/docs/session-capture-foundation-design.md +1273 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +696 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +49 -1
- package/template/claude-task-manager/lib/session-capture.js +421 -0
- package/template/claude-task-manager/lib/session-history.js +135 -15
- package/template/claude-task-manager/lib/session-jobs.js +10 -5
- package/template/claude-task-manager/lib/session-stream.js +87 -19
- package/template/claude-task-manager/lib/setup-provider-config.js +115 -0
- package/template/claude-task-manager/lib/walle-ctm-history.js +72 -0
- package/template/claude-task-manager/lib/walle-session-context.js +61 -0
- package/template/claude-task-manager/lib/walle-transcript.js +176 -0
- package/template/claude-task-manager/public/css/setup.css +35 -8
- package/template/claude-task-manager/public/css/walle-session.css +56 -0
- package/template/claude-task-manager/public/css/walle.css +120 -0
- package/template/claude-task-manager/public/index.html +814 -181
- package/template/claude-task-manager/public/js/message-renderer.js +148 -19
- package/template/claude-task-manager/public/js/reviews.js +120 -62
- package/template/claude-task-manager/public/js/setup.js +75 -31
- package/template/claude-task-manager/public/js/stream-view.js +115 -55
- package/template/claude-task-manager/public/js/walle-session.js +84 -2
- package/template/claude-task-manager/public/js/walle.js +308 -54
- package/template/claude-task-manager/server.js +1092 -146
- package/template/claude-task-manager/session-integrity.js +181 -54
- package/template/claude-task-manager/session-utils.js +123 -41
- package/template/claude-task-manager/workers/state-detectors/codex.js +5 -2
- package/template/package.json +1 -1
- package/template/wall-e/adapters/ctm.js +39 -18
- package/template/wall-e/agent-runners/contract.js +17 -0
- package/template/wall-e/agent-runners/index.js +22 -0
- package/template/wall-e/agent-runtime/harness.js +212 -0
- package/template/wall-e/agent-runtime/index.js +8 -0
- package/template/wall-e/agent-runtime/registry.js +67 -0
- package/template/wall-e/agent-runtime/session-store.js +179 -0
- package/template/wall-e/agent-runtime/spawn.js +208 -0
- package/template/wall-e/api-walle.js +174 -7
- package/template/wall-e/brain.js +266 -28
- package/template/wall-e/channels/policy.js +88 -0
- package/template/wall-e/channels/registry.js +15 -1
- package/template/wall-e/channels/reply-dispatcher.js +70 -0
- package/template/wall-e/channels/session-bindings.js +51 -0
- package/template/wall-e/chat/code-review-context.js +29 -0
- package/template/wall-e/chat.js +188 -42
- package/template/wall-e/coding/acp-adapter.js +188 -0
- package/template/wall-e/coding/agent-catalog.js +129 -0
- package/template/wall-e/coding/compaction-service.js +247 -0
- package/template/wall-e/coding/execution-trace.js +3 -0
- package/template/wall-e/coding/instruction-service.js +224 -0
- package/template/wall-e/coding/model-message.js +67 -0
- package/template/wall-e/coding/permission-rules-store.js +111 -0
- package/template/wall-e/coding/permission-service.js +266 -0
- package/template/wall-e/coding/prompt-bundle.js +67 -0
- package/template/wall-e/coding/prompt-runtime.js +243 -0
- package/template/wall-e/coding/provider-transform.js +188 -0
- package/template/wall-e/coding/runtime-mode.js +132 -0
- package/template/wall-e/coding/snapshot-service.js +155 -0
- package/template/wall-e/coding/stream-processor.js +268 -0
- package/template/wall-e/coding/task-tool.js +255 -0
- package/template/wall-e/coding/tool-registry.js +361 -0
- package/template/wall-e/coding/transcript-writer.js +143 -0
- package/template/wall-e/coding/workspace-replay.js +324 -0
- package/template/wall-e/coding-context.js +4 -22
- package/template/wall-e/coding-orchestrator.js +307 -18
- package/template/wall-e/coding-prompts.js +44 -3
- package/template/wall-e/context/context-builder.js +43 -1
- package/template/wall-e/context/topic-matcher.js +1 -1
- package/template/wall-e/eval/agent-runner.js +59 -13
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +155 -57
- package/template/wall-e/eval/benchmarks.js +100 -16
- package/template/wall-e/eval/eval-orchestrator.js +218 -8
- package/template/wall-e/eval/harvester.js +62 -5
- package/template/wall-e/eval/head-to-head.js +23 -2
- package/template/wall-e/eval/humaneval-adapter.js +30 -5
- package/template/wall-e/eval/livecodebench-adapter.js +29 -5
- package/template/wall-e/eval/manifest.js +186 -0
- package/template/wall-e/eval/run-agent-benchmarks.js +66 -2
- package/template/wall-e/eval/session-retrieval-benchmark.js +150 -0
- package/template/wall-e/eval/session-transcripts.js +57 -4
- package/template/wall-e/eval/swebench-adapter.js +109 -3
- package/template/wall-e/evaluation/agent-router.js +53 -1
- package/template/wall-e/evaluation/coding-quorum.js +48 -1
- package/template/wall-e/evaluation/router.js +4 -2
- package/template/wall-e/evaluation/tier-selector.js +11 -1
- package/template/wall-e/extraction/contradiction.js +2 -2
- package/template/wall-e/extraction/indexer.js +2 -1
- package/template/wall-e/extraction/knowledge-extractor.js +2 -2
- package/template/wall-e/hooks/cli.js +92 -0
- package/template/wall-e/hooks/discovery.js +119 -0
- package/template/wall-e/hooks/index.js +7 -0
- package/template/wall-e/hooks/manifest.js +55 -0
- package/template/wall-e/hooks/runtime.js +84 -0
- package/template/wall-e/hooks/session-memory.js +225 -0
- package/template/wall-e/http/auth.js +6 -2
- package/template/wall-e/http/chat-api.js +54 -8
- package/template/wall-e/integrations/claude-plugin/hooks/hooks.json +27 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-precompact-hook.sh +5 -0
- package/template/wall-e/integrations/claude-plugin/hooks/walle-stop-hook.sh +5 -0
- package/template/wall-e/integrations/codex-plugin/hooks/walle-hook.sh +7 -0
- package/template/wall-e/integrations/codex-plugin/hooks.json +37 -0
- package/template/wall-e/listening/calendar.js +3 -1
- package/template/wall-e/llm/client.js +64 -10
- package/template/wall-e/llm/google.js +39 -5
- package/template/wall-e/llm/ollama.js +1 -1
- package/template/wall-e/llm/ollama.plugin.json +1 -1
- package/template/wall-e/llm/provider-availability.js +10 -0
- package/template/wall-e/llm/provider-error.js +269 -0
- package/template/wall-e/llm/tool-adapter.js +48 -12
- package/template/wall-e/loops/boot.js +2 -1
- package/template/wall-e/loops/initiative.js +2 -2
- package/template/wall-e/loops/tasks.js +8 -47
- package/template/wall-e/loops/workspace-prompts.js +20 -0
- package/template/wall-e/mcp-server.js +442 -1
- package/template/wall-e/memory/session-ingest-service.js +159 -0
- package/template/wall-e/memory/source-indexer.js +289 -0
- package/template/wall-e/plugins/discovery.js +83 -0
- package/template/wall-e/plugins/manifest-loader.js +50 -10
- package/template/wall-e/plugins/manifest-schema.js +69 -0
- package/template/wall-e/plugins/model-catalog.js +55 -0
- package/template/wall-e/prompts/coding/base.txt +2 -0
- package/template/wall-e/prompts/coding/deepseek.txt +1 -0
- package/template/wall-e/prompts/coding/memory-protocol.md +9 -0
- package/template/wall-e/prompts/coding/plan.txt +1 -0
- package/template/wall-e/runtime/execution-trace.js +220 -0
- package/template/wall-e/security/audit.js +266 -0
- package/template/wall-e/security/ssrf.js +236 -0
- package/template/wall-e/session-files.js +303 -0
- package/template/wall-e/skills/_bundled/slack-backfill/SKILL.md +3 -0
- package/template/wall-e/skills/_bundled/slack-sync/SKILL.md +3 -0
- package/template/wall-e/skills/internal-skill-registry.js +2 -2
- package/template/wall-e/skills/script-skill-runner.js +143 -0
- package/template/wall-e/skills/skill-executor.js +5 -6
- package/template/wall-e/skills/skill-fallback.js +3 -1
- package/template/wall-e/skills/skill-harness-registry.js +7 -8
- package/template/wall-e/skills/skill-planner.js +52 -4
- package/template/wall-e/skills/slack-ingest.js +11 -3
- package/template/wall-e/sources/base.js +90 -0
- package/template/wall-e/sources/builtin.js +33 -0
- package/template/wall-e/sources/claude-code-jsonl.js +78 -0
- package/template/wall-e/sources/codex-jsonl.js +125 -0
- package/template/wall-e/sources/coding-session-utils.js +117 -0
- package/template/wall-e/sources/contract-suite.js +59 -0
- package/template/wall-e/sources/gemini-jsonl.js +85 -0
- package/template/wall-e/sources/index.js +9 -0
- package/template/wall-e/sources/jsonl-utils.js +181 -0
- package/template/wall-e/sources/record-types.js +252 -0
- package/template/wall-e/sources/registry.js +92 -0
- package/template/wall-e/sources/transforms.js +100 -0
- package/template/wall-e/sources/walle-jsonl.js +108 -0
- package/template/wall-e/tools/coding-middleware.js +31 -1
- package/template/wall-e/tools/file-tracker.js +25 -1
- package/template/wall-e/tools/local-tools.js +75 -47
- package/template/wall-e/tools/session-sharing.js +68 -1
- package/template/wall-e/tools/shell-analyzer.js +1 -1
- package/template/wall-e/tools/shell-policy.js +47 -0
- package/template/wall-e/tools/snapshot.js +42 -0
- package/template/wall-e/training/harvester.js +62 -5
- package/template/wall-e/utils/repair.js +253 -1
- package/template/website/index.html +3 -3
- package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +0 -18
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
2
4
|
const { v4: uuidv4 } = require('uuid');
|
|
3
5
|
const brain = require('./brain');
|
|
6
|
+
const { createSessionIngestService } = require('./memory/session-ingest-service');
|
|
7
|
+
const { collectIngestRecords } = require('./sources/base');
|
|
8
|
+
const { ensureBuiltinSourceAdapters } = require('./sources/builtin');
|
|
9
|
+
const sourceRegistry = require('./sources/registry');
|
|
10
|
+
const { RECORD_TYPES } = require('./sources/record-types');
|
|
4
11
|
let _embeddings;
|
|
5
12
|
try { _embeddings = require('./embeddings'); } catch { _embeddings = null; }
|
|
6
13
|
|
|
@@ -153,6 +160,129 @@ const MCP_TOOLS = [
|
|
|
153
160
|
properties: {},
|
|
154
161
|
},
|
|
155
162
|
},
|
|
163
|
+
{
|
|
164
|
+
name: 'walle_memory_status',
|
|
165
|
+
description: 'Get coding-session memory status: brain stats, source adapters, diary count, and operational tool availability.',
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'walle_list_sources',
|
|
173
|
+
description: 'List source adapters Wall-E can ingest, including Claude Code, Codex, Gemini, and Wall-E JSONL sessions.',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
privacy_class: { type: 'string', description: 'Optional adapter default privacy class filter' },
|
|
178
|
+
incremental: { type: 'boolean', description: 'Optional incremental-support filter' },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'walle_source_ingest',
|
|
184
|
+
description: 'Ingest a source file through a Wall-E source adapter. Use dry_run first when inspecting an unfamiliar source.',
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {
|
|
188
|
+
adapter_id: { type: 'string', description: 'Source adapter id, e.g. codex-jsonl or claude-code-jsonl' },
|
|
189
|
+
uri: { type: 'string', description: 'Path to the JSONL source file' },
|
|
190
|
+
source_id: { type: 'string', description: 'Optional stable source/session id' },
|
|
191
|
+
cwd: { type: 'string', description: 'Optional project working directory' },
|
|
192
|
+
privacy_class: { type: 'string', description: 'Optional source privacy class' },
|
|
193
|
+
privacy_floor: { type: 'string', description: 'Optional maximum privacy class allowed to write' },
|
|
194
|
+
dry_run: { type: 'boolean', description: 'Parse and validate without writing memories' },
|
|
195
|
+
metadata: { type: 'object', description: 'Optional adapter metadata' },
|
|
196
|
+
},
|
|
197
|
+
required: ['adapter_id', 'uri'],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'walle_search_sessions',
|
|
202
|
+
description: 'Search ingested coding sessions and Wall-E diary entries. Returns attributed snippets plus session pointers.',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
query: { type: 'string', description: 'Search query' },
|
|
207
|
+
limit: { type: 'number', description: 'Max session snippets (default 10, max 50)' },
|
|
208
|
+
},
|
|
209
|
+
required: ['query'],
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'walle_get_session',
|
|
214
|
+
description: 'Retrieve session snippets from ingested memory, or parse a JSONL session directly when adapter_id and uri are provided.',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
properties: {
|
|
218
|
+
source_id: { type: 'string', description: 'Stable source/session id to retrieve from memory' },
|
|
219
|
+
adapter_id: { type: 'string', description: 'Optional adapter id for direct JSONL parse' },
|
|
220
|
+
uri: { type: 'string', description: 'Optional JSONL path for direct parse' },
|
|
221
|
+
limit: { type: 'number', description: 'Max records (default 50, max 200)' },
|
|
222
|
+
include_raw: { type: 'boolean', description: 'Include raw content when available' },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'walle_diary_write',
|
|
228
|
+
description: 'Write an idempotent agent diary entry for a coding session stop, handoff, or compaction boundary.',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
agent: { type: 'string', description: 'Agent name or harness' },
|
|
233
|
+
session_id: { type: 'string', description: 'Stable session id' },
|
|
234
|
+
event: { type: 'string', description: 'Diary event, e.g. stop, precompact, handoff' },
|
|
235
|
+
cwd: { type: 'string', description: 'Project working directory' },
|
|
236
|
+
branch: { type: 'string', description: 'Git branch' },
|
|
237
|
+
changed_files: { type: 'array', items: { type: 'string' } },
|
|
238
|
+
commands: { type: 'array', items: { type: 'string' } },
|
|
239
|
+
decisions: { type: 'array', items: { type: 'string' } },
|
|
240
|
+
blockers: { type: 'array', items: { type: 'string' } },
|
|
241
|
+
next_steps: { type: 'array', items: { type: 'string' } },
|
|
242
|
+
summary: { type: 'string', description: 'Short human-readable summary' },
|
|
243
|
+
},
|
|
244
|
+
required: ['session_id'],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'walle_diary_read',
|
|
249
|
+
description: 'Read recent agent diary entries, optionally filtered by agent or session id.',
|
|
250
|
+
inputSchema: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
agent: { type: 'string', description: 'Optional agent filter' },
|
|
254
|
+
session_id: { type: 'string', description: 'Optional session filter' },
|
|
255
|
+
limit: { type: 'number', description: 'Max entries (default 20, max 100)' },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'walle_reconnect',
|
|
261
|
+
description: 'Refresh Wall-E MCP process state after external DB/source updates. Runs a passive checkpoint and reports current health.',
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'walle_repair_status',
|
|
269
|
+
description: 'Run a dry integrity scan for brain memory, knowledge, entity, and index consistency.',
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'walle_rebuild_source_index',
|
|
277
|
+
description: 'Rebuild the memory/source index from stored memories. Use dry_run to inspect expected work without writing.',
|
|
278
|
+
inputSchema: {
|
|
279
|
+
type: 'object',
|
|
280
|
+
properties: {
|
|
281
|
+
source_id: { type: 'string', description: 'Optional stable source/session id to rebuild' },
|
|
282
|
+
dry_run: { type: 'boolean', description: 'Return current index status without rebuilding' },
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
156
286
|
];
|
|
157
287
|
|
|
158
288
|
async function executeTool(name, args) {
|
|
@@ -276,11 +406,322 @@ async function executeTool(name, args) {
|
|
|
276
406
|
try { embeddingCount = brain.getDb().prepare("SELECT COUNT(*) as c FROM embedding_map").get().c; } catch {}
|
|
277
407
|
return { ...stats, entity_count: entityCount, embedding_count: embeddingCount };
|
|
278
408
|
}
|
|
409
|
+
case 'walle_memory_status': {
|
|
410
|
+
ensureBuiltinSourceAdapters();
|
|
411
|
+
const stats = brain.getBrainStats();
|
|
412
|
+
return {
|
|
413
|
+
ok: true,
|
|
414
|
+
stats,
|
|
415
|
+
source_adapters: sourceRegistry.list().map(formatSourceAdapter),
|
|
416
|
+
diary_count: countDiaryEntries(),
|
|
417
|
+
tools: MCP_TOOLS.filter((tool) => tool.name.startsWith('walle_')).map((tool) => tool.name),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
case 'walle_list_sources': {
|
|
421
|
+
ensureBuiltinSourceAdapters();
|
|
422
|
+
return {
|
|
423
|
+
sources: sourceRegistry.list({
|
|
424
|
+
privacyClass: args.privacy_class,
|
|
425
|
+
incremental: args.incremental,
|
|
426
|
+
}).map(formatSourceAdapter),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
case 'walle_source_ingest': {
|
|
430
|
+
ensureBuiltinSourceAdapters();
|
|
431
|
+
assertJsonlPath(args.uri);
|
|
432
|
+
const sourceRef = {
|
|
433
|
+
adapterId: args.adapter_id,
|
|
434
|
+
uri: path.resolve(args.uri),
|
|
435
|
+
sourceFile: path.resolve(args.uri),
|
|
436
|
+
sourceId: args.source_id,
|
|
437
|
+
cwd: args.cwd || '',
|
|
438
|
+
privacyClass: args.privacy_class,
|
|
439
|
+
metadata: args.metadata || {},
|
|
440
|
+
};
|
|
441
|
+
const service = createSessionIngestService({ brain });
|
|
442
|
+
return service.ingestSource(sourceRef, {
|
|
443
|
+
privacyFloor: args.privacy_floor || null,
|
|
444
|
+
dryRun: Boolean(args.dry_run),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
case 'walle_search_sessions': {
|
|
448
|
+
const limit = clampLimit(args.limit, 10, 50);
|
|
449
|
+
const results = brain.searchMemories({ query: args.query, limit: limit * 4 })
|
|
450
|
+
.filter(isSessionMemory)
|
|
451
|
+
.slice(0, limit)
|
|
452
|
+
.map(formatSessionMemory);
|
|
453
|
+
return { results };
|
|
454
|
+
}
|
|
455
|
+
case 'walle_get_session': {
|
|
456
|
+
const limit = clampLimit(args.limit, 50, 200);
|
|
457
|
+
if (args.adapter_id && args.uri) {
|
|
458
|
+
ensureBuiltinSourceAdapters();
|
|
459
|
+
assertJsonlPath(args.uri);
|
|
460
|
+
const adapter = sourceRegistry.instantiate(args.adapter_id);
|
|
461
|
+
if (!adapter) throw new Error(`Unknown source adapter: ${args.adapter_id}`);
|
|
462
|
+
const sourceRef = {
|
|
463
|
+
adapterId: args.adapter_id,
|
|
464
|
+
uri: path.resolve(args.uri),
|
|
465
|
+
sourceFile: path.resolve(args.uri),
|
|
466
|
+
sourceId: args.source_id,
|
|
467
|
+
};
|
|
468
|
+
const { records, diagnostics, source } = await collectIngestRecords(adapter, sourceRef);
|
|
469
|
+
return {
|
|
470
|
+
source: { adapterId: source.adapterId, sourceId: source.sourceId, sourceFile: source.sourceFile },
|
|
471
|
+
diagnostics,
|
|
472
|
+
records: records
|
|
473
|
+
.filter((record) => record.type === RECORD_TYPES.MEMORY_RECORD)
|
|
474
|
+
.slice(0, limit)
|
|
475
|
+
.map((record) => formatSourceRecord(record, { includeRaw: Boolean(args.include_raw) })),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (!args.source_id) throw new Error('source_id is required when adapter_id and uri are not provided');
|
|
479
|
+
return {
|
|
480
|
+
source_id: args.source_id,
|
|
481
|
+
records: getSessionMemories(args.source_id, { limit }).map(formatSessionMemory),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
case 'walle_diary_write': {
|
|
485
|
+
return writeDiaryEntry(args);
|
|
486
|
+
}
|
|
487
|
+
case 'walle_diary_read': {
|
|
488
|
+
return readDiaryEntries(args);
|
|
489
|
+
}
|
|
490
|
+
case 'walle_reconnect': {
|
|
491
|
+
const status = reconnectBrain();
|
|
492
|
+
return { ok: true, ...status };
|
|
493
|
+
}
|
|
494
|
+
case 'walle_repair_status': {
|
|
495
|
+
const repair = require('./utils/repair');
|
|
496
|
+
return repair.scanIntegrity();
|
|
497
|
+
}
|
|
498
|
+
case 'walle_rebuild_source_index': {
|
|
499
|
+
const { rebuildSourceIndex } = require('./memory/source-indexer');
|
|
500
|
+
return rebuildSourceIndex({
|
|
501
|
+
brain,
|
|
502
|
+
sourceId: args.source_id || '',
|
|
503
|
+
dryRun: Boolean(args.dry_run),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
279
506
|
default:
|
|
280
507
|
return null;
|
|
281
508
|
}
|
|
282
509
|
}
|
|
283
510
|
|
|
511
|
+
function clampLimit(value, fallback, max) {
|
|
512
|
+
const n = Number(value || fallback);
|
|
513
|
+
if (!Number.isFinite(n)) return fallback;
|
|
514
|
+
return Math.min(Math.max(Math.trunc(n), 1), max);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function assertJsonlPath(filePath) {
|
|
518
|
+
if (!filePath || typeof filePath !== 'string') throw new Error('uri must be a JSONL file path');
|
|
519
|
+
if (filePath.split(/[\\/]+/).includes('..')) throw new Error('uri cannot contain traversal segments');
|
|
520
|
+
if (path.extname(filePath) !== '.jsonl') throw new Error('uri must end in .jsonl');
|
|
521
|
+
const resolved = path.resolve(filePath);
|
|
522
|
+
const stat = fs.statSync(resolved);
|
|
523
|
+
if (!stat.isFile()) throw new Error('uri must point to a file');
|
|
524
|
+
return resolved;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function formatSourceAdapter(entry) {
|
|
528
|
+
const schema = entry.schema || {};
|
|
529
|
+
return {
|
|
530
|
+
id: entry.id || schema.adapterId,
|
|
531
|
+
adapterId: schema.adapterId,
|
|
532
|
+
version: schema.version,
|
|
533
|
+
supportsIncremental: Boolean(schema.supportsIncremental),
|
|
534
|
+
defaultPrivacyClass: schema.defaultPrivacyClass,
|
|
535
|
+
declaredTransformations: schema.declaredTransformations || [],
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function isSessionMemory(memory) {
|
|
540
|
+
const source = memory?.source || '';
|
|
541
|
+
const type = memory?.memory_type || '';
|
|
542
|
+
return source === 'walle-diary'
|
|
543
|
+
|| source.endsWith('-jsonl')
|
|
544
|
+
|| type.startsWith('coding_session_')
|
|
545
|
+
|| type === 'agent_diary';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function parseMetadata(memory) {
|
|
549
|
+
try {
|
|
550
|
+
const parsed = JSON.parse(memory?.metadata || '{}');
|
|
551
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
552
|
+
} catch {
|
|
553
|
+
return {};
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function formatSessionMemory(memory) {
|
|
558
|
+
const metadata = parseMetadata(memory);
|
|
559
|
+
return {
|
|
560
|
+
id: memory.id,
|
|
561
|
+
source: memory.source,
|
|
562
|
+
source_id: memory.source_id,
|
|
563
|
+
session_id: metadata.sessionId || metadata.sourceId || inferSourceId(memory.source_id),
|
|
564
|
+
cwd: metadata.cwd || memory.source_channel || '',
|
|
565
|
+
branch: metadata.gitBranch || metadata.branch || '',
|
|
566
|
+
memory_type: memory.memory_type,
|
|
567
|
+
timestamp: memory.timestamp,
|
|
568
|
+
snippet: String(memory.content || '').slice(0, 800),
|
|
569
|
+
metadata: {
|
|
570
|
+
itemId: metadata.itemId,
|
|
571
|
+
role: metadata.role,
|
|
572
|
+
toolCalls: metadata.toolCalls,
|
|
573
|
+
filesEdited: metadata.filesEdited || metadata.changed_files,
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function formatSourceRecord(record, { includeRaw = false } = {}) {
|
|
579
|
+
return {
|
|
580
|
+
source_id: record.sourceId,
|
|
581
|
+
item_id: record.itemId,
|
|
582
|
+
role: record.role,
|
|
583
|
+
memory_type: record.memoryType,
|
|
584
|
+
timestamp: record.timestamp,
|
|
585
|
+
snippet: String(record.content || '').slice(0, 800),
|
|
586
|
+
raw: includeRaw ? record.contentRaw : undefined,
|
|
587
|
+
metadata: record.metadata || {},
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function inferSourceId(sourceId = '') {
|
|
592
|
+
const parts = String(sourceId).split(':');
|
|
593
|
+
if (parts.length <= 2) return sourceId || '';
|
|
594
|
+
return parts.slice(0, 2).join(':');
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function getSessionMemories(sourceId, { limit }) {
|
|
598
|
+
const db = brain.getDb();
|
|
599
|
+
return db.prepare(`
|
|
600
|
+
SELECT * FROM memories
|
|
601
|
+
WHERE archived_at IS NULL
|
|
602
|
+
AND (source_id = ? OR source_id LIKE ?)
|
|
603
|
+
ORDER BY timestamp ASC
|
|
604
|
+
LIMIT ?
|
|
605
|
+
`).all(sourceId, `${sourceId}:%`, limit);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function countDiaryEntries() {
|
|
609
|
+
try {
|
|
610
|
+
return brain.getDb().prepare(`
|
|
611
|
+
SELECT COUNT(*) as c FROM memories
|
|
612
|
+
WHERE archived_at IS NULL AND source = 'walle-diary' AND memory_type = 'agent_diary'
|
|
613
|
+
`).get().c;
|
|
614
|
+
} catch {
|
|
615
|
+
return 0;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function writeDiaryEntry(args = {}) {
|
|
620
|
+
if (!args.session_id) throw new Error('session_id is required');
|
|
621
|
+
const agent = sanitizeToken(args.agent || 'walle');
|
|
622
|
+
const event = sanitizeToken(args.event || 'manual');
|
|
623
|
+
const sourceId = `diary:${agent}:${args.session_id}:${event}`;
|
|
624
|
+
const timestamp = new Date().toISOString();
|
|
625
|
+
const metadata = {
|
|
626
|
+
agent,
|
|
627
|
+
sessionId: args.session_id,
|
|
628
|
+
event,
|
|
629
|
+
cwd: args.cwd || '',
|
|
630
|
+
branch: args.branch || '',
|
|
631
|
+
changed_files: arrayOfStrings(args.changed_files),
|
|
632
|
+
commands: arrayOfStrings(args.commands),
|
|
633
|
+
decisions: arrayOfStrings(args.decisions),
|
|
634
|
+
blockers: arrayOfStrings(args.blockers),
|
|
635
|
+
next_steps: arrayOfStrings(args.next_steps),
|
|
636
|
+
};
|
|
637
|
+
const content = formatDiaryContent({ ...metadata, summary: args.summary || '' });
|
|
638
|
+
const inserted = brain.insertMemory({
|
|
639
|
+
source: 'walle-diary',
|
|
640
|
+
source_id: sourceId,
|
|
641
|
+
source_channel: metadata.cwd,
|
|
642
|
+
memory_type: 'agent_diary',
|
|
643
|
+
direction: 'outbound',
|
|
644
|
+
participants: agent,
|
|
645
|
+
subject: args.session_id,
|
|
646
|
+
content,
|
|
647
|
+
content_raw: content,
|
|
648
|
+
metadata: JSON.stringify(metadata),
|
|
649
|
+
importance: 0.75,
|
|
650
|
+
timestamp,
|
|
651
|
+
});
|
|
652
|
+
return {
|
|
653
|
+
stored: Boolean(inserted),
|
|
654
|
+
duplicate: !inserted,
|
|
655
|
+
id: inserted?.id || null,
|
|
656
|
+
source_id: sourceId,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function readDiaryEntries(args = {}) {
|
|
661
|
+
const limit = clampLimit(args.limit, 20, 100);
|
|
662
|
+
const entries = brain.listMemories({ source: 'walle-diary', limit: limit * 3 })
|
|
663
|
+
.filter((memory) => memory.memory_type === 'agent_diary')
|
|
664
|
+
.map((memory) => ({ memory, metadata: parseMetadata(memory) }))
|
|
665
|
+
.filter(({ metadata }) => !args.agent || metadata.agent === args.agent)
|
|
666
|
+
.filter(({ metadata }) => !args.session_id || metadata.sessionId === args.session_id)
|
|
667
|
+
.slice(0, limit)
|
|
668
|
+
.map(({ memory, metadata }) => ({
|
|
669
|
+
id: memory.id,
|
|
670
|
+
source_id: memory.source_id,
|
|
671
|
+
session_id: metadata.sessionId || '',
|
|
672
|
+
agent: metadata.agent || '',
|
|
673
|
+
event: metadata.event || '',
|
|
674
|
+
cwd: metadata.cwd || '',
|
|
675
|
+
branch: metadata.branch || '',
|
|
676
|
+
timestamp: memory.timestamp,
|
|
677
|
+
content: memory.content,
|
|
678
|
+
changed_files: metadata.changed_files || [],
|
|
679
|
+
commands: metadata.commands || [],
|
|
680
|
+
decisions: metadata.decisions || [],
|
|
681
|
+
blockers: metadata.blockers || [],
|
|
682
|
+
next_steps: metadata.next_steps || [],
|
|
683
|
+
}));
|
|
684
|
+
return { entries };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function formatDiaryContent(entry) {
|
|
688
|
+
const lines = [];
|
|
689
|
+
lines.push(`Agent diary for ${entry.agent} session ${entry.sessionId} (${entry.event})`);
|
|
690
|
+
if (entry.summary) lines.push(`Summary: ${entry.summary}`);
|
|
691
|
+
if (entry.cwd) lines.push(`CWD: ${entry.cwd}`);
|
|
692
|
+
if (entry.branch) lines.push(`Branch: ${entry.branch}`);
|
|
693
|
+
for (const [label, values] of [
|
|
694
|
+
['Changed files', entry.changed_files],
|
|
695
|
+
['Commands', entry.commands],
|
|
696
|
+
['Decisions', entry.decisions],
|
|
697
|
+
['Blockers', entry.blockers],
|
|
698
|
+
['Next steps', entry.next_steps],
|
|
699
|
+
]) {
|
|
700
|
+
if (values.length) lines.push(`${label}: ${values.join('; ')}`);
|
|
701
|
+
}
|
|
702
|
+
return lines.join('\n');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function arrayOfStrings(value) {
|
|
706
|
+
return Array.isArray(value)
|
|
707
|
+
? value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim())
|
|
708
|
+
: [];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function sanitizeToken(value) {
|
|
712
|
+
return String(value || '').trim().replace(/[^A-Za-z0-9_.:-]+/g, '-').slice(0, 80) || 'walle';
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function reconnectBrain() {
|
|
716
|
+
const db = brain.getDb();
|
|
717
|
+
let checkpoint = null;
|
|
718
|
+
try { checkpoint = db.pragma('wal_checkpoint(PASSIVE)'); } catch {}
|
|
719
|
+
return {
|
|
720
|
+
stats: brain.getBrainStats(),
|
|
721
|
+
checkpoint,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
284
725
|
function readBody(req) {
|
|
285
726
|
return new Promise((resolve, reject) => {
|
|
286
727
|
const chunks = [];
|
|
@@ -378,4 +819,4 @@ async function handleMcp(req, res) {
|
|
|
378
819
|
}
|
|
379
820
|
}
|
|
380
821
|
|
|
381
|
-
module.exports = { handleMcp, MCP_TOOLS };
|
|
822
|
+
module.exports = { handleMcp, MCP_TOOLS, executeTool };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SourceIngestContext, collectIngestRecords } = require('../sources/base');
|
|
4
|
+
const { PRIVACY_CLASS_ORDER, RECORD_TYPES, privacyClassAllows } = require('../sources/record-types');
|
|
5
|
+
const sourceRegistry = require('../sources/registry');
|
|
6
|
+
const { ensureBuiltinSourceAdapters } = require('../sources/builtin');
|
|
7
|
+
const { indexMemory } = require('./source-indexer');
|
|
8
|
+
|
|
9
|
+
function createSessionIngestService({ brain = null, registry = sourceRegistry } = {}) {
|
|
10
|
+
return new SessionIngestService({ brain, registry });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class SessionIngestService {
|
|
14
|
+
constructor({ brain = null, registry = sourceRegistry } = {}) {
|
|
15
|
+
this.brain = brain || require('../brain');
|
|
16
|
+
this.registry = registry;
|
|
17
|
+
ensureBuiltinSourceAdapters();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async ingestSource(sourceRef, { privacyFloor = null, dryRun = false } = {}) {
|
|
21
|
+
const adapter = this.registry.instantiate(sourceRef.adapterId);
|
|
22
|
+
if (!adapter) throw new Error(`Unknown source adapter: ${sourceRef.adapterId}`);
|
|
23
|
+
|
|
24
|
+
const context = new SourceIngestContext();
|
|
25
|
+
const { records, diagnostics, source } = await collectIngestRecords(adapter, sourceRef, { context });
|
|
26
|
+
const result = {
|
|
27
|
+
adapterId: adapter.schema.adapterId,
|
|
28
|
+
sourceId: source.sourceId,
|
|
29
|
+
records: records.length,
|
|
30
|
+
memoryRecords: 0,
|
|
31
|
+
inserted: 0,
|
|
32
|
+
indexed: 0,
|
|
33
|
+
skipped: 0,
|
|
34
|
+
rejected: 0,
|
|
35
|
+
diagnostics,
|
|
36
|
+
dryRun,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (const record of records) {
|
|
40
|
+
if (record.type !== RECORD_TYPES.MEMORY_RECORD) continue;
|
|
41
|
+
result.memoryRecords += 1;
|
|
42
|
+
if (!privacyClassAllows({ recordClass: record.privacyClass, floor: privacyFloor })) {
|
|
43
|
+
result.rejected += 1;
|
|
44
|
+
result.diagnostics.push({
|
|
45
|
+
level: 'warn',
|
|
46
|
+
message: `Rejected ${record.itemId} above privacy floor ${privacyFloor}`,
|
|
47
|
+
sourceId: record.sourceId,
|
|
48
|
+
itemId: record.itemId,
|
|
49
|
+
});
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (dryRun) continue;
|
|
53
|
+
const memory = toBrainMemory(record, adapter.schema.adapterId);
|
|
54
|
+
const downgrade = this._privacyDowngrade(memory, record);
|
|
55
|
+
if (downgrade) {
|
|
56
|
+
result.rejected += 1;
|
|
57
|
+
result.diagnostics.push({
|
|
58
|
+
level: 'warn',
|
|
59
|
+
message: downgrade,
|
|
60
|
+
sourceId: record.sourceId,
|
|
61
|
+
itemId: record.itemId,
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const inserted = this.brain.insertMemory(memory);
|
|
66
|
+
if (inserted) {
|
|
67
|
+
result.inserted += 1;
|
|
68
|
+
try {
|
|
69
|
+
indexMemory({ ...memory, id: inserted.id }, { brain: this.brain });
|
|
70
|
+
result.indexed += 1;
|
|
71
|
+
} catch (err) {
|
|
72
|
+
result.diagnostics.push({
|
|
73
|
+
level: 'warn',
|
|
74
|
+
message: `Indexing failed for ${record.itemId}: ${err.message}`,
|
|
75
|
+
sourceId: record.sourceId,
|
|
76
|
+
itemId: record.itemId,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
result.skipped += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this._auditSourceIngest(result);
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_privacyDowngrade(memory, record) {
|
|
89
|
+
if (!this.brain || typeof this.brain.getDb !== 'function') return null;
|
|
90
|
+
try {
|
|
91
|
+
const existing = this.brain.getDb().prepare(
|
|
92
|
+
'SELECT metadata FROM memories WHERE source = ? AND source_id = ?'
|
|
93
|
+
).get(memory.source, memory.source_id);
|
|
94
|
+
if (!existing) return null;
|
|
95
|
+
const metadata = JSON.parse(existing.metadata || '{}');
|
|
96
|
+
const previous = metadata.privacyClass || '';
|
|
97
|
+
const next = record.privacyClass || '';
|
|
98
|
+
if (!previous || !next) return null;
|
|
99
|
+
const previousRank = PRIVACY_CLASS_ORDER.indexOf(previous);
|
|
100
|
+
const nextRank = PRIVACY_CLASS_ORDER.indexOf(next);
|
|
101
|
+
if (previousRank >= 0 && nextRank >= 0 && nextRank < previousRank) {
|
|
102
|
+
return `Rejected privacy downgrade for ${memory.source_id}: ${previous} -> ${next}`;
|
|
103
|
+
}
|
|
104
|
+
} catch {}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_auditSourceIngest(result) {
|
|
109
|
+
if (!this.brain || typeof this.brain.logWrite !== 'function' || result.dryRun) return;
|
|
110
|
+
try {
|
|
111
|
+
this.brain.logWrite('source_ingest', 'memories', {
|
|
112
|
+
id: result.sourceId,
|
|
113
|
+
source: result.adapterId,
|
|
114
|
+
subject: `inserted:${result.inserted};indexed:${result.indexed};rejected:${result.rejected}`,
|
|
115
|
+
});
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function toBrainMemory(record, adapterId) {
|
|
121
|
+
const metadata = {
|
|
122
|
+
...(record.metadata || {}),
|
|
123
|
+
adapterId,
|
|
124
|
+
sourceId: record.sourceId,
|
|
125
|
+
sourceFile: record.sourceFile,
|
|
126
|
+
itemId: record.itemId,
|
|
127
|
+
role: record.role || '',
|
|
128
|
+
transforms: record.transforms || [],
|
|
129
|
+
ingestMode: record.ingestMode || '',
|
|
130
|
+
privacyClass: record.privacyClass || '',
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
source: adapterId,
|
|
134
|
+
source_id: `${record.sourceId}:${record.itemId}`,
|
|
135
|
+
source_channel: metadata.cwd || metadata.sessionId || '',
|
|
136
|
+
memory_type: record.memoryType,
|
|
137
|
+
direction: directionForRole(record.role),
|
|
138
|
+
participants: metadata.participants || '',
|
|
139
|
+
subject: metadata.gitBranch || metadata.cwd || record.sourceId,
|
|
140
|
+
content: record.content,
|
|
141
|
+
content_raw: record.contentRaw || record.content,
|
|
142
|
+
metadata: JSON.stringify(metadata),
|
|
143
|
+
importance: 0.55,
|
|
144
|
+
timestamp: record.timestamp,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function directionForRole(role) {
|
|
149
|
+
if (role === 'user') return 'outbound';
|
|
150
|
+
if (role === 'assistant' || role === 'tool') return 'observed';
|
|
151
|
+
if (role === 'exchange') return 'exchange';
|
|
152
|
+
return 'observed';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
SessionIngestService,
|
|
157
|
+
createSessionIngestService,
|
|
158
|
+
toBrainMemory,
|
|
159
|
+
};
|