@wenrwa/marketplace-cli 0.7.2 → 0.7.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 @@
1
+ export {};
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const launchSupervisor_1 = require("../bridge/launchSupervisor");
4
+ const sessionManager_1 = require("../bridge/sessionManager");
5
+ const ID = '445be432-0000-4000-8000-000000000001';
6
+ // Verbatim shapes from Claude Code 2.1.289 (stderr is red-wrapped; the resume
7
+ // miss is drawn by the TUI with a truecolor escape).
8
+ const IN_USE = `\x1b[31mError: Session ID ${ID} is already in use.\x1b[39m\r\n`;
9
+ const NO_CONVO = `\x1b[?2026h\x1b[38;2;255;107;128mNo conversation found with session ID: ${ID}\x1b[39m\r\r\n`;
10
+ class FakePty {
11
+ args;
12
+ written = [];
13
+ killed = false;
14
+ dataCb = null;
15
+ exitCb = null;
16
+ constructor(args) {
17
+ this.args = args;
18
+ }
19
+ write(d) { this.written.push(d); }
20
+ resize() { }
21
+ kill() { this.killed = true; }
22
+ onData(cb) { this.dataCb = cb; }
23
+ onExit(cb) { this.exitCb = cb; }
24
+ emit(d) { this.dataCb?.(d); }
25
+ exit(code) { this.exitCb?.(code); }
26
+ }
27
+ function harness(args) {
28
+ const spawned = [];
29
+ const factory = {
30
+ spawn: (o) => {
31
+ const p = new FakePty(o.args ?? []);
32
+ spawned.push(p);
33
+ return p;
34
+ },
35
+ };
36
+ let t = 0;
37
+ const logs = [];
38
+ const pty = (0, launchSupervisor_1.superviseLaunch)(factory, { cols: 80, rows: 24, command: 'claude', args }, { now: () => t, log: (m) => logs.push(m), settle: (fn) => fn(), windowMs: 10_000 });
39
+ const out = [];
40
+ const exits = [];
41
+ pty.onData((d) => out.push(d));
42
+ pty.onExit((c) => exits.push(c));
43
+ return { pty, spawned, out, exits, logs, advance: (ms) => { t += ms; } };
44
+ }
45
+ describe('launchSupervisor — pure helpers', () => {
46
+ it('session-in-use on --session-id → swap to --resume, keeping every other arg', () => {
47
+ expect((0, launchSupervisor_1.retryArgsFor)(['--dangerously-skip-permissions', '--settings', '/s.json', '--session-id', ID], IN_USE))
48
+ .toEqual(['--dangerously-skip-permissions', '--settings', '/s.json', '--resume', ID]);
49
+ });
50
+ it('no-conversation on --resume → swap to --session-id', () => {
51
+ expect((0, launchSupervisor_1.retryArgsFor)(['--resume', ID], NO_CONVO)).toEqual(['--session-id', ID]);
52
+ });
53
+ it('no session flag, or an unrelated error → no retry', () => {
54
+ expect((0, launchSupervisor_1.retryArgsFor)([], IN_USE)).toBeNull();
55
+ expect((0, launchSupervisor_1.retryArgsFor)(['--session-id', ID], 'Error: Invalid session ID. Must be a valid UUID.')).toBeNull();
56
+ expect((0, launchSupervisor_1.retryArgsFor)(['--resume', ID], IN_USE)).toBeNull();
57
+ });
58
+ it('firstMeaningfulLine strips ANSI, "Error:" and stack frames', () => {
59
+ expect((0, launchSupervisor_1.firstMeaningfulLine)(IN_USE)).toBe(`Session ID ${ID} is already in use`);
60
+ expect((0, launchSupervisor_1.firstMeaningfulLine)('\r\n\x1b[1m\x1b[0m\r\nError: boom.\n at foo (x.js:1:1)\n')).toBe('boom');
61
+ expect((0, launchSupervisor_1.firstMeaningfulLine)('\x1b[?25h\r\n')).toBeNull();
62
+ });
63
+ it('friendly line is one short sentence with no stack trace', () => {
64
+ const line = (0, launchSupervisor_1.friendlyStartFailure)('Error: kaput\n at a (b.js:1:1)\n at c (d.js:2:2)', 1);
65
+ expect(line).toContain("Claude couldn't start: kaput. Reopen the pane to try again.");
66
+ expect(line).not.toContain('at a (');
67
+ expect((0, launchSupervisor_1.friendlyStartFailure)('', 3)).toContain("Claude couldn't start: it exited with code 3.");
68
+ });
69
+ });
70
+ describe('launchSupervisor — supervised PTY', () => {
71
+ it('session-in-use → exactly one relaunch with --resume; the healthy retry is passed through', () => {
72
+ const h = harness(['--session-id', ID]);
73
+ h.spawned[0].emit(IN_USE);
74
+ h.advance(800);
75
+ h.spawned[0].exit(1);
76
+ expect(h.spawned).toHaveLength(2);
77
+ expect(h.spawned[1].args).toEqual(['--resume', ID]);
78
+ expect(h.exits).toEqual([]); // the pane never sees the first exit
79
+ expect(h.out.join('')).not.toContain("couldn't start");
80
+ h.pty.write('hi');
81
+ expect(h.spawned[1].written).toEqual(['hi']);
82
+ h.spawned[1].emit('welcome back');
83
+ expect(h.out[h.out.length - 1]).toBe('welcome back');
84
+ expect(h.logs.join('\n')).toMatch(/already in use/);
85
+ });
86
+ it('resume-missing → exactly one relaunch with --session-id', () => {
87
+ const h = harness(['--dangerously-skip-permissions', '--resume', ID]);
88
+ h.spawned[0].emit(NO_CONVO);
89
+ h.spawned[0].exit(1);
90
+ expect(h.spawned).toHaveLength(2);
91
+ expect(h.spawned[1].args).toEqual(['--dangerously-skip-permissions', '--session-id', ID]);
92
+ expect(h.exits).toEqual([]);
93
+ });
94
+ it('never loops: a retry that fails the same way gets the friendly line and the exit, no third spawn', () => {
95
+ const h = harness(['--session-id', ID]);
96
+ h.spawned[0].emit(IN_USE);
97
+ h.spawned[0].exit(1);
98
+ h.spawned[1].emit(NO_CONVO);
99
+ h.spawned[1].exit(1);
100
+ expect(h.spawned).toHaveLength(2);
101
+ expect(h.exits).toEqual([1]);
102
+ expect(h.out.join('')).toContain(`Claude couldn't start: No conversation found with session ID: ${ID}. Reopen the pane to try again.`);
103
+ });
104
+ it('generic fast non-zero exit → friendly line, then the exit; raw error kept in the log', () => {
105
+ const h = harness(['--session-id', ID]);
106
+ h.spawned[0].emit('\x1b[31mError: Invalid session ID. Must be a valid UUID.\x1b[39m\r\n');
107
+ h.advance(300);
108
+ h.spawned[0].exit(1);
109
+ expect(h.spawned).toHaveLength(1);
110
+ expect(h.exits).toEqual([1]);
111
+ const last = h.out[h.out.length - 1];
112
+ expect(last).toContain("Claude couldn't start: Invalid session ID. Must be a valid UUID. Reopen the pane to try again.");
113
+ expect(h.logs.join('\n')).toContain('Error: Invalid session ID. Must be a valid UUID.');
114
+ });
115
+ it('a long-running session that exits later is untouched — no retry, no friendly line', () => {
116
+ const h = harness(['--session-id', ID]);
117
+ h.spawned[0].emit('working...');
118
+ h.advance(60_000);
119
+ h.spawned[0].emit(IN_USE); // even matching text, outside the window
120
+ h.spawned[0].exit(1);
121
+ expect(h.spawned).toHaveLength(1);
122
+ expect(h.exits).toEqual([1]);
123
+ expect(h.out.join('')).not.toContain("couldn't start");
124
+ expect(h.logs).toEqual([]);
125
+ });
126
+ it('a fast clean exit (code 0) passes straight through', () => {
127
+ const h = harness(['--resume', ID]);
128
+ h.spawned[0].exit(0);
129
+ expect(h.spawned).toHaveLength(1);
130
+ expect(h.exits).toEqual([0]);
131
+ expect(h.out).toEqual([]);
132
+ });
133
+ it('a bridge kill is never retried or annotated', () => {
134
+ const h = harness(['--session-id', ID]);
135
+ h.spawned[0].emit(IN_USE);
136
+ h.pty.kill();
137
+ expect(h.spawned[0].killed).toBe(true);
138
+ h.spawned[0].exit(1);
139
+ expect(h.spawned).toHaveLength(1);
140
+ expect(h.exits).toEqual([1]);
141
+ expect(h.out.join('')).not.toContain("couldn't start");
142
+ });
143
+ });
144
+ describe('launchSupervisor — wired through SessionManager', () => {
145
+ const fakeSnapshot = () => ({
146
+ write: () => Promise.resolve(),
147
+ resize: () => { },
148
+ serialize: () => Promise.resolve(''),
149
+ dispose: () => { },
150
+ });
151
+ function manager() {
152
+ const spawned = [];
153
+ const factory = { spawn: (o) => { const p = new FakePty(o.args ?? []); spawned.push(p); return p; } };
154
+ const mgr = new sessionManager_1.SessionManager(factory, {
155
+ snapshotFactory: fakeSnapshot,
156
+ launchSupervisor: { now: () => 0, log: () => { }, settle: (fn) => fn() },
157
+ });
158
+ const exits = [];
159
+ mgr.setOnSessionExit((id, code) => exits.push([id, code]));
160
+ return { mgr, spawned, exits };
161
+ }
162
+ it('a claude launch self-heals inside one session: same id, one exit only when the retry dies', async () => {
163
+ const { mgr, spawned, exits } = manager();
164
+ const id = mgr.open({ cols: 80, rows: 24, id: ID, command: 'claude', args: ['--session-id', ID] });
165
+ const seen = [];
166
+ await mgr.attach(id, (f) => { if (f.t === 'data')
167
+ seen.push(f.data); });
168
+ spawned[0].emit(IN_USE);
169
+ spawned[0].exit(1);
170
+ expect(spawned).toHaveLength(2);
171
+ expect(mgr.list()).toEqual([ID]);
172
+ expect(exits).toEqual([]);
173
+ spawned[1].emit('boom\r\n');
174
+ spawned[1].exit(2);
175
+ expect(exits).toEqual([[ID, 2]]);
176
+ expect(seen.join('')).toContain("Claude couldn't start: boom. Reopen the pane to try again.");
177
+ });
178
+ it('a shell launch (no command) is not supervised', () => {
179
+ const { mgr, spawned, exits } = manager();
180
+ mgr.open({ cols: 80, rows: 24 });
181
+ spawned[0].emit('Error: nope\r\n');
182
+ spawned[0].exit(1);
183
+ expect(spawned).toHaveLength(1);
184
+ expect(exits).toHaveLength(1);
185
+ });
186
+ });
@@ -80,3 +80,17 @@ describe('hasClaudeSessionHistory', () => {
80
80
  expect(result).toBe(true);
81
81
  });
82
82
  });
83
+ describe('a registered folder with a trailing slash (2026-10-04 "Session ID … is already in use")', () => {
84
+ it('slugs exactly like the same folder without the slash', () => {
85
+ expect((0, claudeSessionHistory_1.claudeProjectSlug)('/Users/me/Wenrwa Projects/Site/')).toBe((0, claudeSessionHistory_1.claudeProjectSlug)('/Users/me/Wenrwa Projects/Site'));
86
+ expect((0, claudeSessionHistory_1.claudeProjectSlug)('/Users/me/Wenrwa Projects/Site/')).toBe('-Users-me-Wenrwa-Projects-Site');
87
+ });
88
+ it('finds the agent transcript, so the launch resumes instead of re-creating the session', () => {
89
+ const seen = [];
90
+ const found = (0, claudeSessionHistory_1.hasAgentSessionHistory)('/Users/me/Wenrwa Projects/Site/', 'abc', {
91
+ env: { CLAUDE_CONFIG_DIR: '/cfg' },
92
+ existsSync: (p) => { seen.push(String(p)); return String(p) === '/cfg/projects/-Users-me-Wenrwa-Projects-Site/abc.jsonl'; },
93
+ });
94
+ expect(found).toBe(true);
95
+ });
96
+ });
@@ -67,7 +67,13 @@ const path = __importStar(require("path"));
67
67
  /** Claude Code's project-directory naming rule — see module doc for the
68
68
  * empirical verification. */
69
69
  function claudeProjectSlug(cwd) {
70
- return cwd.replace(/[^a-zA-Z0-9]/g, '-');
70
+ // Claude Code names the folder after the cwd WITHOUT a trailing slash. The
71
+ // local folder registry stores paths with one ("…/MyProject/"), which made
72
+ // the slug end in "-", missed the agent's transcript, and launched
73
+ // `--session-id <existing id>` → "Session ID … is already in use" on every
74
+ // reopen (2026-10-04). Normalise first.
75
+ const normalized = cwd.length > 1 ? cwd.replace(/\/+$/, '') : cwd;
76
+ return normalized.replace(/[^a-zA-Z0-9]/g, '-');
71
77
  }
72
78
  /** Where Claude Code keeps its config + session files: `$CLAUDE_CONFIG_DIR`
73
79
  * when set (the hosted image's persistent-volume path), else `~/.claude`
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Launch supervisor — makes a `claude` that dies at startup either heal
3
+ * itself or say so in plain words, instead of leaving its raw stderr as the
4
+ * last thing in the pane.
5
+ *
6
+ * Wraps ONE agent launch (SessionManager.open with `command: 'claude'`) in a
7
+ * PtyLike that the session manager cannot tell apart from a plain PTY. Inside,
8
+ * it watches the first `windowMs` of the process's life:
9
+ *
10
+ * - `--session-id <id>` exits early with "Session ID <id> is already in use"
11
+ * → the conversation already exists (e.g. it was recorded under a project
12
+ * dir the existence check missed), so relaunch ONCE with `--resume <id>`.
13
+ * - `--resume <id>` exits early with "No conversation found with session ID"
14
+ * → there is nothing to resume, so relaunch ONCE with `--session-id <id>`.
15
+ * - any other early non-zero exit (or a retry that also fails) → write one
16
+ * short line into the stream ("Claude couldn't start: …") and let the exit
17
+ * through. The raw output goes to the bridge log, not into a second copy.
18
+ *
19
+ * At most one retry per open — the retry itself is never retried, so no loop.
20
+ * A process that outlives the window, exits 0, or was killed by the bridge is
21
+ * passed through untouched.
22
+ *
23
+ * Wording checked against Claude Code 2.1.289: the session-in-use error is
24
+ * `Error: Session ID <uuid> is already in use.` (stderr); the resume miss is
25
+ * `No conversation found with session ID: <uuid>` (rendered by the TUI).
26
+ * Both arrive on the PTY, so matching the combined output catches both.
27
+ *
28
+ * Both lanes: the local bridge (loopback + crew relay on a laptop) spawns
29
+ * `claude` through here. The hosted container does NOT — it spawns $SHELL and
30
+ * infra/terminal-image/autolaunch.sh runs `claude`, so the same rules live
31
+ * there too.
32
+ */
33
+ import { PtyFactory, PtyLike } from './sessionManager';
34
+ /** How long after launch an exit still counts as "couldn't start". */
35
+ export declare const DEFAULT_LAUNCH_WINDOW_MS = 10000;
36
+ export declare const SESSION_IN_USE_RE: RegExp;
37
+ export declare const RESUME_MISSING_RE: RegExp;
38
+ export declare function stripAnsi(s: string): string;
39
+ /**
40
+ * The first line of `output` worth showing a person: ANSI stripped, blank
41
+ * lines and stack frames skipped, a leading "Error:" dropped, trailing period
42
+ * dropped (the caller adds its own punctuation), clipped to ~160 chars.
43
+ */
44
+ export declare function firstMeaningfulLine(output: string): string | null;
45
+ /**
46
+ * Pure decision for an EARLY exit (caller has already checked the window and
47
+ * the retry budget): the argv to relaunch with, or null for "don't retry".
48
+ */
49
+ export declare function retryArgsFor(args: string[], output: string): string[] | null;
50
+ export declare function friendlyStartFailure(output: string, code: number): string;
51
+ export interface LaunchSupervisorOptions {
52
+ windowMs?: number;
53
+ now?: () => number;
54
+ log?: (msg: string) => void;
55
+ /** node-pty can report exit before the last output chunk is read; an early
56
+ * failure is judged after this short settle. Tests pass a synchronous one. */
57
+ settle?: (fn: () => void) => void;
58
+ }
59
+ type SpawnOpts = Parameters<PtyFactory['spawn']>[0];
60
+ export declare function superviseLaunch(factory: PtyFactory, spawnOpts: SpawnOpts, opts?: LaunchSupervisorOptions): PtyLike;
61
+ export {};
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RESUME_MISSING_RE = exports.SESSION_IN_USE_RE = exports.DEFAULT_LAUNCH_WINDOW_MS = void 0;
4
+ exports.stripAnsi = stripAnsi;
5
+ exports.firstMeaningfulLine = firstMeaningfulLine;
6
+ exports.retryArgsFor = retryArgsFor;
7
+ exports.friendlyStartFailure = friendlyStartFailure;
8
+ exports.superviseLaunch = superviseLaunch;
9
+ /** How long after launch an exit still counts as "couldn't start". */
10
+ exports.DEFAULT_LAUNCH_WINDOW_MS = 10_000;
11
+ /** Cap on how much early output is kept for matching/logging. */
12
+ const MAX_CAPTURE = 16 * 1024;
13
+ exports.SESSION_IN_USE_RE = /Session ID \S+ is already in use/i;
14
+ exports.RESUME_MISSING_RE = /No conversation found/i;
15
+ // CSI, OSC (BEL- or ST-terminated), and lone two-char escapes.
16
+ // eslint-disable-next-line no-control-regex
17
+ const ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_()#%][0-9A-Za-z]?/g;
18
+ function stripAnsi(s) {
19
+ // eslint-disable-next-line no-control-regex
20
+ return s.replace(ANSI_RE, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
21
+ }
22
+ /**
23
+ * The first line of `output` worth showing a person: ANSI stripped, blank
24
+ * lines and stack frames skipped, a leading "Error:" dropped, trailing period
25
+ * dropped (the caller adds its own punctuation), clipped to ~160 chars.
26
+ */
27
+ function firstMeaningfulLine(output) {
28
+ const lines = stripAnsi(output).split(/\r?\n|\r/);
29
+ for (const raw of lines) {
30
+ const line = raw.trim();
31
+ if (!line)
32
+ continue;
33
+ if (/^at\s+\S/.test(line))
34
+ continue;
35
+ if (/^[-─━═_=*·•\s]+$/.test(line))
36
+ continue;
37
+ let msg = line.replace(/^(?:error|fatal)\s*:\s*/i, '').replace(/[.\s]+$/, '');
38
+ if (!msg)
39
+ continue;
40
+ if (msg.length > 160)
41
+ msg = `${msg.slice(0, 157)}...`;
42
+ return msg;
43
+ }
44
+ return null;
45
+ }
46
+ /** Where `--resume`/`--session-id` sits in argv, or null. */
47
+ function sessionFlagIndex(args) {
48
+ for (let i = 0; i < args.length - 1; i++) {
49
+ if (args[i] === '--resume' || args[i] === '--session-id')
50
+ return i;
51
+ }
52
+ return null;
53
+ }
54
+ /**
55
+ * Pure decision for an EARLY exit (caller has already checked the window and
56
+ * the retry budget): the argv to relaunch with, or null for "don't retry".
57
+ */
58
+ function retryArgsFor(args, output) {
59
+ const i = sessionFlagIndex(args);
60
+ if (i === null)
61
+ return null;
62
+ const text = stripAnsi(output);
63
+ const flag = args[i];
64
+ if (flag === '--session-id' && exports.SESSION_IN_USE_RE.test(text)) {
65
+ const next = [...args];
66
+ next[i] = '--resume';
67
+ return next;
68
+ }
69
+ if (flag === '--resume' && exports.RESUME_MISSING_RE.test(text)) {
70
+ const next = [...args];
71
+ next[i] = '--session-id';
72
+ return next;
73
+ }
74
+ return null;
75
+ }
76
+ function friendlyStartFailure(output, code) {
77
+ const reason = firstMeaningfulLine(output) ?? `it exited with code ${code}`;
78
+ return `\r\n\x1b[33mClaude couldn't start: ${reason}. Reopen the pane to try again.\x1b[0m\r\n`;
79
+ }
80
+ /** Clears the pane before the retry so the raw error from the first attempt
81
+ * doesn't sit above the healthy session. */
82
+ const RETRY_CLEAR = '\x1b[2J\x1b[3J\x1b[H';
83
+ function superviseLaunch(factory, spawnOpts, opts = {}) {
84
+ const windowMs = opts.windowMs ?? exports.DEFAULT_LAUNCH_WINDOW_MS;
85
+ const now = opts.now ?? Date.now;
86
+ const log = opts.log ?? ((m) => console.error(m));
87
+ const settle = opts.settle ?? ((fn) => { setTimeout(fn, 100); });
88
+ const dataListeners = [];
89
+ const exitListeners = [];
90
+ const emitData = (d) => { for (const cb of dataListeners)
91
+ cb(d); };
92
+ const emitExit = (code) => { for (const cb of exitListeners)
93
+ cb(code); };
94
+ let cols = spawnOpts.cols;
95
+ let rows = spawnOpts.rows;
96
+ let args = spawnOpts.args ?? [];
97
+ let retried = false;
98
+ let killed = false;
99
+ let current;
100
+ const start = () => {
101
+ const startedAt = now();
102
+ let captured = '';
103
+ const pty = factory.spawn({ ...spawnOpts, cols, rows, args });
104
+ current = pty;
105
+ pty.onData((d) => {
106
+ if (pty !== current)
107
+ return;
108
+ if (captured.length < MAX_CAPTURE && now() - startedAt < windowMs)
109
+ captured += d;
110
+ emitData(d);
111
+ });
112
+ pty.onExit((code) => {
113
+ if (pty !== current)
114
+ return;
115
+ const early = now() - startedAt < windowMs;
116
+ if (killed || !early || code === 0) {
117
+ emitExit(code);
118
+ return;
119
+ }
120
+ settle(() => onEarlyFailure(code));
121
+ });
122
+ const onEarlyFailure = (code) => {
123
+ if (pty !== current)
124
+ return;
125
+ if (killed) {
126
+ emitExit(code);
127
+ return;
128
+ }
129
+ const raw = stripAnsi(captured).trim();
130
+ log(`[bridge] claude exited at startup (code ${code}, args: ${args.join(' ')}): ${raw || '(no output)'}`);
131
+ const nextArgs = retried ? null : retryArgsFor(args, captured);
132
+ if (nextArgs) {
133
+ retried = true;
134
+ log(`[bridge] relaunching claude once with: ${nextArgs.join(' ')}`);
135
+ args = nextArgs;
136
+ emitData(RETRY_CLEAR);
137
+ try {
138
+ start();
139
+ return;
140
+ }
141
+ catch (err) {
142
+ log(`[bridge] claude relaunch failed: ${err instanceof Error ? err.message : String(err)}`);
143
+ }
144
+ }
145
+ emitData(friendlyStartFailure(captured, code));
146
+ emitExit(code);
147
+ };
148
+ };
149
+ start();
150
+ return {
151
+ write: (d) => current.write(d),
152
+ resize: (c, r) => { cols = c; rows = r; current.resize(c, r); },
153
+ kill: () => { killed = true; current.kill(); },
154
+ onData: (cb) => { dataListeners.push(cb); },
155
+ onExit: (cb) => { exitListeners.push(cb); },
156
+ };
157
+ }
@@ -1,4 +1,5 @@
1
1
  import { TermSnapshot } from './termSnapshot';
2
+ import { LaunchSupervisorOptions } from './launchSupervisor';
2
3
  export interface PtyLike {
3
4
  write(data: string): void;
4
5
  resize(cols: number, rows: number): void;
@@ -83,6 +84,10 @@ export interface SessionManagerOptions {
83
84
  snapshotFactory?: (cols: number, rows: number, opts?: {
84
85
  scrollback?: number;
85
86
  }) => TermSnapshot;
87
+ /** Startup supervision for `claude` launches (launchSupervisor.ts): one
88
+ * self-healing retry for a session-id/resume mismatch, else a plain
89
+ * "Claude couldn't start" line. Injectable clock/settle for tests. */
90
+ launchSupervisor?: LaunchSupervisorOptions;
86
91
  }
87
92
  /**
88
93
  * lws-07 (ADR-0033 port): validates a client-supplied size pair for the
@@ -106,6 +111,7 @@ export declare class SessionManager {
106
111
  private scheduleTimeout;
107
112
  private cancelTimeout;
108
113
  private snapshotFactory;
114
+ private launchSupervisor;
109
115
  /** twr-16: cumulative count of attach-pipeline wedges (any stage timeout,
110
116
  * or a serialize-wedge whose recovery retry ALSO timed out) since this
111
117
  * SessionManager was created. Surfaced via attachFailures() into the
@@ -21,6 +21,7 @@ exports.validSize = validSize;
21
21
  */
22
22
  const crypto_1 = __importDefault(require("crypto"));
23
23
  const termSnapshot_1 = require("./termSnapshot");
24
+ const launchSupervisor_1 = require("./launchSupervisor");
24
25
  /** Default settle window (lws-07): after a resize-on-attach, how long we give
25
26
  * the PTY's SIGWINCH-triggered repaint to land in the snapshot before
26
27
  * serializing. Small — this only needs to cover one render tick, not a
@@ -73,6 +74,7 @@ class SessionManager {
73
74
  scheduleTimeout;
74
75
  cancelTimeout;
75
76
  snapshotFactory;
77
+ launchSupervisor;
76
78
  /** twr-16: cumulative count of attach-pipeline wedges (any stage timeout,
77
79
  * or a serialize-wedge whose recovery retry ALSO timed out) since this
78
80
  * SessionManager was created. Surfaced via attachFailures() into the
@@ -89,6 +91,7 @@ class SessionManager {
89
91
  this.scheduleTimeout = opts.scheduleTimeout ?? ((cb, ms) => setTimeout(cb, ms));
90
92
  this.cancelTimeout = opts.cancelTimeout ?? ((h) => clearTimeout(h));
91
93
  this.snapshotFactory = opts.snapshotFactory ?? termSnapshot_1.createTermSnapshot;
94
+ this.launchSupervisor = opts.launchSupervisor ?? {};
92
95
  }
93
96
  /** twr-16: see attachFailureCount. */
94
97
  attachFailures() {
@@ -123,7 +126,12 @@ class SessionManager {
123
126
  prior.snapshot.dispose();
124
127
  this.sessions.delete(id);
125
128
  }
126
- const pty = this.factory.spawn(opts);
129
+ // A `claude` launch (agent panes, crew members) is supervised through its
130
+ // startup window so a fast failure heals or explains itself; a shell is
131
+ // spawned exactly as before.
132
+ const pty = opts.command === 'claude'
133
+ ? (0, launchSupervisor_1.superviseLaunch)(this.factory, opts, this.launchSupervisor)
134
+ : this.factory.spawn(opts);
127
135
  const snapshot = this.snapshotFactory(opts.cols, opts.rows, { scrollback: this.snapshotScrollback });
128
136
  const session = {
129
137
  pty,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenrwa/marketplace-cli",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "CLI tool for the Wenrwa Agent Marketplace \u2014 monitor bounties, agents, workspaces, and treasury from your terminal",
5
5
  "author": "Wenrwa <dev@wenrwa.com>",
6
6
  "license": "MIT",