moderado 0.3.4 → 0.3.7

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.
Files changed (68) hide show
  1. package/dist/budget.d.ts +6 -0
  2. package/dist/budget.d.ts.map +1 -0
  3. package/dist/budget.js +25 -0
  4. package/dist/budget.js.map +1 -0
  5. package/dist/commands/chat.d.ts +5 -1
  6. package/dist/commands/chat.d.ts.map +1 -1
  7. package/dist/commands/chat.js +170 -41
  8. package/dist/commands/chat.js.map +1 -1
  9. package/dist/commands/models.d.ts.map +1 -1
  10. package/dist/commands/models.js +61 -14
  11. package/dist/commands/models.js.map +1 -1
  12. package/dist/config.d.ts +73 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +116 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/model_pricing.d.ts +14 -1
  17. package/dist/model_pricing.d.ts.map +1 -1
  18. package/dist/model_pricing.js +52 -0
  19. package/dist/model_pricing.js.map +1 -1
  20. package/dist/repo_map.d.ts +3 -0
  21. package/dist/repo_map.d.ts.map +1 -0
  22. package/dist/repo_map.js +29 -0
  23. package/dist/repo_map.js.map +1 -0
  24. package/dist/sessions.d.ts +22 -15
  25. package/dist/sessions.d.ts.map +1 -1
  26. package/dist/sessions.js +16 -1
  27. package/dist/sessions.js.map +1 -1
  28. package/dist/skills.d.ts +2 -2
  29. package/dist/ui/model_selector.d.ts +2 -2
  30. package/dist/ui/model_selector.d.ts.map +1 -1
  31. package/dist/ui/model_selector.js +25 -16
  32. package/dist/ui/model_selector.js.map +1 -1
  33. package/dist/ui/provider_connect.d.ts +6 -3
  34. package/dist/ui/provider_connect.d.ts.map +1 -1
  35. package/dist/ui/provider_connect.js +41 -14
  36. package/dist/ui/provider_connect.js.map +1 -1
  37. package/dist/ui/renderer.d.ts +4 -0
  38. package/dist/ui/renderer.d.ts.map +1 -1
  39. package/dist/ui/renderer.js +36 -0
  40. package/dist/ui/renderer.js.map +1 -1
  41. package/dist/ui/welcome.d.ts +11 -0
  42. package/dist/ui/welcome.d.ts.map +1 -1
  43. package/dist/ui/welcome.js +38 -20
  44. package/dist/ui/welcome.js.map +1 -1
  45. package/dist/vendor/contracts/events.d.ts +159 -0
  46. package/dist/vendor/contracts/events.d.ts.map +1 -1
  47. package/dist/vendor/contracts/events.js +12 -0
  48. package/dist/vendor/contracts/events.js.map +1 -1
  49. package/dist/vendor/contracts/provider.d.ts +1 -0
  50. package/dist/vendor/contracts/provider.d.ts.map +1 -1
  51. package/dist/vendor/contracts/provider.js +6 -0
  52. package/dist/vendor/contracts/provider.js.map +1 -1
  53. package/dist/vendor/core/agent.d.ts +2 -1
  54. package/dist/vendor/core/agent.d.ts.map +1 -1
  55. package/dist/vendor/core/agent.js +123 -9
  56. package/dist/vendor/core/agent.js.map +1 -1
  57. package/dist/vendor/providers/model_discovery.d.ts +2 -2
  58. package/dist/vendor/providers/model_discovery.d.ts.map +1 -1
  59. package/dist/vendor/providers/model_discovery.js +4 -2
  60. package/dist/vendor/providers/model_discovery.js.map +1 -1
  61. package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts.map +1 -1
  62. package/dist/vendor/providers/nvidia/nvidia_adapter.js +1 -0
  63. package/dist/vendor/providers/nvidia/nvidia_adapter.js.map +1 -1
  64. package/dist/vendor/providers/nvidia/sse_parser.d.ts +2 -1
  65. package/dist/vendor/providers/nvidia/sse_parser.d.ts.map +1 -1
  66. package/dist/vendor/providers/nvidia/sse_parser.js +30 -16
  67. package/dist/vendor/providers/nvidia/sse_parser.js.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ export declare function parseBudgetCommand(input: string, currentLimit?: number): {
2
+ limit?: number;
3
+ message: string;
4
+ };
5
+ export declare function evaluateTokenBudget(totalTokens: number, limit?: number): 'ok' | 'warning' | 'stop';
6
+ //# sourceMappingURL=budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAY5G;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAKlG"}
package/dist/budget.js ADDED
@@ -0,0 +1,25 @@
1
+ export function parseBudgetCommand(input, currentLimit) {
2
+ const value = input.slice('/budget'.length).trim();
3
+ if (!value)
4
+ return { limit: currentLimit, message: currentLimit
5
+ ? `Token budget: ${currentLimit.toLocaleString()} per task (best-effort stop).`
6
+ : 'Token budget is off. Use /budget <tokens> to set a per-task limit.' };
7
+ if (value === 'off')
8
+ return { limit: undefined, message: 'Token budget disabled.' };
9
+ const amount = value.replace(/^tokens\s+/, '');
10
+ const limit = /^\d+$/.test(amount) ? Number(amount) : NaN;
11
+ if (!Number.isSafeInteger(limit) || limit < 1) {
12
+ return { limit: currentLimit, message: 'Usage: /budget <tokens> | /budget off. Cost limits require reliable provider pricing and are not available.' };
13
+ }
14
+ return { limit, message: `Token budget: ${limit.toLocaleString()} per task (best-effort stop).` };
15
+ }
16
+ export function evaluateTokenBudget(totalTokens, limit) {
17
+ if (!limit)
18
+ return 'ok';
19
+ if (totalTokens >= limit)
20
+ return 'stop';
21
+ if (totalTokens >= limit * 0.8)
22
+ return 'warning';
23
+ return 'ok';
24
+ }
25
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,YAAqB;IACrE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;gBAC7D,CAAC,CAAC,iBAAiB,YAAY,CAAC,cAAc,EAAE,+BAA+B;gBAC/E,CAAC,CAAC,oEAAoE,EAAE,CAAC;IAC3E,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpF,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,6GAA6G,EAAE,CAAC;IACzJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,KAAK,CAAC,cAAc,EAAE,+BAA+B,EAAE,CAAC;AACpG,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,KAAc;IACrE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,WAAW,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC;IACxC,IAAI,WAAW,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,SAAS,CAAC;IACjD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -4,9 +4,13 @@ import type { WebSearchProviderName, WebSearchToolOptions } from '@moderado/tool
4
4
  import { ApprovalDecision, ApprovalRequest, ChatMessage, IToolRegistry, McpServerConfig, ToolResult } from '@moderado/contracts';
5
5
  import { CliParsedArgs } from '../args.js';
6
6
  import { ModeradoConfig } from '../config.js';
7
+ import { StoredSession } from '../sessions.js';
7
8
  import { selectConfirmPopup, selectListPopup } from '../ui/popup.js';
9
+ export declare function formatSessionExitSummary(session: StoredSession, modelId?: string): string;
8
10
  export declare function createAgentTask(request: string, mode: 'Plan' | 'Execute'): string;
9
- export declare const STANDARD_SLASH_COMMANDS: readonly ["/connect", "/model", "/init", "/btw", "/mcp", "/session", "/queue", "/workflow", "/skills", "/clear", "/help", "/exit", "/quit"];
11
+ export declare function createReviewTask(changedFiles: string[]): string;
12
+ export declare function createReviewToolRegistry(): IToolRegistry;
13
+ export declare const STANDARD_SLASH_COMMANDS: readonly ["/connect", "/model", "/init", "/btw", "/mcp", "/session", "/queue", "/workflow", "/review", "/map", "/budget", "/skills", "/clear", "/help", "/exit", "/quit"];
10
14
  export declare function levenshteinDistance(a: string, b: string): number;
11
15
  export interface SlashCommandAdvice {
12
16
  isSlashCommand: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import fs from 'node:fs';
3
3
  import { AgentLoop, PolicyManager, Router, cleanConversationalFiller, DEFAULT_MAX_OUTPUT_TOKENS } from '../vendor/core/index.js';
4
4
  import { NvidiaAdapter } from '../vendor/providers/index.js';
5
- import { createDefaultToolRegistry, canonicalizeRoot, createMcpTools, createWebSearchTool, discoverMcpServers, resolveInJail, WorkspaceCheckpointStore, WriteFileTool } from '../vendor/tools/index.js';
5
+ import { createDefaultToolRegistry, ToolRegistry, canonicalizeRoot, createMcpTools, createWebSearchTool, discoverMcpServers, resolveInJail, WorkspaceCheckpointStore, WriteFileTool } from '../vendor/tools/index.js';
6
6
  import { McpServerConfigSchema } from '../vendor/contracts/index.js';
7
7
  import { getActiveConnection, loadConfig, removeMcpServer, resolveApiKey, resolveConnectionCredential, saveConnection, saveMcpServer, setMcpServerEnabled, storeConnectionCredential } from '../config.js';
8
8
  import { MemoryCredentialStore } from '../credentials.js';
@@ -13,13 +13,29 @@ import { connectProviderInteractive, isAuthenticationFailure, replaceProviderKey
13
13
  import { initWorkspace } from './init.js';
14
14
  import { expandMentions, createWorkspaceFileSource } from '../ui/file_mentions.js';
15
15
  import { listFiles } from '../vendor/tools/index.js';
16
- import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
17
- import { calculateOutputTokenRate, calculateSessionCost, compactSessionMessages, createSession, exportSessionMarkdown, formatSessionCost, SessionStore } from '../sessions.js';
16
+ import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
17
+ import { accumulateSessionUsage, compactSessionMessages, createSession, exportSessionMarkdown, formatSessionCost, SessionStore } from '../sessions.js';
18
18
  import { layerPromptBox, renderBoxLines, selectConfirmPopup, selectListPopup } from '../ui/popup.js';
19
19
  import { askModalChoice } from '../ui/prompt.js';
20
20
  import { findModelPricing } from '../model_pricing.js';
21
21
  import { inspectGitWorkspace, readGitDiff } from '../vendor/tools/index.js';
22
+ import { buildWorkspaceMap } from '../repo_map.js';
23
+ import { evaluateTokenBudget, parseBudgetCommand } from '../budget.js';
22
24
  import { discoverSkills, formatSkillContext } from '../skills.js';
25
+ export function formatSessionExitSummary(session, modelId) {
26
+ const usage = session.usage;
27
+ const tokens = usage.available
28
+ ? `Input ${usage.promptTokens.toLocaleString('en-US')} | Output ${usage.completionTokens.toLocaleString('en-US')} | Total ${usage.totalTokens.toLocaleString('en-US')}${usage.estimated ? ' (estimated)' : ''}`
29
+ : 'Usage unavailable';
30
+ return [
31
+ '\x1b[1;38;5;25mSession Summary\x1b[0m',
32
+ `ID: ${session.id}`,
33
+ `Model: ${modelId ?? session.modelId ?? 'No model selected'}`,
34
+ `Tokens: ${tokens}`,
35
+ `Cost: ${formatSessionCost(usage)}`,
36
+ 'Resume: /session resume',
37
+ ].join('\n');
38
+ }
23
39
  function mutationPaths(toolName, parameters) {
24
40
  const value = parameters;
25
41
  if (toolName === 'apply_patch' && Array.isArray(value.edits)) {
@@ -32,6 +48,19 @@ export function createAgentTask(request, mode) {
32
48
  return request;
33
49
  return `Create a concise implementation checklist for this request. Inspect files as needed, but do not modify files or run commands.\n\nUser request:\n${request}`;
34
50
  }
51
+ export function createReviewTask(changedFiles) {
52
+ return `Review the current workspace changes. Inspect staged and unstaged Git diffs with git_diff, then inspect relevant surrounding code. Also review untracked files listed below with read_file. Do not modify files or run commands. Report only actionable bugs or security issues, ordered by severity, with file paths and line numbers. If none are found, say "No findings". Keep the report concise.\n\nChanged files:\n${changedFiles.join('\n') || '(none)'}`;
53
+ }
54
+ export function createReviewToolRegistry() {
55
+ const source = createDefaultToolRegistry();
56
+ const registry = new ToolRegistry();
57
+ for (const name of ['git_diff', 'read_file', 'list_files', 'search_files', 'get_definition', 'find_references']) {
58
+ const tool = source.get(name);
59
+ if (tool)
60
+ registry.register(tool);
61
+ }
62
+ return registry;
63
+ }
35
64
  export const STANDARD_SLASH_COMMANDS = [
36
65
  '/connect',
37
66
  '/model',
@@ -41,6 +70,9 @@ export const STANDARD_SLASH_COMMANDS = [
41
70
  '/session',
42
71
  '/queue',
43
72
  '/workflow',
73
+ '/review',
74
+ '/map',
75
+ '/budget',
44
76
  '/skills',
45
77
  '/clear',
46
78
  '/help',
@@ -139,6 +171,10 @@ export function formatTurnFailureAnswer(status, errorEvent, modelId, otherConnec
139
171
  return `⚠️ **Model Error (${errorEvent?.code || 'ERR_INFERENCE_FAILED'}):**\n${cleanDetail || 'Model returned no response.'}\n\n👉 **Suggestion:** The model \`${modelId}\` encountered an error. Switch models with \`/model\` or switch providers with \`/connect\`${altSuggestion}.`;
140
172
  }
141
173
  export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastErrorEvent, streamedAnswer = '', currentModel = 'unknown', otherConnections = []) {
174
+ if (result.status === 'cancelled') {
175
+ const partial = cleanConversationalFiller(streamedAnswer);
176
+ return { answer: [partial, 'Task stopped.'].filter(Boolean).join('\n\n'), isError: true };
177
+ }
142
178
  if (result.status === 'failed' || lastErrorEvent) {
143
179
  return {
144
180
  answer: formatTurnFailureAnswer(result.status, lastErrorEvent, result.selectedModel?.id || currentModel, otherConnections),
@@ -216,15 +252,9 @@ export function handleGenerationKeypress(str, key, queue, actions) {
216
252
  actions.abort();
217
253
  return;
218
254
  }
219
- // 2. Escape: if draft has text, clear draft; if draft is empty, abort generation
255
+ // 2. Escape always stops the active task; the caller clears queued input.
220
256
  if (isGenerationCancelKey(key, str)) {
221
- if (queue.currentDraft.length > 0) {
222
- queue.setDraft('');
223
- actions.onDraftChange();
224
- }
225
- else {
226
- actions.abort();
227
- }
257
+ actions.abort();
228
258
  return;
229
259
  }
230
260
  // 3. Return / Enter: commit draft or execute /btw immediately
@@ -422,9 +452,6 @@ export async function decideApproval(request, options, signal) {
422
452
  if (options.autoApprove && !request.toolName.startsWith('mcp.')) {
423
453
  return { requestId: request.requestId, status: 'approved' };
424
454
  }
425
- if (['write_file', 'edit_file', 'apply_patch', 'run_command', 'run_diagnostics'].includes(request.toolName)) {
426
- return { requestId: request.requestId, status: 'approved' };
427
- }
428
455
  return options.requestInteractiveApproval(request, signal);
429
456
  }
430
457
  export async function createMcpToolRegistry(servers, webSearch = {}) {
@@ -697,7 +724,8 @@ export async function handleChatSession(args, version, signal) {
697
724
  if (activeConnection)
698
725
  activateConnection(activeConnection, args.model);
699
726
  let activeMode = args.readOnly ? 'Plan' : 'Execute';
700
- let activeAutoApprove = Boolean(args.autoApprove);
727
+ let activeAutoApprove = !args.nonInteractive;
728
+ let tokenBudget;
701
729
  const sessionStore = new SessionStore();
702
730
  let activeSession = sessionStore.loadLatestSession(canonicalWorkspace) ??
703
731
  createSession(canonicalWorkspace, {
@@ -742,8 +770,10 @@ export async function handleChatSession(args, version, signal) {
742
770
  let lastAnswer = '';
743
771
  let lastThoughtTime = 0;
744
772
  let lastOutputTokenRate;
773
+ let lastTokenUsage;
745
774
  let activePlan;
746
775
  const costLabel = () => formatSessionCost(activeSession.usage);
776
+ const exitMessage = () => `\x1b[32mGoodbye! Stay Tuned with Moderado!\x1b[0m\n\n${formatSessionExitSummary(activeSession, currentModel)}`;
747
777
  const commandQueue = new TurnCommandQueue();
748
778
  const btwHistory = [];
749
779
  while (!signal?.aborted) {
@@ -755,8 +785,9 @@ export async function handleChatSession(args, version, signal) {
755
785
  else {
756
786
  const turn = await promptInteractiveTurn({
757
787
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
758
- usageAvailable: activeSession.usage.available, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, outputTokenRate: lastOutputTokenRate,
788
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, outputTokenRate: lastOutputTokenRate, tokenUsage: lastTokenUsage,
759
789
  queuedCommands: commandQueue.items,
790
+ onExit: exitMessage,
760
791
  questionHistory: conversationHistory.flatMap((message) => message.role === 'user' && message.content?.trim() ? [message.content] : []),
761
792
  onModelSelect: async (drawFrame) => {
762
793
  if (!activeConnection) {
@@ -770,7 +801,6 @@ export async function handleChatSession(args, version, signal) {
770
801
  providerId: activeConnection.id,
771
802
  providerName: activeConnection.displayName,
772
803
  currentModel,
773
- allModelsFree: activeConnection.id === 'agnes-ai',
774
804
  signal,
775
805
  drawFrame,
776
806
  });
@@ -792,7 +822,7 @@ export async function handleChatSession(args, version, signal) {
792
822
  return selection.modelId;
793
823
  },
794
824
  onConnect: async (drawFrame) => {
795
- const connection = await connectProviderInteractive({ signal, drawFrame, savedConnections: config.connections, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
825
+ const connection = await connectProviderInteractive({ signal, drawFrame, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
796
826
  if (!connection)
797
827
  return currentModel;
798
828
  const runtimeConnection = process.platform === 'win32' ? await storeConnectionCredential(connection, credentialStore) : connection;
@@ -808,6 +838,7 @@ export async function handleChatSession(args, version, signal) {
808
838
  lastAnswer = '';
809
839
  lastThoughtTime = 0;
810
840
  lastOutputTokenRate = undefined;
841
+ lastTokenUsage = undefined;
811
842
  activeSession.messages = [];
812
843
  sessionStore.save(activeSession);
813
844
  },
@@ -926,6 +957,7 @@ export async function handleChatSession(args, version, signal) {
926
957
  lastAnswer = '';
927
958
  lastThoughtTime = 0;
928
959
  lastOutputTokenRate = undefined;
960
+ lastTokenUsage = undefined;
929
961
  sessionStore.save(activeSession);
930
962
  return;
931
963
  }
@@ -1026,11 +1058,46 @@ export async function handleChatSession(args, version, signal) {
1026
1058
  continue;
1027
1059
  }
1028
1060
  }
1061
+ if (trimmed === '/budget' || trimmed.startsWith('/budget ')) {
1062
+ const budget = parseBudgetCommand(trimmed, tokenBudget);
1063
+ tokenBudget = budget.limit;
1064
+ lastQuestion = trimmed;
1065
+ lastAnswer = budget.message;
1066
+ lastThoughtTime = 0.001;
1067
+ lastOutputTokenRate = undefined;
1068
+ lastTokenUsage = undefined;
1069
+ process.stdout.write('\x1b[H\x1b[J');
1070
+ process.stdout.write(renderFullWelcomeScreen({
1071
+ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1072
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1073
+ autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1074
+ chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
1075
+ }, process.stdout.rows));
1076
+ process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
1077
+ continue;
1078
+ }
1079
+ if (trimmed === '/map') {
1080
+ lastQuestion = trimmed;
1081
+ lastAnswer = await buildWorkspaceMap(canonicalWorkspace);
1082
+ lastThoughtTime = 0.001;
1083
+ lastOutputTokenRate = undefined;
1084
+ lastTokenUsage = undefined;
1085
+ process.stdout.write('\x1b[H\x1b[J');
1086
+ process.stdout.write(renderFullWelcomeScreen({
1087
+ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1088
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1089
+ autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1090
+ chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
1091
+ }, process.stdout.rows));
1092
+ process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
1093
+ continue;
1094
+ }
1029
1095
  if (trimmed.startsWith('/queue')) {
1030
1096
  const args = trimmed.slice('/queue'.length).trim();
1031
1097
  lastQuestion = trimmed;
1032
1098
  lastThoughtTime = 0.001;
1033
1099
  lastOutputTokenRate = undefined;
1100
+ lastTokenUsage = undefined;
1034
1101
  if (!args || args === 'list') {
1035
1102
  lastAnswer = commandQueue.length === 0
1036
1103
  ? 'Command queue is empty. Use `/queue <command>` to add commands.'
@@ -1053,7 +1120,7 @@ export async function handleChatSession(args, version, signal) {
1053
1120
  process.stdout.write('\x1b[H\x1b[J');
1054
1121
  process.stdout.write(renderFullWelcomeScreen({
1055
1122
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1056
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1123
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1057
1124
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1058
1125
  chatThoughtTime: lastThoughtTime,
1059
1126
  queuedCommands: commandQueue.items,
@@ -1068,6 +1135,7 @@ export async function handleChatSession(args, version, signal) {
1068
1135
  lastAnswer = '';
1069
1136
  lastThoughtTime = 0;
1070
1137
  lastOutputTokenRate = undefined;
1138
+ lastTokenUsage = undefined;
1071
1139
  activeSession.messages = [];
1072
1140
  sessionStore.save(activeSession);
1073
1141
  continue;
@@ -1077,6 +1145,7 @@ export async function handleChatSession(args, version, signal) {
1077
1145
  lastAnswer = 'To leave Moderado, type /exit.';
1078
1146
  lastThoughtTime = 0;
1079
1147
  lastOutputTokenRate = undefined;
1148
+ lastTokenUsage = undefined;
1080
1149
  continue;
1081
1150
  }
1082
1151
  // Non-standard or misspelled slash command rejection with advice
@@ -1086,10 +1155,11 @@ export async function handleChatSession(args, version, signal) {
1086
1155
  lastAnswer = slashAdvice.advice;
1087
1156
  lastThoughtTime = 0.001;
1088
1157
  lastOutputTokenRate = undefined;
1158
+ lastTokenUsage = undefined;
1089
1159
  process.stdout.write('\x1b[H\x1b[J');
1090
1160
  process.stdout.write(renderFullWelcomeScreen({
1091
1161
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1092
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1162
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1093
1163
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1094
1164
  chatThoughtTime: lastThoughtTime,
1095
1165
  queuedCommands: commandQueue.items,
@@ -1105,13 +1175,14 @@ export async function handleChatSession(args, version, signal) {
1105
1175
  : 'No valid user skills found in ~/.moderado/skills.';
1106
1176
  lastThoughtTime = 0.001;
1107
1177
  lastOutputTokenRate = undefined;
1178
+ lastTokenUsage = undefined;
1108
1179
  process.stdout.write('\x1b[H\x1b[J');
1109
- process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
1180
+ process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
1110
1181
  process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
1111
1182
  continue;
1112
1183
  }
1113
1184
  if (trimmed === '/exit' || trimmed === '/quit') {
1114
- exitCleanly('\x1b[32mGoodbye! Stay Tuned with Moderado!\x1b[0m');
1185
+ exitCleanly(exitMessage());
1115
1186
  }
1116
1187
  if (trimmed === '/help') {
1117
1188
  lastQuestion = trimmed;
@@ -1123,16 +1194,20 @@ export async function handleChatSession(args, version, signal) {
1123
1194
  ' /session - Create, resume, undo, redo, share, export, or compact sessions\n' +
1124
1195
  ' /queue - Add, inspect, or clear queued follow-up commands\n' +
1125
1196
  ' /workflow - Inspect Git, build plans, or undo agent changes\n' +
1197
+ ' /review - Review current workspace changes for bugs\n' +
1198
+ ' /map - Show a compact workspace file map\n' +
1199
+ ' /budget - Set a per-task token limit (/budget 5000, /budget off)\n' +
1126
1200
  ' /skills - List installed user skills and reload them\n' +
1127
1201
  ' /clear - Reset conversation memory\n' +
1128
1202
  ' /help - Display commands, shortcuts & version\n' +
1129
1203
  ' /exit - Exit Moderado';
1130
1204
  lastThoughtTime = 0.001;
1131
1205
  lastOutputTokenRate = undefined;
1206
+ lastTokenUsage = undefined;
1132
1207
  process.stdout.write('\x1b[H\x1b[J');
1133
1208
  process.stdout.write(renderFullWelcomeScreen({
1134
1209
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1135
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1210
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1136
1211
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1137
1212
  chatThoughtTime: lastThoughtTime,
1138
1213
  queuedCommands: commandQueue.items,
@@ -1158,13 +1233,14 @@ export async function handleChatSession(args, version, signal) {
1158
1233
  lastAnswer = localMetaAnswer;
1159
1234
  lastThoughtTime = 0.001;
1160
1235
  lastOutputTokenRate = undefined;
1236
+ lastTokenUsage = undefined;
1161
1237
  conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: localMetaAnswer }];
1162
1238
  activeSession.messages = conversationHistory;
1163
1239
  sessionStore.save(activeSession);
1164
1240
  process.stdout.write('\x1b[H\x1b[J');
1165
1241
  process.stdout.write(renderFullWelcomeScreen({
1166
1242
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1167
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1243
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1168
1244
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1169
1245
  chatThoughtTime: lastThoughtTime,
1170
1246
  queuedCommands: commandQueue.items,
@@ -1177,13 +1253,14 @@ export async function handleChatSession(args, version, signal) {
1177
1253
  lastQuestion = trimmed;
1178
1254
  lastThoughtTime = 0.001;
1179
1255
  lastOutputTokenRate = undefined;
1256
+ lastTokenUsage = undefined;
1180
1257
  conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: lastAnswer }];
1181
1258
  activeSession.messages = conversationHistory;
1182
1259
  sessionStore.save(activeSession);
1183
1260
  process.stdout.write('\x1b[H\x1b[J');
1184
1261
  process.stdout.write(renderFullWelcomeScreen({
1185
1262
  model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1186
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1263
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1187
1264
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1188
1265
  chatThoughtTime: lastThoughtTime,
1189
1266
  queuedCommands: commandQueue.items,
@@ -1200,10 +1277,11 @@ export async function handleChatSession(args, version, signal) {
1200
1277
  lastAnswer = 'Searching the web...';
1201
1278
  lastThoughtTime = 0;
1202
1279
  lastOutputTokenRate = undefined;
1280
+ lastTokenUsage = undefined;
1203
1281
  process.stdout.write('\x1b[H\x1b[J');
1204
1282
  process.stdout.write(renderFullWelcomeScreen({
1205
1283
  model: currentModel ?? 'Web search', tokens: Math.round(sessionTokens), cost: costLabel(),
1206
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1284
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1207
1285
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1208
1286
  }, process.stdout.rows));
1209
1287
  process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
@@ -1224,7 +1302,7 @@ export async function handleChatSession(args, version, signal) {
1224
1302
  process.stdout.write('\x1b[H\x1b[J');
1225
1303
  process.stdout.write(renderFullWelcomeScreen({
1226
1304
  model: currentModel ?? 'Web search', tokens: Math.round(sessionTokens), cost: costLabel(),
1227
- usageAvailable: activeSession.usage.available, workspace: canonicalWorkspace, mode: activeMode,
1305
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1228
1306
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1229
1307
  chatThoughtTime: lastThoughtTime,
1230
1308
  }, process.stdout.rows));
@@ -1234,7 +1312,7 @@ export async function handleChatSession(args, version, signal) {
1234
1312
  }
1235
1313
  if (!provider) {
1236
1314
  process.stdout.write('\nNo provider is connected. Let\'s connect one before sending this task.\n');
1237
- const connection = await connectProviderInteractive({ signal, savedConnections: config.connections, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
1315
+ const connection = await connectProviderInteractive({ signal, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
1238
1316
  if (!connection) {
1239
1317
  process.stdout.write('No provider connected. Use /connect whenever you are ready.\n\n');
1240
1318
  continue;
@@ -1244,10 +1322,22 @@ export async function handleChatSession(args, version, signal) {
1244
1322
  config = loadConfig();
1245
1323
  activateConnection(runtimeConnection);
1246
1324
  }
1247
- const policy = new PolicyManager({ maxSteps: args.maxSteps, readOnly: activeMode === 'Plan' || args.readOnly, nonInteractive: args.nonInteractive, timeoutSeconds: args.timeout });
1325
+ const isReview = trimmed === '/review';
1326
+ const reviewFiles = isReview ? (await inspectGitWorkspace(canonicalWorkspace)).files.map(file => `${file.status} ${file.path}`) : [];
1327
+ const policy = new PolicyManager({ maxSteps: args.maxSteps, readOnly: isReview || activeMode === 'Plan' || args.readOnly, nonInteractive: args.nonInteractive, timeoutSeconds: args.timeout });
1248
1328
  const startedAt = Date.now();
1249
1329
  let thinkingTimer;
1250
1330
  let removeGenerationListener;
1331
+ let turnUsage;
1332
+ let usageRecorded = false;
1333
+ lastTokenUsage = undefined;
1334
+ const recordTurnUsage = (pricing) => {
1335
+ if (!turnUsage || usageRecorded)
1336
+ return;
1337
+ activeSession.usage = accumulateSessionUsage(activeSession.usage, turnUsage.usage, turnUsage.estimated, pricing);
1338
+ sessionTokens = activeSession.usage.totalTokens;
1339
+ usageRecorded = true;
1340
+ };
1251
1341
  try {
1252
1342
  const requestAbort = new AbortController();
1253
1343
  const requestSignal = signal ? AbortSignal.any([signal, requestAbort.signal]) : requestAbort.signal;
@@ -1255,6 +1345,10 @@ export async function handleChatSession(args, version, signal) {
1255
1345
  lastAnswer = '';
1256
1346
  let firstAssistantDeltaAt;
1257
1347
  let outputTokenRate;
1348
+ let budgetWarningIssued = false;
1349
+ let budgetStopped = false;
1350
+ let lastUsagePaint = 0;
1351
+ const usedModels = new Set();
1258
1352
  let answerPosition = { row: 15, column: 1 };
1259
1353
  let lastErrorEvent;
1260
1354
  const redrawChatFrame = () => {
@@ -1265,7 +1359,7 @@ export async function handleChatSession(args, version, signal) {
1265
1359
  model: currentModel ?? 'No model connected — use /connect',
1266
1360
  tokens: Math.round(sessionTokens),
1267
1361
  cost: costLabel(),
1268
- usageAvailable: activeSession.usage.available,
1362
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
1269
1363
  workspace: canonicalWorkspace,
1270
1364
  mode: activeMode,
1271
1365
  autoApprove: activeAutoApprove,
@@ -1273,6 +1367,8 @@ export async function handleChatSession(args, version, signal) {
1273
1367
  chatAnswer: displayAnswer,
1274
1368
  chatThoughtTime: thoughtTime,
1275
1369
  outputTokenRate,
1370
+ tokenUsage: turnUsage,
1371
+ budgetStatus: budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined,
1276
1372
  input: commandQueue.currentDraft,
1277
1373
  queuedCommands: commandQueue.items,
1278
1374
  }, process.stdout.rows));
@@ -1311,7 +1407,7 @@ export async function handleChatSession(args, version, signal) {
1311
1407
  model: currentModel ?? 'No model connected',
1312
1408
  tokens: Math.round(sessionTokens),
1313
1409
  cost: costLabel(),
1314
- usageAvailable: activeSession.usage.available,
1410
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
1315
1411
  workspace: canonicalWorkspace,
1316
1412
  mode: activeMode,
1317
1413
  autoApprove: activeAutoApprove,
@@ -1370,17 +1466,36 @@ export async function handleChatSession(args, version, signal) {
1370
1466
  // Move to the chat layout before the provider can emit its first event.
1371
1467
  redrawChatFrame();
1372
1468
  thinkingTimer = setInterval(() => {
1373
- if (!firstAssistantDeltaAt) {
1469
+ if (!firstAssistantDeltaAt && !turnUsage) {
1374
1470
  process.stdout.write(renderChatThoughtTimeUpdate((Date.now() - startedAt) / 1000));
1375
1471
  }
1376
1472
  }, 400);
1377
1473
  const evidenceTask = liveSearch ? buildSearchAnswerTask(effectivePrompt, liveSearch) : undefined;
1378
- const runAgent = () => loop.run(evidenceTask ?? createAgentTask(effectivePrompt, activeMode), {
1379
- workspaceRoot: canonicalWorkspace, provider: provider, tools, approvalHandler, router, policy,
1474
+ const runAgent = () => loop.run(isReview ? createReviewTask(reviewFiles) : evidenceTask ?? createAgentTask(effectivePrompt, activeMode), {
1475
+ workspaceRoot: canonicalWorkspace, provider: provider, tools: isReview ? createReviewToolRegistry() : tools, approvalHandler, router, policy,
1380
1476
  maxOutputTokens: args.maxTokens ?? config.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
1381
1477
  skillContext: skillContext(),
1382
1478
  routeOptions: { pinnedModelId: currentModel === 'auto' ? undefined : currentModel, allowPaid: config.allowPaid ?? args.allowPaid, allowUnknown: config.allowUnknown ?? args.allowUnknown, isLocalProfile: args.profile.includes('local') },
1383
1479
  eventListener: (event) => {
1480
+ if (event.type === 'model_change')
1481
+ usedModels.add(event.newModelId);
1482
+ if (event.type === 'usage') {
1483
+ turnUsage = event;
1484
+ lastTokenUsage = event;
1485
+ outputTokenRate = event.generationMs > 0 ? event.outputTokensPerSecond : undefined;
1486
+ const budgetState = evaluateTokenBudget(event.usage.totalTokens, tokenBudget);
1487
+ const firstBudgetWarning = budgetState === 'warning' && !budgetWarningIssued;
1488
+ if (budgetState === 'warning')
1489
+ budgetWarningIssued = true;
1490
+ if (budgetState === 'stop' && !budgetStopped) {
1491
+ budgetStopped = true;
1492
+ requestAbort.abort();
1493
+ }
1494
+ if (budgetStopped || firstBudgetWarning || event.final || event.timestamp - lastUsagePaint >= 400) {
1495
+ process.stdout.write(renderChatUsageUpdate(event, process.stdout.columns || 80, budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined));
1496
+ lastUsagePaint = event.timestamp;
1497
+ }
1498
+ }
1384
1499
  if (event.type === 'assistant_delta') {
1385
1500
  if (!firstAssistantDeltaAt && event.delta.trim().length > 0) {
1386
1501
  firstAssistantDeltaAt = Date.now();
@@ -1421,28 +1536,38 @@ export async function handleChatSession(args, version, signal) {
1421
1536
  const replacement = await replaceProviderKeyInteractive(activeConnection, { signal });
1422
1537
  if (!replacement)
1423
1538
  throw error;
1539
+ recordTurnUsage();
1540
+ turnUsage = undefined;
1541
+ usageRecorded = false;
1424
1542
  const secured = process.platform === 'win32' ? await storeConnectionCredential(replacement, credentialStore) : replacement;
1425
1543
  saveConnection(secured);
1426
1544
  config = loadConfig();
1427
1545
  activateConnection(secured);
1428
1546
  result = await runAgent();
1429
1547
  }
1430
- if (result.usage) {
1548
+ if (turnUsage && !turnUsage.estimated) {
1431
1549
  let pricing;
1432
1550
  try {
1433
- pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
1551
+ if (usedModels.size <= 1)
1552
+ pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
1434
1553
  }
1435
1554
  catch {
1436
1555
  // A valid answer remains usable when the provider catalog is unavailable.
1437
1556
  }
1438
- activeSession.usage = { ...result.usage, ...calculateSessionCost(result.usage, pricing), available: true };
1439
- sessionTokens = result.usage.totalTokens;
1440
- if (firstAssistantDeltaAt)
1441
- outputTokenRate = calculateOutputTokenRate(result.usage.completionTokens, Date.now() - firstAssistantDeltaAt);
1557
+ recordTurnUsage(pricing);
1442
1558
  }
1559
+ recordTurnUsage();
1560
+ // Failed/cancelled tasks can still consume tokens, even without a saved answer.
1561
+ if (turnUsage)
1562
+ sessionStore.save(activeSession);
1443
1563
  const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
1444
1564
  const resolution = resolveTurnAssistantAnswer(result, conversationHistory.length, lastErrorEvent, streamedAnswer, currentModel, otherConnections);
1445
1565
  lastAnswer = resolution.answer;
1566
+ if (budgetStopped) {
1567
+ const note = `Token budget reached (${tokenBudget?.toLocaleString()} per task). Task stopped; usage may exceed the limit because providers report in chunks.`;
1568
+ lastAnswer = [lastAnswer, note].filter(Boolean).join('\n\n');
1569
+ streamedAnswer = lastAnswer;
1570
+ }
1446
1571
  if (resolution.isError) {
1447
1572
  streamedAnswer = lastAnswer;
1448
1573
  }
@@ -1469,6 +1594,9 @@ export async function handleChatSession(args, version, signal) {
1469
1594
  if (thinkingTimer)
1470
1595
  clearInterval(thinkingTimer);
1471
1596
  removeGenerationListener?.();
1597
+ recordTurnUsage();
1598
+ if (turnUsage)
1599
+ sessionStore.save(activeSession);
1472
1600
  const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
1473
1601
  const altSuggestion = otherConnections.length > 0
1474
1602
  ? ` (other configured providers: ${otherConnections.join(', ')})`
@@ -1486,13 +1614,14 @@ export async function handleChatSession(args, version, signal) {
1486
1614
  model: currentModel ?? 'No model connected — use /connect',
1487
1615
  tokens: Math.round(sessionTokens),
1488
1616
  cost: costLabel(),
1489
- usageAvailable: activeSession.usage.available,
1617
+ usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
1490
1618
  workspace: canonicalWorkspace,
1491
1619
  mode: activeMode,
1492
1620
  autoApprove: activeAutoApprove,
1493
1621
  chatQuestion: lastQuestion,
1494
1622
  chatAnswer: lastAnswer,
1495
1623
  chatThoughtTime: lastThoughtTime,
1624
+ tokenUsage: turnUsage,
1496
1625
  queuedCommands: commandQueue.items,
1497
1626
  }, process.stdout.rows));
1498
1627
  process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));