@gaberrb/polypus 0.4.10 → 0.4.12
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 +338 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -143,6 +143,7 @@ var en = {
|
|
|
143
143
|
"run.autocorrect": "\u21BB tool failed \u2014 auto-correcting with extra context",
|
|
144
144
|
"run.cancelled": "\u25A0 cancelled",
|
|
145
145
|
"compaction.done": "context compacted: ~{before} \u2192 ~{after} tokens",
|
|
146
|
+
"tools.customLoaded": "loaded custom tool(s): {names}",
|
|
146
147
|
"run.jsonNeedsTask": "--json requires a task argument (headless mode has no interactive REPL).",
|
|
147
148
|
"review.approveAll": "approve all",
|
|
148
149
|
"review.reject": "reject",
|
|
@@ -204,6 +205,7 @@ var en = {
|
|
|
204
205
|
"swarm.needsAgents": "Swarm mode needs at least {min} configured agents (you have {have}). Add more with `polypus add-agent`, or use `polypus run` for a single agent.",
|
|
205
206
|
"swarm.status": "swarm agents=[{agents}] workspace={workspace}",
|
|
206
207
|
"swarm.bypassNote": "Workers run in bypass mode inside isolated git worktrees; branches are merged at the end.",
|
|
208
|
+
"swarm.cancelling": "cancelling swarm \u2014 finishing in-flight workers, then merging what committed\u2026",
|
|
207
209
|
"swarm.decomposed": "Decomposed into {n} subtask(s):",
|
|
208
210
|
"swarm.workerStart": "\u25B6 {id} started by {agent}",
|
|
209
211
|
"swarm.workerDone": "\u2713 {id} done",
|
|
@@ -403,6 +405,7 @@ var ptBR = {
|
|
|
403
405
|
"run.autocorrect": "\u21BB tool falhou \u2014 autocorrigindo com contexto extra",
|
|
404
406
|
"run.cancelled": "\u25A0 cancelado",
|
|
405
407
|
"compaction.done": "contexto compactado: ~{before} \u2192 ~{after} tokens",
|
|
408
|
+
"tools.customLoaded": "tool(s) customizada(s) carregada(s): {names}",
|
|
406
409
|
"run.jsonNeedsTask": "--json exige um argumento de tarefa (o modo headless n\xE3o tem REPL interativo).",
|
|
407
410
|
"review.approveAll": "aprovar tudo",
|
|
408
411
|
"review.reject": "rejeitar",
|
|
@@ -462,6 +465,7 @@ var ptBR = {
|
|
|
462
465
|
"swarm.needsAgents": "O modo swarm precisa de pelo menos {min} agentes configurados (voc\xEA tem {have}). Adicione mais com `polypus add-agent`, ou use `polypus run` para um agente s\xF3.",
|
|
463
466
|
"swarm.status": "swarm agentes=[{agents}] workspace={workspace}",
|
|
464
467
|
"swarm.bypassNote": "Os workers rodam em modo bypass dentro de git worktrees isoladas; os branches s\xE3o mesclados no final.",
|
|
468
|
+
"swarm.cancelling": "cancelando o swarm \u2014 encerrando os workers em andamento e mesclando o que commitou\u2026",
|
|
465
469
|
"swarm.decomposed": "Dividido em {n} subtarefa(s):",
|
|
466
470
|
"swarm.workerStart": "\u25B6 {id} iniciada por {agent}",
|
|
467
471
|
"swarm.workerDone": "\u2713 {id} conclu\xEDda",
|
|
@@ -2144,6 +2148,66 @@ ${summary.content.trim()}`
|
|
|
2144
2148
|
return system ? [system, summaryMessage, ...tail] : [summaryMessage, ...tail];
|
|
2145
2149
|
}
|
|
2146
2150
|
|
|
2151
|
+
// src/core/agent/hooks.ts
|
|
2152
|
+
import { exec as exec2 } from "child_process";
|
|
2153
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2154
|
+
import { join as join4 } from "path";
|
|
2155
|
+
import { promisify as promisify2 } from "util";
|
|
2156
|
+
import { z as z8 } from "zod";
|
|
2157
|
+
var execAsync2 = promisify2(exec2);
|
|
2158
|
+
var HOOK_TIMEOUT = 12e4;
|
|
2159
|
+
var HooksSchema = z8.object({
|
|
2160
|
+
/** Shell command run after a successful write_file. `{path}` is substituted. */
|
|
2161
|
+
afterWrite: z8.string().optional(),
|
|
2162
|
+
/** Shell command run after a successful edit_file. `{path}` is substituted. */
|
|
2163
|
+
afterEdit: z8.string().optional(),
|
|
2164
|
+
/** Shell command run after any successful mutating tool. `{tool}`/`{path}` substituted. */
|
|
2165
|
+
afterTool: z8.string().optional(),
|
|
2166
|
+
/** Block run_command when the command contains any of these substrings. */
|
|
2167
|
+
beforeCommand: z8.object({ deny: z8.array(z8.string()).default([]) }).optional()
|
|
2168
|
+
});
|
|
2169
|
+
async function loadHooks(workspace) {
|
|
2170
|
+
try {
|
|
2171
|
+
const raw = await readFile8(join4(workspace, ".poly", "hooks.json"), "utf8");
|
|
2172
|
+
const parsed = HooksSchema.safeParse(JSON.parse(raw));
|
|
2173
|
+
return parsed.success ? parsed.data : void 0;
|
|
2174
|
+
} catch {
|
|
2175
|
+
return void 0;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
function screenCommandHook(hooks, command) {
|
|
2179
|
+
const deny = hooks?.beforeCommand?.deny ?? [];
|
|
2180
|
+
for (const needle of deny) {
|
|
2181
|
+
if (needle && command.includes(needle)) {
|
|
2182
|
+
return { blocked: true, reason: `matches deny rule "${needle}"` };
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return { blocked: false };
|
|
2186
|
+
}
|
|
2187
|
+
function substitute(template, call) {
|
|
2188
|
+
const path = typeof call.arguments.path === "string" ? call.arguments.path : "";
|
|
2189
|
+
return template.replace(/\{path\}/g, path).replace(/\{tool\}/g, call.name);
|
|
2190
|
+
}
|
|
2191
|
+
async function runAfterHook(hooks, call, workspace) {
|
|
2192
|
+
if (!hooks) return void 0;
|
|
2193
|
+
const commands = [];
|
|
2194
|
+
if (call.name === "write_file" && hooks.afterWrite) commands.push({ label: "afterWrite", cmd: hooks.afterWrite });
|
|
2195
|
+
if (call.name === "edit_file" && hooks.afterEdit) commands.push({ label: "afterEdit", cmd: hooks.afterEdit });
|
|
2196
|
+
if (hooks.afterTool) commands.push({ label: "afterTool", cmd: hooks.afterTool });
|
|
2197
|
+
if (commands.length === 0) return void 0;
|
|
2198
|
+
const notes = [];
|
|
2199
|
+
for (const { label, cmd } of commands) {
|
|
2200
|
+
const resolved = substitute(cmd, call);
|
|
2201
|
+
try {
|
|
2202
|
+
await execAsync2(resolved, { cwd: workspace, timeout: HOOK_TIMEOUT, windowsHide: true });
|
|
2203
|
+
notes.push(`\u21AA hook ${label} ok`);
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
notes.push(`\u21AA hook ${label} failed: ${err.message.split("\n")[0]}`);
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
return notes.join("\n");
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2147
2211
|
// src/core/agent/loop.ts
|
|
2148
2212
|
function looksLikeStall(text2) {
|
|
2149
2213
|
const lc = text2.toLowerCase();
|
|
@@ -2184,7 +2248,13 @@ async function runAgent(opts) {
|
|
|
2184
2248
|
const { agent, permissions, events } = opts;
|
|
2185
2249
|
const maxSteps = opts.maxSteps ?? 30;
|
|
2186
2250
|
const maxReprompts = opts.maxReprompts ?? 3;
|
|
2187
|
-
const
|
|
2251
|
+
const extra = opts.extraTools ?? [];
|
|
2252
|
+
const extraByName = new Map(extra.map((tl) => [tl.spec.name, tl]));
|
|
2253
|
+
const baseSpecs = toolSpecs();
|
|
2254
|
+
const finishSpec = baseSpecs[baseSpecs.length - 1];
|
|
2255
|
+
const allSpecs = [...baseSpecs.slice(0, -1), ...extra.map((tl) => tl.spec), finishSpec];
|
|
2256
|
+
const resolveTool = (name) => extraByName.get(name) ?? getTool(name);
|
|
2257
|
+
const driver = makeDriver(agent.toolMode, allSpecs);
|
|
2188
2258
|
const ctx = { workspace: opts.workspace, permissions };
|
|
2189
2259
|
const seeding = !(opts.history && opts.history.length > 0);
|
|
2190
2260
|
const promptContext = seeding && opts.promptContext.projectInstructions === void 0 ? { ...opts.promptContext, projectInstructions: await loadProjectInstructions(opts.workspace) } : opts.promptContext;
|
|
@@ -2265,8 +2335,21 @@ async function runAgent(opts) {
|
|
|
2265
2335
|
const summary = String(call.arguments.summary ?? "").trim();
|
|
2266
2336
|
return { finished: true, reason: "finished", summary, steps: step, messages, usage: usage2 };
|
|
2267
2337
|
}
|
|
2268
|
-
const tool =
|
|
2269
|
-
const
|
|
2338
|
+
const tool = resolveTool(call.name);
|
|
2339
|
+
const hookScreen = call.name === "run_command" ? screenCommandHook(opts.hooks, String(call.arguments.command ?? "")) : { blocked: false };
|
|
2340
|
+
let result;
|
|
2341
|
+
if (hookScreen.blocked) {
|
|
2342
|
+
result = { ok: false, output: `Command blocked by hook: ${hookScreen.reason}` };
|
|
2343
|
+
} else if (tool) {
|
|
2344
|
+
result = await tool.run(call.arguments, ctx);
|
|
2345
|
+
if (result.ok) {
|
|
2346
|
+
const note2 = await runAfterHook(opts.hooks, call, opts.workspace);
|
|
2347
|
+
if (note2) result = { ...result, output: `${result.output}
|
|
2348
|
+
${note2}` };
|
|
2349
|
+
}
|
|
2350
|
+
} else {
|
|
2351
|
+
result = { ok: false, output: `Unknown tool "${call.name}". Available: ${allSpecs.map((t2) => t2.name).join(", ")}` };
|
|
2352
|
+
}
|
|
2270
2353
|
events?.onToolResult?.(call, result);
|
|
2271
2354
|
const sig = `${call.name}:${JSON.stringify(call.arguments)}`;
|
|
2272
2355
|
let resultText = result.output;
|
|
@@ -2310,7 +2393,7 @@ ${guidance}`;
|
|
|
2310
2393
|
}
|
|
2311
2394
|
|
|
2312
2395
|
// src/core/context/mentions.ts
|
|
2313
|
-
import { readdir as readdir4, readFile as
|
|
2396
|
+
import { readdir as readdir4, readFile as readFile9, stat as stat2 } from "fs/promises";
|
|
2314
2397
|
import { resolve as resolve9 } from "path";
|
|
2315
2398
|
var MAX_FILE_CHARS = 1e4;
|
|
2316
2399
|
var MENTION_RE = /(?:^|\s)@([\w./-]+)/g;
|
|
@@ -2337,7 +2420,7 @@ ${t("mentions.notFound", { path: token })}`);
|
|
|
2337
2420
|
${listing || "(empty)"}`);
|
|
2338
2421
|
injected.push(decision.rel);
|
|
2339
2422
|
} else {
|
|
2340
|
-
const raw = await
|
|
2423
|
+
const raw = await readFile9(abs, "utf8");
|
|
2341
2424
|
const content = raw.length > MAX_FILE_CHARS ? raw.slice(0, MAX_FILE_CHARS) + "\n\u2026[truncated]" : raw;
|
|
2342
2425
|
blocks.push(`## @${decision.rel}
|
|
2343
2426
|
\`\`\`
|
|
@@ -2360,16 +2443,16 @@ ${blocks.join("\n\n")}`;
|
|
|
2360
2443
|
}
|
|
2361
2444
|
|
|
2362
2445
|
// src/core/agent/verify.ts
|
|
2363
|
-
import { exec as
|
|
2364
|
-
import { readFile as
|
|
2446
|
+
import { exec as exec3 } from "child_process";
|
|
2447
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
2365
2448
|
import { resolve as resolve10 } from "path";
|
|
2366
|
-
import { promisify as
|
|
2367
|
-
var
|
|
2449
|
+
import { promisify as promisify3 } from "util";
|
|
2450
|
+
var execAsync3 = promisify3(exec3);
|
|
2368
2451
|
var MAX_OUTPUT3 = 8e3;
|
|
2369
2452
|
var CHECK_SCRIPTS = ["typecheck", "build", "test"];
|
|
2370
2453
|
async function detectChecks(workspace) {
|
|
2371
2454
|
try {
|
|
2372
|
-
const raw = await
|
|
2455
|
+
const raw = await readFile10(resolve10(workspace, "package.json"), "utf8");
|
|
2373
2456
|
const scripts = JSON.parse(raw).scripts ?? {};
|
|
2374
2457
|
return CHECK_SCRIPTS.filter((s) => typeof scripts[s] === "string").map((s) => `npm run ${s}`);
|
|
2375
2458
|
} catch {
|
|
@@ -2380,7 +2463,7 @@ async function runChecks(workspace, commands) {
|
|
|
2380
2463
|
const results = [];
|
|
2381
2464
|
for (const command of commands) {
|
|
2382
2465
|
try {
|
|
2383
|
-
const { stdout: stdout2, stderr } = await
|
|
2466
|
+
const { stdout: stdout2, stderr } = await execAsync3(command, {
|
|
2384
2467
|
cwd: workspace,
|
|
2385
2468
|
timeout: 3e5,
|
|
2386
2469
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -2412,8 +2495,8 @@ function clamp2(s) {
|
|
|
2412
2495
|
}
|
|
2413
2496
|
|
|
2414
2497
|
// src/core/agent/usage.ts
|
|
2415
|
-
import { appendFile, mkdir as mkdir3, readFile as
|
|
2416
|
-
import { join as
|
|
2498
|
+
import { appendFile, mkdir as mkdir3, readFile as readFile11 } from "fs/promises";
|
|
2499
|
+
import { join as join5 } from "path";
|
|
2417
2500
|
|
|
2418
2501
|
// src/core/providers/openrouter.ts
|
|
2419
2502
|
var MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
@@ -2513,7 +2596,7 @@ function fmtUsd(n) {
|
|
|
2513
2596
|
return `US$${n.toFixed(2)}`;
|
|
2514
2597
|
}
|
|
2515
2598
|
function usagePath() {
|
|
2516
|
-
return
|
|
2599
|
+
return join5(configDir(), "usage.jsonl");
|
|
2517
2600
|
}
|
|
2518
2601
|
async function recordUsage(entry) {
|
|
2519
2602
|
try {
|
|
@@ -2525,7 +2608,7 @@ async function recordUsage(entry) {
|
|
|
2525
2608
|
async function aggregateUsage() {
|
|
2526
2609
|
let text2 = "";
|
|
2527
2610
|
try {
|
|
2528
|
-
text2 = await
|
|
2611
|
+
text2 = await readFile11(usagePath(), "utf8");
|
|
2529
2612
|
} catch {
|
|
2530
2613
|
return { days: [], total: emptyBucket("total") };
|
|
2531
2614
|
}
|
|
@@ -2559,10 +2642,10 @@ function accumulate(bucket, e) {
|
|
|
2559
2642
|
}
|
|
2560
2643
|
|
|
2561
2644
|
// src/core/agent/session-store.ts
|
|
2562
|
-
import { mkdir as mkdir4, readFile as
|
|
2563
|
-
import { join as
|
|
2645
|
+
import { mkdir as mkdir4, readFile as readFile12, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
|
|
2646
|
+
import { join as join6 } from "path";
|
|
2564
2647
|
function sessionsDir() {
|
|
2565
|
-
return
|
|
2648
|
+
return join6(configDir(), "sessions");
|
|
2566
2649
|
}
|
|
2567
2650
|
function newSessionId() {
|
|
2568
2651
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -2570,7 +2653,7 @@ function newSessionId() {
|
|
|
2570
2653
|
return `${stamp}-${rand}`;
|
|
2571
2654
|
}
|
|
2572
2655
|
function sessionPath(id) {
|
|
2573
|
-
return
|
|
2656
|
+
return join6(sessionsDir(), `${id}.json`);
|
|
2574
2657
|
}
|
|
2575
2658
|
async function saveSession(record) {
|
|
2576
2659
|
await mkdir4(sessionsDir(), { recursive: true });
|
|
@@ -2582,7 +2665,7 @@ async function saveSession(record) {
|
|
|
2582
2665
|
}
|
|
2583
2666
|
async function loadSession(id) {
|
|
2584
2667
|
try {
|
|
2585
|
-
return JSON.parse(await
|
|
2668
|
+
return JSON.parse(await readFile12(sessionPath(id), "utf8"));
|
|
2586
2669
|
} catch {
|
|
2587
2670
|
return void 0;
|
|
2588
2671
|
}
|
|
@@ -2597,7 +2680,7 @@ async function listSessions() {
|
|
|
2597
2680
|
const summaries = [];
|
|
2598
2681
|
for (const f of files) {
|
|
2599
2682
|
try {
|
|
2600
|
-
const r = JSON.parse(await
|
|
2683
|
+
const r = JSON.parse(await readFile12(join6(sessionsDir(), f), "utf8"));
|
|
2601
2684
|
summaries.push({
|
|
2602
2685
|
id: r.id,
|
|
2603
2686
|
updatedAt: r.updatedAt,
|
|
@@ -2621,6 +2704,83 @@ function deriveTitle(messages) {
|
|
|
2621
2704
|
return text2.length > 60 ? text2.slice(0, 60) + "\u2026" : text2 || "(untitled)";
|
|
2622
2705
|
}
|
|
2623
2706
|
|
|
2707
|
+
// src/core/tools/custom.ts
|
|
2708
|
+
import { exec as exec4 } from "child_process";
|
|
2709
|
+
import { readFile as readFile13, readdir as readdir6 } from "fs/promises";
|
|
2710
|
+
import { join as join7 } from "path";
|
|
2711
|
+
import { promisify as promisify4 } from "util";
|
|
2712
|
+
import { z as z9 } from "zod";
|
|
2713
|
+
var execAsync4 = promisify4(exec4);
|
|
2714
|
+
var MAX_OUTPUT4 = 2e4;
|
|
2715
|
+
var CustomToolSchema = z9.object({
|
|
2716
|
+
name: z9.string().min(1).regex(/^[a-z][a-z0-9_]*$/i, "tool name must be alphanumeric/underscore"),
|
|
2717
|
+
description: z9.string().min(1),
|
|
2718
|
+
/** JSON-schema object for the tool parameters (advertised to the model). */
|
|
2719
|
+
parameters: z9.record(z9.unknown()).optional(),
|
|
2720
|
+
/** Shell command template; `{argName}` placeholders are filled from the call arguments. */
|
|
2721
|
+
command: z9.string().min(1)
|
|
2722
|
+
});
|
|
2723
|
+
function makeCommandTool(def) {
|
|
2724
|
+
return {
|
|
2725
|
+
mutating: true,
|
|
2726
|
+
spec: {
|
|
2727
|
+
name: def.name,
|
|
2728
|
+
description: def.description,
|
|
2729
|
+
parameters: def.parameters ?? { type: "object", properties: {} }
|
|
2730
|
+
},
|
|
2731
|
+
async run(rawArgs, ctx) {
|
|
2732
|
+
const command = fillTemplate(def.command, rawArgs);
|
|
2733
|
+
const decision = await ctx.permissions.authorizeCommand(command);
|
|
2734
|
+
if (!decision.allowed) return { ok: false, output: `Command denied: ${decision.reason}` };
|
|
2735
|
+
try {
|
|
2736
|
+
const { stdout: stdout2, stderr } = await execAsync4(command, {
|
|
2737
|
+
cwd: ctx.workspace,
|
|
2738
|
+
timeout: 12e4,
|
|
2739
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
2740
|
+
windowsHide: true
|
|
2741
|
+
});
|
|
2742
|
+
return { ok: true, output: clamp3(`${stdout2}${stderr ? `
|
|
2743
|
+
[stderr]
|
|
2744
|
+
${stderr}` : ""}`.trim() || "(no output)") };
|
|
2745
|
+
} catch (err) {
|
|
2746
|
+
const e = err;
|
|
2747
|
+
return {
|
|
2748
|
+
ok: false,
|
|
2749
|
+
output: clamp3(`Command failed (exit ${e.code ?? "?"}): ${e.message}
|
|
2750
|
+
${e.stdout ?? ""}${e.stderr ?? ""}`)
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
async function loadCustomTools(workspace) {
|
|
2757
|
+
let files;
|
|
2758
|
+
try {
|
|
2759
|
+
files = (await readdir6(join7(workspace, ".poly", "tools"))).filter((f) => f.endsWith(".json"));
|
|
2760
|
+
} catch {
|
|
2761
|
+
return [];
|
|
2762
|
+
}
|
|
2763
|
+
const tools = [];
|
|
2764
|
+
for (const f of files) {
|
|
2765
|
+
try {
|
|
2766
|
+
const raw = await readFile13(join7(workspace, ".poly", "tools", f), "utf8");
|
|
2767
|
+
const parsed = CustomToolSchema.safeParse(JSON.parse(raw));
|
|
2768
|
+
if (parsed.success) tools.push(makeCommandTool(parsed.data));
|
|
2769
|
+
} catch {
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
return tools;
|
|
2773
|
+
}
|
|
2774
|
+
function fillTemplate(template, args) {
|
|
2775
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
2776
|
+
const v = args[key];
|
|
2777
|
+
return v === void 0 || v === null ? "" : String(v);
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
function clamp3(s) {
|
|
2781
|
+
return s.length > MAX_OUTPUT4 ? s.slice(0, MAX_OUTPUT4) + "\n\u2026[truncated]" : s;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2624
2784
|
// src/cli/commands/json-output.ts
|
|
2625
2785
|
var OUTPUT_PREVIEW = 500;
|
|
2626
2786
|
function createJsonCollector() {
|
|
@@ -3419,7 +3579,7 @@ import pc7 from "picocolors";
|
|
|
3419
3579
|
// src/core/git/worktree.ts
|
|
3420
3580
|
import { mkdtemp } from "fs/promises";
|
|
3421
3581
|
import { tmpdir } from "os";
|
|
3422
|
-
import { join as
|
|
3582
|
+
import { join as join8 } from "path";
|
|
3423
3583
|
import { simpleGit } from "simple-git";
|
|
3424
3584
|
async function ensureRepo(workspace) {
|
|
3425
3585
|
const git = simpleGit(workspace);
|
|
@@ -3440,7 +3600,7 @@ async function identityArgs(git) {
|
|
|
3440
3600
|
}
|
|
3441
3601
|
async function createWorktree(git, label) {
|
|
3442
3602
|
const branch = `polypus/${label}-${Date.now().toString(36)}`;
|
|
3443
|
-
const path = await mkdtemp(
|
|
3603
|
+
const path = await mkdtemp(join8(tmpdir(), "polypus-wt-"));
|
|
3444
3604
|
await git.raw(["worktree", "add", "-b", branch, path, "HEAD"]);
|
|
3445
3605
|
return { path, branch };
|
|
3446
3606
|
}
|
|
@@ -3474,7 +3634,7 @@ async function removeWorktree(git, wt) {
|
|
|
3474
3634
|
}
|
|
3475
3635
|
|
|
3476
3636
|
// src/core/agent/worker.ts
|
|
3477
|
-
async function runWorker(subtask, agent, wt, allow, deny, events) {
|
|
3637
|
+
async function runWorker(subtask, agent, wt, allow, deny, events, signal) {
|
|
3478
3638
|
const permissions = new PermissionEngine({
|
|
3479
3639
|
mode: "bypass",
|
|
3480
3640
|
policy: { workspace: wt.path, allow, deny },
|
|
@@ -3486,9 +3646,10 @@ async function runWorker(subtask, agent, wt, allow, deny, events) {
|
|
|
3486
3646
|
agent,
|
|
3487
3647
|
permissions,
|
|
3488
3648
|
promptContext: { workspace: wt.path, mode: "bypass", allow, briefing: subtask.brief },
|
|
3489
|
-
events
|
|
3649
|
+
events,
|
|
3650
|
+
signal
|
|
3490
3651
|
});
|
|
3491
|
-
const committed = await commitWorktree(wt, `polypus(${subtask.id}): ${subtask.title}`);
|
|
3652
|
+
const committed = result.reason === "cancelled" ? false : await commitWorktree(wt, `polypus(${subtask.id}): ${subtask.title}`);
|
|
3492
3653
|
return {
|
|
3493
3654
|
subtask,
|
|
3494
3655
|
agentName: agent.config.name,
|
|
@@ -3505,30 +3666,23 @@ async function runSwarm(opts) {
|
|
|
3505
3666
|
const lead = opts.agents[0];
|
|
3506
3667
|
if (!lead) throw new Error("Swarm requires at least one agent.");
|
|
3507
3668
|
const maxSubtasks = opts.maxSubtasks ?? Math.max(opts.agents.length, 2);
|
|
3669
|
+
const concurrency = Math.max(1, opts.concurrency ?? opts.agents.length);
|
|
3670
|
+
const idleTimeoutMs2 = opts.idleTimeoutMs ?? 0;
|
|
3508
3671
|
const git = await ensureRepo(opts.workspace);
|
|
3509
|
-
const subtasks = await decompose(lead, opts.task, maxSubtasks);
|
|
3672
|
+
const subtasks = await decompose(lead, opts.task, maxSubtasks, opts.signal);
|
|
3510
3673
|
opts.events?.onDecomposed?.(subtasks);
|
|
3511
3674
|
const worktrees = [];
|
|
3512
3675
|
for (const subtask of subtasks) {
|
|
3513
3676
|
worktrees.push(await createWorktree(git, subtask.id));
|
|
3514
3677
|
}
|
|
3515
|
-
const
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
wt,
|
|
3524
|
-
opts.allow,
|
|
3525
|
-
opts.deny,
|
|
3526
|
-
opts.events?.workerEvents?.(subtask)
|
|
3527
|
-
);
|
|
3528
|
-
opts.events?.onWorkerDone?.(outcome);
|
|
3529
|
-
return outcome;
|
|
3530
|
-
})
|
|
3531
|
-
);
|
|
3678
|
+
const runOne = (subtask, i) => guardedWorker(subtask, opts.agents[i % opts.agents.length], worktrees[i], {
|
|
3679
|
+
allow: opts.allow,
|
|
3680
|
+
deny: opts.deny,
|
|
3681
|
+
idleTimeoutMs: idleTimeoutMs2,
|
|
3682
|
+
signal: opts.signal,
|
|
3683
|
+
events: opts.events
|
|
3684
|
+
});
|
|
3685
|
+
const outcomes = await runPool(subtasks, concurrency, runOne);
|
|
3532
3686
|
const merges = [];
|
|
3533
3687
|
for (const outcome of outcomes) {
|
|
3534
3688
|
if (!outcome.committed) continue;
|
|
@@ -3546,13 +3700,69 @@ async function runSwarm(opts) {
|
|
|
3546
3700
|
}
|
|
3547
3701
|
return { subtasks, outcomes, merges };
|
|
3548
3702
|
}
|
|
3703
|
+
async function guardedWorker(subtask, agent, wt, opts) {
|
|
3704
|
+
const ac = new AbortController();
|
|
3705
|
+
const onAbort = () => ac.abort();
|
|
3706
|
+
if (opts.signal) {
|
|
3707
|
+
if (opts.signal.aborted) ac.abort();
|
|
3708
|
+
else opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
3709
|
+
}
|
|
3710
|
+
let idleTimer;
|
|
3711
|
+
const resetIdle = () => {
|
|
3712
|
+
if (opts.idleTimeoutMs <= 0) return;
|
|
3713
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3714
|
+
idleTimer = setTimeout(() => ac.abort(), opts.idleTimeoutMs);
|
|
3715
|
+
idleTimer.unref?.();
|
|
3716
|
+
};
|
|
3717
|
+
const base = opts.events?.workerEvents?.(subtask);
|
|
3718
|
+
const events = {
|
|
3719
|
+
...base,
|
|
3720
|
+
onStep: (n) => {
|
|
3721
|
+
resetIdle();
|
|
3722
|
+
base?.onStep?.(n);
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
opts.events?.onWorkerStart?.(subtask, agent.config.name);
|
|
3726
|
+
resetIdle();
|
|
3727
|
+
let outcome;
|
|
3728
|
+
try {
|
|
3729
|
+
outcome = await runWorker(subtask, agent, wt, opts.allow, opts.deny, events, ac.signal);
|
|
3730
|
+
} catch {
|
|
3731
|
+
outcome = {
|
|
3732
|
+
subtask,
|
|
3733
|
+
agentName: agent.config.name,
|
|
3734
|
+
branch: wt.branch,
|
|
3735
|
+
finished: false,
|
|
3736
|
+
committed: false,
|
|
3737
|
+
steps: 0
|
|
3738
|
+
};
|
|
3739
|
+
} finally {
|
|
3740
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
3741
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
3742
|
+
}
|
|
3743
|
+
opts.events?.onWorkerDone?.(outcome);
|
|
3744
|
+
return outcome;
|
|
3745
|
+
}
|
|
3746
|
+
async function runPool(items, limit, fn) {
|
|
3747
|
+
const results = new Array(items.length);
|
|
3748
|
+
let next = 0;
|
|
3749
|
+
const worker = async () => {
|
|
3750
|
+
for (; ; ) {
|
|
3751
|
+
const i = next++;
|
|
3752
|
+
if (i >= items.length) return;
|
|
3753
|
+
results[i] = await fn(items[i], i);
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
3757
|
+
return results;
|
|
3758
|
+
}
|
|
3549
3759
|
var DECOMPOSE_SYSTEM = [
|
|
3550
3760
|
"You are a tech lead splitting a coding task into independent subtasks that can be done in parallel.",
|
|
3551
3761
|
'Return ONLY a JSON array. Each item: {"title": string, "brief": string}.',
|
|
3552
3762
|
"Make subtasks touch DIFFERENT files/areas to minimize merge conflicts.",
|
|
3553
3763
|
"Keep the list small (prefer 2-4 items). Each brief must be self-contained and actionable."
|
|
3554
3764
|
].join("\n");
|
|
3555
|
-
async function decompose(lead, task, maxSubtasks) {
|
|
3765
|
+
async function decompose(lead, task, maxSubtasks, signal) {
|
|
3556
3766
|
try {
|
|
3557
3767
|
const res = await lead.provider.chat({
|
|
3558
3768
|
messages: [
|
|
@@ -3562,7 +3772,8 @@ ${task}
|
|
|
3562
3772
|
|
|
3563
3773
|
Return at most ${maxSubtasks} subtasks as a JSON array.` }
|
|
3564
3774
|
],
|
|
3565
|
-
params: { temperature: 0 }
|
|
3775
|
+
params: { temperature: 0 },
|
|
3776
|
+
signal
|
|
3566
3777
|
});
|
|
3567
3778
|
const parsed = extractJsonArray(res.content);
|
|
3568
3779
|
if (parsed && parsed.length > 0) {
|
|
@@ -3588,6 +3799,28 @@ function extractJsonArray(text2) {
|
|
|
3588
3799
|
}
|
|
3589
3800
|
}
|
|
3590
3801
|
|
|
3802
|
+
// src/core/agent/concurrency.ts
|
|
3803
|
+
var OLLAMA_ENDPOINT_CONCURRENCY = 2;
|
|
3804
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 3e5;
|
|
3805
|
+
function endpointKey(agent) {
|
|
3806
|
+
return `${agent.config.provider}:${agent.config.baseUrl ?? "default"}`;
|
|
3807
|
+
}
|
|
3808
|
+
function recommendConcurrency(agents) {
|
|
3809
|
+
const perEndpoint = /* @__PURE__ */ new Map();
|
|
3810
|
+
for (const a of agents) {
|
|
3811
|
+
perEndpoint.set(endpointKey(a), (perEndpoint.get(endpointKey(a)) ?? 0) + 1);
|
|
3812
|
+
}
|
|
3813
|
+
let total = 0;
|
|
3814
|
+
for (const [key, count] of perEndpoint) {
|
|
3815
|
+
total += key.startsWith("ollama:") ? Math.min(count, OLLAMA_ENDPOINT_CONCURRENCY) : count;
|
|
3816
|
+
}
|
|
3817
|
+
return Math.max(1, total);
|
|
3818
|
+
}
|
|
3819
|
+
function idleTimeoutMs() {
|
|
3820
|
+
const raw = Number(process.env.POLYPUS_SWARM_IDLE_TIMEOUT_MS);
|
|
3821
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_IDLE_TIMEOUT_MS;
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3591
3824
|
// src/ui/swarm-view.ts
|
|
3592
3825
|
var RESET2 = "\x1B[0m";
|
|
3593
3826
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -3751,6 +3984,35 @@ function pad(s, n) {
|
|
|
3751
3984
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
3752
3985
|
}
|
|
3753
3986
|
|
|
3987
|
+
// src/ui/cancel.ts
|
|
3988
|
+
function listenForCancel(controller) {
|
|
3989
|
+
const stdin2 = process.stdin;
|
|
3990
|
+
if (!stdin2.isTTY) return { pause() {
|
|
3991
|
+
}, resume() {
|
|
3992
|
+
}, dispose() {
|
|
3993
|
+
} };
|
|
3994
|
+
const onData = (buf) => {
|
|
3995
|
+
if (buf.length === 1 && (buf[0] === 27 || buf[0] === 3)) controller.abort();
|
|
3996
|
+
};
|
|
3997
|
+
let active = false;
|
|
3998
|
+
const attach = () => {
|
|
3999
|
+
if (active) return;
|
|
4000
|
+
stdin2.setRawMode(true);
|
|
4001
|
+
stdin2.resume();
|
|
4002
|
+
stdin2.on("data", onData);
|
|
4003
|
+
active = true;
|
|
4004
|
+
};
|
|
4005
|
+
const detach = () => {
|
|
4006
|
+
if (!active) return;
|
|
4007
|
+
stdin2.off("data", onData);
|
|
4008
|
+
stdin2.setRawMode(false);
|
|
4009
|
+
stdin2.pause();
|
|
4010
|
+
active = false;
|
|
4011
|
+
};
|
|
4012
|
+
attach();
|
|
4013
|
+
return { pause: detach, resume: attach, dispose: detach };
|
|
4014
|
+
}
|
|
4015
|
+
|
|
3754
4016
|
// src/cli/commands/swarm.ts
|
|
3755
4017
|
var MIN_SWARM_AGENTS = 3;
|
|
3756
4018
|
function canSwarm(agentCount) {
|
|
@@ -3774,6 +4036,11 @@ async function runSwarmSession(task, config, opts = {}) {
|
|
|
3774
4036
|
pc7.dim(t("swarm.status", { agents: resolved.map((a) => a.config.name).join(", "), workspace }))
|
|
3775
4037
|
);
|
|
3776
4038
|
console.log(pc7.yellow(t("swarm.bypassNote") + "\n"));
|
|
4039
|
+
const controller = new AbortController();
|
|
4040
|
+
const cancel2 = listenForCancel(controller);
|
|
4041
|
+
controller.signal.addEventListener("abort", () => console.log(pc7.dim("\n" + t("swarm.cancelling"))), {
|
|
4042
|
+
once: true
|
|
4043
|
+
});
|
|
3777
4044
|
const view = new SwarmView(resolved[0].config.name);
|
|
3778
4045
|
view.start();
|
|
3779
4046
|
let result;
|
|
@@ -3785,6 +4052,9 @@ async function runSwarmSession(task, config, opts = {}) {
|
|
|
3785
4052
|
allow: config.permissions.allow,
|
|
3786
4053
|
deny: config.permissions.deny,
|
|
3787
4054
|
maxSubtasks: opts.maxSubtasks,
|
|
4055
|
+
concurrency: recommendConcurrency(resolved),
|
|
4056
|
+
idleTimeoutMs: idleTimeoutMs(),
|
|
4057
|
+
signal: controller.signal,
|
|
3788
4058
|
events: {
|
|
3789
4059
|
onDecomposed: (subtasks) => view.setSubtasks(subtasks),
|
|
3790
4060
|
onWorkerStart: (subtask, agentName) => view.workerStart(subtask.id, agentName),
|
|
@@ -3798,6 +4068,7 @@ async function runSwarmSession(task, config, opts = {}) {
|
|
|
3798
4068
|
});
|
|
3799
4069
|
} finally {
|
|
3800
4070
|
view.stop();
|
|
4071
|
+
cancel2.dispose();
|
|
3801
4072
|
}
|
|
3802
4073
|
console.log("");
|
|
3803
4074
|
console.log(pc7.bold("\n" + t("swarm.summary")));
|
|
@@ -3991,6 +4262,10 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
3991
4262
|
return ok;
|
|
3992
4263
|
}
|
|
3993
4264
|
});
|
|
4265
|
+
const [extraTools, hooks] = await Promise.all([loadCustomTools(workspace), loadHooks(workspace)]);
|
|
4266
|
+
if (!json && extraTools.length > 0) {
|
|
4267
|
+
console.log(pc8.dim(t("tools.customLoaded", { names: extraTools.map((tl) => tl.spec.name).join(", ") })));
|
|
4268
|
+
}
|
|
3994
4269
|
const runOnce = (taskText) => runAgent({
|
|
3995
4270
|
task: taskText,
|
|
3996
4271
|
workspace,
|
|
@@ -4000,6 +4275,8 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
4000
4275
|
history: session.history,
|
|
4001
4276
|
maxSteps: session.maxSteps,
|
|
4002
4277
|
compactThresholdTokens: compactionThreshold(),
|
|
4278
|
+
extraTools,
|
|
4279
|
+
hooks,
|
|
4003
4280
|
signal: controller.signal,
|
|
4004
4281
|
events
|
|
4005
4282
|
});
|
|
@@ -4064,33 +4341,6 @@ async function executeTask(task, resolved, workspace, session, json = false, ver
|
|
|
4064
4341
|
function fmtTokens(n) {
|
|
4065
4342
|
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
4066
4343
|
}
|
|
4067
|
-
function listenForCancel(controller) {
|
|
4068
|
-
const stdin2 = process.stdin;
|
|
4069
|
-
if (!stdin2.isTTY) return { pause() {
|
|
4070
|
-
}, resume() {
|
|
4071
|
-
}, dispose() {
|
|
4072
|
-
} };
|
|
4073
|
-
const onData = (buf) => {
|
|
4074
|
-
if (buf.length === 1 && (buf[0] === 27 || buf[0] === 3)) controller.abort();
|
|
4075
|
-
};
|
|
4076
|
-
let active = false;
|
|
4077
|
-
const attach = () => {
|
|
4078
|
-
if (active) return;
|
|
4079
|
-
stdin2.setRawMode(true);
|
|
4080
|
-
stdin2.resume();
|
|
4081
|
-
stdin2.on("data", onData);
|
|
4082
|
-
active = true;
|
|
4083
|
-
};
|
|
4084
|
-
const detach = () => {
|
|
4085
|
-
if (!active) return;
|
|
4086
|
-
stdin2.off("data", onData);
|
|
4087
|
-
stdin2.setRawMode(false);
|
|
4088
|
-
stdin2.pause();
|
|
4089
|
-
active = false;
|
|
4090
|
-
};
|
|
4091
|
-
attach();
|
|
4092
|
-
return { pause: detach, resume: attach, dispose: detach };
|
|
4093
|
-
}
|
|
4094
4344
|
async function confirmAction(req) {
|
|
4095
4345
|
if (req.kind === "write" && req.hunks && req.hunks.length > 0) {
|
|
4096
4346
|
renderDiff(req.hunks);
|
|
@@ -4204,7 +4454,7 @@ import pc9 from "picocolors";
|
|
|
4204
4454
|
|
|
4205
4455
|
// src/core/scaffold/init.ts
|
|
4206
4456
|
import { mkdir as mkdir5, writeFile as writeFile5, access } from "fs/promises";
|
|
4207
|
-
import { dirname as dirname3, join as
|
|
4457
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
4208
4458
|
|
|
4209
4459
|
// src/core/scaffold/templates.ts
|
|
4210
4460
|
function polyTemplates(locale) {
|
|
@@ -4447,7 +4697,7 @@ async function scaffoldPoly(workspace, opts) {
|
|
|
4447
4697
|
const skipped = [];
|
|
4448
4698
|
for (const [rel, content] of Object.entries(templates)) {
|
|
4449
4699
|
const display = `.poly/${rel}`;
|
|
4450
|
-
const abs =
|
|
4700
|
+
const abs = join9(workspace, ".poly", ...rel.split("/"));
|
|
4451
4701
|
if (!opts.force && await exists(abs)) {
|
|
4452
4702
|
skipped.push(display);
|
|
4453
4703
|
continue;
|
|
@@ -4587,9 +4837,9 @@ async function sessions() {
|
|
|
4587
4837
|
}
|
|
4588
4838
|
|
|
4589
4839
|
// src/cli/commands/prd.ts
|
|
4590
|
-
import { writeFile as writeFile6, readFile as
|
|
4840
|
+
import { writeFile as writeFile6, readFile as readFile14 } from "fs/promises";
|
|
4591
4841
|
import { execFile } from "child_process";
|
|
4592
|
-
import { promisify as
|
|
4842
|
+
import { promisify as promisify5 } from "util";
|
|
4593
4843
|
import pc13 from "picocolors";
|
|
4594
4844
|
|
|
4595
4845
|
// src/core/agent/prd.ts
|
|
@@ -4711,7 +4961,7 @@ function stripBom(s) {
|
|
|
4711
4961
|
}
|
|
4712
4962
|
|
|
4713
4963
|
// src/cli/commands/prd.ts
|
|
4714
|
-
var
|
|
4964
|
+
var exec5 = promisify5(execFile);
|
|
4715
4965
|
async function prd(issueRef, opts) {
|
|
4716
4966
|
const issue = await loadIssue(issueRef, opts.input);
|
|
4717
4967
|
const { provider } = resolveFreeProvider(opts.model ?? DEFAULT_PRD_MODEL);
|
|
@@ -4726,11 +4976,11 @@ async function prd(issueRef, opts) {
|
|
|
4726
4976
|
}
|
|
4727
4977
|
async function loadIssue(issueRef, input) {
|
|
4728
4978
|
if (input) {
|
|
4729
|
-
const raw = input === "-" ? await readStdin() : await
|
|
4979
|
+
const raw = input === "-" ? await readStdin() : await readFile14(input, "utf8");
|
|
4730
4980
|
return normalize2(JSON.parse(stripBom(raw)));
|
|
4731
4981
|
}
|
|
4732
4982
|
const num = numericRef(issueRef);
|
|
4733
|
-
const { stdout: stdout2 } = await
|
|
4983
|
+
const { stdout: stdout2 } = await exec5("gh", ["issue", "view", num, "--json", "number,title,body,comments"]);
|
|
4734
4984
|
const data = normalize2(JSON.parse(stdout2));
|
|
4735
4985
|
data.number ??= Number(num);
|
|
4736
4986
|
return data;
|
|
@@ -4745,9 +4995,9 @@ function normalize2(raw) {
|
|
|
4745
4995
|
}
|
|
4746
4996
|
|
|
4747
4997
|
// src/cli/commands/review.ts
|
|
4748
|
-
import { writeFile as writeFile7, readFile as
|
|
4998
|
+
import { writeFile as writeFile7, readFile as readFile15 } from "fs/promises";
|
|
4749
4999
|
import { execFile as execFile2 } from "child_process";
|
|
4750
|
-
import { promisify as
|
|
5000
|
+
import { promisify as promisify6 } from "util";
|
|
4751
5001
|
import pc14 from "picocolors";
|
|
4752
5002
|
|
|
4753
5003
|
// src/core/agent/review.ts
|
|
@@ -4805,7 +5055,7 @@ ${projectGuide}`
|
|
|
4805
5055
|
}
|
|
4806
5056
|
|
|
4807
5057
|
// src/cli/commands/review.ts
|
|
4808
|
-
var
|
|
5058
|
+
var exec6 = promisify6(execFile2);
|
|
4809
5059
|
async function review(prRef, opts) {
|
|
4810
5060
|
const num = opts.input ? prRef.replace(/^#/, "") : numericRef(prRef);
|
|
4811
5061
|
const diff = await loadDiff(num, opts.input);
|
|
@@ -4821,19 +5071,19 @@ async function review(prRef, opts) {
|
|
|
4821
5071
|
}
|
|
4822
5072
|
}
|
|
4823
5073
|
async function loadDiff(num, input) {
|
|
4824
|
-
if (input) return input === "-" ? readStdin() :
|
|
4825
|
-
const { stdout: stdout2 } = await
|
|
5074
|
+
if (input) return input === "-" ? readStdin() : readFile15(input, "utf8");
|
|
5075
|
+
const { stdout: stdout2 } = await exec6("gh", ["pr", "diff", num]);
|
|
4826
5076
|
return stdout2;
|
|
4827
5077
|
}
|
|
4828
5078
|
async function loadMeta(num, input) {
|
|
4829
5079
|
if (input) return { number: Number(num) || void 0, title: `PR ${num}`, body: "" };
|
|
4830
|
-
const { stdout: stdout2 } = await
|
|
5080
|
+
const { stdout: stdout2 } = await exec6("gh", ["pr", "view", num, "--json", "number,title,body"]);
|
|
4831
5081
|
const raw = JSON.parse(stdout2);
|
|
4832
5082
|
return { number: raw.number, title: raw.title ?? "", body: raw.body ?? "" };
|
|
4833
5083
|
}
|
|
4834
5084
|
|
|
4835
5085
|
// src/cli/index.ts
|
|
4836
|
-
import { join as
|
|
5086
|
+
import { join as join10 } from "path";
|
|
4837
5087
|
|
|
4838
5088
|
// src/core/config/dotenv.ts
|
|
4839
5089
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
@@ -4904,7 +5154,7 @@ function buildProgram() {
|
|
|
4904
5154
|
}
|
|
4905
5155
|
async function main() {
|
|
4906
5156
|
try {
|
|
4907
|
-
loadDotenv([
|
|
5157
|
+
loadDotenv([join10(configDir(), ".env"), join10(process.cwd(), ".env")]);
|
|
4908
5158
|
await resolveLocale();
|
|
4909
5159
|
await buildProgram().parseAsync(process.argv);
|
|
4910
5160
|
} catch (err) {
|