reasonix 0.27.2 → 0.28.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.
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  CODE_SYSTEM_PROMPT,
4
4
  codeSystemPrompt
5
- } from "./chunk-R2L5YEEF.js";
5
+ } from "./chunk-COFBA5FV.js";
6
6
  export {
7
7
  CODE_SYSTEM_PROMPT,
8
8
  codeSystemPrompt
9
9
  };
10
- //# sourceMappingURL=prompt-YUL7CYKY.js.map
10
+ //# sourceMappingURL=prompt-VF7B6BWR.js.map
package/dist/index.d.ts CHANGED
@@ -259,6 +259,27 @@ type ChoiceVerdict = {
259
259
  } | {
260
260
  type: "cancel";
261
261
  };
262
+ type ToolConfirmationAuditEvent = {
263
+ type: "tool.confirm.allow";
264
+ kind: "run_command" | "run_background";
265
+ payload: {
266
+ command: string;
267
+ };
268
+ } | {
269
+ type: "tool.confirm.deny";
270
+ kind: "run_command" | "run_background";
271
+ payload: {
272
+ command: string;
273
+ };
274
+ denyContext?: string;
275
+ } | {
276
+ type: "tool.confirm.always_allow";
277
+ kind: "run_command" | "run_background";
278
+ payload: {
279
+ command: string;
280
+ };
281
+ prefix: string;
282
+ };
262
283
  interface PauseResponseMap {
263
284
  run_command: ConfirmationChoice;
264
285
  run_background: ConfirmationChoice;
@@ -303,6 +324,7 @@ type PauseRequest = {
303
324
  payload: unknown;
304
325
  };
305
326
  type GateListener = (request: PauseRequest) => void;
327
+ type AuditListener = (event: ToolConfirmationAuditEvent) => void;
306
328
  /** Named options for PauseGate.ask() — makes it obvious which field is kind vs payload. */
307
329
  interface PauseAskOpts<K extends PauseKind = PauseKind> {
308
330
  kind: K;
@@ -312,15 +334,18 @@ declare class PauseGate {
312
334
  private _nextId;
313
335
  private _pending;
314
336
  private _listeners;
337
+ private _auditListener;
315
338
  /** Block until the user responds. Takes a named options object so the
316
339
  * kind and payload fields don't get confused at the call site. */
317
340
  ask<K extends PauseKind>(opts: PauseAskOpts<K>): Promise<PauseResponseMap[K]>;
318
341
  /** Resolve a pending request. Called by the App's modal callback. */
319
342
  resolve(id: number, data: unknown): void;
343
+ setAuditListener(fn: AuditListener | null): void;
320
344
  /** Subscribe to new pause requests. Returns an unsubscribe function. */
321
345
  on(fn: GateListener): () => void;
322
346
  /** Current pending request, if any (polling fallback). */
323
347
  get current(): PauseRequest | null;
348
+ private emitAuditEvent;
324
349
  }
325
350
 
326
351
  /** Shell-command hooks; project scope first, then global. Exit 0=pass, 2=block on Pre*, other=warn. */
@@ -429,46 +454,30 @@ interface RunHooksOptions {
429
454
  /** Stops at first `block` so a gating hook can prevent later hooks running against a phantom success. */
430
455
  declare function runHooks(opts: RunHooksOptions): Promise<HookReport>;
431
456
 
432
- interface ImmutablePrefixOptions {
433
- system: string;
434
- toolSpecs?: readonly ToolSpec[];
435
- fewShots?: readonly ChatMessage[];
436
- }
437
- declare class ImmutablePrefix {
438
- readonly system: string;
439
- /** Each `addTool` costs one cache-miss turn — DeepSeek's prefix cache is keyed by full tool list. */
440
- private _toolSpecs;
441
- readonly fewShots: readonly ChatMessage[];
442
- /** Invalidated only via `addTool`; bypassing it leaves cache stale → fingerprint diverges from sent prefix. */
443
- private _fingerprintCache;
444
- constructor(opts: ImmutablePrefixOptions);
445
- get toolSpecs(): readonly ToolSpec[];
446
- toMessages(): ChatMessage[];
447
- tools(): ToolSpec[];
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;
451
- get fingerprint(): string;
452
- /** Dev/test only — throws on cache drift, which always means a non-`addTool` mutation slipped in. */
453
- verifyFingerprint(): string;
454
- private computeFingerprint;
455
- }
456
- declare class AppendOnlyLog {
457
- private _entries;
458
- append(message: ChatMessage): void;
459
- extend(messages: ChatMessage[]): void;
460
- /** The one append-only-breaking path — reserved for `/compact` + recovery. Use `append()` otherwise. */
461
- compactInPlace(replacement: ChatMessage[]): void;
462
- get entries(): readonly ChatMessage[];
463
- toMessages(): ChatMessage[];
464
- get length(): number;
465
- }
466
- declare class VolatileScratch {
467
- reasoning: string | null;
468
- planState: Record<string, unknown> | null;
469
- notes: string[];
470
- reset(): void;
471
- }
457
+ /** Single text-layer DeepSeek-error formatter — 429/5xx never reach here (retry.ts swallows). */
458
+ declare function formatLoopError(err: Error): string;
459
+
460
+ /** Drops both unpaired assistant.tool_calls and stray tool messages — DeepSeek 400s on either. */
461
+ declare function fixToolCallPairing(messages: ChatMessage[]): {
462
+ messages: ChatMessage[];
463
+ droppedAssistantCalls: number;
464
+ droppedStrayTools: number;
465
+ };
466
+ declare function healLoadedMessages(messages: ChatMessage[], maxChars: number): {
467
+ messages: ChatMessage[];
468
+ healedCount: number;
469
+ healedFrom: number;
470
+ };
471
+ /** Token-cap variant — char cap would let CJK slip past at 2× the intended token cost. */
472
+ declare function healLoadedMessagesByTokens(messages: ChatMessage[], maxTokens: number): {
473
+ messages: ChatMessage[];
474
+ healedCount: number;
475
+ tokensSaved: number;
476
+ charsSaved: number;
477
+ };
478
+
479
+ /** Strip hallucinated tool-call envelopes — `tools: undefined` doesn't always force prose. */
480
+ declare function stripHallucinatedToolMarkup(s: string): string;
472
481
 
473
482
  /** Mutating calls clear prior read-only entries so a post-edit re-read isn't flagged as repeat. */
474
483
  type IsMutating = (call: ToolCall) => boolean;
@@ -587,6 +596,91 @@ declare class SessionStats {
587
596
  summary(): SessionSummary;
588
597
  }
589
598
 
599
+ type EventRole = "assistant_delta" | "assistant_final"
600
+ /** Only liveness signal during a large-args tool call (no content/reasoning bytes). */
601
+ | "tool_call_delta"
602
+ /** Pre-dispatch ping so the TUI can show a spinner during long tool awaits. */
603
+ | "tool_start" | "tool" | "done" | "error" | "warning"
604
+ /** Transient indicator for silent phases; UI clears on next primary event. */
605
+ | "status" | "branch_start" | "branch_progress" | "branch_done";
606
+ interface BranchSummary {
607
+ budget: number;
608
+ chosenIndex: number;
609
+ uncertainties: number[];
610
+ temperatures: number[];
611
+ }
612
+ interface BranchProgress {
613
+ completed: number;
614
+ total: number;
615
+ latestIndex: number;
616
+ latestTemperature: number;
617
+ latestUncertainties: number;
618
+ }
619
+ interface LoopEvent {
620
+ turn: number;
621
+ role: EventRole;
622
+ content: string;
623
+ reasoningDelta?: string;
624
+ toolName?: string;
625
+ /** Raw args JSON — needed by `reasonix diff` to explain why a tool was called. */
626
+ toolArgs?: string;
627
+ /** Cumulative arguments-string length for `role === "tool_call_delta"`. */
628
+ toolCallArgsChars?: number;
629
+ /** Zero-based index of the tool call this delta belongs to (multi-tool progress). */
630
+ toolCallIndex?: number;
631
+ /** Count of tool calls whose args have parsed as valid JSON (UI progress, not dispatch gate). */
632
+ toolCallReadyCount?: number;
633
+ stats?: TurnStats;
634
+ planState?: TypedPlanState;
635
+ repair?: RepairReport;
636
+ branch?: BranchSummary;
637
+ branchProgress?: BranchProgress;
638
+ error?: string;
639
+ /** Display-only — code-mode applier MUST skip SEARCH/REPLACE in forced-summary text. */
640
+ forcedSummary?: boolean;
641
+ }
642
+
643
+ interface ImmutablePrefixOptions {
644
+ system: string;
645
+ toolSpecs?: readonly ToolSpec[];
646
+ fewShots?: readonly ChatMessage[];
647
+ }
648
+ declare class ImmutablePrefix {
649
+ readonly system: string;
650
+ /** Each `addTool` costs one cache-miss turn — DeepSeek's prefix cache is keyed by full tool list. */
651
+ private _toolSpecs;
652
+ readonly fewShots: readonly ChatMessage[];
653
+ /** Invalidated only via `addTool`; bypassing it leaves cache stale → fingerprint diverges from sent prefix. */
654
+ private _fingerprintCache;
655
+ constructor(opts: ImmutablePrefixOptions);
656
+ get toolSpecs(): readonly ToolSpec[];
657
+ toMessages(): ChatMessage[];
658
+ tools(): ToolSpec[];
659
+ addTool(spec: ToolSpec): boolean;
660
+ /** Mirror of addTool for MCP hot-unbridge. Same cache-miss cost — prefix changes shape. */
661
+ removeTool(name: string): boolean;
662
+ get fingerprint(): string;
663
+ /** Dev/test only — throws on cache drift, which always means a non-`addTool` mutation slipped in. */
664
+ verifyFingerprint(): string;
665
+ private computeFingerprint;
666
+ }
667
+ declare class AppendOnlyLog {
668
+ private _entries;
669
+ append(message: ChatMessage): void;
670
+ extend(messages: ChatMessage[]): void;
671
+ /** The one append-only-breaking path — reserved for `/compact` + recovery. Use `append()` otherwise. */
672
+ compactInPlace(replacement: ChatMessage[]): void;
673
+ get entries(): readonly ChatMessage[];
674
+ toMessages(): ChatMessage[];
675
+ get length(): number;
676
+ }
677
+ declare class VolatileScratch {
678
+ reasoning: string | null;
679
+ planState: Record<string, unknown> | null;
680
+ notes: string[];
681
+ reset(): void;
682
+ }
683
+
590
684
  interface ToolCallContext {
591
685
  signal?: AbortSignal;
592
686
  /** Inject a mock PauseGate for tests. When absent, tools use the singleton. */
@@ -606,6 +700,11 @@ interface ToolRegistryOptions {
606
700
  /** Auto-flatten + re-nest at dispatch; default true. */
607
701
  autoFlatten?: boolean;
608
702
  }
703
+ type ToolCallAuditEvent = {
704
+ name: string;
705
+ args: Record<string, unknown>;
706
+ };
707
+ type ToolCallAuditListener = (event: ToolCallAuditEvent) => void;
609
708
  /** String return short-circuits dispatch; null/undefined falls through to the tool fn. */
610
709
  type ToolInterceptor = (name: string, args: Record<string, unknown>) => string | null | undefined | Promise<string | null | undefined>;
611
710
  declare class ToolRegistry {
@@ -613,6 +712,7 @@ declare class ToolRegistry {
613
712
  private readonly _autoFlatten;
614
713
  private _planMode;
615
714
  private _interceptor;
715
+ private _auditListener;
616
716
  constructor(opts?: ToolRegistryOptions);
617
717
  /** Enable / disable plan-mode enforcement at dispatch. */
618
718
  setPlanMode(on: boolean): void;
@@ -620,6 +720,7 @@ declare class ToolRegistry {
620
720
  get planMode(): boolean;
621
721
  /** At most one interceptor active; calling twice replaces. */
622
722
  setToolInterceptor(fn: ToolInterceptor | null): void;
723
+ setAuditListener(fn: ToolCallAuditListener | null): void;
623
724
  register<A, R>(def: ToolDefinition<A, R>): this;
624
725
  /** Drop a registered tool. Returns true if the name was present. Used by MCP hot-unbridge. */
625
726
  unregister(name: string): boolean;
@@ -638,49 +739,6 @@ declare class ToolRegistry {
638
739
  }): Promise<string>;
639
740
  }
640
741
 
641
- type EventRole = "assistant_delta" | "assistant_final"
642
- /** Only liveness signal during a large-args tool call (no content/reasoning bytes). */
643
- | "tool_call_delta"
644
- /** Pre-dispatch ping so the TUI can show a spinner during long tool awaits. */
645
- | "tool_start" | "tool" | "done" | "error" | "warning"
646
- /** Transient indicator for silent phases; UI clears on next primary event. */
647
- | "status" | "branch_start" | "branch_progress" | "branch_done";
648
- interface BranchSummary {
649
- budget: number;
650
- chosenIndex: number;
651
- uncertainties: number[];
652
- temperatures: number[];
653
- }
654
- interface BranchProgress {
655
- completed: number;
656
- total: number;
657
- latestIndex: number;
658
- latestTemperature: number;
659
- latestUncertainties: number;
660
- }
661
- interface LoopEvent {
662
- turn: number;
663
- role: EventRole;
664
- content: string;
665
- reasoningDelta?: string;
666
- toolName?: string;
667
- /** Raw args JSON — needed by `reasonix diff` to explain why a tool was called. */
668
- toolArgs?: string;
669
- /** Cumulative arguments-string length for `role === "tool_call_delta"`. */
670
- toolCallArgsChars?: number;
671
- /** Zero-based index of the tool call this delta belongs to (multi-tool progress). */
672
- toolCallIndex?: number;
673
- /** Count of tool calls whose args have parsed as valid JSON (UI progress, not dispatch gate). */
674
- toolCallReadyCount?: number;
675
- stats?: TurnStats;
676
- planState?: TypedPlanState;
677
- repair?: RepairReport;
678
- branch?: BranchSummary;
679
- branchProgress?: BranchProgress;
680
- error?: string;
681
- /** Display-only — code-mode applier MUST skip SEARCH/REPLACE in forced-summary text. */
682
- forcedSummary?: boolean;
683
- }
684
742
  interface CacheFirstLoopOptions {
685
743
  client: DeepSeekClient;
686
744
  prefix: ImmutablePrefix;
@@ -746,11 +804,11 @@ declare class CacheFirstLoop {
746
804
  private _turnAbort;
747
805
  private _proArmedForNextTurn;
748
806
  private _escalateThisTurn;
749
- private _turnFailureCount;
750
- private _turnFailureTypes;
807
+ private readonly _turnFailures;
751
808
  private _turnSelfCorrected;
752
809
  private _foldedThisTurn;
753
810
  private context;
811
+ get currentTurn(): number;
754
812
  constructor(opts: CacheFirstLoopOptions);
755
813
  /** Replace older turns with one summary message; keep tail within keepRecentTokens budget. */
756
814
  compactHistory(opts?: {
@@ -780,49 +838,16 @@ declare class CacheFirstLoop {
780
838
  /** UI surface — true while the current turn is running on pro (armed or auto-escalated). */
781
839
  get escalatedThisTurn(): boolean;
782
840
  private modelForCurrentCall;
783
- /** Anchored to lead — mid-text matches are normal content (user asking about the marker). */
784
- private parseEscalationMarker;
785
- /** Convenience boolean — same gate the streaming path used to call. */
786
- private isEscalationRequest;
787
- /** Drives streaming flush — while plausibly partial, keep accumulating; else flush. */
788
- private looksLikePartialEscalationMarker;
789
841
  /** Returns true ONLY on the tipping call — caller surfaces a one-shot warning. */
790
842
  private noteToolFailureSignal;
791
- private formatFailureBreakdown;
792
843
  private buildMessages;
793
844
  abort(): void;
794
845
  /** Drop the last user message + everything after; caller re-sends. Persists to session file. */
795
846
  retryLastUser(): string | null;
796
847
  step(userInput: string): AsyncGenerator<LoopEvent>;
797
- private forceSummaryAfterIterLimit;
848
+ private summaryContext;
798
849
  run(userInput: string, onEvent?: (ev: LoopEvent) => void): Promise<string>;
799
- /** Thinking-mode producer ⇒ reasoning_content MUST be set (even ""), or next call 400s. */
800
- private assistantMessage;
801
- /** Abort notices etc — uses this.model as stand-in producer for the thinking-mode stamp. */
802
- private syntheticAssistantMessage;
803
850
  }
804
- /** Strip hallucinated tool-call envelopes — `tools: undefined` doesn't always force prose. */
805
- declare function stripHallucinatedToolMarkup(s: string): string;
806
- /** Drops both unpaired assistant.tool_calls and stray tool messages — DeepSeek 400s on either. */
807
- declare function fixToolCallPairing(messages: ChatMessage[]): {
808
- messages: ChatMessage[];
809
- droppedAssistantCalls: number;
810
- droppedStrayTools: number;
811
- };
812
- declare function healLoadedMessages(messages: ChatMessage[], maxChars: number): {
813
- messages: ChatMessage[];
814
- healedCount: number;
815
- healedFrom: number;
816
- };
817
- /** Token-cap variant — char cap would let CJK slip past at 2× the intended token cost. */
818
- declare function healLoadedMessagesByTokens(messages: ChatMessage[], maxTokens: number): {
819
- messages: ChatMessage[];
820
- healedCount: number;
821
- tokensSaved: number;
822
- charsSaved: number;
823
- };
824
- /** Single text-layer DeepSeek-error formatter — 429/5xx never reach here (retry.ts swallows). */
825
- declare function formatLoopError(err: Error): string;
826
851
 
827
852
  /** Expand `@path` mentions inline. Paths must resolve inside rootDir; escapes / oversize get a skip note, not content. */
828
853
  /** Caps match tool-result dispatch truncation (0.5.2). */
@@ -1177,26 +1202,6 @@ interface JobReadResult {
1177
1202
  spawnError?: string;
1178
1203
  }
1179
1204
 
1180
- /** cwd pinned to root; non-allowlisted commands throw to a UI confirm gate; spawn is `shell: false`, tokenized argv only. */
1181
-
1182
- interface ShellToolsOptions {
1183
- /** Directory to run commands in. Must be an absolute path. */
1184
- rootDir: string;
1185
- /** Seconds before an individual command is killed. Default: 60. */
1186
- timeoutSec?: number;
1187
- maxOutputChars?: number;
1188
- /** Getter form is load-bearing — newly-persisted "always allow" prefixes MUST take effect mid-session. */
1189
- extraAllowed?: readonly string[] | (() => readonly string[]);
1190
- /** Getter form lets `editMode === "yolo"` flip mid-session without re-registering tools. */
1191
- allowAll?: boolean | (() => boolean);
1192
- jobs?: JobRegistry;
1193
- }
1194
- /** No env / glob / backtick / `$(…)` expansion — prevents bypass of allowlist via concatenation. */
1195
- declare function tokenizeCommand(cmd: string): string[];
1196
- /** Up-front detection — without it, `dir | findstr foo` quotes `|` literal and pipe silently fails. */
1197
- declare function detectShellOperator(cmd: string): string | null;
1198
- /** Allowlist match on leading argv tokens; demoted by `RISKY_ARGS` when a destructive flag appears in the tail. */
1199
- declare function isAllowed(cmd: string, extra?: readonly string[]): boolean;
1200
1205
  interface RunCommandResult {
1201
1206
  exitCode: number | null;
1202
1207
  /** Combined stdout+stderr, truncated to `maxOutputChars` with a marker. */
@@ -1233,6 +1238,28 @@ declare function injectPowerShellUtf8(args: readonly string[]): string[] | null;
1233
1238
  declare function withUtf8Codepage(cmdline: string): string;
1234
1239
  /** Doubles embedded quotes per cmd.exe's `""` escape rule; bare alnum passes through unquoted. */
1235
1240
  declare function quoteForCmdExe(arg: string): string;
1241
+
1242
+ /** No env / glob / backtick / `$(…)` expansion — prevents bypass of allowlist via concatenation. */
1243
+ declare function tokenizeCommand(cmd: string): string[];
1244
+ /** Up-front detection — without it, `dir | findstr foo` quotes `|` literal and pipe silently fails. */
1245
+ declare function detectShellOperator(cmd: string): string | null;
1246
+ /** Allowlist match on leading argv tokens; demoted by `RISKY_ARGS` when a destructive flag appears in the tail. */
1247
+ declare function isAllowed(cmd: string, extra?: readonly string[]): boolean;
1248
+
1249
+ /** cwd pinned to root; non-allowlisted commands throw to a UI confirm gate; spawn is `shell: false`, tokenized argv only. */
1250
+
1251
+ interface ShellToolsOptions {
1252
+ /** Directory to run commands in. Must be an absolute path. */
1253
+ rootDir: string;
1254
+ /** Seconds before an individual command is killed. Default: 60. */
1255
+ timeoutSec?: number;
1256
+ maxOutputChars?: number;
1257
+ /** Getter form is load-bearing — newly-persisted "always allow" prefixes MUST take effect mid-session. */
1258
+ extraAllowed?: readonly string[] | (() => readonly string[]);
1259
+ /** Getter form lets `editMode === "yolo"` flip mid-session without re-registering tools. */
1260
+ allowAll?: boolean | (() => boolean);
1261
+ jobs?: JobRegistry;
1262
+ }
1236
1263
  /** Error thrown by `run_command` when the command isn't allowlisted. */
1237
1264
  declare class NeedsConfirmationError extends Error {
1238
1265
  readonly command: string;