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.
- package/README.md +8 -0
- package/bin/create-walle.js +815 -45
- package/package.json +2 -2
- package/template/bin/ctm-dev-cleanup.js +90 -4
- package/template/bin/ctm-launch.sh +49 -1
- package/template/bin/dev.sh +45 -1
- package/template/bin/ensure-stable-node.js +132 -0
- package/template/bin/install-service.sh +9 -0
- package/template/claude-task-manager/api-prompts.js +899 -119
- package/template/claude-task-manager/approval-agent.js +360 -40
- package/template/claude-task-manager/bin/ctm-disclaim.c +42 -0
- package/template/claude-task-manager/bin/ctm-hotkey.swift +67 -81
- package/template/claude-task-manager/bin/ctm-screen-auth.swift +37 -0
- package/template/claude-task-manager/bin/install-hotkey.sh +97 -49
- package/template/claude-task-manager/bin/restart-ctm.sh +14 -0
- package/template/claude-task-manager/db.js +399 -48
- package/template/claude-task-manager/docs/approval-hook-sandbox.md +84 -0
- package/template/claude-task-manager/docs/codex-app-server-approvals.md +72 -0
- package/template/claude-task-manager/docs/codex-native-sandbox.md +47 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +18 -1
- package/template/claude-task-manager/lib/approval-hook.js +200 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +1 -0
- package/template/claude-task-manager/lib/auth-rules.js +11 -0
- package/template/claude-task-manager/lib/background-llm.js +32 -4
- package/template/claude-task-manager/lib/codesign-identity.js +140 -0
- package/template/claude-task-manager/lib/codex-app-server-client.js +119 -0
- package/template/claude-task-manager/lib/codex-approval-bridge.js +118 -0
- package/template/claude-task-manager/lib/codex-history-terminal-renderer.js +571 -0
- package/template/claude-task-manager/lib/codex-paths.js +73 -0
- package/template/claude-task-manager/lib/codex-rollout-snapshot.js +164 -0
- package/template/claude-task-manager/lib/codex-rollout-tail.js +72 -0
- package/template/claude-task-manager/lib/codex-sandbox-args.js +47 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +118 -71
- package/template/claude-task-manager/lib/command-targets.js +163 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +61 -19
- package/template/claude-task-manager/lib/db-owner-worker-client.js +29 -1
- package/template/claude-task-manager/lib/escalation-review.js +80 -3
- package/template/claude-task-manager/lib/flow-control.js +52 -0
- package/template/claude-task-manager/lib/fs-watcher.js +24 -15
- package/template/claude-task-manager/lib/ingest-cooldown.js +68 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +8 -4
- package/template/claude-task-manager/lib/launchd-recovery.js +92 -0
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +207 -52
- package/template/claude-task-manager/lib/mobile-push-store.js +7 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +102 -1
- package/template/claude-task-manager/lib/model-overview-cache.js +1 -0
- package/template/claude-task-manager/lib/oauth-proxy-supervisor.js +2 -1
- package/template/claude-task-manager/lib/perf-tracker.js +29 -2
- package/template/claude-task-manager/lib/permission-match.js +146 -16
- package/template/claude-task-manager/lib/project-slug.js +33 -0
- package/template/claude-task-manager/lib/prompt-intent.js +51 -4
- package/template/claude-task-manager/lib/read-pool-client.js +48 -3
- package/template/claude-task-manager/lib/real-node.js +73 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +131 -14
- package/template/claude-task-manager/lib/session-content-backfill.js +24 -5
- package/template/claude-task-manager/lib/session-diagnostics-batch.js +87 -0
- package/template/claude-task-manager/lib/session-history.js +5 -7
- package/template/claude-task-manager/lib/session-host-manager.js +19 -0
- package/template/claude-task-manager/lib/session-jobs.js +6 -0
- package/template/claude-task-manager/lib/session-message-response-cache.js +89 -0
- package/template/claude-task-manager/lib/session-messages-page.js +211 -0
- package/template/claude-task-manager/lib/session-messages-projection.js +170 -0
- package/template/claude-task-manager/lib/session-standup.js +8 -0
- package/template/claude-task-manager/lib/session-timeline-summary.js +16 -2
- package/template/claude-task-manager/lib/session-token-usage.js +30 -8
- package/template/claude-task-manager/lib/session-workspace-binding.js +29 -15
- package/template/claude-task-manager/lib/storage-migration.js +2 -1
- package/template/claude-task-manager/lib/transcript-store.js +179 -12
- package/template/claude-task-manager/lib/walle-ctm-history.js +298 -11
- package/template/claude-task-manager/lib/walle-permission-reply.js +49 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +22 -1
- package/template/claude-task-manager/lib/walle-supervisor.js +42 -3
- package/template/claude-task-manager/package.json +5 -2
- package/template/claude-task-manager/prompt-harvest.js +31 -11
- package/template/claude-task-manager/providers/claude-code.js +29 -1
- package/template/claude-task-manager/providers/codex.js +13 -1
- package/template/claude-task-manager/public/css/setup.css +11 -0
- package/template/claude-task-manager/public/css/walle-session.css +132 -4
- package/template/claude-task-manager/public/css/walle.css +89 -0
- package/template/claude-task-manager/public/icon-16.png +0 -0
- package/template/claude-task-manager/public/icon-32.png +0 -0
- package/template/claude-task-manager/public/icon-512.png +0 -0
- package/template/claude-task-manager/public/index.html +2483 -165
- package/template/claude-task-manager/public/js/activation-render-check.js +55 -0
- package/template/claude-task-manager/public/js/flow-control-policy.js +52 -0
- package/template/claude-task-manager/public/js/message-renderer.js +60 -1
- package/template/claude-task-manager/public/js/prompts.js +13 -1
- package/template/claude-task-manager/public/js/session-status-precedence.js +9 -3
- package/template/claude-task-manager/public/js/setup.js +54 -10
- package/template/claude-task-manager/public/js/stream-resize-policy.js +80 -0
- package/template/claude-task-manager/public/js/stream-view.js +78 -0
- package/template/claude-task-manager/public/js/terminal-reconciler.js +52 -2
- package/template/claude-task-manager/public/js/tool-state.js +155 -0
- package/template/claude-task-manager/public/js/walle-session.js +887 -326
- package/template/claude-task-manager/public/js/walle.js +306 -195
- package/template/claude-task-manager/public/m/app.css +1 -0
- package/template/claude-task-manager/public/m/app.js +33 -3
- package/template/claude-task-manager/queue-engine.js +45 -1
- package/template/claude-task-manager/server.js +3367 -540
- package/template/claude-task-manager/workers/approval-blocklist.js +130 -17
- package/template/claude-task-manager/workers/db-owner-worker.js +31 -1
- package/template/claude-task-manager/workers/read-pool-worker.js +92 -5
- package/template/claude-task-manager/workers/session-host-process.js +10 -0
- package/template/claude-task-manager/workers/state-detectors/codex.js +58 -7
- package/template/package.json +2 -3
- package/template/shared/icons/AppIcon-ctm.icns +0 -0
- package/template/shared/icons/AppIcon-walle.icns +0 -0
- package/template/wall-e/agent.js +139 -18
- package/template/wall-e/api-walle.js +201 -22
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +1981 -0
- package/template/wall-e/brain.js +1049 -39
- package/template/wall-e/chat.js +427 -86
- package/template/wall-e/coding/acceptance-contract.js +26 -1
- package/template/wall-e/coding/action-memory-policy.js +353 -0
- package/template/wall-e/coding/action-memory-store.js +814 -0
- package/template/wall-e/coding/initial-messages.js +197 -0
- package/template/wall-e/coding/no-progress-guard.js +327 -0
- package/template/wall-e/coding/permission-service.js +88 -22
- package/template/wall-e/coding/session-workspaces.js +81 -0
- package/template/wall-e/coding/shell-sandbox.js +124 -0
- package/template/wall-e/coding/stream-processor.js +63 -2
- package/template/wall-e/coding/tool-execution-controller.js +14 -1
- package/template/wall-e/coding/tool-registry.js +1 -1
- package/template/wall-e/coding/transcript-writer.js +3 -0
- package/template/wall-e/coding-orchestrator.js +636 -35
- package/template/wall-e/coding-prompts.js +51 -2
- package/template/wall-e/docs/model-routing-policy.md +59 -0
- package/template/wall-e/docs/walle-shell-sandbox.md +61 -0
- package/template/wall-e/extraction/knowledge-extractor.js +76 -23
- package/template/wall-e/http/chat-api.js +30 -12
- package/template/wall-e/http/model-admin.js +93 -1
- package/template/wall-e/lib/background-lanes.js +133 -0
- package/template/wall-e/lib/boot-profile.js +11 -0
- package/template/wall-e/lib/brain-owner-worker-client.js +324 -0
- package/template/wall-e/lib/brain-read-pool-client.js +311 -0
- package/template/wall-e/lib/diagnostics-flags.js +87 -0
- package/template/wall-e/lib/event-loop-monitor.js +74 -3
- package/template/wall-e/lib/mcp-integration.js +7 -1
- package/template/wall-e/lib/real-node.js +98 -0
- package/template/wall-e/lib/runtime-health.js +206 -0
- package/template/wall-e/lib/runtime-worker-pool.js +101 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +231 -0
- package/template/wall-e/lib/scheduler.js +446 -17
- package/template/wall-e/lib/service-health.js +61 -2
- package/template/wall-e/lib/service-readiness.js +258 -0
- package/template/wall-e/lib/usage.js +152 -0
- package/template/wall-e/lib/worker-thread-pool.js +389 -0
- package/template/wall-e/llm/client.js +81 -4
- package/template/wall-e/llm/default-fallback.js +54 -8
- package/template/wall-e/llm/mlx.js +536 -73
- package/template/wall-e/llm/mlx.plugin.json +1 -1
- package/template/wall-e/llm/ollama.js +342 -43
- package/template/wall-e/llm/provider-error.js +18 -1
- package/template/wall-e/llm/provider-health-state.js +176 -0
- package/template/wall-e/llm/routing-policy.js +796 -0
- package/template/wall-e/llm/supported-models.js +5 -0
- package/template/wall-e/loops/tasks.js +60 -14
- package/template/wall-e/loops/think.js +89 -24
- package/template/wall-e/mcp-server.js +192 -28
- package/template/wall-e/server.js +32 -7
- package/template/wall-e/skills/script-skill-runner.js +8 -1
- package/template/wall-e/skills/skill-planner.js +64 -1
- package/template/wall-e/tools/builtin-middleware.js +67 -2
- package/template/wall-e/tools/local-tools.js +116 -26
- package/template/wall-e/tools/permission-checker.js +52 -4
- package/template/wall-e/tools/permission-rules.js +36 -0
- package/template/wall-e/tools/shell-analyzer.js +46 -1
- package/template/wall-e/training/gemma-e4b-qlora.js +314 -0
- package/template/wall-e/training/real-trajectory-miner.js +2617 -0
- package/template/wall-e/training/replay-eval-analysis.js +151 -0
- package/template/wall-e/training/run-shell-command-selector.js +277 -0
- package/template/wall-e/training/tool-sft-dataset.js +312 -0
- package/template/wall-e/training/tool-sft-renderers.js +144 -0
- package/template/wall-e/training/tool-trace-harvester.js +1440 -0
- package/template/wall-e/training/trajectory-action-selector.js +364 -0
- package/template/wall-e/weather-runtime.js +232 -0
- package/template/wall-e/workers/brain-owner-worker.js +162 -0
- package/template/wall-e/workers/brain-read-worker.js +148 -0
- package/template/wall-e/workers/runtime-worker.js +145 -0
|
@@ -150,6 +150,11 @@ const SUPPORTED_MODEL_CATALOG = Object.freeze({
|
|
|
150
150
|
},
|
|
151
151
|
]),
|
|
152
152
|
ollama: Object.freeze([
|
|
153
|
+
{
|
|
154
|
+
id: 'gemma4:e4b-it-qat',
|
|
155
|
+
name: 'Gemma 4 E4B IT QAT',
|
|
156
|
+
capabilities: Object.freeze(['chat', 'code', 'tools', 'reasoning']),
|
|
157
|
+
},
|
|
153
158
|
{
|
|
154
159
|
id: 'gemma4:e4b',
|
|
155
160
|
name: 'Gemma 4 E4B',
|
|
@@ -3,6 +3,8 @@ const brain = require('../brain');
|
|
|
3
3
|
const { spawn } = require('child_process');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { findSkill } = require('../skills/skill-loader');
|
|
6
|
+
const runtimeHealth = require('../lib/runtime-health');
|
|
7
|
+
const { retryOnWriteLockBusy } = require('../shared/sqlite-write-lock');
|
|
6
8
|
|
|
7
9
|
const WALL_E_DIR = path.join(__dirname, '..');
|
|
8
10
|
|
|
@@ -39,6 +41,42 @@ function appendLog(taskId, line) {
|
|
|
39
41
|
if (entry.lines.length > 500) entry.lines.shift();
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
async function safeUpdateTask(taskId, updates, options = {}) {
|
|
45
|
+
const reason = options.reason || 'task-update';
|
|
46
|
+
const bestEffort = options.bestEffort === true;
|
|
47
|
+
const op = runtimeHealth.beginOperation('tasks.updateTask', { reason, bestEffort });
|
|
48
|
+
let retries = 0;
|
|
49
|
+
try {
|
|
50
|
+
await retryOnWriteLockBusy(
|
|
51
|
+
() => brain.updateTask(taskId, updates),
|
|
52
|
+
{
|
|
53
|
+
retries: Number.isInteger(options.retries) ? options.retries : 4,
|
|
54
|
+
backoffMs: Number.isFinite(options.backoffMs) ? options.backoffMs : 75,
|
|
55
|
+
onRetry: ({ attempt, error }) => {
|
|
56
|
+
retries = attempt;
|
|
57
|
+
runtimeHealth.recordOperation('tasks.updateTask.retry', 0, {
|
|
58
|
+
meta: {
|
|
59
|
+
reason,
|
|
60
|
+
attempt,
|
|
61
|
+
holderPid: error?.lockInfo?.pid || 0,
|
|
62
|
+
holderLabel: error?.lockInfo?.label || '',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
op.end({ meta: { reason, retries } });
|
|
69
|
+
return true;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
op.end({ ok: false, error: err, meta: { reason, retries, bestEffort } });
|
|
72
|
+
if (bestEffort) {
|
|
73
|
+
console.warn(`[tasks] Skipped best-effort task update (${reason}): ${err.message}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
42
80
|
/**
|
|
43
81
|
* On daemon startup, recover tasks that were interrupted (stuck in 'running').
|
|
44
82
|
* Resets them to 'pending' so they get re-picked up.
|
|
@@ -80,11 +118,11 @@ async function tryResumeSlackTasks() {
|
|
|
80
118
|
|
|
81
119
|
// Token is valid — un-pause all Slack tasks
|
|
82
120
|
for (const t of pausedSlack) {
|
|
83
|
-
|
|
121
|
+
await safeUpdateTask(t.id, {
|
|
84
122
|
status: 'pending',
|
|
85
123
|
error: null,
|
|
86
124
|
next_run_at: new Date().toISOString(),
|
|
87
|
-
});
|
|
125
|
+
}, { reason: 'slack-auto-resume' });
|
|
88
126
|
console.log(`[tasks] Auto-resumed: ${t.title} (Slack token refreshed)`);
|
|
89
127
|
}
|
|
90
128
|
} catch {}
|
|
@@ -106,7 +144,7 @@ async function runDueTasks() {
|
|
|
106
144
|
|
|
107
145
|
const now = new Date().toISOString();
|
|
108
146
|
const hasCheckpoint = task.checkpoint ? ' (resuming from checkpoint)' : '';
|
|
109
|
-
|
|
147
|
+
await safeUpdateTask(task.id, { status: 'running', started_at: now }, { reason: 'task-start' });
|
|
110
148
|
appendLog(task.id, `[${now.slice(11, 19)}] Starting: ${task.title}${hasCheckpoint}`);
|
|
111
149
|
|
|
112
150
|
try {
|
|
@@ -148,7 +186,7 @@ async function runDueTasks() {
|
|
|
148
186
|
}
|
|
149
187
|
}
|
|
150
188
|
|
|
151
|
-
|
|
189
|
+
await safeUpdateTask(task.id, {
|
|
152
190
|
status: slackDeliveryFailed ? 'pending' : (task.type === 'recurring' ? 'pending' : 'completed'),
|
|
153
191
|
completed_at: completedAt,
|
|
154
192
|
last_run_at: completedAt,
|
|
@@ -160,7 +198,7 @@ async function runDueTasks() {
|
|
|
160
198
|
? new Date(Date.now() + 2 * 60 * 1000).toISOString() // retry in 2 min
|
|
161
199
|
: (task.type === 'recurring' ? computeNextDue(task.schedule) : null),
|
|
162
200
|
// Keep started_at so duration can be computed as completed_at - started_at
|
|
163
|
-
});
|
|
201
|
+
}, { reason: 'task-complete' });
|
|
164
202
|
|
|
165
203
|
// Consolidate briefing items if present
|
|
166
204
|
try {
|
|
@@ -234,7 +272,7 @@ async function runDueTasks() {
|
|
|
234
272
|
const allTasks = brain.listTasks({});
|
|
235
273
|
for (const t of allTasks) {
|
|
236
274
|
if (t.skill && t.skill.includes('slack') && (t.status === 'pending' || t.status === 'running')) {
|
|
237
|
-
|
|
275
|
+
await safeUpdateTask(t.id, { status: 'paused' }, { bestEffort: true, reason: 'slack-auth-pause-peer' });
|
|
238
276
|
console.log(`[tasks] Auto-paused: ${t.title} (Slack auth expired, refresh failed)`);
|
|
239
277
|
}
|
|
240
278
|
}
|
|
@@ -263,7 +301,7 @@ async function runDueTasks() {
|
|
|
263
301
|
}
|
|
264
302
|
}
|
|
265
303
|
|
|
266
|
-
|
|
304
|
+
await safeUpdateTask(task.id, {
|
|
267
305
|
status: task.type === 'recurring' ? 'pending' : 'failed',
|
|
268
306
|
error: isSlackAuth
|
|
269
307
|
? (slackRefreshSucceeded ? 'Slack token refreshed — retrying.' : 'Slack token expired. Reconnect Slack to retry.')
|
|
@@ -272,11 +310,11 @@ async function runDueTasks() {
|
|
|
272
310
|
last_run_at: new Date().toISOString(),
|
|
273
311
|
run_count: (task.run_count || 0) + 1,
|
|
274
312
|
next_run_at: task.type === 'recurring' ? computeNextDue(task.schedule) : null,
|
|
275
|
-
});
|
|
313
|
+
}, { reason: 'task-fail' });
|
|
276
314
|
|
|
277
315
|
// Only pause recurring tasks if refresh truly failed
|
|
278
316
|
if (isSlackAuth && !slackRefreshSucceeded && task.type === 'recurring') {
|
|
279
|
-
|
|
317
|
+
await safeUpdateTask(task.id, { status: 'paused' }, { bestEffort: true, reason: 'slack-auth-pause-current' });
|
|
280
318
|
}
|
|
281
319
|
}
|
|
282
320
|
}
|
|
@@ -296,7 +334,12 @@ async function executeSkill(taskId, task) {
|
|
|
296
334
|
const { runScriptSkill } = require('../skills/script-skill-runner');
|
|
297
335
|
return runScriptSkill(skill, task, {
|
|
298
336
|
log: line => appendLog(taskId, line),
|
|
299
|
-
onCheckpoint: checkpoint =>
|
|
337
|
+
onCheckpoint: checkpoint => {
|
|
338
|
+
void safeUpdateTask(taskId, { checkpoint }, {
|
|
339
|
+
bestEffort: true,
|
|
340
|
+
reason: 'script-skill-checkpoint',
|
|
341
|
+
});
|
|
342
|
+
},
|
|
300
343
|
onProcess: child => taskProcesses.set(taskId, child),
|
|
301
344
|
onClose: child => {
|
|
302
345
|
if (taskProcesses.get(taskId) === child) taskProcesses.delete(taskId);
|
|
@@ -345,7 +388,10 @@ function executeScript(taskId, script, checkpoint, extraEnv) {
|
|
|
345
388
|
appendLog(taskId, line);
|
|
346
389
|
// Lines starting with CHECKPOINT: are saved as resume points
|
|
347
390
|
if (line.startsWith('CHECKPOINT:')) {
|
|
348
|
-
|
|
391
|
+
void safeUpdateTask(taskId, { checkpoint: line.slice(11).trim() }, {
|
|
392
|
+
bestEffort: true,
|
|
393
|
+
reason: 'shell-script-checkpoint',
|
|
394
|
+
});
|
|
349
395
|
}
|
|
350
396
|
});
|
|
351
397
|
});
|
|
@@ -438,9 +484,9 @@ async function executeMultiTurnChat(taskId, task) {
|
|
|
438
484
|
}
|
|
439
485
|
|
|
440
486
|
// Checkpoint between turns
|
|
441
|
-
|
|
487
|
+
await safeUpdateTask(taskId, {
|
|
442
488
|
checkpoint: JSON.stringify({ turn: turn + 1, partial: lastReply.slice(0, 2000) }),
|
|
443
|
-
});
|
|
489
|
+
}, { bestEffort: true, reason: 'multi-turn-checkpoint' });
|
|
444
490
|
}
|
|
445
491
|
|
|
446
492
|
return lastReply;
|
|
@@ -515,7 +561,7 @@ async function runTaskById(taskId) {
|
|
|
515
561
|
const task = brain.getTask(taskId);
|
|
516
562
|
if (!task) throw new Error('Task not found');
|
|
517
563
|
if (task.status === 'running') throw new Error('Task is already running');
|
|
518
|
-
|
|
564
|
+
await safeUpdateTask(taskId, { status: 'pending', next_run_at: new Date().toISOString(), error: null, result: null }, { reason: 'manual-run' });
|
|
519
565
|
return runDueTasks();
|
|
520
566
|
}
|
|
521
567
|
|
|
@@ -3,10 +3,49 @@ const { extractKnowledge } = require('../extraction/knowledge-extractor');
|
|
|
3
3
|
const { detectContradictions } = require('../extraction/contradiction');
|
|
4
4
|
const { linkKnowledgeToEntities, learnEntitiesFromText } = require('../extraction/entity-normalizer');
|
|
5
5
|
const { shouldRunLoop, skipped } = require('./loop-precheck');
|
|
6
|
+
const runtimeHealth = require('../lib/runtime-health');
|
|
7
|
+
const { perfLogsEnabled } = require('../lib/diagnostics-flags');
|
|
6
8
|
let embeddings;
|
|
7
9
|
try { embeddings = require('../embeddings'); } catch { embeddings = null; }
|
|
8
10
|
|
|
9
11
|
const BATCH_SIZE = 20;
|
|
12
|
+
const THINK_PHASE_LOG_THRESHOLD_MS = Math.max(0, Number(process.env.WALLE_THINK_PHASE_LOG_THRESHOLD_MS || 1000));
|
|
13
|
+
|
|
14
|
+
function _phaseMeta(meta = {}) {
|
|
15
|
+
const out = {};
|
|
16
|
+
for (const [key, value] of Object.entries(meta || {})) {
|
|
17
|
+
if (value == null) continue;
|
|
18
|
+
if (typeof value === 'number' || typeof value === 'boolean') out[key] = value;
|
|
19
|
+
else out[key] = String(value).slice(0, 80);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function _measurePhase(name, meta, fn) {
|
|
25
|
+
const started = Date.now();
|
|
26
|
+
let ok = false;
|
|
27
|
+
try {
|
|
28
|
+
const result = await fn();
|
|
29
|
+
ok = true;
|
|
30
|
+
return result;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
const durationMs = Date.now() - started;
|
|
33
|
+
runtimeHealth.recordOperation(`think.phase.${name}`, durationMs, { ok: false, error, meta: _phaseMeta(meta) });
|
|
34
|
+
if (perfLogsEnabled()) {
|
|
35
|
+
console.warn(`[think-perf] phase=${name} failed durationMs=${durationMs} error=${error && (error.code || error.name || error.message)}`);
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
} finally {
|
|
39
|
+
if (ok) {
|
|
40
|
+
const durationMs = Date.now() - started;
|
|
41
|
+
runtimeHealth.recordOperation(`think.phase.${name}`, durationMs, { ok: true, meta: _phaseMeta(meta) });
|
|
42
|
+
if (perfLogsEnabled() && durationMs >= THINK_PHASE_LOG_THRESHOLD_MS) {
|
|
43
|
+
const details = Object.entries(_phaseMeta(meta)).map(([k, v]) => `${k}=${v}`).join(' ');
|
|
44
|
+
console.log(`[think-perf] phase=${name} durationMs=${durationMs}${details ? ` ${details}` : ''}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
10
49
|
|
|
11
50
|
// Contradiction → question policy. Historically EVERY detected contradiction created a
|
|
12
51
|
// pending_question AND superseded the old knowledge — yielding ~2,000 questions/day that
|
|
@@ -63,11 +102,15 @@ async function runOnce(opts = {}) {
|
|
|
63
102
|
|
|
64
103
|
try {
|
|
65
104
|
// Step 1: Extract knowledge + classifications (async API call)
|
|
66
|
-
const entries = await
|
|
105
|
+
const entries = await _measurePhase('extract', { pending: pending.length }, () => (
|
|
106
|
+
extractFn(pending, ownerName, { model: opts.model })
|
|
107
|
+
));
|
|
67
108
|
const classifications = entries._classifications || [];
|
|
68
109
|
|
|
69
110
|
// Step 2: Detect contradictions (async API call, outside transaction)
|
|
70
|
-
const contradictions = await
|
|
111
|
+
const contradictions = await _measurePhase('contradictions', { entries: entries.length }, () => (
|
|
112
|
+
detectContradictions(entries, ownerName, { detectFn: opts.detectFn })
|
|
113
|
+
));
|
|
71
114
|
|
|
72
115
|
// Step 3: DB transaction: insert knowledge + handle contradictions + classify + mark done
|
|
73
116
|
const db = brain.getDb();
|
|
@@ -84,8 +127,6 @@ async function runOnce(opts = {}) {
|
|
|
84
127
|
});
|
|
85
128
|
insertedIds.push({ ...entry, _id: result.id });
|
|
86
129
|
knowledgeExtracted++;
|
|
87
|
-
// Link to entities (rule-based, no API cost)
|
|
88
|
-
try { linkKnowledgeToEntities({ ...entry, id: result.id }, ownerName); } catch {}
|
|
89
130
|
}
|
|
90
131
|
|
|
91
132
|
// Handle contradictions
|
|
@@ -128,47 +169,71 @@ async function runOnce(opts = {}) {
|
|
|
128
169
|
brain.updateMemoryExtraction(mem.id, 'done');
|
|
129
170
|
}
|
|
130
171
|
});
|
|
131
|
-
|
|
172
|
+
await _measurePhase('db_write', {
|
|
173
|
+
pending: pending.length,
|
|
174
|
+
entries: entries.length,
|
|
175
|
+
contradictions: contradictions.length,
|
|
176
|
+
classifications: classifications.length,
|
|
177
|
+
}, () => writeResults());
|
|
178
|
+
|
|
179
|
+
// Entity links are derived from inserted knowledge, so keep them out of the
|
|
180
|
+
// extraction-status transaction. This shortens the critical SQLite write lock
|
|
181
|
+
// while preserving best-effort entity graph enrichment.
|
|
182
|
+
await _measurePhase('entity_linking', { entries: insertedIds.length }, () => {
|
|
183
|
+
for (const entry of insertedIds) {
|
|
184
|
+
try { linkKnowledgeToEntities({ ...entry, id: entry._id }, ownerName); } catch {}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
132
187
|
} catch (err) {
|
|
133
188
|
console.error('[think] Extraction failed:', err.message);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
189
|
+
await _measurePhase('mark_failed', { pending: pending.length }, () => {
|
|
190
|
+
for (const mem of pending) {
|
|
191
|
+
try {
|
|
192
|
+
brain.updateMemoryExtraction(mem.id, 'failed');
|
|
193
|
+
} catch (updateErr) {
|
|
194
|
+
console.error('[think] Failed to mark memory as failed:', updateErr.message);
|
|
195
|
+
}
|
|
139
196
|
}
|
|
140
|
-
}
|
|
197
|
+
});
|
|
141
198
|
}
|
|
142
199
|
|
|
143
200
|
// Step 4a: Learn entities from memory content (rule-based, no API cost)
|
|
144
|
-
|
|
201
|
+
await _measurePhase('entity_learning', { pending: pending.length }, () => {
|
|
145
202
|
for (const mem of pending) {
|
|
146
203
|
if (mem.content && mem.content.length > 50) {
|
|
147
204
|
learnEntitiesFromText(mem.content, ownerName);
|
|
148
205
|
}
|
|
149
206
|
}
|
|
150
|
-
}
|
|
207
|
+
}).catch(() => {});
|
|
151
208
|
|
|
152
209
|
// Step 4: Compute embeddings for processed memories + new knowledge (async, non-blocking)
|
|
153
210
|
if (embeddings && embeddings.isAvailable()) {
|
|
154
211
|
try {
|
|
155
212
|
const textsToEmbed = pending.map(m => (m.content || '').substring(0, 512));
|
|
156
|
-
const memEmbeddings = await embeddings.
|
|
213
|
+
const memEmbeddings = await _measurePhase('embeddings.memory', { pending: pending.length }, () => (
|
|
214
|
+
embeddings.batchComputeEmbeddings(textsToEmbed)
|
|
215
|
+
));
|
|
157
216
|
if (memEmbeddings) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
217
|
+
await _measurePhase('embeddings.memory_store', { pending: pending.length }, () => {
|
|
218
|
+
for (let i = 0; i < pending.length; i++) {
|
|
219
|
+
embeddings.storeEmbedding(pending[i].id, 'memory', memEmbeddings[i]);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
161
222
|
console.log(`[think] Stored ${pending.length} memory embeddings`);
|
|
162
223
|
}
|
|
163
224
|
|
|
164
225
|
// Embed new knowledge triples
|
|
165
226
|
if (insertedIds.length > 0) {
|
|
166
227
|
const kTexts = insertedIds.map(k => `${k.subject} ${k.predicate} ${k.object}`);
|
|
167
|
-
const kEmbeddings = await embeddings.
|
|
228
|
+
const kEmbeddings = await _measurePhase('embeddings.knowledge', { entries: insertedIds.length }, () => (
|
|
229
|
+
embeddings.batchComputeEmbeddings(kTexts)
|
|
230
|
+
));
|
|
168
231
|
if (kEmbeddings) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
232
|
+
await _measurePhase('embeddings.knowledge_store', { entries: insertedIds.length }, () => {
|
|
233
|
+
for (let i = 0; i < insertedIds.length; i++) {
|
|
234
|
+
embeddings.storeEmbedding(insertedIds[i]._id, 'knowledge', kEmbeddings[i]);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
172
237
|
console.log(`[think] Stored ${insertedIds.length} knowledge embeddings`);
|
|
173
238
|
}
|
|
174
239
|
}
|
|
@@ -177,13 +242,13 @@ async function runOnce(opts = {}) {
|
|
|
177
242
|
}
|
|
178
243
|
}
|
|
179
244
|
|
|
180
|
-
brain.upsertCheckpoint('think', {
|
|
245
|
+
await _measurePhase('checkpoint', { pending: pending.length, entries: insertedIds.length }, () => brain.upsertCheckpoint('think', {
|
|
181
246
|
last_memory_at: pending[pending.length - 1].timestamp,
|
|
182
247
|
metadata: JSON.stringify({ memories_processed: pending.length, knowledge_extracted: knowledgeExtracted }),
|
|
183
|
-
});
|
|
248
|
+
}));
|
|
184
249
|
|
|
185
250
|
// Task 5: Self-resolve pending questions
|
|
186
|
-
await selfResolveQuestions(ownerName, opts);
|
|
251
|
+
await _measurePhase('self_resolve', {}, () => selfResolveQuestions(ownerName, opts));
|
|
187
252
|
|
|
188
253
|
return { memoriesProcessed: pending.length, knowledgeExtracted };
|
|
189
254
|
}
|
|
@@ -17,6 +17,10 @@ let _embeddings;
|
|
|
17
17
|
try { _embeddings = require('./embeddings'); } catch { _embeddings = null; }
|
|
18
18
|
|
|
19
19
|
const PROTOCOL_VERSION = '2025-03-26';
|
|
20
|
+
const DEFAULT_MCP_LOOKUP_SOURCE_TIMEOUT_MS = 2000;
|
|
21
|
+
const DEFAULT_MCP_LOOKUP_CONTEXT_PACK_TIMEOUT_MS = 1200;
|
|
22
|
+
const DEFAULT_MCP_LOOKUP_EMBEDDING_TIMEOUT_MS = 700;
|
|
23
|
+
const MAX_MCP_LOOKUP_TIMEOUT_MS = 10_000;
|
|
20
24
|
|
|
21
25
|
const MCP_RESOURCES = [
|
|
22
26
|
{
|
|
@@ -718,6 +722,20 @@ async function executeTool(name, args) {
|
|
|
718
722
|
return routeWallEQuery(args.query, { context: args.context });
|
|
719
723
|
}
|
|
720
724
|
case 'walle_lookup_context': {
|
|
725
|
+
const canOffload =
|
|
726
|
+
process.env.WALL_E_PROCESS_ROLE !== 'wall-e-runtime-worker' &&
|
|
727
|
+
(process.env.NODE_ENV !== 'test' || process.env.WALL_E_RUNTIME_WORKER_POOL_TEST === '1');
|
|
728
|
+
if (canOffload) {
|
|
729
|
+
try {
|
|
730
|
+
return await require('./lib/runtime-worker-pool').requestRuntimeWorker(
|
|
731
|
+
'mcp.lookupWallEContext',
|
|
732
|
+
{ args },
|
|
733
|
+
{ heavy: true, timeoutMs: Number(args?.timeout_ms) || 45000 }
|
|
734
|
+
);
|
|
735
|
+
} catch (err) {
|
|
736
|
+
console.warn(`[mcp] walle_lookup_context worker failed; falling back local: ${err.message}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
721
739
|
return lookupWallEContext(args);
|
|
722
740
|
}
|
|
723
741
|
case 'walle_context_hint': {
|
|
@@ -1215,6 +1233,7 @@ async function lookupWallEContext(args = {}) {
|
|
|
1215
1233
|
if (!query) throw new Error('query is required');
|
|
1216
1234
|
const route = routeWallEQuery(query, { context: args.context });
|
|
1217
1235
|
const limit = clampLimit(args.limit, 5, 20);
|
|
1236
|
+
const timeouts = lookupSourceTimeouts(args);
|
|
1218
1237
|
const shouldUseWallE = Boolean(route.should_use_walle || args.force_memory);
|
|
1219
1238
|
const isProfileLookup = route.matched?.some(match => match.name === 'self_profile');
|
|
1220
1239
|
const includeMemories = args.include_memories === true || (args.include_memories !== false && shouldUseWallE);
|
|
@@ -1243,18 +1262,36 @@ async function lookupWallEContext(args = {}) {
|
|
|
1243
1262
|
};
|
|
1244
1263
|
|
|
1245
1264
|
if (includeCtmOperational) {
|
|
1246
|
-
const operational = await
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1265
|
+
const operational = await withMcpDeadline(
|
|
1266
|
+
() => ctmOperationalContext.lookupCtmOperationalContext({
|
|
1267
|
+
query,
|
|
1268
|
+
context: args.context,
|
|
1269
|
+
limit,
|
|
1270
|
+
force: true,
|
|
1271
|
+
}),
|
|
1272
|
+
timeouts.source,
|
|
1273
|
+
({ duration_ms, timeout_ms }) => ({
|
|
1274
|
+
ok: false,
|
|
1275
|
+
required: true,
|
|
1276
|
+
session_results: [],
|
|
1277
|
+
sources: {},
|
|
1278
|
+
timed_out: true,
|
|
1279
|
+
duration_ms,
|
|
1280
|
+
timeout_ms,
|
|
1281
|
+
reason: 'ctm_operational_context_timeout',
|
|
1282
|
+
error: `CTM operational context lookup exceeded ${timeout_ms}ms`,
|
|
1283
|
+
})
|
|
1284
|
+
);
|
|
1252
1285
|
result.ctm_operational_context = operational;
|
|
1253
1286
|
result.sources.ctm_operational = {
|
|
1254
1287
|
searched: true,
|
|
1255
1288
|
ok: Boolean(operational.ok),
|
|
1256
1289
|
required: Boolean(operational.required),
|
|
1257
|
-
reason: operational.ok ? '' : 'ctm_operational_context_unavailable',
|
|
1290
|
+
reason: operational.ok ? '' : (operational.reason || 'ctm_operational_context_unavailable'),
|
|
1291
|
+
error: operational.error || '',
|
|
1292
|
+
timed_out: Boolean(operational.timed_out),
|
|
1293
|
+
duration_ms: operational.duration_ms,
|
|
1294
|
+
timeout_ms: operational.timeout_ms,
|
|
1258
1295
|
remote_access: operational.sources?.remote_access || null,
|
|
1259
1296
|
ctm_sessions: operational.sources?.ctm_sessions || null,
|
|
1260
1297
|
count: (operational.session_results || []).length,
|
|
@@ -1263,27 +1300,71 @@ async function lookupWallEContext(args = {}) {
|
|
|
1263
1300
|
|
|
1264
1301
|
if (includeSessions) {
|
|
1265
1302
|
const sessionQuery = isProfileLookup ? 'thorough review fix issues commit high-agency' : query;
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1303
|
+
const sessionSearchPromise = withMcpDeadline(
|
|
1304
|
+
() => safeCtmSearchSessions({
|
|
1305
|
+
query: sessionQuery,
|
|
1306
|
+
limit,
|
|
1307
|
+
prefer_db: args.prefer_db !== false,
|
|
1308
|
+
}),
|
|
1309
|
+
timeouts.source,
|
|
1310
|
+
({ duration_ms, timeout_ms }) => ({
|
|
1311
|
+
ok: false,
|
|
1312
|
+
source: 'ctm-db',
|
|
1313
|
+
results: [],
|
|
1314
|
+
reason: 'ctm_session_search_timeout',
|
|
1315
|
+
error: `CTM session search exceeded ${timeout_ms}ms`,
|
|
1316
|
+
timed_out: true,
|
|
1317
|
+
duration_ms,
|
|
1318
|
+
timeout_ms,
|
|
1319
|
+
})
|
|
1320
|
+
);
|
|
1321
|
+
const contextPackPromise = withMcpDeadline(
|
|
1322
|
+
() => safeCtmContextPack({
|
|
1323
|
+
task: query,
|
|
1324
|
+
query: sessionQuery,
|
|
1325
|
+
limit: Math.min(limit, 5),
|
|
1326
|
+
token_budget: 6000,
|
|
1327
|
+
mode: 'compact',
|
|
1328
|
+
include_raw: false,
|
|
1329
|
+
}),
|
|
1330
|
+
timeouts.context_pack,
|
|
1331
|
+
({ duration_ms, timeout_ms }) => ({
|
|
1332
|
+
ok: false,
|
|
1333
|
+
source: 'ctm-db',
|
|
1334
|
+
sessions: [],
|
|
1335
|
+
messages: [],
|
|
1336
|
+
reason: 'ctm_context_pack_timeout',
|
|
1337
|
+
error: `CTM context pack lookup exceeded ${timeout_ms}ms`,
|
|
1338
|
+
timed_out: true,
|
|
1339
|
+
duration_ms,
|
|
1340
|
+
timeout_ms,
|
|
1341
|
+
})
|
|
1342
|
+
);
|
|
1343
|
+
const [sessionSearch, contextPack] = await Promise.all([sessionSearchPromise, contextPackPromise]);
|
|
1271
1344
|
result.session_results = (sessionSearch.results || []).slice(0, limit).map(stripRank);
|
|
1272
1345
|
result.sources.ctm_sessions = {
|
|
1273
1346
|
searched: true,
|
|
1274
1347
|
ok: Boolean(sessionSearch.ok),
|
|
1275
1348
|
source: sessionSearch.source || '',
|
|
1276
1349
|
reason: sessionSearch.reason || '',
|
|
1350
|
+
error: sessionSearch.error || '',
|
|
1351
|
+
timed_out: Boolean(sessionSearch.timed_out),
|
|
1352
|
+
duration_ms: sessionSearch.duration_ms,
|
|
1353
|
+
timeout_ms: sessionSearch.timeout_ms,
|
|
1277
1354
|
count: result.session_results.length,
|
|
1278
1355
|
};
|
|
1279
|
-
result.context_pack =
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1356
|
+
result.context_pack = contextPack;
|
|
1357
|
+
result.sources.ctm_context_pack = {
|
|
1358
|
+
searched: true,
|
|
1359
|
+
ok: contextPack?.ok !== false,
|
|
1360
|
+
source: contextPack?.source || 'ctm-db',
|
|
1361
|
+
reason: contextPack?.reason || '',
|
|
1362
|
+
error: contextPack?.error || '',
|
|
1363
|
+
timed_out: Boolean(contextPack?.timed_out),
|
|
1364
|
+
duration_ms: contextPack?.duration_ms,
|
|
1365
|
+
timeout_ms: contextPack?.timeout_ms,
|
|
1366
|
+
count: (contextPack?.messages || []).length,
|
|
1367
|
+
};
|
|
1287
1368
|
}
|
|
1288
1369
|
|
|
1289
1370
|
if (includeMemories) {
|
|
@@ -1293,12 +1374,28 @@ async function lookupWallEContext(args = {}) {
|
|
|
1293
1374
|
since: args.since,
|
|
1294
1375
|
until: args.until,
|
|
1295
1376
|
};
|
|
1296
|
-
const memorySearch = await
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1377
|
+
const memorySearch = await withMcpDeadline(
|
|
1378
|
+
() => searchWallEMemories({
|
|
1379
|
+
query: memoryQuery,
|
|
1380
|
+
limit,
|
|
1381
|
+
filters,
|
|
1382
|
+
hybrid: !Object.values(filters).some(Boolean),
|
|
1383
|
+
embeddingTimeoutMs: timeouts.embedding,
|
|
1384
|
+
}),
|
|
1385
|
+
timeouts.source,
|
|
1386
|
+
({ duration_ms, timeout_ms }) => ({
|
|
1387
|
+
results: [],
|
|
1388
|
+
vector_search_used: false,
|
|
1389
|
+
source: sourceStatus('wall-e-memory', {
|
|
1390
|
+
ok: false,
|
|
1391
|
+
reason: 'wall_e_memory_search_timeout',
|
|
1392
|
+
error: `Wall-E memory search exceeded ${timeout_ms}ms`,
|
|
1393
|
+
timed_out: true,
|
|
1394
|
+
duration_ms,
|
|
1395
|
+
timeout_ms,
|
|
1396
|
+
}),
|
|
1397
|
+
})
|
|
1398
|
+
);
|
|
1302
1399
|
result.memory_results = memorySearch.results.map(formatMemoryResult);
|
|
1303
1400
|
result.sources.memories = {
|
|
1304
1401
|
searched: true,
|
|
@@ -1308,7 +1405,11 @@ async function lookupWallEContext(args = {}) {
|
|
|
1308
1405
|
source: memorySearch.source.source,
|
|
1309
1406
|
reason: memorySearch.source.reason || '',
|
|
1310
1407
|
error: memorySearch.source.error || '',
|
|
1408
|
+
timed_out: Boolean(memorySearch.source.timed_out),
|
|
1409
|
+
duration_ms: memorySearch.source.duration_ms,
|
|
1410
|
+
timeout_ms: memorySearch.source.timeout_ms,
|
|
1311
1411
|
vector_search_used: memorySearch.vector_search_used,
|
|
1412
|
+
vector: memorySearch.source.vector || null,
|
|
1312
1413
|
count: result.memory_results.length,
|
|
1313
1414
|
};
|
|
1314
1415
|
}
|
|
@@ -1416,7 +1517,7 @@ function safeBrainSearchMemories(args = {}) {
|
|
|
1416
1517
|
}
|
|
1417
1518
|
}
|
|
1418
1519
|
|
|
1419
|
-
async function searchWallEMemories({ query, limit, filters = {}, hybrid = true, terms } = {}) {
|
|
1520
|
+
async function searchWallEMemories({ query, limit, filters = {}, hybrid = true, terms, embeddingTimeoutMs } = {}) {
|
|
1420
1521
|
const base = safeBrainSearchMemories({ query, limit, terms, ...filters });
|
|
1421
1522
|
let results = base.results;
|
|
1422
1523
|
let vectorSearchUsed = false;
|
|
@@ -1425,7 +1526,19 @@ async function searchWallEMemories({ query, limit, filters = {}, hybrid = true,
|
|
|
1425
1526
|
if (base.source.ok && hybrid && _embeddings && _embeddings.isAvailable()) {
|
|
1426
1527
|
vectorStatus.attempted = true;
|
|
1427
1528
|
try {
|
|
1428
|
-
const queryEmb = await
|
|
1529
|
+
const queryEmb = await withMcpDeadline(
|
|
1530
|
+
() => _embeddings.computeEmbedding(query),
|
|
1531
|
+
clampMilliseconds(embeddingTimeoutMs, DEFAULT_MCP_LOOKUP_EMBEDDING_TIMEOUT_MS, MAX_MCP_LOOKUP_TIMEOUT_MS),
|
|
1532
|
+
({ duration_ms, timeout_ms }) => {
|
|
1533
|
+
vectorStatus.ok = false;
|
|
1534
|
+
vectorStatus.reason = 'wall_e_vector_search_timeout';
|
|
1535
|
+
vectorStatus.error = `Embedding search exceeded ${timeout_ms}ms`;
|
|
1536
|
+
vectorStatus.timed_out = true;
|
|
1537
|
+
vectorStatus.duration_ms = duration_ms;
|
|
1538
|
+
vectorStatus.timeout_ms = timeout_ms;
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
);
|
|
1429
1542
|
if (queryEmb) {
|
|
1430
1543
|
const vectorResults = _embeddings.searchSimilarMemories(queryEmb, limit);
|
|
1431
1544
|
if (vectorResults.length > 0) {
|
|
@@ -1800,6 +1913,57 @@ function clampLimit(value, fallback, max) {
|
|
|
1800
1913
|
return Math.min(Math.max(Math.trunc(n), 1), max);
|
|
1801
1914
|
}
|
|
1802
1915
|
|
|
1916
|
+
function clampMilliseconds(value, fallback, max = MAX_MCP_LOOKUP_TIMEOUT_MS) {
|
|
1917
|
+
const n = Number(value || fallback);
|
|
1918
|
+
if (!Number.isFinite(n)) return fallback;
|
|
1919
|
+
return Math.min(Math.max(Math.trunc(n), 25), max);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
function lookupSourceTimeouts(args = {}) {
|
|
1923
|
+
const source = clampMilliseconds(
|
|
1924
|
+
args.source_timeout_ms || process.env.WALLE_MCP_LOOKUP_SOURCE_TIMEOUT_MS,
|
|
1925
|
+
DEFAULT_MCP_LOOKUP_SOURCE_TIMEOUT_MS
|
|
1926
|
+
);
|
|
1927
|
+
return {
|
|
1928
|
+
source,
|
|
1929
|
+
context_pack: Math.min(source, clampMilliseconds(
|
|
1930
|
+
args.context_pack_timeout_ms || process.env.WALLE_MCP_LOOKUP_CONTEXT_PACK_TIMEOUT_MS,
|
|
1931
|
+
DEFAULT_MCP_LOOKUP_CONTEXT_PACK_TIMEOUT_MS
|
|
1932
|
+
)),
|
|
1933
|
+
embedding: Math.min(source, clampMilliseconds(
|
|
1934
|
+
args.embedding_timeout_ms || process.env.WALLE_MCP_LOOKUP_EMBEDDING_TIMEOUT_MS,
|
|
1935
|
+
DEFAULT_MCP_LOOKUP_EMBEDDING_TIMEOUT_MS
|
|
1936
|
+
)),
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
async function withMcpDeadline(factory, timeoutMs, fallbackFactory) {
|
|
1941
|
+
const startedAt = Date.now();
|
|
1942
|
+
const timeout = clampMilliseconds(timeoutMs, DEFAULT_MCP_LOOKUP_SOURCE_TIMEOUT_MS);
|
|
1943
|
+
let timer;
|
|
1944
|
+
const timedOut = Symbol('mcp_deadline_timeout');
|
|
1945
|
+
const task = Promise.resolve().then(factory);
|
|
1946
|
+
task.catch(() => {});
|
|
1947
|
+
let raced;
|
|
1948
|
+
try {
|
|
1949
|
+
raced = await Promise.race([
|
|
1950
|
+
task,
|
|
1951
|
+
new Promise(resolve => {
|
|
1952
|
+
timer = setTimeout(() => resolve(timedOut), timeout);
|
|
1953
|
+
}),
|
|
1954
|
+
]);
|
|
1955
|
+
} finally {
|
|
1956
|
+
if (timer) clearTimeout(timer);
|
|
1957
|
+
}
|
|
1958
|
+
if (raced !== timedOut) {
|
|
1959
|
+
return raced;
|
|
1960
|
+
}
|
|
1961
|
+
return fallbackFactory({
|
|
1962
|
+
duration_ms: Date.now() - startedAt,
|
|
1963
|
+
timeout_ms: timeout,
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1803
1967
|
function formatMemoryResult(memory) {
|
|
1804
1968
|
return {
|
|
1805
1969
|
id: memory.id,
|