@volter/supercode-teams 0.2.102 → 0.2.104

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/bin/fleet.mjs CHANGED
@@ -46,16 +46,13 @@ function channelForOn() {
46
46
  return openOn(onName(), { context: contextName() });
47
47
  }
48
48
 
49
- // Who starts a pane: supercode names it when it runs this CLI (empty: a person); run on its own, supercode is asked
50
- // (`teams caller`), and a launch no supercode answers for records no starter (so no session may act on the pane).
49
+ // Who starts a pane, as supercode answers it (`teams caller`); a launch nothing answers for records no starter, so no
50
+ // session may act on the pane.
51
51
  let launcherAnswer;
52
52
  function launcher() {
53
53
  if (launcherAnswer === undefined) {
54
- if (process.env.SUPERCODE_CALLER !== undefined) launcherAnswer = process.env.SUPERCODE_CALLER || null;
55
- else {
56
- const r = spawnSync(process.env.SUPERCODE_BIN || 'supercode', ['teams', 'caller'], { encoding: 'utf8', shell: process.platform === 'win32', windowsHide: true });
57
- try { launcherAnswer = JSON.parse(r.stdout.trim().split('\n').pop()).caller ?? null; } catch { launcherAnswer = null; }
58
- }
54
+ const r = spawnSync(process.env.SUPERCODE_BIN || 'supercode', ['teams', 'caller'], { encoding: 'utf8', shell: process.platform === 'win32', windowsHide: true });
55
+ try { launcherAnswer = JSON.parse(r.stdout.trim().split('\n').pop()).caller ?? null; } catch { launcherAnswer = null; }
59
56
  }
60
57
  return launcherAnswer;
61
58
  }
package/bin/teams.mjs CHANGED
@@ -108,19 +108,16 @@ async function machineChannel() {
108
108
  }
109
109
 
110
110
  // The session (or board) this command acts for: the pane doors refuse a session acting on a pane it did not start.
111
- // supercode names it when it runs this CLI (empty: a person); run on its own (`supercode-teams`), this CLI asks
112
- // supercode (`teams caller`), which reads the session from this process's ancestry, and refuses when none answers.
111
+ // supercode answers it (`teams caller`: the session in this process's ancestry first, a named caller only as supercode
112
+ // itself takes one), so this bin holds the same rule as \`supercode teams\`; a command nothing answers for is refused.
113
113
  let callerAnswer;
114
114
  function caller() {
115
115
  if (callerAnswer === undefined) {
116
- if (process.env.SUPERCODE_CALLER !== undefined) callerAnswer = process.env.SUPERCODE_CALLER || null;
117
- else {
118
- const r = spawnSync(process.env.SUPERCODE_BIN || 'supercode', ['teams', 'caller'], { encoding: 'utf8', shell: process.platform === 'win32', windowsHide: true });
119
- let answer;
120
- try { answer = JSON.parse(r.stdout.trim().split('\n').pop()); } catch { /* no answer */ }
121
- if (!answer || !('caller' in answer)) die(`cannot tell which session runs this, so the pane doors cannot hold it to the panes it started; nothing was done. Run it as \`supercode teams …\` (${(r.error?.message ?? r.stderr ?? '').trim().slice(0, 200)})`, 1);
122
- callerAnswer = answer.caller ?? null;
123
- }
116
+ const r = spawnSync(process.env.SUPERCODE_BIN || 'supercode', ['teams', 'caller'], { encoding: 'utf8', shell: process.platform === 'win32', windowsHide: true });
117
+ let answer;
118
+ try { answer = JSON.parse(r.stdout.trim().split('\n').pop()); } catch { /* no answer */ }
119
+ if (!answer || !('caller' in answer)) die(`cannot tell which session runs this, so the pane doors cannot hold it to the panes it started; nothing was done (${(r.error?.message ?? r.stderr ?? '').trim().slice(0, 200)})`, 1);
120
+ callerAnswer = answer.caller ?? null;
124
121
  }
125
122
  return callerAnswer ?? undefined;
126
123
  }
package/machine.mjs CHANGED
@@ -582,6 +582,35 @@ 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
+ child = spawn(this.supercodeBin, ['teams', 'caller', '--peer-fd', '3'], { stdio: ['ignore', 'pipe', 'ignore', entry.socket], env: this.env, windowsHide: true });
600
+ } catch { resolve({ unknown: true }); return; }
601
+ child.stdout.on('data', (chunk) => { out += chunk; });
602
+ child.on('error', () => resolve({ unknown: true }));
603
+ child.on('close', () => { try { resolve(JSON.parse(out.trim().split('\n').pop())); } catch { resolve({ unknown: true }); } });
604
+ });
605
+ return entry.peerCaller.then((answer) => {
606
+ // No peer credentials here, or a supercode older than the question (it answers for itself, with no pid): the
607
+ // caller the client names, as before.
608
+ if (answer.unsupported || (answer.caller !== undefined && !('pid' in answer))) return named;
609
+ 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 });
610
+ return answer.caller ?? null;
611
+ });
612
+ }
613
+
585
614
  #refuseForeign(pane, caller) {
586
615
  if (typeof caller !== 'string' || !caller || /^sc:[^:]+:board:/.test(caller)) return;
587
616
  const by = this.launches.all().find((entry) => entry.pane === pane)?.by ?? null;
@@ -1155,7 +1184,7 @@ export class TeamsMachine extends MachineCore {
1155
1184
  const contextKey = params.pane ?? `p_${randomBytes(9).toString('base64url')}`;
1156
1185
  // Who started the pane: the launching session's (or board's) address, null for a person. A session's later
1157
1186
  // input and close on the pane are refused unless it is this one (#refuseForeign).
1158
- const by = typeof params.caller === 'string' && params.caller ? params.caller : null;
1187
+ const by = await this.#callerOf(entry, params).catch(() => null);
1159
1188
  // Who to tell when the pane ends (`open --new … --notify-when-ended`): a session's mail address, and this
1160
1189
  // machine's name as the address a notice from its pane comes from.
1161
1190
  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 +1262,7 @@ export class TeamsMachine extends MachineCore {
1233
1262
  }
1234
1263
  case 'panes.close': {
1235
1264
  need(operator ? 'control' : 'control', `pane:${params.pane}`);
1236
- this.#refuseForeign(params.pane, params.caller);
1265
+ this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
1237
1266
  const session = await this.#paneByContext(params.pane);
1238
1267
  if (!session) throw new Error(`no pane with context ${params.pane}`);
1239
1268
  await this.host.closeSession(session.id);
@@ -1246,7 +1275,7 @@ export class TeamsMachine extends MachineCore {
1246
1275
  // working, only `{input}` is refused — until `resume`, which may carry
1247
1276
  // the input that hands the pane back. Local tmux input is never touched.
1248
1277
  need('control', `pane:${params.pane}`);
1249
- this.#refuseForeign(params.pane, params.caller);
1278
+ this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
1250
1279
  const session = await this.#paneByContext(params.pane);
1251
1280
  if (!session) throw new Error(`no pane with context ${params.pane}`);
1252
1281
  const doc = readJson(this.p.suspended, { v: 1, panes: {} });
@@ -1268,7 +1297,7 @@ export class TeamsMachine extends MachineCore {
1268
1297
  // mechanics) for callers that are services rather than terminals. Same gate as an
1269
1298
  // attachment in control mode, same suspend rule as remote input everywhere.
1270
1299
  need('control', `pane:${params.pane}`);
1271
- this.#refuseForeign(params.pane, params.caller);
1300
+ this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
1272
1301
  const session = await this.#paneByContext(params.pane);
1273
1302
  if (!session) throw Object.assign(new Error(`no pane with context ${params.pane}`), { code: 404 });
1274
1303
  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 +1316,7 @@ export class TeamsMachine extends MachineCore {
1287
1316
  // answer, not an error: the caller keeps the turn and asks again. Its own
1288
1317
  // method, so a daemon without it refuses instead of typing into a draft.
1289
1318
  need('control', `pane:${params.pane}`);
1290
- this.#refuseForeign(params.pane, params.caller);
1319
+ this.#refuseForeign(params.pane, await this.#callerOf(entry, params));
1291
1320
  const session = await this.#paneByContext(params.pane);
1292
1321
  if (!session) throw Object.assign(new Error(`no pane with context ${params.pane}`), { code: 404 });
1293
1322
  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.102",
3
+ "version": "0.2.104",
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",