@volter/teams 0.1.36 → 0.1.38
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/machine-core.mjs +18 -5
- package/package.json +1 -1
- package/viewer/index.html +1 -1
- package/viewer/viewer.mjs +29 -3
package/machine-core.mjs
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// It decides nothing on anyone's behalf. Every act is logged.
|
|
9
9
|
import { createServer, connect as netConnect } from 'node:net';
|
|
10
10
|
import { EventEmitter } from 'node:events';
|
|
11
|
-
import { existsSync, mkdirSync, rmSync, chmodSync } from 'node:fs';
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, chmodSync, statSync } from 'node:fs';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
12
13
|
import { randomBytes } from 'node:crypto';
|
|
13
14
|
import { WebSocket, createWebSocketStream } from 'ws';
|
|
14
15
|
import { Channel, NODE_PROTOCOL } from './channel.mjs';
|
|
@@ -89,13 +90,22 @@ export class MachineCore extends EventEmitter {
|
|
|
89
90
|
await clearStaleSocket(this.socketPath);
|
|
90
91
|
await this.beforeListen();
|
|
91
92
|
this.server = createServer((socket) => this.#accept(socket));
|
|
93
|
+
// A Unix socket is bound under a name of this daemon's own and then renamed into place: Node unlinks the name it
|
|
94
|
+
// bound when the server closes, and that name must never be the shared path, which a later daemon may serve from a
|
|
95
|
+
// file of its own (a daemon stopping later deleted the serving daemon's socket and cut it off).
|
|
96
|
+
const bindPath = isNamedPipe(this.socketPath) ? this.socketPath : join(dirname(this.socketPath), `.m${process.pid.toString(36)}`);
|
|
97
|
+
if (bindPath !== this.socketPath) rmSync(bindPath, { force: true });
|
|
92
98
|
await new Promise((resolveListen, reject) => {
|
|
93
99
|
this.server.once('error', reject);
|
|
94
|
-
this.server.listen(
|
|
100
|
+
this.server.listen(bindPath, () => { this.server.off('error', reject); resolveListen(); });
|
|
95
101
|
});
|
|
96
102
|
// Owner-only: the socket is the local door, and whoever opens it is this OS user. A named pipe's default
|
|
97
103
|
// access already admits only its owner (and SYSTEM and Administrators) to connect.
|
|
98
|
-
if (!isNamedPipe(this.socketPath))
|
|
104
|
+
if (!isNamedPipe(this.socketPath)) {
|
|
105
|
+
chmodSync(bindPath, 0o600);
|
|
106
|
+
this.socketInode = statSync(bindPath).ino;
|
|
107
|
+
renameSync(bindPath, this.socketPath);
|
|
108
|
+
}
|
|
99
109
|
this.address = this.socketPath;
|
|
100
110
|
writeJson(this.p.machine, { pid: process.pid, id: this.id, socket: this.socketPath, startedAt: Date.now(), version: MACHINE_VERSION, protocol: NODE_PROTOCOL, floor: this.floor, server: this.link?.server ?? null });
|
|
101
111
|
this.emit('listening', this.socketPath);
|
|
@@ -116,8 +126,11 @@ export class MachineCore extends EventEmitter {
|
|
|
116
126
|
for (const { channel } of this.connections.values()) channel.close();
|
|
117
127
|
this.server?.close();
|
|
118
128
|
await this.afterStop();
|
|
119
|
-
|
|
120
|
-
if (
|
|
129
|
+
// Only this daemon's own socket and record: a daemon that has since taken the path over keeps them.
|
|
130
|
+
if (!isNamedPipe(this.socketPath) && this.socketInode !== undefined) {
|
|
131
|
+
try { if (statSync(this.socketPath).ino === this.socketInode) rmSync(this.socketPath, { force: true }); } catch { /* already gone */ }
|
|
132
|
+
}
|
|
133
|
+
try { if (JSON.parse(readFileSync(this.p.machine, 'utf8')).pid === process.pid) rmSync(this.p.machine); } catch { /* absent or unreadable */ }
|
|
121
134
|
}
|
|
122
135
|
|
|
123
136
|
/** Before the connections close: a product finishes or abandons what is in flight. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/teams",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
|
|
6
6
|
"license": "MIT",
|
package/viewer/index.html
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
#status { padding: 4px 10px; color: var(--muted); font-size: 12px; border-bottom: 1px solid var(--line); }
|
|
19
19
|
#status:empty { display: none; }
|
|
20
20
|
main { flex: 1; min-height: 0; display: flex; }
|
|
21
|
-
#terminal { flex: 1; min-width: 0; padding: 4px; }
|
|
21
|
+
#terminal { flex: 1; min-width: 0; padding: 4px; color-scheme: light dark; background: var(--volter-terminal-background); }
|
|
22
22
|
#folder { flex: 1; min-width: 0; display: none; }
|
|
23
23
|
#folder.open { display: flex; }
|
|
24
24
|
#entries { width: 280px; flex: none; border-right: 1px solid var(--line); overflow: auto; }
|
package/viewer/viewer.mjs
CHANGED
|
@@ -61,12 +61,37 @@ function useLayout(layout) {
|
|
|
61
61
|
const rows = layout === 'rows';
|
|
62
62
|
$('session').classList.toggle('layout-rows', rows);
|
|
63
63
|
if (rows && !document.getElementById('brand-tokens')) {
|
|
64
|
-
|
|
65
|
-
document.head.append(link);
|
|
64
|
+
brandTokens();
|
|
66
65
|
// A chat's rows are light unless the host's theme names a scheme.
|
|
67
66
|
if (!themed) document.documentElement.style.setProperty('--scui-color-scheme', 'light');
|
|
68
67
|
}
|
|
69
68
|
}
|
|
69
|
+
// The brand's tokens stylesheet, loaded once; resolves when it has loaded (or failed to).
|
|
70
|
+
let brandLoaded = null;
|
|
71
|
+
function brandTokens() {
|
|
72
|
+
if (brandLoaded) return brandLoaded;
|
|
73
|
+
const link = Object.assign(document.createElement('link'), { id: 'brand-tokens', rel: 'stylesheet', href: 'https://brand.volter.ai/tokens.css' });
|
|
74
|
+
brandLoaded = new Promise((resolve) => { link.onload = link.onerror = resolve; });
|
|
75
|
+
document.head.append(link);
|
|
76
|
+
return brandLoaded;
|
|
77
|
+
}
|
|
78
|
+
// A pane looks like Terminal.app's Basic profile: the brand's terminal roles, resolved on the terminal element, which
|
|
79
|
+
// (like Terminal.app) follows the system's appearance. xterm takes concrete values, so each is read through a probe.
|
|
80
|
+
const TERMINAL_SLOTS = { background: 'background', foreground: 'foreground', cursor: 'cursor', selectionBackground: 'selection', black: 'black', red: 'red', green: 'green', yellow: 'yellow', blue: 'blue', magenta: 'magenta', cyan: 'cyan', white: 'white', brightBlack: 'bright-black', brightRed: 'bright-red', brightGreen: 'bright-green', brightYellow: 'bright-yellow', brightBlue: 'bright-blue', brightMagenta: 'bright-magenta', brightCyan: 'bright-cyan', brightWhite: 'bright-white' };
|
|
81
|
+
function terminalLook(element) {
|
|
82
|
+
const probe = document.createElement('span');
|
|
83
|
+
probe.hidden = true;
|
|
84
|
+
element.append(probe);
|
|
85
|
+
const theme = {};
|
|
86
|
+
for (const [slot, role] of Object.entries(TERMINAL_SLOTS)) {
|
|
87
|
+
probe.style.color = `var(--volter-terminal-${role})`;
|
|
88
|
+
theme[slot] = getComputedStyle(probe).color;
|
|
89
|
+
}
|
|
90
|
+
probe.style.font = 'var(--volter-font-size-terminal, 11px) var(--volter-font-terminal, ui-monospace, Menlo, monospace)';
|
|
91
|
+
const { fontFamily, fontSize } = getComputedStyle(probe);
|
|
92
|
+
probe.remove();
|
|
93
|
+
return { fontFamily, fontSize: Number.parseFloat(fontSize), theme };
|
|
94
|
+
}
|
|
70
95
|
// A fragment value that is base64url JSON (UTF-8), or null.
|
|
71
96
|
function fragmentJson(name) {
|
|
72
97
|
try {
|
|
@@ -189,9 +214,10 @@ async function showPane(connect, key, control) {
|
|
|
189
214
|
import('https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/+esm'),
|
|
190
215
|
import('https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/+esm'),
|
|
191
216
|
]);
|
|
217
|
+
await brandTokens();
|
|
192
218
|
const element = $('terminal');
|
|
193
219
|
element.hidden = false;
|
|
194
|
-
const terminal = new Terminal({
|
|
220
|
+
const terminal = new Terminal({ disableStdin: !control, scrollback: 5000, ...terminalLook(element) });
|
|
195
221
|
const fit = new FitAddon();
|
|
196
222
|
terminal.loadAddon(fit);
|
|
197
223
|
terminal.open(element);
|