reasonix 0.5.20 → 0.5.21

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <em>Cache-first agent loop for DeepSeek V3 &amp; R1 — Ink TUI, MCP first-class, no LangChain.</em>
6
+ <em>Cache-first agent loop for DeepSeek V4 (flash + pro) — Ink TUI, MCP first-class, no LangChain.</em>
7
7
  </p>
8
8
 
9
9
  # Reasonix
@@ -133,6 +133,34 @@ shell call will execute. Use for high-stakes changes you want to
133
133
  audit before the model touches disk. `/plan off` or picker
134
134
  Approve/Cancel exits.
135
135
 
136
+ ### Prompt prefixes — `!cmd` and `@path`
137
+
138
+ Two inline shortcuts that don't need a slash:
139
+
140
+ **`!<cmd>` — run a shell command in the sandbox and feed it to the
141
+ model.** Typed at the prompt, like bash. Output lands in the visible
142
+ log AND in the session so the model's next turn reasons about it:
143
+
144
+ ```
145
+ reasonix code › !git status --short
146
+ ▸ M src/users.ts
147
+ ▸ M src/users.test.ts
148
+
149
+ reasonix code › 把这两个文件的改动说明一下
150
+ assistant
151
+ ▸ tool<read_file> → src/users.ts, src/users.test.ts
152
+ ▸ …
153
+ ```
154
+
155
+ No allowlist gate — user-typed shell = explicit consent. 60s timeout,
156
+ 32k char cap, survives session resume since 0.5.14.
157
+
158
+ **`@path/to/file` — inline a file under "Referenced files."** Start
159
+ typing `@` and a picker appears (↑/↓ navigate, Tab/Enter to insert).
160
+ Good for "what does @src/users.ts do?" without making the model
161
+ `read_file` it first. Sandboxed: relative paths only, no `..` escape,
162
+ 64KB per-file cap. Recent files rank higher.
163
+
136
164
  ### `/commit` — stage + commit in one step
137
165
 
138
166
  ```
@@ -145,12 +173,14 @@ reasonix code › /commit "fix: findByEmail case-insensitive"
145
173
 
146
174
  - `/tool 1` — dump the last tool call's full output (when the 400-char
147
175
  inline clip isn't enough).
148
- - `/think` — see the model's full R1 reasoning for the last turn
149
- (reasoner preset only).
176
+ - `/think` — see the model's full reasoning for the last turn
177
+ (thinking-mode models: v4-flash / v4-pro / reasoner alias).
150
178
  - `/undo` — roll back the last applied edit batch.
151
179
  - `/new` — start fresh in the same directory without losing the
152
180
  session file.
153
- - `npx reasonix code --preset max` R1 + 3-way self-consistency
181
+ - `/effort high` step down from the default `max` agent-class
182
+ reasoning_effort for cheaper/faster turns on simple tasks.
183
+ - `npx reasonix code --preset max` — v4-pro + 3-way self-consistency
154
184
  branching for gnarly refactors.
155
185
  - `npx reasonix code src/` — narrower sandbox (only `src/` is
156
186
  writable).
@@ -182,7 +212,9 @@ in the file. No prompts, no completions, no tool arguments.
182
212
  ### Staying current
183
213
 
184
214
  The panel header shows the running version next to `Reasonix` (e.g.
185
- `Reasonix v0.4.22 · model …`). A quiet 24-hour background check against
215
+ `Reasonix v0.5.21 · deepseek-v4-pro · harvest · max …`, the trailing
216
+ `max` is the reasoning-effort badge — `/effort high` to step down).
217
+ A quiet 24-hour background check against
186
218
  the npm registry surfaces a yellow `update: X.Y.Z` on the right side
187
219
  of the same row when a newer version has been published. No blocking,
188
220
  no nagging — the check runs once per day max and is silent on failure
@@ -403,18 +435,25 @@ rendering, retries.
403
435
  | command | what it does |
404
436
  |---|---|
405
437
  | `/preset <fast\|smart\|max>` | one-tap bundle (model + harvest + branch) |
406
- | `/model <id>` | switch DeepSeek model (`deepseek-chat`, `deepseek-reasoner`) |
438
+ | `/model <id>` | switch DeepSeek model (`deepseek-v4-flash`, `deepseek-v4-pro`, plus `deepseek-chat` / `deepseek-reasoner` compat aliases) |
439
+ | `/models` | list live models from DeepSeek `/models` endpoint |
407
440
  | `/harvest [on\|off]` | toggle R1 plan-state extraction |
408
441
  | `/branch <N\|off>` | run N parallel samples per turn, pick best (N ≥ 2) |
409
- | `/think` | dump the last turn's full R1 reasoning |
442
+ | `/effort <high\|max>` | reasoning_effort cap — `max` is the agent default, `high` is cheaper/faster |
443
+ | `/think` | dump the last turn's full thinking-mode reasoning |
410
444
 
411
445
  **Context & tools**
412
446
 
413
447
  | command | what it does |
414
448
  |---|---|
415
- | `/mcp` | list attached MCP servers and their tools |
449
+ | `/mcp` | list attached MCP servers and their tools / resources / prompts |
450
+ | `/resource [uri]` | browse + read MCP resources (no arg → list URIs; `<uri>` → fetch) |
451
+ | `/prompt [name]` | browse + fetch MCP prompts |
416
452
  | `/tool [N]` | dump the Nth tool call's full output (1 = latest) |
417
- | `/compact [cap]` | shrink oversized tool results in the log |
453
+ | `/compact [tokens]` | shrink oversized tool results in the log (default 4000 tokens/result) |
454
+ | `/context` | break down where context tokens are going (system / tools / log) |
455
+ | `/stats` | cross-session cost dashboard (today / week / month / all-time) |
456
+ | `/keys` | keyboard shortcuts + prompt prefixes (`!` / `@` / `/`) cheatsheet |
418
457
 
419
458
  **Memory & skills**
420
459
 
@@ -468,8 +507,8 @@ rendering, retries.
468
507
  - Malformed `assistant.tool_calls` / `tool` pairing is validated on
469
508
  every outgoing API call so a corrupted session can't keep 400ing.
470
509
  - Context gauge turns yellow at 50%, red at 80% with a `/compact`
471
- nudge. Approaching the 131k window triggers an automatic
472
- compaction attempt before falling back to a forced summary.
510
+ nudge. Approaching the 1M-token window (V4 flash + pro) triggers an
511
+ automatic compaction attempt before falling back to a forced summary.
473
512
  - The `reasonix code` sandbox refuses any path that resolves outside
474
513
  the launch directory, including symlink escape and `..` traversal.
475
514
 
@@ -728,7 +767,7 @@ cd reasonix
728
767
  npm install
729
768
  npm run dev code # run CLI from source via tsx
730
769
  npm run build # tsup to dist/
731
- npm test # vitest (648 tests)
770
+ npm test # vitest (1007 tests)
732
771
  npm run lint # biome
733
772
  npm run typecheck # tsc --noEmit
734
773
  ```
package/dist/cli/index.js CHANGED
@@ -210,6 +210,12 @@ var DeepSeekClient = class {
210
210
  if (opts.temperature !== void 0) payload.temperature = opts.temperature;
211
211
  if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
212
212
  if (opts.responseFormat) payload.response_format = opts.responseFormat;
213
+ if (opts.thinking) {
214
+ payload.extra_body = { thinking: { type: opts.thinking } };
215
+ }
216
+ if (opts.reasoningEffort) {
217
+ payload.reasoning_effort = opts.reasoningEffort;
218
+ }
213
219
  return payload;
214
220
  }
215
221
  /**
@@ -424,6 +430,13 @@ async function harvest(reasoningContent, client, options = {}, signal) {
424
430
  responseFormat: { type: "json_object" },
425
431
  temperature: 0,
426
432
  maxTokens: 600,
433
+ // Pin mode + effort so a future default-model swap (e.g. someone
434
+ // sets `options.model = "deepseek-v4-pro"`) can't accidentally
435
+ // turn this micro-extraction into a multi-thousand-reasoning-
436
+ // token call. DeepSeek ignores these on non-thinking models, so
437
+ // the request stays valid regardless of the chosen model.
438
+ thinking: "disabled",
439
+ reasoningEffort: "high",
427
440
  signal
428
441
  });
429
442
  return parsePlanState(resp.content, maxItems, maxItemLen);
@@ -1783,6 +1796,8 @@ var CacheFirstLoop = class {
1783
1796
  harvestOptions;
1784
1797
  branchEnabled;
1785
1798
  branchOptions;
1799
+ /** See ReconfigurableOptions — mutable so `/effort` can flip mid-session. */
1800
+ reasoningEffort;
1786
1801
  sessionName;
1787
1802
  /**
1788
1803
  * Hook list, mutable so `/hooks reload` can swap it without
@@ -1808,7 +1823,8 @@ var CacheFirstLoop = class {
1808
1823
  this.client = opts.client;
1809
1824
  this.prefix = opts.prefix;
1810
1825
  this.tools = opts.tools ?? new ToolRegistry();
1811
- this.model = opts.model ?? "deepseek-chat";
1826
+ this.model = opts.model ?? "deepseek-v4-pro";
1827
+ this.reasoningEffort = opts.reasoningEffort ?? "max";
1812
1828
  this.maxToolIters = opts.maxToolIters ?? 64;
1813
1829
  this.hooks = opts.hooks ?? [];
1814
1830
  this.hookCwd = opts.hookCwd ?? process.cwd();
@@ -1924,6 +1940,7 @@ var CacheFirstLoop = class {
1924
1940
  configure(opts) {
1925
1941
  if (opts.model !== void 0) this.model = opts.model;
1926
1942
  if (opts.stream !== void 0) this._streamPreference = opts.stream;
1943
+ if (opts.reasoningEffort !== void 0) this.reasoningEffort = opts.reasoningEffort;
1927
1944
  if (opts.branch !== void 0) {
1928
1945
  if (typeof opts.branch === "number") {
1929
1946
  this.branchOptions = { budget: opts.branch };
@@ -2102,7 +2119,9 @@ var CacheFirstLoop = class {
2102
2119
  model: this.model,
2103
2120
  messages,
2104
2121
  tools: toolSpecs.length ? toolSpecs : void 0,
2105
- signal
2122
+ signal,
2123
+ thinking: thinkingModeForModel(this.model),
2124
+ reasoningEffort: this.reasoningEffort
2106
2125
  },
2107
2126
  {
2108
2127
  ...this.branchOptions,
@@ -2154,7 +2173,9 @@ var CacheFirstLoop = class {
2154
2173
  model: this.model,
2155
2174
  messages,
2156
2175
  tools: toolSpecs.length ? toolSpecs : void 0,
2157
- signal
2176
+ signal,
2177
+ thinking: thinkingModeForModel(this.model),
2178
+ reasoningEffort: this.reasoningEffort
2158
2179
  })) {
2159
2180
  if (chunk.contentDelta) {
2160
2181
  assistantContent += chunk.contentDelta;
@@ -2208,7 +2229,9 @@ var CacheFirstLoop = class {
2208
2229
  model: this.model,
2209
2230
  messages,
2210
2231
  tools: toolSpecs.length ? toolSpecs : void 0,
2211
- signal
2232
+ signal,
2233
+ thinking: thinkingModeForModel(this.model),
2234
+ reasoningEffort: this.reasoningEffort
2212
2235
  });
2213
2236
  assistantContent = resp.content;
2214
2237
  reasoningContent = resp.reasoningContent ?? "";
@@ -2401,7 +2424,9 @@ ${reason}`;
2401
2424
  model: this.model,
2402
2425
  messages,
2403
2426
  // no tools → model is forced to answer in text
2404
- signal: this._turnAbort.signal
2427
+ signal: this._turnAbort.signal,
2428
+ thinking: thinkingModeForModel(this.model),
2429
+ reasoningEffort: this.reasoningEffort
2405
2430
  });
2406
2431
  const rawContent = resp.content?.trim() ?? "";
2407
2432
  const cleaned = stripHallucinatedToolMarkup(rawContent);
@@ -2469,6 +2494,12 @@ function isThinkingModeModel(model) {
2469
2494
  if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
2470
2495
  return false;
2471
2496
  }
2497
+ function thinkingModeForModel(model) {
2498
+ if (model === "deepseek-chat") return "disabled";
2499
+ if (model.includes("reasoner")) return "enabled";
2500
+ if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return "enabled";
2501
+ return void 0;
2502
+ }
2472
2503
  function stripHallucinatedToolMarkup(s) {
2473
2504
  let out = s;
2474
2505
  out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
@@ -3499,7 +3530,7 @@ function registerPlanTool(registry, opts = {}) {
3499
3530
  // src/tools/subagent.ts
3500
3531
  var DEFAULT_MAX_RESULT_CHARS2 = 8e3;
3501
3532
  var DEFAULT_MAX_ITERS = 16;
3502
- var DEFAULT_SUBAGENT_MODEL = "deepseek-chat";
3533
+ var DEFAULT_SUBAGENT_MODEL = "deepseek-v4-pro";
3503
3534
  var SUBAGENT_TOOL_NAME = "spawn_subagent";
3504
3535
  var NEVER_INHERITED_TOOLS = /* @__PURE__ */ new Set([SUBAGENT_TOOL_NAME, "submit_plan"]);
3505
3536
  async function spawnSubagent(opts) {
@@ -7181,6 +7212,7 @@ function StatsPanel({
7181
7212
  prefixHash,
7182
7213
  harvestOn,
7183
7214
  branchBudget,
7215
+ reasoningEffort,
7184
7216
  planMode,
7185
7217
  balance,
7186
7218
  updateAvailable,
@@ -7201,6 +7233,7 @@ function StatsPanel({
7201
7233
  harvestOn,
7202
7234
  branchOn,
7203
7235
  branchBudget: branchBudget ?? 1,
7236
+ reasoningEffort,
7204
7237
  planMode,
7205
7238
  turns: summary.turns,
7206
7239
  updateAvailable,
@@ -7233,13 +7266,14 @@ function Header({
7233
7266
  harvestOn,
7234
7267
  branchOn,
7235
7268
  branchBudget,
7269
+ reasoningEffort,
7236
7270
  planMode,
7237
7271
  turns,
7238
7272
  updateAvailable,
7239
7273
  narrow,
7240
7274
  busy
7241
7275
  }) {
7242
- return /* @__PURE__ */ React13.createElement(Box12, { justifyContent: "space-between" }, /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Wordmark, { busy }), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, model), narrow ? null : /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React13.createElement(Text12, { color: "blue" }, " \xB7 branch", branchBudget) : null, planMode ? /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, " \xB7 PLAN") : null), /* @__PURE__ */ React13.createElement(Text12, null, updateAvailable ? /* @__PURE__ */ React13.createElement(Text12, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
7276
+ return /* @__PURE__ */ React13.createElement(Box12, { justifyContent: "space-between" }, /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Wordmark, { busy }), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, ` v${VERSION}`), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, model), narrow ? null : /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, " \xB7 "), /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, prefixHash)), harvestOn ? /* @__PURE__ */ React13.createElement(Text12, { color: "magenta" }, " \xB7 harvest") : null, branchOn ? /* @__PURE__ */ React13.createElement(Text12, { color: "blue" }, " \xB7 branch", branchBudget) : null, reasoningEffort === "max" ? /* @__PURE__ */ React13.createElement(Text12, { color: "green" }, " \xB7 max") : null, reasoningEffort === "high" ? /* @__PURE__ */ React13.createElement(Text12, { color: "yellow" }, " \xB7 high") : null, planMode ? /* @__PURE__ */ React13.createElement(Text12, { color: "red", bold: true }, " \xB7 PLAN") : null), /* @__PURE__ */ React13.createElement(Text12, null, updateAvailable ? /* @__PURE__ */ React13.createElement(Text12, { color: "yellow", bold: true }, `update: ${updateAvailable} \xB7 `) : null, /* @__PURE__ */ React13.createElement(Text12, { dimColor: true }, narrow ? `turn ${turns}` : `turn ${turns} \xB7 /help`)));
7243
7277
  }
7244
7278
  function InlineMetrics({
7245
7279
  summary,
@@ -7697,6 +7731,12 @@ var SLASH_COMMANDS = [
7697
7731
  summary: "run N parallel samples per turn (N>=2)",
7698
7732
  argCompleter: ["off", "2", "3", "4", "5"]
7699
7733
  },
7734
+ {
7735
+ cmd: "effort",
7736
+ argsHint: "<high|max>",
7737
+ summary: "reasoning_effort cap \u2014 max is default (agent-class), high is cheaper/faster",
7738
+ argCompleter: ["max", "high"]
7739
+ },
7700
7740
  { cmd: "mcp", summary: "list MCP servers + tools attached to this session" },
7701
7741
  {
7702
7742
  cmd: "resource",
@@ -7874,6 +7914,7 @@ function handleSlash(cmd, args, loop, ctx = {}) {
7874
7914
  " /model <id> deepseek-chat or deepseek-reasoner",
7875
7915
  " /harvest [on|off] Pillar 2: structured plan-state extraction",
7876
7916
  " /branch <N|off> run N parallel samples (N>=2), pick most confident",
7917
+ " /effort <high|max> reasoning_effort cap (max=agent default, high=cheaper)",
7877
7918
  " /mcp list MCP servers + tools attached to this session",
7878
7919
  " /resource [uri] browse + read MCP resources (no arg \u2192 list URIs; <uri> \u2192 fetch)",
7879
7920
  " /prompt [name] browse + fetch MCP prompts (no arg \u2192 list names; <name> \u2192 render)",
@@ -8243,7 +8284,7 @@ ${entry.text}`
8243
8284
  const planLine = ctx.planMode ? " plan ON \u2014 writes gated (submit_plan + approval)" : "";
8244
8285
  const lines = [
8245
8286
  ` model ${loop.model}`,
8246
- ` flags harvest=${loop.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop.stream ? "on" : "off"}`,
8287
+ ` flags harvest=${loop.harvestEnabled ? "on" : "off"} \xB7 branch=${branchBudget > 1 ? branchBudget : "off"} \xB7 stream=${loop.stream ? "on" : "off"} \xB7 effort=${loop.reasoningEffort}`,
8247
8288
  ctxLine,
8248
8289
  mcpLine,
8249
8290
  sessionLine
@@ -8332,6 +8373,19 @@ ${entry.text}`
8332
8373
  loop.configure({ branch: n });
8333
8374
  return { info: `branch \u2192 ${n} (harvest auto-enabled; streaming disabled)` };
8334
8375
  }
8376
+ case "effort": {
8377
+ const raw = (args[0] ?? "").toLowerCase();
8378
+ if (raw === "") {
8379
+ return {
8380
+ info: `reasoning_effort \u2192 ${loop.reasoningEffort} (use /effort high for cheaper/faster, /effort max for the agent-class default)`
8381
+ };
8382
+ }
8383
+ if (raw !== "high" && raw !== "max") {
8384
+ return { info: "usage: /effort <high|max>" };
8385
+ }
8386
+ loop.configure({ reasoningEffort: raw });
8387
+ return { info: `reasoning_effort \u2192 ${raw}` };
8388
+ }
8335
8389
  default:
8336
8390
  return { unknown: true, info: `unknown command: /${cmd} (try /help)` };
8337
8391
  }
@@ -9872,6 +9926,7 @@ Stay in plan mode \u2014 address the feedback (explore more if needed), then sub
9872
9926
  prefixHash,
9873
9927
  harvestOn: loop.harvestEnabled,
9874
9928
  branchBudget: loop.branchOptions.budget,
9929
+ reasoningEffort: loop.reasoningEffort,
9875
9930
  planMode,
9876
9931
  balance,
9877
9932
  busy,