@volter/supercode-teams 0.2.103 → 0.2.105
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.mjs +38 -5
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -582,6 +582,39 @@ export class TeamsMachine extends MachineCore {
|
|
|
582
582
|
* address) on a pane another session, a board or a person started is refused, naming who started it. A person (no
|
|
583
583
|
* caller) and a board (its cards' own door) act on any pane.
|
|
584
584
|
*/
|
|
585
|
+
/**
|
|
586
|
+
* Who is calling, for the pane doors. On the local socket the daemon decides: it hands the connection to its own
|
|
587
|
+
* supercode (`teams caller --peer-fd 3`), which reads the peer's pid from the kernel and resolves that process's
|
|
588
|
+
* session from its ancestry and its own environment, so nothing the client sends about itself is believed. Asked
|
|
589
|
+
* once per connection. A request the Teams connector forwards from another machine keeps the caller it carries,
|
|
590
|
+
* as does a platform with no peer credentials (Windows named pipes). A local peer nothing can resolve is refused.
|
|
591
|
+
*/
|
|
592
|
+
#callerOf(entry, params) {
|
|
593
|
+
const named = typeof params.caller === 'string' && params.caller ? params.caller : null;
|
|
594
|
+
if (entry.principal !== 'local' || !entry.socket) return Promise.resolve(named);
|
|
595
|
+
entry.peerCaller ??= new Promise((resolve) => {
|
|
596
|
+
let out = '';
|
|
597
|
+
let child;
|
|
598
|
+
try {
|
|
599
|
+
// The descriptor's number, not the socket: a stream handed to spawn stops being read here, and the
|
|
600
|
+
// connection's next request would never be answered. The child gets its own duplicate of the descriptor.
|
|
601
|
+
const fd = entry.socket._handle?.fd;
|
|
602
|
+
if (!Number.isInteger(fd) || fd < 0) { resolve({ unsupported: true }); return; }
|
|
603
|
+
child = spawn(this.supercodeBin, ['teams', 'caller', '--peer-fd', '3'], { stdio: ['ignore', 'pipe', 'ignore', fd], env: this.env, windowsHide: true });
|
|
604
|
+
} catch { resolve({ unknown: true }); return; }
|
|
605
|
+
child.stdout.on('data', (chunk) => { out += chunk; });
|
|
606
|
+
child.on('error', () => resolve({ unknown: true }));
|
|
607
|
+
child.on('close', () => { try { resolve(JSON.parse(out.trim().split('\n').pop())); } catch { resolve({ unknown: true }); } });
|
|
608
|
+
});
|
|
609
|
+
return entry.peerCaller.then((answer) => {
|
|
610
|
+
// No peer credentials here, or a supercode older than the question (it answers for itself, with no pid): the
|
|
611
|
+
// caller the client names, as before.
|
|
612
|
+
if (answer.unsupported || (answer.caller !== undefined && !('pid' in answer))) return named;
|
|
613
|
+
if (answer.unknown || !('caller' in answer)) throw Object.assign(new Error('cannot tell which process is calling, so the pane doors cannot hold it to the panes it started; nothing was done'), { code: 403 });
|
|
614
|
+
return answer.caller ?? null;
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
585
618
|
#refuseForeign(pane, caller) {
|
|
586
619
|
if (typeof caller !== 'string' || !caller || /^sc:[^:]+:board:/.test(caller)) return;
|
|
587
620
|
const by = this.launches.all().find((entry) => entry.pane === pane)?.by ?? null;
|
|
@@ -1155,7 +1188,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1155
1188
|
const contextKey = params.pane ?? `p_${randomBytes(9).toString('base64url')}`;
|
|
1156
1189
|
// Who started the pane: the launching session's (or board's) address, null for a person. A session's later
|
|
1157
1190
|
// input and close on the pane are refused unless it is this one (#refuseForeign).
|
|
1158
|
-
const by =
|
|
1191
|
+
const by = await this.#callerOf(entry, params).catch(() => null);
|
|
1159
1192
|
// Who to tell when the pane ends (`open --new … --notify-when-ended`): a session's mail address, and this
|
|
1160
1193
|
// machine's name as the address a notice from its pane comes from.
|
|
1161
1194
|
if (params.notify !== undefined && !(params.notify && typeof params.notify.to === 'string' && /^sc:[^:\s]+:[^:\s]+:\S+$/.test(params.notify.to) && typeof params.notify.machine === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(params.notify.machine))) throw Object.assign(new Error('launch notify is { to: <sc: address>, machine: <this machine\'s mail name> }'), { code: 400 });
|
|
@@ -1233,7 +1266,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1233
1266
|
}
|
|
1234
1267
|
case 'panes.close': {
|
|
1235
1268
|
need(operator ? 'control' : 'control', `pane:${params.pane}`);
|
|
1236
|
-
this.#refuseForeign(params.pane, params
|
|
1269
|
+
this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
|
|
1237
1270
|
const session = await this.#paneByContext(params.pane);
|
|
1238
1271
|
if (!session) throw new Error(`no pane with context ${params.pane}`);
|
|
1239
1272
|
await this.host.closeSession(session.id);
|
|
@@ -1246,7 +1279,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1246
1279
|
// working, only `{input}` is refused — until `resume`, which may carry
|
|
1247
1280
|
// the input that hands the pane back. Local tmux input is never touched.
|
|
1248
1281
|
need('control', `pane:${params.pane}`);
|
|
1249
|
-
this.#refuseForeign(params.pane, params
|
|
1282
|
+
this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
|
|
1250
1283
|
const session = await this.#paneByContext(params.pane);
|
|
1251
1284
|
if (!session) throw new Error(`no pane with context ${params.pane}`);
|
|
1252
1285
|
const doc = readJson(this.p.suspended, { v: 1, panes: {} });
|
|
@@ -1268,7 +1301,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1268
1301
|
// mechanics) for callers that are services rather than terminals. Same gate as an
|
|
1269
1302
|
// attachment in control mode, same suspend rule as remote input everywhere.
|
|
1270
1303
|
need('control', `pane:${params.pane}`);
|
|
1271
|
-
this.#refuseForeign(params.pane, params
|
|
1304
|
+
this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
|
|
1272
1305
|
const session = await this.#paneByContext(params.pane);
|
|
1273
1306
|
if (!session) throw Object.assign(new Error(`no pane with context ${params.pane}`), { code: 404 });
|
|
1274
1307
|
if (this.suspended.has(params.pane)) throw Object.assign(new Error(`pane ${params.pane} is suspended: remote input is refused until resume`), { code: 423 });
|
|
@@ -1287,7 +1320,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1287
1320
|
// answer, not an error: the caller keeps the turn and asks again. Its own
|
|
1288
1321
|
// method, so a daemon without it refuses instead of typing into a draft.
|
|
1289
1322
|
need('control', `pane:${params.pane}`);
|
|
1290
|
-
this.#refuseForeign(params.pane, params
|
|
1323
|
+
this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
|
|
1291
1324
|
const session = await this.#paneByContext(params.pane);
|
|
1292
1325
|
if (!session) throw Object.assign(new Error(`no pane with context ${params.pane}`), { code: 404 });
|
|
1293
1326
|
if (this.suspended.has(params.pane)) throw Object.assign(new Error(`pane ${params.pane} is suspended: remote input is refused until resume`), { code: 423 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.105",
|
|
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",
|