@volter/supercode-terminal 0.2.71 → 0.2.73
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/CAPABILITIES.md +1 -1
- package/README.md +7 -0
- package/index.d.ts +3 -2
- package/index.mjs +48 -14
- package/package.json +1 -1
package/CAPABILITIES.md
CHANGED
|
@@ -23,7 +23,7 @@ means it must not be presented as supported.
|
|
|
23
23
|
| Scrollback, selection and copy | UI/xterm | verified | Bounded scrollback and native xterm selection. |
|
|
24
24
|
| Find in one terminal, next/previous match | UI | verified | Search covers the active xterm buffer. Cross-tab search is missing. |
|
|
25
25
|
| URL detection | UI | partial | The stable xterm web-link provider is active; host policy and end-to-end link opening still need a dedicated proof. |
|
|
26
|
-
| Exclusive input ownership | tmux/host | partial | `acquireInput`, `ownsInput` and `releaseInput` serialize a paste with other input and remain with the pane across a client restart. [Recorded tmux proof](../../docs/evidence/claude-input-2026-10-07.md#recorded-review-fixes) rejects a replacement while the badge is unrecorded, then confirms one native message after daemon restart. Older pane servers refuse this door; Windows ownership restart
|
|
26
|
+
| Exclusive input ownership | tmux/host | partial | `acquireInput`, `ownsInput` and `releaseInput` serialize a paste with other input and remain with the pane across a client restart. [Recorded tmux proof](../../docs/evidence/claude-input-2026-10-07.md#recorded-review-fixes) rejects a replacement while the badge is unrecorded, then confirms one native message after daemon restart. `PaneServerHost.createSession` can require this ABI before creating a pane; [manual old/current server admission](../../docs/evidence/launch-input-ownership-admission-2026-10-09.md) is recorded on macOS. Older pane servers refuse this door; Windows ownership restart and admission remain unmeasured. |
|
|
27
27
|
| Paste and bracketed paste | UI | verified | Multiline or large pastes require confirmation; history is memory-only. Advanced transforms and slow paste are missing. |
|
|
28
28
|
| Save terminal contents | UI | partial | The normal buffer is projected to a bounded plain-text download; the browser download boundary and continuous logging still need dedicated proof. |
|
|
29
29
|
| Bell and background activity | UI | verified | A controlled tmux shell proved active visual bell plus inactive activity/bell tab state. System notifications are host policy. |
|
package/README.md
CHANGED
|
@@ -52,6 +52,13 @@ PTY host reads its own live catalog; an older pane server refuses this operation
|
|
|
52
52
|
This is a host observation, not a physical window/tab reading or an existence
|
|
53
53
|
lock against a subsequent launch.
|
|
54
54
|
|
|
55
|
+
`PaneServerHost.createSession({ ..., requireInputOwnership: true })` requires
|
|
56
|
+
the connected server's ownership operation before creating a pane. An older
|
|
57
|
+
server rejects with `input_ownership_unsupported` and starts no process; the
|
|
58
|
+
same connection handles admission and creation. Omitting the option preserves
|
|
59
|
+
ordinary creation. [Manual old/current server SDK readings](../../docs/evidence/launch-input-ownership-admission-2026-10-09.md)
|
|
60
|
+
do not qualify Windows or native harness first-message delivery.
|
|
61
|
+
|
|
55
62
|
The substrate was moved from a retired package without behavioral changes.
|
|
56
63
|
Its existing home paths, environment variables, and tmux ownership markers stay
|
|
57
64
|
compatible. Those files retain Apache-2.0 licensing; see `NOTICE-SUBSTRATE` and
|
package/index.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export class TmuxTerminalHost {
|
|
|
58
58
|
env?: Record<string, string>;
|
|
59
59
|
name?: string;
|
|
60
60
|
}): Promise<TerminalSession>;
|
|
61
|
-
capture(sessionId: string, options?: { lines?: number; preserveEscapes?: boolean }): string
|
|
61
|
+
capture(sessionId: string, options?: { lines?: number; preserveEscapes?: boolean }): Promise<string>;
|
|
62
62
|
sendInput(
|
|
63
63
|
sessionId: string,
|
|
64
64
|
data: string,
|
|
@@ -91,7 +91,8 @@ export class PaneServerHost {
|
|
|
91
91
|
listSessions(): Promise<TerminalSession[]>;
|
|
92
92
|
/** Requires a compatible pane server; an unsupported operation rejects. */
|
|
93
93
|
hasLiveContext(contextKey: string): Promise<boolean>;
|
|
94
|
-
|
|
94
|
+
/** Require the running pane server's ownership door before creating a pane. */
|
|
95
|
+
createSession(input: { command: TmuxCommand; contextKey?: string; cwd?: string; env?: Record<string, string>; name?: string; requireInputOwnership?: boolean }): Promise<TerminalSession>;
|
|
95
96
|
capture(sessionId: string, options?: { lines?: number; preserveEscapes?: boolean }): Promise<string>;
|
|
96
97
|
sendInput(sessionId: string, data: string, options?: { deadlineMs?: number; submitMode?: 'retry' | 'single' | 'none'; inputOwner?: string }): Promise<TerminalInputDelivery>;
|
|
97
98
|
/** Exclusive input ownership, retained by the pane host across client restart. */
|
package/index.mjs
CHANGED
|
@@ -293,14 +293,24 @@ export class TmuxTerminalHost {
|
|
|
293
293
|
return created;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
296
|
+
// Asynchronous, and one tmux process: the daemon reads every pane's screen each time it judges attention, and a
|
|
297
|
+
// synchronous read (a session listing and a capture per pane, each a spawnSync) held its event loop for all of them
|
|
298
|
+
// back to back, so a local client's hello waited behind the whole pass. A session that is gone is told from the
|
|
299
|
+
// failed capture, by the same listing as before.
|
|
300
|
+
async capture(sessionId, options = {}) {
|
|
301
|
+
const entry = this.#catalog.get(sessionId);
|
|
302
|
+
if (!entry) throw new TerminalHostError('session_not_found', 'The tmux session no longer exists.');
|
|
303
|
+
try {
|
|
304
|
+
return await this.#tmux.capturePaneAsync({
|
|
305
|
+
lines: options.lines,
|
|
306
|
+
preserveEscapes: options.preserveEscapes === true,
|
|
307
|
+
socket: this.#socket,
|
|
308
|
+
target: `${entry.name}:0.0`,
|
|
309
|
+
});
|
|
310
|
+
} catch (error) {
|
|
311
|
+
this.#entry(sessionId);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
304
314
|
}
|
|
305
315
|
|
|
306
316
|
async sendInput(sessionId, data, options = {}) {
|
|
@@ -1117,7 +1127,7 @@ export class PaneServerHost {
|
|
|
1117
1127
|
|
|
1118
1128
|
listSessions() { return this.#request('listSessions', []); }
|
|
1119
1129
|
hasLiveContext(contextKey) { assertContextKey(contextKey); return this.#request('hasLiveContext', [contextKey]); }
|
|
1120
|
-
createSession(input) { return this.#request('createSession', [input]); }
|
|
1130
|
+
createSession(input) { return this.#request('createSession', [input], { requireInputOwnership: input.requireInputOwnership === true }); }
|
|
1121
1131
|
capture(sessionId, options = {}) { return this.#request('capture', [sessionId, options]); }
|
|
1122
1132
|
sendInput(sessionId, data, options = {}) { return this.#request('sendInput', [sessionId, data, options]); }
|
|
1123
1133
|
acquireInput(sessionId, owner) { return this.#request('acquireInput', [sessionId, owner]); }
|
|
@@ -1175,12 +1185,27 @@ export class PaneServerHost {
|
|
|
1175
1185
|
// A server that is leaving refuses a request unrun (`pane_server_restarting`): it is asked again of the next one.
|
|
1176
1186
|
for (let attempt = 0; ; attempt += 1) {
|
|
1177
1187
|
const connection = await this.#connect();
|
|
1178
|
-
const id = this.#next++;
|
|
1179
1188
|
try {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1189
|
+
if (op === 'createSession' && extra.requireInputOwnership) {
|
|
1190
|
+
// The installed client can be newer than the server that still holds
|
|
1191
|
+
// this machine's panes. Check its actual ownership door before any
|
|
1192
|
+
// process starts, on the same connection that will create the pane.
|
|
1193
|
+
// null cannot identify a PtyTerminalHost session: session_not_found
|
|
1194
|
+
// means the vendor's ownership operation ran without touching a pane.
|
|
1195
|
+
try {
|
|
1196
|
+
await this.#call(connection, 'ownsInput', [null, 'capability_check']);
|
|
1197
|
+
throw new TerminalHostError('input_ownership_unconfirmed', 'The pane server did not confirm its input ownership door; no pane was created.');
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (error?.code === 'unsupported') {
|
|
1200
|
+
throw new TerminalHostError('input_ownership_unsupported', 'The running pane server does not support input ownership required for this first message; no pane was created. Update the pane server through its owner before retrying.');
|
|
1201
|
+
}
|
|
1202
|
+
if (error?.code !== 'session_not_found') throw error;
|
|
1203
|
+
}
|
|
1204
|
+
if (connection.destroyed || this.#connection !== connection) {
|
|
1205
|
+
throw new TerminalHostError('pane_server_unreachable', 'The pane server connection changed during input ownership admission; no pane was created.');
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return await this.#call(connection, op, args, { ...extra, requireInputOwnership: undefined });
|
|
1184
1209
|
} catch (error) {
|
|
1185
1210
|
if (error?.code !== 'pane_server_restarting' || attempt >= 20) throw error;
|
|
1186
1211
|
await sleep(250);
|
|
@@ -1188,6 +1213,15 @@ export class PaneServerHost {
|
|
|
1188
1213
|
}
|
|
1189
1214
|
}
|
|
1190
1215
|
|
|
1216
|
+
#call(connection, op, args, extra = {}) {
|
|
1217
|
+
const id = this.#next++;
|
|
1218
|
+
return new Promise((resolve, reject) => {
|
|
1219
|
+
this.#pending.set(id, { resolve, reject, connection });
|
|
1220
|
+
try { connection.write(`${JSON.stringify({ id, op, args, ...extra })}\n`); }
|
|
1221
|
+
catch (error) { this.#pending.delete(id); reject(error); }
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1191
1225
|
#connect() {
|
|
1192
1226
|
if (this.#connection && !this.#connection.destroyed) return Promise.resolve(this.#connection);
|
|
1193
1227
|
return (this.#connecting ??= this.#open().finally(() => { this.#connecting = null; }));
|