@gaberrb/polypus 0.4.8 → 0.4.10
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 +272 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc15 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/cli/commands/add-agent.ts
|
|
8
8
|
import pc from "picocolors";
|
|
@@ -113,6 +113,9 @@ var en = {
|
|
|
113
113
|
"cli.opt.verify": "after the agent finishes, run project checks (typecheck/build/test) and iterate until they pass",
|
|
114
114
|
"cli.opt.budget": "stop the run when the estimated session cost reaches this USD amount (OpenRouter pricing)",
|
|
115
115
|
"cli.cmd.usage": "Show token/cost analytics aggregated per day",
|
|
116
|
+
"cli.cmd.sessions": "List saved sessions that can be resumed",
|
|
117
|
+
"cli.opt.continue": "resume the most recent saved session",
|
|
118
|
+
"cli.opt.resume": "resume a specific saved session by id",
|
|
116
119
|
"cli.arg.swarmTask": "high-level task to split across agents",
|
|
117
120
|
"cli.opt.agents": "comma-separated agent names (default: all configured)",
|
|
118
121
|
"cli.opt.maxSubtasks": "maximum number of parallel subtasks",
|
|
@@ -139,6 +142,7 @@ var en = {
|
|
|
139
142
|
"run.reprompt": "\u21BB no tool call \u2014 reinforcing instructions (attempt {attempt})",
|
|
140
143
|
"run.autocorrect": "\u21BB tool failed \u2014 auto-correcting with extra context",
|
|
141
144
|
"run.cancelled": "\u25A0 cancelled",
|
|
145
|
+
"compaction.done": "context compacted: ~{before} \u2192 ~{after} tokens",
|
|
142
146
|
"run.jsonNeedsTask": "--json requires a task argument (headless mode has no interactive REPL).",
|
|
143
147
|
"review.approveAll": "approve all",
|
|
144
148
|
"review.reject": "reject",
|
|
@@ -156,6 +160,13 @@ var en = {
|
|
|
156
160
|
"usage.empty": "No usage recorded yet. Run a task to start tracking.",
|
|
157
161
|
"usage.total": "total",
|
|
158
162
|
"usage.runs": "runs",
|
|
163
|
+
// sessions
|
|
164
|
+
"sessions.header": "Saved sessions (most recent first):",
|
|
165
|
+
"sessions.empty": "No saved sessions yet.",
|
|
166
|
+
"sessions.hint": "Resume with `polypus run --continue` or `polypus run --resume <id>`.",
|
|
167
|
+
"sessions.notFound": 'No saved session with id "{id}".',
|
|
168
|
+
"sessions.noneToContinue": "No previous session to continue \u2014 starting fresh.",
|
|
169
|
+
"sessions.resumed": "\u21BA resumed session {id} ({n} messages)",
|
|
159
170
|
// repl
|
|
160
171
|
"repl.welcome": "Polypus interactive session.",
|
|
161
172
|
"repl.welcomeHint": " Type /help for commands, /exit to quit.",
|
|
@@ -182,6 +193,8 @@ var en = {
|
|
|
182
193
|
" /allow <glob> add a path glob to the allow-list",
|
|
183
194
|
" /allow show the current allow-list and mode",
|
|
184
195
|
" /reset clear the conversation history",
|
|
196
|
+
" /sessions list saved sessions you can resume",
|
|
197
|
+
" /resume <id> resume a saved session",
|
|
185
198
|
" /help show this help",
|
|
186
199
|
" /exit quit",
|
|
187
200
|
"Anything else is sent to the agent as a task."
|
|
@@ -362,6 +375,9 @@ var ptBR = {
|
|
|
362
375
|
"cli.opt.verify": "ap\xF3s o agente terminar, roda as checagens do projeto (typecheck/build/test) e itera at\xE9 passar",
|
|
363
376
|
"cli.opt.budget": "interrompe a execu\xE7\xE3o quando o custo estimado da sess\xE3o atingir este valor em USD (pre\xE7os do OpenRouter)",
|
|
364
377
|
"cli.cmd.usage": "Mostra analytics de tokens/custo agregados por dia",
|
|
378
|
+
"cli.cmd.sessions": "Lista as sess\xF5es salvas que podem ser retomadas",
|
|
379
|
+
"cli.opt.continue": "retoma a sess\xE3o salva mais recente",
|
|
380
|
+
"cli.opt.resume": "retoma uma sess\xE3o salva espec\xEDfica pelo id",
|
|
365
381
|
"cli.arg.swarmTask": "tarefa de alto n\xEDvel para dividir entre os agentes",
|
|
366
382
|
"cli.opt.agents": "nomes de agentes separados por v\xEDrgula (padr\xE3o: todos)",
|
|
367
383
|
"cli.opt.maxSubtasks": "n\xFAmero m\xE1ximo de subtarefas paralelas",
|
|
@@ -386,6 +402,7 @@ var ptBR = {
|
|
|
386
402
|
"run.reprompt": "\u21BB nenhuma chamada de tool \u2014 refor\xE7ando instru\xE7\xF5es (tentativa {attempt})",
|
|
387
403
|
"run.autocorrect": "\u21BB tool falhou \u2014 autocorrigindo com contexto extra",
|
|
388
404
|
"run.cancelled": "\u25A0 cancelado",
|
|
405
|
+
"compaction.done": "contexto compactado: ~{before} \u2192 ~{after} tokens",
|
|
389
406
|
"run.jsonNeedsTask": "--json exige um argumento de tarefa (o modo headless n\xE3o tem REPL interativo).",
|
|
390
407
|
"review.approveAll": "aprovar tudo",
|
|
391
408
|
"review.reject": "rejeitar",
|
|
@@ -403,6 +420,13 @@ var ptBR = {
|
|
|
403
420
|
"usage.empty": "Nenhum uso registrado ainda. Rode uma tarefa para come\xE7ar a medir.",
|
|
404
421
|
"usage.total": "total",
|
|
405
422
|
"usage.runs": "execu\xE7\xF5es",
|
|
423
|
+
// sessions
|
|
424
|
+
"sessions.header": "Sess\xF5es salvas (mais recentes primeiro):",
|
|
425
|
+
"sessions.empty": "Nenhuma sess\xE3o salva ainda.",
|
|
426
|
+
"sessions.hint": "Retome com `polypus run --continue` ou `polypus run --resume <id>`.",
|
|
427
|
+
"sessions.notFound": 'Nenhuma sess\xE3o salva com id "{id}".',
|
|
428
|
+
"sessions.noneToContinue": "Nenhuma sess\xE3o anterior para continuar \u2014 come\xE7ando do zero.",
|
|
429
|
+
"sessions.resumed": "\u21BA sess\xE3o {id} retomada ({n} mensagens)",
|
|
406
430
|
"repl.welcome": "Sess\xE3o interativa do Polypus.",
|
|
407
431
|
"repl.welcomeHint": " Digite /help para comandos, /exit para sair.",
|
|
408
432
|
"repl.modeChanged": "modo \u2192 {mode}",
|
|
@@ -428,6 +452,8 @@ var ptBR = {
|
|
|
428
452
|
" /allow <glob> adiciona um glob de caminho \xE0 allow-list",
|
|
429
453
|
" /allow mostra a allow-list e o modo atuais",
|
|
430
454
|
" /reset limpa o hist\xF3rico da conversa",
|
|
455
|
+
" /sessions lista as sess\xF5es salvas que voc\xEA pode retomar",
|
|
456
|
+
" /resume <id> retoma uma sess\xE3o salva",
|
|
431
457
|
" /help mostra esta ajuda",
|
|
432
458
|
" /exit sair",
|
|
433
459
|
"Qualquer outra coisa \xE9 enviada ao agente como tarefa."
|
|
@@ -1086,6 +1112,13 @@ var SECRET_PATTERNS = [
|
|
|
1086
1112
|
{ re: /\bsk-[A-Za-z0-9]{32,}\b/, kind: "OpenAI-style secret key" },
|
|
1087
1113
|
{ re: /\bAIza[0-9A-Za-z_-]{35}\b/, kind: "Google API key" }
|
|
1088
1114
|
];
|
|
1115
|
+
function redactSecrets(text2) {
|
|
1116
|
+
let out = text2;
|
|
1117
|
+
for (const { re } of SECRET_PATTERNS) {
|
|
1118
|
+
out = out.replace(new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g"), "[redacted]");
|
|
1119
|
+
}
|
|
1120
|
+
return out;
|
|
1121
|
+
}
|
|
1089
1122
|
function scanSecrets(text2) {
|
|
1090
1123
|
const findings = [];
|
|
1091
1124
|
const lines = text2.split("\n");
|
|
@@ -2061,6 +2094,56 @@ async function loadProjectInstructions(workspace) {
|
|
|
2061
2094
|
return void 0;
|
|
2062
2095
|
}
|
|
2063
2096
|
|
|
2097
|
+
// src/core/agent/compaction.ts
|
|
2098
|
+
function estimateTokens(messages) {
|
|
2099
|
+
let chars = 0;
|
|
2100
|
+
for (const m of messages) chars += m.content.length;
|
|
2101
|
+
return Math.ceil(chars / 4);
|
|
2102
|
+
}
|
|
2103
|
+
var RECENT_KEEP = 8;
|
|
2104
|
+
var MIN_TO_COMPACT = 4;
|
|
2105
|
+
var MAX_SUMMARY_INPUT = 4e4;
|
|
2106
|
+
function findSafeCut(messages, desiredKeep = RECENT_KEEP) {
|
|
2107
|
+
let cut = Math.max(1, messages.length - desiredKeep);
|
|
2108
|
+
while (cut < messages.length && (messages[cut].role === "tool" || messages[cut - 1]?.role === "assistant" && (messages[cut - 1].toolCalls?.length ?? 0) > 0)) {
|
|
2109
|
+
cut++;
|
|
2110
|
+
}
|
|
2111
|
+
return cut;
|
|
2112
|
+
}
|
|
2113
|
+
function serialize(messages) {
|
|
2114
|
+
const text2 = messages.map((m) => {
|
|
2115
|
+
const tools = m.toolCalls?.length ? ` [called: ${m.toolCalls.map((c) => c.name).join(", ")}]` : "";
|
|
2116
|
+
return `${m.role}${tools}: ${m.content}`;
|
|
2117
|
+
}).join("\n\n");
|
|
2118
|
+
return text2.length > MAX_SUMMARY_INPUT ? text2.slice(-MAX_SUMMARY_INPUT) : text2;
|
|
2119
|
+
}
|
|
2120
|
+
async function compactHistory(messages, agent, signal) {
|
|
2121
|
+
if (messages.length === 0) return messages;
|
|
2122
|
+
const system = messages[0].role === "system" ? messages[0] : void 0;
|
|
2123
|
+
const startIdx = system ? 1 : 0;
|
|
2124
|
+
const cut = findSafeCut(messages);
|
|
2125
|
+
if (cut >= messages.length) return messages;
|
|
2126
|
+
const middle = messages.slice(startIdx, cut);
|
|
2127
|
+
if (middle.length < MIN_TO_COMPACT) return messages;
|
|
2128
|
+
const tail = messages.slice(cut);
|
|
2129
|
+
const summary = await agent.provider.chat({
|
|
2130
|
+
messages: [
|
|
2131
|
+
{
|
|
2132
|
+
role: "system",
|
|
2133
|
+
content: "You compress a coding agent's conversation so it can continue with less context. Summarize the messages below into a concise but information-dense brief that preserves: the original task and goal, key decisions, files created/edited and why, important command/test outputs, and any remaining TODOs or open problems. Use terse bullet points. Do not invent details."
|
|
2134
|
+
},
|
|
2135
|
+
{ role: "user", content: serialize(middle) }
|
|
2136
|
+
],
|
|
2137
|
+
signal
|
|
2138
|
+
});
|
|
2139
|
+
const summaryMessage = {
|
|
2140
|
+
role: "user",
|
|
2141
|
+
content: `[Summary of earlier conversation, compacted to save context]
|
|
2142
|
+
${summary.content.trim()}`
|
|
2143
|
+
};
|
|
2144
|
+
return system ? [system, summaryMessage, ...tail] : [summaryMessage, ...tail];
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2064
2147
|
// src/core/agent/loop.ts
|
|
2065
2148
|
function looksLikeStall(text2) {
|
|
2066
2149
|
const lc = text2.toLowerCase();
|
|
@@ -2115,9 +2198,22 @@ async function runAgent(opts) {
|
|
|
2115
2198
|
const maxToolRetries = opts.maxToolRetries ?? 3;
|
|
2116
2199
|
const autoCorrect = opts.autoCorrect ?? true;
|
|
2117
2200
|
const usage2 = { promptTokens: 0, completionTokens: 0 };
|
|
2201
|
+
const compactThreshold = opts.compactThresholdTokens ?? 0;
|
|
2202
|
+
let lastPromptTokens = 0;
|
|
2118
2203
|
for (let step = 1; step <= maxSteps; step++) {
|
|
2119
2204
|
if (opts.signal?.aborted) return { finished: false, reason: "cancelled", steps: step - 1, messages, usage: usage2 };
|
|
2120
2205
|
events?.onStep?.(step);
|
|
2206
|
+
if (compactThreshold > 0) {
|
|
2207
|
+
const current = lastPromptTokens || estimateTokens(messages);
|
|
2208
|
+
if (current >= compactThreshold) {
|
|
2209
|
+
const compacted = await compactHistory(messages, agent, opts.signal);
|
|
2210
|
+
if (compacted.length < messages.length) {
|
|
2211
|
+
messages.splice(0, messages.length, ...compacted);
|
|
2212
|
+
lastPromptTokens = estimateTokens(messages);
|
|
2213
|
+
events?.onCompaction?.(current, lastPromptTokens);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2121
2217
|
let response;
|
|
2122
2218
|
try {
|
|
2123
2219
|
response = await agent.provider.chat({
|
|
@@ -2132,6 +2228,7 @@ async function runAgent(opts) {
|
|
|
2132
2228
|
}
|
|
2133
2229
|
usage2.promptTokens += response.usage?.promptTokens ?? 0;
|
|
2134
2230
|
usage2.completionTokens += response.usage?.completionTokens ?? 0;
|
|
2231
|
+
lastPromptTokens = response.usage?.promptTokens ?? estimateTokens(messages);
|
|
2135
2232
|
events?.onUsage?.(usage2);
|
|
2136
2233
|
const { toolCalls, text: text2 } = driver.parse(response);
|
|
2137
2234
|
messages.push(driver.assistantMessage(response, toolCalls));
|
|
@@ -2461,6 +2558,69 @@ function accumulate(bucket, e) {
|
|
|
2461
2558
|
bucket.runs += 1;
|
|
2462
2559
|
}
|
|
2463
2560
|
|
|
2561
|
+
// src/core/agent/session-store.ts
|
|
2562
|
+
import { mkdir as mkdir4, readFile as readFile11, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
|
|
2563
|
+
import { join as join5 } from "path";
|
|
2564
|
+
function sessionsDir() {
|
|
2565
|
+
return join5(configDir(), "sessions");
|
|
2566
|
+
}
|
|
2567
|
+
function newSessionId() {
|
|
2568
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2569
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
2570
|
+
return `${stamp}-${rand}`;
|
|
2571
|
+
}
|
|
2572
|
+
function sessionPath(id) {
|
|
2573
|
+
return join5(sessionsDir(), `${id}.json`);
|
|
2574
|
+
}
|
|
2575
|
+
async function saveSession(record) {
|
|
2576
|
+
await mkdir4(sessionsDir(), { recursive: true });
|
|
2577
|
+
const safe = {
|
|
2578
|
+
...record,
|
|
2579
|
+
messages: record.messages.map((m) => ({ ...m, content: redactSecrets(m.content) }))
|
|
2580
|
+
};
|
|
2581
|
+
await writeFile4(sessionPath(record.id), JSON.stringify(safe, null, 2) + "\n", "utf8");
|
|
2582
|
+
}
|
|
2583
|
+
async function loadSession(id) {
|
|
2584
|
+
try {
|
|
2585
|
+
return JSON.parse(await readFile11(sessionPath(id), "utf8"));
|
|
2586
|
+
} catch {
|
|
2587
|
+
return void 0;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
async function listSessions() {
|
|
2591
|
+
let files;
|
|
2592
|
+
try {
|
|
2593
|
+
files = (await readdir5(sessionsDir())).filter((f) => f.endsWith(".json"));
|
|
2594
|
+
} catch {
|
|
2595
|
+
return [];
|
|
2596
|
+
}
|
|
2597
|
+
const summaries = [];
|
|
2598
|
+
for (const f of files) {
|
|
2599
|
+
try {
|
|
2600
|
+
const r = JSON.parse(await readFile11(join5(sessionsDir(), f), "utf8"));
|
|
2601
|
+
summaries.push({
|
|
2602
|
+
id: r.id,
|
|
2603
|
+
updatedAt: r.updatedAt,
|
|
2604
|
+
title: r.title,
|
|
2605
|
+
agentName: r.agentName,
|
|
2606
|
+
mode: r.mode,
|
|
2607
|
+
messageCount: r.messages?.length ?? 0
|
|
2608
|
+
});
|
|
2609
|
+
} catch {
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
2613
|
+
}
|
|
2614
|
+
async function latestSession() {
|
|
2615
|
+
const [latest] = await listSessions();
|
|
2616
|
+
return latest ? loadSession(latest.id) : void 0;
|
|
2617
|
+
}
|
|
2618
|
+
function deriveTitle(messages) {
|
|
2619
|
+
const firstUser = messages.find((m) => m.role === "user");
|
|
2620
|
+
const text2 = (firstUser?.content ?? "").replace(/\s+/g, " ").trim();
|
|
2621
|
+
return text2.length > 60 ? text2.slice(0, 60) + "\u2026" : text2 || "(untitled)";
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2464
2624
|
// src/cli/commands/json-output.ts
|
|
2465
2625
|
var OUTPUT_PREVIEW = 500;
|
|
2466
2626
|
function createJsonCollector() {
|
|
@@ -2494,6 +2654,9 @@ function createJsonCollector() {
|
|
|
2494
2654
|
onReprompt(attempt) {
|
|
2495
2655
|
log.push({ type: "reprompt", attempt });
|
|
2496
2656
|
},
|
|
2657
|
+
onCompaction(before, after) {
|
|
2658
|
+
log.push({ type: "compaction", before, after });
|
|
2659
|
+
},
|
|
2497
2660
|
onUsage() {
|
|
2498
2661
|
}
|
|
2499
2662
|
};
|
|
@@ -3145,6 +3308,36 @@ async function handleCommand(cmd, arg, ctx) {
|
|
|
3145
3308
|
session.history = [];
|
|
3146
3309
|
console.log(pc6.dim(t("repl.historyCleared")));
|
|
3147
3310
|
return;
|
|
3311
|
+
case "sessions": {
|
|
3312
|
+
const all = await listSessions();
|
|
3313
|
+
if (all.length === 0) {
|
|
3314
|
+
console.log(pc6.yellow(t("sessions.empty")));
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
console.log(pc6.bold(t("sessions.header")));
|
|
3318
|
+
for (const s of all) {
|
|
3319
|
+
console.log(` ${pc6.cyan(s.id)} ${pc6.dim(`[${s.messageCount} msgs]`)} ${s.title}`);
|
|
3320
|
+
}
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
case "resume": {
|
|
3324
|
+
if (!arg) {
|
|
3325
|
+
console.log(pc6.yellow(t("repl.needName", { usage: "/resume <id>" })));
|
|
3326
|
+
return;
|
|
3327
|
+
}
|
|
3328
|
+
const record = await loadSession(arg);
|
|
3329
|
+
if (!record) {
|
|
3330
|
+
console.log(pc6.red(t("sessions.notFound", { id: arg })));
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
session.id = record.id;
|
|
3334
|
+
session.title = record.title;
|
|
3335
|
+
session.agentName = record.agentName;
|
|
3336
|
+
session.mode = record.mode;
|
|
3337
|
+
session.history = record.messages;
|
|
3338
|
+
console.log(pc6.green(t("sessions.resumed", { id: record.id, n: record.messages.length })));
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3148
3341
|
case "swarm": {
|
|
3149
3342
|
if (!arg) {
|
|
3150
3343
|
console.log(pc6.yellow(t("repl.needName", { usage: "/swarm <task>" })));
|
|
@@ -3226,7 +3419,7 @@ import pc7 from "picocolors";
|
|
|
3226
3419
|
// src/core/git/worktree.ts
|
|
3227
3420
|
import { mkdtemp } from "fs/promises";
|
|
3228
3421
|
import { tmpdir } from "os";
|
|
3229
|
-
import { join as
|
|
3422
|
+
import { join as join6 } from "path";
|
|
3230
3423
|
import { simpleGit } from "simple-git";
|
|
3231
3424
|
async function ensureRepo(workspace) {
|
|
3232
3425
|
const git = simpleGit(workspace);
|
|
@@ -3247,7 +3440,7 @@ async function identityArgs(git) {
|
|
|
3247
3440
|
}
|
|
3248
3441
|
async function createWorktree(git, label) {
|
|
3249
3442
|
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
3250
|
-
const path = await mkdtemp(
|
|
3443
|
+
const path = await mkdtemp(join6(tmpdir(), "polypus-wt-"));
|
|
3251
3444
|
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3252
3445
|
return { path, branch };
|
|
3253
3446
|
}
|
|
@@ -3676,21 +3869,39 @@ var Spinner = class {
|
|
|
3676
3869
|
|
|
3677
3870
|
// src/cli/commands/run.ts
|
|
3678
3871
|
var MAX_VERIFY_FIXES = 3;
|
|
3872
|
+
function compactionThreshold() {
|
|
3873
|
+
if (process.env.POLYPUS_NO_COMPACT) return 0;
|
|
3874
|
+
const v = Number(process.env.POLYPUS_COMPACT_THRESHOLD);
|
|
3875
|
+
return Number.isFinite(v) && v > 0 ? v : 12e4;
|
|
3876
|
+
}
|
|
3679
3877
|
async function run(task, opts) {
|
|
3680
3878
|
let config = await loadConfig();
|
|
3681
|
-
const agentConfig = resolveAgent(config, opts.agent);
|
|
3682
3879
|
const workspace = process.cwd();
|
|
3880
|
+
let seeded;
|
|
3881
|
+
if (opts.resume) {
|
|
3882
|
+
seeded = await loadSession(opts.resume);
|
|
3883
|
+
if (!seeded) throw new Error(t("sessions.notFound", { id: opts.resume }));
|
|
3884
|
+
} else if (opts.continue) {
|
|
3885
|
+
seeded = await latestSession();
|
|
3886
|
+
if (!seeded && !opts.json) console.log(pc8.dim(t("sessions.noneToContinue")));
|
|
3887
|
+
}
|
|
3888
|
+
const agentConfig = resolveAgent(config, opts.agent ?? seeded?.agentName);
|
|
3683
3889
|
const session = {
|
|
3890
|
+
id: seeded?.id ?? newSessionId(),
|
|
3891
|
+
title: seeded?.title ?? "",
|
|
3684
3892
|
agentName: agentConfig.name,
|
|
3685
|
-
mode: opts.mode ?? config.permissions.mode,
|
|
3893
|
+
mode: opts.mode ?? seeded?.mode ?? config.permissions.mode,
|
|
3686
3894
|
allow: config.permissions.allow,
|
|
3687
3895
|
deny: config.permissions.deny,
|
|
3688
3896
|
allowedCommands: config.permissions.allowedCommands,
|
|
3689
3897
|
maxSteps: opts.maxSteps ? Number(opts.maxSteps) : void 0,
|
|
3690
|
-
history: [],
|
|
3898
|
+
history: seeded?.messages ?? [],
|
|
3691
3899
|
budget: opts.budget ? Number(opts.budget) : void 0,
|
|
3692
3900
|
costUsd: 0
|
|
3693
3901
|
};
|
|
3902
|
+
if (seeded && !opts.json) {
|
|
3903
|
+
console.log(pc8.dim(t("sessions.resumed", { id: seeded.id, n: seeded.messages.length })));
|
|
3904
|
+
}
|
|
3694
3905
|
const runTask = async (taskText) => {
|
|
3695
3906
|
const active = resolveAgent(config, session.agentName);
|
|
3696
3907
|
const resolved2 = createProvider(active);
|
|
@@ -3788,6 +3999,7 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3788
3999
|
promptContext: { workspace, mode: session.mode, allow: session.allow },
|
|
3789
4000
|
history: session.history,
|
|
3790
4001
|
maxSteps: session.maxSteps,
|
|
4002
|
+
compactThresholdTokens: compactionThreshold(),
|
|
3791
4003
|
signal: controller.signal,
|
|
3792
4004
|
events
|
|
3793
4005
|
});
|
|
@@ -3803,6 +4015,16 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3803
4015
|
spinner3.stop();
|
|
3804
4016
|
cancel2.dispose();
|
|
3805
4017
|
}
|
|
4018
|
+
if (!session.title) session.title = deriveTitle(session.history);
|
|
4019
|
+
await saveSession({
|
|
4020
|
+
id: session.id,
|
|
4021
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4022
|
+
title: session.title,
|
|
4023
|
+
agentName: session.agentName,
|
|
4024
|
+
mode: session.mode,
|
|
4025
|
+
messages: session.history
|
|
4026
|
+
}).catch(() => {
|
|
4027
|
+
});
|
|
3806
4028
|
const runCost = pricing ? estimateCost(result.usage, pricing) : 0;
|
|
3807
4029
|
session.costUsd += runCost;
|
|
3808
4030
|
await recordUsage({
|
|
@@ -3961,6 +4183,10 @@ function renderEvents(spinner3) {
|
|
|
3961
4183
|
spinner3.stop();
|
|
3962
4184
|
console.log(pc8.yellow(" " + t("run.reprompt", { attempt })));
|
|
3963
4185
|
},
|
|
4186
|
+
onCompaction(before, after) {
|
|
4187
|
+
spinner3.stop();
|
|
4188
|
+
console.log(pc8.dim("\u21AF " + t("compaction.done", { before: fmtTokens(before), after: fmtTokens(after) })));
|
|
4189
|
+
},
|
|
3964
4190
|
onCorrection() {
|
|
3965
4191
|
spinner3.stop();
|
|
3966
4192
|
console.log(pc8.yellow(" \u21BB " + t("run.autocorrect")));
|
|
@@ -3977,8 +4203,8 @@ async function setup() {
|
|
|
3977
4203
|
import pc9 from "picocolors";
|
|
3978
4204
|
|
|
3979
4205
|
// src/core/scaffold/init.ts
|
|
3980
|
-
import { mkdir as
|
|
3981
|
-
import { dirname as dirname3, join as
|
|
4206
|
+
import { mkdir as mkdir5, writeFile as writeFile5, access } from "fs/promises";
|
|
4207
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
3982
4208
|
|
|
3983
4209
|
// src/core/scaffold/templates.ts
|
|
3984
4210
|
function polyTemplates(locale) {
|
|
@@ -4221,13 +4447,13 @@ async function scaffoldPoly(workspace, opts) {
|
|
|
4221
4447
|
const skipped = [];
|
|
4222
4448
|
for (const [rel, content] of Object.entries(templates)) {
|
|
4223
4449
|
const display = `.poly/${rel}`;
|
|
4224
|
-
const abs =
|
|
4450
|
+
const abs = join7(workspace, ".poly", ...rel.split("/"));
|
|
4225
4451
|
if (!opts.force && await exists(abs)) {
|
|
4226
4452
|
skipped.push(display);
|
|
4227
4453
|
continue;
|
|
4228
4454
|
}
|
|
4229
|
-
await
|
|
4230
|
-
await
|
|
4455
|
+
await mkdir5(dirname3(abs), { recursive: true });
|
|
4456
|
+
await writeFile5(abs, content, "utf8");
|
|
4231
4457
|
created.push(display);
|
|
4232
4458
|
}
|
|
4233
4459
|
return { created, skipped };
|
|
@@ -4341,11 +4567,30 @@ function fmtTokens2(n) {
|
|
|
4341
4567
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
4342
4568
|
}
|
|
4343
4569
|
|
|
4570
|
+
// src/cli/commands/sessions.ts
|
|
4571
|
+
import pc12 from "picocolors";
|
|
4572
|
+
async function sessions() {
|
|
4573
|
+
const all = await listSessions();
|
|
4574
|
+
if (all.length === 0) {
|
|
4575
|
+
console.log(pc12.yellow(t("sessions.empty")));
|
|
4576
|
+
return;
|
|
4577
|
+
}
|
|
4578
|
+
console.log(pc12.bold(t("sessions.header")));
|
|
4579
|
+
for (const s of all) {
|
|
4580
|
+
const when = s.updatedAt.replace("T", " ").slice(0, 16);
|
|
4581
|
+
console.log(
|
|
4582
|
+
` ${pc12.cyan(s.id)} ${pc12.dim(when)} ${pc12.dim(`[${s.agentName} \xB7 ${s.mode} \xB7 ${s.messageCount} msgs]`)}`
|
|
4583
|
+
);
|
|
4584
|
+
console.log(` ${s.title}`);
|
|
4585
|
+
}
|
|
4586
|
+
console.log(pc12.dim("\n" + t("sessions.hint")));
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4344
4589
|
// src/cli/commands/prd.ts
|
|
4345
|
-
import { writeFile as
|
|
4590
|
+
import { writeFile as writeFile6, readFile as readFile12 } from "fs/promises";
|
|
4346
4591
|
import { execFile } from "child_process";
|
|
4347
4592
|
import { promisify as promisify3 } from "util";
|
|
4348
|
-
import
|
|
4593
|
+
import pc13 from "picocolors";
|
|
4349
4594
|
|
|
4350
4595
|
// src/core/agent/prd.ts
|
|
4351
4596
|
var SYSTEM = [
|
|
@@ -4473,15 +4718,15 @@ async function prd(issueRef, opts) {
|
|
|
4473
4718
|
const guide = readProjectGuide(["context.md"]);
|
|
4474
4719
|
const markdown = await withRetry(() => generatePrd(issue, provider, guide));
|
|
4475
4720
|
if (opts.out) {
|
|
4476
|
-
await
|
|
4477
|
-
console.error(
|
|
4721
|
+
await writeFile6(opts.out, markdown + "\n", "utf8");
|
|
4722
|
+
console.error(pc13.green(t("prd.wrote", { path: opts.out })));
|
|
4478
4723
|
} else {
|
|
4479
4724
|
process.stdout.write(markdown + "\n");
|
|
4480
4725
|
}
|
|
4481
4726
|
}
|
|
4482
4727
|
async function loadIssue(issueRef, input) {
|
|
4483
4728
|
if (input) {
|
|
4484
|
-
const raw = input === "-" ? await readStdin() : await
|
|
4729
|
+
const raw = input === "-" ? await readStdin() : await readFile12(input, "utf8");
|
|
4485
4730
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
4486
4731
|
}
|
|
4487
4732
|
const num = numericRef(issueRef);
|
|
@@ -4500,10 +4745,10 @@ function normalize2(raw) {
|
|
|
4500
4745
|
}
|
|
4501
4746
|
|
|
4502
4747
|
// src/cli/commands/review.ts
|
|
4503
|
-
import { writeFile as
|
|
4748
|
+
import { writeFile as writeFile7, readFile as readFile13 } from "fs/promises";
|
|
4504
4749
|
import { execFile as execFile2 } from "child_process";
|
|
4505
4750
|
import { promisify as promisify4 } from "util";
|
|
4506
|
-
import
|
|
4751
|
+
import pc14 from "picocolors";
|
|
4507
4752
|
|
|
4508
4753
|
// src/core/agent/review.ts
|
|
4509
4754
|
var MAX_DIFF_CHARS = Number(process.env.POLYPUS_MAX_DIFF_CHARS) || 6e4;
|
|
@@ -4569,14 +4814,14 @@ async function review(prRef, opts) {
|
|
|
4569
4814
|
const guide = readProjectGuide(["rules.md", "context.md"]);
|
|
4570
4815
|
const markdown = await withRetry(() => reviewDiff(diff, meta, provider, guide));
|
|
4571
4816
|
if (opts.out) {
|
|
4572
|
-
await
|
|
4573
|
-
console.error(
|
|
4817
|
+
await writeFile7(opts.out, markdown + "\n", "utf8");
|
|
4818
|
+
console.error(pc14.green(t("review.wrote", { path: opts.out })));
|
|
4574
4819
|
} else {
|
|
4575
4820
|
process.stdout.write(markdown + "\n");
|
|
4576
4821
|
}
|
|
4577
4822
|
}
|
|
4578
4823
|
async function loadDiff(num, input) {
|
|
4579
|
-
if (input) return input === "-" ? readStdin() :
|
|
4824
|
+
if (input) return input === "-" ? readStdin() : readFile13(input, "utf8");
|
|
4580
4825
|
const { stdout: stdout2 } = await exec4("gh", ["pr", "diff", num]);
|
|
4581
4826
|
return stdout2;
|
|
4582
4827
|
}
|
|
@@ -4588,7 +4833,7 @@ async function loadMeta(num, input) {
|
|
|
4588
4833
|
}
|
|
4589
4834
|
|
|
4590
4835
|
// src/cli/index.ts
|
|
4591
|
-
import { join as
|
|
4836
|
+
import { join as join8 } from "path";
|
|
4592
4837
|
|
|
4593
4838
|
// src/core/config/dotenv.ts
|
|
4594
4839
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
@@ -4621,7 +4866,7 @@ async function launchInteractive() {
|
|
|
4621
4866
|
const config = await loadConfig();
|
|
4622
4867
|
if (config.agents.length === 0) {
|
|
4623
4868
|
console.log(banner());
|
|
4624
|
-
console.log(" " +
|
|
4869
|
+
console.log(" " + pc15.yellow(t("welcome.firstRun")) + "\n");
|
|
4625
4870
|
await setup();
|
|
4626
4871
|
}
|
|
4627
4872
|
await run(void 0, {});
|
|
@@ -4648,21 +4893,22 @@ function buildProgram() {
|
|
|
4648
4893
|
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));
|
|
4649
4894
|
program.command("remove-agent").argument("<name>", t("cli.arg.removeAgentName")).description(t("cli.cmd.removeAgent")).action((name) => removeAgent(name));
|
|
4650
4895
|
program.command("list-agents").alias("agents").description(t("cli.cmd.listAgents")).action(() => listAgents());
|
|
4651
|
-
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")).option("--budget <usd>", t("cli.opt.budget")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4896
|
+
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")).option("--budget <usd>", t("cli.opt.budget")).option("--continue", t("cli.opt.continue")).option("--resume <id>", t("cli.opt.resume")).description(t("cli.cmd.run")).action((task, opts) => run(task, opts));
|
|
4652
4897
|
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));
|
|
4653
4898
|
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));
|
|
4654
4899
|
program.command("usage").description(t("cli.cmd.usage")).action(() => usage());
|
|
4900
|
+
program.command("sessions").description(t("cli.cmd.sessions")).action(() => sessions());
|
|
4655
4901
|
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));
|
|
4656
4902
|
program.command("review").argument("<pr>", t("cli.arg.reviewPr")).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.review")).action((pr, opts) => review(pr, opts));
|
|
4657
4903
|
return program;
|
|
4658
4904
|
}
|
|
4659
4905
|
async function main() {
|
|
4660
4906
|
try {
|
|
4661
|
-
loadDotenv([
|
|
4907
|
+
loadDotenv([join8(configDir(), ".env"), join8(process.cwd(), ".env")]);
|
|
4662
4908
|
await resolveLocale();
|
|
4663
4909
|
await buildProgram().parseAsync(process.argv);
|
|
4664
4910
|
} catch (err) {
|
|
4665
|
-
console.error(
|
|
4911
|
+
console.error(pc15.red(`\u2717 ${err.message}`));
|
|
4666
4912
|
process.exitCode = 1;
|
|
4667
4913
|
}
|
|
4668
4914
|
}
|