baychat 0.35.3 → 0.36.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/approve-hook.js +12 -3
- package/dist/denied/claims.js +75 -0
- package/dist/denied/compound-command.js +51 -0
- package/dist/denied/opt-in.js +30 -16
- package/dist/denied/permission-rule.js +10 -9
- package/dist/denied-hook.js +26 -1
- package/dist/mcp.js +3 -0
- package/dist/permissions-command.js +8 -5
- package/dist/protocol-content.js +1 -1
- package/dist/relay/codex-control.js +345 -0
- package/dist/relay/daemon.js +55 -1
- package/dist/relay/launcher/approval-settings.js +2 -2
- package/dist/relay/session-commands.js +7 -5
- package/dist/runtimes.js +4 -0
- package/dist/tool-defs.js +20 -3
- package/hooks/hooks.json +18 -0
- package/package.json +2 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.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": {
|
package/dist/approve-hook.js
CHANGED
|
@@ -103,6 +103,7 @@ const approval_wait_1 = require("./approval-wait");
|
|
|
103
103
|
const config_1 = require("./config");
|
|
104
104
|
const hook_stdin_1 = require("./hook-stdin");
|
|
105
105
|
const claude_link_1 = require("./channel/claude-link");
|
|
106
|
+
const compound_command_1 = require("./denied/compound-command");
|
|
106
107
|
function allowDecision() {
|
|
107
108
|
return {
|
|
108
109
|
hookSpecificOutput: {
|
|
@@ -210,6 +211,12 @@ function describeToolRequest(toolName, toolInput) {
|
|
|
210
211
|
return "(its parameters could not be displayed)";
|
|
211
212
|
}
|
|
212
213
|
}
|
|
214
|
+
/** A shell command that is more than one, as the shell reads it (denied/compound-command.ts). */
|
|
215
|
+
function isChained(toolName, toolInput) {
|
|
216
|
+
if (toolName !== "Bash")
|
|
217
|
+
return false;
|
|
218
|
+
return (0, compound_command_1.compoundKind)(str((toolInput ?? {}).command)) !== null;
|
|
219
|
+
}
|
|
213
220
|
/** The question the card asks. Blunt on purpose: this is a shell on a server, read on a phone,
|
|
214
221
|
* and the safe answer has to be the obvious one. */
|
|
215
222
|
function composeQuestion(params) {
|
|
@@ -225,6 +232,7 @@ function composeQuestion(params) {
|
|
|
225
232
|
params.cwd ? `Working directory: ${params.cwd}` : "",
|
|
226
233
|
params.permissionMode ? `Permission mode: ${params.permissionMode}` : "",
|
|
227
234
|
"",
|
|
235
|
+
isChained(params.toolName, params.toolInput) ? compound_command_1.CHAINED_LINE : "",
|
|
228
236
|
"Allowing runs it on that machine, now. If you did not expect this, deny.",
|
|
229
237
|
].filter((l) => l !== undefined);
|
|
230
238
|
return clip(lines.join("\n").replace(/\n{3,}/g, "\n\n").trim(), QUESTION_MAX);
|
|
@@ -291,7 +299,7 @@ async function decideApproval(rawStdin, argv = [], deps = {}) {
|
|
|
291
299
|
// re-arms across bounded holds until somebody taps (approval-wait.ts). Every outcome that is
|
|
292
300
|
// not "answered with index 0" becomes a deny below.
|
|
293
301
|
const outcome = await (0, approval_wait_1.askOwnerAndWait)(device, { session: opts.session, question, options: [...exports.APPROVE_OPTIONS] }, deadline, { now, sleep, log }, "baychat approve-hook");
|
|
294
|
-
return verdictOf(outcome, opts.waitSeconds);
|
|
302
|
+
return verdictOf(outcome, opts.waitSeconds, isChained(toolName, input.tool_input));
|
|
295
303
|
}
|
|
296
304
|
catch (err) {
|
|
297
305
|
// The catch that makes the guarantee true. Anything at all — a thrown non-Error, an OOM in a
|
|
@@ -309,12 +317,13 @@ async function decideApproval(rawStdin, argv = [], deps = {}) {
|
|
|
309
317
|
* asymmetry is the point: a future server change can make this hook over-refuse, and can never
|
|
310
318
|
* make it over-permit.
|
|
311
319
|
*/
|
|
312
|
-
function verdictOf(outcome, waitSeconds) {
|
|
320
|
+
function verdictOf(outcome, waitSeconds, chained = false) {
|
|
313
321
|
switch (outcome.kind) {
|
|
314
322
|
case "answered":
|
|
315
323
|
if (outcome.index === ALLOW_INDEX)
|
|
316
324
|
return allowDecision();
|
|
317
|
-
|
|
325
|
+
// The deny's message is what Claude reads: the way out of a refused chain, not a retry of it.
|
|
326
|
+
return deny(`answered "${outcome.answer ?? exports.APPROVE_OPTIONS[1]}" in BayChat.`, chained ? compound_command_1.SPLIT_HINT : "");
|
|
318
327
|
case "cancelled":
|
|
319
328
|
return deny("the decision was withdrawn in BayChat without an answer.");
|
|
320
329
|
case "deadline":
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.claimDeniedEvent = claimDeniedEvent;
|
|
37
|
+
// One card per denial. A session BayChat launched has its own PermissionDenied hook (in its
|
|
38
|
+
// `--settings` file) AND the BayChat plugin's (hooks/hooks.json), and Claude Code runs both on
|
|
39
|
+
// the same event: the first to create the event's claim file asks; the other leaves it.
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const config_1 = require("../config");
|
|
43
|
+
/** Claims older than this are swept on the next claim: a denial is answered within its hook's wait. */
|
|
44
|
+
const CLAIM_TTL_MS = 2 * 24 * 3600_000;
|
|
45
|
+
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
46
|
+
const claimsDir = () => path.join((0, config_1.configDir)(), "denied-claims");
|
|
47
|
+
/** True for the first hook to claim `toolUseId`. An id that is no safe file name claims nothing. */
|
|
48
|
+
function claimDeniedEvent(toolUseId, nowMs = Date.now()) {
|
|
49
|
+
if (!SAFE_ID.test(toolUseId))
|
|
50
|
+
return false;
|
|
51
|
+
const dir = claimsDir();
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
53
|
+
sweep(dir, nowMs);
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(path.join(dir, toolUseId), "", { flag: "wx", mode: 0o600 });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (err.code === "EEXIST")
|
|
60
|
+
return false;
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function sweep(dir, nowMs) {
|
|
65
|
+
for (const name of fs.readdirSync(dir)) {
|
|
66
|
+
try {
|
|
67
|
+
const file = path.join(dir, name);
|
|
68
|
+
if (nowMs - fs.statSync(file).mtimeMs > CLAIM_TTL_MS)
|
|
69
|
+
fs.rmSync(file, { force: true });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* gone already, or another hook's: the next sweep tries again */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Is a shell command ONE command, or several? Read the way a shell reads it — quotes and
|
|
3
|
+
// backslashes first — never by searching the text for `&&`: `echo "a && b"` is one command.
|
|
4
|
+
//
|
|
5
|
+
// It matters because Claude Code checks a chain subcommand by subcommand (permissions docs,
|
|
6
|
+
// "Compound commands"): a chain can only be allowed once, and the chain as typed never matches
|
|
7
|
+
// the rules saved for it. Pure: no files, no network.
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.SPLIT_HINT = exports.CHAINED_LINE = void 0;
|
|
10
|
+
exports.compoundKind = compoundKind;
|
|
11
|
+
/** Why `command` is more than one command ("nested" wins over "chain"), or null when it is one. Never throws. */
|
|
12
|
+
function compoundKind(command) {
|
|
13
|
+
let quote = null;
|
|
14
|
+
let chain = false;
|
|
15
|
+
for (let i = 0; i < command.length; i++) {
|
|
16
|
+
const c = command[i];
|
|
17
|
+
const next = command[i + 1];
|
|
18
|
+
if (quote === "'") {
|
|
19
|
+
if (c === "'")
|
|
20
|
+
quote = null;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (c === "\\") {
|
|
24
|
+
i++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// The shell still runs a substitution inside double quotes.
|
|
28
|
+
if (c === "`" || (c === "$" && next === "("))
|
|
29
|
+
return "nested";
|
|
30
|
+
if (quote === '"') {
|
|
31
|
+
if (c === '"')
|
|
32
|
+
quote = null;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (c === "'" || c === '"')
|
|
36
|
+
quote = c;
|
|
37
|
+
else if (c === "(" || c === ")")
|
|
38
|
+
return "nested";
|
|
39
|
+
else if (c === ";" || c === "|" || c === "\n" || c === "\r")
|
|
40
|
+
chain = true;
|
|
41
|
+
// `&&` is a chain; a lone `&` sends to the background; `2>&1` and `&>` are redirections.
|
|
42
|
+
else if (c === "&" && (next === "&" || (command[i - 1] !== ">" && next !== ">")))
|
|
43
|
+
chain = true;
|
|
44
|
+
}
|
|
45
|
+
return chain ? "chain" : null;
|
|
46
|
+
}
|
|
47
|
+
/** The card's line for a command that is more than one (approve-hook, permission-rule). */
|
|
48
|
+
exports.CHAINED_LINE = "This is a chained command: it can only be allowed once.";
|
|
49
|
+
/** What the agent is told when a chain is refused: Claude Code reads a PermissionRequest deny's message. */
|
|
50
|
+
exports.SPLIT_HINT = "It was a chained command: do not retry the chain. Run single commands, or write a script to a file " +
|
|
51
|
+
"and run it with one command, using absolute paths.";
|
package/dist/denied/opt-in.js
CHANGED
|
@@ -33,36 +33,50 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DENIED_CARD_DEFAULT = void 0;
|
|
37
|
+
exports.deniedCardMode = deniedCardMode;
|
|
36
38
|
exports.deniedCardEnabled = deniedCardEnabled;
|
|
37
39
|
exports.enableDeniedCard = enableDeniedCard;
|
|
38
40
|
exports.disableDeniedCard = disableDeniedCard;
|
|
39
41
|
// Whether this computer's owner turned the denied-command card on (`baychat permissions on`).
|
|
40
42
|
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// `~/.baychat`, read when BayChat's
|
|
44
|
-
// (relay/launcher/approval-settings.ts),
|
|
43
|
+
// "ALLOW ONCE" UNTIL SOMEBODY ASKS FOR MORE: the card lets a phone add a permission rule on this
|
|
44
|
+
// computer, so keeping one ("Always") needs a deliberate act at the terminal; `DENIED_CARD_DEFAULT`
|
|
45
|
+
// is the owner's call for "nobody chose". The switch is a file in `~/.baychat`, read when BayChat's
|
|
46
|
+
// launcher writes a session's `--settings` file (relay/launcher/approval-settings.ts), and by the
|
|
47
|
+
// plugin's own hook on each denial (denied-hook --plugin), for sessions started by hand.
|
|
45
48
|
const fs = __importStar(require("fs"));
|
|
46
49
|
const path = __importStar(require("path"));
|
|
47
50
|
const config_1 = require("../config");
|
|
48
51
|
const optInFile = () => path.join((0, config_1.configDir)(), "denied-card.json");
|
|
49
|
-
/**
|
|
50
|
-
|
|
52
|
+
/** WHEN NOBODY CHOSE (no switch file): the owner's decision, kept as one line. Karmen, 2026-10-29: on,
|
|
53
|
+
* "Allow once" only — each tap allows one exact command one time; "Always" needs `baychat permissions on`. */
|
|
54
|
+
exports.DENIED_CARD_DEFAULT = "once";
|
|
55
|
+
/** The switch: what its file says, the default without one, off on any doubt (unreadable, malformed). */
|
|
56
|
+
function deniedCardMode() {
|
|
51
57
|
try {
|
|
52
|
-
const
|
|
53
|
-
return
|
|
58
|
+
const enabled = JSON.parse(fs.readFileSync(optInFile(), "utf8"))?.enabled;
|
|
59
|
+
return enabled === true ? "on" : "off";
|
|
54
60
|
}
|
|
55
61
|
catch (err) {
|
|
56
|
-
if (err.code
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
return
|
|
62
|
+
if (err.code === "ENOENT")
|
|
63
|
+
return exports.DENIED_CARD_DEFAULT;
|
|
64
|
+
process.stderr.write(`baychat: ${optInFile()} could not be read, so the denied-command card is off.\n`);
|
|
65
|
+
return "off";
|
|
60
66
|
}
|
|
61
67
|
}
|
|
68
|
+
/** True only when the owner turned the card on, Always included. */
|
|
69
|
+
function deniedCardEnabled() {
|
|
70
|
+
return deniedCardMode() === "on";
|
|
71
|
+
}
|
|
62
72
|
function enableDeniedCard(now = new Date()) {
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
writeSwitch(true, now);
|
|
74
|
+
}
|
|
75
|
+
/** Written, not deleted: an explicit off outranks any default. */
|
|
76
|
+
function disableDeniedCard(now = new Date()) {
|
|
77
|
+
writeSwitch(false, now);
|
|
65
78
|
}
|
|
66
|
-
function
|
|
67
|
-
fs.
|
|
79
|
+
function writeSwitch(enabled, now) {
|
|
80
|
+
fs.mkdirSync((0, config_1.configDir)(), { recursive: true, mode: 0o700 });
|
|
81
|
+
fs.writeFileSync(optInFile(), `${JSON.stringify({ enabled, since: now.toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
68
82
|
}
|
|
@@ -22,17 +22,19 @@
|
|
|
22
22
|
// The card text is built from `shown`, which masks obvious secrets; the rule is built from the
|
|
23
23
|
// real text, because a rule that differs from the call matches nothing.
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.MAX_RULE_COMMAND_LENGTH = void 0;
|
|
25
|
+
exports.CHAINED_LIMITATION = exports.MAX_RULE_COMMAND_LENGTH = void 0;
|
|
26
26
|
exports.planDeniedCall = planDeniedCall;
|
|
27
27
|
exports.callKeyOf = callKeyOf;
|
|
28
28
|
exports.sameCall = sameCall;
|
|
29
29
|
exports.redactSecrets = redactSecrets;
|
|
30
|
+
const compound_command_1 = require("./compound-command");
|
|
30
31
|
/** Longer than this is not a command a person can check on a phone, nor one a rule should hold. */
|
|
31
32
|
exports.MAX_RULE_COMMAND_LENGTH = 1000;
|
|
32
33
|
/** Claude Code itself saves at most five rules for one approved chain (permissions docs). */
|
|
33
34
|
const MAX_CHAIN_PARTS = 5;
|
|
35
|
+
/** What the card says for every chain: the agent reads it too (the deny message, approve-hook). */
|
|
36
|
+
exports.CHAINED_LIMITATION = `${compound_command_1.CHAINED_LINE} Ask the session to split it into single commands or a script it runs with one command.`;
|
|
34
37
|
/** `&&`, `||`, `|&`, `;`, `|`, and a lone `&` that is not part of a redirection (`2>&1`, `&>`). */
|
|
35
|
-
const CHAIN_OPERATOR = /&&|\|\||\|&|;|\||(?<![>&])&(?![>&])/;
|
|
36
38
|
const CHAIN_SPLIT = /\s*(?:&&|\|\||\|&|;|\||(?<![>&])&(?![>&]))\s*/;
|
|
37
39
|
const NESTED_COMMAND = /`|\$\(|[()]/;
|
|
38
40
|
const MCP_TOOL = /^mcp__[A-Za-z0-9_-]+__[A-Za-z0-9_.-]+$/;
|
|
@@ -75,23 +77,22 @@ function planBash(command) {
|
|
|
75
77
|
"approve it at the computer.");
|
|
76
78
|
}
|
|
77
79
|
const call = { tool: "Bash", subject: command };
|
|
78
|
-
|
|
80
|
+
// A chain as the shell reads it (compound-command.ts): `echo "a && b"` is one command.
|
|
81
|
+
if ((0, compound_command_1.compoundKind)(command) === "chain") {
|
|
79
82
|
if (/["'\\]/.test(command)) {
|
|
80
|
-
return nothing(shown,
|
|
81
|
-
"run them one at a time.");
|
|
83
|
+
return nothing(shown, `${exports.CHAINED_LIMITATION} It also quotes text, so BayChat cannot split it safely; approve it at the computer.`);
|
|
82
84
|
}
|
|
83
85
|
const parts = command.split(CHAIN_SPLIT);
|
|
84
86
|
if (parts.some((p) => p === "") || parts.length > MAX_CHAIN_PARTS) {
|
|
85
|
-
return nothing(shown,
|
|
87
|
+
return nothing(shown, `${exports.CHAINED_LIMITATION} BayChat cannot split this one; approve it at the computer.`);
|
|
86
88
|
}
|
|
87
89
|
return {
|
|
88
90
|
shown,
|
|
89
91
|
onceRules: [...new Set(parts.map((p) => `Bash(${p})`))],
|
|
90
92
|
alwaysAllowed: false,
|
|
91
93
|
// One plain line on WHY, because the usual cause is the agent's own habit (`cd … && x | tail`)
|
|
92
|
-
// and the fix is for the session to run
|
|
93
|
-
limitation:
|
|
94
|
-
`command. Run it on its own (no cd … &&, no | tail) to be offered "Always".`,
|
|
94
|
+
// and the fix is for the session to run single commands.
|
|
95
|
+
limitation: exports.CHAINED_LIMITATION,
|
|
95
96
|
call,
|
|
96
97
|
};
|
|
97
98
|
}
|
package/dist/denied-hook.js
CHANGED
|
@@ -88,11 +88,13 @@ const approve_hook_1 = require("./approve-hook");
|
|
|
88
88
|
const approval_wait_1 = require("./approval-wait");
|
|
89
89
|
const config_1 = require("./config");
|
|
90
90
|
const hook_stdin_1 = require("./hook-stdin");
|
|
91
|
+
const claims_1 = require("./denied/claims");
|
|
91
92
|
const confirm_1 = require("./denied/confirm");
|
|
92
93
|
const grants_1 = require("./denied/grants");
|
|
93
94
|
const hook_log_1 = require("./denied/hook-log");
|
|
94
95
|
const live_session_1 = require("./denied/live-session");
|
|
95
96
|
const notice_1 = require("./denied/notice");
|
|
97
|
+
const opt_in_1 = require("./denied/opt-in");
|
|
96
98
|
const permission_rule_1 = require("./denied/permission-rule");
|
|
97
99
|
const settings_rules_1 = require("./denied/settings-rules");
|
|
98
100
|
const transcript_1 = require("./denied/transcript");
|
|
@@ -180,6 +182,7 @@ function parseArgs(argv) {
|
|
|
180
182
|
return {
|
|
181
183
|
session: at("--session"),
|
|
182
184
|
fromClaude: argv.includes("--session-from-claude"),
|
|
185
|
+
plugin: argv.includes("--plugin"),
|
|
183
186
|
waitSeconds: Number.isFinite(rawTimeout) && rawTimeout > 0 ? rawTimeout : approve_hook_1.DEFAULT_WAIT_SECONDS,
|
|
184
187
|
};
|
|
185
188
|
}
|
|
@@ -227,6 +230,22 @@ async function decideDenied(rawStdin, argv = [], deps = {}) {
|
|
|
227
230
|
log(`${LOG_LABEL}: this session has not joined BayChat yet; the denial stands.`);
|
|
228
231
|
return null;
|
|
229
232
|
}
|
|
233
|
+
// The switch decides on every denial: the plugin's hook runs in every session, and a launched
|
|
234
|
+
// session's own hook was written while the switch was not off (it may have changed since).
|
|
235
|
+
const mode = (deps.cardMode ?? opt_in_1.deniedCardMode)();
|
|
236
|
+
if (mode === "off") {
|
|
237
|
+
log(`${LOG_LABEL}: the denied-command card is off on this computer; the denial stands.`);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const eventId = str(input.tool_use_id);
|
|
241
|
+
if (opts.plugin && !eventId) {
|
|
242
|
+
log(`${LOG_LABEL}: the event has no tool_use_id to take it by; the denial stands.`);
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
if (eventId && !(deps.claimEvent ?? claims_1.claimDeniedEvent)(eventId)) {
|
|
246
|
+
log(`${LOG_LABEL}: another BayChat hook already took this denial.`);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
230
249
|
const device = (deps.loadDevice ?? config_1.loadDeviceCredentials)();
|
|
231
250
|
if (!device) {
|
|
232
251
|
log(`${LOG_LABEL}: this machine is not logged in to BayChat (\`baychat login\`); the denial stands.`);
|
|
@@ -301,7 +320,13 @@ async function decideDenied(rawStdin, argv = [], deps = {}) {
|
|
|
301
320
|
limitation: `BayChat can't allow it from here: ${choice.why}, so no rule can be saved. ` +
|
|
302
321
|
"Approve it at the computer (/permissions → Recently denied).",
|
|
303
322
|
}
|
|
304
|
-
: planned
|
|
323
|
+
: mode === "once" && planned.alwaysAllowed
|
|
324
|
+
? {
|
|
325
|
+
...planned,
|
|
326
|
+
alwaysAllowed: false,
|
|
327
|
+
limitation: "\"Always\" is off on this computer (`baychat permissions on` at the computer turns it on).",
|
|
328
|
+
}
|
|
329
|
+
: planned;
|
|
305
330
|
const target = choice.scope === "none" ? undefined : choice;
|
|
306
331
|
const question = composeDeniedQuestion({
|
|
307
332
|
who,
|
package/dist/mcp.js
CHANGED
|
@@ -252,6 +252,9 @@ async function handleSendMessage(args) {
|
|
|
252
252
|
// message that quotes nothing must not claim to quote something.
|
|
253
253
|
...(args.replyToMessageId ? { replyToMessageId: args.replyToMessageId } : {}),
|
|
254
254
|
...(args.card ? { card: args.card } : {}),
|
|
255
|
+
...(args.threadRootId ? { threadRootId: args.threadRootId } : {}),
|
|
256
|
+
...(args.handoff !== undefined ? { handoff: args.handoff } : {}),
|
|
257
|
+
...(args.result !== undefined ? { result: args.result } : {}),
|
|
255
258
|
});
|
|
256
259
|
const carried = ids.length > 0 ? ` with ${ids.length} attachment${ids.length === 1 ? "" : "s"}` : "";
|
|
257
260
|
return (0, mcp_result_1.ok)(`Sent message ${message.id} at ${message.createdAt}${carried}.`, message);
|
|
@@ -26,9 +26,12 @@ async function cmdPermissions(args, deps = {}) {
|
|
|
26
26
|
const [sub = "list", ...rest] = args;
|
|
27
27
|
switch (sub) {
|
|
28
28
|
case "list": {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const mode = (0, opt_in_1.deniedCardMode)();
|
|
30
|
+
out(mode === "on"
|
|
31
|
+
? "The denied-command card is ON, with Always, for sessions joined to BayChat (`baychat permissions off` turns it off)."
|
|
32
|
+
: mode === "once"
|
|
33
|
+
? "The denied-command card offers Allow once for sessions joined to BayChat (`baychat permissions on` adds Always, `off` turns it off)."
|
|
34
|
+
: "The denied-command card is OFF (`baychat permissions on` turns it on).");
|
|
32
35
|
const grants = (0, grants_1.listGrants)();
|
|
33
36
|
if (grants.length === 0) {
|
|
34
37
|
out("BayChat keeps no permission rules on this computer.");
|
|
@@ -60,13 +63,13 @@ async function cmdPermissions(args, deps = {}) {
|
|
|
60
63
|
return 1;
|
|
61
64
|
}
|
|
62
65
|
(0, opt_in_1.enableDeniedCard)();
|
|
63
|
-
out("The denied-command card is ON
|
|
66
|
+
out("The denied-command card is ON, with Always. A session joined to BayChat puts a card in BayChat when");
|
|
64
67
|
out("Claude Code's auto mode blocks a call. Read docs/features/DENIED_COMMAND_CARD.md for what it can and can't allow.");
|
|
65
68
|
return 0;
|
|
66
69
|
}
|
|
67
70
|
case "off":
|
|
68
71
|
(0, opt_in_1.disableDeniedCard)();
|
|
69
|
-
out("The denied-command card is OFF
|
|
72
|
+
out("The denied-command card is OFF. Rules it kept stay until removed.");
|
|
70
73
|
return 0;
|
|
71
74
|
case "sweep": {
|
|
72
75
|
const now = deps.now ?? Date.now;
|