Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb22433760 | |||
| b6658ddbc1 |
+1
-1
@@ -86,7 +86,7 @@ export const claude = agent({
|
|||||||
// select model and effort level
|
// select model and effort level
|
||||||
const model = claudeEffortModels[ctx.payload.effort];
|
const model = claudeEffortModels[ctx.payload.effort];
|
||||||
const effortLevel = claudeEffortLevels[ctx.payload.effort];
|
const effortLevel = claudeEffortLevels[ctx.payload.effort];
|
||||||
log.info(`» using model: ${model}${effortLevel ? ` (effort: ${effortLevel})` : ""}`);
|
log.info(`» model: ${model}${effortLevel ? ` (effort: ${effortLevel})` : ""}`);
|
||||||
|
|
||||||
// build disallowedTools based on tool permissions
|
// build disallowedTools based on tool permissions
|
||||||
const disallowedTools = buildDisallowedTools(ctx);
|
const disallowedTools = buildDisallowedTools(ctx);
|
||||||
|
|||||||
+3
-4
@@ -155,10 +155,9 @@ export const codex = agent({
|
|||||||
|
|
||||||
// get model and reasoning effort based on effort level
|
// get model and reasoning effort based on effort level
|
||||||
const effortConfig = getCodexEffortConfig(model)[ctx.payload.effort];
|
const effortConfig = getCodexEffortConfig(model)[ctx.payload.effort];
|
||||||
log.info(`» using model: ${effortConfig.model} (effort: ${ctx.payload.effort})`);
|
log.info(
|
||||||
if (effortConfig.reasoningEffort) {
|
`» model: ${effortConfig.model}${effortConfig.reasoningEffort ? ` (reasoningEffort: ${effortConfig.reasoningEffort})` : ""}`
|
||||||
log.info(`» using modelReasoningEffort: ${effortConfig.reasoningEffort}`);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// determine sandbox mode based on push permission
|
// determine sandbox mode based on push permission
|
||||||
// push: "disabled" → read-only sandbox, otherwise workspace-write.
|
// push: "disabled" → read-only sandbox, otherwise workspace-write.
|
||||||
|
|||||||
+5
-4
@@ -134,7 +134,7 @@ export const cursor = agent({
|
|||||||
try {
|
try {
|
||||||
const projectConfig = JSON.parse(readFileSync(projectCliConfigPath, "utf-8"));
|
const projectConfig = JSON.parse(readFileSync(projectCliConfigPath, "utf-8"));
|
||||||
if (projectConfig.model) {
|
if (projectConfig.model) {
|
||||||
log.info(`» using model from project .cursor/cli.json: ${projectConfig.model}`);
|
log.info(`» model: ${projectConfig.model} (from .cursor/cli.json)`);
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[ctx.payload.effort];
|
modelOverride = cursorEffortModels[ctx.payload.effort];
|
||||||
}
|
}
|
||||||
@@ -146,9 +146,9 @@ export const cursor = agent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
log.info(`» using model: ${modelOverride}, effort=${ctx.payload.effort}`);
|
log.info(`» model: ${modelOverride}`);
|
||||||
} else if (!existsSync(projectCliConfigPath)) {
|
} else if (!existsSync(projectCliConfigPath)) {
|
||||||
log.info(`» using default model, effort=${ctx.payload.effort}`);
|
log.info(`» model: default`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// track logged model_call_ids to avoid duplicates
|
// track logged model_call_ids to avoid duplicates
|
||||||
@@ -439,5 +439,6 @@ function configureCursorTools(ctx: AgentRunContext): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
writeFileSync(cliConfigPath, JSON.stringify(config, null, 2), "utf-8");
|
||||||
log.info(`» CLI config written to ${cliConfigPath}`, JSON.stringify(config, null, 2));
|
log.info(`» CLI config written to ${cliConfigPath}`);
|
||||||
|
log.debug(`» CLI config contents: ${JSON.stringify(config, null, 2)}`);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -345,7 +345,7 @@ function configureGeminiSettings(ctx: AgentRunContext): string {
|
|||||||
// allow env var override for tests (e.g., to avoid flash RPD quota limits)
|
// allow env var override for tests (e.g., to avoid flash RPD quota limits)
|
||||||
const model = process.env.GEMINI_MODEL ?? effortConfig.model;
|
const model = process.env.GEMINI_MODEL ?? effortConfig.model;
|
||||||
const thinkingLevel = effortConfig.thinkingLevel;
|
const thinkingLevel = effortConfig.thinkingLevel;
|
||||||
log.info(`» using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
log.info(`» model: ${model} (thinkingLevel: ${thinkingLevel})`);
|
||||||
|
|
||||||
const realHome = homedir();
|
const realHome = homedir();
|
||||||
const geminiConfigDir = join(realHome, ".gemini");
|
const geminiConfigDir = join(realHome, ".gemini");
|
||||||
|
|||||||
+25
-25
@@ -72,7 +72,9 @@ export const opencode = agent({
|
|||||||
const modelOverride = process.env.OPENCODE_MODEL;
|
const modelOverride = process.env.OPENCODE_MODEL;
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
args.push("--model", modelOverride);
|
args.push("--model", modelOverride);
|
||||||
log.info(`» using model override: ${modelOverride}`);
|
log.info(`» model: ${modelOverride} (override)`);
|
||||||
|
} else {
|
||||||
|
log.info(`» model: auto-selected by OpenCode`);
|
||||||
}
|
}
|
||||||
|
|
||||||
process.env.HOME = tempHome;
|
process.env.HOME = tempHome;
|
||||||
@@ -186,13 +188,10 @@ export const opencode = agent({
|
|||||||
lastProviderError = providerError;
|
lastProviderError = providerError;
|
||||||
log.error(`» provider error detected (${providerError}): ${trimmed.substring(0, 500)}`);
|
log.error(`» provider error detected (${providerError}): ${trimmed.substring(0, 500)}`);
|
||||||
} else {
|
} else {
|
||||||
// try to parse as JSON for structured logging, fall back to warning
|
// OpenCode's --print-logs output goes to stderr. demote internal
|
||||||
try {
|
// INFO/DEBUG bus traffic to debug so it doesn't drown out tool
|
||||||
const parsed = JSON.parse(trimmed);
|
// call logs in the GitHub Actions step output.
|
||||||
log.debug(JSON.stringify(parsed, null, 2));
|
log.debug(trimmed);
|
||||||
} catch {
|
|
||||||
log.warning(trimmed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -325,7 +324,7 @@ function configureOpenCode(ctx: AgentRunContext): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info(`» OpenCode config written to ${configPath}`);
|
log.info(`» OpenCode config written to ${configPath}`);
|
||||||
log.info(
|
log.debug(
|
||||||
`» OpenCode permissions: edit=${permission.edit}, bash=${permission.bash}, webfetch=${permission.webfetch}`
|
`» OpenCode permissions: edit=${permission.edit}, bash=${permission.bash}, webfetch=${permission.webfetch}`
|
||||||
);
|
);
|
||||||
log.debug(`OpenCode config contents:\n${configJson}`);
|
log.debug(`OpenCode config contents:\n${configJson}`);
|
||||||
@@ -558,27 +557,28 @@ const messageHandlers = {
|
|||||||
const status = event.part?.state?.status;
|
const status = event.part?.state?.status;
|
||||||
const output = event.part?.state?.output;
|
const output = event.part?.state?.output;
|
||||||
|
|
||||||
// debug log all tool_use events to diagnose missing bash/MCP tool calls
|
|
||||||
if (!toolName || !toolId) {
|
if (!toolName || !toolId) {
|
||||||
log.debug(`» tool_use event missing toolName or toolId: ${JSON.stringify(event)}`);
|
// surface dropped tool_use events visibly so missing tool calls are diagnosable
|
||||||
|
log.info(
|
||||||
|
`» tool_use event missing toolName or toolId: ${JSON.stringify(event).substring(0, 500)}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolName && toolId) {
|
// track tool call in current step
|
||||||
// track tool call in current step
|
if (stepHistory.length > 0) {
|
||||||
if (stepHistory.length > 0) {
|
stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
|
||||||
stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
thinkingTimer.markToolCall();
|
thinkingTimer.markToolCall();
|
||||||
log.toolCall({
|
log.toolCall({
|
||||||
toolName,
|
toolName,
|
||||||
input: parameters || {},
|
input: parameters || {},
|
||||||
});
|
});
|
||||||
|
|
||||||
// if tool already completed (status in same event), log output
|
// if tool already completed (status in same event), log output
|
||||||
if (status === "completed" && output) {
|
if (status === "completed" && output) {
|
||||||
log.debug(` output: ${output}`);
|
log.debug(` output: ${output}`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tool_result: (event: OpenCodeToolResultEvent, thinkingTimer: ThinkingTimer) => {
|
tool_result: (event: OpenCodeToolResultEvent, thinkingTimer: ThinkingTimer) => {
|
||||||
|
|||||||
+10
-8
@@ -28,13 +28,15 @@ export const agent = <const input extends AgentInput>(input: input): defineAgent
|
|||||||
return {
|
return {
|
||||||
...input,
|
...input,
|
||||||
run: async (ctx: AgentRunContext): Promise<AgentResult> => {
|
run: async (ctx: AgentRunContext): Promise<AgentResult> => {
|
||||||
const bash = ctx.payload.bash;
|
log.info(`» agent: ${input.name}`);
|
||||||
const web = ctx.payload.web;
|
log.info(`» effort: ${ctx.payload.effort}`);
|
||||||
const search = ctx.payload.search;
|
if (ctx.payload.timeout) log.info(`» timeout: ${ctx.payload.timeout}`);
|
||||||
const push = ctx.payload.push;
|
log.info(`» web: ${ctx.payload.web}`);
|
||||||
log.info(
|
log.info(`» search: ${ctx.payload.search}`);
|
||||||
`» running ${input.name} with effort=${ctx.payload.effort}, timeout=${ctx.payload.timeout}...`
|
log.info(`» push: ${ctx.payload.push}`);
|
||||||
);
|
log.info(`» bash: ${ctx.payload.bash}`);
|
||||||
|
log.debug(`» payload: ${JSON.stringify(ctx.payload, null, 2)}`);
|
||||||
|
|
||||||
// build log box content: eventInstructions (if any) + user request (if any) + event data
|
// build log box content: eventInstructions (if any) + user request (if any) + event data
|
||||||
const logParts = [
|
const logParts = [
|
||||||
ctx.instructions.eventInstructions
|
ctx.instructions.eventInstructions
|
||||||
@@ -46,7 +48,7 @@ export const agent = <const input extends AgentInput>(input: input): defineAgent
|
|||||||
log.box(logParts.join("\n\n---\n\n"), {
|
log.box(logParts.join("\n\n---\n\n"), {
|
||||||
title: "Instructions",
|
title: "Instructions",
|
||||||
});
|
});
|
||||||
log.info(`» tool permissions: web=${web}, search=${search}, push=${push}, bash=${bash}`);
|
|
||||||
return input.run(ctx);
|
return input.run(ctx);
|
||||||
},
|
},
|
||||||
...agentsManifest[input.name],
|
...agentsManifest[input.name],
|
||||||
|
|||||||
@@ -139709,12 +139709,14 @@ async function resolveTokens(params) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const gitContents = params.push === "disabled" ? "read" : "write";
|
const gitPermissions = params.push === "disabled" ? { contents: "read" } : { contents: "write", workflows: "write" };
|
||||||
const gitToken = await acquireNewToken({ permissions: { contents: gitContents } });
|
const gitToken = await acquireNewToken({ permissions: gitPermissions });
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core3.setSecret(gitToken);
|
core3.setSecret(gitToken);
|
||||||
}
|
}
|
||||||
log.info(`\xBB acquired git token (contents:${gitContents})`);
|
log.info(
|
||||||
|
`\xBB acquired git token (${Object.entries(gitPermissions).map((e) => e.join(":")).join(", ")})`
|
||||||
|
);
|
||||||
const mcpToken = await acquireNewToken();
|
const mcpToken = await acquireNewToken();
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core3.setSecret(mcpToken);
|
core3.setSecret(mcpToken);
|
||||||
@@ -141892,7 +141894,13 @@ ${ctx.modes.map((m) => `- "${m.name}": ${m.description}`).join("\n")}`;
|
|||||||
}
|
}
|
||||||
function resolveSubagentInstructions(ctx) {
|
function resolveSubagentInstructions(ctx) {
|
||||||
const inputs = buildCommonInputs(ctx);
|
const inputs = buildCommonInputs(ctx);
|
||||||
const subagentTaskSection = `You are operating in **${ctx.mode.name}** mode.
|
const subagentTaskSection = `You are operating in **${ctx.mode.name}** mode as a delegated subagent. An orchestrator spawned you and will read your final output to decide what to do next.
|
||||||
|
|
||||||
|
### Delegation rules
|
||||||
|
|
||||||
|
- The \`delegate\` tool is NOT available to you \u2014 complete your task directly using the available tools.
|
||||||
|
- When you finish, end with a clear, concise summary: what you did, what succeeded, what failed, and any blockers or next steps. The orchestrator uses this to decide whether to delegate again or report final results.
|
||||||
|
- If you encounter an error you cannot resolve, report it clearly \u2014 do not attempt to delegate or re-run yourself.
|
||||||
|
|
||||||
${ctx.mode.prompt}`;
|
${ctx.mode.prompt}`;
|
||||||
const system = buildSystemPrompt({
|
const system = buildSystemPrompt({
|
||||||
@@ -141956,7 +141964,7 @@ function DelegateTool(ctx) {
|
|||||||
execute: execute(async (params) => {
|
execute: execute(async (params) => {
|
||||||
if (ctx.toolState.delegationActive) {
|
if (ctx.toolState.delegationActive) {
|
||||||
return {
|
return {
|
||||||
error: "delegation is not available inside a delegated subagent"
|
error: "delegation is not available inside a subagent. you are already running as a delegated subagent. complete the task directly using the available tools. do not attempt to delegate further. your last agent message will be passed to your supervisor and it will decide what to do next."
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const selectedMode = resolveMode(ctx.modes, params.mode);
|
const selectedMode = resolveMode(ctx.modes, params.mode);
|
||||||
@@ -144501,7 +144509,7 @@ import { join as join9 } from "node:path";
|
|||||||
// package.json
|
// package.json
|
||||||
var package_default = {
|
var package_default = {
|
||||||
name: "@pullfrog/pullfrog",
|
name: "@pullfrog/pullfrog",
|
||||||
version: "0.0.163",
|
version: "0.0.164",
|
||||||
type: "module",
|
type: "module",
|
||||||
files: [
|
files: [
|
||||||
"index.js",
|
"index.js",
|
||||||
@@ -144812,13 +144820,14 @@ var agent = (input) => {
|
|||||||
return {
|
return {
|
||||||
...input,
|
...input,
|
||||||
run: async (ctx) => {
|
run: async (ctx) => {
|
||||||
const bash = ctx.payload.bash;
|
log.info(`\xBB agent: ${input.name}`);
|
||||||
const web = ctx.payload.web;
|
log.info(`\xBB effort: ${ctx.payload.effort}`);
|
||||||
const search2 = ctx.payload.search;
|
if (ctx.payload.timeout) log.info(`\xBB timeout: ${ctx.payload.timeout}`);
|
||||||
const push = ctx.payload.push;
|
log.info(`\xBB web: ${ctx.payload.web}`);
|
||||||
log.info(
|
log.info(`\xBB search: ${ctx.payload.search}`);
|
||||||
`\xBB running ${input.name} with effort=${ctx.payload.effort}, timeout=${ctx.payload.timeout}...`
|
log.info(`\xBB push: ${ctx.payload.push}`);
|
||||||
);
|
log.info(`\xBB bash: ${ctx.payload.bash}`);
|
||||||
|
log.debug(`\xBB payload: ${JSON.stringify(ctx.payload, null, 2)}`);
|
||||||
const logParts = [
|
const logParts = [
|
||||||
ctx.instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
|
ctx.instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
|
||||||
${ctx.instructions.eventInstructions}` : null,
|
${ctx.instructions.eventInstructions}` : null,
|
||||||
@@ -144829,7 +144838,6 @@ ${ctx.instructions.user}` : null,
|
|||||||
log.box(logParts.join("\n\n---\n\n"), {
|
log.box(logParts.join("\n\n---\n\n"), {
|
||||||
title: "Instructions"
|
title: "Instructions"
|
||||||
});
|
});
|
||||||
log.info(`\xBB tool permissions: web=${web}, search=${search2}, push=${push}, bash=${bash}`);
|
|
||||||
return input.run(ctx);
|
return input.run(ctx);
|
||||||
},
|
},
|
||||||
...agentsManifest[input.name]
|
...agentsManifest[input.name]
|
||||||
@@ -144885,7 +144893,7 @@ var claude = agent({
|
|||||||
const cliPath = await installClaude();
|
const cliPath = await installClaude();
|
||||||
const model = claudeEffortModels[ctx.payload.effort];
|
const model = claudeEffortModels[ctx.payload.effort];
|
||||||
const effortLevel = claudeEffortLevels[ctx.payload.effort];
|
const effortLevel = claudeEffortLevels[ctx.payload.effort];
|
||||||
log.info(`\xBB using model: ${model}${effortLevel ? ` (effort: ${effortLevel})` : ""}`);
|
log.info(`\xBB model: ${model}${effortLevel ? ` (effort: ${effortLevel})` : ""}`);
|
||||||
const disallowedTools = buildDisallowedTools(ctx);
|
const disallowedTools = buildDisallowedTools(ctx);
|
||||||
if (disallowedTools.length > 0) {
|
if (disallowedTools.length > 0) {
|
||||||
log.info(`\xBB disallowed tools: ${disallowedTools.join(", ")}`);
|
log.info(`\xBB disallowed tools: ${disallowedTools.join(", ")}`);
|
||||||
@@ -145160,10 +145168,9 @@ var codex = agent({
|
|||||||
const [cliPath, model] = await Promise.all([installCodex(), resolveModel(apiKey)]);
|
const [cliPath, model] = await Promise.all([installCodex(), resolveModel(apiKey)]);
|
||||||
const codexDir = writeCodexConfig(ctx);
|
const codexDir = writeCodexConfig(ctx);
|
||||||
const effortConfig = getCodexEffortConfig(model)[ctx.payload.effort];
|
const effortConfig = getCodexEffortConfig(model)[ctx.payload.effort];
|
||||||
log.info(`\xBB using model: ${effortConfig.model} (effort: ${ctx.payload.effort})`);
|
log.info(
|
||||||
if (effortConfig.reasoningEffort) {
|
`\xBB model: ${effortConfig.model}${effortConfig.reasoningEffort ? ` (reasoningEffort: ${effortConfig.reasoningEffort})` : ""}`
|
||||||
log.info(`\xBB using modelReasoningEffort: ${effortConfig.reasoningEffort}`);
|
);
|
||||||
}
|
|
||||||
const sandboxMode = ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
|
const sandboxMode = ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
|
||||||
const networkAccessEnabled = ctx.payload.web !== "disabled";
|
const networkAccessEnabled = ctx.payload.web !== "disabled";
|
||||||
const webSearchEnabled = ctx.payload.search !== "disabled";
|
const webSearchEnabled = ctx.payload.search !== "disabled";
|
||||||
@@ -145381,7 +145388,7 @@ var cursor = agent({
|
|||||||
try {
|
try {
|
||||||
const projectConfig = JSON.parse(readFileSync5(projectCliConfigPath, "utf-8"));
|
const projectConfig = JSON.parse(readFileSync5(projectCliConfigPath, "utf-8"));
|
||||||
if (projectConfig.model) {
|
if (projectConfig.model) {
|
||||||
log.info(`\xBB using model from project .cursor/cli.json: ${projectConfig.model}`);
|
log.info(`\xBB model: ${projectConfig.model} (from .cursor/cli.json)`);
|
||||||
} else {
|
} else {
|
||||||
modelOverride = cursorEffortModels[ctx.payload.effort];
|
modelOverride = cursorEffortModels[ctx.payload.effort];
|
||||||
}
|
}
|
||||||
@@ -145392,9 +145399,9 @@ var cursor = agent({
|
|||||||
modelOverride = cursorEffortModels[ctx.payload.effort];
|
modelOverride = cursorEffortModels[ctx.payload.effort];
|
||||||
}
|
}
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
log.info(`\xBB using model: ${modelOverride}, effort=${ctx.payload.effort}`);
|
log.info(`\xBB model: ${modelOverride}`);
|
||||||
} else if (!existsSync6(projectCliConfigPath)) {
|
} else if (!existsSync6(projectCliConfigPath)) {
|
||||||
log.info(`\xBB using default model, effort=${ctx.payload.effort}`);
|
log.info(`\xBB model: default`);
|
||||||
}
|
}
|
||||||
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
const loggedModelCallIds = /* @__PURE__ */ new Set();
|
||||||
const thinkingTimer = new ThinkingTimer();
|
const thinkingTimer = new ThinkingTimer();
|
||||||
@@ -145594,7 +145601,8 @@ function configureCursorTools(ctx) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
writeFileSync9(cliConfigPath, JSON.stringify(config3, null, 2), "utf-8");
|
writeFileSync9(cliConfigPath, JSON.stringify(config3, null, 2), "utf-8");
|
||||||
log.info(`\xBB CLI config written to ${cliConfigPath}`, JSON.stringify(config3, null, 2));
|
log.info(`\xBB CLI config written to ${cliConfigPath}`);
|
||||||
|
log.debug(`\xBB CLI config contents: ${JSON.stringify(config3, null, 2)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// agents/gemini.ts
|
// agents/gemini.ts
|
||||||
@@ -145813,7 +145821,7 @@ function configureGeminiSettings(ctx) {
|
|||||||
const effortConfig = geminiEffortConfig[ctx.payload.effort];
|
const effortConfig = geminiEffortConfig[ctx.payload.effort];
|
||||||
const model = process.env.GEMINI_MODEL ?? effortConfig.model;
|
const model = process.env.GEMINI_MODEL ?? effortConfig.model;
|
||||||
const thinkingLevel = effortConfig.thinkingLevel;
|
const thinkingLevel = effortConfig.thinkingLevel;
|
||||||
log.info(`\xBB using model: ${model}, thinkingLevel: ${thinkingLevel}`);
|
log.info(`\xBB model: ${model} (thinkingLevel: ${thinkingLevel})`);
|
||||||
const realHome = homedir2();
|
const realHome = homedir2();
|
||||||
const geminiConfigDir = join12(realHome, ".gemini");
|
const geminiConfigDir = join12(realHome, ".gemini");
|
||||||
const settingsPath = join12(geminiConfigDir, "settings.json");
|
const settingsPath = join12(geminiConfigDir, "settings.json");
|
||||||
@@ -145904,7 +145912,9 @@ var opencode = agent({
|
|||||||
const modelOverride = process.env.OPENCODE_MODEL;
|
const modelOverride = process.env.OPENCODE_MODEL;
|
||||||
if (modelOverride) {
|
if (modelOverride) {
|
||||||
args2.push("--model", modelOverride);
|
args2.push("--model", modelOverride);
|
||||||
log.info(`\xBB using model override: ${modelOverride}`);
|
log.info(`\xBB model: ${modelOverride} (override)`);
|
||||||
|
} else {
|
||||||
|
log.info(`\xBB model: auto-selected by OpenCode`);
|
||||||
}
|
}
|
||||||
process.env.HOME = tempHome;
|
process.env.HOME = tempHome;
|
||||||
const env2 = {
|
const env2 = {
|
||||||
@@ -145987,12 +145997,7 @@ var opencode = agent({
|
|||||||
lastProviderError = providerError;
|
lastProviderError = providerError;
|
||||||
log.error(`\xBB provider error detected (${providerError}): ${trimmed.substring(0, 500)}`);
|
log.error(`\xBB provider error detected (${providerError}): ${trimmed.substring(0, 500)}`);
|
||||||
} else {
|
} else {
|
||||||
try {
|
log.debug(trimmed);
|
||||||
const parsed2 = JSON.parse(trimmed);
|
|
||||||
log.debug(JSON.stringify(parsed2, null, 2));
|
|
||||||
} catch {
|
|
||||||
log.warning(trimmed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -146091,7 +146096,7 @@ function configureOpenCode(ctx) {
|
|||||||
throw error49;
|
throw error49;
|
||||||
}
|
}
|
||||||
log.info(`\xBB OpenCode config written to ${configPath}`);
|
log.info(`\xBB OpenCode config written to ${configPath}`);
|
||||||
log.info(
|
log.debug(
|
||||||
`\xBB OpenCode permissions: edit=${permission.edit}, bash=${permission.bash}, webfetch=${permission.webfetch}`
|
`\xBB OpenCode permissions: edit=${permission.edit}, bash=${permission.bash}, webfetch=${permission.webfetch}`
|
||||||
);
|
);
|
||||||
log.debug(`OpenCode config contents:
|
log.debug(`OpenCode config contents:
|
||||||
@@ -146170,20 +146175,21 @@ var messageHandlers4 = {
|
|||||||
const status = event.part?.state?.status;
|
const status = event.part?.state?.status;
|
||||||
const output = event.part?.state?.output;
|
const output = event.part?.state?.output;
|
||||||
if (!toolName || !toolId) {
|
if (!toolName || !toolId) {
|
||||||
log.debug(`\xBB tool_use event missing toolName or toolId: ${JSON.stringify(event)}`);
|
log.info(
|
||||||
|
`\xBB tool_use event missing toolName or toolId: ${JSON.stringify(event).substring(0, 500)}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (toolName && toolId) {
|
if (stepHistory.length > 0) {
|
||||||
if (stepHistory.length > 0) {
|
stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
|
||||||
stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
|
}
|
||||||
}
|
thinkingTimer.markToolCall();
|
||||||
thinkingTimer.markToolCall();
|
log.toolCall({
|
||||||
log.toolCall({
|
toolName,
|
||||||
toolName,
|
input: parameters || {}
|
||||||
input: parameters || {}
|
});
|
||||||
});
|
if (status === "completed" && output) {
|
||||||
if (status === "completed" && output) {
|
log.debug(` output: ${output}`);
|
||||||
log.debug(` output: ${output}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tool_result: (event, thinkingTimer) => {
|
tool_result: (event, thinkingTimer) => {
|
||||||
|
|||||||
+2
-1
@@ -45,7 +45,8 @@ export function DelegateTool(ctx: ToolContext) {
|
|||||||
// guard: prevent subagent recursion
|
// guard: prevent subagent recursion
|
||||||
if (ctx.toolState.delegationActive) {
|
if (ctx.toolState.delegationActive) {
|
||||||
return {
|
return {
|
||||||
error: "delegation is not available inside a delegated subagent",
|
error:
|
||||||
|
"delegation is not available inside a subagent. you are already running as a delegated subagent. complete the task directly using the available tools. do not attempt to delegate further. your last agent message will be passed to your supervisor and it will decide what to do next.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@pullfrog/pullfrog",
|
"name": "@pullfrog/pullfrog",
|
||||||
"version": "0.0.163",
|
"version": "0.0.164",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
|||||||
@@ -41258,7 +41258,7 @@ var Effort = type.enumerated("mini", "auto", "max");
|
|||||||
// package.json
|
// package.json
|
||||||
var package_default = {
|
var package_default = {
|
||||||
name: "@pullfrog/pullfrog",
|
name: "@pullfrog/pullfrog",
|
||||||
version: "0.0.163",
|
version: "0.0.164",
|
||||||
type: "module",
|
type: "module",
|
||||||
files: [
|
files: [
|
||||||
"index.js",
|
"index.js",
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ function validator(result: AgentResult): ValidationCheck[] {
|
|||||||
const setOutputCalled = output !== null;
|
const setOutputCalled = output !== null;
|
||||||
const correctValue = setOutputCalled && /EFFORT_TEST_PASSED/i.test(output);
|
const correctValue = setOutputCalled && /EFFORT_TEST_PASSED/i.test(output);
|
||||||
|
|
||||||
// the orchestrator runs at auto (effort=auto in its log line).
|
// the orchestrator runs at auto (» effort: auto in its log line).
|
||||||
// the delegate tool should spawn the subagent at mini (effort=mini in its log line).
|
// the delegate tool should spawn the subagent at mini (» effort: mini in its log line).
|
||||||
// if effort override works, we should see BOTH effort=auto AND effort=mini in the output.
|
// if effort override works, we should see BOTH effort values in the output.
|
||||||
const orchestratorEffort = /running \w+ with effort=auto/i.test(agentOutput);
|
const orchestratorEffort = /» effort:\s+auto/i.test(agentOutput);
|
||||||
const subagentEffort = /running \w+ with effort=mini/i.test(agentOutput);
|
const subagentEffort = /» effort:\s+mini/i.test(agentOutput);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{ name: "set_output", passed: setOutputCalled },
|
{ name: "set_output", passed: setOutputCalled },
|
||||||
|
|||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# determines which agents need testing based on changed files.
|
||||||
|
# reads changed file paths from stdin (JSON array or newline-delimited).
|
||||||
|
# outputs a JSON array of agent names to stdout.
|
||||||
|
#
|
||||||
|
# only agents whose harness file changed are included.
|
||||||
|
# shared.ts/index.ts and other non-harness action changes fall back to claude as a canary.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# read stdin - auto-detect JSON array vs newline-delimited
|
||||||
|
input=$(cat)
|
||||||
|
if echo "$input" | jq -e 'type == "array"' > /dev/null 2>&1; then
|
||||||
|
files=$(echo "$input" | jq -r '.[]')
|
||||||
|
else
|
||||||
|
files="$input"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# find which agent harness files changed
|
||||||
|
changed_agents=()
|
||||||
|
has_non_agent_change=false
|
||||||
|
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -z "$file" ]] && continue
|
||||||
|
case "$file" in
|
||||||
|
action/agents/shared.ts|action/agents/index.ts)
|
||||||
|
has_non_agent_change=true
|
||||||
|
;;
|
||||||
|
action/agents/*.ts)
|
||||||
|
changed_agents+=("$(basename "$file" .ts)")
|
||||||
|
;;
|
||||||
|
action/*)
|
||||||
|
has_non_agent_change=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<< "$files"
|
||||||
|
|
||||||
|
# output agents based on change type.
|
||||||
|
# non-agent action changes run claude as a canary.
|
||||||
|
if [[ ${#changed_agents[@]} -gt 0 ]]; then
|
||||||
|
printf '%s\n' "${changed_agents[@]}" | sort -u | jq -R . | jq -sc .
|
||||||
|
elif $has_non_agent_change; then
|
||||||
|
echo '["claude"]'
|
||||||
|
else
|
||||||
|
echo '[]'
|
||||||
|
fi
|
||||||
+21
-2
@@ -1,3 +1,4 @@
|
|||||||
|
import { execFileSync } from "node:child_process";
|
||||||
import { readdirSync, readFileSync } from "node:fs";
|
import { readdirSync, readFileSync } from "node:fs";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
@@ -57,6 +58,7 @@ const expectedAgents = Object.keys(agentsManifest).sort();
|
|||||||
const crossagentTests = getTestNamesFromDir("crossagent");
|
const crossagentTests = getTestNamesFromDir("crossagent");
|
||||||
const agnosticTests = getTestNamesFromDir("agnostic");
|
const agnosticTests = getTestNamesFromDir("agnostic");
|
||||||
const adhocTests = getTestNamesFromDir("adhoc");
|
const adhocTests = getTestNamesFromDir("adhoc");
|
||||||
|
const dynamicAgentsExpression = "$" + "{{ fromJSON(needs.changes.outputs.agents) }}";
|
||||||
|
|
||||||
// all API key names from all agents + GITHUB_TOKEN + model overrides
|
// all API key names from all agents + GITHUB_TOKEN + model overrides
|
||||||
const expectedAgentEnvVars = [
|
const expectedAgentEnvVars = [
|
||||||
@@ -84,8 +86,25 @@ describe("ci workflow consistency", () => {
|
|||||||
const rootJob = rootWorkflow.jobs["action-agents"];
|
const rootJob = rootWorkflow.jobs["action-agents"];
|
||||||
const actionJob = actionWorkflow.jobs.agents;
|
const actionJob = actionWorkflow.jobs.agents;
|
||||||
|
|
||||||
it("root agent matrix matches agentsManifest", () => {
|
it("root agent matrix uses dynamic output from changes job", () => {
|
||||||
expect([...rootJob.strategy!.matrix.agent].sort()).toEqual(expectedAgents);
|
expect(rootJob.strategy!.matrix.agent).toBe(dynamicAgentsExpression);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changed-agents.sh falls back to claude when shared agent code changed", () => {
|
||||||
|
const input = JSON.stringify(["action/agents/shared.ts"]);
|
||||||
|
const output = execFileSync("bash", [join(__dirname, "changed-agents.sh")], {
|
||||||
|
input,
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
expect(JSON.parse(output)).toEqual(["claude"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changed-agents.sh falls back to claude for non-agent action changes", () => {
|
||||||
|
const output = execFileSync("bash", [join(__dirname, "changed-agents.sh")], {
|
||||||
|
input: JSON.stringify(["action/mcp/delegate.ts"]),
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
expect(JSON.parse(output)).toEqual(["claude"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("action agent matrix matches agentsManifest", () => {
|
it("action agent matrix matches agentsManifest", () => {
|
||||||
|
|||||||
+12
-9
@@ -53,33 +53,36 @@ function isOIDCAvailable(): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// github installation token permission levels
|
|
||||||
type ReadWrite = "read" | "write";
|
type ReadWrite = "read" | "write";
|
||||||
type WriteOnly = "write";
|
type WriteOnly = "write";
|
||||||
type ReadOnly = "read";
|
|
||||||
|
|
||||||
// permission names use underscores (API format)
|
/**
|
||||||
type InstallationTokenPermissions = {
|
* GitHub App installation access token permissions.
|
||||||
|
* passed to `POST /app/installations/{id}/access_tokens` to scope the token.
|
||||||
|
* fields and allowed values come from the `app-permissions` OpenAPI schema.
|
||||||
|
* @see https://docs.github.com/en/rest/apps/installations#create-an-installation-access-token-for-an-app
|
||||||
|
* @see https://github.com/github/rest-api-description — components.schemas.app-permissions
|
||||||
|
*/
|
||||||
|
type GitHubAppPermissions = {
|
||||||
actions?: ReadWrite;
|
actions?: ReadWrite;
|
||||||
artifact_metadata?: ReadWrite;
|
artifact_metadata?: ReadWrite;
|
||||||
attestations?: ReadWrite;
|
attestations?: ReadWrite;
|
||||||
checks?: ReadWrite;
|
checks?: ReadWrite;
|
||||||
contents?: ReadWrite;
|
contents?: ReadWrite;
|
||||||
deployments?: ReadWrite;
|
deployments?: ReadWrite;
|
||||||
id_token?: WriteOnly;
|
|
||||||
issues?: ReadWrite;
|
|
||||||
models?: ReadOnly;
|
|
||||||
discussions?: ReadWrite;
|
discussions?: ReadWrite;
|
||||||
|
issues?: ReadWrite;
|
||||||
packages?: ReadWrite;
|
packages?: ReadWrite;
|
||||||
pages?: ReadWrite;
|
pages?: ReadWrite;
|
||||||
pull_requests?: ReadWrite;
|
pull_requests?: ReadWrite;
|
||||||
security_events?: ReadWrite;
|
security_events?: ReadWrite;
|
||||||
statuses?: ReadWrite;
|
statuses?: ReadWrite;
|
||||||
|
workflows?: WriteOnly;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AcquireTokenOptions = {
|
type AcquireTokenOptions = {
|
||||||
repos?: string[];
|
repos?: string[];
|
||||||
permissions?: InstallationTokenPermissions;
|
permissions?: GitHubAppPermissions;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function acquireTokenViaOIDC(opts?: AcquireTokenOptions): Promise<string> {
|
async function acquireTokenViaOIDC(opts?: AcquireTokenOptions): Promise<string> {
|
||||||
@@ -215,7 +218,7 @@ const checkRepositoryAccess = async (
|
|||||||
const createInstallationToken = async (
|
const createInstallationToken = async (
|
||||||
jwt: string,
|
jwt: string,
|
||||||
installationId: number,
|
installationId: number,
|
||||||
permissions?: InstallationTokenPermissions
|
permissions?: GitHubAppPermissions
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const requestOpts: { method: string; headers: Record<string, string>; body?: string } = {
|
const requestOpts: { method: string; headers: Record<string, string>; body?: string } = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -422,7 +422,13 @@ export function resolveSubagentInstructions(
|
|||||||
): ResolvedInstructions {
|
): ResolvedInstructions {
|
||||||
const inputs = buildCommonInputs(ctx);
|
const inputs = buildCommonInputs(ctx);
|
||||||
|
|
||||||
const subagentTaskSection = `You are operating in **${ctx.mode.name}** mode.
|
const subagentTaskSection = `You are operating in **${ctx.mode.name}** mode as a delegated subagent. An orchestrator spawned you and will read your final output to decide what to do next.
|
||||||
|
|
||||||
|
### Delegation rules
|
||||||
|
|
||||||
|
- The \`delegate\` tool is NOT available to you — complete your task directly using the available tools.
|
||||||
|
- When you finish, end with a clear, concise summary: what you did, what succeeded, what failed, and any blockers or next steps. The orchestrator uses this to decide whether to delegate again or report final results.
|
||||||
|
- If you encounter an error you cannot resolve, report it clearly — do not attempt to delegate or re-run yourself.
|
||||||
|
|
||||||
${ctx.mode.prompt}`;
|
${ctx.mode.prompt}`;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -69,7 +69,7 @@ export interface SetupGitParams extends GitContext {
|
|||||||
* - sets up authentication via gitToken (minimal contents:write)
|
* - sets up authentication via gitToken (minimal contents:write)
|
||||||
* - for PR events, checks out the PR branch using shared helper
|
* - for PR events, checks out the PR branch using shared helper
|
||||||
*
|
*
|
||||||
* gitToken is a minimal-permission token (contents:write only) used for git operations.
|
* gitToken is a minimal-permission token (contents + workflows) used for git operations.
|
||||||
* it is assumed to be potentially exfiltratable, so it has limited scope.
|
* it is assumed to be potentially exfiltratable, so it has limited scope.
|
||||||
*/
|
*/
|
||||||
export async function setupGit(params: SetupGitParams): Promise<void> {
|
export async function setupGit(params: SetupGitParams): Promise<void> {
|
||||||
|
|||||||
+11
-3
@@ -105,12 +105,20 @@ export async function resolveTokens(params: ResolveTokensParams): Promise<TokenR
|
|||||||
|
|
||||||
// create git token based on push permission (assumed exfiltratable)
|
// create git token based on push permission (assumed exfiltratable)
|
||||||
// disabled = read-only, restricted/enabled = write (MCP tools enforce branch restrictions)
|
// disabled = read-only, restricted/enabled = write (MCP tools enforce branch restrictions)
|
||||||
const gitContents = params.push === "disabled" ? "read" : "write";
|
// workflows permission is write-only in the API, so only requested when pushing is allowed
|
||||||
const gitToken = await acquireNewToken({ permissions: { contents: gitContents } });
|
const gitPermissions =
|
||||||
|
params.push === "disabled"
|
||||||
|
? { contents: "read" as const }
|
||||||
|
: { contents: "write" as const, workflows: "write" as const };
|
||||||
|
const gitToken = await acquireNewToken({ permissions: gitPermissions });
|
||||||
if (isGitHubActions) {
|
if (isGitHubActions) {
|
||||||
core.setSecret(gitToken);
|
core.setSecret(gitToken);
|
||||||
}
|
}
|
||||||
log.info(`» acquired git token (contents:${gitContents})`);
|
log.info(
|
||||||
|
`» acquired git token (${Object.entries(gitPermissions)
|
||||||
|
.map((e) => e.join(":"))
|
||||||
|
.join(", ")})`
|
||||||
|
);
|
||||||
|
|
||||||
// create full MCP token - not exfiltratable (only accessible via MCP tools)
|
// create full MCP token - not exfiltratable (only accessible via MCP tools)
|
||||||
const mcpToken = await acquireNewToken();
|
const mcpToken = await acquireNewToken();
|
||||||
|
|||||||
Reference in New Issue
Block a user