mtech-ai-local-runner 0.1.12 → 0.1.13
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
|
@@ -125,6 +125,20 @@ or cost termination limits on an approved unbounded employee-local Claude run.
|
|
|
125
125
|
Existing generation fencing, scope restrictions, cancellation and audit remain
|
|
126
126
|
in force.
|
|
127
127
|
|
|
128
|
+
### MCP startup failure diagnostics (0.1.13)
|
|
129
|
+
|
|
130
|
+
Codex startup now distinguishes an owned process exiting before admission,
|
|
131
|
+
preparation timeout, explicit abort and rejected MCP discovery. A fixed `detail`
|
|
132
|
+
reports whether initialize, initialized notification or tool inventory is still
|
|
133
|
+
missing (or discovery completed). The first issued cause survives cleanup and
|
|
134
|
+
outer catches and reaches both the failed event and terminal Job result.
|
|
135
|
+
Raw provider stderr remains excluded. These diagnostics do not establish the
|
|
136
|
+
historical failure cause or prove whether provider inference occurred.
|
|
137
|
+
|
|
138
|
+
The preparation deadline and mandatory authenticated MCP checks are unchanged.
|
|
139
|
+
There is no automatic model retry or task duration/count cap. Installing this
|
|
140
|
+
patch does not authorize another actual Codex attempt or change login/settings.
|
|
141
|
+
|
|
128
142
|
### Post-binding execution diagnostics (0.1.12)
|
|
129
143
|
|
|
130
144
|
The manual command and daemon log an `execution_failed` diagnostic (the manual
|
|
@@ -63,13 +63,16 @@ export class CodexExecLaunchHost {
|
|
|
63
63
|
this.ownsAttempt = true;
|
|
64
64
|
this.used = true;
|
|
65
65
|
let stage = 'codex_context';
|
|
66
|
+
let interruption;
|
|
67
|
+
const mcpDetail = () => stage === 'codex_mcp' ? this.deps.execTools?.discoveryDetail() : undefined;
|
|
68
|
+
const interrupt = (code) => { interruption ??= executionError(fail(), stage, code, mcpDetail()); };
|
|
66
69
|
let timer, external;
|
|
67
70
|
try {
|
|
68
71
|
this.assertContext(ctx, jobId);
|
|
69
72
|
requireOfficialExecPlan(this.plan);
|
|
70
73
|
if (typeof prompt !== 'string' || !prompt || prompt.length > 1_000_000 || signal?.aborted)
|
|
71
74
|
throw fail();
|
|
72
|
-
const stopped = new Promise((_, reject) => { external = () => { void this.cancel().catch(() => { }); reject(
|
|
75
|
+
const stopped = new Promise((_, reject) => { external = () => { interrupt('CODEX_EXEC_ABORTED'); void this.cancel().catch(() => { }); reject(interruption); }; this.abort.signal.addEventListener('abort', () => reject(interruption ?? fail()), { once: true }); signal?.addEventListener('abort', external, { once: true }); timer = setTimeout(() => { interrupt('CODEX_EXEC_PREPARATION_TIMEOUT'); external(); }, 30_000); });
|
|
73
76
|
const preparation = (async () => {
|
|
74
77
|
const supported = isReleaseExecMode(this.plan.mode);
|
|
75
78
|
stage = 'codex_inspection';
|
|
@@ -95,6 +98,8 @@ export class CodexExecLaunchHost {
|
|
|
95
98
|
throw fail();
|
|
96
99
|
}
|
|
97
100
|
h.once('exit', (code, signal) => {
|
|
101
|
+
if (!this.stopped && !this.receiptValue)
|
|
102
|
+
interrupt('CODEX_EXEC_PROCESS_EXITED');
|
|
98
103
|
// Preserve audit only for a clean owned exit that was still authorized.
|
|
99
104
|
// This immutable record never restores tool or spawn authority.
|
|
100
105
|
if (code === 0 && signal === null && !this.stopped && this.receiptValue) {
|
|
@@ -153,7 +158,7 @@ export class CodexExecLaunchHost {
|
|
|
153
158
|
return await Promise.race([preparation, stopped]);
|
|
154
159
|
}
|
|
155
160
|
catch (error) {
|
|
156
|
-
const safe = executionError(error, stage, 'CODEX_EXEC_PREFLIGHT_REJECTED');
|
|
161
|
+
const safe = executionError(interruption ?? error, stage, 'CODEX_EXEC_PREFLIGHT_REJECTED', mcpDetail());
|
|
157
162
|
await this.cancel().catch(() => { });
|
|
158
163
|
throw safe;
|
|
159
164
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isReleaseExecMode, requireOfficialExecPlan } from './exec-invocation.js';
|
|
2
2
|
import { localDiscoveryScopeMatches, localToolInventory } from '../../safety/local-tool-server.js';
|
|
3
3
|
import { securityRequirementsFingerprint as hash } from '../../safety/security-requirements.js';
|
|
4
|
+
import { executionError } from '../../safety/execution-failure.js';
|
|
4
5
|
const denied = () => Error('EXEC_TOOL_GATE_REJECTED');
|
|
5
6
|
/** Host-owned capability, never populated from stdout/task data. Authenticated
|
|
6
7
|
* discovery is server-side evidence; it does not attest OS client identity. */
|
|
@@ -29,6 +30,9 @@ export class ExecToolGate {
|
|
|
29
30
|
catch {
|
|
30
31
|
return false;
|
|
31
32
|
} }
|
|
33
|
+
/** Metadata only: survives revocation; contains no endpoint or credential. */
|
|
34
|
+
discoveryDetail() { return ['mcp_initialize_missing', 'mcp_initialized_missing', 'mcp_inventory_missing', 'mcp_discovery_complete'][this.stage]; }
|
|
35
|
+
readinessError() { return executionError(denied(), 'codex_mcp', 'CODEX_EXEC_MCP_REJECTED', this.discoveryDetail()); }
|
|
32
36
|
promptSubmitted(process) { if (this.process !== process || this.submitted || !this.active())
|
|
33
37
|
throw denied(); this.submitted = true; }
|
|
34
38
|
discovery = (event, scope) => {
|
|
@@ -60,17 +64,17 @@ export class ExecToolGate {
|
|
|
60
64
|
};
|
|
61
65
|
async wait(process, signal) {
|
|
62
66
|
if (this.process !== process || !this.submitted || !this.active() || signal.aborted)
|
|
63
|
-
throw
|
|
67
|
+
throw this.readinessError();
|
|
64
68
|
if (this.stage !== 3)
|
|
65
69
|
await new Promise((resolve, reject) => {
|
|
66
|
-
const abort = () => { clear(); reject(
|
|
70
|
+
const abort = () => { clear(); reject(this.readinessError()); };
|
|
67
71
|
const clear = () => { signal.removeEventListener('abort', abort); this.settle = undefined; this.reject = undefined; };
|
|
68
72
|
this.reject = abort;
|
|
69
73
|
this.settle = () => { clear(); resolve(); };
|
|
70
74
|
signal.addEventListener('abort', abort, { once: true });
|
|
71
75
|
});
|
|
72
76
|
if (!this.active() || signal.aborted || this.stage !== 3)
|
|
73
|
-
throw
|
|
77
|
+
throw this.readinessError();
|
|
74
78
|
return { process, executionNonce: this.plan.executionNonce, endpoint: this.plan.endpoint, authenticated: true, ready: true, toolSchemaFingerprint: this.plan.identity.toolSchemaFingerprint, tools: localToolInventory().map(t => t.name), isCurrent: () => this.active() && this.stage === 3 };
|
|
75
79
|
}
|
|
76
80
|
authorize(process) { if (process !== this.process || !this.active() || this.stage !== 3)
|
|
@@ -24,8 +24,12 @@ export function executionFailure(error, stage, fallback = 'EXECUTION_FAILED') {
|
|
|
24
24
|
}
|
|
25
25
|
return { stage, code: fallback };
|
|
26
26
|
}
|
|
27
|
-
export function executionError(error, stage, fallback) {
|
|
27
|
+
export function executionError(error, stage, fallback, detail) {
|
|
28
28
|
const diagnostic = executionFailure(error, stage, fallback);
|
|
29
|
+
// Only trusted callers supply this fixed vocabulary. Serialized provider
|
|
30
|
+
// errors, messages and stderr never become diagnostic authority.
|
|
31
|
+
if (!issued.has(error) && ['mcp_initialize_missing', 'mcp_initialized_missing', 'mcp_inventory_missing', 'mcp_discovery_complete'].includes(detail))
|
|
32
|
+
diagnostic.detail = detail;
|
|
29
33
|
const safe = new Error(fallback);
|
|
30
34
|
issued.set(safe, Object.freeze(diagnostic));
|
|
31
35
|
return safe;
|