reasonix 0.11.1 → 0.11.3
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 +136 -4
- package/README.zh-CN.md +118 -3
- package/dist/cli/index.js +459 -22
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +86 -10
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -70,6 +70,29 @@ function addProjectShellAllowed(rootDir, prefix, path5 = defaultConfigPath()) {
|
|
|
70
70
|
cfg.projects[rootDir].shellAllowed = [...existing, trimmed];
|
|
71
71
|
writeConfig(cfg, path5);
|
|
72
72
|
}
|
|
73
|
+
function removeProjectShellAllowed(rootDir, prefix, path5 = defaultConfigPath()) {
|
|
74
|
+
const trimmed = prefix.trim();
|
|
75
|
+
if (!trimmed) return false;
|
|
76
|
+
const cfg = readConfig(path5);
|
|
77
|
+
const existing = cfg.projects?.[rootDir]?.shellAllowed ?? [];
|
|
78
|
+
if (!existing.includes(trimmed)) return false;
|
|
79
|
+
const next = existing.filter((p) => p !== trimmed);
|
|
80
|
+
if (!cfg.projects) cfg.projects = {};
|
|
81
|
+
if (!cfg.projects[rootDir]) cfg.projects[rootDir] = {};
|
|
82
|
+
cfg.projects[rootDir].shellAllowed = next;
|
|
83
|
+
writeConfig(cfg, path5);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
function clearProjectShellAllowed(rootDir, path5 = defaultConfigPath()) {
|
|
87
|
+
const cfg = readConfig(path5);
|
|
88
|
+
const existing = cfg.projects?.[rootDir]?.shellAllowed ?? [];
|
|
89
|
+
if (existing.length === 0) return 0;
|
|
90
|
+
if (!cfg.projects) cfg.projects = {};
|
|
91
|
+
if (!cfg.projects[rootDir]) cfg.projects[rootDir] = {};
|
|
92
|
+
cfg.projects[rootDir].shellAllowed = [];
|
|
93
|
+
writeConfig(cfg, path5);
|
|
94
|
+
return existing.length;
|
|
95
|
+
}
|
|
73
96
|
function loadEditMode(path5 = defaultConfigPath()) {
|
|
74
97
|
const v = readConfig(path5).editMode;
|
|
75
98
|
return v === "auto" ? "auto" : "review";
|
|
@@ -112,7 +135,7 @@ import { createParser } from "eventsource-parser";
|
|
|
112
135
|
|
|
113
136
|
// src/retry.ts
|
|
114
137
|
var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
|
|
115
|
-
async function fetchWithRetry(fetchFn, url,
|
|
138
|
+
async function fetchWithRetry(fetchFn, url, init2, opts = {}) {
|
|
116
139
|
const maxAttempts = opts.maxAttempts ?? 4;
|
|
117
140
|
const initial = opts.initialBackoffMs ?? 500;
|
|
118
141
|
const cap = opts.maxBackoffMs ?? 1e4;
|
|
@@ -121,7 +144,7 @@ async function fetchWithRetry(fetchFn, url, init, opts = {}) {
|
|
|
121
144
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
122
145
|
if (opts.signal?.aborted) throw new Error("aborted");
|
|
123
146
|
try {
|
|
124
|
-
const resp = await fetchFn(url,
|
|
147
|
+
const resp = await fetchFn(url, init2);
|
|
125
148
|
if (resp.ok || !retryable.has(resp.status)) return resp;
|
|
126
149
|
if (attempt === maxAttempts - 1) return resp;
|
|
127
150
|
await resp.text().catch(() => void 0);
|
|
@@ -6760,6 +6783,159 @@ var SseTransport = class {
|
|
|
6760
6783
|
}
|
|
6761
6784
|
};
|
|
6762
6785
|
|
|
6786
|
+
// src/mcp/streamable-http.ts
|
|
6787
|
+
import { createParser as createParser3 } from "eventsource-parser";
|
|
6788
|
+
var SESSION_HEADER = "mcp-session-id";
|
|
6789
|
+
var StreamableHttpTransport = class {
|
|
6790
|
+
url;
|
|
6791
|
+
extraHeaders;
|
|
6792
|
+
queue = [];
|
|
6793
|
+
waiters = [];
|
|
6794
|
+
controller = new AbortController();
|
|
6795
|
+
/** Session id minted by server on (typically) the initialize response. */
|
|
6796
|
+
sessionId = null;
|
|
6797
|
+
closed = false;
|
|
6798
|
+
/** Background SSE read-loops kicked off by send(); awaited on close(). */
|
|
6799
|
+
streams = /* @__PURE__ */ new Set();
|
|
6800
|
+
constructor(opts) {
|
|
6801
|
+
this.url = opts.url;
|
|
6802
|
+
this.extraHeaders = opts.headers ?? {};
|
|
6803
|
+
}
|
|
6804
|
+
async send(message) {
|
|
6805
|
+
if (this.closed) throw new Error("MCP Streamable HTTP transport is closed");
|
|
6806
|
+
const headers = {
|
|
6807
|
+
"content-type": "application/json",
|
|
6808
|
+
// Both accepted — server picks. application/json first signals a
|
|
6809
|
+
// mild preference for the simpler shape when the response is a
|
|
6810
|
+
// single message.
|
|
6811
|
+
accept: "application/json, text/event-stream",
|
|
6812
|
+
...this.extraHeaders
|
|
6813
|
+
};
|
|
6814
|
+
if (this.sessionId !== null) headers["mcp-session-id"] = this.sessionId;
|
|
6815
|
+
let res;
|
|
6816
|
+
try {
|
|
6817
|
+
res = await fetch(this.url, {
|
|
6818
|
+
method: "POST",
|
|
6819
|
+
headers,
|
|
6820
|
+
body: JSON.stringify(message),
|
|
6821
|
+
signal: this.controller.signal
|
|
6822
|
+
});
|
|
6823
|
+
} catch (err) {
|
|
6824
|
+
throw new Error(`MCP Streamable HTTP POST ${this.url} failed: ${err.message}`);
|
|
6825
|
+
}
|
|
6826
|
+
const serverSessionId = res.headers.get(SESSION_HEADER);
|
|
6827
|
+
if (serverSessionId && this.sessionId === null) {
|
|
6828
|
+
this.sessionId = serverSessionId;
|
|
6829
|
+
}
|
|
6830
|
+
if (res.status === 404 && this.sessionId !== null) {
|
|
6831
|
+
await res.body?.cancel().catch(() => void 0);
|
|
6832
|
+
throw new Error(
|
|
6833
|
+
`MCP Streamable HTTP session expired (server returned 404 with Mcp-Session-Id "${this.sessionId}"). Reinitialize the client.`
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
if (!res.ok) {
|
|
6837
|
+
const body = await res.text().catch(() => "");
|
|
6838
|
+
throw new Error(
|
|
6839
|
+
`MCP Streamable HTTP POST ${this.url} \u2192 ${res.status} ${res.statusText}${body ? `: ${body}` : ""}`
|
|
6840
|
+
);
|
|
6841
|
+
}
|
|
6842
|
+
if (res.status === 202) {
|
|
6843
|
+
await res.body?.cancel().catch(() => void 0);
|
|
6844
|
+
return;
|
|
6845
|
+
}
|
|
6846
|
+
const ct = (res.headers.get("content-type") ?? "").toLowerCase();
|
|
6847
|
+
if (ct.includes("application/json")) {
|
|
6848
|
+
let parsed;
|
|
6849
|
+
try {
|
|
6850
|
+
parsed = await res.json();
|
|
6851
|
+
} catch (err) {
|
|
6852
|
+
throw new Error(`MCP Streamable HTTP body wasn't valid JSON: ${err.message}`);
|
|
6853
|
+
}
|
|
6854
|
+
if (Array.isArray(parsed)) {
|
|
6855
|
+
for (const item of parsed) this.pushMessage(item);
|
|
6856
|
+
} else {
|
|
6857
|
+
this.pushMessage(parsed);
|
|
6858
|
+
}
|
|
6859
|
+
return;
|
|
6860
|
+
}
|
|
6861
|
+
if (ct.includes("text/event-stream")) {
|
|
6862
|
+
if (!res.body) {
|
|
6863
|
+
throw new Error("MCP Streamable HTTP SSE response had no body");
|
|
6864
|
+
}
|
|
6865
|
+
const stream = this.consumeStream(res.body);
|
|
6866
|
+
this.streams.add(stream);
|
|
6867
|
+
stream.finally(() => this.streams.delete(stream));
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6870
|
+
await res.body?.cancel().catch(() => void 0);
|
|
6871
|
+
}
|
|
6872
|
+
async *messages() {
|
|
6873
|
+
while (true) {
|
|
6874
|
+
if (this.queue.length > 0) {
|
|
6875
|
+
yield this.queue.shift();
|
|
6876
|
+
continue;
|
|
6877
|
+
}
|
|
6878
|
+
if (this.closed) return;
|
|
6879
|
+
const next = await new Promise((resolve12) => {
|
|
6880
|
+
this.waiters.push(resolve12);
|
|
6881
|
+
});
|
|
6882
|
+
if (next === null) return;
|
|
6883
|
+
yield next;
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
async close() {
|
|
6887
|
+
if (this.closed) return;
|
|
6888
|
+
this.closed = true;
|
|
6889
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
6890
|
+
try {
|
|
6891
|
+
this.controller.abort();
|
|
6892
|
+
} catch {
|
|
6893
|
+
}
|
|
6894
|
+
await Promise.allSettled(Array.from(this.streams));
|
|
6895
|
+
}
|
|
6896
|
+
/** Visible for tests — confirm session header round-trip. */
|
|
6897
|
+
getSessionId() {
|
|
6898
|
+
return this.sessionId;
|
|
6899
|
+
}
|
|
6900
|
+
// ---------- internals ----------
|
|
6901
|
+
async consumeStream(body) {
|
|
6902
|
+
const parser = createParser3({
|
|
6903
|
+
onEvent: (ev) => {
|
|
6904
|
+
const type = ev.event ?? "message";
|
|
6905
|
+
if (type !== "message") return;
|
|
6906
|
+
try {
|
|
6907
|
+
const parsed = JSON.parse(ev.data);
|
|
6908
|
+
this.pushMessage(parsed);
|
|
6909
|
+
} catch {
|
|
6910
|
+
}
|
|
6911
|
+
}
|
|
6912
|
+
});
|
|
6913
|
+
const decoder = new TextDecoder();
|
|
6914
|
+
try {
|
|
6915
|
+
for await (const chunk of body) {
|
|
6916
|
+
if (this.closed) break;
|
|
6917
|
+
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
6918
|
+
}
|
|
6919
|
+
} catch (err) {
|
|
6920
|
+
if (!this.closed) {
|
|
6921
|
+
this.pushMessage({
|
|
6922
|
+
jsonrpc: "2.0",
|
|
6923
|
+
id: null,
|
|
6924
|
+
error: {
|
|
6925
|
+
code: -32e3,
|
|
6926
|
+
message: `Streamable HTTP stream error: ${err.message}`
|
|
6927
|
+
}
|
|
6928
|
+
});
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6931
|
+
}
|
|
6932
|
+
pushMessage(msg) {
|
|
6933
|
+
const waiter = this.waiters.shift();
|
|
6934
|
+
if (waiter) waiter(msg);
|
|
6935
|
+
else this.queue.push(msg);
|
|
6936
|
+
}
|
|
6937
|
+
};
|
|
6938
|
+
|
|
6763
6939
|
// src/mcp/shell-split.ts
|
|
6764
6940
|
function shellSplit(input) {
|
|
6765
6941
|
const tokens = [];
|
|
@@ -6812,6 +6988,7 @@ function shellSplit(input) {
|
|
|
6812
6988
|
// src/mcp/spec.ts
|
|
6813
6989
|
var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$/;
|
|
6814
6990
|
var HTTP_URL = /^https?:\/\//i;
|
|
6991
|
+
var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
|
|
6815
6992
|
function parseMcpSpec(input) {
|
|
6816
6993
|
const trimmed = input.trim();
|
|
6817
6994
|
if (!trimmed) {
|
|
@@ -6823,6 +7000,10 @@ function parseMcpSpec(input) {
|
|
|
6823
7000
|
if (!body) {
|
|
6824
7001
|
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
6825
7002
|
}
|
|
7003
|
+
const streamMatch = STREAMABLE_PREFIX.exec(body);
|
|
7004
|
+
if (streamMatch) {
|
|
7005
|
+
return { transport: "streamable-http", name, url: streamMatch[1] };
|
|
7006
|
+
}
|
|
6826
7007
|
if (HTTP_URL.test(body)) {
|
|
6827
7008
|
return { transport: "sse", name, url: body };
|
|
6828
7009
|
}
|
|
@@ -7254,12 +7435,12 @@ function formatLogSize(path5 = defaultUsageLogPath()) {
|
|
|
7254
7435
|
}
|
|
7255
7436
|
|
|
7256
7437
|
// src/cli/commands/chat.tsx
|
|
7257
|
-
import { existsSync as
|
|
7438
|
+
import { existsSync as existsSync16, statSync as statSync9 } from "fs";
|
|
7258
7439
|
import { render } from "ink";
|
|
7259
7440
|
import React27, { useState as useState12 } from "react";
|
|
7260
7441
|
|
|
7261
7442
|
// src/cli/ui/App.tsx
|
|
7262
|
-
import * as
|
|
7443
|
+
import * as pathMod7 from "path";
|
|
7263
7444
|
import { Box as Box22, Static, Text as Text20, useApp, useStdout as useStdout8 } from "ink";
|
|
7264
7445
|
import React24, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
|
|
7265
7446
|
|
|
@@ -11579,6 +11760,12 @@ var SLASH_COMMANDS = [
|
|
|
11579
11760
|
argsHint: "[reload]",
|
|
11580
11761
|
summary: "list active hooks (settings.json under .reasonix/) \xB7 reload re-reads from disk"
|
|
11581
11762
|
},
|
|
11763
|
+
{
|
|
11764
|
+
cmd: "permissions",
|
|
11765
|
+
argsHint: "[list|add <prefix>|remove <prefix|N>|clear confirm]",
|
|
11766
|
+
summary: "show / edit shell allowlist (builtin read-only \xB7 per-project: ~/.reasonix/config.json)",
|
|
11767
|
+
argCompleter: ["list", "add", "remove", "clear"]
|
|
11768
|
+
},
|
|
11582
11769
|
{
|
|
11583
11770
|
cmd: "cwd",
|
|
11584
11771
|
argsHint: "<path>",
|
|
@@ -11626,6 +11813,13 @@ var SLASH_COMMANDS = [
|
|
|
11626
11813
|
},
|
|
11627
11814
|
{ cmd: "exit", summary: "quit the TUI" },
|
|
11628
11815
|
// Code-mode only
|
|
11816
|
+
{
|
|
11817
|
+
cmd: "init",
|
|
11818
|
+
argsHint: "[force]",
|
|
11819
|
+
summary: "scan the project and synthesize a baseline REASONIX.md (model writes; review with /apply). `force` overwrites an existing file.",
|
|
11820
|
+
contextual: "code",
|
|
11821
|
+
argCompleter: ["force"]
|
|
11822
|
+
},
|
|
11629
11823
|
{
|
|
11630
11824
|
cmd: "apply",
|
|
11631
11825
|
argsHint: "[N|N,M|N-M]",
|
|
@@ -12460,6 +12654,103 @@ var handlers3 = {
|
|
|
12460
12654
|
walk: walk2
|
|
12461
12655
|
};
|
|
12462
12656
|
|
|
12657
|
+
// src/cli/ui/slash/handlers/init.ts
|
|
12658
|
+
import { existsSync as existsSync15 } from "fs";
|
|
12659
|
+
import * as pathMod6 from "path";
|
|
12660
|
+
var INIT_PROMPT = [
|
|
12661
|
+
"# Task: Initialize REASONIX.md",
|
|
12662
|
+
"",
|
|
12663
|
+
"I want you to generate a REASONIX.md at the project root that captures",
|
|
12664
|
+
"the working knowledge a future Reasonix session needs to be productive",
|
|
12665
|
+
"here. This file is auto-pinned into your system prompt every launch,",
|
|
12666
|
+
"so its size and accuracy matter.",
|
|
12667
|
+
"",
|
|
12668
|
+
"## Hard constraints (do NOT relax these)",
|
|
12669
|
+
"",
|
|
12670
|
+
"- **Length cap: \u2264 80 lines / 3KB total.** Be concise. If you can't fit a",
|
|
12671
|
+
" section, drop it.",
|
|
12672
|
+
"- **Only document things you can verify by reading files.** Do NOT",
|
|
12673
|
+
" speculate about architectural intent, future roadmap, or design",
|
|
12674
|
+
" rationale. If it isn't obvious from the code, leave it out.",
|
|
12675
|
+
"- **No placeholder text.** No 'TODO: describe X', no 'Add more here'.",
|
|
12676
|
+
" Either state a fact or omit the section.",
|
|
12677
|
+
"",
|
|
12678
|
+
"## Procedure",
|
|
12679
|
+
"",
|
|
12680
|
+
"1. Read the top of any existing README* file.",
|
|
12681
|
+
"2. Read the manifest (package.json / Cargo.toml / pyproject.toml /",
|
|
12682
|
+
" go.mod / etc.) \u2014 pick whichever exists.",
|
|
12683
|
+
"3. `directory_tree` 1-2 levels deep on the project root, skipping",
|
|
12684
|
+
" common build/dependency dirs (node_modules, dist, target, .git,",
|
|
12685
|
+
" venv, __pycache__).",
|
|
12686
|
+
"4. Identify: primary language + framework, top-level layout, test",
|
|
12687
|
+
" runner, lint/format setup, build/run/test scripts, any non-obvious",
|
|
12688
|
+
" convention with visible evidence (commit message format, import",
|
|
12689
|
+
" order, naming pattern).",
|
|
12690
|
+
"5. Write REASONIX.md with the sections below, skipping any you can't",
|
|
12691
|
+
" fill from evidence.",
|
|
12692
|
+
"",
|
|
12693
|
+
"## Sections to use (skip ones with no evidence)",
|
|
12694
|
+
"",
|
|
12695
|
+
"- **Stack** \u2014 language + framework + 3-5 key deps. One line each.",
|
|
12696
|
+
"- **Layout** \u2014 top-level dirs and what lives in each. One line each.",
|
|
12697
|
+
"- **Commands** \u2014 verbatim from `scripts` block (or equivalent):",
|
|
12698
|
+
" build / test / lint / typecheck / dev / format. Whatever exists.",
|
|
12699
|
+
"- **Conventions** \u2014 only things visible in the code. Examples:",
|
|
12700
|
+
" '*.test.ts colocated with source', 'named exports only',",
|
|
12701
|
+
" 'commits use Conventional Commits prefix'. If you can't find any",
|
|
12702
|
+
" CONVENTION evidence, omit the whole section.",
|
|
12703
|
+
"- **Watch out for** \u2014 gotchas a new contributor would benefit from",
|
|
12704
|
+
" knowing BEFORE editing. Examples: 'edit_file SEARCH must match",
|
|
12705
|
+
" byte-for-byte', 'this dir is generated, don't edit by hand'.",
|
|
12706
|
+
" Omit if you find nothing concrete.",
|
|
12707
|
+
"",
|
|
12708
|
+
"## Output",
|
|
12709
|
+
"",
|
|
12710
|
+
"Write the result to `REASONIX.md` in the project root using the",
|
|
12711
|
+
"filesystem tools (edit_file with empty SEARCH if creating new,",
|
|
12712
|
+
"write_file if overwriting). After writing, STOP \u2014 do not summarize",
|
|
12713
|
+
"what you did, do not propose follow-up tasks. The user will review",
|
|
12714
|
+
"the pending edit via /apply.",
|
|
12715
|
+
"",
|
|
12716
|
+
"Start now."
|
|
12717
|
+
].join("\n");
|
|
12718
|
+
var init = (args, _loop, ctx) => {
|
|
12719
|
+
if (!ctx.codeRoot) {
|
|
12720
|
+
return {
|
|
12721
|
+
info: [
|
|
12722
|
+
"/init only works in code mode (it needs filesystem tools).",
|
|
12723
|
+
"Run `reasonix code [path]` to start a session rooted at the",
|
|
12724
|
+
"project you want to initialize, then run /init."
|
|
12725
|
+
].join("\n")
|
|
12726
|
+
};
|
|
12727
|
+
}
|
|
12728
|
+
const force = (args[0] ?? "").toLowerCase() === "force";
|
|
12729
|
+
const target = pathMod6.join(ctx.codeRoot, "REASONIX.md");
|
|
12730
|
+
if (existsSync15(target) && !force) {
|
|
12731
|
+
return {
|
|
12732
|
+
info: [
|
|
12733
|
+
`\u25B8 REASONIX.md already exists at ${target}`,
|
|
12734
|
+
"",
|
|
12735
|
+
" /init force regenerate from scratch (overwrites)",
|
|
12736
|
+
"",
|
|
12737
|
+
" Or edit it by hand \u2014 it's just markdown. The current file is",
|
|
12738
|
+
" pinned into the system prompt every launch as-is."
|
|
12739
|
+
].join("\n")
|
|
12740
|
+
};
|
|
12741
|
+
}
|
|
12742
|
+
return {
|
|
12743
|
+
info: [
|
|
12744
|
+
"\u25B8 /init \u2014 model will scan the project and synthesize REASONIX.md.",
|
|
12745
|
+
" The result lands as a pending edit; review with /apply or /walk."
|
|
12746
|
+
].join("\n"),
|
|
12747
|
+
resubmit: INIT_PROMPT
|
|
12748
|
+
};
|
|
12749
|
+
};
|
|
12750
|
+
var handlers4 = {
|
|
12751
|
+
init
|
|
12752
|
+
};
|
|
12753
|
+
|
|
12463
12754
|
// src/cli/ui/slash/handlers/jobs.ts
|
|
12464
12755
|
var jobs = (_args, _loop, ctx) => {
|
|
12465
12756
|
if (!ctx.jobs) {
|
|
@@ -12515,7 +12806,7 @@ $ ${out.command}`;
|
|
|
12515
12806
|
return { info: out.output ? `${header2}
|
|
12516
12807
|
${out.output}` : header2 };
|
|
12517
12808
|
};
|
|
12518
|
-
var
|
|
12809
|
+
var handlers5 = {
|
|
12519
12810
|
jobs,
|
|
12520
12811
|
kill,
|
|
12521
12812
|
logs
|
|
@@ -12576,7 +12867,7 @@ var mcp = (_args, loop2, ctx) => {
|
|
|
12576
12867
|
lines.push("To change this set, exit and run `reasonix setup`.");
|
|
12577
12868
|
return { info: lines.join("\n") };
|
|
12578
12869
|
};
|
|
12579
|
-
var
|
|
12870
|
+
var handlers6 = { mcp };
|
|
12580
12871
|
|
|
12581
12872
|
// src/cli/ui/slash/handlers/memory.ts
|
|
12582
12873
|
var memory = (args, _loop, ctx) => {
|
|
@@ -12711,7 +13002,7 @@ var memory = (args, _loop, ctx) => {
|
|
|
12711
13002
|
);
|
|
12712
13003
|
return { info: parts.join("\n") };
|
|
12713
13004
|
};
|
|
12714
|
-
var
|
|
13005
|
+
var handlers7 = { memory };
|
|
12715
13006
|
|
|
12716
13007
|
// src/cli/ui/slash/handlers/model.ts
|
|
12717
13008
|
var model = (args, loop2, ctx) => {
|
|
@@ -12864,7 +13155,7 @@ var pro = (args, loop2, ctx) => {
|
|
|
12864
13155
|
};
|
|
12865
13156
|
};
|
|
12866
13157
|
var ESCALATION_MODEL_ID = "deepseek-v4-pro";
|
|
12867
|
-
var
|
|
13158
|
+
var handlers8 = {
|
|
12868
13159
|
model,
|
|
12869
13160
|
models,
|
|
12870
13161
|
harvest: harvest2,
|
|
@@ -13017,7 +13308,7 @@ var compact = (args, loop2) => {
|
|
|
13017
13308
|
info: `\u25B8 compacted ${healedCount} payload(s) to ${cap.toLocaleString()} tokens each (tool results + tool-call args), saved ${tokensSaved.toLocaleString()} tokens (${charsSaved.toLocaleString()} chars). Session file rewritten.`
|
|
13018
13309
|
};
|
|
13019
13310
|
};
|
|
13020
|
-
var
|
|
13311
|
+
var handlers9 = {
|
|
13021
13312
|
think,
|
|
13022
13313
|
reasoning: think,
|
|
13023
13314
|
tool,
|
|
@@ -13026,6 +13317,150 @@ var handlers8 = {
|
|
|
13026
13317
|
compact
|
|
13027
13318
|
};
|
|
13028
13319
|
|
|
13320
|
+
// src/cli/ui/slash/handlers/permissions.ts
|
|
13321
|
+
var permissions = (args, _loop, ctx) => {
|
|
13322
|
+
const sub = (args[0] ?? "").toLowerCase();
|
|
13323
|
+
const root = ctx.codeRoot;
|
|
13324
|
+
const mode2 = ctx.editMode ?? null;
|
|
13325
|
+
if (sub === "" || sub === "list" || sub === "ls") {
|
|
13326
|
+
return { info: renderListing(root, mode2) };
|
|
13327
|
+
}
|
|
13328
|
+
if (!root) {
|
|
13329
|
+
return {
|
|
13330
|
+
info: "/permissions add / remove / clear are only available inside `reasonix code` \u2014 they edit the project-scoped allowlist (`~/.reasonix/config.json` projects[<root>].shellAllowed)."
|
|
13331
|
+
};
|
|
13332
|
+
}
|
|
13333
|
+
if (sub === "add") {
|
|
13334
|
+
const prefix = args.slice(1).join(" ").trim();
|
|
13335
|
+
if (!prefix) {
|
|
13336
|
+
return {
|
|
13337
|
+
info: 'usage: /permissions add <prefix> (multi-token OK: /permissions add "git push origin")'
|
|
13338
|
+
};
|
|
13339
|
+
}
|
|
13340
|
+
const before = loadProjectShellAllowed(root);
|
|
13341
|
+
if (before.includes(prefix)) {
|
|
13342
|
+
return { info: `\u25B8 already allowed: ${prefix}` };
|
|
13343
|
+
}
|
|
13344
|
+
if (BUILTIN_ALLOWLIST.includes(prefix)) {
|
|
13345
|
+
return {
|
|
13346
|
+
info: `\u25B8 \`${prefix}\` is already in the builtin allowlist \u2014 no per-project entry needed. (Builtin entries are always on.)`
|
|
13347
|
+
};
|
|
13348
|
+
}
|
|
13349
|
+
addProjectShellAllowed(root, prefix);
|
|
13350
|
+
return {
|
|
13351
|
+
info: `\u25B8 added: ${prefix}
|
|
13352
|
+
\u2192 next \`${prefix}\` invocation runs without prompting in this project.`
|
|
13353
|
+
};
|
|
13354
|
+
}
|
|
13355
|
+
if (sub === "remove" || sub === "rm" || sub === "delete") {
|
|
13356
|
+
const target = args.slice(1).join(" ").trim();
|
|
13357
|
+
if (!target) {
|
|
13358
|
+
return {
|
|
13359
|
+
info: "usage: /permissions remove <prefix-or-index> (e.g. /permissions remove 3, or /permissions remove npm)"
|
|
13360
|
+
};
|
|
13361
|
+
}
|
|
13362
|
+
const existing = loadProjectShellAllowed(root);
|
|
13363
|
+
let prefix = null;
|
|
13364
|
+
if (/^\d+$/.test(target)) {
|
|
13365
|
+
const idx = Number.parseInt(target, 10);
|
|
13366
|
+
if (idx < 1 || idx > existing.length) {
|
|
13367
|
+
return {
|
|
13368
|
+
info: existing.length === 0 ? "\u25B8 no project allowlist entries to remove." : `\u25B8 index out of range: ${idx} (project list has ${existing.length} entries)`
|
|
13369
|
+
};
|
|
13370
|
+
}
|
|
13371
|
+
prefix = existing[idx - 1] ?? null;
|
|
13372
|
+
} else {
|
|
13373
|
+
prefix = target;
|
|
13374
|
+
}
|
|
13375
|
+
if (prefix === null) return { info: "\u25B8 nothing to remove." };
|
|
13376
|
+
if (BUILTIN_ALLOWLIST.includes(prefix) && !existing.includes(prefix)) {
|
|
13377
|
+
return {
|
|
13378
|
+
info: `\u25B8 \`${prefix}\` is in the builtin allowlist (read-only). Builtin entries can't be removed at runtime \u2014 they're baked into the binary.`
|
|
13379
|
+
};
|
|
13380
|
+
}
|
|
13381
|
+
const ok = removeProjectShellAllowed(root, prefix);
|
|
13382
|
+
return {
|
|
13383
|
+
info: ok ? `\u25B8 removed: ${prefix}` : `\u25B8 no such project entry: ${prefix} (try /permissions list to see what's stored)`
|
|
13384
|
+
};
|
|
13385
|
+
}
|
|
13386
|
+
if (sub === "clear") {
|
|
13387
|
+
if ((args[1] ?? "").toLowerCase() !== "confirm") {
|
|
13388
|
+
const count = loadProjectShellAllowed(root).length;
|
|
13389
|
+
return {
|
|
13390
|
+
info: count === 0 ? "\u25B8 project allowlist is already empty." : `about to drop ${count} project allowlist entr${count === 1 ? "y" : "ies"} for ${root}. Re-run with the word 'confirm' to proceed: /permissions clear confirm`
|
|
13391
|
+
};
|
|
13392
|
+
}
|
|
13393
|
+
const dropped = clearProjectShellAllowed(root);
|
|
13394
|
+
return {
|
|
13395
|
+
info: dropped === 0 ? "\u25B8 project allowlist was already empty \u2014 nothing changed." : `\u25B8 cleared ${dropped} project allowlist entr${dropped === 1 ? "y" : "ies"}.`
|
|
13396
|
+
};
|
|
13397
|
+
}
|
|
13398
|
+
return {
|
|
13399
|
+
info: [
|
|
13400
|
+
"usage: /permissions [list] show current state",
|
|
13401
|
+
' /permissions add <prefix> persist (e.g. "npm run build")',
|
|
13402
|
+
" /permissions remove <prefix-or-N> drop one entry",
|
|
13403
|
+
" /permissions clear confirm wipe every project entry"
|
|
13404
|
+
].join("\n")
|
|
13405
|
+
};
|
|
13406
|
+
};
|
|
13407
|
+
function renderListing(root, mode2) {
|
|
13408
|
+
const lines = [];
|
|
13409
|
+
if (mode2 === "yolo") {
|
|
13410
|
+
lines.push(
|
|
13411
|
+
"\u25B8 edit mode: YOLO \u2014 every shell command auto-runs, allowlist is bypassed. /mode review to re-enable prompts."
|
|
13412
|
+
);
|
|
13413
|
+
} else if (mode2 === "auto") {
|
|
13414
|
+
lines.push(
|
|
13415
|
+
"\u25B8 edit mode: auto \u2014 edits auto-apply, shell still gated by allowlist (or ShellConfirm prompt for non-allowlisted)."
|
|
13416
|
+
);
|
|
13417
|
+
} else if (mode2 === "review") {
|
|
13418
|
+
lines.push(
|
|
13419
|
+
"\u25B8 edit mode: review \u2014 both edits and non-allowlisted shell commands ask before running."
|
|
13420
|
+
);
|
|
13421
|
+
}
|
|
13422
|
+
lines.push("");
|
|
13423
|
+
if (root) {
|
|
13424
|
+
const project = loadProjectShellAllowed(root);
|
|
13425
|
+
lines.push(`Project allowlist (${project.length}) \u2014 ${root}`);
|
|
13426
|
+
if (project.length === 0) {
|
|
13427
|
+
lines.push(' (none \u2014 pick "always allow" on a ShellConfirm prompt to add one,');
|
|
13428
|
+
lines.push(" or `/permissions add <prefix>` directly.)");
|
|
13429
|
+
} else {
|
|
13430
|
+
project.forEach((p, i) => {
|
|
13431
|
+
lines.push(` ${String(i + 1).padStart(2)}. ${p}`);
|
|
13432
|
+
});
|
|
13433
|
+
}
|
|
13434
|
+
} else {
|
|
13435
|
+
lines.push("Project allowlist \u2014 (no project root; chat mode shows builtin entries only)");
|
|
13436
|
+
}
|
|
13437
|
+
lines.push("");
|
|
13438
|
+
lines.push(`Builtin allowlist (${BUILTIN_ALLOWLIST.length}) \u2014 read-only, baked in`);
|
|
13439
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
13440
|
+
for (const entry of BUILTIN_ALLOWLIST) {
|
|
13441
|
+
const head = entry.split(" ")[0] ?? entry;
|
|
13442
|
+
if (!grouped.has(head)) grouped.set(head, []);
|
|
13443
|
+
grouped.get(head).push(entry);
|
|
13444
|
+
}
|
|
13445
|
+
for (const [head, items] of grouped) {
|
|
13446
|
+
if (items.length === 1 && items[0] === head) {
|
|
13447
|
+
lines.push(` \xB7 ${head}`);
|
|
13448
|
+
} else {
|
|
13449
|
+
const tail = items.map((i) => i.slice(head.length).trim() || "(bare)").join(", ");
|
|
13450
|
+
lines.push(` \xB7 ${head}: ${tail}`);
|
|
13451
|
+
}
|
|
13452
|
+
}
|
|
13453
|
+
lines.push("");
|
|
13454
|
+
lines.push(
|
|
13455
|
+
"Subcommands: /permissions add <prefix> \xB7 /permissions remove <prefix-or-N> \xB7 /permissions clear confirm"
|
|
13456
|
+
);
|
|
13457
|
+
return lines.join("\n");
|
|
13458
|
+
}
|
|
13459
|
+
var handlers10 = {
|
|
13460
|
+
permissions,
|
|
13461
|
+
perms: permissions
|
|
13462
|
+
};
|
|
13463
|
+
|
|
13029
13464
|
// src/cli/ui/slash/handlers/plans.ts
|
|
13030
13465
|
import { basename } from "path";
|
|
13031
13466
|
var plans = (_args, loop2) => {
|
|
@@ -13105,7 +13540,7 @@ var replay = (args, loop2) => {
|
|
|
13105
13540
|
}
|
|
13106
13541
|
};
|
|
13107
13542
|
};
|
|
13108
|
-
var
|
|
13543
|
+
var handlers11 = {
|
|
13109
13544
|
plans,
|
|
13110
13545
|
replay
|
|
13111
13546
|
};
|
|
@@ -13478,7 +13913,7 @@ async function readIndexMeta(rootDir) {
|
|
|
13478
13913
|
return null;
|
|
13479
13914
|
}
|
|
13480
13915
|
}
|
|
13481
|
-
var
|
|
13916
|
+
var handlers12 = {
|
|
13482
13917
|
semantic
|
|
13483
13918
|
};
|
|
13484
13919
|
|
|
@@ -13513,7 +13948,7 @@ var forget = (_args, loop2) => {
|
|
|
13513
13948
|
info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
|
|
13514
13949
|
};
|
|
13515
13950
|
};
|
|
13516
|
-
var
|
|
13951
|
+
var handlers13 = {
|
|
13517
13952
|
sessions,
|
|
13518
13953
|
forget
|
|
13519
13954
|
};
|
|
@@ -13589,7 +14024,7 @@ ${found.body}${argsLine}`;
|
|
|
13589
14024
|
resubmit: payload
|
|
13590
14025
|
};
|
|
13591
14026
|
};
|
|
13592
|
-
var
|
|
14027
|
+
var handlers14 = {
|
|
13593
14028
|
skill,
|
|
13594
14029
|
skills: skill
|
|
13595
14030
|
};
|
|
@@ -13607,7 +14042,9 @@ var HANDLERS = {
|
|
|
13607
14042
|
...handlers9,
|
|
13608
14043
|
...handlers10,
|
|
13609
14044
|
...handlers11,
|
|
13610
|
-
...handlers12
|
|
14045
|
+
...handlers12,
|
|
14046
|
+
...handlers13,
|
|
14047
|
+
...handlers14
|
|
13611
14048
|
};
|
|
13612
14049
|
function handleSlash(cmd, args, loop2, ctx = {}) {
|
|
13613
14050
|
const h = HANDLERS[cmd];
|
|
@@ -15403,8 +15840,8 @@ function App({
|
|
|
15403
15840
|
const parsed = JSON.parse(ev.toolArgs);
|
|
15404
15841
|
if (typeof parsed.path === "string" && parsed.path.trim()) {
|
|
15405
15842
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
15406
|
-
const expanded = parsed.path.startsWith("~") && home ?
|
|
15407
|
-
const abs =
|
|
15843
|
+
const expanded = parsed.path.startsWith("~") && home ? pathMod7.join(home, parsed.path.slice(1)) : parsed.path;
|
|
15844
|
+
const abs = pathMod7.resolve(expanded);
|
|
15408
15845
|
setPendingWorkspace({ path: abs });
|
|
15409
15846
|
}
|
|
15410
15847
|
} catch {
|
|
@@ -16408,7 +16845,7 @@ async function chatCommand(opts) {
|
|
|
16408
16845
|
try {
|
|
16409
16846
|
const spec = parseMcpSpec(raw);
|
|
16410
16847
|
const prefix = spec.name ? `${spec.name}_` : requestedSpecs.length === 1 && opts.mcpPrefix ? opts.mcpPrefix : "";
|
|
16411
|
-
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
16848
|
+
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : spec.transport === "streamable-http" ? new StreamableHttpTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
16412
16849
|
const mcp3 = new McpClient({ transport });
|
|
16413
16850
|
await mcp3.initialize();
|
|
16414
16851
|
const bridge = await bridgeMcpTools(mcp3, {
|
|
@@ -16430,7 +16867,7 @@ async function chatCommand(opts) {
|
|
|
16430
16867
|
};
|
|
16431
16868
|
}
|
|
16432
16869
|
const label = spec.name ?? "anon";
|
|
16433
|
-
const source = spec.transport === "sse" ? spec.url : `${spec.command} ${spec.args.join(" ")}`;
|
|
16870
|
+
const source = spec.transport === "sse" || spec.transport === "streamable-http" ? spec.url : `${spec.command} ${spec.args.join(" ")}`;
|
|
16434
16871
|
process.stderr.write(
|
|
16435
16872
|
`\u25B8 MCP[${label}]: ${bridge.registeredNames.length} tool(s) from ${source}
|
|
16436
16873
|
`
|
|
@@ -16473,7 +16910,7 @@ async function chatCommand(opts) {
|
|
|
16473
16910
|
const prior = loadSessionMessages(opts.session);
|
|
16474
16911
|
if (prior.length > 0) {
|
|
16475
16912
|
const p = sessionPath(opts.session);
|
|
16476
|
-
const mtime =
|
|
16913
|
+
const mtime = existsSync16(p) ? statSync9(p).mtime : /* @__PURE__ */ new Date();
|
|
16477
16914
|
sessionPreview = { messageCount: prior.length, lastActive: mtime };
|
|
16478
16915
|
}
|
|
16479
16916
|
} else if (opts.session && opts.forceNew) {
|
|
@@ -17559,7 +17996,7 @@ function makeTtyWriter() {
|
|
|
17559
17996
|
// src/cli/commands/mcp-inspect.ts
|
|
17560
17997
|
async function mcpInspectCommand(opts) {
|
|
17561
17998
|
const spec = parseMcpSpec(opts.spec);
|
|
17562
|
-
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
17999
|
+
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : spec.transport === "streamable-http" ? new StreamableHttpTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
17563
18000
|
const client = new McpClient({ transport });
|
|
17564
18001
|
try {
|
|
17565
18002
|
await client.initialize();
|
|
@@ -17891,11 +18328,11 @@ async function runCommand2(opts) {
|
|
|
17891
18328
|
try {
|
|
17892
18329
|
const spec = parseMcpSpec(raw);
|
|
17893
18330
|
const prefix2 = spec.name ? `${spec.name}_` : requestedSpecs.length === 1 && opts.mcpPrefix ? opts.mcpPrefix : "";
|
|
17894
|
-
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
18331
|
+
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : spec.transport === "streamable-http" ? new StreamableHttpTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
17895
18332
|
const mcp3 = new McpClient({ transport });
|
|
17896
18333
|
await mcp3.initialize();
|
|
17897
18334
|
const bridge = await bridgeMcpTools(mcp3, { registry: tools, namePrefix: prefix2 });
|
|
17898
|
-
const source = spec.transport === "sse" ? spec.url : `${spec.command} ${spec.args.join(" ")}`;
|
|
18335
|
+
const source = spec.transport === "sse" || spec.transport === "streamable-http" ? spec.url : `${spec.command} ${spec.args.join(" ")}`;
|
|
17899
18336
|
process.stderr.write(
|
|
17900
18337
|
`\u25B8 MCP[${spec.name ?? "anon"}]: ${bridge.registeredNames.length} tool(s) from ${source}
|
|
17901
18338
|
`
|