mtech-ai-local-runner 0.1.64 → 0.1.65
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.
|
@@ -44,7 +44,8 @@ export function evaluateClaudeReview(input) {
|
|
|
44
44
|
// Source content may reach the provider only inside the allowed read's tool result.
|
|
45
45
|
const implicit = runs.reduce((n, r) => n + r.requests.filter(q => q.canaries.includes('source') && !q.toolResultIds.includes(IDS.allowed)).length, 0);
|
|
46
46
|
const slashFirst = slash ? first(slash) : undefined;
|
|
47
|
-
|
|
47
|
+
// Refused locally means no Messages request at all; any other request without the literal prompt is unexplained.
|
|
48
|
+
const delivery = !slash ? 'missing' : slashFirst?.firstUserText === CLAUDE_REVIEW_SLASH_PROMPT ? 'literal' : slash.terminal && messages(slash).length === 0 ? 'refused_locally' : 'missing';
|
|
48
49
|
const versionLine = new RegExp(`^${input.release.version.replace(/\./g, '\\.')}(?:\\s|$)`);
|
|
49
50
|
return [
|
|
50
51
|
scenario('inventory', { copy_sha256_matches: input.copySha256 === input.release.binary_sha256, copy_unchanged: input.copyUnchanged,
|
|
@@ -118,6 +119,7 @@ export async function runClaudeReviewCampaign(input) {
|
|
|
118
119
|
...(input.authObservation ? { authObservation: { sha256: input.authObservation.sha256, decision_id: input.authObservation.observation.decision_id } } : {}),
|
|
119
120
|
observations: { started_at: startedAt, finished_at: now().toISOString(), root: layout.root, runner_package_version: input.runnerVersion ?? packageVersion(),
|
|
120
121
|
runs: runs.map(r => ({ kind: r.kind, model: r.model, duration_ms: r.durationMs, auxiliary_requests: r.requests.filter(q => q.kind === 'auxiliary').length,
|
|
121
|
-
unknown_requests: r.requests.filter(q => q.kind === 'unknown' || q.kind === 'invalid').length, proxy_attempts: [...r.proxyAttempts], keychain_lookups: r.keychainOperations.length
|
|
122
|
+
unknown_requests: r.requests.filter(q => q.kind === 'unknown' || q.kind === 'invalid').length, proxy_attempts: [...r.proxyAttempts], keychain_lookups: r.keychainOperations.length,
|
|
123
|
+
requests: r.requests.filter(q => q.kind === 'primary' || q.kind === 'auxiliary').map(q => ({ kind: q.kind, step: q.step, model_matches: q.model === r.model, prompt_matched: q.promptMatched, reminders: q.reminders, tools: q.toolNames.length })) })) },
|
|
122
124
|
});
|
|
123
125
|
}
|
|
@@ -12,10 +12,19 @@ function textOf(content) {
|
|
|
12
12
|
const parts = content.filter((block) => record(block) && block.type === 'text' && typeof block.text === 'string').map(block => block.text);
|
|
13
13
|
return parts.length ? parts.join('') : null;
|
|
14
14
|
}
|
|
15
|
+
const REMINDER = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
|
|
16
|
+
/** The user's own text. The real CLI prepends context in system-reminder tags, in its own block or the same one. */
|
|
17
|
+
function userText(content) {
|
|
18
|
+
const joined = textOf(content);
|
|
19
|
+
if (joined === null)
|
|
20
|
+
return { text: null, reminders: 0 };
|
|
21
|
+
const reminders = joined.match(REMINDER)?.length ?? 0;
|
|
22
|
+
return { text: reminders ? joined.replace(REMINDER, '').trim() : joined, reminders };
|
|
23
|
+
}
|
|
15
24
|
const stopReason = (blocks) => blocks.some(block => block.type === 'tool_use') ? 'tool_use' : 'end_turn';
|
|
16
25
|
/** Messages streaming events for fixed blocks: message_start, start/delta/stop per block, message_delta, message_stop. */
|
|
17
|
-
export function claudeReviewSse(model, blocks) {
|
|
18
|
-
const events = [['message_start', { type: 'message_start', message: { id
|
|
26
|
+
export function claudeReviewSse(id, model, blocks) {
|
|
27
|
+
const events = [['message_start', { type: 'message_start', message: { id, type: 'message', role: 'assistant', model, content: [],
|
|
19
28
|
stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } } }]];
|
|
20
29
|
blocks.forEach((block, index) => {
|
|
21
30
|
events.push(['content_block_start', { type: 'content_block_start', index, content_block: block.type === 'text' ? { type: 'text', text: '' } : { type: 'tool_use', id: block.id, name: block.name, input: {} } }], ['content_block_delta', { type: 'content_block_delta', index, delta: block.type === 'text' ? { type: 'text_delta', text: block.text } : { type: 'input_json_delta', partial_json: JSON.stringify(block.input) } }], ['content_block_stop', { type: 'content_block_stop', index }]);
|
|
@@ -33,7 +42,7 @@ async function listen(server) {
|
|
|
33
42
|
export async function startClaudeReviewProvider(script) {
|
|
34
43
|
const requests = [], toolResults = new Map(), proxyAttempts = [];
|
|
35
44
|
const canaryHits = Object.fromEntries(Object.keys(script.canaries).map(name => [name, 0]));
|
|
36
|
-
let fakeKeyInBody = 0, origin = '';
|
|
45
|
+
let fakeKeyInBody = 0, origin = '', responses = 0;
|
|
37
46
|
const keep = (request) => { if (requests.length < MAX_REQUESTS)
|
|
38
47
|
requests.push(request); };
|
|
39
48
|
const attempt = (target) => { if (proxyAttempts.length < MAX_ATTEMPTS)
|
|
@@ -75,7 +84,7 @@ export async function startClaudeReviewProvider(script) {
|
|
|
75
84
|
if (raw.includes(script.fakeKey))
|
|
76
85
|
fakeKeyInBody++;
|
|
77
86
|
const apiKey = req.headers['x-api-key'];
|
|
78
|
-
const base = { step: null, model: null, toolNames: [], toolResultIds: [], apiKey: typeof apiKey === 'string' ? apiKey : null, authorization: req.headers.authorization !== undefined, firstUserText: null, canaries: hit };
|
|
87
|
+
const base = { step: null, model: null, toolNames: [], toolResultIds: [], apiKey: typeof apiKey === 'string' ? apiKey : null, authorization: req.headers.authorization !== undefined, firstUserText: null, canaries: hit, promptMatched: false, reminders: 0 };
|
|
79
88
|
if (url.pathname.startsWith('/planted-mcp/')) {
|
|
80
89
|
keep({ ...base, kind: 'planted_mcp' });
|
|
81
90
|
res.writeHead(404);
|
|
@@ -113,22 +122,23 @@ export async function startClaudeReviewProvider(script) {
|
|
|
113
122
|
if (!toolResults.has(block.tool_use_id))
|
|
114
123
|
toolResults.set(block.tool_use_id, { isError: block.is_error === true, text: typeof block.content === 'string' ? block.content : JSON.stringify(block.content ?? '') });
|
|
115
124
|
}
|
|
116
|
-
const firstUser = messages.find(message => message.role === 'user'), firstUserText = firstUser ?
|
|
125
|
+
const firstUser = messages.find(message => message.role === 'user'), { text: firstUserText, reminders } = firstUser ? userText(firstUser.content) : { text: null, reminders: 0 };
|
|
117
126
|
// The scripted turn: the launched model, the exact review prompt as the first user text and an offered tool list.
|
|
118
127
|
// Title and other auxiliary requests never consume a step; the step is the number of assistant turns so far.
|
|
119
|
-
const
|
|
128
|
+
const promptMatched = firstUserText === script.prompt, primary = model === script.model && promptMatched && toolNames.length > 0;
|
|
120
129
|
const step = primary ? messages.filter(message => message.role === 'assistant').length : null;
|
|
121
|
-
keep({ ...base, kind: primary ? 'primary' : 'auxiliary', step, model, toolNames, toolResultIds, firstUserText: firstUserText === null ? null : firstUserText.slice(0, 256) });
|
|
130
|
+
keep({ ...base, kind: primary ? 'primary' : 'auxiliary', step, model, toolNames, toolResultIds, firstUserText: firstUserText === null ? null : firstUserText.slice(0, 256), promptMatched, reminders });
|
|
122
131
|
const next = step === null ? undefined : script.steps[step];
|
|
123
132
|
const blocks = !primary ? [{ type: 'text', text: CLAUDE_REVIEW_AUXILIARY_TEXT }] : next ? [{ type: 'tool_use', ...next.tool }] : [{ type: 'text', text: CLAUDE_REVIEW_FINAL_TEXT }];
|
|
124
|
-
|
|
133
|
+
// Each response has its own id, as from the API: the CLI (2.1.308) treats assistant messages that share an id as one response.
|
|
134
|
+
const answering = model ?? script.model, id = `msg_review_${++responses}`;
|
|
125
135
|
if (body.stream === true) {
|
|
126
136
|
res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' });
|
|
127
|
-
res.end(claudeReviewSse(answering, blocks));
|
|
137
|
+
res.end(claudeReviewSse(id, answering, blocks));
|
|
128
138
|
return;
|
|
129
139
|
}
|
|
130
140
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
131
|
-
res.end(JSON.stringify({ id
|
|
141
|
+
res.end(JSON.stringify({ id, type: 'message', role: 'assistant', model: answering, content: blocks, stop_reason: stopReason(blocks), stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } }));
|
|
132
142
|
}
|
|
133
143
|
const port = await listen(server);
|
|
134
144
|
origin = `http://127.0.0.1:${port}`;
|