@runuai/host 0.9.8 → 0.9.9

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.
@@ -1634,11 +1634,27 @@ export class Orchestrator {
1634
1634
  return remaining;
1635
1635
  }
1636
1636
 
1637
- /** Retire turn-local state owned by a runner generation before replacement. */
1637
+ /**
1638
+ * Retire turn-local state owned by a runner generation before replacement.
1639
+ *
1640
+ * A replacement's identity guard suppresses the old runner's eventual
1641
+ * terminal event. If the human already stopped that turn, close its cloud
1642
+ * boundary here and discard its replay prompt; otherwise credential refresh
1643
+ * could silently restart canceled work on the new generation.
1644
+ */
1638
1645
  private retireRunnerGeneration(channel: Channel, agentId: string): void {
1646
+ const aborted = channel.interrupted.delete(agentId);
1639
1647
  channel.activeTurns.delete(agentId);
1640
1648
  channel.openTurns.delete(agentId);
1641
- channel.interrupted.delete(agentId);
1649
+ if (aborted) {
1650
+ channel.lastPrompt.delete(agentId);
1651
+ this.emitHost({
1652
+ kind: "agent.turn_complete",
1653
+ taskId: channel.taskId,
1654
+ agentId,
1655
+ aborted: true,
1656
+ });
1657
+ }
1642
1658
  }
1643
1659
 
1644
1660
  private startPrompt(
@@ -1766,9 +1782,12 @@ export class Orchestrator {
1766
1782
  // An interrupted turn is a half-turn — its eventual turn_complete must not
1767
1783
  // hand the buffered fragment to @-mentioned peers. Flag it so the boundary
1768
1784
  // goes out `aborted` and the cloud discards the buffer. Only when the turn
1769
- // actually streamed output (openTurns): an idle-agent ESC must not mark
1770
- // the NEXT legitimate turn as aborted.
1771
- if (channel.openTurns.has(agentId)) channel.interrupted.add(agentId);
1785
+ // is actually in flight (activeTurns): an idle-agent ESC must not mark the
1786
+ // NEXT legitimate turn as aborted, while a stop before the first output is
1787
+ // still a cancellation and must not be replayed by runner replacement.
1788
+ if ((channel.activeTurns.get(agentId) ?? 0) > 0) {
1789
+ channel.interrupted.add(agentId);
1790
+ }
1772
1791
  void session.interrupt();
1773
1792
  this.emitSystemNote(taskId, `Stopped @${agentId}.`);
1774
1793
  return { ok: true };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runuai/host",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "description": "Uai host — runs ephemeral AI coding tasks in Docker on a machine you control.",
5
5
  "license": "MIT",
6
6
  "author": "Diogo Perillo <diogo.perillo@gmail.com>",