@wenrwa/marketplace-cli 0.8.3 → 0.8.4

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.
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * A re-open on a live id (SessionManager#open supersedes) kills the OLD PTY.
5
+ * That process exits asynchronously, AFTER the new session is registered
6
+ * under the same id. Its exit must not drop the NEW session: before this
7
+ * guard, the old onExit ran `sessions.delete(id)` unconditionally, so the
8
+ * fresh PTY kept running but untracked (input dropped, attach answered
9
+ * "Unknown session", nothing could close it) and onSessionExit announced the
10
+ * agent as ended while it was alive.
11
+ *
12
+ * Hit by an explicit restart on either lane: the hosted oc-21 relaunch
13
+ * (relay `open` on a live id) and the browser's local restart after an
14
+ * engine/model change (close → open).
15
+ */
16
+ const sessionManager_1 = require("../bridge/sessionManager");
17
+ class ExitablePty {
18
+ killed = false;
19
+ exitCbs = [];
20
+ write() { }
21
+ resize() { }
22
+ kill() { this.killed = true; }
23
+ onData() { }
24
+ onExit(cb) { this.exitCbs.push(cb); }
25
+ exit(code) { for (const cb of this.exitCbs)
26
+ cb(code); }
27
+ }
28
+ const make = () => {
29
+ const ptys = [];
30
+ const factory = { spawn: () => { const p = new ExitablePty(); ptys.push(p); return p; } };
31
+ const sm = new sessionManager_1.SessionManager(factory);
32
+ const exits = [];
33
+ sm.setOnSessionExit((id) => exits.push(id));
34
+ return { sm, ptys, exits };
35
+ };
36
+ describe('supersede: the old process exiting late never drops the new session', () => {
37
+ it('re-open, then the OLD pty exits → the new session is still tracked and attachable', async () => {
38
+ const { sm, ptys, exits } = make();
39
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' });
40
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' }); // supersede
41
+ expect(ptys[0].killed).toBe(true);
42
+ ptys[0].exit(129); // SIGHUP lands ~600ms later in real life
43
+ expect(sm.list()).toEqual(['agent-1']);
44
+ expect(exits).toEqual([]); // the agent did NOT end
45
+ await expect(sm.attach('agent-1', () => { })).resolves.toBeDefined();
46
+ });
47
+ it('close, re-open, then the OLD pty exits → same', () => {
48
+ const { sm, ptys, exits } = make();
49
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' });
50
+ sm.close('agent-1');
51
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' });
52
+ ptys[0].exit(129);
53
+ expect(sm.list()).toEqual(['agent-1']);
54
+ expect(exits).toEqual([]);
55
+ });
56
+ it('the CURRENT pty exiting still ends the session and notifies (unchanged)', () => {
57
+ const { sm, ptys, exits } = make();
58
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' });
59
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' });
60
+ ptys[1].exit(0);
61
+ expect(sm.list()).toEqual([]);
62
+ expect(exits).toEqual(['agent-1']);
63
+ });
64
+ });
65
+ describe('a plain close still reports the exit (unchanged)', () => {
66
+ it('close with nothing reopened → onSessionExit fires', () => {
67
+ const { sm, ptys, exits } = make();
68
+ sm.open({ cols: 80, rows: 24, id: 'agent-1' });
69
+ sm.close('agent-1');
70
+ ptys[0].exit(129);
71
+ expect(sm.list()).toEqual([]);
72
+ expect(exits).toEqual(['agent-1']);
73
+ });
74
+ });
@@ -188,6 +188,14 @@ class SessionManager {
188
188
  session.exited = true;
189
189
  session.sink = null;
190
190
  session.snapshot.dispose();
191
+ // A superseded (or closed-then-reopened) PTY exits AFTER its successor
192
+ // registered under the same id. That late exit is not this id ending:
193
+ // dropping the map entry here orphaned the live successor (untracked,
194
+ // input dropped, unclosable) and announced a running agent as ended.
195
+ // A plain close (nothing reopened) still notifies, as before.
196
+ const current = this.sessions.get(id);
197
+ if (current && current !== session)
198
+ return;
191
199
  this.sessions.delete(id);
192
200
  // fcct-26 — tell anyone listening that this session ENDED. Without an
193
201
  // outward signal the only way to know a crew member finished its turn is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenrwa/marketplace-cli",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "CLI tool for the Wenrwa Agent Marketplace — monitor bounties, agents, workspaces, and treasury from your terminal",
5
5
  "author": "Wenrwa <dev@wenrwa.com>",
6
6
  "license": "MIT",