@juspay/neurolink 9.93.2 → 9.94.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/CHANGELOG.md +13 -0
- package/dist/browser/neurolink.min.js +263 -263
- package/dist/cli/commands/proxy.js +672 -182
- package/dist/cli/utils/audioPlayer.d.ts +44 -9
- package/dist/cli/utils/audioPlayer.js +162 -65
- package/dist/lib/proxy/rollingProxyServer.d.ts +3 -0
- package/dist/lib/proxy/rollingProxyServer.js +149 -0
- package/dist/lib/proxy/rollingWorkerProcess.d.ts +2 -0
- package/dist/lib/proxy/rollingWorkerProcess.js +194 -0
- package/dist/lib/proxy/rollingWorkerProtocol.d.ts +5 -0
- package/dist/lib/proxy/rollingWorkerProtocol.js +43 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.d.ts +39 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.js +402 -0
- package/dist/lib/proxy/socketWorkerRuntime.d.ts +14 -0
- package/dist/lib/proxy/socketWorkerRuntime.js +294 -0
- package/dist/lib/skills/skillMatcher.d.ts +7 -1
- package/dist/lib/skills/skillMatcher.js +23 -8
- package/dist/lib/types/cli.d.ts +47 -0
- package/dist/lib/types/proxy.d.ts +156 -0
- package/dist/lib/utils/errorHandling.d.ts +8 -0
- package/dist/lib/utils/errorHandling.js +20 -0
- package/dist/proxy/rollingProxyServer.d.ts +3 -0
- package/dist/proxy/rollingProxyServer.js +148 -0
- package/dist/proxy/rollingWorkerProcess.d.ts +2 -0
- package/dist/proxy/rollingWorkerProcess.js +193 -0
- package/dist/proxy/rollingWorkerProtocol.d.ts +5 -0
- package/dist/proxy/rollingWorkerProtocol.js +42 -0
- package/dist/proxy/rollingWorkerSupervisor.d.ts +39 -0
- package/dist/proxy/rollingWorkerSupervisor.js +401 -0
- package/dist/proxy/socketWorkerRuntime.d.ts +14 -0
- package/dist/proxy/socketWorkerRuntime.js +293 -0
- package/dist/skills/skillMatcher.d.ts +7 -1
- package/dist/skills/skillMatcher.js +23 -8
- package/dist/types/cli.d.ts +47 -0
- package/dist/types/proxy.d.ts +156 -0
- package/dist/utils/errorHandling.d.ts +8 -0
- package/dist/utils/errorHandling.js +20 -0
- package/package.json +3 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
import type { SocketWorkerRuntime, SocketWorkerRuntimeOptions } from "../types/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Adapts transferred TCP sockets to an HTTP server without opening another
|
|
5
|
+
* listener. Active responses finish normally; idle keep-alive sockets close as
|
|
6
|
+
* soon as draining begins.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createSocketWorkerRuntime(server: Server, options?: SocketWorkerRuntimeOptions): SocketWorkerRuntime;
|
|
9
|
+
export declare function attachSocketWorkerProcess(server: Server, input: {
|
|
10
|
+
generation: number;
|
|
11
|
+
version: string;
|
|
12
|
+
onActivated?: () => void;
|
|
13
|
+
onDrained?: () => void;
|
|
14
|
+
}): SocketWorkerRuntime;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { isProxyWorkerControlMessage } from "./rollingWorkerProtocol.js";
|
|
2
|
+
function isTransferableProxySocket(handle) {
|
|
3
|
+
if (!handle || typeof handle !== "object") {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const candidate = handle;
|
|
7
|
+
return (typeof candidate.pause === "function" &&
|
|
8
|
+
typeof candidate.resume === "function" &&
|
|
9
|
+
typeof candidate.destroy === "function" &&
|
|
10
|
+
typeof candidate.end === "function" &&
|
|
11
|
+
typeof candidate.once === "function");
|
|
12
|
+
}
|
|
13
|
+
function destroyTransferredHandle(handle) {
|
|
14
|
+
if (handle &&
|
|
15
|
+
typeof handle === "object" &&
|
|
16
|
+
typeof handle.destroy === "function") {
|
|
17
|
+
try {
|
|
18
|
+
handle.destroy();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Invalid IPC handles must not crash the serving worker.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Adapts transferred TCP sockets to an HTTP server without opening another
|
|
27
|
+
* listener. Active responses finish normally; idle keep-alive sockets close as
|
|
28
|
+
* soon as draining begins.
|
|
29
|
+
*/
|
|
30
|
+
export function createSocketWorkerRuntime(server, options) {
|
|
31
|
+
const sockets = new Set();
|
|
32
|
+
const activeBySocket = new Map();
|
|
33
|
+
const activeResponses = new Set();
|
|
34
|
+
let draining = false;
|
|
35
|
+
let drained = false;
|
|
36
|
+
const maybeFinishDrain = () => {
|
|
37
|
+
if (draining && !drained && sockets.size === 0) {
|
|
38
|
+
drained = true;
|
|
39
|
+
options?.onDrained?.();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const requestStarted = (request, response) => {
|
|
43
|
+
const socket = request.socket;
|
|
44
|
+
activeBySocket.set(socket, (activeBySocket.get(socket) ?? 0) + 1);
|
|
45
|
+
activeResponses.add(response);
|
|
46
|
+
if (draining) {
|
|
47
|
+
response.shouldKeepAlive = false;
|
|
48
|
+
}
|
|
49
|
+
let settled = false;
|
|
50
|
+
const settle = () => {
|
|
51
|
+
if (settled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
settled = true;
|
|
55
|
+
activeResponses.delete(response);
|
|
56
|
+
const remaining = Math.max(0, (activeBySocket.get(socket) ?? 1) - 1);
|
|
57
|
+
if (remaining === 0) {
|
|
58
|
+
activeBySocket.delete(socket);
|
|
59
|
+
if (draining) {
|
|
60
|
+
socket.end();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
activeBySocket.set(socket, remaining);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
response.once("finish", settle);
|
|
68
|
+
response.once("close", settle);
|
|
69
|
+
};
|
|
70
|
+
server.prependListener("request", requestStarted);
|
|
71
|
+
const acceptSocket = (socket) => {
|
|
72
|
+
if (draining) {
|
|
73
|
+
socket.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
sockets.add(socket);
|
|
77
|
+
socket.once("close", () => {
|
|
78
|
+
sockets.delete(socket);
|
|
79
|
+
activeBySocket.delete(socket);
|
|
80
|
+
maybeFinishDrain();
|
|
81
|
+
});
|
|
82
|
+
server.emit("connection", socket);
|
|
83
|
+
socket.resume();
|
|
84
|
+
};
|
|
85
|
+
const drain = () => {
|
|
86
|
+
if (draining) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
draining = true;
|
|
90
|
+
for (const response of activeResponses) {
|
|
91
|
+
response.shouldKeepAlive = false;
|
|
92
|
+
}
|
|
93
|
+
for (const socket of sockets) {
|
|
94
|
+
if (!activeBySocket.has(socket)) {
|
|
95
|
+
socket.end();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
maybeFinishDrain();
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
acceptSocket,
|
|
102
|
+
drain,
|
|
103
|
+
close: () => {
|
|
104
|
+
server.off("request", requestStarted);
|
|
105
|
+
for (const socket of sockets) {
|
|
106
|
+
socket.destroy();
|
|
107
|
+
}
|
|
108
|
+
sockets.clear();
|
|
109
|
+
activeBySocket.clear();
|
|
110
|
+
activeResponses.clear();
|
|
111
|
+
draining = true;
|
|
112
|
+
maybeFinishDrain();
|
|
113
|
+
},
|
|
114
|
+
snapshot: () => ({
|
|
115
|
+
draining,
|
|
116
|
+
sockets: sockets.size,
|
|
117
|
+
activeRequests: [...activeBySocket.values()].reduce((total, count) => total + count, 0),
|
|
118
|
+
drained,
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function attachSocketWorkerProcess(server, input) {
|
|
123
|
+
let activated = false;
|
|
124
|
+
let gracefulDrain = false;
|
|
125
|
+
const pendingSockets = new Map();
|
|
126
|
+
const send = (message) => {
|
|
127
|
+
if (!process.connected || !process.send) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
process.send(message);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Parent supervision handles the disconnected worker.
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const runtime = createSocketWorkerRuntime(server, {
|
|
138
|
+
onDrained: () => {
|
|
139
|
+
send({
|
|
140
|
+
type: "proxy-worker:drained",
|
|
141
|
+
generation: input.generation,
|
|
142
|
+
pid: process.pid,
|
|
143
|
+
});
|
|
144
|
+
input.onDrained?.();
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
const drainWhenPendingSettled = () => {
|
|
148
|
+
if (!gracefulDrain || pendingSockets.size > 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Defer to a macrotask so a request buffered on a just-committed socket can
|
|
152
|
+
// reach the HTTP server before drain() ends idle sockets; draining
|
|
153
|
+
// synchronously here would truncate that in-flight request.
|
|
154
|
+
setImmediate(() => runtime.drain());
|
|
155
|
+
};
|
|
156
|
+
const onMessage = (message, handle) => {
|
|
157
|
+
if (message &&
|
|
158
|
+
typeof message === "object" &&
|
|
159
|
+
message.type === "proxy-worker:socket") {
|
|
160
|
+
const socketMessage = message;
|
|
161
|
+
if (socketMessage.generation === input.generation &&
|
|
162
|
+
typeof message.socketId === "string" &&
|
|
163
|
+
message.socketId.length > 0 &&
|
|
164
|
+
isTransferableProxySocket(handle)) {
|
|
165
|
+
const socketId = message.socketId;
|
|
166
|
+
const socket = handle;
|
|
167
|
+
socket.pause();
|
|
168
|
+
if (!activated ||
|
|
169
|
+
!process.connected ||
|
|
170
|
+
!process.send ||
|
|
171
|
+
gracefulDrain) {
|
|
172
|
+
socket.destroy();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (pendingSockets.has(socketId)) {
|
|
176
|
+
socket.destroy();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
pendingSockets.set(socketId, socket);
|
|
180
|
+
try {
|
|
181
|
+
process.send({
|
|
182
|
+
type: "proxy-worker:socket-accepted",
|
|
183
|
+
generation: input.generation,
|
|
184
|
+
pid: process.pid,
|
|
185
|
+
socketId,
|
|
186
|
+
}, (error) => {
|
|
187
|
+
if (error) {
|
|
188
|
+
pendingSockets.delete(socketId);
|
|
189
|
+
socket.destroy();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
pendingSockets.delete(socketId);
|
|
195
|
+
socket.destroy();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
destroyTransferredHandle(handle);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (handle !== undefined) {
|
|
204
|
+
destroyTransferredHandle(handle);
|
|
205
|
+
}
|
|
206
|
+
if (isProxyWorkerControlMessage(message) &&
|
|
207
|
+
message.generation === input.generation) {
|
|
208
|
+
if (message.type === "proxy-worker:activate") {
|
|
209
|
+
if (!activated) {
|
|
210
|
+
try {
|
|
211
|
+
input.onActivated?.();
|
|
212
|
+
activated = true;
|
|
213
|
+
send({
|
|
214
|
+
type: "proxy-worker:activated",
|
|
215
|
+
generation: input.generation,
|
|
216
|
+
pid: process.pid,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
send({
|
|
221
|
+
type: "proxy-worker:fatal",
|
|
222
|
+
generation: input.generation,
|
|
223
|
+
pid: process.pid,
|
|
224
|
+
message: error instanceof Error ? error.message : String(error),
|
|
225
|
+
});
|
|
226
|
+
runtime.drain();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else if (message.type === "proxy-worker:socket-commit" ||
|
|
231
|
+
message.type === "proxy-worker:socket-cancel") {
|
|
232
|
+
const socket = pendingSockets.get(message.socketId);
|
|
233
|
+
if (!socket) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
pendingSockets.delete(message.socketId);
|
|
237
|
+
if (message.type === "proxy-worker:socket-commit") {
|
|
238
|
+
runtime.acceptSocket(socket);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
socket.destroy();
|
|
242
|
+
}
|
|
243
|
+
drainWhenPendingSettled();
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Graceful drain: a socket-commit for an already-accepted socket may
|
|
247
|
+
// still be in flight. Stop admitting new offers and defer the drain
|
|
248
|
+
// until every pending socket is committed or canceled, so an
|
|
249
|
+
// acknowledged connection is served instead of silently dropped.
|
|
250
|
+
gracefulDrain = true;
|
|
251
|
+
drainWhenPendingSettled();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
// Terminal shutdown path: the parent IPC channel is gone (disconnect) or the
|
|
256
|
+
// process is being killed (SIGTERM/SIGINT). Unlike the graceful rolling drain
|
|
257
|
+
// above, pending offers can no longer be committed, so they are destroyed and
|
|
258
|
+
// the drain runs immediately — a best-effort close is correct when the process
|
|
259
|
+
// is exiting. The zero-downtime guarantee applies to the rolling handoff
|
|
260
|
+
// (control-message) path, not to process termination.
|
|
261
|
+
const drain = () => {
|
|
262
|
+
for (const socket of pendingSockets.values()) {
|
|
263
|
+
socket.destroy();
|
|
264
|
+
}
|
|
265
|
+
pendingSockets.clear();
|
|
266
|
+
runtime.drain();
|
|
267
|
+
};
|
|
268
|
+
const onTerminationSignal = () => drain();
|
|
269
|
+
process.on("message", onMessage);
|
|
270
|
+
process.once("disconnect", drain);
|
|
271
|
+
process.once("SIGTERM", onTerminationSignal);
|
|
272
|
+
process.once("SIGINT", onTerminationSignal);
|
|
273
|
+
send({
|
|
274
|
+
type: "proxy-worker:ready",
|
|
275
|
+
generation: input.generation,
|
|
276
|
+
pid: process.pid,
|
|
277
|
+
version: input.version,
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
...runtime,
|
|
281
|
+
close: () => {
|
|
282
|
+
process.off("message", onMessage);
|
|
283
|
+
process.off("disconnect", drain);
|
|
284
|
+
process.off("SIGTERM", onTerminationSignal);
|
|
285
|
+
process.off("SIGINT", onTerminationSignal);
|
|
286
|
+
for (const socket of pendingSockets.values()) {
|
|
287
|
+
socket.destroy();
|
|
288
|
+
}
|
|
289
|
+
pendingSockets.clear();
|
|
290
|
+
runtime.close();
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=socketWorkerRuntime.js.map
|
|
@@ -26,7 +26,13 @@ export declare function filterSkillIndex(items: SkillIndexItem[], query: SkillSe
|
|
|
26
26
|
* keys are built by concatenation (S3, Redis) or path joining.
|
|
27
27
|
*/
|
|
28
28
|
export declare function isSafeSkillResourcePath(resourcePath: string): boolean;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Whether a skill is visible from the calling scope. Fails closed, matching
|
|
31
|
+
* `filterSkillIndex`: a scoped skill is hidden unless the caller supplies a
|
|
32
|
+
* `scopeId` that the skill explicitly lists. Omitting `scopeId` therefore
|
|
33
|
+
* yields global skills only — it must never expose a scoped skill by name
|
|
34
|
+
* through `use_skill` / `read_skill_resource` (#1139).
|
|
35
|
+
*/
|
|
30
36
|
export declare function isSkillVisibleInScope(skill: Pick<SkillDefinition, "scope" | "scopeIds">, scopeId?: string): boolean;
|
|
31
37
|
/**
|
|
32
38
|
* Render the `<available_skills>` block embedded in the use_skill tool
|
|
@@ -29,11 +29,14 @@ export function filterSkillIndex(items, query) {
|
|
|
29
29
|
if ((item.status ?? "active") !== "active") {
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
32
|
-
// Scope filter: global skills always pass
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
// Scope filter: global skills always pass. Scoped skills require a
|
|
33
|
+
// scopeId that matches; when the caller supplies no scopeId we fail
|
|
34
|
+
// closed and exclude them, so a shared multi-tenant instance that forgets
|
|
35
|
+
// to pass a per-call scopeId never leaks one tenant's scoped skills to
|
|
36
|
+
// everyone (#1139). Callers that legitimately want a tenant's scoped
|
|
37
|
+
// skills must pass that tenant's scopeId.
|
|
38
|
+
if (item.scope === "scoped") {
|
|
39
|
+
if (!query.scopeId || !(item.scopeIds ?? []).includes(query.scopeId)) {
|
|
37
40
|
return false;
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -67,11 +70,20 @@ export function isSafeSkillResourcePath(resourcePath) {
|
|
|
67
70
|
!normalized.startsWith("/") &&
|
|
68
71
|
!normalized.split("/").some((segment) => segment === ".." || segment === ""));
|
|
69
72
|
}
|
|
70
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Whether a skill is visible from the calling scope. Fails closed, matching
|
|
75
|
+
* `filterSkillIndex`: a scoped skill is hidden unless the caller supplies a
|
|
76
|
+
* `scopeId` that the skill explicitly lists. Omitting `scopeId` therefore
|
|
77
|
+
* yields global skills only — it must never expose a scoped skill by name
|
|
78
|
+
* through `use_skill` / `read_skill_resource` (#1139).
|
|
79
|
+
*/
|
|
71
80
|
export function isSkillVisibleInScope(skill, scopeId) {
|
|
72
|
-
if (
|
|
81
|
+
if (skill.scope !== "scoped") {
|
|
73
82
|
return true;
|
|
74
83
|
}
|
|
84
|
+
if (!scopeId) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
75
87
|
return (skill.scopeIds ?? []).includes(scopeId);
|
|
76
88
|
}
|
|
77
89
|
/** Trim a description to its first sentence (or the whole text when unsplittable). */
|
|
@@ -127,7 +139,10 @@ export function renderSkillListing(items, budgetChars) {
|
|
|
127
139
|
* Returns null when nothing is visible so callers can skip injection.
|
|
128
140
|
*/
|
|
129
141
|
export function formatSkillsPromptIndex(items, maxItems) {
|
|
130
|
-
|
|
142
|
+
// Nothing visible, or the index is explicitly disabled (maxItems <= 0):
|
|
143
|
+
// skip injection entirely rather than emit a header + "N more skills exist"
|
|
144
|
+
// note with zero skill lines (#1139).
|
|
145
|
+
if (items.length === 0 || maxItems <= 0) {
|
|
131
146
|
return null;
|
|
132
147
|
}
|
|
133
148
|
const visible = items.slice(0, maxItems);
|
package/dist/lib/types/cli.d.ts
CHANGED
|
@@ -820,6 +820,42 @@ export type FallbackInfo = {
|
|
|
820
820
|
provider: string;
|
|
821
821
|
model: string;
|
|
822
822
|
};
|
|
823
|
+
export type ProxyRollingState = {
|
|
824
|
+
generation: number;
|
|
825
|
+
active: {
|
|
826
|
+
pid: number;
|
|
827
|
+
version: string;
|
|
828
|
+
generation: number;
|
|
829
|
+
} | null;
|
|
830
|
+
candidate: {
|
|
831
|
+
pid: number;
|
|
832
|
+
expectedVersion: string;
|
|
833
|
+
generation: number;
|
|
834
|
+
} | null;
|
|
835
|
+
draining: Array<{
|
|
836
|
+
pid: number;
|
|
837
|
+
version: string;
|
|
838
|
+
generation: number;
|
|
839
|
+
}>;
|
|
840
|
+
queuedSockets: number;
|
|
841
|
+
rejectedSockets: number;
|
|
842
|
+
failedTransfers: number;
|
|
843
|
+
lastFailure: {
|
|
844
|
+
at: string;
|
|
845
|
+
generation: number;
|
|
846
|
+
version: string;
|
|
847
|
+
phase: "startup" | "activation" | "runtime" | "transfer";
|
|
848
|
+
message: string;
|
|
849
|
+
} | null;
|
|
850
|
+
};
|
|
851
|
+
export type ProxySupervisorState = {
|
|
852
|
+
pid: number;
|
|
853
|
+
host: string;
|
|
854
|
+
port: number;
|
|
855
|
+
startTime: string;
|
|
856
|
+
updaterPid?: number;
|
|
857
|
+
rolling: ProxyRollingState;
|
|
858
|
+
};
|
|
823
859
|
/** Persisted state for a running proxy instance */
|
|
824
860
|
export type ProxyState = {
|
|
825
861
|
pid: number;
|
|
@@ -840,6 +876,8 @@ export type ProxyState = {
|
|
|
840
876
|
guardPid?: number;
|
|
841
877
|
/** Dedicated updater PID for launchd-managed proxy installations. */
|
|
842
878
|
updaterPid?: number;
|
|
879
|
+
/** Stable listener supervisor PID when requests are served by socket workers. */
|
|
880
|
+
supervisorPid?: number;
|
|
843
881
|
/** How the proxy was launched — "launchd" if installed as service, "manual" otherwise */
|
|
844
882
|
managedBy?: "launchd" | "manual";
|
|
845
883
|
/** Whether the proxy is running in transparent passthrough mode */
|
|
@@ -1637,3 +1675,12 @@ export type CliNetworkCommandArgs = BaseCommandArgs & {
|
|
|
1637
1675
|
/** Show detailed information */
|
|
1638
1676
|
detailed?: boolean;
|
|
1639
1677
|
};
|
|
1678
|
+
/**
|
|
1679
|
+
* A single audio-player invocation for CLI TTS playback: a binary plus its
|
|
1680
|
+
* arguments for `execFile`. The player list is tried in order until one
|
|
1681
|
+
* succeeds (see `src/cli/utils/audioPlayer.ts`).
|
|
1682
|
+
*/
|
|
1683
|
+
export type CliAudioPlayerCommand = {
|
|
1684
|
+
command: string;
|
|
1685
|
+
args: string[];
|
|
1686
|
+
};
|
|
@@ -1455,6 +1455,162 @@ export type UpdaterWorkerSupervisor = {
|
|
|
1455
1455
|
checkNow: () => number | undefined;
|
|
1456
1456
|
stop: () => void;
|
|
1457
1457
|
};
|
|
1458
|
+
export type ProxyWorkerControlMessage = {
|
|
1459
|
+
type: "proxy-worker:activate";
|
|
1460
|
+
generation: number;
|
|
1461
|
+
} | {
|
|
1462
|
+
type: "proxy-worker:drain";
|
|
1463
|
+
generation: number;
|
|
1464
|
+
} | {
|
|
1465
|
+
type: "proxy-worker:shutdown";
|
|
1466
|
+
generation: number;
|
|
1467
|
+
} | {
|
|
1468
|
+
type: "proxy-worker:socket-commit";
|
|
1469
|
+
generation: number;
|
|
1470
|
+
socketId: string;
|
|
1471
|
+
} | {
|
|
1472
|
+
type: "proxy-worker:socket-cancel";
|
|
1473
|
+
generation: number;
|
|
1474
|
+
socketId: string;
|
|
1475
|
+
};
|
|
1476
|
+
export type ProxyWorkerStatusMessage = {
|
|
1477
|
+
type: "proxy-worker:ready";
|
|
1478
|
+
generation: number;
|
|
1479
|
+
pid: number;
|
|
1480
|
+
version: string;
|
|
1481
|
+
} | {
|
|
1482
|
+
type: "proxy-worker:drained";
|
|
1483
|
+
generation: number;
|
|
1484
|
+
pid: number;
|
|
1485
|
+
} | {
|
|
1486
|
+
type: "proxy-worker:activated";
|
|
1487
|
+
generation: number;
|
|
1488
|
+
pid: number;
|
|
1489
|
+
} | {
|
|
1490
|
+
type: "proxy-worker:fatal";
|
|
1491
|
+
generation: number;
|
|
1492
|
+
pid: number;
|
|
1493
|
+
message: string;
|
|
1494
|
+
} | {
|
|
1495
|
+
type: "proxy-worker:socket-accepted";
|
|
1496
|
+
generation: number;
|
|
1497
|
+
pid: number;
|
|
1498
|
+
socketId: string;
|
|
1499
|
+
};
|
|
1500
|
+
export type ProxyWorkerSocketMessage = {
|
|
1501
|
+
type: "proxy-worker:socket";
|
|
1502
|
+
generation: number;
|
|
1503
|
+
socketId: string;
|
|
1504
|
+
};
|
|
1505
|
+
export type ProxyWorkerIpcMessage = ProxyWorkerControlMessage | ProxyWorkerStatusMessage | ProxyWorkerSocketMessage;
|
|
1506
|
+
export type TransferableProxySocket = Pick<import("node:net").Socket, "destroy" | "end" | "pause" | "resume" | "once">;
|
|
1507
|
+
export type RollingWorkerHandle = {
|
|
1508
|
+
pid: number;
|
|
1509
|
+
sendControl: (message: ProxyWorkerControlMessage) => void;
|
|
1510
|
+
sendSocket: (generation: number, socket: TransferableProxySocket, callback: (error?: Error | null) => void) => void;
|
|
1511
|
+
terminate: (signal?: NodeJS.Signals) => void;
|
|
1512
|
+
onMessage: (listener: (message: ProxyWorkerStatusMessage) => void) => () => void;
|
|
1513
|
+
onExit: (listener: (code: number | null, signal: string | null) => void) => () => void;
|
|
1514
|
+
};
|
|
1515
|
+
export type SpawnProxySocketWorkerOptions = {
|
|
1516
|
+
generation: number;
|
|
1517
|
+
expectedVersion: string;
|
|
1518
|
+
command: string;
|
|
1519
|
+
args: string[];
|
|
1520
|
+
env?: NodeJS.ProcessEnv;
|
|
1521
|
+
stdout?: "inherit" | "ignore";
|
|
1522
|
+
stderr?: "inherit" | "ignore";
|
|
1523
|
+
socketAckTimeoutMs?: number;
|
|
1524
|
+
};
|
|
1525
|
+
export type RollingWorkerSupervisorSnapshot = {
|
|
1526
|
+
generation: number;
|
|
1527
|
+
active: {
|
|
1528
|
+
pid: number;
|
|
1529
|
+
version: string;
|
|
1530
|
+
generation: number;
|
|
1531
|
+
} | null;
|
|
1532
|
+
candidate: {
|
|
1533
|
+
pid: number;
|
|
1534
|
+
expectedVersion: string;
|
|
1535
|
+
generation: number;
|
|
1536
|
+
} | null;
|
|
1537
|
+
draining: Array<{
|
|
1538
|
+
pid: number;
|
|
1539
|
+
version: string;
|
|
1540
|
+
generation: number;
|
|
1541
|
+
}>;
|
|
1542
|
+
queuedSockets: number;
|
|
1543
|
+
rejectedSockets: number;
|
|
1544
|
+
failedTransfers: number;
|
|
1545
|
+
lastFailure: {
|
|
1546
|
+
at: string;
|
|
1547
|
+
generation: number;
|
|
1548
|
+
version: string;
|
|
1549
|
+
phase: "startup" | "activation" | "runtime" | "transfer";
|
|
1550
|
+
message: string;
|
|
1551
|
+
} | null;
|
|
1552
|
+
};
|
|
1553
|
+
export type RollingWorkerSupervisorOptions = {
|
|
1554
|
+
spawnWorker: (generation: number, expectedVersion: string) => RollingWorkerHandle;
|
|
1555
|
+
readyTimeoutMs?: number;
|
|
1556
|
+
socketQueueLimit?: number;
|
|
1557
|
+
socketQueueTimeoutMs?: number;
|
|
1558
|
+
shutdownTimeoutMs?: number;
|
|
1559
|
+
onStateChange?: (snapshot: RollingWorkerSupervisorSnapshot) => void;
|
|
1560
|
+
log?: (message: string) => void;
|
|
1561
|
+
};
|
|
1562
|
+
export type RollingProxyServerOptions = {
|
|
1563
|
+
host: string;
|
|
1564
|
+
port: number;
|
|
1565
|
+
initialVersion: string;
|
|
1566
|
+
spawnWorker: (generation: number, expectedVersion: string) => RollingWorkerHandle;
|
|
1567
|
+
readyTimeoutMs?: number;
|
|
1568
|
+
socketQueueLimit?: number;
|
|
1569
|
+
socketQueueTimeoutMs?: number;
|
|
1570
|
+
shutdownTimeoutMs?: number;
|
|
1571
|
+
recoveryDelayMs?: number;
|
|
1572
|
+
maxRecoveryDelayMs?: number;
|
|
1573
|
+
onStateChange?: (snapshot: RollingWorkerSupervisorSnapshot) => void;
|
|
1574
|
+
log?: (message: string) => void;
|
|
1575
|
+
};
|
|
1576
|
+
export type RollingProxyServer = {
|
|
1577
|
+
address: {
|
|
1578
|
+
host: string;
|
|
1579
|
+
port: number;
|
|
1580
|
+
};
|
|
1581
|
+
replace: (expectedVersion: string) => Promise<RollingWorkerSupervisorSnapshot>;
|
|
1582
|
+
snapshot: () => RollingWorkerSupervisorSnapshot;
|
|
1583
|
+
close: () => Promise<void>;
|
|
1584
|
+
};
|
|
1585
|
+
export type RollingManagedWorker = {
|
|
1586
|
+
handle: RollingWorkerHandle;
|
|
1587
|
+
generation: number;
|
|
1588
|
+
version: string;
|
|
1589
|
+
dispose: () => void;
|
|
1590
|
+
};
|
|
1591
|
+
export type RollingCandidateWorker = RollingManagedWorker & {
|
|
1592
|
+
expectedVersion: string;
|
|
1593
|
+
activationRequested: boolean;
|
|
1594
|
+
settle: (error?: Error) => void;
|
|
1595
|
+
};
|
|
1596
|
+
export type RollingQueuedSocket = {
|
|
1597
|
+
socket: TransferableProxySocket;
|
|
1598
|
+
timeout: NodeJS.Timeout;
|
|
1599
|
+
};
|
|
1600
|
+
export type SocketWorkerRuntime = {
|
|
1601
|
+
acceptSocket: (socket: TransferableProxySocket) => void;
|
|
1602
|
+
drain: () => void;
|
|
1603
|
+
close: () => void;
|
|
1604
|
+
snapshot: () => {
|
|
1605
|
+
draining: boolean;
|
|
1606
|
+
sockets: number;
|
|
1607
|
+
activeRequests: number;
|
|
1608
|
+
drained: boolean;
|
|
1609
|
+
};
|
|
1610
|
+
};
|
|
1611
|
+
export type SocketWorkerRuntimeOptions = {
|
|
1612
|
+
onDrained?: () => void;
|
|
1613
|
+
};
|
|
1458
1614
|
/** Shape of the dynamically-imported js-yaml module. `dump` is optional —
|
|
1459
1615
|
* read-only consumers (proxy config loader) only need `load`; writers
|
|
1460
1616
|
* (CLI primary-account commands) check `dump` before calling. */
|
|
@@ -14,6 +14,7 @@ export declare const ERROR_CODES: {
|
|
|
14
14
|
readonly MEMORY_EXHAUSTED: "MEMORY_EXHAUSTED";
|
|
15
15
|
readonly NETWORK_ERROR: "NETWORK_ERROR";
|
|
16
16
|
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
17
|
+
readonly PROXY_WORKER_LIFECYCLE_FAILED: "PROXY_WORKER_LIFECYCLE_FAILED";
|
|
17
18
|
readonly PROVIDER_NOT_AVAILABLE: "PROVIDER_NOT_AVAILABLE";
|
|
18
19
|
readonly PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED";
|
|
19
20
|
readonly PROVIDER_QUOTA_EXCEEDED: "PROVIDER_QUOTA_EXCEEDED";
|
|
@@ -247,6 +248,13 @@ export declare class ErrorFactory {
|
|
|
247
248
|
* Create an evaluation execution failed error
|
|
248
249
|
*/
|
|
249
250
|
static evaluationExecutionFailed(operation: string, originalError: Error): NeuroLinkError;
|
|
251
|
+
/**
|
|
252
|
+
* Create a proxy rolling-worker lifecycle error. Preserves the caller's exact
|
|
253
|
+
* message so existing substring-based assertions keep working, while giving
|
|
254
|
+
* callers a typed `code`/`category` to branch on instead of string-matching
|
|
255
|
+
* generic `Error` instances.
|
|
256
|
+
*/
|
|
257
|
+
static proxyWorkerLifecycle(message: string, context?: Record<string, unknown>): NeuroLinkError;
|
|
250
258
|
}
|
|
251
259
|
/**
|
|
252
260
|
* Timeout wrapper for async operations
|
|
@@ -20,6 +20,7 @@ export const ERROR_CODES = {
|
|
|
20
20
|
MEMORY_EXHAUSTED: "MEMORY_EXHAUSTED",
|
|
21
21
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
22
22
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
23
|
+
PROXY_WORKER_LIFECYCLE_FAILED: "PROXY_WORKER_LIFECYCLE_FAILED",
|
|
23
24
|
// Provider errors
|
|
24
25
|
PROVIDER_NOT_AVAILABLE: "PROVIDER_NOT_AVAILABLE",
|
|
25
26
|
PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
|
|
@@ -818,6 +819,25 @@ export class ErrorFactory {
|
|
|
818
819
|
originalError,
|
|
819
820
|
});
|
|
820
821
|
}
|
|
822
|
+
// ============================================================================
|
|
823
|
+
// PROXY ROLLING-WORKER ERRORS
|
|
824
|
+
// ============================================================================
|
|
825
|
+
/**
|
|
826
|
+
* Create a proxy rolling-worker lifecycle error. Preserves the caller's exact
|
|
827
|
+
* message so existing substring-based assertions keep working, while giving
|
|
828
|
+
* callers a typed `code`/`category` to branch on instead of string-matching
|
|
829
|
+
* generic `Error` instances.
|
|
830
|
+
*/
|
|
831
|
+
static proxyWorkerLifecycle(message, context) {
|
|
832
|
+
return new NeuroLinkError({
|
|
833
|
+
code: ERROR_CODES.PROXY_WORKER_LIFECYCLE_FAILED,
|
|
834
|
+
message,
|
|
835
|
+
category: ErrorCategory.SYSTEM,
|
|
836
|
+
severity: ErrorSeverity.HIGH,
|
|
837
|
+
retriable: false,
|
|
838
|
+
context: context || {},
|
|
839
|
+
});
|
|
840
|
+
}
|
|
821
841
|
}
|
|
822
842
|
/**
|
|
823
843
|
* Timeout wrapper for async operations
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RollingProxyServer, RollingProxyServerOptions } from "../types/index.js";
|
|
2
|
+
/** Keep one public listener stable while serving workers start and rotate. */
|
|
3
|
+
export declare function startRollingProxyServer(options: RollingProxyServerOptions): Promise<RollingProxyServer>;
|