create-walle 0.9.24 → 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 +1053 -43
- 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/shared/sqlite-owner-guard.js +30 -0
- package/template/wall-e/shared/sqlite-owner-write-queue.js +225 -0
- package/template/wall-e/shared/sqlite-storage-policy.js +111 -0
- package/template/wall-e/shared/sqlite-write-lock.js +428 -0
- 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
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { Worker } = require('node:worker_threads');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 20 * 1000;
|
|
7
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 5 * 1000;
|
|
8
|
+
const DEFAULT_POOL_SIZE = 2;
|
|
9
|
+
|
|
10
|
+
function _durationMs(value, fallback, min = 0) {
|
|
11
|
+
const n = Number(value);
|
|
12
|
+
return Number.isFinite(n) ? Math.max(min, Math.trunc(n)) : fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function _poolSize(value) {
|
|
16
|
+
const n = Number(value);
|
|
17
|
+
if (!Number.isFinite(n) || n <= 0) return DEFAULT_POOL_SIZE;
|
|
18
|
+
return Math.min(8, Math.max(1, Math.trunc(n)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class BrainReadWorkerSlot {
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
if (!options.dbPath) throw new Error('BrainReadWorkerSlot requires dbPath');
|
|
24
|
+
this.dbPath = options.dbPath;
|
|
25
|
+
this.workerPath = options.workerPath;
|
|
26
|
+
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
27
|
+
this.closeTimeoutMs = options.closeTimeoutMs;
|
|
28
|
+
this.logger = options.logger || console;
|
|
29
|
+
this.index = options.index || 0;
|
|
30
|
+
this._worker = null;
|
|
31
|
+
this._startPromise = null;
|
|
32
|
+
this._ready = false;
|
|
33
|
+
this._closed = false;
|
|
34
|
+
this._nextRequestId = 1;
|
|
35
|
+
this._pending = new Map();
|
|
36
|
+
this._status = {
|
|
37
|
+
ready: false,
|
|
38
|
+
pendingClientRequests: 0,
|
|
39
|
+
completed: 0,
|
|
40
|
+
failed: 0,
|
|
41
|
+
active: null,
|
|
42
|
+
pending: 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
start() {
|
|
47
|
+
if (this._closed) return Promise.reject(new Error('Brain read worker is closed'));
|
|
48
|
+
if (this._ready) return Promise.resolve(this);
|
|
49
|
+
if (this._startPromise) return this._startPromise;
|
|
50
|
+
|
|
51
|
+
this._startPromise = new Promise((resolve, reject) => {
|
|
52
|
+
const worker = new Worker(this.workerPath, {
|
|
53
|
+
workerData: { dbPath: this.dbPath, slot: this.index },
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
WALL_E_PROCESS_ROLE: 'brain-read-worker',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
this._worker = worker;
|
|
60
|
+
|
|
61
|
+
const failStart = (err) => {
|
|
62
|
+
if (this._ready) return;
|
|
63
|
+
this._startPromise = null;
|
|
64
|
+
this._worker = null;
|
|
65
|
+
try { worker.terminate(); } catch {}
|
|
66
|
+
reject(err instanceof Error ? err : new Error(String(err || 'Brain read worker failed to start')));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
worker.on('message', (msg) => {
|
|
70
|
+
if (!msg || typeof msg !== 'object') return;
|
|
71
|
+
if (msg.type === 'ready') {
|
|
72
|
+
this._ready = true;
|
|
73
|
+
this._status = { ...this._status, ...(msg.status || {}), ready: true };
|
|
74
|
+
resolve(this);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (msg.type === 'status') {
|
|
78
|
+
this._status = {
|
|
79
|
+
...this._status,
|
|
80
|
+
...(msg.status || {}),
|
|
81
|
+
ready: this._ready,
|
|
82
|
+
pendingClientRequests: this._pending.size,
|
|
83
|
+
};
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (msg.type === 'response') {
|
|
87
|
+
this._finishRequest(msg.requestId, msg.error, msg.result);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (msg.type === 'closed') {
|
|
91
|
+
this._ready = false;
|
|
92
|
+
this._closed = true;
|
|
93
|
+
this._status.ready = false;
|
|
94
|
+
this._rejectAllPending(new Error('Brain read worker closed'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (msg.type === 'fatal') {
|
|
98
|
+
const err = new Error(msg.message || msg.error || 'Brain read worker fatal error');
|
|
99
|
+
err.code = msg.code || 'WALL_E_BRAIN_READ_WORKER_FATAL';
|
|
100
|
+
if (!this._ready) failStart(err);
|
|
101
|
+
this._rejectAllPending(err);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
worker.on('error', (err) => {
|
|
106
|
+
if (!this._ready) failStart(err);
|
|
107
|
+
this._rejectAllPending(err);
|
|
108
|
+
});
|
|
109
|
+
worker.on('exit', (code) => {
|
|
110
|
+
const wasReady = this._ready;
|
|
111
|
+
this._ready = false;
|
|
112
|
+
this._worker = null;
|
|
113
|
+
this._startPromise = null;
|
|
114
|
+
this._status.ready = false;
|
|
115
|
+
if (!this._closed && code !== 0) {
|
|
116
|
+
const err = new Error(`Brain read worker exited with code ${code}`);
|
|
117
|
+
err.code = 'WALL_E_BRAIN_READ_WORKER_EXITED';
|
|
118
|
+
if (!wasReady) failStart(err);
|
|
119
|
+
this._rejectAllPending(err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return this._startPromise;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async request(op, payload = {}, options = {}) {
|
|
128
|
+
await this.start();
|
|
129
|
+
if (!this._worker) throw new Error('Brain read worker is not running');
|
|
130
|
+
const requestId = this._nextRequestId++;
|
|
131
|
+
const timeoutMs = _durationMs(options.timeoutMs, this.requestTimeoutMs);
|
|
132
|
+
return await new Promise((resolve, reject) => {
|
|
133
|
+
let timer = null;
|
|
134
|
+
if (timeoutMs > 0) {
|
|
135
|
+
timer = setTimeout(() => {
|
|
136
|
+
this._pending.delete(requestId);
|
|
137
|
+
this._status.pendingClientRequests = this._pending.size;
|
|
138
|
+
reject(new Error(`Brain read worker request timed out: ${op}`));
|
|
139
|
+
}, timeoutMs);
|
|
140
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
141
|
+
}
|
|
142
|
+
this._pending.set(requestId, { op, resolve, reject, timer });
|
|
143
|
+
this._status.pendingClientRequests = this._pending.size;
|
|
144
|
+
try {
|
|
145
|
+
this._worker.postMessage({ type: 'request', requestId, op, payload });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
this._finishRequest(requestId, err);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getStatus() {
|
|
153
|
+
return {
|
|
154
|
+
...this._status,
|
|
155
|
+
ready: this._ready,
|
|
156
|
+
closed: this._closed,
|
|
157
|
+
pendingClientRequests: this._pending.size,
|
|
158
|
+
index: this.index,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async close(options = {}) {
|
|
163
|
+
if (this._closed) return { ok: true, alreadyClosed: true };
|
|
164
|
+
const worker = this._worker;
|
|
165
|
+
this._closed = true;
|
|
166
|
+
if (!worker) {
|
|
167
|
+
this._rejectAllPending(new Error('Brain read worker closed'));
|
|
168
|
+
return { ok: true, alreadyClosed: true };
|
|
169
|
+
}
|
|
170
|
+
const timeoutMs = _durationMs(options.timeoutMs, this.closeTimeoutMs);
|
|
171
|
+
await new Promise((resolve) => {
|
|
172
|
+
let done = false;
|
|
173
|
+
const finish = () => {
|
|
174
|
+
if (done) return;
|
|
175
|
+
done = true;
|
|
176
|
+
resolve();
|
|
177
|
+
};
|
|
178
|
+
let timer = null;
|
|
179
|
+
if (timeoutMs > 0) {
|
|
180
|
+
timer = setTimeout(() => worker.terminate().finally(finish), timeoutMs);
|
|
181
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
182
|
+
}
|
|
183
|
+
const onMessage = (msg) => {
|
|
184
|
+
if (msg?.type !== 'closed') return;
|
|
185
|
+
if (timer) clearTimeout(timer);
|
|
186
|
+
worker.off('message', onMessage);
|
|
187
|
+
finish();
|
|
188
|
+
};
|
|
189
|
+
worker.on('message', onMessage);
|
|
190
|
+
try { worker.postMessage({ type: 'close' }); }
|
|
191
|
+
catch {
|
|
192
|
+
if (timer) clearTimeout(timer);
|
|
193
|
+
worker.off('message', onMessage);
|
|
194
|
+
worker.terminate().finally(finish);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
this._ready = false;
|
|
198
|
+
this._worker = null;
|
|
199
|
+
this._rejectAllPending(new Error('Brain read worker closed'));
|
|
200
|
+
return { ok: true };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_finishRequest(requestId, error, result) {
|
|
204
|
+
const pending = this._pending.get(requestId);
|
|
205
|
+
if (!pending) return;
|
|
206
|
+
this._pending.delete(requestId);
|
|
207
|
+
this._status.pendingClientRequests = this._pending.size;
|
|
208
|
+
if (pending.timer) clearTimeout(pending.timer);
|
|
209
|
+
if (error) {
|
|
210
|
+
const err = error instanceof Error ? error : new Error(String(error.message || error || 'Brain read worker request failed'));
|
|
211
|
+
if (error.code) err.code = error.code;
|
|
212
|
+
pending.reject(err);
|
|
213
|
+
} else {
|
|
214
|
+
pending.resolve(result);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_rejectAllPending(err) {
|
|
219
|
+
const pending = Array.from(this._pending.values());
|
|
220
|
+
this._pending.clear();
|
|
221
|
+
this._status.pendingClientRequests = 0;
|
|
222
|
+
for (const req of pending) {
|
|
223
|
+
if (req.timer) clearTimeout(req.timer);
|
|
224
|
+
req.reject(err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
class BrainReadPoolClient {
|
|
230
|
+
constructor(options = {}) {
|
|
231
|
+
if (!options.dbPath) throw new Error('BrainReadPoolClient requires dbPath');
|
|
232
|
+
this.dbPath = options.dbPath;
|
|
233
|
+
this.workerPath = options.workerPath || path.resolve(__dirname, '..', 'workers', 'brain-read-worker.js');
|
|
234
|
+
this.requestTimeoutMs = _durationMs(options.requestTimeoutMs, DEFAULT_REQUEST_TIMEOUT_MS);
|
|
235
|
+
this.closeTimeoutMs = _durationMs(options.closeTimeoutMs, DEFAULT_CLOSE_TIMEOUT_MS);
|
|
236
|
+
this.size = _poolSize(options.size ?? process.env.WALL_E_BRAIN_READ_POOL_SIZE);
|
|
237
|
+
this.logger = options.logger || console;
|
|
238
|
+
this._workers = Array.from({ length: this.size }, (_, index) => new BrainReadWorkerSlot({
|
|
239
|
+
dbPath: this.dbPath,
|
|
240
|
+
workerPath: this.workerPath,
|
|
241
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
242
|
+
closeTimeoutMs: this.closeTimeoutMs,
|
|
243
|
+
logger: this.logger,
|
|
244
|
+
index,
|
|
245
|
+
}));
|
|
246
|
+
this._roundRobin = 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async request(op, payload = {}, options = {}) {
|
|
250
|
+
if (!op) throw new Error('Brain read pool request requires op');
|
|
251
|
+
const worker = this._pickWorker(options.heavy);
|
|
252
|
+
return worker.request(op, payload, options);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
getBrainStats(options) {
|
|
256
|
+
return this.request('getBrainStats', {}, options);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
listMemories(payload = {}, options) {
|
|
260
|
+
return this.request('listMemories', payload, options);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
listChatMessages(payload = {}, options) {
|
|
264
|
+
return this.request('listChatMessages', payload, options);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
listSessions(payload = {}, options) {
|
|
268
|
+
return this.request('listSessions', payload, options);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
listRuntimeEvents(payload = {}, options) {
|
|
272
|
+
return this.request('listRuntimeEvents', payload, options);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
searchMemories(payload = {}, options) {
|
|
276
|
+
return this.request('searchMemories', payload, { ...(options || {}), heavy: true });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getStatus() {
|
|
280
|
+
return {
|
|
281
|
+
size: this.size,
|
|
282
|
+
workers: this._workers.map((worker) => worker.getStatus()),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async close(options = {}) {
|
|
287
|
+
await Promise.allSettled(this._workers.map((worker) => worker.close(options)));
|
|
288
|
+
return { ok: true };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_pickWorker(heavy) {
|
|
292
|
+
if (heavy) {
|
|
293
|
+
return this._workers
|
|
294
|
+
.slice()
|
|
295
|
+
.sort((a, b) => a.getStatus().pendingClientRequests - b.getStatus().pendingClientRequests)[0];
|
|
296
|
+
}
|
|
297
|
+
const worker = this._workers[this._roundRobin % this._workers.length];
|
|
298
|
+
this._roundRobin += 1;
|
|
299
|
+
return worker;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function createBrainReadPoolClient(options = {}) {
|
|
304
|
+
return new BrainReadPoolClient(options);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
BrainReadPoolClient,
|
|
309
|
+
BrainReadWorkerSlot,
|
|
310
|
+
createBrainReadPoolClient,
|
|
311
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);
|
|
4
|
+
const FALSE_VALUES = new Set(['0', 'false', 'no', 'off']);
|
|
5
|
+
|
|
6
|
+
function _flagValue(env, names) {
|
|
7
|
+
for (const name of names) {
|
|
8
|
+
if (Object.prototype.hasOwnProperty.call(env, name)) return env[name];
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function _truthy(value) {
|
|
14
|
+
if (value == null) return false;
|
|
15
|
+
return TRUE_VALUES.has(String(value).trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _falsey(value) {
|
|
19
|
+
if (value == null) return false;
|
|
20
|
+
return FALSE_VALUES.has(String(value).trim().toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _enabledByFlag(env, names, defaultValue = false) {
|
|
24
|
+
const value = _flagValue(env, names);
|
|
25
|
+
if (_truthy(value)) return true;
|
|
26
|
+
if (_falsey(value)) return false;
|
|
27
|
+
return defaultValue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function runtimeDiagnosticsEnabled(env = process.env) {
|
|
31
|
+
return _enabledByFlag(env, [
|
|
32
|
+
'WALL_E_RUNTIME_DIAGNOSTICS',
|
|
33
|
+
'WALLE_RUNTIME_DIAGNOSTICS',
|
|
34
|
+
'WALL_E_DIAGNOSTICS',
|
|
35
|
+
'WALLE_DIAGNOSTICS',
|
|
36
|
+
], false);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function eventLoopMonitorEnabled(env = process.env) {
|
|
40
|
+
return runtimeDiagnosticsEnabled(env) && _enabledByFlag(env, [
|
|
41
|
+
'WALL_E_EVENT_LOOP_MONITOR',
|
|
42
|
+
'WALLE_EVENT_LOOP_MONITOR',
|
|
43
|
+
], true);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runtimeEventLedgerEnabled(env = process.env) {
|
|
47
|
+
return runtimeDiagnosticsEnabled(env) && _enabledByFlag(env, [
|
|
48
|
+
'WALL_E_RUNTIME_EVENT_LEDGER',
|
|
49
|
+
'WALLE_RUNTIME_EVENT_LEDGER',
|
|
50
|
+
], true);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function bootProfileEnabled(env = process.env) {
|
|
54
|
+
return runtimeDiagnosticsEnabled(env) && _enabledByFlag(env, [
|
|
55
|
+
'WALL_E_BOOT_PROFILE',
|
|
56
|
+
'WALLE_BOOT_PROFILE',
|
|
57
|
+
], true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function perfLogsEnabled(env = process.env) {
|
|
61
|
+
return runtimeDiagnosticsEnabled(env) && _enabledByFlag(env, [
|
|
62
|
+
'WALL_E_PERF_LOGS',
|
|
63
|
+
'WALLE_PERF_LOGS',
|
|
64
|
+
], true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function flagSnapshot(env = process.env) {
|
|
68
|
+
const runtime = runtimeDiagnosticsEnabled(env);
|
|
69
|
+
return {
|
|
70
|
+
runtime_diagnostics: runtime,
|
|
71
|
+
event_loop_monitor: eventLoopMonitorEnabled(env),
|
|
72
|
+
runtime_event_ledger: runtimeEventLedgerEnabled(env),
|
|
73
|
+
boot_profile: bootProfileEnabled(env),
|
|
74
|
+
perf_logs: perfLogsEnabled(env),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
runtimeDiagnosticsEnabled,
|
|
80
|
+
eventLoopMonitorEnabled,
|
|
81
|
+
runtimeEventLedgerEnabled,
|
|
82
|
+
bootProfileEnabled,
|
|
83
|
+
perfLogsEnabled,
|
|
84
|
+
flagSnapshot,
|
|
85
|
+
_truthy,
|
|
86
|
+
_falsey,
|
|
87
|
+
};
|
|
@@ -16,16 +16,77 @@
|
|
|
16
16
|
// All timers are unref'd so the monitor never keeps the process alive.
|
|
17
17
|
|
|
18
18
|
const { monitorEventLoopDelay } = require('perf_hooks');
|
|
19
|
+
const { eventLoopMonitorEnabled } = require('./diagnostics-flags');
|
|
19
20
|
|
|
20
21
|
let _histogram = null;
|
|
21
22
|
let _timer = null;
|
|
22
23
|
let _peakMs = 0;
|
|
23
24
|
let _lastWarn = null;
|
|
24
25
|
let _onWarn = null;
|
|
26
|
+
let _getContext = null;
|
|
25
27
|
|
|
26
28
|
const NS_PER_MS = 1e6;
|
|
27
29
|
const _ms = (ns) => Math.round((Number(ns) || 0) / NS_PER_MS);
|
|
28
30
|
|
|
31
|
+
function _safeString(value, limit = 160) {
|
|
32
|
+
return String(value || '')
|
|
33
|
+
.replace(/\/Users\/[^/\s]+/g, '/Users/<user>')
|
|
34
|
+
.replace(/[A-Za-z]:\\Users\\[^\\\s]+/g, 'C:\\Users\\<user>')
|
|
35
|
+
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<uuid>')
|
|
36
|
+
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '<email>')
|
|
37
|
+
.slice(0, limit);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _safeContext(value, depth = 0) {
|
|
41
|
+
if (value == null) return value;
|
|
42
|
+
if (depth > 4) return '[truncated]';
|
|
43
|
+
if (typeof value === 'string') return _safeString(value);
|
|
44
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value;
|
|
45
|
+
if (Array.isArray(value)) return value.slice(0, 10).map((item) => _safeContext(item, depth + 1));
|
|
46
|
+
if (typeof value === 'object') {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, item] of Object.entries(value).slice(0, 20)) {
|
|
49
|
+
out[_safeString(key, 80)] = _safeContext(item, depth + 1);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
return _safeString(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _summarizeContext(context) {
|
|
57
|
+
if (!context || typeof context !== 'object') return '';
|
|
58
|
+
const parts = [];
|
|
59
|
+
const schedulerActive = Array.isArray(context.scheduler?.active) ? context.scheduler.active : [];
|
|
60
|
+
if (schedulerActive.length) {
|
|
61
|
+
const active = schedulerActive.slice(0, 3).map((job) => {
|
|
62
|
+
const name = _safeString(job.name || job.job || job.id || 'job', 60);
|
|
63
|
+
const age = Number(job.ageMs || job.runtimeMs || job.durationMs || 0);
|
|
64
|
+
return age > 0 ? `${name}@${Math.round(age)}ms` : name;
|
|
65
|
+
});
|
|
66
|
+
parts.push(`scheduler=${active.join(',')}`);
|
|
67
|
+
}
|
|
68
|
+
const runtimeActive = Array.isArray(context.runtime_active) ? context.runtime_active : [];
|
|
69
|
+
if (runtimeActive.length) {
|
|
70
|
+
const active = runtimeActive.slice(0, 3).map((op) => {
|
|
71
|
+
const name = _safeString(op.name || 'operation', 60);
|
|
72
|
+
const age = Number(op.ageMs || 0);
|
|
73
|
+
return age > 0 ? `${name}@${Math.round(age)}ms` : name;
|
|
74
|
+
});
|
|
75
|
+
parts.push(`runtime=${active.join(',')}`);
|
|
76
|
+
}
|
|
77
|
+
const pool = context.worker_pool;
|
|
78
|
+
if (pool && typeof pool === 'object') {
|
|
79
|
+
const busy = Number(pool.busy ?? pool.busyCount);
|
|
80
|
+
const size = Number(pool.size ?? pool.maxWorkers ?? pool.concurrency);
|
|
81
|
+
const queued = Number(pool.queued ?? pool.queueDepth ?? pool.queuedOrRunning);
|
|
82
|
+
const poolBits = [];
|
|
83
|
+
if (Number.isFinite(busy) && Number.isFinite(size)) poolBits.push(`${busy}/${size} busy`);
|
|
84
|
+
if (Number.isFinite(queued)) poolBits.push(`${queued} queued`);
|
|
85
|
+
if (poolBits.length) parts.push(`workers=${poolBits.join(',')}`);
|
|
86
|
+
}
|
|
87
|
+
return parts.join('; ').slice(0, 360);
|
|
88
|
+
}
|
|
89
|
+
|
|
29
90
|
function isRunning() {
|
|
30
91
|
return _histogram != null;
|
|
31
92
|
}
|
|
@@ -37,15 +98,18 @@ function isRunning() {
|
|
|
37
98
|
* @param {number} [opts.sampleMs=10000] how often to evaluate + reset the window
|
|
38
99
|
* @param {number} [opts.warnThresholdMs=1000] warn if the worst spell in a window exceeds this
|
|
39
100
|
* @param {function} [opts.onWarn] called as ({ maxMs, meanMs, p99Ms }) on a warning
|
|
101
|
+
* @param {function} [opts.getContext] returns scheduler/runtime context for attribution
|
|
40
102
|
* @param {function} [opts.log=console.warn] where warnings are written
|
|
41
103
|
*/
|
|
42
104
|
function start(opts = {}) {
|
|
105
|
+
if (opts.force !== true && !eventLoopMonitorEnabled()) return false;
|
|
43
106
|
if (_histogram) return; // already running
|
|
44
107
|
const resolution = Math.max(1, Number(opts.resolutionMs) || 20);
|
|
45
108
|
const sampleMs = Math.max(1000, Number(opts.sampleMs) || 10000);
|
|
46
109
|
const warnThresholdMs = Math.max(50, Number(opts.warnThresholdMs) || 1000);
|
|
47
110
|
const log = typeof opts.log === 'function' ? opts.log : console.warn;
|
|
48
111
|
_onWarn = typeof opts.onWarn === 'function' ? opts.onWarn : null;
|
|
112
|
+
_getContext = typeof opts.getContext === 'function' ? opts.getContext : null;
|
|
49
113
|
|
|
50
114
|
_histogram = monitorEventLoopDelay({ resolution });
|
|
51
115
|
_histogram.enable();
|
|
@@ -58,13 +122,19 @@ function start(opts = {}) {
|
|
|
58
122
|
if (maxMs > _peakMs) _peakMs = maxMs;
|
|
59
123
|
|
|
60
124
|
if (maxMs >= warnThresholdMs) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
125
|
+
let context = null;
|
|
126
|
+
if (_getContext) {
|
|
127
|
+
try { context = _safeContext(_getContext()); } catch {}
|
|
128
|
+
}
|
|
129
|
+
_lastWarn = { at: new Date().toISOString(), maxMs, meanMs, p99Ms, context };
|
|
130
|
+
const contextSummary = _summarizeContext(context);
|
|
131
|
+
log(`[wall-e] event loop blocked up to ${maxMs}ms in the last ${Math.round(sampleMs / 1000)}s (mean ${meanMs}ms, p99 ${p99Ms}ms) — a synchronous DB query or sync I/O is starving the loop${contextSummary ? `; active: ${contextSummary}` : ''}`);
|
|
132
|
+
if (_onWarn) { try { _onWarn({ maxMs, meanMs, p99Ms, context }); } catch { /* never let a hook break the monitor */ } }
|
|
64
133
|
}
|
|
65
134
|
_histogram.reset();
|
|
66
135
|
}, sampleMs);
|
|
67
136
|
_timer.unref?.();
|
|
137
|
+
return true;
|
|
68
138
|
}
|
|
69
139
|
|
|
70
140
|
/** Cheap snapshot for a health endpoint. Safe to call when not running. */
|
|
@@ -88,6 +158,7 @@ function stop() {
|
|
|
88
158
|
if (_timer) { clearInterval(_timer); _timer = null; }
|
|
89
159
|
if (_histogram) { try { _histogram.disable(); } catch {} _histogram = null; }
|
|
90
160
|
_onWarn = null;
|
|
161
|
+
_getContext = null;
|
|
91
162
|
}
|
|
92
163
|
|
|
93
164
|
module.exports = { start, stop, stats, isRunning };
|
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { spawn } = require('child_process');
|
|
7
|
+
const { resolveRealNode } = require('./real-node');
|
|
7
8
|
|
|
8
9
|
const WALLE_SERVER_NAME = 'wall-e';
|
|
9
10
|
const AGENT_INSTRUCTIONS_BEGIN = '<!-- wall-e-memory-routing:start -->';
|
|
@@ -54,7 +55,12 @@ function _requestedMcpTransport(opts = {}) {
|
|
|
54
55
|
function wallEMcpStdioConfig() {
|
|
55
56
|
return {
|
|
56
57
|
type: 'stdio',
|
|
57
|
-
|
|
58
|
+
// resolveRealNode() de-bundles a SELF-SIGNED bundle clone (agents spawn this MCP per session,
|
|
59
|
+
// so a self-signed bundle path is a restart-storm multiplier) but KEEPS a Developer-ID-signed
|
|
60
|
+
// bundle exec (trust-cached → no storm, AND macOS TCC prompts show "Wall-E", not "node"). See
|
|
61
|
+
// lib/real-node.js. Configs self-heal when this changes: the mismatch is detected as
|
|
62
|
+
// `wrong_command` / `updated` and rewritten on the next ensure.
|
|
63
|
+
command: resolveRealNode(),
|
|
58
64
|
args: [wallEMcpStdioScript()],
|
|
59
65
|
};
|
|
60
66
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Resolve the REAL node binary for spawning children / configuring the Wall-E MCP command.
|
|
4
|
+
//
|
|
5
|
+
// create-walle (macOS) clones node into self-signed `.app` bundles so the top-level CTM and
|
|
6
|
+
// Wall-E daemons show a real icon + name in Activity Monitor. When Wall-E runs from that
|
|
7
|
+
// bundle, `process.execPath` IS the clone. Writing that clone as the MCP `command` (or
|
|
8
|
+
// spawning children from it) makes every agent launch the Wall-E MCP — and every other
|
|
9
|
+
// child — as a novel, self-signed, un-trust-cached executable. macOS's endpoint-security
|
|
10
|
+
// stack (Gatekeeper/syspolicyd/amfid, plus EDR like PANW Traps and Santa) cold-authorizes
|
|
11
|
+
// each through the synchronous, serialized EndpointSecurity AUTH_EXEC hook; on a
|
|
12
|
+
// multi-session restart, dozens fire at once and stall process creation system-wide.
|
|
13
|
+
//
|
|
14
|
+
// MCP servers need no icon, so point them at the real node binary (one stable, trust-cached
|
|
15
|
+
// cdhash) and keep the bundle exec only for the top-level daemon itself.
|
|
16
|
+
//
|
|
17
|
+
// EXCEPTION — a Developer-ID-signed bundle: the stall above is specific to SELF-SIGNED bundle
|
|
18
|
+
// clones, which carry no Team Identifier and are never in the OS trust cache, so each exec is
|
|
19
|
+
// cold-authorized. A Developer-ID-signed bundle exec has a stable Team ID and IS trust-cached, so
|
|
20
|
+
// it does NOT trigger the storm — and running the MCP under it makes macOS TCC prompts show the
|
|
21
|
+
// branded name ("Wall-E") instead of the anonymous "node". So when the current bundle exec carries
|
|
22
|
+
// a stable Team Identifier, we keep it; we only de-bundle the self-signed case.
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const { execFileSync, spawnSync } = require('node:child_process');
|
|
27
|
+
|
|
28
|
+
let _cached;
|
|
29
|
+
|
|
30
|
+
function isBundleExec(p) {
|
|
31
|
+
return !!p && p.includes('.app/Contents/MacOS/');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The code-signing Team Identifier of a binary, or '' when unsigned/self-signed. A non-empty
|
|
35
|
+
// value ⇒ Developer-ID/notarized ⇒ stable + trust-cached (safe to exec repeatedly, branded TCC).
|
|
36
|
+
function execTeamId(p) {
|
|
37
|
+
if (process.platform !== 'darwin' || !p) return '';
|
|
38
|
+
try {
|
|
39
|
+
const out = spawnSync('codesign', ['-dv', '--verbose=4', p], { encoding: 'utf8' });
|
|
40
|
+
const text = `${out.stdout || ''}\n${out.stderr || ''}`; // codesign reports to stderr
|
|
41
|
+
const m = text.match(/^TeamIdentifier=(.+)$/m);
|
|
42
|
+
const team = m ? m[1].trim() : '';
|
|
43
|
+
return team === 'not set' ? '' : team;
|
|
44
|
+
} catch {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Pure decision — all IO injected so both branches are unit-testable without macOS / codesign.
|
|
50
|
+
// deps.execPath — the current process.execPath
|
|
51
|
+
// deps.teamIdOf — (path) => Team Identifier string ('' if self-signed)
|
|
52
|
+
// deps.existsSync — (path) => boolean
|
|
53
|
+
// deps.readOrigin — () => the `node-origin` path recorded next to the bundle, or ''
|
|
54
|
+
// deps.realNode — () => `node -e process.execPath` result, or ''
|
|
55
|
+
// deps.env — process.env (for CTM_REAL_NODE)
|
|
56
|
+
function _resolveRealNode(deps) {
|
|
57
|
+
const { execPath, teamIdOf, existsSync, readOrigin, realNode, env } = deps;
|
|
58
|
+
if (!isBundleExec(execPath)) return execPath;
|
|
59
|
+
// A Developer-ID-signed (or notarized) bundle exec is stable + trust-cached + branded — keep it:
|
|
60
|
+
// no exec-storm (it's trust-cached) and TCC prompts show "Wall-E", not "node". Only de-bundle a
|
|
61
|
+
// SELF-SIGNED bundle (no Team ID), whose novel cdhash would trigger the AUTH_EXEC storm above.
|
|
62
|
+
if (teamIdOf(execPath)) return execPath;
|
|
63
|
+
|
|
64
|
+
const candidates = [];
|
|
65
|
+
try { const o = readOrigin(); if (o) candidates.push(o); } catch {}
|
|
66
|
+
if (env && env.CTM_REAL_NODE) candidates.push(env.CTM_REAL_NODE);
|
|
67
|
+
try { const r = realNode(); if (r) candidates.push(r); } catch {}
|
|
68
|
+
candidates.push('/opt/homebrew/bin/node', '/usr/local/bin/node', '/usr/bin/node');
|
|
69
|
+
|
|
70
|
+
for (const c of candidates) {
|
|
71
|
+
try {
|
|
72
|
+
if (c && !isBundleExec(c) && existsSync(c)) return c;
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
return execPath;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function resolveRealNode() {
|
|
79
|
+
if (_cached) return _cached;
|
|
80
|
+
_cached = _resolveRealNode({
|
|
81
|
+
execPath: process.execPath,
|
|
82
|
+
teamIdOf: execTeamId,
|
|
83
|
+
existsSync: fs.existsSync,
|
|
84
|
+
env: process.env,
|
|
85
|
+
readOrigin: () => {
|
|
86
|
+
const bundleRoot = path.resolve(path.dirname(process.execPath), '..', '..', '..');
|
|
87
|
+
return fs.readFileSync(path.join(bundleRoot, 'node-origin'), 'utf8').trim();
|
|
88
|
+
},
|
|
89
|
+
realNode: () => execFileSync('node', ['-e', 'process.stdout.write(process.execPath)'], { encoding: 'utf8' }).trim(),
|
|
90
|
+
});
|
|
91
|
+
return _cached;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function _resetCacheForTests() {
|
|
95
|
+
_cached = undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { resolveRealNode, isBundleExec, execTeamId, _resolveRealNode, _resetCacheForTests };
|