@chainlesschain/personal-data-hub 0.1.0 → 0.2.1
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/__tests__/adapters/ai-chat-cookie-capture-spec.test.js +211 -0
- package/__tests__/adapters/ai-chat-health-checker.test.js +262 -0
- package/__tests__/adapters/ai-chat-history.test.js +396 -0
- package/__tests__/adapters/ai-chat-http-client.test.js +242 -0
- package/__tests__/adapters/ai-chat-vendors.test.js +874 -0
- package/__tests__/adapters/alipay-bill-adapter.test.js +538 -0
- package/__tests__/adapters/email-adapter.test.js +138 -1
- package/__tests__/adapters/email-classifier.test.js +347 -0
- package/__tests__/adapters/email-pdf-extractor.test.js +529 -0
- package/__tests__/adapters/email-retry-progress.test.js +294 -0
- package/__tests__/adapters/email-templates.test.js +699 -0
- package/__tests__/adapters/social-toutiao-kuaishou-scaffold.test.js +269 -0
- package/__tests__/adapters/system-data-adapter.test.js +440 -0
- package/__tests__/adapters/system-data-android-ingest.test.js +144 -0
- package/__tests__/adapters/system-data-android.test.js +387 -0
- package/__tests__/adapters/system-data-disclosure.test.js +153 -0
- package/__tests__/adapters/wechat-bootstrap.test.js +240 -0
- package/__tests__/adapters/wechat-env-probe.test.js +162 -0
- package/__tests__/adapters/wechat-frida-agent.test.js +191 -0
- package/__tests__/adapters/wechat-frida-integration.test.js +149 -0
- package/__tests__/adapters/wechat-frida-key-provider.test.js +188 -0
- package/__tests__/adapters/wechat-md5-key-provider.test.js +101 -0
- package/__tests__/analysis-skills.test.js +556 -0
- package/__tests__/analysis.test.js +329 -1
- package/__tests__/e2e/ai-chat-cross-source-journey.test.js +213 -0
- package/__tests__/e2e/full-user-journey.test.js +188 -0
- package/__tests__/entity-resolver-ingest-hook.test.js +177 -0
- package/__tests__/entity-resolver-stages.test.js +411 -0
- package/__tests__/entity-resolver-vault.test.js +246 -0
- package/__tests__/entity-resolver.test.js +526 -0
- package/__tests__/fixtures/entity-resolver-200-mock.json +96 -0
- package/__tests__/integration/ai-chat-history-registry.test.js +228 -0
- package/__tests__/integration/aichat-wizard-end-to-end.test.js +282 -0
- package/__tests__/integration/cross-adapter-pipelines.test.js +396 -0
- package/__tests__/integration/wechat-bootstrap-end-to-end.test.js +390 -0
- package/__tests__/longtail-adapters.test.js +217 -0
- package/__tests__/mobile-extractor.test.js +288 -0
- package/__tests__/registry.test.js +4 -2
- package/__tests__/shopping-adapters.test.js +296 -0
- package/__tests__/sidecar-contacts-cross-validate.test.js +163 -0
- package/__tests__/sidecar-supervisor.test.js +120 -0
- package/__tests__/social-adapters.test.js +206 -0
- package/__tests__/travel-adapters.test.js +325 -0
- package/__tests__/vault.test.js +3 -3
- package/__tests__/wechat-adapter.test.js +476 -0
- package/__tests__/whatsapp-adapter.test.js +135 -0
- package/lib/adapter-spec.js +12 -0
- package/lib/adapters/_python-sidecar-base.js +207 -0
- package/lib/adapters/ai-chat-history/ai-chat-adapter.js +374 -0
- package/lib/adapters/ai-chat-history/cookie-auth.js +109 -0
- package/lib/adapters/ai-chat-history/cookie-capture-spec.js +331 -0
- package/lib/adapters/ai-chat-history/health-checker.js +210 -0
- package/lib/adapters/ai-chat-history/http-client.js +211 -0
- package/lib/adapters/ai-chat-history/index.js +28 -0
- package/lib/adapters/ai-chat-history/schema-map.js +258 -0
- package/lib/adapters/ai-chat-history/vendor-spec.js +86 -0
- package/lib/adapters/ai-chat-history/vendors/coze.js +179 -0
- package/lib/adapters/ai-chat-history/vendors/deepseek.js +199 -0
- package/lib/adapters/ai-chat-history/vendors/doubao.js +255 -0
- package/lib/adapters/ai-chat-history/vendors/dreamina.js +174 -0
- package/lib/adapters/ai-chat-history/vendors/hunyuan.js +176 -0
- package/lib/adapters/ai-chat-history/vendors/kimi.js +182 -0
- package/lib/adapters/ai-chat-history/vendors/qianfan.js +160 -0
- package/lib/adapters/ai-chat-history/vendors/tongyi.js +193 -0
- package/lib/adapters/ai-chat-history/vendors/zhipu.js +202 -0
- package/lib/adapters/ai-chat-history/wizard-controller.js +473 -0
- package/lib/adapters/alipay-bill/alipay-bill-adapter.js +311 -0
- package/lib/adapters/alipay-bill/counterparty.js +129 -0
- package/lib/adapters/alipay-bill/csv-parser.js +217 -0
- package/lib/adapters/alipay-bill/index.js +41 -0
- package/lib/adapters/alipay-bill/zip-decryptor.js +111 -0
- package/lib/adapters/email-imap/classifier.js +495 -0
- package/lib/adapters/email-imap/email-adapter.js +419 -8
- package/lib/adapters/email-imap/index.js +42 -0
- package/lib/adapters/email-imap/pdf-extractor.js +192 -0
- package/lib/adapters/email-imap/templates/bill.js +232 -0
- package/lib/adapters/email-imap/templates/government.js +120 -0
- package/lib/adapters/email-imap/templates/index.js +78 -0
- package/lib/adapters/email-imap/templates/order.js +186 -0
- package/lib/adapters/email-imap/templates/other.js +114 -0
- package/lib/adapters/email-imap/templates/register.js +113 -0
- package/lib/adapters/email-imap/templates/travel.js +157 -0
- package/lib/adapters/email-imap/templates/utils.js +275 -0
- package/lib/adapters/email-imap/transactions.js +234 -0
- package/lib/adapters/messaging-qq/index.js +158 -0
- package/lib/adapters/messaging-telegram/index.js +142 -0
- package/lib/adapters/messaging-whatsapp/index.js +189 -0
- package/lib/adapters/shopping-base/index.js +208 -0
- package/lib/adapters/shopping-jd/index.js +150 -0
- package/lib/adapters/shopping-meituan/index.js +154 -0
- package/lib/adapters/shopping-taobao/index.js +176 -0
- package/lib/adapters/social-bilibili/index.js +171 -0
- package/lib/adapters/social-douyin/index.js +116 -0
- package/lib/adapters/social-kuaishou/index.js +237 -0
- package/lib/adapters/social-toutiao/index.js +236 -0
- package/lib/adapters/social-weibo/index.js +164 -0
- package/lib/adapters/social-xiaohongshu/index.js +96 -0
- package/lib/adapters/system-data/disclosure.js +166 -0
- package/lib/adapters/system-data/index.js +34 -0
- package/lib/adapters/system-data/system-data-adapter.js +344 -0
- package/lib/adapters/system-data-android/adapter.js +348 -0
- package/lib/adapters/system-data-android/index.js +76 -0
- package/lib/adapters/travel-12306/index.js +151 -0
- package/lib/adapters/travel-amap/index.js +164 -0
- package/lib/adapters/travel-baidu-map/index.js +162 -0
- package/lib/adapters/travel-base/index.js +240 -0
- package/lib/adapters/travel-ctrip/index.js +151 -0
- package/lib/adapters/wechat/bootstrap.js +146 -0
- package/lib/adapters/wechat/content-parser.js +326 -0
- package/lib/adapters/wechat/db-reader.js +209 -0
- package/lib/adapters/wechat/env-probe.js +218 -0
- package/lib/adapters/wechat/frida-agent/loader.js +67 -0
- package/lib/adapters/wechat/frida-agent/wechat-key-hook.js +126 -0
- package/lib/adapters/wechat/index.js +37 -0
- package/lib/adapters/wechat/key-extractor.js +158 -0
- package/lib/adapters/wechat/key-providers/frida-key-provider.js +244 -0
- package/lib/adapters/wechat/key-providers/index.js +22 -0
- package/lib/adapters/wechat/key-providers/key-provider-base.js +44 -0
- package/lib/adapters/wechat/key-providers/md5-key-provider.js +81 -0
- package/lib/adapters/wechat/normalize.js +220 -0
- package/lib/adapters/wechat/wechat-adapter.js +205 -0
- package/lib/analysis-skills/base.js +113 -0
- package/lib/analysis-skills/footprint.js +167 -0
- package/lib/analysis-skills/index.js +58 -0
- package/lib/analysis-skills/interests.js +161 -0
- package/lib/analysis-skills/relations.js +226 -0
- package/lib/analysis-skills/spending.js +219 -0
- package/lib/analysis-skills/timeline.js +167 -0
- package/lib/analysis.js +191 -2
- package/lib/entity-resolver/embedding-stage.js +198 -0
- package/lib/entity-resolver/entity-resolver.js +384 -0
- package/lib/entity-resolver/index.js +42 -0
- package/lib/entity-resolver/llm-stage.js +191 -0
- package/lib/entity-resolver/rule-stage.js +208 -0
- package/lib/entity-resolver/worker.js +149 -0
- package/lib/index.js +131 -0
- package/lib/migrations.js +73 -0
- package/lib/mobile-extractor/android.js +193 -0
- package/lib/mobile-extractor/index.js +9 -0
- package/lib/mobile-extractor/ios.js +223 -0
- package/lib/prompt-builder.js +11 -1
- package/lib/query-parser.js +7 -1
- package/lib/registry.js +42 -0
- package/lib/sidecar/index.js +15 -0
- package/lib/sidecar/supervisor.js +359 -0
- package/lib/vault.js +343 -0
- package/package.json +36 -3
- package/scripts/_make-fixture-all.js +126 -0
- package/scripts/_make-fixture-contacts.js +84 -0
- package/scripts/evaluate-entity-resolver.js +213 -0
- package/scripts/smoke-phase-5-5.js +196 -0
- package/scripts/smoke-phase-5-7.js +181 -0
- package/scripts/smoke-system-data-contacts.js +309 -0
- package/scripts/smoke-system-data.js +312 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SidecarSupervisor — manages the forensics-bridge Python sidecar lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Protocol: docs/design/Personal_Data_Hub_Python_Sidecar.md §3 (JSON-lines).
|
|
5
|
+
* Counterpart: packages/personal-data-hub-bridge/forensics_bridge/ipc_server.py.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Spawn / health-check / restart the sidecar subprocess
|
|
9
|
+
* - Frame stdin/stdout as JSON-lines (one envelope per line)
|
|
10
|
+
* - Correlate request id → pending promise; route progress/chunk callbacks
|
|
11
|
+
* - Forward stderr logs as events for hub audit logging
|
|
12
|
+
*
|
|
13
|
+
* Non-goals (this layer):
|
|
14
|
+
* - Persisting credentials (caller passes them per-invoke)
|
|
15
|
+
* - Audit logging (caller subscribes to "log" events)
|
|
16
|
+
* - Schema validation of params (Python side enforces)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const { spawn } = require("node:child_process");
|
|
22
|
+
const { EventEmitter } = require("node:events");
|
|
23
|
+
const crypto = require("node:crypto");
|
|
24
|
+
const readline = require("node:readline");
|
|
25
|
+
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
27
|
+
const DEFAULT_HEALTHCHECK_INTERVAL_MS = 30_000;
|
|
28
|
+
const STOP_GRACE_MS = 5_000;
|
|
29
|
+
|
|
30
|
+
class SidecarTimeoutError extends Error {
|
|
31
|
+
constructor(method, timeoutMs) {
|
|
32
|
+
super(`sidecar method '${method}' timed out after ${timeoutMs}ms`);
|
|
33
|
+
this.name = "SidecarTimeoutError";
|
|
34
|
+
this.code = "TIMEOUT";
|
|
35
|
+
this.retryable = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class SidecarMethodError extends Error {
|
|
40
|
+
constructor({ code, msg, retryable }) {
|
|
41
|
+
super(msg || code);
|
|
42
|
+
this.name = "SidecarMethodError";
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.retryable = Boolean(retryable);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class SidecarNotRunningError extends Error {
|
|
49
|
+
constructor(method) {
|
|
50
|
+
super(`sidecar is not running; cannot invoke '${method}'`);
|
|
51
|
+
this.name = "SidecarNotRunningError";
|
|
52
|
+
this.code = "SIDECAR_NOT_RUNNING";
|
|
53
|
+
this.retryable = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class SidecarSupervisor extends EventEmitter {
|
|
58
|
+
/**
|
|
59
|
+
* @param {object} opts
|
|
60
|
+
* @param {string|string[]} opts.command - Executable (string) or [exec, ...args] for spawn.
|
|
61
|
+
* @param {string[]} [opts.args] - Extra args appended after `command`.
|
|
62
|
+
* @param {string} [opts.cwd] - Working directory for the child.
|
|
63
|
+
* @param {object} [opts.env] - Env vars merged over process.env.
|
|
64
|
+
* @param {number} [opts.healthCheckIntervalMs] - 0 to disable.
|
|
65
|
+
* @param {number} [opts.defaultTimeoutMs] - Per-invoke default.
|
|
66
|
+
*/
|
|
67
|
+
constructor(opts) {
|
|
68
|
+
super();
|
|
69
|
+
if (!opts || !opts.command) {
|
|
70
|
+
throw new Error("SidecarSupervisor requires opts.command");
|
|
71
|
+
}
|
|
72
|
+
const [first, ...rest] = Array.isArray(opts.command)
|
|
73
|
+
? opts.command
|
|
74
|
+
: [opts.command];
|
|
75
|
+
this._exec = first;
|
|
76
|
+
this._args = [...rest, ...(opts.args || [])];
|
|
77
|
+
this._cwd = opts.cwd;
|
|
78
|
+
this._env = opts.env;
|
|
79
|
+
this._healthCheckIntervalMs =
|
|
80
|
+
opts.healthCheckIntervalMs ?? DEFAULT_HEALTHCHECK_INTERVAL_MS;
|
|
81
|
+
this._defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
82
|
+
|
|
83
|
+
this._proc = null;
|
|
84
|
+
this._stdoutReader = null;
|
|
85
|
+
this._stderrReader = null;
|
|
86
|
+
this._pending = new Map(); // id → { resolve, reject, timer, onProgress, onChunk, method }
|
|
87
|
+
this._healthTimer = null;
|
|
88
|
+
this._stopping = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Spawn the sidecar and verify it responds to sidecar.ping.
|
|
93
|
+
* Idempotent — calling start() twice returns the same ready promise.
|
|
94
|
+
*/
|
|
95
|
+
async start({ readyTimeoutMs = 5_000 } = {}) {
|
|
96
|
+
if (this._proc && !this._proc.killed) {
|
|
97
|
+
return; // already running
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const env = {
|
|
101
|
+
...process.env,
|
|
102
|
+
...(this._env || {}),
|
|
103
|
+
PYTHONIOENCODING: "utf-8",
|
|
104
|
+
PYTHONUNBUFFERED: "1",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
this._proc = spawn(this._exec, this._args, {
|
|
108
|
+
cwd: this._cwd,
|
|
109
|
+
env,
|
|
110
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
111
|
+
windowsHide: true,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this._stopping = false;
|
|
115
|
+
this._wireStreams();
|
|
116
|
+
|
|
117
|
+
this._proc.on("error", (err) => {
|
|
118
|
+
this.emit("error", err);
|
|
119
|
+
this._failAllPending(err);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
this._proc.on("exit", (code, signal) => {
|
|
123
|
+
this.emit("exit", { code, signal });
|
|
124
|
+
const reason = new Error(
|
|
125
|
+
`sidecar exited (code=${code} signal=${signal})`,
|
|
126
|
+
);
|
|
127
|
+
this._failAllPending(reason);
|
|
128
|
+
this._teardown();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await this.invoke("sidecar.ping", {}, { timeoutMs: readyTimeoutMs });
|
|
132
|
+
this._scheduleHealthCheck();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Invoke a sidecar method.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} method
|
|
139
|
+
* @param {object} params
|
|
140
|
+
* @param {object} [opts]
|
|
141
|
+
* @param {number} [opts.timeoutMs]
|
|
142
|
+
* @param {(data: object) => void} [opts.onProgress] - invoked on progress envelopes
|
|
143
|
+
* @param {(data: object) => void} [opts.onChunk] - invoked on chunk envelopes
|
|
144
|
+
* @returns {Promise<object>} resolves with the final `result.data`
|
|
145
|
+
*/
|
|
146
|
+
invoke(method, params = {}, opts = {}) {
|
|
147
|
+
if (!this._proc || this._proc.killed || this._stopping) {
|
|
148
|
+
return Promise.reject(new SidecarNotRunningError(method));
|
|
149
|
+
}
|
|
150
|
+
const timeoutMs = opts.timeoutMs ?? this._defaultTimeoutMs;
|
|
151
|
+
const id =
|
|
152
|
+
typeof crypto.randomUUID === "function"
|
|
153
|
+
? crypto.randomUUID()
|
|
154
|
+
: `req-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
155
|
+
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const timer = setTimeout(() => {
|
|
158
|
+
this._pending.delete(id);
|
|
159
|
+
// Best-effort cancel on the sidecar side; don't await the response.
|
|
160
|
+
this._writeLine({
|
|
161
|
+
id: `cancel-${id}`,
|
|
162
|
+
method: "request.cancel",
|
|
163
|
+
params: { id },
|
|
164
|
+
}).catch(() => {});
|
|
165
|
+
reject(new SidecarTimeoutError(method, timeoutMs));
|
|
166
|
+
}, timeoutMs);
|
|
167
|
+
// Allow the process to exit even if a sidecar invocation timer is pending.
|
|
168
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
169
|
+
|
|
170
|
+
this._pending.set(id, {
|
|
171
|
+
resolve,
|
|
172
|
+
reject,
|
|
173
|
+
timer,
|
|
174
|
+
onProgress: opts.onProgress,
|
|
175
|
+
onChunk: opts.onChunk,
|
|
176
|
+
method,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const envelope = { id, method, params, timeout_ms: timeoutMs };
|
|
180
|
+
this._writeLine(envelope).catch((err) => {
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
this._pending.delete(id);
|
|
183
|
+
reject(err);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Stop the sidecar: SIGTERM → wait grace → SIGKILL.
|
|
190
|
+
* Pending invocations reject with SidecarNotRunningError.
|
|
191
|
+
*/
|
|
192
|
+
async stop({ graceMs = STOP_GRACE_MS } = {}) {
|
|
193
|
+
if (!this._proc || this._proc.killed) {
|
|
194
|
+
this._teardown();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
this._stopping = true;
|
|
198
|
+
if (this._healthTimer) {
|
|
199
|
+
clearInterval(this._healthTimer);
|
|
200
|
+
this._healthTimer = null;
|
|
201
|
+
}
|
|
202
|
+
const proc = this._proc;
|
|
203
|
+
const exited = new Promise((res) => proc.once("exit", res));
|
|
204
|
+
try {
|
|
205
|
+
proc.stdin?.end();
|
|
206
|
+
} catch (_err) {
|
|
207
|
+
/* already closed */
|
|
208
|
+
}
|
|
209
|
+
proc.kill("SIGTERM");
|
|
210
|
+
const killed = await Promise.race([
|
|
211
|
+
exited.then(() => true),
|
|
212
|
+
new Promise((res) => {
|
|
213
|
+
const t = setTimeout(() => res(false), graceMs);
|
|
214
|
+
if (typeof t.unref === "function") t.unref();
|
|
215
|
+
}),
|
|
216
|
+
]);
|
|
217
|
+
if (!killed) {
|
|
218
|
+
proc.kill("SIGKILL");
|
|
219
|
+
await exited;
|
|
220
|
+
}
|
|
221
|
+
this._teardown();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
isRunning() {
|
|
225
|
+
return Boolean(this._proc) && !this._proc.killed && !this._stopping;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// -------------------------------------------------------------------------
|
|
229
|
+
// Internals
|
|
230
|
+
// -------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
_wireStreams() {
|
|
233
|
+
this._stdoutReader = readline.createInterface({
|
|
234
|
+
input: this._proc.stdout,
|
|
235
|
+
crlfDelay: Infinity,
|
|
236
|
+
});
|
|
237
|
+
this._stdoutReader.on("line", (line) => {
|
|
238
|
+
if (!line) return;
|
|
239
|
+
let envelope;
|
|
240
|
+
try {
|
|
241
|
+
envelope = JSON.parse(line);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
this.emit("error", new Error(`invalid envelope from sidecar: ${line}`));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this._handleEnvelope(envelope);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
this._stderrReader = readline.createInterface({
|
|
250
|
+
input: this._proc.stderr,
|
|
251
|
+
crlfDelay: Infinity,
|
|
252
|
+
});
|
|
253
|
+
this._stderrReader.on("line", (line) => {
|
|
254
|
+
if (!line) return;
|
|
255
|
+
// Sidecar logs pino-style JSON; pass through for hub audit.
|
|
256
|
+
this.emit("log", line);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_handleEnvelope(env) {
|
|
261
|
+
// Envelopes with no id (e.g. INVALID_JSON parse failures) surface as events.
|
|
262
|
+
if (env.id === null || env.id === undefined) {
|
|
263
|
+
this.emit("orphan", env);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const pending = this._pending.get(env.id);
|
|
267
|
+
if (!pending) {
|
|
268
|
+
this.emit("orphan", env);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (env.type === "progress") {
|
|
273
|
+
try {
|
|
274
|
+
pending.onProgress?.(env.data);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
this.emit("error", err);
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (env.type === "chunk") {
|
|
281
|
+
try {
|
|
282
|
+
pending.onChunk?.(env.data);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
this.emit("error", err);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
// Terminal frames remove the pending entry.
|
|
289
|
+
clearTimeout(pending.timer);
|
|
290
|
+
this._pending.delete(env.id);
|
|
291
|
+
|
|
292
|
+
if (env.type === "result") {
|
|
293
|
+
pending.resolve(env.data);
|
|
294
|
+
} else if (env.type === "error") {
|
|
295
|
+
pending.reject(new SidecarMethodError(env.error || { code: "UNKNOWN" }));
|
|
296
|
+
} else {
|
|
297
|
+
pending.reject(
|
|
298
|
+
new SidecarMethodError({
|
|
299
|
+
code: "UNKNOWN_ENVELOPE_TYPE",
|
|
300
|
+
msg: `unexpected envelope type: ${env.type}`,
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_writeLine(envelope) {
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
if (!this._proc || !this._proc.stdin || this._proc.stdin.destroyed) {
|
|
309
|
+
reject(new SidecarNotRunningError(envelope.method || "<unknown>"));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const line = JSON.stringify(envelope) + "\n";
|
|
313
|
+
this._proc.stdin.write(line, "utf8", (err) => {
|
|
314
|
+
if (err) reject(err);
|
|
315
|
+
else resolve();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
_scheduleHealthCheck() {
|
|
321
|
+
if (!this._healthCheckIntervalMs) return;
|
|
322
|
+
if (this._healthTimer) clearInterval(this._healthTimer);
|
|
323
|
+
this._healthTimer = setInterval(() => {
|
|
324
|
+
this.invoke("sidecar.ping", {}, { timeoutMs: 3_000 }).catch((err) => {
|
|
325
|
+
this.emit("healthCheckFailed", err);
|
|
326
|
+
});
|
|
327
|
+
}, this._healthCheckIntervalMs);
|
|
328
|
+
if (typeof this._healthTimer.unref === "function") {
|
|
329
|
+
this._healthTimer.unref();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
_failAllPending(err) {
|
|
334
|
+
for (const [, pending] of this._pending) {
|
|
335
|
+
clearTimeout(pending.timer);
|
|
336
|
+
pending.reject(err);
|
|
337
|
+
}
|
|
338
|
+
this._pending.clear();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_teardown() {
|
|
342
|
+
if (this._healthTimer) {
|
|
343
|
+
clearInterval(this._healthTimer);
|
|
344
|
+
this._healthTimer = null;
|
|
345
|
+
}
|
|
346
|
+
this._stdoutReader?.close();
|
|
347
|
+
this._stderrReader?.close();
|
|
348
|
+
this._stdoutReader = null;
|
|
349
|
+
this._stderrReader = null;
|
|
350
|
+
this._proc = null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = {
|
|
355
|
+
SidecarSupervisor,
|
|
356
|
+
SidecarTimeoutError,
|
|
357
|
+
SidecarMethodError,
|
|
358
|
+
SidecarNotRunningError,
|
|
359
|
+
};
|