@volter/supercode-teams 0.2.90 → 0.2.92

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/teams.mjs CHANGED
@@ -135,6 +135,8 @@ async function main() {
135
135
  const socket = await machine.start();
136
136
  out({ id: machine.id, socket, home, protocol: NODE_PROTOCOL, floor: machine.floor });
137
137
  const stop = async () => { await machine.stop(); process.exit(0); };
138
+ // Another daemon took the socket path: this one serves nobody, and its children would run beside that one's.
139
+ machine.once('superseded', (path) => { process.stderr.write(`supercode teams: another machine daemon now serves ${path}; this one stops\n`); void stop(); });
138
140
  process.on('SIGINT', stop);
139
141
  process.on('SIGTERM', stop);
140
142
  return;
package/bin/workspace.mjs CHANGED
@@ -732,6 +732,8 @@ async function connectorCommand(verb, { flags, rest, output, client, context, te
732
732
  // A connected machine is a person's: each pane it launches opens a window on its screen (SUPERCODE_POPUPS=0: none).
733
733
  const machine = new TeamsMachine({ home: daemonHome, supercode: binary, popups: process.env.SUPERCODE_POPUPS !== '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) });
734
734
  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`); });
735
+ // The connector is the service's daemon and keeps serving; a daemon that took its socket path is the one to stop.
736
+ machine.on('superseded', (path) => process.stderr.write(`supercode teams: another machine daemon replaced this connector's socket at ${path}; stop it (supercode teams status)\n`));
735
737
  const stopAll = async () => { await machine.stop().catch(() => {}); await connector.stop(); await core.close(); await largeCore.close(); await slowCore.close(); await liveCore.close(); await lifecycle.close(); state.update(current => current ? ({ ...current, process: undefined }) : null); };
736
738
  try { await connector.start({ roots: () => saved.roots }); await machine.start(); } catch (error) { await stopAll(); throw error; }
737
739
  output({ version: 1, context: scope(context), data: { machine: saved.machine, source: saved.source, roots: saved.roots.map(({ path: _path, ...item }) => item), socket: machine.socketPath, foreground: true } });
package/machine.mjs CHANGED
@@ -96,6 +96,8 @@ function trustClaudeFolder(cwd) {
96
96
 
97
97
  /** How long a launch watches its new pane before answering, so a program that ends at once is reported. */
98
98
  const LAUNCH_SETTLE_MS = 500;
99
+ /** How many typed lines' keys a machine remembers; a server retries a line for minutes, not thousands of lines. */
100
+ const TURN_KEYS_KEPT = 1000;
99
101
 
100
102
  /**
101
103
  * What a pane runs for a launch: the program with its arguments, or, when the caller named a whole command line
@@ -139,6 +141,9 @@ function paneExit(pane) {
139
141
  }
140
142
 
141
143
  export class TeamsMachine extends MachineCore {
144
+ /** Typed lines by key → the answer their first attempt gives (see #serveTurn). */
145
+ #turnKeys = new Map();
146
+
142
147
  /**
143
148
  * `link`: { server, teamId, machineId, credential, incarnationId, configuration } to hold the
144
149
  * server link; `configuration()` resolves the machine's current configuration from the server
@@ -485,6 +490,17 @@ export class TeamsMachine extends MachineCore {
485
490
  // The user-turn door every supercode has (harness.v1 sessions.message, as_user): the session's runtime input,
486
491
  // its pane's empty composer, or its mailbox until the composer is free. The sender is named after the member.
487
492
  const answer = (value) => { if (!stream.closed) { stream.write(JSON.stringify(value)); stream.close('delivered'); } };
493
+ // A keyed line is typed once: the server retries a line whose open outlived its bound, and the attempt it gave up
494
+ // on may still be accepted here. A repeat of a key answers what its first attempt answers.
495
+ const turnKey = typeof request.turn_key === 'string' && request.turn_key.length <= 128 ? `${target}\u0000${request.turn_key}` : null;
496
+ const seen = turnKey ? this.#turnKeys.get(turnKey) : null;
497
+ if (seen) { void seen.then(answer); return; }
498
+ let settle;
499
+ if (turnKey) {
500
+ this.#turnKeys.set(turnKey, new Promise((resolve) => { settle = resolve; }));
501
+ if (this.#turnKeys.size > TURN_KEYS_KEPT) this.#turnKeys.delete(this.#turnKeys.keys().next().value);
502
+ }
503
+ const reply = (value) => { settle?.(value); answer(value); };
488
504
  const at = Date.now();
489
505
  this.#serveRequest('harness.v1.sessions.message', {
490
506
  locator: { harness, session_id: session, storage: { kind: 'file', path: '' } },
@@ -495,12 +511,12 @@ export class TeamsMachine extends MachineCore {
495
511
  const delivered = result?.message_id && !result.refusal;
496
512
  this.log.append({ principal, cap: 'interact', target, outcome: delivered ? 'turn' : 'failed', detail: result?.refusal?.message?.slice(0, 200) });
497
513
  if (delivered) try { this.onTurn?.({ harness, session, text: request.text, at, source: { via: 'room', from: { principalId: principal ?? null, name: entry.name ?? null } } }); } catch { /* the turn is delivered either way */ }
498
- answer(delivered
514
+ reply(delivered
499
515
  ? { code: 0, turn_id: result.message_id, door: result.delivery?.how === 'waiting' ? 'waiting' : (result.delivery?.door ?? 'pane') }
500
516
  : { code: 1, text: result?.refusal?.message ?? 'the session did not take the turn' });
501
517
  }, (error) => {
502
518
  this.log.append({ principal, cap: 'interact', target, outcome: 'failed', detail: error.message.slice(0, 200) });
503
- answer({ code: 1, text: error.message });
519
+ reply({ code: 1, text: error.message });
504
520
  });
505
521
  }
506
522
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.2.90",
3
+ "version": "0.2.92",
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",