praxis-agent 0.16.0 → 0.17.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.
Files changed (43) hide show
  1. package/README.md +11 -5
  2. package/dist/application/background-agent-manager.d.ts +7 -1
  3. package/dist/application/background-agent-manager.js +148 -5
  4. package/dist/application/metered-model-completion.d.ts +15 -0
  5. package/dist/application/metered-model-completion.js +76 -0
  6. package/dist/application/model-compactor.js +25 -1
  7. package/dist/application/session-cost-tracker.d.ts +50 -0
  8. package/dist/application/session-cost-tracker.js +254 -0
  9. package/dist/application/session-service.d.ts +13 -0
  10. package/dist/application/session-service.js +594 -69
  11. package/dist/application/subagent-service.d.ts +11 -1
  12. package/dist/application/subagent-service.js +41 -1
  13. package/dist/application/tool-use-summary.d.ts +7 -2
  14. package/dist/application/tool-use-summary.js +42 -10
  15. package/dist/application/workflow-manager.d.ts +9 -4
  16. package/dist/application/workflow-manager.js +151 -7
  17. package/dist/cli/interactive.d.ts +5 -1
  18. package/dist/cli/interactive.js +227 -15
  19. package/dist/cli/protocol.d.ts +11 -0
  20. package/dist/cli/protocol.js +16 -1
  21. package/dist/cli/tui/claude-command-inventory.d.ts +27 -27
  22. package/dist/cli/tui/claude-command-inventory.js +26 -26
  23. package/dist/cli/tui/config-dashboard.d.ts +2 -9
  24. package/dist/cli/tui/config-dashboard.js +6 -6
  25. package/dist/cli/tui/cost-summary.d.ts +34 -0
  26. package/dist/cli/tui/cost-summary.js +192 -0
  27. package/dist/cli/tui/doctor-dashboard.d.ts +15 -0
  28. package/dist/cli/tui/doctor-dashboard.js +81 -0
  29. package/dist/cli/tui/slash-commands.js +10 -0
  30. package/dist/cli.d.ts +2 -0
  31. package/dist/cli.js +192 -15
  32. package/dist/core/compaction.d.ts +2 -0
  33. package/dist/core/runtime.d.ts +38 -1
  34. package/dist/core/runtime.js +265 -11
  35. package/dist/persistence/claude-cost-state-store.d.ts +48 -0
  36. package/dist/persistence/claude-cost-state-store.js +335 -0
  37. package/dist/providers/anthropic-compatible.js +34 -9
  38. package/dist/providers/fallback-provider.d.ts +2 -1
  39. package/dist/providers/fallback-provider.js +28 -3
  40. package/dist/tools/line-changes.d.ts +8 -0
  41. package/dist/tools/line-changes.js +35 -0
  42. package/dist/tools/local-tools.js +30 -1
  43. package/package.json +2 -1
@@ -1,6 +1,6 @@
1
1
  import { type ClaudeSidechainPermissionMode } from '../compatibility/claude/sidechain.js';
2
2
  import { type ContextAssembler } from '../core/context.js';
3
- import { type ModelProvider, type ModelToolCall, type ModelToolDefinition, type ModelUsage, type PermissionResolver, type PermissionApproval, type PermissionDecision, type PermissionUpdate, type RuntimeEventSink, type ToolExecutionContext, type ToolExecutionResult, type ToolRegistry } from '../core/runtime.js';
3
+ import { type ModelProvider, type ModelToolCall, type ModelToolDefinition, type ModelUsage, type ModelUsageByModel, type PermissionResolver, type PermissionApproval, type PermissionDecision, type PermissionUpdate, type RuntimeEventSink, type ToolExecutionContext, type ToolExecutionResult, type ToolRegistry } from '../core/runtime.js';
4
4
  import { type ClaudeAgentRuntimeDefinition, type ClaudeExtensionCatalog } from '../extensions/claude-extensions.js';
5
5
  import type { ClaudeHookRunner } from '../hooks/claude-hooks.js';
6
6
  import type { ClaudeMcpRuntime } from '../mcp/claude-mcp-tools.js';
@@ -23,6 +23,9 @@ export interface WorkflowAgentRunOptions {
23
23
  export interface WorkflowAgentRunResult {
24
24
  result: unknown;
25
25
  usage: ModelUsage;
26
+ modelUsage?: ModelUsageByModel;
27
+ durationApiMs?: number;
28
+ durationApiWithoutRetriesMs?: number;
26
29
  toolUseCount: number;
27
30
  durationMs: number;
28
31
  resolvedModel: string;
@@ -71,6 +74,10 @@ export interface ClaudeSubagentExecutorOptions {
71
74
  toolNames?: readonly string[];
72
75
  backgroundTaskNotifications?: (waitForRunning: boolean) => Promise<string[]>;
73
76
  persistence?: 'disk' | 'memory';
77
+ onLineChanges?: (changes: {
78
+ readonly linesAdded: number;
79
+ readonly linesRemoved: number;
80
+ }) => void | Promise<void>;
74
81
  }
75
82
  export declare class ClaudeSubagentExecutor {
76
83
  private readonly options;
@@ -100,6 +107,9 @@ export declare class ClaudeSubagentExecutor {
100
107
  notifications(waitForRunning: boolean): Promise<{
101
108
  messages: string[];
102
109
  usage: ModelUsage;
110
+ modelUsage?: ModelUsageByModel;
111
+ durationApiMs?: number;
112
+ durationApiWithoutRetriesMs?: number;
103
113
  }>;
104
114
  private hydrateCompletedTask;
105
115
  private resolvePersistedAgentId;
@@ -673,6 +673,15 @@ export class ClaudeSubagentExecutor {
673
673
  ].join('\n\n'),
674
674
  isError: false,
675
675
  usage: result.usage,
676
+ ...(result.modelUsage ? { modelUsage: { ...result.modelUsage } } : {}),
677
+ ...(result.durationApiMs === undefined
678
+ ? {}
679
+ : { durationApiMs: result.durationApiMs }),
680
+ ...(result.durationApiWithoutRetriesMs === undefined
681
+ ? {}
682
+ : {
683
+ durationApiWithoutRetriesMs: result.durationApiWithoutRetriesMs,
684
+ }),
676
685
  nativeToolUseResult: {
677
686
  ...createClaudeAgentToolUseResult({
678
687
  prompt: input.prompt,
@@ -863,6 +872,15 @@ export class ClaudeSubagentExecutor {
863
872
  return {
864
873
  result: options.schema ? structured.value : run.text,
865
874
  usage: run.usage,
875
+ ...(run.modelUsage ? { modelUsage: { ...run.modelUsage } } : {}),
876
+ ...(run.durationApiMs === undefined
877
+ ? {}
878
+ : { durationApiMs: run.durationApiMs }),
879
+ ...(run.durationApiWithoutRetriesMs === undefined
880
+ ? {}
881
+ : {
882
+ durationApiWithoutRetriesMs: run.durationApiWithoutRetriesMs,
883
+ }),
866
884
  toolUseCount: run.toolUseCount,
867
885
  durationMs: Date.now() - startedAt,
868
886
  resolvedModel: provider.model ?? 'praxis/provider',
@@ -1253,6 +1271,13 @@ export class ClaudeSubagentExecutor {
1253
1271
  await append(toClaudeSidechainEntry(entry, options.agentId, options.input.subagentType));
1254
1272
  },
1255
1273
  toolCompleted: async (call, result) => {
1274
+ if (result.isError === false) {
1275
+ const linesAdded = result.linesAdded ?? 0;
1276
+ const linesRemoved = result.linesRemoved ?? 0;
1277
+ if (linesAdded !== 0 || linesRemoved !== 0) {
1278
+ await this.options.onLineChanges?.({ linesAdded, linesRemoved });
1279
+ }
1280
+ }
1256
1281
  const nestedToolUseCount = result.nativeToolUseResult?.totalToolUseCount;
1257
1282
  if (call.name === 'Agent' &&
1258
1283
  typeof nestedToolUseCount === 'number' &&
@@ -1357,6 +1382,7 @@ export class ClaudeSubagentExecutor {
1357
1382
  const effectiveEffort = options.effort ?? configuredEffort;
1358
1383
  const result = await runtime.run({
1359
1384
  messages: await assembleMessages(),
1385
+ collectMetrics: true,
1360
1386
  reloadMessages: assembleMessages,
1361
1387
  cwd,
1362
1388
  ...(effectiveEffort ? { effort: effectiveEffort } : {}),
@@ -1395,7 +1421,21 @@ export class ClaudeSubagentExecutor {
1395
1421
  });
1396
1422
  messages.push(...background.messages);
1397
1423
  messages.push(...((await this.options.backgroundTaskNotifications?.(true)) ?? []));
1398
- return { messages, usage: background.usage };
1424
+ return {
1425
+ messages,
1426
+ usage: background.usage,
1427
+ ...(background.modelUsage
1428
+ ? { modelUsage: background.modelUsage }
1429
+ : {}),
1430
+ ...(background.durationApiMs === undefined
1431
+ ? {}
1432
+ : { durationApiMs: background.durationApiMs }),
1433
+ ...(background.durationApiWithoutRetriesMs === undefined
1434
+ ? {}
1435
+ : {
1436
+ durationApiWithoutRetriesMs: background.durationApiWithoutRetriesMs,
1437
+ }),
1438
+ };
1399
1439
  },
1400
1440
  }
1401
1441
  : {}),
@@ -1,8 +1,13 @@
1
- import type { ModelProvider } from '../core/runtime.js';
1
+ import type { ModelProvider, ModelUsage, ToolUseSummaryOutcome } from '../core/runtime.js';
2
2
  export interface ToolUseSummaryInput {
3
3
  name: string;
4
4
  input: Record<string, unknown>;
5
5
  output: string;
6
6
  }
7
- export declare function generateToolUseSummary(provider: ModelProvider, tools: readonly ToolUseSummaryInput[], signal: AbortSignal, lastAssistantText?: string): Promise<string | null>;
7
+ export declare function generateToolUseSummary(provider: ModelProvider, tools: readonly ToolUseSummaryInput[], signal: AbortSignal, lastAssistantText?: string, onMetrics?: (metrics: {
8
+ usage: ModelUsage;
9
+ model?: string;
10
+ durationApiMs: number;
11
+ durationApiWithoutRetriesMs: number;
12
+ }) => void): Promise<ToolUseSummaryOutcome | null>;
8
13
  //# sourceMappingURL=tool-use-summary.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { completeMeteredModelRequest, } from './metered-model-completion.js';
1
2
  const TOOL_USE_SUMMARY_SYSTEM_PROMPT = `Write a short summary label describing what these tool calls accomplished. It appears as a single-line row in a mobile app and truncates around 30 characters, so think git-commit-subject, not sentence.
2
3
 
3
4
  Keep the verb in past tense and the most distinctive noun. Drop articles, connectors, and long location context first.
@@ -20,7 +21,14 @@ function truncateJson(value, maxLength) {
20
21
  return serialized;
21
22
  return `${serialized.slice(0, maxLength - 3)}...`;
22
23
  }
23
- export async function generateToolUseSummary(provider, tools, signal, lastAssistantText) {
24
+ function hasNonZeroUsage(usage) {
25
+ return (usage.inputTokens > 0 ||
26
+ usage.outputTokens > 0 ||
27
+ (usage.cacheReadInputTokens ?? 0) > 0 ||
28
+ (usage.cacheCreationInputTokens ?? 0) > 0 ||
29
+ (usage.webSearchRequests ?? 0) > 0);
30
+ }
31
+ export async function generateToolUseSummary(provider, tools, signal, lastAssistantText, onMetrics) {
24
32
  if (tools.length === 0 || signal.aborted)
25
33
  return null;
26
34
  const toolSummaries = tools
@@ -36,18 +44,42 @@ export async function generateToolUseSummary(provider, tools, signal, lastAssist
36
44
  content: `${contextPrefix}Tools completed:\n\n${toolSummaries}\n\nLabel:`,
37
45
  },
38
46
  ];
39
- let summary = '';
47
+ let metrics;
48
+ let summary = null;
40
49
  try {
41
- for await (const event of provider.complete({ messages, signal })) {
42
- if (signal.aborted)
43
- return null;
44
- if (event.type === 'text-delta')
45
- summary += event.delta;
46
- }
50
+ const completed = await completeMeteredModelRequest(provider, { messages, signal }, {
51
+ onMetrics: (recorded) => {
52
+ metrics = recorded;
53
+ },
54
+ });
55
+ summary =
56
+ completed.toolCalls.length === 0 ? completed.text.trim() || null : null;
47
57
  }
48
58
  catch {
49
- return null;
59
+ // Provider/validation/abort errors keep the captured metrics but yield no
60
+ // summary, preserving the auxiliary-failure semantics.
61
+ summary = null;
62
+ }
63
+ // Invoke the external callback exactly once when the provider call started.
64
+ // Callback/tracker errors are intentionally not caught so cost accounting
65
+ // fails closed. Without an external callback nothing was committed, so the
66
+ // outcome stays unrecorded.
67
+ let meteredExternally = false;
68
+ if (metrics) {
69
+ onMetrics?.(metrics);
70
+ meteredExternally = onMetrics !== undefined;
50
71
  }
51
- return summary.trim() || null;
72
+ const usage = metrics?.usage ?? { inputTokens: 0, outputTokens: 0 };
73
+ const model = metrics?.model !== undefined && metrics.model.trim() !== ''
74
+ ? metrics.model
75
+ : 'praxis/provider';
76
+ return {
77
+ summary,
78
+ usage,
79
+ ...(hasNonZeroUsage(usage) ? { modelUsage: { [model]: usage } } : {}),
80
+ durationApiMs: metrics?.durationApiMs ?? 0,
81
+ durationApiWithoutRetriesMs: metrics?.durationApiWithoutRetriesMs ?? 0,
82
+ meteredExternally,
83
+ };
52
84
  }
53
85
  //# sourceMappingURL=tool-use-summary.js.map
@@ -1,3 +1,4 @@
1
+ import type { ModelUsage, ModelUsageByModel } from '../core/runtime.js';
1
2
  import { ClaudeWorkflowStore } from '../persistence/claude-workflow-store.js';
2
3
  import type { WorkflowAgentRunOptions, WorkflowAgentRunResult } from './subagent-service.js';
3
4
  import { parseWorkflowScript, type ParsedWorkflowScript, type WorkflowMeta } from './workflow-meta.js';
@@ -74,6 +75,10 @@ interface WorkflowTask {
74
75
  totalTokens: number;
75
76
  totalInputTokens: number;
76
77
  totalToolCalls: number;
78
+ usage: ModelUsage;
79
+ modelUsage: Map<string, ModelUsage>;
80
+ durationApiMs: number;
81
+ durationApiWithoutRetriesMs: number;
77
82
  controller: AbortController;
78
83
  promise: Promise<void>;
79
84
  notificationPending: boolean;
@@ -107,10 +112,10 @@ export declare class WorkflowManager {
107
112
  stopAndWait(taskId: string): Promise<void>;
108
113
  notifications(waitForRunning: boolean): Promise<{
109
114
  messages: string[];
110
- usage: {
111
- inputTokens: number;
112
- outputTokens: number;
113
- };
115
+ usage: ModelUsage;
116
+ modelUsage?: ModelUsageByModel;
117
+ durationApiMs?: number;
118
+ durationApiWithoutRetriesMs?: number;
114
119
  }>;
115
120
  list(sessionId?: string): readonly WorkflowTaskSnapshot[];
116
121
  abortAll(): void;
@@ -81,6 +81,117 @@ function validateAgentOptions(options) {
81
81
  throw new Error('agent schema must be an object');
82
82
  }
83
83
  }
84
+ const workflowUsageCounterFields = [
85
+ 'inputTokens',
86
+ 'outputTokens',
87
+ 'cacheReadInputTokens',
88
+ 'cacheCreationInputTokens',
89
+ 'webSearchRequests',
90
+ ];
91
+ const workflowUsageMetadataFields = [
92
+ 'contextWindow',
93
+ 'maxOutputTokens',
94
+ ];
95
+ function assertValidWorkflowUsage(usage) {
96
+ for (const field of workflowUsageCounterFields) {
97
+ const value = usage[field];
98
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
99
+ throw new Error(`Workflow agent usage has an invalid ${field} counter`);
100
+ }
101
+ }
102
+ }
103
+ function addWorkflowApiDuration(value, total, field) {
104
+ if (!Number.isFinite(value) || value < 0) {
105
+ throw new TypeError(`Workflow ${field} must be a finite nonnegative number`);
106
+ }
107
+ const next = total + value;
108
+ if (!Number.isFinite(next) || next < 0) {
109
+ throw new TypeError(`Workflow ${field} total overflow`);
110
+ }
111
+ return next;
112
+ }
113
+ function addWorkflowUsageChecked(model, left, right) {
114
+ assertValidWorkflowUsage(right);
115
+ const inputTokens = left.inputTokens + right.inputTokens;
116
+ const outputTokens = left.outputTokens + right.outputTokens;
117
+ const cacheReadInputTokens = (left.cacheReadInputTokens ?? 0) + (right.cacheReadInputTokens ?? 0);
118
+ const cacheCreationInputTokens = (left.cacheCreationInputTokens ?? 0) + (right.cacheCreationInputTokens ?? 0);
119
+ const webSearchRequests = (left.webSearchRequests ?? 0) + (right.webSearchRequests ?? 0);
120
+ if (!Number.isSafeInteger(inputTokens) ||
121
+ !Number.isSafeInteger(outputTokens) ||
122
+ !Number.isSafeInteger(cacheReadInputTokens) ||
123
+ !Number.isSafeInteger(cacheCreationInputTokens) ||
124
+ !Number.isSafeInteger(webSearchRequests)) {
125
+ throw new Error('Workflow model usage total overflow');
126
+ }
127
+ // Aggregates without a model stay counter-only; per-model rows merge their
128
+ // capability metadata with conflict rejection.
129
+ const metadata = model === undefined ? {} : mergeWorkflowUsageMetadata(model, left, right);
130
+ return {
131
+ inputTokens,
132
+ outputTokens,
133
+ ...(cacheReadInputTokens === 0 ? {} : { cacheReadInputTokens }),
134
+ ...(cacheCreationInputTokens === 0 ? {} : { cacheCreationInputTokens }),
135
+ ...(webSearchRequests === 0 ? {} : { webSearchRequests }),
136
+ ...metadata,
137
+ };
138
+ }
139
+ function mergeWorkflowUsageMetadata(model, left, right) {
140
+ const contextWindow = mergeWorkflowUsageMetadataField(model, 'contextWindow', left.contextWindow, right.contextWindow);
141
+ const maxOutputTokens = mergeWorkflowUsageMetadataField(model, 'maxOutputTokens', left.maxOutputTokens, right.maxOutputTokens);
142
+ return {
143
+ ...(contextWindow === undefined ? {} : { contextWindow }),
144
+ ...(maxOutputTokens === undefined ? {} : { maxOutputTokens }),
145
+ };
146
+ }
147
+ function mergeWorkflowUsageMetadataField(model, field, left, right) {
148
+ if (left === undefined)
149
+ return right;
150
+ if (right === undefined)
151
+ return left;
152
+ if (left !== right) {
153
+ throw new Error(`Workflow model usage for "${model}" has conflicting ${field} values: ${left} vs ${right}`);
154
+ }
155
+ return left;
156
+ }
157
+ function assertValidWorkflowModelUsageEntry(model, usage) {
158
+ if (model.trim() === '') {
159
+ throw new Error('Workflow model usage breakdown contains a blank model name');
160
+ }
161
+ assertValidWorkflowUsage(usage);
162
+ for (const field of workflowUsageMetadataFields) {
163
+ const value = usage[field];
164
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {
165
+ throw new Error(`Workflow model usage for "${model}" has an invalid ${field} metadata value`);
166
+ }
167
+ }
168
+ }
169
+ function mergeWorkflowModelUsageEntry(map, model, usage) {
170
+ assertValidWorkflowModelUsageEntry(model, usage);
171
+ const existing = map.get(model);
172
+ if (existing === undefined) {
173
+ map.set(model, { ...usage });
174
+ return;
175
+ }
176
+ map.set(model, addWorkflowUsageChecked(model, existing, usage));
177
+ }
178
+ function mergeWorkflowModelUsage(map, breakdown) {
179
+ const entries = Object.entries(breakdown);
180
+ if (entries.length === 0)
181
+ return;
182
+ // Validate every key, counter, metadata, and merged sum before adding any
183
+ // entry from this agent result so a malformed or conflicting breakdown never
184
+ // merges partially.
185
+ for (const [model, usage] of entries) {
186
+ assertValidWorkflowModelUsageEntry(model, usage);
187
+ const existing = map.get(model);
188
+ if (existing !== undefined)
189
+ addWorkflowUsageChecked(model, existing, usage);
190
+ }
191
+ for (const [model, usage] of entries) {
192
+ mergeWorkflowModelUsageEntry(map, model, usage);
193
+ }
194
+ }
84
195
  export class WorkflowManager {
85
196
  configRoot;
86
197
  cwd;
@@ -140,6 +251,10 @@ export class WorkflowManager {
140
251
  totalTokens: 0,
141
252
  totalInputTokens: 0,
142
253
  totalToolCalls: 0,
254
+ usage: { inputTokens: 0, outputTokens: 0 },
255
+ modelUsage: new Map(),
256
+ durationApiMs: 0,
257
+ durationApiWithoutRetriesMs: 0,
143
258
  controller,
144
259
  promise: Promise.resolve(),
145
260
  notificationPending: false,
@@ -219,20 +334,39 @@ export class WorkflowManager {
219
334
  .map(({ promise }) => promise));
220
335
  }
221
336
  const messages = [];
222
- let inputTokens = 0;
223
- let outputTokens = 0;
337
+ let usage = { inputTokens: 0, outputTokens: 0 };
338
+ const modelUsage = new Map();
339
+ let durationApiMs = 0;
340
+ let durationApiWithoutRetriesMs = 0;
341
+ let durationSeen = false;
342
+ const consumedTasks = [];
224
343
  for (const task of this.tasks.values()) {
225
344
  if (task.status === 'running' || !task.notificationPending)
226
345
  continue;
227
- task.notificationPending = false;
228
346
  messages.push(this.notification(task));
229
- inputTokens += task.totalInputTokens;
230
- outputTokens += task.totalTokens;
347
+ usage = addWorkflowUsageChecked(undefined, usage, task.usage);
348
+ for (const [model, entry] of task.modelUsage) {
349
+ mergeWorkflowModelUsageEntry(modelUsage, model, entry);
350
+ }
351
+ if (task.durationApiMs !== 0 || task.durationApiWithoutRetriesMs !== 0) {
352
+ durationSeen = true;
353
+ durationApiMs = addWorkflowApiDuration(task.durationApiMs, durationApiMs, 'durationApiMs');
354
+ durationApiWithoutRetriesMs = addWorkflowApiDuration(task.durationApiWithoutRetriesMs, durationApiWithoutRetriesMs, 'durationApiWithoutRetriesMs');
355
+ }
356
+ consumedTasks.push(task);
231
357
  }
232
- return {
358
+ const modelUsageByModel = modelUsage.size === 0 ? undefined : Object.fromEntries(modelUsage);
359
+ const result = {
233
360
  messages,
234
- usage: { inputTokens, outputTokens },
361
+ usage,
362
+ ...(modelUsageByModel === undefined
363
+ ? {}
364
+ : { modelUsage: modelUsageByModel }),
365
+ ...(durationSeen ? { durationApiMs, durationApiWithoutRetriesMs } : {}),
235
366
  };
367
+ for (const task of consumedTasks)
368
+ task.notificationPending = false;
369
+ return result;
236
370
  }
237
371
  list(sessionId) {
238
372
  return [...this.tasks.values()]
@@ -409,6 +543,16 @@ export class WorkflowManager {
409
543
  task.totalTokens += progress.totalTokens;
410
544
  task.totalInputTokens += result.usage.inputTokens;
411
545
  task.totalToolCalls += result.toolUseCount;
546
+ task.usage = addWorkflowUsageChecked(undefined, task.usage, result.usage);
547
+ if (result.modelUsage !== undefined) {
548
+ mergeWorkflowModelUsage(task.modelUsage, result.modelUsage);
549
+ }
550
+ if (result.durationApiMs !== undefined ||
551
+ result.durationApiWithoutRetriesMs !== undefined) {
552
+ const total = result.durationApiMs ?? 0;
553
+ task.durationApiMs = addWorkflowApiDuration(total, task.durationApiMs, 'durationApiMs');
554
+ task.durationApiWithoutRetriesMs = addWorkflowApiDuration(result.durationApiWithoutRetriesMs ?? total, task.durationApiWithoutRetriesMs, 'durationApiWithoutRetriesMs');
555
+ }
412
556
  await task.store.append({
413
557
  type: 'result',
414
558
  key,
@@ -1,4 +1,5 @@
1
1
  import type { ForkResult, ManualCompactResult, ManualCompactSelection, RewindPoint, SessionForkCheckpoint, SessionRunResult, SessionSummary, SideQuestionForkResult, SideQuestionResult } from '../application/session-service.js';
2
+ import type { ClaudeSessionCostSnapshot } from '../application/session-cost-tracker.js';
2
3
  import type { ModelImage, ModelToolCall, PermissionApproval, PermissionDecision, RuntimeEventSink } from '../core/runtime.js';
3
4
  import type { CliElicitationRequest, CliElicitationResult, CliRuntimeInfo } from './protocol.js';
4
5
  import type { ClaudeInteractiveToolCallbacks } from '../tools/claude-interactive-tools.js';
@@ -13,6 +14,7 @@ import type { AgentColorName } from '../compatibility/claude/agent-color.js';
13
14
  import type { ClaudeResourceScope } from '../compatibility/claude/shared-resources.js';
14
15
  import type { TuiHookConfiguration } from './tui/hook-settings.js';
15
16
  import { type TuiSlashCommand } from './tui/slash-commands.js';
17
+ import { type DoctorReport } from '../maintenance/doctor.js';
16
18
  import { type TuiAgentEntry, type TuiFileEntry } from './tui/file-picker.js';
17
19
  import { type TuiEditorOptions, type TuiEditorResult } from './tui/external-editor.js';
18
20
  import { type TuiClipboardContent } from './tui/clipboard.js';
@@ -34,6 +36,7 @@ interface InteractiveSessionCommands {
34
36
  fork(sessionId: string, targetSessionId?: string, resumeSessionAt?: string): Promise<ForkResult>;
35
37
  sessions(): Promise<SessionSummary[]>;
36
38
  transcript?(sessionId: string): Promise<TranscriptItem[]>;
39
+ costSnapshot?(sessionId: string): Promise<ClaudeSessionCostSnapshot>;
37
40
  compact?(sessionId: string, signal?: AbortSignal, selection?: ManualCompactSelection): Promise<ManualCompactResult>;
38
41
  rewindFiles?(sessionId: string, userMessageId: string): Promise<void>;
39
42
  rewindPoints?(sessionId: string): Promise<RewindPoint[]>;
@@ -132,6 +135,7 @@ interface InteractiveAppProps {
132
135
  allowDangerouslySkipPermissions?: boolean;
133
136
  additionalDirectories?: readonly string[];
134
137
  diffLoader?: () => Promise<TuiDiffSnapshot>;
138
+ doctorLoader?: () => Promise<DoctorReport>;
135
139
  fileLoader?: () => Promise<readonly TuiFileEntry[]>;
136
140
  externalEditor?: (prompt: string, options: TuiEditorOptions) => Promise<TuiEditorResult>;
137
141
  keybindingsConfigRoot?: string;
@@ -184,7 +188,7 @@ interface InteractiveAppProps {
184
188
  releaseNotesLoader?: (configRoot: string) => Promise<string>;
185
189
  settingSources?: readonly ClaudeResourceScope[];
186
190
  }
187
- export declare function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
191
+ export declare function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, doctorLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
188
192
  export declare function runInteractive(options: {
189
193
  factory: InteractiveServiceFactory;
190
194
  initialPrompt?: string;