pullfrog 0.1.49 → 0.1.51

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/cli.mjs CHANGED
@@ -102476,14 +102476,18 @@ function formatMcpToolRef(agentId, toolName) {
102476
102476
  return agentId;
102477
102477
  }
102478
102478
  }
102479
+ function hasSimilarIssues(params) {
102480
+ return params.repoIntelligence && !!params.event.issue_number && !params.event.is_pr;
102481
+ }
102479
102482
 
102480
102483
  // utils/activity.ts
102481
102484
  import { performance as performance2 } from "node:perf_hooks";
102482
- function isMonitorDebugEnabled() {
102485
+ function isDebugEnabled() {
102483
102486
  return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
102484
102487
  }
102485
102488
  var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
102486
102489
  var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
102490
+ var AGENT_FIRST_EVENT_TIMEOUT_MS = 12e4;
102487
102491
  var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
102488
102492
  var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
102489
102493
  var ACTIVITY_NOISE_PATTERNS = [
@@ -102527,7 +102531,7 @@ function startProcessOutputMonitor(ctx) {
102527
102531
  process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
102528
102532
  process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
102529
102533
  const debugBypass = (msg) => {
102530
- if (!isMonitorDebugEnabled()) return;
102534
+ if (!isDebugEnabled()) return;
102531
102535
  originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
102532
102536
  `);
102533
102537
  };
@@ -102736,7 +102740,6 @@ function prefixPlain(name) {
102736
102740
  }
102737
102741
  var isRunnerDebugEnabled = () => core.isDebug();
102738
102742
  var isLocalDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true";
102739
- var isDebugEnabled = () => isLocalDebugEnabled() || isRunnerDebugEnabled();
102740
102743
  function ts() {
102741
102744
  return isDebugEnabled() ? `[${(/* @__PURE__ */ new Date()).toISOString()}] ` : "";
102742
102745
  }
@@ -103177,7 +103180,7 @@ var import_semver = __toESM(require_semver2(), 1);
103177
103180
  // package.json
103178
103181
  var package_default = {
103179
103182
  name: "pullfrog",
103180
- version: "0.1.49",
103183
+ version: "0.1.51",
103181
103184
  type: "module",
103182
103185
  bin: {
103183
103186
  pullfrog: "dist/cli.mjs",
@@ -111048,7 +111051,7 @@ async function installOpencodeCli(params) {
111048
111051
  installDependencies: true
111049
111052
  });
111050
111053
  }
111051
- var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
111054
+ var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this \u2014 if the picker is locked, this run had no Pullfrog Router funding: add a card or top up your credit.";
111052
111055
  function autoSelectModel() {
111053
111056
  const authorized2 = getAuthorizedModels();
111054
111057
  if (authorized2.size > 0) {
@@ -111123,9 +111126,11 @@ function parseModel2(value2) {
111123
111126
  return { providerID: value2.slice(0, slash), modelID: value2.slice(slash + 1) };
111124
111127
  }
111125
111128
  function bootOpencodeServer(params) {
111129
+ const logLevel = isDebugEnabled() ? "INFO" : "ERROR";
111130
+ if (logLevel !== "ERROR") log.info(`\xBB opencode log level: ${logLevel} (debug run)`);
111126
111131
  const proc = nodeSpawn2(
111127
111132
  params.cliPath,
111128
- ["serve", "--port", "0", "--hostname", "127.0.0.1", "--print-logs", "--log-level", "ERROR"],
111133
+ ["serve", "--port", "0", "--hostname", "127.0.0.1", "--print-logs", "--log-level", logLevel],
111129
111134
  {
111130
111135
  cwd: params.cwd,
111131
111136
  env: params.env,
@@ -111260,9 +111265,18 @@ async function consumeEvents(ctx, signal) {
111260
111265
  }
111261
111266
  }
111262
111267
  }
111268
+ function isModelOutput(ctx, part) {
111269
+ if (part.type !== "text") return true;
111270
+ if (ctx.promptMessageID === void 0) {
111271
+ ctx.promptMessageID = part.messageID;
111272
+ return false;
111273
+ }
111274
+ return part.messageID !== ctx.promptMessageID;
111275
+ }
111263
111276
  async function dispatchEvent(ctx, event) {
111264
111277
  if (event.type === "message.part.updated") {
111265
111278
  ctx.lastEventAt = performance6.now();
111279
+ if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
111266
111280
  await onPartUpdated(ctx, event.properties.part);
111267
111281
  return;
111268
111282
  }
@@ -111406,6 +111420,8 @@ async function runPromptTurn(ctx, params) {
111406
111420
  ctx.currentTurn = newTurn();
111407
111421
  const turn = ctx.currentTurn;
111408
111422
  const part = { type: "text", text: params.text };
111423
+ ctx.promptMessageID = void 0;
111424
+ ctx.sawModelOutput = false;
111409
111425
  let assistant;
111410
111426
  let returnedParts;
111411
111427
  let networkError = null;
@@ -111579,11 +111595,14 @@ function startInnerActivityWatchdog(params) {
111579
111595
  const id = setInterval(() => {
111580
111596
  if (fired) return;
111581
111597
  const idleMs = performance6.now() - params.ctx.lastEventAt;
111582
- if (idleMs <= params.timeoutMs) return;
111598
+ const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
111599
+ if (idleMs <= budgetMs) return;
111583
111600
  fired = true;
111584
111601
  const idleSec = Math.round(idleMs / 1e3);
111602
+ params.ctx.diagnostic.idleSec = idleSec;
111603
+ params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
111585
111604
  log.info(
111586
- `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness`
111605
+ params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
111587
111606
  );
111588
111607
  params.abortController.abort();
111589
111608
  try {
@@ -111708,6 +111727,8 @@ var opencode = agent({
111708
111727
  currentTurn: null,
111709
111728
  eventCount: 0,
111710
111729
  lastEventAt: performance6.now(),
111730
+ sawModelOutput: false,
111731
+ promptMessageID: void 0,
111711
111732
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
111712
111733
  loggedToolCallIDs: /* @__PURE__ */ new Set(),
111713
111734
  recentStderr: server.recentStderr,
@@ -111715,6 +111736,8 @@ var opencode = agent({
111715
111736
  label: "Pullfrog",
111716
111737
  recentStderr: server.recentStderr,
111717
111738
  lastProviderError: void 0,
111739
+ idleSec: void 0,
111740
+ sawModelOutput: false,
111718
111741
  eventCount: 0
111719
111742
  }
111720
111743
  };
@@ -159948,6 +159971,12 @@ function fixDoubleEscapedString(str) {
159948
159971
  return str;
159949
159972
  }
159950
159973
 
159974
+ // utils/isPullfrog.ts
159975
+ function isPullfrog(actor) {
159976
+ actor = actor?.toLowerCase().replace("[bot]", "");
159977
+ return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
159978
+ }
159979
+
159951
159980
  // utils/isTransientNetworkError.ts
159952
159981
  function isTransientNetworkError(error49, extraPatterns = []) {
159953
159982
  if (!(error49 instanceof Error)) return false;
@@ -160054,216 +160083,6 @@ function aggregateUsage(entries) {
160054
160083
  return out;
160055
160084
  }
160056
160085
 
160057
- // utils/payload.ts
160058
- var core4 = __toESM(require_core(), 1);
160059
- import { readFileSync as readFileSync4 } from "node:fs";
160060
- import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
160061
-
160062
- // utils/versioning.ts
160063
- var import_semver2 = __toESM(require_semver2(), 1);
160064
- var COMPATIBILITY_POLICY = "non-breaking";
160065
- function validateCompatibility(payloadVersion, actionVersion) {
160066
- const payloadSemVer = import_semver2.default.parse(payloadVersion);
160067
- if (!payloadSemVer)
160068
- throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
160069
- const major = payloadSemVer.major;
160070
- const minor = payloadSemVer.minor;
160071
- const patch = payloadSemVer.patch;
160072
- const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
160073
- if (!import_semver2.default.satisfies(actionVersion, compatibilityRange)) {
160074
- throw new Error(
160075
- `Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. Please update your workflow to use at least ${import_semver2.default.minVersion(compatibilityRange)} version of the action.`
160076
- );
160077
- }
160078
- }
160079
-
160080
- // utils/payload.ts
160081
- var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
160082
- var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
160083
- var StatusChecksInput = type.enumerated("disabled", "enabled");
160084
- var ProgressCommentsInput = type.enumerated("disabled", "enabled");
160085
- var JsonPayload = type({
160086
- "~pullfrog": "true",
160087
- version: "string",
160088
- "model?": "string | undefined",
160089
- "modelExplicit?": "boolean | undefined",
160090
- "effort?": "number | string | undefined",
160091
- prompt: "string",
160092
- "triggerer?": "string | undefined",
160093
- "baseInstructions?": "string | undefined",
160094
- "eventInstructions?": "string",
160095
- "previousRunsNote?": "string",
160096
- "event?": "object",
160097
- "xrepo?": type({
160098
- mode: "'all' | 'explicit'",
160099
- read: "string[]",
160100
- write: "string[]",
160101
- // optional so a payload from an older server build (pre-`unavailable`)
160102
- // still parses against a newer action across a rolling deploy.
160103
- "unavailable?": "string[]"
160104
- }).or("undefined"),
160105
- "timeout?": "string | undefined",
160106
- "progressComment?": type({
160107
- id: "string",
160108
- type: "'issue' | 'review'"
160109
- }).or("undefined"),
160110
- // optional so a payload from an older server build (pre-`checkRun`) still parses
160111
- // against a newer action across a rolling deploy.
160112
- "checkRun?": type({ id: "string" }).or("undefined"),
160113
- "generateSummary?": "boolean | undefined"
160114
- });
160115
- var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
160116
- function isCollaborator(event) {
160117
- const perm = event.authorPermission;
160118
- return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
160119
- }
160120
- var Inputs = type({
160121
- "prompt?": type.string.or("undefined"),
160122
- "prompt_file?": type.string.or("undefined"),
160123
- "model?": type.string.or("undefined"),
160124
- "effort?": type.string.or("undefined"),
160125
- "timeout?": type.string.or("undefined"),
160126
- "push?": PushPermissionInput.or("undefined"),
160127
- "shell?": ShellPermissionInput.or("undefined"),
160128
- "status_checks?": StatusChecksInput.or("undefined"),
160129
- "progress_comments?": ProgressCommentsInput.or("undefined"),
160130
- "cwd?": type.string.or("undefined"),
160131
- "output_schema?": type.string.or("undefined")
160132
- });
160133
- function isPayloadEvent(value2) {
160134
- return typeof value2 === "object" && value2 !== null && "trigger" in value2;
160135
- }
160136
- function resolveCwd(cwd) {
160137
- const workspace = process.env.GITHUB_WORKSPACE;
160138
- if (!cwd) return workspace;
160139
- if (isAbsolute2(cwd)) return cwd;
160140
- return workspace ? resolve2(workspace, cwd) : cwd;
160141
- }
160142
- function resolvePromptInput() {
160143
- const promptInput = core4.getInput("prompt");
160144
- const promptFile = core4.getInput("prompt_file");
160145
- if (promptInput && promptFile) {
160146
- throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
160147
- }
160148
- if (promptFile) {
160149
- return resolvePromptFile(promptFile);
160150
- }
160151
- if (!promptInput) {
160152
- throw new Error("one of 'prompt' or 'prompt_file' inputs is required.");
160153
- }
160154
- let parsed2;
160155
- try {
160156
- parsed2 = JSON.parse(promptInput);
160157
- } catch {
160158
- return promptInput;
160159
- }
160160
- if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
160161
- return promptInput;
160162
- }
160163
- const jsonPayload = JsonPayload.assert(parsed2);
160164
- validateCompatibility(jsonPayload.version, package_default.version);
160165
- return jsonPayload;
160166
- }
160167
- function resolvePromptFile(input) {
160168
- const workspace = process.env.GITHUB_WORKSPACE;
160169
- const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
160170
- const content = readFileSync4(path4, "utf-8");
160171
- if (!content.trim()) {
160172
- throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
160173
- }
160174
- return content;
160175
- }
160176
- function resolveNonPromptInputs() {
160177
- return Inputs.omit("prompt", "prompt_file").assert({
160178
- model: core4.getInput("model") || void 0,
160179
- effort: core4.getInput("effort") || void 0,
160180
- timeout: core4.getInput("timeout") || void 0,
160181
- cwd: core4.getInput("cwd") || void 0,
160182
- push: core4.getInput("push") || void 0,
160183
- shell: core4.getInput("shell") || void 0,
160184
- status_checks: core4.getInput("status_checks") || void 0,
160185
- progress_comments: core4.getInput("progress_comments") || void 0
160186
- });
160187
- }
160188
- var isPullfrog = (actor) => {
160189
- actor = actor?.replace("[bot]", "");
160190
- return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
160191
- };
160192
- function resolvePayload(resolvedPromptInput, repoSettings) {
160193
- const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
160194
- const inputs = resolveNonPromptInputs();
160195
- const rawEvent = jsonPayload?.event;
160196
- const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
160197
- const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
160198
- const rawEffort = jsonPayload?.effort ?? inputs.effort ?? repoSettings.effort ?? void 0;
160199
- const effort = rawEffort === void 0 ? void 0 : parseEffortPosition(String(rawEffort));
160200
- const isNonCollaborator = !isCollaborator(event);
160201
- const repoShell = repoSettings.shell ?? "restricted";
160202
- const inputShell = inputs.shell;
160203
- let resolvedShell = repoShell;
160204
- if (inputShell === "disabled") {
160205
- resolvedShell = "disabled";
160206
- } else if (inputShell === "restricted" && resolvedShell === "enabled") {
160207
- resolvedShell = "restricted";
160208
- }
160209
- if (isNonCollaborator && resolvedShell === "enabled") {
160210
- resolvedShell = "restricted";
160211
- }
160212
- return {
160213
- "~pullfrog": true,
160214
- version: jsonPayload?.version ?? package_default.version,
160215
- model,
160216
- // explicit only when the model came from a per-run override flag (carried on
160217
- // the JSON payload). a GHA `model` input or the repo default is not explicit.
160218
- modelExplicit: jsonPayload?.modelExplicit ?? false,
160219
- effort,
160220
- prompt,
160221
- triggerer: jsonPayload?.triggerer ?? // it's not a common use case but GITHUB_ACTOR can be a user when the workflow is manually triggered by a user through GitHub Actions UI
160222
- (!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
160223
- baseInstructions: jsonPayload?.baseInstructions,
160224
- eventInstructions: jsonPayload?.eventInstructions,
160225
- previousRunsNote: jsonPayload?.previousRunsNote,
160226
- event,
160227
- xrepo: jsonPayload?.xrepo,
160228
- timeout: inputs.timeout ?? jsonPayload?.timeout,
160229
- cwd: resolveCwd(inputs.cwd),
160230
- progressComment: jsonPayload?.progressComment,
160231
- checkRun: jsonPayload?.checkRun,
160232
- generateSummary: jsonPayload?.generateSummary,
160233
- // permissions: inputs > repoSettings > fallbacks
160234
- push: inputs.push ?? repoSettings.push ?? "restricted",
160235
- shell: resolvedShell,
160236
- // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
160237
- // shows whether Pullfrog is running without anyone having to opt in. the workflow
160238
- // input is the source of truth when set (mirrors `push`); otherwise the repo
160239
- // setting decides.
160240
- runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
160241
- // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
160242
- // be *required* by branch protection, so it must never turn itself on.
160243
- approvalCheck: inputs.status_checks === "enabled",
160244
- // temporary progress chrome. the workflow input is the source of truth when
160245
- // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
160246
- progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
160247
- // set by proxy logic in main.ts when routing through OpenRouter
160248
- proxyModel: void 0
160249
- };
160250
- }
160251
- function resolveOutputSchema() {
160252
- const raw2 = core4.getInput("output_schema");
160253
- if (!raw2) return void 0;
160254
- let parsed2;
160255
- try {
160256
- parsed2 = JSON.parse(raw2);
160257
- } catch {
160258
- throw new Error(`invalid output_schema: not valid JSON`);
160259
- }
160260
- if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
160261
- throw new Error(`invalid output_schema: must be a JSON object`);
160262
- }
160263
- log.info("\xBB structured output schema provided \u2014 output will be required");
160264
- return parsed2;
160265
- }
160266
-
160267
160086
  // mcp/comment.ts
160268
160087
  function isNotFoundError(error49) {
160269
160088
  return error49 instanceof Error && error49.message.includes("Not Found");
@@ -162574,7 +162393,7 @@ function isMetadataYarnClassic(metadataPath) {
162574
162393
  }
162575
162394
 
162576
162395
  // utils/packageManager.ts
162577
- var import_semver3 = __toESM(require_semver2(), 1);
162396
+ var import_semver2 = __toESM(require_semver2(), 1);
162578
162397
  import { existsSync as existsSync5 } from "node:fs";
162579
162398
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
162580
162399
  import { delimiter, join as join17 } from "node:path";
@@ -162596,7 +162415,7 @@ function parsePackageManagerField(value2) {
162596
162415
  return {
162597
162416
  name,
162598
162417
  version: version3,
162599
- concrete: import_semver3.default.valid(version3) !== null,
162418
+ concrete: import_semver2.default.valid(version3) !== null,
162600
162419
  source: "packageManager"
162601
162420
  };
162602
162421
  }
@@ -162610,7 +162429,7 @@ function parseDevEnginesField(field) {
162610
162429
  return {
162611
162430
  name: field.name,
162612
162431
  version: version3,
162613
- concrete: import_semver3.default.valid(version3) !== null,
162432
+ concrete: import_semver2.default.valid(version3) !== null,
162614
162433
  source: "devEngines"
162615
162434
  };
162616
162435
  }
@@ -162644,7 +162463,7 @@ async function resolvePackageManagerSpec(cwd) {
162644
162463
  }
162645
162464
  return devSpec;
162646
162465
  }
162647
- if (pmSpec.concrete && import_semver3.default.satisfies(pmSpec.version, devSpec.version)) {
162466
+ if (pmSpec.concrete && import_semver2.default.satisfies(pmSpec.version, devSpec.version)) {
162648
162467
  return pmSpec;
162649
162468
  }
162650
162469
  if (pmSpec.concrete) {
@@ -162856,10 +162675,10 @@ ${errorMessage}`]
162856
162675
  };
162857
162676
 
162858
162677
  // prep/installPythonDependencies.ts
162859
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
162678
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
162860
162679
  import { join as join19 } from "node:path";
162861
162680
  function declaresBuildSystem(path4) {
162862
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
162681
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
162863
162682
  }
162864
162683
  function configApplies(config3, cwd) {
162865
162684
  const path4 = join19(cwd, config3.file);
@@ -164994,6 +164813,36 @@ function KillBackgroundTool(ctx) {
164994
164813
  });
164995
164814
  }
164996
164815
 
164816
+ // mcp/similarIssues.ts
164817
+ var SimilarIssues = type({
164818
+ issue_number: type.number.describe("The issue number to find older duplicate candidates for")
164819
+ });
164820
+ function SimilarIssuesTool(ctx) {
164821
+ return tool({
164822
+ name: "find_similar_issues",
164823
+ description: "Find high-recall older duplicate candidates for an issue. Similarity is only retrieval: inspect promising candidates with get_issue before deciding whether they are duplicates.",
164824
+ mutates: true,
164825
+ parameters: SimilarIssues,
164826
+ execute: execute(async (input) => {
164827
+ if (ctx.payload.event.is_pr || ctx.payload.event.issue_number !== input.issue_number) {
164828
+ throw new Error("find_similar_issues is limited to the issue that triggered this run");
164829
+ }
164830
+ const response = await apiFetch({
164831
+ path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/issues/${input.issue_number}/similar`,
164832
+ headers: {
164833
+ authorization: `Bearer ${ctx.apiToken}`
164834
+ },
164835
+ signal: AbortSignal.timeout(13 * 6e4)
164836
+ });
164837
+ if (!response.ok) {
164838
+ const message = await response.text();
164839
+ throw new Error(`similar issue lookup returned ${response.status}: ${message}`);
164840
+ }
164841
+ return { result: await response.json() };
164842
+ })
164843
+ });
164844
+ }
164845
+
164997
164846
  // mcp/upload.ts
164998
164847
  import * as fs4 from "node:fs";
164999
164848
  import * as path3 from "node:path";
@@ -165232,6 +165081,9 @@ function buildCommonTools(ctx, outputSchema) {
165232
165081
  if (ctx.xrepo) {
165233
165082
  tools.push(ListReposTool(ctx), CheckoutRepoTool(ctx));
165234
165083
  }
165084
+ if (hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event })) {
165085
+ tools.push(SimilarIssuesTool(ctx));
165086
+ }
165235
165087
  const isStandalone = ctx.payload.event.trigger === "unknown";
165236
165088
  if (isStandalone || outputSchema) {
165237
165089
  tools.push(SetOutputTool(ctx, outputSchema));
@@ -166175,6 +166027,11 @@ Rules:
166175
166027
  ### GitHub
166176
166028
 
166177
166029
  Use MCP tools from ${pullfrogMcpName} for all GitHub operations. Never use the \`gh\` CLI \u2014 it is not authenticated and will fail. The MCP tools handle authentication and enforce permissions.
166030
+ ${hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event }) ? `
166031
+ #### Duplicate detection (enabled for this repository)
166032
+
166033
+ Call \`${t2("find_similar_issues")}\` for #${ctx.payload.event.issue_number} before planning. If it duplicates an existing issue, link that instead of producing a plan; never close or label on similarity alone.
166034
+ ` : ""}
166178
166035
 
166179
166036
  ${getShellInstructions(ctx.payload.shell, t2)}
166180
166037
 
@@ -166501,7 +166358,7 @@ function buildModelAccessError(input) {
166501
166358
  }
166502
166359
 
166503
166360
  // utils/normalizeEnv.ts
166504
- var core5 = __toESM(require_core(), 1);
166361
+ var core4 = __toESM(require_core(), 1);
166505
166362
  function sanitizeSecret(key, value2) {
166506
166363
  const trimmed = value2.trim();
166507
166364
  if (trimmed.length === 0) {
@@ -166515,7 +166372,7 @@ function sanitizeSecret(key, value2) {
166515
166372
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
166516
166373
  );
166517
166374
  }
166518
- core5.setSecret(trimmed);
166375
+ core4.setSecret(trimmed);
166519
166376
  return trimmed;
166520
166377
  }
166521
166378
  function normalizeEnv() {
@@ -166559,7 +166416,7 @@ function normalizeEnv() {
166559
166416
  }
166560
166417
 
166561
166418
  // utils/overrides.ts
166562
- var core6 = __toESM(require_core(), 1);
166419
+ var core5 = __toESM(require_core(), 1);
166563
166420
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
166564
166421
  "GITHUB_TOKEN",
166565
166422
  "GH_TOKEN",
@@ -166605,7 +166462,7 @@ function applyOverrides(params) {
166605
166462
  denied.push(key);
166606
166463
  continue;
166607
166464
  }
166608
- if (value2.length > 0) core6.setSecret(value2);
166465
+ if (value2.length > 0) core5.setSecret(value2);
166609
166466
  params.env[key] = value2;
166610
166467
  applied.push(key);
166611
166468
  }
@@ -166613,6 +166470,212 @@ function applyOverrides(params) {
166613
166470
  return { applied, denied };
166614
166471
  }
166615
166472
 
166473
+ // utils/payload.ts
166474
+ var core6 = __toESM(require_core(), 1);
166475
+ import { readFileSync as readFileSync6 } from "node:fs";
166476
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
166477
+
166478
+ // utils/versioning.ts
166479
+ var import_semver3 = __toESM(require_semver2(), 1);
166480
+ var COMPATIBILITY_POLICY = "non-breaking";
166481
+ function validateCompatibility(payloadVersion, actionVersion) {
166482
+ const payloadSemVer = import_semver3.default.parse(payloadVersion);
166483
+ if (!payloadSemVer)
166484
+ throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
166485
+ const major = payloadSemVer.major;
166486
+ const minor = payloadSemVer.minor;
166487
+ const patch = payloadSemVer.patch;
166488
+ const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
166489
+ if (!import_semver3.default.satisfies(actionVersion, compatibilityRange)) {
166490
+ throw new Error(
166491
+ `Payload version ${payloadVersion} is incompatible with action version ${actionVersion}. Please update your workflow to use at least ${import_semver3.default.minVersion(compatibilityRange)} version of the action.`
166492
+ );
166493
+ }
166494
+ }
166495
+
166496
+ // utils/payload.ts
166497
+ var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
166498
+ var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
166499
+ var StatusChecksInput = type.enumerated("disabled", "enabled");
166500
+ var ProgressCommentsInput = type.enumerated("disabled", "enabled");
166501
+ var JsonPayload = type({
166502
+ "~pullfrog": "true",
166503
+ version: "string",
166504
+ "model?": "string | undefined",
166505
+ "modelExplicit?": "boolean | undefined",
166506
+ "effort?": "number | string | undefined",
166507
+ prompt: "string",
166508
+ "triggerer?": "string | undefined",
166509
+ "baseInstructions?": "string | undefined",
166510
+ "eventInstructions?": "string",
166511
+ "previousRunsNote?": "string",
166512
+ "event?": "object",
166513
+ "xrepo?": type({
166514
+ mode: "'all' | 'explicit'",
166515
+ read: "string[]",
166516
+ write: "string[]",
166517
+ // optional so a payload from an older server build (pre-`unavailable`)
166518
+ // still parses against a newer action across a rolling deploy.
166519
+ "unavailable?": "string[]"
166520
+ }).or("undefined"),
166521
+ "timeout?": "string | undefined",
166522
+ "progressComment?": type({
166523
+ id: "string",
166524
+ type: "'issue' | 'review'"
166525
+ }).or("undefined"),
166526
+ // optional so a payload from an older server build (pre-`checkRun`) still parses
166527
+ // against a newer action across a rolling deploy.
166528
+ "checkRun?": type({ id: "string" }).or("undefined"),
166529
+ "generateSummary?": "boolean | undefined"
166530
+ });
166531
+ var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
166532
+ function isCollaborator(event) {
166533
+ const perm = event.authorPermission;
166534
+ return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
166535
+ }
166536
+ var Inputs = type({
166537
+ "prompt?": type.string.or("undefined"),
166538
+ "prompt_file?": type.string.or("undefined"),
166539
+ "model?": type.string.or("undefined"),
166540
+ "effort?": type.string.or("undefined"),
166541
+ "timeout?": type.string.or("undefined"),
166542
+ "push?": PushPermissionInput.or("undefined"),
166543
+ "shell?": ShellPermissionInput.or("undefined"),
166544
+ "status_checks?": StatusChecksInput.or("undefined"),
166545
+ "progress_comments?": ProgressCommentsInput.or("undefined"),
166546
+ "cwd?": type.string.or("undefined"),
166547
+ "output_schema?": type.string.or("undefined")
166548
+ });
166549
+ function isPayloadEvent(value2) {
166550
+ return typeof value2 === "object" && value2 !== null && "trigger" in value2;
166551
+ }
166552
+ function resolveCwd(cwd) {
166553
+ const workspace = process.env.GITHUB_WORKSPACE;
166554
+ if (!cwd) return workspace;
166555
+ if (isAbsolute2(cwd)) return cwd;
166556
+ return workspace ? resolve2(workspace, cwd) : cwd;
166557
+ }
166558
+ function resolvePromptInput() {
166559
+ const promptInput = core6.getInput("prompt");
166560
+ const promptFile = core6.getInput("prompt_file");
166561
+ if (promptInput && promptFile) {
166562
+ throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
166563
+ }
166564
+ if (promptFile) {
166565
+ return resolvePromptFile(promptFile);
166566
+ }
166567
+ if (!promptInput) {
166568
+ throw new Error("one of 'prompt' or 'prompt_file' inputs is required.");
166569
+ }
166570
+ let parsed2;
166571
+ try {
166572
+ parsed2 = JSON.parse(promptInput);
166573
+ } catch {
166574
+ return promptInput;
166575
+ }
166576
+ if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
166577
+ return promptInput;
166578
+ }
166579
+ const jsonPayload = JsonPayload.assert(parsed2);
166580
+ validateCompatibility(jsonPayload.version, package_default.version);
166581
+ return jsonPayload;
166582
+ }
166583
+ function resolvePromptFile(input) {
166584
+ const workspace = process.env.GITHUB_WORKSPACE;
166585
+ const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
166586
+ const content = readFileSync6(path4, "utf-8");
166587
+ if (!content.trim()) {
166588
+ throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
166589
+ }
166590
+ return content;
166591
+ }
166592
+ function resolveNonPromptInputs() {
166593
+ return Inputs.omit("prompt", "prompt_file").assert({
166594
+ model: core6.getInput("model") || void 0,
166595
+ effort: core6.getInput("effort") || void 0,
166596
+ timeout: core6.getInput("timeout") || void 0,
166597
+ cwd: core6.getInput("cwd") || void 0,
166598
+ push: core6.getInput("push") || void 0,
166599
+ shell: core6.getInput("shell") || void 0,
166600
+ status_checks: core6.getInput("status_checks") || void 0,
166601
+ progress_comments: core6.getInput("progress_comments") || void 0
166602
+ });
166603
+ }
166604
+ function resolvePayload(resolvedPromptInput, repoSettings) {
166605
+ const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
166606
+ const inputs = resolveNonPromptInputs();
166607
+ const rawEvent = jsonPayload?.event;
166608
+ const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
166609
+ const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
166610
+ const rawEffort = jsonPayload?.effort ?? inputs.effort ?? repoSettings.effort ?? void 0;
166611
+ const effort = rawEffort === void 0 ? void 0 : parseEffortPosition(String(rawEffort));
166612
+ const isNonCollaborator = !isCollaborator(event);
166613
+ const repoShell = repoSettings.shell ?? "restricted";
166614
+ const inputShell = inputs.shell;
166615
+ let resolvedShell = repoShell;
166616
+ if (inputShell === "disabled") {
166617
+ resolvedShell = "disabled";
166618
+ } else if (inputShell === "restricted" && resolvedShell === "enabled") {
166619
+ resolvedShell = "restricted";
166620
+ }
166621
+ if (isNonCollaborator && resolvedShell === "enabled") {
166622
+ resolvedShell = "restricted";
166623
+ }
166624
+ return {
166625
+ "~pullfrog": true,
166626
+ version: jsonPayload?.version ?? package_default.version,
166627
+ model,
166628
+ // explicit only when the model came from a per-run override flag (carried on
166629
+ // the JSON payload). a GHA `model` input or the repo default is not explicit.
166630
+ modelExplicit: jsonPayload?.modelExplicit ?? false,
166631
+ effort,
166632
+ prompt,
166633
+ triggerer: jsonPayload?.triggerer ?? // it's not a common use case but GITHUB_ACTOR can be a user when the workflow is manually triggered by a user through GitHub Actions UI
166634
+ (!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
166635
+ baseInstructions: jsonPayload?.baseInstructions,
166636
+ eventInstructions: jsonPayload?.eventInstructions,
166637
+ previousRunsNote: jsonPayload?.previousRunsNote,
166638
+ event,
166639
+ xrepo: jsonPayload?.xrepo,
166640
+ timeout: inputs.timeout ?? jsonPayload?.timeout,
166641
+ cwd: resolveCwd(inputs.cwd),
166642
+ progressComment: jsonPayload?.progressComment,
166643
+ checkRun: jsonPayload?.checkRun,
166644
+ generateSummary: jsonPayload?.generateSummary,
166645
+ // permissions: inputs > repoSettings > fallbacks
166646
+ push: inputs.push ?? repoSettings.push ?? "restricted",
166647
+ shell: resolvedShell,
166648
+ // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
166649
+ // shows whether Pullfrog is running without anyone having to opt in. the workflow
166650
+ // input is the source of truth when set (mirrors `push`); otherwise the repo
166651
+ // setting decides.
166652
+ runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
166653
+ // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
166654
+ // be *required* by branch protection, so it must never turn itself on.
166655
+ approvalCheck: inputs.status_checks === "enabled",
166656
+ // temporary progress chrome. the workflow input is the source of truth when
166657
+ // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
166658
+ progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
166659
+ // set by proxy logic in main.ts when routing through OpenRouter
166660
+ proxyModel: void 0
166661
+ };
166662
+ }
166663
+ function resolveOutputSchema() {
166664
+ const raw2 = core6.getInput("output_schema");
166665
+ if (!raw2) return void 0;
166666
+ let parsed2;
166667
+ try {
166668
+ parsed2 = JSON.parse(raw2);
166669
+ } catch {
166670
+ throw new Error(`invalid output_schema: not valid JSON`);
166671
+ }
166672
+ if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
166673
+ throw new Error(`invalid output_schema: must be a JSON object`);
166674
+ }
166675
+ log.info("\xBB structured output schema provided \u2014 output will be required");
166676
+ return parsed2;
166677
+ }
166678
+
166616
166679
  // utils/proxy.ts
166617
166680
  var core8 = __toESM(require_core(), 1);
166618
166681
 
@@ -167160,6 +167223,7 @@ var defaultSettings = {
167160
167223
  prApproveEnabled: false,
167161
167224
  autoMergeEnabled: false,
167162
167225
  signedCommits: false,
167226
+ repoIntelligence: false,
167163
167227
  progressComments: true,
167164
167228
  statusChecks: true,
167165
167229
  modeInstructions: {},
@@ -167299,7 +167363,8 @@ function formatAgentHangBody(input) {
167299
167363
  const headline = `**${input.diagnostic.label} ${verb}**${cause}`;
167300
167364
  const explanation = formatExplanation({
167301
167365
  isHang: input.isHang,
167302
- errorMessage: input.errorMessage
167366
+ errorMessage: input.errorMessage,
167367
+ idleSec: input.diagnostic.idleSec
167303
167368
  });
167304
167369
  const parts = [headline, "", `${explanation} ${formatEventsPart(input.diagnostic)}`];
167305
167370
  const tail = renderStderrTail(input.diagnostic.recentStderr);
@@ -167320,7 +167385,7 @@ function formatAgentHangBody(input) {
167320
167385
  }
167321
167386
  function formatExplanation(input) {
167322
167387
  if (!input.isHang) return `The agent exited unexpectedly: ${input.errorMessage}`;
167323
- const idleSec = parseIdleSec(input.errorMessage);
167388
+ const idleSec = input.idleSec ?? parseIdleSec(input.errorMessage);
167324
167389
  if (idleSec === void 0) {
167325
167390
  return "The agent stopped emitting events and was killed by the activity-timeout watchdog.";
167326
167391
  }
@@ -167331,11 +167396,13 @@ function parseIdleSec(message) {
167331
167396
  return match3 ? Number(match3[1]) : void 0;
167332
167397
  }
167333
167398
  function formatEventsPart(diagnostic) {
167334
- if (diagnostic.eventCount > 0) {
167399
+ if (diagnostic.lastProviderError) {
167335
167400
  return `${diagnostic.eventCount} events were processed before the failure.`;
167336
167401
  }
167337
- if (diagnostic.lastProviderError) return "No events were emitted before the failure.";
167338
- return "No events were emitted \u2014 check whether the model provider is reachable.";
167402
+ if (!diagnostic.sawModelOutput) {
167403
+ return "The model produced no output at all before the stall \u2014 the request was sent but nothing came back. This is usually transient; re-running often succeeds.";
167404
+ }
167405
+ return `${diagnostic.eventCount} events were processed before the failure.`;
167339
167406
  }
167340
167407
  function renderStderrTail(lines) {
167341
167408
  if (lines.length === 0) return "";
@@ -167691,8 +167758,8 @@ function getCurrentWorkflowFilename() {
167691
167758
  }
167692
167759
 
167693
167760
  // utils/runStatusCheck.ts
167694
- var RUN_STATUS_CHECK_NAME = "Pullfrog";
167695
- var APPROVAL_CHECK_NAME = "Pullfrog approval";
167761
+ var RUN_STATUS_CHECK_NAME = "pullfrog";
167762
+ var APPROVAL_CHECK_NAME = "pullfrog-approval";
167696
167763
  function parseCheckRunId(raw2) {
167697
167764
  if (!raw2?.id) return void 0;
167698
167765
  const id = parseInt(raw2.id, 10);
@@ -168217,7 +168284,7 @@ async function main() {
168217
168284
  _promise2 && await _promise2;
168218
168285
  }
168219
168286
  }
168220
- createTempDirectory();
168287
+ const tmpdir4 = createTempDirectory();
168221
168288
  const opencodeCliPath = await agents.opencode.install();
168222
168289
  captureBaselineModels(opencodeCliPath);
168223
168290
  if (runContext.dbSecrets) {
@@ -168271,7 +168338,6 @@ async function main() {
168271
168338
  if (payload.cwd && process.cwd() !== payload.cwd) {
168272
168339
  process.chdir(payload.cwd);
168273
168340
  }
168274
- const tmpdir4 = createTempDirectory();
168275
168341
  const originalBody = payload.event.body;
168276
168342
  const resolvedBody = await resolveBody({
168277
168343
  event: payload.event,
@@ -168380,6 +168446,7 @@ async function main() {
168380
168446
  prApproveEnabled: runContext.repoSettings.prApproveEnabled,
168381
168447
  autoMergeEnabled: runContext.repoSettings.autoMergeEnabled,
168382
168448
  signedCommits: runContext.repoSettings.signedCommits,
168449
+ repoIntelligence: runContext.repoSettings.repoIntelligence,
168383
168450
  modeInstructions: runContext.repoSettings.modeInstructions,
168384
168451
  toolState,
168385
168452
  runId: runInfo.runId,
@@ -168454,6 +168521,7 @@ async function main() {
168454
168521
  agentId,
168455
168522
  outputSchema,
168456
168523
  signedCommits: runContext.repoSettings.signedCommits,
168524
+ repoIntelligence: runContext.repoSettings.repoIntelligence,
168457
168525
  learningsFilePath: toolState.learningsFilePath ?? null,
168458
168526
  learningsHeadings: runContext.repoSettings.learningsHeadings,
168459
168527
  setupHookFailure: describeSetupFailure(setupHook.failure),
@@ -169650,7 +169718,7 @@ async function runCli4(input) {
169650
169718
  }
169651
169719
 
169652
169720
  // cli.ts
169653
- var VERSION10 = "0.1.49";
169721
+ var VERSION10 = "0.1.51";
169654
169722
  var bin = basename2(process.argv[1] || "");
169655
169723
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
169656
169724
  var rawArgs = process.argv.slice(2);