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/index.js CHANGED
@@ -100541,14 +100541,18 @@ function formatMcpToolRef(agentId, toolName) {
100541
100541
  return agentId;
100542
100542
  }
100543
100543
  }
100544
+ function hasSimilarIssues(params) {
100545
+ return params.repoIntelligence && !!params.event.issue_number && !params.event.is_pr;
100546
+ }
100544
100547
 
100545
100548
  // utils/activity.ts
100546
100549
  import { performance as performance2 } from "node:perf_hooks";
100547
- function isMonitorDebugEnabled() {
100550
+ function isDebugEnabled() {
100548
100551
  return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
100549
100552
  }
100550
100553
  var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
100551
100554
  var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
100555
+ var AGENT_FIRST_EVENT_TIMEOUT_MS = 12e4;
100552
100556
  var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
100553
100557
  var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
100554
100558
  var ACTIVITY_NOISE_PATTERNS = [
@@ -100592,7 +100596,7 @@ function startProcessOutputMonitor(ctx) {
100592
100596
  process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
100593
100597
  process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
100594
100598
  const debugBypass = (msg) => {
100595
- if (!isMonitorDebugEnabled()) return;
100599
+ if (!isDebugEnabled()) return;
100596
100600
  originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
100597
100601
  `);
100598
100602
  };
@@ -100801,7 +100805,6 @@ function prefixPlain(name) {
100801
100805
  }
100802
100806
  var isRunnerDebugEnabled = () => core.isDebug();
100803
100807
  var isLocalDebugEnabled = () => process.env.LOG_LEVEL === "debug" || process.env.ACTIONS_STEP_DEBUG === "true";
100804
- var isDebugEnabled = () => isLocalDebugEnabled() || isRunnerDebugEnabled();
100805
100808
  function ts() {
100806
100809
  return isDebugEnabled() ? `[${(/* @__PURE__ */ new Date()).toISOString()}] ` : "";
100807
100810
  }
@@ -101242,7 +101245,7 @@ var import_semver = __toESM(require_semver2(), 1);
101242
101245
  // package.json
101243
101246
  var package_default = {
101244
101247
  name: "pullfrog",
101245
- version: "0.1.49",
101248
+ version: "0.1.51",
101246
101249
  type: "module",
101247
101250
  bin: {
101248
101251
  pullfrog: "dist/cli.mjs",
@@ -109155,7 +109158,7 @@ async function installOpencodeCli(params) {
109155
109158
  installDependencies: true
109156
109159
  });
109157
109160
  }
109158
- var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
109161
+ 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.";
109159
109162
  function autoSelectModel() {
109160
109163
  const authorized2 = getAuthorizedModels();
109161
109164
  if (authorized2.size > 0) {
@@ -109230,9 +109233,11 @@ function parseModel2(value2) {
109230
109233
  return { providerID: value2.slice(0, slash), modelID: value2.slice(slash + 1) };
109231
109234
  }
109232
109235
  function bootOpencodeServer(params) {
109236
+ const logLevel = isDebugEnabled() ? "INFO" : "ERROR";
109237
+ if (logLevel !== "ERROR") log.info(`\xBB opencode log level: ${logLevel} (debug run)`);
109233
109238
  const proc = nodeSpawn2(
109234
109239
  params.cliPath,
109235
- ["serve", "--port", "0", "--hostname", "127.0.0.1", "--print-logs", "--log-level", "ERROR"],
109240
+ ["serve", "--port", "0", "--hostname", "127.0.0.1", "--print-logs", "--log-level", logLevel],
109236
109241
  {
109237
109242
  cwd: params.cwd,
109238
109243
  env: params.env,
@@ -109367,9 +109372,18 @@ async function consumeEvents(ctx, signal) {
109367
109372
  }
109368
109373
  }
109369
109374
  }
109375
+ function isModelOutput(ctx, part) {
109376
+ if (part.type !== "text") return true;
109377
+ if (ctx.promptMessageID === void 0) {
109378
+ ctx.promptMessageID = part.messageID;
109379
+ return false;
109380
+ }
109381
+ return part.messageID !== ctx.promptMessageID;
109382
+ }
109370
109383
  async function dispatchEvent(ctx, event) {
109371
109384
  if (event.type === "message.part.updated") {
109372
109385
  ctx.lastEventAt = performance6.now();
109386
+ if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
109373
109387
  await onPartUpdated(ctx, event.properties.part);
109374
109388
  return;
109375
109389
  }
@@ -109513,6 +109527,8 @@ async function runPromptTurn(ctx, params) {
109513
109527
  ctx.currentTurn = newTurn();
109514
109528
  const turn = ctx.currentTurn;
109515
109529
  const part = { type: "text", text: params.text };
109530
+ ctx.promptMessageID = void 0;
109531
+ ctx.sawModelOutput = false;
109516
109532
  let assistant;
109517
109533
  let returnedParts;
109518
109534
  let networkError = null;
@@ -109686,11 +109702,14 @@ function startInnerActivityWatchdog(params) {
109686
109702
  const id = setInterval(() => {
109687
109703
  if (fired) return;
109688
109704
  const idleMs = performance6.now() - params.ctx.lastEventAt;
109689
- if (idleMs <= params.timeoutMs) return;
109705
+ const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
109706
+ if (idleMs <= budgetMs) return;
109690
109707
  fired = true;
109691
109708
  const idleSec = Math.round(idleMs / 1e3);
109709
+ params.ctx.diagnostic.idleSec = idleSec;
109710
+ params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
109692
109711
  log.info(
109693
- `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness`
109712
+ 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`
109694
109713
  );
109695
109714
  params.abortController.abort();
109696
109715
  try {
@@ -109815,6 +109834,8 @@ var opencode = agent({
109815
109834
  currentTurn: null,
109816
109835
  eventCount: 0,
109817
109836
  lastEventAt: performance6.now(),
109837
+ sawModelOutput: false,
109838
+ promptMessageID: void 0,
109818
109839
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
109819
109840
  loggedToolCallIDs: /* @__PURE__ */ new Set(),
109820
109841
  recentStderr: server.recentStderr,
@@ -109822,6 +109843,8 @@ var opencode = agent({
109822
109843
  label: "Pullfrog",
109823
109844
  recentStderr: server.recentStderr,
109824
109845
  lastProviderError: void 0,
109846
+ idleSec: void 0,
109847
+ sawModelOutput: false,
109825
109848
  eventCount: 0
109826
109849
  }
109827
109850
  };
@@ -158055,6 +158078,12 @@ function fixDoubleEscapedString(str) {
158055
158078
  return str;
158056
158079
  }
158057
158080
 
158081
+ // utils/isPullfrog.ts
158082
+ function isPullfrog(actor) {
158083
+ actor = actor?.toLowerCase().replace("[bot]", "");
158084
+ return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
158085
+ }
158086
+
158058
158087
  // utils/isTransientNetworkError.ts
158059
158088
  function isTransientNetworkError(error49, extraPatterns = []) {
158060
158089
  if (!(error49 instanceof Error)) return false;
@@ -158161,216 +158190,6 @@ function aggregateUsage(entries) {
158161
158190
  return out;
158162
158191
  }
158163
158192
 
158164
- // utils/payload.ts
158165
- var core4 = __toESM(require_core(), 1);
158166
- import { readFileSync as readFileSync3 } from "node:fs";
158167
- import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
158168
-
158169
- // utils/versioning.ts
158170
- var import_semver2 = __toESM(require_semver2(), 1);
158171
- var COMPATIBILITY_POLICY = "non-breaking";
158172
- function validateCompatibility(payloadVersion, actionVersion) {
158173
- const payloadSemVer = import_semver2.default.parse(payloadVersion);
158174
- if (!payloadSemVer)
158175
- throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
158176
- const major = payloadSemVer.major;
158177
- const minor = payloadSemVer.minor;
158178
- const patch = payloadSemVer.patch;
158179
- const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
158180
- if (!import_semver2.default.satisfies(actionVersion, compatibilityRange)) {
158181
- throw new Error(
158182
- `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.`
158183
- );
158184
- }
158185
- }
158186
-
158187
- // utils/payload.ts
158188
- var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
158189
- var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
158190
- var StatusChecksInput = type.enumerated("disabled", "enabled");
158191
- var ProgressCommentsInput = type.enumerated("disabled", "enabled");
158192
- var JsonPayload = type({
158193
- "~pullfrog": "true",
158194
- version: "string",
158195
- "model?": "string | undefined",
158196
- "modelExplicit?": "boolean | undefined",
158197
- "effort?": "number | string | undefined",
158198
- prompt: "string",
158199
- "triggerer?": "string | undefined",
158200
- "baseInstructions?": "string | undefined",
158201
- "eventInstructions?": "string",
158202
- "previousRunsNote?": "string",
158203
- "event?": "object",
158204
- "xrepo?": type({
158205
- mode: "'all' | 'explicit'",
158206
- read: "string[]",
158207
- write: "string[]",
158208
- // optional so a payload from an older server build (pre-`unavailable`)
158209
- // still parses against a newer action across a rolling deploy.
158210
- "unavailable?": "string[]"
158211
- }).or("undefined"),
158212
- "timeout?": "string | undefined",
158213
- "progressComment?": type({
158214
- id: "string",
158215
- type: "'issue' | 'review'"
158216
- }).or("undefined"),
158217
- // optional so a payload from an older server build (pre-`checkRun`) still parses
158218
- // against a newer action across a rolling deploy.
158219
- "checkRun?": type({ id: "string" }).or("undefined"),
158220
- "generateSummary?": "boolean | undefined"
158221
- });
158222
- var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
158223
- function isCollaborator(event) {
158224
- const perm = event.authorPermission;
158225
- return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
158226
- }
158227
- var Inputs = type({
158228
- "prompt?": type.string.or("undefined"),
158229
- "prompt_file?": type.string.or("undefined"),
158230
- "model?": type.string.or("undefined"),
158231
- "effort?": type.string.or("undefined"),
158232
- "timeout?": type.string.or("undefined"),
158233
- "push?": PushPermissionInput.or("undefined"),
158234
- "shell?": ShellPermissionInput.or("undefined"),
158235
- "status_checks?": StatusChecksInput.or("undefined"),
158236
- "progress_comments?": ProgressCommentsInput.or("undefined"),
158237
- "cwd?": type.string.or("undefined"),
158238
- "output_schema?": type.string.or("undefined")
158239
- });
158240
- function isPayloadEvent(value2) {
158241
- return typeof value2 === "object" && value2 !== null && "trigger" in value2;
158242
- }
158243
- function resolveCwd(cwd) {
158244
- const workspace = process.env.GITHUB_WORKSPACE;
158245
- if (!cwd) return workspace;
158246
- if (isAbsolute2(cwd)) return cwd;
158247
- return workspace ? resolve2(workspace, cwd) : cwd;
158248
- }
158249
- function resolvePromptInput() {
158250
- const promptInput = core4.getInput("prompt");
158251
- const promptFile = core4.getInput("prompt_file");
158252
- if (promptInput && promptFile) {
158253
- throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
158254
- }
158255
- if (promptFile) {
158256
- return resolvePromptFile(promptFile);
158257
- }
158258
- if (!promptInput) {
158259
- throw new Error("one of 'prompt' or 'prompt_file' inputs is required.");
158260
- }
158261
- let parsed2;
158262
- try {
158263
- parsed2 = JSON.parse(promptInput);
158264
- } catch {
158265
- return promptInput;
158266
- }
158267
- if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
158268
- return promptInput;
158269
- }
158270
- const jsonPayload = JsonPayload.assert(parsed2);
158271
- validateCompatibility(jsonPayload.version, package_default.version);
158272
- return jsonPayload;
158273
- }
158274
- function resolvePromptFile(input) {
158275
- const workspace = process.env.GITHUB_WORKSPACE;
158276
- const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
158277
- const content = readFileSync3(path4, "utf-8");
158278
- if (!content.trim()) {
158279
- throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
158280
- }
158281
- return content;
158282
- }
158283
- function resolveNonPromptInputs() {
158284
- return Inputs.omit("prompt", "prompt_file").assert({
158285
- model: core4.getInput("model") || void 0,
158286
- effort: core4.getInput("effort") || void 0,
158287
- timeout: core4.getInput("timeout") || void 0,
158288
- cwd: core4.getInput("cwd") || void 0,
158289
- push: core4.getInput("push") || void 0,
158290
- shell: core4.getInput("shell") || void 0,
158291
- status_checks: core4.getInput("status_checks") || void 0,
158292
- progress_comments: core4.getInput("progress_comments") || void 0
158293
- });
158294
- }
158295
- var isPullfrog = (actor) => {
158296
- actor = actor?.replace("[bot]", "");
158297
- return !!actor && (actor === "pullfrog" || actor === "pullfrogdev");
158298
- };
158299
- function resolvePayload(resolvedPromptInput, repoSettings) {
158300
- const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
158301
- const inputs = resolveNonPromptInputs();
158302
- const rawEvent = jsonPayload?.event;
158303
- const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
158304
- const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
158305
- const rawEffort = jsonPayload?.effort ?? inputs.effort ?? repoSettings.effort ?? void 0;
158306
- const effort = rawEffort === void 0 ? void 0 : parseEffortPosition(String(rawEffort));
158307
- const isNonCollaborator = !isCollaborator(event);
158308
- const repoShell = repoSettings.shell ?? "restricted";
158309
- const inputShell = inputs.shell;
158310
- let resolvedShell = repoShell;
158311
- if (inputShell === "disabled") {
158312
- resolvedShell = "disabled";
158313
- } else if (inputShell === "restricted" && resolvedShell === "enabled") {
158314
- resolvedShell = "restricted";
158315
- }
158316
- if (isNonCollaborator && resolvedShell === "enabled") {
158317
- resolvedShell = "restricted";
158318
- }
158319
- return {
158320
- "~pullfrog": true,
158321
- version: jsonPayload?.version ?? package_default.version,
158322
- model,
158323
- // explicit only when the model came from a per-run override flag (carried on
158324
- // the JSON payload). a GHA `model` input or the repo default is not explicit.
158325
- modelExplicit: jsonPayload?.modelExplicit ?? false,
158326
- effort,
158327
- prompt,
158328
- 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
158329
- (!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
158330
- baseInstructions: jsonPayload?.baseInstructions,
158331
- eventInstructions: jsonPayload?.eventInstructions,
158332
- previousRunsNote: jsonPayload?.previousRunsNote,
158333
- event,
158334
- xrepo: jsonPayload?.xrepo,
158335
- timeout: inputs.timeout ?? jsonPayload?.timeout,
158336
- cwd: resolveCwd(inputs.cwd),
158337
- progressComment: jsonPayload?.progressComment,
158338
- checkRun: jsonPayload?.checkRun,
158339
- generateSummary: jsonPayload?.generateSummary,
158340
- // permissions: inputs > repoSettings > fallbacks
158341
- push: inputs.push ?? repoSettings.push ?? "restricted",
158342
- shell: resolvedShell,
158343
- // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
158344
- // shows whether Pullfrog is running without anyone having to opt in. the workflow
158345
- // input is the source of truth when set (mirrors `push`); otherwise the repo
158346
- // setting decides.
158347
- runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
158348
- // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
158349
- // be *required* by branch protection, so it must never turn itself on.
158350
- approvalCheck: inputs.status_checks === "enabled",
158351
- // temporary progress chrome. the workflow input is the source of truth when
158352
- // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
158353
- progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
158354
- // set by proxy logic in main.ts when routing through OpenRouter
158355
- proxyModel: void 0
158356
- };
158357
- }
158358
- function resolveOutputSchema() {
158359
- const raw2 = core4.getInput("output_schema");
158360
- if (!raw2) return void 0;
158361
- let parsed2;
158362
- try {
158363
- parsed2 = JSON.parse(raw2);
158364
- } catch {
158365
- throw new Error(`invalid output_schema: not valid JSON`);
158366
- }
158367
- if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
158368
- throw new Error(`invalid output_schema: must be a JSON object`);
158369
- }
158370
- log.info("\xBB structured output schema provided \u2014 output will be required");
158371
- return parsed2;
158372
- }
158373
-
158374
158193
  // mcp/comment.ts
158375
158194
  function isNotFoundError(error49) {
158376
158195
  return error49 instanceof Error && error49.message.includes("Not Found");
@@ -160681,7 +160500,7 @@ function isMetadataYarnClassic(metadataPath) {
160681
160500
  }
160682
160501
 
160683
160502
  // utils/packageManager.ts
160684
- var import_semver3 = __toESM(require_semver2(), 1);
160503
+ var import_semver2 = __toESM(require_semver2(), 1);
160685
160504
  import { existsSync as existsSync5 } from "node:fs";
160686
160505
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
160687
160506
  import { delimiter, join as join16 } from "node:path";
@@ -160703,7 +160522,7 @@ function parsePackageManagerField(value2) {
160703
160522
  return {
160704
160523
  name,
160705
160524
  version: version3,
160706
- concrete: import_semver3.default.valid(version3) !== null,
160525
+ concrete: import_semver2.default.valid(version3) !== null,
160707
160526
  source: "packageManager"
160708
160527
  };
160709
160528
  }
@@ -160717,7 +160536,7 @@ function parseDevEnginesField(field) {
160717
160536
  return {
160718
160537
  name: field.name,
160719
160538
  version: version3,
160720
- concrete: import_semver3.default.valid(version3) !== null,
160539
+ concrete: import_semver2.default.valid(version3) !== null,
160721
160540
  source: "devEngines"
160722
160541
  };
160723
160542
  }
@@ -160751,7 +160570,7 @@ async function resolvePackageManagerSpec(cwd) {
160751
160570
  }
160752
160571
  return devSpec;
160753
160572
  }
160754
- if (pmSpec.concrete && import_semver3.default.satisfies(pmSpec.version, devSpec.version)) {
160573
+ if (pmSpec.concrete && import_semver2.default.satisfies(pmSpec.version, devSpec.version)) {
160755
160574
  return pmSpec;
160756
160575
  }
160757
160576
  if (pmSpec.concrete) {
@@ -160963,10 +160782,10 @@ ${errorMessage}`]
160963
160782
  };
160964
160783
 
160965
160784
  // prep/installPythonDependencies.ts
160966
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
160785
+ import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
160967
160786
  import { join as join18 } from "node:path";
160968
160787
  function declaresBuildSystem(path4) {
160969
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
160788
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync3(path4, "utf8"));
160970
160789
  }
160971
160790
  function configApplies(config3, cwd) {
160972
160791
  const path4 = join18(cwd, config3.file);
@@ -163101,6 +162920,36 @@ function KillBackgroundTool(ctx) {
163101
162920
  });
163102
162921
  }
163103
162922
 
162923
+ // mcp/similarIssues.ts
162924
+ var SimilarIssues = type({
162925
+ issue_number: type.number.describe("The issue number to find older duplicate candidates for")
162926
+ });
162927
+ function SimilarIssuesTool(ctx) {
162928
+ return tool({
162929
+ name: "find_similar_issues",
162930
+ 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.",
162931
+ mutates: true,
162932
+ parameters: SimilarIssues,
162933
+ execute: execute(async (input) => {
162934
+ if (ctx.payload.event.is_pr || ctx.payload.event.issue_number !== input.issue_number) {
162935
+ throw new Error("find_similar_issues is limited to the issue that triggered this run");
162936
+ }
162937
+ const response = await apiFetch({
162938
+ path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/issues/${input.issue_number}/similar`,
162939
+ headers: {
162940
+ authorization: `Bearer ${ctx.apiToken}`
162941
+ },
162942
+ signal: AbortSignal.timeout(13 * 6e4)
162943
+ });
162944
+ if (!response.ok) {
162945
+ const message = await response.text();
162946
+ throw new Error(`similar issue lookup returned ${response.status}: ${message}`);
162947
+ }
162948
+ return { result: await response.json() };
162949
+ })
162950
+ });
162951
+ }
162952
+
163104
162953
  // mcp/upload.ts
163105
162954
  import * as fs4 from "node:fs";
163106
162955
  import * as path3 from "node:path";
@@ -163339,6 +163188,9 @@ function buildCommonTools(ctx, outputSchema) {
163339
163188
  if (ctx.xrepo) {
163340
163189
  tools.push(ListReposTool(ctx), CheckoutRepoTool(ctx));
163341
163190
  }
163191
+ if (hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event })) {
163192
+ tools.push(SimilarIssuesTool(ctx));
163193
+ }
163342
163194
  const isStandalone = ctx.payload.event.trigger === "unknown";
163343
163195
  if (isStandalone || outputSchema) {
163344
163196
  tools.push(SetOutputTool(ctx, outputSchema));
@@ -164282,6 +164134,11 @@ Rules:
164282
164134
  ### GitHub
164283
164135
 
164284
164136
  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.
164137
+ ${hasSimilarIssues({ repoIntelligence: ctx.repoIntelligence, event: ctx.payload.event }) ? `
164138
+ #### Duplicate detection (enabled for this repository)
164139
+
164140
+ Call \`${t("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.
164141
+ ` : ""}
164285
164142
 
164286
164143
  ${getShellInstructions(ctx.payload.shell, t)}
164287
164144
 
@@ -164608,7 +164465,7 @@ function buildModelAccessError(input) {
164608
164465
  }
164609
164466
 
164610
164467
  // utils/normalizeEnv.ts
164611
- var core5 = __toESM(require_core(), 1);
164468
+ var core4 = __toESM(require_core(), 1);
164612
164469
  function sanitizeSecret(key, value2) {
164613
164470
  const trimmed = value2.trim();
164614
164471
  if (trimmed.length === 0) {
@@ -164622,7 +164479,7 @@ function sanitizeSecret(key, value2) {
164622
164479
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
164623
164480
  );
164624
164481
  }
164625
- core5.setSecret(trimmed);
164482
+ core4.setSecret(trimmed);
164626
164483
  return trimmed;
164627
164484
  }
164628
164485
  function normalizeEnv() {
@@ -164666,7 +164523,7 @@ function normalizeEnv() {
164666
164523
  }
164667
164524
 
164668
164525
  // utils/overrides.ts
164669
- var core6 = __toESM(require_core(), 1);
164526
+ var core5 = __toESM(require_core(), 1);
164670
164527
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
164671
164528
  "GITHUB_TOKEN",
164672
164529
  "GH_TOKEN",
@@ -164712,7 +164569,7 @@ function applyOverrides(params) {
164712
164569
  denied.push(key);
164713
164570
  continue;
164714
164571
  }
164715
- if (value2.length > 0) core6.setSecret(value2);
164572
+ if (value2.length > 0) core5.setSecret(value2);
164716
164573
  params.env[key] = value2;
164717
164574
  applied.push(key);
164718
164575
  }
@@ -164720,6 +164577,212 @@ function applyOverrides(params) {
164720
164577
  return { applied, denied };
164721
164578
  }
164722
164579
 
164580
+ // utils/payload.ts
164581
+ var core6 = __toESM(require_core(), 1);
164582
+ import { readFileSync as readFileSync5 } from "node:fs";
164583
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
164584
+
164585
+ // utils/versioning.ts
164586
+ var import_semver3 = __toESM(require_semver2(), 1);
164587
+ var COMPATIBILITY_POLICY = "non-breaking";
164588
+ function validateCompatibility(payloadVersion, actionVersion) {
164589
+ const payloadSemVer = import_semver3.default.parse(payloadVersion);
164590
+ if (!payloadSemVer)
164591
+ throw new Error(`Payload version ${payloadVersion} is not a valid semantic version.`);
164592
+ const major = payloadSemVer.major;
164593
+ const minor = payloadSemVer.minor;
164594
+ const patch = payloadSemVer.patch;
164595
+ const compatibilityRange = COMPATIBILITY_POLICY === "same-features" ? `^${major}.${minor}.${major === 0 ? patch : 0}` : `^${major}.${major === 0 ? minor : 0}.${major === 0 ? "x" : 0}`;
164596
+ if (!import_semver3.default.satisfies(actionVersion, compatibilityRange)) {
164597
+ throw new Error(
164598
+ `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.`
164599
+ );
164600
+ }
164601
+ }
164602
+
164603
+ // utils/payload.ts
164604
+ var ShellPermissionInput = type.enumerated("disabled", "restricted", "enabled");
164605
+ var PushPermissionInput = type.enumerated("disabled", "restricted", "enabled");
164606
+ var StatusChecksInput = type.enumerated("disabled", "enabled");
164607
+ var ProgressCommentsInput = type.enumerated("disabled", "enabled");
164608
+ var JsonPayload = type({
164609
+ "~pullfrog": "true",
164610
+ version: "string",
164611
+ "model?": "string | undefined",
164612
+ "modelExplicit?": "boolean | undefined",
164613
+ "effort?": "number | string | undefined",
164614
+ prompt: "string",
164615
+ "triggerer?": "string | undefined",
164616
+ "baseInstructions?": "string | undefined",
164617
+ "eventInstructions?": "string",
164618
+ "previousRunsNote?": "string",
164619
+ "event?": "object",
164620
+ "xrepo?": type({
164621
+ mode: "'all' | 'explicit'",
164622
+ read: "string[]",
164623
+ write: "string[]",
164624
+ // optional so a payload from an older server build (pre-`unavailable`)
164625
+ // still parses against a newer action across a rolling deploy.
164626
+ "unavailable?": "string[]"
164627
+ }).or("undefined"),
164628
+ "timeout?": "string | undefined",
164629
+ "progressComment?": type({
164630
+ id: "string",
164631
+ type: "'issue' | 'review'"
164632
+ }).or("undefined"),
164633
+ // optional so a payload from an older server build (pre-`checkRun`) still parses
164634
+ // against a newer action across a rolling deploy.
164635
+ "checkRun?": type({ id: "string" }).or("undefined"),
164636
+ "generateSummary?": "boolean | undefined"
164637
+ });
164638
+ var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
164639
+ function isCollaborator(event) {
164640
+ const perm = event.authorPermission;
164641
+ return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
164642
+ }
164643
+ var Inputs = type({
164644
+ "prompt?": type.string.or("undefined"),
164645
+ "prompt_file?": type.string.or("undefined"),
164646
+ "model?": type.string.or("undefined"),
164647
+ "effort?": type.string.or("undefined"),
164648
+ "timeout?": type.string.or("undefined"),
164649
+ "push?": PushPermissionInput.or("undefined"),
164650
+ "shell?": ShellPermissionInput.or("undefined"),
164651
+ "status_checks?": StatusChecksInput.or("undefined"),
164652
+ "progress_comments?": ProgressCommentsInput.or("undefined"),
164653
+ "cwd?": type.string.or("undefined"),
164654
+ "output_schema?": type.string.or("undefined")
164655
+ });
164656
+ function isPayloadEvent(value2) {
164657
+ return typeof value2 === "object" && value2 !== null && "trigger" in value2;
164658
+ }
164659
+ function resolveCwd(cwd) {
164660
+ const workspace = process.env.GITHUB_WORKSPACE;
164661
+ if (!cwd) return workspace;
164662
+ if (isAbsolute2(cwd)) return cwd;
164663
+ return workspace ? resolve2(workspace, cwd) : cwd;
164664
+ }
164665
+ function resolvePromptInput() {
164666
+ const promptInput = core6.getInput("prompt");
164667
+ const promptFile = core6.getInput("prompt_file");
164668
+ if (promptInput && promptFile) {
164669
+ throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
164670
+ }
164671
+ if (promptFile) {
164672
+ return resolvePromptFile(promptFile);
164673
+ }
164674
+ if (!promptInput) {
164675
+ throw new Error("one of 'prompt' or 'prompt_file' inputs is required.");
164676
+ }
164677
+ let parsed2;
164678
+ try {
164679
+ parsed2 = JSON.parse(promptInput);
164680
+ } catch {
164681
+ return promptInput;
164682
+ }
164683
+ if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
164684
+ return promptInput;
164685
+ }
164686
+ const jsonPayload = JsonPayload.assert(parsed2);
164687
+ validateCompatibility(jsonPayload.version, package_default.version);
164688
+ return jsonPayload;
164689
+ }
164690
+ function resolvePromptFile(input) {
164691
+ const workspace = process.env.GITHUB_WORKSPACE;
164692
+ const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
164693
+ const content = readFileSync5(path4, "utf-8");
164694
+ if (!content.trim()) {
164695
+ throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
164696
+ }
164697
+ return content;
164698
+ }
164699
+ function resolveNonPromptInputs() {
164700
+ return Inputs.omit("prompt", "prompt_file").assert({
164701
+ model: core6.getInput("model") || void 0,
164702
+ effort: core6.getInput("effort") || void 0,
164703
+ timeout: core6.getInput("timeout") || void 0,
164704
+ cwd: core6.getInput("cwd") || void 0,
164705
+ push: core6.getInput("push") || void 0,
164706
+ shell: core6.getInput("shell") || void 0,
164707
+ status_checks: core6.getInput("status_checks") || void 0,
164708
+ progress_comments: core6.getInput("progress_comments") || void 0
164709
+ });
164710
+ }
164711
+ function resolvePayload(resolvedPromptInput, repoSettings) {
164712
+ const [prompt, jsonPayload] = typeof resolvedPromptInput !== "string" ? [resolvedPromptInput.prompt, resolvedPromptInput] : [resolvedPromptInput, void 0];
164713
+ const inputs = resolveNonPromptInputs();
164714
+ const rawEvent = jsonPayload?.event;
164715
+ const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
164716
+ const model = jsonPayload?.model ?? inputs.model ?? repoSettings.model ?? void 0;
164717
+ const rawEffort = jsonPayload?.effort ?? inputs.effort ?? repoSettings.effort ?? void 0;
164718
+ const effort = rawEffort === void 0 ? void 0 : parseEffortPosition(String(rawEffort));
164719
+ const isNonCollaborator = !isCollaborator(event);
164720
+ const repoShell = repoSettings.shell ?? "restricted";
164721
+ const inputShell = inputs.shell;
164722
+ let resolvedShell = repoShell;
164723
+ if (inputShell === "disabled") {
164724
+ resolvedShell = "disabled";
164725
+ } else if (inputShell === "restricted" && resolvedShell === "enabled") {
164726
+ resolvedShell = "restricted";
164727
+ }
164728
+ if (isNonCollaborator && resolvedShell === "enabled") {
164729
+ resolvedShell = "restricted";
164730
+ }
164731
+ return {
164732
+ "~pullfrog": true,
164733
+ version: jsonPayload?.version ?? package_default.version,
164734
+ model,
164735
+ // explicit only when the model came from a per-run override flag (carried on
164736
+ // the JSON payload). a GHA `model` input or the repo default is not explicit.
164737
+ modelExplicit: jsonPayload?.modelExplicit ?? false,
164738
+ effort,
164739
+ prompt,
164740
+ 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
164741
+ (!isPullfrog(process.env.GITHUB_ACTOR) ? process.env.GITHUB_ACTOR : void 0),
164742
+ baseInstructions: jsonPayload?.baseInstructions,
164743
+ eventInstructions: jsonPayload?.eventInstructions,
164744
+ previousRunsNote: jsonPayload?.previousRunsNote,
164745
+ event,
164746
+ xrepo: jsonPayload?.xrepo,
164747
+ timeout: inputs.timeout ?? jsonPayload?.timeout,
164748
+ cwd: resolveCwd(inputs.cwd),
164749
+ progressComment: jsonPayload?.progressComment,
164750
+ checkRun: jsonPayload?.checkRun,
164751
+ generateSummary: jsonPayload?.generateSummary,
164752
+ // permissions: inputs > repoSettings > fallbacks
164753
+ push: inputs.push ?? repoSettings.push ?? "restricted",
164754
+ shell: resolvedShell,
164755
+ // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
164756
+ // shows whether Pullfrog is running without anyone having to opt in. the workflow
164757
+ // input is the source of truth when set (mirrors `push`); otherwise the repo
164758
+ // setting decides.
164759
+ runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
164760
+ // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
164761
+ // be *required* by branch protection, so it must never turn itself on.
164762
+ approvalCheck: inputs.status_checks === "enabled",
164763
+ // temporary progress chrome. the workflow input is the source of truth when
164764
+ // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
164765
+ progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
164766
+ // set by proxy logic in main.ts when routing through OpenRouter
164767
+ proxyModel: void 0
164768
+ };
164769
+ }
164770
+ function resolveOutputSchema() {
164771
+ const raw2 = core6.getInput("output_schema");
164772
+ if (!raw2) return void 0;
164773
+ let parsed2;
164774
+ try {
164775
+ parsed2 = JSON.parse(raw2);
164776
+ } catch {
164777
+ throw new Error(`invalid output_schema: not valid JSON`);
164778
+ }
164779
+ if (!parsed2 || typeof parsed2 !== "object" || Array.isArray(parsed2)) {
164780
+ throw new Error(`invalid output_schema: must be a JSON object`);
164781
+ }
164782
+ log.info("\xBB structured output schema provided \u2014 output will be required");
164783
+ return parsed2;
164784
+ }
164785
+
164723
164786
  // utils/proxy.ts
164724
164787
  var core8 = __toESM(require_core(), 1);
164725
164788
 
@@ -165267,6 +165330,7 @@ var defaultSettings = {
165267
165330
  prApproveEnabled: false,
165268
165331
  autoMergeEnabled: false,
165269
165332
  signedCommits: false,
165333
+ repoIntelligence: false,
165270
165334
  progressComments: true,
165271
165335
  statusChecks: true,
165272
165336
  modeInstructions: {},
@@ -165406,7 +165470,8 @@ function formatAgentHangBody(input) {
165406
165470
  const headline = `**${input.diagnostic.label} ${verb}**${cause}`;
165407
165471
  const explanation = formatExplanation({
165408
165472
  isHang: input.isHang,
165409
- errorMessage: input.errorMessage
165473
+ errorMessage: input.errorMessage,
165474
+ idleSec: input.diagnostic.idleSec
165410
165475
  });
165411
165476
  const parts = [headline, "", `${explanation} ${formatEventsPart(input.diagnostic)}`];
165412
165477
  const tail = renderStderrTail(input.diagnostic.recentStderr);
@@ -165427,7 +165492,7 @@ function formatAgentHangBody(input) {
165427
165492
  }
165428
165493
  function formatExplanation(input) {
165429
165494
  if (!input.isHang) return `The agent exited unexpectedly: ${input.errorMessage}`;
165430
- const idleSec = parseIdleSec(input.errorMessage);
165495
+ const idleSec = input.idleSec ?? parseIdleSec(input.errorMessage);
165431
165496
  if (idleSec === void 0) {
165432
165497
  return "The agent stopped emitting events and was killed by the activity-timeout watchdog.";
165433
165498
  }
@@ -165438,11 +165503,13 @@ function parseIdleSec(message) {
165438
165503
  return match3 ? Number(match3[1]) : void 0;
165439
165504
  }
165440
165505
  function formatEventsPart(diagnostic) {
165441
- if (diagnostic.eventCount > 0) {
165506
+ if (diagnostic.lastProviderError) {
165442
165507
  return `${diagnostic.eventCount} events were processed before the failure.`;
165443
165508
  }
165444
- if (diagnostic.lastProviderError) return "No events were emitted before the failure.";
165445
- return "No events were emitted \u2014 check whether the model provider is reachable.";
165509
+ if (!diagnostic.sawModelOutput) {
165510
+ 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.";
165511
+ }
165512
+ return `${diagnostic.eventCount} events were processed before the failure.`;
165446
165513
  }
165447
165514
  function renderStderrTail(lines) {
165448
165515
  if (lines.length === 0) return "";
@@ -165798,8 +165865,8 @@ function getCurrentWorkflowFilename() {
165798
165865
  }
165799
165866
 
165800
165867
  // utils/runStatusCheck.ts
165801
- var RUN_STATUS_CHECK_NAME = "Pullfrog";
165802
- var APPROVAL_CHECK_NAME = "Pullfrog approval";
165868
+ var RUN_STATUS_CHECK_NAME = "pullfrog";
165869
+ var APPROVAL_CHECK_NAME = "pullfrog-approval";
165803
165870
  function parseCheckRunId(raw2) {
165804
165871
  if (!raw2?.id) return void 0;
165805
165872
  const id = parseInt(raw2.id, 10);
@@ -166324,7 +166391,7 @@ async function main() {
166324
166391
  _promise2 && await _promise2;
166325
166392
  }
166326
166393
  }
166327
- createTempDirectory();
166394
+ const tmpdir3 = createTempDirectory();
166328
166395
  const opencodeCliPath = await agents.opencode.install();
166329
166396
  captureBaselineModels(opencodeCliPath);
166330
166397
  if (runContext.dbSecrets) {
@@ -166378,7 +166445,6 @@ async function main() {
166378
166445
  if (payload.cwd && process.cwd() !== payload.cwd) {
166379
166446
  process.chdir(payload.cwd);
166380
166447
  }
166381
- const tmpdir3 = createTempDirectory();
166382
166448
  const originalBody = payload.event.body;
166383
166449
  const resolvedBody = await resolveBody({
166384
166450
  event: payload.event,
@@ -166487,6 +166553,7 @@ async function main() {
166487
166553
  prApproveEnabled: runContext.repoSettings.prApproveEnabled,
166488
166554
  autoMergeEnabled: runContext.repoSettings.autoMergeEnabled,
166489
166555
  signedCommits: runContext.repoSettings.signedCommits,
166556
+ repoIntelligence: runContext.repoSettings.repoIntelligence,
166490
166557
  modeInstructions: runContext.repoSettings.modeInstructions,
166491
166558
  toolState,
166492
166559
  runId: runInfo.runId,
@@ -166561,6 +166628,7 @@ async function main() {
166561
166628
  agentId,
166562
166629
  outputSchema,
166563
166630
  signedCommits: runContext.repoSettings.signedCommits,
166631
+ repoIntelligence: runContext.repoSettings.repoIntelligence,
166564
166632
  learningsFilePath: toolState.learningsFilePath ?? null,
166565
166633
  learningsHeadings: runContext.repoSettings.learningsHeadings,
166566
166634
  setupHookFailure: describeSetupFailure(setupHook.failure),