@volter/supercode-teams 0.3.140 → 0.3.141

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.
Files changed (2) hide show
  1. package/machine.mjs +38 -10
  2. package/package.json +1 -1
package/machine.mjs CHANGED
@@ -2390,6 +2390,13 @@ export class TeamsMachine extends MachineCore {
2390
2390
  */
2391
2391
  #deliverInitialInput({ key, contextKey, session, kind, by, input, act, nativeInput, executable = {}, resumed = {} }) {
2392
2392
  if (this.pendingInitialInputs.has(key)) return;
2393
+ // Input confirmation can finish after the child. Keep the launch's latest
2394
+ // status and completion; a late input receipt must not make it ready again.
2395
+ const recordInput = facts => {
2396
+ const launch = this.launches.find(key);
2397
+ if (launch?.pane !== contextKey) return;
2398
+ this.launches.record({ ...launch, ...facts, status: launch.status });
2399
+ };
2393
2400
  const abort = new AbortController();
2394
2401
  const task = (async () => {
2395
2402
  let nativeReadyBy = 'native-prompt';
@@ -2399,7 +2406,7 @@ export class TeamsMachine extends MachineCore {
2399
2406
  const result = nativeReadyBy === 'pane-gone' || nativeReadyBy === 'deadline'
2400
2407
  ? { delivered: false, refused: true, reason: nativeReadyBy }
2401
2408
  : await this.#deliverClaudeInitialInput(session, contextKey, nativeInput, abort.signal);
2402
- this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: result.delivered,
2409
+ recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: result.delivered,
2403
2410
  inputReadyBy: nativeReadyBy, ...(result.delivered ? { inputReceipt: result.receipt } : { inputError: result.reason }),
2404
2411
  inputRefused: result.refused === true, inputPending: result.queued === true,
2405
2412
  ...(result.native_session ? { native_session: result.native_session } : {}) });
@@ -2415,7 +2422,7 @@ export class TeamsMachine extends MachineCore {
2415
2422
  const typed = nativeReadyBy === 'pane-gone' || nativeReadyBy === 'deadline' ? { delivered: false, reason: nativeReadyBy }
2416
2423
  : await this.#typeCodexFirstInput(session, input, abort.signal);
2417
2424
  if (!typed.delivered) {
2418
- this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: false,
2425
+ recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: false,
2419
2426
  inputReadyBy: nativeReadyBy, inputError: typed.reason });
2420
2427
  act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${typed.reason}`);
2421
2428
  return;
@@ -2424,7 +2431,7 @@ export class TeamsMachine extends MachineCore {
2424
2431
  if (nativeInput) {
2425
2432
  const result = await confirmNativeInitialInput(nativeInput, { signal: abort.signal,
2426
2433
  deadlineMs: Date.now() + INITIAL_INPUT_READY_DEADLINE_MS, live: async () => Boolean(await this.#paneByContext(contextKey)) });
2427
- this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: result.delivered,
2434
+ recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: result.delivered,
2428
2435
  inputReadyBy: nativeReadyBy, ...(result.delivered ? { inputReceipt: result.receipt } : { inputError: result.reason }),
2429
2436
  ...(result.native_session ? { native_session: result.native_session } : {}) });
2430
2437
  act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : result.queued ? 'input-pending' : 'failed',
@@ -2434,19 +2441,19 @@ export class TeamsMachine extends MachineCore {
2434
2441
  const readyBy = await this.#awaitInputReady(session, contextKey, kind, abort.signal);
2435
2442
  if (readyBy === 'aborted') return;
2436
2443
  if (readyBy === 'pane-gone' || readyBy === 'deadline') {
2437
- this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: false, inputReadyBy: readyBy });
2444
+ recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: false, inputReadyBy: readyBy });
2438
2445
  act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${readyBy}`);
2439
2446
  return;
2440
2447
  }
2441
2448
  const result = await this.#deliverPaneText(session, contextKey, input, { signal: abort.signal });
2442
- this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: result.delivered,
2449
+ recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: result.delivered,
2443
2450
  inputReadyBy: readyBy, ...(!result.delivered ? { inputError: result.reason } : {}) });
2444
2451
  act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : result.queued ? 'input-pending' : 'failed',
2445
2452
  result.delivered ? `first input delivered (${readyBy})` : `first input unconfirmed: ${result.reason}`);
2446
2453
  })().catch((error) => {
2447
2454
  // Nothing above is expected to throw; if something does, the launch record
2448
2455
  // is where it is answerable, not a crashed daemon.
2449
- this.launches.record({ key, pane: contextKey, kind, by, ...resumed, ...executable, status: 'ready', inputDelivered: false, inputError: error?.message ?? String(error) });
2456
+ recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: false, inputError: error?.message ?? String(error) });
2450
2457
  }).finally(() => {
2451
2458
  if (this.pendingInitialInputs.get(key)?.task === task) this.pendingInitialInputs.delete(key);
2452
2459
  });
@@ -3202,7 +3209,8 @@ export class TeamsMachine extends MachineCore {
3202
3209
  cancelled_at: found.cancelled_at ?? null,
3203
3210
  live: Boolean(session && !session.dead), label: session?.label ?? null, activeCommand: session?.activeCommand ?? null,
3204
3211
  exit_output: found.pane && (!session || session.dead) ? paneExitOutput(found.pane) : null,
3205
- exit: session?.dead ? deadPaneExit(session) : found.pane && !session ? paneExit(found.pane) : null,
3212
+ exit: found.completion ?? (found.status === 'finished' ? { failed: false, how: 'status 0' }
3213
+ : session?.dead ? deadPaneExit(session) : found.pane && !session ? paneExit(found.pane) : null),
3206
3214
  ...initialInputFacts(found),
3207
3215
  };
3208
3216
  }
@@ -3502,6 +3510,11 @@ export class TeamsMachine extends MachineCore {
3502
3510
  let existing = this.launches.find(key);
3503
3511
  while (existing) {
3504
3512
  if (existing.status === 'cancelled') throw Object.assign(new Error(`launch key ${key} was cancelled before issuance; use a new key`), { code: 409, reason: 'launch_key_cancelled' });
3513
+ // Finished itself records an observed clean exit, including older
3514
+ // journals that did not keep a separate completion field.
3515
+ if (existing.status === 'finished') {
3516
+ return { key, pane: existing.pane, reused: true, status: 'finished', exit: existing.completion?.how ?? 'status 0' };
3517
+ }
3505
3518
  if (existing.status === 'issued') return { key, pane: existing.pane, reused: true, status: existing.status };
3506
3519
  if (existing.status === 'ready') {
3507
3520
  const session = await this.#paneByContext(existing.pane);
@@ -3511,6 +3524,14 @@ export class TeamsMachine extends MachineCore {
3511
3524
  this.#recoverClaudeInitialInputs([session], key);
3512
3525
  return { key, pane: existing.pane, reused: true, status: existing.status };
3513
3526
  }
3527
+ // The child can finish after creation but before the original
3528
+ // caller's settlement read. Retain its observed exit before a
3529
+ // concurrent or restarted caller can issue this key again.
3530
+ const exit = session?.dead ? deadPaneExit(session) : paneExit(existing.pane);
3531
+ if (exit?.failed === false) {
3532
+ this.launches.record({ ...existing, status: 'finished', completion: exit });
3533
+ return { key, pane: existing.pane, reused: true, status: 'finished', exit: exit.how };
3534
+ }
3514
3535
  }
3515
3536
  break;
3516
3537
  }
@@ -3561,7 +3582,13 @@ export class TeamsMachine extends MachineCore {
3561
3582
  // The agent the launch's environment names is kept with the launch: the pane's session is bound to it once the
3562
3583
  // session is seen in the pane (sessionBindings).
3563
3584
  const agent = launchAgent(params.env);
3564
- this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable, status: 'ready', ...(agent ? { agent } : {}) });
3585
+ // createSession can return a retained exit descriptor with no pane.
3586
+ // Its first journal write must already contain that completion, before
3587
+ // any title/input/settlement await lets another caller see the key.
3588
+ const createdExit = session.dead ? deadPaneExit(session) : null;
3589
+ this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable,
3590
+ status: createdExit ? createdExit.failed ? 'exited' : 'finished' : 'ready',
3591
+ ...(createdExit?.failed === false ? { completion: createdExit } : {}), ...(agent ? { agent } : {}) });
3565
3592
  if (params.kind === 'codex' && (params.arguments ?? []).includes('--dangerously-bypass-hook-trust')) this.#settleCodexStartup(key, contextKey, session, act);
3566
3593
  act('launch', `pane:${contextKey}`, 'ok', params.kind ?? params.program);
3567
3594
  if (typeof params.name === 'string' && params.name.trim()) {
@@ -3585,9 +3612,10 @@ export class TeamsMachine extends MachineCore {
3585
3612
  const settled = await this.#paneByContext(contextKey);
3586
3613
  if (!settled || settled.dead) {
3587
3614
  // Finished (it ran to a clean end) is an answer; exited (it failed at once) is the caller's error.
3588
- const exit = settled?.dead ? deadPaneExit(settled) : paneExit(contextKey);
3615
+ const exit = settled?.dead ? deadPaneExit(settled) : session.dead ? deadPaneExit(session) : paneExit(contextKey);
3589
3616
  const status = exit && !exit.failed ? 'finished' : 'exited';
3590
- this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable, status, error: exit?.failed ? exit.how : undefined });
3617
+ this.launches.record({ ...this.launches.find(key), key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable, status,
3618
+ ...(exit && !exit.failed ? { completion: exit } : {}), error: exit?.failed ? exit.how : undefined });
3591
3619
  this.#panesChanged();
3592
3620
  return { key, pane: contextKey, reused: false, status, exit: exit?.how ?? null };
3593
3621
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/supercode-teams",
3
- "version": "0.3.140",
3
+ "version": "0.3.141",
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",