pullfrog 0.1.74 → 0.1.76
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/dist/agents/opencodeShared.d.ts +20 -0
- package/dist/cli.mjs +1125 -1038
- package/dist/external.d.ts +1 -1
- package/dist/index.js +1109 -1036
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal.js +61 -44
- package/dist/mcp/shell.d.ts +5 -0
- package/dist/models.d.ts +21 -0
- package/dist/utils/lifecycle.d.ts +13 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28027,7 +28027,7 @@ var require_cross_spawn = __commonJS({
|
|
|
28027
28027
|
enoent.hookChildProcess(spawned, parsed2);
|
|
28028
28028
|
return spawned;
|
|
28029
28029
|
}
|
|
28030
|
-
function
|
|
28030
|
+
function spawnSync8(command, args2, options) {
|
|
28031
28031
|
const parsed2 = parse6(command, args2, options);
|
|
28032
28032
|
const result = cp.spawnSync(parsed2.command, parsed2.args, parsed2.options);
|
|
28033
28033
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed2);
|
|
@@ -28035,7 +28035,7 @@ var require_cross_spawn = __commonJS({
|
|
|
28035
28035
|
}
|
|
28036
28036
|
module.exports = spawn4;
|
|
28037
28037
|
module.exports.spawn = spawn4;
|
|
28038
|
-
module.exports.sync =
|
|
28038
|
+
module.exports.sync = spawnSync8;
|
|
28039
28039
|
module.exports._parse = parse6;
|
|
28040
28040
|
module.exports._enoent = enoent;
|
|
28041
28041
|
}
|
|
@@ -108971,6 +108971,17 @@ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
|
|
|
108971
108971
|
var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
|
|
108972
108972
|
var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
|
|
108973
108973
|
var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
|
|
108974
|
+
var PROVIDER_GATEWAY_URL_ENV = {
|
|
108975
|
+
anthropic: "ANTHROPIC_BASE_URL",
|
|
108976
|
+
openai: "OPENAI_BASE_URL"
|
|
108977
|
+
};
|
|
108978
|
+
function getProviderGatewayUrl(specifier) {
|
|
108979
|
+
const slashIndex = specifier?.indexOf("/") ?? -1;
|
|
108980
|
+
if (!specifier || slashIndex <= 0) return void 0;
|
|
108981
|
+
const envVar = PROVIDER_GATEWAY_URL_ENV[specifier.slice(0, slashIndex)];
|
|
108982
|
+
if (!envVar) return void 0;
|
|
108983
|
+
return process.env[envVar]?.trim().replace(/\/$/, "") || void 0;
|
|
108984
|
+
}
|
|
108974
108985
|
function isBedrockAnthropicId(bedrockModelId) {
|
|
108975
108986
|
return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
|
|
108976
108987
|
}
|
|
@@ -109795,7 +109806,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
109795
109806
|
// package.json
|
|
109796
109807
|
var package_default = {
|
|
109797
109808
|
name: "pullfrog",
|
|
109798
|
-
version: "0.1.
|
|
109809
|
+
version: "0.1.76",
|
|
109799
109810
|
cliContractVersion: "1",
|
|
109800
109811
|
type: "module",
|
|
109801
109812
|
bin: {
|
|
@@ -110594,26 +110605,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
110594
110605
|
|
|
110595
110606
|
2. Checkout the PR branch via \`${t("checkout_pr")}\`.
|
|
110596
110607
|
|
|
110597
|
-
3. Fetch review comments via \`${t("get_review_comments")}
|
|
110608
|
+
3. Fetch review comments via \`${t("get_review_comments")}\` and read the full conversation in each relevant thread.
|
|
110598
110609
|
|
|
110599
|
-
4. For each
|
|
110600
|
-
-
|
|
110610
|
+
4. For each thread:
|
|
110611
|
+
- **check intent first**: questions, explanations, and disagreement call for a reply, not a code change. Change code only when explicitly requested. Automatic review feedback on Pullfrog-authored PRs (\`address_scope: all\`) remains actionable without a separate request; later replies can narrow or withdraw that permission.
|
|
110612
|
+
- under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
|
|
110601
110613
|
- **verify the finding yourself** against the actual code before deciding whether to apply \u2014 every comment (human or agent) is a hypothesis, not a directive. agent reviewers especially are fallible.
|
|
110602
|
-
-
|
|
110603
|
-
- if the
|
|
110614
|
+
- keep requested changes **sound, minimal, and elegant**. Reject defensive checks for impossible cases, single-use abstractions, redundant comments, and tautological tests rather than mechanically applying reviewer suggestions.
|
|
110615
|
+
- if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
|
|
110604
110616
|
- record what was done (or why nothing was done)
|
|
110605
110617
|
|
|
110606
110618
|
5. Quality check:
|
|
110607
|
-
- re-call \`${t("get_review_comments")}\` with \`fresh: true\` before
|
|
110608
|
-
-
|
|
110609
|
-
- ${commitStep}
|
|
110619
|
+
- re-call \`${t("get_review_comments")}\` with \`fresh: true\` before finishing, even when only replying. Apply the same scope and intent checks to new comments; respect requests to stop or change direction before committing.
|
|
110620
|
+
- if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
|
|
110610
110621
|
|
|
110611
|
-
6. Finalize
|
|
110612
|
-
- ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
110613
|
-
-
|
|
110614
|
-
-
|
|
110615
|
-
- reply ONCE via \`${t("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
|
|
110616
|
-
- **immediately** call \`${t("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
|
|
110622
|
+
6. Finalize:
|
|
110623
|
+
- **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
|
|
110624
|
+
- reply ONCE per relevant thread via \`${t("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
|
|
110625
|
+
- resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
|
|
110617
110626
|
- call \`${t("report_progress")}\` with a brief summary. If your last \`${t("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
|
|
110618
110627
|
},
|
|
110619
110628
|
// Review and IncrementalReview route the minimum reviewfrog specialists
|
|
@@ -112514,6 +112523,19 @@ function warnIfNativeEditsUnavailable(ctx) {
|
|
|
112514
112523
|
"\xBB this run cannot change files: codex's native write tool depends on bubblewrap, which Ubuntu 24.04 blocks (https://github.com/openai/codex/issues/29908), and the MCP shell is disabled for this repo. read-only work is unaffected \u2014 set `shell: restricted` if the agent needs to edit anything."
|
|
112515
112524
|
);
|
|
112516
112525
|
}
|
|
112526
|
+
var CODEX_GATEWAY_PROVIDER = "pullfrog-gateway";
|
|
112527
|
+
function gatewayProvider(gatewayUrl) {
|
|
112528
|
+
if (!gatewayUrl) return [];
|
|
112529
|
+
return [
|
|
112530
|
+
`[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
|
|
112531
|
+
'name = "OpenAI gateway"',
|
|
112532
|
+
`base_url = ${JSON.stringify(gatewayUrl)}`,
|
|
112533
|
+
'wire_api = "responses"',
|
|
112534
|
+
// where the harness puts the run's OPENAI_API_KEY
|
|
112535
|
+
'env_key = "CODEX_API_KEY"',
|
|
112536
|
+
""
|
|
112537
|
+
];
|
|
112538
|
+
}
|
|
112517
112539
|
function writeCodexConfig(params) {
|
|
112518
112540
|
const shellEnabled = params.ctx.payload.shell === "enabled";
|
|
112519
112541
|
const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
|
|
@@ -112530,6 +112552,7 @@ function writeCodexConfig(params) {
|
|
|
112530
112552
|
'web_search = "live"',
|
|
112531
112553
|
...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
|
|
112532
112554
|
...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
|
|
112555
|
+
...params.gatewayUrl ? [`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`] : [],
|
|
112533
112556
|
"",
|
|
112534
112557
|
"[features]",
|
|
112535
112558
|
tomlBool(CODEX_DISABLED_FEATURES, false),
|
|
@@ -112559,6 +112582,7 @@ function writeCodexConfig(params) {
|
|
|
112559
112582
|
"tool_timeout_sec = 660",
|
|
112560
112583
|
"",
|
|
112561
112584
|
...repoMcpApprovals(repoDir),
|
|
112585
|
+
...gatewayProvider(params.gatewayUrl),
|
|
112562
112586
|
// UNTRUSTED, deliberately, and this one line is the whole config-precedence
|
|
112563
112587
|
// boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
|
|
112564
112588
|
// OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
|
|
@@ -112827,7 +112851,11 @@ var codex = agent({
|
|
|
112827
112851
|
ctx,
|
|
112828
112852
|
codexHome,
|
|
112829
112853
|
model: resolveCodexModel(ctx),
|
|
112830
|
-
effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
|
|
112854
|
+
effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0,
|
|
112855
|
+
// keyed off OpenAI, not the resolved model: codex serves nothing else, an
|
|
112856
|
+
// auto-selected run has no model, and a ChatGPT-subscription token is
|
|
112857
|
+
// valid only at ChatGPT's own backend
|
|
112858
|
+
gatewayUrl: codexHomeAuth ? void 0 : getProviderGatewayUrl("openai/")
|
|
112831
112859
|
});
|
|
112832
112860
|
if (codexHomeAuth) {
|
|
112833
112861
|
core3.saveState(
|
|
@@ -118594,6 +118622,14 @@ function openAICompatibleProvider(model) {
|
|
|
118594
118622
|
}
|
|
118595
118623
|
};
|
|
118596
118624
|
}
|
|
118625
|
+
var OPENCODE_GATEWAY_PATH = { anthropic: "/v1" };
|
|
118626
|
+
function providerGatewayOverride(model) {
|
|
118627
|
+
const gatewayUrl = getProviderGatewayUrl(model);
|
|
118628
|
+
if (!model || !gatewayUrl) return {};
|
|
118629
|
+
const provider2 = model.slice(0, model.indexOf("/"));
|
|
118630
|
+
const baseURL = `${gatewayUrl}${OPENCODE_GATEWAY_PATH[provider2] ?? ""}`;
|
|
118631
|
+
return { [provider2]: { options: { baseURL } } };
|
|
118632
|
+
}
|
|
118597
118633
|
function azureProvider(model) {
|
|
118598
118634
|
const prefix = `${AZURE_PROVIDER}/`;
|
|
118599
118635
|
if (!model?.startsWith(prefix)) return {};
|
|
@@ -118709,7 +118745,8 @@ function buildSecurityConfig(ctx, model) {
|
|
|
118709
118745
|
provider: {
|
|
118710
118746
|
openrouter: { models: kimiOpenRouterProviderOverrides() },
|
|
118711
118747
|
...openAICompatibleProvider(model),
|
|
118712
|
-
...azureProvider(model)
|
|
118748
|
+
...azureProvider(model),
|
|
118749
|
+
...providerGatewayOverride(model)
|
|
118713
118750
|
}
|
|
118714
118751
|
};
|
|
118715
118752
|
if (model) {
|
|
@@ -176983,8 +177020,8 @@ function closeBrowserDaemon(toolState) {
|
|
|
176983
177020
|
|
|
176984
177021
|
// mcp/checkout.ts
|
|
176985
177022
|
import { createHash as createHash2 } from "node:crypto";
|
|
176986
|
-
import { statSync, unlinkSync as unlinkSync3, writeFileSync as
|
|
176987
|
-
import { join as
|
|
177023
|
+
import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
177024
|
+
import { join as join15 } from "node:path";
|
|
176988
177025
|
|
|
176989
177026
|
// node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
|
|
176990
177027
|
var ArkError = class _ArkError extends CastableBase {
|
|
@@ -185256,6 +185293,960 @@ async function $gitFetchWithDeepen(args2, options, label) {
|
|
|
185256
185293
|
}
|
|
185257
185294
|
}
|
|
185258
185295
|
|
|
185296
|
+
// utils/lifecycle.ts
|
|
185297
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
185298
|
+
|
|
185299
|
+
// mcp/shell.ts
|
|
185300
|
+
import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
|
|
185301
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
185302
|
+
import { closeSync, openSync, writeFileSync as writeFileSync8 } from "node:fs";
|
|
185303
|
+
import { userInfo as userInfo2 } from "node:os";
|
|
185304
|
+
import { join as join10 } from "node:path";
|
|
185305
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
185306
|
+
|
|
185307
|
+
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
185308
|
+
var LIST_ITEM_MARKER = "-";
|
|
185309
|
+
var LIST_ITEM_PREFIX = "- ";
|
|
185310
|
+
var COMMA = ",";
|
|
185311
|
+
var PIPE = "|";
|
|
185312
|
+
var DOT = ".";
|
|
185313
|
+
var NULL_LITERAL = "null";
|
|
185314
|
+
var TRUE_LITERAL = "true";
|
|
185315
|
+
var FALSE_LITERAL = "false";
|
|
185316
|
+
var BACKSLASH = "\\";
|
|
185317
|
+
var DOUBLE_QUOTE = '"';
|
|
185318
|
+
var TAB = " ";
|
|
185319
|
+
var DELIMITERS = {
|
|
185320
|
+
comma: COMMA,
|
|
185321
|
+
tab: TAB,
|
|
185322
|
+
pipe: PIPE
|
|
185323
|
+
};
|
|
185324
|
+
var DEFAULT_DELIMITER = DELIMITERS.comma;
|
|
185325
|
+
function escapeString(value2) {
|
|
185326
|
+
return value2.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`).replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`).replace(/\n/g, `${BACKSLASH}n`).replace(/\r/g, `${BACKSLASH}r`).replace(/\t/g, `${BACKSLASH}t`);
|
|
185327
|
+
}
|
|
185328
|
+
function isBooleanOrNullLiteral(token) {
|
|
185329
|
+
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
|
|
185330
|
+
}
|
|
185331
|
+
function normalizeValue(value2) {
|
|
185332
|
+
if (value2 === null) return null;
|
|
185333
|
+
if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
185334
|
+
if (typeof value2 === "number") {
|
|
185335
|
+
if (Object.is(value2, -0)) return 0;
|
|
185336
|
+
if (!Number.isFinite(value2)) return null;
|
|
185337
|
+
return value2;
|
|
185338
|
+
}
|
|
185339
|
+
if (typeof value2 === "bigint") {
|
|
185340
|
+
if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
|
|
185341
|
+
return value2.toString();
|
|
185342
|
+
}
|
|
185343
|
+
if (value2 instanceof Date) return value2.toISOString();
|
|
185344
|
+
if (Array.isArray(value2)) return value2.map(normalizeValue);
|
|
185345
|
+
if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
|
|
185346
|
+
if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
|
|
185347
|
+
if (isPlainObject6(value2)) {
|
|
185348
|
+
const normalized = {};
|
|
185349
|
+
for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
|
|
185350
|
+
return normalized;
|
|
185351
|
+
}
|
|
185352
|
+
return null;
|
|
185353
|
+
}
|
|
185354
|
+
function isJsonPrimitive(value2) {
|
|
185355
|
+
return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
|
|
185356
|
+
}
|
|
185357
|
+
function isJsonArray(value2) {
|
|
185358
|
+
return Array.isArray(value2);
|
|
185359
|
+
}
|
|
185360
|
+
function isJsonObject4(value2) {
|
|
185361
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
185362
|
+
}
|
|
185363
|
+
function isEmptyObject2(value2) {
|
|
185364
|
+
return Object.keys(value2).length === 0;
|
|
185365
|
+
}
|
|
185366
|
+
function isPlainObject6(value2) {
|
|
185367
|
+
if (value2 === null || typeof value2 !== "object") return false;
|
|
185368
|
+
const prototype = Object.getPrototypeOf(value2);
|
|
185369
|
+
return prototype === null || prototype === Object.prototype;
|
|
185370
|
+
}
|
|
185371
|
+
function isArrayOfPrimitives(value2) {
|
|
185372
|
+
return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
|
|
185373
|
+
}
|
|
185374
|
+
function isArrayOfArrays(value2) {
|
|
185375
|
+
return value2.length === 0 || value2.every((item) => isJsonArray(item));
|
|
185376
|
+
}
|
|
185377
|
+
function isArrayOfObjects(value2) {
|
|
185378
|
+
return value2.length === 0 || value2.every((item) => isJsonObject4(item));
|
|
185379
|
+
}
|
|
185380
|
+
function isValidUnquotedKey(key) {
|
|
185381
|
+
return /^[A-Z_][\w.]*$/i.test(key);
|
|
185382
|
+
}
|
|
185383
|
+
function isIdentifierSegment(key) {
|
|
185384
|
+
return /^[A-Z_]\w*$/i.test(key);
|
|
185385
|
+
}
|
|
185386
|
+
function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
|
|
185387
|
+
if (!value2) return false;
|
|
185388
|
+
if (value2 !== value2.trim()) return false;
|
|
185389
|
+
if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
|
|
185390
|
+
if (value2.includes(":")) return false;
|
|
185391
|
+
if (value2.includes('"') || value2.includes("\\")) return false;
|
|
185392
|
+
if (/[[\]{}]/.test(value2)) return false;
|
|
185393
|
+
if (/[\n\r\t]/.test(value2)) return false;
|
|
185394
|
+
if (value2.includes(delimiter2)) return false;
|
|
185395
|
+
if (value2.startsWith(LIST_ITEM_MARKER)) return false;
|
|
185396
|
+
return true;
|
|
185397
|
+
}
|
|
185398
|
+
function isNumericLike(value2) {
|
|
185399
|
+
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
|
|
185400
|
+
}
|
|
185401
|
+
var QUOTED_KEY_MARKER = Symbol("quotedKey");
|
|
185402
|
+
function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
185403
|
+
if (options.keyFolding !== "safe") return;
|
|
185404
|
+
if (!isJsonObject4(value2)) return;
|
|
185405
|
+
const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
|
|
185406
|
+
if (segments.length < 2) return;
|
|
185407
|
+
if (!segments.every((seg) => isIdentifierSegment(seg))) return;
|
|
185408
|
+
const foldedKey = buildFoldedKey(segments);
|
|
185409
|
+
const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
185410
|
+
if (siblings.includes(foldedKey)) return;
|
|
185411
|
+
if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
|
|
185412
|
+
return {
|
|
185413
|
+
foldedKey,
|
|
185414
|
+
remainder: tail,
|
|
185415
|
+
leafValue,
|
|
185416
|
+
segmentCount: segments.length
|
|
185417
|
+
};
|
|
185418
|
+
}
|
|
185419
|
+
function collectSingleKeyChain(startKey, startValue, maxDepth) {
|
|
185420
|
+
const segments = [startKey];
|
|
185421
|
+
let currentValue = startValue;
|
|
185422
|
+
while (segments.length < maxDepth) {
|
|
185423
|
+
if (!isJsonObject4(currentValue)) break;
|
|
185424
|
+
const keys = Object.keys(currentValue);
|
|
185425
|
+
if (keys.length !== 1) break;
|
|
185426
|
+
const nextKey = keys[0];
|
|
185427
|
+
const nextValue = currentValue[nextKey];
|
|
185428
|
+
segments.push(nextKey);
|
|
185429
|
+
currentValue = nextValue;
|
|
185430
|
+
}
|
|
185431
|
+
if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
|
|
185432
|
+
segments,
|
|
185433
|
+
tail: void 0,
|
|
185434
|
+
leafValue: currentValue
|
|
185435
|
+
};
|
|
185436
|
+
return {
|
|
185437
|
+
segments,
|
|
185438
|
+
tail: currentValue,
|
|
185439
|
+
leafValue: currentValue
|
|
185440
|
+
};
|
|
185441
|
+
}
|
|
185442
|
+
function buildFoldedKey(segments) {
|
|
185443
|
+
return segments.join(DOT);
|
|
185444
|
+
}
|
|
185445
|
+
function encodePrimitive(value2, delimiter2) {
|
|
185446
|
+
if (value2 === null) return NULL_LITERAL;
|
|
185447
|
+
if (typeof value2 === "boolean") return String(value2);
|
|
185448
|
+
if (typeof value2 === "number") return String(value2);
|
|
185449
|
+
return encodeStringLiteral(value2, delimiter2);
|
|
185450
|
+
}
|
|
185451
|
+
function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
|
|
185452
|
+
if (isSafeUnquoted(value2, delimiter2)) return value2;
|
|
185453
|
+
return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
|
|
185454
|
+
}
|
|
185455
|
+
function encodeKey(key) {
|
|
185456
|
+
if (isValidUnquotedKey(key)) return key;
|
|
185457
|
+
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
|
|
185458
|
+
}
|
|
185459
|
+
function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
|
|
185460
|
+
return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
|
|
185461
|
+
}
|
|
185462
|
+
function formatHeader(length, options) {
|
|
185463
|
+
const key = options?.key;
|
|
185464
|
+
const fields = options?.fields;
|
|
185465
|
+
const delimiter2 = options?.delimiter ?? COMMA;
|
|
185466
|
+
let header = "";
|
|
185467
|
+
if (key) header += encodeKey(key);
|
|
185468
|
+
header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
|
|
185469
|
+
if (fields) {
|
|
185470
|
+
const quotedFields = fields.map((f) => encodeKey(f));
|
|
185471
|
+
header += `{${quotedFields.join(delimiter2)}}`;
|
|
185472
|
+
}
|
|
185473
|
+
header += ":";
|
|
185474
|
+
return header;
|
|
185475
|
+
}
|
|
185476
|
+
function* encodeJsonValue(value2, options, depth) {
|
|
185477
|
+
if (isJsonPrimitive(value2)) {
|
|
185478
|
+
const encodedPrimitive = encodePrimitive(value2, options.delimiter);
|
|
185479
|
+
if (encodedPrimitive !== "") yield encodedPrimitive;
|
|
185480
|
+
return;
|
|
185481
|
+
}
|
|
185482
|
+
if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
|
|
185483
|
+
else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
|
|
185484
|
+
}
|
|
185485
|
+
function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
|
|
185486
|
+
const keys = Object.keys(value2);
|
|
185487
|
+
if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
|
|
185488
|
+
const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
|
|
185489
|
+
for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
185490
|
+
}
|
|
185491
|
+
function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
185492
|
+
const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
|
|
185493
|
+
const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
|
|
185494
|
+
if (options.keyFolding === "safe" && siblings) {
|
|
185495
|
+
const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
185496
|
+
if (foldResult) {
|
|
185497
|
+
const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
|
|
185498
|
+
const encodedFoldedKey = encodeKey(foldedKey);
|
|
185499
|
+
if (remainder === void 0) {
|
|
185500
|
+
if (isJsonPrimitive(leafValue)) {
|
|
185501
|
+
yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
|
|
185502
|
+
return;
|
|
185503
|
+
} else if (isJsonArray(leafValue)) {
|
|
185504
|
+
yield* encodeArrayLines(foldedKey, leafValue, depth, options);
|
|
185505
|
+
return;
|
|
185506
|
+
} else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
|
|
185507
|
+
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
185508
|
+
return;
|
|
185509
|
+
}
|
|
185510
|
+
}
|
|
185511
|
+
if (isJsonObject4(remainder)) {
|
|
185512
|
+
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
185513
|
+
const remainingDepth = effectiveFlattenDepth - segmentCount;
|
|
185514
|
+
const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
185515
|
+
yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
|
|
185516
|
+
return;
|
|
185517
|
+
}
|
|
185518
|
+
}
|
|
185519
|
+
}
|
|
185520
|
+
const encodedKey = encodeKey(key);
|
|
185521
|
+
if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
|
|
185522
|
+
else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
|
|
185523
|
+
else if (isJsonObject4(value2)) {
|
|
185524
|
+
yield indentedLine(depth, `${encodedKey}:`, options.indent);
|
|
185525
|
+
if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
|
|
185526
|
+
}
|
|
185527
|
+
}
|
|
185528
|
+
function* encodeArrayLines(key, value2, depth, options) {
|
|
185529
|
+
if (value2.length === 0) {
|
|
185530
|
+
yield indentedLine(depth, formatHeader(0, {
|
|
185531
|
+
key,
|
|
185532
|
+
delimiter: options.delimiter
|
|
185533
|
+
}), options.indent);
|
|
185534
|
+
return;
|
|
185535
|
+
}
|
|
185536
|
+
if (isArrayOfPrimitives(value2)) {
|
|
185537
|
+
yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
|
|
185538
|
+
return;
|
|
185539
|
+
}
|
|
185540
|
+
if (isArrayOfArrays(value2)) {
|
|
185541
|
+
if (value2.every((arr) => isArrayOfPrimitives(arr))) {
|
|
185542
|
+
yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
|
|
185543
|
+
return;
|
|
185544
|
+
}
|
|
185545
|
+
}
|
|
185546
|
+
if (isArrayOfObjects(value2)) {
|
|
185547
|
+
const header = extractTabularHeader(value2);
|
|
185548
|
+
if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
|
|
185549
|
+
else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
185550
|
+
return;
|
|
185551
|
+
}
|
|
185552
|
+
yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
185553
|
+
}
|
|
185554
|
+
function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
|
|
185555
|
+
yield indentedLine(depth, formatHeader(values.length, {
|
|
185556
|
+
key: prefix,
|
|
185557
|
+
delimiter: options.delimiter
|
|
185558
|
+
}), options.indent);
|
|
185559
|
+
for (const arr of values) if (isArrayOfPrimitives(arr)) {
|
|
185560
|
+
const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
|
|
185561
|
+
yield indentedListItem(depth + 1, arrayLine, options.indent);
|
|
185562
|
+
}
|
|
185563
|
+
}
|
|
185564
|
+
function encodeInlineArrayLine(values, delimiter2, prefix) {
|
|
185565
|
+
const header = formatHeader(values.length, {
|
|
185566
|
+
key: prefix,
|
|
185567
|
+
delimiter: delimiter2
|
|
185568
|
+
});
|
|
185569
|
+
const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
|
|
185570
|
+
if (values.length === 0) return header;
|
|
185571
|
+
return `${header} ${joinedValue}`;
|
|
185572
|
+
}
|
|
185573
|
+
function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
|
|
185574
|
+
yield indentedLine(depth, formatHeader(rows.length, {
|
|
185575
|
+
key: prefix,
|
|
185576
|
+
fields: header,
|
|
185577
|
+
delimiter: options.delimiter
|
|
185578
|
+
}), options.indent);
|
|
185579
|
+
yield* writeTabularRowsLines(rows, header, depth + 1, options);
|
|
185580
|
+
}
|
|
185581
|
+
function extractTabularHeader(rows) {
|
|
185582
|
+
if (rows.length === 0) return;
|
|
185583
|
+
const firstRow = rows[0];
|
|
185584
|
+
const firstKeys = Object.keys(firstRow);
|
|
185585
|
+
if (firstKeys.length === 0) return;
|
|
185586
|
+
if (isTabularArray(rows, firstKeys)) return firstKeys;
|
|
185587
|
+
}
|
|
185588
|
+
function isTabularArray(rows, header) {
|
|
185589
|
+
for (const row of rows) {
|
|
185590
|
+
if (Object.keys(row).length !== header.length) return false;
|
|
185591
|
+
for (const key of header) {
|
|
185592
|
+
if (!(key in row)) return false;
|
|
185593
|
+
if (!isJsonPrimitive(row[key])) return false;
|
|
185594
|
+
}
|
|
185595
|
+
}
|
|
185596
|
+
return true;
|
|
185597
|
+
}
|
|
185598
|
+
function* writeTabularRowsLines(rows, header, depth, options) {
|
|
185599
|
+
for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
|
|
185600
|
+
}
|
|
185601
|
+
function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
|
|
185602
|
+
yield indentedLine(depth, formatHeader(items.length, {
|
|
185603
|
+
key: prefix,
|
|
185604
|
+
delimiter: options.delimiter
|
|
185605
|
+
}), options.indent);
|
|
185606
|
+
for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
185607
|
+
}
|
|
185608
|
+
function* encodeObjectAsListItemLines(obj, depth, options) {
|
|
185609
|
+
if (isEmptyObject2(obj)) {
|
|
185610
|
+
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
185611
|
+
return;
|
|
185612
|
+
}
|
|
185613
|
+
const entries = Object.entries(obj);
|
|
185614
|
+
if (entries.length === 1) {
|
|
185615
|
+
const [key, value2] = entries[0];
|
|
185616
|
+
if (isJsonArray(value2) && isArrayOfObjects(value2)) {
|
|
185617
|
+
const header = extractTabularHeader(value2);
|
|
185618
|
+
if (header) {
|
|
185619
|
+
yield indentedListItem(depth, formatHeader(value2.length, {
|
|
185620
|
+
key,
|
|
185621
|
+
fields: header,
|
|
185622
|
+
delimiter: options.delimiter
|
|
185623
|
+
}), options.indent);
|
|
185624
|
+
yield* writeTabularRowsLines(value2, header, depth + 1, options);
|
|
185625
|
+
return;
|
|
185626
|
+
}
|
|
185627
|
+
}
|
|
185628
|
+
}
|
|
185629
|
+
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
185630
|
+
yield* encodeObjectLines(obj, depth + 1, options);
|
|
185631
|
+
}
|
|
185632
|
+
function* encodeListItemValueLines(value2, depth, options) {
|
|
185633
|
+
if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
|
|
185634
|
+
else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
|
|
185635
|
+
else {
|
|
185636
|
+
yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
|
|
185637
|
+
for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
185638
|
+
}
|
|
185639
|
+
else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
|
|
185640
|
+
}
|
|
185641
|
+
function indentedLine(depth, content, indentSize) {
|
|
185642
|
+
return " ".repeat(indentSize * depth) + content;
|
|
185643
|
+
}
|
|
185644
|
+
function indentedListItem(depth, content, indentSize) {
|
|
185645
|
+
return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
|
|
185646
|
+
}
|
|
185647
|
+
function encode4(input2, options) {
|
|
185648
|
+
return Array.from(encodeLines(input2, options)).join("\n");
|
|
185649
|
+
}
|
|
185650
|
+
function encodeLines(input2, options) {
|
|
185651
|
+
return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
|
|
185652
|
+
}
|
|
185653
|
+
function resolveOptions(options) {
|
|
185654
|
+
return {
|
|
185655
|
+
indent: options?.indent ?? 2,
|
|
185656
|
+
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
|
|
185657
|
+
keyFolding: options?.keyFolding ?? "off",
|
|
185658
|
+
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
|
|
185659
|
+
};
|
|
185660
|
+
}
|
|
185661
|
+
|
|
185662
|
+
// mcp/geminiSanitizer.ts
|
|
185663
|
+
function parseStringEnumBranch(item) {
|
|
185664
|
+
if (!item || typeof item !== "object") return null;
|
|
185665
|
+
const record3 = item;
|
|
185666
|
+
if (Array.isArray(record3.enum)) {
|
|
185667
|
+
const strings = record3.enum.filter((v) => typeof v === "string");
|
|
185668
|
+
return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
|
|
185669
|
+
}
|
|
185670
|
+
if (typeof record3.const === "string") {
|
|
185671
|
+
return { values: [record3.const] };
|
|
185672
|
+
}
|
|
185673
|
+
return null;
|
|
185674
|
+
}
|
|
185675
|
+
function collapseStringUnion(branches) {
|
|
185676
|
+
const values = [];
|
|
185677
|
+
for (const item of branches) {
|
|
185678
|
+
const parsed2 = parseStringEnumBranch(item);
|
|
185679
|
+
if (!parsed2) return null;
|
|
185680
|
+
values.push(...parsed2.values);
|
|
185681
|
+
}
|
|
185682
|
+
if (values.length === 0) return null;
|
|
185683
|
+
return { type: "string", enum: [...new Set(values)] };
|
|
185684
|
+
}
|
|
185685
|
+
function sanitizeForGemini(schema2) {
|
|
185686
|
+
if (!schema2 || typeof schema2 !== "object") return schema2;
|
|
185687
|
+
if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
|
|
185688
|
+
const source = schema2;
|
|
185689
|
+
if (Array.isArray(source.enum) && typeof source.type !== "string") {
|
|
185690
|
+
const allStrings = source.enum.every((v) => typeof v === "string");
|
|
185691
|
+
if (allStrings) {
|
|
185692
|
+
const result = { type: "string", enum: source.enum };
|
|
185693
|
+
if (typeof source.description === "string") result.description = source.description;
|
|
185694
|
+
return result;
|
|
185695
|
+
}
|
|
185696
|
+
}
|
|
185697
|
+
for (const unionKey of ["anyOf", "oneOf"]) {
|
|
185698
|
+
const branches = source[unionKey];
|
|
185699
|
+
if (Array.isArray(branches) && branches.length > 0) {
|
|
185700
|
+
const collapsed = collapseStringUnion(branches);
|
|
185701
|
+
if (collapsed) {
|
|
185702
|
+
const result = { ...collapsed };
|
|
185703
|
+
if (typeof source.description === "string") result.description = source.description;
|
|
185704
|
+
return result;
|
|
185705
|
+
}
|
|
185706
|
+
}
|
|
185707
|
+
}
|
|
185708
|
+
if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
|
|
185709
|
+
const result = {};
|
|
185710
|
+
if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
|
|
185711
|
+
if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
|
|
185712
|
+
return result;
|
|
185713
|
+
}
|
|
185714
|
+
const sanitized = {};
|
|
185715
|
+
for (const [key, value2] of Object.entries(source)) {
|
|
185716
|
+
if (key === "$schema") continue;
|
|
185717
|
+
if (key === "$defs") {
|
|
185718
|
+
sanitized.definitions = sanitizeForGemini(value2);
|
|
185719
|
+
continue;
|
|
185720
|
+
}
|
|
185721
|
+
sanitized[key] = sanitizeForGemini(value2);
|
|
185722
|
+
}
|
|
185723
|
+
return sanitized;
|
|
185724
|
+
}
|
|
185725
|
+
function wrapJsonSchemaProducer(producer) {
|
|
185726
|
+
return new Proxy(producer, {
|
|
185727
|
+
get(target, prop, receiver) {
|
|
185728
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
185729
|
+
if ((prop === "input" || prop === "output") && typeof value2 === "function") {
|
|
185730
|
+
const fn2 = value2;
|
|
185731
|
+
return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
|
|
185732
|
+
}
|
|
185733
|
+
return value2;
|
|
185734
|
+
}
|
|
185735
|
+
});
|
|
185736
|
+
}
|
|
185737
|
+
function wrapStandard(standard) {
|
|
185738
|
+
return new Proxy(standard, {
|
|
185739
|
+
get(target, prop, receiver) {
|
|
185740
|
+
if (prop === "jsonSchema") {
|
|
185741
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
185742
|
+
if (value2 && typeof value2 === "object") {
|
|
185743
|
+
return wrapJsonSchemaProducer(value2);
|
|
185744
|
+
}
|
|
185745
|
+
return value2;
|
|
185746
|
+
}
|
|
185747
|
+
return Reflect.get(target, prop, receiver);
|
|
185748
|
+
}
|
|
185749
|
+
});
|
|
185750
|
+
}
|
|
185751
|
+
function wrapSchemaForGemini(schema2) {
|
|
185752
|
+
return new Proxy(schema2, {
|
|
185753
|
+
get(target, prop, receiver) {
|
|
185754
|
+
if (prop === "~standard") {
|
|
185755
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
185756
|
+
if (value2 && typeof value2 === "object") {
|
|
185757
|
+
return wrapStandard(value2);
|
|
185758
|
+
}
|
|
185759
|
+
return value2;
|
|
185760
|
+
}
|
|
185761
|
+
if (prop === "toJsonSchema") {
|
|
185762
|
+
const method = Reflect.get(target, prop, receiver);
|
|
185763
|
+
if (typeof method === "function") {
|
|
185764
|
+
return () => sanitizeForGemini(method.call(target));
|
|
185765
|
+
}
|
|
185766
|
+
return method;
|
|
185767
|
+
}
|
|
185768
|
+
return Reflect.get(target, prop, receiver);
|
|
185769
|
+
}
|
|
185770
|
+
});
|
|
185771
|
+
}
|
|
185772
|
+
function sanitizeToolForGemini(tool2) {
|
|
185773
|
+
if (!tool2.parameters) return tool2;
|
|
185774
|
+
return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
|
|
185775
|
+
}
|
|
185776
|
+
function isGeminiRouted(ctx) {
|
|
185777
|
+
const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
|
|
185778
|
+
if (!effective) return true;
|
|
185779
|
+
const normalized = effective.toLowerCase();
|
|
185780
|
+
if (normalized.includes("gemini")) return true;
|
|
185781
|
+
if (!normalized.includes("/")) return true;
|
|
185782
|
+
return false;
|
|
185783
|
+
}
|
|
185784
|
+
|
|
185785
|
+
// mcp/shared.ts
|
|
185786
|
+
function getHttpStatus(err) {
|
|
185787
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
|
|
185788
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
185789
|
+
}
|
|
185790
|
+
var tool = (toolDef) => toolDef;
|
|
185791
|
+
var handleToolSuccess = (data) => {
|
|
185792
|
+
const text = typeof data === "string" ? data : encode4(data);
|
|
185793
|
+
return {
|
|
185794
|
+
content: [{ type: "text", text }]
|
|
185795
|
+
};
|
|
185796
|
+
};
|
|
185797
|
+
var handleToolError = (error62) => {
|
|
185798
|
+
const errorMessage = error62 instanceof Error ? error62.message : String(error62);
|
|
185799
|
+
return {
|
|
185800
|
+
content: [
|
|
185801
|
+
{
|
|
185802
|
+
type: "text",
|
|
185803
|
+
text: `Error: ${errorMessage}`
|
|
185804
|
+
}
|
|
185805
|
+
],
|
|
185806
|
+
isError: true
|
|
185807
|
+
};
|
|
185808
|
+
};
|
|
185809
|
+
var execute = (fn2, toolName) => {
|
|
185810
|
+
const _fn = async (params) => {
|
|
185811
|
+
try {
|
|
185812
|
+
const result = await fn2(params);
|
|
185813
|
+
return handleToolSuccess(result);
|
|
185814
|
+
} catch (error62) {
|
|
185815
|
+
const errorMessage = error62 instanceof Error ? error62.message : String(error62);
|
|
185816
|
+
const prefix = toolName ? `[${toolName}]` : "tool";
|
|
185817
|
+
log.info(`${prefix} error: ${errorMessage}`);
|
|
185818
|
+
log.debug(`${prefix} params: ${formatJsonValue(params)}`);
|
|
185819
|
+
return handleToolError(error62);
|
|
185820
|
+
}
|
|
185821
|
+
};
|
|
185822
|
+
return _fn;
|
|
185823
|
+
};
|
|
185824
|
+
var addTools = (ctx, server, tools) => {
|
|
185825
|
+
const shouldSanitize = isGeminiRouted(ctx);
|
|
185826
|
+
for (const tool2 of tools) {
|
|
185827
|
+
server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
|
|
185828
|
+
}
|
|
185829
|
+
return server;
|
|
185830
|
+
};
|
|
185831
|
+
|
|
185832
|
+
// mcp/shell.ts
|
|
185833
|
+
var ShellParams = type({
|
|
185834
|
+
command: "string",
|
|
185835
|
+
// advisory and unread — no code path consumes it. kept in the schema only
|
|
185836
|
+
// because it nudges the model to state intent before running a command;
|
|
185837
|
+
// REQUIRING it cost a full rejected turn every time one was omitted. #1140.
|
|
185838
|
+
"description?": "string",
|
|
185839
|
+
"timeout?": type.number.describe(
|
|
185840
|
+
"Timeout in MILLISECONDS (not seconds). Default 30000 (30s), max 120000 (2m). e.g. timeout: 180000 for 3 minutes; timeout: 180 means 180ms and will kill the process almost immediately."
|
|
185841
|
+
),
|
|
185842
|
+
"working_directory?": "string",
|
|
185843
|
+
"background?": "boolean"
|
|
185844
|
+
});
|
|
185845
|
+
var detectedSandboxMethod;
|
|
185846
|
+
var usernsProbeError;
|
|
185847
|
+
function getSandboxMethod() {
|
|
185848
|
+
return detectSandboxMethod();
|
|
185849
|
+
}
|
|
185850
|
+
function detectSandboxMethod() {
|
|
185851
|
+
if (detectedSandboxMethod !== void 0) {
|
|
185852
|
+
return detectedSandboxMethod;
|
|
185853
|
+
}
|
|
185854
|
+
if (process.env.CI !== "true") {
|
|
185855
|
+
detectedSandboxMethod = "none";
|
|
185856
|
+
log.debug("sandbox disabled (CI !== true)");
|
|
185857
|
+
return "none";
|
|
185858
|
+
}
|
|
185859
|
+
try {
|
|
185860
|
+
const result = spawnSync6("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
|
|
185861
|
+
timeout: 5e3,
|
|
185862
|
+
stdio: "ignore"
|
|
185863
|
+
});
|
|
185864
|
+
if (result.status === 0) {
|
|
185865
|
+
detectedSandboxMethod = "unshare";
|
|
185866
|
+
log.debug("PID namespace isolation enabled (unprivileged unshare)");
|
|
185867
|
+
return "unshare";
|
|
185868
|
+
}
|
|
185869
|
+
} catch {
|
|
185870
|
+
}
|
|
185871
|
+
try {
|
|
185872
|
+
const result = spawnSync6("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
|
|
185873
|
+
timeout: 5e3,
|
|
185874
|
+
stdio: "ignore"
|
|
185875
|
+
});
|
|
185876
|
+
if (result.status === 0) {
|
|
185877
|
+
detectedSandboxMethod = "sudo-unshare";
|
|
185878
|
+
log.debug("PID namespace isolation enabled (sudo unshare)");
|
|
185879
|
+
return "sudo-unshare";
|
|
185880
|
+
}
|
|
185881
|
+
} catch {
|
|
185882
|
+
}
|
|
185883
|
+
try {
|
|
185884
|
+
const result = spawnSync6(
|
|
185885
|
+
"unshare",
|
|
185886
|
+
[
|
|
185887
|
+
"--user",
|
|
185888
|
+
"--map-root-user",
|
|
185889
|
+
"--pid",
|
|
185890
|
+
"--fork",
|
|
185891
|
+
"--mount",
|
|
185892
|
+
"setpriv",
|
|
185893
|
+
"--no-new-privs",
|
|
185894
|
+
"--bounding-set",
|
|
185895
|
+
"-all",
|
|
185896
|
+
"--inh-caps",
|
|
185897
|
+
"-all",
|
|
185898
|
+
"--ambient-caps",
|
|
185899
|
+
"-all",
|
|
185900
|
+
"true"
|
|
185901
|
+
],
|
|
185902
|
+
{ timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
|
|
185903
|
+
);
|
|
185904
|
+
if (result.status === 0) {
|
|
185905
|
+
detectedSandboxMethod = "userns-unshare";
|
|
185906
|
+
log.debug("PID namespace isolation enabled (unprivileged userns unshare)");
|
|
185907
|
+
return "userns-unshare";
|
|
185908
|
+
}
|
|
185909
|
+
usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
|
|
185910
|
+
} catch {
|
|
185911
|
+
}
|
|
185912
|
+
detectedSandboxMethod = "none";
|
|
185913
|
+
log.info("PID namespace isolation not available");
|
|
185914
|
+
return "none";
|
|
185915
|
+
}
|
|
185916
|
+
var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
|
185917
|
+
var SOCKET_CLEANUP = [
|
|
185918
|
+
"/var/run/docker.sock",
|
|
185919
|
+
"/run/docker.sock",
|
|
185920
|
+
"/var/run/podman/podman.sock",
|
|
185921
|
+
"/run/podman/podman.sock",
|
|
185922
|
+
"/run/containerd/containerd.sock",
|
|
185923
|
+
"/var/run/crio/crio.sock"
|
|
185924
|
+
].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
|
|
185925
|
+
function buildFsMounts(repoDir) {
|
|
185926
|
+
const escaped = repoDir.replace(/'/g, "'\\''");
|
|
185927
|
+
return [
|
|
185928
|
+
`mkdir -p /var/lib/pullfrog 2>/dev/null;`,
|
|
185929
|
+
`mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
|
|
185930
|
+
`[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
|
|
185931
|
+
`[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
|
|
185932
|
+
// the corepack shim dir sits FIRST on PATH (utils/packageManager.ts) and
|
|
185933
|
+
// PATH survives filterEnv, so a writable pm-bin lets sandboxed code plant
|
|
185934
|
+
// a `git`/`pnpm` that a later UNSANDBOXED spawn resolves with the parent
|
|
185935
|
+
// env. read-only closes that for every sandboxed command at once.
|
|
185936
|
+
//
|
|
185937
|
+
// the path is interpolated HERE rather than read as `$PULLFROG_TEMP_DIR`
|
|
185938
|
+
// inside the sandbox: that name is not on the `filterEnv` safe list, so in
|
|
185939
|
+
// restricted mode it expands to empty and the mount silently no-ops in the
|
|
185940
|
+
// one tier it exists to protect. `$RUNNER_TEMP` above is safe only because
|
|
185941
|
+
// `RUNNER_` is an allowed prefix.
|
|
185942
|
+
shimMount()
|
|
185943
|
+
].filter(Boolean).join(" ");
|
|
185944
|
+
}
|
|
185945
|
+
function shimMount() {
|
|
185946
|
+
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
185947
|
+
if (!tempDir) return "";
|
|
185948
|
+
const dir = join10(tempDir, "pm-bin").replace(/'/g, "'\\''");
|
|
185949
|
+
return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
|
|
185950
|
+
}
|
|
185951
|
+
var _repoRoot;
|
|
185952
|
+
function resolveRepoRoot() {
|
|
185953
|
+
if (_repoRoot) return _repoRoot;
|
|
185954
|
+
const fromEnv = process.env.GITHUB_WORKSPACE;
|
|
185955
|
+
if (fromEnv) {
|
|
185956
|
+
_repoRoot = fromEnv;
|
|
185957
|
+
return _repoRoot;
|
|
185958
|
+
}
|
|
185959
|
+
try {
|
|
185960
|
+
_repoRoot = spawnSync6("git", ["rev-parse", "--show-toplevel"], {
|
|
185961
|
+
cwd: process.cwd(),
|
|
185962
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
185963
|
+
encoding: "utf-8"
|
|
185964
|
+
}).stdout?.trim();
|
|
185965
|
+
} catch {
|
|
185966
|
+
}
|
|
185967
|
+
if (!_repoRoot) _repoRoot = process.cwd();
|
|
185968
|
+
return _repoRoot;
|
|
185969
|
+
}
|
|
185970
|
+
function spawnShell(params) {
|
|
185971
|
+
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
|
185972
|
+
const sandboxMethod = detectSandboxMethod();
|
|
185973
|
+
const ci = process.env.CI === "true";
|
|
185974
|
+
if (ci && sandboxMethod === "none") {
|
|
185975
|
+
throw new Error(
|
|
185976
|
+
"PID-namespace isolation is required in CI but unavailable: unprivileged unshare, sudo unshare, and userns-nested unshare all failed. " + (usernsProbeError ? `userns probe said: ${usernsProbeError}. ` : "") + "on a containerized / Kubernetes self-hosted runner the cause is usually one of: the pod's seccomp profile blocks user-namespace creation (Pod Security Standards baseline/restricted); the pod runs as root without CAP_SETFCAP, which the kernel requires to map uid 0; or the node kernel disallows unprivileged user namespaces (Ubuntu >= 23.10, Bottlerocket, Talos). elsewhere, check that `unshare` and `setpriv` (util-linux >= 2.31, which is what added --ambient-caps) are on PATH. see https://docs.pullfrog.com/security#self-hosted-runners"
|
|
185977
|
+
);
|
|
185978
|
+
}
|
|
185979
|
+
const repoRoot = resolveRepoRoot();
|
|
185980
|
+
const fsMounts = buildFsMounts(repoRoot);
|
|
185981
|
+
if (sandboxMethod === "unshare") {
|
|
185982
|
+
return spawn3(
|
|
185983
|
+
"unshare",
|
|
185984
|
+
[
|
|
185985
|
+
"--pid",
|
|
185986
|
+
"--fork",
|
|
185987
|
+
"--mount-proc",
|
|
185988
|
+
"bash",
|
|
185989
|
+
"-c",
|
|
185990
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
|
|
185991
|
+
],
|
|
185992
|
+
spawnOpts
|
|
185993
|
+
);
|
|
185994
|
+
}
|
|
185995
|
+
if (sandboxMethod === "userns-unshare") {
|
|
185996
|
+
const escaped = params.command.replace(/'/g, "'\\''");
|
|
185997
|
+
return spawn3(
|
|
185998
|
+
"unshare",
|
|
185999
|
+
[
|
|
186000
|
+
"--user",
|
|
186001
|
+
"--map-root-user",
|
|
186002
|
+
"--pid",
|
|
186003
|
+
"--fork",
|
|
186004
|
+
"--mount",
|
|
186005
|
+
"bash",
|
|
186006
|
+
"-c",
|
|
186007
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
|
|
186008
|
+
],
|
|
186009
|
+
spawnOpts
|
|
186010
|
+
);
|
|
186011
|
+
}
|
|
186012
|
+
if (sandboxMethod === "sudo-unshare") {
|
|
186013
|
+
const envArgs = [];
|
|
186014
|
+
for (const [k2, v] of Object.entries(params.env)) {
|
|
186015
|
+
if (v !== void 0) {
|
|
186016
|
+
envArgs.push(`${k2}=${v}`);
|
|
186017
|
+
}
|
|
186018
|
+
}
|
|
186019
|
+
const username = userInfo2().username;
|
|
186020
|
+
const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
|
|
186021
|
+
const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
|
|
186022
|
+
envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
|
|
186023
|
+
return spawn3(
|
|
186024
|
+
"sudo",
|
|
186025
|
+
[
|
|
186026
|
+
"env",
|
|
186027
|
+
...envArgs,
|
|
186028
|
+
"unshare",
|
|
186029
|
+
"--pid",
|
|
186030
|
+
"--fork",
|
|
186031
|
+
"--mount-proc",
|
|
186032
|
+
"bash",
|
|
186033
|
+
"-c",
|
|
186034
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
|
|
186035
|
+
],
|
|
186036
|
+
{ ...spawnOpts, env: {} }
|
|
186037
|
+
);
|
|
186038
|
+
}
|
|
186039
|
+
return spawn3("bash", ["-c", params.command], spawnOpts);
|
|
186040
|
+
}
|
|
186041
|
+
async function killProcessGroup(proc) {
|
|
186042
|
+
if (!proc.pid) return;
|
|
186043
|
+
try {
|
|
186044
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
186045
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
186046
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
186047
|
+
} catch {
|
|
186048
|
+
try {
|
|
186049
|
+
proc.kill("SIGKILL");
|
|
186050
|
+
} catch {
|
|
186051
|
+
}
|
|
186052
|
+
}
|
|
186053
|
+
}
|
|
186054
|
+
function getTempDir() {
|
|
186055
|
+
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
186056
|
+
if (!tempDir) {
|
|
186057
|
+
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
186058
|
+
}
|
|
186059
|
+
return tempDir;
|
|
186060
|
+
}
|
|
186061
|
+
var MAX_OUTPUT_CHARS = 5e3;
|
|
186062
|
+
async function runSandboxed(params) {
|
|
186063
|
+
const proc = spawnShell({
|
|
186064
|
+
command: params.command,
|
|
186065
|
+
env: params.env,
|
|
186066
|
+
cwd: params.cwd,
|
|
186067
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
186068
|
+
});
|
|
186069
|
+
const stdout = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
186070
|
+
const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
186071
|
+
let timedOut = false, exited = false, spawnError = "";
|
|
186072
|
+
proc.stdout?.on("data", (chunk) => {
|
|
186073
|
+
const text = chunk.toString();
|
|
186074
|
+
stdout.append(text);
|
|
186075
|
+
params.onStdout?.(text);
|
|
186076
|
+
});
|
|
186077
|
+
proc.stderr?.on("data", (chunk) => {
|
|
186078
|
+
const text = chunk.toString();
|
|
186079
|
+
stderr.append(text);
|
|
186080
|
+
params.onStderr?.(text);
|
|
186081
|
+
});
|
|
186082
|
+
const timeoutId = setTimeout(async () => {
|
|
186083
|
+
if (!exited) {
|
|
186084
|
+
timedOut = true;
|
|
186085
|
+
await killProcessGroup(proc);
|
|
186086
|
+
}
|
|
186087
|
+
}, params.timeout);
|
|
186088
|
+
const exitCode = await new Promise((resolve3) => {
|
|
186089
|
+
const done = (code) => {
|
|
186090
|
+
exited = true;
|
|
186091
|
+
clearTimeout(timeoutId);
|
|
186092
|
+
resolve3(code);
|
|
186093
|
+
};
|
|
186094
|
+
proc.on("exit", done);
|
|
186095
|
+
proc.on("error", (err) => {
|
|
186096
|
+
spawnError = err.message;
|
|
186097
|
+
done(null);
|
|
186098
|
+
});
|
|
186099
|
+
});
|
|
186100
|
+
proc.stdout?.destroy();
|
|
186101
|
+
proc.stderr?.destroy();
|
|
186102
|
+
const outText = stdout.toString();
|
|
186103
|
+
const errText = stderr.toString();
|
|
186104
|
+
let output2 = errText ? outText ? `${outText}
|
|
186105
|
+
${errText}` : errText : outText;
|
|
186106
|
+
if (spawnError) output2 = output2 ? `${output2}
|
|
186107
|
+
${spawnError}` : spawnError;
|
|
186108
|
+
if (timedOut)
|
|
186109
|
+
output2 = output2 ? `${output2}
|
|
186110
|
+
[timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
|
|
186111
|
+
return {
|
|
186112
|
+
output: output2.trim(),
|
|
186113
|
+
exitCode: exitCode ?? (timedOut ? 124 : -1),
|
|
186114
|
+
timedOut
|
|
186115
|
+
};
|
|
186116
|
+
}
|
|
186117
|
+
function capOutput(output2) {
|
|
186118
|
+
if (output2.length <= MAX_OUTPUT_CHARS) return output2;
|
|
186119
|
+
const fullPath = join10(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
|
|
186120
|
+
writeFileSync8(fullPath, output2);
|
|
186121
|
+
const elided = output2.length - MAX_OUTPUT_CHARS;
|
|
186122
|
+
return `... [${elided} chars truncated; output saved to ${fullPath}] ...
|
|
186123
|
+
${output2.slice(-MAX_OUTPUT_CHARS)}`;
|
|
186124
|
+
}
|
|
186125
|
+
function isGitCommand(command) {
|
|
186126
|
+
const trimmed = command.trim();
|
|
186127
|
+
if (trimmed === "git" || trimmed.startsWith("git ")) return true;
|
|
186128
|
+
if (trimmed.startsWith("sudo git")) return true;
|
|
186129
|
+
return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
|
|
186130
|
+
}
|
|
186131
|
+
function ShellTool(ctx) {
|
|
186132
|
+
return tool({
|
|
186133
|
+
name: "shell",
|
|
186134
|
+
timeoutMs: 12e4,
|
|
186135
|
+
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
186136
|
+
|
|
186137
|
+
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
186138
|
+
|
|
186139
|
+
Use this tool to:
|
|
186140
|
+
- Run shell commands (ls, cat, grep, find, etc.)
|
|
186141
|
+
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
186142
|
+
- Run tests and linters
|
|
186143
|
+
|
|
186144
|
+
Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the captured output is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
|
|
186145
|
+
|
|
186146
|
+
Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
|
|
186147
|
+
parameters: ShellParams,
|
|
186148
|
+
execute: execute(async (params) => {
|
|
186149
|
+
if (isGitCommand(params.command)) {
|
|
186150
|
+
throw new Error(
|
|
186151
|
+
'git commands are not allowed in the shell tool. use the dedicated git tools instead:\n- git: local operations, e.g. git({ command: "log", args: ["--oneline"] }) \u2014 `command` is the subcommand ONLY\n- push_branch: push to remote (handles authentication)\n- git_fetch: fetch from remote (handles authentication)\n- checkout_pr: check out PR branches'
|
|
186152
|
+
);
|
|
186153
|
+
}
|
|
186154
|
+
const timeout = Math.min(params.timeout ?? 3e4, 12e4);
|
|
186155
|
+
const cwd = params.working_directory ?? process.cwd();
|
|
186156
|
+
const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
|
|
186157
|
+
if (params.command.includes("agent-browser")) {
|
|
186158
|
+
const daemonError = ensureBrowserDaemon(ctx.toolState);
|
|
186159
|
+
if (daemonError) {
|
|
186160
|
+
return {
|
|
186161
|
+
output: `browser daemon unavailable: ${daemonError}`,
|
|
186162
|
+
exit_code: 1,
|
|
186163
|
+
timed_out: false
|
|
186164
|
+
};
|
|
186165
|
+
}
|
|
186166
|
+
const binDir = ctx.toolState.browserDaemon?.binDir;
|
|
186167
|
+
if (binDir) {
|
|
186168
|
+
env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
|
|
186169
|
+
}
|
|
186170
|
+
}
|
|
186171
|
+
if (params.background) {
|
|
186172
|
+
const tempDir = getTempDir();
|
|
186173
|
+
const handle = `bg-${randomUUID3().slice(0, 8)}`;
|
|
186174
|
+
const outputPath = join10(tempDir, `${handle}.log`);
|
|
186175
|
+
const pidPath = join10(tempDir, `${handle}.pid`);
|
|
186176
|
+
const logFd = openSync(outputPath, "a");
|
|
186177
|
+
let proc;
|
|
186178
|
+
try {
|
|
186179
|
+
proc = spawnShell({
|
|
186180
|
+
command: params.command,
|
|
186181
|
+
env: env2,
|
|
186182
|
+
cwd,
|
|
186183
|
+
stdio: ["ignore", logFd, logFd]
|
|
186184
|
+
});
|
|
186185
|
+
} finally {
|
|
186186
|
+
closeSync(logFd);
|
|
186187
|
+
}
|
|
186188
|
+
if (!proc.pid) {
|
|
186189
|
+
throw new Error("failed to start background process");
|
|
186190
|
+
}
|
|
186191
|
+
proc.unref();
|
|
186192
|
+
writeFileSync8(pidPath, `${proc.pid}
|
|
186193
|
+
`);
|
|
186194
|
+
ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
|
|
186195
|
+
return {
|
|
186196
|
+
handle,
|
|
186197
|
+
outputPath,
|
|
186198
|
+
pidPath,
|
|
186199
|
+
message: `started background process ${handle} (pid ${proc.pid})`
|
|
186200
|
+
};
|
|
186201
|
+
}
|
|
186202
|
+
const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
|
|
186203
|
+
if (result.exitCode !== 0) {
|
|
186204
|
+
log.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
|
|
186205
|
+
if (result.output) log.info(`output: ${result.output}`);
|
|
186206
|
+
}
|
|
186207
|
+
return {
|
|
186208
|
+
output: capOutput(result.output),
|
|
186209
|
+
exit_code: result.exitCode,
|
|
186210
|
+
timed_out: result.timedOut
|
|
186211
|
+
};
|
|
186212
|
+
})
|
|
186213
|
+
});
|
|
186214
|
+
}
|
|
186215
|
+
var KillBackgroundParams = type({
|
|
186216
|
+
handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
|
|
186217
|
+
});
|
|
186218
|
+
function KillBackgroundTool(ctx) {
|
|
186219
|
+
return tool({
|
|
186220
|
+
name: "kill_background",
|
|
186221
|
+
mutates: true,
|
|
186222
|
+
description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
|
|
186223
|
+
parameters: KillBackgroundParams,
|
|
186224
|
+
execute: execute(async (params) => {
|
|
186225
|
+
const proc = ctx.toolState.backgroundProcesses.get(params.handle);
|
|
186226
|
+
if (!proc) {
|
|
186227
|
+
return {
|
|
186228
|
+
success: false,
|
|
186229
|
+
message: `no background process with handle ${params.handle}`
|
|
186230
|
+
};
|
|
186231
|
+
}
|
|
186232
|
+
try {
|
|
186233
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
186234
|
+
} catch {
|
|
186235
|
+
}
|
|
186236
|
+
await sleep2(200);
|
|
186237
|
+
try {
|
|
186238
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
186239
|
+
} catch {
|
|
186240
|
+
}
|
|
186241
|
+
ctx.toolState.backgroundProcesses.delete(params.handle);
|
|
186242
|
+
return {
|
|
186243
|
+
success: true,
|
|
186244
|
+
message: `killed background process ${params.handle} (pid ${proc.pid})`
|
|
186245
|
+
};
|
|
186246
|
+
})
|
|
186247
|
+
});
|
|
186248
|
+
}
|
|
186249
|
+
|
|
185259
186250
|
// utils/lifecycle.ts
|
|
185260
186251
|
function describeSetupFailure(failure2) {
|
|
185261
186252
|
if (!failure2) return "";
|
|
@@ -185277,44 +186268,47 @@ ${failure2.output || "(empty)"}`;
|
|
|
185277
186268
|
async function executeLifecycleHook(params) {
|
|
185278
186269
|
if (!params.script) return {};
|
|
185279
186270
|
log.info(`\xBB executing ${params.event} lifecycle hook...`);
|
|
186271
|
+
resolveTrustedGit();
|
|
185280
186272
|
const preHookTrackedCount = params.normalizeWorkingTreeAfter ? (await runGitLines(["diff", "--name-only", "HEAD"])).length : 0;
|
|
185281
186273
|
let result;
|
|
185282
186274
|
try {
|
|
186275
|
+
let sandboxed;
|
|
185283
186276
|
try {
|
|
185284
|
-
|
|
185285
|
-
|
|
185286
|
-
|
|
185287
|
-
|
|
186277
|
+
sandboxed = await runSandboxed({
|
|
186278
|
+
command: params.script,
|
|
186279
|
+
env: resolveEnv(params.shell === "enabled" ? "inherit" : "restricted"),
|
|
186280
|
+
cwd: process.cwd(),
|
|
185288
186281
|
timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
|
|
185289
|
-
activityTimeout: 0,
|
|
185290
186282
|
onStdout: (chunk) => process.stdout.write(chunk),
|
|
185291
186283
|
onStderr: (chunk) => process.stderr.write(chunk)
|
|
185292
186284
|
});
|
|
185293
|
-
if (spawnResult.exitCode !== 0) {
|
|
185294
|
-
const output2 = (spawnResult.stderr || spawnResult.stdout).trim();
|
|
185295
|
-
result = {
|
|
185296
|
-
failure: { kind: "exit", output: output2, exitCode: spawnResult.exitCode },
|
|
185297
|
-
warning: `lifecycle hook '${params.event}' failed with exit code ${spawnResult.exitCode}. output: ${output2 || "(empty)"}. retry the operation if the failure looks flaky (network blips, transient rate limits). do NOT retry if the script is broken (missing commands, syntax errors) or the error is persistent.`
|
|
185298
|
-
};
|
|
185299
|
-
} else {
|
|
185300
|
-
log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
|
|
185301
|
-
result = {};
|
|
185302
|
-
}
|
|
185303
186285
|
} catch (err) {
|
|
185304
|
-
const
|
|
185305
|
-
|
|
185306
|
-
|
|
185307
|
-
|
|
185308
|
-
|
|
185309
|
-
|
|
185310
|
-
|
|
185311
|
-
|
|
185312
|
-
|
|
185313
|
-
|
|
185314
|
-
|
|
185315
|
-
|
|
185316
|
-
|
|
185317
|
-
|
|
186286
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
186287
|
+
return {
|
|
186288
|
+
failure: { kind: "spawn", spawnError: msg },
|
|
186289
|
+
warning: `lifecycle hook '${params.event}' could not be sandboxed, so it did not run: ${msg}`
|
|
186290
|
+
};
|
|
186291
|
+
}
|
|
186292
|
+
if (sandboxed.timedOut) {
|
|
186293
|
+
const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
|
|
186294
|
+
result = {
|
|
186295
|
+
failure: { kind: "timeout" },
|
|
186296
|
+
warning: `lifecycle hook '${params.event}' timed out after ${minutes}min. do NOT retry \u2014 the script is likely hung or doing too much work. ask the repo owner to simplify the hook (e.g. move long-running work out of the hook, add caching, or split it).`
|
|
186297
|
+
};
|
|
186298
|
+
} else if (sandboxed.exitCode === -1) {
|
|
186299
|
+
result = {
|
|
186300
|
+
failure: { kind: "spawn", spawnError: sandboxed.output },
|
|
186301
|
+
warning: `lifecycle hook '${params.event}' failed to spawn: ${sandboxed.output}. this is likely a transient failure \u2014 retry the operation.`
|
|
186302
|
+
};
|
|
186303
|
+
} else if (sandboxed.exitCode !== 0) {
|
|
186304
|
+
const output2 = capOutput(sandboxed.output);
|
|
186305
|
+
result = {
|
|
186306
|
+
failure: { kind: "exit", output: output2, exitCode: sandboxed.exitCode },
|
|
186307
|
+
warning: `lifecycle hook '${params.event}' failed with exit code ${sandboxed.exitCode}. output: ${output2 || "(empty)"}. retry the operation if the failure looks flaky (network blips, transient rate limits). do NOT retry if the script is broken (missing commands, syntax errors) or the error is persistent. if it failed on a missing environment variable, the repo's env allowlist has to name it \u2014 hooks run with the same filtered env as the shell tool.`
|
|
186308
|
+
};
|
|
186309
|
+
} else {
|
|
186310
|
+
log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
|
|
186311
|
+
result = {};
|
|
185318
186312
|
}
|
|
185319
186313
|
} finally {
|
|
185320
186314
|
if (params.normalizeWorkingTreeAfter) {
|
|
@@ -185335,8 +186329,20 @@ async function normalizeWorkingTreeAfterHook(params) {
|
|
|
185335
186329
|
await runGit(["restore", "--staged", "--worktree", "."]);
|
|
185336
186330
|
log.info(`\xBB discarded ${trackedCount} tracked changes from ${params.event} hook`);
|
|
185337
186331
|
}
|
|
186332
|
+
var trustedGit;
|
|
186333
|
+
function resolveTrustedGit() {
|
|
186334
|
+
if (trustedGit) return trustedGit;
|
|
186335
|
+
const resolved = spawnSync7("sh", ["-c", "command -v git"], { encoding: "utf-8" });
|
|
186336
|
+
trustedGit = resolved.stdout?.trim() || "git";
|
|
186337
|
+
return trustedGit;
|
|
186338
|
+
}
|
|
185338
186339
|
async function runGit(args2) {
|
|
185339
|
-
const result = await spawn({
|
|
186340
|
+
const result = await spawn({
|
|
186341
|
+
cmd: resolveTrustedGit(),
|
|
186342
|
+
args: args2,
|
|
186343
|
+
env: filterEnvForUntrustedCode(),
|
|
186344
|
+
activityTimeout: 0
|
|
186345
|
+
});
|
|
185340
186346
|
if (result.exitCode !== 0) {
|
|
185341
186347
|
throw new Error(
|
|
185342
186348
|
`git ${args2.join(" ")} failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
|
|
@@ -185456,7 +186462,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
185456
186462
|
|
|
185457
186463
|
// yes/index.ts
|
|
185458
186464
|
import { performance as performance8 } from "node:perf_hooks";
|
|
185459
|
-
import { setTimeout as
|
|
186465
|
+
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
185460
186466
|
|
|
185461
186467
|
// node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
|
|
185462
186468
|
import { tracingChannel as G, channel as P } from "node:diagnostics_channel";
|
|
@@ -186250,7 +187256,7 @@ function _op(fn2, options) {
|
|
|
186250
187256
|
);
|
|
186251
187257
|
log2.info({ key, error: error62, attempt });
|
|
186252
187258
|
}
|
|
186253
|
-
await
|
|
187259
|
+
await sleep3(delay2);
|
|
186254
187260
|
}
|
|
186255
187261
|
}
|
|
186256
187262
|
}
|
|
@@ -186318,14 +187324,14 @@ function _op(fn2, options) {
|
|
|
186318
187324
|
}
|
|
186319
187325
|
|
|
186320
187326
|
// mcp/git.ts
|
|
186321
|
-
import { randomUUID as
|
|
186322
|
-
import { writeFileSync as
|
|
186323
|
-
import { join as
|
|
187327
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
187328
|
+
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
187329
|
+
import { join as join13 } from "node:path";
|
|
186324
187330
|
|
|
186325
187331
|
// utils/apiCommit.ts
|
|
186326
187332
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
186327
187333
|
import { lstat, readlink } from "node:fs/promises";
|
|
186328
|
-
import { join as
|
|
187334
|
+
import { join as join11 } from "node:path";
|
|
186329
187335
|
var GITHUB_API = "https://api.github.com";
|
|
186330
187336
|
var MAX_BLOB_BYTES = 30 * 1024 * 1024;
|
|
186331
187337
|
var BLOB_UPLOAD_CONCURRENCY = 8;
|
|
@@ -186390,7 +187396,7 @@ async function assertApiCommittable(files) {
|
|
|
186390
187396
|
const root = getRepoRoot();
|
|
186391
187397
|
const attrs = $(
|
|
186392
187398
|
"git",
|
|
186393
|
-
["check-attr", "filter", "-z", "--", ...present.map((p) =>
|
|
187399
|
+
["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
|
|
186394
187400
|
{ log: false }
|
|
186395
187401
|
);
|
|
186396
187402
|
const parts = attrs.split("\0");
|
|
@@ -186402,7 +187408,7 @@ async function assertApiCommittable(files) {
|
|
|
186402
187408
|
}
|
|
186403
187409
|
}
|
|
186404
187410
|
for (const path4 of present) {
|
|
186405
|
-
const stat = await lstat(
|
|
187411
|
+
const stat = await lstat(join11(root, path4));
|
|
186406
187412
|
if (stat.isDirectory()) {
|
|
186407
187413
|
throw new Error(
|
|
186408
187414
|
`'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
|
|
@@ -186411,7 +187417,7 @@ async function assertApiCommittable(files) {
|
|
|
186411
187417
|
}
|
|
186412
187418
|
}
|
|
186413
187419
|
async function createBlobEntry(params) {
|
|
186414
|
-
const absPath =
|
|
187420
|
+
const absPath = join11(params.repoRoot, params.path);
|
|
186415
187421
|
const stat = await lstat(absPath);
|
|
186416
187422
|
if (stat.size > MAX_BLOB_BYTES) {
|
|
186417
187423
|
throw new Error(
|
|
@@ -186591,7 +187597,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
|
|
|
186591
187597
|
var core5 = __toESM(require_core(), 1);
|
|
186592
187598
|
import { createSign } from "node:crypto";
|
|
186593
187599
|
import { rename, writeFile } from "node:fs/promises";
|
|
186594
|
-
import { dirname as dirname3, join as
|
|
187600
|
+
import { dirname as dirname3, join as join12 } from "node:path";
|
|
186595
187601
|
|
|
186596
187602
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
186597
187603
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -186963,7 +187969,7 @@ function lowercaseKeys(object5) {
|
|
|
186963
187969
|
return newObj;
|
|
186964
187970
|
}, {});
|
|
186965
187971
|
}
|
|
186966
|
-
function
|
|
187972
|
+
function isPlainObject7(value2) {
|
|
186967
187973
|
if (typeof value2 !== "object" || value2 === null) return false;
|
|
186968
187974
|
if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
|
|
186969
187975
|
const proto = Object.getPrototypeOf(value2);
|
|
@@ -186974,7 +187980,7 @@ function isPlainObject6(value2) {
|
|
|
186974
187980
|
function mergeDeep(defaults, options) {
|
|
186975
187981
|
const result = Object.assign({}, defaults);
|
|
186976
187982
|
Object.keys(options).forEach((key) => {
|
|
186977
|
-
if (
|
|
187983
|
+
if (isPlainObject7(options[key])) {
|
|
186978
187984
|
if (!(key in defaults)) Object.assign(result, { [key]: options[key] });
|
|
186979
187985
|
else result[key] = mergeDeep(defaults[key], options[key]);
|
|
186980
187986
|
} else {
|
|
@@ -187261,7 +188267,7 @@ var NullObject = /* @__PURE__ */ (() => {
|
|
|
187261
188267
|
return C;
|
|
187262
188268
|
})();
|
|
187263
188269
|
function parse5(header, options) {
|
|
187264
|
-
const stopChar = options?.comma === true ?
|
|
188270
|
+
const stopChar = options?.comma === true ? COMMA2 : 65536;
|
|
187265
188271
|
const len = header.length;
|
|
187266
188272
|
let index = skipOWS(header, options?.start ?? 0, len);
|
|
187267
188273
|
const valueStart = index;
|
|
@@ -187279,7 +188285,7 @@ var SEMI = 59;
|
|
|
187279
188285
|
var EQ = 61;
|
|
187280
188286
|
var DQUOTE = 34;
|
|
187281
188287
|
var BSLASH = 92;
|
|
187282
|
-
var
|
|
188288
|
+
var COMMA2 = 44;
|
|
187283
188289
|
function parseParameters(header, type2, index, len, stopChar) {
|
|
187284
188290
|
const parameters = new NullObject();
|
|
187285
188291
|
parameter: while (index < len) {
|
|
@@ -187738,7 +188744,7 @@ var defaults_default = {
|
|
|
187738
188744
|
"user-agent": `octokit-request.js/${VERSION3} ${getUserAgent2()}`
|
|
187739
188745
|
}
|
|
187740
188746
|
};
|
|
187741
|
-
function
|
|
188747
|
+
function isPlainObject8(value2) {
|
|
187742
188748
|
if (typeof value2 !== "object" || value2 === null) return false;
|
|
187743
188749
|
if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
|
|
187744
188750
|
const proto = Object.getPrototypeOf(value2);
|
|
@@ -187756,7 +188762,7 @@ async function fetchWrapper(requestOptions) {
|
|
|
187756
188762
|
}
|
|
187757
188763
|
const log3 = requestOptions.request?.log || console;
|
|
187758
188764
|
const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
|
|
187759
|
-
const body =
|
|
188765
|
+
const body = isPlainObject8(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
|
|
187760
188766
|
const requestHeaders = Object.fromEntries(
|
|
187761
188767
|
Object.entries(requestOptions.headers).map(([name, value2]) => [
|
|
187762
188768
|
name,
|
|
@@ -191061,7 +192067,7 @@ function getGitHubUsageSummary() {
|
|
|
191061
192067
|
}
|
|
191062
192068
|
async function writeGitHubUsageSummaryToFile(path4) {
|
|
191063
192069
|
const summary2 = getGitHubUsageSummary();
|
|
191064
|
-
const tmpPath =
|
|
192070
|
+
const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
|
|
191065
192071
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
191066
192072
|
await rename(tmpPath, path4);
|
|
191067
192073
|
}
|
|
@@ -191168,531 +192174,6 @@ function resolveRepoCtx(ctx, repo) {
|
|
|
191168
192174
|
};
|
|
191169
192175
|
}
|
|
191170
192176
|
|
|
191171
|
-
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
191172
|
-
var LIST_ITEM_MARKER = "-";
|
|
191173
|
-
var LIST_ITEM_PREFIX = "- ";
|
|
191174
|
-
var COMMA2 = ",";
|
|
191175
|
-
var PIPE = "|";
|
|
191176
|
-
var DOT = ".";
|
|
191177
|
-
var NULL_LITERAL = "null";
|
|
191178
|
-
var TRUE_LITERAL = "true";
|
|
191179
|
-
var FALSE_LITERAL = "false";
|
|
191180
|
-
var BACKSLASH = "\\";
|
|
191181
|
-
var DOUBLE_QUOTE = '"';
|
|
191182
|
-
var TAB = " ";
|
|
191183
|
-
var DELIMITERS = {
|
|
191184
|
-
comma: COMMA2,
|
|
191185
|
-
tab: TAB,
|
|
191186
|
-
pipe: PIPE
|
|
191187
|
-
};
|
|
191188
|
-
var DEFAULT_DELIMITER = DELIMITERS.comma;
|
|
191189
|
-
function escapeString(value2) {
|
|
191190
|
-
return value2.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`).replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`).replace(/\n/g, `${BACKSLASH}n`).replace(/\r/g, `${BACKSLASH}r`).replace(/\t/g, `${BACKSLASH}t`);
|
|
191191
|
-
}
|
|
191192
|
-
function isBooleanOrNullLiteral(token) {
|
|
191193
|
-
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
|
|
191194
|
-
}
|
|
191195
|
-
function normalizeValue(value2) {
|
|
191196
|
-
if (value2 === null) return null;
|
|
191197
|
-
if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
191198
|
-
if (typeof value2 === "number") {
|
|
191199
|
-
if (Object.is(value2, -0)) return 0;
|
|
191200
|
-
if (!Number.isFinite(value2)) return null;
|
|
191201
|
-
return value2;
|
|
191202
|
-
}
|
|
191203
|
-
if (typeof value2 === "bigint") {
|
|
191204
|
-
if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
|
|
191205
|
-
return value2.toString();
|
|
191206
|
-
}
|
|
191207
|
-
if (value2 instanceof Date) return value2.toISOString();
|
|
191208
|
-
if (Array.isArray(value2)) return value2.map(normalizeValue);
|
|
191209
|
-
if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
|
|
191210
|
-
if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
|
|
191211
|
-
if (isPlainObject8(value2)) {
|
|
191212
|
-
const normalized = {};
|
|
191213
|
-
for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
|
|
191214
|
-
return normalized;
|
|
191215
|
-
}
|
|
191216
|
-
return null;
|
|
191217
|
-
}
|
|
191218
|
-
function isJsonPrimitive(value2) {
|
|
191219
|
-
return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
|
|
191220
|
-
}
|
|
191221
|
-
function isJsonArray(value2) {
|
|
191222
|
-
return Array.isArray(value2);
|
|
191223
|
-
}
|
|
191224
|
-
function isJsonObject4(value2) {
|
|
191225
|
-
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
191226
|
-
}
|
|
191227
|
-
function isEmptyObject2(value2) {
|
|
191228
|
-
return Object.keys(value2).length === 0;
|
|
191229
|
-
}
|
|
191230
|
-
function isPlainObject8(value2) {
|
|
191231
|
-
if (value2 === null || typeof value2 !== "object") return false;
|
|
191232
|
-
const prototype = Object.getPrototypeOf(value2);
|
|
191233
|
-
return prototype === null || prototype === Object.prototype;
|
|
191234
|
-
}
|
|
191235
|
-
function isArrayOfPrimitives(value2) {
|
|
191236
|
-
return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
|
|
191237
|
-
}
|
|
191238
|
-
function isArrayOfArrays(value2) {
|
|
191239
|
-
return value2.length === 0 || value2.every((item) => isJsonArray(item));
|
|
191240
|
-
}
|
|
191241
|
-
function isArrayOfObjects(value2) {
|
|
191242
|
-
return value2.length === 0 || value2.every((item) => isJsonObject4(item));
|
|
191243
|
-
}
|
|
191244
|
-
function isValidUnquotedKey(key) {
|
|
191245
|
-
return /^[A-Z_][\w.]*$/i.test(key);
|
|
191246
|
-
}
|
|
191247
|
-
function isIdentifierSegment(key) {
|
|
191248
|
-
return /^[A-Z_]\w*$/i.test(key);
|
|
191249
|
-
}
|
|
191250
|
-
function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
|
|
191251
|
-
if (!value2) return false;
|
|
191252
|
-
if (value2 !== value2.trim()) return false;
|
|
191253
|
-
if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
|
|
191254
|
-
if (value2.includes(":")) return false;
|
|
191255
|
-
if (value2.includes('"') || value2.includes("\\")) return false;
|
|
191256
|
-
if (/[[\]{}]/.test(value2)) return false;
|
|
191257
|
-
if (/[\n\r\t]/.test(value2)) return false;
|
|
191258
|
-
if (value2.includes(delimiter2)) return false;
|
|
191259
|
-
if (value2.startsWith(LIST_ITEM_MARKER)) return false;
|
|
191260
|
-
return true;
|
|
191261
|
-
}
|
|
191262
|
-
function isNumericLike(value2) {
|
|
191263
|
-
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
|
|
191264
|
-
}
|
|
191265
|
-
var QUOTED_KEY_MARKER = Symbol("quotedKey");
|
|
191266
|
-
function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
191267
|
-
if (options.keyFolding !== "safe") return;
|
|
191268
|
-
if (!isJsonObject4(value2)) return;
|
|
191269
|
-
const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
|
|
191270
|
-
if (segments.length < 2) return;
|
|
191271
|
-
if (!segments.every((seg) => isIdentifierSegment(seg))) return;
|
|
191272
|
-
const foldedKey = buildFoldedKey(segments);
|
|
191273
|
-
const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
191274
|
-
if (siblings.includes(foldedKey)) return;
|
|
191275
|
-
if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
|
|
191276
|
-
return {
|
|
191277
|
-
foldedKey,
|
|
191278
|
-
remainder: tail,
|
|
191279
|
-
leafValue,
|
|
191280
|
-
segmentCount: segments.length
|
|
191281
|
-
};
|
|
191282
|
-
}
|
|
191283
|
-
function collectSingleKeyChain(startKey, startValue, maxDepth) {
|
|
191284
|
-
const segments = [startKey];
|
|
191285
|
-
let currentValue = startValue;
|
|
191286
|
-
while (segments.length < maxDepth) {
|
|
191287
|
-
if (!isJsonObject4(currentValue)) break;
|
|
191288
|
-
const keys = Object.keys(currentValue);
|
|
191289
|
-
if (keys.length !== 1) break;
|
|
191290
|
-
const nextKey = keys[0];
|
|
191291
|
-
const nextValue = currentValue[nextKey];
|
|
191292
|
-
segments.push(nextKey);
|
|
191293
|
-
currentValue = nextValue;
|
|
191294
|
-
}
|
|
191295
|
-
if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
|
|
191296
|
-
segments,
|
|
191297
|
-
tail: void 0,
|
|
191298
|
-
leafValue: currentValue
|
|
191299
|
-
};
|
|
191300
|
-
return {
|
|
191301
|
-
segments,
|
|
191302
|
-
tail: currentValue,
|
|
191303
|
-
leafValue: currentValue
|
|
191304
|
-
};
|
|
191305
|
-
}
|
|
191306
|
-
function buildFoldedKey(segments) {
|
|
191307
|
-
return segments.join(DOT);
|
|
191308
|
-
}
|
|
191309
|
-
function encodePrimitive(value2, delimiter2) {
|
|
191310
|
-
if (value2 === null) return NULL_LITERAL;
|
|
191311
|
-
if (typeof value2 === "boolean") return String(value2);
|
|
191312
|
-
if (typeof value2 === "number") return String(value2);
|
|
191313
|
-
return encodeStringLiteral(value2, delimiter2);
|
|
191314
|
-
}
|
|
191315
|
-
function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
|
|
191316
|
-
if (isSafeUnquoted(value2, delimiter2)) return value2;
|
|
191317
|
-
return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
|
|
191318
|
-
}
|
|
191319
|
-
function encodeKey(key) {
|
|
191320
|
-
if (isValidUnquotedKey(key)) return key;
|
|
191321
|
-
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
|
|
191322
|
-
}
|
|
191323
|
-
function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
|
|
191324
|
-
return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
|
|
191325
|
-
}
|
|
191326
|
-
function formatHeader(length, options) {
|
|
191327
|
-
const key = options?.key;
|
|
191328
|
-
const fields = options?.fields;
|
|
191329
|
-
const delimiter2 = options?.delimiter ?? COMMA2;
|
|
191330
|
-
let header = "";
|
|
191331
|
-
if (key) header += encodeKey(key);
|
|
191332
|
-
header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
|
|
191333
|
-
if (fields) {
|
|
191334
|
-
const quotedFields = fields.map((f) => encodeKey(f));
|
|
191335
|
-
header += `{${quotedFields.join(delimiter2)}}`;
|
|
191336
|
-
}
|
|
191337
|
-
header += ":";
|
|
191338
|
-
return header;
|
|
191339
|
-
}
|
|
191340
|
-
function* encodeJsonValue(value2, options, depth) {
|
|
191341
|
-
if (isJsonPrimitive(value2)) {
|
|
191342
|
-
const encodedPrimitive = encodePrimitive(value2, options.delimiter);
|
|
191343
|
-
if (encodedPrimitive !== "") yield encodedPrimitive;
|
|
191344
|
-
return;
|
|
191345
|
-
}
|
|
191346
|
-
if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
|
|
191347
|
-
else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
|
|
191348
|
-
}
|
|
191349
|
-
function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
|
|
191350
|
-
const keys = Object.keys(value2);
|
|
191351
|
-
if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
|
|
191352
|
-
const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
|
|
191353
|
-
for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
191354
|
-
}
|
|
191355
|
-
function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
191356
|
-
const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
|
|
191357
|
-
const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
|
|
191358
|
-
if (options.keyFolding === "safe" && siblings) {
|
|
191359
|
-
const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
191360
|
-
if (foldResult) {
|
|
191361
|
-
const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
|
|
191362
|
-
const encodedFoldedKey = encodeKey(foldedKey);
|
|
191363
|
-
if (remainder === void 0) {
|
|
191364
|
-
if (isJsonPrimitive(leafValue)) {
|
|
191365
|
-
yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
|
|
191366
|
-
return;
|
|
191367
|
-
} else if (isJsonArray(leafValue)) {
|
|
191368
|
-
yield* encodeArrayLines(foldedKey, leafValue, depth, options);
|
|
191369
|
-
return;
|
|
191370
|
-
} else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
|
|
191371
|
-
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
191372
|
-
return;
|
|
191373
|
-
}
|
|
191374
|
-
}
|
|
191375
|
-
if (isJsonObject4(remainder)) {
|
|
191376
|
-
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
191377
|
-
const remainingDepth = effectiveFlattenDepth - segmentCount;
|
|
191378
|
-
const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
191379
|
-
yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
|
|
191380
|
-
return;
|
|
191381
|
-
}
|
|
191382
|
-
}
|
|
191383
|
-
}
|
|
191384
|
-
const encodedKey = encodeKey(key);
|
|
191385
|
-
if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
|
|
191386
|
-
else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
|
|
191387
|
-
else if (isJsonObject4(value2)) {
|
|
191388
|
-
yield indentedLine(depth, `${encodedKey}:`, options.indent);
|
|
191389
|
-
if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
|
|
191390
|
-
}
|
|
191391
|
-
}
|
|
191392
|
-
function* encodeArrayLines(key, value2, depth, options) {
|
|
191393
|
-
if (value2.length === 0) {
|
|
191394
|
-
yield indentedLine(depth, formatHeader(0, {
|
|
191395
|
-
key,
|
|
191396
|
-
delimiter: options.delimiter
|
|
191397
|
-
}), options.indent);
|
|
191398
|
-
return;
|
|
191399
|
-
}
|
|
191400
|
-
if (isArrayOfPrimitives(value2)) {
|
|
191401
|
-
yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
|
|
191402
|
-
return;
|
|
191403
|
-
}
|
|
191404
|
-
if (isArrayOfArrays(value2)) {
|
|
191405
|
-
if (value2.every((arr) => isArrayOfPrimitives(arr))) {
|
|
191406
|
-
yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
|
|
191407
|
-
return;
|
|
191408
|
-
}
|
|
191409
|
-
}
|
|
191410
|
-
if (isArrayOfObjects(value2)) {
|
|
191411
|
-
const header = extractTabularHeader(value2);
|
|
191412
|
-
if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
|
|
191413
|
-
else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
191414
|
-
return;
|
|
191415
|
-
}
|
|
191416
|
-
yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
191417
|
-
}
|
|
191418
|
-
function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
|
|
191419
|
-
yield indentedLine(depth, formatHeader(values.length, {
|
|
191420
|
-
key: prefix,
|
|
191421
|
-
delimiter: options.delimiter
|
|
191422
|
-
}), options.indent);
|
|
191423
|
-
for (const arr of values) if (isArrayOfPrimitives(arr)) {
|
|
191424
|
-
const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
|
|
191425
|
-
yield indentedListItem(depth + 1, arrayLine, options.indent);
|
|
191426
|
-
}
|
|
191427
|
-
}
|
|
191428
|
-
function encodeInlineArrayLine(values, delimiter2, prefix) {
|
|
191429
|
-
const header = formatHeader(values.length, {
|
|
191430
|
-
key: prefix,
|
|
191431
|
-
delimiter: delimiter2
|
|
191432
|
-
});
|
|
191433
|
-
const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
|
|
191434
|
-
if (values.length === 0) return header;
|
|
191435
|
-
return `${header} ${joinedValue}`;
|
|
191436
|
-
}
|
|
191437
|
-
function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
|
|
191438
|
-
yield indentedLine(depth, formatHeader(rows.length, {
|
|
191439
|
-
key: prefix,
|
|
191440
|
-
fields: header,
|
|
191441
|
-
delimiter: options.delimiter
|
|
191442
|
-
}), options.indent);
|
|
191443
|
-
yield* writeTabularRowsLines(rows, header, depth + 1, options);
|
|
191444
|
-
}
|
|
191445
|
-
function extractTabularHeader(rows) {
|
|
191446
|
-
if (rows.length === 0) return;
|
|
191447
|
-
const firstRow = rows[0];
|
|
191448
|
-
const firstKeys = Object.keys(firstRow);
|
|
191449
|
-
if (firstKeys.length === 0) return;
|
|
191450
|
-
if (isTabularArray(rows, firstKeys)) return firstKeys;
|
|
191451
|
-
}
|
|
191452
|
-
function isTabularArray(rows, header) {
|
|
191453
|
-
for (const row of rows) {
|
|
191454
|
-
if (Object.keys(row).length !== header.length) return false;
|
|
191455
|
-
for (const key of header) {
|
|
191456
|
-
if (!(key in row)) return false;
|
|
191457
|
-
if (!isJsonPrimitive(row[key])) return false;
|
|
191458
|
-
}
|
|
191459
|
-
}
|
|
191460
|
-
return true;
|
|
191461
|
-
}
|
|
191462
|
-
function* writeTabularRowsLines(rows, header, depth, options) {
|
|
191463
|
-
for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
|
|
191464
|
-
}
|
|
191465
|
-
function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
|
|
191466
|
-
yield indentedLine(depth, formatHeader(items.length, {
|
|
191467
|
-
key: prefix,
|
|
191468
|
-
delimiter: options.delimiter
|
|
191469
|
-
}), options.indent);
|
|
191470
|
-
for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
191471
|
-
}
|
|
191472
|
-
function* encodeObjectAsListItemLines(obj, depth, options) {
|
|
191473
|
-
if (isEmptyObject2(obj)) {
|
|
191474
|
-
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
191475
|
-
return;
|
|
191476
|
-
}
|
|
191477
|
-
const entries = Object.entries(obj);
|
|
191478
|
-
if (entries.length === 1) {
|
|
191479
|
-
const [key, value2] = entries[0];
|
|
191480
|
-
if (isJsonArray(value2) && isArrayOfObjects(value2)) {
|
|
191481
|
-
const header = extractTabularHeader(value2);
|
|
191482
|
-
if (header) {
|
|
191483
|
-
yield indentedListItem(depth, formatHeader(value2.length, {
|
|
191484
|
-
key,
|
|
191485
|
-
fields: header,
|
|
191486
|
-
delimiter: options.delimiter
|
|
191487
|
-
}), options.indent);
|
|
191488
|
-
yield* writeTabularRowsLines(value2, header, depth + 1, options);
|
|
191489
|
-
return;
|
|
191490
|
-
}
|
|
191491
|
-
}
|
|
191492
|
-
}
|
|
191493
|
-
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
191494
|
-
yield* encodeObjectLines(obj, depth + 1, options);
|
|
191495
|
-
}
|
|
191496
|
-
function* encodeListItemValueLines(value2, depth, options) {
|
|
191497
|
-
if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
|
|
191498
|
-
else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
|
|
191499
|
-
else {
|
|
191500
|
-
yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
|
|
191501
|
-
for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
191502
|
-
}
|
|
191503
|
-
else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
|
|
191504
|
-
}
|
|
191505
|
-
function indentedLine(depth, content, indentSize) {
|
|
191506
|
-
return " ".repeat(indentSize * depth) + content;
|
|
191507
|
-
}
|
|
191508
|
-
function indentedListItem(depth, content, indentSize) {
|
|
191509
|
-
return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
|
|
191510
|
-
}
|
|
191511
|
-
function encode4(input2, options) {
|
|
191512
|
-
return Array.from(encodeLines(input2, options)).join("\n");
|
|
191513
|
-
}
|
|
191514
|
-
function encodeLines(input2, options) {
|
|
191515
|
-
return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
|
|
191516
|
-
}
|
|
191517
|
-
function resolveOptions(options) {
|
|
191518
|
-
return {
|
|
191519
|
-
indent: options?.indent ?? 2,
|
|
191520
|
-
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
|
|
191521
|
-
keyFolding: options?.keyFolding ?? "off",
|
|
191522
|
-
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
|
|
191523
|
-
};
|
|
191524
|
-
}
|
|
191525
|
-
|
|
191526
|
-
// mcp/geminiSanitizer.ts
|
|
191527
|
-
function parseStringEnumBranch(item) {
|
|
191528
|
-
if (!item || typeof item !== "object") return null;
|
|
191529
|
-
const record3 = item;
|
|
191530
|
-
if (Array.isArray(record3.enum)) {
|
|
191531
|
-
const strings = record3.enum.filter((v) => typeof v === "string");
|
|
191532
|
-
return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
|
|
191533
|
-
}
|
|
191534
|
-
if (typeof record3.const === "string") {
|
|
191535
|
-
return { values: [record3.const] };
|
|
191536
|
-
}
|
|
191537
|
-
return null;
|
|
191538
|
-
}
|
|
191539
|
-
function collapseStringUnion(branches) {
|
|
191540
|
-
const values = [];
|
|
191541
|
-
for (const item of branches) {
|
|
191542
|
-
const parsed2 = parseStringEnumBranch(item);
|
|
191543
|
-
if (!parsed2) return null;
|
|
191544
|
-
values.push(...parsed2.values);
|
|
191545
|
-
}
|
|
191546
|
-
if (values.length === 0) return null;
|
|
191547
|
-
return { type: "string", enum: [...new Set(values)] };
|
|
191548
|
-
}
|
|
191549
|
-
function sanitizeForGemini(schema2) {
|
|
191550
|
-
if (!schema2 || typeof schema2 !== "object") return schema2;
|
|
191551
|
-
if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
|
|
191552
|
-
const source = schema2;
|
|
191553
|
-
if (Array.isArray(source.enum) && typeof source.type !== "string") {
|
|
191554
|
-
const allStrings = source.enum.every((v) => typeof v === "string");
|
|
191555
|
-
if (allStrings) {
|
|
191556
|
-
const result = { type: "string", enum: source.enum };
|
|
191557
|
-
if (typeof source.description === "string") result.description = source.description;
|
|
191558
|
-
return result;
|
|
191559
|
-
}
|
|
191560
|
-
}
|
|
191561
|
-
for (const unionKey of ["anyOf", "oneOf"]) {
|
|
191562
|
-
const branches = source[unionKey];
|
|
191563
|
-
if (Array.isArray(branches) && branches.length > 0) {
|
|
191564
|
-
const collapsed = collapseStringUnion(branches);
|
|
191565
|
-
if (collapsed) {
|
|
191566
|
-
const result = { ...collapsed };
|
|
191567
|
-
if (typeof source.description === "string") result.description = source.description;
|
|
191568
|
-
return result;
|
|
191569
|
-
}
|
|
191570
|
-
}
|
|
191571
|
-
}
|
|
191572
|
-
if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
|
|
191573
|
-
const result = {};
|
|
191574
|
-
if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
|
|
191575
|
-
if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
|
|
191576
|
-
return result;
|
|
191577
|
-
}
|
|
191578
|
-
const sanitized = {};
|
|
191579
|
-
for (const [key, value2] of Object.entries(source)) {
|
|
191580
|
-
if (key === "$schema") continue;
|
|
191581
|
-
if (key === "$defs") {
|
|
191582
|
-
sanitized.definitions = sanitizeForGemini(value2);
|
|
191583
|
-
continue;
|
|
191584
|
-
}
|
|
191585
|
-
sanitized[key] = sanitizeForGemini(value2);
|
|
191586
|
-
}
|
|
191587
|
-
return sanitized;
|
|
191588
|
-
}
|
|
191589
|
-
function wrapJsonSchemaProducer(producer) {
|
|
191590
|
-
return new Proxy(producer, {
|
|
191591
|
-
get(target, prop, receiver) {
|
|
191592
|
-
const value2 = Reflect.get(target, prop, receiver);
|
|
191593
|
-
if ((prop === "input" || prop === "output") && typeof value2 === "function") {
|
|
191594
|
-
const fn2 = value2;
|
|
191595
|
-
return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
|
|
191596
|
-
}
|
|
191597
|
-
return value2;
|
|
191598
|
-
}
|
|
191599
|
-
});
|
|
191600
|
-
}
|
|
191601
|
-
function wrapStandard(standard) {
|
|
191602
|
-
return new Proxy(standard, {
|
|
191603
|
-
get(target, prop, receiver) {
|
|
191604
|
-
if (prop === "jsonSchema") {
|
|
191605
|
-
const value2 = Reflect.get(target, prop, receiver);
|
|
191606
|
-
if (value2 && typeof value2 === "object") {
|
|
191607
|
-
return wrapJsonSchemaProducer(value2);
|
|
191608
|
-
}
|
|
191609
|
-
return value2;
|
|
191610
|
-
}
|
|
191611
|
-
return Reflect.get(target, prop, receiver);
|
|
191612
|
-
}
|
|
191613
|
-
});
|
|
191614
|
-
}
|
|
191615
|
-
function wrapSchemaForGemini(schema2) {
|
|
191616
|
-
return new Proxy(schema2, {
|
|
191617
|
-
get(target, prop, receiver) {
|
|
191618
|
-
if (prop === "~standard") {
|
|
191619
|
-
const value2 = Reflect.get(target, prop, receiver);
|
|
191620
|
-
if (value2 && typeof value2 === "object") {
|
|
191621
|
-
return wrapStandard(value2);
|
|
191622
|
-
}
|
|
191623
|
-
return value2;
|
|
191624
|
-
}
|
|
191625
|
-
if (prop === "toJsonSchema") {
|
|
191626
|
-
const method = Reflect.get(target, prop, receiver);
|
|
191627
|
-
if (typeof method === "function") {
|
|
191628
|
-
return () => sanitizeForGemini(method.call(target));
|
|
191629
|
-
}
|
|
191630
|
-
return method;
|
|
191631
|
-
}
|
|
191632
|
-
return Reflect.get(target, prop, receiver);
|
|
191633
|
-
}
|
|
191634
|
-
});
|
|
191635
|
-
}
|
|
191636
|
-
function sanitizeToolForGemini(tool2) {
|
|
191637
|
-
if (!tool2.parameters) return tool2;
|
|
191638
|
-
return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
|
|
191639
|
-
}
|
|
191640
|
-
function isGeminiRouted(ctx) {
|
|
191641
|
-
const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
|
|
191642
|
-
if (!effective) return true;
|
|
191643
|
-
const normalized = effective.toLowerCase();
|
|
191644
|
-
if (normalized.includes("gemini")) return true;
|
|
191645
|
-
if (!normalized.includes("/")) return true;
|
|
191646
|
-
return false;
|
|
191647
|
-
}
|
|
191648
|
-
|
|
191649
|
-
// mcp/shared.ts
|
|
191650
|
-
function getHttpStatus(err) {
|
|
191651
|
-
if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
|
|
191652
|
-
return typeof err.status === "number" ? err.status : void 0;
|
|
191653
|
-
}
|
|
191654
|
-
var tool = (toolDef) => toolDef;
|
|
191655
|
-
var handleToolSuccess = (data) => {
|
|
191656
|
-
const text = typeof data === "string" ? data : encode4(data);
|
|
191657
|
-
return {
|
|
191658
|
-
content: [{ type: "text", text }]
|
|
191659
|
-
};
|
|
191660
|
-
};
|
|
191661
|
-
var handleToolError = (error62) => {
|
|
191662
|
-
const errorMessage = error62 instanceof Error ? error62.message : String(error62);
|
|
191663
|
-
return {
|
|
191664
|
-
content: [
|
|
191665
|
-
{
|
|
191666
|
-
type: "text",
|
|
191667
|
-
text: `Error: ${errorMessage}`
|
|
191668
|
-
}
|
|
191669
|
-
],
|
|
191670
|
-
isError: true
|
|
191671
|
-
};
|
|
191672
|
-
};
|
|
191673
|
-
var execute = (fn2, toolName) => {
|
|
191674
|
-
const _fn = async (params) => {
|
|
191675
|
-
try {
|
|
191676
|
-
const result = await fn2(params);
|
|
191677
|
-
return handleToolSuccess(result);
|
|
191678
|
-
} catch (error62) {
|
|
191679
|
-
const errorMessage = error62 instanceof Error ? error62.message : String(error62);
|
|
191680
|
-
const prefix = toolName ? `[${toolName}]` : "tool";
|
|
191681
|
-
log.info(`${prefix} error: ${errorMessage}`);
|
|
191682
|
-
log.debug(`${prefix} params: ${formatJsonValue(params)}`);
|
|
191683
|
-
return handleToolError(error62);
|
|
191684
|
-
}
|
|
191685
|
-
};
|
|
191686
|
-
return _fn;
|
|
191687
|
-
};
|
|
191688
|
-
var addTools = (ctx, server, tools) => {
|
|
191689
|
-
const shouldSanitize = isGeminiRouted(ctx);
|
|
191690
|
-
for (const tool2 of tools) {
|
|
191691
|
-
server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
|
|
191692
|
-
}
|
|
191693
|
-
return server;
|
|
191694
|
-
};
|
|
191695
|
-
|
|
191696
192177
|
// mcp/git.ts
|
|
191697
192178
|
function getPushDestination(branch, storedDest, cwd) {
|
|
191698
192179
|
if (storedDest && storedDest.localBranch === branch) {
|
|
@@ -191818,7 +192299,8 @@ async function runPrepushHook(ctx, retryTool) {
|
|
|
191818
192299
|
if (!ctx.prepushScript) return false;
|
|
191819
192300
|
const prepushHook = await executeLifecycleHook({
|
|
191820
192301
|
event: "prepush",
|
|
191821
|
-
script: ctx.prepushScript
|
|
192302
|
+
script: ctx.prepushScript,
|
|
192303
|
+
shell: ctx.payload.shell
|
|
191822
192304
|
});
|
|
191823
192305
|
if (prepushHook.failure) {
|
|
191824
192306
|
ctx.toolState.prepushFailureCount += 1;
|
|
@@ -192213,8 +192695,8 @@ function countAhead(head, base, cwd) {
|
|
|
192213
192695
|
function spillGitOutput(params) {
|
|
192214
192696
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
192215
192697
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
|
|
192216
|
-
const outputPath =
|
|
192217
|
-
|
|
192698
|
+
const outputPath = join13(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
|
|
192699
|
+
writeFileSync9(outputPath, params.output);
|
|
192218
192700
|
const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
|
|
192219
192701
|
const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
|
|
192220
192702
|
log.info(
|
|
@@ -193740,9 +194222,9 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
193740
194222
|
import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
|
|
193741
194223
|
import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
193742
194224
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
193743
|
-
import { join as
|
|
194225
|
+
import { join as join14 } from "node:path";
|
|
193744
194226
|
function createTempDirectory() {
|
|
193745
|
-
const sharedTempDir = mkdtempSync(
|
|
194227
|
+
const sharedTempDir = mkdtempSync(join14(tmpdir2(), "pullfrog-"));
|
|
193746
194228
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
193747
194229
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
193748
194230
|
return sharedTempDir;
|
|
@@ -193787,13 +194269,13 @@ function wipeRunnerLeakSurface() {
|
|
|
193787
194269
|
return [];
|
|
193788
194270
|
}
|
|
193789
194271
|
};
|
|
193790
|
-
const fileCommandsDir =
|
|
194272
|
+
const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
|
|
193791
194273
|
for (const entry of listDir(fileCommandsDir)) {
|
|
193792
|
-
tryUnlink(
|
|
194274
|
+
tryUnlink(join14(fileCommandsDir, entry));
|
|
193793
194275
|
}
|
|
193794
194276
|
for (const entry of listDir(runnerTemp)) {
|
|
193795
194277
|
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
193796
|
-
tryUnlink(
|
|
194278
|
+
tryUnlink(join14(runnerTemp, entry));
|
|
193797
194279
|
}
|
|
193798
194280
|
}
|
|
193799
194281
|
if (wiped.length > 0) {
|
|
@@ -194268,6 +194750,7 @@ async function checkoutPrBranch(pr, params) {
|
|
|
194268
194750
|
const postCheckoutHook = await executeLifecycleHook({
|
|
194269
194751
|
event: "post-checkout",
|
|
194270
194752
|
script: params.postCheckoutScript,
|
|
194753
|
+
shell: params.shell,
|
|
194271
194754
|
normalizeWorkingTreeAfter: true
|
|
194272
194755
|
});
|
|
194273
194756
|
return { hookWarning: postCheckoutHook.warning };
|
|
@@ -194349,11 +194832,11 @@ function CheckoutPrTool(ctx) {
|
|
|
194349
194832
|
headSha: checkoutSha
|
|
194350
194833
|
});
|
|
194351
194834
|
if (incremental.status === "ok") {
|
|
194352
|
-
incrementalDiffPath =
|
|
194835
|
+
incrementalDiffPath = join15(
|
|
194353
194836
|
tempDir,
|
|
194354
194837
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
194355
194838
|
);
|
|
194356
|
-
|
|
194839
|
+
writeFileSync10(incrementalDiffPath, incremental.diff);
|
|
194357
194840
|
log.info(
|
|
194358
194841
|
`\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
194359
194842
|
);
|
|
@@ -194366,8 +194849,8 @@ function CheckoutPrTool(ctx) {
|
|
|
194366
194849
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
194367
194850
|
log.debug(`formatted diff preview (first 100 lines):
|
|
194368
194851
|
${diffPreview}`);
|
|
194369
|
-
const diffPath =
|
|
194370
|
-
|
|
194852
|
+
const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
194853
|
+
writeFileSync10(diffPath, formatResult.content);
|
|
194371
194854
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
194372
194855
|
primary.diffCoverage = createDiffCoverageState({
|
|
194373
194856
|
diffPath,
|
|
@@ -194524,8 +195007,8 @@ ${dirty}`
|
|
|
194524
195007
|
}
|
|
194525
195008
|
|
|
194526
195009
|
// mcp/checkSuite.ts
|
|
194527
|
-
import { mkdirSync as mkdirSync8, writeFileSync as
|
|
194528
|
-
import { join as
|
|
195010
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
|
|
195011
|
+
import { join as join16 } from "node:path";
|
|
194529
195012
|
var GetCheckSuiteLogs = type({
|
|
194530
195013
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
194531
195014
|
});
|
|
@@ -194621,7 +195104,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
194621
195104
|
if (!tempDir) {
|
|
194622
195105
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
194623
195106
|
}
|
|
194624
|
-
const logsDir =
|
|
195107
|
+
const logsDir = join16(tempDir, "ci-logs");
|
|
194625
195108
|
mkdirSync8(logsDir, { recursive: true });
|
|
194626
195109
|
const jobResults = [];
|
|
194627
195110
|
for (const run of failedRuns) {
|
|
@@ -194648,8 +195131,8 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
194648
195131
|
);
|
|
194649
195132
|
}
|
|
194650
195133
|
const logsText = await logsResult.text();
|
|
194651
|
-
const logPath =
|
|
194652
|
-
|
|
195134
|
+
const logPath = join16(logsDir, `job-${job.id}.log`);
|
|
195135
|
+
writeFileSync11(logPath, logsText);
|
|
194653
195136
|
const analysis = analyzeLog(logsText, 80);
|
|
194654
195137
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
194655
195138
|
jobResults.push({
|
|
@@ -194697,8 +195180,8 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
194697
195180
|
}
|
|
194698
195181
|
|
|
194699
195182
|
// mcp/commitInfo.ts
|
|
194700
|
-
import { writeFileSync as
|
|
194701
|
-
import { join as
|
|
195183
|
+
import { writeFileSync as writeFileSync12 } from "node:fs";
|
|
195184
|
+
import { join as join17 } from "node:path";
|
|
194702
195185
|
var CommitInfo = type({
|
|
194703
195186
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
194704
195187
|
});
|
|
@@ -194722,8 +195205,8 @@ function CommitInfoTool(ctx) {
|
|
|
194722
195205
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
194723
195206
|
);
|
|
194724
195207
|
}
|
|
194725
|
-
const diffFile =
|
|
194726
|
-
|
|
195208
|
+
const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
195209
|
+
writeFileSync12(diffFile, formatResult.content);
|
|
194727
195210
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
194728
195211
|
return {
|
|
194729
195212
|
sha: data.sha,
|
|
@@ -194860,13 +195343,13 @@ import { performance as performance9 } from "node:perf_hooks";
|
|
|
194860
195343
|
|
|
194861
195344
|
// prep/installNodeDependencies.ts
|
|
194862
195345
|
import { existsSync as existsSync8 } from "node:fs";
|
|
194863
|
-
import { join as
|
|
195346
|
+
import { join as join19 } from "node:path";
|
|
194864
195347
|
|
|
194865
195348
|
// utils/packageManager.ts
|
|
194866
195349
|
var import_semver2 = __toESM(require_semver2(), 1);
|
|
194867
195350
|
import { existsSync as existsSync7 } from "node:fs";
|
|
194868
195351
|
import { mkdir, readFile as readFile2 } from "node:fs/promises";
|
|
194869
|
-
import { delimiter, join as
|
|
195352
|
+
import { delimiter, join as join18 } from "node:path";
|
|
194870
195353
|
var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
|
|
194871
195354
|
var COREPACK_MANAGED = ["pnpm", "yarn"];
|
|
194872
195355
|
function isSupported(name) {
|
|
@@ -194904,7 +195387,7 @@ function parseDevEnginesField(field) {
|
|
|
194904
195387
|
};
|
|
194905
195388
|
}
|
|
194906
195389
|
async function resolvePackageManagerSpec(cwd) {
|
|
194907
|
-
const pkgPath =
|
|
195390
|
+
const pkgPath = join18(cwd, "package.json");
|
|
194908
195391
|
if (!existsSync7(pkgPath)) return null;
|
|
194909
195392
|
let pkg;
|
|
194910
195393
|
try {
|
|
@@ -194963,7 +195446,7 @@ async function currentVersion(name) {
|
|
|
194963
195446
|
return result.stdout.trim();
|
|
194964
195447
|
}
|
|
194965
195448
|
function packageManagerBinDir(tmpdir3) {
|
|
194966
|
-
return
|
|
195449
|
+
return join18(tmpdir3, "pm-bin");
|
|
194967
195450
|
}
|
|
194968
195451
|
async function ensurePackageManager(params) {
|
|
194969
195452
|
const spec = params.spec;
|
|
@@ -195026,7 +195509,7 @@ async function installFallback(name, installSpec) {
|
|
|
195026
195509
|
});
|
|
195027
195510
|
if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
|
|
195028
195511
|
if (name === "deno") {
|
|
195029
|
-
const denoPath =
|
|
195512
|
+
const denoPath = join18(process.env.HOME || "", ".deno", "bin");
|
|
195030
195513
|
process.env.PATH = `${denoPath}:${process.env.PATH}`;
|
|
195031
195514
|
}
|
|
195032
195515
|
log.info(`\xBB installed ${name}`);
|
|
@@ -195059,7 +195542,7 @@ async function isCommandAvailable2(command) {
|
|
|
195059
195542
|
var installNodeDependencies = {
|
|
195060
195543
|
name: "installNodeDependencies",
|
|
195061
195544
|
shouldRun: () => {
|
|
195062
|
-
const packageJsonPath =
|
|
195545
|
+
const packageJsonPath = join19(process.cwd(), "package.json");
|
|
195063
195546
|
return existsSync8(packageJsonPath);
|
|
195064
195547
|
},
|
|
195065
195548
|
run: async (options) => {
|
|
@@ -195155,12 +195638,12 @@ ${errorMessage}`]
|
|
|
195155
195638
|
|
|
195156
195639
|
// prep/installPythonDependencies.ts
|
|
195157
195640
|
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
|
|
195158
|
-
import { join as
|
|
195641
|
+
import { join as join20 } from "node:path";
|
|
195159
195642
|
function declaresBuildSystem(path4) {
|
|
195160
195643
|
return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
|
|
195161
195644
|
}
|
|
195162
195645
|
function configApplies(config3, cwd) {
|
|
195163
|
-
const path4 =
|
|
195646
|
+
const path4 = join20(cwd, config3.file);
|
|
195164
195647
|
return existsSync9(path4) && (config3.applies?.(path4) ?? true);
|
|
195165
195648
|
}
|
|
195166
195649
|
var PYTHON_CONFIGS = [
|
|
@@ -195484,411 +195967,6 @@ function AwaitDependencyInstallationTool(ctx) {
|
|
|
195484
195967
|
// mcp/gh.ts
|
|
195485
195968
|
import { mkdirSync as mkdirSync9 } from "node:fs";
|
|
195486
195969
|
import { join as join21 } from "node:path";
|
|
195487
|
-
|
|
195488
|
-
// mcp/shell.ts
|
|
195489
|
-
import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
|
|
195490
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
195491
|
-
import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
|
|
195492
|
-
import { userInfo as userInfo2 } from "node:os";
|
|
195493
|
-
import { join as join20 } from "node:path";
|
|
195494
|
-
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
195495
|
-
var ShellParams = type({
|
|
195496
|
-
command: "string",
|
|
195497
|
-
// advisory and unread — no code path consumes it. kept in the schema only
|
|
195498
|
-
// because it nudges the model to state intent before running a command;
|
|
195499
|
-
// REQUIRING it cost a full rejected turn every time one was omitted. #1140.
|
|
195500
|
-
"description?": "string",
|
|
195501
|
-
"timeout?": type.number.describe(
|
|
195502
|
-
"Timeout in MILLISECONDS (not seconds). Default 30000 (30s), max 120000 (2m). e.g. timeout: 180000 for 3 minutes; timeout: 180 means 180ms and will kill the process almost immediately."
|
|
195503
|
-
),
|
|
195504
|
-
"working_directory?": "string",
|
|
195505
|
-
"background?": "boolean"
|
|
195506
|
-
});
|
|
195507
|
-
var detectedSandboxMethod;
|
|
195508
|
-
var usernsProbeError;
|
|
195509
|
-
function getSandboxMethod() {
|
|
195510
|
-
return detectSandboxMethod();
|
|
195511
|
-
}
|
|
195512
|
-
function detectSandboxMethod() {
|
|
195513
|
-
if (detectedSandboxMethod !== void 0) {
|
|
195514
|
-
return detectedSandboxMethod;
|
|
195515
|
-
}
|
|
195516
|
-
if (process.env.CI !== "true") {
|
|
195517
|
-
detectedSandboxMethod = "none";
|
|
195518
|
-
log.debug("sandbox disabled (CI !== true)");
|
|
195519
|
-
return "none";
|
|
195520
|
-
}
|
|
195521
|
-
try {
|
|
195522
|
-
const result = spawnSync6("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
|
|
195523
|
-
timeout: 5e3,
|
|
195524
|
-
stdio: "ignore"
|
|
195525
|
-
});
|
|
195526
|
-
if (result.status === 0) {
|
|
195527
|
-
detectedSandboxMethod = "unshare";
|
|
195528
|
-
log.debug("PID namespace isolation enabled (unprivileged unshare)");
|
|
195529
|
-
return "unshare";
|
|
195530
|
-
}
|
|
195531
|
-
} catch {
|
|
195532
|
-
}
|
|
195533
|
-
try {
|
|
195534
|
-
const result = spawnSync6("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
|
|
195535
|
-
timeout: 5e3,
|
|
195536
|
-
stdio: "ignore"
|
|
195537
|
-
});
|
|
195538
|
-
if (result.status === 0) {
|
|
195539
|
-
detectedSandboxMethod = "sudo-unshare";
|
|
195540
|
-
log.debug("PID namespace isolation enabled (sudo unshare)");
|
|
195541
|
-
return "sudo-unshare";
|
|
195542
|
-
}
|
|
195543
|
-
} catch {
|
|
195544
|
-
}
|
|
195545
|
-
try {
|
|
195546
|
-
const result = spawnSync6(
|
|
195547
|
-
"unshare",
|
|
195548
|
-
[
|
|
195549
|
-
"--user",
|
|
195550
|
-
"--map-root-user",
|
|
195551
|
-
"--pid",
|
|
195552
|
-
"--fork",
|
|
195553
|
-
"--mount",
|
|
195554
|
-
"setpriv",
|
|
195555
|
-
"--no-new-privs",
|
|
195556
|
-
"--bounding-set",
|
|
195557
|
-
"-all",
|
|
195558
|
-
"--inh-caps",
|
|
195559
|
-
"-all",
|
|
195560
|
-
"--ambient-caps",
|
|
195561
|
-
"-all",
|
|
195562
|
-
"true"
|
|
195563
|
-
],
|
|
195564
|
-
{ timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
|
|
195565
|
-
);
|
|
195566
|
-
if (result.status === 0) {
|
|
195567
|
-
detectedSandboxMethod = "userns-unshare";
|
|
195568
|
-
log.debug("PID namespace isolation enabled (unprivileged userns unshare)");
|
|
195569
|
-
return "userns-unshare";
|
|
195570
|
-
}
|
|
195571
|
-
usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
|
|
195572
|
-
} catch {
|
|
195573
|
-
}
|
|
195574
|
-
detectedSandboxMethod = "none";
|
|
195575
|
-
log.info("PID namespace isolation not available");
|
|
195576
|
-
return "none";
|
|
195577
|
-
}
|
|
195578
|
-
var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
|
195579
|
-
var SOCKET_CLEANUP = [
|
|
195580
|
-
"/var/run/docker.sock",
|
|
195581
|
-
"/run/docker.sock",
|
|
195582
|
-
"/var/run/podman/podman.sock",
|
|
195583
|
-
"/run/podman/podman.sock",
|
|
195584
|
-
"/run/containerd/containerd.sock",
|
|
195585
|
-
"/var/run/crio/crio.sock"
|
|
195586
|
-
].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
|
|
195587
|
-
function buildFsMounts(repoDir) {
|
|
195588
|
-
const escaped = repoDir.replace(/'/g, "'\\''");
|
|
195589
|
-
return [
|
|
195590
|
-
`mkdir -p /var/lib/pullfrog 2>/dev/null;`,
|
|
195591
|
-
`mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
|
|
195592
|
-
`[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
|
|
195593
|
-
`[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`
|
|
195594
|
-
].join(" ");
|
|
195595
|
-
}
|
|
195596
|
-
var _repoRoot;
|
|
195597
|
-
function resolveRepoRoot() {
|
|
195598
|
-
if (_repoRoot) return _repoRoot;
|
|
195599
|
-
const fromEnv = process.env.GITHUB_WORKSPACE;
|
|
195600
|
-
if (fromEnv) {
|
|
195601
|
-
_repoRoot = fromEnv;
|
|
195602
|
-
return _repoRoot;
|
|
195603
|
-
}
|
|
195604
|
-
try {
|
|
195605
|
-
_repoRoot = spawnSync6("git", ["rev-parse", "--show-toplevel"], {
|
|
195606
|
-
cwd: process.cwd(),
|
|
195607
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
195608
|
-
encoding: "utf-8"
|
|
195609
|
-
}).stdout?.trim();
|
|
195610
|
-
} catch {
|
|
195611
|
-
}
|
|
195612
|
-
if (!_repoRoot) _repoRoot = process.cwd();
|
|
195613
|
-
return _repoRoot;
|
|
195614
|
-
}
|
|
195615
|
-
function spawnShell(params) {
|
|
195616
|
-
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
|
195617
|
-
const sandboxMethod = detectSandboxMethod();
|
|
195618
|
-
const ci = process.env.CI === "true";
|
|
195619
|
-
if (ci && sandboxMethod === "none") {
|
|
195620
|
-
throw new Error(
|
|
195621
|
-
"PID-namespace isolation is required in CI but unavailable: unprivileged unshare, sudo unshare, and userns-nested unshare all failed. " + (usernsProbeError ? `userns probe said: ${usernsProbeError}. ` : "") + "on a containerized / Kubernetes self-hosted runner the cause is usually one of: the pod's seccomp profile blocks user-namespace creation (Pod Security Standards baseline/restricted); the pod runs as root without CAP_SETFCAP, which the kernel requires to map uid 0; or the node kernel disallows unprivileged user namespaces (Ubuntu >= 23.10, Bottlerocket, Talos). elsewhere, check that `unshare` and `setpriv` (util-linux >= 2.31, which is what added --ambient-caps) are on PATH. see https://docs.pullfrog.com/security#self-hosted-runners"
|
|
195622
|
-
);
|
|
195623
|
-
}
|
|
195624
|
-
const repoRoot = resolveRepoRoot();
|
|
195625
|
-
const fsMounts = buildFsMounts(repoRoot);
|
|
195626
|
-
if (sandboxMethod === "unshare") {
|
|
195627
|
-
return spawn3(
|
|
195628
|
-
"unshare",
|
|
195629
|
-
[
|
|
195630
|
-
"--pid",
|
|
195631
|
-
"--fork",
|
|
195632
|
-
"--mount-proc",
|
|
195633
|
-
"bash",
|
|
195634
|
-
"-c",
|
|
195635
|
-
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
|
|
195636
|
-
],
|
|
195637
|
-
spawnOpts
|
|
195638
|
-
);
|
|
195639
|
-
}
|
|
195640
|
-
if (sandboxMethod === "userns-unshare") {
|
|
195641
|
-
const escaped = params.command.replace(/'/g, "'\\''");
|
|
195642
|
-
return spawn3(
|
|
195643
|
-
"unshare",
|
|
195644
|
-
[
|
|
195645
|
-
"--user",
|
|
195646
|
-
"--map-root-user",
|
|
195647
|
-
"--pid",
|
|
195648
|
-
"--fork",
|
|
195649
|
-
"--mount",
|
|
195650
|
-
"bash",
|
|
195651
|
-
"-c",
|
|
195652
|
-
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
|
|
195653
|
-
],
|
|
195654
|
-
spawnOpts
|
|
195655
|
-
);
|
|
195656
|
-
}
|
|
195657
|
-
if (sandboxMethod === "sudo-unshare") {
|
|
195658
|
-
const envArgs = [];
|
|
195659
|
-
for (const [k2, v] of Object.entries(params.env)) {
|
|
195660
|
-
if (v !== void 0) {
|
|
195661
|
-
envArgs.push(`${k2}=${v}`);
|
|
195662
|
-
}
|
|
195663
|
-
}
|
|
195664
|
-
const username = userInfo2().username;
|
|
195665
|
-
const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
|
|
195666
|
-
const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
|
|
195667
|
-
envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
|
|
195668
|
-
return spawn3(
|
|
195669
|
-
"sudo",
|
|
195670
|
-
[
|
|
195671
|
-
"env",
|
|
195672
|
-
...envArgs,
|
|
195673
|
-
"unshare",
|
|
195674
|
-
"--pid",
|
|
195675
|
-
"--fork",
|
|
195676
|
-
"--mount-proc",
|
|
195677
|
-
"bash",
|
|
195678
|
-
"-c",
|
|
195679
|
-
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
|
|
195680
|
-
],
|
|
195681
|
-
{ ...spawnOpts, env: {} }
|
|
195682
|
-
);
|
|
195683
|
-
}
|
|
195684
|
-
return spawn3("bash", ["-c", params.command], spawnOpts);
|
|
195685
|
-
}
|
|
195686
|
-
async function killProcessGroup(proc) {
|
|
195687
|
-
if (!proc.pid) return;
|
|
195688
|
-
try {
|
|
195689
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
195690
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
195691
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
195692
|
-
} catch {
|
|
195693
|
-
try {
|
|
195694
|
-
proc.kill("SIGKILL");
|
|
195695
|
-
} catch {
|
|
195696
|
-
}
|
|
195697
|
-
}
|
|
195698
|
-
}
|
|
195699
|
-
function getTempDir() {
|
|
195700
|
-
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
195701
|
-
if (!tempDir) {
|
|
195702
|
-
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
195703
|
-
}
|
|
195704
|
-
return tempDir;
|
|
195705
|
-
}
|
|
195706
|
-
var MAX_OUTPUT_CHARS = 5e3;
|
|
195707
|
-
async function runSandboxed(params) {
|
|
195708
|
-
const proc = spawnShell({
|
|
195709
|
-
command: params.command,
|
|
195710
|
-
env: params.env,
|
|
195711
|
-
cwd: params.cwd,
|
|
195712
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
195713
|
-
});
|
|
195714
|
-
const stdout = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
195715
|
-
const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
195716
|
-
let timedOut = false, exited = false, spawnError = "";
|
|
195717
|
-
proc.stdout?.on("data", (chunk) => {
|
|
195718
|
-
stdout.append(chunk.toString());
|
|
195719
|
-
});
|
|
195720
|
-
proc.stderr?.on("data", (chunk) => {
|
|
195721
|
-
stderr.append(chunk.toString());
|
|
195722
|
-
});
|
|
195723
|
-
const timeoutId = setTimeout(async () => {
|
|
195724
|
-
if (!exited) {
|
|
195725
|
-
timedOut = true;
|
|
195726
|
-
await killProcessGroup(proc);
|
|
195727
|
-
}
|
|
195728
|
-
}, params.timeout);
|
|
195729
|
-
const exitCode = await new Promise((resolve3) => {
|
|
195730
|
-
const done = (code) => {
|
|
195731
|
-
exited = true;
|
|
195732
|
-
clearTimeout(timeoutId);
|
|
195733
|
-
resolve3(code);
|
|
195734
|
-
};
|
|
195735
|
-
proc.on("exit", done);
|
|
195736
|
-
proc.on("error", (err) => {
|
|
195737
|
-
spawnError = err.message;
|
|
195738
|
-
done(null);
|
|
195739
|
-
});
|
|
195740
|
-
});
|
|
195741
|
-
proc.stdout?.destroy();
|
|
195742
|
-
proc.stderr?.destroy();
|
|
195743
|
-
const outText = stdout.toString();
|
|
195744
|
-
const errText = stderr.toString();
|
|
195745
|
-
let output2 = errText ? outText ? `${outText}
|
|
195746
|
-
${errText}` : errText : outText;
|
|
195747
|
-
if (spawnError) output2 = output2 ? `${output2}
|
|
195748
|
-
${spawnError}` : spawnError;
|
|
195749
|
-
if (timedOut)
|
|
195750
|
-
output2 = output2 ? `${output2}
|
|
195751
|
-
[timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
|
|
195752
|
-
return {
|
|
195753
|
-
output: output2.trim(),
|
|
195754
|
-
exitCode: exitCode ?? (timedOut ? 124 : -1),
|
|
195755
|
-
timedOut
|
|
195756
|
-
};
|
|
195757
|
-
}
|
|
195758
|
-
function capOutput(output2) {
|
|
195759
|
-
if (output2.length <= MAX_OUTPUT_CHARS) return output2;
|
|
195760
|
-
const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
|
|
195761
|
-
writeFileSync12(fullPath, output2);
|
|
195762
|
-
const elided = output2.length - MAX_OUTPUT_CHARS;
|
|
195763
|
-
return `... [${elided} chars truncated; output saved to ${fullPath}] ...
|
|
195764
|
-
${output2.slice(-MAX_OUTPUT_CHARS)}`;
|
|
195765
|
-
}
|
|
195766
|
-
function isGitCommand(command) {
|
|
195767
|
-
const trimmed = command.trim();
|
|
195768
|
-
if (trimmed === "git" || trimmed.startsWith("git ")) return true;
|
|
195769
|
-
if (trimmed.startsWith("sudo git")) return true;
|
|
195770
|
-
return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
|
|
195771
|
-
}
|
|
195772
|
-
function ShellTool(ctx) {
|
|
195773
|
-
return tool({
|
|
195774
|
-
name: "shell",
|
|
195775
|
-
timeoutMs: 12e4,
|
|
195776
|
-
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
195777
|
-
|
|
195778
|
-
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
195779
|
-
|
|
195780
|
-
Use this tool to:
|
|
195781
|
-
- Run shell commands (ls, cat, grep, find, etc.)
|
|
195782
|
-
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
195783
|
-
- Run tests and linters
|
|
195784
|
-
|
|
195785
|
-
Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the captured output is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
|
|
195786
|
-
|
|
195787
|
-
Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
|
|
195788
|
-
parameters: ShellParams,
|
|
195789
|
-
execute: execute(async (params) => {
|
|
195790
|
-
if (isGitCommand(params.command)) {
|
|
195791
|
-
throw new Error(
|
|
195792
|
-
'git commands are not allowed in the shell tool. use the dedicated git tools instead:\n- git: local operations, e.g. git({ command: "log", args: ["--oneline"] }) \u2014 `command` is the subcommand ONLY\n- push_branch: push to remote (handles authentication)\n- git_fetch: fetch from remote (handles authentication)\n- checkout_pr: check out PR branches'
|
|
195793
|
-
);
|
|
195794
|
-
}
|
|
195795
|
-
const timeout = Math.min(params.timeout ?? 3e4, 12e4);
|
|
195796
|
-
const cwd = params.working_directory ?? process.cwd();
|
|
195797
|
-
const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
|
|
195798
|
-
if (params.command.includes("agent-browser")) {
|
|
195799
|
-
const daemonError = ensureBrowserDaemon(ctx.toolState);
|
|
195800
|
-
if (daemonError) {
|
|
195801
|
-
return {
|
|
195802
|
-
output: `browser daemon unavailable: ${daemonError}`,
|
|
195803
|
-
exit_code: 1,
|
|
195804
|
-
timed_out: false
|
|
195805
|
-
};
|
|
195806
|
-
}
|
|
195807
|
-
const binDir = ctx.toolState.browserDaemon?.binDir;
|
|
195808
|
-
if (binDir) {
|
|
195809
|
-
env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
|
|
195810
|
-
}
|
|
195811
|
-
}
|
|
195812
|
-
if (params.background) {
|
|
195813
|
-
const tempDir = getTempDir();
|
|
195814
|
-
const handle = `bg-${randomUUID4().slice(0, 8)}`;
|
|
195815
|
-
const outputPath = join20(tempDir, `${handle}.log`);
|
|
195816
|
-
const pidPath = join20(tempDir, `${handle}.pid`);
|
|
195817
|
-
const logFd = openSync(outputPath, "a");
|
|
195818
|
-
let proc;
|
|
195819
|
-
try {
|
|
195820
|
-
proc = spawnShell({
|
|
195821
|
-
command: params.command,
|
|
195822
|
-
env: env2,
|
|
195823
|
-
cwd,
|
|
195824
|
-
stdio: ["ignore", logFd, logFd]
|
|
195825
|
-
});
|
|
195826
|
-
} finally {
|
|
195827
|
-
closeSync(logFd);
|
|
195828
|
-
}
|
|
195829
|
-
if (!proc.pid) {
|
|
195830
|
-
throw new Error("failed to start background process");
|
|
195831
|
-
}
|
|
195832
|
-
proc.unref();
|
|
195833
|
-
writeFileSync12(pidPath, `${proc.pid}
|
|
195834
|
-
`);
|
|
195835
|
-
ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
|
|
195836
|
-
return {
|
|
195837
|
-
handle,
|
|
195838
|
-
outputPath,
|
|
195839
|
-
pidPath,
|
|
195840
|
-
message: `started background process ${handle} (pid ${proc.pid})`
|
|
195841
|
-
};
|
|
195842
|
-
}
|
|
195843
|
-
const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
|
|
195844
|
-
if (result.exitCode !== 0) {
|
|
195845
|
-
log.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
|
|
195846
|
-
if (result.output) log.info(`output: ${result.output}`);
|
|
195847
|
-
}
|
|
195848
|
-
return {
|
|
195849
|
-
output: capOutput(result.output),
|
|
195850
|
-
exit_code: result.exitCode,
|
|
195851
|
-
timed_out: result.timedOut
|
|
195852
|
-
};
|
|
195853
|
-
})
|
|
195854
|
-
});
|
|
195855
|
-
}
|
|
195856
|
-
var KillBackgroundParams = type({
|
|
195857
|
-
handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
|
|
195858
|
-
});
|
|
195859
|
-
function KillBackgroundTool(ctx) {
|
|
195860
|
-
return tool({
|
|
195861
|
-
name: "kill_background",
|
|
195862
|
-
mutates: true,
|
|
195863
|
-
description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
|
|
195864
|
-
parameters: KillBackgroundParams,
|
|
195865
|
-
execute: execute(async (params) => {
|
|
195866
|
-
const proc = ctx.toolState.backgroundProcesses.get(params.handle);
|
|
195867
|
-
if (!proc) {
|
|
195868
|
-
return {
|
|
195869
|
-
success: false,
|
|
195870
|
-
message: `no background process with handle ${params.handle}`
|
|
195871
|
-
};
|
|
195872
|
-
}
|
|
195873
|
-
try {
|
|
195874
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
195875
|
-
} catch {
|
|
195876
|
-
}
|
|
195877
|
-
await sleep3(200);
|
|
195878
|
-
try {
|
|
195879
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
195880
|
-
} catch {
|
|
195881
|
-
}
|
|
195882
|
-
ctx.toolState.backgroundProcesses.delete(params.handle);
|
|
195883
|
-
return {
|
|
195884
|
-
success: true,
|
|
195885
|
-
message: `killed background process ${params.handle} (pid ${proc.pid})`
|
|
195886
|
-
};
|
|
195887
|
-
})
|
|
195888
|
-
});
|
|
195889
|
-
}
|
|
195890
|
-
|
|
195891
|
-
// mcp/gh.ts
|
|
195892
195970
|
var GH_TIMEOUT_MS = 12e4;
|
|
195893
195971
|
var GhParams = type({
|
|
195894
195972
|
args: type.string.array().describe(
|
|
@@ -197139,7 +197217,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
197139
197217
|
threadCount: threadBlocks.length,
|
|
197140
197218
|
commentsPath,
|
|
197141
197219
|
toc: formatted.toc,
|
|
197142
|
-
instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR
|
|
197220
|
+
instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR, including comments that landed after this run started. Being returned does not authorize a code change; follow the task's scope and each thread's latest request. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). read each full thread before deciding whether it calls for a reply or a code change.`
|
|
197143
197221
|
};
|
|
197144
197222
|
})
|
|
197145
197223
|
});
|
|
@@ -197262,8 +197340,7 @@ An existing plan comment was found for this issue. Update that comment with the
|
|
|
197262
197340
|
};
|
|
197263
197341
|
}
|
|
197264
197342
|
var modeInstructionParent = {
|
|
197265
|
-
IncrementalReview: "Review"
|
|
197266
|
-
Fix: "Build"
|
|
197343
|
+
IncrementalReview: "Review"
|
|
197267
197344
|
};
|
|
197268
197345
|
function buildOrchestratorGuidance(ctx, mode, overrideGuidance) {
|
|
197269
197346
|
const hardcoded = overrideGuidance ?? mode.prompt ?? "";
|
|
@@ -198475,18 +198552,13 @@ function hasEnvVar4(name) {
|
|
|
198475
198552
|
const value2 = process.env[name];
|
|
198476
198553
|
return typeof value2 === "string" && value2.length > 0;
|
|
198477
198554
|
}
|
|
198478
|
-
var HOST_OVERRIDES = {
|
|
198479
|
-
ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
|
|
198480
|
-
CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
|
|
198481
|
-
};
|
|
198482
198555
|
function envVarsFor(model) {
|
|
198483
198556
|
return model.includes("/") ? getModelEnvVars(model) : [];
|
|
198484
198557
|
}
|
|
198485
198558
|
async function checkOne(params) {
|
|
198486
198559
|
const value2 = process.env[params.envVar];
|
|
198487
198560
|
if (!value2) return null;
|
|
198488
|
-
|
|
198489
|
-
if (hostOverride && hasEnvVar4(hostOverride)) return null;
|
|
198561
|
+
if (getProviderGatewayUrl(params.model)) return null;
|
|
198490
198562
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
198491
198563
|
const preflight = await preflightClaudeSubscription({
|
|
198492
198564
|
token: value2,
|
|
@@ -201687,6 +201759,7 @@ async function main() {
|
|
|
201687
201759
|
const setupHook = await executeLifecycleHook({
|
|
201688
201760
|
event: "setup",
|
|
201689
201761
|
script: runContext.repoSettings.setupScript,
|
|
201762
|
+
shell: payload.shell,
|
|
201690
201763
|
normalizeWorkingTreeAfter: true
|
|
201691
201764
|
});
|
|
201692
201765
|
if (setupHook.warning) {
|