mtech-ai-local-runner 0.1.16 → 0.1.17
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
CHANGED
|
@@ -10,6 +10,22 @@ 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.17: owned startup, pipe and transport observations
|
|
14
|
+
|
|
15
|
+
An owned-process collector marker distinguishes available diagnostics from a
|
|
16
|
+
missing collector. Fixed observations separate spawn, stdin write/end requests,
|
|
17
|
+
OS pipe finish, stdout/stderr activity/EOF, selected JSON event type names, exit,
|
|
18
|
+
and MCP TCP connection from authenticated HTTP discovery. No event payload is
|
|
19
|
+
copied into diagnostics. Pipe finish does **not** prove the CLI consumed stdin;
|
|
20
|
+
TCP activity does **not** prove the connection belongs to Codex or authorize tools.
|
|
21
|
+
Provider event type observations are not attestations of policy or model use.
|
|
22
|
+
The legacy `Reading prompt from stdin...` marker is not emitted for forced
|
|
23
|
+
`exec -` input in the inspected release source; its absence cannot locate a wait.
|
|
24
|
+
Failure snapshots are captured before owned cleanup. Existing preparation limits,
|
|
25
|
+
actual employee task limits, credentials, permissions and tool gates are unchanged.
|
|
26
|
+
This is local diagnostic coverage, not proof of the previous account failure's
|
|
27
|
+
cause or a successful real-account run. No automatic stack sampler is enabled.
|
|
28
|
+
|
|
13
29
|
## 0.1.16: bounded startup and MCP failure observations
|
|
14
30
|
|
|
15
31
|
Failed Codex preparation now preserves fixed native error markers and separates
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const startup = new WeakMap();
|
|
2
|
-
/** Fixed text observations from this owned host only. Never raw stderr or authority. */
|
|
2
|
+
/** Fixed lifecycle/text observations from this owned host only. Never raw stderr or authority. */
|
|
3
3
|
export function processStartupObservations(handle) { return [...(startup.get(handle) ?? [])]; }
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
/** Each invocation owns one new process group; no name-based process sweeping. */
|
|
@@ -8,6 +8,9 @@ export class NodeProcessGroupHost {
|
|
|
8
8
|
if (process.platform !== "darwin")
|
|
9
9
|
throw new Error("Codex process isolation requires macOS");
|
|
10
10
|
const child = spawn(command, args, { ...options, shell: false, detached: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
11
|
+
const observations = new Set(['host_observer_v2']);
|
|
12
|
+
child.once('spawn', () => observations.add('host_process_spawned'));
|
|
13
|
+
child.stdin.once('finish', () => observations.add('host_stdin_finished'));
|
|
11
14
|
let failure;
|
|
12
15
|
let owned = true;
|
|
13
16
|
let finished = false, consumed = false, queuedBytes = 0, wake;
|
|
@@ -44,8 +47,9 @@ export class NodeProcessGroupHost {
|
|
|
44
47
|
if (signal === "SIGKILL")
|
|
45
48
|
owned = false;
|
|
46
49
|
};
|
|
47
|
-
child.on("error", () => { failure ??= new Error("Provider process could not start"); terminated(null, null); notify(); });
|
|
50
|
+
child.on("error", () => { observations.add('host_process_spawn_failed'); failure ??= new Error("Provider process could not start"); terminated(null, null); notify(); });
|
|
48
51
|
child.stdin.on("error", () => {
|
|
52
|
+
observations.add('host_stdin_failed');
|
|
49
53
|
failure ??= new Error("Provider process input closed");
|
|
50
54
|
try {
|
|
51
55
|
kill();
|
|
@@ -58,8 +62,9 @@ export class NodeProcessGroupHost {
|
|
|
58
62
|
});
|
|
59
63
|
// Keep fixed observations and fixed marker prefixes only: no raw stderr,
|
|
60
64
|
// account text, paths or URLs survive a data callback, even for split chunks.
|
|
61
|
-
const observations = new Set();
|
|
62
65
|
const markers = [
|
|
66
|
+
// Legacy wording appears only with implicit piped stdin, NOT forced `exec -`.
|
|
67
|
+
// Its absence cannot locate startup; pipe finish does not prove native consumption.
|
|
63
68
|
{ name: 'native_stdin_read', text: 'Reading prompt from stdin...', prefix: '' },
|
|
64
69
|
{ name: 'native_model_catalog_failed', text: 'codex_models_manager::manager: failed to refresh available models:', prefix: '' },
|
|
65
70
|
{ name: 'native_cloud_policy_failed', text: 'Failed to fetch cloud config bundle', prefix: '' },
|
|
@@ -71,6 +76,7 @@ export class NodeProcessGroupHost {
|
|
|
71
76
|
{ name: 'native_auth_refresh_unsupported', text: 'chatgpt auth token refresh is not supported in exec mode', prefix: '' },
|
|
72
77
|
];
|
|
73
78
|
child.stderr.on('data', (chunk) => {
|
|
79
|
+
observations.add('host_stderr_data');
|
|
74
80
|
const data = chunk.toString('utf8');
|
|
75
81
|
for (const marker of markers) {
|
|
76
82
|
if (observations.has(marker.name))
|
|
@@ -90,9 +96,10 @@ export class NodeProcessGroupHost {
|
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
});
|
|
93
|
-
child.stderr.once('end', () => { for (const marker of markers)
|
|
99
|
+
child.stderr.once('end', () => { observations.add('host_stderr_ended'); for (const marker of markers)
|
|
94
100
|
marker.prefix = ''; });
|
|
95
101
|
child.once("exit", (code, signal) => {
|
|
102
|
+
observations.add('host_process_exited');
|
|
96
103
|
try {
|
|
97
104
|
kill();
|
|
98
105
|
}
|
|
@@ -111,12 +118,25 @@ export class NodeProcessGroupHost {
|
|
|
111
118
|
throw new Error("Provider event exceeded the allowed size");
|
|
112
119
|
if (queuedBytes + line.length + 1 > MAX_QUEUED_BYTES || lines.length >= MAX_QUEUED_LINES)
|
|
113
120
|
throw new Error("Provider event buffer exceeded the allowed size");
|
|
121
|
+
observations.add('host_stdout_line');
|
|
122
|
+
// Diagnostic type names only; no event payload, IDs, text or provider error.
|
|
123
|
+
try {
|
|
124
|
+
const event = JSON.parse(line.toString('utf8'));
|
|
125
|
+
const types = {
|
|
126
|
+
'thread.started': 'host_event_thread_started', 'turn.started': 'host_event_turn_started',
|
|
127
|
+
'turn.completed': 'host_event_turn_completed', 'turn.failed': 'host_event_turn_failed', 'error': 'host_event_error',
|
|
128
|
+
};
|
|
129
|
+
if (event && typeof event === 'object' && 'type' in event && typeof event.type === 'string' && Object.hasOwn(types, event.type))
|
|
130
|
+
observations.add(types[event.type]);
|
|
131
|
+
}
|
|
132
|
+
catch { /* Malformed or unrelated output has no diagnostic authority. */ }
|
|
114
133
|
lines.push({ text: line.toString('utf8').replace(/\r$/, ''), bytes: line.length + 1 });
|
|
115
134
|
queuedBytes += line.length + 1;
|
|
116
135
|
notify();
|
|
117
136
|
};
|
|
118
137
|
try {
|
|
119
138
|
for await (const chunk of child.stdout) {
|
|
139
|
+
observations.add('host_stdout_data');
|
|
120
140
|
pending = Buffer.concat([pending, Buffer.from(chunk)]);
|
|
121
141
|
let end;
|
|
122
142
|
while ((end = pending.indexOf(10)) !== -1) {
|
|
@@ -128,6 +148,7 @@ export class NodeProcessGroupHost {
|
|
|
128
148
|
}
|
|
129
149
|
if (pending.length)
|
|
130
150
|
enqueue(pending);
|
|
151
|
+
observations.add('host_stdout_ended');
|
|
131
152
|
}
|
|
132
153
|
catch (error) {
|
|
133
154
|
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');
|
|
@@ -144,7 +165,7 @@ export class NodeProcessGroupHost {
|
|
|
144
165
|
}
|
|
145
166
|
})();
|
|
146
167
|
const handle = {
|
|
147
|
-
stdin: { write: (text) => { child.stdin.write(text); }, end: () => { child.stdin.end(); } },
|
|
168
|
+
stdin: { write: (text) => { observations.add('host_stdin_write_requested'); child.stdin.write(text); }, end: () => { observations.add('host_stdin_end_requested'); child.stdin.end(); } },
|
|
148
169
|
stdout: { async *[Symbol.asyncIterator]() {
|
|
149
170
|
if (consumed)
|
|
150
171
|
throw new Error('Provider event stream already consumed');
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { GatewayToolError } from '../gateway-client.js';
|
|
2
2
|
/** Observations only: provider text cannot authorize a tool or certify a cause. */
|
|
3
|
-
export const STARTUP_OBSERVATIONS = Object.freeze([
|
|
3
|
+
export const STARTUP_OBSERVATIONS = Object.freeze([
|
|
4
|
+
'host_observer_v2', 'host_process_spawned', 'host_process_spawn_failed', 'host_process_exited',
|
|
5
|
+
'host_stdin_write_requested', 'host_stdin_end_requested', 'host_stdin_finished', 'host_stdin_failed',
|
|
6
|
+
'host_stdout_data', 'host_stdout_line', 'host_stdout_ended', 'host_stderr_data', 'host_stderr_ended',
|
|
7
|
+
'host_event_thread_started', 'host_event_turn_started', 'host_event_turn_completed', 'host_event_turn_failed', 'host_event_error',
|
|
8
|
+
'local_mcp_tcp_connected',
|
|
9
|
+
'native_stdin_read', 'native_model_catalog_failed', 'native_cloud_policy_failed', 'native_cloud_policy_timeout',
|
|
10
|
+
'native_required_mcp_failed', 'native_config_failed', 'native_auth_refresh_unsupported',
|
|
11
|
+
'local_mcp_request_received', 'local_mcp_auth_rejected', 'local_mcp_origin_or_host_rejected', 'local_mcp_initialize_received',
|
|
12
|
+
]);
|
|
4
13
|
const issued = new WeakMap();
|
|
5
14
|
export function executionFailure(error, stage, fallback = 'EXECUTION_FAILED') {
|
|
6
15
|
if (error && typeof error === 'object') {
|
|
@@ -189,6 +189,8 @@ export class LocalToolServer {
|
|
|
189
189
|
reply(400, { error: 'Invalid request' });
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
|
+
// Transport activity is not authenticated MCP discovery or tool authority.
|
|
193
|
+
this.server.on('connection', () => this.observations.add('local_mcp_tcp_connected'));
|
|
192
194
|
this.server.requestTimeout = 15000;
|
|
193
195
|
this.server.headersTimeout = 10000;
|
|
194
196
|
this.starting = new Promise((resolve, reject) => { this.server.once('error', reject); this.server.listen(0, '127.0.0.1', () => resolve()); });
|