mtech-ai-local-runner 0.1.5 → 0.1.7
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 +12 -2
- package/dist/src/adapters/claude-code/adapter.js +19 -1
- package/dist/src/adapters/claude-code/completion-report.js +64 -0
- package/dist/src/adapters/claude-code/prompt-compiler.js +2 -0
- package/dist/src/cli.js +1 -1
- package/dist/src/daemon/runtime.js +1 -1
- 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.7
|
|
14
|
+
|
|
15
|
+
- Accept one fenced final JSON report after an explanatory preamble while preserving status, evidence, length and pending-work validation. Reject ambiguous multiple code blocks.
|
|
16
|
+
|
|
17
|
+
## 0.1.6
|
|
18
|
+
|
|
19
|
+
- Require a final JSON report with actual findings, nonempty evidence and no pending work before reporting completion. Incomplete or missing reports fail without a successful investigation artifact.
|
|
20
|
+
- Explicitly restrict available built-in tools and ignore child messages as executor completion evidence.
|
|
21
|
+
- Preserve employee-local duration/count policy and Gateway model selection. Structured assertions remain client-reported and require separate semantic review.
|
|
22
|
+
|
|
13
23
|
## 0.1.5
|
|
14
24
|
|
|
15
25
|
- Authenticate local MCP relay callers with a random per-job bearer capability.
|
|
@@ -29,6 +39,6 @@ The relay binds only to loopback. Local capabilities are stored in private mode-
|
|
|
29
39
|
Existing employees can upgrade their installed daemon with:
|
|
30
40
|
|
|
31
41
|
```bash
|
|
32
|
-
npx mtech-ai-local-runner@0.1.
|
|
33
|
-
npx mtech-ai-local-runner@0.1.
|
|
42
|
+
npx mtech-ai-local-runner@0.1.7 setup
|
|
43
|
+
npx mtech-ai-local-runner@0.1.7 doctor
|
|
34
44
|
```
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { isTerminalEventType } from "../../protocol.js";
|
|
4
4
|
import { parseClaudeLine } from "./stream-parser.js";
|
|
5
5
|
import { compilePrompt } from "./prompt-compiler.js";
|
|
6
|
+
import { completionEvent } from "./completion-report.js";
|
|
6
7
|
import { buildHookPolicy, encodeHookPolicy } from "../../safety/hook-policy-builder.js";
|
|
7
8
|
import { HOOK_POLICY_ENV_VAR } from "../../safety/hook-cli.js";
|
|
8
9
|
const ALLOWED_ENV_KEYS = ["PATH", "HOME", "USER", "LOGNAME", "LANG", "LC_ALL", "TMPDIR", "NODE_ENV"];
|
|
@@ -108,6 +109,7 @@ export class ClaudeCodeAdapter {
|
|
|
108
109
|
"--settings", JSON.stringify(settings),
|
|
109
110
|
"--setting-sources", "user",
|
|
110
111
|
"--disable-slash-commands",
|
|
112
|
+
"--tools", builtinToolSurface.join(","),
|
|
111
113
|
"--allowedTools", toolSurface.join(","),
|
|
112
114
|
];
|
|
113
115
|
if (ctx.resumeSessionId) {
|
|
@@ -136,9 +138,25 @@ export class ClaudeCodeAdapter {
|
|
|
136
138
|
});
|
|
137
139
|
let sawTerminal = false;
|
|
138
140
|
for await (const line of handle.stdout) {
|
|
139
|
-
|
|
141
|
+
let message;
|
|
142
|
+
try {
|
|
143
|
+
const value = JSON.parse(line);
|
|
144
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
145
|
+
continue;
|
|
146
|
+
message = value;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// A nested agent message is not evidence that the executor has finished.
|
|
152
|
+
if (message.parent_tool_use_id != null)
|
|
153
|
+
continue;
|
|
154
|
+
let parsed = parseClaudeLine(line, this.options.worktreeRoot);
|
|
140
155
|
if (!parsed)
|
|
141
156
|
continue;
|
|
157
|
+
if (parsed.eventType === "completed") {
|
|
158
|
+
parsed = completionEvent(message.result, parsed.payload?.cost_usd);
|
|
159
|
+
}
|
|
142
160
|
if (isTerminalEventType(parsed.eventType))
|
|
143
161
|
sawTerminal = true;
|
|
144
162
|
yield parsed;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { MAX_EVENT_SUMMARY_CHARS } from "../../protocol.js";
|
|
2
|
+
const MAX_EVIDENCE_ITEMS = 20;
|
|
3
|
+
const MAX_EVIDENCE_CHARS = 500;
|
|
4
|
+
const MAX_REPORT_ENVELOPE_CHARS = 16_000;
|
|
5
|
+
export const COMPLETION_REPORT_NOTICE = "Do the assigned work yourself using the available tools; do not delegate or start background work. " +
|
|
6
|
+
"Do not report completed while waiting for results, tests, or another agent. " +
|
|
7
|
+
"Your final response must be one JSON object, without Markdown fences: " +
|
|
8
|
+
'{"status":"completed","report":"actual findings and results","evidence":["file path and observed finding, or command and actual result"],"pending_work":[]}. ' +
|
|
9
|
+
`The report must be nonempty and at most ${MAX_EVENT_SUMMARY_CHARS} characters. ` +
|
|
10
|
+
`Include 1-${MAX_EVIDENCE_ITEMS} nonempty evidence strings, each at most ${MAX_EVIDENCE_CHARS} characters. ` +
|
|
11
|
+
"Address the assigned objective and each applicable acceptance criterion with observed evidence, not a plan or promise. " +
|
|
12
|
+
"Only attest to executor work you observed; Gateway records transport, completion, and review receipts separately. " +
|
|
13
|
+
'If the task is unfinished, use "status":"blocked" and list the remaining work in "pending_work". ' +
|
|
14
|
+
"An incomplete or missing report is a failed execution, never a successful investigation artifact.";
|
|
15
|
+
function boundedText(value, max) {
|
|
16
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
17
|
+
}
|
|
18
|
+
/** Validate the full final result before truncation; never publish malformed/interim output. */
|
|
19
|
+
export function completionEvent(result, costUsd) {
|
|
20
|
+
const usage = typeof costUsd === "number" && Number.isFinite(costUsd) && costUsd >= 0
|
|
21
|
+
? { cost_usd: costUsd }
|
|
22
|
+
: {};
|
|
23
|
+
const failed = {
|
|
24
|
+
eventType: "failed",
|
|
25
|
+
summary: "completion report is missing, incomplete or invalid",
|
|
26
|
+
payload: usage,
|
|
27
|
+
};
|
|
28
|
+
if (typeof result !== "string" || result.length > MAX_REPORT_ENVELOPE_CHARS)
|
|
29
|
+
return failed;
|
|
30
|
+
let report;
|
|
31
|
+
try {
|
|
32
|
+
report = JSON.parse(result);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Claude may wrap the final report in one Markdown fence after a short
|
|
36
|
+
// explanation. Accept framing only; retain every completion/evidence check.
|
|
37
|
+
if ((result.match(/```/g) ?? []).length !== 2)
|
|
38
|
+
return failed;
|
|
39
|
+
const framed = result.trim().match(/^(?:[\s\S]*?\n)?```(?:json)?[ \t]*\r?\n([\s\S]*?)\r?\n```$/);
|
|
40
|
+
if (!framed)
|
|
41
|
+
return failed;
|
|
42
|
+
try {
|
|
43
|
+
report = JSON.parse(framed[1]);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return failed;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!report || typeof report !== "object" || Array.isArray(report))
|
|
50
|
+
return failed;
|
|
51
|
+
const value = report;
|
|
52
|
+
if (Object.keys(value).some((key) => !["status", "report", "evidence", "pending_work"].includes(key))
|
|
53
|
+
|| value.status !== "completed"
|
|
54
|
+
|| !boundedText(value.report, MAX_EVENT_SUMMARY_CHARS)
|
|
55
|
+
|| !Array.isArray(value.pending_work) || value.pending_work.length !== 0
|
|
56
|
+
|| !Array.isArray(value.evidence) || value.evidence.length < 1 || value.evidence.length > MAX_EVIDENCE_ITEMS
|
|
57
|
+
|| !value.evidence.every((item) => boundedText(item, MAX_EVIDENCE_CHARS)))
|
|
58
|
+
return failed;
|
|
59
|
+
return {
|
|
60
|
+
eventType: "completed",
|
|
61
|
+
summary: value.report.trim(),
|
|
62
|
+
payload: { ...usage, investigation_summary: value.report.trim(), completion_evidence: value.evidence },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COMPLETION_REPORT_NOTICE } from "./completion-report.js";
|
|
1
2
|
const STOP_BEFORE_PUBLICATION_RULE = "Stop before publication. Never run git commit, git push, create a pull request, merge, " +
|
|
2
3
|
"deploy, access production, send an external message, or write data outside this worktree.";
|
|
3
4
|
const EVENT_SCHEMA_NOTICE = "Report progress only through the normalized event lifecycle (started, progress, file_changed, question, " +
|
|
@@ -53,6 +54,7 @@ export function compilePrompt(taskPackage, options) {
|
|
|
53
54
|
`## Prohibited actions\n${prohibitedActions.map((item) => `- ${item}`).join("\n")}`,
|
|
54
55
|
`## Stop-before-publication rule\n${STOP_BEFORE_PUBLICATION_RULE}`,
|
|
55
56
|
`## Event and checkpoint obligations\n${EVENT_SCHEMA_NOTICE}`,
|
|
57
|
+
`## Final completion report\n${COMPLETION_REPORT_NOTICE}`,
|
|
56
58
|
];
|
|
57
59
|
return sections.join("\n\n");
|
|
58
60
|
}
|
package/dist/src/cli.js
CHANGED
|
@@ -182,7 +182,7 @@ export async function main(argv, env, io = {}) {
|
|
|
182
182
|
const authenticated = await runFile(binary, ["auth", "status"], { encoding: "utf8" }).then(() => true).catch(() => false);
|
|
183
183
|
const registered = await gateway.registerLocalRunner({
|
|
184
184
|
deviceFingerprint: createHash("sha256").update(`${process.getuid?.() ?? 0}|${runnerDisplayName()}|${config.gatewayMcpUrl}`).digest("hex"),
|
|
185
|
-
displayName: runnerDisplayName(), packageVersion: "0.1.
|
|
185
|
+
displayName: runnerDisplayName(), packageVersion: "0.1.7",
|
|
186
186
|
capabilities: [{ adapter: "claude_code_cli", version: "1", capabilities: ["repository_read", "repository_edit", "shell_test", "git_diff"], authenticated }],
|
|
187
187
|
});
|
|
188
188
|
await localStore.save({ schemaVersion: 1, gatewayMcpUrl: config.gatewayMcpUrl, runnerId: registered.runner.id, workspaceRoot: config.workspaceRoot, concurrency: 1 });
|
|
@@ -72,7 +72,7 @@ export async function buildDaemon(input) {
|
|
|
72
72
|
const poller = new JobPoller({
|
|
73
73
|
heartbeat: () => gateway.heartbeatLocalRunner({
|
|
74
74
|
deviceFingerprint: createHash("sha256").update(`${process.getuid?.() ?? 0}|${runnerDisplayName()}|${input.gatewayMcpUrl}`).digest("hex"),
|
|
75
|
-
displayName: runnerDisplayName(), packageVersion: "0.1.
|
|
75
|
+
displayName: runnerDisplayName(), packageVersion: "0.1.7",
|
|
76
76
|
capabilities: probes.map((probe) => ({ adapter: probe.id, version: probe.version,
|
|
77
77
|
capabilities: probe.capabilities, authenticated: probe.available && probe.authenticated })),
|
|
78
78
|
}).then(() => undefined),
|