@volter/supercode-terminal 0.2.56 → 0.2.58
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 +2 -2
- package/index.mjs +21 -24
- package/package.json +7 -17
- package/substrate/pane-server.mjs +8 -1
package/index.d.ts
CHANGED
|
@@ -252,7 +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
|
|
255
|
+
export function composerText(kind: string, screen: string | undefined): string | null;
|
|
256
256
|
export function restoreComposerInput(host: TmuxTerminalHost | PtyTerminalHost | PaneServerHost, sessionId: string, kind: string, text: string): Promise<{ restored: boolean; reason?: string; remaining?: string }>;
|
|
257
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;
|
|
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
|
@@ -4,6 +4,7 @@ import { appendFileSync, cpSync, existsSync, mkdirSync, readFileSync, renameSync
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
import { connect as netConnect } from 'node:net';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
9
|
import { createServer as defaultCreateServer } from 'node:http';
|
|
9
10
|
|
|
@@ -560,11 +561,18 @@ export class PtyTerminalHost {
|
|
|
560
561
|
if ([...this.#sessions.values()].some((entry) => entry.name === name)) {
|
|
561
562
|
throw new TerminalHostError('session_exists', `A pane named ${name} already exists.`);
|
|
562
563
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
564
|
+
let pty, headless, serialize;
|
|
565
|
+
try {
|
|
566
|
+
[{ default: pty }, { default: headless }, { default: serialize }] = await Promise.all([
|
|
567
|
+
import('@homebridge/node-pty-prebuilt-multiarch'),
|
|
568
|
+
import('@xterm/headless'),
|
|
569
|
+
import('@xterm/addon-serialize'),
|
|
570
|
+
]);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
// A process that failed this load may keep failing it after the package is installed (Node keeps what it read of
|
|
573
|
+
// a half-installed package): the pane server answers this code by ending itself, and the next one loads afresh.
|
|
574
|
+
throw new TerminalHostError('pane_runtime_missing', `The pane runtime could not load: ${errorMessage(error)}`);
|
|
575
|
+
}
|
|
568
576
|
const env = { ...process.env, ...(input.env ?? {}) };
|
|
569
577
|
const { file, args } = windowsLaunch(input.command.program, input.command.arguments ?? [], env);
|
|
570
578
|
// The watchdog is running before the pane is, and the pane is on its list the moment it exists.
|
|
@@ -974,6 +982,10 @@ export class PaneServerHost {
|
|
|
974
982
|
rmSync(staging, { recursive: true, force: true });
|
|
975
983
|
cpSync(root, join(staging, 'node_modules', '@volter', 'supercode-terminal'), { recursive: true, filter: (path) => !path.slice(root.length).split(/[\\/]/).includes('node_modules') });
|
|
976
984
|
for (const name of PANE_RUNTIME_PACKAGES) cpSync(packageDir(name), join(staging, 'node_modules', ...name.split('/')), { recursive: true });
|
|
985
|
+
// A copy taken of a half-installed package would stay broken under its version: it is complete only when every
|
|
986
|
+
// runtime package resolves to an entry that exists in it.
|
|
987
|
+
const fromCopy = createRequire(join(staging, 'node_modules', '@volter', 'supercode-terminal', 'package.json'));
|
|
988
|
+
for (const name of PANE_RUNTIME_PACKAGES) if (!existsSync(fromCopy.resolve(name))) throw new Error(`${name} has no entry in the copy`);
|
|
977
989
|
writeFileSync(join(staging, '.complete'), `${version}\n`);
|
|
978
990
|
rmSync(target, { recursive: true, force: true });
|
|
979
991
|
renameSync(staging, target);
|
|
@@ -1738,11 +1750,10 @@ export function composerHoldsDraft(kind, screen) {
|
|
|
1738
1750
|
/** Read only a bounded, live Claude/Codex composer, never a prompt in scrollback.
|
|
1739
1751
|
* Unknown layouts and collapsed pastes are deliberately unconfirmed.
|
|
1740
1752
|
*/
|
|
1741
|
-
export function composerText(kind, screen
|
|
1753
|
+
export function composerText(kind, screen) {
|
|
1742
1754
|
if (kind !== 'claude-code' && kind !== 'codex') return null;
|
|
1743
1755
|
const lines = String(screen ?? '').split('\n').map(styledCells);
|
|
1744
1756
|
const plain = lines.map((cells) => cells.map((cell) => cell.char).join(''));
|
|
1745
|
-
if (!allowBusy && composerBusy(screen)) return null;
|
|
1746
1757
|
const rule = /^\s*[─━]{4,}\s*$/u;
|
|
1747
1758
|
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;
|
|
1748
1759
|
const caret = kind === 'codex' ? /^\s*›(?:\s|$)/u : /^\s*[❯>](?:\s|$)/u;
|
|
@@ -1791,22 +1802,10 @@ function composerMatches(value, wanted) {
|
|
|
1791
1802
|
return offsets.has(wanted.length);
|
|
1792
1803
|
}
|
|
1793
1804
|
|
|
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
1805
|
/** Undo only text this input wrote, including when a turn started meanwhile.
|
|
1807
1806
|
* Never Enter, Escape, or Ctrl-C: restoration must not submit or stop work. */
|
|
1808
1807
|
export async function restoreComposerInput(host, sessionId, kind, text) {
|
|
1809
|
-
const read=async()=>composerText(kind,await host.capture(sessionId,{preserveEscapes:true})
|
|
1808
|
+
const read=async()=>composerText(kind,await host.capture(sessionId,{preserveEscapes:true}));
|
|
1810
1809
|
let remaining=await read();
|
|
1811
1810
|
if(remaining==='')return {restored:true};
|
|
1812
1811
|
if(!composerMatches(remaining,String(text).trim()))return {restored:false,reason:'composer-changed-or-unreadable'};
|
|
@@ -1833,7 +1832,7 @@ export async function restoreComposerInput(host, sessionId, kind, text) {
|
|
|
1833
1832
|
* unreadable/concurrently changed composer returns an explicit repair obligation. */
|
|
1834
1833
|
export async function deliverComposerInput(host, sessionId, kind, text, { deadlineMs = Date.now() + 10_000, signal } = {}) {
|
|
1835
1834
|
const capture=()=>host.capture(sessionId,{preserveEscapes:true});
|
|
1836
|
-
const read=async()=>composerText(kind,await capture()
|
|
1835
|
+
const read=async()=>composerText(kind,await capture());
|
|
1837
1836
|
const expired=()=>signal?.aborted||Date.now()>=deadlineMs;
|
|
1838
1837
|
let touched=false;
|
|
1839
1838
|
const failed=async (reason, evidence={})=>{
|
|
@@ -1847,7 +1846,6 @@ export async function deliverComposerInput(host, sessionId, kind, text, { deadli
|
|
|
1847
1846
|
if(!wanted||/[\x00-\x08\x0b-\x1f\x7f]/u.test(text))return failed('unsupported-text');
|
|
1848
1847
|
for(let check=0;check<2;check++) {
|
|
1849
1848
|
const screen=await capture(),before=composerText(kind,screen);
|
|
1850
|
-
if(composerBusy(screen))return failed('busy-composer',{phase:'before-paste',busy:composerBusyEvidence(screen)});
|
|
1851
1849
|
if(before!=='')return failed(before===null?'no-composer':'draft');
|
|
1852
1850
|
if(check===0)await sleep(200);
|
|
1853
1851
|
}
|
|
@@ -1864,14 +1862,13 @@ export async function deliverComposerInput(host, sessionId, kind, text, { deadli
|
|
|
1864
1862
|
for(let attempt=0;attempt<3;attempt++) {
|
|
1865
1863
|
if(expired())return failed('deadline');
|
|
1866
1864
|
const screen=await capture();
|
|
1867
|
-
if(composerBusy(screen))return failed('busy-composer',{phase:'before-submit',busy:composerBusyEvidence(screen)});
|
|
1868
1865
|
if(!composerMatches(composerText(kind,screen),wanted))return failed('composer-changed');
|
|
1869
1866
|
if(expired())return failed('deadline');
|
|
1870
1867
|
await host.sendInput(sessionId,'\r',{submitMode:'single',deadlineMs});
|
|
1871
1868
|
for(let poll=0;poll<10&&!expired();poll++) {
|
|
1872
1869
|
await sleep(100);
|
|
1873
1870
|
const current=await read();
|
|
1874
|
-
if(current==='')return {delivered:true,delivery};
|
|
1871
|
+
if(current==='')return kind === 'claude-code' ? {delivered:true,delivery,path:'composer_submit'} : {delivered:false,reason:'native-transcript-confirmation-required',delivery};
|
|
1875
1872
|
if(!composerMatches(current,wanted))return failed('submission-unconfirmed');
|
|
1876
1873
|
}
|
|
1877
1874
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-terminal",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.58",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Optional leak-safe terminal and tmux host adapter for Volter Harness frontends",
|
|
6
6
|
"exports": {
|
|
@@ -95,8 +95,12 @@
|
|
|
95
95
|
"prepack": "npm run build",
|
|
96
96
|
"build:substrate": "tsc -p tsconfig.substrate.json"
|
|
97
97
|
},
|
|
98
|
-
"
|
|
98
|
+
"dependencies": {
|
|
99
99
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1 || ^0.14.1",
|
|
100
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
101
|
+
"@xterm/headless": "^6.0.0"
|
|
102
|
+
},
|
|
103
|
+
"peerDependencies": {
|
|
100
104
|
"@xterm/addon-fit": "^0.11.0",
|
|
101
105
|
"@xterm/addon-search": "^0.16.0",
|
|
102
106
|
"@xterm/addon-web-links": "^0.12.0",
|
|
@@ -104,14 +108,9 @@
|
|
|
104
108
|
"preact": ">=10.19.0 <11",
|
|
105
109
|
"react": ">=18 <20",
|
|
106
110
|
"ws": "^8.21.0",
|
|
107
|
-
"@volter/supercode-harness-sdk": "^0.3.18"
|
|
108
|
-
"@xterm/headless": "^6.0.0",
|
|
109
|
-
"@xterm/addon-serialize": "^0.14.0"
|
|
111
|
+
"@volter/supercode-harness-sdk": "^0.3.18"
|
|
110
112
|
},
|
|
111
113
|
"peerDependenciesMeta": {
|
|
112
|
-
"@homebridge/node-pty-prebuilt-multiarch": {
|
|
113
|
-
"optional": true
|
|
114
|
-
},
|
|
115
114
|
"@xterm/addon-fit": {
|
|
116
115
|
"optional": true
|
|
117
116
|
},
|
|
@@ -135,24 +134,15 @@
|
|
|
135
134
|
},
|
|
136
135
|
"@volter/supercode-harness-sdk": {
|
|
137
136
|
"optional": true
|
|
138
|
-
},
|
|
139
|
-
"@xterm/headless": {
|
|
140
|
-
"optional": true
|
|
141
|
-
},
|
|
142
|
-
"@xterm/addon-serialize": {
|
|
143
|
-
"optional": true
|
|
144
137
|
}
|
|
145
138
|
},
|
|
146
139
|
"devDependencies": {
|
|
147
|
-
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
148
140
|
"@types/node": "^22.20.1",
|
|
149
141
|
"@types/ws": "^8.18.1",
|
|
150
142
|
"@volter/supercode-harness-sdk": "^0.3.18",
|
|
151
143
|
"@xterm/addon-fit": "^0.11.0",
|
|
152
144
|
"@xterm/addon-search": "^0.16.0",
|
|
153
|
-
"@xterm/addon-serialize": "^0.14.0",
|
|
154
145
|
"@xterm/addon-web-links": "^0.12.0",
|
|
155
|
-
"@xterm/headless": "^6.0.0",
|
|
156
146
|
"@xterm/xterm": "^6.0.0",
|
|
157
147
|
"esbuild": "^0.25.0",
|
|
158
148
|
"preact": "^10.19.0",
|
|
@@ -86,7 +86,14 @@ const server = createServer((connection) => {
|
|
|
86
86
|
const { id, op, args = [] } = message;
|
|
87
87
|
if (op === 'attach') { attach(id, args, message.stream); continue; }
|
|
88
88
|
if (!OPS.has(op)) { refuse(id, { code: 'unsupported', message: `pane server: no operation ${op}` }); continue; }
|
|
89
|
-
Promise.resolve().then(() => host[op](...args)).then((result) => answer(id, result), (error) =>
|
|
89
|
+
Promise.resolve().then(() => host[op](...args)).then((result) => answer(id, result), async (error) => {
|
|
90
|
+
refuse(id, error);
|
|
91
|
+
// The pane runtime failed to load in this process: with no pane to keep, it ends once the answer is out, so
|
|
92
|
+
// the next request starts a pane server that loads the runtime afresh (one installed since this one started).
|
|
93
|
+
if (error?.code === 'pane_runtime_missing' && !(await host.listSessions().catch(() => [])).length) {
|
|
94
|
+
connection.end(() => { host.dispose(); process.exit(0); });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
90
97
|
}
|
|
91
98
|
});
|
|
92
99
|
// A client that goes (its daemon restarting) takes only its viewers with it: the panes stay.
|