openhorse 0.2.12 → 0.2.15

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 (143) hide show
  1. package/dist/commands/index.d.ts.map +1 -1
  2. package/dist/commands/index.js +814 -61
  3. package/dist/commands/index.js.map +1 -1
  4. package/dist/commands/types.d.ts +7 -2
  5. package/dist/commands/types.d.ts.map +1 -1
  6. package/dist/commands/types.js.map +1 -1
  7. package/dist/core/checkpoint.d.ts +8 -0
  8. package/dist/core/checkpoint.d.ts.map +1 -1
  9. package/dist/core/checkpoint.js +69 -10
  10. package/dist/core/checkpoint.js.map +1 -1
  11. package/dist/core/cost-tracker.d.ts.map +1 -1
  12. package/dist/core/cost-tracker.js +11 -0
  13. package/dist/core/cost-tracker.js.map +1 -1
  14. package/dist/core/tool-artifacts.d.ts +10 -0
  15. package/dist/core/tool-artifacts.d.ts.map +1 -1
  16. package/dist/core/tool-artifacts.js +81 -3
  17. package/dist/core/tool-artifacts.js.map +1 -1
  18. package/dist/framework/index.d.ts +2 -2
  19. package/dist/framework/index.d.ts.map +1 -1
  20. package/dist/framework/index.js +5 -1
  21. package/dist/framework/index.js.map +1 -1
  22. package/dist/framework/query.d.ts +87 -2
  23. package/dist/framework/query.d.ts.map +1 -1
  24. package/dist/framework/query.js +281 -14
  25. package/dist/framework/query.js.map +1 -1
  26. package/dist/framework/tool-scheduler.d.ts +9 -0
  27. package/dist/framework/tool-scheduler.d.ts.map +1 -1
  28. package/dist/framework/tool-scheduler.js +34 -0
  29. package/dist/framework/tool-scheduler.js.map +1 -1
  30. package/dist/ink-ui/components/PromptInput.d.ts.map +1 -1
  31. package/dist/ink-ui/components/PromptInput.js +5 -3
  32. package/dist/ink-ui/components/PromptInput.js.map +1 -1
  33. package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
  34. package/dist/ink-ui/components/StatusLine.js +7 -1
  35. package/dist/ink-ui/components/StatusLine.js.map +1 -1
  36. package/dist/ink-ui/screens/ReplScreen.d.ts +3 -1
  37. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  38. package/dist/ink-ui/screens/ReplScreen.js +40 -93
  39. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  40. package/dist/print-ui/launch.d.ts.map +1 -1
  41. package/dist/print-ui/launch.js +1 -0
  42. package/dist/print-ui/launch.js.map +1 -1
  43. package/dist/runtime/agent-runtime-controller.d.ts +5 -0
  44. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  45. package/dist/runtime/agent-runtime-controller.js +15 -3
  46. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  47. package/dist/runtime/agent-runtime-protocol.d.ts +13 -1
  48. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
  49. package/dist/runtime/agent-runtime-protocol.js +24 -0
  50. package/dist/runtime/agent-runtime-protocol.js.map +1 -1
  51. package/dist/runtime/agent-status.d.ts +5 -0
  52. package/dist/runtime/agent-status.d.ts.map +1 -1
  53. package/dist/runtime/agent-status.js +28 -2
  54. package/dist/runtime/agent-status.js.map +1 -1
  55. package/dist/runtime/chat-controller.d.ts +7 -0
  56. package/dist/runtime/chat-controller.d.ts.map +1 -1
  57. package/dist/runtime/chat-controller.js +1084 -47
  58. package/dist/runtime/chat-controller.js.map +1 -1
  59. package/dist/runtime/loop-budget.d.ts +5 -0
  60. package/dist/runtime/loop-budget.d.ts.map +1 -0
  61. package/dist/runtime/loop-budget.js +77 -0
  62. package/dist/runtime/loop-budget.js.map +1 -0
  63. package/dist/runtime/turn-controller.d.ts +4 -0
  64. package/dist/runtime/turn-controller.d.ts.map +1 -1
  65. package/dist/runtime/turn-controller.js +8 -0
  66. package/dist/runtime/turn-controller.js.map +1 -1
  67. package/dist/runtime/ui-events.d.ts +41 -1
  68. package/dist/runtime/ui-events.d.ts.map +1 -1
  69. package/dist/runtime/ui-events.js.map +1 -1
  70. package/dist/runtime/ui-view-model.d.ts +287 -0
  71. package/dist/runtime/ui-view-model.d.ts.map +1 -0
  72. package/dist/runtime/ui-view-model.js +642 -0
  73. package/dist/runtime/ui-view-model.js.map +1 -0
  74. package/dist/services/config-dir.d.ts +2 -0
  75. package/dist/services/config-dir.d.ts.map +1 -1
  76. package/dist/services/config-dir.js +5 -0
  77. package/dist/services/config-dir.js.map +1 -1
  78. package/dist/services/config.d.ts +4 -2
  79. package/dist/services/config.d.ts.map +1 -1
  80. package/dist/services/config.js +19 -0
  81. package/dist/services/config.js.map +1 -1
  82. package/dist/services/file-glob.d.ts +6 -2
  83. package/dist/services/file-glob.d.ts.map +1 -1
  84. package/dist/services/file-glob.js +10 -4
  85. package/dist/services/file-glob.js.map +1 -1
  86. package/dist/services/global-config.d.ts +17 -0
  87. package/dist/services/global-config.d.ts.map +1 -1
  88. package/dist/services/global-config.js.map +1 -1
  89. package/dist/services/llm.d.ts +16 -0
  90. package/dist/services/llm.d.ts.map +1 -1
  91. package/dist/services/llm.js +56 -11
  92. package/dist/services/llm.js.map +1 -1
  93. package/dist/services/model-catalog.d.ts +14 -0
  94. package/dist/services/model-catalog.d.ts.map +1 -0
  95. package/dist/services/model-catalog.js +73 -0
  96. package/dist/services/model-catalog.js.map +1 -0
  97. package/dist/services/model-context.d.ts.map +1 -1
  98. package/dist/services/model-context.js +3 -0
  99. package/dist/services/model-context.js.map +1 -1
  100. package/dist/services/redaction.d.ts +2 -0
  101. package/dist/services/redaction.d.ts.map +1 -0
  102. package/dist/services/redaction.js +27 -0
  103. package/dist/services/redaction.js.map +1 -0
  104. package/dist/services/session-index.d.ts.map +1 -1
  105. package/dist/services/session-index.js +7 -3
  106. package/dist/services/session-index.js.map +1 -1
  107. package/dist/services/session-storage.d.ts +99 -0
  108. package/dist/services/session-storage.d.ts.map +1 -1
  109. package/dist/services/session-storage.js +153 -2
  110. package/dist/services/session-storage.js.map +1 -1
  111. package/dist/services/verification-profile.d.ts +51 -0
  112. package/dist/services/verification-profile.d.ts.map +1 -0
  113. package/dist/services/verification-profile.js +306 -0
  114. package/dist/services/verification-profile.js.map +1 -0
  115. package/dist/services/workspace-state.d.ts +25 -0
  116. package/dist/services/workspace-state.d.ts.map +1 -0
  117. package/dist/services/workspace-state.js +115 -0
  118. package/dist/services/workspace-state.js.map +1 -0
  119. package/dist/terminal-ui/completion.d.ts.map +1 -1
  120. package/dist/terminal-ui/completion.js +9 -6
  121. package/dist/terminal-ui/completion.js.map +1 -1
  122. package/dist/terminal-ui/launch.d.ts +15 -1
  123. package/dist/terminal-ui/launch.d.ts.map +1 -1
  124. package/dist/terminal-ui/launch.js +300 -56
  125. package/dist/terminal-ui/launch.js.map +1 -1
  126. package/dist/terminal-ui/raw-editor.d.ts +4 -0
  127. package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
  128. package/dist/terminal-ui/raw-editor.js +29 -0
  129. package/dist/terminal-ui/raw-editor.js.map +1 -1
  130. package/dist/tui-ui/launch.d.ts.map +1 -1
  131. package/dist/tui-ui/launch.js +1 -0
  132. package/dist/tui-ui/launch.js.map +1 -1
  133. package/dist/tui-ui/layout.d.ts.map +1 -1
  134. package/dist/tui-ui/layout.js +21 -28
  135. package/dist/tui-ui/layout.js.map +1 -1
  136. package/dist/tui-ui/pickers.d.ts.map +1 -1
  137. package/dist/tui-ui/pickers.js +21 -62
  138. package/dist/tui-ui/pickers.js.map +1 -1
  139. package/dist/tui-ui/state.d.ts +2 -4
  140. package/dist/tui-ui/state.d.ts.map +1 -1
  141. package/dist/tui-ui/state.js +13 -11
  142. package/dist/tui-ui/state.js.map +1 -1
  143. package/package.json +1 -1
@@ -17,7 +17,7 @@ exports.getCommandNames = getCommandNames;
17
17
  exports.executeChat = handleChat;
18
18
  const chalk_1 = __importDefault(require("chalk"));
19
19
  const types_1 = require("./types");
20
- const ui_events_1 = require("../runtime/ui-events");
20
+ const ui_view_model_1 = require("../runtime/ui-view-model");
21
21
  const task_manager_1 = require("../services/task-manager");
22
22
  const agent_runner_1 = require("../services/agent-runner");
23
23
  const config_1 = require("../services/config");
@@ -38,10 +38,14 @@ const project_instructions_1 = require("../services/project-instructions");
38
38
  const prompt_context_1 = require("../services/prompt-context");
39
39
  const doctor_1 = require("../services/doctor");
40
40
  const model_context_1 = require("../services/model-context");
41
+ const model_catalog_1 = require("../services/model-catalog");
41
42
  const workspace_diff_1 = require("../services/workspace-diff");
42
43
  const commit_plan_1 = require("../services/commit-plan");
44
+ const tool_artifacts_1 = require("../core/tool-artifacts");
45
+ const checkpoint_1 = require("../core/checkpoint");
43
46
  const storage_maintenance_1 = require("../services/storage-maintenance");
44
47
  const agent_status_1 = require("../runtime/agent-status");
48
+ const loop_budget_1 = require("../runtime/loop-budget");
45
49
  // ============================================================================
46
50
  // 颜色常量
47
51
  // ============================================================================
@@ -63,7 +67,10 @@ const CATEGORY_ORDER = [
63
67
  'legacy',
64
68
  ];
65
69
  function commandUICapabilities(ctx) {
66
- return (0, ui_events_1.resolveUiRendererCapabilities)(ctx.uiCapabilities);
70
+ return (0, ui_view_model_1.createStatusSnapshot)({
71
+ renderer: ctx.uiRenderer ?? ctx.config.ui?.renderer ?? 'terminal',
72
+ capabilities: ctx.uiCapabilities,
73
+ }).renderer.capabilities;
67
74
  }
68
75
  function formatTokenCount(tokens) {
69
76
  if (tokens >= 1000000)
@@ -75,6 +82,129 @@ function formatTokenCount(tokens) {
75
82
  function formatThreshold(value) {
76
83
  return `${Math.round(value * 100)}%`;
77
84
  }
85
+ function formatDurationMs(ms) {
86
+ if (ms >= 1000)
87
+ return `${(ms / 1000).toFixed(1)}s`;
88
+ return `${Math.round(ms)}ms`;
89
+ }
90
+ function formatRendererStatus(ctx) {
91
+ const snapshot = (0, ui_view_model_1.createStatusSnapshot)({
92
+ renderer: ctx.uiRenderer ?? ctx.config.ui?.renderer ?? 'terminal',
93
+ capabilities: ctx.uiCapabilities,
94
+ });
95
+ const status = snapshot.renderer.status === 'beta' || (0, config_1.isBetaUIRenderer)(snapshot.renderer.name)
96
+ ? WARN('beta')
97
+ : snapshot.renderer.status === 'stable'
98
+ ? SUCCESS('stable')
99
+ : snapshot.renderer.status === 'non-interactive'
100
+ ? DIM('non-interactive')
101
+ : DIM('custom');
102
+ return `${BRAND(snapshot.renderer.name)} ${status} ${DIM(snapshot.renderer.capabilityLabels.join(', '))}`;
103
+ }
104
+ function formatModelAliasHelp() {
105
+ return Object.keys((0, model_catalog_1.getModelCatalogAliases)()).sort().join(', ');
106
+ }
107
+ function formatLoopBudgetSource(stats) {
108
+ const source = stats.loopBudgetSource ?? 'unknown';
109
+ if (source === 'config' && stats.loopBudgetBaseProfile) {
110
+ return `config over ${stats.loopBudgetBaseProfile}`;
111
+ }
112
+ return source;
113
+ }
114
+ function formatLoopStatsLines(stats, detail = false) {
115
+ const lines = [
116
+ `Finish ${stats.finishReason}`,
117
+ `Requests ${stats.llmRequests} LLM / ${stats.turnsStarted} turns`,
118
+ `Tools ${stats.toolCalls} total (${stats.readOnlyToolCalls} read-only, ${stats.unsafeToolCalls} unsafe)`,
119
+ `Tool bytes ${(0, format_1.formatBytes)(stats.modelVisibleToolBytes)} model-visible / ${(0, format_1.formatBytes)(stats.toolResultBytes)} total`,
120
+ ];
121
+ if (stats.summarizedBytes > 0) {
122
+ lines.push(`Saved ${(0, format_1.formatBytes)(stats.summarizedBytes)} from model context`);
123
+ }
124
+ if (stats.compactTrigger) {
125
+ lines.push(`Compact ${stats.compactTrigger}`);
126
+ }
127
+ if (stats.localFastPathUsed) {
128
+ lines.push('Fast path yes');
129
+ }
130
+ if (stats.budgetExceededReason) {
131
+ lines.push(`Budget ${stats.budgetExceededReason}`);
132
+ }
133
+ if (stats.continuationActions && stats.continuationActions.length > 0) {
134
+ lines.push(`Next ${stats.continuationActions.join(', ')}`);
135
+ }
136
+ if ((stats.providerRetryCount ?? 0) > 0) {
137
+ const retryParts = [
138
+ `${stats.providerRetryCount} retries`,
139
+ `delay ${formatDurationMs(stats.providerRetryDelayMs ?? 0)}`,
140
+ stats.providerLastRetryErrorType
141
+ ? `last ${stats.providerLastRetryErrorType}${stats.providerLastRetryStatus ? `/${stats.providerLastRetryStatus}` : ''}`
142
+ : undefined,
143
+ ].filter(Boolean);
144
+ lines.push(`Provider ${retryParts.join(', ')}`);
145
+ }
146
+ if ((stats.providerFallbackCount ?? 0) > 0 || stats.providerUsingFallback) {
147
+ const fallbackPath = stats.providerFallbackFromModel && stats.providerFallbackToModel
148
+ ? `${stats.providerFallbackFromModel} -> ${stats.providerFallbackToModel}`
149
+ : stats.providerFinalModel
150
+ ? `final ${stats.providerFinalModel}`
151
+ : 'active';
152
+ lines.push(`Fallback ${fallbackPath}`);
153
+ }
154
+ if (typeof stats.verificationClaimAllowed === 'boolean') {
155
+ const verificationParts = [
156
+ stats.verificationProfile ?? 'unknown',
157
+ `required=${stats.verificationRequired ? 'yes' : 'no'}`,
158
+ `passed=${stats.verificationPassedCommands?.length ?? 0}`,
159
+ `failed=${stats.verificationFailedCommands?.length ?? 0}`,
160
+ `missing=${stats.verificationMissingCommands?.length ?? 0}`,
161
+ `claim=${stats.verificationClaimAllowed ? 'yes' : 'no'}`,
162
+ ];
163
+ lines.push(`Verify ${verificationParts.join(' ')}`);
164
+ }
165
+ if (typeof stats.loopBudgetMaxLlmRequests === 'number'
166
+ || typeof stats.loopBudgetMaxToolCalls === 'number'
167
+ || typeof stats.loopBudgetMaxModelVisibleBytes === 'number') {
168
+ const caps = [
169
+ typeof stats.loopBudgetMaxLlmRequests === 'number'
170
+ ? `${stats.llmRequests}/${stats.loopBudgetMaxLlmRequests} LLM`
171
+ : undefined,
172
+ typeof stats.loopBudgetMaxToolCalls === 'number'
173
+ ? `${stats.toolCalls}/${stats.loopBudgetMaxToolCalls} tools`
174
+ : undefined,
175
+ typeof stats.loopBudgetMaxModelVisibleBytes === 'number'
176
+ ? `${(0, format_1.formatBytes)(stats.modelVisibleToolBytes)}/${(0, format_1.formatBytes)(stats.loopBudgetMaxModelVisibleBytes)} visible`
177
+ : undefined,
178
+ typeof stats.loopBudgetMaxReadOnlyFragmentation === 'number'
179
+ ? `fragment ${stats.singleReadOnlyStreak}/${stats.loopBudgetMaxReadOnlyFragmentation}`
180
+ : undefined,
181
+ ].filter(Boolean);
182
+ lines.push(`Budget cap ${caps.join(', ')} (${formatLoopBudgetSource(stats)})`);
183
+ }
184
+ if (stats.singleReadOnlyStreak > 0 || stats.batchReadSuggestionCount > 0) {
185
+ lines.push(`Read-only streak ${stats.singleReadOnlyStreak}, batch_read hints ${stats.batchReadSuggestionCount}`);
186
+ }
187
+ if (detail) {
188
+ lines.push(`Unsafe ${stats.unsafeToolCalls}`);
189
+ if (stats.providerRetryErrorTypes && stats.providerRetryErrorTypes.length > 0) {
190
+ lines.push(`Retry type ${stats.providerRetryErrorTypes.join(', ')}`);
191
+ }
192
+ if (stats.verificationFailedCommands && stats.verificationFailedCommands.length > 0) {
193
+ lines.push(`Failed ${stats.verificationFailedCommands.join(' && ')}`);
194
+ }
195
+ if (stats.verificationMissingCommands && stats.verificationMissingCommands.length > 0) {
196
+ lines.push(`Missing ${stats.verificationMissingCommands.join(' && ')}`);
197
+ }
198
+ if (stats.verificationSkippedReason) {
199
+ lines.push(`Verify why ${stats.verificationSkippedReason}`);
200
+ }
201
+ if (stats.continuationHint) {
202
+ lines.push(`Next why ${stats.continuationHint}`);
203
+ }
204
+ lines.push(`Budget hit ${stats.finishReason === 'budget_exceeded' ? 'yes' : 'no'}`);
205
+ }
206
+ return lines;
207
+ }
78
208
  const CATEGORY_LABELS = {
79
209
  workflow: 'Workflow',
80
210
  session: 'Session',
@@ -171,6 +301,7 @@ function showStatus(ctx) {
171
301
  console.log(` Model ${BRAND(modelId)}`);
172
302
  console.log(` Context ${DIM(`${formatTokenCount(modelContext.contextWindow)} tokens (${modelContext.source}${modelContext.source === 'fuzzy' ? `:${modelContext.matchedId}` : ''})`)}`);
173
303
  console.log(` Compact ${compactStats.enabled ? SUCCESS('auto') : WARN('off')} ${DIM(`predict ${formatThreshold(compactStats.predictiveCompactThreshold)}, hard ${formatThreshold(compactStats.threshold)}, used ${compactStats.ctxPercent}%`)}`);
304
+ console.log(` Renderer ${formatRendererStatus(ctx)}`);
174
305
  console.log();
175
306
  console.log(` Agents ${SUCCESS(brainStatus.agents.length)} registered`);
176
307
  console.log(` Tasks ${brainStatus.pendingTasks} pending (${brainStatus.strategy} strategy)`);
@@ -203,15 +334,8 @@ function showStatus(ctx) {
203
334
  const stats = snapshot.lastLoopStats;
204
335
  console.log();
205
336
  console.log(` Last loop:`);
206
- console.log(` Finish ${stats.finishReason}`);
207
- console.log(` Requests ${stats.llmRequests} LLM / ${stats.turnsStarted} turns`);
208
- console.log(` Tools ${stats.toolCalls} total (${stats.readOnlyToolCalls} read-only, ${stats.unsafeToolCalls} unsafe)`);
209
- console.log(` Tool bytes ${(0, format_1.formatBytes)(stats.modelVisibleToolBytes)} model-visible / ${(0, format_1.formatBytes)(stats.toolResultBytes)} total`);
210
- if (stats.summarizedBytes > 0) {
211
- console.log(` Saved ${(0, format_1.formatBytes)(stats.summarizedBytes)} from model context`);
212
- }
213
- if (stats.compactTrigger) {
214
- console.log(` Compact ${stats.compactTrigger}`);
337
+ for (const line of formatLoopStatsLines(stats)) {
338
+ console.log(` ${line}`);
215
339
  }
216
340
  }
217
341
  const harnessState = snapshot.harnessState;
@@ -232,6 +356,533 @@ function showStatus(ctx) {
232
356
  console.log();
233
357
  return { success: true };
234
358
  }
359
+ function handleLoopStats(ctx) {
360
+ const stats = ctx.store.getSnapshot().lastLoopStats;
361
+ if (!stats) {
362
+ console.log('No agent-loop stats recorded yet.');
363
+ return { success: true };
364
+ }
365
+ console.log(HEADER('Agent Loop Stats'));
366
+ console.log();
367
+ for (const line of formatLoopStatsLines(stats, true)) {
368
+ console.log(` ${line}`);
369
+ }
370
+ console.log();
371
+ console.log(DIM('Use this to spot excessive LLM requests, fragmented read-only tool calls, and local fast-path hits.'));
372
+ return { success: true };
373
+ }
374
+ function formatTraceEventLine(event) {
375
+ const time = new Date(event.timestamp).toLocaleTimeString();
376
+ const prefix = `${DIM(time)} ${ACCENT(event.type)}`;
377
+ switch (event.type) {
378
+ case 'turn_start':
379
+ return `${prefix} input=${(0, format_1.formatBytes)(event.inputBytes ?? 0)}${event.localFastPathUsed ? ' fast-path' : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
380
+ case 'request_start':
381
+ return `${prefix} model=${event.model ?? 'unknown'} iteration=${event.turn ?? '?'}`;
382
+ case 'provider_retry': {
383
+ const parts = [
384
+ `count=${event.providerRetryCount ?? 0}`,
385
+ `delay=${formatDurationMs(event.providerRetryDelayMs ?? 0)}`,
386
+ event.providerLastRetryErrorType
387
+ ? `last=${event.providerLastRetryErrorType}${event.providerLastRetryStatus ? `/${event.providerLastRetryStatus}` : ''}`
388
+ : '',
389
+ event.providerRetryErrorTypes?.length ? `types=${event.providerRetryErrorTypes.join(',')}` : '',
390
+ event.providerFinalModel ? `final=${event.providerFinalModel}` : '',
391
+ ].filter(Boolean);
392
+ return `${prefix} ${parts.join(' ')}`;
393
+ }
394
+ case 'provider_fallback': {
395
+ const path = event.providerFallbackFromModel && event.providerFallbackToModel
396
+ ? `${event.providerFallbackFromModel}->${event.providerFallbackToModel}`
397
+ : event.providerFinalModel ?? 'active';
398
+ const parts = [
399
+ `count=${event.providerFallbackCount ?? 0}`,
400
+ `path=${path}`,
401
+ `using=${event.providerUsingFallback ? 'yes' : 'no'}`,
402
+ ];
403
+ return `${prefix} ${parts.join(' ')}`;
404
+ }
405
+ case 'prompt_assembly': {
406
+ const parts = [
407
+ `model=${event.promptModelId ?? 'unknown'}`,
408
+ `tokens=${event.promptEstimatedTokens ?? 0}/${event.promptBudgetTokens ?? 0}`,
409
+ `core=${event.promptCoreTokens ?? 0}`,
410
+ `evidenceBudget=${event.promptEvidenceBudgetTokens ?? 0}`,
411
+ `recentBudget=${event.promptRecentTurnBudgetTokens ?? 0}`,
412
+ `included=${event.promptIncludedEvidenceCount ?? 0}`,
413
+ `omitted=${event.promptOmittedEvidenceCount ?? 0}`,
414
+ ];
415
+ const sections = event.promptSections?.length
416
+ ? ` sections=${event.promptSections.join(',')}`
417
+ : '';
418
+ const evidence = event.promptIncludedEvidence?.length
419
+ ? ` evidence=${event.promptIncludedEvidence.slice(0, 6).join(', ')}${event.promptIncludedEvidence.length > 6 ? ', ...' : ''}`
420
+ : '';
421
+ return `${prefix} ${parts.join(' ')}${sections ? DIM(sections) : ''}${evidence ? ` ${DIM(evidence)}` : ''}`;
422
+ }
423
+ case 'assistant_tool_calls':
424
+ return `${prefix} calls=${event.toolCallCount ?? 0} assistant=${(0, format_1.formatBytes)(event.contentBytes ?? 0)}`;
425
+ case 'checkpoint': {
426
+ const files = event.checkpointFiles?.length
427
+ ? ` files=${event.checkpointFiles.slice(0, 6).join(', ')}${event.checkpointFiles.length > 6 ? ', ...' : ''}`
428
+ : '';
429
+ const targets = event.workspaceFiles?.length && !files
430
+ ? ` targets=${event.workspaceFiles.slice(0, 6).join(', ')}${event.workspaceFiles.length > 6 ? ', ...' : ''}`
431
+ : '';
432
+ return `${prefix} id=${event.checkpointId ?? 'unknown'} saved=${event.checkpointFileCount ?? 0}${files}${targets}${event.note ? ` ${DIM(event.note)}` : ''}`;
433
+ }
434
+ case 'tool_call':
435
+ return `${prefix} ${event.name ?? 'tool'}${event.argsSummary ? ` ${DIM(event.argsSummary)}` : ''}${event.argsArtifactId ? ` ${DIM(`args=/artifacts show ${event.argsArtifactId} --full${event.argsBytes ? ` (${(0, format_1.formatBytes)(event.argsBytes)})` : ''}`)}` : ''}${event.callId ? ` ${DIM(event.callId)}` : ''}`;
436
+ case 'permission_decision': {
437
+ const status = event.permissionApproved ? SUCCESS('approved') : ERROR('denied');
438
+ const parts = [
439
+ `source=${event.permissionSource ?? 'unknown'}`,
440
+ `behavior=${event.permissionBehavior ?? 'unknown'}`,
441
+ ];
442
+ if (typeof event.permissionDuration === 'number') {
443
+ parts.push(`${event.permissionDuration}ms`);
444
+ }
445
+ return `${prefix} ${status} ${event.name ?? 'tool'} ${DIM(parts.join(' '))}${event.permissionReason ? ` ${DIM(event.permissionReason)}` : ''}`;
446
+ }
447
+ case 'tool_result': {
448
+ const status = event.success === false ? ERROR('error') : SUCCESS('ok');
449
+ const bytes = [
450
+ `output=${(0, format_1.formatBytes)(event.outputBytes ?? 0)}`,
451
+ `model=${(0, format_1.formatBytes)(event.modelVisibleBytes ?? 0)}`,
452
+ ];
453
+ if (event.artifactId)
454
+ bytes.push(`artifact=${event.artifactId}`);
455
+ return `${prefix} ${status} ${event.name ?? 'tool'} ${DIM(`${event.duration ?? 0}ms`)} ${DIM(bytes.join(' '))}${event.error ? ` ${ERROR(event.error)}` : ''}`;
456
+ }
457
+ case 'message':
458
+ return `${prefix} assistant=${(0, format_1.formatBytes)(event.contentBytes ?? 0)}`;
459
+ case 'strategy_exhausted':
460
+ return `${prefix}${event.note ? ` ${WARN(event.note)}` : ''}`;
461
+ case 'complete': {
462
+ const stats = [
463
+ `finish=${event.finishReason ?? 'unknown'}`,
464
+ `llm=${typeof event.llmRequests === 'number' ? event.llmRequests : 'unknown'}`,
465
+ `tools=${typeof event.toolCalls === 'number' ? event.toolCalls : 'unknown'}`,
466
+ ];
467
+ if (event.loopBudgetSource
468
+ || typeof event.loopBudgetMaxLlmRequests === 'number'
469
+ || typeof event.loopBudgetMaxToolCalls === 'number'
470
+ || typeof event.loopBudgetMaxModelVisibleBytes === 'number') {
471
+ const source = event.loopBudgetSource === 'config' && event.loopBudgetBaseProfile
472
+ ? `config/${event.loopBudgetBaseProfile}`
473
+ : event.loopBudgetSource ?? 'unknown';
474
+ const caps = [
475
+ typeof event.loopBudgetMaxLlmRequests === 'number'
476
+ ? `${typeof event.llmRequests === 'number' ? event.llmRequests : '?'}/${event.loopBudgetMaxLlmRequests}llm`
477
+ : undefined,
478
+ typeof event.loopBudgetMaxToolCalls === 'number'
479
+ ? `${typeof event.toolCalls === 'number' ? event.toolCalls : '?'}/${event.loopBudgetMaxToolCalls}tools`
480
+ : undefined,
481
+ typeof event.loopBudgetMaxModelVisibleBytes === 'number'
482
+ ? `${(0, format_1.formatBytes)(event.loopBudgetMaxModelVisibleBytes)}visible`
483
+ : undefined,
484
+ typeof event.loopBudgetMaxReadOnlyFragmentation === 'number'
485
+ ? `frag=${event.loopBudgetMaxReadOnlyFragmentation}`
486
+ : undefined,
487
+ event.loopBudgetConfigOverride ? 'override=yes' : undefined,
488
+ ].filter(Boolean);
489
+ stats.push(`budgetProfile=${source}${caps.length ? `(${caps.join(',')})` : ''}`);
490
+ }
491
+ if (event.budgetExceededReason)
492
+ stats.push(`budget=${event.budgetExceededReason}`);
493
+ if (event.continuationActions?.length)
494
+ stats.push(`next=${event.continuationActions.join(',')}`);
495
+ if (event.continuationHint)
496
+ stats.push(`hint=${event.continuationHint}`);
497
+ if (event.localFastPathUsed)
498
+ stats.push('fast-path=yes');
499
+ return `${prefix} ${stats.join(' ')}`;
500
+ }
501
+ case 'local_fast_path':
502
+ return `${prefix} ${event.name ?? 'tool'}${event.argsSummary ? ` ${DIM(event.argsSummary)}` : ''}${event.argsArtifactId ? ` ${DIM(`args=/artifacts show ${event.argsArtifactId} --full${event.argsBytes ? ` (${(0, format_1.formatBytes)(event.argsBytes)})` : ''}`)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
503
+ case 'workspace_snapshot': {
504
+ const state = event.workspaceGitAvailable === false
505
+ ? WARN('not-git')
506
+ : event.workspaceDirty ? WARN('dirty') : SUCCESS('clean');
507
+ const files = event.workspaceFiles?.length
508
+ ? ` files=${event.workspaceFiles.slice(0, 6).join(', ')}${event.workspaceFiles.length > 6 ? ', ...' : ''}`
509
+ : '';
510
+ return `${prefix} ${event.workspacePhase ?? 'unknown'} ${state} count=${event.workspaceFileCount ?? 0}${event.workspaceBranch ? ` branch=${event.workspaceBranch}` : ''}${files}${event.error ? ` ${ERROR(event.error)}` : ''}`;
511
+ }
512
+ case 'workspace_delta': {
513
+ const added = event.workspaceNewByTurn ?? [];
514
+ const changed = event.workspaceChangedByTurn ?? [];
515
+ const modifiedPreExisting = event.workspaceModifiedPreExistingByTurn ?? [];
516
+ const resolved = event.workspaceResolvedByTurn ?? [];
517
+ const parts = [
518
+ `after=${event.workspaceFileCount ?? 0}`,
519
+ `new=${added.length}`,
520
+ `changed=${changed.length}`,
521
+ `pre-existing-modified=${modifiedPreExisting.length}`,
522
+ `resolved=${resolved.length}`,
523
+ ];
524
+ const details = [
525
+ added.length ? `new: ${added.slice(0, 6).join(', ')}${added.length > 6 ? ', ...' : ''}` : '',
526
+ changed.length ? `changed: ${changed.slice(0, 6).join(', ')}${changed.length > 6 ? ', ...' : ''}` : '',
527
+ modifiedPreExisting.length ? `pre-existing modified: ${modifiedPreExisting.slice(0, 6).join(', ')}${modifiedPreExisting.length > 6 ? ', ...' : ''}` : '',
528
+ resolved.length ? `resolved: ${resolved.slice(0, 6).join(', ')}${resolved.length > 6 ? ', ...' : ''}` : '',
529
+ ].filter(Boolean).join(' | ');
530
+ return `${prefix} ${parts.join(' ')}${details ? ` ${DIM(details)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
531
+ }
532
+ case 'verification_profile': {
533
+ const commands = event.verificationCommands ?? [];
534
+ const files = event.verificationChangedFiles ?? [];
535
+ const details = [
536
+ `profile=${event.verificationProfile ?? 'unknown'}`,
537
+ `required=${event.verificationRequired === false ? 'no' : 'yes'}`,
538
+ `commands=${commands.length}`,
539
+ `files=${files.length}`,
540
+ ];
541
+ if (event.verificationRisky) {
542
+ details.push(`risk=${WARN('high')}(${files.length} files)`);
543
+ }
544
+ const commandPreview = commands.length
545
+ ? ` cmds: ${commands.slice(0, 4).join(' && ')}${commands.length > 4 ? ' && ...' : ''}`
546
+ : '';
547
+ return `${prefix} ${details.join(' ')}${commandPreview ? ` ${DIM(commandPreview)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
548
+ }
549
+ case 'verification_result': {
550
+ const status = event.verificationPassed === true ? SUCCESS('passed') : ERROR('failed');
551
+ const command = event.verificationCommand ?? 'unknown';
552
+ return `${prefix} ${status} ${command}${typeof event.outputBytes === 'number' ? ` ${DIM((0, format_1.formatBytes)(event.outputBytes))}` : ''}${event.error ? ` ${ERROR(event.error)}` : ''}`;
553
+ }
554
+ case 'verification_summary': {
555
+ const passed = event.verificationPassedCommands?.length ?? 0;
556
+ const failed = event.verificationFailedCommands?.length ?? 0;
557
+ const missing = event.verificationMissingCommands?.length ?? 0;
558
+ const parts = [
559
+ `profile=${event.verificationProfile ?? 'unknown'}`,
560
+ `required=${event.verificationRequired === false ? 'no' : 'yes'}`,
561
+ `passed=${passed}`,
562
+ `failed=${failed}`,
563
+ `missing=${missing}`,
564
+ `claimAllowed=${event.verificationClaimAllowed ? 'yes' : 'no'}`,
565
+ ];
566
+ const missingPreview = missing > 0
567
+ ? ` missing: ${(event.verificationMissingCommands ?? []).slice(0, 4).join(' && ')}${missing > 4 ? ' && ...' : ''}`
568
+ : '';
569
+ return `${prefix} ${parts.join(' ')}${missingPreview ? ` ${DIM(missingPreview)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
570
+ }
571
+ case 'aborted':
572
+ return `${prefix}${event.note ? ` ${WARN(event.note)}` : ''}`;
573
+ case 'error':
574
+ return `${prefix} ${ERROR(event.error ?? 'unknown error')}`;
575
+ default:
576
+ return prefix;
577
+ }
578
+ }
579
+ function handleTrace(ctx, args = '') {
580
+ const session = ctx.getSession?.() ?? (ctx.sessionId ? (0, session_storage_1.loadSessionMeta)(ctx.sessionId) : null);
581
+ if (!session) {
582
+ console.log(ERROR('No active session.'));
583
+ console.log(DIM('Use /resume <session-id> first, or start a chat turn to create a session.'));
584
+ return { success: false };
585
+ }
586
+ const events = (0, session_storage_1.readSessionTraceEvents)(session.id);
587
+ if (events.length === 0) {
588
+ console.log(DIM(`No trace events recorded for session ${session.id.slice(0, 8)} yet.`));
589
+ return { success: true };
590
+ }
591
+ const ref = args.trim();
592
+ const requestedTurnId = ref && ref !== 'latest' ? ref : events[events.length - 1].turnId;
593
+ const turnEvents = events.filter(event => event.turnId === requestedTurnId);
594
+ if (turnEvents.length === 0) {
595
+ const recentTurnIds = [...new Set(events.map(event => event.turnId))].slice(-8).reverse();
596
+ console.log(ERROR(`Trace turn not found: ${requestedTurnId}`));
597
+ console.log(DIM(`Recent turns: ${recentTurnIds.join(', ')}`));
598
+ return { success: false };
599
+ }
600
+ console.log(HEADER(`Trace ${requestedTurnId}`));
601
+ console.log(DIM(`Session ${session.id} Events ${turnEvents.length}`));
602
+ console.log(DIM('─'.repeat(40)));
603
+ for (const event of turnEvents) {
604
+ console.log(` ${formatTraceEventLine(event)}`);
605
+ }
606
+ console.log();
607
+ console.log(DIM('Trace stores metadata only; full transcript and tool output stay in session/artifacts.'));
608
+ return { success: true };
609
+ }
610
+ function latestToolTrace(events) {
611
+ for (let index = events.length - 1; index >= 0; index--) {
612
+ const event = events[index];
613
+ if (event.type !== 'tool_result' && event.type !== 'tool_call')
614
+ continue;
615
+ const callId = event.callId;
616
+ const result = event.type === 'tool_result' ? event : undefined;
617
+ const call = event.type === 'tool_call'
618
+ ? event
619
+ : callId
620
+ ? events.slice(0, index).reverse().find(candidate => candidate.type === 'tool_call' && candidate.callId === callId)
621
+ : undefined;
622
+ return { call, result };
623
+ }
624
+ return null;
625
+ }
626
+ function parseLastToolArgs(args = '') {
627
+ const parts = args.trim().split(/\s+/).filter(Boolean);
628
+ return {
629
+ full: parts.includes('--full'),
630
+ preview: !parts.includes('--no-preview'),
631
+ };
632
+ }
633
+ function printLastToolArtifactPreview(projectPath, label, artifactId, full) {
634
+ if (!artifactId)
635
+ return;
636
+ const artifact = (0, tool_artifacts_1.findArtifact)(projectPath, artifactId);
637
+ if (!artifact) {
638
+ console.log(` ${label} preview ${DIM(`artifact not found or ambiguous: ${artifactId}`)}`);
639
+ return;
640
+ }
641
+ const content = (0, tool_artifacts_1.retrieveArtifact)(artifact.path);
642
+ if (content == null) {
643
+ console.log(` ${label} preview ${DIM(`artifact cannot be read: ${artifact.id}`)}`);
644
+ return;
645
+ }
646
+ const redacted = (0, session_storage_1.redactTraceText)(content);
647
+ const maxPreviewBytes = 4 * 1024;
648
+ const shouldTruncate = !full && Buffer.byteLength(redacted, 'utf8') > maxPreviewBytes;
649
+ const preview = shouldTruncate
650
+ ? Buffer.from(redacted, 'utf8').subarray(0, maxPreviewBytes).toString('utf8')
651
+ : redacted;
652
+ console.log(` ${label} preview`);
653
+ console.log(` ${DIM('─'.repeat(40))}`);
654
+ for (const line of preview.split('\n')) {
655
+ console.log(` ${line}`);
656
+ }
657
+ if (shouldTruncate) {
658
+ console.log(` ${DIM(`... preview truncated at ${(0, format_1.formatBytes)(maxPreviewBytes)}. Use /last-tool --full or /artifacts show ${artifact.id} --full.`)}`);
659
+ }
660
+ }
661
+ function lastToolInputLabel(toolName) {
662
+ return toolName === 'exec_command' ? 'Command' : 'Args';
663
+ }
664
+ function lastToolField(label) {
665
+ return label.padEnd(13, ' ');
666
+ }
667
+ function handleLastTool(ctx, args = '') {
668
+ const options = parseLastToolArgs(args);
669
+ const session = ctx.getSession?.() ?? (ctx.sessionId ? (0, session_storage_1.loadSessionMeta)(ctx.sessionId) : null);
670
+ if (!session) {
671
+ console.log(ERROR('No active session.'));
672
+ console.log(DIM('Use /resume <session-id> first, or start a chat turn to create a session.'));
673
+ return { success: false };
674
+ }
675
+ const events = (0, session_storage_1.readSessionTraceEvents)(session.id);
676
+ const latest = latestToolTrace(events);
677
+ if (!latest) {
678
+ console.log(DIM(`No tool trace events recorded for session ${session.id.slice(0, 8)} yet.`));
679
+ return { success: true };
680
+ }
681
+ const call = latest.call;
682
+ const result = latest.result;
683
+ const source = result ?? call;
684
+ const argsSource = call ?? result;
685
+ const name = source?.name ?? 'tool';
686
+ const inputLabel = lastToolInputLabel(name);
687
+ const callId = source?.callId ?? call?.callId;
688
+ const status = result ? (result.success === false ? ERROR('error') : SUCCESS('ok')) : WARN('running');
689
+ console.log(HEADER('Last Tool'));
690
+ console.log(DIM('─'.repeat(40)));
691
+ console.log(` Tool ${ACCENT(name)}`);
692
+ console.log(` Turn ${DIM(source?.turnId ?? 'unknown')}`);
693
+ if (callId)
694
+ console.log(` Call ${DIM(callId)}`);
695
+ console.log(` Status ${status}`);
696
+ if (typeof result?.duration === 'number') {
697
+ console.log(` Time ${DIM(formatDurationMs(result.duration))}`);
698
+ }
699
+ if (argsSource?.argsSummary) {
700
+ console.log(` ${lastToolField(inputLabel)}${DIM((0, session_storage_1.redactTraceText)(argsSource.argsSummary))}`);
701
+ }
702
+ if (argsSource?.argsArtifactId) {
703
+ const size = typeof argsSource.argsBytes === 'number' ? ` (${(0, format_1.formatBytes)(argsSource.argsBytes)})` : '';
704
+ console.log(` ${lastToolField(`${inputLabel} full`)}${DIM(`/artifacts show ${argsSource.argsArtifactId} --full${size}`)}`);
705
+ }
706
+ if (typeof result?.outputBytes === 'number') {
707
+ const modelVisible = typeof result.modelVisibleBytes === 'number'
708
+ ? `, model-visible ${(0, format_1.formatBytes)(result.modelVisibleBytes)}`
709
+ : '';
710
+ console.log(` Output ${DIM(`${(0, format_1.formatBytes)(result.outputBytes)}${modelVisible}`)}`);
711
+ }
712
+ if (result?.artifactId) {
713
+ console.log(` Output full ${DIM(`/artifacts show ${result.artifactId} --full`)}`);
714
+ }
715
+ if (result?.error) {
716
+ console.log(` Error ${ERROR((0, session_storage_1.redactTraceText)(result.error))}`);
717
+ }
718
+ if (options.preview) {
719
+ const projectPath = session.projectPath || ctx.cwd;
720
+ printLastToolArtifactPreview(projectPath, inputLabel, argsSource?.argsArtifactId, options.full);
721
+ printLastToolArtifactPreview(projectPath, 'Output', result?.artifactId, options.full);
722
+ }
723
+ console.log();
724
+ console.log(DIM('Use /last-tool --full for redacted full previews, --no-preview for metadata only, or /trace latest for the ordered turn timeline.'));
725
+ return { success: true };
726
+ }
727
+ function formatDateTime(timestamp) {
728
+ return new Date(timestamp).toLocaleString();
729
+ }
730
+ function parseArtifactArgs(args) {
731
+ const parts = args.trim().split(/\s+/).filter(Boolean);
732
+ let full = false;
733
+ let limit = 20;
734
+ const positional = [];
735
+ for (let i = 0; i < parts.length; i++) {
736
+ const part = parts[i];
737
+ if (part === '--full') {
738
+ full = true;
739
+ continue;
740
+ }
741
+ if ((part === '--limit' || part === '-n') && parts[i + 1]) {
742
+ const parsed = Number(parts[i + 1]);
743
+ if (Number.isInteger(parsed) && parsed > 0) {
744
+ limit = Math.min(parsed, 200);
745
+ }
746
+ i++;
747
+ continue;
748
+ }
749
+ positional.push(part);
750
+ }
751
+ if (positional[0] === 'show' || positional[0] === 'cat') {
752
+ return { action: 'show', ref: positional[1], full, limit };
753
+ }
754
+ if (positional[0]) {
755
+ return { action: 'show', ref: positional[0], full, limit };
756
+ }
757
+ return { action: 'list', full, limit };
758
+ }
759
+ function printArtifactPreview(content, full) {
760
+ const maxPreviewBytes = 16 * 1024;
761
+ if (full || Buffer.byteLength(content, 'utf8') <= maxPreviewBytes) {
762
+ console.log(content);
763
+ return;
764
+ }
765
+ const preview = Buffer.from(content, 'utf8').subarray(0, maxPreviewBytes).toString('utf8');
766
+ console.log(preview);
767
+ console.log();
768
+ console.log(DIM(`... preview truncated at ${(0, format_1.formatBytes)(maxPreviewBytes)}. Use /artifacts show <id> --full for full output.`));
769
+ }
770
+ function formatArtifactPathForDisplay(artifactPath) {
771
+ return (0, session_storage_1.redactTraceText)(artifactPath);
772
+ }
773
+ function handleArtifacts(ctx, args = '') {
774
+ const parsed = parseArtifactArgs(args);
775
+ if (parsed.action === 'list') {
776
+ const artifacts = (0, tool_artifacts_1.listArtifacts)(ctx.cwd, parsed.limit);
777
+ console.log(HEADER('Artifacts'));
778
+ console.log(DIM('─'.repeat(40)));
779
+ if (artifacts.length === 0) {
780
+ console.log(DIM('No artifacts found for this project.'));
781
+ return { success: true };
782
+ }
783
+ for (const artifact of artifacts) {
784
+ console.log(`${ACCENT(artifact.id)} ${DIM(artifact.toolName)} ${(0, format_1.formatBytes)(artifact.outputBytes)}`);
785
+ console.log(` ${DIM(formatDateTime(artifact.modifiedAt))}`);
786
+ console.log(` ${DIM(formatArtifactPathForDisplay(artifact.path))}`);
787
+ }
788
+ console.log();
789
+ console.log(DIM('Use /artifacts show <id|prefix> to preview, or add --full for full output.'));
790
+ return { success: true };
791
+ }
792
+ if (!parsed.ref) {
793
+ console.log(ERROR('Usage: /artifacts show <id|prefix> [--full]'));
794
+ return { success: false };
795
+ }
796
+ const artifact = (0, tool_artifacts_1.findArtifact)(ctx.cwd, parsed.ref);
797
+ if (!artifact) {
798
+ console.log(ERROR(`Artifact not found or ambiguous: ${parsed.ref}`));
799
+ console.log(DIM('Run /artifacts to list available artifact ids.'));
800
+ return { success: false };
801
+ }
802
+ const content = (0, tool_artifacts_1.retrieveArtifact)(artifact.path);
803
+ if (content == null) {
804
+ console.log(ERROR(`Artifact exists but cannot be read: ${artifact.id}`));
805
+ console.log(DIM(formatArtifactPathForDisplay(artifact.path)));
806
+ return { success: false };
807
+ }
808
+ console.log(HEADER(`Artifact ${artifact.id}`));
809
+ console.log(DIM(`Tool ${artifact.toolName} Size ${(0, format_1.formatBytes)(artifact.outputBytes)} Modified ${formatDateTime(artifact.modifiedAt)}`));
810
+ console.log(DIM(formatArtifactPathForDisplay(artifact.path)));
811
+ console.log(DIM('─'.repeat(40)));
812
+ printArtifactPreview(content, parsed.full);
813
+ return { success: true };
814
+ }
815
+ function findCheckpointByRef(ctx, ref) {
816
+ const checkpoints = (0, checkpoint_1.listCheckpoints)(ctx.cwd);
817
+ const exact = checkpoints.find(checkpoint => checkpoint.turnId === ref);
818
+ if (exact)
819
+ return { checkpoint: exact, ambiguous: false };
820
+ const matches = checkpoints.filter(checkpoint => checkpoint.turnId.startsWith(ref));
821
+ return {
822
+ checkpoint: matches.length === 1 ? matches[0] : undefined,
823
+ ambiguous: matches.length > 1,
824
+ };
825
+ }
826
+ function handleCheckpoint(ctx, args = '') {
827
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
828
+ const action = tokens[0] ?? 'list';
829
+ if (action === 'list') {
830
+ const checkpoints = (0, checkpoint_1.listCheckpoints)(ctx.cwd);
831
+ console.log(HEADER('Checkpoints'));
832
+ console.log(DIM('─'.repeat(40)));
833
+ if (checkpoints.length === 0) {
834
+ console.log(DIM('No checkpoints found for this project.'));
835
+ return { success: true };
836
+ }
837
+ for (const checkpoint of checkpoints.slice(0, 20)) {
838
+ console.log(`${ACCENT(checkpoint.turnId)} ${DIM(formatDateTime(checkpoint.createdAt))}`);
839
+ const files = checkpoint.files.map(file => file.path).slice(0, 8).join(', ');
840
+ console.log(` ${DIM(`${checkpoint.files.length} file(s)${files ? `: ${files}` : ''}`)}`);
841
+ }
842
+ console.log();
843
+ console.log(DIM('Use /checkpoint restore <turn-id|prefix> to preview, then add --yes to restore.'));
844
+ return { success: true };
845
+ }
846
+ if (action !== 'restore') {
847
+ console.log(ERROR('Usage: /checkpoint [list|restore <turn-id|prefix> [--yes]]'));
848
+ return { success: false };
849
+ }
850
+ const ref = tokens.find(token => token !== 'restore' && token !== '--yes');
851
+ const confirmed = tokens.includes('--yes');
852
+ if (!ref) {
853
+ console.log(ERROR('Usage: /checkpoint restore <turn-id|prefix> [--yes]'));
854
+ return { success: false };
855
+ }
856
+ const { checkpoint, ambiguous } = findCheckpointByRef(ctx, ref);
857
+ if (!checkpoint) {
858
+ console.log(ERROR(ambiguous
859
+ ? `Checkpoint prefix is ambiguous: ${ref}`
860
+ : `Checkpoint not found: ${ref}`));
861
+ console.log(DIM('Run /checkpoint to list available checkpoint ids.'));
862
+ return { success: false };
863
+ }
864
+ if (!confirmed) {
865
+ console.log(HEADER(`Checkpoint ${checkpoint.turnId}`));
866
+ console.log(DIM(`${formatDateTime(checkpoint.createdAt)} ${checkpoint.files.length} file(s)`));
867
+ for (const file of checkpoint.files.slice(0, 20)) {
868
+ console.log(` ${file.path} ${DIM((0, format_1.formatBytes)(file.sizeBytes))}`);
869
+ }
870
+ console.log();
871
+ console.log(WARN(`This will overwrite current files from checkpoint ${checkpoint.turnId}.`));
872
+ console.log(DIM(`Run /checkpoint restore ${checkpoint.turnId} --yes to restore.`));
873
+ return { success: true };
874
+ }
875
+ const result = (0, checkpoint_1.restoreCheckpoint)(ctx.cwd, checkpoint.turnId);
876
+ if (result.error) {
877
+ console.log(ERROR(result.error));
878
+ return { success: false, error: result.error };
879
+ }
880
+ console.log(SUCCESS(`Restored ${result.restored.length} file(s) from checkpoint ${checkpoint.turnId}.`));
881
+ for (const file of result.restored.slice(0, 20)) {
882
+ console.log(` ${file}`);
883
+ }
884
+ return { success: true, output: result.restored.join('\n') };
885
+ }
235
886
  function snapshotCurrentModel(ctx) {
236
887
  return ctx.store.getSnapshot().currentModel || ctx.config.model;
237
888
  }
@@ -367,6 +1018,47 @@ function showHarness(ctx, args = '') {
367
1018
  console.log(DIM(' (no contract established)'));
368
1019
  }
369
1020
  console.log();
1021
+ // Context source
1022
+ console.log(HEADER(' Context Source'));
1023
+ const session = ctx.getSession?.() ?? (ctx.sessionId ? (0, session_storage_1.loadSessionMeta)(ctx.sessionId) : null);
1024
+ const isRestored = session?.transcriptDisplayStartTime != null;
1025
+ const isCompactActive = Boolean(state.promptAssemblyStats);
1026
+ console.log(` Root ${ACCENT(state.rootObjective || contract?.objective || '(none)')}`);
1027
+ console.log(` Active ${DIM(state.activeInstruction || contract?.userIntent || '(none)')}`);
1028
+ console.log(` Source ${isRestored ? WARN('restored session') : DIM('live turn')}`);
1029
+ if (isRestored && session) {
1030
+ const restoredTime = session.transcriptDisplayStartTime
1031
+ ? new Date(session.transcriptDisplayStartTime).toLocaleString()
1032
+ : 'unknown';
1033
+ console.log(` Restored ${DIM(restoredTime)}`);
1034
+ }
1035
+ if (isCompactActive) {
1036
+ console.log(` Compact ${SUCCESS('active')}`);
1037
+ }
1038
+ console.log();
1039
+ // Evidence index summary
1040
+ console.log(HEADER(' Evidence Index'));
1041
+ const evidenceItems = state.evidenceIndex?.length ?? 0;
1042
+ const evidenceKinds = new Map();
1043
+ if (state.evidenceIndex) {
1044
+ for (const item of state.evidenceIndex) {
1045
+ const kind = item.kind || 'unknown';
1046
+ evidenceKinds.set(kind, (evidenceKinds.get(kind) || 0) + 1);
1047
+ }
1048
+ }
1049
+ console.log(` Total ${ACCENT(String(evidenceItems))} items`);
1050
+ if (evidenceKinds.size > 0) {
1051
+ const kinds = Array.from(evidenceKinds.entries())
1052
+ .sort((a, b) => b[1] - a[1])
1053
+ .slice(0, 10);
1054
+ for (const [kind, count] of kinds) {
1055
+ console.log(` ${DIM(kind.padEnd(16))} ${count}`);
1056
+ }
1057
+ }
1058
+ else {
1059
+ console.log(DIM(' (no evidence records yet)'));
1060
+ }
1061
+ console.log();
370
1062
  // Intent history
371
1063
  console.log(HEADER(' Recent Intents'));
372
1064
  const intents = state.intentHistory?.slice(-5) ?? [];
@@ -498,51 +1190,13 @@ function showConfig(ctx) {
498
1190
  return { success: true };
499
1191
  }
500
1192
  function handleModel(ctx, args) {
501
- // 模型别名映射
502
- const MODEL_ALIASES = {
503
- 'opus': 'claude-opus-4-7',
504
- 'sonnet': 'claude-sonnet-4-6',
505
- 'haiku': 'claude-haiku-4-5-20251001',
506
- 'claude': 'claude-sonnet-4-6',
507
- 'gpt4': 'gpt-4o',
508
- 'gpt4o': 'gpt-4o',
509
- 'gpt35': 'gpt-3.5-turbo',
510
- // Bailian (coding.dashscope.aliyuncs.com) — OpenAI-compatible
511
- 'qwen': 'qwen3.5-plus',
512
- 'qwenplus': 'qwen3.5-plus',
513
- 'qwen36': 'qwen3.6-plus',
514
- 'qwenmax': 'qwen3-max-2026-01-23',
515
- 'coder': 'qwen3-coder-plus',
516
- 'codernext': 'qwen3-coder-next',
517
- 'glm': 'glm-5',
518
- 'glm47': 'glm-4.7',
519
- 'kimi': 'kimi-k2.5',
520
- 'minimax': 'MiniMax-M2.5',
521
- };
522
- // 可用模型列表
523
- const AVAILABLE_MODELS = [
524
- { name: 'claude-opus-4-7', alias: 'opus', provider: 'Anthropic' },
525
- { name: 'claude-sonnet-4-6', alias: 'sonnet', provider: 'Anthropic' },
526
- { name: 'claude-haiku-4-5-20251001', alias: 'haiku', provider: 'Anthropic' },
527
- { name: 'gpt-4o', alias: 'gpt4o', provider: 'OpenAI' },
528
- { name: 'gpt-3.5-turbo', alias: 'gpt35', provider: 'OpenAI' },
529
- { name: 'glm-5', alias: 'glm', provider: 'Bailian (Zhipu)' },
530
- { name: 'glm-4.7', alias: 'glm47', provider: 'Bailian (Zhipu)' },
531
- { name: 'qwen3.5-plus', alias: 'qwen', provider: 'Bailian (Alibaba)' },
532
- { name: 'qwen3.6-plus', alias: 'qwen36', provider: 'Bailian (Alibaba)' },
533
- { name: 'qwen3-max-2026-01-23', alias: 'qwenmax', provider: 'Bailian (Alibaba)' },
534
- { name: 'qwen3-coder-plus', alias: 'coder', provider: 'Bailian (Alibaba)' },
535
- { name: 'qwen3-coder-next', alias: 'codernext', provider: 'Bailian (Alibaba)' },
536
- { name: 'kimi-k2.5', alias: 'kimi', provider: 'Bailian (Moonshot)' },
537
- { name: 'MiniMax-M2.5', alias: 'minimax', provider: 'Bailian (MiniMax)' },
538
- ];
539
1193
  const trimmedArgs = args.trim().toLowerCase();
540
1194
  // 显示当前模型
541
1195
  if (!args || trimmedArgs === '?' || trimmedArgs === 'info') {
542
1196
  console.log();
543
1197
  if (ctx.llm) {
544
1198
  const currentModel = ctx.llm.getModel();
545
- const aliasEntry = AVAILABLE_MODELS.find(m => m.name === currentModel || m.alias === currentModel);
1199
+ const aliasEntry = (0, model_catalog_1.getModelCatalogEntry)(currentModel);
546
1200
  const contextInfo = (0, model_context_1.resolveModelContext)(currentModel);
547
1201
  const compactStats = (0, auto_compact_1.getAutoCompact)({ modelId: currentModel }).getStats();
548
1202
  console.log(HEADER('Current Model'));
@@ -571,12 +1225,25 @@ function handleModel(ctx, args) {
571
1225
  console.log(HEADER('Available Models'));
572
1226
  console.log(DIM('─'.repeat(40)));
573
1227
  const currentModel = ctx.llm?.getModel() || '';
574
- for (const m of AVAILABLE_MODELS) {
575
- const isCurrent = m.name === currentModel || m.alias === currentModel;
576
- const marker = isCurrent ? SUCCESS('●') : DIM('○');
577
- const contextInfo = (0, model_context_1.resolveModelContext)(m.name);
578
- console.log(` ${marker} ${ACCENT(m.name)} ${DIM(`(${m.alias})`)} ${DIM(`${formatTokenCount(contextInfo.contextWindow)} ctx`)} ${isCurrent ? BRAND('(current)') : ''}`);
579
- console.log(` ${DIM(m.provider)}`);
1228
+ const modelPicker = (0, ui_view_model_1.createModelPickerState)({
1229
+ currentModel,
1230
+ models: (0, model_catalog_1.listModelCatalogEntries)().map(model => {
1231
+ const contextInfo = (0, model_context_1.resolveModelContext)(model.name);
1232
+ return {
1233
+ ...model,
1234
+ contextWindow: contextInfo.contextWindow,
1235
+ maxOutputTokens: contextInfo.maxOutputTokens,
1236
+ source: contextInfo.source,
1237
+ };
1238
+ }),
1239
+ });
1240
+ for (const item of modelPicker.visibleItems) {
1241
+ const marker = item.isCurrent ? SUCCESS('●') : DIM('○');
1242
+ const alias = item.alias ? `(${item.alias})` : '';
1243
+ const context = `${formatTokenCount(item.contextWindow ?? 0)} ctx`;
1244
+ const current = item.isCurrent ? BRAND('(current)') : '';
1245
+ console.log(` ${marker} ${ACCENT(item.name)} ${DIM(alias)} ${DIM(context)} ${current}`);
1246
+ console.log(` ${DIM(item.provider ?? 'unknown')}`);
580
1247
  }
581
1248
  console.log();
582
1249
  console.log(DIM('Use /model <name|alias> to switch, e.g. /model sonnet'));
@@ -594,7 +1261,7 @@ function handleModel(ctx, args) {
594
1261
  console.log(` ${ACCENT('/model <name>')} Switch to specific model`);
595
1262
  console.log(` ${ACCENT('/model <alias>')} Switch using alias (opus, sonnet, haiku)`);
596
1263
  console.log();
597
- console.log(DIM('Aliases: opus, sonnet, haiku, gpt4o, qwen, glm'));
1264
+ console.log(DIM(`Aliases: ${formatModelAliasHelp()}`));
598
1265
  console.log();
599
1266
  return { success: true };
600
1267
  }
@@ -605,7 +1272,7 @@ function handleModel(ctx, args) {
605
1272
  return { success: false };
606
1273
  }
607
1274
  // 解析别名
608
- const resolvedModel = MODEL_ALIASES[trimmedArgs] || args.trim();
1275
+ const resolvedModel = (0, model_catalog_1.resolveModelAlias)(args);
609
1276
  ctx.llm.setModel(resolvedModel);
610
1277
  (0, auto_compact_1.getAutoCompact)({ modelId: resolvedModel });
611
1278
  ctx.store.setState({ currentModel: resolvedModel });
@@ -917,6 +1584,7 @@ async function handleChat(ctx, input) {
917
1584
  abortSignal: ctx.abortSignal,
918
1585
  harness,
919
1586
  input,
1587
+ loopBudget: (0, loop_budget_1.resolveRuntimeLoopBudget)(input, ctx.config, harness.toJSON()),
920
1588
  })) {
921
1589
  switch (event.type) {
922
1590
  case 'request_start':
@@ -1383,6 +2051,7 @@ async function handleCompact(ctx, args) {
1383
2051
  console.log();
1384
2052
  return { success: true };
1385
2053
  }
2054
+ console.log(DIM((0, agent_status_1.compactStatus)()));
1386
2055
  try {
1387
2056
  const autoCompact = (0, auto_compact_1.getAutoCompact)();
1388
2057
  autoCompact.configure({
@@ -1398,7 +2067,7 @@ async function handleCompact(ctx, args) {
1398
2067
  const percent = Math.round((reduction / history.length) * 100);
1399
2068
  const sessionId = ctx.getSession?.()?.id ?? ctx.sessionId;
1400
2069
  if (sessionId) {
1401
- (0, session_storage_1.markSessionTranscriptDisplayStart)(sessionId);
2070
+ (0, session_storage_1.persistSessionCompactHistory)(sessionId, compacted);
1402
2071
  }
1403
2072
  console.log(SUCCESS(`✔ Compacted ${history.length} → ${compacted.length} messages`));
1404
2073
  console.log(DIM(` Reduced by ${reduction} messages (${percent}%)`));
@@ -1676,16 +2345,32 @@ function restoreSession(ctx, session, isLast) {
1676
2345
  const history = (0, session_storage_1.loadSessionHistory)(resumed.id);
1677
2346
  if (history.length > 0) {
1678
2347
  const summary = generateHistorySummary(history);
2348
+ const eventSummary = generateRestoredSessionEventSummary(history);
1679
2349
  console.log(DIM(` Summary: ${summary}`));
1680
2350
  console.log();
1681
2351
  ctx.store.setState({ conversationHistory: history });
1682
2352
  ctx.store.setState({ harnessState: (0, session_storage_1.loadSessionHarnessState)(resumed.id) ?? resumed.harnessState });
1683
2353
  (0, framework_1.resetToolState)();
2354
+ ctx.sessionRestored?.({
2355
+ sessionId: resumed.id,
2356
+ projectPath: resumed.projectPath,
2357
+ model: resumed.model,
2358
+ restoredMessages: history.length,
2359
+ messageCount: resumed.messageCount,
2360
+ summary: eventSummary,
2361
+ });
1684
2362
  console.log(SUCCESS(`✔ Restored ${history.length} messages`));
1685
2363
  }
1686
2364
  else {
1687
2365
  console.log();
1688
2366
  console.log(DIM(' No messages in session'));
2367
+ ctx.sessionRestored?.({
2368
+ sessionId: resumed.id,
2369
+ projectPath: resumed.projectPath,
2370
+ model: resumed.model,
2371
+ restoredMessages: 0,
2372
+ messageCount: resumed.messageCount,
2373
+ });
1689
2374
  }
1690
2375
  console.log();
1691
2376
  return { success: true };
@@ -1809,13 +2494,26 @@ async function handleEditPreview(ctx) {
1809
2494
  return { success: parsed.success === true };
1810
2495
  }
1811
2496
  /** Generate a brief summary of conversation history */
2497
+ function truncateRedactedSummary(text, maxLength) {
2498
+ const redacted = (0, session_storage_1.redactTraceText)(text);
2499
+ if (redacted.length <= maxLength)
2500
+ return redacted;
2501
+ let truncated = redacted.slice(0, maxLength);
2502
+ for (const marker of ['[REDACTED_SECRET]']) {
2503
+ const markerStart = truncated.indexOf(marker.slice(0, 6));
2504
+ if (markerStart >= 0 && !truncated.includes(marker)) {
2505
+ truncated = `${truncated.slice(0, markerStart)}${marker}`;
2506
+ break;
2507
+ }
2508
+ }
2509
+ return `${truncated}...`;
2510
+ }
1812
2511
  function generateHistorySummary(messages) {
1813
2512
  const userMsgs = messages.filter(m => m.role === 'user' && m.content);
1814
2513
  const assistantMsgsWithTools = messages.filter(m => m.role === 'assistant' && m.tool_calls && m.tool_calls.length > 0);
1815
2514
  // Extract topics from first few user messages
1816
2515
  const topics = userMsgs.slice(0, 3).map(m => {
1817
- const content = m.content || '';
1818
- return content.length > 40 ? content.slice(0, 40) + '...' : content;
2516
+ return truncateRedactedSummary(m.content || '', 40);
1819
2517
  });
1820
2518
  // Extract tools used
1821
2519
  const toolsUsed = assistantMsgsWithTools.flatMap(m => m.tool_calls?.map(tc => tc.function.name) || []);
@@ -1833,6 +2531,14 @@ function generateHistorySummary(messages) {
1833
2531
  }
1834
2532
  return parts.join('. ');
1835
2533
  }
2534
+ function generateRestoredSessionEventSummary(messages) {
2535
+ const assistantMsgsWithTools = messages.filter(m => m.role === 'assistant' && m.tool_calls && m.tool_calls.length > 0);
2536
+ const toolsUsed = assistantMsgsWithTools.flatMap(m => m.tool_calls?.map(tc => tc.function.name) || []);
2537
+ const uniqueTools = [...new Set(toolsUsed)].slice(0, 8);
2538
+ if (uniqueTools.length === 0)
2539
+ return undefined;
2540
+ return `Tools: ${uniqueTools.join(', ')}`;
2541
+ }
1836
2542
  function continueAsSlashChat(name, args) {
1837
2543
  const trimmed = args.trim();
1838
2544
  return {
@@ -2115,6 +2821,53 @@ const COMMANDS = [
2115
2821
  type: 'builtin',
2116
2822
  execute: (ctx) => handleUsage(ctx),
2117
2823
  },
2824
+ {
2825
+ name: 'loop-stats',
2826
+ aliases: ['loop'],
2827
+ description: 'Show detailed agent-loop budget and efficiency diagnostics',
2828
+ category: 'diagnostics',
2829
+ priority: 12,
2830
+ type: 'builtin',
2831
+ execute: (ctx) => handleLoopStats(ctx),
2832
+ },
2833
+ {
2834
+ name: 'trace',
2835
+ description: 'Show structured event timeline for the latest or selected turn',
2836
+ argumentHint: '[latest|turn-id]',
2837
+ category: 'diagnostics',
2838
+ priority: 14,
2839
+ type: 'builtin',
2840
+ execute: (ctx, args) => handleTrace(ctx, args),
2841
+ },
2842
+ {
2843
+ name: 'last-tool',
2844
+ aliases: ['tool-last'],
2845
+ description: 'Show the latest tool call/result with full inspection hints',
2846
+ category: 'diagnostics',
2847
+ priority: 15,
2848
+ type: 'builtin',
2849
+ execute: (ctx, args) => handleLastTool(ctx, args),
2850
+ },
2851
+ {
2852
+ name: 'artifacts',
2853
+ aliases: ['artifact'],
2854
+ description: 'List or inspect saved full tool outputs for this project',
2855
+ argumentHint: '[show <id|prefix> --full]',
2856
+ category: 'diagnostics',
2857
+ priority: 16,
2858
+ type: 'builtin',
2859
+ execute: (ctx, args) => handleArtifacts(ctx, args),
2860
+ },
2861
+ {
2862
+ name: 'checkpoint',
2863
+ aliases: ['checkpoints'],
2864
+ description: 'List or restore file checkpoints created before agent edits',
2865
+ argumentHint: '[list|restore <turn-id|prefix> --yes]',
2866
+ category: 'diagnostics',
2867
+ priority: 18,
2868
+ type: 'builtin',
2869
+ execute: (ctx, args) => handleCheckpoint(ctx, args),
2870
+ },
2118
2871
  {
2119
2872
  name: 'cost',
2120
2873
  description: 'Show session token usage',