diff --git a/main.ts b/main.ts index ede1253..479ab0e 100644 --- a/main.ts +++ b/main.ts @@ -2,6 +2,7 @@ import * as core from "@actions/core"; import { deleteProgressComment, reportProgress } from "./mcp/comment.ts"; +import { startInstallation } from "./mcp/dependencies.ts"; import { initToolState, startMcpHttpServer, @@ -307,6 +308,8 @@ export async function main(): Promise { log.info(`» MCP server started at ${mcpHttpServer.url}`); timer.checkpoint("mcpServer"); + startInstallation(toolContext); + if (payload.model) log.info(`» model: ${payload.model}`); if (payload.timeout) log.info(`» timeout: ${payload.timeout}`); log.info(`» push: ${payload.push}`); diff --git a/mcp/dependencies.ts b/mcp/dependencies.ts index 9b5c37a..93c351f 100644 --- a/mcp/dependencies.ts +++ b/mcp/dependencies.ts @@ -67,9 +67,10 @@ Inspect the repository structure to determine how dependencies should be install } /** - * start dependency installation in the background (non-blocking, idempotent) + * start dependency installation in the background (non-blocking, idempotent). + * called eagerly from main.ts at startup and also available via MCP tools. */ -function startInstallation(ctx: ToolContext): void { +export function startInstallation(ctx: ToolContext): void { // already started or completed - do nothing if (ctx.toolState.dependencyInstallation) { return; diff --git a/mcp/upload.ts b/mcp/upload.ts index 4b1c94c..36bac81 100644 --- a/mcp/upload.ts +++ b/mcp/upload.ts @@ -14,7 +14,7 @@ export function UploadFileTool(ctx: ToolContext) { return tool({ name: "upload_file", description: - "upload a file to get a permanent public URL. use for screenshots, artifacts, or any files you want to reference in PRs/comments. max 10MB, images/text/archives allowed.", + "upload a file to get a permanent public URL. use for screenshots, artifacts, or any files you want to reference in PRs/comments. max 10MB, images/text/archives allowed. when embedding uploaded images in comments or PR bodies, always use markdown image syntax: ![description](url)", parameters: UploadFileParams, execute: execute(async (params) => { // read file from disk eagerly on purpose to avoid its content being changed by the time it's uploaded diff --git a/utils/buildPullfrogFooter.ts b/utils/buildPullfrogFooter.ts index 37de638..9c25b57 100644 --- a/utils/buildPullfrogFooter.ts +++ b/utils/buildPullfrogFooter.ts @@ -13,7 +13,7 @@ export interface WorkflowRunFooterInfo { } export interface BuildPullfrogFooterParams { - /** add "Triggered by Pullfrog" link */ + /** add "via Pullfrog" link */ triggeredBy?: boolean; /** add "View workflow run" link */ workflowRun?: WorkflowRunFooterInfo | undefined; @@ -52,7 +52,7 @@ export function buildPullfrogFooter(params: BuildPullfrogFooterParams): string { } if (params.triggeredBy) { - parts.push("Triggered by [Pullfrog](https://pullfrog.com)"); + parts.push("via [Pullfrog](https://pullfrog.com)"); } if (params.model) { diff --git a/utils/instructions.ts b/utils/instructions.ts index 507617a..05c59b2 100644 --- a/utils/instructions.ts +++ b/utils/instructions.ts @@ -153,9 +153,10 @@ ${ctx.userQuoted}`; const eventInstructions = ctx.payload.eventInstructions ?? ""; if (eventInstructions) { + const parts = [ctx.eventTitle, eventInstructions].filter(Boolean); return `************* YOUR TASK ************* -${eventInstructions}`; +${parts.join("\n\n")}`; } return ""; diff --git a/utils/skills.ts b/utils/skills.ts index a54580a..e1a2a70 100644 --- a/utils/skills.ts +++ b/utils/skills.ts @@ -1,9 +1,19 @@ import { spawnSync } from "node:child_process"; -import { resolve } from "node:path"; +import { tmpdir } from "node:os"; import { log } from "./cli.ts"; +import { getDevDependencyVersion } from "./version.ts"; -const skillsBin = resolve(import.meta.dirname, "../node_modules/.bin/skills"); +const skillsVersion = getDevDependencyVersion("skills"); +/** + * install a skill globally via the `skills` CLI. + * + * runs `npx skills add --skill -g` with `cwd` set to os tmpdir + * so npm doesn't walk up and find a project-level `.npmrc` with pnpm-specific + * settings (e.g. `public-hoist-pattern`) that break npx binary resolution. + * the `-g` flag writes to `$HOME/.agents/skills/` which is controlled by + * `params.env.HOME` (the fake HOME), so cwd has no effect on install location. + */ export function addSkill(params: { ref: string; skill: string; @@ -11,9 +21,21 @@ export function addSkill(params: { agent: string; }): void { const result = spawnSync( - skillsBin, - ["add", params.ref, "--skill", params.skill, "-g", "-a", params.agent, "-y"], + "npx", + [ + "-y", + `skills@${skillsVersion}`, + "add", + params.ref, + "--skill", + params.skill, + "-g", + "-a", + params.agent, + "-y", + ], { + cwd: tmpdir(), env: { ...process.env, ...params.env }, stdio: "pipe", timeout: 30_000, @@ -22,6 +44,8 @@ export function addSkill(params: { if (result.status === 0) { log.info(`installed ${params.skill} skill (${params.agent})`); } else { - log.info(`${params.skill} skill install failed: ${(result.stderr?.toString() || "").trim()}`); + const stderr = (result.stderr?.toString() || "").trim(); + const errorMsg = result.error ? result.error.message : stderr; + log.info(`${params.skill} skill install failed: ${errorMsg}`); } }