@volter/supercode-terminal 0.2.73 → 0.2.75
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 +1 -0
- package/index.mjs +10 -3
- package/package.json +2 -1
- package/substrate/harness-env.d.ts +12 -0
- package/substrate/harness-env.js +29 -0
- package/substrate/harness-markers.json +23 -0
- package/substrate/internal/exec.js +2 -1
- package/substrate/tmux-stream.js +2 -1
- package/substrate/tmux.js +5 -2
package/index.d.ts
CHANGED
|
@@ -258,6 +258,7 @@ export function composerReadyForInput(kind: string, screen: string | undefined):
|
|
|
258
258
|
export function composerHoldsDraft(kind: string, screen: string | undefined): boolean | null;
|
|
259
259
|
|
|
260
260
|
export { classifyAttentionSignal, observeLifecycle, gatherLifecycleObservation, createLifecycleStore, snapshotProcessTree, snapshotProcessTreeAsync } from './substrate/session-lifecycle.js';
|
|
261
|
+
export { HARNESS_MARKERS, HARNESS_CHILD_SESSION_MARKERS, isHarnessMarker, withoutHarnessMarkers, dropOwnHarnessMarkers } from './substrate/harness-env.js';
|
|
261
262
|
export type {
|
|
262
263
|
LifecycleSnapshot,
|
|
263
264
|
LifecycleStore,
|
package/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { createServer as defaultCreateServer } from 'node:http';
|
|
10
10
|
|
|
11
11
|
import { NativeTerminalSessionHost } from './substrate/native-host.js';
|
|
12
|
+
import { withoutHarnessMarkers } from './substrate/harness-env.js';
|
|
12
13
|
import * as defaultTmux from './substrate/tmux.js';
|
|
13
14
|
import { openLocalTerminal as defaultOpenLocalTerminal } from './substrate/local-terminal.js';
|
|
14
15
|
|
|
@@ -682,7 +683,7 @@ export class PtyTerminalHost {
|
|
|
682
683
|
// a half-installed package): the pane server answers this code by ending itself, and the next one loads afresh.
|
|
683
684
|
throw new TerminalHostError('pane_runtime_missing', `The pane runtime could not load: ${errorMessage(error)}`);
|
|
684
685
|
}
|
|
685
|
-
const env = { ...process.env, ...(input.env ?? {}) };
|
|
686
|
+
const env = { ...withoutHarnessMarkers(process.env), ...(input.env ?? {}) };
|
|
686
687
|
const { file, args } = windowsLaunch(input.command.program, input.command.arguments ?? [], env);
|
|
687
688
|
// The watchdog is running before the pane is, and the pane is on its list the moment it exists.
|
|
688
689
|
this.#startWatchdog();
|
|
@@ -1240,7 +1241,7 @@ export class PaneServerHost {
|
|
|
1240
1241
|
} catch (error) {
|
|
1241
1242
|
// No server on the pipe yet: start one, detached from this daemon, and try again as it comes up.
|
|
1242
1243
|
if (attempt === 0 && (error.code === 'ENOENT' || error.code === 'ECONNREFUSED')) {
|
|
1243
|
-
const child = spawn(process.execPath, [this.#script(), this.#pipe], { detached: true, stdio: 'ignore', windowsHide: true, env: process.env });
|
|
1244
|
+
const child = spawn(process.execPath, [this.#script(), this.#pipe], { detached: true, stdio: 'ignore', windowsHide: true, env: withoutHarnessMarkers(process.env) });
|
|
1244
1245
|
child.on('error', () => {});
|
|
1245
1246
|
child.unref();
|
|
1246
1247
|
}
|
|
@@ -1941,7 +1942,12 @@ export function composerText(kind, screen) {
|
|
|
1941
1942
|
const plain = lines.map((cells) => cells.map((cell) => cell.char).join(''));
|
|
1942
1943
|
// A session started with `--name` labels the rule above its editor (`──── name ─`); the label does not end the rule.
|
|
1943
1944
|
const rule = /^\s*[─━]{4,}(?:[^─━]+[─━]+)?\s*$/u;
|
|
1944
|
-
|
|
1945
|
+
// Codex's status row is `model · cwd` plus whatever else it has to say (a goal's state, a warning count). On a row
|
|
1946
|
+
// too narrow for all of it Codex cuts the cwd down to its ellipsis (`· … Goal stalled (/goal resume)`, `· ~… Goal
|
|
1947
|
+
// paused (/goal resume)`, both read from 80-column panes on yuerans, 2026-10-10), and a row whose cwd was required
|
|
1948
|
+
// to open with a path root read as no footer: the idle composer above it read as absent and the session's mail
|
|
1949
|
+
// waited as `no-composer` until it expired.
|
|
1950
|
+
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;
|
|
1945
1951
|
const caret = kind === 'codex' ? /^\s*›(?:\s|$)/u : /^\s*[❯>](?:\s|$)/u;
|
|
1946
1952
|
for (let row = plain.length - 1; row >= 0; row -= 1) {
|
|
1947
1953
|
if (!caret.test(plain[row])) continue;
|
|
@@ -2174,3 +2180,4 @@ function randomToken() {
|
|
|
2174
2180
|
// every surface that triages panes shares one precision rather than each
|
|
2175
2181
|
// inventing its own patterns.
|
|
2176
2182
|
export { classifyAttentionSignal, observeLifecycle, gatherLifecycleObservation, createLifecycleStore, snapshotProcessTree, snapshotProcessTreeAsync } from './substrate/session-lifecycle.js';
|
|
2183
|
+
export { HARNESS_MARKERS, HARNESS_CHILD_SESSION_MARKERS, isHarnessMarker, withoutHarnessMarkers, dropOwnHarnessMarkers } from './substrate/harness-env.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-terminal",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.75",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Optional leak-safe terminal and tmux host adapter for Volter Harness frontends",
|
|
6
6
|
"exports": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"types": "./react/stream.d.ts",
|
|
30
30
|
"import": "./react/stream.mjs"
|
|
31
31
|
},
|
|
32
|
+
"./harness-markers.json": "./substrate/harness-markers.json",
|
|
32
33
|
"./docker-exec": {
|
|
33
34
|
"types": "./substrate/docker-exec.d.ts",
|
|
34
35
|
"import": "./substrate/docker-exec.js"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** What a harness takes itself for a nested session by. Each is read for truth, so an empty value is no mark. */
|
|
2
|
+
export declare const HARNESS_CHILD_SESSION_MARKERS: readonly string[];
|
|
3
|
+
/** Every mark: the child-session ones, and what names the session a command ran for. */
|
|
4
|
+
export declare const HARNESS_MARKERS: readonly string[];
|
|
5
|
+
export declare function isHarnessMarker(name: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The environment a process supercode starts gets: `env` without the calling harness's own marks. `keep` names what
|
|
8
|
+
* the launch sets on purpose.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withoutHarnessMarkers<T extends Record<string, string | undefined>>(env: T, keep?: Iterable<string>): T;
|
|
11
|
+
/** Drops the marks from this process's own environment, so nothing it starts later can inherit them. */
|
|
12
|
+
export declare function dropOwnHarnessMarkers(env?: NodeJS.ProcessEnv): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
// A coding harness marks what its tools run: Claude Code exports CLAUDECODE, CLAUDE_CODE_CHILD_SESSION and the rest
|
|
3
|
+
// to every command of a session, Codex its thread. A process supercode starts that outlives the command (a terminal
|
|
4
|
+
// app, an attach client, a pane, the daemon) is not that session's child: carrying the marks, every shell a person
|
|
5
|
+
// opens in that terminal starts harnesses that take themselves for a nested session (Claude Code then saves no
|
|
6
|
+
// transcript). The names are harness-markers.json beside the built module, the one list; the CLI reads the same file
|
|
7
|
+
// (crates/cli/src/harness_env.rs).
|
|
8
|
+
const listed = JSON.parse(readFileSync(new URL("./harness-markers.json", import.meta.url), "utf8"));
|
|
9
|
+
/** What a harness takes itself for a nested session by. Each is read for truth, so an empty value is no mark. */
|
|
10
|
+
export const HARNESS_CHILD_SESSION_MARKERS = Object.freeze([...listed.child_session]);
|
|
11
|
+
/** Every mark: the child-session ones, and what names the session a command ran for. */
|
|
12
|
+
export const HARNESS_MARKERS = Object.freeze([...listed.child_session, ...listed.session]);
|
|
13
|
+
const markers = new Set(HARNESS_MARKERS);
|
|
14
|
+
export function isHarnessMarker(name) {
|
|
15
|
+
return markers.has(name);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The environment a process supercode starts gets: `env` without the calling harness's own marks. `keep` names what
|
|
19
|
+
* the launch sets on purpose.
|
|
20
|
+
*/
|
|
21
|
+
export function withoutHarnessMarkers(env, keep = []) {
|
|
22
|
+
const kept = new Set(keep);
|
|
23
|
+
return Object.fromEntries(Object.entries(env).filter(([name]) => !markers.has(name) || kept.has(name)));
|
|
24
|
+
}
|
|
25
|
+
/** Drops the marks from this process's own environment, so nothing it starts later can inherit them. */
|
|
26
|
+
export function dropOwnHarnessMarkers(env = process.env) {
|
|
27
|
+
for (const name of HARNESS_MARKERS)
|
|
28
|
+
delete env[name];
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"child_session": [
|
|
3
|
+
"CLAUDECODE",
|
|
4
|
+
"CLAUDE_CODE_CHILD_SESSION"
|
|
5
|
+
],
|
|
6
|
+
"session": [
|
|
7
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
8
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
9
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
10
|
+
"CLAUDE_CODE_EXECPATH",
|
|
11
|
+
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
12
|
+
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
13
|
+
"CLAUDE_CODE_INVOKED_SKILLS",
|
|
14
|
+
"CLAUDE_PID",
|
|
15
|
+
"CLAUDE_EFFORT",
|
|
16
|
+
"AI_AGENT",
|
|
17
|
+
"CODEX_THREAD_ID",
|
|
18
|
+
"CODEX_SESSION_ID",
|
|
19
|
+
"CODEX_SANDBOX",
|
|
20
|
+
"CODEX_SANDBOX_NETWORK_DISABLED",
|
|
21
|
+
"CODEX_INTERNAL_ORIGINATOR_OVERRIDE"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { withoutHarnessMarkers } from "../harness-env.js";
|
|
2
3
|
function runEnvironment(options) {
|
|
3
4
|
if (options.inheritEnv === false) {
|
|
4
5
|
return options.env ?? {};
|
|
@@ -181,7 +182,7 @@ async function spawnAsync(command, args, options, input) {
|
|
|
181
182
|
});
|
|
182
183
|
}
|
|
183
184
|
export function spawnInherited(command, args) {
|
|
184
|
-
const child = spawn(command, args, { stdio: "inherit" });
|
|
185
|
+
const child = spawn(command, args, { stdio: "inherit", env: withoutHarnessMarkers(process.env) });
|
|
185
186
|
child.on("exit", (code, signal) => {
|
|
186
187
|
if (signal) {
|
|
187
188
|
process.kill(process.pid, signal);
|
package/substrate/tmux-stream.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pty from "@homebridge/node-pty-prebuilt-multiarch";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import { withoutHarnessMarkers } from "./harness-env.js";
|
|
3
4
|
import { assertSession, getStatusLineCount, getWindowSize } from "./tmux.js";
|
|
4
5
|
export class TmuxTerminalAttachError extends Error {
|
|
5
6
|
phase;
|
|
@@ -46,7 +47,7 @@ export function attachTmuxTerminalSocket(options) {
|
|
|
46
47
|
cols,
|
|
47
48
|
cwd: options.cwd ?? process.cwd(),
|
|
48
49
|
env: {
|
|
49
|
-
...process.env,
|
|
50
|
+
...withoutHarnessMarkers(process.env),
|
|
50
51
|
TERM: "screen-256color"
|
|
51
52
|
},
|
|
52
53
|
rows
|
package/substrate/tmux.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { isHarnessMarker } from "./harness-env.js";
|
|
4
5
|
import { requireCommand, run, runAsync, runWithInputAsync, spawnInherited } from "./internal/exec.js";
|
|
5
6
|
import { snapshotDescendantPids } from "./internal/process-tree.js";
|
|
6
7
|
const TMUX_INSTALL_HINT = "Install it with: brew install tmux";
|
|
@@ -63,12 +64,14 @@ export async function createSession({ command, cwd, env, name, owner, ownerOptio
|
|
|
63
64
|
// it. When an agent's session started the server (or is the launcher), that includes the session's own harness
|
|
64
65
|
// identity, so a new pane would resolve as that session. A pane's identity is only what its own session sets:
|
|
65
66
|
// every identity variable the server or the launcher holds and the session does not set is unset in the pane.
|
|
66
|
-
|
|
67
|
+
// The harnesses' own marks are the shared list (harness-env); supercode's caller and agent variables are its own.
|
|
68
|
+
const supercodeIdentity = /^(SUPERCODE_CALLER|SUPERCODE_CALLER_NAME|SUPERCODE_LAUNCHED_BY|SUPERCODE_AGENT_[A-Z_]+)$/;
|
|
69
|
+
const identity = (name) => isHarnessMarker(name) || supercodeIdentity.test(name);
|
|
67
70
|
const serverNames = await runAsync("tmux", tmuxArgs(socket, ["show-environment", "-g"]))
|
|
68
71
|
.then((out) => out.split("\n").map((line) => /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line)?.[1]).filter((name) => Boolean(name)))
|
|
69
72
|
.catch(() => []); // no server yet: the one this creates starts from this launcher's environment
|
|
70
73
|
const inherited = [...new Set([...serverNames, ...Object.keys(process.env)])]
|
|
71
|
-
.filter((name) => identity
|
|
74
|
+
.filter((name) => identity(name) && !(name in sessionEnv));
|
|
72
75
|
const bootstrapDirectories = [];
|
|
73
76
|
const paneBootstrapArgs = (pane) => {
|
|
74
77
|
const commandArgs = command === undefined ? [] : [command.program, ...(command.arguments ?? [])];
|