@code-yeongyu/senpi 2026.8.21 → 2026.8.22

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 (44) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/core/auth-storage.d.ts.map +1 -1
  3. package/dist/core/auth-storage.js +5 -4
  4. package/dist/core/auth-storage.js.map +1 -1
  5. package/dist/core/extensions/builtin/permission-system/index.d.ts.map +1 -1
  6. package/dist/core/extensions/builtin/permission-system/index.js +7 -11
  7. package/dist/core/extensions/builtin/permission-system/index.js.map +1 -1
  8. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  9. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  10. package/dist/modes/interactive/interactive-mode.js +79 -31
  11. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  12. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +40 -0
  13. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  14. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +8 -3
  15. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +15 -1
  16. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  17. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.d.ts +21 -0
  18. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.d.ts.map +1 -0
  19. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.js +36 -0
  20. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/stream-retry.js.map +1 -0
  21. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/types.d.ts +10 -0
  22. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/types.d.ts.map +1 -1
  23. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/types.js.map +1 -1
  24. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +6 -0
  25. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  26. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +166 -26
  27. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  28. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  29. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  30. package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
  31. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  32. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  33. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  34. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  35. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  36. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  37. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  38. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  39. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  40. package/package.json +7 -7
  41. package/dist/modes/interactive/split-trailing-assistant-text.d.ts +0 -7
  42. package/dist/modes/interactive/split-trailing-assistant-text.d.ts.map +0 -1
  43. package/dist/modes/interactive/split-trailing-assistant-text.js +0 -19
  44. package/dist/modes/interactive/split-trailing-assistant-text.js.map +0 -1
@@ -91,7 +91,6 @@ import { applyKeybindingsFileEdit, seedKeybindingsFile } from "./keybindings-com
91
91
  import { refreshModelCatalogs } from "./model-catalog-refresh.js";
92
92
  import { getModelSearchText } from "./model-search.js";
93
93
  import { isRiskyMainModel, RISKY_MAIN_MODEL_WARNING } from "./risky-main-model-warning.js";
94
- import { splitTrailingAssistantContent } from "./split-trailing-assistant-text.js";
95
94
  import { DEFAULT_SMOOTH_FPS, StreamingRevealController } from "./streaming-reveal.js";
96
95
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
97
96
  import { InteractiveThemeController } from "./theme/theme-controller.js";
@@ -286,6 +285,17 @@ function formatLoginProviderCompletionDescription(provider) {
286
285
  const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
287
286
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
288
287
  }
288
+ /**
289
+ * Content the streaming component owns: everything through the FIRST toolCall.
290
+ * Text painted between tool cards lives in persistent per-segment components
291
+ * below the cards (regression 1064), so the head must never reabsorb it.
292
+ */
293
+ function assistantStreamingHeadMessage(message) {
294
+ const firstToolIndex = message.content.findIndex((block) => block.type === "toolCall");
295
+ if (firstToolIndex === -1)
296
+ return message;
297
+ return { ...message, content: message.content.slice(0, firstToolIndex + 1) };
298
+ }
289
299
  /** Coordinates render-only user echoes with AgentSession's canonical input lifecycle. */
290
300
  export class OptimisticUserEchoController {
291
301
  constructor(render) {
@@ -308,10 +318,14 @@ export class OptimisticUserEchoController {
308
318
  const record = this.pending.find((candidate) => candidate.id === id);
309
319
  if (!record)
310
320
  return;
311
- if (disposition === "handled")
312
- this.reject(id);
313
- else
321
+ // Only a prompt that actually STARTED keeps its optimistic echo; queued
322
+ // (steer/follow-up) input must render as the pending-queue waiting state,
323
+ // matching upstream pi where user messages appear only at canonical
324
+ // message_start and queued text lives in the pending display.
325
+ if (disposition === "started")
314
326
  record.eligibleForCanonicalStart = true;
327
+ else
328
+ this.reject(id);
315
329
  },
316
330
  };
317
331
  }
@@ -518,7 +532,7 @@ export class InteractiveMode {
518
532
  this.managedToolStatusStarted = false;
519
533
  // Streaming message tracking
520
534
  this.streamingComponent = undefined;
521
- this.trailingAssistantComponent = undefined;
535
+ this.assistantTextSegments = new Map();
522
536
  this.streamingMessage = undefined;
523
537
  // Tool execution tracking: toolCallId -> component
524
538
  this.pendingTools = new Map();
@@ -1976,6 +1990,7 @@ export class InteractiveMode {
1976
1990
  this.toolResultReveal.stop();
1977
1991
  this.streamingComponent = undefined;
1978
1992
  this.streamingMessage = undefined;
1993
+ this.assistantTextSegments.clear();
1979
1994
  this.clearPendingTools();
1980
1995
  this.clearToolHookStatuses();
1981
1996
  this.renderInitialMessages();
@@ -3470,17 +3485,14 @@ export class InteractiveMode {
3470
3485
  this.streamingComponent.setExpanded(this.toolOutputExpanded);
3471
3486
  this.streamingMessage = event.message;
3472
3487
  this.chatContainer.addChild(this.streamingComponent);
3473
- this.streamingReveal.begin(this.streamingComponent, this.streamingMessage);
3488
+ this.streamingReveal.begin(this.streamingComponent, assistantStreamingHeadMessage(this.streamingMessage));
3474
3489
  this.requestStreamingRender();
3475
3490
  }
3476
3491
  break;
3477
3492
  case "message_update":
3478
3493
  if (this.streamingComponent && event.message.role === "assistant") {
3479
3494
  this.streamingMessage = event.message;
3480
- {
3481
- const { head } = splitTrailingAssistantContent(event.message.content);
3482
- this.streamingReveal.setTarget({ ...event.message, content: head });
3483
- }
3495
+ this.streamingReveal.setTarget(assistantStreamingHeadMessage(event.message));
3484
3496
  for (const content of this.streamingMessage.content) {
3485
3497
  if (content.type === "toolCall") {
3486
3498
  let component = this.pendingTools.get(content.id);
@@ -3525,7 +3537,7 @@ export class InteractiveMode {
3525
3537
  this.streamingMessage.errorMessage = errorMessage;
3526
3538
  }
3527
3539
  this.syncTrailingAssistantText(this.streamingMessage);
3528
- this.trailingAssistantComponent = undefined;
3540
+ this.assistantTextSegments.clear();
3529
3541
  this.addContinuityNotice(this.streamingMessage);
3530
3542
  if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
3531
3543
  if (!errorMessage) {
@@ -3625,6 +3637,7 @@ export class InteractiveMode {
3625
3637
  this.streamingComponent = undefined;
3626
3638
  this.streamingMessage = undefined;
3627
3639
  }
3640
+ this.detachAssistantTextSegments();
3628
3641
  this.clearPendingTools();
3629
3642
  this.ui.requestRender();
3630
3643
  break;
@@ -4109,25 +4122,57 @@ export class InteractiveMode {
4109
4122
  syncTrailingAssistantText(message) {
4110
4123
  if (!this.streamingComponent)
4111
4124
  return;
4112
- const { head, tail } = splitTrailingAssistantContent(message.content);
4113
- this.streamingComponent.updateContent({ ...message, content: head }, true);
4114
- if (tail.length === 0) {
4115
- if (this.trailingAssistantComponent) {
4116
- this.chatContainer.detachChild(this.trailingAssistantComponent);
4117
- this.trailingAssistantComponent = undefined;
4118
- }
4125
+ this.streamingComponent.updateContent(assistantStreamingHeadMessage(message), true);
4126
+ const content = message.content;
4127
+ const firstToolIndex = content.findIndex((block) => block.type === "toolCall");
4128
+ if (firstToolIndex === -1) {
4129
+ this.detachAssistantTextSegments();
4119
4130
  return;
4120
4131
  }
4121
- const tailMessage = { ...message, content: tail };
4122
- if (!this.trailingAssistantComponent) {
4123
- this.trailingAssistantComponent = new AssistantMessageComponent(tailMessage, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
4124
- this.trailingAssistantComponent.setExpanded(this.toolOutputExpanded);
4125
- this.chatContainer.addChild(this.trailingAssistantComponent);
4126
- return;
4132
+ let index = firstToolIndex + 1;
4133
+ while (index < content.length) {
4134
+ if (content[index]?.type === "toolCall") {
4135
+ index += 1;
4136
+ continue;
4137
+ }
4138
+ const runStart = index;
4139
+ const runBlocks = [];
4140
+ while (index < content.length && content[index]?.type !== "toolCall") {
4141
+ const runBlock = content[index];
4142
+ if (runBlock)
4143
+ runBlocks.push(runBlock);
4144
+ index += 1;
4145
+ }
4146
+ const runMessage = { ...message, content: runBlocks };
4147
+ const existing = this.assistantTextSegments.get(runStart);
4148
+ if (existing) {
4149
+ existing.updateContent(runMessage, true);
4150
+ continue;
4151
+ }
4152
+ const segment = new AssistantMessageComponent(runMessage, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
4153
+ segment.setExpanded(this.toolOutputExpanded);
4154
+ this.assistantTextSegments.set(runStart, segment);
4155
+ const followingToolCall = content.slice(index).find((block) => block.type === "toolCall");
4156
+ const followingToolCallId = followingToolCall?.type === "toolCall" ? followingToolCall.id : undefined;
4157
+ const followingToolComponent = followingToolCallId ? this.pendingTools.get(followingToolCallId) : undefined;
4158
+ const anchorIndex = followingToolComponent ? this.chatContainer.children.indexOf(followingToolComponent) : -1;
4159
+ if (anchorIndex >= 0)
4160
+ this.chatContainer.children.splice(anchorIndex, 0, segment);
4161
+ else
4162
+ this.chatContainer.addChild(segment);
4163
+ }
4164
+ for (const [runStart, segment] of this.assistantTextSegments) {
4165
+ if (runStart >= content.length || content[runStart]?.type === "toolCall") {
4166
+ this.chatContainer.detachChild(segment);
4167
+ this.assistantTextSegments.delete(runStart);
4168
+ }
4169
+ }
4170
+ }
4171
+ detachAssistantTextSegments() {
4172
+ for (const segment of this.assistantTextSegments.values()) {
4173
+ this.chatContainer.detachChild(segment);
4127
4174
  }
4128
- this.trailingAssistantComponent.updateContent(tailMessage, true);
4129
- this.chatContainer.detachChild(this.trailingAssistantComponent);
4130
- this.chatContainer.addChild(this.trailingAssistantComponent);
4175
+ this.assistantTextSegments.clear();
4131
4176
  }
4132
4177
  createToolExecutionComponent(toolName, toolCallId, args) {
4133
4178
  if (this.chrome) {
@@ -4906,12 +4951,12 @@ export class InteractiveMode {
4906
4951
  return allQueued.length;
4907
4952
  }
4908
4953
  queueCompactionMessage(text, mode, droppedImageCount = 0) {
4909
- const pendingEchoId = this.optimisticUserEchoes.begin(text);
4954
+ // No optimistic echo here: compaction-queued input is waiting state and must
4955
+ // render only in the pending-messages display until it is actually delivered.
4910
4956
  this.compactionQueuedMessages.push({
4911
4957
  text,
4912
4958
  mode,
4913
4959
  enqueueOrder: this.session.reserveQueuedInputOrder(),
4914
- pendingEchoId,
4915
4960
  });
4916
4961
  this.getSessionLogger().debug("compaction_queue_enqueue", {
4917
4962
  mode,
@@ -5384,11 +5429,15 @@ export class InteractiveMode {
5384
5429
  return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
5385
5430
  }
5386
5431
  async selectModelFromUi(model, done) {
5432
+ // The selector overlay is already disposed on Enter, so releasing it only
5433
+ // after setModel resolves leaves a stale frozen frame for the whole provider
5434
+ // auth round trip. Release and repaint first, then apply the switch.
5435
+ done?.();
5436
+ this.ui?.requestRender();
5387
5437
  try {
5388
5438
  const systemPromptChange = await this.session.setModel(model);
5389
5439
  this.footer.invalidate();
5390
5440
  this.updateEditorBorderColor();
5391
- done?.();
5392
5441
  const systemPromptStr = systemPromptChange?.systemPromptName
5393
5442
  ? ` (optimized system prompt applied: ${systemPromptChange.systemPromptName})`
5394
5443
  : "";
@@ -5398,7 +5447,6 @@ export class InteractiveMode {
5398
5447
  this.checkDaxnutsEasterEgg(model);
5399
5448
  }
5400
5449
  catch (error) {
5401
- done?.();
5402
5450
  this.showError(error instanceof Error ? error.message : String(error));
5403
5451
  }
5404
5452
  }