@volter/supercode-teams 0.3.107 → 0.3.109
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/codex-startup.mjs +22 -5
- package/machine.mjs +52 -3
- package/package.json +1 -1
- package/terminal-mail.mjs +2 -0
package/codex-startup.mjs
CHANGED
|
@@ -148,13 +148,30 @@ function codexWindowsCharacter(character) {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/** Text as the raw writes that paste it into a Windows Codex composer: bracketed pastes of at most
|
|
151
|
-
* CODEX_WINDOWS_PASTE_CHARS characters each
|
|
152
|
-
*
|
|
151
|
+
* CODEX_WINDOWS_PASTE_CHARS written characters each (a key event counts as its whole encoding, so non-ASCII-heavy
|
|
152
|
+
* text takes more, smaller pastes), non-ASCII BMP characters as key events. A piece ends after its last line break
|
|
153
|
+
* when it has one, and never begins with one: a line break arriving first after the gap between pastes could read as
|
|
154
|
+
* Enter and submit part of the text. Line ends are normalized to `\n`. Written as they are (a terminal host turns `\n` into `\r`, which Codex keeps
|
|
155
|
+
* as a line break inside a paste); never submitted here. */
|
|
153
156
|
export function codexWindowsPastes(text) {
|
|
154
|
-
|
|
157
|
+
// Line ends as `\n` only: Codex reads a pasted `\r\n` as two line breaks.
|
|
158
|
+
const encoded = Array.from(String(text).replace(/\r\n?/g, '\n')).map(codexWindowsCharacter);
|
|
155
159
|
const pastes = [];
|
|
156
|
-
for (let
|
|
157
|
-
|
|
160
|
+
for (let start = 0; start < encoded.length;) {
|
|
161
|
+
let end = start, written = 0, lastBreak = -1;
|
|
162
|
+
while (end < encoded.length && (end === start || written + encoded[end].length <= CODEX_WINDOWS_PASTE_CHARS)) {
|
|
163
|
+
written += encoded[end].length;
|
|
164
|
+
if (encoded[end] === '\n') lastBreak = end;
|
|
165
|
+
end += 1;
|
|
166
|
+
}
|
|
167
|
+
// Cut after the piece's last line break when the text goes on; else keep any line breaks that follow the cut
|
|
168
|
+
// with this piece, so the next one does not open with one.
|
|
169
|
+
if (end < encoded.length) {
|
|
170
|
+
if (lastBreak >= start && lastBreak + 1 < end) end = lastBreak + 1;
|
|
171
|
+
else while (end < encoded.length && encoded[end] === '\n') end += 1;
|
|
172
|
+
}
|
|
173
|
+
pastes.push(`\u001b[200~${encoded.slice(start, end).join('')}\u001b[201~`);
|
|
174
|
+
start = end;
|
|
158
175
|
}
|
|
159
176
|
return pastes;
|
|
160
177
|
}
|
package/machine.mjs
CHANGED
|
@@ -498,6 +498,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
498
498
|
// Set by a service-run connector: how `machine.restart` ends this daemon so its service manager starts it again.
|
|
499
499
|
this.restartHandler = null;
|
|
500
500
|
this.launches = new Launches(this.p.launches);
|
|
501
|
+
// the panes' stable porthole hook and the machine's stable supercode brought up to this release's at start, under
|
|
502
|
+
// every pane already running
|
|
503
|
+
this.#portholeHook();
|
|
504
|
+
this.#stableSupercode();
|
|
501
505
|
// Windows has no tmux: its panes live in the machine's pane server, a process of their own that outlives this
|
|
502
506
|
// daemon (a restart or an upgrade never ends a pane), as tmux panes live in tmux's server.
|
|
503
507
|
if (process.platform === 'win32' && !terminal.PaneServerHost) throw new Error('panes on Windows need a newer @volter/supercode-terminal (one with PaneServerHost)');
|
|
@@ -1774,6 +1778,49 @@ export class TeamsMachine extends MachineCore {
|
|
|
1774
1778
|
}
|
|
1775
1779
|
}
|
|
1776
1780
|
|
|
1781
|
+
/**
|
|
1782
|
+
* The porthole hook as a pane names it: one stable path in the machine's home, never this package's own. A pane keeps
|
|
1783
|
+
* the NODE_OPTIONS it was launched with for as long as it runs, and `--require` of a missing file kills every node
|
|
1784
|
+
* program in it; a path inside a Teams package goes missing when the daemon moves to another package and the old one
|
|
1785
|
+
* is removed (yuerans, 2026-10-06: the daemon moved from ~/.local to /opt/homebrew, ~/.local's packages were
|
|
1786
|
+
* removed, and every node program in the older panes, supercode itself among them, died with MODULE_NOT_FOUND). The
|
|
1787
|
+
* package's hook is copied there whenever it differs (replaced whole, by rename), so every pane, old or new, runs
|
|
1788
|
+
* the daemon's current hook; a home that cannot hold it falls back to the package's path.
|
|
1789
|
+
*/
|
|
1790
|
+
/**
|
|
1791
|
+
* The machine's stable supercode (`<SUPERCODE_HOME>/bin/supercode`, claude_relay.rs `stable_program_path`), pointed at
|
|
1792
|
+
* this daemon's native once per daemon (`__stable-program`), for a command a pane records; this daemon's own
|
|
1793
|
+
* supercode when its native has no such verb or the shim cannot be written.
|
|
1794
|
+
*/
|
|
1795
|
+
#stableSupercode() {
|
|
1796
|
+
if (this.stableSupercodePath !== undefined) return this.stableSupercodePath ?? this.supercodeBin;
|
|
1797
|
+
this.stableSupercodePath = null;
|
|
1798
|
+
try {
|
|
1799
|
+
const answer = spawnSync(this.supercodeBin, ['__stable-program'], { env: this.env, encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1800
|
+
const path = answer.status === 0 ? answer.stdout.trim() : '';
|
|
1801
|
+
if (path && isAbsolute(path) && existsSync(path)) this.stableSupercodePath = path;
|
|
1802
|
+
} catch { /* this daemon's own supercode */ }
|
|
1803
|
+
return this.stableSupercodePath ?? this.supercodeBin;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
#portholeHook() {
|
|
1807
|
+
if (this.portholeHookPath) return this.portholeHookPath;
|
|
1808
|
+
const stable = join(supercodeHome(this.env), 'porthole-hook.cjs');
|
|
1809
|
+
try {
|
|
1810
|
+
const hook = readFileSync(PORTHOLE_HOOK);
|
|
1811
|
+
let current = null;
|
|
1812
|
+
try { current = readFileSync(stable); } catch { /* not there yet */ }
|
|
1813
|
+
if (!current?.equals(hook)) {
|
|
1814
|
+
mkdirSync(dirname(stable), { recursive: true });
|
|
1815
|
+
const staged = `${stable}.${process.pid}.tmp`;
|
|
1816
|
+
writeFileSync(staged, hook, { mode: 0o644 });
|
|
1817
|
+
renameSync(staged, stable);
|
|
1818
|
+
}
|
|
1819
|
+
this.portholeHookPath = stable;
|
|
1820
|
+
} catch { this.portholeHookPath = PORTHOLE_HOOK; }
|
|
1821
|
+
return this.portholeHookPath;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1777
1824
|
/**
|
|
1778
1825
|
* The environment every launched pane carries: the porthole hook preloaded into
|
|
1779
1826
|
* each node process the pane runs, the announce directory it writes into, and the pane's
|
|
@@ -1786,9 +1833,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
1786
1833
|
env.PATH ??= this.env.PATH;
|
|
1787
1834
|
// Its CLI reads this machine's contexts and sockets, not whatever home that server started with.
|
|
1788
1835
|
if (this.env.SUPERCODE_HOME) env.SUPERCODE_HOME ??= this.env.SUPERCODE_HOME;
|
|
1789
|
-
|
|
1836
|
+
// an inherited porthole preload (a daemon started from a pane, or an older daemon's path) is replaced, never stacked
|
|
1837
|
+
const inherited = String(env.NODE_OPTIONS ?? this.env.NODE_OPTIONS ?? '').replace(/\s*--require[= ]("[^"]*porthole-hook\.cjs"|\S*porthole-hook\.cjs)/g, '');
|
|
1790
1838
|
// Quoted: NODE_OPTIONS splits on spaces, and a home path may hold one (Windows user folders often do).
|
|
1791
|
-
env.NODE_OPTIONS = `${inherited} --require ${JSON.stringify(
|
|
1839
|
+
env.NODE_OPTIONS = `${inherited} --require ${JSON.stringify(this.#portholeHook())}`.trim();
|
|
1792
1840
|
env.SUPERCODE_TEAMS_PORTS_DIR = this.portsDir;
|
|
1793
1841
|
env.SUPERCODE_TEAMS_PANE = contextKey;
|
|
1794
1842
|
return env;
|
|
@@ -1841,7 +1889,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
1841
1889
|
if (initialInput) nativeInput({ binding, kind: params.kind, text: initialInput, startedAt: Date.now() });
|
|
1842
1890
|
const claudeCommand = (next) => launchCommand(params.program, initialInput ? [...next, '--', initialInput] : next);
|
|
1843
1891
|
const quote = (value) => `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
1844
|
-
|
|
1892
|
+
// the machine's stable supercode: the session outlives this daemon's install path (an upgrade or a move)
|
|
1893
|
+
const command = `env SUPERCODE_HOME=${quote(mailHome)} ${quote(this.#stableSupercode())} message native-question-hook ${quote(binding)}`;
|
|
1845
1894
|
const handler = { type: 'command', command, timeout: 3600 };
|
|
1846
1895
|
if (params.kind === 'claude-code') {
|
|
1847
1896
|
const hooks = { SessionStart: [{ hooks: [handler] }], PreToolUse: [{ matcher: 'AskUserQuestion', hooks: [handler] }], PostToolUse: [{ matcher: 'AskUserQuestion', hooks: [handler] }] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.109",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
|
|
6
6
|
"license": "MIT",
|
package/terminal-mail.mjs
CHANGED
|
@@ -40,6 +40,8 @@ function confirmed(batch) {
|
|
|
40
40
|
const text = textOf(content);
|
|
41
41
|
if (text === null || text.length === 0) continue;
|
|
42
42
|
if (text === batch.text) return { fragments: 1 };
|
|
43
|
+
// A Windows Codex is pasted its text with line ends as `\n` (codexWindowsPastes).
|
|
44
|
+
if (batch.kind === 'codex' && text === batch.text.replace(/\r\n?/g, '\n')) return { fragments: 1 };
|
|
43
45
|
// Older mail delivery omitted the paste delimiter, so terminal newlines
|
|
44
46
|
// submitted one envelope as several native user messages. Recover only
|
|
45
47
|
// exact, ordered fragments of the retained bytes, separated by original
|