reasonix 0.5.8 → 0.5.20

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
@@ -69,6 +69,14 @@ interface ChatMessage {
69
69
  name?: string;
70
70
  tool_call_id?: string;
71
71
  tool_calls?: ToolCall[];
72
+ /**
73
+ * R1 `reasoning_content` captured from the assistant's thinking turn.
74
+ * DeepSeek's thinking mode 400s with "reasoning_content in the
75
+ * thinking mode must be passed back" when a tool-loop continuation
76
+ * omits it from the preceding assistant message. Round-tripped for
77
+ * deepseek-reasoner turns with tool_calls; absent for deepseek-chat.
78
+ */
79
+ reasoning_content?: string | null;
72
80
  }
73
81
  interface RawUsage {
74
82
  prompt_tokens?: number;
@@ -790,6 +798,19 @@ interface LoopEvent {
790
798
  toolArgs?: string;
791
799
  /** Cumulative arguments-string length for `role === "tool_call_delta"`. */
792
800
  toolCallArgsChars?: number;
801
+ /**
802
+ * Zero-based index of the tool call this delta belongs to. Surfaces
803
+ * multi-tool turns: on a response emitting 4 write_file calls the UI
804
+ * can show "building call 3/?" instead of a context-free spinner.
805
+ */
806
+ toolCallIndex?: number;
807
+ /**
808
+ * Count of prior tool calls (this turn) whose arguments have finished
809
+ * streaming into valid JSON. Not all ready calls have been dispatched
810
+ * yet — dispatch still happens post-stream — but the user gets "2
811
+ * ready" progress feedback while later calls keep streaming.
812
+ */
813
+ toolCallReadyCount?: number;
793
814
  stats?: TurnStats;
794
815
  planState?: TypedPlanState;
795
816
  repair?: RepairReport;
@@ -922,7 +943,7 @@ declare class CacheFirstLoop {
922
943
  tokensSaved: number;
923
944
  charsSaved: number;
924
945
  };
925
- private appendAndPersist;
946
+ appendAndPersist(message: ChatMessage): void;
926
947
  /**
927
948
  * Start a fresh conversation WITHOUT exiting. Drops every message
928
949
  * in the in-memory log AND rewrites the session file to empty so
@@ -970,6 +991,15 @@ declare class CacheFirstLoop {
970
991
  private forceSummaryAfterIterLimit;
971
992
  run(userInput: string, onEvent?: (ev: LoopEvent) => void): Promise<string>;
972
993
  private assistantMessage;
994
+ /**
995
+ * Build a synthetic assistant message we insert into the log without
996
+ * a real API round trip (abort notices, future system injections).
997
+ * Reasoner models reject follow-up requests whose assistant history
998
+ * is missing `reasoning_content`, so we stamp an empty-string
999
+ * placeholder on reasoner sessions to satisfy the validator. V3
1000
+ * doesn't care — field stays absent there.
1001
+ */
1002
+ private syntheticAssistantMessage;
973
1003
  }
974
1004
  /**
975
1005
  * R1 occasionally hallucinates tool-call markup as plain text when the
@@ -1085,6 +1115,22 @@ interface ListFilesOptions {
1085
1115
  * - entries the walker can't read (permission errors, broken links).
1086
1116
  */
1087
1117
  declare function listFilesSync(root: string, opts?: ListFilesOptions): string[];
1118
+ interface FileWithStats {
1119
+ /** Relative path with forward-slash separator. */
1120
+ path: string;
1121
+ /** Modification time (Date.getTime() / ms since epoch). 0 when stat failed. */
1122
+ mtimeMs: number;
1123
+ }
1124
+ /**
1125
+ * Same walk as {@link listFilesSync} but also statS each file for
1126
+ * modification time. Used by the `@` picker to surface recently-
1127
+ * edited files first — matches VS Code Quick Open / similar UX.
1128
+ *
1129
+ * Stat failures don't throw: the entry is kept with `mtimeMs: 0` so
1130
+ * it still appears in the picker (just sinks to the bottom of the
1131
+ * recency sort).
1132
+ */
1133
+ declare function listFilesWithStatsSync(root: string, opts?: ListFilesOptions): FileWithStats[];
1088
1134
  /**
1089
1135
  * Prefix pattern used by the `@` picker to detect an IN-PROGRESS
1090
1136
  * mention at the END of the input buffer. Captures the partial path
@@ -1106,14 +1152,36 @@ declare function detectAtPicker(input: string): {
1106
1152
  query: string;
1107
1153
  atOffset: number;
1108
1154
  } | null;
1155
+ /** A candidate accepted by the picker ranker — either a bare path or a path with mtime. */
1156
+ type PickerCandidate = string | FileWithStats;
1157
+ interface RankPickerOptions {
1158
+ /** Upper bound on returned entries. Default 40. */
1159
+ limit?: number;
1160
+ /**
1161
+ * Paths the user or model has touched recently (via tool calls like
1162
+ * `read_file` / `edit_file`). Matching paths get a recency boost so
1163
+ * the picker surfaces "stuff I just looked at" near the top.
1164
+ */
1165
+ recentlyUsed?: readonly string[];
1166
+ }
1109
1167
  /**
1110
1168
  * Filter and rank candidate files against the picker's partial query.
1111
- * Empty query → return the first `limit` candidates as-is (alpha).
1112
- * Non-empty query → case-insensitive substring match, with a modest
1113
- * boost for basename-starts-with matches so `src/l` still puts
1114
- * `loop.ts`-shaped paths near the top.
1169
+ *
1170
+ * Empty query:
1171
+ * - Sort by "recently used" bucket first (if provided), then mtime
1172
+ * descending (newer first), then path alpha.
1173
+ * - Pure-string input (no mtime data) falls back to alpha since
1174
+ * recency info isn't available.
1175
+ *
1176
+ * Non-empty query:
1177
+ * - Case-insensitive substring match, with a basename-prefix boost
1178
+ * so `lo` floats `loop.ts`-shaped paths to the top.
1179
+ * - Ties broken first by recently-used membership, then mtime.
1180
+ *
1181
+ * Back-compat: passes `string[]` through the same logic (mtime = 0,
1182
+ * recently-used still honored).
1115
1183
  */
1116
- declare function rankPickerCandidates(files: readonly string[], query: string, limit?: number): string[];
1184
+ declare function rankPickerCandidates(files: readonly PickerCandidate[], query: string, limitOrOpts?: number | RankPickerOptions): string[];
1117
1185
  /**
1118
1186
  * Matches `@` at a word boundary (start-of-string or preceded by
1119
1187
  * whitespace) followed by a path-like token. Deliberately rejects `@`
@@ -1512,6 +1580,10 @@ interface SubagentEvent {
1512
1580
  kind: "start" | "progress" | "end";
1513
1581
  /** First ~30 chars of the task prompt — used for the TUI status row. */
1514
1582
  task: string;
1583
+ /** Skill that spawned this subagent, when applicable. Stamped on every event so the TUI/logger can attribute without extra plumbing. */
1584
+ skillName?: string;
1585
+ /** Model id the child loop ran on. Stamped alongside skillName. */
1586
+ model?: string;
1515
1587
  /** Iteration count inside the child loop (number of tool results so far). */
1516
1588
  iter?: number;
1517
1589
  /** Wall-clock ms since the subagent started. */
@@ -1522,6 +1594,10 @@ interface SubagentEvent {
1522
1594
  error?: string;
1523
1595
  /** Total turns the subagent took. Set on `end`. */
1524
1596
  turns?: number;
1597
+ /** Total USD spent inside the child loop. Set on `end`. */
1598
+ costUsd?: number;
1599
+ /** Aggregated child-loop Usage (sum across turns). Set on `end`. */
1600
+ usage?: Usage;
1525
1601
  }
1526
1602
  /**
1527
1603
  * Mutable ref the registration writes through. The TUI sets `.current`
@@ -3028,6 +3104,22 @@ interface UsageRecord {
3028
3104
  costUsd: number;
3029
3105
  /** What the same turn would have cost at Claude Sonnet 4.6 rates. */
3030
3106
  claudeEquivUsd: number;
3107
+ /**
3108
+ * Distinguishes ordinary parent-loop turns from subagent summary rows.
3109
+ * Absent on pre-0.5.14 records — treat as "turn" when missing.
3110
+ */
3111
+ kind?: "turn" | "subagent";
3112
+ /** Present when `kind === "subagent"`. Attribution metadata for the /stats roll-up. */
3113
+ subagent?: {
3114
+ /** Skill that spawned it, when the spawn came from a `runAs: subagent` skill. */
3115
+ skillName?: string;
3116
+ /** First ~60 chars of the task prompt — enough context to recognize a run, never the full text. */
3117
+ taskPreview: string;
3118
+ /** Tool calls the child loop dispatched before returning. */
3119
+ toolIters: number;
3120
+ /** Wall-clock ms. */
3121
+ durationMs: number;
3122
+ };
3031
3123
  }
3032
3124
  /** Where the log lives. Tests override via `opts.path`. */
3033
3125
  declare function defaultUsageLogPath(homeDirOverride?: string): string;
@@ -3039,6 +3131,9 @@ interface AppendUsageInput {
3039
3131
  now?: number;
3040
3132
  /** Override the log path (tests). */
3041
3133
  path?: string;
3134
+ /** When appending a subagent summary row, set `kind: "subagent"` and populate `subagent`. */
3135
+ kind?: "turn" | "subagent";
3136
+ subagent?: UsageRecord["subagent"];
3042
3137
  }
3043
3138
  /**
3044
3139
  * Append one record and return it. Swallows disk errors — the TUI
@@ -3093,6 +3188,25 @@ interface UsageAggregate {
3093
3188
  firstSeen: number | null;
3094
3189
  /** Latest record's ts, or `null` when the log is empty. */
3095
3190
  lastSeen: number | null;
3191
+ /**
3192
+ * Subagent-specific rollup. Undefined when no subagent records exist
3193
+ * in the log so consumers can cheaply skip the section. Counts reflect
3194
+ * subagent SPAWNS (not internal child-loop turns) — one row per run.
3195
+ */
3196
+ subagents?: SubagentAggregate;
3197
+ }
3198
+ /** Rolled-up view of all `kind: "subagent"` records. */
3199
+ interface SubagentAggregate {
3200
+ total: number;
3201
+ costUsd: number;
3202
+ totalDurationMs: number;
3203
+ /** Per-skill breakdown. Records without `skillName` (raw spawn_subagent calls) group under `"(adhoc)"`. */
3204
+ bySkill: Array<{
3205
+ skillName: string;
3206
+ count: number;
3207
+ costUsd: number;
3208
+ durationMs: number;
3209
+ }>;
3096
3210
  }
3097
3211
  /**
3098
3212
  * Fold a flat record list into the dashboard shape — rolling windows
@@ -3107,4 +3221,4 @@ declare function aggregateUsage(records: UsageRecord[], opts?: AggregateOptions)
3107
3221
  /** File-size helper for the stats header — "1.2 MB" etc. Returns "" if missing. */
3108
3222
  declare function formatLogSize(path?: string): string;
3109
3223
 
3110
- export { AT_MENTION_PATTERN, AT_PICKER_PREFIX, type AggregateOptions, AppendOnlyLog, type AppendUsageInput, type ApplyResult, type ApplyStatus, type AtMentionExpansion, type AtMentionOptions, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_AT_MENTION_MAX_BYTES, DEFAULT_MAX_RESULT_CHARS, DEFAULT_MAX_RESULT_TOKENS, DEFAULT_PICKER_IGNORE_DIRS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FilesystemToolsOptions, type FlattenDecision, type FlattenOptions, type GetLatestVersionOptions, type GetPromptResult, HOOK_EVENTS, HOOK_SETTINGS_DIRNAME, HOOK_SETTINGS_FILENAME, type HarvestOptions, type HookConfig, type HookEvent, type HookOutcome, type HookPayload, type HookReport, type HookScope, type HookSettings, type HookSpawnInput, type HookSpawnResult, type HookSpawner, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type InspectionReport, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, LATEST_CACHE_TTL_MS, LATEST_FETCH_TIMEOUT_MS, type ListFilesOptions, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoadHookSettingsOptions, type LoopEvent, MCP_PROTOCOL_VERSION, MEMORY_INDEX_FILE, MEMORY_INDEX_MAX_CHARS, McpClient, type McpClientOptions, type McpContentBlock, type McpProgressHandler, type McpProgressInfo, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type MemoryEntry, type MemoryScope, MemoryStore, type MemoryStoreOptions, type MemoryToolsOptions, type MemoryType, type WriteInput as MemoryWriteInput, NeedsConfirmationError, PROJECT_MEMORY_FILE, PROJECT_MEMORY_MAX_CHARS, type PageContent, PlanProposedError, type PlanToolOptions, type ProgressNotificationParams, type ProjectMemory, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type ResolvedHook, type RetryInfo, type RetryOptions, type Role, type RunCommandResult, type RunHooksOptions, type ScavengeOptions, type ScavengeResult, type SearchResult, type SectionResult, type SessionInfo, SessionStats, type SessionSummary, type ShellToolsOptions, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type SubagentEvent, type SubagentSink, type SubagentToolOptions, type ToolCall, type ToolCallContext, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, USER_MEMORY_DIR, Usage, type UsageAggregate, type UsageBucket, type UsageRecord, VERSION, VolatileScratch, type WebFetchOptions, type WebSearchOptions, type WebToolsOptions, aggregateBranchUsage, aggregateUsage, analyzeSchema, appendSessionMessage, appendUsage, applyEditBlock, applyEditBlocks, applyMemoryStack, applyProjectMemory, applyUserMemory, bridgeMcpTools, bucketCacheHitRatio, bucketSavingsFraction, claudeEquivalentCost, codeSystemPrompt, compareVersions, computeReplayStats, costUsd, decideOutcome, defaultConfigPath, defaultSelector, defaultUsageLogPath, deleteSession, detectAtPicker, detectShellOperator, diffTranscripts, emptyPlanState, expandAtMentions, fetchWithRetry, fixToolCallPairing, flattenMcpResult, flattenSchema, forkRegistryExcluding, formatCommandResult, formatHookOutcomeMessage, formatLogSize, formatLoopError, formatSearchResults, getLatestVersion, globalSettingsPath, harvest, healLoadedMessages, healLoadedMessagesByTokens, htmlToText, injectPowerShellUtf8, inputCostUsd, inspectMcpServer, isAllowed, isJsonRpcError, isNpxInstall, isPlanStateEmpty, isPlausibleKey, listFilesSync, listSessions, loadApiKey, loadDotenv, loadHooks, loadSessionMessages, matchesTool, memoryEnabled, nestArguments, openTranscriptFile, outputCostUsd, parseEditBlocks, parseMcpSpec, parseMojeekResults, parseTranscript, prepareSpawn, projectHash, projectSettingsPath, quoteForCmdExe, rankPickerCandidates, readConfig, readProjectMemory, readTranscript, readUsageLog, recordFromLoopEvent, redactKey, registerFilesystemTools, registerMemoryTools, registerPlanTool, registerShellTools, registerSubagentTool, registerWebTools, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, resolveExecutable, restoreSnapshots, runBranches, runCommand, runHooks, sanitizeMemoryName, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, tokenizeCommand, truncateForModel, truncateForModelByTokens, webFetch, webSearch, withUtf8Codepage, writeConfig, writeMeta, writeRecord };
3224
+ export { AT_MENTION_PATTERN, AT_PICKER_PREFIX, type AggregateOptions, AppendOnlyLog, type AppendUsageInput, type ApplyResult, type ApplyStatus, type AtMentionExpansion, type AtMentionOptions, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_AT_MENTION_MAX_BYTES, DEFAULT_MAX_RESULT_CHARS, DEFAULT_MAX_RESULT_TOKENS, DEFAULT_PICKER_IGNORE_DIRS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FileWithStats, type FilesystemToolsOptions, type FlattenDecision, type FlattenOptions, type GetLatestVersionOptions, type GetPromptResult, HOOK_EVENTS, HOOK_SETTINGS_DIRNAME, HOOK_SETTINGS_FILENAME, type HarvestOptions, type HookConfig, type HookEvent, type HookOutcome, type HookPayload, type HookReport, type HookScope, type HookSettings, type HookSpawnInput, type HookSpawnResult, type HookSpawner, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type InspectionReport, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, LATEST_CACHE_TTL_MS, LATEST_FETCH_TIMEOUT_MS, type ListFilesOptions, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoadHookSettingsOptions, type LoopEvent, MCP_PROTOCOL_VERSION, MEMORY_INDEX_FILE, MEMORY_INDEX_MAX_CHARS, McpClient, type McpClientOptions, type McpContentBlock, type McpProgressHandler, type McpProgressInfo, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type MemoryEntry, type MemoryScope, MemoryStore, type MemoryStoreOptions, type MemoryToolsOptions, type MemoryType, type WriteInput as MemoryWriteInput, NeedsConfirmationError, PROJECT_MEMORY_FILE, PROJECT_MEMORY_MAX_CHARS, type PageContent, type PickerCandidate, PlanProposedError, type PlanToolOptions, type ProgressNotificationParams, type ProjectMemory, type RankPickerOptions, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type ResolvedHook, type RetryInfo, type RetryOptions, type Role, type RunCommandResult, type RunHooksOptions, type ScavengeOptions, type ScavengeResult, type SearchResult, type SectionResult, type SessionInfo, SessionStats, type SessionSummary, type ShellToolsOptions, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type SubagentEvent, type SubagentSink, type SubagentToolOptions, type ToolCall, type ToolCallContext, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, USER_MEMORY_DIR, Usage, type UsageAggregate, type UsageBucket, type UsageRecord, VERSION, VolatileScratch, type WebFetchOptions, type WebSearchOptions, type WebToolsOptions, aggregateBranchUsage, aggregateUsage, analyzeSchema, appendSessionMessage, appendUsage, applyEditBlock, applyEditBlocks, applyMemoryStack, applyProjectMemory, applyUserMemory, bridgeMcpTools, bucketCacheHitRatio, bucketSavingsFraction, claudeEquivalentCost, codeSystemPrompt, compareVersions, computeReplayStats, costUsd, decideOutcome, defaultConfigPath, defaultSelector, defaultUsageLogPath, deleteSession, detectAtPicker, detectShellOperator, diffTranscripts, emptyPlanState, expandAtMentions, fetchWithRetry, fixToolCallPairing, flattenMcpResult, flattenSchema, forkRegistryExcluding, formatCommandResult, formatHookOutcomeMessage, formatLogSize, formatLoopError, formatSearchResults, getLatestVersion, globalSettingsPath, harvest, healLoadedMessages, healLoadedMessagesByTokens, htmlToText, injectPowerShellUtf8, inputCostUsd, inspectMcpServer, isAllowed, isJsonRpcError, isNpxInstall, isPlanStateEmpty, isPlausibleKey, listFilesSync, listFilesWithStatsSync, listSessions, loadApiKey, loadDotenv, loadHooks, loadSessionMessages, matchesTool, memoryEnabled, nestArguments, openTranscriptFile, outputCostUsd, parseEditBlocks, parseMcpSpec, parseMojeekResults, parseTranscript, prepareSpawn, projectHash, projectSettingsPath, quoteForCmdExe, rankPickerCandidates, readConfig, readProjectMemory, readTranscript, readUsageLog, recordFromLoopEvent, redactKey, registerFilesystemTools, registerMemoryTools, registerPlanTool, registerShellTools, registerSubagentTool, registerWebTools, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, resolveExecutable, restoreSnapshots, runBranches, runCommand, runHooks, sanitizeMemoryName, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, tokenizeCommand, truncateForModel, truncateForModelByTokens, webFetch, webSearch, withUtf8Codepage, writeConfig, writeMeta, writeRecord };
package/dist/index.js CHANGED
@@ -1563,6 +1563,11 @@ function deleteSession(name) {
1563
1563
  const path = sessionPath(name);
1564
1564
  try {
1565
1565
  unlinkSync(path);
1566
+ const sidecar = path.replace(/\.jsonl$/, ".pending.json");
1567
+ try {
1568
+ unlinkSync(sidecar);
1569
+ } catch {
1570
+ }
1566
1571
  return true;
1567
1572
  } catch {
1568
1573
  return false;
@@ -1590,13 +1595,18 @@ function countLines(path) {
1590
1595
 
1591
1596
  // src/telemetry.ts
1592
1597
  var DEEPSEEK_PRICING = {
1593
- "deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 },
1594
- "deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 }
1598
+ "deepseek-v4-flash": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
1599
+ "deepseek-v4-pro": { inputCacheHit: 0.139, inputCacheMiss: 1.667, output: 3.333 },
1600
+ // Compat aliases — priced as v4-flash per the deprecation notice.
1601
+ "deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 },
1602
+ "deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.139, output: 0.278 }
1595
1603
  };
1596
1604
  var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
1597
1605
  var DEEPSEEK_CONTEXT_TOKENS = {
1598
- "deepseek-chat": 131072,
1599
- "deepseek-reasoner": 131072
1606
+ "deepseek-v4-flash": 1e6,
1607
+ "deepseek-v4-pro": 1e6,
1608
+ "deepseek-chat": 1e6,
1609
+ "deepseek-reasoner": 1e6
1600
1610
  };
1601
1611
  var DEFAULT_CONTEXT_TOKENS = 131072;
1602
1612
  function costUsd(model, usage) {
@@ -1924,7 +1934,7 @@ var CacheFirstLoop = class {
1924
1934
  content: `aborted at iter ${iter}/${this.maxToolIters} \u2014 stopped without producing a summary (press \u2191 + Enter or /retry to resume)`
1925
1935
  };
1926
1936
  const stoppedMsg = "[aborted by user (Esc) \u2014 no summary produced. Ask again or /retry when ready; prior tool output is still in the log.]";
1927
- this.appendAndPersist({ role: "assistant", content: stoppedMsg });
1937
+ this.appendAndPersist(this.syntheticAssistantMessage(stoppedMsg));
1928
1938
  yield {
1929
1939
  turn: this._turn,
1930
1940
  role: "assistant_final",
@@ -2060,6 +2070,7 @@ var CacheFirstLoop = class {
2060
2070
  };
2061
2071
  } else if (this.stream) {
2062
2072
  const callBuf = /* @__PURE__ */ new Map();
2073
+ const readyIndices = /* @__PURE__ */ new Set();
2063
2074
  for await (const chunk of this.client.stream({
2064
2075
  model: this.model,
2065
2076
  messages,
@@ -2095,13 +2106,18 @@ var CacheFirstLoop = class {
2095
2106
  if (d.argumentsDelta)
2096
2107
  cur.function.arguments = (cur.function.arguments ?? "") + d.argumentsDelta;
2097
2108
  callBuf.set(d.index, cur);
2109
+ if (!readyIndices.has(d.index) && cur.function.name && looksLikeCompleteJson(cur.function.arguments ?? "")) {
2110
+ readyIndices.add(d.index);
2111
+ }
2098
2112
  if (cur.function.name) {
2099
2113
  yield {
2100
2114
  turn: this._turn,
2101
2115
  role: "tool_call_delta",
2102
2116
  content: "",
2103
2117
  toolName: cur.function.name,
2104
- toolCallArgsChars: (cur.function.arguments ?? "").length
2118
+ toolCallArgsChars: (cur.function.arguments ?? "").length,
2119
+ toolCallIndex: d.index,
2120
+ toolCallReadyCount: readyIndices.size
2105
2121
  };
2106
2122
  }
2107
2123
  }
@@ -2152,7 +2168,9 @@ var CacheFirstLoop = class {
2152
2168
  reasoningContent || null,
2153
2169
  assistantContent || null
2154
2170
  );
2155
- this.appendAndPersist(this.assistantMessage(assistantContent, repairedCalls));
2171
+ this.appendAndPersist(
2172
+ this.assistantMessage(assistantContent, repairedCalls, reasoningContent)
2173
+ );
2156
2174
  yield {
2157
2175
  turn: this._turn,
2158
2176
  role: "assistant_final",
@@ -2314,7 +2332,7 @@ ${reason}`;
2314
2332
 
2315
2333
  ${summary}`;
2316
2334
  const summaryStats = this.stats.record(this._turn, this.model, resp.usage ?? new Usage());
2317
- this.appendAndPersist({ role: "assistant", content: summary });
2335
+ this.appendAndPersist(this.assistantMessage(summary, [], resp.reasoningContent ?? void 0));
2318
2336
  yield {
2319
2337
  turn: this._turn,
2320
2338
  role: "assistant_final",
@@ -2343,12 +2361,35 @@ ${summary}`;
2343
2361
  }
2344
2362
  return final;
2345
2363
  }
2346
- assistantMessage(content, toolCalls) {
2364
+ assistantMessage(content, toolCalls, reasoningContent) {
2347
2365
  const msg = { role: "assistant", content };
2348
2366
  if (toolCalls.length > 0) msg.tool_calls = toolCalls;
2367
+ if (reasoningContent && reasoningContent.length > 0) {
2368
+ msg.reasoning_content = reasoningContent;
2369
+ }
2370
+ return msg;
2371
+ }
2372
+ /**
2373
+ * Build a synthetic assistant message we insert into the log without
2374
+ * a real API round trip (abort notices, future system injections).
2375
+ * Reasoner models reject follow-up requests whose assistant history
2376
+ * is missing `reasoning_content`, so we stamp an empty-string
2377
+ * placeholder on reasoner sessions to satisfy the validator. V3
2378
+ * doesn't care — field stays absent there.
2379
+ */
2380
+ syntheticAssistantMessage(content) {
2381
+ const msg = { role: "assistant", content };
2382
+ if (isThinkingModeModel(this.model)) {
2383
+ msg.reasoning_content = "";
2384
+ }
2349
2385
  return msg;
2350
2386
  }
2351
2387
  };
2388
+ function isThinkingModeModel(model) {
2389
+ if (model.includes("reasoner")) return true;
2390
+ if (model === "deepseek-v4-flash" || model === "deepseek-v4-pro") return true;
2391
+ return false;
2392
+ }
2352
2393
  function stripHallucinatedToolMarkup(s) {
2353
2394
  let out = s;
2354
2395
  out = out.replace(/<|DSML|function_calls>[\s\S]*?<\/?|DSML|function_calls>/g, "");
@@ -2364,6 +2405,15 @@ function safeParseToolArgs(raw) {
2364
2405
  return raw;
2365
2406
  }
2366
2407
  }
2408
+ function looksLikeCompleteJson(s) {
2409
+ if (!s || !s.trim()) return false;
2410
+ try {
2411
+ JSON.parse(s);
2412
+ return true;
2413
+ } catch {
2414
+ return false;
2415
+ }
2416
+ }
2367
2417
  function* hookWarnings(outcomes, turn) {
2368
2418
  for (const o of outcomes) {
2369
2419
  if (o.decision === "pass") continue;
@@ -2511,6 +2561,9 @@ var DEFAULT_PICKER_IGNORE_DIRS = [
2511
2561
  "__pycache__"
2512
2562
  ];
2513
2563
  function listFilesSync(root, opts = {}) {
2564
+ return listFilesWithStatsSync(root, opts).map((e) => e.path);
2565
+ }
2566
+ function listFilesWithStatsSync(root, opts = {}) {
2514
2567
  const maxResults = Math.max(1, opts.maxResults ?? 500);
2515
2568
  const ignore = new Set(opts.ignoreDirs ?? DEFAULT_PICKER_IGNORE_DIRS);
2516
2569
  const rootAbs = resolve(root);
@@ -2531,7 +2584,12 @@ function listFilesSync(root, opts = {}) {
2531
2584
  if (ent.name.startsWith(".") || ignore.has(ent.name)) continue;
2532
2585
  walk2(join4(dirAbs, ent.name), relPath);
2533
2586
  } else if (ent.isFile()) {
2534
- out.push(relPath);
2587
+ let mtimeMs = 0;
2588
+ try {
2589
+ mtimeMs = statSync2(join4(dirAbs, ent.name)).mtimeMs;
2590
+ } catch {
2591
+ }
2592
+ out.push({ path: relPath, mtimeMs });
2535
2593
  }
2536
2594
  }
2537
2595
  };
@@ -2546,12 +2604,31 @@ function detectAtPicker(input) {
2546
2604
  const atOffset = input.length - query.length - 1;
2547
2605
  return { query, atOffset };
2548
2606
  }
2549
- function rankPickerCandidates(files, query, limit = 40) {
2550
- if (!query) return files.slice(0, limit);
2607
+ function rankPickerCandidates(files, query, limitOrOpts) {
2608
+ const opts = typeof limitOrOpts === "number" ? { limit: limitOrOpts } : limitOrOpts ?? {};
2609
+ const limit = opts.limit ?? 40;
2610
+ const recent = new Set(opts.recentlyUsed ?? []);
2611
+ const entries = files.map(
2612
+ (f) => typeof f === "string" ? { path: f, mtimeMs: 0 } : f
2613
+ );
2614
+ if (!query) {
2615
+ const anyMtime = entries.some((e) => e.mtimeMs > 0);
2616
+ if (!anyMtime && recent.size === 0) {
2617
+ return entries.slice(0, limit).map((e) => e.path);
2618
+ }
2619
+ const sorted = [...entries].sort((a, b) => {
2620
+ const aRecent = recent.has(a.path) ? 1 : 0;
2621
+ const bRecent = recent.has(b.path) ? 1 : 0;
2622
+ if (aRecent !== bRecent) return bRecent - aRecent;
2623
+ if (a.mtimeMs !== b.mtimeMs) return b.mtimeMs - a.mtimeMs;
2624
+ return a.path.localeCompare(b.path);
2625
+ });
2626
+ return sorted.slice(0, limit).map((e) => e.path);
2627
+ }
2551
2628
  const needle = query.toLowerCase();
2552
2629
  const scored = [];
2553
- for (const f of files) {
2554
- const lower = f.toLowerCase();
2630
+ for (const e of entries) {
2631
+ const lower = e.path.toLowerCase();
2555
2632
  const hit = lower.indexOf(needle);
2556
2633
  if (hit < 0) continue;
2557
2634
  const slash = lower.lastIndexOf("/");
@@ -2559,9 +2636,18 @@ function rankPickerCandidates(files, query, limit = 40) {
2559
2636
  let score = 2;
2560
2637
  if (base.startsWith(needle)) score = 0;
2561
2638
  else if (lower.startsWith(needle)) score = 1;
2562
- scored.push({ path: f, score: score * 1e4 + hit });
2639
+ scored.push({
2640
+ path: e.path,
2641
+ score: score * 1e4 + hit,
2642
+ mtimeMs: e.mtimeMs,
2643
+ recent: recent.has(e.path)
2644
+ });
2563
2645
  }
2564
- scored.sort((a, b) => a.score - b.score);
2646
+ scored.sort((a, b) => {
2647
+ if (a.score !== b.score) return a.score - b.score;
2648
+ if (a.recent !== b.recent) return a.recent ? -1 : 1;
2649
+ return b.mtimeMs - a.mtimeMs;
2650
+ });
2565
2651
  return scored.slice(0, limit).map((s) => s.path);
2566
2652
  }
2567
2653
  var AT_MENTION_PATTERN = /(?<=^|\s)@([a-zA-Z0-9_./\\-]+)/g;
@@ -3925,11 +4011,14 @@ async function spawnSubagent(opts) {
3925
4011
  const maxToolIters = opts.maxToolIters ?? DEFAULT_MAX_ITERS;
3926
4012
  const maxResultChars = opts.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS2;
3927
4013
  const sink = opts.sink;
4014
+ const skillName = opts.skillName;
3928
4015
  const startedAt = Date.now();
3929
4016
  const taskPreview = opts.task.length > 30 ? `${opts.task.slice(0, 30)}\u2026` : opts.task;
3930
4017
  sink?.current?.({
3931
4018
  kind: "start",
3932
4019
  task: taskPreview,
4020
+ skillName,
4021
+ model,
3933
4022
  iter: 0,
3934
4023
  elapsedMs: 0
3935
4024
  });
@@ -3959,6 +4048,8 @@ async function spawnSubagent(opts) {
3959
4048
  sink?.current?.({
3960
4049
  kind: "progress",
3961
4050
  task: taskPreview,
4051
+ skillName,
4052
+ model,
3962
4053
  iter: toolIter,
3963
4054
  elapsedMs: Date.now() - startedAt
3964
4055
  });
@@ -3981,17 +4072,22 @@ async function spawnSubagent(opts) {
3981
4072
  const elapsedMs = Date.now() - startedAt;
3982
4073
  const turns = childLoop.stats.turns.length;
3983
4074
  const costUsd2 = childLoop.stats.totalCost;
4075
+ const usage = aggregateChildUsage(childLoop);
3984
4076
  const truncated = final.length > maxResultChars ? `${final.slice(0, maxResultChars)}
3985
4077
 
3986
4078
  [\u2026truncated ${final.length - maxResultChars} chars; ask the subagent for a tighter summary if you need more.]` : final;
3987
4079
  sink?.current?.({
3988
4080
  kind: "end",
3989
4081
  task: taskPreview,
4082
+ skillName,
4083
+ model,
3990
4084
  iter: toolIter,
3991
4085
  elapsedMs,
3992
4086
  summary: errorMessage ? void 0 : truncated.slice(0, 120),
3993
4087
  error: errorMessage,
3994
- turns
4088
+ turns,
4089
+ costUsd: costUsd2,
4090
+ usage
3995
4091
  });
3996
4092
  return {
3997
4093
  success: !errorMessage,
@@ -4000,9 +4096,23 @@ async function spawnSubagent(opts) {
4000
4096
  turns,
4001
4097
  toolIters: toolIter,
4002
4098
  elapsedMs,
4003
- costUsd: costUsd2
4099
+ costUsd: costUsd2,
4100
+ model,
4101
+ skillName,
4102
+ usage
4004
4103
  };
4005
4104
  }
4105
+ function aggregateChildUsage(loop) {
4106
+ const agg = new Usage();
4107
+ for (const t of loop.stats.turns) {
4108
+ agg.promptTokens += t.usage.promptTokens;
4109
+ agg.completionTokens += t.usage.completionTokens;
4110
+ agg.totalTokens += t.usage.totalTokens;
4111
+ agg.promptCacheHitTokens += t.usage.promptCacheHitTokens;
4112
+ agg.promptCacheMissTokens += t.usage.promptCacheMissTokens;
4113
+ }
4114
+ return agg;
4115
+ }
4006
4116
  function formatSubagentResult(r) {
4007
4117
  if (!r.success) {
4008
4118
  return JSON.stringify({
@@ -4045,8 +4155,8 @@ function registerSubagentTool(parentRegistry, opts) {
4045
4155
  },
4046
4156
  model: {
4047
4157
  type: "string",
4048
- enum: ["deepseek-chat", "deepseek-reasoner"],
4049
- description: "Which DeepSeek model the subagent runs on. 'deepseek-chat' (V3) is the default \u2014 fast and cheap. Use 'deepseek-reasoner' (R1) only when the subtask genuinely needs planning or multi-step reasoning; it is roughly 5-10x more expensive."
4158
+ enum: ["deepseek-v4-flash", "deepseek-v4-pro", "deepseek-chat", "deepseek-reasoner"],
4159
+ description: "Which DeepSeek model the subagent runs on. 'deepseek-v4-flash' (default; thinking mode) is fast and cheap and is what the legacy 'deepseek-chat' / 'deepseek-reasoner' aliases route to today. Use 'deepseek-v4-pro' only when the subtask needs the strongest model \u2014 roughly 12\xD7 the input cost and 12\xD7 the output cost vs flash."
4050
4160
  }
4051
4161
  },
4052
4162
  required: ["task"]
@@ -6209,6 +6319,8 @@ function appendUsage(input) {
6209
6319
  costUsd: costUsd(input.model, input.usage),
6210
6320
  claudeEquivUsd: claudeEquivalentCost(input.usage)
6211
6321
  };
6322
+ if (input.kind === "subagent") record.kind = "subagent";
6323
+ if (input.subagent) record.subagent = input.subagent;
6212
6324
  const path = input.path ?? defaultUsageLogPath();
6213
6325
  try {
6214
6326
  mkdirSync6(dirname7(path), { recursive: true });
@@ -6282,6 +6394,10 @@ function aggregateUsage(records, opts = {}) {
6282
6394
  const sessionCounts = /* @__PURE__ */ new Map();
6283
6395
  let firstSeen = null;
6284
6396
  let lastSeen = null;
6397
+ const skillCounts = /* @__PURE__ */ new Map();
6398
+ let subagentTotal = 0;
6399
+ let subagentCost = 0;
6400
+ let subagentDuration = 0;
6285
6401
  for (const r of records) {
6286
6402
  addToBucket(all, r);
6287
6403
  if (r.ts >= today.since) addToBucket(today, r);
@@ -6292,15 +6408,34 @@ function aggregateUsage(records, opts = {}) {
6292
6408
  sessionCounts.set(sessKey, (sessionCounts.get(sessKey) ?? 0) + 1);
6293
6409
  if (firstSeen === null || r.ts < firstSeen) firstSeen = r.ts;
6294
6410
  if (lastSeen === null || r.ts > lastSeen) lastSeen = r.ts;
6411
+ if (r.kind === "subagent") {
6412
+ subagentTotal += 1;
6413
+ subagentCost += r.costUsd;
6414
+ const dur = r.subagent?.durationMs ?? 0;
6415
+ subagentDuration += dur;
6416
+ const key = r.subagent?.skillName?.trim() || "(adhoc)";
6417
+ const prev = skillCounts.get(key) ?? { count: 0, costUsd: 0, durationMs: 0 };
6418
+ prev.count += 1;
6419
+ prev.costUsd += r.costUsd;
6420
+ prev.durationMs += dur;
6421
+ skillCounts.set(key, prev);
6422
+ }
6295
6423
  }
6296
6424
  const byModel = Array.from(modelCounts.entries()).map(([model, turns]) => ({ model, turns })).sort((a, b) => b.turns - a.turns);
6297
6425
  const bySession = Array.from(sessionCounts.entries()).map(([session, turns]) => ({ session, turns })).sort((a, b) => b.turns - a.turns);
6426
+ const subagents = subagentTotal > 0 ? {
6427
+ total: subagentTotal,
6428
+ costUsd: subagentCost,
6429
+ totalDurationMs: subagentDuration,
6430
+ bySkill: Array.from(skillCounts.entries()).map(([skillName, v]) => ({ skillName, ...v })).sort((a, b) => b.count - a.count)
6431
+ } : void 0;
6298
6432
  return {
6299
6433
  buckets: [today, week, month, all],
6300
6434
  byModel,
6301
6435
  bySession,
6302
6436
  firstSeen,
6303
- lastSeen
6437
+ lastSeen,
6438
+ subagents
6304
6439
  };
6305
6440
  }
6306
6441
  function formatLogSize(path = defaultUsageLogPath()) {
@@ -6404,6 +6539,7 @@ export {
6404
6539
  isPlanStateEmpty,
6405
6540
  isPlausibleKey,
6406
6541
  listFilesSync,
6542
+ listFilesWithStatsSync,
6407
6543
  listSessions,
6408
6544
  loadApiKey,
6409
6545
  loadDotenv,