@code-yeongyu/senpi 2026.5.16 → 2026.5.18

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 (94) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/cli/config-selector.d.ts.map +1 -1
  3. package/dist/cli/config-selector.js +1 -1
  4. package/dist/cli/config-selector.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +5 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +12 -3
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/agent-session.d.ts +2 -0
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +47 -6
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/compaction/compaction.d.ts +5 -3
  16. package/dist/core/compaction/compaction.d.ts.map +1 -1
  17. package/dist/core/compaction/compaction.js +22 -14
  18. package/dist/core/compaction/compaction.js.map +1 -1
  19. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js +5 -128
  21. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js.map +1 -1
  22. package/dist/core/model-registry.d.ts +1 -0
  23. package/dist/core/model-registry.d.ts.map +1 -1
  24. package/dist/core/model-registry.js +64 -9
  25. package/dist/core/model-registry.js.map +1 -1
  26. package/dist/core/package-manager.d.ts +5 -0
  27. package/dist/core/package-manager.d.ts.map +1 -1
  28. package/dist/core/package-manager.js +72 -31
  29. package/dist/core/package-manager.js.map +1 -1
  30. package/dist/core/prompt-templates.d.ts.map +1 -1
  31. package/dist/core/prompt-templates.js +6 -4
  32. package/dist/core/prompt-templates.js.map +1 -1
  33. package/dist/core/session-manager.d.ts.map +1 -1
  34. package/dist/core/session-manager.js +38 -8
  35. package/dist/core/session-manager.js.map +1 -1
  36. package/dist/core/skills.d.ts.map +1 -1
  37. package/dist/core/skills.js +2 -5
  38. package/dist/core/skills.js.map +1 -1
  39. package/dist/core/system-prompt.d.ts.map +1 -1
  40. package/dist/core/system-prompt.js +3 -2
  41. package/dist/core/system-prompt.js.map +1 -1
  42. package/dist/core/tools/diff-render.d.ts +13 -0
  43. package/dist/core/tools/diff-render.d.ts.map +1 -0
  44. package/dist/core/tools/diff-render.js +130 -0
  45. package/dist/core/tools/diff-render.js.map +1 -0
  46. package/dist/core/tools/edit.d.ts.map +1 -1
  47. package/dist/core/tools/edit.js +8 -3
  48. package/dist/core/tools/edit.js.map +1 -1
  49. package/dist/core/tools/write.d.ts.map +1 -1
  50. package/dist/core/tools/write.js +28 -7
  51. package/dist/core/tools/write.js.map +1 -1
  52. package/dist/modes/interactive/components/config-selector.d.ts +2 -2
  53. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  54. package/dist/modes/interactive/components/config-selector.js +7 -4
  55. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  56. package/dist/modes/interactive/components/footer.d.ts +0 -1
  57. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/footer.js +42 -44
  59. package/dist/modes/interactive/components/footer.js.map +1 -1
  60. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  61. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  62. package/dist/modes/interactive/interactive-mode.js +55 -48
  63. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  64. package/dist/modes/interactive/session-info-format.d.ts +3 -0
  65. package/dist/modes/interactive/session-info-format.d.ts.map +1 -0
  66. package/dist/modes/interactive/session-info-format.js +44 -0
  67. package/dist/modes/interactive/session-info-format.js.map +1 -0
  68. package/dist/modes/interactive/working-status.d.ts +6 -0
  69. package/dist/modes/interactive/working-status.d.ts.map +1 -1
  70. package/dist/modes/interactive/working-status.js +11 -0
  71. package/dist/modes/interactive/working-status.js.map +1 -1
  72. package/dist/package-manager-cli.d.ts.map +1 -1
  73. package/dist/package-manager-cli.js +3 -4
  74. package/dist/package-manager-cli.js.map +1 -1
  75. package/dist/senpi +5 -1
  76. package/dist/utils/child-process.d.ts +7 -1
  77. package/dist/utils/child-process.d.ts.map +1 -1
  78. package/dist/utils/child-process.js +60 -7
  79. package/dist/utils/child-process.js.map +1 -1
  80. package/dist/utils/tools-manager.d.ts.map +1 -1
  81. package/dist/utils/tools-manager.js +4 -1
  82. package/dist/utils/tools-manager.js.map +1 -1
  83. package/docs/custom-provider.md +55 -0
  84. package/docs/extensions.md +1 -1
  85. package/docs/settings.md +1 -3
  86. package/docs/skills.md +3 -4
  87. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  88. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  89. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  90. package/examples/extensions/sandbox/package-lock.json +2 -2
  91. package/examples/extensions/sandbox/package.json +1 -1
  92. package/examples/extensions/with-deps/package-lock.json +2 -2
  93. package/examples/extensions/with-deps/package.json +1 -1
  94. package/package.json +6 -6
@@ -58,9 +58,10 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
58
58
  import { TreeSelectorComponent } from "./components/tree-selector.js";
59
59
  import { UserMessageComponent } from "./components/user-message.js";
60
60
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
61
+ import { formatSessionInfo } from "./session-info-format.js";
61
62
  import { resolveStartupToolPaths } from "./startup-tools.js";
62
63
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
63
- import { formatWorkingStatusMessageFrame } from "./working-status.js";
64
+ import { blendWorkingStatusShimmerRgbColor, formatWorkingStatusMessageFrame, } from "./working-status.js";
64
65
  function isExpandable(obj) {
65
66
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
66
67
  }
@@ -101,17 +102,6 @@ function parseAnsiRgbForeground(ansi) {
101
102
  function isWorkingLightTheme() {
102
103
  return theme.name?.toLowerCase().includes("light") ?? false;
103
104
  }
104
- function clampColorChannel(value) {
105
- return Math.max(0, Math.min(255, Math.round(value)));
106
- }
107
- function mixRgbColor(base, highlight, amount) {
108
- const clampedAmount = Math.max(0, Math.min(1, amount));
109
- return {
110
- r: clampColorChannel(base.r + (highlight.r - base.r) * clampedAmount),
111
- g: clampColorChannel(base.g + (highlight.g - base.g) * clampedAmount),
112
- b: clampColorChannel(base.b + (highlight.b - base.b) * clampedAmount),
113
- };
114
- }
115
105
  function formatWorkingStatusShimmerText(text, intensity) {
116
106
  if (theme.getColorMode() !== "truecolor") {
117
107
  if (intensity < 0.2) {
@@ -123,11 +113,11 @@ function formatWorkingStatusShimmerText(text, intensity) {
123
113
  return theme.bold(theme.fg("text", text));
124
114
  }
125
115
  const lightTheme = isWorkingLightTheme();
126
- const base = parseAnsiRgbForeground(theme.getFgAnsi("dim")) ??
116
+ const highlight = parseAnsiRgbForeground(theme.getFgAnsi("dim")) ??
127
117
  (lightTheme ? LIGHT_DEFAULT_WORKING_BASE_RGB : DARK_DEFAULT_WORKING_BASE_RGB);
128
- const highlight = parseAnsiRgbForeground(theme.getFgAnsi("text")) ??
118
+ const base = parseAnsiRgbForeground(theme.getFgAnsi("text")) ??
129
119
  (lightTheme ? LIGHT_DEFAULT_WORKING_TEXT_RGB : DARK_DEFAULT_WORKING_TEXT_RGB);
130
- const color = mixRgbColor(base, highlight, intensity * 0.9);
120
+ const color = blendWorkingStatusShimmerRgbColor(highlight, base, intensity * 0.9);
131
121
  return `\x1b[1m\x1b[38;2;${color.r};${color.g};${color.b}m${text}\x1b[39m\x1b[22m`;
132
122
  }
133
123
  function isDeadTerminalError(error) {
@@ -448,6 +438,19 @@ export class InteractiveMode {
448
438
  // Startup should never wait for tool downloads. Missing tools are resolved
449
439
  // lazily by the tools that need them.
450
440
  this.fdPath = resolveStartupToolPaths().fdPath;
441
+ if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
442
+ const modelList = this.session.scopedModels
443
+ .map((sm) => {
444
+ const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
445
+ return `${sm.model.id}${thinkingStr}`;
446
+ })
447
+ .join(", ");
448
+ const cycleKeys = this.keybindings.getKeys("app.model.cycleForward");
449
+ const cycleHint = cycleKeys.length > 0
450
+ ? theme.fg("muted", ` (${formatKeyText(cycleKeys.join("/"), { capitalize: true })} to cycle)`)
451
+ : "";
452
+ console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
453
+ }
451
454
  // Add header container as first child
452
455
  this.ui.addChild(this.headerContainer);
453
456
  // Add header with keybindings from config (unless silenced)
@@ -1947,8 +1950,8 @@ export class InteractiveMode {
1947
1950
  // Set up handlers on defaultEditor - they use this.editor for text access
1948
1951
  // so they work correctly regardless of which editor is active
1949
1952
  this.defaultEditor.onEscape = () => {
1950
- if (this.session.isStreaming) {
1951
- this.restoreQueuedMessagesToEditor({ abort: true });
1953
+ if (this.session.isStreaming || this.session.retryAttempt > 0) {
1954
+ void this.abortAndFireQueuedMessages();
1952
1955
  }
1953
1956
  else if (this.session.isBashRunning) {
1954
1957
  this.session.abortBash();
@@ -2470,11 +2473,11 @@ export class InteractiveMode {
2470
2473
  break;
2471
2474
  }
2472
2475
  case "auto_retry_start": {
2473
- // Set up escape to abort retry
2474
- this.retryEscapeHandler = this.defaultEditor.onEscape;
2475
- this.defaultEditor.onEscape = () => {
2476
- this.session.abortRetry();
2477
- };
2476
+ // During retry waits, isStreaming flips false between attempts. The main Esc handler
2477
+ // keys off both isStreaming and retryAttempt so we keep the same close-out path here;
2478
+ // no separate retry-only handler is installed (the prior one only called
2479
+ // session.abortRetry() and left queued steering messages stranded).
2480
+ this.retryEscapeHandler = undefined;
2478
2481
  // Show retry indicator
2479
2482
  this.statusContainer.clear();
2480
2483
  this.retryCountdown?.dispose();
@@ -3142,6 +3145,35 @@ export class InteractiveMode {
3142
3145
  }
3143
3146
  return allQueued.length;
3144
3147
  }
3148
+ /**
3149
+ * User-abort path that drains the queue, aborts the active run (and any in-flight retry),
3150
+ * waits for settle, then re-fires queued messages as a single fresh prompt.
3151
+ *
3152
+ * Behavior contract:
3153
+ * - clearAllQueues() is called synchronously so the pending-messages display empties immediately.
3154
+ * - `await session.abort()` ensures the previous run is fully idle before the fresh prompt fires,
3155
+ * so it starts a new turn instead of being re-queued as steering behind the dying run.
3156
+ * - When no messages were queued, the helper is just a sync clear + abort (no fresh prompt).
3157
+ * - Failures from the fresh prompt are surfaced via showError but do not throw.
3158
+ */
3159
+ async abortAndFireQueuedMessages() {
3160
+ const { steering, followUp } = this.clearAllQueues();
3161
+ const allQueued = [...steering, ...followUp];
3162
+ this.updatePendingMessagesDisplay();
3163
+ await this.session.abort();
3164
+ if (allQueued.length === 0) {
3165
+ return 0;
3166
+ }
3167
+ const queuedText = allQueued.join("\n\n");
3168
+ try {
3169
+ await this.session.prompt(queuedText);
3170
+ }
3171
+ catch (error) {
3172
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
3173
+ this.showError(`Failed to fire queued message: ${errorMessage}`);
3174
+ }
3175
+ return allQueued.length;
3176
+ }
3145
3177
  queueCompactionMessage(text, mode) {
3146
3178
  this.compactionQueuedMessages.push({ text, mode });
3147
3179
  this.editor.addToHistory?.(text);
@@ -4398,32 +4430,7 @@ export class InteractiveMode {
4398
4430
  handleSessionCommand() {
4399
4431
  const stats = this.session.getSessionStats();
4400
4432
  const sessionName = this.sessionManager.getSessionName();
4401
- let info = `${theme.bold("Session Info")}\n\n`;
4402
- if (sessionName) {
4403
- info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
4404
- }
4405
- info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
4406
- info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
4407
- info += `${theme.bold("Messages")}\n`;
4408
- info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
4409
- info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
4410
- info += `${theme.fg("dim", "Tool Calls:")} ${stats.toolCalls}\n`;
4411
- info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
4412
- info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
4413
- info += `${theme.bold("Tokens")}\n`;
4414
- info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
4415
- info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
4416
- if (stats.tokens.cacheRead > 0) {
4417
- info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
4418
- }
4419
- if (stats.tokens.cacheWrite > 0) {
4420
- info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
4421
- }
4422
- info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
4423
- if (stats.cost > 0) {
4424
- info += `\n${theme.bold("Cost")}\n`;
4425
- info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}`;
4426
- }
4433
+ const info = formatSessionInfo(stats, sessionName);
4427
4434
  this.chatContainer.addChild(new Spacer(1));
4428
4435
  this.chatContainer.addChild(new Text(info, 1, 0));
4429
4436
  this.ui.requestRender();