@volter/supercode-terminal 0.2.46 → 0.2.48
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/index.mjs +37 -6
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1545,9 +1545,12 @@ export class SupercodeTerminalController {
|
|
|
1545
1545
|
const abort = new AbortController();
|
|
1546
1546
|
const task = (async () => {
|
|
1547
1547
|
const deadline = Date.now() + 5 * 60_000;
|
|
1548
|
+
let readySince = null;
|
|
1548
1549
|
while (!abort.signal.aborted && Date.now() < deadline) {
|
|
1549
1550
|
const content = await this.#host.capture(sessionId, { preserveEscapes: true });
|
|
1550
|
-
|
|
1551
|
+
const ready = composerReadyForInput(harness, content) === true;
|
|
1552
|
+
readySince = ready ? (readySince ?? Date.now()) : null;
|
|
1553
|
+
if (ready && Date.now() - readySince >= 1_000) {
|
|
1551
1554
|
if (harness !== 'claude-code' && harness !== 'codex') {
|
|
1552
1555
|
await this.#host.sendInput(sessionId, input, { submitMode: 'single', deadlineMs: Date.now() + 10_000 });
|
|
1553
1556
|
return;
|
|
@@ -1736,14 +1739,18 @@ export function composerText(kind, screen) {
|
|
|
1736
1739
|
const lines = String(screen ?? '').split('\n').map(styledCells);
|
|
1737
1740
|
const plain = lines.map((cells) => cells.map((cell) => cell.char).join(''));
|
|
1738
1741
|
const rule = /^\s*[─━]{4,}\s*$/u;
|
|
1742
|
+
const footer = /^\s*(?:\? for shortcuts\b|[^\n]+ · (?:~[\\/]|[\\/]|[A-Z]:[\\/])[^\n]+$|.*← for agents\s*$|.*\bcontext(?:\s+\d+%)?\s+left\b)/iu;
|
|
1739
1743
|
const caret = kind === 'codex' ? /^\s*›(?:\s|$)/u : /^\s*[❯>](?:\s|$)/u;
|
|
1740
1744
|
for (let row = plain.length - 1; row >= 0; row -= 1) {
|
|
1741
1745
|
if (!caret.test(plain[row])) continue;
|
|
1742
1746
|
let end = row + 1;
|
|
1743
|
-
while (end < plain.length && !rule.test(plain[end])
|
|
1744
|
-
!/^\s*(?:\? for shortcuts|.*\bcontext left\b)/u.test(plain[end])) end += 1;
|
|
1747
|
+
while (end < plain.length && !(kind === 'codex' ? footer.test(plain[end]) : rule.test(plain[end]))) end += 1;
|
|
1745
1748
|
// Claude encloses the editor in rules; Codex ends it at its shortcut/context footer.
|
|
1746
1749
|
if (end === plain.length) return null;
|
|
1750
|
+
// Startup can draw a caret before loading finishes or a modal opens. A rule
|
|
1751
|
+
// alone is not Codex's ready editor; require its footer at the live bottom.
|
|
1752
|
+
if (kind === 'codex' && (plain.slice(end + 1).some((line) => line.trim()) ||
|
|
1753
|
+
plain.some((line) => /\bmodel:\s+loading\b/iu.test(line)))) return null;
|
|
1747
1754
|
if (kind === 'claude-code' && (row === 0 || !rule.test(plain[row - 1]))) return null;
|
|
1748
1755
|
const first = plain[row].match(caret)[0].length;
|
|
1749
1756
|
const value = lines.slice(row, end).map((cells, index) => cells
|
|
@@ -1754,6 +1761,30 @@ export function composerText(kind, screen) {
|
|
|
1754
1761
|
return null;
|
|
1755
1762
|
}
|
|
1756
1763
|
|
|
1764
|
+
/** Match screen rows against the original text. A visual row boundary can be
|
|
1765
|
+
* a hard newline, a word wrap (one space), or a wrap inside a word. Never erase
|
|
1766
|
+
* whitespace within rows: a changed or partially pasted draft stays unconfirmed.
|
|
1767
|
+
*/
|
|
1768
|
+
function composerMatches(value, wanted) {
|
|
1769
|
+
if (value === null) return false;
|
|
1770
|
+
let offsets = new Set([0]);
|
|
1771
|
+
const rows = value.split('\n');
|
|
1772
|
+
for (let row = 0; row < rows.length; row += 1) {
|
|
1773
|
+
const next = new Set();
|
|
1774
|
+
for (const offset of offsets) {
|
|
1775
|
+
if (!wanted.startsWith(rows[row], offset)) continue;
|
|
1776
|
+
const end = offset + rows[row].length;
|
|
1777
|
+
if (row === rows.length - 1) next.add(end);
|
|
1778
|
+
else {
|
|
1779
|
+
next.add(end);
|
|
1780
|
+
if (wanted[end] === ' ' || wanted[end] === '\n') next.add(end + 1);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
offsets = next;
|
|
1784
|
+
}
|
|
1785
|
+
return offsets.has(wanted.length);
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1757
1788
|
/** Paste once, then submit only text read back from the live composer.
|
|
1758
1789
|
* False means unconfirmed, and can leave a draft: callers must not blindly resend.
|
|
1759
1790
|
*/
|
|
@@ -1773,14 +1804,14 @@ export async function deliverComposerInput(host, sessionId, kind, text, { deadli
|
|
|
1773
1804
|
for (let attempt = 0; attempt < 10 && !expired(); attempt += 1) {
|
|
1774
1805
|
await sleep(100);
|
|
1775
1806
|
const current = await read();
|
|
1776
|
-
if (current
|
|
1807
|
+
if (composerMatches(current, wanted)) { pasted = true; break; }
|
|
1777
1808
|
if (current !== '') return failed('paste-unconfirmed');
|
|
1778
1809
|
}
|
|
1779
1810
|
if (!pasted) return failed('paste-unconfirmed');
|
|
1780
1811
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1781
1812
|
// Re-read immediately before each Enter, including the first one.
|
|
1782
1813
|
if (expired()) return failed('deadline');
|
|
1783
|
-
if (await read()
|
|
1814
|
+
if (!composerMatches(await read(), wanted)) return failed('composer-changed');
|
|
1784
1815
|
if (expired()) return failed('deadline');
|
|
1785
1816
|
// A keystream Enter has no hidden delay or retry between the read and the write.
|
|
1786
1817
|
await host.sendInput(sessionId, '\r', { submitMode: 'single', deadlineMs });
|
|
@@ -1788,7 +1819,7 @@ export async function deliverComposerInput(host, sessionId, kind, text, { deadli
|
|
|
1788
1819
|
await sleep(100);
|
|
1789
1820
|
const current = await read();
|
|
1790
1821
|
if (current === '') return { delivered: true, delivery };
|
|
1791
|
-
if (current
|
|
1822
|
+
if (!composerMatches(current, wanted)) return failed('submission-unconfirmed');
|
|
1792
1823
|
}
|
|
1793
1824
|
}
|
|
1794
1825
|
return failed('submit-unconfirmed');
|