reasonix 0.26.0 → 0.27.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/dist/index.d.ts CHANGED
@@ -446,6 +446,8 @@ declare class ImmutablePrefix {
446
446
  toMessages(): ChatMessage[];
447
447
  tools(): ToolSpec[];
448
448
  addTool(spec: ToolSpec): boolean;
449
+ /** Mirror of addTool for MCP hot-unbridge. Same cache-miss cost — prefix changes shape. */
450
+ removeTool(name: string): boolean;
449
451
  get fingerprint(): string;
450
452
  /** Dev/test only — throws on cache drift, which always means a non-`addTool` mutation slipped in. */
451
453
  verifyFingerprint(): string;
@@ -619,6 +621,8 @@ declare class ToolRegistry {
619
621
  /** At most one interceptor active; calling twice replaces. */
620
622
  setToolInterceptor(fn: ToolInterceptor | null): void;
621
623
  register<A, R>(def: ToolDefinition<A, R>): this;
624
+ /** Drop a registered tool. Returns true if the name was present. Used by MCP hot-unbridge. */
625
+ unregister(name: string): boolean;
622
626
  has(name: string): boolean;
623
627
  get(name: string): ToolDefinition | undefined;
624
628
  get size(): number;
@@ -1191,7 +1195,7 @@ interface ShellToolsOptions {
1191
1195
  declare function tokenizeCommand(cmd: string): string[];
1192
1196
  /** Up-front detection — without it, `dir | findstr foo` quotes `|` literal and pipe silently fails. */
1193
1197
  declare function detectShellOperator(cmd: string): string | null;
1194
- /** Match on space-normalized leading tokens `git status -s` matches the `git status` prefix. */
1198
+ /** Allowlist match on leading argv tokens; demoted by `RISKY_ARGS` when a destructive flag appears in the tail. */
1195
1199
  declare function isAllowed(cmd: string, extra?: readonly string[]): boolean;
1196
1200
  interface RunCommandResult {
1197
1201
  exitCode: number | null;
package/dist/index.js CHANGED
@@ -981,6 +981,10 @@ var ToolRegistry = class {
981
981
  this._tools.set(def.name, internal);
982
982
  return this;
983
983
  }
984
+ /** Drop a registered tool. Returns true if the name was present. Used by MCP hot-unbridge. */
985
+ unregister(name) {
986
+ return this._tools.delete(name);
987
+ }
984
988
  has(name) {
985
989
  return this._tools.has(name);
986
990
  }
@@ -1645,6 +1649,14 @@ var ImmutablePrefix = class {
1645
1649
  this._fingerprintCache = null;
1646
1650
  return true;
1647
1651
  }
1652
+ /** Mirror of addTool for MCP hot-unbridge. Same cache-miss cost — prefix changes shape. */
1653
+ removeTool(name) {
1654
+ const idx = this._toolSpecs.findIndex((t) => t.function?.name === name);
1655
+ if (idx < 0) return false;
1656
+ this._toolSpecs.splice(idx, 1);
1657
+ this._fingerprintCache = null;
1658
+ return true;
1659
+ }
1648
1660
  get fingerprint() {
1649
1661
  if (this._fingerprintCache !== null) return this._fingerprintCache;
1650
1662
  this._fingerprintCache = this.computeFingerprint();
@@ -6406,12 +6418,65 @@ function detectShellOperator(cmd) {
6406
6418
  if (quote) return null;
6407
6419
  return check();
6408
6420
  }
6421
+ var RISKY_ARGS = {
6422
+ // Branch / remote mutation
6423
+ "git branch": ["-d", "-D", "--delete", "-m", "-M", "--move", "-c", "-C", "--copy", "--force"],
6424
+ "git remote": ["add", "remove", "rm", "rename", "set-url", "set-head", "prune"],
6425
+ // `--output` writes to an arbitrary path; `--ext-diff` invokes user-config'd external programs.
6426
+ "git diff": ["--output", "--ext-diff"],
6427
+ "git log": ["--output"],
6428
+ "git show": ["--output"],
6429
+ // `-exec*` / `-ok*` are RCE; `-delete` and `-fprint*` / `-fls` write to arbitrary paths.
6430
+ find: [
6431
+ "-delete",
6432
+ "-exec",
6433
+ "-execdir",
6434
+ "-ok",
6435
+ "-okdir",
6436
+ "-fprint",
6437
+ "-fprint0",
6438
+ "-fprintf",
6439
+ "-fls"
6440
+ ],
6441
+ // `-o FILE` writes the tree to an arbitrary path.
6442
+ tree: ["-o"],
6443
+ // Auto-fix mutates source files.
6444
+ "npx eslint": ["--fix", "--fix-dry-run"],
6445
+ "npx biome check": ["--write", "--apply", "--apply-unsafe"],
6446
+ ruff: ["--fix", "--unsafe-fixes", "format"]
6447
+ };
6448
+ function tailHasRisky(tail, risky) {
6449
+ for (const a of tail) {
6450
+ for (const r of risky) {
6451
+ if (a === r) return true;
6452
+ if (a.startsWith(`${r}=`)) return true;
6453
+ }
6454
+ }
6455
+ return false;
6456
+ }
6409
6457
  function isAllowed(cmd, extra = []) {
6410
- const normalized = cmd.trim().replace(/\s+/g, " ");
6458
+ let argv;
6459
+ try {
6460
+ argv = tokenizeCommand(cmd);
6461
+ } catch {
6462
+ return false;
6463
+ }
6464
+ if (argv.length === 0) return false;
6411
6465
  const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
6412
6466
  for (const prefix of allowlist) {
6413
- if (normalized === prefix) return true;
6414
- if (normalized.startsWith(`${prefix} `)) return true;
6467
+ const prefixTokens = prefix.split(" ");
6468
+ if (argv.length < prefixTokens.length) continue;
6469
+ let match = true;
6470
+ for (let i = 0; i < prefixTokens.length; i++) {
6471
+ if (argv[i] !== prefixTokens[i]) {
6472
+ match = false;
6473
+ break;
6474
+ }
6475
+ }
6476
+ if (!match) continue;
6477
+ const risky = RISKY_ARGS[prefix];
6478
+ if (risky && tailHasRisky(argv.slice(prefixTokens.length), risky)) return false;
6479
+ return true;
6415
6480
  }
6416
6481
  return false;
6417
6482
  }