baychat 0.22.0 → 0.23.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.
- package/README.md +22 -0
- package/dist/connect-acp.js +165 -0
- package/dist/connect-cursor.js +65 -0
- package/dist/connect-dsh.js +17 -137
- package/dist/connect.js +10 -0
- package/dist/doctor.js +13 -14
- package/dist/index.js +5 -0
- package/dist/mcp.js +64 -0
- package/dist/protocol-content.js +1 -1
- package/dist/relay/acp/agents.js +76 -31
- package/dist/relay/acp/client.js +13 -1
- package/dist/relay/acp/commands.js +3 -3
- package/dist/relay/acp/cursor.js +271 -0
- package/dist/relay/acp/daemon-glue.js +12 -15
- package/dist/relay/acp/modes.js +5 -5
- package/dist/relay/acp/permissions.js +65 -3
- package/dist/relay/acp/policy.js +7 -6
- package/dist/relay/acp/prompt.js +2 -3
- package/dist/relay/acp/runner.js +65 -12
- package/dist/relay/acp/types.js +3 -0
- package/dist/relay/daemon.js +1 -1
- package/dist/relay/updates.js +57 -1
- package/dist/runtime-binary.js +17 -0
- package/dist/tool-defs.js +50 -0
- package/package.json +4 -2
package/dist/relay/acp/runner.js
CHANGED
|
@@ -36,15 +36,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.AcpSessionRunner = void 0;
|
|
37
37
|
exports.effectiveMode = effectiveMode;
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
|
+
const runtime_binary_1 = require("../../runtime-binary");
|
|
39
40
|
const spawn_env_1 = require("../spawn-env");
|
|
40
41
|
const agents_1 = require("./agents");
|
|
41
42
|
const approval_1 = require("./approval");
|
|
42
43
|
const client_1 = require("./client");
|
|
43
44
|
const modes_1 = require("./modes");
|
|
44
|
-
const permissions_1 = require("./permissions");
|
|
45
45
|
const policy_1 = require("./policy");
|
|
46
46
|
const presence_1 = require("./presence");
|
|
47
47
|
const prompt_1 = require("./prompt");
|
|
48
|
+
const types_1 = require("./types");
|
|
48
49
|
/**
|
|
49
50
|
* How much power ONE turn gets.
|
|
50
51
|
*
|
|
@@ -62,6 +63,17 @@ function effectiveMode(sessionMode, ceiling, allAuthorized, allowedHere) {
|
|
|
62
63
|
const usable = modes_1.ACP_MODES.filter((m) => allowedHere.includes(m) && (0, modes_1.modeRank)(m) <= (0, modes_1.modeRank)(wanted));
|
|
63
64
|
return usable.at(-1) ?? "chat";
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Is this call BayChat's `send_message` — the agent's answer going into the room? `mcpServer` is
|
|
68
|
+
* the name BayChat's server was handed over under this turn. dsh names the call by its title;
|
|
69
|
+
* Cursor titles it "<server>: send_message" and names server and tool in its input [run].
|
|
70
|
+
* Another server's `send_message` is not ours.
|
|
71
|
+
*/
|
|
72
|
+
function isBayChatSend(call, mcpServer) {
|
|
73
|
+
if (call.title === `mcp__${mcpServer}__send_message`)
|
|
74
|
+
return true;
|
|
75
|
+
return call.rawInput.providerIdentifier === mcpServer && call.rawInput.toolName === "send_message";
|
|
76
|
+
}
|
|
65
77
|
const RETRY_MAX_MS = 60_000;
|
|
66
78
|
const errText = (err) => (err instanceof Error ? err.message : String(err));
|
|
67
79
|
/**
|
|
@@ -174,6 +186,10 @@ class AcpSessionRunner {
|
|
|
174
186
|
return;
|
|
175
187
|
seen.add(key);
|
|
176
188
|
this.said.set(session, seen);
|
|
189
|
+
await this.tell(session, key, conversationId, text);
|
|
190
|
+
}
|
|
191
|
+
/** Say it in the room now, every time. A failure to say is logged, never thrown. */
|
|
192
|
+
async tell(session, key, conversationId, text) {
|
|
177
193
|
try {
|
|
178
194
|
await this.deps.say(conversationId, session, text);
|
|
179
195
|
}
|
|
@@ -193,28 +209,46 @@ class AcpSessionRunner {
|
|
|
193
209
|
const acp = target?.acp;
|
|
194
210
|
const row = acp ? (0, agents_1.acpAgent)(acp.agent) : undefined;
|
|
195
211
|
if (!target || !acp || !row)
|
|
196
|
-
return drop("setup", `"${session}" is not set up on that computer any more. Run
|
|
212
|
+
return drop("setup", `"${session}" is not set up on that computer any more. Run \`${(0, agents_1.connectCommandFor)(acp?.agent)}\` there.`);
|
|
197
213
|
const auth = deps.auth();
|
|
198
214
|
if (!auth)
|
|
199
|
-
return drop("setup", `The relay on that computer is not logged in to BayChat. Run
|
|
215
|
+
return drop("setup", `The relay on that computer is not logged in to BayChat. Run \`${row.connectCommand}\` there.`);
|
|
200
216
|
if (!target.runtimeBin)
|
|
201
217
|
return drop("spawn", `${row.label} is not installed where the relay can find it. ${row.installHint}`);
|
|
202
218
|
const folder = (0, policy_1.checkFolder)((deps.loadPolicy ?? policy_1.loadPolicy)(), row.id, acp.cwd);
|
|
203
219
|
if (!folder.ok)
|
|
204
220
|
return drop("policy", folder.reason);
|
|
221
|
+
// Every turn, every mode: what is on disk now, not what was there at `connect`.
|
|
222
|
+
const refusal = row.turnRefusal?.(acp.cwd);
|
|
223
|
+
if (refusal)
|
|
224
|
+
return drop("override", refusal);
|
|
225
|
+
const env = { ...(deps.env ?? spawn_env_1.headlessSpawnEnv)() };
|
|
226
|
+
if (row.versionPattern) {
|
|
227
|
+
// The recorded path may since have been replaced by something else answering to the name.
|
|
228
|
+
const probed = await (deps.binaryVersion ?? ((bin) => (0, runtime_binary_1.probeBinaryVersion)(bin, env)))(target.runtimeBin);
|
|
229
|
+
if (!probed.ok || !(0, agents_1.binaryIdentified)(row, probed.version)) {
|
|
230
|
+
return drop("spawn", (0, agents_1.unidentifiedBinary)(row, target.runtimeBin, probed.ok ? probed.version : `nothing usable: ${probed.detail}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
205
233
|
const platform = deps.platform ?? process.platform;
|
|
206
234
|
const allAuthorized = turn.addressed.every((m) => m.senderIsOwner === true || m.commandAuthorized === true);
|
|
207
235
|
const mode = effectiveMode(acp.mode, folder.maxMode, allAuthorized, row.modesFor(platform));
|
|
208
236
|
const patchDir = deps.patchDir ?? (0, agents_1.acpPatchDir)();
|
|
209
|
-
(0, agents_1.
|
|
210
|
-
const launch = row.launch(mode, patchDir);
|
|
237
|
+
(0, agents_1.writeModePatch)(row, mode, session, patchDir); // cheap; an upgrade or a /mode-* takes effect at once
|
|
238
|
+
const launch = row.launch(mode, patchDir, session);
|
|
239
|
+
const mcpServer = row.mcpServerName?.() ?? types_1.BAYCHAT_MCP_SERVER;
|
|
240
|
+
const toolNote = row.toolNote?.(mcpServer);
|
|
211
241
|
const onPermission = async (call) => {
|
|
212
242
|
const host = (deps.hostname ?? os.hostname)();
|
|
213
|
-
const decision =
|
|
243
|
+
const decision = row.decidePermission(mode, call, { cwd: acp.cwd, host, agentLabel: row.label, mcpServer });
|
|
214
244
|
if (decision.kind === "refuse") {
|
|
215
245
|
deps.log(`acp: ${session} permission REFUSED — ${decision.reason}`);
|
|
216
246
|
return "reject";
|
|
217
247
|
}
|
|
248
|
+
if (decision.kind === "allow") {
|
|
249
|
+
deps.log(`acp: ${session} permission ALLOWED without asking — ${decision.reason}`);
|
|
250
|
+
return "allow";
|
|
251
|
+
}
|
|
218
252
|
typing?.pause(); // the card is the signal while a person decides
|
|
219
253
|
const answer = await (deps.askPhone ?? approval_1.askPhone)({ auth, session, conversationId: room, question: decision.question });
|
|
220
254
|
typing?.resume(); // a no-op once the turn has stopped typing — `stop()` is final
|
|
@@ -224,24 +258,32 @@ class AcpSessionRunner {
|
|
|
224
258
|
const caller = (deps.caller ?? presence_1.deviceMcpCaller)(auth);
|
|
225
259
|
const abort = new AbortController();
|
|
226
260
|
let typing;
|
|
261
|
+
let stopGuard;
|
|
262
|
+
/** What the row's guard found on disk mid-turn — the turn is stopped the moment it is set. */
|
|
263
|
+
let breach;
|
|
227
264
|
let outcome;
|
|
228
265
|
try {
|
|
229
266
|
this.running.set(session, abort);
|
|
267
|
+
stopGuard = row.guardTurn?.(acp.cwd, (finding) => {
|
|
268
|
+
breach = `${row.label} was stopped in the middle of its turn: ${finding}`;
|
|
269
|
+
abort.abort();
|
|
270
|
+
});
|
|
230
271
|
typing = (0, presence_1.startTyping)(caller, session, room, { log: deps.log });
|
|
231
272
|
deps.log(`acp: ${session} turn — ${turn.addressed.length} addressed, ${turn.digest.messages.length} context, mode ${mode}${allAuthorized ? "" : " (not all from someone with command access)"}`);
|
|
232
273
|
outcome = await (deps.runTurn ?? client_1.runAcpTurn)({
|
|
233
274
|
binaryPath: target.runtimeBin,
|
|
234
275
|
args: launch.args,
|
|
235
|
-
env: { ...
|
|
276
|
+
env: { ...env, ...launch.env },
|
|
236
277
|
cwd: acp.cwd,
|
|
237
278
|
sessionId: acp.sessionId,
|
|
238
|
-
mcpServers: [{ type: "http", name:
|
|
239
|
-
prompt: `Mode for this turn: ${mode}.\n\n${(0, prompt_1.turnPrompt)(turn)}`,
|
|
240
|
-
freshPreamble: (0, prompt_1.setupPreamble)(session, mode),
|
|
279
|
+
mcpServers: [{ type: "http", name: mcpServer, url: `${auth.baseUrl.replace(/\/$/, "")}/api/mcp`, headers: [{ name: "Authorization", value: `Bearer ${auth.token}` }] }],
|
|
280
|
+
prompt: `Mode for this turn: ${mode}.\n\n${toolNote ? `${toolNote}\n\n` : ""}${(0, prompt_1.turnPrompt)(turn)}`,
|
|
281
|
+
freshPreamble: (0, prompt_1.setupPreamble)(session, (0, agents_1.describeAgentMode)(row, mode)),
|
|
282
|
+
sessionModeId: row.acpModeId,
|
|
241
283
|
onPermission,
|
|
242
284
|
// Its message is in the room; a refreshed indicator after that would be a ghost.
|
|
243
285
|
onToolCall: (call) => {
|
|
244
|
-
if (call
|
|
286
|
+
if (isBayChatSend(call, mcpServer))
|
|
245
287
|
typing?.stop();
|
|
246
288
|
},
|
|
247
289
|
classifyError: (m) => row.classifyError(m),
|
|
@@ -249,10 +291,21 @@ class AcpSessionRunner {
|
|
|
249
291
|
}, { spawn: deps.spawn, log: deps.log, platform });
|
|
250
292
|
}
|
|
251
293
|
finally {
|
|
252
|
-
// Every exit — outcome or throw — ends typing and frees `/stop`, or `/stop` would lie.
|
|
294
|
+
// Every exit — outcome or throw — ends typing and the guard and frees `/stop`, or `/stop` would lie.
|
|
295
|
+
stopGuard?.();
|
|
253
296
|
typing?.stop();
|
|
254
297
|
this.running.delete(session);
|
|
255
298
|
}
|
|
299
|
+
if (breach !== undefined) {
|
|
300
|
+
// Said every time, not once: it happened in THIS turn. The next turn's `turnRefusal` then
|
|
301
|
+
// refuses until the file is gone.
|
|
302
|
+
deps.log(`acp: ${session} ${breach}`);
|
|
303
|
+
await this.tell(session, "override", room, breach);
|
|
304
|
+
if (outcome.kind !== "completed") {
|
|
305
|
+
deps.onPending(session, turn.addressed, breach);
|
|
306
|
+
return "next";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
256
309
|
if (outcome.kind === "completed") {
|
|
257
310
|
deps.registry.setAcpSession(session, outcome.sessionId);
|
|
258
311
|
deps.onDelivered(session, turn.addressed);
|
package/dist/relay/acp/types.js
CHANGED
package/dist/relay/daemon.js
CHANGED
|
@@ -271,7 +271,7 @@ class RelayDaemon {
|
|
|
271
271
|
});
|
|
272
272
|
writePidFile();
|
|
273
273
|
// After the socket is ours: a relay refused as a duplicate must not keep sessions alive.
|
|
274
|
-
this.acpKeepAlive = (0, daemon_glue_1.startAcpUpkeep)(this.acpAlive.run
|
|
274
|
+
this.acpKeepAlive = (0, daemon_glue_1.startAcpUpkeep)(this.acpAlive.run);
|
|
275
275
|
this.log(`listening on ${sockPath} (pid ${process.pid}) as device "${device.user.name}"`);
|
|
276
276
|
this.log(`${this.registry.all().length} known session target(s)`);
|
|
277
277
|
// The event SOURCE is pluggable (socket preferred, long-poll floor); every
|
package/dist/relay/updates.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUSPENDED_RECHECK_MS = void 0;
|
|
3
4
|
exports.runUpdatesLoop = runUpdatesLoop;
|
|
4
5
|
exports.catchUpFromWatermarks = catchUpFromWatermarks;
|
|
5
6
|
const api_1 = require("../api");
|
|
@@ -9,6 +10,49 @@ const WAIT_SECONDS = 25;
|
|
|
9
10
|
const BACKOFF_MIN_MS = 1_000;
|
|
10
11
|
const BACKOFF_MAX_MS = 30_000;
|
|
11
12
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
/**
|
|
14
|
+
* How long the relay waits before asking again once the server has said the account or the
|
|
15
|
+
* Bay is SUSPENDED. Long on purpose — nothing the relay does changes the answer, so this is
|
|
16
|
+
* only about noticing when a person has lifted it.
|
|
17
|
+
*/
|
|
18
|
+
exports.SUSPENDED_RECHECK_MS = 15 * 60_000;
|
|
19
|
+
/** `sleep`, but over at once when `signal` aborts — a relay told to stop must not sit out 15 minutes. */
|
|
20
|
+
function sleepUnlessAborted(ms, signal) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
if (signal.aborted)
|
|
23
|
+
return resolve();
|
|
24
|
+
const timer = setTimeout(done, ms);
|
|
25
|
+
function done() {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
signal.removeEventListener("abort", done);
|
|
28
|
+
resolve();
|
|
29
|
+
}
|
|
30
|
+
signal.addEventListener("abort", done);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The server's refusal about STANDING, not about the credential (docs/features/MODERATION.md
|
|
35
|
+
* in the API): 403 ACCOUNT_SUSPENDED — the account is suspended or being deleted by BayChat —
|
|
36
|
+
* or 403 BAY_SUSPENDED — the Bay this laptop is logged in to is suspended.
|
|
37
|
+
*
|
|
38
|
+
* Neither is fixed by `npx baychat login`, and neither is transient in the 5xx sense. Treating
|
|
39
|
+
* them as terminal (the default for a 4xx) made the daemon exit, and every service manager this
|
|
40
|
+
* CLI installs (`systemd` Restart=always, launchd KeepAlive, the Windows task's
|
|
41
|
+
* RestartOnFailure) restarted it into the same refusal every few seconds: a crash-loop that
|
|
42
|
+
* says nothing useful, the exact shape of the relay that restarted 111k times on an expired
|
|
43
|
+
* credential. An exit code cannot stop that without changing units that are already installed,
|
|
44
|
+
* so the relay stays up instead, says plainly what happened, and asks again rarely — which also
|
|
45
|
+
* means it resumes by itself when a Bay suspension is lifted.
|
|
46
|
+
*/
|
|
47
|
+
function standingRefusal(err) {
|
|
48
|
+
if (!(err instanceof api_1.ApiError) || err.status !== 403)
|
|
49
|
+
return null;
|
|
50
|
+
if (err.code === "ACCOUNT_SUSPENDED")
|
|
51
|
+
return "account";
|
|
52
|
+
if (err.code === "BAY_SUSPENDED")
|
|
53
|
+
return "bay";
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
12
56
|
/**
|
|
13
57
|
* Transient failures are ridden out, not surfaced. A 502 while the api
|
|
14
58
|
* container restarts, a network throw, a 429 — all mean "later", not "stop".
|
|
@@ -43,7 +87,7 @@ function isTransient(err) {
|
|
|
43
87
|
* watermark, then poll again with no cursor.
|
|
44
88
|
*/
|
|
45
89
|
async function runUpdatesLoop(opts) {
|
|
46
|
-
const { auth, watermarks, onEvents, onSessions, onPoll, onError, initialCursor, signal, request = api_1.apiRequest } = opts;
|
|
90
|
+
const { auth, watermarks, onEvents, onSessions, onPoll, onError, initialCursor, signal, request = api_1.apiRequest, suspendedRecheckMs = exports.SUSPENDED_RECHECK_MS, } = opts;
|
|
47
91
|
let cursor = initialCursor;
|
|
48
92
|
let backoff = BACKOFF_MIN_MS;
|
|
49
93
|
while (!signal.aborted) {
|
|
@@ -86,6 +130,18 @@ async function runUpdatesLoop(opts) {
|
|
|
86
130
|
cursor = undefined;
|
|
87
131
|
continue;
|
|
88
132
|
}
|
|
133
|
+
const standing = standingRefusal(err);
|
|
134
|
+
if (standing) {
|
|
135
|
+
const minutes = Math.round(suspendedRecheckMs / 60_000);
|
|
136
|
+
onError?.(new Error(standing === "account"
|
|
137
|
+
? `this BayChat account has been suspended — the relay is paused and will check again in ${minutes} min. ` +
|
|
138
|
+
"Logging in again will not help; if you think this is a mistake, contact support@baychat.io."
|
|
139
|
+
: `the Bay this machine is logged in to has been suspended — the relay is paused and will check again in ${minutes} min. ` +
|
|
140
|
+
"If you think this is a mistake, contact support@baychat.io."), true);
|
|
141
|
+
await sleepUnlessAborted(suspendedRecheckMs, signal);
|
|
142
|
+
backoff = BACKOFF_MIN_MS;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
89
145
|
if (!isTransient(err)) {
|
|
90
146
|
onError?.(err, false);
|
|
91
147
|
throw err; // terminal — an expired device credential needs `npx baychat login`
|
package/dist/runtime-binary.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.resolveRuntimeBinary = resolveRuntimeBinary;
|
|
37
37
|
exports.describeResolution = describeResolution;
|
|
38
38
|
exports.spawnPlanFor = spawnPlanFor;
|
|
39
|
+
exports.probeBinaryVersion = probeBinaryVersion;
|
|
39
40
|
exports.summarizeProbe = summarizeProbe;
|
|
40
41
|
exports.currentBinaryEnv = currentBinaryEnv;
|
|
41
42
|
exports.summarizeResolutionFailure = summarizeResolutionFailure;
|
|
@@ -213,6 +214,22 @@ function spawnPlanFor(candidate, platform) {
|
|
|
213
214
|
}
|
|
214
215
|
/** How long a `--version` probe may run before it is treated as broken. */
|
|
215
216
|
const PROBE_TIMEOUT_MS = 5_000;
|
|
217
|
+
/**
|
|
218
|
+
* `<binary> --version`, without blocking the event loop — for a caller that probes a known path
|
|
219
|
+
* while other work runs (the relay, before starting an agent). Same rules as the resolution
|
|
220
|
+
* probe: no shell, the environment the agent will get, `summarizeProbe` decides.
|
|
221
|
+
*/
|
|
222
|
+
function probeBinaryVersion(binaryPath, env) {
|
|
223
|
+
const plan = spawnPlanFor(binaryPath, process.platform);
|
|
224
|
+
return new Promise((resolve) => {
|
|
225
|
+
(0, child_process_1.execFile)(plan.file, [...plan.prefixArgs, "--version"], { timeout: PROBE_TIMEOUT_MS, windowsHide: true, shell: false, env, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
226
|
+
const status = err ? (typeof err.code === "number" ? err.code : null) : 0;
|
|
227
|
+
// A non-zero exit is still a run: only a failure to run at all is an `error`.
|
|
228
|
+
const failedToRun = err && typeof err.code !== "number" ? err : undefined;
|
|
229
|
+
resolve(summarizeProbe({ status, stdout: stdout ?? "", stderr: stderr ?? "", error: failedToRun }));
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
216
233
|
/** The most stderr worth quoting back to a user in a rejection reason. */
|
|
217
234
|
const PROBE_DETAIL_LIMIT = 200;
|
|
218
235
|
/**
|
package/dist/tool-defs.js
CHANGED
|
@@ -266,6 +266,56 @@ exports.CONVERSATION_TOOL_DEFS = [
|
|
|
266
266
|
"attachments, never both."),
|
|
267
267
|
},
|
|
268
268
|
},
|
|
269
|
+
{
|
|
270
|
+
name: "create_poll",
|
|
271
|
+
title: "Ask the room to choose",
|
|
272
|
+
description: "Put a poll in the room: one question, two to ten answers, counted by the server. " +
|
|
273
|
+
"USE IT WHEN THE ANSWER IS A CHOICE SEVERAL PEOPLE OWN — a release date, which of two " +
|
|
274
|
+
"designs, who is free on Thursday. For a decision ONE person owns, use request_approval " +
|
|
275
|
+
"instead: that blocks your session until they answer, while a poll does not block " +
|
|
276
|
+
"anything and you read the result later. " +
|
|
277
|
+
"Everyone in the room can vote, including other agents, and every vote is visible to the " +
|
|
278
|
+
"room. Do not create a second poll to ask the same thing again — read the first one's " +
|
|
279
|
+
"tally with get_messages.",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
conversationId: zod_1.z
|
|
282
|
+
.string()
|
|
283
|
+
.describe("The conversation id (from list_conversations)."),
|
|
284
|
+
question: zod_1.z.string().describe("The question, as the room will read it."),
|
|
285
|
+
options: zod_1.z
|
|
286
|
+
.array(zod_1.z.string())
|
|
287
|
+
.describe("The answers, in the order they should appear: at least two, at most ten, each " +
|
|
288
|
+
"distinct. Keep them short — they are drawn as rows on a phone."),
|
|
289
|
+
allowMultiple: zod_1.z
|
|
290
|
+
.boolean()
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("May one voter pick more than one answer? Defaults to false — one answer each."),
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "vote_in_poll",
|
|
297
|
+
title: "Vote in a poll",
|
|
298
|
+
description: "Cast, change or withdraw YOUR OWN vote in a poll. You are a participant of the room like " +
|
|
299
|
+
"anyone else, and your vote is counted and shown beside theirs. " +
|
|
300
|
+
"GET pollId FROM THE POLL CARD: a poll arrives through get_messages as a message whose " +
|
|
301
|
+
"`cardPayload` holds { pollId, question, options }, and whose `poll` holds the tally so " +
|
|
302
|
+
"far. " +
|
|
303
|
+
"`optionIndexes` COUNTS FROM ZERO — the first answer is 0, whatever the numbering in the " +
|
|
304
|
+
"message text says. It is the COMPLETE set of answers you want to hold: send one index " +
|
|
305
|
+
"for a single-answer poll, several for a poll that allows them, and an empty array to " +
|
|
306
|
+
"take your vote back. Sending the same vote twice changes nothing. " +
|
|
307
|
+
"VOTE ONLY WHEN YOU WERE ASKED TO, or when you genuinely hold the information the " +
|
|
308
|
+
"question is about. A poll is the room's decision, not yours to settle.",
|
|
309
|
+
inputSchema: {
|
|
310
|
+
pollId: zod_1.z
|
|
311
|
+
.string()
|
|
312
|
+
.describe("The poll's id, from the poll card's `cardPayload.pollId`."),
|
|
313
|
+
optionIndexes: zod_1.z
|
|
314
|
+
.array(zod_1.z.number())
|
|
315
|
+
.describe("The complete set of answers you want to hold, counting from ZERO. Empty withdraws " +
|
|
316
|
+
"your vote."),
|
|
317
|
+
},
|
|
318
|
+
},
|
|
269
319
|
{
|
|
270
320
|
name: "set_typing",
|
|
271
321
|
title: "Show that you are working",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "BayChat connector CLI — pair an agent session (Claude Code, Codex) with BayChat
|
|
3
|
+
"version": "0.23.1",
|
|
4
|
+
"description": "BayChat connector CLI — pair an agent session (Claude Code, Codex, Cursor) with BayChat, or have the relay run DeepSeek or the Cursor CLI for you",
|
|
5
5
|
"bin": {
|
|
6
6
|
"baychat": "dist/index.js"
|
|
7
7
|
},
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"chat",
|
|
30
30
|
"claude-code",
|
|
31
31
|
"codex",
|
|
32
|
+
"cursor",
|
|
33
|
+
"deepseek",
|
|
32
34
|
"cli",
|
|
33
35
|
"connector",
|
|
34
36
|
"group-chat"
|