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/cli.mjs
CHANGED
|
@@ -28300,7 +28300,7 @@ var require_cross_spawn = __commonJS({
|
|
|
28300
28300
|
enoent.hookChildProcess(spawned, parsed2);
|
|
28301
28301
|
return spawned;
|
|
28302
28302
|
}
|
|
28303
|
-
function
|
|
28303
|
+
function spawnSync9(command, args2, options) {
|
|
28304
28304
|
const parsed2 = parse8(command, args2, options);
|
|
28305
28305
|
const result = cp.spawnSync(parsed2.command, parsed2.args, parsed2.options);
|
|
28306
28306
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed2);
|
|
@@ -28308,7 +28308,7 @@ var require_cross_spawn = __commonJS({
|
|
|
28308
28308
|
}
|
|
28309
28309
|
module.exports = spawn6;
|
|
28310
28310
|
module.exports.spawn = spawn6;
|
|
28311
|
-
module.exports.sync =
|
|
28311
|
+
module.exports.sync = spawnSync9;
|
|
28312
28312
|
module.exports._parse = parse8;
|
|
28313
28313
|
module.exports._enoent = enoent;
|
|
28314
28314
|
}
|
|
@@ -123000,7 +123000,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
123000
123000
|
// package.json
|
|
123001
123001
|
var package_default = {
|
|
123002
123002
|
name: "pullfrog",
|
|
123003
|
-
version: "0.1.
|
|
123003
|
+
version: "0.1.76",
|
|
123004
123004
|
cliContractVersion: "1",
|
|
123005
123005
|
type: "module",
|
|
123006
123006
|
bin: {
|
|
@@ -124108,6 +124108,20 @@ var repoConfigFields = [
|
|
|
124108
124108
|
nullable: true,
|
|
124109
124109
|
description: "additional instructions for planning"
|
|
124110
124110
|
},
|
|
124111
|
+
{
|
|
124112
|
+
key: "prompts.address-reviews",
|
|
124113
|
+
field: "AddressReviews",
|
|
124114
|
+
kind: "prompt",
|
|
124115
|
+
nullable: true,
|
|
124116
|
+
description: "additional instructions for addressing review feedback"
|
|
124117
|
+
},
|
|
124118
|
+
{
|
|
124119
|
+
key: "prompts.fix-ci",
|
|
124120
|
+
field: "Fix",
|
|
124121
|
+
kind: "prompt",
|
|
124122
|
+
nullable: true,
|
|
124123
|
+
description: "additional instructions for fixing CI failures"
|
|
124124
|
+
},
|
|
124111
124125
|
{ key: "mention.enabled", field: "mention", kind: "toggle", description: "respond to mentions" },
|
|
124112
124126
|
{
|
|
124113
124127
|
key: "mention.instructions",
|
|
@@ -126138,6 +126152,17 @@ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
|
|
|
126138
126152
|
var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
|
|
126139
126153
|
var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
|
|
126140
126154
|
var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
|
|
126155
|
+
var PROVIDER_GATEWAY_URL_ENV = {
|
|
126156
|
+
anthropic: "ANTHROPIC_BASE_URL",
|
|
126157
|
+
openai: "OPENAI_BASE_URL"
|
|
126158
|
+
};
|
|
126159
|
+
function getProviderGatewayUrl(specifier) {
|
|
126160
|
+
const slashIndex = specifier?.indexOf("/") ?? -1;
|
|
126161
|
+
if (!specifier || slashIndex <= 0) return void 0;
|
|
126162
|
+
const envVar = PROVIDER_GATEWAY_URL_ENV[specifier.slice(0, slashIndex)];
|
|
126163
|
+
if (!envVar) return void 0;
|
|
126164
|
+
return process.env[envVar]?.trim().replace(/\/$/, "") || void 0;
|
|
126165
|
+
}
|
|
126141
126166
|
function isBedrockAnthropicId(bedrockModelId) {
|
|
126142
126167
|
return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
|
|
126143
126168
|
}
|
|
@@ -127644,26 +127669,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
127644
127669
|
|
|
127645
127670
|
2. Checkout the PR branch via \`${t2("checkout_pr")}\`.
|
|
127646
127671
|
|
|
127647
|
-
3. Fetch review comments via \`${t2("get_review_comments")}
|
|
127672
|
+
3. Fetch review comments via \`${t2("get_review_comments")}\` and read the full conversation in each relevant thread.
|
|
127648
127673
|
|
|
127649
|
-
4. For each
|
|
127650
|
-
-
|
|
127674
|
+
4. For each thread:
|
|
127675
|
+
- **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.
|
|
127676
|
+
- under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
|
|
127651
127677
|
- **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.
|
|
127652
|
-
-
|
|
127653
|
-
- if the
|
|
127678
|
+
- 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.
|
|
127679
|
+
- if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
|
|
127654
127680
|
- record what was done (or why nothing was done)
|
|
127655
127681
|
|
|
127656
127682
|
5. Quality check:
|
|
127657
|
-
- re-call \`${t2("get_review_comments")}\` with \`fresh: true\` before
|
|
127658
|
-
-
|
|
127659
|
-
- ${commitStep}
|
|
127683
|
+
- re-call \`${t2("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.
|
|
127684
|
+
- if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
|
|
127660
127685
|
|
|
127661
|
-
6. Finalize
|
|
127662
|
-
- ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
127663
|
-
-
|
|
127664
|
-
-
|
|
127665
|
-
- reply ONCE via \`${t2("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t2("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.
|
|
127666
|
-
- **immediately** call \`${t2("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.
|
|
127686
|
+
6. Finalize:
|
|
127687
|
+
- **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.
|
|
127688
|
+
- reply ONCE per relevant thread via \`${t2("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t2("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
|
|
127689
|
+
- resolve a thread via \`${t2("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.
|
|
127667
127690
|
- call \`${t2("report_progress")}\` with a brief summary. If your last \`${t2("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.`
|
|
127668
127691
|
},
|
|
127669
127692
|
// Review and IncrementalReview route the minimum reviewfrog specialists
|
|
@@ -129640,6 +129663,19 @@ function warnIfNativeEditsUnavailable(ctx) {
|
|
|
129640
129663
|
"\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."
|
|
129641
129664
|
);
|
|
129642
129665
|
}
|
|
129666
|
+
var CODEX_GATEWAY_PROVIDER = "pullfrog-gateway";
|
|
129667
|
+
function gatewayProvider(gatewayUrl) {
|
|
129668
|
+
if (!gatewayUrl) return [];
|
|
129669
|
+
return [
|
|
129670
|
+
`[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
|
|
129671
|
+
'name = "OpenAI gateway"',
|
|
129672
|
+
`base_url = ${JSON.stringify(gatewayUrl)}`,
|
|
129673
|
+
'wire_api = "responses"',
|
|
129674
|
+
// where the harness puts the run's OPENAI_API_KEY
|
|
129675
|
+
'env_key = "CODEX_API_KEY"',
|
|
129676
|
+
""
|
|
129677
|
+
];
|
|
129678
|
+
}
|
|
129643
129679
|
function writeCodexConfig(params) {
|
|
129644
129680
|
const shellEnabled = params.ctx.payload.shell === "enabled";
|
|
129645
129681
|
const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
|
|
@@ -129656,6 +129692,7 @@ function writeCodexConfig(params) {
|
|
|
129656
129692
|
'web_search = "live"',
|
|
129657
129693
|
...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
|
|
129658
129694
|
...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
|
|
129695
|
+
...params.gatewayUrl ? [`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`] : [],
|
|
129659
129696
|
"",
|
|
129660
129697
|
"[features]",
|
|
129661
129698
|
tomlBool(CODEX_DISABLED_FEATURES, false),
|
|
@@ -129685,6 +129722,7 @@ function writeCodexConfig(params) {
|
|
|
129685
129722
|
"tool_timeout_sec = 660",
|
|
129686
129723
|
"",
|
|
129687
129724
|
...repoMcpApprovals(repoDir),
|
|
129725
|
+
...gatewayProvider(params.gatewayUrl),
|
|
129688
129726
|
// UNTRUSTED, deliberately, and this one line is the whole config-precedence
|
|
129689
129727
|
// boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
|
|
129690
129728
|
// OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
|
|
@@ -129953,7 +129991,11 @@ var codex = agent({
|
|
|
129953
129991
|
ctx,
|
|
129954
129992
|
codexHome,
|
|
129955
129993
|
model: resolveCodexModel(ctx),
|
|
129956
|
-
effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
|
|
129994
|
+
effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0,
|
|
129995
|
+
// keyed off OpenAI, not the resolved model: codex serves nothing else, an
|
|
129996
|
+
// auto-selected run has no model, and a ChatGPT-subscription token is
|
|
129997
|
+
// valid only at ChatGPT's own backend
|
|
129998
|
+
gatewayUrl: codexHomeAuth ? void 0 : getProviderGatewayUrl("openai/")
|
|
129957
129999
|
});
|
|
129958
130000
|
if (codexHomeAuth) {
|
|
129959
130001
|
core3.saveState(
|
|
@@ -135720,6 +135762,14 @@ function openAICompatibleProvider(model) {
|
|
|
135720
135762
|
}
|
|
135721
135763
|
};
|
|
135722
135764
|
}
|
|
135765
|
+
var OPENCODE_GATEWAY_PATH = { anthropic: "/v1" };
|
|
135766
|
+
function providerGatewayOverride(model) {
|
|
135767
|
+
const gatewayUrl = getProviderGatewayUrl(model);
|
|
135768
|
+
if (!model || !gatewayUrl) return {};
|
|
135769
|
+
const provider2 = model.slice(0, model.indexOf("/"));
|
|
135770
|
+
const baseURL = `${gatewayUrl}${OPENCODE_GATEWAY_PATH[provider2] ?? ""}`;
|
|
135771
|
+
return { [provider2]: { options: { baseURL } } };
|
|
135772
|
+
}
|
|
135723
135773
|
function azureProvider(model) {
|
|
135724
135774
|
const prefix = `${AZURE_PROVIDER}/`;
|
|
135725
135775
|
if (!model?.startsWith(prefix)) return {};
|
|
@@ -135835,7 +135885,8 @@ function buildSecurityConfig(ctx, model) {
|
|
|
135835
135885
|
provider: {
|
|
135836
135886
|
openrouter: { models: kimiOpenRouterProviderOverrides() },
|
|
135837
135887
|
...openAICompatibleProvider(model),
|
|
135838
|
-
...azureProvider(model)
|
|
135888
|
+
...azureProvider(model),
|
|
135889
|
+
...providerGatewayOverride(model)
|
|
135839
135890
|
}
|
|
135840
135891
|
};
|
|
135841
135892
|
if (model) {
|
|
@@ -194109,8 +194160,8 @@ function closeBrowserDaemon(toolState) {
|
|
|
194109
194160
|
|
|
194110
194161
|
// mcp/checkout.ts
|
|
194111
194162
|
import { createHash as createHash2 } from "node:crypto";
|
|
194112
|
-
import { statSync, unlinkSync as unlinkSync3, writeFileSync as
|
|
194113
|
-
import { join as
|
|
194163
|
+
import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync11 } from "node:fs";
|
|
194164
|
+
import { join as join16 } from "node:path";
|
|
194114
194165
|
|
|
194115
194166
|
// node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
|
|
194116
194167
|
var ArkError = class _ArkError extends CastableBase {
|
|
@@ -202382,6 +202433,960 @@ async function $gitFetchWithDeepen(args2, options, label) {
|
|
|
202382
202433
|
}
|
|
202383
202434
|
}
|
|
202384
202435
|
|
|
202436
|
+
// utils/lifecycle.ts
|
|
202437
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
202438
|
+
|
|
202439
|
+
// mcp/shell.ts
|
|
202440
|
+
import { spawn as spawn5, spawnSync as spawnSync7 } from "node:child_process";
|
|
202441
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
202442
|
+
import { closeSync, openSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
202443
|
+
import { userInfo as userInfo2 } from "node:os";
|
|
202444
|
+
import { join as join11 } from "node:path";
|
|
202445
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
202446
|
+
|
|
202447
|
+
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
202448
|
+
var LIST_ITEM_MARKER = "-";
|
|
202449
|
+
var LIST_ITEM_PREFIX = "- ";
|
|
202450
|
+
var COMMA = ",";
|
|
202451
|
+
var PIPE = "|";
|
|
202452
|
+
var DOT = ".";
|
|
202453
|
+
var NULL_LITERAL = "null";
|
|
202454
|
+
var TRUE_LITERAL = "true";
|
|
202455
|
+
var FALSE_LITERAL = "false";
|
|
202456
|
+
var BACKSLASH = "\\";
|
|
202457
|
+
var DOUBLE_QUOTE = '"';
|
|
202458
|
+
var TAB = " ";
|
|
202459
|
+
var DELIMITERS = {
|
|
202460
|
+
comma: COMMA,
|
|
202461
|
+
tab: TAB,
|
|
202462
|
+
pipe: PIPE
|
|
202463
|
+
};
|
|
202464
|
+
var DEFAULT_DELIMITER = DELIMITERS.comma;
|
|
202465
|
+
function escapeString(value2) {
|
|
202466
|
+
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`);
|
|
202467
|
+
}
|
|
202468
|
+
function isBooleanOrNullLiteral(token) {
|
|
202469
|
+
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
|
|
202470
|
+
}
|
|
202471
|
+
function normalizeValue(value2) {
|
|
202472
|
+
if (value2 === null) return null;
|
|
202473
|
+
if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
202474
|
+
if (typeof value2 === "number") {
|
|
202475
|
+
if (Object.is(value2, -0)) return 0;
|
|
202476
|
+
if (!Number.isFinite(value2)) return null;
|
|
202477
|
+
return value2;
|
|
202478
|
+
}
|
|
202479
|
+
if (typeof value2 === "bigint") {
|
|
202480
|
+
if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
|
|
202481
|
+
return value2.toString();
|
|
202482
|
+
}
|
|
202483
|
+
if (value2 instanceof Date) return value2.toISOString();
|
|
202484
|
+
if (Array.isArray(value2)) return value2.map(normalizeValue);
|
|
202485
|
+
if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
|
|
202486
|
+
if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
|
|
202487
|
+
if (isPlainObject7(value2)) {
|
|
202488
|
+
const normalized = {};
|
|
202489
|
+
for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
|
|
202490
|
+
return normalized;
|
|
202491
|
+
}
|
|
202492
|
+
return null;
|
|
202493
|
+
}
|
|
202494
|
+
function isJsonPrimitive(value2) {
|
|
202495
|
+
return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
|
|
202496
|
+
}
|
|
202497
|
+
function isJsonArray(value2) {
|
|
202498
|
+
return Array.isArray(value2);
|
|
202499
|
+
}
|
|
202500
|
+
function isJsonObject4(value2) {
|
|
202501
|
+
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
202502
|
+
}
|
|
202503
|
+
function isEmptyObject2(value2) {
|
|
202504
|
+
return Object.keys(value2).length === 0;
|
|
202505
|
+
}
|
|
202506
|
+
function isPlainObject7(value2) {
|
|
202507
|
+
if (value2 === null || typeof value2 !== "object") return false;
|
|
202508
|
+
const prototype = Object.getPrototypeOf(value2);
|
|
202509
|
+
return prototype === null || prototype === Object.prototype;
|
|
202510
|
+
}
|
|
202511
|
+
function isArrayOfPrimitives(value2) {
|
|
202512
|
+
return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
|
|
202513
|
+
}
|
|
202514
|
+
function isArrayOfArrays(value2) {
|
|
202515
|
+
return value2.length === 0 || value2.every((item) => isJsonArray(item));
|
|
202516
|
+
}
|
|
202517
|
+
function isArrayOfObjects(value2) {
|
|
202518
|
+
return value2.length === 0 || value2.every((item) => isJsonObject4(item));
|
|
202519
|
+
}
|
|
202520
|
+
function isValidUnquotedKey(key) {
|
|
202521
|
+
return /^[A-Z_][\w.]*$/i.test(key);
|
|
202522
|
+
}
|
|
202523
|
+
function isIdentifierSegment(key) {
|
|
202524
|
+
return /^[A-Z_]\w*$/i.test(key);
|
|
202525
|
+
}
|
|
202526
|
+
function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
|
|
202527
|
+
if (!value2) return false;
|
|
202528
|
+
if (value2 !== value2.trim()) return false;
|
|
202529
|
+
if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
|
|
202530
|
+
if (value2.includes(":")) return false;
|
|
202531
|
+
if (value2.includes('"') || value2.includes("\\")) return false;
|
|
202532
|
+
if (/[[\]{}]/.test(value2)) return false;
|
|
202533
|
+
if (/[\n\r\t]/.test(value2)) return false;
|
|
202534
|
+
if (value2.includes(delimiter3)) return false;
|
|
202535
|
+
if (value2.startsWith(LIST_ITEM_MARKER)) return false;
|
|
202536
|
+
return true;
|
|
202537
|
+
}
|
|
202538
|
+
function isNumericLike(value2) {
|
|
202539
|
+
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
|
|
202540
|
+
}
|
|
202541
|
+
var QUOTED_KEY_MARKER = Symbol("quotedKey");
|
|
202542
|
+
function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
202543
|
+
if (options.keyFolding !== "safe") return;
|
|
202544
|
+
if (!isJsonObject4(value2)) return;
|
|
202545
|
+
const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
|
|
202546
|
+
if (segments.length < 2) return;
|
|
202547
|
+
if (!segments.every((seg) => isIdentifierSegment(seg))) return;
|
|
202548
|
+
const foldedKey = buildFoldedKey(segments);
|
|
202549
|
+
const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
202550
|
+
if (siblings.includes(foldedKey)) return;
|
|
202551
|
+
if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
|
|
202552
|
+
return {
|
|
202553
|
+
foldedKey,
|
|
202554
|
+
remainder: tail,
|
|
202555
|
+
leafValue,
|
|
202556
|
+
segmentCount: segments.length
|
|
202557
|
+
};
|
|
202558
|
+
}
|
|
202559
|
+
function collectSingleKeyChain(startKey, startValue, maxDepth) {
|
|
202560
|
+
const segments = [startKey];
|
|
202561
|
+
let currentValue = startValue;
|
|
202562
|
+
while (segments.length < maxDepth) {
|
|
202563
|
+
if (!isJsonObject4(currentValue)) break;
|
|
202564
|
+
const keys = Object.keys(currentValue);
|
|
202565
|
+
if (keys.length !== 1) break;
|
|
202566
|
+
const nextKey = keys[0];
|
|
202567
|
+
const nextValue = currentValue[nextKey];
|
|
202568
|
+
segments.push(nextKey);
|
|
202569
|
+
currentValue = nextValue;
|
|
202570
|
+
}
|
|
202571
|
+
if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
|
|
202572
|
+
segments,
|
|
202573
|
+
tail: void 0,
|
|
202574
|
+
leafValue: currentValue
|
|
202575
|
+
};
|
|
202576
|
+
return {
|
|
202577
|
+
segments,
|
|
202578
|
+
tail: currentValue,
|
|
202579
|
+
leafValue: currentValue
|
|
202580
|
+
};
|
|
202581
|
+
}
|
|
202582
|
+
function buildFoldedKey(segments) {
|
|
202583
|
+
return segments.join(DOT);
|
|
202584
|
+
}
|
|
202585
|
+
function encodePrimitive(value2, delimiter3) {
|
|
202586
|
+
if (value2 === null) return NULL_LITERAL;
|
|
202587
|
+
if (typeof value2 === "boolean") return String(value2);
|
|
202588
|
+
if (typeof value2 === "number") return String(value2);
|
|
202589
|
+
return encodeStringLiteral(value2, delimiter3);
|
|
202590
|
+
}
|
|
202591
|
+
function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
|
|
202592
|
+
if (isSafeUnquoted(value2, delimiter3)) return value2;
|
|
202593
|
+
return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
|
|
202594
|
+
}
|
|
202595
|
+
function encodeKey(key) {
|
|
202596
|
+
if (isValidUnquotedKey(key)) return key;
|
|
202597
|
+
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
|
|
202598
|
+
}
|
|
202599
|
+
function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
|
|
202600
|
+
return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
|
|
202601
|
+
}
|
|
202602
|
+
function formatHeader(length, options) {
|
|
202603
|
+
const key = options?.key;
|
|
202604
|
+
const fields = options?.fields;
|
|
202605
|
+
const delimiter3 = options?.delimiter ?? COMMA;
|
|
202606
|
+
let header = "";
|
|
202607
|
+
if (key) header += encodeKey(key);
|
|
202608
|
+
header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
|
|
202609
|
+
if (fields) {
|
|
202610
|
+
const quotedFields = fields.map((f) => encodeKey(f));
|
|
202611
|
+
header += `{${quotedFields.join(delimiter3)}}`;
|
|
202612
|
+
}
|
|
202613
|
+
header += ":";
|
|
202614
|
+
return header;
|
|
202615
|
+
}
|
|
202616
|
+
function* encodeJsonValue(value2, options, depth) {
|
|
202617
|
+
if (isJsonPrimitive(value2)) {
|
|
202618
|
+
const encodedPrimitive = encodePrimitive(value2, options.delimiter);
|
|
202619
|
+
if (encodedPrimitive !== "") yield encodedPrimitive;
|
|
202620
|
+
return;
|
|
202621
|
+
}
|
|
202622
|
+
if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
|
|
202623
|
+
else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
|
|
202624
|
+
}
|
|
202625
|
+
function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
|
|
202626
|
+
const keys = Object.keys(value2);
|
|
202627
|
+
if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
|
|
202628
|
+
const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
|
|
202629
|
+
for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
202630
|
+
}
|
|
202631
|
+
function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
202632
|
+
const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
|
|
202633
|
+
const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
|
|
202634
|
+
if (options.keyFolding === "safe" && siblings) {
|
|
202635
|
+
const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
202636
|
+
if (foldResult) {
|
|
202637
|
+
const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
|
|
202638
|
+
const encodedFoldedKey = encodeKey(foldedKey);
|
|
202639
|
+
if (remainder === void 0) {
|
|
202640
|
+
if (isJsonPrimitive(leafValue)) {
|
|
202641
|
+
yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
|
|
202642
|
+
return;
|
|
202643
|
+
} else if (isJsonArray(leafValue)) {
|
|
202644
|
+
yield* encodeArrayLines(foldedKey, leafValue, depth, options);
|
|
202645
|
+
return;
|
|
202646
|
+
} else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
|
|
202647
|
+
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
202648
|
+
return;
|
|
202649
|
+
}
|
|
202650
|
+
}
|
|
202651
|
+
if (isJsonObject4(remainder)) {
|
|
202652
|
+
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
202653
|
+
const remainingDepth = effectiveFlattenDepth - segmentCount;
|
|
202654
|
+
const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
202655
|
+
yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
|
|
202656
|
+
return;
|
|
202657
|
+
}
|
|
202658
|
+
}
|
|
202659
|
+
}
|
|
202660
|
+
const encodedKey = encodeKey(key);
|
|
202661
|
+
if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
|
|
202662
|
+
else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
|
|
202663
|
+
else if (isJsonObject4(value2)) {
|
|
202664
|
+
yield indentedLine(depth, `${encodedKey}:`, options.indent);
|
|
202665
|
+
if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
|
|
202666
|
+
}
|
|
202667
|
+
}
|
|
202668
|
+
function* encodeArrayLines(key, value2, depth, options) {
|
|
202669
|
+
if (value2.length === 0) {
|
|
202670
|
+
yield indentedLine(depth, formatHeader(0, {
|
|
202671
|
+
key,
|
|
202672
|
+
delimiter: options.delimiter
|
|
202673
|
+
}), options.indent);
|
|
202674
|
+
return;
|
|
202675
|
+
}
|
|
202676
|
+
if (isArrayOfPrimitives(value2)) {
|
|
202677
|
+
yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
|
|
202678
|
+
return;
|
|
202679
|
+
}
|
|
202680
|
+
if (isArrayOfArrays(value2)) {
|
|
202681
|
+
if (value2.every((arr) => isArrayOfPrimitives(arr))) {
|
|
202682
|
+
yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
|
|
202683
|
+
return;
|
|
202684
|
+
}
|
|
202685
|
+
}
|
|
202686
|
+
if (isArrayOfObjects(value2)) {
|
|
202687
|
+
const header = extractTabularHeader(value2);
|
|
202688
|
+
if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
|
|
202689
|
+
else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
202690
|
+
return;
|
|
202691
|
+
}
|
|
202692
|
+
yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
202693
|
+
}
|
|
202694
|
+
function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
|
|
202695
|
+
yield indentedLine(depth, formatHeader(values.length, {
|
|
202696
|
+
key: prefix,
|
|
202697
|
+
delimiter: options.delimiter
|
|
202698
|
+
}), options.indent);
|
|
202699
|
+
for (const arr of values) if (isArrayOfPrimitives(arr)) {
|
|
202700
|
+
const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
|
|
202701
|
+
yield indentedListItem(depth + 1, arrayLine, options.indent);
|
|
202702
|
+
}
|
|
202703
|
+
}
|
|
202704
|
+
function encodeInlineArrayLine(values, delimiter3, prefix) {
|
|
202705
|
+
const header = formatHeader(values.length, {
|
|
202706
|
+
key: prefix,
|
|
202707
|
+
delimiter: delimiter3
|
|
202708
|
+
});
|
|
202709
|
+
const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
|
|
202710
|
+
if (values.length === 0) return header;
|
|
202711
|
+
return `${header} ${joinedValue}`;
|
|
202712
|
+
}
|
|
202713
|
+
function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
|
|
202714
|
+
yield indentedLine(depth, formatHeader(rows.length, {
|
|
202715
|
+
key: prefix,
|
|
202716
|
+
fields: header,
|
|
202717
|
+
delimiter: options.delimiter
|
|
202718
|
+
}), options.indent);
|
|
202719
|
+
yield* writeTabularRowsLines(rows, header, depth + 1, options);
|
|
202720
|
+
}
|
|
202721
|
+
function extractTabularHeader(rows) {
|
|
202722
|
+
if (rows.length === 0) return;
|
|
202723
|
+
const firstRow = rows[0];
|
|
202724
|
+
const firstKeys = Object.keys(firstRow);
|
|
202725
|
+
if (firstKeys.length === 0) return;
|
|
202726
|
+
if (isTabularArray(rows, firstKeys)) return firstKeys;
|
|
202727
|
+
}
|
|
202728
|
+
function isTabularArray(rows, header) {
|
|
202729
|
+
for (const row of rows) {
|
|
202730
|
+
if (Object.keys(row).length !== header.length) return false;
|
|
202731
|
+
for (const key of header) {
|
|
202732
|
+
if (!(key in row)) return false;
|
|
202733
|
+
if (!isJsonPrimitive(row[key])) return false;
|
|
202734
|
+
}
|
|
202735
|
+
}
|
|
202736
|
+
return true;
|
|
202737
|
+
}
|
|
202738
|
+
function* writeTabularRowsLines(rows, header, depth, options) {
|
|
202739
|
+
for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
|
|
202740
|
+
}
|
|
202741
|
+
function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
|
|
202742
|
+
yield indentedLine(depth, formatHeader(items.length, {
|
|
202743
|
+
key: prefix,
|
|
202744
|
+
delimiter: options.delimiter
|
|
202745
|
+
}), options.indent);
|
|
202746
|
+
for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
202747
|
+
}
|
|
202748
|
+
function* encodeObjectAsListItemLines(obj, depth, options) {
|
|
202749
|
+
if (isEmptyObject2(obj)) {
|
|
202750
|
+
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
202751
|
+
return;
|
|
202752
|
+
}
|
|
202753
|
+
const entries = Object.entries(obj);
|
|
202754
|
+
if (entries.length === 1) {
|
|
202755
|
+
const [key, value2] = entries[0];
|
|
202756
|
+
if (isJsonArray(value2) && isArrayOfObjects(value2)) {
|
|
202757
|
+
const header = extractTabularHeader(value2);
|
|
202758
|
+
if (header) {
|
|
202759
|
+
yield indentedListItem(depth, formatHeader(value2.length, {
|
|
202760
|
+
key,
|
|
202761
|
+
fields: header,
|
|
202762
|
+
delimiter: options.delimiter
|
|
202763
|
+
}), options.indent);
|
|
202764
|
+
yield* writeTabularRowsLines(value2, header, depth + 1, options);
|
|
202765
|
+
return;
|
|
202766
|
+
}
|
|
202767
|
+
}
|
|
202768
|
+
}
|
|
202769
|
+
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
202770
|
+
yield* encodeObjectLines(obj, depth + 1, options);
|
|
202771
|
+
}
|
|
202772
|
+
function* encodeListItemValueLines(value2, depth, options) {
|
|
202773
|
+
if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
|
|
202774
|
+
else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
|
|
202775
|
+
else {
|
|
202776
|
+
yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
|
|
202777
|
+
for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
202778
|
+
}
|
|
202779
|
+
else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
|
|
202780
|
+
}
|
|
202781
|
+
function indentedLine(depth, content, indentSize) {
|
|
202782
|
+
return " ".repeat(indentSize * depth) + content;
|
|
202783
|
+
}
|
|
202784
|
+
function indentedListItem(depth, content, indentSize) {
|
|
202785
|
+
return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
|
|
202786
|
+
}
|
|
202787
|
+
function encode6(input2, options) {
|
|
202788
|
+
return Array.from(encodeLines(input2, options)).join("\n");
|
|
202789
|
+
}
|
|
202790
|
+
function encodeLines(input2, options) {
|
|
202791
|
+
return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
|
|
202792
|
+
}
|
|
202793
|
+
function resolveOptions(options) {
|
|
202794
|
+
return {
|
|
202795
|
+
indent: options?.indent ?? 2,
|
|
202796
|
+
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
|
|
202797
|
+
keyFolding: options?.keyFolding ?? "off",
|
|
202798
|
+
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
|
|
202799
|
+
};
|
|
202800
|
+
}
|
|
202801
|
+
|
|
202802
|
+
// mcp/geminiSanitizer.ts
|
|
202803
|
+
function parseStringEnumBranch(item) {
|
|
202804
|
+
if (!item || typeof item !== "object") return null;
|
|
202805
|
+
const record4 = item;
|
|
202806
|
+
if (Array.isArray(record4.enum)) {
|
|
202807
|
+
const strings = record4.enum.filter((v) => typeof v === "string");
|
|
202808
|
+
return strings.length === record4.enum.length && strings.length > 0 ? { values: strings } : null;
|
|
202809
|
+
}
|
|
202810
|
+
if (typeof record4.const === "string") {
|
|
202811
|
+
return { values: [record4.const] };
|
|
202812
|
+
}
|
|
202813
|
+
return null;
|
|
202814
|
+
}
|
|
202815
|
+
function collapseStringUnion(branches) {
|
|
202816
|
+
const values = [];
|
|
202817
|
+
for (const item of branches) {
|
|
202818
|
+
const parsed2 = parseStringEnumBranch(item);
|
|
202819
|
+
if (!parsed2) return null;
|
|
202820
|
+
values.push(...parsed2.values);
|
|
202821
|
+
}
|
|
202822
|
+
if (values.length === 0) return null;
|
|
202823
|
+
return { type: "string", enum: [...new Set(values)] };
|
|
202824
|
+
}
|
|
202825
|
+
function sanitizeForGemini(schema2) {
|
|
202826
|
+
if (!schema2 || typeof schema2 !== "object") return schema2;
|
|
202827
|
+
if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
|
|
202828
|
+
const source = schema2;
|
|
202829
|
+
if (Array.isArray(source.enum) && typeof source.type !== "string") {
|
|
202830
|
+
const allStrings = source.enum.every((v) => typeof v === "string");
|
|
202831
|
+
if (allStrings) {
|
|
202832
|
+
const result = { type: "string", enum: source.enum };
|
|
202833
|
+
if (typeof source.description === "string") result.description = source.description;
|
|
202834
|
+
return result;
|
|
202835
|
+
}
|
|
202836
|
+
}
|
|
202837
|
+
for (const unionKey of ["anyOf", "oneOf"]) {
|
|
202838
|
+
const branches = source[unionKey];
|
|
202839
|
+
if (Array.isArray(branches) && branches.length > 0) {
|
|
202840
|
+
const collapsed = collapseStringUnion(branches);
|
|
202841
|
+
if (collapsed) {
|
|
202842
|
+
const result = { ...collapsed };
|
|
202843
|
+
if (typeof source.description === "string") result.description = source.description;
|
|
202844
|
+
return result;
|
|
202845
|
+
}
|
|
202846
|
+
}
|
|
202847
|
+
}
|
|
202848
|
+
if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
|
|
202849
|
+
const result = {};
|
|
202850
|
+
if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
|
|
202851
|
+
if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
|
|
202852
|
+
return result;
|
|
202853
|
+
}
|
|
202854
|
+
const sanitized = {};
|
|
202855
|
+
for (const [key, value2] of Object.entries(source)) {
|
|
202856
|
+
if (key === "$schema") continue;
|
|
202857
|
+
if (key === "$defs") {
|
|
202858
|
+
sanitized.definitions = sanitizeForGemini(value2);
|
|
202859
|
+
continue;
|
|
202860
|
+
}
|
|
202861
|
+
sanitized[key] = sanitizeForGemini(value2);
|
|
202862
|
+
}
|
|
202863
|
+
return sanitized;
|
|
202864
|
+
}
|
|
202865
|
+
function wrapJsonSchemaProducer(producer) {
|
|
202866
|
+
return new Proxy(producer, {
|
|
202867
|
+
get(target, prop, receiver) {
|
|
202868
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
202869
|
+
if ((prop === "input" || prop === "output") && typeof value2 === "function") {
|
|
202870
|
+
const fn2 = value2;
|
|
202871
|
+
return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
|
|
202872
|
+
}
|
|
202873
|
+
return value2;
|
|
202874
|
+
}
|
|
202875
|
+
});
|
|
202876
|
+
}
|
|
202877
|
+
function wrapStandard(standard) {
|
|
202878
|
+
return new Proxy(standard, {
|
|
202879
|
+
get(target, prop, receiver) {
|
|
202880
|
+
if (prop === "jsonSchema") {
|
|
202881
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
202882
|
+
if (value2 && typeof value2 === "object") {
|
|
202883
|
+
return wrapJsonSchemaProducer(value2);
|
|
202884
|
+
}
|
|
202885
|
+
return value2;
|
|
202886
|
+
}
|
|
202887
|
+
return Reflect.get(target, prop, receiver);
|
|
202888
|
+
}
|
|
202889
|
+
});
|
|
202890
|
+
}
|
|
202891
|
+
function wrapSchemaForGemini(schema2) {
|
|
202892
|
+
return new Proxy(schema2, {
|
|
202893
|
+
get(target, prop, receiver) {
|
|
202894
|
+
if (prop === "~standard") {
|
|
202895
|
+
const value2 = Reflect.get(target, prop, receiver);
|
|
202896
|
+
if (value2 && typeof value2 === "object") {
|
|
202897
|
+
return wrapStandard(value2);
|
|
202898
|
+
}
|
|
202899
|
+
return value2;
|
|
202900
|
+
}
|
|
202901
|
+
if (prop === "toJsonSchema") {
|
|
202902
|
+
const method = Reflect.get(target, prop, receiver);
|
|
202903
|
+
if (typeof method === "function") {
|
|
202904
|
+
return () => sanitizeForGemini(method.call(target));
|
|
202905
|
+
}
|
|
202906
|
+
return method;
|
|
202907
|
+
}
|
|
202908
|
+
return Reflect.get(target, prop, receiver);
|
|
202909
|
+
}
|
|
202910
|
+
});
|
|
202911
|
+
}
|
|
202912
|
+
function sanitizeToolForGemini(tool2) {
|
|
202913
|
+
if (!tool2.parameters) return tool2;
|
|
202914
|
+
return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
|
|
202915
|
+
}
|
|
202916
|
+
function isGeminiRouted(ctx) {
|
|
202917
|
+
const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
|
|
202918
|
+
if (!effective) return true;
|
|
202919
|
+
const normalized = effective.toLowerCase();
|
|
202920
|
+
if (normalized.includes("gemini")) return true;
|
|
202921
|
+
if (!normalized.includes("/")) return true;
|
|
202922
|
+
return false;
|
|
202923
|
+
}
|
|
202924
|
+
|
|
202925
|
+
// mcp/shared.ts
|
|
202926
|
+
function getHttpStatus(err) {
|
|
202927
|
+
if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
|
|
202928
|
+
return typeof err.status === "number" ? err.status : void 0;
|
|
202929
|
+
}
|
|
202930
|
+
var tool = (toolDef) => toolDef;
|
|
202931
|
+
var handleToolSuccess = (data) => {
|
|
202932
|
+
const text = typeof data === "string" ? data : encode6(data);
|
|
202933
|
+
return {
|
|
202934
|
+
content: [{ type: "text", text }]
|
|
202935
|
+
};
|
|
202936
|
+
};
|
|
202937
|
+
var handleToolError = (error109) => {
|
|
202938
|
+
const errorMessage = error109 instanceof Error ? error109.message : String(error109);
|
|
202939
|
+
return {
|
|
202940
|
+
content: [
|
|
202941
|
+
{
|
|
202942
|
+
type: "text",
|
|
202943
|
+
text: `Error: ${errorMessage}`
|
|
202944
|
+
}
|
|
202945
|
+
],
|
|
202946
|
+
isError: true
|
|
202947
|
+
};
|
|
202948
|
+
};
|
|
202949
|
+
var execute = (fn2, toolName) => {
|
|
202950
|
+
const _fn = async (params) => {
|
|
202951
|
+
try {
|
|
202952
|
+
const result = await fn2(params);
|
|
202953
|
+
return handleToolSuccess(result);
|
|
202954
|
+
} catch (error109) {
|
|
202955
|
+
const errorMessage = error109 instanceof Error ? error109.message : String(error109);
|
|
202956
|
+
const prefix = toolName ? `[${toolName}]` : "tool";
|
|
202957
|
+
log2.info(`${prefix} error: ${errorMessage}`);
|
|
202958
|
+
log2.debug(`${prefix} params: ${formatJsonValue(params)}`);
|
|
202959
|
+
return handleToolError(error109);
|
|
202960
|
+
}
|
|
202961
|
+
};
|
|
202962
|
+
return _fn;
|
|
202963
|
+
};
|
|
202964
|
+
var addTools = (ctx, server, tools) => {
|
|
202965
|
+
const shouldSanitize = isGeminiRouted(ctx);
|
|
202966
|
+
for (const tool2 of tools) {
|
|
202967
|
+
server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
|
|
202968
|
+
}
|
|
202969
|
+
return server;
|
|
202970
|
+
};
|
|
202971
|
+
|
|
202972
|
+
// mcp/shell.ts
|
|
202973
|
+
var ShellParams = type({
|
|
202974
|
+
command: "string",
|
|
202975
|
+
// advisory and unread — no code path consumes it. kept in the schema only
|
|
202976
|
+
// because it nudges the model to state intent before running a command;
|
|
202977
|
+
// REQUIRING it cost a full rejected turn every time one was omitted. #1140.
|
|
202978
|
+
"description?": "string",
|
|
202979
|
+
"timeout?": type.number.describe(
|
|
202980
|
+
"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."
|
|
202981
|
+
),
|
|
202982
|
+
"working_directory?": "string",
|
|
202983
|
+
"background?": "boolean"
|
|
202984
|
+
});
|
|
202985
|
+
var detectedSandboxMethod;
|
|
202986
|
+
var usernsProbeError;
|
|
202987
|
+
function getSandboxMethod() {
|
|
202988
|
+
return detectSandboxMethod();
|
|
202989
|
+
}
|
|
202990
|
+
function detectSandboxMethod() {
|
|
202991
|
+
if (detectedSandboxMethod !== void 0) {
|
|
202992
|
+
return detectedSandboxMethod;
|
|
202993
|
+
}
|
|
202994
|
+
if (process.env.CI !== "true") {
|
|
202995
|
+
detectedSandboxMethod = "none";
|
|
202996
|
+
log2.debug("sandbox disabled (CI !== true)");
|
|
202997
|
+
return "none";
|
|
202998
|
+
}
|
|
202999
|
+
try {
|
|
203000
|
+
const result = spawnSync7("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
|
|
203001
|
+
timeout: 5e3,
|
|
203002
|
+
stdio: "ignore"
|
|
203003
|
+
});
|
|
203004
|
+
if (result.status === 0) {
|
|
203005
|
+
detectedSandboxMethod = "unshare";
|
|
203006
|
+
log2.debug("PID namespace isolation enabled (unprivileged unshare)");
|
|
203007
|
+
return "unshare";
|
|
203008
|
+
}
|
|
203009
|
+
} catch {
|
|
203010
|
+
}
|
|
203011
|
+
try {
|
|
203012
|
+
const result = spawnSync7("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
|
|
203013
|
+
timeout: 5e3,
|
|
203014
|
+
stdio: "ignore"
|
|
203015
|
+
});
|
|
203016
|
+
if (result.status === 0) {
|
|
203017
|
+
detectedSandboxMethod = "sudo-unshare";
|
|
203018
|
+
log2.debug("PID namespace isolation enabled (sudo unshare)");
|
|
203019
|
+
return "sudo-unshare";
|
|
203020
|
+
}
|
|
203021
|
+
} catch {
|
|
203022
|
+
}
|
|
203023
|
+
try {
|
|
203024
|
+
const result = spawnSync7(
|
|
203025
|
+
"unshare",
|
|
203026
|
+
[
|
|
203027
|
+
"--user",
|
|
203028
|
+
"--map-root-user",
|
|
203029
|
+
"--pid",
|
|
203030
|
+
"--fork",
|
|
203031
|
+
"--mount",
|
|
203032
|
+
"setpriv",
|
|
203033
|
+
"--no-new-privs",
|
|
203034
|
+
"--bounding-set",
|
|
203035
|
+
"-all",
|
|
203036
|
+
"--inh-caps",
|
|
203037
|
+
"-all",
|
|
203038
|
+
"--ambient-caps",
|
|
203039
|
+
"-all",
|
|
203040
|
+
"true"
|
|
203041
|
+
],
|
|
203042
|
+
{ timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
|
|
203043
|
+
);
|
|
203044
|
+
if (result.status === 0) {
|
|
203045
|
+
detectedSandboxMethod = "userns-unshare";
|
|
203046
|
+
log2.debug("PID namespace isolation enabled (unprivileged userns unshare)");
|
|
203047
|
+
return "userns-unshare";
|
|
203048
|
+
}
|
|
203049
|
+
usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
|
|
203050
|
+
} catch {
|
|
203051
|
+
}
|
|
203052
|
+
detectedSandboxMethod = "none";
|
|
203053
|
+
log2.info("PID namespace isolation not available");
|
|
203054
|
+
return "none";
|
|
203055
|
+
}
|
|
203056
|
+
var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
|
203057
|
+
var SOCKET_CLEANUP = [
|
|
203058
|
+
"/var/run/docker.sock",
|
|
203059
|
+
"/run/docker.sock",
|
|
203060
|
+
"/var/run/podman/podman.sock",
|
|
203061
|
+
"/run/podman/podman.sock",
|
|
203062
|
+
"/run/containerd/containerd.sock",
|
|
203063
|
+
"/var/run/crio/crio.sock"
|
|
203064
|
+
].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
|
|
203065
|
+
function buildFsMounts(repoDir) {
|
|
203066
|
+
const escaped = repoDir.replace(/'/g, "'\\''");
|
|
203067
|
+
return [
|
|
203068
|
+
`mkdir -p /var/lib/pullfrog 2>/dev/null;`,
|
|
203069
|
+
`mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
|
|
203070
|
+
`[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
|
|
203071
|
+
`[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
|
|
203072
|
+
// the corepack shim dir sits FIRST on PATH (utils/packageManager.ts) and
|
|
203073
|
+
// PATH survives filterEnv, so a writable pm-bin lets sandboxed code plant
|
|
203074
|
+
// a `git`/`pnpm` that a later UNSANDBOXED spawn resolves with the parent
|
|
203075
|
+
// env. read-only closes that for every sandboxed command at once.
|
|
203076
|
+
//
|
|
203077
|
+
// the path is interpolated HERE rather than read as `$PULLFROG_TEMP_DIR`
|
|
203078
|
+
// inside the sandbox: that name is not on the `filterEnv` safe list, so in
|
|
203079
|
+
// restricted mode it expands to empty and the mount silently no-ops in the
|
|
203080
|
+
// one tier it exists to protect. `$RUNNER_TEMP` above is safe only because
|
|
203081
|
+
// `RUNNER_` is an allowed prefix.
|
|
203082
|
+
shimMount()
|
|
203083
|
+
].filter(Boolean).join(" ");
|
|
203084
|
+
}
|
|
203085
|
+
function shimMount() {
|
|
203086
|
+
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
203087
|
+
if (!tempDir) return "";
|
|
203088
|
+
const dir = join11(tempDir, "pm-bin").replace(/'/g, "'\\''");
|
|
203089
|
+
return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
|
|
203090
|
+
}
|
|
203091
|
+
var _repoRoot;
|
|
203092
|
+
function resolveRepoRoot() {
|
|
203093
|
+
if (_repoRoot) return _repoRoot;
|
|
203094
|
+
const fromEnv = process.env.GITHUB_WORKSPACE;
|
|
203095
|
+
if (fromEnv) {
|
|
203096
|
+
_repoRoot = fromEnv;
|
|
203097
|
+
return _repoRoot;
|
|
203098
|
+
}
|
|
203099
|
+
try {
|
|
203100
|
+
_repoRoot = spawnSync7("git", ["rev-parse", "--show-toplevel"], {
|
|
203101
|
+
cwd: process.cwd(),
|
|
203102
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
203103
|
+
encoding: "utf-8"
|
|
203104
|
+
}).stdout?.trim();
|
|
203105
|
+
} catch {
|
|
203106
|
+
}
|
|
203107
|
+
if (!_repoRoot) _repoRoot = process.cwd();
|
|
203108
|
+
return _repoRoot;
|
|
203109
|
+
}
|
|
203110
|
+
function spawnShell(params) {
|
|
203111
|
+
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
|
203112
|
+
const sandboxMethod = detectSandboxMethod();
|
|
203113
|
+
const ci = process.env.CI === "true";
|
|
203114
|
+
if (ci && sandboxMethod === "none") {
|
|
203115
|
+
throw new Error(
|
|
203116
|
+
"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"
|
|
203117
|
+
);
|
|
203118
|
+
}
|
|
203119
|
+
const repoRoot = resolveRepoRoot();
|
|
203120
|
+
const fsMounts = buildFsMounts(repoRoot);
|
|
203121
|
+
if (sandboxMethod === "unshare") {
|
|
203122
|
+
return spawn5(
|
|
203123
|
+
"unshare",
|
|
203124
|
+
[
|
|
203125
|
+
"--pid",
|
|
203126
|
+
"--fork",
|
|
203127
|
+
"--mount-proc",
|
|
203128
|
+
"bash",
|
|
203129
|
+
"-c",
|
|
203130
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
|
|
203131
|
+
],
|
|
203132
|
+
spawnOpts
|
|
203133
|
+
);
|
|
203134
|
+
}
|
|
203135
|
+
if (sandboxMethod === "userns-unshare") {
|
|
203136
|
+
const escaped = params.command.replace(/'/g, "'\\''");
|
|
203137
|
+
return spawn5(
|
|
203138
|
+
"unshare",
|
|
203139
|
+
[
|
|
203140
|
+
"--user",
|
|
203141
|
+
"--map-root-user",
|
|
203142
|
+
"--pid",
|
|
203143
|
+
"--fork",
|
|
203144
|
+
"--mount",
|
|
203145
|
+
"bash",
|
|
203146
|
+
"-c",
|
|
203147
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
|
|
203148
|
+
],
|
|
203149
|
+
spawnOpts
|
|
203150
|
+
);
|
|
203151
|
+
}
|
|
203152
|
+
if (sandboxMethod === "sudo-unshare") {
|
|
203153
|
+
const envArgs = [];
|
|
203154
|
+
for (const [k2, v] of Object.entries(params.env)) {
|
|
203155
|
+
if (v !== void 0) {
|
|
203156
|
+
envArgs.push(`${k2}=${v}`);
|
|
203157
|
+
}
|
|
203158
|
+
}
|
|
203159
|
+
const username = userInfo2().username;
|
|
203160
|
+
const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
|
|
203161
|
+
const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
|
|
203162
|
+
envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
|
|
203163
|
+
return spawn5(
|
|
203164
|
+
"sudo",
|
|
203165
|
+
[
|
|
203166
|
+
"env",
|
|
203167
|
+
...envArgs,
|
|
203168
|
+
"unshare",
|
|
203169
|
+
"--pid",
|
|
203170
|
+
"--fork",
|
|
203171
|
+
"--mount-proc",
|
|
203172
|
+
"bash",
|
|
203173
|
+
"-c",
|
|
203174
|
+
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
|
|
203175
|
+
],
|
|
203176
|
+
{ ...spawnOpts, env: {} }
|
|
203177
|
+
);
|
|
203178
|
+
}
|
|
203179
|
+
return spawn5("bash", ["-c", params.command], spawnOpts);
|
|
203180
|
+
}
|
|
203181
|
+
async function killProcessGroup(proc) {
|
|
203182
|
+
if (!proc.pid) return;
|
|
203183
|
+
try {
|
|
203184
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
203185
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
203186
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
203187
|
+
} catch {
|
|
203188
|
+
try {
|
|
203189
|
+
proc.kill("SIGKILL");
|
|
203190
|
+
} catch {
|
|
203191
|
+
}
|
|
203192
|
+
}
|
|
203193
|
+
}
|
|
203194
|
+
function getTempDir() {
|
|
203195
|
+
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
203196
|
+
if (!tempDir) {
|
|
203197
|
+
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
203198
|
+
}
|
|
203199
|
+
return tempDir;
|
|
203200
|
+
}
|
|
203201
|
+
var MAX_OUTPUT_CHARS = 5e3;
|
|
203202
|
+
async function runSandboxed(params) {
|
|
203203
|
+
const proc = spawnShell({
|
|
203204
|
+
command: params.command,
|
|
203205
|
+
env: params.env,
|
|
203206
|
+
cwd: params.cwd,
|
|
203207
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
203208
|
+
});
|
|
203209
|
+
const stdout2 = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
203210
|
+
const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
203211
|
+
let timedOut = false, exited = false, spawnError = "";
|
|
203212
|
+
proc.stdout?.on("data", (chunk) => {
|
|
203213
|
+
const text = chunk.toString();
|
|
203214
|
+
stdout2.append(text);
|
|
203215
|
+
params.onStdout?.(text);
|
|
203216
|
+
});
|
|
203217
|
+
proc.stderr?.on("data", (chunk) => {
|
|
203218
|
+
const text = chunk.toString();
|
|
203219
|
+
stderr.append(text);
|
|
203220
|
+
params.onStderr?.(text);
|
|
203221
|
+
});
|
|
203222
|
+
const timeoutId = setTimeout(async () => {
|
|
203223
|
+
if (!exited) {
|
|
203224
|
+
timedOut = true;
|
|
203225
|
+
await killProcessGroup(proc);
|
|
203226
|
+
}
|
|
203227
|
+
}, params.timeout);
|
|
203228
|
+
const exitCode = await new Promise((resolve3) => {
|
|
203229
|
+
const done = (code) => {
|
|
203230
|
+
exited = true;
|
|
203231
|
+
clearTimeout(timeoutId);
|
|
203232
|
+
resolve3(code);
|
|
203233
|
+
};
|
|
203234
|
+
proc.on("exit", done);
|
|
203235
|
+
proc.on("error", (err) => {
|
|
203236
|
+
spawnError = err.message;
|
|
203237
|
+
done(null);
|
|
203238
|
+
});
|
|
203239
|
+
});
|
|
203240
|
+
proc.stdout?.destroy();
|
|
203241
|
+
proc.stderr?.destroy();
|
|
203242
|
+
const outText = stdout2.toString();
|
|
203243
|
+
const errText = stderr.toString();
|
|
203244
|
+
let output2 = errText ? outText ? `${outText}
|
|
203245
|
+
${errText}` : errText : outText;
|
|
203246
|
+
if (spawnError) output2 = output2 ? `${output2}
|
|
203247
|
+
${spawnError}` : spawnError;
|
|
203248
|
+
if (timedOut)
|
|
203249
|
+
output2 = output2 ? `${output2}
|
|
203250
|
+
[timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
|
|
203251
|
+
return {
|
|
203252
|
+
output: output2.trim(),
|
|
203253
|
+
exitCode: exitCode ?? (timedOut ? 124 : -1),
|
|
203254
|
+
timedOut
|
|
203255
|
+
};
|
|
203256
|
+
}
|
|
203257
|
+
function capOutput(output2) {
|
|
203258
|
+
if (output2.length <= MAX_OUTPUT_CHARS) return output2;
|
|
203259
|
+
const fullPath = join11(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
|
|
203260
|
+
writeFileSync9(fullPath, output2);
|
|
203261
|
+
const elided = output2.length - MAX_OUTPUT_CHARS;
|
|
203262
|
+
return `... [${elided} chars truncated; output saved to ${fullPath}] ...
|
|
203263
|
+
${output2.slice(-MAX_OUTPUT_CHARS)}`;
|
|
203264
|
+
}
|
|
203265
|
+
function isGitCommand(command) {
|
|
203266
|
+
const trimmed = command.trim();
|
|
203267
|
+
if (trimmed === "git" || trimmed.startsWith("git ")) return true;
|
|
203268
|
+
if (trimmed.startsWith("sudo git")) return true;
|
|
203269
|
+
return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
|
|
203270
|
+
}
|
|
203271
|
+
function ShellTool(ctx) {
|
|
203272
|
+
return tool({
|
|
203273
|
+
name: "shell",
|
|
203274
|
+
timeoutMs: 12e4,
|
|
203275
|
+
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
203276
|
+
|
|
203277
|
+
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
203278
|
+
|
|
203279
|
+
Use this tool to:
|
|
203280
|
+
- Run shell commands (ls, cat, grep, find, etc.)
|
|
203281
|
+
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
203282
|
+
- Run tests and linters
|
|
203283
|
+
|
|
203284
|
+
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.
|
|
203285
|
+
|
|
203286
|
+
Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
|
|
203287
|
+
parameters: ShellParams,
|
|
203288
|
+
execute: execute(async (params) => {
|
|
203289
|
+
if (isGitCommand(params.command)) {
|
|
203290
|
+
throw new Error(
|
|
203291
|
+
'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'
|
|
203292
|
+
);
|
|
203293
|
+
}
|
|
203294
|
+
const timeout = Math.min(params.timeout ?? 3e4, 12e4);
|
|
203295
|
+
const cwd = params.working_directory ?? process.cwd();
|
|
203296
|
+
const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
|
|
203297
|
+
if (params.command.includes("agent-browser")) {
|
|
203298
|
+
const daemonError = ensureBrowserDaemon(ctx.toolState);
|
|
203299
|
+
if (daemonError) {
|
|
203300
|
+
return {
|
|
203301
|
+
output: `browser daemon unavailable: ${daemonError}`,
|
|
203302
|
+
exit_code: 1,
|
|
203303
|
+
timed_out: false
|
|
203304
|
+
};
|
|
203305
|
+
}
|
|
203306
|
+
const binDir = ctx.toolState.browserDaemon?.binDir;
|
|
203307
|
+
if (binDir) {
|
|
203308
|
+
env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
|
|
203309
|
+
}
|
|
203310
|
+
}
|
|
203311
|
+
if (params.background) {
|
|
203312
|
+
const tempDir = getTempDir();
|
|
203313
|
+
const handle = `bg-${randomUUID3().slice(0, 8)}`;
|
|
203314
|
+
const outputPath = join11(tempDir, `${handle}.log`);
|
|
203315
|
+
const pidPath = join11(tempDir, `${handle}.pid`);
|
|
203316
|
+
const logFd = openSync(outputPath, "a");
|
|
203317
|
+
let proc;
|
|
203318
|
+
try {
|
|
203319
|
+
proc = spawnShell({
|
|
203320
|
+
command: params.command,
|
|
203321
|
+
env: env2,
|
|
203322
|
+
cwd,
|
|
203323
|
+
stdio: ["ignore", logFd, logFd]
|
|
203324
|
+
});
|
|
203325
|
+
} finally {
|
|
203326
|
+
closeSync(logFd);
|
|
203327
|
+
}
|
|
203328
|
+
if (!proc.pid) {
|
|
203329
|
+
throw new Error("failed to start background process");
|
|
203330
|
+
}
|
|
203331
|
+
proc.unref();
|
|
203332
|
+
writeFileSync9(pidPath, `${proc.pid}
|
|
203333
|
+
`);
|
|
203334
|
+
ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
|
|
203335
|
+
return {
|
|
203336
|
+
handle,
|
|
203337
|
+
outputPath,
|
|
203338
|
+
pidPath,
|
|
203339
|
+
message: `started background process ${handle} (pid ${proc.pid})`
|
|
203340
|
+
};
|
|
203341
|
+
}
|
|
203342
|
+
const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
|
|
203343
|
+
if (result.exitCode !== 0) {
|
|
203344
|
+
log2.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
|
|
203345
|
+
if (result.output) log2.info(`output: ${result.output}`);
|
|
203346
|
+
}
|
|
203347
|
+
return {
|
|
203348
|
+
output: capOutput(result.output),
|
|
203349
|
+
exit_code: result.exitCode,
|
|
203350
|
+
timed_out: result.timedOut
|
|
203351
|
+
};
|
|
203352
|
+
})
|
|
203353
|
+
});
|
|
203354
|
+
}
|
|
203355
|
+
var KillBackgroundParams = type({
|
|
203356
|
+
handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
|
|
203357
|
+
});
|
|
203358
|
+
function KillBackgroundTool(ctx) {
|
|
203359
|
+
return tool({
|
|
203360
|
+
name: "kill_background",
|
|
203361
|
+
mutates: true,
|
|
203362
|
+
description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
|
|
203363
|
+
parameters: KillBackgroundParams,
|
|
203364
|
+
execute: execute(async (params) => {
|
|
203365
|
+
const proc = ctx.toolState.backgroundProcesses.get(params.handle);
|
|
203366
|
+
if (!proc) {
|
|
203367
|
+
return {
|
|
203368
|
+
success: false,
|
|
203369
|
+
message: `no background process with handle ${params.handle}`
|
|
203370
|
+
};
|
|
203371
|
+
}
|
|
203372
|
+
try {
|
|
203373
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
203374
|
+
} catch {
|
|
203375
|
+
}
|
|
203376
|
+
await sleep2(200);
|
|
203377
|
+
try {
|
|
203378
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
203379
|
+
} catch {
|
|
203380
|
+
}
|
|
203381
|
+
ctx.toolState.backgroundProcesses.delete(params.handle);
|
|
203382
|
+
return {
|
|
203383
|
+
success: true,
|
|
203384
|
+
message: `killed background process ${params.handle} (pid ${proc.pid})`
|
|
203385
|
+
};
|
|
203386
|
+
})
|
|
203387
|
+
});
|
|
203388
|
+
}
|
|
203389
|
+
|
|
202385
203390
|
// utils/lifecycle.ts
|
|
202386
203391
|
function describeSetupFailure(failure2) {
|
|
202387
203392
|
if (!failure2) return "";
|
|
@@ -202403,44 +203408,47 @@ ${failure2.output || "(empty)"}`;
|
|
|
202403
203408
|
async function executeLifecycleHook(params) {
|
|
202404
203409
|
if (!params.script) return {};
|
|
202405
203410
|
log2.info(`\xBB executing ${params.event} lifecycle hook...`);
|
|
203411
|
+
resolveTrustedGit();
|
|
202406
203412
|
const preHookTrackedCount = params.normalizeWorkingTreeAfter ? (await runGitLines(["diff", "--name-only", "HEAD"])).length : 0;
|
|
202407
203413
|
let result;
|
|
202408
203414
|
try {
|
|
203415
|
+
let sandboxed;
|
|
202409
203416
|
try {
|
|
202410
|
-
|
|
202411
|
-
|
|
202412
|
-
|
|
202413
|
-
|
|
203417
|
+
sandboxed = await runSandboxed({
|
|
203418
|
+
command: params.script,
|
|
203419
|
+
env: resolveEnv(params.shell === "enabled" ? "inherit" : "restricted"),
|
|
203420
|
+
cwd: process.cwd(),
|
|
202414
203421
|
timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
|
|
202415
|
-
activityTimeout: 0,
|
|
202416
203422
|
onStdout: (chunk) => process.stdout.write(chunk),
|
|
202417
203423
|
onStderr: (chunk) => process.stderr.write(chunk)
|
|
202418
203424
|
});
|
|
202419
|
-
if (spawnResult.exitCode !== 0) {
|
|
202420
|
-
const output2 = (spawnResult.stderr || spawnResult.stdout).trim();
|
|
202421
|
-
result = {
|
|
202422
|
-
failure: { kind: "exit", output: output2, exitCode: spawnResult.exitCode },
|
|
202423
|
-
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.`
|
|
202424
|
-
};
|
|
202425
|
-
} else {
|
|
202426
|
-
log2.info(`\xBB ${params.event} lifecycle hook completed successfully`);
|
|
202427
|
-
result = {};
|
|
202428
|
-
}
|
|
202429
203425
|
} catch (err) {
|
|
202430
|
-
const
|
|
202431
|
-
|
|
202432
|
-
|
|
202433
|
-
|
|
202434
|
-
|
|
202435
|
-
|
|
202436
|
-
|
|
202437
|
-
|
|
202438
|
-
|
|
202439
|
-
|
|
202440
|
-
|
|
202441
|
-
|
|
202442
|
-
|
|
202443
|
-
|
|
203426
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
203427
|
+
return {
|
|
203428
|
+
failure: { kind: "spawn", spawnError: msg },
|
|
203429
|
+
warning: `lifecycle hook '${params.event}' could not be sandboxed, so it did not run: ${msg}`
|
|
203430
|
+
};
|
|
203431
|
+
}
|
|
203432
|
+
if (sandboxed.timedOut) {
|
|
203433
|
+
const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
|
|
203434
|
+
result = {
|
|
203435
|
+
failure: { kind: "timeout" },
|
|
203436
|
+
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).`
|
|
203437
|
+
};
|
|
203438
|
+
} else if (sandboxed.exitCode === -1) {
|
|
203439
|
+
result = {
|
|
203440
|
+
failure: { kind: "spawn", spawnError: sandboxed.output },
|
|
203441
|
+
warning: `lifecycle hook '${params.event}' failed to spawn: ${sandboxed.output}. this is likely a transient failure \u2014 retry the operation.`
|
|
203442
|
+
};
|
|
203443
|
+
} else if (sandboxed.exitCode !== 0) {
|
|
203444
|
+
const output2 = capOutput(sandboxed.output);
|
|
203445
|
+
result = {
|
|
203446
|
+
failure: { kind: "exit", output: output2, exitCode: sandboxed.exitCode },
|
|
203447
|
+
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.`
|
|
203448
|
+
};
|
|
203449
|
+
} else {
|
|
203450
|
+
log2.info(`\xBB ${params.event} lifecycle hook completed successfully`);
|
|
203451
|
+
result = {};
|
|
202444
203452
|
}
|
|
202445
203453
|
} finally {
|
|
202446
203454
|
if (params.normalizeWorkingTreeAfter) {
|
|
@@ -202461,8 +203469,20 @@ async function normalizeWorkingTreeAfterHook(params) {
|
|
|
202461
203469
|
await runGit(["restore", "--staged", "--worktree", "."]);
|
|
202462
203470
|
log2.info(`\xBB discarded ${trackedCount} tracked changes from ${params.event} hook`);
|
|
202463
203471
|
}
|
|
203472
|
+
var trustedGit;
|
|
203473
|
+
function resolveTrustedGit() {
|
|
203474
|
+
if (trustedGit) return trustedGit;
|
|
203475
|
+
const resolved = spawnSync8("sh", ["-c", "command -v git"], { encoding: "utf-8" });
|
|
203476
|
+
trustedGit = resolved.stdout?.trim() || "git";
|
|
203477
|
+
return trustedGit;
|
|
203478
|
+
}
|
|
202464
203479
|
async function runGit(args2) {
|
|
202465
|
-
const result = await spawn3({
|
|
203480
|
+
const result = await spawn3({
|
|
203481
|
+
cmd: resolveTrustedGit(),
|
|
203482
|
+
args: args2,
|
|
203483
|
+
env: filterEnvForUntrustedCode(),
|
|
203484
|
+
activityTimeout: 0
|
|
203485
|
+
});
|
|
202466
203486
|
if (result.exitCode !== 0) {
|
|
202467
203487
|
throw new Error(
|
|
202468
203488
|
`git ${args2.join(" ")} failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
|
|
@@ -202582,7 +203602,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
202582
203602
|
|
|
202583
203603
|
// yes/index.ts
|
|
202584
203604
|
import { performance as performance8 } from "node:perf_hooks";
|
|
202585
|
-
import { setTimeout as
|
|
203605
|
+
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
202586
203606
|
|
|
202587
203607
|
// node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
|
|
202588
203608
|
import { tracingChannel as G, channel as P } from "node:diagnostics_channel";
|
|
@@ -203376,7 +204396,7 @@ function _op(fn2, options) {
|
|
|
203376
204396
|
);
|
|
203377
204397
|
log3.info({ key, error: error109, attempt });
|
|
203378
204398
|
}
|
|
203379
|
-
await
|
|
204399
|
+
await sleep3(delay2);
|
|
203380
204400
|
}
|
|
203381
204401
|
}
|
|
203382
204402
|
}
|
|
@@ -203444,14 +204464,14 @@ function _op(fn2, options) {
|
|
|
203444
204464
|
}
|
|
203445
204465
|
|
|
203446
204466
|
// mcp/git.ts
|
|
203447
|
-
import { randomUUID as
|
|
203448
|
-
import { writeFileSync as
|
|
203449
|
-
import { join as
|
|
204467
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
204468
|
+
import { writeFileSync as writeFileSync10 } from "node:fs";
|
|
204469
|
+
import { join as join14 } from "node:path";
|
|
203450
204470
|
|
|
203451
204471
|
// utils/apiCommit.ts
|
|
203452
204472
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
203453
204473
|
import { lstat, readlink } from "node:fs/promises";
|
|
203454
|
-
import { join as
|
|
204474
|
+
import { join as join12 } from "node:path";
|
|
203455
204475
|
var GITHUB_API = "https://api.github.com";
|
|
203456
204476
|
var MAX_BLOB_BYTES = 30 * 1024 * 1024;
|
|
203457
204477
|
var BLOB_UPLOAD_CONCURRENCY = 8;
|
|
@@ -203516,7 +204536,7 @@ async function assertApiCommittable(files) {
|
|
|
203516
204536
|
const root = getRepoRoot();
|
|
203517
204537
|
const attrs = $(
|
|
203518
204538
|
"git",
|
|
203519
|
-
["check-attr", "filter", "-z", "--", ...present.map((p) =>
|
|
204539
|
+
["check-attr", "filter", "-z", "--", ...present.map((p) => join12(root, p))],
|
|
203520
204540
|
{ log: false }
|
|
203521
204541
|
);
|
|
203522
204542
|
const parts = attrs.split("\0");
|
|
@@ -203528,7 +204548,7 @@ async function assertApiCommittable(files) {
|
|
|
203528
204548
|
}
|
|
203529
204549
|
}
|
|
203530
204550
|
for (const path4 of present) {
|
|
203531
|
-
const stat = await lstat(
|
|
204551
|
+
const stat = await lstat(join12(root, path4));
|
|
203532
204552
|
if (stat.isDirectory()) {
|
|
203533
204553
|
throw new Error(
|
|
203534
204554
|
`'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
|
|
@@ -203537,7 +204557,7 @@ async function assertApiCommittable(files) {
|
|
|
203537
204557
|
}
|
|
203538
204558
|
}
|
|
203539
204559
|
async function createBlobEntry(params) {
|
|
203540
|
-
const absPath =
|
|
204560
|
+
const absPath = join12(params.repoRoot, params.path);
|
|
203541
204561
|
const stat = await lstat(absPath);
|
|
203542
204562
|
if (stat.size > MAX_BLOB_BYTES) {
|
|
203543
204563
|
throw new Error(
|
|
@@ -203717,7 +204737,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
|
|
|
203717
204737
|
var core5 = __toESM(require_core(), 1);
|
|
203718
204738
|
import { createSign } from "node:crypto";
|
|
203719
204739
|
import { rename, writeFile } from "node:fs/promises";
|
|
203720
|
-
import { dirname as dirname3, join as
|
|
204740
|
+
import { dirname as dirname3, join as join13 } from "node:path";
|
|
203721
204741
|
|
|
203722
204742
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
203723
204743
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -204089,7 +205109,7 @@ function lowercaseKeys(object6) {
|
|
|
204089
205109
|
return newObj;
|
|
204090
205110
|
}, {});
|
|
204091
205111
|
}
|
|
204092
|
-
function
|
|
205112
|
+
function isPlainObject8(value2) {
|
|
204093
205113
|
if (typeof value2 !== "object" || value2 === null) return false;
|
|
204094
205114
|
if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
|
|
204095
205115
|
const proto = Object.getPrototypeOf(value2);
|
|
@@ -204100,7 +205120,7 @@ function isPlainObject7(value2) {
|
|
|
204100
205120
|
function mergeDeep(defaults2, options) {
|
|
204101
205121
|
const result = Object.assign({}, defaults2);
|
|
204102
205122
|
Object.keys(options).forEach((key) => {
|
|
204103
|
-
if (
|
|
205123
|
+
if (isPlainObject8(options[key])) {
|
|
204104
205124
|
if (!(key in defaults2)) Object.assign(result, { [key]: options[key] });
|
|
204105
205125
|
else result[key] = mergeDeep(defaults2[key], options[key]);
|
|
204106
205126
|
} else {
|
|
@@ -204387,7 +205407,7 @@ var NullObject = /* @__PURE__ */ (() => {
|
|
|
204387
205407
|
return C;
|
|
204388
205408
|
})();
|
|
204389
205409
|
function parse7(header, options) {
|
|
204390
|
-
const stopChar = options?.comma === true ?
|
|
205410
|
+
const stopChar = options?.comma === true ? COMMA2 : 65536;
|
|
204391
205411
|
const len = header.length;
|
|
204392
205412
|
let index = skipOWS(header, options?.start ?? 0, len);
|
|
204393
205413
|
const valueStart = index;
|
|
@@ -204405,7 +205425,7 @@ var SEMI = 59;
|
|
|
204405
205425
|
var EQ = 61;
|
|
204406
205426
|
var DQUOTE = 34;
|
|
204407
205427
|
var BSLASH = 92;
|
|
204408
|
-
var
|
|
205428
|
+
var COMMA2 = 44;
|
|
204409
205429
|
function parseParameters(header, type2, index, len, stopChar) {
|
|
204410
205430
|
const parameters = new NullObject();
|
|
204411
205431
|
parameter: while (index < len) {
|
|
@@ -204864,7 +205884,7 @@ var defaults_default = {
|
|
|
204864
205884
|
"user-agent": `octokit-request.js/${VERSION3} ${getUserAgent2()}`
|
|
204865
205885
|
}
|
|
204866
205886
|
};
|
|
204867
|
-
function
|
|
205887
|
+
function isPlainObject9(value2) {
|
|
204868
205888
|
if (typeof value2 !== "object" || value2 === null) return false;
|
|
204869
205889
|
if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
|
|
204870
205890
|
const proto = Object.getPrototypeOf(value2);
|
|
@@ -204882,7 +205902,7 @@ async function fetchWrapper(requestOptions) {
|
|
|
204882
205902
|
}
|
|
204883
205903
|
const log4 = requestOptions.request?.log || console;
|
|
204884
205904
|
const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
|
|
204885
|
-
const body =
|
|
205905
|
+
const body = isPlainObject9(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
|
|
204886
205906
|
const requestHeaders = Object.fromEntries(
|
|
204887
205907
|
Object.entries(requestOptions.headers).map(([name, value2]) => [
|
|
204888
205908
|
name,
|
|
@@ -208187,7 +209207,7 @@ function getGitHubUsageSummary() {
|
|
|
208187
209207
|
}
|
|
208188
209208
|
async function writeGitHubUsageSummaryToFile(path4) {
|
|
208189
209209
|
const summary2 = getGitHubUsageSummary();
|
|
208190
|
-
const tmpPath =
|
|
209210
|
+
const tmpPath = join13(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
|
|
208191
209211
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
208192
209212
|
await rename(tmpPath, path4);
|
|
208193
209213
|
}
|
|
@@ -208294,531 +209314,6 @@ function resolveRepoCtx(ctx, repo) {
|
|
|
208294
209314
|
};
|
|
208295
209315
|
}
|
|
208296
209316
|
|
|
208297
|
-
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
208298
|
-
var LIST_ITEM_MARKER = "-";
|
|
208299
|
-
var LIST_ITEM_PREFIX = "- ";
|
|
208300
|
-
var COMMA2 = ",";
|
|
208301
|
-
var PIPE = "|";
|
|
208302
|
-
var DOT = ".";
|
|
208303
|
-
var NULL_LITERAL = "null";
|
|
208304
|
-
var TRUE_LITERAL = "true";
|
|
208305
|
-
var FALSE_LITERAL = "false";
|
|
208306
|
-
var BACKSLASH = "\\";
|
|
208307
|
-
var DOUBLE_QUOTE = '"';
|
|
208308
|
-
var TAB = " ";
|
|
208309
|
-
var DELIMITERS = {
|
|
208310
|
-
comma: COMMA2,
|
|
208311
|
-
tab: TAB,
|
|
208312
|
-
pipe: PIPE
|
|
208313
|
-
};
|
|
208314
|
-
var DEFAULT_DELIMITER = DELIMITERS.comma;
|
|
208315
|
-
function escapeString(value2) {
|
|
208316
|
-
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`);
|
|
208317
|
-
}
|
|
208318
|
-
function isBooleanOrNullLiteral(token) {
|
|
208319
|
-
return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
|
|
208320
|
-
}
|
|
208321
|
-
function normalizeValue(value2) {
|
|
208322
|
-
if (value2 === null) return null;
|
|
208323
|
-
if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
|
|
208324
|
-
if (typeof value2 === "number") {
|
|
208325
|
-
if (Object.is(value2, -0)) return 0;
|
|
208326
|
-
if (!Number.isFinite(value2)) return null;
|
|
208327
|
-
return value2;
|
|
208328
|
-
}
|
|
208329
|
-
if (typeof value2 === "bigint") {
|
|
208330
|
-
if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
|
|
208331
|
-
return value2.toString();
|
|
208332
|
-
}
|
|
208333
|
-
if (value2 instanceof Date) return value2.toISOString();
|
|
208334
|
-
if (Array.isArray(value2)) return value2.map(normalizeValue);
|
|
208335
|
-
if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
|
|
208336
|
-
if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
|
|
208337
|
-
if (isPlainObject9(value2)) {
|
|
208338
|
-
const normalized = {};
|
|
208339
|
-
for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
|
|
208340
|
-
return normalized;
|
|
208341
|
-
}
|
|
208342
|
-
return null;
|
|
208343
|
-
}
|
|
208344
|
-
function isJsonPrimitive(value2) {
|
|
208345
|
-
return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
|
|
208346
|
-
}
|
|
208347
|
-
function isJsonArray(value2) {
|
|
208348
|
-
return Array.isArray(value2);
|
|
208349
|
-
}
|
|
208350
|
-
function isJsonObject4(value2) {
|
|
208351
|
-
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
208352
|
-
}
|
|
208353
|
-
function isEmptyObject2(value2) {
|
|
208354
|
-
return Object.keys(value2).length === 0;
|
|
208355
|
-
}
|
|
208356
|
-
function isPlainObject9(value2) {
|
|
208357
|
-
if (value2 === null || typeof value2 !== "object") return false;
|
|
208358
|
-
const prototype = Object.getPrototypeOf(value2);
|
|
208359
|
-
return prototype === null || prototype === Object.prototype;
|
|
208360
|
-
}
|
|
208361
|
-
function isArrayOfPrimitives(value2) {
|
|
208362
|
-
return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
|
|
208363
|
-
}
|
|
208364
|
-
function isArrayOfArrays(value2) {
|
|
208365
|
-
return value2.length === 0 || value2.every((item) => isJsonArray(item));
|
|
208366
|
-
}
|
|
208367
|
-
function isArrayOfObjects(value2) {
|
|
208368
|
-
return value2.length === 0 || value2.every((item) => isJsonObject4(item));
|
|
208369
|
-
}
|
|
208370
|
-
function isValidUnquotedKey(key) {
|
|
208371
|
-
return /^[A-Z_][\w.]*$/i.test(key);
|
|
208372
|
-
}
|
|
208373
|
-
function isIdentifierSegment(key) {
|
|
208374
|
-
return /^[A-Z_]\w*$/i.test(key);
|
|
208375
|
-
}
|
|
208376
|
-
function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
|
|
208377
|
-
if (!value2) return false;
|
|
208378
|
-
if (value2 !== value2.trim()) return false;
|
|
208379
|
-
if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
|
|
208380
|
-
if (value2.includes(":")) return false;
|
|
208381
|
-
if (value2.includes('"') || value2.includes("\\")) return false;
|
|
208382
|
-
if (/[[\]{}]/.test(value2)) return false;
|
|
208383
|
-
if (/[\n\r\t]/.test(value2)) return false;
|
|
208384
|
-
if (value2.includes(delimiter3)) return false;
|
|
208385
|
-
if (value2.startsWith(LIST_ITEM_MARKER)) return false;
|
|
208386
|
-
return true;
|
|
208387
|
-
}
|
|
208388
|
-
function isNumericLike(value2) {
|
|
208389
|
-
return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
|
|
208390
|
-
}
|
|
208391
|
-
var QUOTED_KEY_MARKER = Symbol("quotedKey");
|
|
208392
|
-
function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
208393
|
-
if (options.keyFolding !== "safe") return;
|
|
208394
|
-
if (!isJsonObject4(value2)) return;
|
|
208395
|
-
const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
|
|
208396
|
-
if (segments.length < 2) return;
|
|
208397
|
-
if (!segments.every((seg) => isIdentifierSegment(seg))) return;
|
|
208398
|
-
const foldedKey = buildFoldedKey(segments);
|
|
208399
|
-
const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
208400
|
-
if (siblings.includes(foldedKey)) return;
|
|
208401
|
-
if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
|
|
208402
|
-
return {
|
|
208403
|
-
foldedKey,
|
|
208404
|
-
remainder: tail,
|
|
208405
|
-
leafValue,
|
|
208406
|
-
segmentCount: segments.length
|
|
208407
|
-
};
|
|
208408
|
-
}
|
|
208409
|
-
function collectSingleKeyChain(startKey, startValue, maxDepth) {
|
|
208410
|
-
const segments = [startKey];
|
|
208411
|
-
let currentValue = startValue;
|
|
208412
|
-
while (segments.length < maxDepth) {
|
|
208413
|
-
if (!isJsonObject4(currentValue)) break;
|
|
208414
|
-
const keys = Object.keys(currentValue);
|
|
208415
|
-
if (keys.length !== 1) break;
|
|
208416
|
-
const nextKey = keys[0];
|
|
208417
|
-
const nextValue = currentValue[nextKey];
|
|
208418
|
-
segments.push(nextKey);
|
|
208419
|
-
currentValue = nextValue;
|
|
208420
|
-
}
|
|
208421
|
-
if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
|
|
208422
|
-
segments,
|
|
208423
|
-
tail: void 0,
|
|
208424
|
-
leafValue: currentValue
|
|
208425
|
-
};
|
|
208426
|
-
return {
|
|
208427
|
-
segments,
|
|
208428
|
-
tail: currentValue,
|
|
208429
|
-
leafValue: currentValue
|
|
208430
|
-
};
|
|
208431
|
-
}
|
|
208432
|
-
function buildFoldedKey(segments) {
|
|
208433
|
-
return segments.join(DOT);
|
|
208434
|
-
}
|
|
208435
|
-
function encodePrimitive(value2, delimiter3) {
|
|
208436
|
-
if (value2 === null) return NULL_LITERAL;
|
|
208437
|
-
if (typeof value2 === "boolean") return String(value2);
|
|
208438
|
-
if (typeof value2 === "number") return String(value2);
|
|
208439
|
-
return encodeStringLiteral(value2, delimiter3);
|
|
208440
|
-
}
|
|
208441
|
-
function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
|
|
208442
|
-
if (isSafeUnquoted(value2, delimiter3)) return value2;
|
|
208443
|
-
return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
|
|
208444
|
-
}
|
|
208445
|
-
function encodeKey(key) {
|
|
208446
|
-
if (isValidUnquotedKey(key)) return key;
|
|
208447
|
-
return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
|
|
208448
|
-
}
|
|
208449
|
-
function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
|
|
208450
|
-
return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
|
|
208451
|
-
}
|
|
208452
|
-
function formatHeader(length, options) {
|
|
208453
|
-
const key = options?.key;
|
|
208454
|
-
const fields = options?.fields;
|
|
208455
|
-
const delimiter3 = options?.delimiter ?? COMMA2;
|
|
208456
|
-
let header = "";
|
|
208457
|
-
if (key) header += encodeKey(key);
|
|
208458
|
-
header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
|
|
208459
|
-
if (fields) {
|
|
208460
|
-
const quotedFields = fields.map((f) => encodeKey(f));
|
|
208461
|
-
header += `{${quotedFields.join(delimiter3)}}`;
|
|
208462
|
-
}
|
|
208463
|
-
header += ":";
|
|
208464
|
-
return header;
|
|
208465
|
-
}
|
|
208466
|
-
function* encodeJsonValue(value2, options, depth) {
|
|
208467
|
-
if (isJsonPrimitive(value2)) {
|
|
208468
|
-
const encodedPrimitive = encodePrimitive(value2, options.delimiter);
|
|
208469
|
-
if (encodedPrimitive !== "") yield encodedPrimitive;
|
|
208470
|
-
return;
|
|
208471
|
-
}
|
|
208472
|
-
if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
|
|
208473
|
-
else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
|
|
208474
|
-
}
|
|
208475
|
-
function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
|
|
208476
|
-
const keys = Object.keys(value2);
|
|
208477
|
-
if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
|
|
208478
|
-
const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
|
|
208479
|
-
for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
208480
|
-
}
|
|
208481
|
-
function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
|
|
208482
|
-
const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
|
|
208483
|
-
const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
|
|
208484
|
-
if (options.keyFolding === "safe" && siblings) {
|
|
208485
|
-
const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
|
|
208486
|
-
if (foldResult) {
|
|
208487
|
-
const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
|
|
208488
|
-
const encodedFoldedKey = encodeKey(foldedKey);
|
|
208489
|
-
if (remainder === void 0) {
|
|
208490
|
-
if (isJsonPrimitive(leafValue)) {
|
|
208491
|
-
yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
|
|
208492
|
-
return;
|
|
208493
|
-
} else if (isJsonArray(leafValue)) {
|
|
208494
|
-
yield* encodeArrayLines(foldedKey, leafValue, depth, options);
|
|
208495
|
-
return;
|
|
208496
|
-
} else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
|
|
208497
|
-
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
208498
|
-
return;
|
|
208499
|
-
}
|
|
208500
|
-
}
|
|
208501
|
-
if (isJsonObject4(remainder)) {
|
|
208502
|
-
yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
|
|
208503
|
-
const remainingDepth = effectiveFlattenDepth - segmentCount;
|
|
208504
|
-
const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
|
|
208505
|
-
yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
|
|
208506
|
-
return;
|
|
208507
|
-
}
|
|
208508
|
-
}
|
|
208509
|
-
}
|
|
208510
|
-
const encodedKey = encodeKey(key);
|
|
208511
|
-
if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
|
|
208512
|
-
else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
|
|
208513
|
-
else if (isJsonObject4(value2)) {
|
|
208514
|
-
yield indentedLine(depth, `${encodedKey}:`, options.indent);
|
|
208515
|
-
if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
|
|
208516
|
-
}
|
|
208517
|
-
}
|
|
208518
|
-
function* encodeArrayLines(key, value2, depth, options) {
|
|
208519
|
-
if (value2.length === 0) {
|
|
208520
|
-
yield indentedLine(depth, formatHeader(0, {
|
|
208521
|
-
key,
|
|
208522
|
-
delimiter: options.delimiter
|
|
208523
|
-
}), options.indent);
|
|
208524
|
-
return;
|
|
208525
|
-
}
|
|
208526
|
-
if (isArrayOfPrimitives(value2)) {
|
|
208527
|
-
yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
|
|
208528
|
-
return;
|
|
208529
|
-
}
|
|
208530
|
-
if (isArrayOfArrays(value2)) {
|
|
208531
|
-
if (value2.every((arr) => isArrayOfPrimitives(arr))) {
|
|
208532
|
-
yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
|
|
208533
|
-
return;
|
|
208534
|
-
}
|
|
208535
|
-
}
|
|
208536
|
-
if (isArrayOfObjects(value2)) {
|
|
208537
|
-
const header = extractTabularHeader(value2);
|
|
208538
|
-
if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
|
|
208539
|
-
else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
208540
|
-
return;
|
|
208541
|
-
}
|
|
208542
|
-
yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
|
|
208543
|
-
}
|
|
208544
|
-
function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
|
|
208545
|
-
yield indentedLine(depth, formatHeader(values.length, {
|
|
208546
|
-
key: prefix,
|
|
208547
|
-
delimiter: options.delimiter
|
|
208548
|
-
}), options.indent);
|
|
208549
|
-
for (const arr of values) if (isArrayOfPrimitives(arr)) {
|
|
208550
|
-
const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
|
|
208551
|
-
yield indentedListItem(depth + 1, arrayLine, options.indent);
|
|
208552
|
-
}
|
|
208553
|
-
}
|
|
208554
|
-
function encodeInlineArrayLine(values, delimiter3, prefix) {
|
|
208555
|
-
const header = formatHeader(values.length, {
|
|
208556
|
-
key: prefix,
|
|
208557
|
-
delimiter: delimiter3
|
|
208558
|
-
});
|
|
208559
|
-
const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
|
|
208560
|
-
if (values.length === 0) return header;
|
|
208561
|
-
return `${header} ${joinedValue}`;
|
|
208562
|
-
}
|
|
208563
|
-
function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
|
|
208564
|
-
yield indentedLine(depth, formatHeader(rows.length, {
|
|
208565
|
-
key: prefix,
|
|
208566
|
-
fields: header,
|
|
208567
|
-
delimiter: options.delimiter
|
|
208568
|
-
}), options.indent);
|
|
208569
|
-
yield* writeTabularRowsLines(rows, header, depth + 1, options);
|
|
208570
|
-
}
|
|
208571
|
-
function extractTabularHeader(rows) {
|
|
208572
|
-
if (rows.length === 0) return;
|
|
208573
|
-
const firstRow = rows[0];
|
|
208574
|
-
const firstKeys = Object.keys(firstRow);
|
|
208575
|
-
if (firstKeys.length === 0) return;
|
|
208576
|
-
if (isTabularArray(rows, firstKeys)) return firstKeys;
|
|
208577
|
-
}
|
|
208578
|
-
function isTabularArray(rows, header) {
|
|
208579
|
-
for (const row of rows) {
|
|
208580
|
-
if (Object.keys(row).length !== header.length) return false;
|
|
208581
|
-
for (const key of header) {
|
|
208582
|
-
if (!(key in row)) return false;
|
|
208583
|
-
if (!isJsonPrimitive(row[key])) return false;
|
|
208584
|
-
}
|
|
208585
|
-
}
|
|
208586
|
-
return true;
|
|
208587
|
-
}
|
|
208588
|
-
function* writeTabularRowsLines(rows, header, depth, options) {
|
|
208589
|
-
for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
|
|
208590
|
-
}
|
|
208591
|
-
function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
|
|
208592
|
-
yield indentedLine(depth, formatHeader(items.length, {
|
|
208593
|
-
key: prefix,
|
|
208594
|
-
delimiter: options.delimiter
|
|
208595
|
-
}), options.indent);
|
|
208596
|
-
for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
208597
|
-
}
|
|
208598
|
-
function* encodeObjectAsListItemLines(obj, depth, options) {
|
|
208599
|
-
if (isEmptyObject2(obj)) {
|
|
208600
|
-
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
208601
|
-
return;
|
|
208602
|
-
}
|
|
208603
|
-
const entries = Object.entries(obj);
|
|
208604
|
-
if (entries.length === 1) {
|
|
208605
|
-
const [key, value2] = entries[0];
|
|
208606
|
-
if (isJsonArray(value2) && isArrayOfObjects(value2)) {
|
|
208607
|
-
const header = extractTabularHeader(value2);
|
|
208608
|
-
if (header) {
|
|
208609
|
-
yield indentedListItem(depth, formatHeader(value2.length, {
|
|
208610
|
-
key,
|
|
208611
|
-
fields: header,
|
|
208612
|
-
delimiter: options.delimiter
|
|
208613
|
-
}), options.indent);
|
|
208614
|
-
yield* writeTabularRowsLines(value2, header, depth + 1, options);
|
|
208615
|
-
return;
|
|
208616
|
-
}
|
|
208617
|
-
}
|
|
208618
|
-
}
|
|
208619
|
-
yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
|
|
208620
|
-
yield* encodeObjectLines(obj, depth + 1, options);
|
|
208621
|
-
}
|
|
208622
|
-
function* encodeListItemValueLines(value2, depth, options) {
|
|
208623
|
-
if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
|
|
208624
|
-
else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
|
|
208625
|
-
else {
|
|
208626
|
-
yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
|
|
208627
|
-
for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
|
|
208628
|
-
}
|
|
208629
|
-
else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
|
|
208630
|
-
}
|
|
208631
|
-
function indentedLine(depth, content, indentSize) {
|
|
208632
|
-
return " ".repeat(indentSize * depth) + content;
|
|
208633
|
-
}
|
|
208634
|
-
function indentedListItem(depth, content, indentSize) {
|
|
208635
|
-
return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
|
|
208636
|
-
}
|
|
208637
|
-
function encode6(input2, options) {
|
|
208638
|
-
return Array.from(encodeLines(input2, options)).join("\n");
|
|
208639
|
-
}
|
|
208640
|
-
function encodeLines(input2, options) {
|
|
208641
|
-
return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
|
|
208642
|
-
}
|
|
208643
|
-
function resolveOptions(options) {
|
|
208644
|
-
return {
|
|
208645
|
-
indent: options?.indent ?? 2,
|
|
208646
|
-
delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
|
|
208647
|
-
keyFolding: options?.keyFolding ?? "off",
|
|
208648
|
-
flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
|
|
208649
|
-
};
|
|
208650
|
-
}
|
|
208651
|
-
|
|
208652
|
-
// mcp/geminiSanitizer.ts
|
|
208653
|
-
function parseStringEnumBranch(item) {
|
|
208654
|
-
if (!item || typeof item !== "object") return null;
|
|
208655
|
-
const record4 = item;
|
|
208656
|
-
if (Array.isArray(record4.enum)) {
|
|
208657
|
-
const strings = record4.enum.filter((v) => typeof v === "string");
|
|
208658
|
-
return strings.length === record4.enum.length && strings.length > 0 ? { values: strings } : null;
|
|
208659
|
-
}
|
|
208660
|
-
if (typeof record4.const === "string") {
|
|
208661
|
-
return { values: [record4.const] };
|
|
208662
|
-
}
|
|
208663
|
-
return null;
|
|
208664
|
-
}
|
|
208665
|
-
function collapseStringUnion(branches) {
|
|
208666
|
-
const values = [];
|
|
208667
|
-
for (const item of branches) {
|
|
208668
|
-
const parsed2 = parseStringEnumBranch(item);
|
|
208669
|
-
if (!parsed2) return null;
|
|
208670
|
-
values.push(...parsed2.values);
|
|
208671
|
-
}
|
|
208672
|
-
if (values.length === 0) return null;
|
|
208673
|
-
return { type: "string", enum: [...new Set(values)] };
|
|
208674
|
-
}
|
|
208675
|
-
function sanitizeForGemini(schema2) {
|
|
208676
|
-
if (!schema2 || typeof schema2 !== "object") return schema2;
|
|
208677
|
-
if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
|
|
208678
|
-
const source = schema2;
|
|
208679
|
-
if (Array.isArray(source.enum) && typeof source.type !== "string") {
|
|
208680
|
-
const allStrings = source.enum.every((v) => typeof v === "string");
|
|
208681
|
-
if (allStrings) {
|
|
208682
|
-
const result = { type: "string", enum: source.enum };
|
|
208683
|
-
if (typeof source.description === "string") result.description = source.description;
|
|
208684
|
-
return result;
|
|
208685
|
-
}
|
|
208686
|
-
}
|
|
208687
|
-
for (const unionKey of ["anyOf", "oneOf"]) {
|
|
208688
|
-
const branches = source[unionKey];
|
|
208689
|
-
if (Array.isArray(branches) && branches.length > 0) {
|
|
208690
|
-
const collapsed = collapseStringUnion(branches);
|
|
208691
|
-
if (collapsed) {
|
|
208692
|
-
const result = { ...collapsed };
|
|
208693
|
-
if (typeof source.description === "string") result.description = source.description;
|
|
208694
|
-
return result;
|
|
208695
|
-
}
|
|
208696
|
-
}
|
|
208697
|
-
}
|
|
208698
|
-
if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
|
|
208699
|
-
const result = {};
|
|
208700
|
-
if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
|
|
208701
|
-
if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
|
|
208702
|
-
return result;
|
|
208703
|
-
}
|
|
208704
|
-
const sanitized = {};
|
|
208705
|
-
for (const [key, value2] of Object.entries(source)) {
|
|
208706
|
-
if (key === "$schema") continue;
|
|
208707
|
-
if (key === "$defs") {
|
|
208708
|
-
sanitized.definitions = sanitizeForGemini(value2);
|
|
208709
|
-
continue;
|
|
208710
|
-
}
|
|
208711
|
-
sanitized[key] = sanitizeForGemini(value2);
|
|
208712
|
-
}
|
|
208713
|
-
return sanitized;
|
|
208714
|
-
}
|
|
208715
|
-
function wrapJsonSchemaProducer(producer) {
|
|
208716
|
-
return new Proxy(producer, {
|
|
208717
|
-
get(target, prop, receiver) {
|
|
208718
|
-
const value2 = Reflect.get(target, prop, receiver);
|
|
208719
|
-
if ((prop === "input" || prop === "output") && typeof value2 === "function") {
|
|
208720
|
-
const fn2 = value2;
|
|
208721
|
-
return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
|
|
208722
|
-
}
|
|
208723
|
-
return value2;
|
|
208724
|
-
}
|
|
208725
|
-
});
|
|
208726
|
-
}
|
|
208727
|
-
function wrapStandard(standard) {
|
|
208728
|
-
return new Proxy(standard, {
|
|
208729
|
-
get(target, prop, receiver) {
|
|
208730
|
-
if (prop === "jsonSchema") {
|
|
208731
|
-
const value2 = Reflect.get(target, prop, receiver);
|
|
208732
|
-
if (value2 && typeof value2 === "object") {
|
|
208733
|
-
return wrapJsonSchemaProducer(value2);
|
|
208734
|
-
}
|
|
208735
|
-
return value2;
|
|
208736
|
-
}
|
|
208737
|
-
return Reflect.get(target, prop, receiver);
|
|
208738
|
-
}
|
|
208739
|
-
});
|
|
208740
|
-
}
|
|
208741
|
-
function wrapSchemaForGemini(schema2) {
|
|
208742
|
-
return new Proxy(schema2, {
|
|
208743
|
-
get(target, prop, receiver) {
|
|
208744
|
-
if (prop === "~standard") {
|
|
208745
|
-
const value2 = Reflect.get(target, prop, receiver);
|
|
208746
|
-
if (value2 && typeof value2 === "object") {
|
|
208747
|
-
return wrapStandard(value2);
|
|
208748
|
-
}
|
|
208749
|
-
return value2;
|
|
208750
|
-
}
|
|
208751
|
-
if (prop === "toJsonSchema") {
|
|
208752
|
-
const method = Reflect.get(target, prop, receiver);
|
|
208753
|
-
if (typeof method === "function") {
|
|
208754
|
-
return () => sanitizeForGemini(method.call(target));
|
|
208755
|
-
}
|
|
208756
|
-
return method;
|
|
208757
|
-
}
|
|
208758
|
-
return Reflect.get(target, prop, receiver);
|
|
208759
|
-
}
|
|
208760
|
-
});
|
|
208761
|
-
}
|
|
208762
|
-
function sanitizeToolForGemini(tool2) {
|
|
208763
|
-
if (!tool2.parameters) return tool2;
|
|
208764
|
-
return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
|
|
208765
|
-
}
|
|
208766
|
-
function isGeminiRouted(ctx) {
|
|
208767
|
-
const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
|
|
208768
|
-
if (!effective) return true;
|
|
208769
|
-
const normalized = effective.toLowerCase();
|
|
208770
|
-
if (normalized.includes("gemini")) return true;
|
|
208771
|
-
if (!normalized.includes("/")) return true;
|
|
208772
|
-
return false;
|
|
208773
|
-
}
|
|
208774
|
-
|
|
208775
|
-
// mcp/shared.ts
|
|
208776
|
-
function getHttpStatus(err) {
|
|
208777
|
-
if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
|
|
208778
|
-
return typeof err.status === "number" ? err.status : void 0;
|
|
208779
|
-
}
|
|
208780
|
-
var tool = (toolDef) => toolDef;
|
|
208781
|
-
var handleToolSuccess = (data) => {
|
|
208782
|
-
const text = typeof data === "string" ? data : encode6(data);
|
|
208783
|
-
return {
|
|
208784
|
-
content: [{ type: "text", text }]
|
|
208785
|
-
};
|
|
208786
|
-
};
|
|
208787
|
-
var handleToolError = (error109) => {
|
|
208788
|
-
const errorMessage = error109 instanceof Error ? error109.message : String(error109);
|
|
208789
|
-
return {
|
|
208790
|
-
content: [
|
|
208791
|
-
{
|
|
208792
|
-
type: "text",
|
|
208793
|
-
text: `Error: ${errorMessage}`
|
|
208794
|
-
}
|
|
208795
|
-
],
|
|
208796
|
-
isError: true
|
|
208797
|
-
};
|
|
208798
|
-
};
|
|
208799
|
-
var execute = (fn2, toolName) => {
|
|
208800
|
-
const _fn = async (params) => {
|
|
208801
|
-
try {
|
|
208802
|
-
const result = await fn2(params);
|
|
208803
|
-
return handleToolSuccess(result);
|
|
208804
|
-
} catch (error109) {
|
|
208805
|
-
const errorMessage = error109 instanceof Error ? error109.message : String(error109);
|
|
208806
|
-
const prefix = toolName ? `[${toolName}]` : "tool";
|
|
208807
|
-
log2.info(`${prefix} error: ${errorMessage}`);
|
|
208808
|
-
log2.debug(`${prefix} params: ${formatJsonValue(params)}`);
|
|
208809
|
-
return handleToolError(error109);
|
|
208810
|
-
}
|
|
208811
|
-
};
|
|
208812
|
-
return _fn;
|
|
208813
|
-
};
|
|
208814
|
-
var addTools = (ctx, server, tools) => {
|
|
208815
|
-
const shouldSanitize = isGeminiRouted(ctx);
|
|
208816
|
-
for (const tool2 of tools) {
|
|
208817
|
-
server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
|
|
208818
|
-
}
|
|
208819
|
-
return server;
|
|
208820
|
-
};
|
|
208821
|
-
|
|
208822
209317
|
// mcp/git.ts
|
|
208823
209318
|
function getPushDestination(branch, storedDest, cwd) {
|
|
208824
209319
|
if (storedDest && storedDest.localBranch === branch) {
|
|
@@ -208944,7 +209439,8 @@ async function runPrepushHook(ctx, retryTool) {
|
|
|
208944
209439
|
if (!ctx.prepushScript) return false;
|
|
208945
209440
|
const prepushHook = await executeLifecycleHook({
|
|
208946
209441
|
event: "prepush",
|
|
208947
|
-
script: ctx.prepushScript
|
|
209442
|
+
script: ctx.prepushScript,
|
|
209443
|
+
shell: ctx.payload.shell
|
|
208948
209444
|
});
|
|
208949
209445
|
if (prepushHook.failure) {
|
|
208950
209446
|
ctx.toolState.prepushFailureCount += 1;
|
|
@@ -209339,8 +209835,8 @@ function countAhead(head, base, cwd) {
|
|
|
209339
209835
|
function spillGitOutput(params) {
|
|
209340
209836
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
209341
209837
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
|
|
209342
|
-
const outputPath =
|
|
209343
|
-
|
|
209838
|
+
const outputPath = join14(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
|
|
209839
|
+
writeFileSync10(outputPath, params.output);
|
|
209344
209840
|
const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
|
|
209345
209841
|
const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
|
|
209346
209842
|
log2.info(
|
|
@@ -210866,9 +211362,9 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
210866
211362
|
import { execFileSync as execFileSync7, execSync as execSync2 } from "node:child_process";
|
|
210867
211363
|
import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
210868
211364
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
210869
|
-
import { join as
|
|
211365
|
+
import { join as join15 } from "node:path";
|
|
210870
211366
|
function createTempDirectory() {
|
|
210871
|
-
const sharedTempDir = mkdtempSync2(
|
|
211367
|
+
const sharedTempDir = mkdtempSync2(join15(tmpdir3(), "pullfrog-"));
|
|
210872
211368
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
210873
211369
|
log2.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
210874
211370
|
return sharedTempDir;
|
|
@@ -210913,13 +211409,13 @@ function wipeRunnerLeakSurface() {
|
|
|
210913
211409
|
return [];
|
|
210914
211410
|
}
|
|
210915
211411
|
};
|
|
210916
|
-
const fileCommandsDir =
|
|
211412
|
+
const fileCommandsDir = join15(runnerTemp, "_runner_file_commands");
|
|
210917
211413
|
for (const entry of listDir(fileCommandsDir)) {
|
|
210918
|
-
tryUnlink(
|
|
211414
|
+
tryUnlink(join15(fileCommandsDir, entry));
|
|
210919
211415
|
}
|
|
210920
211416
|
for (const entry of listDir(runnerTemp)) {
|
|
210921
211417
|
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
210922
|
-
tryUnlink(
|
|
211418
|
+
tryUnlink(join15(runnerTemp, entry));
|
|
210923
211419
|
}
|
|
210924
211420
|
}
|
|
210925
211421
|
if (wiped.length > 0) {
|
|
@@ -211394,6 +211890,7 @@ async function checkoutPrBranch(pr, params) {
|
|
|
211394
211890
|
const postCheckoutHook = await executeLifecycleHook({
|
|
211395
211891
|
event: "post-checkout",
|
|
211396
211892
|
script: params.postCheckoutScript,
|
|
211893
|
+
shell: params.shell,
|
|
211397
211894
|
normalizeWorkingTreeAfter: true
|
|
211398
211895
|
});
|
|
211399
211896
|
return { hookWarning: postCheckoutHook.warning };
|
|
@@ -211475,11 +211972,11 @@ function CheckoutPrTool(ctx) {
|
|
|
211475
211972
|
headSha: checkoutSha
|
|
211476
211973
|
});
|
|
211477
211974
|
if (incremental.status === "ok") {
|
|
211478
|
-
incrementalDiffPath =
|
|
211975
|
+
incrementalDiffPath = join16(
|
|
211479
211976
|
tempDir,
|
|
211480
211977
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
211481
211978
|
);
|
|
211482
|
-
|
|
211979
|
+
writeFileSync11(incrementalDiffPath, incremental.diff);
|
|
211483
211980
|
log2.info(
|
|
211484
211981
|
`\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
211485
211982
|
);
|
|
@@ -211492,8 +211989,8 @@ function CheckoutPrTool(ctx) {
|
|
|
211492
211989
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
211493
211990
|
log2.debug(`formatted diff preview (first 100 lines):
|
|
211494
211991
|
${diffPreview}`);
|
|
211495
|
-
const diffPath =
|
|
211496
|
-
|
|
211992
|
+
const diffPath = join16(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
211993
|
+
writeFileSync11(diffPath, formatResult.content);
|
|
211497
211994
|
log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
211498
211995
|
primary.diffCoverage = createDiffCoverageState({
|
|
211499
211996
|
diffPath,
|
|
@@ -211650,8 +212147,8 @@ ${dirty}`
|
|
|
211650
212147
|
}
|
|
211651
212148
|
|
|
211652
212149
|
// mcp/checkSuite.ts
|
|
211653
|
-
import { mkdirSync as mkdirSync8, writeFileSync as
|
|
211654
|
-
import { join as
|
|
212150
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "node:fs";
|
|
212151
|
+
import { join as join17 } from "node:path";
|
|
211655
212152
|
var GetCheckSuiteLogs = type({
|
|
211656
212153
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
211657
212154
|
});
|
|
@@ -211747,7 +212244,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
211747
212244
|
if (!tempDir) {
|
|
211748
212245
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
211749
212246
|
}
|
|
211750
|
-
const logsDir =
|
|
212247
|
+
const logsDir = join17(tempDir, "ci-logs");
|
|
211751
212248
|
mkdirSync8(logsDir, { recursive: true });
|
|
211752
212249
|
const jobResults = [];
|
|
211753
212250
|
for (const run4 of failedRuns) {
|
|
@@ -211774,8 +212271,8 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
211774
212271
|
);
|
|
211775
212272
|
}
|
|
211776
212273
|
const logsText = await logsResult.text();
|
|
211777
|
-
const logPath =
|
|
211778
|
-
|
|
212274
|
+
const logPath = join17(logsDir, `job-${job.id}.log`);
|
|
212275
|
+
writeFileSync12(logPath, logsText);
|
|
211779
212276
|
const analysis = analyzeLog(logsText, 80);
|
|
211780
212277
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
211781
212278
|
jobResults.push({
|
|
@@ -211823,8 +212320,8 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
211823
212320
|
}
|
|
211824
212321
|
|
|
211825
212322
|
// mcp/commitInfo.ts
|
|
211826
|
-
import { writeFileSync as
|
|
211827
|
-
import { join as
|
|
212323
|
+
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
212324
|
+
import { join as join18 } from "node:path";
|
|
211828
212325
|
var CommitInfo = type({
|
|
211829
212326
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
211830
212327
|
});
|
|
@@ -211848,8 +212345,8 @@ function CommitInfoTool(ctx) {
|
|
|
211848
212345
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
211849
212346
|
);
|
|
211850
212347
|
}
|
|
211851
|
-
const diffFile =
|
|
211852
|
-
|
|
212348
|
+
const diffFile = join18(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
212349
|
+
writeFileSync13(diffFile, formatResult.content);
|
|
211853
212350
|
log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
211854
212351
|
return {
|
|
211855
212352
|
sha: data.sha,
|
|
@@ -211986,13 +212483,13 @@ import { performance as performance9 } from "node:perf_hooks";
|
|
|
211986
212483
|
|
|
211987
212484
|
// prep/installNodeDependencies.ts
|
|
211988
212485
|
import { existsSync as existsSync10 } from "node:fs";
|
|
211989
|
-
import { join as
|
|
212486
|
+
import { join as join20 } from "node:path";
|
|
211990
212487
|
|
|
211991
212488
|
// utils/packageManager.ts
|
|
211992
212489
|
var import_semver2 = __toESM(require_semver2(), 1);
|
|
211993
212490
|
import { existsSync as existsSync9 } from "node:fs";
|
|
211994
212491
|
import { mkdir, readFile as readFile3 } from "node:fs/promises";
|
|
211995
|
-
import { delimiter as delimiter2, join as
|
|
212492
|
+
import { delimiter as delimiter2, join as join19 } from "node:path";
|
|
211996
212493
|
var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
|
|
211997
212494
|
var COREPACK_MANAGED = ["pnpm", "yarn"];
|
|
211998
212495
|
function isSupported(name) {
|
|
@@ -212030,7 +212527,7 @@ function parseDevEnginesField(field) {
|
|
|
212030
212527
|
};
|
|
212031
212528
|
}
|
|
212032
212529
|
async function resolvePackageManagerSpec(cwd) {
|
|
212033
|
-
const pkgPath =
|
|
212530
|
+
const pkgPath = join19(cwd, "package.json");
|
|
212034
212531
|
if (!existsSync9(pkgPath)) return null;
|
|
212035
212532
|
let pkg;
|
|
212036
212533
|
try {
|
|
@@ -212089,7 +212586,7 @@ async function currentVersion(name) {
|
|
|
212089
212586
|
return result.stdout.trim();
|
|
212090
212587
|
}
|
|
212091
212588
|
function packageManagerBinDir(tmpdir4) {
|
|
212092
|
-
return
|
|
212589
|
+
return join19(tmpdir4, "pm-bin");
|
|
212093
212590
|
}
|
|
212094
212591
|
async function ensurePackageManager(params) {
|
|
212095
212592
|
const spec = params.spec;
|
|
@@ -212152,7 +212649,7 @@ async function installFallback(name, installSpec) {
|
|
|
212152
212649
|
});
|
|
212153
212650
|
if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
|
|
212154
212651
|
if (name === "deno") {
|
|
212155
|
-
const denoPath =
|
|
212652
|
+
const denoPath = join19(process.env.HOME || "", ".deno", "bin");
|
|
212156
212653
|
process.env.PATH = `${denoPath}:${process.env.PATH}`;
|
|
212157
212654
|
}
|
|
212158
212655
|
log2.info(`\xBB installed ${name}`);
|
|
@@ -212185,7 +212682,7 @@ async function isCommandAvailable2(command) {
|
|
|
212185
212682
|
var installNodeDependencies = {
|
|
212186
212683
|
name: "installNodeDependencies",
|
|
212187
212684
|
shouldRun: () => {
|
|
212188
|
-
const packageJsonPath =
|
|
212685
|
+
const packageJsonPath = join20(process.cwd(), "package.json");
|
|
212189
212686
|
return existsSync10(packageJsonPath);
|
|
212190
212687
|
},
|
|
212191
212688
|
run: async (options) => {
|
|
@@ -212281,12 +212778,12 @@ ${errorMessage}`]
|
|
|
212281
212778
|
|
|
212282
212779
|
// prep/installPythonDependencies.ts
|
|
212283
212780
|
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
|
|
212284
|
-
import { join as
|
|
212781
|
+
import { join as join21 } from "node:path";
|
|
212285
212782
|
function declaresBuildSystem(path4) {
|
|
212286
212783
|
return /^\s*\[\s*build-system\s*\]/m.test(readFileSync7(path4, "utf8"));
|
|
212287
212784
|
}
|
|
212288
212785
|
function configApplies(config4, cwd) {
|
|
212289
|
-
const path4 =
|
|
212786
|
+
const path4 = join21(cwd, config4.file);
|
|
212290
212787
|
return existsSync11(path4) && (config4.applies?.(path4) ?? true);
|
|
212291
212788
|
}
|
|
212292
212789
|
var PYTHON_CONFIGS = [
|
|
@@ -212610,411 +213107,6 @@ function AwaitDependencyInstallationTool(ctx) {
|
|
|
212610
213107
|
// mcp/gh.ts
|
|
212611
213108
|
import { mkdirSync as mkdirSync9 } from "node:fs";
|
|
212612
213109
|
import { join as join22 } from "node:path";
|
|
212613
|
-
|
|
212614
|
-
// mcp/shell.ts
|
|
212615
|
-
import { spawn as spawn5, spawnSync as spawnSync7 } from "node:child_process";
|
|
212616
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
212617
|
-
import { closeSync, openSync, writeFileSync as writeFileSync13 } from "node:fs";
|
|
212618
|
-
import { userInfo as userInfo2 } from "node:os";
|
|
212619
|
-
import { join as join21 } from "node:path";
|
|
212620
|
-
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
212621
|
-
var ShellParams = type({
|
|
212622
|
-
command: "string",
|
|
212623
|
-
// advisory and unread — no code path consumes it. kept in the schema only
|
|
212624
|
-
// because it nudges the model to state intent before running a command;
|
|
212625
|
-
// REQUIRING it cost a full rejected turn every time one was omitted. #1140.
|
|
212626
|
-
"description?": "string",
|
|
212627
|
-
"timeout?": type.number.describe(
|
|
212628
|
-
"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."
|
|
212629
|
-
),
|
|
212630
|
-
"working_directory?": "string",
|
|
212631
|
-
"background?": "boolean"
|
|
212632
|
-
});
|
|
212633
|
-
var detectedSandboxMethod;
|
|
212634
|
-
var usernsProbeError;
|
|
212635
|
-
function getSandboxMethod() {
|
|
212636
|
-
return detectSandboxMethod();
|
|
212637
|
-
}
|
|
212638
|
-
function detectSandboxMethod() {
|
|
212639
|
-
if (detectedSandboxMethod !== void 0) {
|
|
212640
|
-
return detectedSandboxMethod;
|
|
212641
|
-
}
|
|
212642
|
-
if (process.env.CI !== "true") {
|
|
212643
|
-
detectedSandboxMethod = "none";
|
|
212644
|
-
log2.debug("sandbox disabled (CI !== true)");
|
|
212645
|
-
return "none";
|
|
212646
|
-
}
|
|
212647
|
-
try {
|
|
212648
|
-
const result = spawnSync7("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
|
|
212649
|
-
timeout: 5e3,
|
|
212650
|
-
stdio: "ignore"
|
|
212651
|
-
});
|
|
212652
|
-
if (result.status === 0) {
|
|
212653
|
-
detectedSandboxMethod = "unshare";
|
|
212654
|
-
log2.debug("PID namespace isolation enabled (unprivileged unshare)");
|
|
212655
|
-
return "unshare";
|
|
212656
|
-
}
|
|
212657
|
-
} catch {
|
|
212658
|
-
}
|
|
212659
|
-
try {
|
|
212660
|
-
const result = spawnSync7("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
|
|
212661
|
-
timeout: 5e3,
|
|
212662
|
-
stdio: "ignore"
|
|
212663
|
-
});
|
|
212664
|
-
if (result.status === 0) {
|
|
212665
|
-
detectedSandboxMethod = "sudo-unshare";
|
|
212666
|
-
log2.debug("PID namespace isolation enabled (sudo unshare)");
|
|
212667
|
-
return "sudo-unshare";
|
|
212668
|
-
}
|
|
212669
|
-
} catch {
|
|
212670
|
-
}
|
|
212671
|
-
try {
|
|
212672
|
-
const result = spawnSync7(
|
|
212673
|
-
"unshare",
|
|
212674
|
-
[
|
|
212675
|
-
"--user",
|
|
212676
|
-
"--map-root-user",
|
|
212677
|
-
"--pid",
|
|
212678
|
-
"--fork",
|
|
212679
|
-
"--mount",
|
|
212680
|
-
"setpriv",
|
|
212681
|
-
"--no-new-privs",
|
|
212682
|
-
"--bounding-set",
|
|
212683
|
-
"-all",
|
|
212684
|
-
"--inh-caps",
|
|
212685
|
-
"-all",
|
|
212686
|
-
"--ambient-caps",
|
|
212687
|
-
"-all",
|
|
212688
|
-
"true"
|
|
212689
|
-
],
|
|
212690
|
-
{ timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
|
|
212691
|
-
);
|
|
212692
|
-
if (result.status === 0) {
|
|
212693
|
-
detectedSandboxMethod = "userns-unshare";
|
|
212694
|
-
log2.debug("PID namespace isolation enabled (unprivileged userns unshare)");
|
|
212695
|
-
return "userns-unshare";
|
|
212696
|
-
}
|
|
212697
|
-
usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
|
|
212698
|
-
} catch {
|
|
212699
|
-
}
|
|
212700
|
-
detectedSandboxMethod = "none";
|
|
212701
|
-
log2.info("PID namespace isolation not available");
|
|
212702
|
-
return "none";
|
|
212703
|
-
}
|
|
212704
|
-
var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
|
212705
|
-
var SOCKET_CLEANUP = [
|
|
212706
|
-
"/var/run/docker.sock",
|
|
212707
|
-
"/run/docker.sock",
|
|
212708
|
-
"/var/run/podman/podman.sock",
|
|
212709
|
-
"/run/podman/podman.sock",
|
|
212710
|
-
"/run/containerd/containerd.sock",
|
|
212711
|
-
"/var/run/crio/crio.sock"
|
|
212712
|
-
].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
|
|
212713
|
-
function buildFsMounts(repoDir) {
|
|
212714
|
-
const escaped = repoDir.replace(/'/g, "'\\''");
|
|
212715
|
-
return [
|
|
212716
|
-
`mkdir -p /var/lib/pullfrog 2>/dev/null;`,
|
|
212717
|
-
`mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
|
|
212718
|
-
`[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
|
|
212719
|
-
`[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`
|
|
212720
|
-
].join(" ");
|
|
212721
|
-
}
|
|
212722
|
-
var _repoRoot;
|
|
212723
|
-
function resolveRepoRoot() {
|
|
212724
|
-
if (_repoRoot) return _repoRoot;
|
|
212725
|
-
const fromEnv = process.env.GITHUB_WORKSPACE;
|
|
212726
|
-
if (fromEnv) {
|
|
212727
|
-
_repoRoot = fromEnv;
|
|
212728
|
-
return _repoRoot;
|
|
212729
|
-
}
|
|
212730
|
-
try {
|
|
212731
|
-
_repoRoot = spawnSync7("git", ["rev-parse", "--show-toplevel"], {
|
|
212732
|
-
cwd: process.cwd(),
|
|
212733
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
212734
|
-
encoding: "utf-8"
|
|
212735
|
-
}).stdout?.trim();
|
|
212736
|
-
} catch {
|
|
212737
|
-
}
|
|
212738
|
-
if (!_repoRoot) _repoRoot = process.cwd();
|
|
212739
|
-
return _repoRoot;
|
|
212740
|
-
}
|
|
212741
|
-
function spawnShell(params) {
|
|
212742
|
-
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
|
212743
|
-
const sandboxMethod = detectSandboxMethod();
|
|
212744
|
-
const ci = process.env.CI === "true";
|
|
212745
|
-
if (ci && sandboxMethod === "none") {
|
|
212746
|
-
throw new Error(
|
|
212747
|
-
"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"
|
|
212748
|
-
);
|
|
212749
|
-
}
|
|
212750
|
-
const repoRoot = resolveRepoRoot();
|
|
212751
|
-
const fsMounts = buildFsMounts(repoRoot);
|
|
212752
|
-
if (sandboxMethod === "unshare") {
|
|
212753
|
-
return spawn5(
|
|
212754
|
-
"unshare",
|
|
212755
|
-
[
|
|
212756
|
-
"--pid",
|
|
212757
|
-
"--fork",
|
|
212758
|
-
"--mount-proc",
|
|
212759
|
-
"bash",
|
|
212760
|
-
"-c",
|
|
212761
|
-
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
|
|
212762
|
-
],
|
|
212763
|
-
spawnOpts
|
|
212764
|
-
);
|
|
212765
|
-
}
|
|
212766
|
-
if (sandboxMethod === "userns-unshare") {
|
|
212767
|
-
const escaped = params.command.replace(/'/g, "'\\''");
|
|
212768
|
-
return spawn5(
|
|
212769
|
-
"unshare",
|
|
212770
|
-
[
|
|
212771
|
-
"--user",
|
|
212772
|
-
"--map-root-user",
|
|
212773
|
-
"--pid",
|
|
212774
|
-
"--fork",
|
|
212775
|
-
"--mount",
|
|
212776
|
-
"bash",
|
|
212777
|
-
"-c",
|
|
212778
|
-
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
|
|
212779
|
-
],
|
|
212780
|
-
spawnOpts
|
|
212781
|
-
);
|
|
212782
|
-
}
|
|
212783
|
-
if (sandboxMethod === "sudo-unshare") {
|
|
212784
|
-
const envArgs = [];
|
|
212785
|
-
for (const [k2, v] of Object.entries(params.env)) {
|
|
212786
|
-
if (v !== void 0) {
|
|
212787
|
-
envArgs.push(`${k2}=${v}`);
|
|
212788
|
-
}
|
|
212789
|
-
}
|
|
212790
|
-
const username = userInfo2().username;
|
|
212791
|
-
const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
|
|
212792
|
-
const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
|
|
212793
|
-
envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
|
|
212794
|
-
return spawn5(
|
|
212795
|
-
"sudo",
|
|
212796
|
-
[
|
|
212797
|
-
"env",
|
|
212798
|
-
...envArgs,
|
|
212799
|
-
"unshare",
|
|
212800
|
-
"--pid",
|
|
212801
|
-
"--fork",
|
|
212802
|
-
"--mount-proc",
|
|
212803
|
-
"bash",
|
|
212804
|
-
"-c",
|
|
212805
|
-
`${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
|
|
212806
|
-
],
|
|
212807
|
-
{ ...spawnOpts, env: {} }
|
|
212808
|
-
);
|
|
212809
|
-
}
|
|
212810
|
-
return spawn5("bash", ["-c", params.command], spawnOpts);
|
|
212811
|
-
}
|
|
212812
|
-
async function killProcessGroup(proc) {
|
|
212813
|
-
if (!proc.pid) return;
|
|
212814
|
-
try {
|
|
212815
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
212816
|
-
await new Promise((r2) => setTimeout(r2, 200));
|
|
212817
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
212818
|
-
} catch {
|
|
212819
|
-
try {
|
|
212820
|
-
proc.kill("SIGKILL");
|
|
212821
|
-
} catch {
|
|
212822
|
-
}
|
|
212823
|
-
}
|
|
212824
|
-
}
|
|
212825
|
-
function getTempDir() {
|
|
212826
|
-
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
212827
|
-
if (!tempDir) {
|
|
212828
|
-
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
212829
|
-
}
|
|
212830
|
-
return tempDir;
|
|
212831
|
-
}
|
|
212832
|
-
var MAX_OUTPUT_CHARS = 5e3;
|
|
212833
|
-
async function runSandboxed(params) {
|
|
212834
|
-
const proc = spawnShell({
|
|
212835
|
-
command: params.command,
|
|
212836
|
-
env: params.env,
|
|
212837
|
-
cwd: params.cwd,
|
|
212838
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
212839
|
-
});
|
|
212840
|
-
const stdout2 = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
212841
|
-
const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
212842
|
-
let timedOut = false, exited = false, spawnError = "";
|
|
212843
|
-
proc.stdout?.on("data", (chunk) => {
|
|
212844
|
-
stdout2.append(chunk.toString());
|
|
212845
|
-
});
|
|
212846
|
-
proc.stderr?.on("data", (chunk) => {
|
|
212847
|
-
stderr.append(chunk.toString());
|
|
212848
|
-
});
|
|
212849
|
-
const timeoutId = setTimeout(async () => {
|
|
212850
|
-
if (!exited) {
|
|
212851
|
-
timedOut = true;
|
|
212852
|
-
await killProcessGroup(proc);
|
|
212853
|
-
}
|
|
212854
|
-
}, params.timeout);
|
|
212855
|
-
const exitCode = await new Promise((resolve3) => {
|
|
212856
|
-
const done = (code) => {
|
|
212857
|
-
exited = true;
|
|
212858
|
-
clearTimeout(timeoutId);
|
|
212859
|
-
resolve3(code);
|
|
212860
|
-
};
|
|
212861
|
-
proc.on("exit", done);
|
|
212862
|
-
proc.on("error", (err) => {
|
|
212863
|
-
spawnError = err.message;
|
|
212864
|
-
done(null);
|
|
212865
|
-
});
|
|
212866
|
-
});
|
|
212867
|
-
proc.stdout?.destroy();
|
|
212868
|
-
proc.stderr?.destroy();
|
|
212869
|
-
const outText = stdout2.toString();
|
|
212870
|
-
const errText = stderr.toString();
|
|
212871
|
-
let output2 = errText ? outText ? `${outText}
|
|
212872
|
-
${errText}` : errText : outText;
|
|
212873
|
-
if (spawnError) output2 = output2 ? `${output2}
|
|
212874
|
-
${spawnError}` : spawnError;
|
|
212875
|
-
if (timedOut)
|
|
212876
|
-
output2 = output2 ? `${output2}
|
|
212877
|
-
[timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
|
|
212878
|
-
return {
|
|
212879
|
-
output: output2.trim(),
|
|
212880
|
-
exitCode: exitCode ?? (timedOut ? 124 : -1),
|
|
212881
|
-
timedOut
|
|
212882
|
-
};
|
|
212883
|
-
}
|
|
212884
|
-
function capOutput(output2) {
|
|
212885
|
-
if (output2.length <= MAX_OUTPUT_CHARS) return output2;
|
|
212886
|
-
const fullPath = join21(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
|
|
212887
|
-
writeFileSync13(fullPath, output2);
|
|
212888
|
-
const elided = output2.length - MAX_OUTPUT_CHARS;
|
|
212889
|
-
return `... [${elided} chars truncated; output saved to ${fullPath}] ...
|
|
212890
|
-
${output2.slice(-MAX_OUTPUT_CHARS)}`;
|
|
212891
|
-
}
|
|
212892
|
-
function isGitCommand(command) {
|
|
212893
|
-
const trimmed = command.trim();
|
|
212894
|
-
if (trimmed === "git" || trimmed.startsWith("git ")) return true;
|
|
212895
|
-
if (trimmed.startsWith("sudo git")) return true;
|
|
212896
|
-
return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
|
|
212897
|
-
}
|
|
212898
|
-
function ShellTool(ctx) {
|
|
212899
|
-
return tool({
|
|
212900
|
-
name: "shell",
|
|
212901
|
-
timeoutMs: 12e4,
|
|
212902
|
-
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
212903
|
-
|
|
212904
|
-
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
212905
|
-
|
|
212906
|
-
Use this tool to:
|
|
212907
|
-
- Run shell commands (ls, cat, grep, find, etc.)
|
|
212908
|
-
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
212909
|
-
- Run tests and linters
|
|
212910
|
-
|
|
212911
|
-
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.
|
|
212912
|
-
|
|
212913
|
-
Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
|
|
212914
|
-
parameters: ShellParams,
|
|
212915
|
-
execute: execute(async (params) => {
|
|
212916
|
-
if (isGitCommand(params.command)) {
|
|
212917
|
-
throw new Error(
|
|
212918
|
-
'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'
|
|
212919
|
-
);
|
|
212920
|
-
}
|
|
212921
|
-
const timeout = Math.min(params.timeout ?? 3e4, 12e4);
|
|
212922
|
-
const cwd = params.working_directory ?? process.cwd();
|
|
212923
|
-
const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
|
|
212924
|
-
if (params.command.includes("agent-browser")) {
|
|
212925
|
-
const daemonError = ensureBrowserDaemon(ctx.toolState);
|
|
212926
|
-
if (daemonError) {
|
|
212927
|
-
return {
|
|
212928
|
-
output: `browser daemon unavailable: ${daemonError}`,
|
|
212929
|
-
exit_code: 1,
|
|
212930
|
-
timed_out: false
|
|
212931
|
-
};
|
|
212932
|
-
}
|
|
212933
|
-
const binDir = ctx.toolState.browserDaemon?.binDir;
|
|
212934
|
-
if (binDir) {
|
|
212935
|
-
env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
|
|
212936
|
-
}
|
|
212937
|
-
}
|
|
212938
|
-
if (params.background) {
|
|
212939
|
-
const tempDir = getTempDir();
|
|
212940
|
-
const handle = `bg-${randomUUID4().slice(0, 8)}`;
|
|
212941
|
-
const outputPath = join21(tempDir, `${handle}.log`);
|
|
212942
|
-
const pidPath = join21(tempDir, `${handle}.pid`);
|
|
212943
|
-
const logFd = openSync(outputPath, "a");
|
|
212944
|
-
let proc;
|
|
212945
|
-
try {
|
|
212946
|
-
proc = spawnShell({
|
|
212947
|
-
command: params.command,
|
|
212948
|
-
env: env2,
|
|
212949
|
-
cwd,
|
|
212950
|
-
stdio: ["ignore", logFd, logFd]
|
|
212951
|
-
});
|
|
212952
|
-
} finally {
|
|
212953
|
-
closeSync(logFd);
|
|
212954
|
-
}
|
|
212955
|
-
if (!proc.pid) {
|
|
212956
|
-
throw new Error("failed to start background process");
|
|
212957
|
-
}
|
|
212958
|
-
proc.unref();
|
|
212959
|
-
writeFileSync13(pidPath, `${proc.pid}
|
|
212960
|
-
`);
|
|
212961
|
-
ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
|
|
212962
|
-
return {
|
|
212963
|
-
handle,
|
|
212964
|
-
outputPath,
|
|
212965
|
-
pidPath,
|
|
212966
|
-
message: `started background process ${handle} (pid ${proc.pid})`
|
|
212967
|
-
};
|
|
212968
|
-
}
|
|
212969
|
-
const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
|
|
212970
|
-
if (result.exitCode !== 0) {
|
|
212971
|
-
log2.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
|
|
212972
|
-
if (result.output) log2.info(`output: ${result.output}`);
|
|
212973
|
-
}
|
|
212974
|
-
return {
|
|
212975
|
-
output: capOutput(result.output),
|
|
212976
|
-
exit_code: result.exitCode,
|
|
212977
|
-
timed_out: result.timedOut
|
|
212978
|
-
};
|
|
212979
|
-
})
|
|
212980
|
-
});
|
|
212981
|
-
}
|
|
212982
|
-
var KillBackgroundParams = type({
|
|
212983
|
-
handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
|
|
212984
|
-
});
|
|
212985
|
-
function KillBackgroundTool(ctx) {
|
|
212986
|
-
return tool({
|
|
212987
|
-
name: "kill_background",
|
|
212988
|
-
mutates: true,
|
|
212989
|
-
description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
|
|
212990
|
-
parameters: KillBackgroundParams,
|
|
212991
|
-
execute: execute(async (params) => {
|
|
212992
|
-
const proc = ctx.toolState.backgroundProcesses.get(params.handle);
|
|
212993
|
-
if (!proc) {
|
|
212994
|
-
return {
|
|
212995
|
-
success: false,
|
|
212996
|
-
message: `no background process with handle ${params.handle}`
|
|
212997
|
-
};
|
|
212998
|
-
}
|
|
212999
|
-
try {
|
|
213000
|
-
process.kill(-proc.pid, "SIGTERM");
|
|
213001
|
-
} catch {
|
|
213002
|
-
}
|
|
213003
|
-
await sleep3(200);
|
|
213004
|
-
try {
|
|
213005
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
213006
|
-
} catch {
|
|
213007
|
-
}
|
|
213008
|
-
ctx.toolState.backgroundProcesses.delete(params.handle);
|
|
213009
|
-
return {
|
|
213010
|
-
success: true,
|
|
213011
|
-
message: `killed background process ${params.handle} (pid ${proc.pid})`
|
|
213012
|
-
};
|
|
213013
|
-
})
|
|
213014
|
-
});
|
|
213015
|
-
}
|
|
213016
|
-
|
|
213017
|
-
// mcp/gh.ts
|
|
213018
213110
|
var GH_TIMEOUT_MS = 12e4;
|
|
213019
213111
|
var GhParams = type({
|
|
213020
213112
|
args: type.string.array().describe(
|
|
@@ -214265,7 +214357,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
214265
214357
|
threadCount: threadBlocks.length,
|
|
214266
214358
|
commentsPath,
|
|
214267
214359
|
toc: formatted.toc,
|
|
214268
|
-
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
|
|
214360
|
+
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.`
|
|
214269
214361
|
};
|
|
214270
214362
|
})
|
|
214271
214363
|
});
|
|
@@ -214388,8 +214480,7 @@ An existing plan comment was found for this issue. Update that comment with the
|
|
|
214388
214480
|
};
|
|
214389
214481
|
}
|
|
214390
214482
|
var modeInstructionParent = {
|
|
214391
|
-
IncrementalReview: "Review"
|
|
214392
|
-
Fix: "Build"
|
|
214483
|
+
IncrementalReview: "Review"
|
|
214393
214484
|
};
|
|
214394
214485
|
function buildOrchestratorGuidance(ctx, mode, overrideGuidance) {
|
|
214395
214486
|
const hardcoded = overrideGuidance ?? mode.prompt ?? "";
|
|
@@ -215601,18 +215692,13 @@ function hasEnvVar4(name) {
|
|
|
215601
215692
|
const value2 = process.env[name];
|
|
215602
215693
|
return typeof value2 === "string" && value2.length > 0;
|
|
215603
215694
|
}
|
|
215604
|
-
var HOST_OVERRIDES = {
|
|
215605
|
-
ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
|
|
215606
|
-
CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
|
|
215607
|
-
};
|
|
215608
215695
|
function envVarsFor(model) {
|
|
215609
215696
|
return model.includes("/") ? getModelEnvVars(model) : [];
|
|
215610
215697
|
}
|
|
215611
215698
|
async function checkOne(params) {
|
|
215612
215699
|
const value2 = process.env[params.envVar];
|
|
215613
215700
|
if (!value2) return null;
|
|
215614
|
-
|
|
215615
|
-
if (hostOverride && hasEnvVar4(hostOverride)) return null;
|
|
215701
|
+
if (getProviderGatewayUrl(params.model)) return null;
|
|
215616
215702
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
215617
215703
|
const preflight = await preflightClaudeSubscription({
|
|
215618
215704
|
token: value2,
|
|
@@ -218813,6 +218899,7 @@ async function main() {
|
|
|
218813
218899
|
const setupHook = await executeLifecycleHook({
|
|
218814
218900
|
event: "setup",
|
|
218815
218901
|
script: runContext.repoSettings.setupScript,
|
|
218902
|
+
shell: payload.shell,
|
|
218816
218903
|
normalizeWorkingTreeAfter: true
|
|
218817
218904
|
});
|
|
218818
218905
|
if (setupHook.warning) {
|
|
@@ -219688,7 +219775,7 @@ function resolveTarget2(repo) {
|
|
|
219688
219775
|
return { owner: match3[1], repo: match3[2] };
|
|
219689
219776
|
}
|
|
219690
219777
|
function cliVersion() {
|
|
219691
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
219778
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.76");
|
|
219692
219779
|
if (!parts) return "0.0.0";
|
|
219693
219780
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
219694
219781
|
}
|
|
@@ -219855,7 +219942,7 @@ async function runCli8(input2) {
|
|
|
219855
219942
|
}
|
|
219856
219943
|
|
|
219857
219944
|
// cli.ts
|
|
219858
|
-
var VERSION10 = "0.1.
|
|
219945
|
+
var VERSION10 = "0.1.76";
|
|
219859
219946
|
var bin = basename2(process.argv[1] || "");
|
|
219860
219947
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
219861
219948
|
var rawArgs = process.argv.slice(2);
|