reasonix 0.3.1 → 0.3.2

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
@@ -433,7 +433,7 @@ declare class ToolRegistry {
433
433
  dispatch(name: string, argumentsRaw: string | Record<string, unknown>): Promise<string>;
434
434
  }
435
435
 
436
- type EventRole = "assistant_delta" | "assistant_final" | "tool" | "done" | "error" | "branch_start" | "branch_progress" | "branch_done";
436
+ type EventRole = "assistant_delta" | "assistant_final" | "tool" | "done" | "error" | "warning" | "branch_start" | "branch_progress" | "branch_done";
437
437
  interface BranchSummary {
438
438
  budget: number;
439
439
  chosenIndex: number;
@@ -528,6 +528,12 @@ declare class CacheFirstLoop {
528
528
  readonly resumedMessageCount: number;
529
529
  private _turn;
530
530
  private _streamPreference;
531
+ /**
532
+ * Set by {@link abort} to short-circuit the tool-call loop after the
533
+ * current iteration. Reset at the start of each `step()` so an Esc
534
+ * during one turn doesn't poison the next.
535
+ */
536
+ private _aborted;
531
537
  constructor(opts: CacheFirstLoopOptions);
532
538
  /**
533
539
  * Shrink the log by re-truncating oversized tool results to a tighter
@@ -553,6 +559,14 @@ declare class CacheFirstLoop {
553
559
  */
554
560
  configure(opts: ReconfigurableOptions): void;
555
561
  private buildMessages;
562
+ /**
563
+ * Signal the currently-running {@link step} that the user wants to
564
+ * stop exploring. Takes effect at the next iteration boundary — if a
565
+ * tool call is mid-flight it will be allowed to finish, then the
566
+ * loop diverts to the forced-summary path so the user gets an
567
+ * answer instead of a cliff. Called by the TUI on Esc.
568
+ */
569
+ abort(): void;
556
570
  step(userInput: string): AsyncGenerator<LoopEvent>;
557
571
  private forceSummaryAfterIterLimit;
558
572
  run(userInput: string, onEvent?: (ev: LoopEvent) => void): Promise<string>;
@@ -1277,6 +1291,6 @@ declare function redactKey(key: string): string;
1277
1291
 
1278
1292
  /** Reasonix — DeepSeek-native agent framework. Library entry point. */
1279
1293
 
1280
- declare const VERSION = "0.3.1";
1294
+ declare const VERSION = "0.3.2";
1281
1295
 
1282
1296
  export { AppendOnlyLog, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EventRole, type FlattenDecision, type FlattenOptions, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type ScavengeOptions, type ScavengeResult, type SessionInfo, SessionStats, type SessionSummary, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, aggregateBranchUsage, analyzeSchema, appendSessionMessage, bridgeMcpTools, claudeEquivalentCost, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatLoopError, harvest, healLoadedMessages, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, nestArguments, openTranscriptFile, parseMcpSpec, parseTranscript, readConfig, readTranscript, recordFromLoopEvent, redactKey, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, runBranches, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, truncateForModel, writeConfig, writeMeta, writeRecord };
package/dist/index.js CHANGED
@@ -1102,6 +1102,12 @@ var CacheFirstLoop = class {
1102
1102
  resumedMessageCount;
1103
1103
  _turn = 0;
1104
1104
  _streamPreference;
1105
+ /**
1106
+ * Set by {@link abort} to short-circuit the tool-call loop after the
1107
+ * current iteration. Reset at the start of each `step()` so an Esc
1108
+ * during one turn doesn't poison the next.
1109
+ */
1110
+ _aborted = false;
1105
1111
  constructor(opts) {
1106
1112
  this.client = opts.client;
1107
1113
  this.prefix = opts.prefix;
@@ -1213,12 +1219,42 @@ var CacheFirstLoop = class {
1213
1219
  if (pendingUser !== null) msgs.push({ role: "user", content: pendingUser });
1214
1220
  return msgs;
1215
1221
  }
1222
+ /**
1223
+ * Signal the currently-running {@link step} that the user wants to
1224
+ * stop exploring. Takes effect at the next iteration boundary — if a
1225
+ * tool call is mid-flight it will be allowed to finish, then the
1226
+ * loop diverts to the forced-summary path so the user gets an
1227
+ * answer instead of a cliff. Called by the TUI on Esc.
1228
+ */
1229
+ abort() {
1230
+ this._aborted = true;
1231
+ }
1216
1232
  async *step(userInput) {
1217
1233
  this._turn++;
1218
1234
  this.scratch.reset();
1235
+ this._aborted = false;
1219
1236
  let pendingUser = userInput;
1220
1237
  const toolSpecs = this.prefix.tools();
1238
+ const warnAt = Math.max(1, Math.floor(this.maxToolIters * 0.7));
1239
+ let warnedForIterBudget = false;
1221
1240
  for (let iter = 0; iter < this.maxToolIters; iter++) {
1241
+ if (this._aborted) {
1242
+ yield {
1243
+ turn: this._turn,
1244
+ role: "warning",
1245
+ content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 forcing summary from what was gathered`
1246
+ };
1247
+ yield* this.forceSummaryAfterIterLimit({ reason: "aborted" });
1248
+ return;
1249
+ }
1250
+ if (!warnedForIterBudget && iter >= warnAt) {
1251
+ warnedForIterBudget = true;
1252
+ yield {
1253
+ turn: this._turn,
1254
+ role: "warning",
1255
+ content: `${iter}/${this.maxToolIters} tool calls used \u2014 approaching budget. Press Esc to force a summary now.`
1256
+ };
1257
+ }
1222
1258
  const messages = this.buildMessages(pendingUser);
1223
1259
  let assistantContent = "";
1224
1260
  let reasoningContent = "";
@@ -1406,9 +1442,9 @@ var CacheFirstLoop = class {
1406
1442
  };
1407
1443
  }
1408
1444
  }
1409
- yield* this.forceSummaryAfterIterLimit();
1445
+ yield* this.forceSummaryAfterIterLimit({ reason: "budget" });
1410
1446
  }
1411
- async *forceSummaryAfterIterLimit() {
1447
+ async *forceSummaryAfterIterLimit(opts = { reason: "budget" }) {
1412
1448
  try {
1413
1449
  const messages = this.buildMessages(null);
1414
1450
  const resp = await this.client.chat({
@@ -1417,7 +1453,8 @@ var CacheFirstLoop = class {
1417
1453
  // no tools → model is forced to answer in text
1418
1454
  });
1419
1455
  const summary = resp.content?.trim() || "(model returned no text; try a narrower question or raise --max-tool-iters)";
1420
- const annotated = `[tool-call budget (${this.maxToolIters}) reached \u2014 forcing summary from what I found]
1456
+ const reasonPrefix = opts.reason === "aborted" ? "[aborted by user (Esc) \u2014 summarizing what I found so far]" : `[tool-call budget (${this.maxToolIters}) reached \u2014 forcing summary from what I found]`;
1457
+ const annotated = `${reasonPrefix}
1421
1458
 
1422
1459
  ${summary}`;
1423
1460
  const summaryStats = this.stats.record(this._turn, this.model, resp.usage ?? new Usage());
@@ -1430,11 +1467,12 @@ ${summary}`;
1430
1467
  };
1431
1468
  yield { turn: this._turn, role: "done", content: summary };
1432
1469
  } catch (err) {
1470
+ const label = opts.reason === "aborted" ? "aborted by user" : `tool-call budget (${this.maxToolIters}) reached`;
1433
1471
  yield {
1434
1472
  turn: this._turn,
1435
1473
  role: "error",
1436
1474
  content: "",
1437
- error: `tool-call budget (${this.maxToolIters}) reached and the fallback summary call failed: ${err.message}. Run /clear and retry with a narrower question, or pass --max-tool-iters higher.`
1475
+ error: `${label} and the fallback summary call failed: ${err.message}. Run /clear and retry with a narrower question, or raise --max-tool-iters.`
1438
1476
  };
1439
1477
  yield { turn: this._turn, role: "done", content: "" };
1440
1478
  }
@@ -2492,7 +2530,7 @@ function redactKey(key) {
2492
2530
  }
2493
2531
 
2494
2532
  // src/index.ts
2495
- var VERSION = "0.3.1";
2533
+ var VERSION = "0.3.2";
2496
2534
  export {
2497
2535
  AppendOnlyLog,
2498
2536
  CacheFirstLoop,