openhorse 0.2.11 → 0.2.13

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 (105) hide show
  1. package/dist/commands/index.d.ts.map +1 -1
  2. package/dist/commands/index.js +681 -6
  3. package/dist/commands/index.js.map +1 -1
  4. package/dist/commands/types.d.ts +3 -1
  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 +1 -0
  8. package/dist/core/checkpoint.d.ts.map +1 -1
  9. package/dist/core/checkpoint.js +59 -10
  10. package/dist/core/checkpoint.js.map +1 -1
  11. package/dist/core/tool-artifacts.d.ts +10 -0
  12. package/dist/core/tool-artifacts.d.ts.map +1 -1
  13. package/dist/core/tool-artifacts.js +81 -3
  14. package/dist/core/tool-artifacts.js.map +1 -1
  15. package/dist/framework/index.d.ts +2 -2
  16. package/dist/framework/index.d.ts.map +1 -1
  17. package/dist/framework/index.js +5 -1
  18. package/dist/framework/index.js.map +1 -1
  19. package/dist/framework/query.d.ts +104 -1
  20. package/dist/framework/query.d.ts.map +1 -1
  21. package/dist/framework/query.js +454 -18
  22. package/dist/framework/query.js.map +1 -1
  23. package/dist/framework/store.d.ts +5 -0
  24. package/dist/framework/store.d.ts.map +1 -1
  25. package/dist/framework/store.js +4 -0
  26. package/dist/framework/store.js.map +1 -1
  27. package/dist/framework/tool-scheduler.d.ts +9 -0
  28. package/dist/framework/tool-scheduler.d.ts.map +1 -1
  29. package/dist/framework/tool-scheduler.js +34 -0
  30. package/dist/framework/tool-scheduler.js.map +1 -1
  31. package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
  32. package/dist/ink-ui/components/StatusLine.js +7 -1
  33. package/dist/ink-ui/components/StatusLine.js.map +1 -1
  34. package/dist/runtime/agent-runtime-protocol.d.ts +13 -1
  35. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
  36. package/dist/runtime/agent-runtime-protocol.js +24 -0
  37. package/dist/runtime/agent-runtime-protocol.js.map +1 -1
  38. package/dist/runtime/chat-controller.d.ts +3 -0
  39. package/dist/runtime/chat-controller.d.ts.map +1 -1
  40. package/dist/runtime/chat-controller.js +930 -5
  41. package/dist/runtime/chat-controller.js.map +1 -1
  42. package/dist/runtime/loop-budget.d.ts +5 -0
  43. package/dist/runtime/loop-budget.d.ts.map +1 -0
  44. package/dist/runtime/loop-budget.js +77 -0
  45. package/dist/runtime/loop-budget.js.map +1 -0
  46. package/dist/runtime/ui-events.d.ts +39 -1
  47. package/dist/runtime/ui-events.d.ts.map +1 -1
  48. package/dist/runtime/ui-events.js.map +1 -1
  49. package/dist/services/config-dir.d.ts +2 -0
  50. package/dist/services/config-dir.d.ts.map +1 -1
  51. package/dist/services/config-dir.js +5 -0
  52. package/dist/services/config-dir.js.map +1 -1
  53. package/dist/services/config.d.ts +4 -2
  54. package/dist/services/config.d.ts.map +1 -1
  55. package/dist/services/config.js +19 -0
  56. package/dist/services/config.js.map +1 -1
  57. package/dist/services/doctor.d.ts.map +1 -1
  58. package/dist/services/doctor.js +34 -4
  59. package/dist/services/doctor.js.map +1 -1
  60. package/dist/services/global-config.d.ts +17 -0
  61. package/dist/services/global-config.d.ts.map +1 -1
  62. package/dist/services/global-config.js.map +1 -1
  63. package/dist/services/llm.d.ts +18 -0
  64. package/dist/services/llm.d.ts.map +1 -1
  65. package/dist/services/llm.js +175 -163
  66. package/dist/services/llm.js.map +1 -1
  67. package/dist/services/provider-diagnostics.d.ts +30 -0
  68. package/dist/services/provider-diagnostics.d.ts.map +1 -0
  69. package/dist/services/provider-diagnostics.js +308 -0
  70. package/dist/services/provider-diagnostics.js.map +1 -0
  71. package/dist/services/redaction.d.ts +2 -0
  72. package/dist/services/redaction.d.ts.map +1 -0
  73. package/dist/services/redaction.js +27 -0
  74. package/dist/services/redaction.js.map +1 -0
  75. package/dist/services/session-index.d.ts.map +1 -1
  76. package/dist/services/session-index.js +7 -3
  77. package/dist/services/session-index.js.map +1 -1
  78. package/dist/services/session-storage.d.ts +100 -0
  79. package/dist/services/session-storage.d.ts.map +1 -1
  80. package/dist/services/session-storage.js +154 -3
  81. package/dist/services/session-storage.js.map +1 -1
  82. package/dist/services/verification-profile.d.ts +36 -0
  83. package/dist/services/verification-profile.d.ts.map +1 -0
  84. package/dist/services/verification-profile.js +239 -0
  85. package/dist/services/verification-profile.js.map +1 -0
  86. package/dist/services/workspace-state.d.ts +25 -0
  87. package/dist/services/workspace-state.d.ts.map +1 -0
  88. package/dist/services/workspace-state.js +115 -0
  89. package/dist/services/workspace-state.js.map +1 -0
  90. package/dist/skills/loader.d.ts +5 -1
  91. package/dist/skills/loader.d.ts.map +1 -1
  92. package/dist/skills/loader.js +34 -0
  93. package/dist/skills/loader.js.map +1 -1
  94. package/dist/skills/registry.d.ts +5 -1
  95. package/dist/skills/registry.d.ts.map +1 -1
  96. package/dist/skills/registry.js +23 -0
  97. package/dist/skills/registry.js.map +1 -1
  98. package/dist/skills/runtime.d.ts +6 -0
  99. package/dist/skills/runtime.d.ts.map +1 -1
  100. package/dist/skills/runtime.js +88 -4
  101. package/dist/skills/runtime.js.map +1 -1
  102. package/dist/skills/types.d.ts +19 -0
  103. package/dist/skills/types.d.ts.map +1 -1
  104. package/dist/skills/types.js.map +1 -1
  105. package/package.json +1 -1
@@ -40,8 +40,11 @@ const doctor_1 = require("../services/doctor");
40
40
  const model_context_1 = require("../services/model-context");
41
41
  const workspace_diff_1 = require("../services/workspace-diff");
42
42
  const commit_plan_1 = require("../services/commit-plan");
43
+ const tool_artifacts_1 = require("../core/tool-artifacts");
44
+ const checkpoint_1 = require("../core/checkpoint");
43
45
  const storage_maintenance_1 = require("../services/storage-maintenance");
44
46
  const agent_status_1 = require("../runtime/agent-status");
47
+ const loop_budget_1 = require("../runtime/loop-budget");
45
48
  // ============================================================================
46
49
  // 颜色常量
47
50
  // ============================================================================
@@ -75,6 +78,112 @@ function formatTokenCount(tokens) {
75
78
  function formatThreshold(value) {
76
79
  return `${Math.round(value * 100)}%`;
77
80
  }
81
+ function formatDurationMs(ms) {
82
+ if (ms >= 1000)
83
+ return `${(ms / 1000).toFixed(1)}s`;
84
+ return `${Math.round(ms)}ms`;
85
+ }
86
+ function formatLoopBudgetSource(stats) {
87
+ const source = stats.loopBudgetSource ?? 'unknown';
88
+ if (source === 'config' && stats.loopBudgetBaseProfile) {
89
+ return `config over ${stats.loopBudgetBaseProfile}`;
90
+ }
91
+ return source;
92
+ }
93
+ function formatLoopStatsLines(stats, detail = false) {
94
+ const lines = [
95
+ `Finish ${stats.finishReason}`,
96
+ `Requests ${stats.llmRequests} LLM / ${stats.turnsStarted} turns`,
97
+ `Tools ${stats.toolCalls} total (${stats.readOnlyToolCalls} read-only, ${stats.unsafeToolCalls} unsafe)`,
98
+ `Tool bytes ${(0, format_1.formatBytes)(stats.modelVisibleToolBytes)} model-visible / ${(0, format_1.formatBytes)(stats.toolResultBytes)} total`,
99
+ ];
100
+ if (stats.summarizedBytes > 0) {
101
+ lines.push(`Saved ${(0, format_1.formatBytes)(stats.summarizedBytes)} from model context`);
102
+ }
103
+ if (stats.compactTrigger) {
104
+ lines.push(`Compact ${stats.compactTrigger}`);
105
+ }
106
+ if (stats.localFastPathUsed) {
107
+ lines.push('Fast path yes');
108
+ }
109
+ if (stats.budgetExceededReason) {
110
+ lines.push(`Budget ${stats.budgetExceededReason}`);
111
+ }
112
+ if (stats.continuationActions && stats.continuationActions.length > 0) {
113
+ lines.push(`Next ${stats.continuationActions.join(', ')}`);
114
+ }
115
+ if ((stats.providerRetryCount ?? 0) > 0) {
116
+ const retryParts = [
117
+ `${stats.providerRetryCount} retries`,
118
+ `delay ${formatDurationMs(stats.providerRetryDelayMs ?? 0)}`,
119
+ stats.providerLastRetryErrorType
120
+ ? `last ${stats.providerLastRetryErrorType}${stats.providerLastRetryStatus ? `/${stats.providerLastRetryStatus}` : ''}`
121
+ : undefined,
122
+ ].filter(Boolean);
123
+ lines.push(`Provider ${retryParts.join(', ')}`);
124
+ }
125
+ if ((stats.providerFallbackCount ?? 0) > 0 || stats.providerUsingFallback) {
126
+ const fallbackPath = stats.providerFallbackFromModel && stats.providerFallbackToModel
127
+ ? `${stats.providerFallbackFromModel} -> ${stats.providerFallbackToModel}`
128
+ : stats.providerFinalModel
129
+ ? `final ${stats.providerFinalModel}`
130
+ : 'active';
131
+ lines.push(`Fallback ${fallbackPath}`);
132
+ }
133
+ if (typeof stats.verificationClaimAllowed === 'boolean') {
134
+ const verificationParts = [
135
+ stats.verificationProfile ?? 'unknown',
136
+ `required=${stats.verificationRequired ? 'yes' : 'no'}`,
137
+ `passed=${stats.verificationPassedCommands?.length ?? 0}`,
138
+ `failed=${stats.verificationFailedCommands?.length ?? 0}`,
139
+ `missing=${stats.verificationMissingCommands?.length ?? 0}`,
140
+ `claim=${stats.verificationClaimAllowed ? 'yes' : 'no'}`,
141
+ ];
142
+ lines.push(`Verify ${verificationParts.join(' ')}`);
143
+ }
144
+ if (typeof stats.loopBudgetMaxLlmRequests === 'number'
145
+ || typeof stats.loopBudgetMaxToolCalls === 'number'
146
+ || typeof stats.loopBudgetMaxModelVisibleBytes === 'number') {
147
+ const caps = [
148
+ typeof stats.loopBudgetMaxLlmRequests === 'number'
149
+ ? `${stats.llmRequests}/${stats.loopBudgetMaxLlmRequests} LLM`
150
+ : undefined,
151
+ typeof stats.loopBudgetMaxToolCalls === 'number'
152
+ ? `${stats.toolCalls}/${stats.loopBudgetMaxToolCalls} tools`
153
+ : undefined,
154
+ typeof stats.loopBudgetMaxModelVisibleBytes === 'number'
155
+ ? `${(0, format_1.formatBytes)(stats.modelVisibleToolBytes)}/${(0, format_1.formatBytes)(stats.loopBudgetMaxModelVisibleBytes)} visible`
156
+ : undefined,
157
+ typeof stats.loopBudgetMaxReadOnlyFragmentation === 'number'
158
+ ? `fragment ${stats.singleReadOnlyStreak}/${stats.loopBudgetMaxReadOnlyFragmentation}`
159
+ : undefined,
160
+ ].filter(Boolean);
161
+ lines.push(`Budget cap ${caps.join(', ')} (${formatLoopBudgetSource(stats)})`);
162
+ }
163
+ if (stats.singleReadOnlyStreak > 0 || stats.batchReadSuggestionCount > 0) {
164
+ lines.push(`Read-only streak ${stats.singleReadOnlyStreak}, batch_read hints ${stats.batchReadSuggestionCount}`);
165
+ }
166
+ if (detail) {
167
+ lines.push(`Unsafe ${stats.unsafeToolCalls}`);
168
+ if (stats.providerRetryErrorTypes && stats.providerRetryErrorTypes.length > 0) {
169
+ lines.push(`Retry type ${stats.providerRetryErrorTypes.join(', ')}`);
170
+ }
171
+ if (stats.verificationFailedCommands && stats.verificationFailedCommands.length > 0) {
172
+ lines.push(`Failed ${stats.verificationFailedCommands.join(' && ')}`);
173
+ }
174
+ if (stats.verificationMissingCommands && stats.verificationMissingCommands.length > 0) {
175
+ lines.push(`Missing ${stats.verificationMissingCommands.join(' && ')}`);
176
+ }
177
+ if (stats.verificationSkippedReason) {
178
+ lines.push(`Verify why ${stats.verificationSkippedReason}`);
179
+ }
180
+ if (stats.continuationHint) {
181
+ lines.push(`Next why ${stats.continuationHint}`);
182
+ }
183
+ lines.push(`Budget hit ${stats.finishReason === 'budget_exceeded' ? 'yes' : 'no'}`);
184
+ }
185
+ return lines;
186
+ }
78
187
  const CATEGORY_LABELS = {
79
188
  workflow: 'Workflow',
80
189
  session: 'Session',
@@ -199,6 +308,14 @@ function showStatus(ctx) {
199
308
  console.log(` Prompt rules ${snapshot.projectInstructionsContent ? SUCCESS(`${snapshot.projectInstructionsContent.length} chars`) : DIM('none')}`);
200
309
  console.log(` Project memory ${snapshot.memoryContent ? SUCCESS(`${snapshot.memoryContent.length} chars`) : DIM('none')}`);
201
310
  console.log(` Skills index ${snapshot.skillsContent ? SUCCESS(`${snapshot.skillsContent.length} chars`) : DIM('none')}`);
311
+ if (snapshot.lastLoopStats) {
312
+ const stats = snapshot.lastLoopStats;
313
+ console.log();
314
+ console.log(` Last loop:`);
315
+ for (const line of formatLoopStatsLines(stats)) {
316
+ console.log(` ${line}`);
317
+ }
318
+ }
202
319
  const harnessState = snapshot.harnessState;
203
320
  if (harnessState?.contract || harnessState?.capsule) {
204
321
  console.log();
@@ -217,6 +334,413 @@ function showStatus(ctx) {
217
334
  console.log();
218
335
  return { success: true };
219
336
  }
337
+ function handleLoopStats(ctx) {
338
+ const stats = ctx.store.getSnapshot().lastLoopStats;
339
+ if (!stats) {
340
+ console.log('No agent-loop stats recorded yet.');
341
+ return { success: true };
342
+ }
343
+ console.log(HEADER('Agent Loop Stats'));
344
+ console.log();
345
+ for (const line of formatLoopStatsLines(stats, true)) {
346
+ console.log(` ${line}`);
347
+ }
348
+ console.log();
349
+ console.log(DIM('Use this to spot excessive LLM requests, fragmented read-only tool calls, and local fast-path hits.'));
350
+ return { success: true };
351
+ }
352
+ function formatTraceEventLine(event) {
353
+ const time = new Date(event.timestamp).toLocaleTimeString();
354
+ const prefix = `${DIM(time)} ${ACCENT(event.type)}`;
355
+ switch (event.type) {
356
+ case 'turn_start':
357
+ return `${prefix} input=${(0, format_1.formatBytes)(event.inputBytes ?? 0)}${event.localFastPathUsed ? ' fast-path' : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
358
+ case 'request_start':
359
+ return `${prefix} model=${event.model ?? 'unknown'} iteration=${event.turn ?? '?'}`;
360
+ case 'provider_retry': {
361
+ const parts = [
362
+ `count=${event.providerRetryCount ?? 0}`,
363
+ `delay=${formatDurationMs(event.providerRetryDelayMs ?? 0)}`,
364
+ event.providerLastRetryErrorType
365
+ ? `last=${event.providerLastRetryErrorType}${event.providerLastRetryStatus ? `/${event.providerLastRetryStatus}` : ''}`
366
+ : '',
367
+ event.providerRetryErrorTypes?.length ? `types=${event.providerRetryErrorTypes.join(',')}` : '',
368
+ event.providerFinalModel ? `final=${event.providerFinalModel}` : '',
369
+ ].filter(Boolean);
370
+ return `${prefix} ${parts.join(' ')}`;
371
+ }
372
+ case 'provider_fallback': {
373
+ const path = event.providerFallbackFromModel && event.providerFallbackToModel
374
+ ? `${event.providerFallbackFromModel}->${event.providerFallbackToModel}`
375
+ : event.providerFinalModel ?? 'active';
376
+ const parts = [
377
+ `count=${event.providerFallbackCount ?? 0}`,
378
+ `path=${path}`,
379
+ `using=${event.providerUsingFallback ? 'yes' : 'no'}`,
380
+ ];
381
+ return `${prefix} ${parts.join(' ')}`;
382
+ }
383
+ case 'prompt_assembly': {
384
+ const parts = [
385
+ `model=${event.promptModelId ?? 'unknown'}`,
386
+ `tokens=${event.promptEstimatedTokens ?? 0}/${event.promptBudgetTokens ?? 0}`,
387
+ `core=${event.promptCoreTokens ?? 0}`,
388
+ `evidenceBudget=${event.promptEvidenceBudgetTokens ?? 0}`,
389
+ `recentBudget=${event.promptRecentTurnBudgetTokens ?? 0}`,
390
+ `included=${event.promptIncludedEvidenceCount ?? 0}`,
391
+ `omitted=${event.promptOmittedEvidenceCount ?? 0}`,
392
+ ];
393
+ const sections = event.promptSections?.length
394
+ ? ` sections=${event.promptSections.join(',')}`
395
+ : '';
396
+ const evidence = event.promptIncludedEvidence?.length
397
+ ? ` evidence=${event.promptIncludedEvidence.slice(0, 6).join(', ')}${event.promptIncludedEvidence.length > 6 ? ', ...' : ''}`
398
+ : '';
399
+ return `${prefix} ${parts.join(' ')}${sections ? DIM(sections) : ''}${evidence ? ` ${DIM(evidence)}` : ''}`;
400
+ }
401
+ case 'assistant_tool_calls':
402
+ return `${prefix} calls=${event.toolCallCount ?? 0} assistant=${(0, format_1.formatBytes)(event.contentBytes ?? 0)}`;
403
+ case 'checkpoint': {
404
+ const files = event.checkpointFiles?.length
405
+ ? ` files=${event.checkpointFiles.slice(0, 6).join(', ')}${event.checkpointFiles.length > 6 ? ', ...' : ''}`
406
+ : '';
407
+ const targets = event.workspaceFiles?.length && !files
408
+ ? ` targets=${event.workspaceFiles.slice(0, 6).join(', ')}${event.workspaceFiles.length > 6 ? ', ...' : ''}`
409
+ : '';
410
+ return `${prefix} id=${event.checkpointId ?? 'unknown'} saved=${event.checkpointFileCount ?? 0}${files}${targets}${event.note ? ` ${DIM(event.note)}` : ''}`;
411
+ }
412
+ case 'tool_call':
413
+ 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)}` : ''}`;
414
+ case 'permission_decision': {
415
+ const status = event.permissionApproved ? SUCCESS('approved') : ERROR('denied');
416
+ const parts = [
417
+ `source=${event.permissionSource ?? 'unknown'}`,
418
+ `behavior=${event.permissionBehavior ?? 'unknown'}`,
419
+ ];
420
+ if (typeof event.permissionDuration === 'number') {
421
+ parts.push(`${event.permissionDuration}ms`);
422
+ }
423
+ return `${prefix} ${status} ${event.name ?? 'tool'} ${DIM(parts.join(' '))}${event.permissionReason ? ` ${DIM(event.permissionReason)}` : ''}`;
424
+ }
425
+ case 'tool_result': {
426
+ const status = event.success === false ? ERROR('error') : SUCCESS('ok');
427
+ const bytes = [
428
+ `output=${(0, format_1.formatBytes)(event.outputBytes ?? 0)}`,
429
+ `model=${(0, format_1.formatBytes)(event.modelVisibleBytes ?? 0)}`,
430
+ ];
431
+ if (event.artifactId)
432
+ bytes.push(`artifact=${event.artifactId}`);
433
+ return `${prefix} ${status} ${event.name ?? 'tool'} ${DIM(`${event.duration ?? 0}ms`)} ${DIM(bytes.join(' '))}${event.error ? ` ${ERROR(event.error)}` : ''}`;
434
+ }
435
+ case 'message':
436
+ return `${prefix} assistant=${(0, format_1.formatBytes)(event.contentBytes ?? 0)}`;
437
+ case 'strategy_exhausted':
438
+ return `${prefix}${event.note ? ` ${WARN(event.note)}` : ''}`;
439
+ case 'complete': {
440
+ const stats = [
441
+ `finish=${event.finishReason ?? 'unknown'}`,
442
+ `llm=${typeof event.llmRequests === 'number' ? event.llmRequests : 'unknown'}`,
443
+ `tools=${typeof event.toolCalls === 'number' ? event.toolCalls : 'unknown'}`,
444
+ ];
445
+ if (event.loopBudgetSource
446
+ || typeof event.loopBudgetMaxLlmRequests === 'number'
447
+ || typeof event.loopBudgetMaxToolCalls === 'number'
448
+ || typeof event.loopBudgetMaxModelVisibleBytes === 'number') {
449
+ const source = event.loopBudgetSource === 'config' && event.loopBudgetBaseProfile
450
+ ? `config/${event.loopBudgetBaseProfile}`
451
+ : event.loopBudgetSource ?? 'unknown';
452
+ const caps = [
453
+ typeof event.loopBudgetMaxLlmRequests === 'number'
454
+ ? `${typeof event.llmRequests === 'number' ? event.llmRequests : '?'}/${event.loopBudgetMaxLlmRequests}llm`
455
+ : undefined,
456
+ typeof event.loopBudgetMaxToolCalls === 'number'
457
+ ? `${typeof event.toolCalls === 'number' ? event.toolCalls : '?'}/${event.loopBudgetMaxToolCalls}tools`
458
+ : undefined,
459
+ typeof event.loopBudgetMaxModelVisibleBytes === 'number'
460
+ ? `${(0, format_1.formatBytes)(event.loopBudgetMaxModelVisibleBytes)}visible`
461
+ : undefined,
462
+ typeof event.loopBudgetMaxReadOnlyFragmentation === 'number'
463
+ ? `frag=${event.loopBudgetMaxReadOnlyFragmentation}`
464
+ : undefined,
465
+ event.loopBudgetConfigOverride ? 'override=yes' : undefined,
466
+ ].filter(Boolean);
467
+ stats.push(`budgetProfile=${source}${caps.length ? `(${caps.join(',')})` : ''}`);
468
+ }
469
+ if (event.budgetExceededReason)
470
+ stats.push(`budget=${event.budgetExceededReason}`);
471
+ if (event.continuationActions?.length)
472
+ stats.push(`next=${event.continuationActions.join(',')}`);
473
+ if (event.continuationHint)
474
+ stats.push(`hint=${event.continuationHint}`);
475
+ if (event.localFastPathUsed)
476
+ stats.push('fast-path=yes');
477
+ return `${prefix} ${stats.join(' ')}`;
478
+ }
479
+ case 'local_fast_path':
480
+ 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)}` : ''}`;
481
+ case 'workspace_snapshot': {
482
+ const state = event.workspaceGitAvailable === false
483
+ ? WARN('not-git')
484
+ : event.workspaceDirty ? WARN('dirty') : SUCCESS('clean');
485
+ const files = event.workspaceFiles?.length
486
+ ? ` files=${event.workspaceFiles.slice(0, 6).join(', ')}${event.workspaceFiles.length > 6 ? ', ...' : ''}`
487
+ : '';
488
+ return `${prefix} ${event.workspacePhase ?? 'unknown'} ${state} count=${event.workspaceFileCount ?? 0}${event.workspaceBranch ? ` branch=${event.workspaceBranch}` : ''}${files}${event.error ? ` ${ERROR(event.error)}` : ''}`;
489
+ }
490
+ case 'workspace_delta': {
491
+ const added = event.workspaceNewByTurn ?? [];
492
+ const changed = event.workspaceChangedByTurn ?? [];
493
+ const modifiedPreExisting = event.workspaceModifiedPreExistingByTurn ?? [];
494
+ const resolved = event.workspaceResolvedByTurn ?? [];
495
+ const parts = [
496
+ `after=${event.workspaceFileCount ?? 0}`,
497
+ `new=${added.length}`,
498
+ `changed=${changed.length}`,
499
+ `pre-existing-modified=${modifiedPreExisting.length}`,
500
+ `resolved=${resolved.length}`,
501
+ ];
502
+ const details = [
503
+ added.length ? `new: ${added.slice(0, 6).join(', ')}${added.length > 6 ? ', ...' : ''}` : '',
504
+ changed.length ? `changed: ${changed.slice(0, 6).join(', ')}${changed.length > 6 ? ', ...' : ''}` : '',
505
+ modifiedPreExisting.length ? `pre-existing modified: ${modifiedPreExisting.slice(0, 6).join(', ')}${modifiedPreExisting.length > 6 ? ', ...' : ''}` : '',
506
+ resolved.length ? `resolved: ${resolved.slice(0, 6).join(', ')}${resolved.length > 6 ? ', ...' : ''}` : '',
507
+ ].filter(Boolean).join(' | ');
508
+ return `${prefix} ${parts.join(' ')}${details ? ` ${DIM(details)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
509
+ }
510
+ case 'verification_profile': {
511
+ const commands = event.verificationCommands ?? [];
512
+ const files = event.verificationChangedFiles ?? [];
513
+ const details = [
514
+ `profile=${event.verificationProfile ?? 'unknown'}`,
515
+ `required=${event.verificationRequired === false ? 'no' : 'yes'}`,
516
+ `commands=${commands.length}`,
517
+ `files=${files.length}`,
518
+ ];
519
+ const commandPreview = commands.length
520
+ ? ` cmds: ${commands.slice(0, 4).join(' && ')}${commands.length > 4 ? ' && ...' : ''}`
521
+ : '';
522
+ return `${prefix} ${details.join(' ')}${commandPreview ? ` ${DIM(commandPreview)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
523
+ }
524
+ case 'verification_result': {
525
+ const status = event.verificationPassed === true ? SUCCESS('passed') : ERROR('failed');
526
+ const command = event.verificationCommand ?? 'unknown';
527
+ return `${prefix} ${status} ${command}${typeof event.outputBytes === 'number' ? ` ${DIM((0, format_1.formatBytes)(event.outputBytes))}` : ''}${event.error ? ` ${ERROR(event.error)}` : ''}`;
528
+ }
529
+ case 'verification_summary': {
530
+ const passed = event.verificationPassedCommands?.length ?? 0;
531
+ const failed = event.verificationFailedCommands?.length ?? 0;
532
+ const missing = event.verificationMissingCommands?.length ?? 0;
533
+ const parts = [
534
+ `profile=${event.verificationProfile ?? 'unknown'}`,
535
+ `required=${event.verificationRequired === false ? 'no' : 'yes'}`,
536
+ `passed=${passed}`,
537
+ `failed=${failed}`,
538
+ `missing=${missing}`,
539
+ `claimAllowed=${event.verificationClaimAllowed ? 'yes' : 'no'}`,
540
+ ];
541
+ const missingPreview = missing > 0
542
+ ? ` missing: ${(event.verificationMissingCommands ?? []).slice(0, 4).join(' && ')}${missing > 4 ? ' && ...' : ''}`
543
+ : '';
544
+ return `${prefix} ${parts.join(' ')}${missingPreview ? ` ${DIM(missingPreview)}` : ''}${event.note ? ` ${DIM(event.note)}` : ''}`;
545
+ }
546
+ case 'aborted':
547
+ return `${prefix}${event.note ? ` ${WARN(event.note)}` : ''}`;
548
+ case 'error':
549
+ return `${prefix} ${ERROR(event.error ?? 'unknown error')}`;
550
+ default:
551
+ return prefix;
552
+ }
553
+ }
554
+ function handleTrace(ctx, args = '') {
555
+ const session = ctx.getSession?.() ?? (ctx.sessionId ? (0, session_storage_1.loadSessionMeta)(ctx.sessionId) : null);
556
+ if (!session) {
557
+ console.log(ERROR('No active session.'));
558
+ console.log(DIM('Use /resume <session-id> first, or start a chat turn to create a session.'));
559
+ return { success: false };
560
+ }
561
+ const events = (0, session_storage_1.readSessionTraceEvents)(session.id);
562
+ if (events.length === 0) {
563
+ console.log(DIM(`No trace events recorded for session ${session.id.slice(0, 8)} yet.`));
564
+ return { success: true };
565
+ }
566
+ const ref = args.trim();
567
+ const requestedTurnId = ref && ref !== 'latest' ? ref : events[events.length - 1].turnId;
568
+ const turnEvents = events.filter(event => event.turnId === requestedTurnId);
569
+ if (turnEvents.length === 0) {
570
+ const recentTurnIds = [...new Set(events.map(event => event.turnId))].slice(-8).reverse();
571
+ console.log(ERROR(`Trace turn not found: ${requestedTurnId}`));
572
+ console.log(DIM(`Recent turns: ${recentTurnIds.join(', ')}`));
573
+ return { success: false };
574
+ }
575
+ console.log(HEADER(`Trace ${requestedTurnId}`));
576
+ console.log(DIM(`Session ${session.id} Events ${turnEvents.length}`));
577
+ console.log(DIM('─'.repeat(40)));
578
+ for (const event of turnEvents) {
579
+ console.log(` ${formatTraceEventLine(event)}`);
580
+ }
581
+ console.log();
582
+ console.log(DIM('Trace stores metadata only; full transcript and tool output stay in session/artifacts.'));
583
+ return { success: true };
584
+ }
585
+ function formatDateTime(timestamp) {
586
+ return new Date(timestamp).toLocaleString();
587
+ }
588
+ function parseArtifactArgs(args) {
589
+ const parts = args.trim().split(/\s+/).filter(Boolean);
590
+ let full = false;
591
+ let limit = 20;
592
+ const positional = [];
593
+ for (let i = 0; i < parts.length; i++) {
594
+ const part = parts[i];
595
+ if (part === '--full') {
596
+ full = true;
597
+ continue;
598
+ }
599
+ if ((part === '--limit' || part === '-n') && parts[i + 1]) {
600
+ const parsed = Number(parts[i + 1]);
601
+ if (Number.isInteger(parsed) && parsed > 0) {
602
+ limit = Math.min(parsed, 200);
603
+ }
604
+ i++;
605
+ continue;
606
+ }
607
+ positional.push(part);
608
+ }
609
+ if (positional[0] === 'show' || positional[0] === 'cat') {
610
+ return { action: 'show', ref: positional[1], full, limit };
611
+ }
612
+ if (positional[0]) {
613
+ return { action: 'show', ref: positional[0], full, limit };
614
+ }
615
+ return { action: 'list', full, limit };
616
+ }
617
+ function printArtifactPreview(content, full) {
618
+ const maxPreviewBytes = 16 * 1024;
619
+ if (full || Buffer.byteLength(content, 'utf8') <= maxPreviewBytes) {
620
+ console.log(content);
621
+ return;
622
+ }
623
+ const preview = Buffer.from(content, 'utf8').subarray(0, maxPreviewBytes).toString('utf8');
624
+ console.log(preview);
625
+ console.log();
626
+ console.log(DIM(`... preview truncated at ${(0, format_1.formatBytes)(maxPreviewBytes)}. Use /artifacts show <id> --full for full output.`));
627
+ }
628
+ function formatArtifactPathForDisplay(artifactPath) {
629
+ return (0, session_storage_1.redactTraceText)(artifactPath);
630
+ }
631
+ function handleArtifacts(ctx, args = '') {
632
+ const parsed = parseArtifactArgs(args);
633
+ if (parsed.action === 'list') {
634
+ const artifacts = (0, tool_artifacts_1.listArtifacts)(ctx.cwd, parsed.limit);
635
+ console.log(HEADER('Artifacts'));
636
+ console.log(DIM('─'.repeat(40)));
637
+ if (artifacts.length === 0) {
638
+ console.log(DIM('No artifacts found for this project.'));
639
+ return { success: true };
640
+ }
641
+ for (const artifact of artifacts) {
642
+ console.log(`${ACCENT(artifact.id)} ${DIM(artifact.toolName)} ${(0, format_1.formatBytes)(artifact.outputBytes)}`);
643
+ console.log(` ${DIM(formatDateTime(artifact.modifiedAt))}`);
644
+ console.log(` ${DIM(formatArtifactPathForDisplay(artifact.path))}`);
645
+ }
646
+ console.log();
647
+ console.log(DIM('Use /artifacts show <id|prefix> to preview, or add --full for full output.'));
648
+ return { success: true };
649
+ }
650
+ if (!parsed.ref) {
651
+ console.log(ERROR('Usage: /artifacts show <id|prefix> [--full]'));
652
+ return { success: false };
653
+ }
654
+ const artifact = (0, tool_artifacts_1.findArtifact)(ctx.cwd, parsed.ref);
655
+ if (!artifact) {
656
+ console.log(ERROR(`Artifact not found or ambiguous: ${parsed.ref}`));
657
+ console.log(DIM('Run /artifacts to list available artifact ids.'));
658
+ return { success: false };
659
+ }
660
+ const content = (0, tool_artifacts_1.retrieveArtifact)(artifact.path);
661
+ if (content == null) {
662
+ console.log(ERROR(`Artifact exists but cannot be read: ${artifact.id}`));
663
+ console.log(DIM(formatArtifactPathForDisplay(artifact.path)));
664
+ return { success: false };
665
+ }
666
+ console.log(HEADER(`Artifact ${artifact.id}`));
667
+ console.log(DIM(`Tool ${artifact.toolName} Size ${(0, format_1.formatBytes)(artifact.outputBytes)} Modified ${formatDateTime(artifact.modifiedAt)}`));
668
+ console.log(DIM(formatArtifactPathForDisplay(artifact.path)));
669
+ console.log(DIM('─'.repeat(40)));
670
+ printArtifactPreview(content, parsed.full);
671
+ return { success: true };
672
+ }
673
+ function findCheckpointByRef(ctx, ref) {
674
+ const checkpoints = (0, checkpoint_1.listCheckpoints)(ctx.cwd);
675
+ const exact = checkpoints.find(checkpoint => checkpoint.turnId === ref);
676
+ if (exact)
677
+ return { checkpoint: exact, ambiguous: false };
678
+ const matches = checkpoints.filter(checkpoint => checkpoint.turnId.startsWith(ref));
679
+ return {
680
+ checkpoint: matches.length === 1 ? matches[0] : undefined,
681
+ ambiguous: matches.length > 1,
682
+ };
683
+ }
684
+ function handleCheckpoint(ctx, args = '') {
685
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
686
+ const action = tokens[0] ?? 'list';
687
+ if (action === 'list') {
688
+ const checkpoints = (0, checkpoint_1.listCheckpoints)(ctx.cwd);
689
+ console.log(HEADER('Checkpoints'));
690
+ console.log(DIM('─'.repeat(40)));
691
+ if (checkpoints.length === 0) {
692
+ console.log(DIM('No checkpoints found for this project.'));
693
+ return { success: true };
694
+ }
695
+ for (const checkpoint of checkpoints.slice(0, 20)) {
696
+ console.log(`${ACCENT(checkpoint.turnId)} ${DIM(formatDateTime(checkpoint.createdAt))}`);
697
+ const files = checkpoint.files.map(file => file.path).slice(0, 8).join(', ');
698
+ console.log(` ${DIM(`${checkpoint.files.length} file(s)${files ? `: ${files}` : ''}`)}`);
699
+ }
700
+ console.log();
701
+ console.log(DIM('Use /checkpoint restore <turn-id|prefix> to preview, then add --yes to restore.'));
702
+ return { success: true };
703
+ }
704
+ if (action !== 'restore') {
705
+ console.log(ERROR('Usage: /checkpoint [list|restore <turn-id|prefix> [--yes]]'));
706
+ return { success: false };
707
+ }
708
+ const ref = tokens.find(token => token !== 'restore' && token !== '--yes');
709
+ const confirmed = tokens.includes('--yes');
710
+ if (!ref) {
711
+ console.log(ERROR('Usage: /checkpoint restore <turn-id|prefix> [--yes]'));
712
+ return { success: false };
713
+ }
714
+ const { checkpoint, ambiguous } = findCheckpointByRef(ctx, ref);
715
+ if (!checkpoint) {
716
+ console.log(ERROR(ambiguous
717
+ ? `Checkpoint prefix is ambiguous: ${ref}`
718
+ : `Checkpoint not found: ${ref}`));
719
+ console.log(DIM('Run /checkpoint to list available checkpoint ids.'));
720
+ return { success: false };
721
+ }
722
+ if (!confirmed) {
723
+ console.log(HEADER(`Checkpoint ${checkpoint.turnId}`));
724
+ console.log(DIM(`${formatDateTime(checkpoint.createdAt)} ${checkpoint.files.length} file(s)`));
725
+ for (const file of checkpoint.files.slice(0, 20)) {
726
+ console.log(` ${file.path} ${DIM((0, format_1.formatBytes)(file.sizeBytes))}`);
727
+ }
728
+ console.log();
729
+ console.log(WARN(`This will overwrite current files from checkpoint ${checkpoint.turnId}.`));
730
+ console.log(DIM(`Run /checkpoint restore ${checkpoint.turnId} --yes to restore.`));
731
+ return { success: true };
732
+ }
733
+ const result = (0, checkpoint_1.restoreCheckpoint)(ctx.cwd, checkpoint.turnId);
734
+ if (result.error) {
735
+ console.log(ERROR(result.error));
736
+ return { success: false, error: result.error };
737
+ }
738
+ console.log(SUCCESS(`Restored ${result.restored.length} file(s) from checkpoint ${checkpoint.turnId}.`));
739
+ for (const file of result.restored.slice(0, 20)) {
740
+ console.log(` ${file}`);
741
+ }
742
+ return { success: true, output: result.restored.join('\n') };
743
+ }
220
744
  function snapshotCurrentModel(ctx) {
221
745
  return ctx.store.getSnapshot().currentModel || ctx.config.model;
222
746
  }
@@ -902,6 +1426,7 @@ async function handleChat(ctx, input) {
902
1426
  abortSignal: ctx.abortSignal,
903
1427
  harness,
904
1428
  input,
1429
+ loopBudget: (0, loop_budget_1.resolveRuntimeLoopBudget)(input, ctx.config, harness.toJSON()),
905
1430
  })) {
906
1431
  switch (event.type) {
907
1432
  case 'request_start':
@@ -952,6 +1477,7 @@ async function handleChat(ctx, input) {
952
1477
  sessionMessagesToRecord.push({
953
1478
  role: 'tool',
954
1479
  content: event.result,
1480
+ modelVisibleContent: event.modelVisibleResult,
955
1481
  timestamp: Date.now(),
956
1482
  toolCallId: event.callId,
957
1483
  });
@@ -972,6 +1498,9 @@ async function handleChat(ctx, input) {
972
1498
  finalContent = event.content;
973
1499
  finalModel = event.model;
974
1500
  finalUsage = event.usage;
1501
+ if (event.stats) {
1502
+ ctx.store.setLastLoopStats(event.stats);
1503
+ }
975
1504
  break;
976
1505
  }
977
1506
  }
@@ -1102,12 +1631,11 @@ function handleCost(ctx) {
1102
1631
  return { success: true };
1103
1632
  }
1104
1633
  function handleSkills(ctx) {
1105
- const { getSkillsRegistry } = require('../skills');
1106
1634
  console.log();
1107
1635
  console.log(HEADER('Loaded Skills'));
1108
1636
  console.log(DIM('─'.repeat(40)));
1109
1637
  try {
1110
- const registry = getSkillsRegistry();
1638
+ const registry = (0, skills_1.getSkillsRegistry)();
1111
1639
  const summary = registry.getSummary();
1112
1640
  if (summary.count === 0) {
1113
1641
  console.log();
@@ -1120,10 +1648,18 @@ function handleSkills(ctx) {
1120
1648
  console.log(` Total ${SUCCESS(summary.count)} skills (${WARN(summary.autoCount)} auto-trigger)`);
1121
1649
  console.log();
1122
1650
  for (const skill of registry.getAllSkills()) {
1123
- const source = registry['loader']?.getSource(skill.name);
1651
+ const source = registry.getSource(skill.name);
1124
1652
  const sourceType = source?.type || 'unknown';
1653
+ const resourceRoot = skill.resourceRoot || skill.source || source?.path;
1654
+ const skillFile = resourceRoot
1655
+ ? `${resourceRoot.replace(/\/SKILL\.md$/i, '')}/SKILL.md`
1656
+ : source?.skillFile;
1125
1657
  console.log(` ${ACCENT(skill.name)} ${DIM(`(${sourceType})`)}`);
1126
1658
  console.log(` ${DIM(skill.description || '(no description)')}`);
1659
+ if (skillFile)
1660
+ console.log(` ${DIM(`SKILL.md ${skillFile}`)}`);
1661
+ if (resourceRoot)
1662
+ console.log(` ${DIM(`Root ${resourceRoot}`)}`);
1127
1663
  }
1128
1664
  console.log();
1129
1665
  }
@@ -1133,6 +1669,60 @@ function handleSkills(ctx) {
1133
1669
  }
1134
1670
  return { success: true };
1135
1671
  }
1672
+ function handleSkill(args) {
1673
+ const trimmed = args.trim();
1674
+ const registry = (0, skills_1.getSkillsRegistry)();
1675
+ registry.initialize();
1676
+ if (!trimmed) {
1677
+ const names = registry.getAllSkills().map(skill => skill.name).sort();
1678
+ return {
1679
+ success: false,
1680
+ error: [
1681
+ 'Usage: /skill <name> <task>',
1682
+ `Loaded skills: ${names.join(', ') || 'none'}`,
1683
+ ].join('\n'),
1684
+ };
1685
+ }
1686
+ const [rawName, ...rest] = trimmed.split(/\s+/);
1687
+ const requestedName = (0, skills_1.normalizeRequestedSkillName)(rawName);
1688
+ const skill = registry.getAllSkills()
1689
+ .find(candidate => (0, skills_1.skillActivationNames)(candidate)
1690
+ .some(name => (0, skills_1.normalizeRequestedSkillName)(name) === requestedName));
1691
+ if (!skill) {
1692
+ const suggestions = registry.getAllSkills()
1693
+ .map(candidate => candidate.name)
1694
+ .filter(name => name.includes(requestedName) || requestedName.includes(name))
1695
+ .slice(0, 5);
1696
+ return {
1697
+ success: false,
1698
+ error: suggestions.length > 0
1699
+ ? `Unknown skill: ${rawName}\nDid you mean: ${suggestions.join(', ')}?`
1700
+ : `Unknown skill: ${rawName}`,
1701
+ };
1702
+ }
1703
+ const task = rest.join(' ').trim();
1704
+ const source = registry.getSource(skill.name);
1705
+ const resourceRoot = skill.resourceRoot || skill.source || source?.path;
1706
+ const skillFile = resourceRoot
1707
+ ? `${resourceRoot.replace(/\/SKILL\.md$/i, '')}/SKILL.md`
1708
+ : source?.skillFile;
1709
+ if (!task) {
1710
+ return {
1711
+ success: true,
1712
+ output: [
1713
+ `Skill ${skill.name} is loaded.`,
1714
+ skillFile ? `SKILL.md ${skillFile}` : '',
1715
+ resourceRoot ? `Root ${resourceRoot}` : '',
1716
+ `Use: /skill ${skill.name} <task>`,
1717
+ ].filter(Boolean).join('\n'),
1718
+ };
1719
+ }
1720
+ return {
1721
+ success: true,
1722
+ continueAsChat: true,
1723
+ chatInput: `/skill ${skill.name} ${task}`,
1724
+ };
1725
+ }
1136
1726
  function handleMcp(ctx) {
1137
1727
  console.log();
1138
1728
  console.log(HEADER('MCP Servers'));
@@ -1318,7 +1908,7 @@ async function handleCompact(ctx, args) {
1318
1908
  const percent = Math.round((reduction / history.length) * 100);
1319
1909
  const sessionId = ctx.getSession?.()?.id ?? ctx.sessionId;
1320
1910
  if (sessionId) {
1321
- (0, session_storage_1.markSessionTranscriptDisplayStart)(sessionId);
1911
+ (0, session_storage_1.persistSessionCompactHistory)(sessionId, compacted);
1322
1912
  }
1323
1913
  console.log(SUCCESS(`✔ Compacted ${history.length} → ${compacted.length} messages`));
1324
1914
  console.log(DIM(` Reduced by ${reduction} messages (${percent}%)`));
@@ -1596,16 +2186,32 @@ function restoreSession(ctx, session, isLast) {
1596
2186
  const history = (0, session_storage_1.loadSessionHistory)(resumed.id);
1597
2187
  if (history.length > 0) {
1598
2188
  const summary = generateHistorySummary(history);
2189
+ const eventSummary = generateRestoredSessionEventSummary(history);
1599
2190
  console.log(DIM(` Summary: ${summary}`));
1600
2191
  console.log();
1601
2192
  ctx.store.setState({ conversationHistory: history });
1602
2193
  ctx.store.setState({ harnessState: (0, session_storage_1.loadSessionHarnessState)(resumed.id) ?? resumed.harnessState });
1603
2194
  (0, framework_1.resetToolState)();
2195
+ ctx.sessionRestored?.({
2196
+ sessionId: resumed.id,
2197
+ projectPath: resumed.projectPath,
2198
+ model: resumed.model,
2199
+ restoredMessages: history.length,
2200
+ messageCount: resumed.messageCount,
2201
+ summary: eventSummary,
2202
+ });
1604
2203
  console.log(SUCCESS(`✔ Restored ${history.length} messages`));
1605
2204
  }
1606
2205
  else {
1607
2206
  console.log();
1608
2207
  console.log(DIM(' No messages in session'));
2208
+ ctx.sessionRestored?.({
2209
+ sessionId: resumed.id,
2210
+ projectPath: resumed.projectPath,
2211
+ model: resumed.model,
2212
+ restoredMessages: 0,
2213
+ messageCount: resumed.messageCount,
2214
+ });
1609
2215
  }
1610
2216
  console.log();
1611
2217
  return { success: true };
@@ -1729,13 +2335,26 @@ async function handleEditPreview(ctx) {
1729
2335
  return { success: parsed.success === true };
1730
2336
  }
1731
2337
  /** Generate a brief summary of conversation history */
2338
+ function truncateRedactedSummary(text, maxLength) {
2339
+ const redacted = (0, session_storage_1.redactTraceText)(text);
2340
+ if (redacted.length <= maxLength)
2341
+ return redacted;
2342
+ let truncated = redacted.slice(0, maxLength);
2343
+ for (const marker of ['[REDACTED_SECRET]']) {
2344
+ const markerStart = truncated.indexOf(marker.slice(0, 6));
2345
+ if (markerStart >= 0 && !truncated.includes(marker)) {
2346
+ truncated = `${truncated.slice(0, markerStart)}${marker}`;
2347
+ break;
2348
+ }
2349
+ }
2350
+ return `${truncated}...`;
2351
+ }
1732
2352
  function generateHistorySummary(messages) {
1733
2353
  const userMsgs = messages.filter(m => m.role === 'user' && m.content);
1734
2354
  const assistantMsgsWithTools = messages.filter(m => m.role === 'assistant' && m.tool_calls && m.tool_calls.length > 0);
1735
2355
  // Extract topics from first few user messages
1736
2356
  const topics = userMsgs.slice(0, 3).map(m => {
1737
- const content = m.content || '';
1738
- return content.length > 40 ? content.slice(0, 40) + '...' : content;
2357
+ return truncateRedactedSummary(m.content || '', 40);
1739
2358
  });
1740
2359
  // Extract tools used
1741
2360
  const toolsUsed = assistantMsgsWithTools.flatMap(m => m.tool_calls?.map(tc => tc.function.name) || []);
@@ -1753,6 +2372,14 @@ function generateHistorySummary(messages) {
1753
2372
  }
1754
2373
  return parts.join('. ');
1755
2374
  }
2375
+ function generateRestoredSessionEventSummary(messages) {
2376
+ const assistantMsgsWithTools = messages.filter(m => m.role === 'assistant' && m.tool_calls && m.tool_calls.length > 0);
2377
+ const toolsUsed = assistantMsgsWithTools.flatMap(m => m.tool_calls?.map(tc => tc.function.name) || []);
2378
+ const uniqueTools = [...new Set(toolsUsed)].slice(0, 8);
2379
+ if (uniqueTools.length === 0)
2380
+ return undefined;
2381
+ return `Tools: ${uniqueTools.join(', ')}`;
2382
+ }
1756
2383
  function continueAsSlashChat(name, args) {
1757
2384
  const trimmed = args.trim();
1758
2385
  return {
@@ -1887,6 +2514,16 @@ const COMMANDS = [
1887
2514
  type: 'builtin',
1888
2515
  execute: (ctx) => handleSkills(ctx),
1889
2516
  },
2517
+ {
2518
+ name: 'skill',
2519
+ aliases: ['use-skill', 'activate-skill'],
2520
+ description: 'Activate a loaded skill for one chat turn',
2521
+ argumentHint: '<name> <task>',
2522
+ category: 'context',
2523
+ priority: 21,
2524
+ type: 'chat',
2525
+ execute: (_ctx, args) => handleSkill(args),
2526
+ },
1890
2527
  {
1891
2528
  name: 'memory',
1892
2529
  description: 'Show memory status, or `/memory reindex` to rebuild semantic index',
@@ -2025,6 +2662,44 @@ const COMMANDS = [
2025
2662
  type: 'builtin',
2026
2663
  execute: (ctx) => handleUsage(ctx),
2027
2664
  },
2665
+ {
2666
+ name: 'loop-stats',
2667
+ aliases: ['loop'],
2668
+ description: 'Show detailed agent-loop budget and efficiency diagnostics',
2669
+ category: 'diagnostics',
2670
+ priority: 12,
2671
+ type: 'builtin',
2672
+ execute: (ctx) => handleLoopStats(ctx),
2673
+ },
2674
+ {
2675
+ name: 'trace',
2676
+ description: 'Show structured event timeline for the latest or selected turn',
2677
+ argumentHint: '[latest|turn-id]',
2678
+ category: 'diagnostics',
2679
+ priority: 14,
2680
+ type: 'builtin',
2681
+ execute: (ctx, args) => handleTrace(ctx, args),
2682
+ },
2683
+ {
2684
+ name: 'artifacts',
2685
+ aliases: ['artifact'],
2686
+ description: 'List or inspect saved full tool outputs for this project',
2687
+ argumentHint: '[show <id|prefix> --full]',
2688
+ category: 'diagnostics',
2689
+ priority: 16,
2690
+ type: 'builtin',
2691
+ execute: (ctx, args) => handleArtifacts(ctx, args),
2692
+ },
2693
+ {
2694
+ name: 'checkpoint',
2695
+ aliases: ['checkpoints'],
2696
+ description: 'List or restore file checkpoints created before agent edits',
2697
+ argumentHint: '[list|restore <turn-id|prefix> --yes]',
2698
+ category: 'diagnostics',
2699
+ priority: 18,
2700
+ type: 'builtin',
2701
+ execute: (ctx, args) => handleCheckpoint(ctx, args),
2702
+ },
2028
2703
  {
2029
2704
  name: 'cost',
2030
2705
  description: 'Show session token usage',