@gaberrb/polypus 0.4.6 → 0.4.7
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 +131 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -110,6 +110,7 @@ var en = {
|
|
|
110
110
|
"cli.opt.mode": "plan | review | bypass (overrides config)",
|
|
111
111
|
"cli.opt.maxSteps": "maximum agent steps",
|
|
112
112
|
"cli.opt.json": "headless mode: emit a single JSON object (steps, tool calls, files changed, usage) instead of the TUI \u2014 use with --mode bypass",
|
|
113
|
+
"cli.opt.verify": "after the agent finishes, run project checks (typecheck/build/test) and iterate until they pass",
|
|
113
114
|
"cli.arg.swarmTask": "high-level task to split across agents",
|
|
114
115
|
"cli.opt.agents": "comma-separated agent names (default: all configured)",
|
|
115
116
|
"cli.opt.maxSubtasks": "maximum number of parallel subtasks",
|
|
@@ -141,6 +142,11 @@ var en = {
|
|
|
141
142
|
"review.reject": "reject",
|
|
142
143
|
"review.pickHunks": "pick hunks\u2026",
|
|
143
144
|
"review.selectHunks": "Select the hunks to apply (space to toggle, enter to confirm)",
|
|
145
|
+
"verify.running": "verifying (running project checks)",
|
|
146
|
+
"verify.noChecks": "no verification checks detected (no package.json scripts) \u2014 skipping",
|
|
147
|
+
"verify.passed": "verification passed",
|
|
148
|
+
"verify.failed": "{n} check(s) failed \u2014 handing the output back to the agent (attempt {attempt})",
|
|
149
|
+
"verify.giveUp": "{n} check(s) still failing after the retry budget \u2014 stopping",
|
|
144
150
|
// repl
|
|
145
151
|
"repl.welcome": "Polypus interactive session.",
|
|
146
152
|
"repl.welcomeHint": " Type /help for commands, /exit to quit.",
|
|
@@ -344,6 +350,7 @@ var ptBR = {
|
|
|
344
350
|
"cli.opt.mode": "plan | review | bypass (sobrescreve a config)",
|
|
345
351
|
"cli.opt.maxSteps": "n\xFAmero m\xE1ximo de passos do agente",
|
|
346
352
|
"cli.opt.json": "modo headless: emite um \xFAnico objeto JSON (passos, tool calls, arquivos alterados, uso) em vez da TUI \u2014 use com --mode bypass",
|
|
353
|
+
"cli.opt.verify": "ap\xF3s o agente terminar, roda as checagens do projeto (typecheck/build/test) e itera at\xE9 passar",
|
|
347
354
|
"cli.arg.swarmTask": "tarefa de alto n\xEDvel para dividir entre os agentes",
|
|
348
355
|
"cli.opt.agents": "nomes de agentes separados por v\xEDrgula (padr\xE3o: todos)",
|
|
349
356
|
"cli.opt.maxSubtasks": "n\xFAmero m\xE1ximo de subtarefas paralelas",
|
|
@@ -373,6 +380,11 @@ var ptBR = {
|
|
|
373
380
|
"review.reject": "rejeitar",
|
|
374
381
|
"review.pickHunks": "escolher hunks\u2026",
|
|
375
382
|
"review.selectHunks": "Selecione os hunks a aplicar (espa\xE7o alterna, enter confirma)",
|
|
383
|
+
"verify.running": "verificando (rodando as checagens do projeto)",
|
|
384
|
+
"verify.noChecks": "nenhuma checagem detectada (sem scripts no package.json) \u2014 pulando",
|
|
385
|
+
"verify.passed": "verifica\xE7\xE3o passou",
|
|
386
|
+
"verify.failed": "{n} checagem(ns) falharam \u2014 devolvendo a sa\xEDda ao agente (tentativa {attempt})",
|
|
387
|
+
"verify.giveUp": "{n} checagem(ns) ainda falhando ap\xF3s o limite de tentativas \u2014 parando",
|
|
376
388
|
"repl.welcome": "Sess\xE3o interativa do Polypus.",
|
|
377
389
|
"repl.welcomeHint": " Digite /help para comandos, /exit para sair.",
|
|
378
390
|
"repl.modeChanged": "modo \u2192 {mode}",
|
|
@@ -2232,6 +2244,58 @@ ${blocks.join("\n\n")}`;
|
|
|
2232
2244
|
return { task: augmented, injected };
|
|
2233
2245
|
}
|
|
2234
2246
|
|
|
2247
|
+
// src/core/agent/verify.ts
|
|
2248
|
+
import { exec as exec2 } from "child_process";
|
|
2249
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2250
|
+
import { resolve as resolve10 } from "path";
|
|
2251
|
+
import { promisify as promisify2 } from "util";
|
|
2252
|
+
var execAsync2 = promisify2(exec2);
|
|
2253
|
+
var MAX_OUTPUT3 = 8e3;
|
|
2254
|
+
var CHECK_SCRIPTS = ["typecheck", "build", "test"];
|
|
2255
|
+
async function detectChecks(workspace) {
|
|
2256
|
+
try {
|
|
2257
|
+
const raw = await readFile9(resolve10(workspace, "package.json"), "utf8");
|
|
2258
|
+
const scripts = JSON.parse(raw).scripts ?? {};
|
|
2259
|
+
return CHECK_SCRIPTS.filter((s) => typeof scripts[s] === "string").map((s) => `npm run ${s}`);
|
|
2260
|
+
} catch {
|
|
2261
|
+
return [];
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
async function runChecks(workspace, commands) {
|
|
2265
|
+
const results = [];
|
|
2266
|
+
for (const command of commands) {
|
|
2267
|
+
try {
|
|
2268
|
+
const { stdout: stdout2, stderr } = await execAsync2(command, {
|
|
2269
|
+
cwd: workspace,
|
|
2270
|
+
timeout: 3e5,
|
|
2271
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2272
|
+
windowsHide: true
|
|
2273
|
+
});
|
|
2274
|
+
results.push({ command, ok: true, output: clamp2(`${stdout2}${stderr ? `
|
|
2275
|
+
${stderr}` : ""}`.trim()) });
|
|
2276
|
+
} catch (err) {
|
|
2277
|
+
const e = err;
|
|
2278
|
+
const body = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || e.message;
|
|
2279
|
+
results.push({ command, ok: false, output: clamp2(body) });
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
return results;
|
|
2283
|
+
}
|
|
2284
|
+
function buildVerifyFeedback(failed) {
|
|
2285
|
+
const blocks = failed.map((f) => `$ ${f.command}
|
|
2286
|
+
${f.output}`).join("\n\n");
|
|
2287
|
+
return [
|
|
2288
|
+
"Verification failed. The following checks did not pass:",
|
|
2289
|
+
"",
|
|
2290
|
+
blocks,
|
|
2291
|
+
"",
|
|
2292
|
+
"Fix the underlying problem in the code (do not disable or skip the checks), then call `finish`."
|
|
2293
|
+
].join("\n");
|
|
2294
|
+
}
|
|
2295
|
+
function clamp2(s) {
|
|
2296
|
+
return s.length > MAX_OUTPUT3 ? s.slice(-MAX_OUTPUT3) : s;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2235
2299
|
// src/cli/commands/json-output.ts
|
|
2236
2300
|
var OUTPUT_PREVIEW = 500;
|
|
2237
2301
|
function createJsonCollector() {
|
|
@@ -2924,10 +2988,10 @@ async function readLineTTY(prompt) {
|
|
|
2924
2988
|
stdin.resume();
|
|
2925
2989
|
stdin.on("data", onData);
|
|
2926
2990
|
try {
|
|
2927
|
-
const line = await new Promise((
|
|
2928
|
-
rl.question(prompt).then(
|
|
2929
|
-
rl.on("SIGINT", () =>
|
|
2930
|
-
rl.on("close", () =>
|
|
2991
|
+
const line = await new Promise((resolve12) => {
|
|
2992
|
+
rl.question(prompt).then(resolve12, () => resolve12(null));
|
|
2993
|
+
rl.on("SIGINT", () => resolve12(null));
|
|
2994
|
+
rl.on("close", () => resolve12(null));
|
|
2931
2995
|
});
|
|
2932
2996
|
return line === null ? null : store.expand(line);
|
|
2933
2997
|
} finally {
|
|
@@ -3521,6 +3585,7 @@ var Spinner = class {
|
|
|
3521
3585
|
};
|
|
3522
3586
|
|
|
3523
3587
|
// src/cli/commands/run.ts
|
|
3588
|
+
var MAX_VERIFY_FIXES = 3;
|
|
3524
3589
|
async function run(task, opts) {
|
|
3525
3590
|
let config = await loadConfig();
|
|
3526
3591
|
const agentConfig = resolveAgent(config, opts.agent);
|
|
@@ -3555,7 +3620,7 @@ async function run(task, opts) {
|
|
|
3555
3620
|
)
|
|
3556
3621
|
);
|
|
3557
3622
|
}
|
|
3558
|
-
await executeTask(task, resolved2, workspace, session, opts.json ?? false);
|
|
3623
|
+
await executeTask(task, resolved2, workspace, session, opts.json ?? false, opts.verify ?? false);
|
|
3559
3624
|
return;
|
|
3560
3625
|
}
|
|
3561
3626
|
const resolved = createProvider(agentConfig);
|
|
@@ -3578,7 +3643,7 @@ async function run(task, opts) {
|
|
|
3578
3643
|
};
|
|
3579
3644
|
await startRepl(ctx);
|
|
3580
3645
|
}
|
|
3581
|
-
async function executeTask(task, resolved, workspace, session, json = false) {
|
|
3646
|
+
async function executeTask(task, resolved, workspace, session, json = false, verify = false) {
|
|
3582
3647
|
const mention = await resolveMentions(task, {
|
|
3583
3648
|
workspace,
|
|
3584
3649
|
allow: session.allow,
|
|
@@ -3605,25 +3670,29 @@ async function executeTask(task, resolved, workspace, session, json = false) {
|
|
|
3605
3670
|
return ok;
|
|
3606
3671
|
}
|
|
3607
3672
|
});
|
|
3673
|
+
const runOnce = (taskText) => runAgent({
|
|
3674
|
+
task: taskText,
|
|
3675
|
+
workspace,
|
|
3676
|
+
agent: resolved,
|
|
3677
|
+
permissions,
|
|
3678
|
+
promptContext: { workspace, mode: session.mode, allow: session.allow },
|
|
3679
|
+
history: session.history,
|
|
3680
|
+
maxSteps: session.maxSteps,
|
|
3681
|
+
signal: controller.signal,
|
|
3682
|
+
events: collector ? collector.events : renderEvents(spinner3)
|
|
3683
|
+
});
|
|
3608
3684
|
if (!json) spinner3.start(t("ui.thinking"));
|
|
3609
3685
|
let result;
|
|
3610
3686
|
try {
|
|
3611
|
-
result = await
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
promptContext: { workspace, mode: session.mode, allow: session.allow },
|
|
3617
|
-
history: session.history,
|
|
3618
|
-
maxSteps: session.maxSteps,
|
|
3619
|
-
signal: controller.signal,
|
|
3620
|
-
events: collector ? collector.events : renderEvents(spinner3)
|
|
3621
|
-
});
|
|
3687
|
+
result = await runOnce(task);
|
|
3688
|
+
session.history = result.messages;
|
|
3689
|
+
if (verify && result.reason === "finished" && !controller.signal.aborted) {
|
|
3690
|
+
result = await runVerification(runOnce, workspace, session, spinner3, json, controller.signal, result);
|
|
3691
|
+
}
|
|
3622
3692
|
} finally {
|
|
3623
3693
|
spinner3.stop();
|
|
3624
3694
|
cancel2.dispose();
|
|
3625
3695
|
}
|
|
3626
|
-
session.history = result.messages;
|
|
3627
3696
|
if (collector) {
|
|
3628
3697
|
process.stdout.write(JSON.stringify(collector.build(result)) + "\n");
|
|
3629
3698
|
return;
|
|
@@ -3702,6 +3771,36 @@ async function confirmAction(req) {
|
|
|
3702
3771
|
if (p2.isCancel(answer)) return false;
|
|
3703
3772
|
return answer === true;
|
|
3704
3773
|
}
|
|
3774
|
+
async function runVerification(runOnce, workspace, session, spinner3, json, signal, initial) {
|
|
3775
|
+
const checks = await detectChecks(workspace);
|
|
3776
|
+
if (checks.length === 0) {
|
|
3777
|
+
if (!json) console.log(pc8.dim(t("verify.noChecks")));
|
|
3778
|
+
return initial;
|
|
3779
|
+
}
|
|
3780
|
+
let result = initial;
|
|
3781
|
+
for (let fix = 0; ; fix++) {
|
|
3782
|
+
if (signal.aborted) return result;
|
|
3783
|
+
if (!json) spinner3.start(t("verify.running"));
|
|
3784
|
+
const results = await runChecks(workspace, checks);
|
|
3785
|
+
spinner3.stop();
|
|
3786
|
+
const failed = results.filter((r) => !r.ok);
|
|
3787
|
+
if (failed.length === 0) {
|
|
3788
|
+
if (!json) console.log(pc8.green("\u2713 " + t("verify.passed")));
|
|
3789
|
+
return result;
|
|
3790
|
+
}
|
|
3791
|
+
if (fix >= MAX_VERIFY_FIXES) {
|
|
3792
|
+
if (!json) console.log(pc8.yellow("\u26A0 " + t("verify.giveUp", { n: failed.length })));
|
|
3793
|
+
return result;
|
|
3794
|
+
}
|
|
3795
|
+
if (!json) {
|
|
3796
|
+
console.log(pc8.yellow("\u2717 " + t("verify.failed", { n: failed.length, attempt: fix + 1 })));
|
|
3797
|
+
}
|
|
3798
|
+
if (!json) spinner3.start(t("ui.thinking"));
|
|
3799
|
+
result = await runOnce(buildVerifyFeedback(failed));
|
|
3800
|
+
spinner3.stop();
|
|
3801
|
+
session.history = result.messages;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3705
3804
|
function renderDiff(hunks) {
|
|
3706
3805
|
for (const h of hunks) {
|
|
3707
3806
|
console.log(pc8.cyan(`@@ -${h.oldStart + 1},${h.oldCount} +${h.newStart + 1},${h.newCount} @@`));
|
|
@@ -4100,9 +4199,9 @@ async function resolveOpenRouterKey() {
|
|
|
4100
4199
|
}
|
|
4101
4200
|
|
|
4102
4201
|
// src/cli/commands/prd.ts
|
|
4103
|
-
import { writeFile as writeFile5, readFile as
|
|
4202
|
+
import { writeFile as writeFile5, readFile as readFile10 } from "fs/promises";
|
|
4104
4203
|
import { execFile } from "child_process";
|
|
4105
|
-
import { promisify as
|
|
4204
|
+
import { promisify as promisify3 } from "util";
|
|
4106
4205
|
import pc11 from "picocolors";
|
|
4107
4206
|
|
|
4108
4207
|
// src/core/agent/prd.ts
|
|
@@ -4192,13 +4291,13 @@ async function withRetry(fn, opts = {}) {
|
|
|
4192
4291
|
|
|
4193
4292
|
// src/cli/commands/cli-io.ts
|
|
4194
4293
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
4195
|
-
import { resolve as
|
|
4294
|
+
import { resolve as resolve11 } from "path";
|
|
4196
4295
|
var GUIDE_MAX = 12e3;
|
|
4197
4296
|
function readProjectGuide(files) {
|
|
4198
4297
|
const parts = [];
|
|
4199
4298
|
for (const file of files) {
|
|
4200
4299
|
try {
|
|
4201
|
-
const path =
|
|
4300
|
+
const path = resolve11(process.cwd(), file);
|
|
4202
4301
|
if (existsSync2(path)) parts.push(`# ${file}
|
|
4203
4302
|
${readFileSync(path, "utf8").trim()}`);
|
|
4204
4303
|
} catch {
|
|
@@ -4224,7 +4323,7 @@ function stripBom(s) {
|
|
|
4224
4323
|
}
|
|
4225
4324
|
|
|
4226
4325
|
// src/cli/commands/prd.ts
|
|
4227
|
-
var
|
|
4326
|
+
var exec3 = promisify3(execFile);
|
|
4228
4327
|
async function prd(issueRef, opts) {
|
|
4229
4328
|
const issue = await loadIssue(issueRef, opts.input);
|
|
4230
4329
|
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_PRD_MODEL);
|
|
@@ -4239,11 +4338,11 @@ async function prd(issueRef, opts) {
|
|
|
4239
4338
|
}
|
|
4240
4339
|
async function loadIssue(issueRef, input) {
|
|
4241
4340
|
if (input) {
|
|
4242
|
-
const raw = input === "-" ? await readStdin() : await
|
|
4341
|
+
const raw = input === "-" ? await readStdin() : await readFile10(input, "utf8");
|
|
4243
4342
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
4244
4343
|
}
|
|
4245
4344
|
const num = numericRef(issueRef);
|
|
4246
|
-
const { stdout: stdout2 } = await
|
|
4345
|
+
const { stdout: stdout2 } = await exec3("gh", ["issue", "view", num, "--json", "number,title,body,comments"]);
|
|
4247
4346
|
const data = normalize2(JSON.parse(stdout2));
|
|
4248
4347
|
data.number ??= Number(num);
|
|
4249
4348
|
return data;
|
|
@@ -4258,9 +4357,9 @@ function normalize2(raw) {
|
|
|
4258
4357
|
}
|
|
4259
4358
|
|
|
4260
4359
|
// src/cli/commands/review.ts
|
|
4261
|
-
import { writeFile as writeFile6, readFile as
|
|
4360
|
+
import { writeFile as writeFile6, readFile as readFile11 } from "fs/promises";
|
|
4262
4361
|
import { execFile as execFile2 } from "child_process";
|
|
4263
|
-
import { promisify as
|
|
4362
|
+
import { promisify as promisify4 } from "util";
|
|
4264
4363
|
import pc12 from "picocolors";
|
|
4265
4364
|
|
|
4266
4365
|
// src/core/agent/review.ts
|
|
@@ -4318,7 +4417,7 @@ ${projectGuide}`
|
|
|
4318
4417
|
}
|
|
4319
4418
|
|
|
4320
4419
|
// src/cli/commands/review.ts
|
|
4321
|
-
var
|
|
4420
|
+
var exec4 = promisify4(execFile2);
|
|
4322
4421
|
async function review(prRef, opts) {
|
|
4323
4422
|
const num = opts.input ? prRef.replace(/^#/, "") : numericRef(prRef);
|
|
4324
4423
|
const diff = await loadDiff(num, opts.input);
|
|
@@ -4334,13 +4433,13 @@ async function review(prRef, opts) {
|
|
|
4334
4433
|
}
|
|
4335
4434
|
}
|
|
4336
4435
|
async function loadDiff(num, input) {
|
|
4337
|
-
if (input) return input === "-" ? readStdin() :
|
|
4338
|
-
const { stdout: stdout2 } = await
|
|
4436
|
+
if (input) return input === "-" ? readStdin() : readFile11(input, "utf8");
|
|
4437
|
+
const { stdout: stdout2 } = await exec4("gh", ["pr", "diff", num]);
|
|
4339
4438
|
return stdout2;
|
|
4340
4439
|
}
|
|
4341
4440
|
async function loadMeta(num, input) {
|
|
4342
4441
|
if (input) return { number: Number(num) || void 0, title: `PR ${num}`, body: "" };
|
|
4343
|
-
const { stdout: stdout2 } = await
|
|
4442
|
+
const { stdout: stdout2 } = await exec4("gh", ["pr", "view", num, "--json", "number,title,body"]);
|
|
4344
4443
|
const raw = JSON.parse(stdout2);
|
|
4345
4444
|
return { number: raw.number, title: raw.title ?? "", body: raw.body ?? "" };
|
|
4346
4445
|
}
|
|
@@ -4406,7 +4505,7 @@ function buildProgram() {
|
|
|
4406
4505
|
program.command("add-agent").argument("<name>", t("cli.arg.addAgentName")).requiredOption("--provider <provider>", t("cli.opt.provider")).requiredOption("--model <model>", t("cli.opt.model")).option("--api-key <key>", t("cli.opt.apiKey")).option("--base-url <url>", t("cli.opt.baseUrl")).option("--tool-mode <mode>", t("cli.opt.toolMode"), "auto").option("--set-default", t("cli.opt.setDefault")).description(t("cli.cmd.addAgent")).action((name, opts) => addAgent(name, opts));
|
|
4407
4506
|
program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
|
|
4408
4507
|
program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
|
|
4409
|
-
program.command("run").argument("[task]", t("cli.arg.runTask")).option("--agent <name>", t("cli.opt.agent")).option("--mode <mode>", t("cli.opt.mode")).option("--max-steps <n>", t("cli.opt.maxSteps")).option("--json", t("cli.opt.json")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4508
|
+
program.command("run").argument("[task]", t("cli.arg.runTask")).option("--agent <name>", t("cli.opt.agent")).option("--mode <mode>", t("cli.opt.mode")).option("--max-steps <n>", t("cli.opt.maxSteps")).option("--json", t("cli.opt.json")).option("--verify", t("cli.opt.verify")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4410
4509
|
program.command("swarm").argument("<task>", t("cli.arg.swarmTask")).option("--agents <names>", t("cli.opt.agents")).option("--max-subtasks <n>", t("cli.opt.maxSubtasks")).description(t("cli.cmd.swarm")).action((task, opts) => swarm(task, opts));
|
|
4411
4510
|
program.command("models").option("--search <text>", t("cli.opt.search")).option("--tools", t("cli.opt.toolsOnly")).option("--free", t("cli.opt.free")).option("--max-price <usd>", t("cli.opt.maxPrice")).option("--sort <order>", t("cli.opt.sort")).option("--limit <n>", t("cli.opt.limit")).description(t("cli.cmd.models")).action((opts) => models(opts));
|
|
4412
4511
|
program.command("prd").argument("<issue>", t("cli.arg.prdIssue")).option("--out <file>", t("cli.opt.out")).option("--model <model>", t("cli.opt.model")).option("--input <file>", t("cli.opt.input")).description(t("cli.cmd.prd")).action((issue, opts) => prd(issue, opts));
|