baychat 0.33.2 → 0.34.0
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/.claude-plugin/plugin.json +1 -1
- package/dist/auto-update/policy.js +6 -3
- package/dist/auto-update/updater.js +85 -23
- package/dist/channel/relay-link.js +1 -0
- package/dist/channel/server.js +1 -1
- package/dist/launch/command.js +18 -3
- package/dist/launch/policy.js +4 -2
- package/dist/multiplexer-launch.js +20 -8
- package/dist/protocol-content.js +1 -1
- package/dist/relay/acp/policy.js +6 -2
- package/dist/relay/acp/presence.js +4 -3
- package/dist/relay/codex-app-server.js +102 -0
- package/dist/relay/daemon.js +28 -1
- package/dist/relay/launcher/acp-launch.js +107 -0
- package/dist/relay/launcher/argv.js +18 -2
- package/dist/relay/launcher/codex-launch.js +103 -0
- package/dist/relay/launcher/handle.js +135 -20
- package/dist/relay/launcher/loop.js +46 -3
- package/dist/relay/launcher/multiplexer.js +77 -2
- package/dist/relay/launcher/session-spots.js +73 -0
- package/dist/relay/launcher/stop-registered.js +155 -0
- package/dist/relay/terminal-pane.js +12 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"displayName": "BayChat",
|
|
5
5
|
"description": "Wakes this Claude Code session the moment a BayChat message arrives: no Monitor, no 30-minute deadline. Uses the baychat CLI already on this computer (npm install -g baychat, then baychat login).",
|
|
6
6
|
"author": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HELD_FRESH_MS = exports.QUIET_MS = exports.CARD_POLL_MS = exports.ASK_AGAIN_MS = exports.HINT_COOLDOWN_MS = exports.HINT_SPREAD_MS = exports.HINT_MIN_MS = exports.CHECK_INTERVAL_MS = exports.AUTO_UPDATE_ENV = void 0;
|
|
3
|
+
exports.HELD_FRESH_MS = exports.QUIET_MS = exports.CARD_POLL_MS = exports.ASK_AGAIN_MS = exports.HINT_RECHECK_MS = exports.HINT_COOLDOWN_MS = exports.HINT_SPREAD_MS = exports.HINT_MIN_MS = exports.CHECK_INTERVAL_MS = exports.AUTO_UPDATE_ENV = void 0;
|
|
4
4
|
exports.planUpdate = planUpdate;
|
|
5
5
|
exports.autoUpdateOffReason = autoUpdateOffReason;
|
|
6
6
|
exports.nextCheckAt = nextCheckAt;
|
|
@@ -54,9 +54,12 @@ const FIRST_CHECK_SPREAD_MS = 10 * MINUTE;
|
|
|
54
54
|
* 1–5 minutes — spread so a fleet that hears the same frame never asks npm together. */
|
|
55
55
|
exports.HINT_MIN_MS = MINUTE;
|
|
56
56
|
exports.HINT_SPREAD_MS = 4 * MINUTE;
|
|
57
|
-
/** A hint
|
|
58
|
-
* must not become an npm query on every reconnect. */
|
|
57
|
+
/** A hint never brings a check closer than this to the last one: a server that names a release
|
|
58
|
+
* on every reconnect must not become an npm query on every reconnect. */
|
|
59
59
|
exports.HINT_COOLDOWN_MS = 15 * MINUTE;
|
|
60
|
+
/** The server can name a release before npm has it (deployed first, published after): npm is
|
|
61
|
+
* asked again this long after each answer that is still older, then the hint is given up. */
|
|
62
|
+
exports.HINT_RECHECK_MS = [15 * MINUTE, 30 * MINUTE, HOUR];
|
|
60
63
|
/** A check that failed (offline, npm down) is tried again sooner than a day, then backs off. */
|
|
61
64
|
const CHECK_RETRY_MS = HOUR;
|
|
62
65
|
/** A failed install of one version waits a day, then two, … up to a week. A newer version resets it. */
|
|
@@ -17,10 +17,6 @@ class AutoUpdater {
|
|
|
17
17
|
running = false;
|
|
18
18
|
stopped = false;
|
|
19
19
|
lastCardPoll = 0;
|
|
20
|
-
/** A release the server named (hint.ts), and when npm is asked about it. In memory only: a
|
|
21
|
-
* restarted relay hears the next `ready` frame anyway. */
|
|
22
|
-
hinted;
|
|
23
|
-
hintCheckAt;
|
|
24
20
|
constructor(deps) {
|
|
25
21
|
this.deps = deps;
|
|
26
22
|
this.startedAt = deps.now();
|
|
@@ -28,6 +24,7 @@ class AutoUpdater {
|
|
|
28
24
|
}
|
|
29
25
|
/** Finish an update the previous relay installed, then look at the state once a minute. */
|
|
30
26
|
start() {
|
|
27
|
+
this.freshStart();
|
|
31
28
|
void this.runExclusive(() => this.finishPendingUpdate());
|
|
32
29
|
this.timer = setInterval(() => void this.tick(), exports.TICK_MS);
|
|
33
30
|
this.timer.unref();
|
|
@@ -50,34 +47,86 @@ class AutoUpdater {
|
|
|
50
47
|
failure: state.failure?.reason,
|
|
51
48
|
};
|
|
52
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* New code is a new chance. Check failures recorded by ANOTHER version say nothing about this
|
|
52
|
+
* one — the failure may be the very bug this version fixes. Real case, 2026-10-21: relays that
|
|
53
|
+
* failed 11 times on 0.33.0 ("npm was not found on PATH") carried that backoff into 0.33.1, which
|
|
54
|
+
* fixed it, and would not have asked npm again for a day. So the schedule starts over, and the
|
|
55
|
+
* first check comes 5–15 minutes after this start.
|
|
56
|
+
*/
|
|
57
|
+
freshStart() {
|
|
58
|
+
const state = this.deps.loadState();
|
|
59
|
+
if (!state.current || state.current === this.deps.current)
|
|
60
|
+
return;
|
|
61
|
+
if (state.lastCheckAt === undefined && !state.checkFailures)
|
|
62
|
+
return;
|
|
63
|
+
const was = state.current;
|
|
64
|
+
state.lastCheckAt = undefined;
|
|
65
|
+
state.checkFailures = undefined;
|
|
66
|
+
state.lastCheckError = undefined;
|
|
67
|
+
state.current = this.deps.current;
|
|
68
|
+
this.deps.saveState(state);
|
|
69
|
+
this.deps.log(`auto-update: now running ${this.deps.current} (checks were recorded by ${was}); asking npm afresh`);
|
|
70
|
+
}
|
|
53
71
|
/**
|
|
54
72
|
* The server's feed named `version` as the newest BayChat (hint.ts). Newer than this copy, and
|
|
55
73
|
* newer than what npm last said: ask npm in 1–5 minutes instead of at the daily check. Only the
|
|
56
74
|
* moment moves — npm still names the version, and planUpdate still decides what happens.
|
|
57
75
|
*
|
|
58
|
-
* The moment never moves earlier than the backoff allows: not within
|
|
59
|
-
* last check,
|
|
60
|
-
*
|
|
76
|
+
* The moment never moves earlier than the backoff allows (`hintDue`): not within
|
|
77
|
+
* HINT_COOLDOWN_MS of the last check, not before a failed check's retry. The hint is kept, on
|
|
78
|
+
* disk, rather than dropped: a server deployed before the npm publish names a version npm does
|
|
79
|
+
* not have yet (2026-10-21, 0.33.2: the hint's check came 1 minute before the publish), so npm
|
|
80
|
+
* is asked again after each HINT_RECHECK_MS step until it names that version, then given up.
|
|
61
81
|
*/
|
|
62
82
|
hint(version) {
|
|
63
83
|
const { mode } = this.mode();
|
|
64
84
|
if (mode === "off" || mode === "none")
|
|
65
85
|
return;
|
|
66
86
|
const ahead = (0, self_upgrade_1.compareVersions)(version, this.deps.current);
|
|
67
|
-
if (ahead === undefined || ahead <= 0
|
|
87
|
+
if (ahead === undefined || ahead <= 0)
|
|
68
88
|
return;
|
|
69
89
|
const state = this.deps.loadState();
|
|
70
90
|
const known = state.latest ? (0, self_upgrade_1.compareVersions)(state.latest, version) : undefined;
|
|
71
91
|
if (known !== undefined && known >= 0)
|
|
72
92
|
return; // npm already named it (or newer)
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
if (validHint(state.hint)?.version === version)
|
|
94
|
+
return; // waiting on it, or gave up on it
|
|
95
|
+
const nextAt = this.hintDue(state, this.deps.now() + policy_1.HINT_MIN_MS + this.deps.random() * policy_1.HINT_SPREAD_MS);
|
|
96
|
+
state.hint = { version, attempts: 0, nextAt };
|
|
97
|
+
this.deps.saveState(state);
|
|
98
|
+
this.deps.log(`auto-update: the server names ${version}; asking npm in ${Math.ceil((nextAt - this.deps.now()) / 60_000)} min`);
|
|
99
|
+
}
|
|
100
|
+
/** The earliest a hinted check may run: `wanted`, or later if the backoff says so. */
|
|
101
|
+
hintDue(state, wanted) {
|
|
102
|
+
let at = wanted;
|
|
103
|
+
if (state.lastCheckAt !== undefined)
|
|
104
|
+
at = Math.max(at, state.lastCheckAt + policy_1.HINT_COOLDOWN_MS);
|
|
105
|
+
if ((state.checkFailures ?? 0) > 0)
|
|
106
|
+
at = Math.max(at, (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread));
|
|
107
|
+
return at;
|
|
108
|
+
}
|
|
109
|
+
/** After a check: the hint is answered (npm names it, or this copy has it), retried, or given up. */
|
|
110
|
+
settleHint(state, now, wasDue) {
|
|
111
|
+
const hint = validHint(state.hint);
|
|
112
|
+
if (!hint)
|
|
113
|
+
return void (state.hint = undefined);
|
|
114
|
+
const latest = state.latest ? (0, self_upgrade_1.compareVersions)(state.latest, hint.version) : undefined;
|
|
115
|
+
if ((latest !== undefined && latest >= 0) || (0, self_upgrade_1.compareVersions)(hint.version, this.deps.current) !== 1) {
|
|
116
|
+
state.hint = undefined;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!wasDue || hint.nextAt === undefined)
|
|
75
120
|
return;
|
|
76
|
-
|
|
121
|
+
const attempts = hint.attempts + 1;
|
|
122
|
+
const wait = policy_1.HINT_RECHECK_MS[attempts - 1];
|
|
123
|
+
if (wait === undefined) {
|
|
124
|
+
state.hint = { version: hint.version, attempts };
|
|
125
|
+
this.deps.log(`auto-update: npm still does not have ${hint.version}; the daily check takes over`);
|
|
77
126
|
return;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.deps.log(`auto-update:
|
|
127
|
+
}
|
|
128
|
+
state.hint = { version: hint.version, attempts, nextAt: now + wait };
|
|
129
|
+
this.deps.log(`auto-update: npm does not have ${hint.version} yet; asking again in ${wait / 60_000} min`);
|
|
81
130
|
}
|
|
82
131
|
/** One look at the state. Never throws; never runs twice at once. */
|
|
83
132
|
async tick() {
|
|
@@ -97,11 +146,10 @@ class AutoUpdater {
|
|
|
97
146
|
if (mode === "off" || mode === "none")
|
|
98
147
|
return;
|
|
99
148
|
const now = this.deps.now();
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await this.check(state, mode);
|
|
104
|
-
}
|
|
149
|
+
const hintAt = validHint(state.hint)?.nextAt;
|
|
150
|
+
const hinted = hintAt !== undefined && now >= hintAt;
|
|
151
|
+
if (hinted || now >= (0, policy_1.nextCheckAt)(state, this.startedAt, this.spread))
|
|
152
|
+
await this.check(state, mode, hinted);
|
|
105
153
|
if (mode !== "auto")
|
|
106
154
|
return;
|
|
107
155
|
if (state.card?.requestId && now - this.lastCardPoll >= policy_1.CARD_POLL_MS)
|
|
@@ -129,20 +177,25 @@ class AutoUpdater {
|
|
|
129
177
|
return { mode: "notify", reason: "for a relay started by hand (install with `baychat update`)" };
|
|
130
178
|
return { mode: "auto" };
|
|
131
179
|
}
|
|
132
|
-
/** Ask npm, and decide. */
|
|
133
|
-
async check(state, mode) {
|
|
180
|
+
/** Ask npm, and decide. `hinted`: the check a server's hint asked for (counts as a hint attempt). */
|
|
181
|
+
async check(state, mode, hinted = false) {
|
|
134
182
|
const looked = await this.deps.lookupLatest();
|
|
135
|
-
|
|
183
|
+
const now = this.deps.now();
|
|
184
|
+
state.lastCheckAt = now;
|
|
136
185
|
if (!looked.ok) {
|
|
137
|
-
// Offline, npm down: quiet. Tried again within the hour, then less often.
|
|
186
|
+
// Offline, npm down: quiet. Tried again within the hour, then less often — a waiting hint too.
|
|
138
187
|
state.checkFailures = (state.checkFailures ?? 0) + 1;
|
|
139
188
|
state.lastCheckError = looked.reason;
|
|
189
|
+
const hint = validHint(state.hint);
|
|
190
|
+
if (hint?.nextAt !== undefined)
|
|
191
|
+
state.hint = { ...hint, nextAt: this.hintDue(state, now) };
|
|
140
192
|
this.deps.log(`auto-update: could not ask npm (${looked.reason})`);
|
|
141
193
|
return this.deps.saveState(state);
|
|
142
194
|
}
|
|
143
195
|
state.checkFailures = 0;
|
|
144
196
|
state.lastCheckError = undefined;
|
|
145
197
|
state.latest = looked.release.version;
|
|
198
|
+
this.settleHint(state, now, hinted);
|
|
146
199
|
const plan = (0, policy_1.planUpdate)(this.deps.current, looked.release);
|
|
147
200
|
if (plan.kind === "current") {
|
|
148
201
|
// Updated some other way (`baychat update`), or nothing new: nothing may stay on the phone.
|
|
@@ -355,6 +408,15 @@ class AutoUpdater {
|
|
|
355
408
|
}
|
|
356
409
|
}
|
|
357
410
|
exports.AutoUpdater = AutoUpdater;
|
|
411
|
+
/** The persisted hint, if it is one: the state file is ours, but a hand edit or an older shape must
|
|
412
|
+
* not stop the updater. */
|
|
413
|
+
function validHint(raw) {
|
|
414
|
+
if (!raw || typeof raw !== "object" || typeof raw.version !== "string" || typeof raw.attempts !== "number")
|
|
415
|
+
return undefined;
|
|
416
|
+
if (raw.nextAt !== undefined && typeof raw.nextAt !== "number")
|
|
417
|
+
return undefined;
|
|
418
|
+
return raw;
|
|
419
|
+
}
|
|
358
420
|
function errText(err) {
|
|
359
421
|
return err instanceof Error ? err.message : String(err);
|
|
360
422
|
}
|
|
@@ -155,6 +155,7 @@ class RelayLink {
|
|
|
155
155
|
runtime: "claude",
|
|
156
156
|
...(this.opts.pane ? { pane: this.opts.pane } : {}),
|
|
157
157
|
...(this.opts.ownerPid !== undefined ? { ownerPid: this.opts.ownerPid } : {}),
|
|
158
|
+
...(this.opts.cwd ? { cwd: this.opts.cwd } : {}),
|
|
158
159
|
};
|
|
159
160
|
(0, socket_1.writeFrame)(socket, frame);
|
|
160
161
|
});
|
package/dist/channel/server.js
CHANGED
|
@@ -107,7 +107,7 @@ const LISTEN_TOOL = {
|
|
|
107
107
|
* parent are the session's (owner-pid.ts walks the live process tree to prove the latter).
|
|
108
108
|
*/
|
|
109
109
|
function realCommandLink(log) {
|
|
110
|
-
return (session, bay, hooks) => new relay_link_1.RelayLink({ socketPath: relaySocketFor(bay), session, pane: (0, terminal_pane_1.detectPane)(), ownerPid: (0, owner_pid_1.currentOwnerPid)("claude"), ...hooks, log });
|
|
110
|
+
return (session, bay, hooks) => new relay_link_1.RelayLink({ socketPath: relaySocketFor(bay), session, pane: (0, terminal_pane_1.detectPane)(), ownerPid: (0, owner_pid_1.currentOwnerPid)("claude"), cwd: process.cwd(), ...hooks, log });
|
|
111
111
|
}
|
|
112
112
|
/** The relay that serves a Bay's sessions: the default relay, or that Bay's own (relay/bay-relays.ts). */
|
|
113
113
|
function relaySocketFor(bay) {
|
package/dist/launch/command.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.trustByHand = exports.APPROVALS_NOTICE = exports.LAUNCH_USAGE = void 0;
|
|
36
|
+
exports.trustByHand = exports.CODEX_UNATTENDED_NOTICE = exports.APPROVALS_NOTICE = exports.LAUNCH_USAGE = void 0;
|
|
37
37
|
exports.cmdLaunch = cmdLaunch;
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const args_1 = require("../args");
|
|
@@ -41,12 +41,16 @@ const multiplexer_launch_1 = require("../multiplexer-launch");
|
|
|
41
41
|
const policy_1 = require("../relay/acp/policy");
|
|
42
42
|
const claude_trust_1 = require("./claude-trust");
|
|
43
43
|
const policy_2 = require("./policy");
|
|
44
|
-
exports.LAUNCH_USAGE = `Usage: baychat launch add <folder> [--label <label>] [--runtime claude,codex] [--max <n>]
|
|
44
|
+
exports.LAUNCH_USAGE = `Usage: baychat launch add <folder> [--label <label>] [--runtime claude,codex] [--max <n>] [--unattended]
|
|
45
45
|
Let BayChat start sessions in <folder> from the app
|
|
46
|
+
(--unattended: also let it start Codex there, which then never asks)
|
|
46
47
|
baychat launch list The folders BayChat may start sessions in
|
|
47
48
|
baychat launch remove <id> Stop offering one of them`;
|
|
48
49
|
/** Printed by every `add`: the one thing a person must know before saying yes. */
|
|
49
|
-
exports.APPROVALS_NOTICE = "
|
|
50
|
+
exports.APPROVALS_NOTICE = "Claude Code sessions started here from BayChat ask for approvals in BayChat.";
|
|
51
|
+
/** Printed when Codex is offered: it has no BayChat approvals, so what it does is decided up front. */
|
|
52
|
+
exports.CODEX_UNATTENDED_NOTICE = "Codex started here from BayChat does NOT ask for approvals: it runs --ask-for-approval never inside its workspace sandbox, with no network. " +
|
|
53
|
+
"It can change files in this folder, and anyone who may wake it in the chat can ask it to. MCP servers in its own config run as usual.";
|
|
50
54
|
const realDeps = () => {
|
|
51
55
|
const file = (0, claude_trust_1.claudeConfigPath)();
|
|
52
56
|
return {
|
|
@@ -81,8 +85,13 @@ async function addSpot(args, deps) {
|
|
|
81
85
|
const label = (0, args_1.flag)(args, "--label");
|
|
82
86
|
const runtimeList = (0, args_1.flag)(args, "--runtime");
|
|
83
87
|
const maxRaw = (0, args_1.flag)(args, "--max");
|
|
88
|
+
const unattended = args.includes("--unattended");
|
|
84
89
|
const valued = new Set([label, runtimeList, maxRaw].filter((v) => v !== undefined));
|
|
85
90
|
const folderArg = args.find((a) => !a.startsWith("--") && !valued.has(a));
|
|
91
|
+
if (unattended && !(runtimeList ?? "").split(",").map((r) => r.trim()).includes("codex")) {
|
|
92
|
+
deps.print("--unattended is for Codex: add it with --runtime including codex.");
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
86
95
|
if (!folderArg)
|
|
87
96
|
throw new Error(exports.LAUNCH_USAGE);
|
|
88
97
|
const folder = (0, policy_1.validateNewRoot)(folderArg);
|
|
@@ -114,11 +123,17 @@ async function addSpot(args, deps) {
|
|
|
114
123
|
path: folder.path,
|
|
115
124
|
runtimes: [...new Set(requested)],
|
|
116
125
|
maxSessions,
|
|
126
|
+
...(unattended ? { unattendedCodex: true } : {}),
|
|
117
127
|
};
|
|
118
128
|
(0, policy_2.saveLaunchPolicy)((0, policy_2.upsertSpot)(policy, spot), deps.policyFile);
|
|
119
129
|
deps.print(`${existing ? "Updated" : "Added"} "${spot.label}" (${spot.id}): ${spot.path}`);
|
|
120
130
|
deps.print(` Agents: ${spot.runtimes.join(", ")} · at most ${spot.maxSessions} at once`);
|
|
121
131
|
deps.print(` ${exports.APPROVALS_NOTICE}`);
|
|
132
|
+
if (spot.runtimes.includes("codex")) {
|
|
133
|
+
deps.print(spot.unattendedCodex
|
|
134
|
+
? ` ${exports.CODEX_UNATTENDED_NOTICE}`
|
|
135
|
+
: " Codex is listed but not allowed to start unattended here; add --unattended (read what it means first) to start it from BayChat.");
|
|
136
|
+
}
|
|
122
137
|
if (spot.runtimes.includes("claude"))
|
|
123
138
|
await offerClaudeTrust(spot.path, deps);
|
|
124
139
|
return 0;
|
package/dist/launch/policy.js
CHANGED
|
@@ -136,7 +136,7 @@ function resolveSpotFolder(spot) {
|
|
|
136
136
|
function parseSpot(entry) {
|
|
137
137
|
if (!entry || typeof entry !== "object")
|
|
138
138
|
return null;
|
|
139
|
-
const { id, label, path: p, runtimes, maxSessions } = entry;
|
|
139
|
+
const { id, label, path: p, runtimes, maxSessions, unattendedCodex } = entry;
|
|
140
140
|
if (typeof id !== "string" || !exports.SPOT_ID_RE.test(id))
|
|
141
141
|
return null;
|
|
142
142
|
if (typeof label !== "string" || label.trim().length === 0 || label.length > exports.SPOT_LABEL_MAX)
|
|
@@ -148,5 +148,7 @@ function parseSpot(entry) {
|
|
|
148
148
|
return null;
|
|
149
149
|
if (typeof maxSessions !== "number" || !Number.isInteger(maxSessions) || maxSessions < 1 || maxSessions > exports.MAX_SESSIONS_PER_COMPUTER)
|
|
150
150
|
return null;
|
|
151
|
-
|
|
151
|
+
if (unattendedCodex !== undefined && unattendedCodex !== true)
|
|
152
|
+
return null;
|
|
153
|
+
return { id, label, path: p, runtimes: [...new Set(runtimes)], maxSessions, ...(unattendedCodex ? { unattendedCodex } : {}) };
|
|
152
154
|
}
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TMUX_SESSION_VAR = void 0;
|
|
36
37
|
exports.launchAgent = launchAgent;
|
|
37
38
|
exports.runHere = runHere;
|
|
38
39
|
exports.multiplexerArgv = multiplexerArgv;
|
|
@@ -75,6 +76,8 @@ const readline = __importStar(require("readline"));
|
|
|
75
76
|
const config_1 = require("./config");
|
|
76
77
|
const terminal_pane_1 = require("./relay/terminal-pane");
|
|
77
78
|
const runtime_binary_1 = require("./runtime-binary");
|
|
79
|
+
/** Set in the agent's environment to the tmux session our CLI created for it (read by detectPane). */
|
|
80
|
+
exports.TMUX_SESSION_VAR = "BAYCHAT_TMUX_SESSION";
|
|
78
81
|
/**
|
|
79
82
|
* Start `program` where session commands can reach it — inside tmux or screen when possible —
|
|
80
83
|
* and return its exit status. Never refuses to start the agent: every failure on the way falls
|
|
@@ -119,20 +122,29 @@ function runHere(program, deps = realLaunchDeps()) {
|
|
|
119
122
|
function multiplexerArgv(multiplexer, naming, program,
|
|
120
123
|
/** Needed only for a `fresh` name (its pid suffix and the tmux uniqueness check). */
|
|
121
124
|
deps) {
|
|
122
|
-
const
|
|
123
|
-
|
|
125
|
+
const commandWith = (extra) => {
|
|
126
|
+
const vars = Object.entries({ ...program.env, ...extra }).map(([k, v]) => `${k}=${v}`);
|
|
127
|
+
return vars.length ? ["env", ...vars, program.binary, ...program.args] : [program.binary, ...program.args];
|
|
128
|
+
};
|
|
129
|
+
// The tmux session's exact name rides into the agent as BAYCHAT_TMUX_SESSION, so the relay can
|
|
130
|
+
// tell later that a pane is in the session OUR CLI made, not one of the person's own that happens
|
|
131
|
+
// to share the name (an End from the app closes only the former: stop-registered.ts).
|
|
132
|
+
const tmuxCommand = (session) => commandWith({ [exports.TMUX_SESSION_VAR]: session });
|
|
124
133
|
if (naming.kind === "detached") {
|
|
125
|
-
return multiplexer === "tmux" ? ["new", "-d", "-s", naming.name, "-c", naming.cwd, ...
|
|
134
|
+
return multiplexer === "tmux" ? ["new", "-d", "-s", naming.name, "-c", naming.cwd, ...tmuxCommand(naming.name)] : ["-dmS", naming.name, ...commandWith({})];
|
|
126
135
|
}
|
|
127
136
|
if (!deps)
|
|
128
137
|
throw new Error("multiplexerArgv: a fresh or reattached session needs the launch deps");
|
|
129
138
|
if (multiplexer === "tmux") {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
if (naming.kind === "reattach") {
|
|
140
|
+
// `-A` attaches to an existing session of that name instead of failing, so running this
|
|
141
|
+
// twice returns you to your agent rather than erroring at you.
|
|
142
|
+
return ["new", "-A", "-s", naming.name, ...tmuxCommand(naming.name)];
|
|
143
|
+
}
|
|
144
|
+
const name = uniqueTmuxName(naming.prefix, deps);
|
|
145
|
+
return ["new", "-s", name, ...tmuxCommand(name)];
|
|
135
146
|
}
|
|
147
|
+
const command = commandWith({});
|
|
136
148
|
// screen tells same-named sessions apart by pid, so a fresh one needs no uniqueness check.
|
|
137
149
|
return naming.kind === "reattach" ? ["-S", naming.name, "-R", ...command] : ["-S", `${naming.prefix}-${deps.pid}`, ...command];
|
|
138
150
|
}
|