返回 CodeWhale
harness.mjs
根目录 / crates / tui / extension-host / dist / builtin / harness.mjs
1 // codewhale built-in host module harness — generated by crates/tui/extension-host/build.mjs; do not edit.
2
3 // src/dsh/upstream/hooks/hook-protocol/src/matcher.ts
4 function isMatchAll(matcher) {
5 return matcher === void 0 || matcher === "" || matcher === "*";
6 }
7 var CLAUDE_LITERAL = /^[A-Za-z0-9_|]+$/;
8 function compileRegex(pattern) {
9 try {
10 return new RegExp(pattern);
11 } catch (_syntaxError) {
12 return void 0;
13 }
14 }
15 function matchesMatcher(matcher, query, mode) {
16 if (isMatchAll(matcher)) return true;
17 const pattern = matcher;
18 if (mode === "claude-code" && CLAUDE_LITERAL.test(pattern)) {
19 return pattern.split("|").includes(query);
20 }
21 return compileRegex(pattern)?.test(query) ?? false;
22 }
23
24 // src/dsh/upstream/hooks/hook-protocol/src/codec.ts
25 var BLOCKING_EXIT_CODE = 2;
26 function str(obj2, key) {
27 const v = obj2[key];
28 return typeof v === "string" ? v : void 0;
29 }
30 function bool(obj2, key) {
31 const v = obj2[key];
32 return typeof v === "boolean" ? v : void 0;
33 }
34 function obj(value) {
35 return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
36 }
37 function topLevelDecisionOf(value) {
38 return value === "approve" || value === "block" ? value : void 0;
39 }
40 function permissionDecisionOf(value) {
41 return value === "allow" || value === "deny" || value === "ask" ? value : void 0;
42 }
43 function parseHookOutput(exitCode, stdout, stderr, expectedEventName) {
44 const trimmedErr = stderr.trim();
45 const trimmedOut = stdout.trim();
46 const output = { exitCode, stderr: trimmedErr, stdout: trimmedOut };
47 if (exitCode === BLOCKING_EXIT_CODE) {
48 output.decision = "block";
49 if (trimmedErr.length > 0) output.reason = trimmedErr;
50 }
51 if (exitCode === 0) {
52 if (trimmedOut.startsWith("{")) {
53 let parsed;
54 try {
55 parsed = obj(JSON.parse(trimmedOut));
56 } catch {
57 parsed = void 0;
58 }
59 if (parsed) applyStructured(output, parsed, expectedEventName);
60 }
61 }
62 return output;
63 }
64 function applyStructured(output, parsed, expectedEventName) {
65 const cont = bool(parsed, "continue");
66 if (cont !== void 0) output.continue = cont;
67 const stopReason = str(parsed, "stopReason");
68 if (stopReason !== void 0) output.stopReason = stopReason;
69 const sysMsg = str(parsed, "systemMessage");
70 if (sysMsg !== void 0) output.systemMessage = sysMsg;
71 const topDecision = topLevelDecisionOf(str(parsed, "decision"));
72 if (topDecision !== void 0) output.decision = topDecision;
73 const topReason = str(parsed, "reason");
74 if (topReason !== void 0) output.reason = topReason;
75 const hso = obj(parsed.hookSpecificOutput);
76 if (hso) {
77 const eventName = str(hso, "hookEventName");
78 if (eventName !== void 0) output.hookEventName = eventName;
79 if (expectedEventName !== void 0 && eventName !== expectedEventName) {
80 return;
81 }
82 const permission = permissionDecisionOf(str(hso, "permissionDecision"));
83 if (permission !== void 0) output.decision = permission;
84 const permissionReason = str(hso, "permissionDecisionReason");
85 if (permissionReason !== void 0) output.reason = permissionReason;
86 const addCtx = str(hso, "additionalContext");
87 if (addCtx !== void 0) output.additionalContext = addCtx;
88 const updated = obj(hso.updatedInput);
89 if (updated !== void 0) output.updatedInput = updated;
90 }
91 }
92
93 // src/dsh/upstream/hooks/hook-protocol/src/merge.ts
94 function rank(decision) {
95 switch (decision) {
96 case "deny":
97 case "block":
98 return 3;
99 case "ask":
100 return 2;
101 case "approve":
102 case "allow":
103 return 1;
104 default:
105 return 0;
106 }
107 }
108 function decisionForRank(maxRank) {
109 switch (maxRank) {
110 case 3:
111 return "deny";
112 case 2:
113 return "ask";
114 case 1:
115 return "allow";
116 default:
117 return "none";
118 }
119 }
120 function mergeHookOutputs(outputs) {
121 let maxRank = 0;
122 const reasonsByRank = /* @__PURE__ */ new Map();
123 let stop = false;
124 let stopReason;
125 const additionalContext = [];
126 const systemMessages = [];
127 for (const out of outputs) {
128 const r = rank(out.decision);
129 if (r > maxRank) maxRank = r;
130 if ((r === 3 || r === 2) && out.reason !== void 0 && out.reason.length > 0) {
131 const list2 = reasonsByRank.get(r) ?? [];
132 list2.push(out.reason);
133 reasonsByRank.set(r, list2);
134 }
135 if (out.continue === false && !stop) {
136 stop = true;
137 if (out.stopReason !== void 0) stopReason = out.stopReason;
138 }
139 if (out.additionalContext !== void 0 && out.additionalContext.length > 0) {
140 additionalContext.push(out.additionalContext);
141 }
142 if (out.systemMessage !== void 0 && out.systemMessage.length > 0) {
143 systemMessages.push(out.systemMessage);
144 }
145 }
146 const reasons = reasonsByRank.get(maxRank) ?? [];
147 return {
148 decision: decisionForRank(maxRank),
149 ...reasons.length > 0 ? { reason: reasons.join("\n\n") } : {},
150 stop,
151 ...stopReason !== void 0 ? { stopReason } : {},
152 additionalContext,
153 systemMessages
154 };
155 }
156
157 // src/json.ts
158 function isJson(value, depth = 0) {
159 if (depth > 64) return false;
160 if (value === null) return true;
161 switch (typeof value) {
162 case "boolean":
163 case "string":
164 return true;
165 case "number":
166 return Number.isFinite(value);
167 case "object": {
168 const array2 = Array.isArray(value);
169 if (!array2 && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
170 const keys = Reflect.ownKeys(value);
171 if (array2 && keys.length !== value.length + 1) return false;
172 for (const key of keys) {
173 if (array2 && key === "length") continue;
174 if (typeof key !== "string") return false;
175 if (array2 && (!/^(0|[1-9]\d*)$/u.test(key) || Number(key) >= value.length)) return false;
176 const descriptor = Object.getOwnPropertyDescriptor(value, key);
177 if (!descriptor.enumerable || !("value" in descriptor) || !isJson(descriptor.value, depth + 1)) return false;
178 }
179 return true;
180 }
181 default:
182 return false;
183 }
184 }
185
186 // src/builtin/shared/web-adapters.ts
187 var own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
188 function row(value) {
189 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid captured web record");
190 return value;
191 }
192 function text(value) {
193 if (typeof value !== "string") throw new Error("invalid captured web text");
194 return value;
195 }
196 function string(value) {
197 return typeof value === "string" ? value : void 0;
198 }
199 function array(value) {
200 return Array.isArray(value) ? value : [];
201 }
202 function count(value, max = 10) {
203 if (!Number.isSafeInteger(value) || value < 0 || value > max) throw new Error("invalid captured web count");
204 return value;
205 }
206 function rustTrim(value) {
207 return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, "");
208 }
209 function firstPresent(value, keys) {
210 for (const key of keys) if (own(value, key)) return value[key];
211 return void 0;
212 }
213 function firstText(value, keys) {
214 for (const key of keys) {
215 const found = string(value[key]);
216 if (found !== void 0) {
217 const trimmed = rustTrim(found);
218 if (trimmed) return trimmed;
219 }
220 }
221 return void 0;
222 }
223 function integer(facts, path) {
224 const value = facts[path];
225 if (value === void 0) return void 0;
226 const number2 = row(value);
227 if (number2.i64 === null || number2.i64 === void 0) return void 0;
228 const result3 = text(number2.i64);
229 if (!/^-?\d{1,19}$/.test(result3) || BigInt(result3) < -9223372036854775808n || BigInt(result3) > 9223372036854775807n) throw new Error("invalid captured web integer");
230 return result3;
231 }
232 function number(facts, path) {
233 const value = facts[path];
234 if (value === void 0) return 0;
235 const result3 = Number(text(row(value).score));
236 if (!Number.isFinite(result3)) throw new Error("invalid captured web score");
237 return result3;
238 }
239 function result(metadata) {
240 return { ok: true, result: { success: true, content: "", metadata } };
241 }
242 function filters(input) {
243 const recency = input.recency_days === null || input.recency_days === void 0 ? void 0 : count(input.recency_days, 3650);
244 const locale = input.locale === null || input.locale === void 0 ? void 0 : rustTrim(text(input.locale));
245 const parts = locale?.split(/[-_]/);
246 const language = parts?.[0] !== void 0 && /^[A-Za-z]{2,3}$/.test(parts[0]) ? parts[0].toLowerCase() : null;
247 const region = parts?.[1] !== void 0 && /^[A-Za-z]{2}$/.test(parts[1]) ? parts[1].toUpperCase() : null;
248 const window = recency === void 0 ? null : recency <= 1 ? "day" : recency <= 7 ? "week" : recency <= 31 ? "month" : "year";
249 return { kind: "web_filters", window, language, region };
250 }
251 function validJson(value) {
252 const pending = [{ value, depth: 0 }];
253 while (pending.length) {
254 const item = pending.pop();
255 if (typeof item.value === "number" && !Number.isFinite(item.value)) return false;
256 if (typeof item.value === "string" && [...item.value].some((ch) => ch.length === 1 && ch.charCodeAt(0) >= 55296 && ch.charCodeAt(0) <= 57343)) return false;
257 if (item.value && typeof item.value === "object") {
258 if (item.depth >= 128) return false;
259 for (const child of Object.values(item.value)) pending.push({ value: child, depth: item.depth + 1 });
260 }
261 }
262 return true;
263 }
264 function provider(input) {
265 const backend = text(input.backend), limit = count(input.max_results), parsed = row(input.parsed), facts = row(input.number_facts);
266 let entries2 = [], error = null;
267 let items = [], titleKeys = ["title"], urlKeys = ["url"], snippetKeys = ["content", "snippet"], trim2 = true, capSnippet = false, scorePath;
268 switch (backend) {
269 case "tavily":
270 items = array(parsed.results);
271 break;
272 case "firecrawl": {
273 const data2 = parsed.data;
274 items = array(data2 && typeof data2 === "object" && !Array.isArray(data2) && own(row(data2), "web") ? row(data2).web : data2);
275 snippetKeys = ["description", "markdown", "content"];
276 capSnippet = true;
277 if (parsed.success === false) error = `Firecrawl search failed: ${firstText(parsed, ["error", "message"]) ?? "unknown API error"}`;
278 break;
279 }
280 case "metaso": {
281 items = array(parsed.webpages);
282 urlKeys = ["link"];
283 snippetKeys = ["snippet", "summary"];
284 const code = integer(facts, "/code");
285 if (code !== void 0 && code !== "0") error = code === "3003" ? "Metaso: daily search limit reached — set METASO_API_KEY or get one at https://metaso.cn/search-api/playground" : code === "2005" ? "Metaso API key rejected — check METASO_API_KEY or set `[search] api_key` in config.toml" : `Metaso API error (code ${code}: ${string(parsed.message) ?? "unknown error"})`;
286 break;
287 }
288 case "bocha": {
289 const data2 = parsed.data && typeof parsed.data === "object" && !Array.isArray(parsed.data) ? row(parsed.data) : void 0;
290 let pages;
291 if (data2) {
292 const web = data2.webPages && typeof data2.webPages === "object" && !Array.isArray(data2.webPages) ? row(data2.webPages) : void 0;
293 pages = web && own(web, "value") ? web.value : data2.pages;
294 }
295 if (pages === void 0) pages = parsed.pages;
296 items = array(pages);
297 titleKeys = ["name", "title"];
298 urlKeys = ["url", "link"];
299 snippetKeys = ["summary", "snippet", "description"];
300 const code = integer(facts, "/code");
301 if (code !== void 0 && code !== "0" && code !== "200") error = `Bocha search API error (code ${code}: ${string(firstPresent(parsed, ["msg", "message"])) ?? "unknown error"})`;
302 break;
303 }
304 case "baidu": {
305 items = array(parsed.references);
306 titleKeys = ["title", "name"];
307 urlKeys = ["url", "link"];
308 snippetKeys = ["content", "snippet", "summary"];
309 const key = own(parsed, "error_code") ? "error_code" : "code", code = integer(facts, `/${key}`);
310 if (code !== void 0 && code !== "0") error = `Baidu search API error (code ${code}: ${string(firstPresent(parsed, ["error_msg", "message"])) ?? "unknown error"})`;
311 break;
312 }
313 case "searxng":
314 items = array(parsed.results);
315 scorePath = "/results";
316 break;
317 case "sofya":
318 items = array(parsed.results);
319 snippetKeys = ["content", "description"];
320 trim2 = false;
321 break;
322 case "serply":
323 items = array(parsed.results);
324 urlKeys = ["link"];
325 snippetKeys = ["description", "snippet"];
326 trim2 = false;
327 break;
328 case "volcengine": {
329 if (own(parsed, "error")) {
330 const value = parsed.error && typeof parsed.error === "object" && !Array.isArray(parsed.error) ? row(parsed.error) : {};
331 error = `Volcengine API error (code ${string(value.code) ?? "unknown"}: ${string(value.message) ?? "no details"})`;
332 }
333 let response;
334 const message = array(parsed.output).slice().reverse().find((value) => value && typeof value === "object" && !Array.isArray(value) && row(value).type === "message");
335 if (message) {
336 const content = array(row(message).content).find((value) => value && typeof value === "object" && !Array.isArray(value) && typeof row(value).text === "string");
337 if (content) response = text(row(content).text);
338 }
339 if (response === void 0 && error === null) error = "Volcengine response contains no output text";
340 if (response !== void 0) {
341 let body = response;
342 const fence2 = response.indexOf("```json");
343 if (fence2 >= 0) {
344 const rest = response.slice(fence2 + 7), end = rest.indexOf("```");
345 if (end >= 0) body = rustTrim(rest.slice(0, end));
346 else {
347 const first = response.indexOf("{"), last = response.lastIndexOf("}");
348 if (first >= 0 && last >= first) body = response.slice(first, last + 1);
349 }
350 } else {
351 const first = response.indexOf("{"), last = response.lastIndexOf("}");
352 if (first >= 0 && last >= first) body = response.slice(first, last + 1);
353 }
354 try {
355 const value = JSON.parse(body);
356 if (!validJson(value)) throw new Error("model JSON failed Core-compatible scalar/depth guard");
357 if (value && typeof value === "object" && !Array.isArray(value)) items = array(row(value).results);
358 } catch {
359 items = [];
360 }
361 }
362 snippetKeys = ["snippet"];
363 break;
364 }
365 default:
366 throw new Error("unadmitted web provider");
367 }
368 const scored = [];
369 for (const [index, value] of items.entries()) {
370 if (!value || typeof value !== "object" || Array.isArray(value)) continue;
371 const item = row(value), rawTitle = string(firstPresent(item, titleKeys)), rawUrl = string(firstPresent(item, urlKeys));
372 if (rawTitle === void 0 || rawUrl === void 0) continue;
373 const title = trim2 ? rustTrim(rawTitle) : rawTitle, url = trim2 ? rustTrim(rawUrl) : rawUrl;
374 if (trim2 && (!title || !url)) continue;
375 const presentSnippet = ["bocha", "baidu", "volcengine"].includes(backend);
376 const selected = presentSnippet ? string(firstPresent(item, snippetKeys)) : void 0;
377 const snippet = presentSnippet ? selected === void 0 ? void 0 : rustTrim(selected) || void 0 : firstText(item, snippetKeys);
378 const entry = { title, url, ...snippet === void 0 ? {} : { snippet: capSnippet ? [...snippet].slice(0, 1e3).join("") : snippet } };
379 scored.push({ entry, score: scorePath === void 0 ? 0 : number(facts, `${scorePath}/${index}/score`) });
380 if (scorePath === void 0 && scored.length === limit) break;
381 }
382 if (scorePath !== void 0) scored.sort((a, b) => a.score === b.score ? Object.is(a.score, -0) === Object.is(b.score, -0) ? 0 : Object.is(a.score, -0) ? 1 : -1 : a.score > b.score ? -1 : 1);
383 entries2 = scored.slice(0, limit).map((value) => value.entry);
384 return { kind: "web_provider", entries: entries2, error };
385 }
386 function extraction(input) {
387 if (!Array.isArray(input.candidates) || input.candidates.length > 3) throw new Error("invalid captured web regions");
388 const facts = [];
389 let previous = -1;
390 for (const value of input.candidates) {
391 const candidate = row(value), id = count(candidate.id, 2);
392 if (Object.keys(candidate).some((key) => !["id", "non_whitespace", "words"].includes(key)) || id <= previous) throw new Error("invalid ordered captured web region");
393 previous = id;
394 facts.push({ id, chars: count(candidate.non_whitespace, 1e8), words: count(candidate.words, 1e8) });
395 }
396 return { kind: "web_extract", candidate: facts.find((value) => value.chars >= 32 && value.words >= 5)?.id ?? null };
397 }
398 function images(input) {
399 const limit = count(input.max_results), parsed = row(input.parsed);
400 if (!Array.isArray(parsed.results)) throw new Error("invalid captured image results");
401 const entries2 = [];
402 for (const value of parsed.results) {
403 const entry = row(value), image = text(entry.image);
404 for (const key of ["thumbnail", "title", "url", "source"]) if (entry[key] !== null && entry[key] !== void 0) text(entry[key]);
405 for (const key of ["width", "height"]) if (entry[key] !== null && entry[key] !== void 0) count(entry[key], 4294967295);
406 if (!rustTrim(image)) continue;
407 entries2.push({ image, ...Object.fromEntries(["thumbnail", "title", "url", "source", "width", "height"].filter((key) => entry[key] !== null && entry[key] !== void 0).map((key) => [key, entry[key]])) });
408 }
409 return { kind: "web_images", entries: entries2, max_results: limit };
410 }
411 function request(input) {
412 const backend = text(input.backend), query = text(input.query), max = count(input.max_results);
413 const f = row(input.filters), window = f.window === null ? void 0 : text(f.window), region = f.region === null ? void 0 : text(f.region), language = f.language === null ? void 0 : text(f.language);
414 const locale = input.locale === null || input.locale === void 0 ? void 0 : text(input.locale);
415 let payload = null, pairs = [];
416 switch (backend) {
417 case "firecrawl":
418 payload = { query, limit: max, sources: [{ type: "web" }], ...window === void 0 ? {} : { tbs: `qdr:${window[0]}` }, ...region === void 0 ? {} : { country: region } };
419 break;
420 case "tavily":
421 payload = { query, search_depth: "basic", max_results: max, ...window === void 0 ? {} : { time_range: window } };
422 break;
423 case "bocha":
424 payload = { query, freshness: "noLimit", count: max };
425 break;
426 case "metaso":
427 payload = { q: query, scope: "webpage", size: Math.max(1, Math.min(100, max)) };
428 break;
429 case "sofya":
430 payload = { query, max_results: max };
431 break;
432 case "baidu":
433 payload = { messages: [{ role: "user", content: query }], search_source: "baidu_search_v2", resource_type_filter: [{ type: "web", top_k: max }] };
434 break;
435 case "volcengine":
436 payload = { input: [{ role: "user", content: [{ type: "input_text", text: `Search the web for: ${query}
437
438 CRITICAL: Respond ONLY with a valid JSON object. No markdown, no explanation.
439 Schema: {"results":[{"title":"...","url":"https://...","snippet":"..."}]}
440 - results: 1-${max} most relevant pages
441 - title: page title (required)
442 - url: full URL starting with https:// (required)
443 - snippet: 1-2 sentence factual summary (required)
444 - If zero results: {"results":[]}
445 - Your entire response must be valid, parseable JSON.` }] }] };
446 break;
447 case "serply":
448 pairs = [["q", query], ["num", String(max)], ...language === void 0 ? [] : [["hl", language]], ...region === void 0 ? [] : [["gl", region.toLowerCase()]]];
449 break;
450 case "searxng":
451 pairs = [["q", query], ["format", "json"], ...window === void 0 ? [] : [["time_range", window]], ...locale === void 0 ? [] : [["language", locale]]];
452 break;
453 case "bing":
454 case "duckduckgo":
455 pairs = [["q", query]];
456 break;
457 default:
458 throw new Error("unadmitted web request provider");
459 }
460 return { kind: "web_request", payload, pairs };
461 }
462 function entries(input) {
463 if (!Array.isArray(input.entries)) throw new Error("invalid captured web candidates");
464 const values = input.entries.map((value) => {
465 const item = row(value);
466 return { title: text(item.title), url: text(item.url), ...item.snippet === null || item.snippet === void 0 ? {} : { snippet: text(item.snippet) }, ...item.published === null || item.published === void 0 ? {} : { published: text(item.published) } };
467 });
468 return { kind: "web_entries", entries: values };
469 }
470 function finalization(input) {
471 const requested = row(input.requested), capabilities = row(input.capabilities), counted = count(input.count);
472 if (!Array.isArray(input.degraded)) throw new Error("invalid captured web receipt");
473 const degraded = input.degraded.map((value) => row(value));
474 const ignored = (knob) => degraded.some((value) => row(value).kind === "knob_ignored" && row(value).knob === knob);
475 const honored = { max_results: capabilities.max_results === "supported", recency: false, domains: requested.domains === true, locale: false };
476 for (const knob of ["recency", "locale"]) {
477 if (knob === "locale") {
478 if (!Array.isArray(input.domain_extra)) throw new Error("invalid captured domain receipt");
479 degraded.push(...input.domain_extra.map((value) => row(value)));
480 }
481 if (requested[knob] === true && !ignored(knob)) {
482 if (capabilities[knob] === "supported") honored[knob] = true;
483 else degraded.push({ kind: "knob_ignored", knob });
484 }
485 }
486 const hasNote = input.has_note === true;
487 const prefix = counted === 0 ? hasNote ? "No results found. " : "No results found" : `Found ${counted} result(s)${hasNote ? ". " : ""}`;
488 const suffix = degraded.some((value) => row(value).kind === "answer_cut_by_provider") ? "\n[the provider stopped the search answer at its output limit; the answer is incomplete]" : "";
489 return { kind: "web_finalize", honored, degraded, prefix, suffix };
490 }
491 function transformWebSnapshot(operation, value) {
492 const input = row(value);
493 switch (operation) {
494 case "web_request":
495 return result(request(input));
496 case "web_entries":
497 return result(entries(input));
498 case "web_finalize":
499 return result(finalization(input));
500 case "web_filters":
501 return result(filters(input));
502 case "web_provider":
503 return result(provider(input));
504 case "web_extract":
505 return result(extraction(input));
506 case "web_images":
507 return result(images(input));
508 default:
509 throw new Error("unadmitted web adapter operation");
510 }
511 }
512
513 // src/builtin/shared/github-adapter.ts
514 var REPOSITORY = "Hmbown/CodeWhale";
515 function row2(value) {
516 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid GitHub snapshot");
517 return value;
518 }
519 function text2(value) {
520 if (typeof value !== "string") throw new Error("invalid GitHub text");
521 return value;
522 }
523 function strings(value) {
524 if (!Array.isArray(value)) throw new Error("invalid GitHub list");
525 return value.map(text2);
526 }
527 function githubSummary(value, limit) {
528 let out = "", index = 0;
529 for (const char of value) {
530 if (index++ >= Math.max(0, limit - 3)) return `${out}...`;
531 const code = char.codePointAt(0);
532 if ((code <= 31 || code >= 127 && code <= 159) && char !== "\n" && char !== " ") continue;
533 out += char;
534 }
535 return out;
536 }
537 function renderIssueReview(value) {
538 const report2 = row2(value), fields = row2(report2.fields), id = text2(report2.id);
539 let out = `# ${text2(fields.title)}
540
541 Draft: ${id}
542 Status: ready for review
543 Publication: unavailable
544 Duplicate search: not performed
545 Destination: ${REPOSITORY}
546
547 Review the contents before sharing. Redaction does not guarantee privacy.
548 `;
549 if (report2.revises !== null) out += `Revises: ${text2(report2.revises)}
550 `;
551 for (const [heading, key] of [["Expected behavior", "expected"], ["Actual behavior", "actual"], ["Impact", "impact"]]) out += `
552 ## ${heading}
553
554 ${text2(fields[key])}
555 `;
556 for (const [heading, key] of [["Steps to reproduce (agent reported)", "steps"], ["Observed by the agent", "observed"], ["Inferences (not verified)", "inferred"]]) {
557 const items = strings(fields[key]);
558 out += `
559 ## ${heading}
560
561 `;
562 out += items.length ? items.map((item) => `- ${item}
563 `).join("") : "None recorded.\n";
564 }
565 out += `
566 ## Runtime context
567
568 - Codewhale: ${text2(report2.version)}
569 - Platform: ${text2(report2.platform)}
570 - Active model: ${text2(report2.model)}
571 `;
572 for (const [label, key] of [["Provider", "reported_provider"], ["Tool", "reported_tool"], ["Terminal", "reported_terminal"]]) out += `- ${label} (agent reported): ${fields[key] === null ? "unknown" : text2(fields[key])}
573 `;
574 if (!Array.isArray(fields.related_issues) || fields.related_issues.some((value2) => !Number.isInteger(value2) || Number(value2) < 1 || Number(value2) > 4294967295)) throw new Error("invalid related issue");
575 if (fields.related_issues.length) out += "\n## Related issues (agent supplied; not verified or searched)\n\n" + fields.related_issues.map((number2) => `- [#${number2}](https://github.com/${REPOSITORY}/issues/${number2})
576 `).join("");
577 const redactions = strings(report2.redactions);
578 if (redactions.length) out += `
579 Redacted categories: ${redactions.join(", ")}
580 `;
581 return out + `
582 Review: \`/feedback review ${id}\`
583 Revise: \`/feedback edit ${id} <change>\`
584 `;
585 }
586 function transformGithubSnapshot(value) {
587 const snapshot = row2(value), action = text2(snapshot.action);
588 if (action === "report_draft" || action === "report_read" || action === "report_review") {
589 const report2 = row2(snapshot.report), id = text2(report2.id);
590 const review = renderIssueReview(report2);
591 const content2 = action === "report_review" ? review : JSON.stringify({ report_id: id, revises: report2.revises, state: "ready_for_review", publication: "unavailable", duplicate_search: "not_performed", review, artifact: `artifacts/issue-reports/${id}.json` });
592 return { ok: true, result: { success: true, content: content2, metadata: null } };
593 }
594 const number2 = text2(snapshot.number);
595 if (!/^[0-9]{1,20}$/.test(number2)) throw new Error("invalid captured issue number");
596 if (action === "issue_context" || action === "pr_context") {
597 const raw = structuredClone(row2(snapshot.raw)), kind = action === "issue_context" ? "issue" : "pr";
598 if (snapshot.large_body === true) {
599 const body = text2(raw.body);
600 raw.body_summary = githubSummary(body, 900);
601 raw.body_artifact = snapshot.body_artifact;
602 raw.body = githubSummary(body, 1200);
603 }
604 if (action === "pr_context" && snapshot.diff !== null) {
605 raw.diff_summary = githubSummary(text2(snapshot.diff), 900);
606 raw.diff_artifact = snapshot.diff_artifact;
607 }
608 const subject2 = kind === "issue" ? "Issue" : "PR";
609 return { ok: true, result: { success: true, metadata: null, content: JSON.stringify({ summary: `${subject2} #${number2}: ${typeof raw.title === "string" ? raw.title : ""}`, [kind]: raw }) } };
610 }
611 const target = text2(snapshot.target);
612 if (!["issue", "pr"].includes(target)) throw new Error("invalid captured GitHub target");
613 const subject = target === "issue" ? "issue" : "PR";
614 let content;
615 if (snapshot.dirty === true) content = `Refusing to close ${subject}: worktree is dirty and allow_dirty was false.`;
616 else if (action === "comment") content = snapshot.dry_run === true ? `Dry run: would comment on ${target} #${number2}.` : `Commented on ${target} #${number2}.`;
617 else if (action === "close_issue" || action === "close_pr") content = snapshot.dry_run === true ? `Dry run: would close ${subject} #${number2}.` : `Closed ${subject} #${number2}.`;
618 else throw new Error("unknown captured GitHub operation");
619 return { ok: true, result: { success: snapshot.dirty !== true, content, metadata: null } };
620 }
621
622 // src/builtin/shared/review-adapter.ts
623 var REVIEW_LIMIT = 16 * 1024 * 1024;
624 var OPERATIONS = ["review_source_prompt", "review_pass_prompt", "review_interactive_pr", "review_report"];
625 function isReviewOperation(value) {
626 return typeof value === "string" && OPERATIONS.includes(value);
627 }
628 function reviewEnvelopeBytes(value) {
629 return 8 + Buffer.byteLength(`{"jsonrpc":"2.0","id":18446744073709551615,"result":${JSON.stringify(value)}}`);
630 }
631 function row3(value) {
632 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid Core review snapshot");
633 return value;
634 }
635 function text3(value) {
636 if (typeof value !== "string") throw new Error("invalid Core review text");
637 return value;
638 }
639 function bool2(value) {
640 if (typeof value !== "boolean") throw new Error("invalid Core review flag");
641 return value;
642 }
643 function integer2(value) {
644 if (!Number.isSafeInteger(value) || value < 0) throw new Error("invalid Core review count");
645 return value;
646 }
647 function list(value) {
648 if (!Array.isArray(value)) throw new Error("invalid Core review list");
649 return value;
650 }
651 function optional(value) {
652 return value === null || value === void 0 ? void 0 : text3(value);
653 }
654 function trim(value) {
655 return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, "");
656 }
657 function lines(value) {
658 if (!value.length) return [];
659 const out = value.split("\n");
660 if (value.endsWith("\n")) out.pop();
661 return out.map((line, i) => (i < out.length - 1 || value.endsWith("\n")) && line.endsWith("\r") ? line.slice(0, -1) : line);
662 }
663 function skipped(value) {
664 return list(value).map((value2) => {
665 const skip = row3(value2);
666 return `${text3(skip.file)} (${integer2(skip.chars)} chars; ${text3(skip.reason)})`;
667 }).join(", ");
668 }
669 function ordered(value) {
670 if (Array.isArray(value)) return value.map(ordered);
671 if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, value2]) => [key, ordered(value2)]));
672 return value;
673 }
674 function source(input) {
675 const kind = text3(input.kind);
676 if (kind === "file") {
677 const content = lines(text3(input.content)).map((line, i) => `${String(i + 1).padStart(4)} | ${line}`).join("\n");
678 return `Review the following file and provide feedback.
679 Path: ${text3(input.display)}
680
681 ${content}
682
683 End of file.`;
684 }
685 if (kind === "diff") return `Review the following ${text3(input.label)} and provide feedback.
686
687 ${text3(input.diff)}
688
689 End of diff.`;
690 if (kind === "cli_diff") return `Review the following diff and provide feedback:
691
692 ${text3(input.diff)}
693
694 End of diff.`;
695 if (kind === "pr") return `Review the complete pull request diff (${text3(input.label)}) at head ${text3(input.head_sha)} and base ${text3(input.base_sha)}. Binary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence.
696
697 ${text3(input.diff)}
698
699 End of diff.`;
700 throw new Error("unadmitted review source");
701 }
702 function pass(input) {
703 const manifest = row3(input.manifest), part = row3(input.pass), view = row3(input.view);
704 const task = list(manifest.skipped_files).length === 0 ? "Review only defects introduced in this pass. Use supplementary source to check surrounding guards and declarations; it does not expand the commentable diff. Binary contents and omitted callers are not inspected. No build or tests have been run." : `Review only defects introduced in this pass. This is a partial review (pass ${integer2(part.number)} of ${list(manifest.passes).length}): the gate did not read ${skipped(manifest.skipped_files)}. Do not claim full coverage. Use supplementary source to check surrounding guards and declarations; it does not expand the commentable diff. Binary contents and omitted callers are not inspected. No build or tests have been run.`;
705 if (!isJson(input.context)) throw new Error("invalid Core review context");
706 const prompt = {
707 task,
708 untrusted_repository_data: true,
709 pull_request: { number: integer2(input.number), title: text3(view.title), description: text3(view.body) },
710 manifest,
711 pass: part,
712 diff: text3(input.diff),
713 repository_context: input.context,
714 context_limit: "Context is bounded supplementary excerpts from the exact head. Null means no source context could fit. Missing files or omitted lines are not evidence of a defect."
715 };
716 return JSON.stringify(bool2(input.sort_keys) ? ordered(prompt) : prompt);
717 }
718 function interactive(input) {
719 const view = row3(input.view), number2 = integer2(input.number);
720 const base = text3(view.base), head = text3(view.head);
721 const branches = base && head ? `${base} ← ${head}` : base || head || "(unknown)";
722 return `Review PR #${number2} — ${trim(text3(view.title)) || `(PR #${number2})`}
723
724 URL: ${text3(view.url) || "(unavailable)"}
725 Branches: ${branches}
726 Revision: ${text3(view.head_sha)} (base ${text3(view.base_sha)}); ${integer2(view.changed_files)} file patches.
727 Binary changes are represented by metadata; their contents are not semantically inspected. Exact binary object IDs remain in the review evidence.
728
729 ## Description
730
731 ${trim(text3(view.body)) || "(no description)"}
732
733 ## Diff
734
735 \`\`\`diff
736 ${text3(input.diff)}
737 \`\`\`
738 `;
739 }
740 function fence(value) {
741 let longest = 0, run = 0;
742 for (const char of value) {
743 run = char === "`" ? run + 1 : 0;
744 longest = Math.max(longest, run);
745 }
746 return "`".repeat(Math.max(3, longest + 1));
747 }
748 function location(value) {
749 const path = optional(value.path);
750 return path === void 0 ? "" : value.line === null || value.line === void 0 ? `\`${trim(path)}\`` : `\`${trim(path)}:${integer2(value.line)}\``;
751 }
752 function report(input) {
753 if (input.review === null) return text3(input.output);
754 const review = row3(input.review), posted = bool2(input.posted);
755 let out = "## Codewhale review\n\n";
756 const summary2 = text3(review.summary), assessment = text3(review.overall_assessment);
757 if (summary2.length) out += trim(summary2) + "\n\n";
758 const issues = list(review.issues), suggestions = list(review.suggestions);
759 if (issues.length) {
760 out += "### Findings\n\n";
761 for (const item of issues) {
762 const issue = row3(item), at = location(issue);
763 out += `- **[${text3(issue.severity).toUpperCase()}] ${text3(issue.title)}**${at ? ` (${at})` : ""}
764 `;
765 const description = text3(issue.description);
766 if (description.length) out += ` ${description}
767 `;
768 }
769 out += "\n";
770 }
771 if (suggestions.length) {
772 out += "### Suggestions\n\n";
773 for (const item of suggestions) {
774 const suggestion = row3(item), at = location(suggestion);
775 out += `- ${at ? `${at} — ` : ""}${text3(suggestion.suggestion)}
776 `;
777 const replacement = optional(suggestion.replacement);
778 if (replacement !== void 0 && trim(replacement).length) {
779 const codeFence = fence(replacement);
780 out += `
781 ${codeFence}${posted ? "text" : "suggestion"}
782 `;
783 for (const line of replacement.split("\n")) out += ` ${line}
784 `;
785 out += ` ${codeFence}
786 `;
787 }
788 }
789 out += "\n";
790 }
791 if (assessment.length) out += "### Assessment\n\n" + trim(assessment) + "\n\n";
792 return out;
793 }
794 function transformReviewSnapshot(operation, value) {
795 if (!isReviewOperation(operation)) throw new Error("unadmitted review operation");
796 const input = row3(value);
797 const content = operation === "review_source_prompt" ? source(input) : operation === "review_pass_prompt" ? pass(input) : operation === "review_interactive_pr" ? interactive(input) : report(input);
798 const result3 = { ok: true, result: { content, success: true, metadata: null } };
799 if (reviewEnvelopeBytes(result3) > REVIEW_LIMIT) throw new Error("serialized review result exceeds 16 MiB");
800 return result3;
801 }
802
803 // src/builtin/shared/stock-adapters.ts
804 var MAX_SNAPSHOT = 1024 * 1024;
805 function row4(value) {
806 if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid adapter snapshot");
807 return value;
808 }
809 function text4(value) {
810 if (typeof value !== "string") throw new Error("invalid adapter text");
811 return value;
812 }
813 function optionalText(value) {
814 return value === null || value === void 0 ? void 0 : text4(value);
815 }
816 function optionalNumber(value) {
817 if (value === null || value === void 0) return void 0;
818 if (typeof value !== "number" || !Number.isFinite(value)) throw new Error("invalid captured numeric field");
819 return value;
820 }
821 function float(value) {
822 if (Object.is(value, -0)) return "-0";
823 if (value === Infinity) return "inf";
824 if (value === -Infinity) return "-inf";
825 return String(value);
826 }
827 function timestamp(value) {
828 const time = optionalText(value);
829 if (time === void 0) return void 0;
830 if (!/^-?\d{1,19}$/.test(time)) throw new Error("invalid captured timestamp");
831 const integer3 = BigInt(time);
832 if (integer3 < -9223372036854775808n || integer3 > 9223372036854775807n) throw new Error("captured timestamp exceeds i64");
833 return time;
834 }
835 function result2(success, metadata, content = "") {
836 return { ok: true, result: { success, content, metadata } };
837 }
838 function failure(endpoint, kind, detail) {
839 return result2(false, { endpoint, kind, detail });
840 }
841 function asciiLower(value) {
842 return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
843 }
844 function finance(operation, input) {
845 const request2 = row4(input.request);
846 const requested = text4(request2.requested_ticker);
847 const symbol = text4(request2.resolved_symbol);
848 const parsed = row4(input.parsed);
849 const chart = operation === "finance_chart";
850 const source2 = chart ? "yahoo_chart" : "yahoo_quote";
851 let quote;
852 if (chart) {
853 const body = row4(parsed.chart);
854 if (body.error !== null && body.error !== void 0) {
855 const error = row4(body.error);
856 const description = optionalText(error.description) ?? "chart endpoint returned an error";
857 const code = optionalText(error.code);
858 const notFound = (code === void 0 ? void 0 : asciiLower(code)) === "not found" || asciiLower(description).includes("not found") || asciiLower(description).includes("symbol may be delisted");
859 return failure(source2, notFound ? "not_found" : "upstream", description);
860 }
861 if (body.result !== null && body.result !== void 0 && !Array.isArray(body.result)) throw new Error("invalid captured chart results");
862 const entries2 = body.result;
863 if (!entries2?.length) return failure(source2, "not_found", `no chart data for symbol '${symbol}'`);
864 quote = row4(row4(entries2[0]).meta);
865 } else {
866 const body = row4(parsed.quoteResponse);
867 if (!Array.isArray(body.result)) throw new Error("invalid captured quote results");
868 const candidate = body.result.map(row4).find((value2) => asciiLower(text4(value2.symbol)) === asciiLower(symbol));
869 if (!candidate) return failure(source2, "not_found", `no result for symbol '${symbol}'`);
870 quote = candidate;
871 }
872 const price = optionalNumber(quote.regularMarketPrice);
873 if (price === void 0) return failure(source2, "upstream", "response missing regularMarketPrice");
874 const previous = chart ? optionalNumber(quote.chartPreviousClose) ?? optionalNumber(quote.previousClose) : optionalNumber(quote.regularMarketPreviousClose);
875 const computedChange = previous === void 0 ? void 0 : price - previous;
876 const computedPercent = previous === void 0 || Math.abs(previous) < Number.EPSILON ? void 0 : (price - previous) / previous * 100;
877 const change = chart ? computedChange : optionalNumber(quote.regularMarketChange) ?? computedChange;
878 const percent = chart ? computedPercent : optionalNumber(quote.regularMarketChangePercent) ?? computedPercent;
879 const name = optionalText(quote.longName) ?? optionalText(quote.shortName);
880 const currency = optionalText(quote.currency);
881 const state = chart ? void 0 : optionalText(quote.marketState);
882 const type = optionalText(chart ? quote.instrumentType : quote.quoteType);
883 const exchange = optionalText(quote.fullExchangeName) ?? optionalText(chart ? quote.exchangeName : quote.exchange);
884 const time = timestamp(quote.regularMarketTime);
885 const value = {
886 requested_ticker: requested,
887 ticker: text4(quote.symbol),
888 price: float(price),
889 source: source2,
890 fallback_used: chart,
891 ...name === void 0 ? {} : { name },
892 ...currency === void 0 ? {} : { currency },
893 ...change === void 0 ? {} : { change: float(change) },
894 ...percent === void 0 ? {} : { change_percent: float(percent) },
895 ...previous === void 0 ? {} : { previous_close: float(previous) },
896 ...state === void 0 ? {} : { market_state: state },
897 ...type === void 0 ? {} : { quote_type: type },
898 ...exchange === void 0 ? {} : { exchange },
899 ...time === void 0 ? {} : { market_time: time }
900 };
901 return result2(true, value);
902 }
903 function summary(value, format) {
904 const descriptor = row4(value);
905 const kind = text4(descriptor.kind);
906 const accepted = format === "json" ? ["object", "array", "string", "number", "boolean", "null"] : ["table", "array", "string", "integer", "float", "boolean", "datetime"];
907 if (!accepted.includes(kind)) throw new Error("invalid captured parser kind");
908 if (kind === "object" || kind === "table") {
909 if (!Array.isArray(descriptor.keys) || descriptor.keys.some((key) => typeof key !== "string")) throw new Error("invalid captured parser keys");
910 const keys = descriptor.keys;
911 if (new Set(keys).size !== keys.length) throw new Error("duplicate captured parser keys");
912 return { top_level: kind, entries: keys.length, keys_preview: keys.slice(0, 10) };
913 }
914 if (kind === "array") {
915 if (!Number.isSafeInteger(descriptor.entries) || descriptor.entries < 0) throw new Error("invalid captured array count");
916 return { top_level: kind, entries: descriptor.entries };
917 }
918 return { top_level: kind };
919 }
920 function data(input) {
921 const format = text4(input.format);
922 if (!["auto", "json", "toml"].includes(format)) throw new Error("invalid captured data format");
923 const source2 = text4(input.source);
924 const extension = optionalText(input.extension);
925 const requested = format === "auto" && (extension === "json" || extension === "toml") ? extension : format;
926 const json = row4(input.json);
927 const toml = row4(input.toml);
928 if (typeof json.ok !== "boolean" || typeof toml.ok !== "boolean") throw new Error("invalid captured parser result");
929 const use = (parser, selected) => {
930 if (parser.ok) return result2(true, { valid: true, format: selected, source: source2, summary: summary(parser.value, selected) });
931 const error = text4(parser.error);
932 return result2(false, { valid: false, format: selected, source: source2, error }, `Invalid ${selected.toUpperCase()}: ${error}`);
933 };
934 if (requested === "json") return use(json, "json");
935 if (requested === "toml") return use(toml, "toml");
936 if (json.ok) return use(json, "json");
937 if (toml.ok) return use(toml, "toml");
938 return result2(false, { valid: false, format: "auto", source: source2, json_error: text4(json.error), toml_error: text4(toml.error) }, "Validation failed in auto mode: content is neither valid JSON nor TOML.");
939 }
940 function rustTrim2(value) {
941 return value.replace(/^[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+|[\u0009-\u000d\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, "");
942 }
943 function bool3(value) {
944 if (typeof value !== "boolean") throw new Error("invalid captured boolean");
945 return value;
946 }
947 function speech(operation, input) {
948 const surface = text4(input.surface);
949 if (surface !== "tool" && surface !== "cli") throw new Error("invalid speech surface");
950 const tool = surface === "tool";
951 const invalid = (error) => result2(false, { error });
952 if (operation === "speech_format") {
953 const raw = text4(input.format);
954 const format = asciiLower(rustTrim2(raw));
955 if (format === "wav" || format === "mp3" || format === "pcm16" || format === "pcm") return result2(true, { format: format === "pcm" ? "pcm16" : format });
956 return invalid(tool ? `unsupported speech format '${raw}' (allowed: wav, mp3, pcm16)` : `Unsupported speech format '${raw}' (allowed: wav, mp3, pcm16)`);
957 }
958 const modelHint = optionalText(input.model);
959 const hasClone = bool3(input.has_clone_path);
960 const hasVoice = bool3(input.has_voice);
961 const nonemptyVoice = bool3(input.voice_nonempty);
962 const dataUri = bool3(input.voice_is_data_uri);
963 if ((nonemptyVoice || dataUri) && !hasVoice) throw new Error("inconsistent captured voice presence");
964 const instructionInput = optionalText(input.instruction);
965 const promptInput = optionalText(input.voice_prompt);
966 if (hasClone && hasVoice) return invalid(tool ? "use either clone_voice or voice for cloned voice data, not both" : "Use either --clone-voice or --voice for cloned voice data, not both");
967 const model = modelHint ?? (hasClone || dataUri ? "mimo-v2.5-tts-voiceclone" : promptInput !== void 0 ? "mimo-v2.5-tts-voicedesign" : "mimo-v2.5-tts");
968 const lower = asciiLower(model);
969 if (!lower.includes("tts")) {
970 const examples = "mimo-v2.5-tts, mimo-v2.5-tts-voicedesign, mimo-v2.5-tts-voiceclone, mimo-v2-tts";
971 return invalid(tool ? `speech tool requires a TTS model (examples: ${examples}), got '${model}'` : `speech requires a TTS model (examples: ${examples}); got ${model}`);
972 }
973 const parts = [promptInput, instructionInput].map((value) => value === void 0 ? void 0 : rustTrim2(value)).filter((value) => value !== void 0 && value !== "");
974 const instruction = parts.length ? parts.join("\n\n") : null;
975 if (lower.includes("voicedesign") && instruction === null) return invalid(tool ? "mimo-v2.5-tts-voicedesign requires voice_prompt or instruction" : "mimo-v2.5-tts-voicedesign requires --voice-prompt or --instruction to describe the voice");
976 let voice;
977 if (hasClone) voice = "clone";
978 else if (lower.includes("voicedesign")) voice = "omit";
979 else if (nonemptyVoice) voice = "raw";
980 else if (lower.includes("voiceclone")) return invalid(tool ? "mimo-v2.5-tts-voiceclone requires clone_voice <mp3|wav> or voice <data-uri>" : "mimo-v2.5-tts-voiceclone requires --clone-voice <mp3|wav> or --voice <data-uri>");
981 else voice = "default";
982 return result2(true, { model, instruction, voice });
983 }
984 function pdfProjection(input) {
985 const state = text4(input.state);
986 const decision = (code2, message) => result2(true, { kind: "pdf_decision", code: code2, ...message === void 0 ? {} : { message } });
987 if (state !== "complete") {
988 if (!["binary_unavailable", "cancelled", "timed_out", "execution"].includes(state)) throw new Error("invalid PDF process state");
989 return decision(state, text4(input.message));
990 }
991 const success = bool3(input.success);
992 const stdoutOverflow = bool3(input.stdout_truncated);
993 const stderrOverflow = bool3(input.stderr_truncated);
994 const stderr = text4(input.stderr);
995 const code = input.exit_code;
996 if (code !== null && (!Number.isInteger(code) || code < -2147483648 || code > 2147483647)) throw new Error("invalid PDF exit code");
997 if (stdoutOverflow) return decision("execution", "pdftotext output exceeded the 16777216 byte safety limit");
998 if (!success) return decision("execution", `pdftotext failed (exit ${code === null ? "None" : `Some(${code})`}): ${stderr || "no diagnostic output"}${stderrOverflow ? " [truncated]" : ""}`);
999 return decision("success");
1000 }
1001 function ocrProjection(input) {
1002 const decision = (code2, trim_end = false, message) => result2(true, { kind: "ocr_decision", code: code2, trim_end, ...message === void 0 ? {} : { message } });
1003 const state = text4(input.state), status = text4(input.status);
1004 if (state === "native") {
1005 if (Object.keys(input).some((key) => !["kind", "state", "status", "can_fallback", "next_ticket"].includes(key)) || !["success", "error", "unavailable"].includes(status)) throw new Error("invalid OCR Native projection");
1006 const fallback = bool3(input.can_fallback);
1007 if (status === "success") {
1008 if (fallback || input.next_ticket !== void 0) throw new Error("successful OCR Native step cannot launch fallback");
1009 return decision("native_success");
1010 }
1011 if (fallback) {
1012 if (typeof input.next_ticket !== "string" || input.next_ticket.length < 1 || input.next_ticket.length > 256) throw new Error("OCR fallback has no exact continuation grant");
1013 return decision("fallback");
1014 }
1015 if (input.next_ticket !== void 0) throw new Error("OCR fallback is not admitted");
1016 if (status === "error") return decision("native_error");
1017 return decision("no_backend", false, "image_ocr: no local OCR backend is available. On macOS, update to a version with the Vision framework; on Linux/Windows install tesseract and restart codewhale.");
1018 }
1019 if (state !== "tesseract") throw new Error("invalid OCR process stage");
1020 if (status === "fault") {
1021 if (Object.keys(input).some((key) => !["kind", "state", "status"].includes(key))) throw new Error("invalid OCR fault projection");
1022 return decision("fault");
1023 }
1024 if (status !== "complete" || Object.keys(input).some((key) => !["kind", "state", "status", "success", "exit_code"].includes(key))) throw new Error("invalid OCR Tesseract projection");
1025 const success = bool3(input.success), code = input.exit_code;
1026 if (code !== null && (!Number.isInteger(code) || code < -2147483648 || code > 2147483647)) throw new Error("invalid OCR exit code");
1027 return success ? decision("tesseract_success", true) : decision("execution", false, `tesseract failed (exit ${code === null ? "None" : `Some(${code})`}): `);
1028 }
1029 function transformStockSnapshot(value) {
1030 const snapshot = row4(value);
1031 const review = snapshot.kind === "stock_adapter" && isReviewOperation(snapshot.operation);
1032 if (review ? reviewEnvelopeBytes(snapshot) > REVIEW_LIMIT : Buffer.byteLength(JSON.stringify(value)) > MAX_SNAPSHOT) throw new Error(review ? "serialized review snapshot/envelope exceeds 16 MiB" : "adapter snapshot exceeds 1 MiB");
1033 if (snapshot.kind === "ocr_process") return ocrProjection(snapshot);
1034 if (snapshot.kind === "pdf_process") return pdfProjection(snapshot);
1035 if (snapshot.kind !== "stock_adapter") throw new Error("invalid adapter projection");
1036 const input = row4(snapshot.input);
1037 if (review) return transformReviewSnapshot(snapshot.operation, input);
1038 switch (snapshot.operation) {
1039 case "web_filters":
1040 case "web_request":
1041 case "web_provider":
1042 case "web_entries":
1043 case "web_finalize":
1044 case "web_extract":
1045 case "web_images":
1046 return transformWebSnapshot(snapshot.operation, input);
1047 case "github_result":
1048 return transformGithubSnapshot(input);
1049 case "finance_quote":
1050 case "finance_chart":
1051 return finance(snapshot.operation, input);
1052 case "validate_data":
1053 return data(input);
1054 case "speech_options":
1055 case "speech_format":
1056 return speech(snapshot.operation, input);
1057 default:
1058 throw new Error("unadmitted adapter operation");
1059 }
1060 }
1061
1062 // src/builtin/harness.ts
1063 var MAX_PENDING = 32;
1064 var MAX_STDOUT = 1024 * 1024;
1065 var MAX_STDERR = 64 * 1024;
1066 function normalizeOutput(value) {
1067 const out = value;
1068 if (!out || typeof out.success !== "boolean" || typeof out.stdout !== "string" || typeof out.stderr !== "string" || Buffer.byteLength(out.stdout) > MAX_STDOUT || Buffer.byteLength(out.stderr) > MAX_STDERR) throw new Error("invalid or oversized execution output");
1069 if (!out.success) return { ok: false, error: out.stderr ? out.stdout ? `${out.stdout}
1070 ${out.stderr}` : out.stderr : out.stdout };
1071 try {
1072 const result3 = JSON.parse(out.stdout);
1073 if (result3 && typeof result3.content === "string" && typeof result3.success === "boolean" && (result3.metadata === void 0 || isJson(result3.metadata))) {
1074 return { ok: true, result: { content: result3.content, success: result3.success, ...result3.metadata === void 0 ? {} : { metadata: result3.metadata } } };
1075 }
1076 } catch {
1077 }
1078 return { ok: true, result: { content: out.stdout, success: true } };
1079 }
1080 function createHarnessModule(rpc, reference) {
1081 if (reference.plugin_id !== "host:harness") throw new Error("execution runner requires its builtin owner");
1082 const owner = Object.freeze(structuredClone(reference));
1083 const stop = new AbortController();
1084 const pending = /* @__PURE__ */ new Map();
1085 let disposed = false;
1086 return {
1087 async run(params, signal) {
1088 if (disposed || signal.aborted || params.owner.plugin_id !== owner.plugin_id || params.owner.generation !== owner.generation || params.owner.owner_token !== owner.owner_token) throw new Error("execution runner owner is stale or cancelled");
1089 if (!params.execution_id || !params.ticket || !Number.isSafeInteger(params.deadline_ms) || params.deadline_ms < 1 || params.deadline_ms > (params.hook ? 2147483647 : 12e4) || pending.size >= MAX_PENDING || pending.has(params.execution_id)) throw new Error("execution is not admitted");
1090 if (params.hook) {
1091 const { event, dialect, point, matcher, query } = params.hook;
1092 if (typeof event !== "string" || typeof point !== "string" || typeof query !== "string" || query.length > 1024 || matcher !== void 0 && (typeof matcher !== "string" || matcher.length > 1024) || !["codewhale", "claude-code", "codex"].includes(dialect)) throw new Error("invalid hook projection");
1093 if (dialect !== "codewhale" && !matchesMatcher(matcher, query, dialect)) return { ok: true, hook_skipped: true };
1094 }
1095 const abort = new AbortController();
1096 const cancel = () => abort.abort();
1097 signal.addEventListener("abort", cancel, { once: true });
1098 stop.signal.addEventListener("abort", cancel, { once: true });
1099 const timer = setTimeout(cancel, params.deadline_ms);
1100 const done = (async () => {
1101 const cancelled = new Promise((_, reject) => {
1102 abort.signal.addEventListener("abort", () => reject(new Error("execution cancelled")), { once: true });
1103 });
1104 let value = await Promise.race([rpc.request("exec/redeem", { owner, execution_id: params.execution_id, ticket: params.ticket }, abort.signal), cancelled]);
1105 if (disposed || abort.signal.aborted) throw new Error("execution cancelled");
1106 if (params.hook) {
1107 const out = value;
1108 if (!out || out.kind !== "hook" || out.event !== params.hook.event || typeof out.success !== "boolean" || out.exit_code !== null && !Number.isInteger(out.exit_code)) throw new Error("invalid hook completion projection");
1109 if (params.hook.event === "shell_env") {
1110 if ("stdout" in out || "stderr" in out || Object.keys(out).some((key) => !["kind", "event", "success", "exit_code", "keys"].includes(key))) throw new Error("ShellEnv projection contains private process data");
1111 return { ok: true, hook_completed: true };
1112 }
1113 if (params.hook.dialect === "codewhale") return { ok: true, hook_completed: true };
1114 if (typeof out.stdout !== "string" || typeof out.stderr !== "string" || Buffer.byteLength(out.stdout) > 65536 || Buffer.byteLength(out.stderr) > 65536) throw new Error("invalid dialect hook output");
1115 const decoded = parseHookOutput(out.exit_code === null ? void 0 : out.exit_code, out.stdout, out.stderr, params.hook.point);
1116 const merged = mergeHookOutputs([decoded]);
1117 if (params.hook.event === "tool_call_before") return { ok: true, hook_completed: true, proposal: JSON.stringify({ ...merged.decision === "deny" ? { decision: "deny", reason: merged.reason ?? "Blocked by hook" } : merged.decision === "ask" && params.hook.dialect !== "codex" ? { decision: "ask", reason: merged.reason } : {}, ...merged.additionalContext.length ? { additional_context: merged.additionalContext.join("\n\n") } : {} }) };
1118 if (params.hook.event === "message_submit" && (merged.stop || decoded.updatedInput !== void 0 || merged.additionalContext.length)) throw new Error("hook requested unsupported prompt steering");
1119 if (params.hook.event === "message_submit") return { ok: true, hook_completed: true, proposal: JSON.stringify(merged.decision === "deny" ? { block: true, reason: merged.reason ?? "Blocked by hook" } : {}) };
1120 if (merged.stop || merged.decision === "deny" || decoded.updatedInput !== void 0 || merged.additionalContext.length) throw new Error("hook requested steering unavailable at this observer boundary");
1121 return { ok: true, hook_completed: true };
1122 }
1123 if (value && typeof value === "object" && value.kind === "ocr_process") {
1124 if (value.state !== "native") throw new Error("OCR must begin with its admitted Native step");
1125 const first = transformStockSnapshot(value);
1126 if (first.result.metadata.code !== "fallback") return first;
1127 const next = value.next_ticket;
1128 value = await Promise.race([rpc.request("exec/redeem", { owner, execution_id: params.execution_id, ticket: next }, abort.signal), cancelled]);
1129 if (disposed || abort.signal.aborted) throw new Error("execution cancelled");
1130 if (!value || typeof value !== "object" || value.kind !== "ocr_process" || value.state !== "tesseract") throw new Error("OCR continuation returned the wrong stage");
1131 return transformStockSnapshot(value);
1132 }
1133 if (value && typeof value === "object" && ["stock_adapter", "pdf_process"].includes(String(value.kind))) return transformStockSnapshot(value);
1134 return normalizeOutput(value);
1135 })();
1136 pending.set(params.execution_id, { abort, done });
1137 try {
1138 return await done;
1139 } finally {
1140 clearTimeout(timer);
1141 signal.removeEventListener("abort", cancel);
1142 stop.signal.removeEventListener("abort", cancel);
1143 pending.delete(params.execution_id);
1144 }
1145 },
1146 async dispose() {
1147 if (disposed) return;
1148 disposed = true;
1149 stop.abort();
1150 await Promise.allSettled([...pending.values()].map((row5) => row5.done));
1151 }
1152 };
1153 }
1154 export {
1155 createHarnessModule,
1156 normalizeOutput
1157 };
1158
1158 lines Plain Text