@volter/supercode-terminal 0.2.63 → 0.2.64

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.
Files changed (3) hide show
  1. package/index.d.ts +1 -1
  2. package/index.mjs +23 -11
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -253,6 +253,6 @@ export type { TerminalInputDelivery, TmuxCommand, TmuxWindowSize };
253
253
 
254
254
  /** The bounded live composer text, or null for an unrecognized screen. */
255
255
  export function composerText(kind: string, screen: string | undefined): string | null;
256
- export function restoreComposerInput(host: TmuxTerminalHost | PtyTerminalHost | PaneServerHost, sessionId: string, kind: string, text: string): Promise<{ restored: boolean; reason?: string; remaining?: string }>;
256
+ export function restoreComposerInput(host: TmuxTerminalHost | PtyTerminalHost | PaneServerHost, sessionId: string, kind: string, text: string, options?: { pasted?: string | null }): Promise<{ restored: boolean; reason?: string; remaining?: string }>;
257
257
  /** Failed input is restored or returns an explicit cleanup obligation; never blindly resend. */
258
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
@@ -1844,22 +1844,26 @@ function composerMatches(value, wanted) {
1844
1844
  }
1845
1845
 
1846
1846
  /** Claude collapses a multi-line paste to `[Pasted text #N +K lines]`, and its composer then shows that placeholder,
1847
- * never the text. It stands for `text` when K is the pasted text's line breaks (the trimmed text's, or the sent
1848
- * payload's with its closing newline): a placeholder whose count differs is another paste. */
1849
- function pastePlaceholderMatches(composer, text) {
1850
- const placeholder=/^\[Pasted text #\d+ \+(\d+) lines?\]$/u.exec(String(composer??'').trim());
1851
- if(!placeholder)return false;
1847
+ * never the text. The placeholder (as shown) when it can stand for `text`: K is the pasted text's line breaks (the
1848
+ * trimmed text's, or the sent payload's with its closing newline). Which paste it is, #N, only the paste itself
1849
+ * tells: a caller holds the placeholder its own paste produced and accepts no other. */
1850
+ function pastePlaceholder(composer, text) {
1851
+ const shown=String(composer??'').trim();
1852
+ const placeholder=/^\[Pasted text #\d+ \+(\d+) lines?\]$/u.exec(shown);
1853
+ if(!placeholder)return null;
1852
1854
  const breaks=value=>(value.match(/\n/gu)??[]).length;
1853
- return [breaks(String(text).trim()),breaks(`${text}\n`)].includes(Number(placeholder[1]));
1855
+ return [breaks(String(text).trim()),breaks(`${text}\n`)].includes(Number(placeholder[1]))?shown:null;
1854
1856
  }
1855
1857
 
1856
1858
  /** Undo only text this input wrote, including when a turn started meanwhile.
1857
- * Never Enter, Escape, or Ctrl-C: restoration must not submit or stop work. */
1858
- export async function restoreComposerInput(host, sessionId, kind, text) {
1859
+ * Never Enter, Escape, or Ctrl-C: restoration must not submit or stop work.
1860
+ * `pasted`: the `[Pasted text #N …]` placeholder this input's own paste produced. Only that one is cleared: another
1861
+ * of the same size is a person's own paste. */
1862
+ export async function restoreComposerInput(host, sessionId, kind, text, { pasted = null } = {}) {
1859
1863
  const read=async()=>composerText(kind,await host.capture(sessionId,{preserveEscapes:true}));
1860
1864
  let remaining=await read();
1861
1865
  if(remaining==='')return {restored:true};
1862
- if(!composerMatches(remaining,String(text).trim())&&!pastePlaceholderMatches(remaining,text))return {restored:false,reason:'composer-changed-or-unreadable'};
1866
+ if(!composerMatches(remaining,String(text).trim())&&!(pasted&&remaining.trim()===pasted))return {restored:false,reason:'composer-changed-or-unreadable'};
1863
1867
  const lines=remaining.split('\n').length+2;
1864
1868
  for(let line=0;line<lines;line++) {
1865
1869
  if(await read()!==remaining)return {restored:false,reason:'composer-changed',remaining};
@@ -1886,15 +1890,23 @@ export async function deliverComposerInput(host, sessionId, kind, text, { deadli
1886
1890
  const read=async()=>composerText(kind,await capture());
1887
1891
  const expired=()=>signal?.aborted||Date.now()>=deadlineMs;
1888
1892
  let touched=false;
1893
+ // The placeholder this paste produced: the first one seen after pasting into a composer read empty twice. From then
1894
+ // on only that one (its #N) is this text, and only that one is restored away.
1895
+ let pasted=null;
1889
1896
  const failed=async (reason, evidence={})=>{
1890
1897
  if(!touched)return {delivered:false,reason,...evidence};
1891
1898
  let restored;
1892
- try{restored=await restoreComposerInput(host,sessionId,kind,text);}catch(error){restored={restored:false,reason:error.message};}
1899
+ try{restored=await restoreComposerInput(host,sessionId,kind,text,{pasted});}catch(error){restored={restored:false,reason:error.message};}
1893
1900
  return {delivered:false,reason,...evidence,restored:restored.restored,cleanup_required:!restored.restored,cleanup_reason:restored.reason,cleanup_remaining:restored.remaining};
1894
1901
  };
1895
1902
  if(expired())return failed('deadline');
1896
1903
  const wanted=String(text).trim();
1897
- const holds=composer=>composerMatches(composer,wanted)||pastePlaceholderMatches(composer,text);
1904
+ const holds=composer=>{
1905
+ if(composerMatches(composer,wanted))return true;
1906
+ const placeholder=pastePlaceholder(composer,text);
1907
+ if(placeholder&&touched&&pasted===null)pasted=placeholder;
1908
+ return placeholder!==null&&placeholder===pasted;
1909
+ };
1898
1910
  if(!wanted||/[\x00-\x08\x0b-\x1f\x7f]/u.test(text))return failed('unsupported-text');
1899
1911
  for(let check=0;check<2;check++) {
1900
1912
  const screen=await capture(),before=composerText(kind,screen);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-terminal",
3
- "version": "0.2.63",
3
+ "version": "0.2.64",
4
4
  "type": "module",
5
5
  "description": "Optional leak-safe terminal and tmux host adapter for Volter Harness frontends",
6
6
  "exports": {