querysub 0.442.0 → 0.446.0
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/.claude/settings.local.json +10 -1
- package/package.json +4 -2
- package/src/-a-archives/archivesBackBlaze.ts +9 -3
- package/src/0-path-value-core/PathRouter.ts +2 -2
- package/src/0-path-value-core/pathValueArchives.ts +1 -1
- package/src/0-path-value-core/pathValueCore.ts +5 -1
- package/src/2-proxy/PathValueProxyWatcher.ts +0 -3
- package/src/2-proxy/TransactionDelayer.ts +1 -1
- package/src/3-path-functions/PathFunctionRunner.ts +1 -1
- package/src/4-deploy/git.ts +56 -1
- package/src/4-querysub/QuerysubController.ts +2 -0
- package/src/4-querysub/querysubPrediction.ts +5 -2
- package/src/deployManager/components/CommitModal.tsx +274 -0
- package/src/deployManager/components/deployButtons.tsx +14 -54
- package/src/deployManager/machineApplyMainCode.ts +11 -6
- package/src/deployManager/machineSchema.ts +17 -1
- package/src/diagnostics/debugger/debugger-remote.ts +231 -0
- package/src/diagnostics/debugger/mcp-server.ts +775 -0
- package/src/diagnostics/logs/errorNotifications2/openRouterHelper.ts +127 -0
- package/src/diagnostics/pathAuditer.ts +5 -0
- package/test.ts +12 -3
|
@@ -26,7 +26,12 @@ import { PromiseObj } from "../promise";
|
|
|
26
26
|
import path from "path";
|
|
27
27
|
import { fsExistsAsync } from "../fs";
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// The deployed-service stdout log (pipe.txt) is a rolling buffer: once it grows
|
|
30
|
+
// to PIPE_FILE_LINE_LIMIT lines it is truncated down to PIPE_FILE_LINE_KEEP. The
|
|
31
|
+
// file size is checked every PIPE_FILE_LINE_KEEP lines, so the file stays within
|
|
32
|
+
// [PIPE_FILE_LINE_KEEP, PIPE_FILE_LINE_LIMIT] lines.
|
|
33
|
+
const PIPE_FILE_LINE_LIMIT = 2_000;
|
|
34
|
+
const PIPE_FILE_LINE_KEEP = 1_000;
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
const getMemoryInfo = measureWrap(async function getMemoryInfo(): Promise<{ value: number; max: number } | undefined> {
|
|
@@ -551,16 +556,16 @@ while IFS= read -r line; do
|
|
|
551
556
|
echo "$line" >> "${pipeFile}"
|
|
552
557
|
((line_count++))
|
|
553
558
|
|
|
554
|
-
# Check line count every ${
|
|
555
|
-
if (( line_count % ${
|
|
559
|
+
# Check line count every ${PIPE_FILE_LINE_KEEP} lines to avoid too much overhead
|
|
560
|
+
if (( line_count % ${PIPE_FILE_LINE_KEEP} == 0 )); then
|
|
556
561
|
if [ -f "${pipeFile}" ]; then
|
|
557
562
|
# Count total lines in file
|
|
558
563
|
total_lines=$(wc -l < "${pipeFile}")
|
|
559
|
-
if [ "$total_lines" -
|
|
564
|
+
if [ "$total_lines" -ge ${PIPE_FILE_LINE_LIMIT} ]; then
|
|
560
565
|
# Wait 2 seconds to give the watcher time to read pending content
|
|
561
566
|
sleep 2
|
|
562
|
-
# Keep only the last ${
|
|
563
|
-
tail -n ${
|
|
567
|
+
# Keep only the last ${PIPE_FILE_LINE_KEEP} lines when file gets too many lines
|
|
568
|
+
tail -n ${PIPE_FILE_LINE_KEEP} "${pipeFile}" > "${pipeFile}.tmp" && mv "${pipeFile}.tmp" "${pipeFile}"
|
|
564
569
|
# AND, give it time to read the truncation
|
|
565
570
|
sleep 2
|
|
566
571
|
fi
|
|
@@ -13,7 +13,7 @@ import { getOwnMachineId } from "../-a-auth/certs";
|
|
|
13
13
|
import { delay, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
14
14
|
import { lazy } from "socket-function/src/caching";
|
|
15
15
|
import { errorToUndefinedSilent } from "../errors";
|
|
16
|
-
import { commitAndPush, getGitRefInfo, getGitRefLive, getGitURLLive, getGitUncommitted, getLatestRefOnUpstreamBranch, setGitRef } from "../4-deploy/git";
|
|
16
|
+
import { commitAndPush, getGitDiff, getGitRefInfo, getGitRefLive, getGitURLLive, getGitUncommitted, getLatestRefOnUpstreamBranch, setGitRef } from "../4-deploy/git";
|
|
17
17
|
import { OnServiceChange } from "./machineController";
|
|
18
18
|
import path from "path";
|
|
19
19
|
import fs from "fs";
|
|
@@ -291,6 +291,21 @@ export class MachineServiceControllerBase {
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/** Returns a unified-diff-style summary of all uncommitted changes, for the
|
|
295
|
+
* same repo `commitPushService` / `commitPushAndPublishQuerysub` would commit. */
|
|
296
|
+
public async getGitDiff(config: {
|
|
297
|
+
useQuerysub?: boolean;
|
|
298
|
+
}): Promise<string> {
|
|
299
|
+
let gitDir = ".";
|
|
300
|
+
if (config.useQuerysub) {
|
|
301
|
+
gitDir = path.resolve("../querysub");
|
|
302
|
+
if (!await fsExistsAsync(gitDir)) {
|
|
303
|
+
throw new Error(`Querysub folder does not exist at ${gitDir}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return await getGitDiff(gitDir);
|
|
307
|
+
}
|
|
308
|
+
|
|
294
309
|
public async commitPushService(commitMessage: string) {
|
|
295
310
|
if (commitMessage.toLowerCase().includes("querysub")) {
|
|
296
311
|
let querysubFolder = path.resolve("../querysub");
|
|
@@ -448,6 +463,7 @@ export const MachineServiceController = getSyncedController(
|
|
|
448
463
|
deleteServiceConfig: {},
|
|
449
464
|
getGitInfo: {},
|
|
450
465
|
getGitRefInfo: {},
|
|
466
|
+
getGitDiff: {},
|
|
451
467
|
commitPushService: {},
|
|
452
468
|
commitPushAndPublishQuerysub: {},
|
|
453
469
|
getPendingFunctions: {},
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DebuggerRemote — a small wrapper around a remote Node.js V8 inspector.
|
|
3
|
+
*
|
|
4
|
+
* Connects over the Chrome DevTools Protocol (CDP) and exposes `evaluate()`
|
|
5
|
+
* for running expressions inside the remote process. Connecting is lazy: the
|
|
6
|
+
* constructor kicks it off, and every request awaits it, so callers can just
|
|
7
|
+
* `new DebuggerRemote(...)` and `await remote.evaluate(...)`.
|
|
8
|
+
*
|
|
9
|
+
* Copied from https://github.com/sliftist/debug
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "fs";
|
|
12
|
+
|
|
13
|
+
/** How the remote inspector should be located. */
|
|
14
|
+
export type DebuggerRemoteOptions =
|
|
15
|
+
/** A ready-to-use CDP WebSocket URL (ws://host:port/<uuid>). */
|
|
16
|
+
| { wsUrl: string }
|
|
17
|
+
/** An inspector HTTP port; the live WebSocket URL is looked up. */
|
|
18
|
+
| { port: number; host?: string }
|
|
19
|
+
/** The `debugger-port.json` file written by the target script. */
|
|
20
|
+
| { portFile: string };
|
|
21
|
+
|
|
22
|
+
interface InspectorTarget {
|
|
23
|
+
webSocketDebuggerUrl?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PortFile {
|
|
27
|
+
port: number;
|
|
28
|
+
url: string;
|
|
29
|
+
pid: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PendingRequest {
|
|
33
|
+
resolve: (result: any) => void;
|
|
34
|
+
reject: (err: Error) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
38
|
+
const CONNECT_MAX_ATTEMPTS = 10;
|
|
39
|
+
const CONNECT_RETRY_DELAY_MS = 1_000;
|
|
40
|
+
|
|
41
|
+
function delay(ms: number): Promise<void> {
|
|
42
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class DebuggerRemote {
|
|
46
|
+
private ready: Promise<WebSocket>;
|
|
47
|
+
private nextId = 1;
|
|
48
|
+
private pending = new Map<number, PendingRequest>();
|
|
49
|
+
private eventListeners = new Map<string, Set<(params: any) => void>>();
|
|
50
|
+
private closed = false;
|
|
51
|
+
|
|
52
|
+
constructor(private options: DebuggerRemoteOptions) {
|
|
53
|
+
this.ready = this.connect();
|
|
54
|
+
// Avoid an unhandled-rejection warning if nobody awaits a request
|
|
55
|
+
// before the connection itself fails; real callers still see the error.
|
|
56
|
+
this.ready.catch(() => {});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Evaluate a JS expression inside the remote process and return its value. */
|
|
60
|
+
async evaluate(expression: string): Promise<unknown> {
|
|
61
|
+
const response = await this.send("Runtime.evaluate", {
|
|
62
|
+
expression,
|
|
63
|
+
returnByValue: true,
|
|
64
|
+
awaitPromise: true,
|
|
65
|
+
});
|
|
66
|
+
if (response.exceptionDetails) {
|
|
67
|
+
throw new Error(`Remote threw: ${JSON.stringify(response.exceptionDetails)}`);
|
|
68
|
+
}
|
|
69
|
+
return response.result.value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to a CDP event notification (e.g. "Debugger.scriptParsed",
|
|
74
|
+
* "Runtime.consoleAPICalled"). Returns an unsubscribe function.
|
|
75
|
+
*/
|
|
76
|
+
on(method: string, listener: (params: any) => void): () => void {
|
|
77
|
+
let listeners = this.eventListeners.get(method);
|
|
78
|
+
if (!listeners) {
|
|
79
|
+
listeners = new Set();
|
|
80
|
+
this.eventListeners.set(method, listeners);
|
|
81
|
+
}
|
|
82
|
+
listeners.add(listener);
|
|
83
|
+
return () => {
|
|
84
|
+
listeners!.delete(listener);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Send a raw CDP command and await its result. */
|
|
89
|
+
send(method: string, params: object = {}, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<any> {
|
|
90
|
+
return this.ready.then(
|
|
91
|
+
(ws) =>
|
|
92
|
+
new Promise<any>((resolve, reject) => {
|
|
93
|
+
if (this.closed) {
|
|
94
|
+
reject(new Error("DebuggerRemote is closed"));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const id = this.nextId++;
|
|
98
|
+
const timeout = setTimeout(() => {
|
|
99
|
+
this.pending.delete(id);
|
|
100
|
+
reject(new Error(`Timed out after ${timeoutMs}ms waiting for "${method}"`));
|
|
101
|
+
}, timeoutMs);
|
|
102
|
+
this.pending.set(id, {
|
|
103
|
+
resolve: (result) => {
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
resolve(result);
|
|
106
|
+
},
|
|
107
|
+
reject: (err) => {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
reject(err);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
ws.send(JSON.stringify({ id, method, params }));
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Close the connection and reject any in-flight requests. */
|
|
118
|
+
close(): void {
|
|
119
|
+
this.closed = true;
|
|
120
|
+
this.ready.then((ws) => ws.close(), () => {});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Connect, retrying on any error so `attach` can be started before the
|
|
125
|
+
* target is up (missing port file, inspector not listening, etc.).
|
|
126
|
+
*/
|
|
127
|
+
private async connect(): Promise<WebSocket> {
|
|
128
|
+
let lastErr: unknown;
|
|
129
|
+
for (let attempt = 1; attempt <= CONNECT_MAX_ATTEMPTS; attempt++) {
|
|
130
|
+
if (this.closed) throw new Error("DebuggerRemote was closed before connecting");
|
|
131
|
+
try {
|
|
132
|
+
return await this.tryConnect();
|
|
133
|
+
} catch (err) {
|
|
134
|
+
lastErr = err;
|
|
135
|
+
const message = (err as Error)?.message ?? String(err);
|
|
136
|
+
if (attempt < CONNECT_MAX_ATTEMPTS) {
|
|
137
|
+
// stderr, not stdout — callers may use stdout for protocol I/O.
|
|
138
|
+
console.error(
|
|
139
|
+
`[DebuggerRemote] connect attempt ${attempt}/${CONNECT_MAX_ATTEMPTS} ` +
|
|
140
|
+
`failed (${message}); retrying in ${CONNECT_RETRY_DELAY_MS}ms...`,
|
|
141
|
+
);
|
|
142
|
+
await delay(CONNECT_RETRY_DELAY_MS);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Could not connect to inspector after ${CONNECT_MAX_ATTEMPTS} attempts: ` +
|
|
148
|
+
`${(lastErr as Error)?.message ?? lastErr}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** A single connection attempt: resolve the URL and open the WebSocket. */
|
|
153
|
+
private async tryConnect(): Promise<WebSocket> {
|
|
154
|
+
const wsUrl = await this.resolveWsUrl();
|
|
155
|
+
const ws = new WebSocket(wsUrl);
|
|
156
|
+
await new Promise<void>((resolve, reject) => {
|
|
157
|
+
ws.addEventListener("open", () => resolve(), { once: true });
|
|
158
|
+
ws.addEventListener(
|
|
159
|
+
"error",
|
|
160
|
+
() => reject(new Error(`Could not connect to inspector at ${wsUrl}`)),
|
|
161
|
+
{ once: true },
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
ws.addEventListener("message", (event) => this.onMessage(event));
|
|
165
|
+
ws.addEventListener("close", () => this.onClose());
|
|
166
|
+
return ws;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Turn the constructor options into a concrete CDP WebSocket URL. */
|
|
170
|
+
private async resolveWsUrl(): Promise<string> {
|
|
171
|
+
const opts = this.options;
|
|
172
|
+
if ("wsUrl" in opts) {
|
|
173
|
+
return opts.wsUrl;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let port: number;
|
|
177
|
+
let host = "127.0.0.1";
|
|
178
|
+
if ("portFile" in opts) {
|
|
179
|
+
if (!fs.existsSync(opts.portFile)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Port file not found: ${opts.portFile}\n` +
|
|
182
|
+
`Start the target first with \`yarn test\`.`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const info = JSON.parse(fs.readFileSync(opts.portFile, "utf8")) as PortFile;
|
|
186
|
+
port = info.port;
|
|
187
|
+
} else {
|
|
188
|
+
port = opts.port;
|
|
189
|
+
if (opts.host) host = opts.host;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// The UUID in the WebSocket URL changes per process, so always look it
|
|
193
|
+
// up fresh from the inspector's HTTP endpoint.
|
|
194
|
+
const res = await fetch(`http://${host}:${port}/json/list`);
|
|
195
|
+
const targets = (await res.json()) as InspectorTarget[];
|
|
196
|
+
const wsUrl = targets[0]?.webSocketDebuggerUrl;
|
|
197
|
+
if (!wsUrl) {
|
|
198
|
+
throw new Error(`No webSocketDebuggerUrl returned by inspector on port ${port}`);
|
|
199
|
+
}
|
|
200
|
+
return wsUrl;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private onMessage(event: MessageEvent): void {
|
|
204
|
+
const msg = JSON.parse(event.data as string);
|
|
205
|
+
if (typeof msg.id !== "number") {
|
|
206
|
+
// A CDP event notification: { method, params }, no `id`.
|
|
207
|
+
if (typeof msg.method === "string") {
|
|
208
|
+
const listeners = this.eventListeners.get(msg.method);
|
|
209
|
+
if (listeners) {
|
|
210
|
+
for (const listener of [...listeners]) listener(msg.params ?? {});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const pending = this.pending.get(msg.id);
|
|
216
|
+
if (!pending) return;
|
|
217
|
+
this.pending.delete(msg.id);
|
|
218
|
+
if (msg.error) {
|
|
219
|
+
pending.reject(new Error(`CDP error: ${JSON.stringify(msg.error)}`));
|
|
220
|
+
} else {
|
|
221
|
+
pending.resolve(msg.result);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private onClose(): void {
|
|
226
|
+
for (const pending of this.pending.values()) {
|
|
227
|
+
pending.reject(new Error("Inspector connection closed"));
|
|
228
|
+
}
|
|
229
|
+
this.pending.clear();
|
|
230
|
+
}
|
|
231
|
+
}
|