baychat 0.33.3 → 0.34.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baychat",
3
- "version": "0.33.3",
3
+ "version": "0.34.1",
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": {
@@ -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
  });
@@ -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) {
@@ -213,6 +213,17 @@ async function mintTicket(deps, { bay, session, cursor, commands }) {
213
213
  }
214
214
  catch (err) {
215
215
  const status = httpStatus(err);
216
+ // The Bay requires two-step login and this person has not set it up (API
217
+ // mfa-policy.service.ts, 403 MFA_REQUIRED). Logging in again would not help; setting it up
218
+ // does, and the same computer login works again the moment it is done.
219
+ if (status === 403 && /MFA_REQUIRED/.test(err instanceof Error ? err.message : String(err))) {
220
+ return {
221
+ ok: false,
222
+ fatal: true,
223
+ detail: "Stopped: this Bay requires two-step login. Set it up in the BayChat app (Settings → Two-step login), " +
224
+ "then start this session again.",
225
+ };
226
+ }
216
227
  if (status === 401 || status === 403) {
217
228
  return { ok: false, fatal: true, detail: "BayChat refused this computer's login. Run `baychat login`." };
218
229
  }
@@ -17,9 +17,9 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native Monitor** (a W
17
17
  2. Preserve the requested name and the exact group title or id. For automatic naming, run
18
18
  only \`baychat session-name --runtime claude\` to obtain the verified name.
19
19
  A bare invocation lists sessions and stops; it never joins.
20
- For a supplied name call \`join_session({session: "<name>", sessions: true})\`.
20
+ For a supplied name alone call \`join_session({session: "<name>"})\`: your private chat with your owner.
21
21
  For a supplied group use \`join_session({session: "<name>", group: "<exact title or id>"})\`.
22
- For explicit \`--private\`, omit both \`sessions\` and \`group\`.
22
+ For explicit \`--sessions\`, pass \`sessions: true\` (the shared Sessions group).
23
23
  When the owner explicitly asks to CREATE a group, instead call
24
24
  \`create_group({session: "<name>", title: "<exact title>"})\`: it creates the session and joins it in one call.
25
25
  Do not ask again when the owner already requested creation. A supplied group title
@@ -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 \`sessions: true\` instead of \`group\`. With \`--private\`
21
- pass neither. For automatic naming run \`baychat session-name --runtime codex\` (local,
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.
@@ -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 = "Sessions started here from BayChat ask for approvals in BayChat.";
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;
@@ -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
- return { id, label, path: p, runtimes: [...new Set(runtimes)], maxSessions };
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 vars = Object.entries(program.env).map(([k, v]) => `${k}=${v}`);
123
- const command = vars.length ? ["env", ...vars, program.binary, ...program.args] : [program.binary, ...program.args];
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, ...command] : ["-dmS", naming.name, ...command];
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
- return naming.kind === "reattach"
131
- ? // `-A` attaches to an existing session of that name instead of failing, so running this
132
- // twice returns you to your agent rather than erroring at you.
133
- ["new", "-A", "-s", naming.name, ...command]
134
- : ["new", "-s", uniqueTmuxName(naming.prefix, deps), ...command];
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
  }