@volter/supercode-terminal 0.2.53 → 0.2.55
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.d.ts +4 -3
- package/index.mjs +86 -37
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -252,6 +252,7 @@ export type {
|
|
|
252
252
|
export type { TerminalInputDelivery, TmuxCommand, TmuxWindowSize };
|
|
253
253
|
|
|
254
254
|
/** The bounded live composer text, or null for an unrecognized screen. */
|
|
255
|
-
export function composerText(kind: string, screen: string | undefined): string | null;
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
export function composerText(kind: string, screen: string | undefined, options?: { allowBusy?: boolean }): string | null;
|
|
256
|
+
export function restoreComposerInput(host: TmuxTerminalHost | PtyTerminalHost | PaneServerHost, sessionId: string, kind: string, text: string): Promise<{ restored: boolean; reason?: string; remaining?: string }>;
|
|
257
|
+
/** Failed input is restored or returns an explicit cleanup obligation; never blindly resend. */
|
|
258
|
+
export function deliverComposerInput(host: TmuxTerminalHost | PtyTerminalHost | PaneServerHost, sessionId: string, kind: string, text: string, options?: { deadlineMs?: number; signal?: AbortSignal }): Promise<{ delivered: boolean; reason?: string; restored?: boolean; cleanup_required?: boolean; cleanup_reason?: string; cleanup_remaining?: string; delivery?: TerminalInputDelivery }>;
|
package/index.mjs
CHANGED
|
@@ -1738,12 +1738,13 @@ export function composerHoldsDraft(kind, screen) {
|
|
|
1738
1738
|
/** Read only a bounded, live Claude/Codex composer, never a prompt in scrollback.
|
|
1739
1739
|
* Unknown layouts and collapsed pastes are deliberately unconfirmed.
|
|
1740
1740
|
*/
|
|
1741
|
-
export function composerText(kind, screen) {
|
|
1741
|
+
export function composerText(kind, screen, { allowBusy = false } = {}) {
|
|
1742
1742
|
if (kind !== 'claude-code' && kind !== 'codex') return null;
|
|
1743
1743
|
const lines = String(screen ?? '').split('\n').map(styledCells);
|
|
1744
1744
|
const plain = lines.map((cells) => cells.map((cell) => cell.char).join(''));
|
|
1745
|
+
if (!allowBusy && composerBusy(screen)) return null;
|
|
1745
1746
|
const rule = /^\s*[─━]{4,}\s*$/u;
|
|
1746
|
-
const footer = /^\s*(
|
|
1747
|
+
const footer = /^\s*(?:.*\btab to queue (?:a )?message\b.*$|\? for shortcuts\b|⚠\s+\d+\s+warnings?\s+·\s+f2 to view\s*$|[^\n]+ · (?:~(?:[\\/]|(?=\s|$))|[\\/]|[A-Z]:[\\/])[^\n]*$|.*← for agents\s*$|.*\bcontext(?:\s+\d+%)?\s+left\b)/iu;
|
|
1747
1748
|
const caret = kind === 'codex' ? /^\s*›(?:\s|$)/u : /^\s*[❯>](?:\s|$)/u;
|
|
1748
1749
|
for (let row = plain.length - 1; row >= 0; row -= 1) {
|
|
1749
1750
|
if (!caret.test(plain[row])) continue;
|
|
@@ -1790,44 +1791,92 @@ function composerMatches(value, wanted) {
|
|
|
1790
1791
|
return offsets.has(wanted.length);
|
|
1791
1792
|
}
|
|
1792
1793
|
|
|
1793
|
-
/**
|
|
1794
|
-
*
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1794
|
+
/** Busy evidence must come from the live UI bottom, not a fleet label or a
|
|
1795
|
+
* quoted phrase in the transcript. */
|
|
1796
|
+
function composerBusyEvidence(screen) {
|
|
1797
|
+
const bottom=String(screen??'').split('\n').map(line=>styledCells(line).map(cell=>cell.char).join('')).filter(line=>line.trim()).slice(-8);
|
|
1798
|
+
for (const row of bottom) {
|
|
1799
|
+
const match=/\b(?:esc to (?:interrupt|cancel)|tab to queue (?:a )?message)\b/iu.exec(row);
|
|
1800
|
+
if (match) return { indicator: match[0], row: row.trim().slice(0, 240) };
|
|
1801
|
+
}
|
|
1802
|
+
return null;
|
|
1803
|
+
}
|
|
1804
|
+
function composerBusy(screen) { return composerBusyEvidence(screen) !== null; }
|
|
1805
|
+
|
|
1806
|
+
/** Undo only text this input wrote, including when a turn started meanwhile.
|
|
1807
|
+
* Never Enter, Escape, or Ctrl-C: restoration must not submit or stop work. */
|
|
1808
|
+
export async function restoreComposerInput(host, sessionId, kind, text) {
|
|
1809
|
+
const read=async()=>composerText(kind,await host.capture(sessionId,{preserveEscapes:true}),{allowBusy:true});
|
|
1810
|
+
let remaining=await read();
|
|
1811
|
+
if(remaining==='')return {restored:true};
|
|
1812
|
+
if(!composerMatches(remaining,String(text).trim()))return {restored:false,reason:'composer-changed-or-unreadable'};
|
|
1813
|
+
const lines=remaining.split('\n').length+2;
|
|
1814
|
+
for(let line=0;line<lines;line++) {
|
|
1815
|
+
if(await read()!==remaining)return {restored:false,reason:'composer-changed',remaining};
|
|
1816
|
+
await host.sendInput(sessionId,'\u0005\u0015',{submitMode:'none'});
|
|
1810
1817
|
await sleep(100);
|
|
1811
|
-
|
|
1812
|
-
if
|
|
1813
|
-
if
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
if
|
|
1819
|
-
if
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1818
|
+
let after=await read();
|
|
1819
|
+
if(after==='')return {restored:true};
|
|
1820
|
+
if(after===null||!remaining.includes(after))return {restored:false,reason:'composer-changed',remaining};
|
|
1821
|
+
remaining=after;
|
|
1822
|
+
if(await read()!==remaining)return {restored:false,reason:'composer-changed',remaining};
|
|
1823
|
+
await host.sendInput(sessionId,'\u007f',{submitMode:'none'});
|
|
1824
|
+
await sleep(100);after=await read();
|
|
1825
|
+
if(after==='')return {restored:true};
|
|
1826
|
+
if(after===null||!remaining.replace(/\n/g,'').includes(after.replace(/\n/g,'')))return {restored:false,reason:'composer-changed',remaining};
|
|
1827
|
+
remaining=after;
|
|
1828
|
+
}
|
|
1829
|
+
return {restored:false,reason:'restore-unconfirmed',remaining};
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/** Paste only into a stable idle composer. A failed paste is restored; an
|
|
1833
|
+
* unreadable/concurrently changed composer returns an explicit repair obligation. */
|
|
1834
|
+
export async function deliverComposerInput(host, sessionId, kind, text, { deadlineMs = Date.now() + 10_000, signal } = {}) {
|
|
1835
|
+
const capture=()=>host.capture(sessionId,{preserveEscapes:true});
|
|
1836
|
+
const read=async()=>composerText(kind,await capture(),{allowBusy:true});
|
|
1837
|
+
const expired=()=>signal?.aborted||Date.now()>=deadlineMs;
|
|
1838
|
+
let touched=false;
|
|
1839
|
+
const failed=async (reason, evidence={})=>{
|
|
1840
|
+
if(!touched)return {delivered:false,reason,...evidence};
|
|
1841
|
+
let restored;
|
|
1842
|
+
try{restored=await restoreComposerInput(host,sessionId,kind,text);}catch(error){restored={restored:false,reason:error.message};}
|
|
1843
|
+
return {delivered:false,reason,...evidence,restored:restored.restored,cleanup_required:!restored.restored,cleanup_reason:restored.reason,cleanup_remaining:restored.remaining};
|
|
1844
|
+
};
|
|
1845
|
+
if(expired())return failed('deadline');
|
|
1846
|
+
const wanted=String(text).trim();
|
|
1847
|
+
if(!wanted||/[\x00-\x08\x0b-\x1f\x7f]/u.test(text))return failed('unsupported-text');
|
|
1848
|
+
for(let check=0;check<2;check++) {
|
|
1849
|
+
const screen=await capture(),before=composerText(kind,screen);
|
|
1850
|
+
if(composerBusy(screen))return failed('busy-composer',{phase:'before-paste',busy:composerBusyEvidence(screen)});
|
|
1851
|
+
if(before!=='')return failed(before===null?'no-composer':'draft');
|
|
1852
|
+
if(check===0)await sleep(200);
|
|
1853
|
+
}
|
|
1854
|
+
if(expired())return failed('deadline');
|
|
1855
|
+
try {
|
|
1856
|
+
touched=true;
|
|
1857
|
+
const delivery=await host.sendInput(sessionId,`${text}\n`,{submitMode:'none',deadlineMs});
|
|
1858
|
+
let pasted=false;
|
|
1859
|
+
for(let attempt=0;attempt<10&&!expired();attempt++) {
|
|
1824
1860
|
await sleep(100);
|
|
1825
|
-
|
|
1826
|
-
if (current === '') return { delivered: true, delivery };
|
|
1827
|
-
if (!composerMatches(current, wanted)) return failed('submission-unconfirmed');
|
|
1861
|
+
if(composerMatches(await read(),wanted)){pasted=true;break;}
|
|
1828
1862
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
1863
|
+
if(!pasted)return failed('paste-unconfirmed');
|
|
1864
|
+
for(let attempt=0;attempt<3;attempt++) {
|
|
1865
|
+
if(expired())return failed('deadline');
|
|
1866
|
+
const screen=await capture();
|
|
1867
|
+
if(composerBusy(screen))return failed('busy-composer',{phase:'before-submit',busy:composerBusyEvidence(screen)});
|
|
1868
|
+
if(!composerMatches(composerText(kind,screen),wanted))return failed('composer-changed');
|
|
1869
|
+
if(expired())return failed('deadline');
|
|
1870
|
+
await host.sendInput(sessionId,'\r',{submitMode:'single',deadlineMs});
|
|
1871
|
+
for(let poll=0;poll<10&&!expired();poll++) {
|
|
1872
|
+
await sleep(100);
|
|
1873
|
+
const current=await read();
|
|
1874
|
+
if(current==='')return {delivered:true,delivery};
|
|
1875
|
+
if(!composerMatches(current,wanted))return failed('submission-unconfirmed');
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
return failed('submit-unconfirmed');
|
|
1879
|
+
}catch(error){return failed(error?.message??String(error));}
|
|
1831
1880
|
}
|
|
1832
1881
|
|
|
1833
1882
|
/** The carets each kind draws at the start of its composer line (Claude on Windows draws `>`). */
|