@volter/supercode-teams 0.3.130 → 0.3.132

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
@@ -162,19 +162,50 @@ async function main() {
162
162
  if (typeof flags.for === 'string') setTimeout(() => { fc.detachChannel(); channel.close(); process.exit(0); }, Number(flags.for));
163
163
  return;
164
164
  }
165
- const term = await attachPane(channel, { pane, mode, cols: process.stdout.columns ?? 120, rows: process.stdout.rows ?? 40, acting: await launcher() });
166
- term.on('hello', (frame) => process.stderr.write(`[attached ${pane} ${frame.cols}x${frame.rows}${mode === 'control' ? ' — ctrl-] detaches' : ''}]\n`));
167
- term.on('error-message', (message) => process.stderr.write(`[${message}]\n`));
168
- term.on('data', (text) => process.stdout.write(text));
169
- term.on('close', (reason) => { process.stderr.write(`\n[detached${reason ? `: ${reason}` : ''}]\n`); channel.close(); process.exit(0); });
165
+ // The view outlives a restart of the machine daemon (an upgrade restarts it; the pane itself lives in the pane
166
+ // host and goes on): when the daemon's connection drops, this window attaches again once it answers, for up to
167
+ // REATTACH_MS. The pane ending, a refusal, or ctrl-] ends the view as before.
168
+ const REATTACH_MS = 180_000;
169
+ let term = null;
170
+ let detaching = false;
171
+ const attach = async (into) => {
172
+ const next = await attachPane(into, { pane, mode, cols: process.stdout.columns ?? 120, rows: process.stdout.rows ?? 40, acting: await launcher() });
173
+ next.on('hello', (frame) => process.stderr.write(`[attached ${pane} ${frame.cols}x${frame.rows}${mode === 'control' ? ' — ctrl-] detaches' : ''}]\n`));
174
+ next.on('error-message', (message) => process.stderr.write(`[${message}]\n`));
175
+ next.on('data', (text) => process.stdout.write(text));
176
+ next.on('close', (reason) => {
177
+ into.close();
178
+ if (!detaching && typeof reason === 'string' && reason.startsWith('connection closed')) { void reattach(); return; }
179
+ process.stderr.write(`\n[detached${reason ? `: ${reason}` : ''}]\n`);
180
+ process.exit(0);
181
+ });
182
+ term = next;
183
+ };
184
+ const reattach = async () => {
185
+ process.stderr.write('\n[the machine daemon went away (an upgrade restarts it); attaching again when it is back…]\n');
186
+ const deadline = Date.now() + REATTACH_MS;
187
+ let last = null;
188
+ while (Date.now() < deadline) {
189
+ await new Promise((done) => setTimeout(done, 1_000));
190
+ try { await attach(await channelForOn()); return; } catch (error) {
191
+ last = error;
192
+ // the daemon saying this pane is gone (`terminal refused: no pane …`) will not change by waiting; a daemon not
193
+ // answering yet (ECONNREFUSED, no pipe) will
194
+ if (/terminal refused: .*(no pane|no \w+ on pane)/i.test(error?.message ?? '')) break;
195
+ }
196
+ }
197
+ process.stderr.write(`\n[detached: the machine daemon did not take this pane back${last ? ` (${last.message})` : ''}]\n`);
198
+ process.exit(1);
199
+ };
200
+ await attach(channel);
170
201
  if (mode === 'control' && process.stdin.isTTY) {
171
202
  process.stdin.setRawMode(true);
172
203
  process.stdin.on('data', (chunk) => {
173
- if (chunk[0] === 0x1d) { term.close(); return; } // ctrl-] detaches
174
- term.write(chunk.toString('utf8'));
204
+ if (chunk[0] === 0x1d) { detaching = true; term?.close(); return; } // ctrl-] detaches
205
+ term?.write(chunk.toString('utf8'));
175
206
  });
176
207
  }
177
- process.stdout.on('resize', () => term.resize(process.stdout.columns, process.stdout.rows));
208
+ process.stdout.on('resize', () => term?.resize(process.stdout.columns, process.stdout.rows));
178
209
  return;
179
210
  }
180
211
  default:
package/machine.mjs CHANGED
@@ -2112,6 +2112,8 @@ export class TeamsMachine extends MachineCore {
2112
2112
  const launch = this.launches.all().find((entry) => entry.pane === pane);
2113
2113
  const by = launch?.by ?? null;
2114
2114
  if (by === caller) return null;
2115
+ // the session running in the pane acts on its own pane, whoever opened it (it closes itself when its work is done)
2116
+ if (this.live?.sessions().some((live) => live.address === caller && String(live.pane ?? '').split(':')[0] === pane)) return null;
2115
2117
  // A pane that resumes a session is that session's as well: its first starter acts on it as before the resume
2116
2118
  // (t_09a795fd item 18). The starter is the oldest harness launch whose own transcript confirmed it started that
2117
2119
  // session, never a launch row's claim (any caller can launch a program with --session-id S).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.130",
3
+ "version": "0.3.132",
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",