@bunny-agent/runner-cli 0.9.42 → 0.9.43-beta.1
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/bundle.mjs +321 -40
- package/package.json +3 -3
package/dist/bundle.mjs
CHANGED
|
@@ -1277,16 +1277,283 @@ function createOpenCodeRunner(options = {}) {
|
|
|
1277
1277
|
};
|
|
1278
1278
|
}
|
|
1279
1279
|
|
|
1280
|
+
// ../../packages/runner-pi/dist/ask-user-question-tool.js
|
|
1281
|
+
import { createHash } from "node:crypto";
|
|
1282
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1283
|
+
import { join as join5 } from "node:path";
|
|
1284
|
+
var TOOL_NAME = "ask_user_question";
|
|
1285
|
+
var APPROVAL_DIR = join5(".bunny-agent", "approvals");
|
|
1286
|
+
var ASK_USER_QUESTION_TIMEOUT_MS = 12e4;
|
|
1287
|
+
var ASK_USER_QUESTION_POLL_MS = 500;
|
|
1288
|
+
var MAX_QUESTIONS = 4;
|
|
1289
|
+
var MIN_OPTIONS = 2;
|
|
1290
|
+
var MAX_OPTIONS = 4;
|
|
1291
|
+
var MAX_HEADER_LEN = 12;
|
|
1292
|
+
var MAX_QUESTION_LEN = 500;
|
|
1293
|
+
var MAX_OPTION_LABEL_LEN = 80;
|
|
1294
|
+
var MAX_OPTION_DESCRIPTION_LEN = 240;
|
|
1295
|
+
var ESC = String.fromCharCode(27);
|
|
1296
|
+
var CSI = String.fromCharCode(155);
|
|
1297
|
+
var ST = String.fromCharCode(156);
|
|
1298
|
+
var OSC = String.fromCharCode(157);
|
|
1299
|
+
var BEL = String.fromCharCode(7);
|
|
1300
|
+
var ANSI_OSC_RE = new RegExp(`(?:${ESC}\\]|${OSC})[^${ESC}${ST}${BEL}]*(?:${BEL}|${ESC}\\\\|${ST})`, "g");
|
|
1301
|
+
var ANSI_CTL_RE = new RegExp(`(?:${ESC}\\[[0-?]*[ -/]*[@-~]|${CSI}[0-?]*[ -/]*[@-~]|${ESC}[@-Z\\\\\\-_])`, "g");
|
|
1302
|
+
var CTL_CHAR_RE = new RegExp(`[${String.fromCharCode(0)}-${String.fromCharCode(31)}${String.fromCharCode(127)}-${String.fromCharCode(159)}]+`, "g");
|
|
1303
|
+
var askUserQuestionSchema = {
|
|
1304
|
+
type: "object",
|
|
1305
|
+
required: ["questions"],
|
|
1306
|
+
properties: {
|
|
1307
|
+
questions: {
|
|
1308
|
+
type: "array",
|
|
1309
|
+
minItems: 1,
|
|
1310
|
+
maxItems: MAX_QUESTIONS,
|
|
1311
|
+
description: `Between 1 and ${MAX_QUESTIONS} questions to ask the user.`,
|
|
1312
|
+
items: {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
required: ["question", "header", "multiSelect", "options"],
|
|
1315
|
+
properties: {
|
|
1316
|
+
question: {
|
|
1317
|
+
type: "string",
|
|
1318
|
+
description: "The full question to ask the user. Should be specific, end with a question mark, and avoid jargon the user has not used."
|
|
1319
|
+
},
|
|
1320
|
+
header: {
|
|
1321
|
+
type: "string",
|
|
1322
|
+
description: `Short label (max ${MAX_HEADER_LEN} chars) shown as a chip/tag in the UI. Examples: "Library", "Auth method", "Approach".`
|
|
1323
|
+
},
|
|
1324
|
+
multiSelect: {
|
|
1325
|
+
type: "boolean",
|
|
1326
|
+
description: "Set true when the choices are not mutually exclusive and the user may select more than one."
|
|
1327
|
+
},
|
|
1328
|
+
options: {
|
|
1329
|
+
type: "array",
|
|
1330
|
+
minItems: MIN_OPTIONS,
|
|
1331
|
+
maxItems: MAX_OPTIONS,
|
|
1332
|
+
description: `Between ${MIN_OPTIONS} and ${MAX_OPTIONS} mutually exclusive options. Do not add an "Other" option \u2014 the host adds one automatically.`,
|
|
1333
|
+
items: {
|
|
1334
|
+
type: "object",
|
|
1335
|
+
required: ["label", "description"],
|
|
1336
|
+
properties: {
|
|
1337
|
+
label: {
|
|
1338
|
+
type: "string",
|
|
1339
|
+
description: "Display text for the option (1-5 words). Should clearly describe the choice."
|
|
1340
|
+
},
|
|
1341
|
+
description: {
|
|
1342
|
+
type: "string",
|
|
1343
|
+
description: "What this option means or what happens if chosen \u2014 call out trade-offs."
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
var TOOL_DESCRIPTION = `Ask the user one or more multiple-choice questions when the task is genuinely ambiguous and you need user input to proceed (choosing between libraries, design approaches, trade-offs, etc.). Each question carries 2-${MAX_OPTIONS} options; up to ${MAX_QUESTIONS} questions per call. The user can also select an automatically-provided "Other" option to give free-form input. Use sparingly \u2014 prefer making a sensible default choice when the task is clear.`;
|
|
1354
|
+
function sanitizeText(value) {
|
|
1355
|
+
if (typeof value !== "string")
|
|
1356
|
+
return "";
|
|
1357
|
+
return value.replace(ANSI_OSC_RE, "").replace(ANSI_CTL_RE, "").replace(CTL_CHAR_RE, " ").replace(/\s+/g, " ").trim();
|
|
1358
|
+
}
|
|
1359
|
+
function truncate(value, max) {
|
|
1360
|
+
return value.length <= max ? value : `${value.slice(0, Math.max(0, max - 3))}...`;
|
|
1361
|
+
}
|
|
1362
|
+
function sanitizeQuestions(questions) {
|
|
1363
|
+
return questions.map((q) => ({
|
|
1364
|
+
question: truncate(sanitizeText(q.question), MAX_QUESTION_LEN),
|
|
1365
|
+
header: truncate(sanitizeText(q.header), MAX_HEADER_LEN),
|
|
1366
|
+
multiSelect: !!q.multiSelect,
|
|
1367
|
+
options: q.options.map((o) => ({
|
|
1368
|
+
label: truncate(sanitizeText(o.label), MAX_OPTION_LABEL_LEN),
|
|
1369
|
+
description: truncate(sanitizeText(o.description), MAX_OPTION_DESCRIPTION_LEN)
|
|
1370
|
+
}))
|
|
1371
|
+
}));
|
|
1372
|
+
}
|
|
1373
|
+
function readApproval(file) {
|
|
1374
|
+
try {
|
|
1375
|
+
const raw = readFileSync3(file, "utf-8");
|
|
1376
|
+
return JSON.parse(raw);
|
|
1377
|
+
} catch {
|
|
1378
|
+
return void 0;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
var TOOL_CALL_ID_PREFIX_LEN = 32;
|
|
1382
|
+
var TOOL_CALL_ID_HASH_LEN = 16;
|
|
1383
|
+
function sanitizeToolCallId(toolCallId) {
|
|
1384
|
+
const safePrefix = toolCallId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, TOOL_CALL_ID_PREFIX_LEN);
|
|
1385
|
+
const hash = createHash("sha1").update(toolCallId).digest("hex").slice(0, TOOL_CALL_ID_HASH_LEN);
|
|
1386
|
+
return `${safePrefix}-${hash}`;
|
|
1387
|
+
}
|
|
1388
|
+
function safeUnlink(file) {
|
|
1389
|
+
try {
|
|
1390
|
+
if (existsSync4(file))
|
|
1391
|
+
unlinkSync3(file);
|
|
1392
|
+
} catch {
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
async function pollApprovalFile(approvalFile, signal, options = {}) {
|
|
1396
|
+
const timeoutMs = options.timeoutMs ?? ASK_USER_QUESTION_TIMEOUT_MS;
|
|
1397
|
+
const pollMs = options.pollMs ?? ASK_USER_QUESTION_POLL_MS;
|
|
1398
|
+
const deadline = Date.now() + timeoutMs;
|
|
1399
|
+
let lastApproval;
|
|
1400
|
+
while (true) {
|
|
1401
|
+
if (signal?.aborted) {
|
|
1402
|
+
return { kind: "aborted", answers: lastApproval?.answers ?? {} };
|
|
1403
|
+
}
|
|
1404
|
+
const approval = readApproval(approvalFile);
|
|
1405
|
+
if (approval)
|
|
1406
|
+
lastApproval = approval;
|
|
1407
|
+
if (approval && approval.status !== "pending") {
|
|
1408
|
+
const answers = approval.answers ?? {};
|
|
1409
|
+
if (approval.status === "completed" && Object.keys(answers).length > 0) {
|
|
1410
|
+
return { kind: "answered", answers };
|
|
1411
|
+
}
|
|
1412
|
+
if (approval.status === "declined") {
|
|
1413
|
+
return { kind: "declined", answers, reason: approval.reason };
|
|
1414
|
+
}
|
|
1415
|
+
if (approval.status === "cancelled") {
|
|
1416
|
+
return { kind: "cancelled", answers, reason: approval.reason };
|
|
1417
|
+
}
|
|
1418
|
+
return {
|
|
1419
|
+
kind: "declined",
|
|
1420
|
+
answers,
|
|
1421
|
+
reason: approval.reason ?? "no_answer_provided"
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
if (Date.now() >= deadline) {
|
|
1425
|
+
const partial = lastApproval?.answers ?? {};
|
|
1426
|
+
return {
|
|
1427
|
+
kind: Object.keys(partial).length > 0 ? "answered" : "timeout",
|
|
1428
|
+
answers: partial
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
await sleepWithAbort(Math.min(pollMs, Math.max(0, deadline - Date.now())), signal);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
function sleepWithAbort(ms, signal) {
|
|
1435
|
+
return new Promise((resolve4) => {
|
|
1436
|
+
if (ms <= 0) {
|
|
1437
|
+
resolve4();
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
if (signal?.aborted) {
|
|
1441
|
+
resolve4();
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const onAbort = () => {
|
|
1445
|
+
clearTimeout(timer);
|
|
1446
|
+
resolve4();
|
|
1447
|
+
};
|
|
1448
|
+
const timer = setTimeout(() => {
|
|
1449
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1450
|
+
resolve4();
|
|
1451
|
+
}, ms);
|
|
1452
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
function formatAcceptedText(questions, answers) {
|
|
1456
|
+
const lines = ["User answered:"];
|
|
1457
|
+
for (const q of questions) {
|
|
1458
|
+
const raw = answers[q.question];
|
|
1459
|
+
const formatted = Array.isArray(raw) ? raw.join(", ") : typeof raw === "string" && raw.length > 0 ? raw : "(no answer)";
|
|
1460
|
+
lines.push(`Q: ${q.question}`);
|
|
1461
|
+
lines.push(`A: ${formatted}`);
|
|
1462
|
+
}
|
|
1463
|
+
return lines.join("\n");
|
|
1464
|
+
}
|
|
1465
|
+
function formatDeclinedText(reason, partial) {
|
|
1466
|
+
const partialNote = Object.keys(partial).length > 0 ? ` Partial answers received: ${JSON.stringify(partial)}.` : "";
|
|
1467
|
+
return `User did not answer the questions (reason: ${reason}). Make a sensible default choice and continue, or ask again if the choice is genuinely required.${partialNote}`;
|
|
1468
|
+
}
|
|
1469
|
+
function buildAskUserQuestionTool(opts) {
|
|
1470
|
+
const { cwd, timeoutMs, pollMs } = opts;
|
|
1471
|
+
return {
|
|
1472
|
+
name: TOOL_NAME,
|
|
1473
|
+
label: "ask_user_question",
|
|
1474
|
+
description: TOOL_DESCRIPTION,
|
|
1475
|
+
promptSnippet: "ask_user_question: ask the user 1-4 multiple-choice questions when the task is genuinely ambiguous.",
|
|
1476
|
+
promptGuidelines: [
|
|
1477
|
+
"Use ask_user_question only when the task is genuinely ambiguous and a user choice is required to proceed; otherwise pick a sensible default.",
|
|
1478
|
+
"Each question must carry 2-4 mutually exclusive options. Do not add an 'Other' option \u2014 the host adds one automatically."
|
|
1479
|
+
],
|
|
1480
|
+
// Pause other tool calls while a user prompt is open; otherwise pi may
|
|
1481
|
+
// surface output the user has not yet had a chance to react to.
|
|
1482
|
+
executionMode: "sequential",
|
|
1483
|
+
// biome-ignore lint/suspicious/noExplicitAny: TypeBox accepts plain JSON Schema literals here, see image-tools.ts.
|
|
1484
|
+
parameters: askUserQuestionSchema,
|
|
1485
|
+
async execute(toolCallId, params, signal) {
|
|
1486
|
+
const raw = params.questions ?? [];
|
|
1487
|
+
const sanitized = sanitizeQuestions(raw);
|
|
1488
|
+
const sanitizedInput = { questions: sanitized };
|
|
1489
|
+
const approvalDir = join5(cwd, APPROVAL_DIR);
|
|
1490
|
+
const approvalFile = join5(approvalDir, `${sanitizeToolCallId(toolCallId)}.json`);
|
|
1491
|
+
try {
|
|
1492
|
+
mkdirSync2(approvalDir, { recursive: true });
|
|
1493
|
+
const pending = {
|
|
1494
|
+
status: "pending",
|
|
1495
|
+
toolName: TOOL_NAME,
|
|
1496
|
+
input: sanitizedInput,
|
|
1497
|
+
questions: sanitized,
|
|
1498
|
+
answers: {}
|
|
1499
|
+
};
|
|
1500
|
+
writeFileSync3(approvalFile, JSON.stringify(pending));
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1503
|
+
return {
|
|
1504
|
+
content: [
|
|
1505
|
+
{
|
|
1506
|
+
type: "text",
|
|
1507
|
+
text: `ask_user_question failed to create approval file: ${message}`
|
|
1508
|
+
}
|
|
1509
|
+
],
|
|
1510
|
+
details: void 0
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
let outcome;
|
|
1514
|
+
try {
|
|
1515
|
+
outcome = await pollApprovalFile(approvalFile, signal, {
|
|
1516
|
+
timeoutMs,
|
|
1517
|
+
pollMs
|
|
1518
|
+
});
|
|
1519
|
+
} finally {
|
|
1520
|
+
safeUnlink(approvalFile);
|
|
1521
|
+
}
|
|
1522
|
+
if (outcome.kind === "answered") {
|
|
1523
|
+
return {
|
|
1524
|
+
content: [
|
|
1525
|
+
{
|
|
1526
|
+
type: "text",
|
|
1527
|
+
text: formatAcceptedText(sanitized, outcome.answers)
|
|
1528
|
+
}
|
|
1529
|
+
],
|
|
1530
|
+
details: void 0
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
const reason = outcome.kind === "timeout" ? "timeout" : outcome.kind === "aborted" ? "run_aborted" : outcome.reason ?? outcome.kind;
|
|
1534
|
+
return {
|
|
1535
|
+
content: [
|
|
1536
|
+
{
|
|
1537
|
+
type: "text",
|
|
1538
|
+
text: formatDeclinedText(reason, outcome.answers)
|
|
1539
|
+
}
|
|
1540
|
+
],
|
|
1541
|
+
details: void 0
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1280
1547
|
// ../../packages/runner-pi/dist/pi-runner.js
|
|
1281
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
1282
|
-
import { join as
|
|
1548
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync6, unlinkSync as unlinkSync4 } from "node:fs";
|
|
1549
|
+
import { join as join9 } from "node:path";
|
|
1283
1550
|
import { getModel } from "@earendil-works/pi-ai";
|
|
1284
1551
|
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
1285
1552
|
|
|
1286
1553
|
// ../../packages/runner-pi/dist/bunny-agent-resource-loader.js
|
|
1287
|
-
import { existsSync as
|
|
1554
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1288
1555
|
import { homedir } from "node:os";
|
|
1289
|
-
import { isAbsolute, join as
|
|
1556
|
+
import { isAbsolute, join as join6, resolve as resolve2 } from "node:path";
|
|
1290
1557
|
import { DefaultResourceLoader, loadSkills } from "@earendil-works/pi-coding-agent";
|
|
1291
1558
|
var LOG_PREFIX = "[bunny-agent:pi]";
|
|
1292
1559
|
function logSkillLoad(cwd, agentDir, skillPaths, result) {
|
|
@@ -1298,7 +1565,7 @@ function logSkillLoad(cwd, agentDir, skillPaths, result) {
|
|
|
1298
1565
|
];
|
|
1299
1566
|
for (const raw of skillPaths) {
|
|
1300
1567
|
const abs = isAbsolute(raw) ? raw : resolve2(cwd, raw);
|
|
1301
|
-
lines.push(` ${raw} -> ${abs} (exists: ${
|
|
1568
|
+
lines.push(` ${raw} -> ${abs} (exists: ${existsSync5(abs) ? "yes" : "no"})`);
|
|
1302
1569
|
}
|
|
1303
1570
|
lines.push(` loaded skills: ${result.skills.length}`);
|
|
1304
1571
|
if (result.skills.length > 0) {
|
|
@@ -1316,7 +1583,7 @@ function logSkillLoad(cwd, agentDir, skillPaths, result) {
|
|
|
1316
1583
|
var BunnyAgentResourceLoader = class {
|
|
1317
1584
|
constructor(options = {}) {
|
|
1318
1585
|
this.cwd = options.cwd ?? process.cwd();
|
|
1319
|
-
this.agentDir = options.agentDir ??
|
|
1586
|
+
this.agentDir = options.agentDir ?? join6(homedir(), ".bunny", "agent");
|
|
1320
1587
|
this.skillPaths = options.skillPaths ?? [];
|
|
1321
1588
|
this.extraAppendPrompt = options.appendSystemPrompt;
|
|
1322
1589
|
this.delegate = new DefaultResourceLoader({
|
|
@@ -1373,8 +1640,8 @@ var BunnyAgentResourceLoader = class {
|
|
|
1373
1640
|
};
|
|
1374
1641
|
|
|
1375
1642
|
// ../../packages/runner-pi/dist/image-tools.js
|
|
1376
|
-
import { mkdirSync as
|
|
1377
|
-
import { dirname as dirname3, extname, join as
|
|
1643
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1644
|
+
import { dirname as dirname3, extname, join as join7 } from "node:path";
|
|
1378
1645
|
var generateImageSchema = {
|
|
1379
1646
|
type: "object",
|
|
1380
1647
|
properties: {
|
|
@@ -1607,8 +1874,8 @@ async function saveImageItem(item, filePath, apiKey) {
|
|
|
1607
1874
|
const b64 = await resolveB64(item, apiKey);
|
|
1608
1875
|
if (!b64)
|
|
1609
1876
|
return void 0;
|
|
1610
|
-
|
|
1611
|
-
|
|
1877
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
1878
|
+
writeFileSync4(filePath, Buffer.from(b64, "base64"));
|
|
1612
1879
|
return filePath;
|
|
1613
1880
|
}
|
|
1614
1881
|
function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
@@ -1635,7 +1902,7 @@ function buildImageGenerateTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1635
1902
|
const imageSize = p.imageSize;
|
|
1636
1903
|
const rawFilename = p.filename;
|
|
1637
1904
|
const filename = rawFilename ? extname(rawFilename) ? rawFilename : `${rawFilename}.png` : `image_${Date.now()}.png`;
|
|
1638
|
-
const filePath =
|
|
1905
|
+
const filePath = join7(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
|
|
1639
1906
|
try {
|
|
1640
1907
|
const url = `${baseUrl.replace(/\/$/, "")}/v1/images/generations`;
|
|
1641
1908
|
const res = await fetch(url, {
|
|
@@ -1783,7 +2050,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1783
2050
|
// biome-ignore lint/suspicious/noExplicitAny: plain JSON Schema compatible with TypeBox TSchema
|
|
1784
2051
|
parameters: editImageSchema,
|
|
1785
2052
|
async execute(_toolCallId, params, signal, _onUpdate) {
|
|
1786
|
-
const { readFileSync:
|
|
2053
|
+
const { readFileSync: readFileSync5, existsSync: existsSync9 } = await import("node:fs");
|
|
1787
2054
|
const { resolve: resolve4, basename: basename2 } = await import("node:path");
|
|
1788
2055
|
const p = params;
|
|
1789
2056
|
const imagePath = p.image;
|
|
@@ -1796,7 +2063,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1796
2063
|
const rawFilename = p.filename;
|
|
1797
2064
|
const safePrompt = buildPolicySafeEditPrompt(prompt);
|
|
1798
2065
|
const resolvedImage = resolve4(cwd, imagePath);
|
|
1799
|
-
if (!
|
|
2066
|
+
if (!existsSync9(resolvedImage)) {
|
|
1800
2067
|
return {
|
|
1801
2068
|
content: [
|
|
1802
2069
|
{
|
|
@@ -1808,9 +2075,9 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1808
2075
|
};
|
|
1809
2076
|
}
|
|
1810
2077
|
const filename = rawFilename ? extname(rawFilename) ? rawFilename : `${rawFilename}.png` : `edited_${Date.now()}.png`;
|
|
1811
|
-
const filePath =
|
|
2078
|
+
const filePath = join7(cwd, filename.replace(/[^a-zA-Z0-9_\-./]/g, "_"));
|
|
1812
2079
|
try {
|
|
1813
|
-
const imageBuffer =
|
|
2080
|
+
const imageBuffer = readFileSync5(resolvedImage);
|
|
1814
2081
|
const fields = [
|
|
1815
2082
|
{ name: "model", value: imageModelId },
|
|
1816
2083
|
{ name: "prompt", value: safePrompt.prompt },
|
|
@@ -1840,11 +2107,11 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1840
2107
|
];
|
|
1841
2108
|
if (maskPath) {
|
|
1842
2109
|
const resolvedMask = resolve4(cwd, maskPath);
|
|
1843
|
-
if (
|
|
2110
|
+
if (existsSync9(resolvedMask)) {
|
|
1844
2111
|
files.push({
|
|
1845
2112
|
name: "mask",
|
|
1846
2113
|
filename: basename2(resolvedMask),
|
|
1847
|
-
buffer:
|
|
2114
|
+
buffer: readFileSync5(resolvedMask),
|
|
1848
2115
|
mime: detectImageMime(resolvedMask)
|
|
1849
2116
|
});
|
|
1850
2117
|
}
|
|
@@ -1907,7 +2174,7 @@ function buildImageEditTool(cwd, imageModelId, baseUrl, apiKey) {
|
|
|
1907
2174
|
|
|
1908
2175
|
// ../../packages/runner-pi/dist/session-utils.js
|
|
1909
2176
|
import { closeSync, fstatSync, openSync, readdirSync as readdirSync2, readSync, statSync as statSync2 } from "node:fs";
|
|
1910
|
-
import { join as
|
|
2177
|
+
import { join as join8 } from "node:path";
|
|
1911
2178
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
1912
2179
|
var MAX_SESSION_FILE_BYTES = Number(process.env.SANDAGENT_MAX_SESSION_BYTES) || 10 * 1024 * 1024;
|
|
1913
2180
|
function resolveSessionPathById(cwd, sessionId) {
|
|
@@ -1916,7 +2183,7 @@ function resolveSessionPathById(cwd, sessionId) {
|
|
|
1916
2183
|
try {
|
|
1917
2184
|
const suffix = `_${sessionId}.jsonl`;
|
|
1918
2185
|
const match = readdirSync2(sessionsDir).find((f) => f.endsWith(suffix));
|
|
1919
|
-
return match ?
|
|
2186
|
+
return match ? join8(sessionsDir, match) : void 0;
|
|
1920
2187
|
} catch {
|
|
1921
2188
|
return void 0;
|
|
1922
2189
|
}
|
|
@@ -2096,15 +2363,16 @@ var PiAISDKStreamConverter = class {
|
|
|
2096
2363
|
}
|
|
2097
2364
|
if (event.type === "tool_execution_start") {
|
|
2098
2365
|
chunks.push(...this.endTextStreamIfOpen());
|
|
2366
|
+
const toolCallId = sanitizeToolCallId(event.toolCallId);
|
|
2099
2367
|
chunks.push(sseData({
|
|
2100
2368
|
type: "tool-input-start",
|
|
2101
|
-
toolCallId
|
|
2369
|
+
toolCallId,
|
|
2102
2370
|
toolName: event.toolName,
|
|
2103
2371
|
dynamic: true,
|
|
2104
2372
|
providerExecuted: true
|
|
2105
2373
|
}), sseData({
|
|
2106
2374
|
type: "tool-input-available",
|
|
2107
|
-
toolCallId
|
|
2375
|
+
toolCallId,
|
|
2108
2376
|
toolName: event.toolName,
|
|
2109
2377
|
input: event.args,
|
|
2110
2378
|
dynamic: true,
|
|
@@ -2117,10 +2385,11 @@ var PiAISDKStreamConverter = class {
|
|
|
2117
2385
|
const raw = event.result?.details?.usage?.raw;
|
|
2118
2386
|
if (raw != null)
|
|
2119
2387
|
accumulateToolUsage(this.toolUsageTally, raw);
|
|
2388
|
+
const toolCallId = sanitizeToolCallId(event.toolCallId);
|
|
2120
2389
|
if (event.isError) {
|
|
2121
2390
|
chunks.push(sseData({
|
|
2122
2391
|
type: "tool-output-error",
|
|
2123
|
-
toolCallId
|
|
2392
|
+
toolCallId,
|
|
2124
2393
|
errorText: output,
|
|
2125
2394
|
dynamic: true,
|
|
2126
2395
|
providerExecuted: true
|
|
@@ -2128,7 +2397,7 @@ var PiAISDKStreamConverter = class {
|
|
|
2128
2397
|
} else {
|
|
2129
2398
|
chunks.push(sseData({
|
|
2130
2399
|
type: "tool-output-available",
|
|
2131
|
-
toolCallId
|
|
2400
|
+
toolCallId,
|
|
2132
2401
|
output,
|
|
2133
2402
|
dynamic: true,
|
|
2134
2403
|
providerExecuted: true
|
|
@@ -5351,6 +5620,12 @@ function resolveImageModelName(chatProvider, env) {
|
|
|
5351
5620
|
function getEnvValue(optionsEnv, name) {
|
|
5352
5621
|
return optionsEnv?.[name] ?? process.env[name];
|
|
5353
5622
|
}
|
|
5623
|
+
function parsePositiveInt(value) {
|
|
5624
|
+
if (value === void 0 || value === "")
|
|
5625
|
+
return void 0;
|
|
5626
|
+
const n = Number(value);
|
|
5627
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : void 0;
|
|
5628
|
+
}
|
|
5354
5629
|
function applyModelOverrides(model, provider, optionsEnv) {
|
|
5355
5630
|
if (model == null)
|
|
5356
5631
|
return;
|
|
@@ -5380,9 +5655,9 @@ function traceRawMessage(debugCwd, data, reset = false, optionsEnv) {
|
|
|
5380
5655
|
if (!enabled)
|
|
5381
5656
|
return;
|
|
5382
5657
|
try {
|
|
5383
|
-
const file =
|
|
5384
|
-
if (reset &&
|
|
5385
|
-
|
|
5658
|
+
const file = join9(debugCwd, "pi-message-stream-debug.json");
|
|
5659
|
+
if (reset && existsSync6(file))
|
|
5660
|
+
unlinkSync4(file);
|
|
5386
5661
|
const type = data !== null && typeof data === "object" ? data.type : void 0;
|
|
5387
5662
|
let payload = data;
|
|
5388
5663
|
try {
|
|
@@ -5485,6 +5760,12 @@ function createPiRunner(options = {}) {
|
|
|
5485
5760
|
customTools.push(buildImageGenerateTool(cwd, imageModelName, model.baseUrl, apiKey), buildImageEditTool(cwd, imageModelName, model.baseUrl, apiKey));
|
|
5486
5761
|
}
|
|
5487
5762
|
const toolRefDefinitions = options.toolRefs && options.toolRefs.length > 0 ? buildToolDefinitionsFromRefs(options.toolRefs) : [];
|
|
5763
|
+
const askTimeoutSeconds = parsePositiveInt(getEnvValue(options.env, "ASK_USER_QUESTION_TOOL_TIMEOUT"));
|
|
5764
|
+
const askUserQuestionTool = buildAskUserQuestionTool({
|
|
5765
|
+
cwd,
|
|
5766
|
+
timeoutMs: askTimeoutSeconds !== void 0 ? askTimeoutSeconds * 1e3 : void 0
|
|
5767
|
+
});
|
|
5768
|
+
customTools.push(askUserQuestionTool);
|
|
5488
5769
|
const { session } = await createAgentSession({
|
|
5489
5770
|
cwd,
|
|
5490
5771
|
model,
|
|
@@ -5593,47 +5874,47 @@ function createPiRunner(options = {}) {
|
|
|
5593
5874
|
}
|
|
5594
5875
|
|
|
5595
5876
|
// ../../packages/runner-harness/dist/session.js
|
|
5596
|
-
import { existsSync as
|
|
5597
|
-
import { join as
|
|
5877
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5878
|
+
import { join as join10 } from "node:path";
|
|
5598
5879
|
var DIR = ".bunny-agent";
|
|
5599
5880
|
var FILE = "session-id";
|
|
5600
5881
|
function sessionPath(cwd) {
|
|
5601
|
-
return
|
|
5882
|
+
return join10(cwd, DIR, FILE);
|
|
5602
5883
|
}
|
|
5603
5884
|
function readSessionId(cwd) {
|
|
5604
5885
|
try {
|
|
5605
5886
|
const p = sessionPath(cwd);
|
|
5606
|
-
if (!
|
|
5887
|
+
if (!existsSync7(p))
|
|
5607
5888
|
return void 0;
|
|
5608
|
-
return
|
|
5889
|
+
return readFileSync4(p, "utf8").trim() || void 0;
|
|
5609
5890
|
} catch {
|
|
5610
5891
|
return void 0;
|
|
5611
5892
|
}
|
|
5612
5893
|
}
|
|
5613
5894
|
function writeSessionId(cwd, id) {
|
|
5614
5895
|
try {
|
|
5615
|
-
|
|
5616
|
-
|
|
5896
|
+
mkdirSync4(join10(cwd, DIR), { recursive: true });
|
|
5897
|
+
writeFileSync5(sessionPath(cwd), id, "utf8");
|
|
5617
5898
|
} catch {
|
|
5618
5899
|
}
|
|
5619
5900
|
}
|
|
5620
5901
|
|
|
5621
5902
|
// ../../packages/runner-harness/dist/skills.js
|
|
5622
|
-
import { existsSync as
|
|
5903
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
|
|
5623
5904
|
import { homedir as homedir2 } from "node:os";
|
|
5624
|
-
import { join as
|
|
5905
|
+
import { join as join11 } from "node:path";
|
|
5625
5906
|
function discoverSkillPaths(cwd) {
|
|
5626
5907
|
const paths = [];
|
|
5627
5908
|
for (const base of [
|
|
5628
|
-
|
|
5629
|
-
|
|
5909
|
+
join11(cwd, "skills"),
|
|
5910
|
+
join11(homedir2(), ".bunny-agent", "skills")
|
|
5630
5911
|
]) {
|
|
5631
|
-
if (!
|
|
5912
|
+
if (!existsSync8(base))
|
|
5632
5913
|
continue;
|
|
5633
5914
|
try {
|
|
5634
5915
|
for (const entry of readdirSync3(base)) {
|
|
5635
|
-
const full =
|
|
5636
|
-
if (statSync3(full).isDirectory() &&
|
|
5916
|
+
const full = join11(base, entry);
|
|
5917
|
+
if (statSync3(full).isDirectory() && existsSync8(join11(full, "SKILL.md"))) {
|
|
5637
5918
|
paths.push(full);
|
|
5638
5919
|
}
|
|
5639
5920
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bunny-agent/runner-cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.43-beta.1",
|
|
4
4
|
"description": "BunnyAgent Runner CLI - Like gemini-cli or claude-code, runs in your local terminal with AI SDK UI streaming",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"esbuild": "^0.27.2",
|
|
54
54
|
"typescript": "^5.3.0",
|
|
55
55
|
"vitest": "^1.6.1",
|
|
56
|
-
"@bunny-agent/runner-codex": "0.6.2",
|
|
57
56
|
"@bunny-agent/runner-claude": "0.6.2",
|
|
58
57
|
"@bunny-agent/runner-harness": "0.1.1-beta.0",
|
|
59
|
-
"@bunny-agent/runner-
|
|
58
|
+
"@bunny-agent/runner-codex": "0.6.2",
|
|
60
59
|
"@bunny-agent/runner-opencode": "0.6.2",
|
|
60
|
+
"@bunny-agent/runner-gemini": "0.6.2",
|
|
61
61
|
"@bunny-agent/runner-pi": "0.6.4-beta.0"
|
|
62
62
|
},
|
|
63
63
|
"scripts": {
|