ultrahope 0.1.9 → 0.1.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/git-ultrahope.js +635 -479
- package/dist/index.js +641 -481
- package/package.json +1 -1
package/dist/git-ultrahope.js
CHANGED
|
@@ -1037,6 +1037,10 @@ var DEFAULT_MODELS = [
|
|
|
1037
1037
|
"mistral/ministral-3b",
|
|
1038
1038
|
"xai/grok-code-fast-1"
|
|
1039
1039
|
];
|
|
1040
|
+
var DEFAULT_ESCALATION_MODELS = [
|
|
1041
|
+
"anthropic/claude-sonnet-4.6",
|
|
1042
|
+
"openai/gpt-5.3-codex"
|
|
1043
|
+
];
|
|
1040
1044
|
var isAbortError = (error) => error instanceof Error && error.name === "AbortError";
|
|
1041
1045
|
var isInvalidCliSessionIdError = (error) => error instanceof Error && error.message.includes("Invalid cliSessionId");
|
|
1042
1046
|
var delay = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
@@ -1278,7 +1282,7 @@ function findNearestProjectConfig(cwd) {
|
|
|
1278
1282
|
current = parent;
|
|
1279
1283
|
}
|
|
1280
1284
|
}
|
|
1281
|
-
function
|
|
1285
|
+
function readConfigField(configPath, field) {
|
|
1282
1286
|
let raw = "";
|
|
1283
1287
|
try {
|
|
1284
1288
|
raw = fs2.readFileSync(configPath, "utf-8");
|
|
@@ -1293,10 +1297,10 @@ function readConfigModels(configPath) {
|
|
|
1293
1297
|
const message = error instanceof Error ? error.message : String(error);
|
|
1294
1298
|
fail(`Failed to parse TOML config ${configPath}: ${message}`);
|
|
1295
1299
|
}
|
|
1296
|
-
if (parsed
|
|
1300
|
+
if (parsed[field] === void 0) {
|
|
1297
1301
|
return void 0;
|
|
1298
1302
|
}
|
|
1299
|
-
return validateModels(parsed
|
|
1303
|
+
return validateModels(parsed[field], configPath);
|
|
1300
1304
|
}
|
|
1301
1305
|
function resolveModels(cliModels) {
|
|
1302
1306
|
if (cliModels && cliModels.length > 0) {
|
|
@@ -1304,20 +1308,43 @@ function resolveModels(cliModels) {
|
|
|
1304
1308
|
}
|
|
1305
1309
|
const projectConfigPath = findNearestProjectConfig(process.cwd());
|
|
1306
1310
|
if (projectConfigPath) {
|
|
1307
|
-
const projectModels =
|
|
1311
|
+
const projectModels = readConfigField(projectConfigPath, "models");
|
|
1308
1312
|
if (projectModels) {
|
|
1309
1313
|
return projectModels;
|
|
1310
1314
|
}
|
|
1311
1315
|
}
|
|
1312
1316
|
const globalConfigPath = getGlobalConfigPath();
|
|
1313
1317
|
if (fs2.existsSync(globalConfigPath)) {
|
|
1314
|
-
const globalModels =
|
|
1318
|
+
const globalModels = readConfigField(globalConfigPath, "models");
|
|
1315
1319
|
if (globalModels) {
|
|
1316
1320
|
return globalModels;
|
|
1317
1321
|
}
|
|
1318
1322
|
}
|
|
1319
1323
|
return DEFAULT_MODELS;
|
|
1320
1324
|
}
|
|
1325
|
+
function resolveEscalationModels(cliEscalationModels) {
|
|
1326
|
+
if (cliEscalationModels && cliEscalationModels.length > 0) {
|
|
1327
|
+
return cliEscalationModels;
|
|
1328
|
+
}
|
|
1329
|
+
const projectConfigPath = findNearestProjectConfig(process.cwd());
|
|
1330
|
+
if (projectConfigPath) {
|
|
1331
|
+
const projectModels = readConfigField(
|
|
1332
|
+
projectConfigPath,
|
|
1333
|
+
"escalation_models"
|
|
1334
|
+
);
|
|
1335
|
+
if (projectModels) {
|
|
1336
|
+
return projectModels;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
const globalConfigPath = getGlobalConfigPath();
|
|
1340
|
+
if (fs2.existsSync(globalConfigPath)) {
|
|
1341
|
+
const globalModels = readConfigField(globalConfigPath, "escalation_models");
|
|
1342
|
+
if (globalModels) {
|
|
1343
|
+
return globalModels;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return DEFAULT_ESCALATION_MODELS;
|
|
1347
|
+
}
|
|
1321
1348
|
|
|
1322
1349
|
// lib/diff-stats.ts
|
|
1323
1350
|
import { execSync } from "child_process";
|
|
@@ -1353,6 +1380,441 @@ function formatDiffStats(stats) {
|
|
|
1353
1380
|
|
|
1354
1381
|
// lib/renderer.ts
|
|
1355
1382
|
import * as readline2 from "readline";
|
|
1383
|
+
|
|
1384
|
+
// ../shared/terminal-selector-helpers.ts
|
|
1385
|
+
function formatModelName(model) {
|
|
1386
|
+
const parts = model.split("/");
|
|
1387
|
+
return parts.length > 1 ? parts[1] : model;
|
|
1388
|
+
}
|
|
1389
|
+
function formatCost(cost) {
|
|
1390
|
+
return `$${cost.toFixed(7).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
1391
|
+
}
|
|
1392
|
+
function formatTotalCostLabel(cost) {
|
|
1393
|
+
return `$${cost.toFixed(6)}`;
|
|
1394
|
+
}
|
|
1395
|
+
function getReadyCount(slots) {
|
|
1396
|
+
return slots.filter((slot) => slot.status === "ready").length;
|
|
1397
|
+
}
|
|
1398
|
+
function getTotalCost(slots) {
|
|
1399
|
+
return slots.reduce((sum, slot) => {
|
|
1400
|
+
if (slot.status === "ready" && slot.candidate.cost != null) {
|
|
1401
|
+
return sum + slot.candidate.cost;
|
|
1402
|
+
}
|
|
1403
|
+
return sum;
|
|
1404
|
+
}, 0);
|
|
1405
|
+
}
|
|
1406
|
+
function getLatestQuota(slots) {
|
|
1407
|
+
for (const slot of slots) {
|
|
1408
|
+
if (slot.status === "ready" && slot.candidate.quota) {
|
|
1409
|
+
return slot.candidate.quota;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return void 0;
|
|
1413
|
+
}
|
|
1414
|
+
function normalizeCandidateLineBreaks(content) {
|
|
1415
|
+
const normalized = content.replace(/\r\n?|[\u2028\u2029]/g, "\n");
|
|
1416
|
+
return normalized.split("\n")[0]?.trim() || "";
|
|
1417
|
+
}
|
|
1418
|
+
function normalizeCandidateContentForDisplay(content) {
|
|
1419
|
+
const line = normalizeCandidateLineBreaks(content);
|
|
1420
|
+
if (!line) {
|
|
1421
|
+
return "";
|
|
1422
|
+
}
|
|
1423
|
+
const collapsed = line.replace(/\s+/g, " ").trim();
|
|
1424
|
+
if (!collapsed) {
|
|
1425
|
+
return "";
|
|
1426
|
+
}
|
|
1427
|
+
if (collapsed.length % 2 === 0) {
|
|
1428
|
+
const half = collapsed.length / 2;
|
|
1429
|
+
if (collapsed.slice(0, half) === collapsed.slice(half)) {
|
|
1430
|
+
return collapsed.slice(0, half);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
const words = collapsed.split(" ");
|
|
1434
|
+
if (words.length >= 6 && words.length % 2 === 0) {
|
|
1435
|
+
const half = words.length / 2;
|
|
1436
|
+
const firstHalf = words.slice(0, half).join(" ");
|
|
1437
|
+
const secondHalf = words.slice(half).join(" ");
|
|
1438
|
+
if (firstHalf === secondHalf) {
|
|
1439
|
+
return firstHalf;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return collapsed;
|
|
1443
|
+
}
|
|
1444
|
+
function hasReadySlot(slots) {
|
|
1445
|
+
return getReadyCount(slots) > 0;
|
|
1446
|
+
}
|
|
1447
|
+
function selectNearestReady(slots, startIndex, direction) {
|
|
1448
|
+
for (let index = startIndex + direction; index >= 0 && index < slots.length; index += direction) {
|
|
1449
|
+
if (slots[index]?.status === "ready") {
|
|
1450
|
+
return index;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return startIndex;
|
|
1454
|
+
}
|
|
1455
|
+
function getSelectedCandidate(slots, selectedIndex) {
|
|
1456
|
+
const slot = slots[selectedIndex];
|
|
1457
|
+
return slot?.status === "ready" ? slot.candidate : void 0;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// ../shared/terminal-selector-view-model.ts
|
|
1461
|
+
var DEFAULT_SPINNER_FRAMES = [
|
|
1462
|
+
"\u280B",
|
|
1463
|
+
"\u2819",
|
|
1464
|
+
"\u2839",
|
|
1465
|
+
"\u2838",
|
|
1466
|
+
"\u283C",
|
|
1467
|
+
"\u2834",
|
|
1468
|
+
"\u2826",
|
|
1469
|
+
"\u2827",
|
|
1470
|
+
"\u2807",
|
|
1471
|
+
"\u280F"
|
|
1472
|
+
];
|
|
1473
|
+
var HINT_ACTION_ORDER = [
|
|
1474
|
+
"navigate",
|
|
1475
|
+
"confirm",
|
|
1476
|
+
"clickConfirm",
|
|
1477
|
+
"edit",
|
|
1478
|
+
"refine",
|
|
1479
|
+
"escalate",
|
|
1480
|
+
"quit"
|
|
1481
|
+
];
|
|
1482
|
+
var PROMPT_EDIT_HINT = "enter apply | esc back to select";
|
|
1483
|
+
var PROMPT_REFINE_HINT = "enter refine | esc back to select";
|
|
1484
|
+
var PROMPT_EDIT_PREFIX = " > ";
|
|
1485
|
+
var PROMPT_REFINE_PREFIX = " Refine: ";
|
|
1486
|
+
var PROMPT_REFINE_PLACEHOLDER = "e.g. more formal / shorter / in Japanese";
|
|
1487
|
+
var DEFAULT_SELECTOR_COPY = {
|
|
1488
|
+
runningLabel: "Generating commit messages...",
|
|
1489
|
+
selectionLabel: "Select a commit message",
|
|
1490
|
+
itemLabelSingular: "commit message",
|
|
1491
|
+
itemLabelPlural: "commit messages"
|
|
1492
|
+
};
|
|
1493
|
+
var DEFAULT_SELECTOR_CAPABILITIES = {
|
|
1494
|
+
clickConfirm: false,
|
|
1495
|
+
edit: false,
|
|
1496
|
+
refine: false,
|
|
1497
|
+
escalate: false
|
|
1498
|
+
};
|
|
1499
|
+
function formatDuration(ms) {
|
|
1500
|
+
const safeMs = Math.max(0, Math.round(ms));
|
|
1501
|
+
if (safeMs < 1e3) {
|
|
1502
|
+
return `${safeMs}ms`;
|
|
1503
|
+
}
|
|
1504
|
+
const seconds = (safeMs / 1e3).toFixed(1).replace(/\.0$/, "");
|
|
1505
|
+
return `${seconds}s`;
|
|
1506
|
+
}
|
|
1507
|
+
function resolveRenderMode(inputState) {
|
|
1508
|
+
return inputState.mode === "prompt" ? "prompt" : "list";
|
|
1509
|
+
}
|
|
1510
|
+
function resolvePromptLineLabel(kind) {
|
|
1511
|
+
return kind === "edit" ? "Edit mode" : "\u2192 Refine mode";
|
|
1512
|
+
}
|
|
1513
|
+
function buildPromptSlots(viewModelSlots, promptKind, targetIndex, bufferText) {
|
|
1514
|
+
if (promptKind === "edit") {
|
|
1515
|
+
return viewModelSlots.map((slot, index) => {
|
|
1516
|
+
const isTarget = index === targetIndex;
|
|
1517
|
+
return {
|
|
1518
|
+
...slot,
|
|
1519
|
+
radio: isTarget ? ">" : "\u25CB",
|
|
1520
|
+
title: isTarget ? bufferText : slot.title,
|
|
1521
|
+
selected: isTarget,
|
|
1522
|
+
muted: isTarget ? false : slot.muted
|
|
1523
|
+
};
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
return viewModelSlots;
|
|
1527
|
+
}
|
|
1528
|
+
function estimatePromptSlotLineCount(slots) {
|
|
1529
|
+
let count = 1;
|
|
1530
|
+
for (const slot of slots) {
|
|
1531
|
+
const lineCount = 1 + (slot.meta == null ? 0 : 1) + 1;
|
|
1532
|
+
count += lineCount;
|
|
1533
|
+
}
|
|
1534
|
+
return count;
|
|
1535
|
+
}
|
|
1536
|
+
function estimatePromptEditInputLineIndex(promptSlots, targetIndex) {
|
|
1537
|
+
let lineIndex = 2;
|
|
1538
|
+
for (const [index, slot] of promptSlots.entries()) {
|
|
1539
|
+
if (index === targetIndex) {
|
|
1540
|
+
return lineIndex;
|
|
1541
|
+
}
|
|
1542
|
+
const slotLineCount = 1 + (slot.meta == null ? 0 : 1) + 1;
|
|
1543
|
+
lineIndex += slotLineCount;
|
|
1544
|
+
}
|
|
1545
|
+
return Math.max(2, lineIndex);
|
|
1546
|
+
}
|
|
1547
|
+
function buildPromptViewModel(input, viewModel) {
|
|
1548
|
+
const state = input.state;
|
|
1549
|
+
const promptKind = state.promptKind ?? "refine";
|
|
1550
|
+
const targetIndex = state.promptTargetIndex ?? state.selectedIndex;
|
|
1551
|
+
const targetSlot = state.slots[targetIndex];
|
|
1552
|
+
const targetText = targetSlot?.status === "ready" ? normalizeCandidateContentForDisplay(targetSlot.candidate.content) : "(no selection)";
|
|
1553
|
+
const totalCost = getTotalCost(state.slots);
|
|
1554
|
+
const costLineLabel = totalCost > 0 ? formatTotalCostLabel(totalCost) : "$0.000000";
|
|
1555
|
+
const generatedCostSuffix = viewModel.header.totalCostLabel ? ` (total: ${viewModel.header.totalCostLabel})` : "";
|
|
1556
|
+
const generatedLine = `${viewModel.header.generatedLabel}${generatedCostSuffix}`;
|
|
1557
|
+
const modeLine = resolvePromptLineLabel(promptKind);
|
|
1558
|
+
const targetLineLabel = `Target [${Math.min(state.totalSlots, targetIndex + 1)}]:`;
|
|
1559
|
+
const duration = formatDuration(input.nowMs - state.createdAtMs);
|
|
1560
|
+
const costLine = `Cost/Time (current): ${costLineLabel} / ${duration}`;
|
|
1561
|
+
const isEdit = promptKind === "edit";
|
|
1562
|
+
const promptInputPrefix = isEdit ? PROMPT_EDIT_PREFIX : PROMPT_REFINE_PREFIX;
|
|
1563
|
+
const promptHintLine = isEdit ? PROMPT_EDIT_HINT : PROMPT_REFINE_HINT;
|
|
1564
|
+
const promptPlaceholderLine = isEdit ? void 0 : PROMPT_REFINE_PLACEHOLDER;
|
|
1565
|
+
const promptInputText = input.bufferText ?? "";
|
|
1566
|
+
const promptSlots = buildPromptSlots(
|
|
1567
|
+
viewModel.slots,
|
|
1568
|
+
promptKind,
|
|
1569
|
+
targetIndex,
|
|
1570
|
+
promptInputText
|
|
1571
|
+
);
|
|
1572
|
+
const promptSlotLineCount = estimatePromptSlotLineCount(promptSlots);
|
|
1573
|
+
const promptExtraLines = isEdit ? 1 : 4;
|
|
1574
|
+
const promptLineCount = 1 + promptSlotLineCount + promptExtraLines;
|
|
1575
|
+
const promptInputLineIndex = isEdit ? estimatePromptEditInputLineIndex(promptSlots, targetIndex) : promptLineCount - promptExtraLines;
|
|
1576
|
+
const promptInputPrefixWidth = promptInputPrefix.length;
|
|
1577
|
+
const selectedLine = promptKind === "edit" ? `Selected: ${targetText}` : void 0;
|
|
1578
|
+
return {
|
|
1579
|
+
kind: promptKind,
|
|
1580
|
+
generatedLine,
|
|
1581
|
+
selectedLine,
|
|
1582
|
+
modeLine,
|
|
1583
|
+
targetLineLabel,
|
|
1584
|
+
targetText,
|
|
1585
|
+
targetIndex,
|
|
1586
|
+
costLine,
|
|
1587
|
+
questionLine: "?",
|
|
1588
|
+
slots: promptSlots,
|
|
1589
|
+
promptLineCount,
|
|
1590
|
+
promptInputLineIndex,
|
|
1591
|
+
promptInputPrefix,
|
|
1592
|
+
promptInputPrefixWidth,
|
|
1593
|
+
promptInputText,
|
|
1594
|
+
promptPlaceholderLine,
|
|
1595
|
+
promptHintLine
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function normalizeHintActions(actions) {
|
|
1599
|
+
const set = new Set(actions);
|
|
1600
|
+
const ordered = [];
|
|
1601
|
+
for (const action of HINT_ACTION_ORDER) {
|
|
1602
|
+
if (set.has(action)) {
|
|
1603
|
+
ordered.push(action);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return ordered;
|
|
1607
|
+
}
|
|
1608
|
+
function resolveHintActions(input) {
|
|
1609
|
+
const actions = ["navigate", "confirm", "quit"];
|
|
1610
|
+
if (input.capabilities.clickConfirm) {
|
|
1611
|
+
actions.push("clickConfirm");
|
|
1612
|
+
}
|
|
1613
|
+
if (input.capabilities.edit) {
|
|
1614
|
+
actions.push("edit");
|
|
1615
|
+
}
|
|
1616
|
+
if (input.capabilities.refine) {
|
|
1617
|
+
actions.push("refine");
|
|
1618
|
+
}
|
|
1619
|
+
if (input.capabilities.escalate) {
|
|
1620
|
+
actions.push("escalate");
|
|
1621
|
+
}
|
|
1622
|
+
return normalizeHintActions(actions);
|
|
1623
|
+
}
|
|
1624
|
+
function formatReadyMeta(slot) {
|
|
1625
|
+
const { candidate } = slot;
|
|
1626
|
+
if (!candidate.model) {
|
|
1627
|
+
return "";
|
|
1628
|
+
}
|
|
1629
|
+
const formattedModel = formatModelName(candidate.model);
|
|
1630
|
+
const formattedDuration = candidate.generationMs == null ? "" : ` ${formatDuration(candidate.generationMs)}`;
|
|
1631
|
+
if (candidate.cost != null) {
|
|
1632
|
+
return `${formattedModel} ${formatCost(candidate.cost)}${formattedDuration}`;
|
|
1633
|
+
}
|
|
1634
|
+
return `${formattedModel}${formattedDuration}`;
|
|
1635
|
+
}
|
|
1636
|
+
function createSlotViewModel(slot, index, selectedIndex) {
|
|
1637
|
+
if (slot.status === "pending") {
|
|
1638
|
+
return {
|
|
1639
|
+
status: "pending",
|
|
1640
|
+
selected: false,
|
|
1641
|
+
radio: "\u25CB",
|
|
1642
|
+
title: "Generating...",
|
|
1643
|
+
meta: slot.model ? formatModelName(slot.model) : void 0,
|
|
1644
|
+
muted: true
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
if (slot.status === "error") {
|
|
1648
|
+
return {
|
|
1649
|
+
status: "error",
|
|
1650
|
+
selected: false,
|
|
1651
|
+
radio: "\u25CB",
|
|
1652
|
+
title: slot.content,
|
|
1653
|
+
muted: true
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
const selected = index === selectedIndex;
|
|
1657
|
+
const title = slot.candidate.content.split("\n")[0]?.trim() || "";
|
|
1658
|
+
const meta = formatReadyMeta(slot);
|
|
1659
|
+
return {
|
|
1660
|
+
status: "ready",
|
|
1661
|
+
selected,
|
|
1662
|
+
radio: selected ? "\u25CF" : "\u25CB",
|
|
1663
|
+
title,
|
|
1664
|
+
meta: meta || void 0,
|
|
1665
|
+
muted: !selected
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function resolveEditedSummary(input) {
|
|
1669
|
+
const selectedSlot = input.state.slots[input.state.selectedIndex];
|
|
1670
|
+
if (selectedSlot?.status !== "ready") {
|
|
1671
|
+
return void 0;
|
|
1672
|
+
}
|
|
1673
|
+
const edited = input.editedSelections?.get(selectedSlot.candidate.slotId);
|
|
1674
|
+
if (!edited) {
|
|
1675
|
+
return void 0;
|
|
1676
|
+
}
|
|
1677
|
+
return edited.split("\n")[0]?.slice(0, 120) || "";
|
|
1678
|
+
}
|
|
1679
|
+
function buildSelectorViewModel(input) {
|
|
1680
|
+
const spinnerFrames = input.spinnerFrames ?? DEFAULT_SPINNER_FRAMES;
|
|
1681
|
+
const copy = { ...DEFAULT_SELECTOR_COPY, ...input.copy };
|
|
1682
|
+
const capabilities = {
|
|
1683
|
+
...DEFAULT_SELECTOR_CAPABILITIES,
|
|
1684
|
+
...input.capabilities
|
|
1685
|
+
};
|
|
1686
|
+
const readyCount = getReadyCount(input.state.slots);
|
|
1687
|
+
const totalCost = getTotalCost(input.state.slots);
|
|
1688
|
+
const frame = Math.floor(input.nowMs / 80) % spinnerFrames.length;
|
|
1689
|
+
const generatedLabel = readyCount === 1 ? `Generated 1 ${copy.itemLabelSingular}` : `Generated ${readyCount} ${copy.itemLabelPlural}`;
|
|
1690
|
+
const hintActions = resolveHintActions({
|
|
1691
|
+
readyCount,
|
|
1692
|
+
capabilities
|
|
1693
|
+
});
|
|
1694
|
+
return {
|
|
1695
|
+
mode: resolveRenderMode(input.state),
|
|
1696
|
+
header: {
|
|
1697
|
+
mode: input.state.isGenerating ? "running" : "done",
|
|
1698
|
+
spinner: spinnerFrames[frame],
|
|
1699
|
+
progress: `${readyCount}/${input.state.totalSlots}`,
|
|
1700
|
+
totalCostLabel: totalCost > 0 ? formatTotalCostLabel(totalCost) : void 0,
|
|
1701
|
+
runningLabel: copy.runningLabel,
|
|
1702
|
+
generatedLabel
|
|
1703
|
+
},
|
|
1704
|
+
hint: {
|
|
1705
|
+
kind: readyCount > 0 ? "ready" : "empty",
|
|
1706
|
+
selectionLabel: readyCount > 0 ? copy.selectionLabel : void 0,
|
|
1707
|
+
actions: hintActions
|
|
1708
|
+
},
|
|
1709
|
+
slots: input.state.slots.map(
|
|
1710
|
+
(slot, index) => createSlotViewModel(slot, index, input.state.selectedIndex)
|
|
1711
|
+
),
|
|
1712
|
+
editedSummary: resolveEditedSummary(input)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
function selectorRenderFrame(input) {
|
|
1716
|
+
const viewModel = buildSelectorViewModel(input);
|
|
1717
|
+
if (viewModel.mode === "prompt") {
|
|
1718
|
+
return {
|
|
1719
|
+
mode: "prompt",
|
|
1720
|
+
viewModel,
|
|
1721
|
+
prompt: buildPromptViewModel(input, viewModel)
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
return {
|
|
1725
|
+
mode: "list",
|
|
1726
|
+
viewModel
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
function slotToRenderLines(slot) {
|
|
1730
|
+
const lines = [
|
|
1731
|
+
{
|
|
1732
|
+
type: "slot",
|
|
1733
|
+
radio: slot.radio,
|
|
1734
|
+
title: slot.title,
|
|
1735
|
+
selected: slot.selected,
|
|
1736
|
+
muted: slot.muted
|
|
1737
|
+
}
|
|
1738
|
+
];
|
|
1739
|
+
if (slot.meta) {
|
|
1740
|
+
lines.push({ type: "slotMeta", text: slot.meta, muted: slot.muted });
|
|
1741
|
+
}
|
|
1742
|
+
return lines;
|
|
1743
|
+
}
|
|
1744
|
+
function buildHeaderLine(viewModel) {
|
|
1745
|
+
const costSuffix = viewModel.header.totalCostLabel != null ? ` (total: ${viewModel.header.totalCostLabel})` : "";
|
|
1746
|
+
if (viewModel.header.mode === "running") {
|
|
1747
|
+
return {
|
|
1748
|
+
type: "headerRunning",
|
|
1749
|
+
spinner: viewModel.header.spinner ?? "",
|
|
1750
|
+
label: viewModel.header.runningLabel,
|
|
1751
|
+
progress: viewModel.header.progress,
|
|
1752
|
+
costSuffix
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
return {
|
|
1756
|
+
type: "headerDone",
|
|
1757
|
+
label: viewModel.header.generatedLabel,
|
|
1758
|
+
costSuffix
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
function buildPromptRenderLines(frame) {
|
|
1762
|
+
const prompt = frame.prompt;
|
|
1763
|
+
if (!prompt) return [];
|
|
1764
|
+
const lines = [{ type: "blank" }];
|
|
1765
|
+
for (const slot of prompt.slots) {
|
|
1766
|
+
lines.push(...slotToRenderLines(slot));
|
|
1767
|
+
lines.push({ type: "blank" });
|
|
1768
|
+
}
|
|
1769
|
+
if (prompt.kind === "refine") {
|
|
1770
|
+
lines.push({
|
|
1771
|
+
type: "promptInput",
|
|
1772
|
+
prefix: prompt.promptInputPrefix,
|
|
1773
|
+
text: prompt.promptInputText
|
|
1774
|
+
});
|
|
1775
|
+
if (prompt.promptPlaceholderLine != null) {
|
|
1776
|
+
lines.push({ type: "placeholder", text: prompt.promptPlaceholderLine });
|
|
1777
|
+
lines.push({ type: "blank" });
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
lines.push({
|
|
1781
|
+
type: "hint",
|
|
1782
|
+
text: prompt.promptHintLine,
|
|
1783
|
+
actions: [],
|
|
1784
|
+
readyCount: 0
|
|
1785
|
+
});
|
|
1786
|
+
return lines;
|
|
1787
|
+
}
|
|
1788
|
+
function buildListRenderLines(viewModel) {
|
|
1789
|
+
const lines = [{ type: "blank" }];
|
|
1790
|
+
for (const slot of viewModel.slots) {
|
|
1791
|
+
lines.push(...slotToRenderLines(slot));
|
|
1792
|
+
lines.push({ type: "blank" });
|
|
1793
|
+
}
|
|
1794
|
+
if (viewModel.editedSummary) {
|
|
1795
|
+
lines.push({ type: "blank" });
|
|
1796
|
+
lines.push({ type: "editedSummary", text: viewModel.editedSummary });
|
|
1797
|
+
}
|
|
1798
|
+
const readyCount = viewModel.slots.filter((s) => s.status === "ready").length;
|
|
1799
|
+
lines.push({
|
|
1800
|
+
type: "hint",
|
|
1801
|
+
text: "",
|
|
1802
|
+
actions: viewModel.hint.actions,
|
|
1803
|
+
readyCount
|
|
1804
|
+
});
|
|
1805
|
+
return lines;
|
|
1806
|
+
}
|
|
1807
|
+
function buildSelectorRenderLines(frame) {
|
|
1808
|
+
const lines = [buildHeaderLine(frame.viewModel)];
|
|
1809
|
+
if (frame.mode === "prompt") {
|
|
1810
|
+
lines.push(...buildPromptRenderLines(frame));
|
|
1811
|
+
} else {
|
|
1812
|
+
lines.push(...buildListRenderLines(frame.viewModel));
|
|
1813
|
+
}
|
|
1814
|
+
return lines;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// lib/renderer.ts
|
|
1356
1818
|
var SPINNER_FRAMES = [
|
|
1357
1819
|
"\u280B",
|
|
1358
1820
|
"\u2819",
|
|
@@ -1394,101 +1856,50 @@ var SELECTOR_HINT_LABELS = {
|
|
|
1394
1856
|
clickConfirm: "click confirm",
|
|
1395
1857
|
edit: "(e)dit",
|
|
1396
1858
|
refine: "(r)efine",
|
|
1859
|
+
escalate: "(E)scalate",
|
|
1397
1860
|
quit: "(q)uit"
|
|
1398
1861
|
};
|
|
1399
|
-
function
|
|
1400
|
-
|
|
1401
|
-
const
|
|
1402
|
-
const linePrefix = ` ${radio} `;
|
|
1403
|
-
const titleColor = selected ? theme.primary : theme.dim;
|
|
1404
|
-
const titleFont = selected ? theme.bold : "";
|
|
1405
|
-
const lines = [
|
|
1406
|
-
`${linePrefix}${titleColor}${titleFont}${slot.title}${theme.reset}`
|
|
1407
|
-
];
|
|
1408
|
-
if (slot.meta) {
|
|
1409
|
-
const metaColor = slot.muted ? theme.dim : theme.primary;
|
|
1410
|
-
lines.push(` ${metaColor}${slot.meta}${theme.reset}`);
|
|
1411
|
-
}
|
|
1412
|
-
return lines;
|
|
1413
|
-
}
|
|
1414
|
-
function renderHintLine(viewModelHintActions, readyCount) {
|
|
1415
|
-
const hasReady = readyCount > 0;
|
|
1416
|
-
const canNavigate = readyCount >= 2;
|
|
1417
|
-
const canEdit = hasReady;
|
|
1418
|
-
const canRefine = hasReady;
|
|
1419
|
-
const canQuit = true;
|
|
1420
|
-
const canConfirm = hasReady;
|
|
1421
|
-
const canClickConfirm = hasReady;
|
|
1422
|
-
const actionSet = new Set(viewModelHintActions);
|
|
1423
|
-
const asHint = (label, enabled) => enabled ? label : `${theme.dim}${label}${theme.reset}`;
|
|
1424
|
-
const labels = [
|
|
1425
|
-
actionSet.has("navigate") ? asHint(SELECTOR_HINT_LABELS.navigate, canNavigate) : "",
|
|
1426
|
-
actionSet.has("edit") ? asHint(SELECTOR_HINT_LABELS.edit, canEdit) : "",
|
|
1427
|
-
actionSet.has("refine") ? asHint(SELECTOR_HINT_LABELS.refine, canRefine) : "",
|
|
1428
|
-
actionSet.has("quit") ? asHint(SELECTOR_HINT_LABELS.quit, canQuit) : "",
|
|
1429
|
-
actionSet.has("confirm") ? asHint(SELECTOR_HINT_LABELS.confirm, canConfirm) : "",
|
|
1430
|
-
actionSet.has("clickConfirm") ? asHint(SELECTOR_HINT_LABELS.clickConfirm, canClickConfirm) : ""
|
|
1431
|
-
].filter((line) => line !== "");
|
|
1862
|
+
function renderHintActions(actions) {
|
|
1863
|
+
if (actions.length === 0) return "";
|
|
1864
|
+
const labels = actions.map((a) => SELECTOR_HINT_LABELS[a]).filter(Boolean);
|
|
1432
1865
|
return labels.length > 0 ? ` ${labels.join(" | ")}` : "";
|
|
1433
1866
|
}
|
|
1434
|
-
function
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1867
|
+
function renderLineToString(line) {
|
|
1868
|
+
switch (line.type) {
|
|
1869
|
+
case "headerRunning":
|
|
1870
|
+
return `${theme.progress}${line.spinner}${theme.reset} ${theme.primary}${line.label} ${line.progress}${line.costSuffix}${theme.reset}`;
|
|
1871
|
+
case "headerDone":
|
|
1872
|
+
return ui.success(`${line.label}${line.costSuffix}`);
|
|
1873
|
+
case "blank":
|
|
1874
|
+
return "";
|
|
1875
|
+
case "slot": {
|
|
1876
|
+
if (line.radio === ">") {
|
|
1877
|
+
return ` ${theme.success}>${theme.reset} ${line.title}`;
|
|
1878
|
+
}
|
|
1879
|
+
const radioStr = `${line.selected ? `${theme.success}\u25CF` : `${theme.dim}\u25CB`}${theme.reset}`;
|
|
1880
|
+
const titleColor = line.selected ? theme.primary : theme.dim;
|
|
1881
|
+
const titleFont = line.selected ? theme.bold : "";
|
|
1882
|
+
return ` ${radioStr} ${titleColor}${titleFont}${line.title}${theme.reset}`;
|
|
1883
|
+
}
|
|
1884
|
+
case "slotMeta": {
|
|
1885
|
+
const metaColor = line.muted ? theme.dim : theme.primary;
|
|
1886
|
+
return ` ${metaColor}${line.text}${theme.reset}`;
|
|
1887
|
+
}
|
|
1888
|
+
case "promptInput":
|
|
1889
|
+
return `${theme.primary}${line.prefix}${theme.reset}${line.text}`;
|
|
1890
|
+
case "placeholder":
|
|
1891
|
+
return ` ${theme.dim}${line.text}${theme.reset}`;
|
|
1892
|
+
case "hint":
|
|
1893
|
+
if (line.actions.length > 0) {
|
|
1894
|
+
return renderHintActions(line.actions);
|
|
1895
|
+
}
|
|
1896
|
+
return ` ${ui.hint(line.text)}`;
|
|
1897
|
+
case "editedSummary":
|
|
1898
|
+
return ui.success(`Edited: ${line.text}`);
|
|
1899
|
+
}
|
|
1455
1900
|
}
|
|
1456
1901
|
function renderSelectorLinesFromRenderFrame(frame) {
|
|
1457
|
-
|
|
1458
|
-
const viewModel = frame.viewModel;
|
|
1459
|
-
const costSuffix = viewModel.header.totalCostLabel != null ? ` (total: ${viewModel.header.totalCostLabel})` : "";
|
|
1460
|
-
if (viewModel.header.mode === "running") {
|
|
1461
|
-
lines.push(
|
|
1462
|
-
`${theme.progress}${viewModel.header.spinner}${theme.reset} ${theme.primary}${viewModel.header.runningLabel} ${viewModel.header.progress}${costSuffix}${theme.reset}`
|
|
1463
|
-
);
|
|
1464
|
-
} else {
|
|
1465
|
-
lines.push(ui.success(`${viewModel.header.generatedLabel}${costSuffix}`));
|
|
1466
|
-
}
|
|
1467
|
-
if (frame.mode === "prompt") {
|
|
1468
|
-
lines.push(...renderPromptLines(frame));
|
|
1469
|
-
return lines;
|
|
1470
|
-
}
|
|
1471
|
-
lines.push("");
|
|
1472
|
-
for (const slot of viewModel.slots) {
|
|
1473
|
-
for (const line of renderSlotLine(slot)) {
|
|
1474
|
-
lines.push(line);
|
|
1475
|
-
}
|
|
1476
|
-
lines.push("");
|
|
1477
|
-
}
|
|
1478
|
-
if (viewModel.editedSummary) {
|
|
1479
|
-
lines.push("");
|
|
1480
|
-
lines.push(ui.success(`Edited: ${viewModel.editedSummary}`));
|
|
1481
|
-
}
|
|
1482
|
-
const readyCount = viewModel.slots.filter(
|
|
1483
|
-
(slot) => slot.status === "ready"
|
|
1484
|
-
).length;
|
|
1485
|
-
lines.push(
|
|
1486
|
-
renderHintLine(viewModel.hint.actions, readyCount) || renderHintLine(
|
|
1487
|
-
["navigate", "edit", "refine", "quit", "confirm"],
|
|
1488
|
-
readyCount
|
|
1489
|
-
)
|
|
1490
|
-
);
|
|
1491
|
-
return lines;
|
|
1902
|
+
return buildSelectorRenderLines(frame).map(renderLineToString);
|
|
1492
1903
|
}
|
|
1493
1904
|
function renderSelectorTextFromRenderFrame(frame) {
|
|
1494
1905
|
return `${renderSelectorLinesFromRenderFrame(frame).join("\n")}
|
|
@@ -1519,113 +1930,37 @@ function createRenderer(output) {
|
|
|
1519
1930
|
};
|
|
1520
1931
|
const clearAll = () => {
|
|
1521
1932
|
if (!isTTY(output)) {
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
const totalHeight = pendingHeight + committedHeight;
|
|
1525
|
-
if (totalHeight > 0) {
|
|
1526
|
-
readline2.moveCursor(output, 0, -totalHeight);
|
|
1527
|
-
readline2.cursorTo(output, 0);
|
|
1528
|
-
readline2.clearScreenDown(output);
|
|
1529
|
-
}
|
|
1530
|
-
pendingHeight = 0;
|
|
1531
|
-
committedHeight = 0;
|
|
1532
|
-
};
|
|
1533
|
-
const reset = () => {
|
|
1534
|
-
pendingHeight = 0;
|
|
1535
|
-
committedHeight = 0;
|
|
1536
|
-
};
|
|
1537
|
-
return {
|
|
1538
|
-
render,
|
|
1539
|
-
renderSelectorFrame,
|
|
1540
|
-
flush,
|
|
1541
|
-
clearAll,
|
|
1542
|
-
reset
|
|
1543
|
-
};
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
// lib/selector.ts
|
|
1547
|
-
import { spawn } from "child_process";
|
|
1548
|
-
import { mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
1549
|
-
import { tmpdir } from "os";
|
|
1550
|
-
import { join as join4 } from "path";
|
|
1551
|
-
import * as readline3 from "readline";
|
|
1552
|
-
|
|
1553
|
-
// ../shared/terminal-selector-helpers.ts
|
|
1554
|
-
function formatModelName(model) {
|
|
1555
|
-
const parts = model.split("/");
|
|
1556
|
-
return parts.length > 1 ? parts[1] : model;
|
|
1557
|
-
}
|
|
1558
|
-
function formatCost(cost) {
|
|
1559
|
-
return `$${cost.toFixed(7).replace(/0+$/, "").replace(/\.$/, "")}`;
|
|
1560
|
-
}
|
|
1561
|
-
function formatTotalCostLabel(cost) {
|
|
1562
|
-
return `$${cost.toFixed(6)}`;
|
|
1563
|
-
}
|
|
1564
|
-
function getReadyCount(slots) {
|
|
1565
|
-
return slots.filter((slot) => slot.status === "ready").length;
|
|
1566
|
-
}
|
|
1567
|
-
function getTotalCost(slots) {
|
|
1568
|
-
return slots.reduce((sum, slot) => {
|
|
1569
|
-
if (slot.status === "ready" && slot.candidate.cost != null) {
|
|
1570
|
-
return sum + slot.candidate.cost;
|
|
1571
|
-
}
|
|
1572
|
-
return sum;
|
|
1573
|
-
}, 0);
|
|
1574
|
-
}
|
|
1575
|
-
function getLatestQuota(slots) {
|
|
1576
|
-
for (const slot of slots) {
|
|
1577
|
-
if (slot.status === "ready" && slot.candidate.quota) {
|
|
1578
|
-
return slot.candidate.quota;
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
return void 0;
|
|
1582
|
-
}
|
|
1583
|
-
function normalizeCandidateLineBreaks(content) {
|
|
1584
|
-
const normalized = content.replace(/\r\n?|[\u2028\u2029]/g, "\n");
|
|
1585
|
-
return normalized.split("\n")[0]?.trim() || "";
|
|
1586
|
-
}
|
|
1587
|
-
function normalizeCandidateContentForDisplay(content) {
|
|
1588
|
-
const line = normalizeCandidateLineBreaks(content);
|
|
1589
|
-
if (!line) {
|
|
1590
|
-
return "";
|
|
1591
|
-
}
|
|
1592
|
-
const collapsed = line.replace(/\s+/g, " ").trim();
|
|
1593
|
-
if (!collapsed) {
|
|
1594
|
-
return "";
|
|
1595
|
-
}
|
|
1596
|
-
if (collapsed.length % 2 === 0) {
|
|
1597
|
-
const half = collapsed.length / 2;
|
|
1598
|
-
if (collapsed.slice(0, half) === collapsed.slice(half)) {
|
|
1599
|
-
return collapsed.slice(0, half);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
const words = collapsed.split(" ");
|
|
1603
|
-
if (words.length >= 6 && words.length % 2 === 0) {
|
|
1604
|
-
const half = words.length / 2;
|
|
1605
|
-
const firstHalf = words.slice(0, half).join(" ");
|
|
1606
|
-
const secondHalf = words.slice(half).join(" ");
|
|
1607
|
-
if (firstHalf === secondHalf) {
|
|
1608
|
-
return firstHalf;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return collapsed;
|
|
1612
|
-
}
|
|
1613
|
-
function hasReadySlot(slots) {
|
|
1614
|
-
return getReadyCount(slots) > 0;
|
|
1615
|
-
}
|
|
1616
|
-
function selectNearestReady(slots, startIndex, direction) {
|
|
1617
|
-
for (let index = startIndex + direction; index >= 0 && index < slots.length; index += direction) {
|
|
1618
|
-
if (slots[index]?.status === "ready") {
|
|
1619
|
-
return index;
|
|
1933
|
+
return;
|
|
1620
1934
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1935
|
+
const totalHeight = pendingHeight + committedHeight;
|
|
1936
|
+
if (totalHeight > 0) {
|
|
1937
|
+
readline2.moveCursor(output, 0, -totalHeight);
|
|
1938
|
+
readline2.cursorTo(output, 0);
|
|
1939
|
+
readline2.clearScreenDown(output);
|
|
1940
|
+
}
|
|
1941
|
+
pendingHeight = 0;
|
|
1942
|
+
committedHeight = 0;
|
|
1943
|
+
};
|
|
1944
|
+
const reset = () => {
|
|
1945
|
+
pendingHeight = 0;
|
|
1946
|
+
committedHeight = 0;
|
|
1947
|
+
};
|
|
1948
|
+
return {
|
|
1949
|
+
render,
|
|
1950
|
+
renderSelectorFrame,
|
|
1951
|
+
flush,
|
|
1952
|
+
clearAll,
|
|
1953
|
+
reset
|
|
1954
|
+
};
|
|
1627
1955
|
}
|
|
1628
1956
|
|
|
1957
|
+
// lib/selector.ts
|
|
1958
|
+
import { spawn } from "child_process";
|
|
1959
|
+
import { mkdtempSync, readFileSync as readFileSync2, rmSync, writeFileSync } from "fs";
|
|
1960
|
+
import { tmpdir } from "os";
|
|
1961
|
+
import { join as join4 } from "path";
|
|
1962
|
+
import * as readline3 from "readline";
|
|
1963
|
+
|
|
1629
1964
|
// ../shared/terminal-selector-flow.ts
|
|
1630
1965
|
function cloneSlots(slots) {
|
|
1631
1966
|
return slots.map((slot) => {
|
|
@@ -1910,6 +2245,20 @@ function transitionSelectorFlow(context, event) {
|
|
|
1910
2245
|
}
|
|
1911
2246
|
break;
|
|
1912
2247
|
}
|
|
2248
|
+
case "ESCALATE": {
|
|
2249
|
+
if (next.mode !== "list" || !hasReadySlot(next.slots)) break;
|
|
2250
|
+
const selectedCandidate = getSelectedCandidate(
|
|
2251
|
+
next.slots,
|
|
2252
|
+
next.selectedIndex
|
|
2253
|
+
);
|
|
2254
|
+
const selected = selectedCandidate ? next.editedSelections.get(selectedCandidate.slotId) ?? selectedCandidate.content : void 0;
|
|
2255
|
+
result = {
|
|
2256
|
+
action: "escalate",
|
|
2257
|
+
selected,
|
|
2258
|
+
selectedCandidate
|
|
2259
|
+
};
|
|
2260
|
+
break;
|
|
2261
|
+
}
|
|
1913
2262
|
case "QUIT": {
|
|
1914
2263
|
if (next.mode === "prompt") {
|
|
1915
2264
|
next.mode = "list";
|
|
@@ -1941,208 +2290,6 @@ function transitionSelectorFlow(context, event) {
|
|
|
1941
2290
|
};
|
|
1942
2291
|
}
|
|
1943
2292
|
|
|
1944
|
-
// ../shared/terminal-selector-view-model.ts
|
|
1945
|
-
var DEFAULT_SPINNER_FRAMES = [
|
|
1946
|
-
"\u280B",
|
|
1947
|
-
"\u2819",
|
|
1948
|
-
"\u2839",
|
|
1949
|
-
"\u2838",
|
|
1950
|
-
"\u283C",
|
|
1951
|
-
"\u2834",
|
|
1952
|
-
"\u2826",
|
|
1953
|
-
"\u2827",
|
|
1954
|
-
"\u2807",
|
|
1955
|
-
"\u280F"
|
|
1956
|
-
];
|
|
1957
|
-
var HINT_ACTION_ORDER = [
|
|
1958
|
-
"navigate",
|
|
1959
|
-
"confirm",
|
|
1960
|
-
"clickConfirm",
|
|
1961
|
-
"edit",
|
|
1962
|
-
"refine",
|
|
1963
|
-
"quit"
|
|
1964
|
-
];
|
|
1965
|
-
var DEFAULT_SELECTOR_COPY = {
|
|
1966
|
-
runningLabel: "Generating commit messages...",
|
|
1967
|
-
selectionLabel: "Select a commit message",
|
|
1968
|
-
itemLabelSingular: "commit message",
|
|
1969
|
-
itemLabelPlural: "commit messages"
|
|
1970
|
-
};
|
|
1971
|
-
var DEFAULT_SELECTOR_CAPABILITIES = {
|
|
1972
|
-
clickConfirm: false,
|
|
1973
|
-
edit: false,
|
|
1974
|
-
refine: false
|
|
1975
|
-
};
|
|
1976
|
-
function formatDuration(ms) {
|
|
1977
|
-
const safeMs = Math.max(0, Math.round(ms));
|
|
1978
|
-
if (safeMs < 1e3) {
|
|
1979
|
-
return `${safeMs}ms`;
|
|
1980
|
-
}
|
|
1981
|
-
const seconds = (safeMs / 1e3).toFixed(1).replace(/\.0$/, "");
|
|
1982
|
-
return `${seconds}s`;
|
|
1983
|
-
}
|
|
1984
|
-
function resolveRenderMode(inputState) {
|
|
1985
|
-
return inputState.mode === "prompt" ? "prompt" : "list";
|
|
1986
|
-
}
|
|
1987
|
-
function resolvePromptLineLabel(kind) {
|
|
1988
|
-
return kind === "edit" ? "Edit mode" : "\u2192 Refine mode";
|
|
1989
|
-
}
|
|
1990
|
-
function buildPromptViewModel(input, viewModel) {
|
|
1991
|
-
const state = input.state;
|
|
1992
|
-
const promptKind = state.promptKind ?? "refine";
|
|
1993
|
-
const targetIndex = state.promptTargetIndex ?? state.selectedIndex;
|
|
1994
|
-
const targetSlot = state.slots[targetIndex];
|
|
1995
|
-
const targetText = targetSlot?.status === "ready" ? normalizeCandidateContentForDisplay(targetSlot.candidate.content) : "(no selection)";
|
|
1996
|
-
const totalCost = getTotalCost(state.slots);
|
|
1997
|
-
const costLineLabel = totalCost > 0 ? formatTotalCostLabel(totalCost) : "$0.000000";
|
|
1998
|
-
const generatedCostSuffix = viewModel.header.totalCostLabel ? ` (total: ${viewModel.header.totalCostLabel})` : "";
|
|
1999
|
-
const generatedLine = `${viewModel.header.generatedLabel}${generatedCostSuffix}`;
|
|
2000
|
-
const modeLine = resolvePromptLineLabel(promptKind);
|
|
2001
|
-
const targetLineLabel = `Target [${Math.min(state.totalSlots, targetIndex + 1)}]:`;
|
|
2002
|
-
const duration = formatDuration(input.nowMs - state.createdAtMs);
|
|
2003
|
-
const costLine = `Cost/Time (current): ${costLineLabel} / ${duration}`;
|
|
2004
|
-
const selectedLine = promptKind === "edit" ? `Selected: ${targetText}` : void 0;
|
|
2005
|
-
return {
|
|
2006
|
-
kind: promptKind,
|
|
2007
|
-
generatedLine,
|
|
2008
|
-
selectedLine,
|
|
2009
|
-
modeLine,
|
|
2010
|
-
targetLineLabel,
|
|
2011
|
-
targetText,
|
|
2012
|
-
targetIndex,
|
|
2013
|
-
costLine,
|
|
2014
|
-
questionLine: "?"
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
function normalizeHintActions(actions) {
|
|
2018
|
-
const set = new Set(actions);
|
|
2019
|
-
const ordered = [];
|
|
2020
|
-
for (const action of HINT_ACTION_ORDER) {
|
|
2021
|
-
if (set.has(action)) {
|
|
2022
|
-
ordered.push(action);
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
return ordered;
|
|
2026
|
-
}
|
|
2027
|
-
function resolveHintActions(input) {
|
|
2028
|
-
const actions = ["navigate", "confirm", "quit"];
|
|
2029
|
-
if (input.capabilities.clickConfirm) {
|
|
2030
|
-
actions.push("clickConfirm");
|
|
2031
|
-
}
|
|
2032
|
-
if (input.capabilities.edit) {
|
|
2033
|
-
actions.push("edit");
|
|
2034
|
-
}
|
|
2035
|
-
if (input.capabilities.refine) {
|
|
2036
|
-
actions.push("refine");
|
|
2037
|
-
}
|
|
2038
|
-
return normalizeHintActions(actions);
|
|
2039
|
-
}
|
|
2040
|
-
function formatReadyMeta(slot) {
|
|
2041
|
-
const { candidate } = slot;
|
|
2042
|
-
if (!candidate.model) {
|
|
2043
|
-
return "";
|
|
2044
|
-
}
|
|
2045
|
-
const formattedModel = formatModelName(candidate.model);
|
|
2046
|
-
const formattedDuration = candidate.generationMs == null ? "" : ` ${formatDuration(candidate.generationMs)}`;
|
|
2047
|
-
if (candidate.cost != null) {
|
|
2048
|
-
return `${formattedModel} ${formatCost(candidate.cost)}${formattedDuration}`;
|
|
2049
|
-
}
|
|
2050
|
-
return `${formattedModel}${formattedDuration}`;
|
|
2051
|
-
}
|
|
2052
|
-
function createSlotViewModel(slot, index, selectedIndex) {
|
|
2053
|
-
if (slot.status === "pending") {
|
|
2054
|
-
return {
|
|
2055
|
-
status: "pending",
|
|
2056
|
-
selected: false,
|
|
2057
|
-
radio: "\u25CB",
|
|
2058
|
-
title: "Generating...",
|
|
2059
|
-
meta: slot.model ? formatModelName(slot.model) : void 0,
|
|
2060
|
-
muted: true
|
|
2061
|
-
};
|
|
2062
|
-
}
|
|
2063
|
-
if (slot.status === "error") {
|
|
2064
|
-
return {
|
|
2065
|
-
status: "error",
|
|
2066
|
-
selected: false,
|
|
2067
|
-
radio: "\u25CB",
|
|
2068
|
-
title: slot.content,
|
|
2069
|
-
muted: true
|
|
2070
|
-
};
|
|
2071
|
-
}
|
|
2072
|
-
const selected = index === selectedIndex;
|
|
2073
|
-
const title = slot.candidate.content.split("\n")[0]?.trim() || "";
|
|
2074
|
-
const meta = formatReadyMeta(slot);
|
|
2075
|
-
return {
|
|
2076
|
-
status: "ready",
|
|
2077
|
-
selected,
|
|
2078
|
-
radio: selected ? "\u25CF" : "\u25CB",
|
|
2079
|
-
title,
|
|
2080
|
-
meta: meta || void 0,
|
|
2081
|
-
muted: !selected
|
|
2082
|
-
};
|
|
2083
|
-
}
|
|
2084
|
-
function resolveEditedSummary(input) {
|
|
2085
|
-
const selectedSlot = input.state.slots[input.state.selectedIndex];
|
|
2086
|
-
if (selectedSlot?.status !== "ready") {
|
|
2087
|
-
return void 0;
|
|
2088
|
-
}
|
|
2089
|
-
const edited = input.editedSelections?.get(selectedSlot.candidate.slotId);
|
|
2090
|
-
if (!edited) {
|
|
2091
|
-
return void 0;
|
|
2092
|
-
}
|
|
2093
|
-
return edited.split("\n")[0]?.slice(0, 120) || "";
|
|
2094
|
-
}
|
|
2095
|
-
function buildSelectorViewModel(input) {
|
|
2096
|
-
const spinnerFrames = input.spinnerFrames ?? DEFAULT_SPINNER_FRAMES;
|
|
2097
|
-
const copy = { ...DEFAULT_SELECTOR_COPY, ...input.copy };
|
|
2098
|
-
const capabilities = {
|
|
2099
|
-
...DEFAULT_SELECTOR_CAPABILITIES,
|
|
2100
|
-
...input.capabilities
|
|
2101
|
-
};
|
|
2102
|
-
const readyCount = getReadyCount(input.state.slots);
|
|
2103
|
-
const totalCost = getTotalCost(input.state.slots);
|
|
2104
|
-
const frame = Math.floor(input.nowMs / 80) % spinnerFrames.length;
|
|
2105
|
-
const generatedLabel = readyCount === 1 ? `Generated 1 ${copy.itemLabelSingular}` : `Generated ${readyCount} ${copy.itemLabelPlural}`;
|
|
2106
|
-
const hintActions = resolveHintActions({
|
|
2107
|
-
readyCount,
|
|
2108
|
-
capabilities
|
|
2109
|
-
});
|
|
2110
|
-
return {
|
|
2111
|
-
mode: resolveRenderMode(input.state),
|
|
2112
|
-
header: {
|
|
2113
|
-
mode: input.state.isGenerating ? "running" : "done",
|
|
2114
|
-
spinner: spinnerFrames[frame],
|
|
2115
|
-
progress: `${readyCount}/${input.state.totalSlots}`,
|
|
2116
|
-
totalCostLabel: totalCost > 0 ? formatTotalCostLabel(totalCost) : void 0,
|
|
2117
|
-
runningLabel: copy.runningLabel,
|
|
2118
|
-
generatedLabel
|
|
2119
|
-
},
|
|
2120
|
-
hint: {
|
|
2121
|
-
kind: readyCount > 0 ? "ready" : "empty",
|
|
2122
|
-
selectionLabel: readyCount > 0 ? copy.selectionLabel : void 0,
|
|
2123
|
-
actions: hintActions
|
|
2124
|
-
},
|
|
2125
|
-
slots: input.state.slots.map(
|
|
2126
|
-
(slot, index) => createSlotViewModel(slot, index, input.state.selectedIndex)
|
|
2127
|
-
),
|
|
2128
|
-
editedSummary: resolveEditedSummary(input)
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
function selectorRenderFrame(input) {
|
|
2132
|
-
const viewModel = buildSelectorViewModel(input);
|
|
2133
|
-
if (viewModel.mode === "prompt") {
|
|
2134
|
-
return {
|
|
2135
|
-
mode: "prompt",
|
|
2136
|
-
viewModel,
|
|
2137
|
-
prompt: buildPromptViewModel(input, viewModel)
|
|
2138
|
-
};
|
|
2139
|
-
}
|
|
2140
|
-
return {
|
|
2141
|
-
mode: "list",
|
|
2142
|
-
viewModel
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
2293
|
// lib/line-editor.ts
|
|
2147
2294
|
import stringWidth from "string-width";
|
|
2148
2295
|
var WORD_SEPARATORS = "`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?";
|
|
@@ -2805,6 +2952,7 @@ var selectorRenderCopy = {
|
|
|
2805
2952
|
var selectorRenderCapabilities = {
|
|
2806
2953
|
edit: true,
|
|
2807
2954
|
refine: true,
|
|
2955
|
+
escalate: true,
|
|
2808
2956
|
clickConfirm: false
|
|
2809
2957
|
};
|
|
2810
2958
|
function renderError(error, slotsLength, output) {
|
|
@@ -2870,6 +3018,7 @@ async function selectCandidate(options) {
|
|
|
2870
3018
|
initialListMode = "initial",
|
|
2871
3019
|
initialGuideHint,
|
|
2872
3020
|
inlineEditPrompt = false,
|
|
3021
|
+
isEscalation = false,
|
|
2873
3022
|
io
|
|
2874
3023
|
} = options;
|
|
2875
3024
|
const abortController = new AbortController();
|
|
@@ -2929,6 +3078,12 @@ async function selectCandidate(options) {
|
|
|
2929
3078
|
model: models?.[i]
|
|
2930
3079
|
})
|
|
2931
3080
|
);
|
|
3081
|
+
const renderCopy = isEscalation ? {
|
|
3082
|
+
...selectorRenderCopy,
|
|
3083
|
+
runningLabel: "Generating commit messages with escalate models...",
|
|
3084
|
+
itemLabelPlural: "commit messages with escalate models",
|
|
3085
|
+
itemLabelSingular: "commit message with escalate models"
|
|
3086
|
+
} : selectorRenderCopy;
|
|
2932
3087
|
return new Promise((resolve3) => {
|
|
2933
3088
|
let resolved = false;
|
|
2934
3089
|
const resolveOnce = (result) => {
|
|
@@ -2973,7 +3128,7 @@ async function selectCandidate(options) {
|
|
|
2973
3128
|
},
|
|
2974
3129
|
nowMs: Date.now(),
|
|
2975
3130
|
spinnerFrames: SPINNER_FRAMES,
|
|
2976
|
-
copy:
|
|
3131
|
+
copy: renderCopy,
|
|
2977
3132
|
capabilities: selectorRenderCapabilities
|
|
2978
3133
|
});
|
|
2979
3134
|
renderer.render(renderSelectorTextFromRenderFrame(frame));
|
|
@@ -2989,7 +3144,7 @@ async function selectCandidate(options) {
|
|
|
2989
3144
|
},
|
|
2990
3145
|
nowMs: Date.now(),
|
|
2991
3146
|
spinnerFrames: SPINNER_FRAMES,
|
|
2992
|
-
copy:
|
|
3147
|
+
copy: renderCopy,
|
|
2993
3148
|
capabilities: selectorRenderCapabilities
|
|
2994
3149
|
});
|
|
2995
3150
|
const selected = result.selectedCandidate?.content ?? result.selected ?? "";
|
|
@@ -3104,6 +3259,28 @@ async function selectCandidate(options) {
|
|
|
3104
3259
|
cleanup(false);
|
|
3105
3260
|
return;
|
|
3106
3261
|
}
|
|
3262
|
+
if (result.action === "escalate") {
|
|
3263
|
+
const frame = selectorRenderFrame({
|
|
3264
|
+
state: {
|
|
3265
|
+
...context,
|
|
3266
|
+
mode: context.mode,
|
|
3267
|
+
promptKind: context.promptKind,
|
|
3268
|
+
promptTargetIndex: context.promptTargetIndex
|
|
3269
|
+
},
|
|
3270
|
+
nowMs: Date.now(),
|
|
3271
|
+
spinnerFrames: SPINNER_FRAMES,
|
|
3272
|
+
copy: renderCopy,
|
|
3273
|
+
capabilities: selectorRenderCapabilities
|
|
3274
|
+
});
|
|
3275
|
+
const costSuffix = frame.viewModel.header.totalCostLabel ? ` (total: ${frame.viewModel.header.totalCostLabel})` : "";
|
|
3276
|
+
const generatedLine = `${frame.viewModel.header.generatedLabel}${costSuffix}`;
|
|
3277
|
+
renderer.clearAll();
|
|
3278
|
+
ttyWriter.write(`${ui.success(generatedLine)}
|
|
3279
|
+
`);
|
|
3280
|
+
resolveOnce(result);
|
|
3281
|
+
cleanup(false);
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3107
3284
|
if (result.action === "abort") {
|
|
3108
3285
|
resolveOnce(result);
|
|
3109
3286
|
cleanup(false);
|
|
@@ -3191,7 +3368,7 @@ async function selectCandidate(options) {
|
|
|
3191
3368
|
};
|
|
3192
3369
|
const openRefinePrompt = async () => {
|
|
3193
3370
|
await withPromptSuspended(async () => {
|
|
3194
|
-
let
|
|
3371
|
+
let lastCursorRow = 0;
|
|
3195
3372
|
const guide = await editLine({
|
|
3196
3373
|
input: ttyReader,
|
|
3197
3374
|
output: ttyWriter,
|
|
@@ -3199,65 +3376,46 @@ async function selectCandidate(options) {
|
|
|
3199
3376
|
initialValue: "",
|
|
3200
3377
|
finalizeMode: "none",
|
|
3201
3378
|
onRender: ({ buffer }) => {
|
|
3202
|
-
if (
|
|
3203
|
-
readline3.moveCursor(ttyWriter, 0, -
|
|
3379
|
+
if (lastCursorRow > 0) {
|
|
3380
|
+
readline3.moveCursor(ttyWriter, 0, -lastCursorRow);
|
|
3204
3381
|
}
|
|
3205
3382
|
readline3.cursorTo(ttyWriter, 0);
|
|
3206
3383
|
readline3.clearScreenDown(ttyWriter);
|
|
3207
3384
|
const frame = selectorRenderFrame({
|
|
3208
3385
|
state: {
|
|
3209
3386
|
...context,
|
|
3210
|
-
mode:
|
|
3387
|
+
mode: context.mode,
|
|
3211
3388
|
promptKind: context.promptKind,
|
|
3212
3389
|
promptTargetIndex: context.promptTargetIndex
|
|
3213
3390
|
},
|
|
3214
3391
|
nowMs: Date.now(),
|
|
3215
3392
|
spinnerFrames: SPINNER_FRAMES,
|
|
3216
|
-
copy:
|
|
3217
|
-
capabilities: selectorRenderCapabilities
|
|
3393
|
+
copy: renderCopy,
|
|
3394
|
+
capabilities: selectorRenderCapabilities,
|
|
3395
|
+
bufferText: buffer.getText()
|
|
3218
3396
|
});
|
|
3219
|
-
const
|
|
3220
|
-
|
|
3221
|
-
const
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
for (const slot of vm.slots) {
|
|
3225
|
-
const radio = slot.selected ? `${theme.success}\u25CF${theme.reset}` : `${theme.dim}\u25CB${theme.reset}`;
|
|
3226
|
-
const titleColor = slot.selected ? theme.primary : theme.dim;
|
|
3227
|
-
const titleFont = slot.selected ? theme.bold : "";
|
|
3228
|
-
lines.push(
|
|
3229
|
-
` ${radio} ${titleColor}${titleFont}${slot.title}${theme.reset}`
|
|
3230
|
-
);
|
|
3231
|
-
if (slot.meta) {
|
|
3232
|
-
const metaColor = slot.selected ? theme.primary : theme.dim;
|
|
3233
|
-
lines.push(` ${metaColor}${slot.meta}${theme.reset}`);
|
|
3234
|
-
}
|
|
3235
|
-
lines.push("");
|
|
3236
|
-
}
|
|
3237
|
-
const refineLineIndex = lines.length;
|
|
3238
|
-
const refinePrefix = ` ${theme.primary}Refine:${theme.reset} `;
|
|
3239
|
-
lines.push(`${refinePrefix}${buffer.getText()}`);
|
|
3240
|
-
lines.push(
|
|
3241
|
-
` ${theme.dim}e.g. more formal / shorter / in Japanese${theme.reset}`
|
|
3397
|
+
const prompt = frame.prompt;
|
|
3398
|
+
if (!prompt) return;
|
|
3399
|
+
const rendered = renderSelectorTextFromRenderFrame(frame).replace(
|
|
3400
|
+
/\n$/,
|
|
3401
|
+
""
|
|
3242
3402
|
);
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
ttyWriter.write(lines.join("\n"));
|
|
3246
|
-
const moveUp = lines.length - 1 - refineLineIndex;
|
|
3403
|
+
ttyWriter.write(rendered);
|
|
3404
|
+
const moveUp = prompt.promptLineCount - 1 - prompt.promptInputLineIndex;
|
|
3247
3405
|
if (moveUp > 0) {
|
|
3248
3406
|
readline3.moveCursor(ttyWriter, 0, -moveUp);
|
|
3249
3407
|
}
|
|
3250
3408
|
readline3.cursorTo(ttyWriter, 0);
|
|
3251
|
-
const prefixWidth =
|
|
3409
|
+
const prefixWidth = prompt.promptInputPrefixWidth;
|
|
3252
3410
|
const col = prefixWidth + buffer.getDisplayCursor();
|
|
3253
3411
|
if (col > 0) {
|
|
3254
3412
|
readline3.moveCursor(ttyWriter, col, 0);
|
|
3255
3413
|
}
|
|
3256
|
-
|
|
3414
|
+
lastCursorRow = prompt.promptInputLineIndex;
|
|
3257
3415
|
}
|
|
3258
3416
|
});
|
|
3259
|
-
if (
|
|
3260
|
-
readline3.moveCursor(ttyWriter, 0, -
|
|
3417
|
+
if (lastCursorRow > 0) {
|
|
3418
|
+
readline3.moveCursor(ttyWriter, 0, -lastCursorRow);
|
|
3261
3419
|
}
|
|
3262
3420
|
readline3.cursorTo(ttyWriter, 0);
|
|
3263
3421
|
readline3.clearScreenDown(ttyWriter);
|
|
@@ -3286,7 +3444,7 @@ async function selectCandidate(options) {
|
|
|
3286
3444
|
}
|
|
3287
3445
|
if (inlineEditPrompt) {
|
|
3288
3446
|
await withPromptSuspended(async () => {
|
|
3289
|
-
let
|
|
3447
|
+
let lastCursorRow = 0;
|
|
3290
3448
|
const edited = await editLine({
|
|
3291
3449
|
input: ttyReader,
|
|
3292
3450
|
output: ttyWriter,
|
|
@@ -3294,64 +3452,45 @@ async function selectCandidate(options) {
|
|
|
3294
3452
|
initialValue: selected.content,
|
|
3295
3453
|
finalizeMode: "none",
|
|
3296
3454
|
onRender: ({ buffer }) => {
|
|
3297
|
-
if (
|
|
3298
|
-
readline3.moveCursor(ttyWriter, 0, -
|
|
3455
|
+
if (lastCursorRow > 0) {
|
|
3456
|
+
readline3.moveCursor(ttyWriter, 0, -lastCursorRow);
|
|
3299
3457
|
}
|
|
3300
3458
|
readline3.cursorTo(ttyWriter, 0);
|
|
3301
3459
|
readline3.clearScreenDown(ttyWriter);
|
|
3302
3460
|
const frame = selectorRenderFrame({
|
|
3303
3461
|
state: {
|
|
3304
3462
|
...context,
|
|
3305
|
-
mode:
|
|
3463
|
+
mode: context.mode,
|
|
3306
3464
|
promptKind: context.promptKind,
|
|
3307
3465
|
promptTargetIndex: context.promptTargetIndex
|
|
3308
3466
|
},
|
|
3309
3467
|
nowMs: Date.now(),
|
|
3310
3468
|
spinnerFrames: SPINNER_FRAMES,
|
|
3311
|
-
copy:
|
|
3312
|
-
capabilities: selectorRenderCapabilities
|
|
3469
|
+
copy: renderCopy,
|
|
3470
|
+
capabilities: selectorRenderCapabilities,
|
|
3471
|
+
bufferText: buffer.getText()
|
|
3313
3472
|
});
|
|
3314
|
-
const
|
|
3315
|
-
const
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
ui.success(`${vm.header.generatedLabel}${costSuffix}`)
|
|
3473
|
+
const prompt = frame.prompt;
|
|
3474
|
+
const rendered = renderSelectorTextFromRenderFrame(frame).replace(
|
|
3475
|
+
/\n$/,
|
|
3476
|
+
""
|
|
3319
3477
|
);
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
if (slot.selected) {
|
|
3324
|
-
editLineIndex = lines.length;
|
|
3325
|
-
lines.push(
|
|
3326
|
-
` ${theme.success}>${theme.reset} ${buffer.getText()}`
|
|
3327
|
-
);
|
|
3328
|
-
} else {
|
|
3329
|
-
lines.push(
|
|
3330
|
-
` ${theme.dim}\u25CB${theme.reset} ${theme.dim}${slot.title}${theme.reset}`
|
|
3331
|
-
);
|
|
3332
|
-
}
|
|
3333
|
-
if (slot.meta) {
|
|
3334
|
-
const metaColor = slot.selected ? theme.primary : theme.dim;
|
|
3335
|
-
lines.push(` ${metaColor}${slot.meta}${theme.reset}`);
|
|
3336
|
-
}
|
|
3337
|
-
lines.push("");
|
|
3338
|
-
}
|
|
3339
|
-
lines.push(` ${ui.hint("enter apply | esc back to select")}`);
|
|
3340
|
-
ttyWriter.write(lines.join("\n"));
|
|
3341
|
-
const moveUp = lines.length - 1 - editLineIndex;
|
|
3478
|
+
ttyWriter.write(rendered);
|
|
3479
|
+
if (!prompt) return;
|
|
3480
|
+
const moveUp = prompt.promptLineCount - 1 - prompt.promptInputLineIndex;
|
|
3342
3481
|
if (moveUp > 0) {
|
|
3343
3482
|
readline3.moveCursor(ttyWriter, 0, -moveUp);
|
|
3344
3483
|
}
|
|
3345
3484
|
readline3.cursorTo(ttyWriter, 0);
|
|
3346
|
-
const col =
|
|
3485
|
+
const col = prompt.promptInputPrefixWidth + buffer.getDisplayCursor();
|
|
3347
3486
|
if (col > 0) {
|
|
3348
3487
|
readline3.moveCursor(ttyWriter, col, 0);
|
|
3349
3488
|
}
|
|
3350
|
-
|
|
3489
|
+
lastCursorRow = prompt.promptInputLineIndex;
|
|
3351
3490
|
}
|
|
3352
3491
|
});
|
|
3353
|
-
if (
|
|
3354
|
-
readline3.moveCursor(ttyWriter, 0, -
|
|
3492
|
+
if (lastCursorRow > 0) {
|
|
3493
|
+
readline3.moveCursor(ttyWriter, 0, -lastCursorRow);
|
|
3355
3494
|
}
|
|
3356
3495
|
readline3.cursorTo(ttyWriter, 0);
|
|
3357
3496
|
readline3.clearScreenDown(ttyWriter);
|
|
@@ -3410,7 +3549,12 @@ async function selectCandidate(options) {
|
|
|
3410
3549
|
}
|
|
3411
3550
|
return;
|
|
3412
3551
|
}
|
|
3413
|
-
if (key.name === "e") {
|
|
3552
|
+
if (key.name === "e" && key.shift) {
|
|
3553
|
+
if (!hasReadySlot(context.slots)) return;
|
|
3554
|
+
applyResult(transitionSelectorFlow(context, { type: "ESCALATE" }));
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3557
|
+
if (key.name === "e" && !key.shift) {
|
|
3414
3558
|
if (!hasReadySlot(context.slots)) return;
|
|
3415
3559
|
const transition = transitionSelectorFlow(context, {
|
|
3416
3560
|
type: "OPEN_PROMPT",
|
|
@@ -3726,7 +3870,9 @@ async function commit(args2) {
|
|
|
3726
3870
|
args: ["commit", ...args2],
|
|
3727
3871
|
apiPath: "/v1/commit-message/stream"
|
|
3728
3872
|
});
|
|
3729
|
-
const
|
|
3873
|
+
const baseModels = resolveModels(options.cliModels);
|
|
3874
|
+
const escalationModels = resolveEscalationModels();
|
|
3875
|
+
let models = baseModels;
|
|
3730
3876
|
const diff = getStagedDiff();
|
|
3731
3877
|
if (!diff.trim()) {
|
|
3732
3878
|
console.error(
|
|
@@ -3793,6 +3939,7 @@ async function commit(args2) {
|
|
|
3793
3939
|
};
|
|
3794
3940
|
const stats = getGitStagedStats();
|
|
3795
3941
|
console.log(ui.success(`Found ${formatDiffStats(stats)}`));
|
|
3942
|
+
let isEscalation = false;
|
|
3796
3943
|
while (true) {
|
|
3797
3944
|
const isRefineAttempt = refineMessage !== void 0;
|
|
3798
3945
|
const { commandExecutionSignal, commandExecutionPromise, cliSessionId } = startCommandExecutionSession(isRefineAttempt);
|
|
@@ -3815,7 +3962,8 @@ async function commit(args2) {
|
|
|
3815
3962
|
abortSignal: commandExecutionSignal,
|
|
3816
3963
|
models,
|
|
3817
3964
|
inlineEditPrompt: true,
|
|
3818
|
-
initialGuideHint: guideHint
|
|
3965
|
+
initialGuideHint: guideHint,
|
|
3966
|
+
isEscalation
|
|
3819
3967
|
});
|
|
3820
3968
|
if (result.action === "abort") {
|
|
3821
3969
|
if (result.error instanceof InvalidModelError) {
|
|
@@ -3827,6 +3975,14 @@ async function commit(args2) {
|
|
|
3827
3975
|
console.error("Aborting commit.");
|
|
3828
3976
|
process.exit(1);
|
|
3829
3977
|
}
|
|
3978
|
+
if (result.action === "escalate") {
|
|
3979
|
+
console.log(ui.hint(" -> Escalate"));
|
|
3980
|
+
models = escalationModels;
|
|
3981
|
+
guideHint = void 0;
|
|
3982
|
+
refineMessage = void 0;
|
|
3983
|
+
isEscalation = true;
|
|
3984
|
+
continue;
|
|
3985
|
+
}
|
|
3830
3986
|
if (result.action === "refine") {
|
|
3831
3987
|
guideHint = result.guide;
|
|
3832
3988
|
refineMessage = result.selected ?? result.selectedCandidate?.content;
|