baychat 0.34.0 → 0.34.2
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/README.md +13 -2
- package/dist/bays-command.js +6 -0
- package/dist/channel/feed.js +48 -4
- package/dist/channel/log-file.js +37 -0
- package/dist/channel/server.js +44 -3
- package/dist/claude-context-flush.js +31 -8
- package/dist/claude-context.js +19 -5
- package/dist/claude-onboarding.js +23 -10
- package/dist/cli-help.js +3 -0
- package/dist/client-paths.js +6 -1
- package/dist/client-scope.js +34 -0
- package/dist/codex-onboarding.js +2 -2
- package/dist/commands.js +16 -8
- package/dist/launch/command.js +6 -3
- package/dist/main.js +14 -1
- package/dist/mcp-files.js +2 -0
- package/dist/mcp-register.js +2 -0
- package/dist/protocol-content.js +1 -1
- package/dist/refresh-clients.js +7 -0
- package/dist/relay/acp/approval.js +99 -30
- package/dist/relay/acp/daemon-glue.js +2 -1
- package/dist/relay/adapters.js +5 -1
- package/dist/relay/bay-relays.js +18 -5
- package/dist/relay/codex-allowances.js +113 -0
- package/dist/relay/codex-app-server.js +637 -157
- package/dist/relay/codex-approvals.js +390 -0
- package/dist/relay/codex-commands.js +61 -0
- package/dist/relay/codex-log.js +260 -0
- package/dist/relay/codex-runner.js +505 -0
- package/dist/relay/codex-version.js +50 -0
- package/dist/relay/daemon.js +190 -6
- package/dist/relay/fresh-start.js +2 -1
- package/dist/relay/launcher/capabilities.js +24 -9
- package/dist/relay/launcher/codex-launch.js +9 -2
- package/dist/relay/launcher/codex-relay-launch.js +80 -0
- package/dist/relay/launcher/handle.js +101 -30
- package/dist/relay/launcher/loop.js +5 -1
- package/dist/relay/launcher/stop-registered.js +124 -9
- package/dist/relay/registry.js +30 -0
- package/dist/relay/runtime-limits.js +167 -0
- package/dist/relay/spawn-env.js +40 -3
- package/dist/relay/terminal-pane.js +1 -0
- package/dist/runtimes.js +4 -3
- package/dist/session-command.js +7 -4
- package/dist/skill-bootstrap.js +2 -0
- package/dist/tool-defs.js +30 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.2",
|
|
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": {
|
package/README.md
CHANGED
|
@@ -381,9 +381,18 @@ listener, and in whether anything reaches them once the window is closed.
|
|
|
381
381
|
| Runtime | Who re-arms the listener | Window closed |
|
|
382
382
|
|---|---|---|
|
|
383
383
|
| **Claude Code (native)** | socket stays open; a closed socket needs a replacement Monitor | not reachable until resumed |
|
|
384
|
-
| **Codex** | nobody needs to — its own queue |
|
|
384
|
+
| **Codex** | nobody needs to — its own queue | resumed headlessly; its approvals come to your phone (Codex ≥ 0.160) |
|
|
385
385
|
| **Cursor** | **the agent itself**, after every wake | not reachable |
|
|
386
386
|
|
|
387
|
+
**Codex started from the app is run by the relay** (since 2026-10-23, Codex ≥ 0.160): one
|
|
388
|
+
`codex app-server` turn per message, in its folder's sandbox with no network, and every approval
|
|
389
|
+
Codex asks for — a command, a file change with its diff, network access — comes to your phone
|
|
390
|
+
as a card: "Allow once", "Allow for this session" (confirmed with Face ID), "Deny". Nobody
|
|
391
|
+
answering within 10 minutes is a Deny. Turns woken by someone without command access are read
|
|
392
|
+
only and never ask you. `tmux attach -t bc-<name>` (or `baychat relay log <name> -f`) shows
|
|
393
|
+
what it does. With `BAYCHAT_CODEX_TRANSPORT=exec` on the relay, Codex can't answer in BayChat, so
|
|
394
|
+
launches from the phone are refused and messages to these sessions wait (the room is told why).
|
|
395
|
+
|
|
387
396
|
Cursor's relay does work — it runs shell commands, so it holds the listener
|
|
388
397
|
like anything else. It is simply the least robust of the three, and the only
|
|
389
398
|
one where remembering to re-arm falls to the agent.
|
|
@@ -431,6 +440,7 @@ one where remembering to re-arm falls to the agent.
|
|
|
431
440
|
| `baychat relay start [--foreground]` | Run the **relay**: one long-poll for this whole machine that wakes local sessions the moment a message arrives. Installs a systemd user unit so it returns after a reboot (see [Relay](#relay)) |
|
|
432
441
|
| `baychat relay status` | Sessions, cursor, and any **delivery pending** — messages that reached this box and that nothing answered (exit 2 if any) |
|
|
433
442
|
| `baychat relay stop` | Stop the relay and disable it at boot |
|
|
443
|
+
| `baychat relay log <session> [-f]` | What a Codex session the relay runs did — commands, file changes, answers, approval cards. `-f` keeps following it (works on Windows too) |
|
|
434
444
|
| `baychat claude [args…]` | Start Claude Code in this terminal with the BayChat channel on (see [No Monitor deadline with the channel](#coding-sessions-one-short-command)). Every argument is passed to Claude Code, `--help` included |
|
|
435
445
|
| `baychat doctor [--json]` | **Run this when something does not work.** Checks every link between this machine and BayChat — credential, relay, and per runtime its MCP registration, skill, executable and live session (and for Claude Code the channel: a plugin that carries it, on Node 22+) — and prints exactly what to type for each thing that is wrong. Exit 0 clear, 1 broken, 2 messages nothing answered. `--json` for pasting into a support thread (see [When something does not work](#when-something-does-not-work)) |
|
|
436
446
|
| `baychat relay attach --session <name> [--runtime claude\|codex\|cursor\|hermes] [--resume-id <id>] [--conversation <id>] [--timeout <sec>]` | Register this session with the relay and block until it is woken (exit 0) or the wait lapses (exit 2) |
|
|
@@ -1071,7 +1081,8 @@ Two refusals worth knowing in advance:
|
|
|
1071
1081
|
| `BAYCHAT_TOKEN` | Use this agent API token instead of the credentials file (headless/CI) |
|
|
1072
1082
|
| `BAYCHAT_DEVICE_TOKEN` | Use this `bay_u_*` device token (from `baychat login`) instead of the credentials file (headless/CI) |
|
|
1073
1083
|
| `BAYCHAT_API_URL` | API origin (default `https://api.baychat.io`) |
|
|
1074
|
-
| `BAYCHAT_CONFIG_DIR` | Credentials/cursor directory (default `~/.baychat`) |
|
|
1084
|
+
| `BAYCHAT_CONFIG_DIR` | Credentials/cursor directory (default `~/.baychat`). A login made with it set — or against a server other than `https://api.baychat.io` — leaves the computer's agent clients (Claude Code, Claude Desktop, Codex, Cursor) as they are; connect one on purpose with `baychat connect <client>` |
|
|
1085
|
+
| `BAYCHAT_REFRESH_CLIENTS=1` | Refresh every connected agent client after such a login anyway |
|
|
1075
1086
|
|
|
1076
1087
|
## Security
|
|
1077
1088
|
|
package/dist/bays-command.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.cmdBays = cmdBays;
|
|
|
7
7
|
const config_1 = require("./config");
|
|
8
8
|
const bays_1 = require("./bays");
|
|
9
9
|
const bay_mcp_1 = require("./bay-mcp");
|
|
10
|
+
const client_scope_1 = require("./client-scope");
|
|
10
11
|
const bay_deleted_1 = require("./bay-deleted");
|
|
11
12
|
const commands_1 = require("./commands");
|
|
12
13
|
const client_paths_1 = require("./client-paths");
|
|
@@ -50,6 +51,11 @@ function cmdBays(args) {
|
|
|
50
51
|
console.log(`✓ ${target.bayName} is now this computer's default Bay.`);
|
|
51
52
|
// The default is the `baychat` entry: every connected client gets the new token there, and
|
|
52
53
|
// the Bay that WAS the default moves to its own `baychat-<bay>` entry.
|
|
54
|
+
const isolated = (0, client_scope_1.isolatedLoginReason)(next.device.baseUrl);
|
|
55
|
+
if (isolated) {
|
|
56
|
+
console.log((0, client_scope_1.isolatedLoginNotice)(isolated));
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
53
59
|
(0, commands_1.refreshConnectedClients)(next.device.baseUrl, next.device.token);
|
|
54
60
|
(0, commands_1.registerWithClaude)(next.device.baseUrl, next.device.token);
|
|
55
61
|
(0, bay_mcp_1.applyBayEntries)(next.device.baseUrl, bays, next.bays, {
|
package/dist/channel/feed.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ChannelFeed = exports.BACKOFF_MS = exports.CLOSE_STOPPED = void 0;
|
|
3
|
+
exports.ChannelFeed = exports.MAX_FAILED_RECONNECTS = exports.BACKOFF_MS = exports.CLOSE_STOPPED = void 0;
|
|
4
4
|
/** Close code the server sends when a feed was stopped or replaced on purpose. */
|
|
5
5
|
exports.CLOSE_STOPPED = 4000;
|
|
6
6
|
/** Seconds between reconnect attempts: quick at first, then no more than once a minute. */
|
|
7
7
|
exports.BACKOFF_MS = [1_000, 2_000, 5_000, 10_000, 30_000, 60_000];
|
|
8
8
|
const READY_TIMEOUT_MS = 10_000;
|
|
9
|
+
/**
|
|
10
|
+
* Consecutive reconnects that fail before the feed gives up and SAYS so (about six minutes of
|
|
11
|
+
* backoff — longer than a deploy). Before 2026-10-23 it retried forever without a word, and a
|
|
12
|
+
* feed that never came back looked exactly like a quiet Bay.
|
|
13
|
+
*/
|
|
14
|
+
exports.MAX_FAILED_RECONNECTS = 10;
|
|
9
15
|
const WATCHDOG_MS = 2 * 60_000;
|
|
10
16
|
/** A timer that fires this much later than asked means the computer slept: reconnect at once. */
|
|
11
17
|
const SLEEP_GAP_MS = 60_000;
|
|
@@ -131,6 +137,25 @@ class ChannelFeed {
|
|
|
131
137
|
const current = () => generation === this.generation && this.state !== "stopped";
|
|
132
138
|
let ready = false;
|
|
133
139
|
this.state = "connecting";
|
|
140
|
+
// A RECONNECT waits for `ready` too (the first open has `start`'s own timer): a socket that
|
|
141
|
+
// opens and never says ready used to leave the feed "connecting" for good, hearing nothing.
|
|
142
|
+
const readyTimer = first
|
|
143
|
+
? undefined
|
|
144
|
+
: this.setTimer(() => {
|
|
145
|
+
if (!current() || ready)
|
|
146
|
+
return;
|
|
147
|
+
this.log(`feed for ${this.deps.session} did not say ready within ${(this.deps.readyTimeoutMs ?? READY_TIMEOUT_MS) / 1000}s`);
|
|
148
|
+
const socket = this.socket;
|
|
149
|
+
this.generation++;
|
|
150
|
+
this.socket = undefined;
|
|
151
|
+
try {
|
|
152
|
+
socket?.close(1000, "No ready");
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* Already gone. */
|
|
156
|
+
}
|
|
157
|
+
this.scheduleReconnect("did not say ready");
|
|
158
|
+
}, this.deps.readyTimeoutMs ?? READY_TIMEOUT_MS);
|
|
134
159
|
try {
|
|
135
160
|
this.socket = this.deps.connect(ticket.url, ticket.protocols, {
|
|
136
161
|
onOpen: () => { },
|
|
@@ -144,8 +169,11 @@ class ChannelFeed {
|
|
|
144
169
|
this.cursor = frame.cursor;
|
|
145
170
|
if (frame.type === "ready") {
|
|
146
171
|
ready = true;
|
|
172
|
+
if (readyTimer !== undefined)
|
|
173
|
+
this.clearTimer(readyTimer);
|
|
147
174
|
this.state = "open";
|
|
148
175
|
this.attempt = 0;
|
|
176
|
+
this.log(`feed for ${this.deps.session} ready`);
|
|
149
177
|
first?.onReady();
|
|
150
178
|
return;
|
|
151
179
|
}
|
|
@@ -158,7 +186,10 @@ class ChannelFeed {
|
|
|
158
186
|
onClose: (code, reason) => {
|
|
159
187
|
if (!current())
|
|
160
188
|
return;
|
|
189
|
+
if (readyTimer !== undefined)
|
|
190
|
+
this.clearTimer(readyTimer);
|
|
161
191
|
this.socket = undefined;
|
|
192
|
+
this.log(`feed for ${this.deps.session} closed ${code}${reason ? ` (${reason})` : ""}`);
|
|
162
193
|
if (!ready && first) {
|
|
163
194
|
first.onFailBeforeReady(`The BayChat feed closed before it was ready (${code}${reason ? `: ${reason}` : ""}).`);
|
|
164
195
|
return;
|
|
@@ -168,6 +199,7 @@ class ChannelFeed {
|
|
|
168
199
|
// listener for this session. Reconnecting would fight whichever replaced it.
|
|
169
200
|
this.log(`feed for ${this.deps.session} stopped by the server (${reason || code})`);
|
|
170
201
|
this.stop();
|
|
202
|
+
void this.pushStopped(`the server stopped this feed (${reason || code}) — another listener for this session replaced it, or it was ended`);
|
|
171
203
|
return;
|
|
172
204
|
}
|
|
173
205
|
this.scheduleReconnect(`closed ${code}${reason ? `: ${reason}` : ""}`);
|
|
@@ -185,6 +217,12 @@ class ChannelFeed {
|
|
|
185
217
|
scheduleReconnect(why) {
|
|
186
218
|
if (this.state === "stopped")
|
|
187
219
|
return;
|
|
220
|
+
if (this.attempt >= exports.MAX_FAILED_RECONNECTS) {
|
|
221
|
+
this.log(`feed for ${this.deps.session} gave up after ${this.attempt} failed reconnects (${why})`);
|
|
222
|
+
this.stop();
|
|
223
|
+
void this.pushStopped(`${this.attempt} reconnects in a row failed (last: ${why})`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
188
226
|
this.state = "retrying";
|
|
189
227
|
const delay = exports.BACKOFF_MS[Math.min(this.attempt, exports.BACKOFF_MS.length - 1)];
|
|
190
228
|
this.attempt++;
|
|
@@ -204,14 +242,14 @@ class ChannelFeed {
|
|
|
204
242
|
if (this.stopped())
|
|
205
243
|
return;
|
|
206
244
|
if (minted.ok) {
|
|
245
|
+
this.log(`feed for ${this.deps.session} minted a ticket; opening`);
|
|
207
246
|
this.open(minted.ticket);
|
|
208
247
|
return;
|
|
209
248
|
}
|
|
249
|
+
this.log(`feed for ${this.deps.session} could not mint a ticket${minted.fatal ? " (final)" : ""}: ${minted.detail}`);
|
|
210
250
|
if (minted.fatal) {
|
|
211
251
|
this.stop();
|
|
212
|
-
await this.
|
|
213
|
-
`If the session expired, call join_session again with the same name and room, then call ` +
|
|
214
|
-
`this BayChat channel's listen tool again. Until then this session does not hear the Bay.`, this.meta({ event: "stopped" }));
|
|
252
|
+
await this.pushStopped(minted.detail);
|
|
215
253
|
return;
|
|
216
254
|
}
|
|
217
255
|
this.scheduleReconnect(`could not reconnect (${minted.detail})`);
|
|
@@ -309,6 +347,12 @@ class ChannelFeed {
|
|
|
309
347
|
message_ids: c.messageId,
|
|
310
348
|
}));
|
|
311
349
|
}
|
|
350
|
+
/** The feed is over and will not come back by itself: say so, and what to do. */
|
|
351
|
+
async pushStopped(detail) {
|
|
352
|
+
await this.deps.push(`BayChat stopped listening for session "${this.deps.session}": ${detail}. ` +
|
|
353
|
+
`If the session expired, call join_session again with the same name and room, then call ` +
|
|
354
|
+
`this BayChat channel's listen tool again. Until then this session does not hear the Bay.`, this.meta({ event: "stopped" }));
|
|
355
|
+
}
|
|
312
356
|
async pushReset() {
|
|
313
357
|
await this.deps.push(`BayChat reset the feed for session "${this.deps.session}". Catch up on your joined conversations with get_messages; ` +
|
|
314
358
|
`a reset never means there were no messages. The channel stays open.`, this.meta({ event: "reset" }));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.channelLogFile = channelLogFile;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
/**
|
|
11
|
+
* The channel's own log, `~/.baychat/channel.log` (2026-10-23). Claude Code swallows a channel
|
|
12
|
+
* server's stderr, so when a feed went quiet there was nothing to read afterwards. Every state
|
|
13
|
+
* change, close code and reason, and ticket mint is written here with the time and process id.
|
|
14
|
+
*
|
|
15
|
+
* Kept small: past `maxBytes` the file moves to `channel.log.1` (one old file, replaced each
|
|
16
|
+
* time). Owner-only (0600): it names sessions and Bays. Never throws — a log that cannot be
|
|
17
|
+
* written must not take the channel down with it.
|
|
18
|
+
*/
|
|
19
|
+
function channelLogFile(dir = (0, config_1.configDir)(), maxBytes = 1_000_000) {
|
|
20
|
+
const file = node_path_1.default.join(dir, "channel.log");
|
|
21
|
+
return (line) => {
|
|
22
|
+
try {
|
|
23
|
+
node_fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
24
|
+
try {
|
|
25
|
+
if (node_fs_1.default.statSync(file).size > maxBytes)
|
|
26
|
+
node_fs_1.default.renameSync(file, `${file}.1`);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* No file yet. */
|
|
30
|
+
}
|
|
31
|
+
node_fs_1.default.appendFileSync(file, `${new Date().toISOString()} [${process.pid}] ${line}\n`, { mode: 0o600 });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
/* Best effort: stderr still has the line. */
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/channel/server.js
CHANGED
|
@@ -71,6 +71,7 @@ const ws_1 = require("../relay/ws");
|
|
|
71
71
|
const claude_link_1 = require("./claude-link");
|
|
72
72
|
const detect_1 = require("./detect");
|
|
73
73
|
const feed_1 = require("./feed");
|
|
74
|
+
const log_file_1 = require("./log-file");
|
|
74
75
|
const permission_1 = require("./permission");
|
|
75
76
|
const relay_link_1 = require("./relay-link");
|
|
76
77
|
/** Delivered to Claude as context when this server connects. */
|
|
@@ -213,6 +214,38 @@ async function mintTicket(deps, { bay, session, cursor, commands }) {
|
|
|
213
214
|
}
|
|
214
215
|
catch (err) {
|
|
215
216
|
const status = httpStatus(err);
|
|
217
|
+
// The Bay requires two-step login and this person has not set it up (API
|
|
218
|
+
// mfa-policy.service.ts, 403 MFA_REQUIRED). Logging in again would not help; setting it up
|
|
219
|
+
// does, and the same computer login works again the moment it is done.
|
|
220
|
+
const said = err instanceof Error ? err.message : String(err);
|
|
221
|
+
// Bay rules about terminal agents (API docs/features/TERMINAL_RULES.md, AGENTS_PAGE.md):
|
|
222
|
+
// each says what to do, none of them is fixed by logging in again.
|
|
223
|
+
if (status === 403 && /TERMINAL_AGENTS_PAUSED/.test(said)) {
|
|
224
|
+
return { ok: false, fatal: true, detail: "Stopped: the admins of this Bay paused terminal agents. Ask them to switch it back on, then start this session again." };
|
|
225
|
+
}
|
|
226
|
+
if (status === 403 && /DEVICE_PENDING_APPROVAL/.test(said)) {
|
|
227
|
+
return { ok: false, fatal: true, detail: "Stopped: this computer is waiting for a Bay admin to approve it (Settings → Agents in this Bay). Start this session again once it is approved." };
|
|
228
|
+
}
|
|
229
|
+
if (status === 403 && /AGENT_DISABLED/.test(said)) {
|
|
230
|
+
return { ok: false, fatal: true, detail: "Stopped: this session was switched off in BayChat. You or a Bay admin can switch it on in Settings → Agents in this Bay." };
|
|
231
|
+
}
|
|
232
|
+
if (status === 403 && /MFA_REQUIRED/.test(said)) {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
fatal: true,
|
|
236
|
+
detail: "Stopped: this Bay requires two-step login. Set it up in the BayChat app (Settings → Two-step login), " +
|
|
237
|
+
"then start this session again.",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// "Company sign-in only" (API sso-gate.service.ts): this computer's login was not approved
|
|
241
|
+
// from the Bay's company sign-in. Logging in again, approved from one, fixes it.
|
|
242
|
+
if (status === 403 && /SSO_REQUIRED/.test(said)) {
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
fatal: true,
|
|
246
|
+
detail: "Stopped: this Bay requires company sign-in. Run `baychat login` again and approve it while signed in with your company account.",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
216
249
|
if (status === 401 || status === 403) {
|
|
217
250
|
return { ok: false, fatal: true, detail: "BayChat refused this computer's login. Run `baychat login`." };
|
|
218
251
|
}
|
|
@@ -259,7 +292,9 @@ class ChannelService {
|
|
|
259
292
|
bayName = this.deps.login(bay).bayName;
|
|
260
293
|
}
|
|
261
294
|
catch (err) {
|
|
262
|
-
|
|
295
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
296
|
+
this.deps.log(`listen ${session}: sign-in failed: ${detail}`);
|
|
297
|
+
return toolResult("failed", `Failed: ${detail} Use listen_messages and a native Monitor instead.`, { session, stage: "login", detail });
|
|
263
298
|
}
|
|
264
299
|
const key = `${bayName ?? ""}\u0000${session}`;
|
|
265
300
|
this.feeds.get(key)?.stop();
|
|
@@ -304,7 +339,8 @@ class ChannelService {
|
|
|
304
339
|
this.closeCommandLink(key, link);
|
|
305
340
|
if (this.last?.key === key)
|
|
306
341
|
this.last = undefined;
|
|
307
|
-
|
|
342
|
+
this.deps.log(`listen ${session}: the feed did not start: ${started.detail}`);
|
|
343
|
+
return toolResult("failed", `Failed: ${started.detail} Use listen_messages and a native Monitor instead.`, { session, stage: "feed", detail: started.detail });
|
|
308
344
|
}
|
|
309
345
|
this.last = { key, session, bay };
|
|
310
346
|
// Approvals for `baychat claude` / `baychat start` (approve-hook --session-from-claude): this
|
|
@@ -398,7 +434,12 @@ const PermissionRequestSchema = zod_1.z.object({
|
|
|
398
434
|
});
|
|
399
435
|
/** Boot on stdio and block on the transport. */
|
|
400
436
|
async function startChannelServer(deps) {
|
|
401
|
-
const
|
|
437
|
+
const toFile = (0, log_file_1.channelLogFile)();
|
|
438
|
+
const log = deps?.log ??
|
|
439
|
+
((line) => {
|
|
440
|
+
console.error(`[baychat channel] ${line}`);
|
|
441
|
+
toFile(line);
|
|
442
|
+
});
|
|
402
443
|
const mcp = new index_js_1.Server({ name: detect_1.CHANNEL_MCP_ENTRY, version: "1" }, {
|
|
403
444
|
// `claude/channel/permission`: Claude Code also offers this channel its tool-approval
|
|
404
445
|
// dialogs (permission.ts). Safe to declare because only the session's owner can answer the
|
|
@@ -45,6 +45,8 @@ const claude_context_state_1 = require("./claude-context-state");
|
|
|
45
45
|
const config_1 = require("./config");
|
|
46
46
|
const context_report_1 = require("./context-report");
|
|
47
47
|
const registry_1 = require("./relay/registry");
|
|
48
|
+
const runtime_limits_1 = require("./relay/runtime-limits");
|
|
49
|
+
const api_1 = require("./api");
|
|
48
50
|
/**
|
|
49
51
|
* The most recent BayChat tool call in the transcript tail that names a session.
|
|
50
52
|
*
|
|
@@ -121,15 +123,26 @@ function acquireLock(file, now) {
|
|
|
121
123
|
return false;
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Remove what was handled, re-reading first so a sample noted meanwhile is not lost. `limitsDone`:
|
|
128
|
+
* the limits report with this key was attempted — dropped unless a newer one was noted since.
|
|
129
|
+
*/
|
|
130
|
+
function settle(sid, handled, patch, limitsDone) {
|
|
126
131
|
const state = (0, claude_context_state_1.readState)(sid);
|
|
127
132
|
state.queue = state.queue.filter((q) => !handled.has(q.id));
|
|
128
|
-
(0,
|
|
133
|
+
const limits = state.limits && limitsDone !== undefined && (0, runtime_limits_1.limitsKey)(state.limits) === limitsDone ? undefined : state.limits;
|
|
134
|
+
(0, claude_context_state_1.writeState)({ ...state, ...patch, queue: state.queue, limits });
|
|
129
135
|
}
|
|
130
136
|
/** `baychat context-flush --claude <id>`: send what the status line and hooks noted. */
|
|
131
137
|
async function runContextFlush(argv, deps = {}) {
|
|
132
|
-
const d = {
|
|
138
|
+
const d = {
|
|
139
|
+
now: Date.now,
|
|
140
|
+
post: context_report_1.postContextEvent,
|
|
141
|
+
postLimits: runtime_limits_1.postSessionLimits,
|
|
142
|
+
loadDevice: config_1.loadDeviceCredentials,
|
|
143
|
+
identify: identifySession,
|
|
144
|
+
...deps,
|
|
145
|
+
};
|
|
133
146
|
const i = argv.indexOf("--claude");
|
|
134
147
|
const sid = (0, claude_context_state_1.safeId)(i >= 0 ? argv[i + 1] : undefined);
|
|
135
148
|
if (!sid)
|
|
@@ -140,7 +153,7 @@ async function runContextFlush(argv, deps = {}) {
|
|
|
140
153
|
try {
|
|
141
154
|
for (let round = 0; round < 5; round++) {
|
|
142
155
|
const state = (0, claude_context_state_1.readState)(sid);
|
|
143
|
-
if (state.queue.length === 0)
|
|
156
|
+
if (state.queue.length === 0 && !state.limits)
|
|
144
157
|
return 0;
|
|
145
158
|
if ((0, claude_context_state_1.backoffUntil)() > d.now())
|
|
146
159
|
return 0;
|
|
@@ -157,14 +170,14 @@ async function runContextFlush(argv, deps = {}) {
|
|
|
157
170
|
// grow. Said once per Claude session, in the log.
|
|
158
171
|
if (!state.unidentifiedAt)
|
|
159
172
|
(0, claude_context_state_1.logContextError)(`Claude session ${sid} is not joined to BayChat as far as this machine can prove — not reporting its context`);
|
|
160
|
-
settle(sid, all, { unidentifiedAt: d.now(), baychatSession: undefined, identifiedAt: undefined });
|
|
173
|
+
settle(sid, all, { unidentifiedAt: d.now(), baychatSession: undefined, identifiedAt: undefined }, state.limits && (0, runtime_limits_1.limitsKey)(state.limits));
|
|
161
174
|
return 0;
|
|
162
175
|
}
|
|
163
176
|
const identityPatch = { baychatSession: identity.session, conversationId: identity.conversationId, identifiedAt: d.now(), unidentifiedAt: undefined };
|
|
164
177
|
const device = d.loadDevice();
|
|
165
178
|
if (!device) {
|
|
166
179
|
(0, claude_context_state_1.logContextError)("not logged in to BayChat (`baychat login`) — context not reported");
|
|
167
|
-
settle(sid, all, identityPatch);
|
|
180
|
+
settle(sid, all, identityPatch, state.limits && (0, runtime_limits_1.limitsKey)(state.limits));
|
|
168
181
|
return 0;
|
|
169
182
|
}
|
|
170
183
|
const handled = new Set();
|
|
@@ -200,7 +213,17 @@ async function runContextFlush(argv, deps = {}) {
|
|
|
200
213
|
settle(sid, handled, identityPatch);
|
|
201
214
|
return 0;
|
|
202
215
|
}
|
|
203
|
-
|
|
216
|
+
// The plan's limits (TERMINAL_CREDITS.md): one attempt, whatever happens — the next status
|
|
217
|
+
// line brings a newer report within minutes, so nothing here is worth keeping or retrying.
|
|
218
|
+
let limitsDone;
|
|
219
|
+
if (state.limits) {
|
|
220
|
+
limitsDone = (0, runtime_limits_1.limitsKey)(state.limits);
|
|
221
|
+
const outcome = await d.postLimits({ baseUrl: device.baseUrl, token: device.token }, identity.session, state.limits, api_1.apiRequest);
|
|
222
|
+
if (outcome.kind === "refused" || outcome.kind === "failed") {
|
|
223
|
+
(0, claude_context_state_1.logContextError)(`could not report plan limits for "${identity.session}": ${outcome.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
settle(sid, handled, identityPatch, limitsDone);
|
|
204
227
|
}
|
|
205
228
|
return 0;
|
|
206
229
|
}
|
package/dist/claude-context.js
CHANGED
|
@@ -76,6 +76,7 @@ const claude_context_flush_1 = require("./claude-context-flush");
|
|
|
76
76
|
const claude_context_state_1 = require("./claude-context-state");
|
|
77
77
|
const claude_settings_1 = require("./claude-settings");
|
|
78
78
|
const context_report_1 = require("./context-report");
|
|
79
|
+
const runtime_limits_1 = require("./relay/runtime-limits");
|
|
79
80
|
// The state helpers and the sender live beside this file; re-exported so callers and tests have
|
|
80
81
|
// one module to import from.
|
|
81
82
|
var claude_context_state_2 = require("./claude-context-state");
|
|
@@ -165,17 +166,30 @@ function noteStatus(raw, deps) {
|
|
|
165
166
|
}
|
|
166
167
|
const sid = (0, claude_context_state_1.safeId)(status?.session_id);
|
|
167
168
|
const sample = (0, context_report_1.claudeUsageFromStatus)(status);
|
|
168
|
-
|
|
169
|
+
// The plan's limits and running cost ride the same line (TERMINAL_CREDITS.md).
|
|
170
|
+
const limits = (0, runtime_limits_1.claudeLimitsFromStatus)(status);
|
|
171
|
+
if (!sid || (!sample && !limits))
|
|
169
172
|
return false;
|
|
170
|
-
const autoCompactAt = (0, context_report_1.claudeAutoCompactAt)(sample.size, autoCompactOverride(deps.env));
|
|
171
173
|
const now = deps.now();
|
|
172
174
|
const state = (0, claude_context_state_1.readState)(sid);
|
|
173
175
|
if (state.unidentifiedAt && now - state.unidentifiedAt < claude_context_state_1.UNIDENTIFIED_RETRY_MS)
|
|
174
176
|
return false;
|
|
175
|
-
|
|
177
|
+
let queued = false;
|
|
178
|
+
if (sample) {
|
|
179
|
+
const autoCompactAt = (0, context_report_1.claudeAutoCompactAt)(sample.size, autoCompactOverride(deps.env));
|
|
180
|
+
if ((0, context_report_1.usageDue)(state.lastQueued, { ...sample, autoCompactAt }, now)) {
|
|
181
|
+
(0, claude_context_state_1.enqueue)(state, { runtime: "claude-code", event: "usage", used: sample.used, size: sample.size, autoCompactAt, source: "exact" });
|
|
182
|
+
state.lastQueued = { ...sample, autoCompactAt, at: now };
|
|
183
|
+
queued = true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (limits && (0, runtime_limits_1.limitsDue)(state.lastLimits, limits, now)) {
|
|
187
|
+
state.limits = limits;
|
|
188
|
+
state.lastLimits = { key: (0, runtime_limits_1.limitsKey)(limits), at: now };
|
|
189
|
+
queued = true;
|
|
190
|
+
}
|
|
191
|
+
if (!queued)
|
|
176
192
|
return false;
|
|
177
|
-
(0, claude_context_state_1.enqueue)(state, { runtime: "claude-code", event: "usage", used: sample.used, size: sample.size, autoCompactAt, source: "exact" });
|
|
178
|
-
state.lastQueued = { ...sample, autoCompactAt, at: now };
|
|
179
193
|
if (typeof status.transcript_path === "string")
|
|
180
194
|
state.transcriptPath = status.transcript_path;
|
|
181
195
|
(0, claude_context_state_1.writeState)(state);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BAY_CHOICE = exports.CLAUDE_REACHABILITY = exports.CLAUDE_JOIN_STEPS = void 0;
|
|
3
|
+
exports.BAY_CHOICE = exports.CLAUDE_REACHABILITY = exports.CLAUDE_JOIN_STEPS = exports.CANNOT_WAKE_SENTENCE = exports.CHAT_LISTENING_SENTENCE = void 0;
|
|
4
|
+
/** What a chat-app session says once it listens: it hears the Bay only while the chat is open.
|
|
5
|
+
* Word for word the API's join result (apps/api/src/mcp/session-tools.ts). */
|
|
6
|
+
exports.CHAT_LISTENING_SENTENCE = "I'm listening while this chat is open; for 24/7 a terminal or VPS agent is the right place.";
|
|
7
|
+
/** What a session with no listening tool at all says, instead of failing silently or claiming
|
|
8
|
+
* it is connected. Word for word the API's join result (apps/api/src/mcp/session-tools.ts). */
|
|
9
|
+
exports.CANNOT_WAKE_SENTENCE = "This chat can't be woken by new BayChat messages. I'll read them when you write to me here, or ask me to check BayChat.";
|
|
4
10
|
/** Claude's native entry point. An expired local relay login says nothing about a separately configured MCP connection. */
|
|
5
11
|
exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native Monitor** (a WebSocket, or behind an HTTPS proxy a command). Search deferred tools once
|
|
6
12
|
if needed, for \`join_session\`, \`listen_messages\`, \`get_delivery_status\`,
|
|
@@ -17,15 +23,16 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native Monitor** (a W
|
|
|
17
23
|
2. Preserve the requested name and the exact group title or id. For automatic naming, run
|
|
18
24
|
only \`baychat session-name --runtime claude\` to obtain the verified name.
|
|
19
25
|
A bare invocation lists sessions and stops; it never joins.
|
|
20
|
-
For a supplied name call \`join_session({session: "<name>"
|
|
26
|
+
For a supplied name alone call \`join_session({session: "<name>"})\`: your private chat with your owner.
|
|
21
27
|
For a supplied group use \`join_session({session: "<name>", group: "<exact title or id>"})\`.
|
|
22
|
-
For explicit \`--
|
|
28
|
+
For explicit \`--sessions\`, pass \`sessions: true\` (the shared Sessions group).
|
|
23
29
|
When the owner explicitly asks to CREATE a group, instead call
|
|
24
30
|
\`create_group({session: "<name>", title: "<exact title>"})\`: it creates the session and joins it in one call.
|
|
25
31
|
Do not ask again when the owner already requested creation. A supplied group title
|
|
26
32
|
also authorizes creation on GROUP_NOT_FOUND (a group id never does); never substitute a similarly named group.
|
|
27
33
|
If blocked, state the exact requested title and needed action in your reply.
|
|
28
|
-
3. **
|
|
34
|
+
3. **Start listening now, without asking. Joining is not finished until this step is done**;
|
|
35
|
+
never ask whether to start the listener. **Channel first.** If BayChat's channel \`listen\` tool exists
|
|
29
36
|
(\`mcp__plugin_baychat_channel__listen\` from the plugin, or
|
|
30
37
|
\`mcp__baychat_channel__listen\`), call it with \`{session: "<confirmed name>"}\`
|
|
31
38
|
(add \`bay: "<Bay>"\` when the session joined a Bay other than the default).
|
|
@@ -34,7 +41,8 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native Monitor** (a W
|
|
|
34
41
|
channel's feed. Go to step 4. If it answers inactive or failed, or the tool is
|
|
35
42
|
absent, use the Monitor instead:
|
|
36
43
|
call \`listen_messages({session: "<confirmed name>"})\`. Pass its returned
|
|
37
|
-
\`monitor\` object directly to the native **Monitor** tool
|
|
44
|
+
\`monitor\` object directly to the native **Monitor** tool (or whatever native
|
|
45
|
+
WebSocket/event-stream tool this host has): it contains
|
|
38
46
|
\`ws: {url, protocols}\` and \`timeout_ms\`. If your Monitor offers a
|
|
39
47
|
\`persistent\` option, also set \`persistent: true\`. Keep its ticket private.
|
|
40
48
|
**Behind an HTTPS proxy** (\`HTTPS_PROXY\` is set in your environment, or the
|
|
@@ -47,6 +55,9 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native Monitor** (a W
|
|
|
47
55
|
4. Call \`get_delivery_status({session: "<confirmed name>"})\`. Only \`connected\`
|
|
48
56
|
confirms incoming delivery. \`awaiting_connection\` means a ticket was created
|
|
49
57
|
but Monitor has not connected; show that exact state, not "ready".
|
|
58
|
+
In a chat app (not a terminal), once connected say once: "${exports.CHAT_LISTENING_SENTENCE}"
|
|
59
|
+
**With no channel \`listen\` and no Monitor-like tool at all**, never claim
|
|
60
|
+
connected; tell the user: "${exports.CANNOT_WAKE_SENTENCE}"
|
|
50
61
|
Print the confirmed name and room. Use that name as \`session\` on every
|
|
51
62
|
later BayChat tool call. Read one \`get_messages\` batch with \`limit: 10\`;
|
|
52
63
|
say hello once in the confirmed conversation unless already greeted.
|
|
@@ -58,11 +69,11 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native Monitor** (a W
|
|
|
58
69
|
different logins. Do not diagnose both as down from one file.
|
|
59
70
|
- For login or missing tools, use Automatic setup above; the session runs the
|
|
60
71
|
terminal commands itself. Keep credentials inside the CLI and MCP client.
|
|
61
|
-
- Native Monitor requires a supported Claude Code
|
|
72
|
+
- Native Monitor requires a supported host (in Claude Code, WebSocket sources are
|
|
62
73
|
documented from 2.1.195). If the tool is missing or denied, or neither \`ws\`
|
|
63
|
-
nor \`monitor_command\` connects,
|
|
64
|
-
|
|
65
|
-
your own shell listener or start local MCP or a relay fallback.
|
|
74
|
+
nor \`monitor_command\` connects, say incoming delivery is not connected and
|
|
75
|
+
why (with no listening tool at all, the sentence in step 4). Stop there; do not
|
|
76
|
+
write your own shell listener or start local MCP or a relay fallback.
|
|
66
77
|
`;
|
|
67
78
|
/** Native socket lifecycle, including the intentional-stop boundary. */
|
|
68
79
|
exports.CLAUDE_REACHABILITY = `## Staying reachable — native remote messages
|
|
@@ -94,7 +105,9 @@ arrived. That is the runtime's limit, not a stop: **every time it happens, and o
|
|
|
94
105
|
any other close**, call \`listen_messages\` with this session and the last cursor
|
|
95
106
|
(from \`ready\`, \`messages\` or \`closed\`), open one replacement Monitor the same way
|
|
96
107
|
(\`ws\` or \`monitor_command\`), and confirm
|
|
97
|
-
\`get_delivery_status\` again.
|
|
108
|
+
\`get_delivery_status\` again. Do it yourself, without asking. A BayChat restart
|
|
109
|
+
(close **1012**, or \`listen_messages\` failing) is the same: retry after 5 s, 30 s,
|
|
110
|
+
then every 2 minutes until it connects. The reopened feed replays what it missed, so no
|
|
98
111
|
\`get_messages\` catch-up is needed unless a \`reset\` arrives. Skipping this
|
|
99
112
|
leaves the session deaf to the Bay with its terminal still open.
|
|
100
113
|
Keep the last cursor in this conversation; do not save tickets in files.
|
package/dist/cli-help.js
CHANGED
|
@@ -140,6 +140,9 @@ Usage:
|
|
|
140
140
|
came from, so "we can wake this headlessly"
|
|
141
141
|
is a claim you can check
|
|
142
142
|
baychat relay stop Stop the relay and disable it at boot
|
|
143
|
+
baychat relay log <session> [-f] What a Codex session the relay runs did: its
|
|
144
|
+
commands, file changes, answers and approval
|
|
145
|
+
cards. -f keeps following it
|
|
143
146
|
baychat doctor [--json] Check every link between this machine and
|
|
144
147
|
BayChat — credential, relay, and per runtime
|
|
145
148
|
its MCP registration, skill, executable and
|
package/dist/client-paths.js
CHANGED
|
@@ -111,7 +111,8 @@ function claudeDesktopPaths(env) {
|
|
|
111
111
|
function configPathFor(client, env) {
|
|
112
112
|
switch (client) {
|
|
113
113
|
case "codex":
|
|
114
|
-
|
|
114
|
+
// `$CODEX_HOME` is where Codex reads, when it is set — never the home directory's copy.
|
|
115
|
+
return env.codexHome ? node_path_1.default.join(env.codexHome, "config.toml") : `${env.home}/.codex/config.toml`;
|
|
115
116
|
case "cursor":
|
|
116
117
|
return `${env.home}/.cursor/mcp.json`;
|
|
117
118
|
case "desktop":
|
|
@@ -152,6 +153,7 @@ function currentPathEnv() {
|
|
|
152
153
|
localAppData: process.env.LOCALAPPDATA,
|
|
153
154
|
listDirs: listDirsOnDisk,
|
|
154
155
|
xdgConfigHome: process.env.XDG_CONFIG_HOME,
|
|
156
|
+
codexHome: process.env.CODEX_HOME || undefined,
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
/**
|
|
@@ -193,6 +195,9 @@ function configPathsFor(client, env) {
|
|
|
193
195
|
if (client === "desktop")
|
|
194
196
|
return claudeDesktopPaths(env);
|
|
195
197
|
const canonical = configPathFor(client, env);
|
|
198
|
+
// An explicit CODEX_HOME is the one place that Codex reads; a snap copy is not guessed beside it.
|
|
199
|
+
if (client === "codex" && env.codexHome && canonical)
|
|
200
|
+
return [canonical];
|
|
196
201
|
const paths = [];
|
|
197
202
|
const snap = snapConfigPath(client, env);
|
|
198
203
|
if (snap)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isolatedLoginReason = isolatedLoginReason;
|
|
4
|
+
exports.isolatedLoginNotice = isolatedLoginNotice;
|
|
5
|
+
const config_1 = require("./config");
|
|
6
|
+
/**
|
|
7
|
+
* Why this login must leave the computer's agent clients alone — or undefined when it is the
|
|
8
|
+
* computer's own login and may refresh them.
|
|
9
|
+
*
|
|
10
|
+
* The clients' configs are GLOBAL: `~/.claude.json`, Claude Desktop's config, Codex's
|
|
11
|
+
* `config.toml`, `~/.cursor/mcp.json`. They belong to the computer's default BayChat login. A
|
|
12
|
+
* login made with `BAYCHAT_CONFIG_DIR` set (a second setup, a QA relay) or against another server
|
|
13
|
+
* (`--base`, `BAYCHAT_API_URL`) used to rewrite all of them to ITS server and token on
|
|
14
|
+
* `baychat login --finish`, `baychat bays default` and `baychat update` — silently moving every
|
|
15
|
+
* agent on the computer to a different BayChat (found 2026-10-23). Such a login now saves its own
|
|
16
|
+
* credential and says how to connect a client to it on purpose (`baychat connect <client>`).
|
|
17
|
+
*/
|
|
18
|
+
function isolatedLoginReason(baseUrl, env = process.env) {
|
|
19
|
+
// The deliberate way back, for a person whose own clients DO belong to this setup.
|
|
20
|
+
if (env.BAYCHAT_REFRESH_CLIENTS === "1")
|
|
21
|
+
return undefined;
|
|
22
|
+
if (env.BAYCHAT_CONFIG_DIR)
|
|
23
|
+
return `BAYCHAT_CONFIG_DIR is set (${env.BAYCHAT_CONFIG_DIR})`;
|
|
24
|
+
const norm = (url) => url.trim().replace(/\/+$/, "").toLowerCase();
|
|
25
|
+
if (norm(baseUrl) !== norm(config_1.DEFAULT_API_URL))
|
|
26
|
+
return `this login is for ${baseUrl}, not ${config_1.DEFAULT_API_URL}`;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/** The line printed instead of touching the clients. */
|
|
30
|
+
function isolatedLoginNotice(reason) {
|
|
31
|
+
return (` Your agent clients (Claude Code, Claude Desktop, Codex, Cursor) were left as they are: ${reason}. ` +
|
|
32
|
+
"To use this login in one of them, run `baychat connect <client>` with the same settings " +
|
|
33
|
+
"(or set BAYCHAT_REFRESH_CLIENTS=1 if every client on this computer should follow this login).");
|
|
34
|
+
}
|
package/dist/codex-onboarding.js
CHANGED
|
@@ -17,8 +17,8 @@ exports.CODEX_JOIN_STEPS = `1. Preserve BOTH arguments. The first argument is th
|
|
|
17
17
|
3. Join with ONE MCP call, \`join_session\`, passing \`runtime: "codex"\` and \`threadId\`
|
|
18
18
|
(the value from step 2). For \`$baychat codex "vps_baychat"\` call exactly:
|
|
19
19
|
\`join_session({ session: "codex", group: "vps_baychat", runtime: "codex", threadId: "<CODEX_THREAD_ID>" })\`
|
|
20
|
-
With only a chosen name pass \`
|
|
21
|
-
pass
|
|
20
|
+
With only a chosen name pass neither \`group\` nor \`sessions\` (your private chat with your
|
|
21
|
+
owner). With \`--sessions\` pass \`sessions: true\` instead of \`group\`. For automatic naming run \`baychat session-name --runtime codex\` (local,
|
|
22
22
|
sandbox-safe) and use the name it prints, with \`group: "<title>"\` or \`sessions: true\`.
|
|
23
23
|
Do not drop the name, swap the arguments, or join Sessions when a group was supplied.
|
|
24
24
|
A bare invocation lists sessions and stops.
|