mtech-ai-local-runner 0.1.22 → 0.1.24

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,31 @@ 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.24: bounded report identity diagnostics
14
+
15
+ Failed report validation records pair counts for same/different wire item IDs
16
+ and exact text, using at most 64 transient keyed samples. Raw IDs, text, keys
17
+ and hashes are never included in these diagnostics. Sampling does not limit
18
+ actual task duration or messages. Multiple reports still fail; none are chosen,
19
+ merged or deduplicated.
20
+
21
+ These observations distinguish wire-output patterns, not model-generation
22
+ identity: the inspected exec serializer assigns fresh CLI IDs. Earlier failures
23
+ cannot be reclassified without missing identity evidence. Publication and
24
+ foreground installation do not rerun Codex or update the daemon.
25
+
26
+ ## 0.1.23: separate progress from the final report
27
+
28
+ Codex receives native-message instructions: optional progress is plain text and
29
+ the structured completed/blocked report is emitted once at the end. The Runner
30
+ derives lifecycle events; Codex is no longer asked to author normalized progress
31
+ or checkpoint events. Claude's existing instructions are unchanged.
32
+
33
+ Strict report uniqueness and final-message validation remain in force. Fake
34
+ tests reproduce the observed multiple-report failure, but do not prove why the
35
+ historical model emitted three reports or guarantee future model compliance.
36
+ Publication and foreground installation do not rerun Codex or update the daemon.
37
+
13
38
  ## 0.1.22: completion report classification and terminal diagnostics
14
39
 
15
40
  Report validation distinguishes missing messages, zero valid reports, multiple
@@ -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 })}\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. 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.`;
4
4
  }
5
5
  export const CODEX_REPORT_SCHEMA = {
6
6
  type: "object", additionalProperties: false, required: ["status", "report", "evidence", "pending_work"],
@@ -1,3 +1,4 @@
1
+ import { createHmac, randomBytes } from "node:crypto";
1
2
  import { completionEnvelope, completionEvent } from "../completion-report.js";
2
3
  function record(value) {
3
4
  return value !== null && typeof value === "object" && !Array.isArray(value)
@@ -22,6 +23,8 @@ export class CodexStreamParser {
22
23
  report;
23
24
  messages = 0;
24
25
  candidates = 0;
26
+ identityKey = randomBytes(32);
27
+ reportSamples = [];
25
28
  parse(line) {
26
29
  if (this.stopped)
27
30
  return [];
@@ -50,8 +53,17 @@ export class CodexStreamParser {
50
53
  if (event.type === "item.completed" && item.type === "agent_message") {
51
54
  this.messages += 1;
52
55
  this.report = finalReport(item.text);
53
- if (["completed", "blocked"].includes(this.report.eventType))
56
+ if (["completed", "blocked"].includes(this.report.eventType)) {
54
57
  this.candidates += 1;
58
+ // Bounded diagnostic sample only. Neither raw IDs nor text digests
59
+ // leave this parser, and sampling never changes report acceptance.
60
+ if (this.reportSamples.length < 64) {
61
+ const digest = (domain, value) => createHmac("sha256", this.identityKey).update(domain).update(value).digest("hex");
62
+ const id = typeof item.id === "string" && item.id.length > 0 && item.id.length <= 256
63
+ ? digest("id:", item.id) : undefined;
64
+ this.reportSamples.push({ id, text: digest("text:", String(item.text)) });
65
+ }
66
+ }
55
67
  }
56
68
  }
57
69
  if (event.type !== "turn.completed")
@@ -89,11 +101,41 @@ export class CodexStreamParser {
89
101
  return { eventType: "failed", summary: summaries[code], payload: { completion_report: {
90
102
  version: "codex-completion-report-v1", code, agent_message_count: this.messages,
91
103
  valid_report_count: this.candidates, invalid_message_count: this.messages - this.candidates,
104
+ identity: this.reportIdentity(),
92
105
  } } };
93
106
  }
107
+ reportIdentity() {
108
+ const counts = { same_item_same_text_pairs: 0, same_item_different_text_pairs: 0,
109
+ different_item_same_text_pairs: 0, different_item_different_text_pairs: 0 };
110
+ for (let i = 0; i < this.reportSamples.length; i++) {
111
+ const a = this.reportSamples[i];
112
+ if (!a.id)
113
+ continue;
114
+ for (let j = i + 1; j < this.reportSamples.length; j++) {
115
+ const b = this.reportSamples[j];
116
+ if (!b.id)
117
+ continue;
118
+ if (a.id === b.id) {
119
+ if (a.text === b.text)
120
+ counts.same_item_same_text_pairs++;
121
+ else
122
+ counts.same_item_different_text_pairs++;
123
+ }
124
+ else if (a.text === b.text)
125
+ counts.different_item_same_text_pairs++;
126
+ else
127
+ counts.different_item_different_text_pairs++;
128
+ }
129
+ }
130
+ return { version: "codex-report-identity-v1", sampled_reports: this.reportSamples.length,
131
+ unsampled_reports: this.candidates - this.reportSamples.length,
132
+ missing_item_id_reports: this.reportSamples.filter(sample => !sample.id).length, ...counts };
133
+ }
94
134
  finish(event) {
95
135
  this.stopped = true;
96
136
  this.report = undefined;
137
+ this.reportSamples.length = 0;
138
+ this.identityKey.fill(0);
97
139
  return [event];
98
140
  }
99
141
  }
@@ -3,6 +3,24 @@ 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
+ /** Pair counts only; no item IDs, text, stable digests or arbitrary keys. */
7
+ function reportIdentityDiagnostic(value, valid) {
8
+ if (!value || typeof value !== "object" || Array.isArray(value))
9
+ return undefined;
10
+ const d = value;
11
+ const keys = ["sampled_reports", "unsampled_reports", "missing_item_id_reports", "same_item_same_text_pairs",
12
+ "same_item_different_text_pairs", "different_item_same_text_pairs", "different_item_different_text_pairs"];
13
+ if (d.version !== "codex-report-identity-v1" || keys.some(key => typeof d[key] !== "number" || !Number.isSafeInteger(d[key]) || Number(d[key]) < 0))
14
+ return undefined;
15
+ const sampled = Number(d.sampled_reports), missing = Number(d.missing_item_id_reports);
16
+ if (sampled !== Math.min(valid, 64) || Number(d.unsampled_reports) !== valid - sampled || missing > sampled)
17
+ return undefined;
18
+ const pairs = keys.slice(3).map(key => Number(d[key]));
19
+ const identified = sampled - missing, expected = identified * (identified - 1) / 2;
20
+ if (pairs.some(count => count > expected) || pairs.reduce((sum, count) => sum + count, 0) !== expected)
21
+ return undefined;
22
+ return { version: "codex-report-identity-v1", ...Object.fromEntries(keys.map(key => [key, d[key]])) };
23
+ }
6
24
  /** Diagnostic metadata only, never completion authority. Copy no provider text. */
7
25
  export function completionReportDiagnostic(value) {
8
26
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -18,7 +36,9 @@ export function completionReportDiagnostic(value) {
18
36
  || code === "CODEX_COMPLETION_REPORT_MULTIPLE" && valid < 2
19
37
  || code === "CODEX_COMPLETION_REPORT_NOT_FINAL" && (valid !== 1 || invalid === 0))
20
38
  return undefined;
21
- return { version: "codex-completion-report-v1", code, agent_message_count: messages, valid_report_count: valid, invalid_message_count: invalid };
39
+ const identity = reportIdentityDiagnostic(d.identity, valid);
40
+ return { version: "codex-completion-report-v1", code, agent_message_count: messages, valid_report_count: valid, invalid_message_count: invalid,
41
+ ...(identity ? { identity } : {}) };
22
42
  }
23
43
  export const COMPLETION_REPORT_NOTICE = "Do the assigned work yourself using the available tools; do not delegate or start background work. " +
24
44
  "Do not report completed while waiting for results, tests, or another agent. " +
@@ -4,6 +4,11 @@ const STOP_BEFORE_PUBLICATION_RULE = "Stop before publication. Never run git com
4
4
  const EVENT_SCHEMA_NOTICE = "Report progress only through the normalized event lifecycle (started, progress, file_changed, question, " +
5
5
  "test_started, test_result, budget_warning, checkpoint_ready, blocked, completed, failed). " +
6
6
  "Checkpoint your work with checkpoint_ready before stopping whenever it is safe to do so.";
7
+ const NATIVE_PROGRESS_NOTICE = "The Runner derives lifecycle events from the native CLI stream; do not author normalized lifecycle or checkpoint events. " +
8
+ "If progress commentary is needed, use brief plain text, not the completion-report schema. " +
9
+ "Do not emit report-shaped JSON in commentary or progress messages. " +
10
+ "Emit the structured completion report exactly once, as your final response after all available work and checks. " +
11
+ "Do not repeat, preview, revise or re-emit a completed or blocked report. If blocked, stop work and emit that one final blocked report.";
7
12
  function requiredArray(value, label) {
8
13
  if (!Array.isArray(value)) {
9
14
  throw new Error(`task package is missing ${label}`);
@@ -53,7 +58,9 @@ export function compilePrompt(taskPackage, options) {
53
58
  `## Required tests\n${requiredTests.map((item) => `- ${item}`).join("\n")}`,
54
59
  `## Prohibited actions\n${prohibitedActions.map((item) => `- ${item}`).join("\n")}`,
55
60
  `## Stop-before-publication rule\n${STOP_BEFORE_PUBLICATION_RULE}`,
56
- `## Event and checkpoint obligations\n${EVENT_SCHEMA_NOTICE}`,
61
+ options.progressReporting === "native_messages"
62
+ ? `## Native progress and terminal reporting\n${NATIVE_PROGRESS_NOTICE}`
63
+ : `## Event and checkpoint obligations\n${EVENT_SCHEMA_NOTICE}`,
57
64
  `## Final completion report\n${COMPLETION_REPORT_NOTICE}`,
58
65
  ];
59
66
  return sections.join("\n\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mtech-ai-local-runner",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "bin": {