mtech-ai-local-runner 0.1.24 → 0.1.26

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,25 @@ 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.26: compiled private-file denial compatibility
14
+
15
+ The compiled `.env.*` blocked-path rule now denies that private-file family
16
+ without rejecting unrelated scoped reads. Private files at all depths,
17
+ read-only access and unsupported-pattern rejection remain enforced. Shared
18
+ Claude/Codex policy and actual MCP fake-data regressions use the full compiled
19
+ path combination. Foreground installation does not rerun Codex or update the
20
+ daemon; actual document acceptance still requires a separately approved run.
21
+
22
+ ## 0.1.25: scoped reads and blocked diagnostics
23
+
24
+ Codex receives explicit file-scoped ai_local read/search guidance; launch and
25
+ Gateway reporting remain host duties. Valid blocked reports now retain bounded
26
+ report counts and identity comparisons in events and terminal results. They
27
+ remain blocked, and multiple reports still fail closed. Fake-data integration
28
+ checks generated settings with the actual scoped server; native model-visible
29
+ tool exposure still needs a separately approved actual attempt. Foreground
30
+ installation does not run Codex or update the daemon.
31
+
13
32
  ## 0.1.24: bounded report identity diagnostics
14
33
 
15
34
  Failed report validation records pair counts for same/different wire item IDs
@@ -1,6 +1,6 @@
1
1
  import { compilePrompt as compileBrief } from "../prompt-compiler.js";
2
2
  export function compileCodexPrompt(taskPackage, label) {
3
- return `${compileBrief(taskPackage, { workItemLabel: label, progressReporting: "native_messages" })}\n\nUse only the ai_local job tools for repository reads, edits and approved tests. Native filesystem, commands, web, images and delegation are unavailable or denied. A denied tool is a scope boundary, never a reason to try another path. If required work cannot proceed, return the structured blocked report.`;
3
+ return `${compileBrief(taskPackage, { workItemLabel: label, progressReporting: "native_messages" })}\n\nUse only the ai_local job tools for repository reads, edits and approved tests. For reading, use server ai_local's local_read({path: "docs/example.md"}) or local_search({path: "docs/example.md", query: "text"}) with checkout-relative paths. In a read-only task, use only read/search tools; the Runner enforces the approved paths and operations. A denied native cwd is intentional: it is a private control directory, not the source checkout. Native filesystem, commands, web, images and delegation are unavailable or denied. A denied tool is a scope boundary, never a reason to try another path. Do not repeat host orchestration: claim, shadow, fingerprints, binding, administrator checks, launches, Gateway usage and Job reporting belong to the Runner/host, even when described in the objective. Perform only the assigned repository work and report evidence you actually observed. Do not claim that no model call occurred. If the required tool is unavailable, emit one final structured blocked report naming the missing tool and unfinished repository work; do not request or attempt a new launch.`;
4
4
  }
5
5
  export const CODEX_REPORT_SCHEMA = {
6
6
  type: "object", additionalProperties: false, required: ["status", "report", "evidence", "pending_work"],
@@ -75,6 +75,11 @@ export class CodexStreamParser {
75
75
  : this.candidates > 1 ? this.reportFailure("CODEX_COMPLETION_REPORT_MULTIPLE")
76
76
  : this.report && ["completed", "blocked"].includes(this.report.eventType) ? this.report
77
77
  : this.reportFailure("CODEX_COMPLETION_REPORT_NOT_FINAL");
78
+ if (terminal.eventType === "blocked")
79
+ terminal.payload = { ...terminal.payload, completion_report: {
80
+ version: "codex-completion-report-v1", code: "CODEX_COMPLETION_REPORT_BLOCKED", agent_message_count: this.messages,
81
+ valid_report_count: this.candidates, invalid_message_count: this.messages - this.candidates, identity: this.reportIdentity(),
82
+ } };
78
83
  const usage = record(event.usage);
79
84
  const observed = {};
80
85
  for (const field of ["input_tokens", "cached_input_tokens", "output_tokens"]) {
@@ -3,6 +3,7 @@ const MAX_EVIDENCE_ITEMS = 20;
3
3
  const MAX_EVIDENCE_CHARS = 500;
4
4
  const MAX_REPORT_ENVELOPE_CHARS = 16_000;
5
5
  const REPORT_FAILURE_CODES = ["CODEX_COMPLETION_REPORT_MISSING", "CODEX_COMPLETION_REPORT_INVALID", "CODEX_COMPLETION_REPORT_MULTIPLE", "CODEX_COMPLETION_REPORT_NOT_FINAL", "CODEX_TURN_INCOMPLETE"];
6
+ const REPORT_DIAGNOSTIC_CODES = [...REPORT_FAILURE_CODES, "CODEX_COMPLETION_REPORT_BLOCKED"];
6
7
  /** Pair counts only; no item IDs, text, stable digests or arbitrary keys. */
7
8
  function reportIdentityDiagnostic(value, valid) {
8
9
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -27,14 +28,15 @@ export function completionReportDiagnostic(value) {
27
28
  return undefined;
28
29
  const d = value, code = d.code;
29
30
  const messages = d.agent_message_count, valid = d.valid_report_count, invalid = d.invalid_message_count;
30
- if (d.version !== "codex-completion-report-v1" || typeof code !== "string" || !REPORT_FAILURE_CODES.includes(code)
31
+ if (d.version !== "codex-completion-report-v1" || typeof code !== "string" || !REPORT_DIAGNOSTIC_CODES.includes(code)
31
32
  || typeof messages !== "number" || !Number.isSafeInteger(messages) || messages < 0
32
33
  || typeof valid !== "number" || !Number.isSafeInteger(valid) || valid < 0 || valid > messages
33
34
  || typeof invalid !== "number" || !Number.isSafeInteger(invalid) || invalid !== messages - valid
34
35
  || code === "CODEX_COMPLETION_REPORT_MISSING" && messages !== 0
35
36
  || code === "CODEX_COMPLETION_REPORT_INVALID" && (messages === 0 || valid !== 0)
36
37
  || code === "CODEX_COMPLETION_REPORT_MULTIPLE" && valid < 2
37
- || code === "CODEX_COMPLETION_REPORT_NOT_FINAL" && (valid !== 1 || invalid === 0))
38
+ || code === "CODEX_COMPLETION_REPORT_NOT_FINAL" && (valid !== 1 || invalid === 0)
39
+ || code === "CODEX_COMPLETION_REPORT_BLOCKED" && valid !== 1)
38
40
  return undefined;
39
41
  const identity = reportIdentityDiagnostic(d.identity, valid);
40
42
  return { version: "codex-completion-report-v1", code, agent_message_count: messages, valid_report_count: valid, invalid_message_count: invalid,
@@ -269,7 +269,9 @@ export class ExecutionCoordinator {
269
269
  clearInterval(heartbeatTimer);
270
270
  }
271
271
  const status = terminal.eventType === "completed" ? "completed" : terminal.eventType === "cancelled" ? "cancelled" : terminal.eventType === "blocked" || terminal.eventType === "scope_conflict" ? "blocked" : "failed";
272
- const reportDiagnostic = status === "failed" ? completionReportDiagnostic(terminal.payload?.completion_report) : undefined;
272
+ const diagnostic = status === "failed" || terminal.eventType === "blocked" ? completionReportDiagnostic(terminal.payload?.completion_report) : undefined;
273
+ const reportDiagnostic = diagnostic && (terminal.eventType === "blocked"
274
+ ? diagnostic.code === "CODEX_COMPLETION_REPORT_BLOCKED" : diagnostic.code !== "CODEX_COMPLETION_REPORT_BLOCKED") ? diagnostic : undefined;
273
275
  await this.deps.gateway.complete({ jobId: job.id, generation: job.generation, leaseToken: job.leaseToken, status, result: { binding_id: binding.bindingId, event_type: terminal.eventType, summary: terminal.summary, ...(failureDiagnostic ? { execution_failure: { ...failureDiagnostic } } : {}), ...(reportDiagnostic ? { completion_report: reportDiagnostic } : {}), ...(adapter.securityReceipt?.() ? { security_receipt: adapter.securityReceipt() } : {}) } });
274
276
  await this.deps.state.clear();
275
277
  }
@@ -15,6 +15,13 @@ function scopePath(root, scope) {
15
15
  throw new Error('DENIED_UNSUPPORTED_SCOPE');
16
16
  return path.resolve(root, directory);
17
17
  }
18
+ function blockedScopeContains(root, scope, file) {
19
+ // The compiled repository policy includes this private-file family. Handle
20
+ // it only as a denial, never as a wildcard grant or an ignored block.
21
+ if (scope === '.env.*')
22
+ return file.startsWith(path.join(root, '.env.'));
23
+ return contains(scopePath(root, scope), file);
24
+ }
18
25
  export class LocalToolPolicy {
19
26
  options;
20
27
  abort = new AbortController();
@@ -51,7 +58,7 @@ export class LocalToolPolicy {
51
58
  if (typeof value !== 'string' || !value || value.includes('\0') || path.isAbsolute(value) || value.split(/[\\/]/).some(p => p === '..' || sensitive(p)))
52
59
  throw new Error('DENIED_PATH');
53
60
  const root = path.resolve(this.options.worktreeRoot), file = path.resolve(root, value);
54
- if (!contains(root, file) || !this.options.allowedPaths.some(scope => contains(scopePath(root, scope), file)) || this.options.blockedPaths.some(scope => contains(scopePath(root, scope), file)))
61
+ if (!contains(root, file) || !this.options.allowedPaths.some(scope => contains(scopePath(root, scope), file)) || this.options.blockedPaths.some(scope => blockedScopeContains(root, scope, file)))
55
62
  throw new Error('DENIED_SCOPE');
56
63
  if (await realpath(root) !== root)
57
64
  throw new Error('DENIED_ROOT');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtech-ai-local-runner",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "bin": {