mtech-ai-local-runner 0.1.14 → 0.1.15
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 +10 -0
- package/dist/src/process/node-process-group-host.js +91 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,16 @@ The package stores safe runner configuration locally and keeps OAuth refresh cre
|
|
|
10
10
|
|
|
11
11
|
Public npm installation requires no npm account or publishing OTP. Setup requires Gateway OAuth login, and local Claude Code must already be authenticated (`claude auth status`).
|
|
12
12
|
|
|
13
|
+
## 0.1.15: startup output before MCP readiness
|
|
14
|
+
|
|
15
|
+
The process host drains stdout from spawn, before the adapter starts consuming
|
|
16
|
+
events after authenticated MCP discovery. A bounded queue prevents the child
|
|
17
|
+
from blocking on ordinary startup output; overflow stops only its owned process
|
|
18
|
+
group. This changes no tool permissions, preparation deadlines or task limits.
|
|
19
|
+
Fake-process tests reproduce the defect, but do not prove the cause of a previous
|
|
20
|
+
real-account failure or successful account/MCP acceptance. Publication and a
|
|
21
|
+
foreground installation do not update the daemon or authorize a model run.
|
|
22
|
+
|
|
13
23
|
## 0.1.14: foreground administrator authentication wait
|
|
14
24
|
|
|
15
25
|
The manual Codex pilot waits for administrator authentication before starting
|
|
@@ -7,6 +7,19 @@ export class NodeProcessGroupHost {
|
|
|
7
7
|
const child = spawn(command, args, { ...options, shell: false, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
8
8
|
let failure;
|
|
9
9
|
let owned = true;
|
|
10
|
+
let finished = false, consumed = false, queuedBytes = 0, wake;
|
|
11
|
+
const lines = [];
|
|
12
|
+
const MAX_QUEUED_BYTES = 1_048_576, MAX_QUEUED_LINES = 8192;
|
|
13
|
+
const notify = () => { const resolve = wake; wake = undefined; resolve?.(); };
|
|
14
|
+
let termination;
|
|
15
|
+
const exitCallbacks = [];
|
|
16
|
+
const terminated = (code, signal) => {
|
|
17
|
+
if (termination)
|
|
18
|
+
return;
|
|
19
|
+
termination = { code, signal };
|
|
20
|
+
for (const callback of exitCallbacks.splice(0))
|
|
21
|
+
callback(code, signal);
|
|
22
|
+
};
|
|
10
23
|
const kill = (signal = "SIGKILL") => {
|
|
11
24
|
if (!owned || !child.pid)
|
|
12
25
|
return;
|
|
@@ -28,47 +41,105 @@ export class NodeProcessGroupHost {
|
|
|
28
41
|
if (signal === "SIGKILL")
|
|
29
42
|
owned = false;
|
|
30
43
|
};
|
|
31
|
-
child.on("error", () => { failure
|
|
32
|
-
child.stdin.on("error", () => {
|
|
44
|
+
child.on("error", () => { failure ??= new Error("Provider process could not start"); terminated(null, null); notify(); });
|
|
45
|
+
child.stdin.on("error", () => {
|
|
46
|
+
failure ??= new Error("Provider process input closed");
|
|
47
|
+
try {
|
|
48
|
+
kill();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
failure = new Error('Provider process group cleanup could not be confirmed');
|
|
52
|
+
}
|
|
53
|
+
terminated(null, null);
|
|
54
|
+
notify();
|
|
55
|
+
});
|
|
33
56
|
// Provider stderr can contain account details. Consume without publishing it.
|
|
34
57
|
child.stderr.resume();
|
|
35
|
-
child.once("exit", () => {
|
|
58
|
+
child.once("exit", (code, signal) => {
|
|
36
59
|
try {
|
|
37
60
|
kill();
|
|
38
61
|
}
|
|
39
62
|
catch {
|
|
40
63
|
failure = new Error("Provider process group cleanup could not be confirmed");
|
|
41
64
|
}
|
|
65
|
+
terminated(code, signal);
|
|
42
66
|
});
|
|
67
|
+
// Drain from spawn, not from the first adapter event read: official exec can
|
|
68
|
+
// emit startup events before it initializes MCP, while the adapter awaits
|
|
69
|
+
// that initialization before consuming events. Bound the pre-consumer queue.
|
|
70
|
+
void (async () => {
|
|
71
|
+
let pending = Buffer.alloc(0);
|
|
72
|
+
const enqueue = (line) => {
|
|
73
|
+
if (line.length > 256_000)
|
|
74
|
+
throw new Error("Provider event exceeded the allowed size");
|
|
75
|
+
if (queuedBytes + line.length + 1 > MAX_QUEUED_BYTES || lines.length >= MAX_QUEUED_LINES)
|
|
76
|
+
throw new Error("Provider event buffer exceeded the allowed size");
|
|
77
|
+
lines.push({ text: line.toString('utf8').replace(/\r$/, ''), bytes: line.length + 1 });
|
|
78
|
+
queuedBytes += line.length + 1;
|
|
79
|
+
notify();
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
for await (const chunk of child.stdout) {
|
|
83
|
+
pending = Buffer.concat([pending, Buffer.from(chunk)]);
|
|
84
|
+
let end;
|
|
85
|
+
while ((end = pending.indexOf(10)) !== -1) {
|
|
86
|
+
enqueue(pending.subarray(0, end));
|
|
87
|
+
pending = pending.subarray(end + 1);
|
|
88
|
+
}
|
|
89
|
+
if (pending.length > 256_000)
|
|
90
|
+
throw new Error("Provider event exceeded the allowed size");
|
|
91
|
+
}
|
|
92
|
+
if (pending.length)
|
|
93
|
+
enqueue(pending);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
failure ??= error instanceof Error && ['Provider event exceeded the allowed size', 'Provider event buffer exceeded the allowed size'].includes(error.message) ? error : new Error('Provider event stream failed');
|
|
97
|
+
try {
|
|
98
|
+
kill();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
failure = new Error('Provider process group cleanup could not be confirmed');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
finished = true;
|
|
106
|
+
notify();
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
43
109
|
return {
|
|
44
110
|
stdin: { write: (text) => { child.stdin.write(text); }, end: () => { child.stdin.end(); } },
|
|
45
111
|
stdout: { async *[Symbol.asyncIterator]() {
|
|
46
|
-
|
|
112
|
+
if (consumed)
|
|
113
|
+
throw new Error('Provider event stream already consumed');
|
|
114
|
+
consumed = true;
|
|
47
115
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
yield line;
|
|
116
|
+
while (true) {
|
|
117
|
+
if (failure)
|
|
118
|
+
throw failure;
|
|
119
|
+
const line = lines.shift();
|
|
120
|
+
if (line) {
|
|
121
|
+
queuedBytes -= line.bytes;
|
|
122
|
+
yield line.text;
|
|
123
|
+
continue;
|
|
57
124
|
}
|
|
58
|
-
if (
|
|
59
|
-
|
|
125
|
+
if (finished)
|
|
126
|
+
break;
|
|
127
|
+
await new Promise(resolve => { wake = resolve; });
|
|
60
128
|
}
|
|
61
|
-
if (pending.length)
|
|
62
|
-
yield pending.toString("utf8");
|
|
63
|
-
if (failure)
|
|
64
|
-
throw failure;
|
|
65
129
|
}
|
|
66
130
|
finally {
|
|
131
|
+
lines.length = 0;
|
|
132
|
+
queuedBytes = 0;
|
|
67
133
|
kill();
|
|
68
134
|
}
|
|
69
135
|
} },
|
|
70
136
|
kill: (signal) => kill((signal ?? "SIGKILL")),
|
|
71
|
-
once: (_event, callback) => {
|
|
137
|
+
once: (_event, callback) => { if (termination) {
|
|
138
|
+
const ended = termination;
|
|
139
|
+
queueMicrotask(() => callback(ended.code, ended.signal));
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
exitCallbacks.push(callback); },
|
|
72
143
|
};
|
|
73
144
|
}
|
|
74
145
|
}
|