@volter/supercode-teams 0.2.174 → 0.2.176

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/README.md CHANGED
@@ -61,7 +61,13 @@ anything else, for a wildcard (`pane:*`) or for the whole filesystem (`root:/`)
61
61
  token must carry `iat` and live at most 15 minutes (`exp - iat` and `exp - now`). The hosted viewer
62
62
  at `/view#token=…` opens it; the server turns the token into a machine grant on exactly that target
63
63
  (a pane: `observe`, and `control` to type; a folder: `observe` to read, `files` to change) that
64
- expires with the token, and closes the channel then. A page embedding the viewer sends it a fresh
64
+ expires with the token, and closes the channel then. Before it writes that grant, and while the
65
+ channel is open every 15 seconds, the server asks the workplace whether the token still admits what it was issued for
66
+ (`POST <issuer>/api/v3/delegated/status`); when the workplace answers that it does not (the member's
67
+ binding revoked, their Task closed, their contract ended), the grant is deleted and the channel closes
68
+ with `access_revoked`, which the viewer shows rather than reconnecting; an open with such a token is
69
+ refused (403 `access_revoked`) and writes no grant. An unreachable workplace leaves the channel to
70
+ the token's expiry. A page embedding the viewer sends it a fresh
65
71
  token by `postMessage({ type: 'delegated-token', token })`, which the viewer asks for with
66
72
  `delegated-token-needed`.
67
73
 
package/bin/fleet.mjs CHANGED
@@ -96,8 +96,9 @@ async function main() {
96
96
  cwd: flags.cwd,
97
97
  initialInput: flags.input,
98
98
  name: flags.name,
99
- // `open` attaching this terminal to the pane itself asks the machine for no window of its own.
100
- ...(flags.popup === 'false' ? { popup: false } : {}),
99
+ // The launch's window choice, when its caller states one: `open` attaching this terminal to the pane asks
100
+ // for none, and the board's dispatcher states its workflow's choice on every launch.
101
+ ...(flags.popup === 'false' || flags.popup === 'true' ? { popup: flags.popup === 'true' } : {}),
101
102
  // Who starts the pane (the session or board supercode resolved; unset for a person), and whether the pane
102
103
  // runs its one verb as that caller (`close`/`stop` on another machine).
103
104
  ...(launcher() ? { caller: launcher() } : {}),
package/bin/workspace.mjs CHANGED
@@ -793,7 +793,7 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
793
793
  if (flags.install || (!flags.foreground && process.stdin.isTTY && process.stdout.isTTY)) {
794
794
  const serviceBin = process.env.SUPERCODE_BIN;
795
795
  if (!serviceBin) unsupported('--install requires invocation through the native supercode CLI');
796
- const serviceEnv = { ...process.env, SUPERCODE_POPUPS: process.env.SUPERCODE_POPUPS ?? '0', SUPERCODE_TEAMS_ENTRY: fileURLToPath(new URL('./teams.mjs', import.meta.url)) };
796
+ const serviceEnv = { ...process.env, SUPERCODE_TEAMS_ENTRY: fileURLToPath(new URL('./teams.mjs', import.meta.url)) };
797
797
  // Refuse source-backed installs before Windows stops the running connector.
798
798
  try {
799
799
  execFileSync(serviceBin, ['__teams-connector-service', 'validate', contextName, path, context.server_id, context.team_id], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], env: serviceEnv });
@@ -838,9 +838,9 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
838
838
  // Followed (live) sessions follow and load through one of their own, so a watched session never waits behind a pass.
839
839
  const liveCore = new SupercodeHarnessClient({ command: binary, args: ['harness', 'serve'] });
840
840
  const connector = new TeamsConnector({ server: context.origin, teamId: context.team_id, machineId: saved.machine.id, sourceId: saved.source.id, credential: saved.credential.token, stateDir: state.directory, core, largeCore, slowCore, liveCore, hostKeyId });
841
- // A connected machine is a person's: each pane it launches opens a window on its screen (SUPERCODE_POPUPS=0: none).
841
+ // A connected machine is a person's: each pane it launches opens a window on its screen.
842
842
  const machineEvents = new MachineEvents({home:daemonHome,server:context.origin,teamId:context.team_id,credential:saved.credential.token,machineName:saved.machine.name,binary});
843
- const machine = new TeamsMachine({ home: daemonHome, supercode: binary, popups: (process.env.SUPERCODE_POPUPS ?? '0') !== '0', ...(flags.tmux_socket ? { tmuxSocket: String(flags.tmux_socket) } : {}), link: { server: context.origin, teamId: context.team_id, machineId: saved.machine.id, credential: saved.credential.token, incarnationId: connector.state.incarnation_id, configuration: () => connector.configuration() }, onTurn: (turn) => connector.noteTurn(turn), onTurnWithdrawn: (turn) => connector.withdrawTurn(turn) });
843
+ const machine = new TeamsMachine({ home: daemonHome, supercode: binary, ...(flags.tmux_socket ? { tmuxSocket: String(flags.tmux_socket) } : {}), link: { server: context.origin, teamId: context.team_id, machineId: saved.machine.id, credential: saved.credential.token, incarnationId: connector.state.incarnation_id, configuration: () => connector.configuration() }, onTurn: (turn) => connector.noteTurn(turn), onTurnWithdrawn: (turn) => connector.withdrawTurn(turn) });
844
844
  machine.machineEvents = machineEvents;
845
845
  machine.on('link', (event) => { if (event.event === 'replaced') process.stderr.write(`supercode teams: ${event.error}. Two connectors were running for this machine; stop one of them (\`supercode teams disconnect\` where it should not run).\n`); });
846
846
  // The link's state for the watch after start: down until it is up, down again (since when) when it drops. A
package/fleet.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  // `open --on`, and `harness drive --on`. A machine named with `--on` is an
3
3
  // enrolled machine of the selected Teams context, reached through its server;
4
4
  // without `--on` the verb acts on this OS user's own daemon.
5
+ import { execFile } from 'node:child_process';
5
6
  import { resolve } from 'node:path';
6
7
  import { SupercodeHarnessClient } from '@volter/supercode-harness-sdk';
7
8
  import { harnessTransport, nodeRpc, openRemoteMachine } from './client.mjs';
@@ -52,6 +53,9 @@ export async function fleetDiscover({ context = null, machines = null, params =
52
53
  const { client, context: selected } = fromContext({ supercodeHome, name, fetch: globalThis.fetch });
53
54
  const token = contexts.credential(selected.credential_ref).token;
54
55
  const listed = (await client.team(selected.team_id).machines.list()).items.filter((item) => item.enrollment === 'active');
56
+ // This machine's own sessions are read here, as `discover` reads them, not through the server and back
57
+ // into this machine's daemon.
58
+ const here = contexts.connector(selected.server_id, selected.team_id).read()?.machine?.id ?? null;
55
59
  const targets = machines && machines.length
56
60
  ? machines.map((wanted) => listed.find((item) => item.name === wanted || item.id === wanted) ?? { name: wanted, id: null })
57
61
  : listed;
@@ -60,6 +64,10 @@ export async function fleetDiscover({ context = null, machines = null, params =
60
64
  // A machine the server reports offline is not asked (asking only waits for a link that is not coming); it is
61
65
  // named as offline, not as a failure, and only when it was named or seen in the last day.
62
66
  if (machine.connection === 'offline') return { name: machine.name, offline: true, lastSeenAt: machine.last_seen_at ?? null };
67
+ if (machine.id === here) {
68
+ try { return { name: machine.name, sessions: await discoverHere(params, env) }; }
69
+ catch (error) { return { name: machine.name, error: error.message ?? String(error) }; }
70
+ }
63
71
  let channel = null;
64
72
  try {
65
73
  channel = await openRemoteMachine({ origin: selected.origin, teamId: selected.team_id, machineId: machine.id, token });
@@ -83,3 +91,14 @@ export async function fleetDiscover({ context = null, machines = null, params =
83
91
  }
84
92
  return { sessions, unreachable, ...(offline.length ? { offline } : {}) };
85
93
  }
94
+
95
+ /** `harness.v1.sessions.discover` on this machine: the local `supercode discover` page. */
96
+ function discoverHere(params, env) {
97
+ const args = ['discover', '--context', 'local', '--page'];
98
+ if (params.query) args.push('--query', String(params.query));
99
+ if (params.limit) args.push('--limit', String(params.limit));
100
+ if (params.workspace) args.push('--workspace', String(params.workspace));
101
+ for (const harness of params.harnesses ?? []) args.push('--harness', String(harness));
102
+ return new Promise((done, fail) => execFile(env.SUPERCODE_BIN || 'supercode', args, { env, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, timeout: 60_000 },
103
+ (error, stdout, stderr) => error ? fail(new Error((stderr || error.message).trim().split('\n').at(-1))) : done(JSON.parse(stdout).sessions ?? [])));
104
+ }
package/machine.mjs CHANGED
@@ -161,15 +161,13 @@ export class TeamsMachine extends MachineCore {
161
161
  * server link; `configuration()` resolves the machine's current configuration from the server
162
162
  * (custodian and machine grants). Without it the daemon serves its local door only.
163
163
  */
164
- constructor({ home = teamsHome(), supercode = 'supercode', tmuxSocket, floor = null, limits = null, env = process.env, link = null, popups = false, onTurn = null, onTurnWithdrawn = null } = {}) {
164
+ constructor({ home = teamsHome(), supercode = 'supercode', tmuxSocket, floor = null, limits = null, env = process.env, link = null, onTurn = null, onTurnWithdrawn = null } = {}) {
165
165
  super({ home, floor, limits, env, link });
166
166
  this.supercodeBin = supercode;
167
167
  // Told of each turn a Room member typed here (the connector labels the captured message with who typed it).
168
168
  this.onTurn = onTurn;
169
169
  this.onTurnWithdrawn = onTurnWithdrawn;
170
170
  this.tmuxSocket = tmuxSocket ?? null;
171
- // Each pane launched here opens a terminal window on this machine's screen (popupWindow).
172
- this.popups = Boolean(popups);
173
171
  this.launches = new Launches(this.p.launches);
174
172
  // Windows has no tmux: its panes live in the machine's pane server, a process of their own that outlives this
175
173
  // daemon (a restart or an upgrade never ends a pane), as tmux panes live in tmux's server.
@@ -913,6 +911,13 @@ export class TeamsMachine extends MachineCore {
913
911
  queue.identity = saved.binding.identity; queue.host = this.host; queue.session = session;
914
912
  this.terminalMailboxes.set(saved.recipient, queue);
915
913
  }
914
+ // A queue whose pane has ended is not retried every second into a terminal that no longer
915
+ // exists. Its mail stays retained: the session's next pane takes it up.
916
+ for (const [recipient, queue] of this.terminalMailboxes) {
917
+ const binding = queue.state.binding;
918
+ const live = binding ? await this.#paneByContext(binding.context) : null;
919
+ if (!live || live.id !== binding.terminal) this.terminalMailboxes.delete(recipient);
920
+ }
916
921
  await Promise.all([...this.terminalMailboxes.values()].filter(queue => !this.inputRepairs[queue.state.binding?.context] && !this.inputInFlight.has(queue.session?.id)).map(queue => queue.kick()));
917
922
  }
918
923
 
@@ -1536,7 +1541,7 @@ export class TeamsMachine extends MachineCore {
1536
1541
  this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...executable, status, error: exit?.failed ? exit.how : undefined });
1537
1542
  return { key, pane: contextKey, reused: false, status, exit: exit?.how ?? null };
1538
1543
  }
1539
- if (this.popups && params.popup !== false) this.#popupWindow(contextKey, session);
1544
+ if (params.popup !== false) this.#popupWindow(contextKey, session);
1540
1545
  if (params.notify) this.#noticeWhenEnded({ key, pane: contextKey, notify: params.notify, label: params.name ?? params.kind ?? params.program });
1541
1546
  return { key, pane: contextKey, reused: false, status: 'ready' };
1542
1547
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.2.174",
3
+ "version": "0.2.176",
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",