@code-yeongyu/senpi 2026.7.9-2 → 2026.7.9

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 (207) hide show
  1. package/CHANGELOG.md +1 -25
  2. package/README.md +1 -1
  3. package/dist/cli/args.js +3 -3
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/cli/config-selector.d.ts +2 -4
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +3 -18
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +36 -153
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts +2 -1
  14. package/dist/core/compaction/compaction.d.ts.map +1 -1
  15. package/dist/core/compaction/compaction.js +79 -54
  16. package/dist/core/compaction/compaction.js.map +1 -1
  17. package/dist/core/extensions/builtin/goal/continuation.js +1 -1
  18. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  19. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/goal/index.js +0 -1
  21. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  22. package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/import-repro.js +6 -100
  24. package/dist/core/extensions/builtin/import-repro.js.map +1 -1
  25. package/dist/core/extensions/builtin/mcp/connection.d.ts +0 -1
  26. package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/mcp/connection.js +0 -15
  28. package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
  29. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  30. package/dist/core/extensions/builtin/mcp/service.js +1 -3
  31. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  32. package/dist/core/extensions/index.d.ts +1 -1
  33. package/dist/core/extensions/index.d.ts.map +1 -1
  34. package/dist/core/extensions/index.js.map +1 -1
  35. package/dist/core/extensions/runner.d.ts +3 -4
  36. package/dist/core/extensions/runner.d.ts.map +1 -1
  37. package/dist/core/extensions/runner.js +24 -30
  38. package/dist/core/extensions/runner.js.map +1 -1
  39. package/dist/core/extensions/types.d.ts +3 -23
  40. package/dist/core/extensions/types.d.ts.map +1 -1
  41. package/dist/core/extensions/types.js.map +1 -1
  42. package/dist/core/model-registry.d.ts +0 -3
  43. package/dist/core/model-registry.d.ts.map +1 -1
  44. package/dist/core/model-registry.js +3 -20
  45. package/dist/core/model-registry.js.map +1 -1
  46. package/dist/core/package-manager.d.ts +1 -4
  47. package/dist/core/package-manager.d.ts.map +1 -1
  48. package/dist/core/package-manager.js +26 -68
  49. package/dist/core/package-manager.js.map +1 -1
  50. package/dist/core/resource-loader.d.ts +2 -2
  51. package/dist/core/resource-loader.d.ts.map +1 -1
  52. package/dist/core/resource-loader.js +7 -6
  53. package/dist/core/resource-loader.js.map +1 -1
  54. package/dist/core/sdk.d.ts +1 -1
  55. package/dist/core/sdk.d.ts.map +1 -1
  56. package/dist/core/sdk.js +2 -9
  57. package/dist/core/sdk.js.map +1 -1
  58. package/dist/core/session-manager.d.ts.map +1 -1
  59. package/dist/core/session-manager.js +2 -11
  60. package/dist/core/session-manager.js.map +1 -1
  61. package/dist/core/settings-manager.d.ts +0 -5
  62. package/dist/core/settings-manager.d.ts.map +1 -1
  63. package/dist/core/settings-manager.js +0 -8
  64. package/dist/core/settings-manager.js.map +1 -1
  65. package/dist/core/slash-commands.d.ts +0 -1
  66. package/dist/core/slash-commands.d.ts.map +1 -1
  67. package/dist/core/slash-commands.js +3 -3
  68. package/dist/core/slash-commands.js.map +1 -1
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/main.d.ts +2 -2
  73. package/dist/main.d.ts.map +1 -1
  74. package/dist/main.js +0 -1
  75. package/dist/main.js.map +1 -1
  76. package/dist/modes/interactive/components/config-selector.d.ts +3 -34
  77. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/config-selector.js +24 -272
  79. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  80. package/dist/modes/interactive/components/footer.d.ts +0 -4
  81. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/footer.js +2 -5
  83. package/dist/modes/interactive/components/footer.js.map +1 -1
  84. package/dist/modes/interactive/components/oauth-selector.d.ts +1 -3
  85. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/oauth-selector.js +6 -16
  87. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  88. package/dist/modes/interactive/components/settings-selector.d.ts +0 -2
  89. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/settings-selector.js +0 -10
  91. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  92. package/dist/modes/interactive/interactive-mode.d.ts +0 -10
  93. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  94. package/dist/modes/interactive/interactive-mode.js +37 -261
  95. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  96. package/dist/modes/print-mode.js +1 -1
  97. package/dist/modes/print-mode.js.map +1 -1
  98. package/dist/modes/rpc/rpc-client.d.ts +6 -6
  99. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  100. package/dist/modes/rpc/rpc-client.js +3 -3
  101. package/dist/modes/rpc/rpc-client.js.map +1 -1
  102. package/dist/package-manager-cli.d.ts +2 -2
  103. package/dist/package-manager-cli.d.ts.map +1 -1
  104. package/dist/package-manager-cli.js +17 -64
  105. package/dist/package-manager-cli.js.map +1 -1
  106. package/dist/utils/clipboard-image.js +1 -1
  107. package/dist/utils/clipboard-image.js.map +1 -1
  108. package/docs/extensions.md +9 -33
  109. package/docs/models.md +4 -4
  110. package/docs/packages.md +10 -7
  111. package/docs/rpc.md +3 -13
  112. package/docs/sdk.md +0 -22
  113. package/docs/settings.md +0 -1
  114. package/docs/usage.md +1 -1
  115. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  116. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  117. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  118. package/examples/extensions/gondolin/package-lock.json +2 -2
  119. package/examples/extensions/gondolin/package.json +1 -1
  120. package/examples/extensions/reload-runtime.ts +2 -2
  121. package/examples/extensions/sandbox/package-lock.json +2 -2
  122. package/examples/extensions/sandbox/package.json +1 -1
  123. package/examples/extensions/with-deps/package-lock.json +2 -2
  124. package/examples/extensions/with-deps/package.json +1 -1
  125. package/examples/sdk/README.md +1 -1
  126. package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
  127. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  128. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +2 -37
  129. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  130. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.d.ts.map +1 -1
  131. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js +0 -2
  132. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js.map +1 -1
  133. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts +0 -1
  134. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts.map +1 -1
  135. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +0 -7
  136. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js.map +1 -1
  137. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +4 -18
  138. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
  139. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +37 -72
  140. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
  141. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +0 -2
  142. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  144. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +0 -2
  145. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  146. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +0 -2
  147. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  148. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  149. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  150. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -2
  151. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  154. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +1 -4
  156. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
  157. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +195 -446
  158. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  159. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +0 -84
  160. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  161. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +0 -72
  162. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
  163. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +4 -4
  164. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  165. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +0 -63
  166. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +0 -54
  168. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +0 -84
  170. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +0 -72
  172. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
  173. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +63 -138
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +76 -136
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +0 -77
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +0 -71
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts +44 -0
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +45 -9
  184. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  185. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts +44 -0
  186. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +45 -9
  188. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts +44 -0
  190. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +45 -9
  192. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  193. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  194. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +0 -3
  195. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  196. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  197. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  198. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  199. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +1 -22
  200. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  201. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  202. package/npm-shrinkwrap.json +14 -14
  203. package/package.json +6 -6
  204. package/dist/core/cache-stats.d.ts +0 -49
  205. package/dist/core/cache-stats.d.ts.map +0 -1
  206. package/dist/core/cache-stats.js +0 -101
  207. package/dist/core/cache-stats.js.map +0 -1
@@ -13,7 +13,6 @@ import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, expandTildePath, getAuthPath, get
13
13
  import { parseSkillBlock } from "../../core/agent-session.js";
14
14
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
15
15
  import { isApiKeyLoginProvider } from "../../core/auth-providers.js";
16
- import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
17
16
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
18
17
  import { appendHiddenTuiStdout } from "../../core/hidden-stdout-log.js";
19
18
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
@@ -50,12 +49,12 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
50
49
  import { ExtensionInputComponent } from "./components/extension-input.js";
51
50
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
52
51
  import { FavoriteModelsSelectorComponent } from "./components/favorite-models-selector.js";
53
- import { FooterComponent, formatTokens } from "./components/footer.js";
52
+ import { FooterComponent } from "./components/footer.js";
54
53
  import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
55
54
  import { LoginDialogComponent } from "./components/login-dialog.js";
56
55
  import { getModelFullId } from "./components/model-favorites.js";
57
56
  import { ModelSelectorComponent } from "./components/model-selector.js";
58
- import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
57
+ import { OAuthSelectorComponent } from "./components/oauth-selector.js";
59
58
  import { SessionSelectorComponent } from "./components/session-selector.js";
60
59
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
61
60
  import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
@@ -67,6 +66,7 @@ import { UserMessageComponent } from "./components/user-message.js";
67
66
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
68
67
  import { restoreInteractiveStderr, takeOverInteractiveStderr } from "./interactive-stderr-guard.js";
69
68
  import { getModelSearchText } from "./model-search.js";
69
+ import { formatSessionInfo } from "./session-info-format.js";
70
70
  import { resolveStartupToolPaths } from "./startup-tools.js";
71
71
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
72
72
  import { InteractiveThemeController } from "./theme/theme-controller.js";
@@ -179,42 +179,6 @@ const BEDROCK_PROVIDER_ID = "amazon-bedrock";
179
179
  // keep the existing public import (test/oauth-selector.test.ts) working, and used
180
180
  // locally by getLoginProviderOptions below.
181
181
  export { isApiKeyLoginProvider };
182
- const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
183
- function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
184
- const filtered = fuzzyFilter(items, prefix, getSearchText);
185
- if (filtered.length === 0)
186
- return null;
187
- return filtered.map(toAutocompleteItem);
188
- }
189
- function getLoginProviderCompletionOptions(providerOptions) {
190
- const byId = new Map();
191
- for (const provider of providerOptions) {
192
- const existing = byId.get(provider.id);
193
- if (existing) {
194
- if (!existing.authTypes.includes(provider.authType)) {
195
- existing.authTypes.push(provider.authType);
196
- existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
197
- }
198
- continue;
199
- }
200
- byId.set(provider.id, {
201
- id: provider.id,
202
- name: provider.name,
203
- authTypes: [provider.authType],
204
- });
205
- }
206
- return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
207
- }
208
- function getLoginProviderSearchText(provider) {
209
- const authTypes = provider.authTypes
210
- .map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
211
- .join(" ");
212
- return `${provider.id} ${provider.name} ${authTypes}`;
213
- }
214
- function formatLoginProviderCompletionDescription(provider) {
215
- const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
216
- return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
217
- }
218
182
  export class InteractiveMode {
219
183
  requestStreamingRender() {
220
184
  this.ui.requestRender();
@@ -386,7 +350,6 @@ export class InteractiveMode {
386
350
  const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
387
351
  name: command.name,
388
352
  description: command.description,
389
- ...(command.argumentHint && { argumentHint: command.argumentHint }),
390
353
  }));
391
354
  const modelCommand = slashCommands.find((command) => command.name === "model");
392
355
  if (modelCommand) {
@@ -404,24 +367,17 @@ export class InteractiveMode {
404
367
  name: m.name,
405
368
  label: `${m.provider}/${m.id}`,
406
369
  }));
407
- return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
370
+ // Fuzzy filter by model ID + provider in either order.
371
+ const filtered = fuzzyFilter(items, prefix, getModelSearchText);
372
+ if (filtered.length === 0)
373
+ return null;
374
+ return filtered.map((item) => ({
408
375
  value: item.label,
409
376
  label: item.id,
410
377
  description: item.provider,
411
378
  }));
412
379
  };
413
380
  }
414
- const loginCommand = slashCommands.find((command) => command.name === "login");
415
- if (loginCommand) {
416
- loginCommand.getArgumentCompletions = (prefix) => {
417
- const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
418
- return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
419
- value: provider.id,
420
- label: provider.id,
421
- description: formatLoginProviderCompletionDescription(provider),
422
- }));
423
- };
424
- }
425
381
  // Convert prompt templates to SlashCommand format for autocomplete
426
382
  const templateCommands = this.session.promptTemplates.map((cmd) => ({
427
383
  name: cmd.name,
@@ -1249,7 +1205,7 @@ export class InteractiveMode {
1249
1205
  this.restoreQueuedMessagesToEditor({ abort: true });
1250
1206
  },
1251
1207
  commandContextActions: {
1252
- waitForIdle: () => this.session.waitForIdle(),
1208
+ waitForIdle: () => this.session.agent.waitForIdle(),
1253
1209
  newSession: async (options) => {
1254
1210
  this.clearStatusIndicator();
1255
1211
  try {
@@ -1300,7 +1256,7 @@ export class InteractiveMode {
1300
1256
  },
1301
1257
  shutdownHandler: () => {
1302
1258
  this.shutdownRequested = true;
1303
- if (this.session.isIdle) {
1259
+ if (!this.session.isStreaming) {
1304
1260
  void this.shutdown();
1305
1261
  }
1306
1262
  },
@@ -1395,7 +1351,7 @@ export class InteractiveMode {
1395
1351
  modelRegistry: this.session.modelRegistry,
1396
1352
  model: this.session.model,
1397
1353
  serviceTier: this.session.serviceTier,
1398
- isIdle: () => this.session.isIdle,
1354
+ isIdle: () => !this.session.isStreaming,
1399
1355
  isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
1400
1356
  signal: this.session.agent.signal,
1401
1357
  abort: () => this.session.abort(),
@@ -2391,10 +2347,9 @@ export class InteractiveMode {
2391
2347
  this.editor.setText("");
2392
2348
  return;
2393
2349
  }
2394
- if (text === "/login" || text.startsWith("/login ")) {
2395
- const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
2350
+ if (text === "/login") {
2351
+ this.showOAuthSelector("login");
2396
2352
  this.editor.setText("");
2397
- await this.handleLoginCommand(providerRef);
2398
2353
  return;
2399
2354
  }
2400
2355
  if (text === "/logout") {
@@ -2627,7 +2582,6 @@ export class InteractiveMode {
2627
2582
  for (const [, component] of this.pendingTools.entries()) {
2628
2583
  component.setArgsComplete();
2629
2584
  }
2630
- this.maybeShowCacheMissNotice(this.streamingMessage);
2631
2585
  }
2632
2586
  this.streamingComponent = undefined;
2633
2587
  this.streamingMessage = undefined;
@@ -2685,10 +2639,8 @@ export class InteractiveMode {
2685
2639
  this.streamingMessage = undefined;
2686
2640
  }
2687
2641
  this.clearPendingTools();
2688
- this.ui.requestRender();
2689
- break;
2690
- case "agent_settled":
2691
2642
  await this.checkShutdownRequested();
2643
+ this.ui.requestRender();
2692
2644
  break;
2693
2645
  case "compaction_start": {
2694
2646
  if (this.settingsManager.getShowTerminalProgress()) {
@@ -2919,11 +2871,6 @@ export class InteractiveMode {
2919
2871
  renderSessionItems(items, options = {}) {
2920
2872
  this.clearPendingTools();
2921
2873
  const renderedPendingTools = new Map();
2922
- // Cache-miss notices are not persisted; re-derive them from the full entry
2923
- // list and re-inject them after the assistant messages that paid for them.
2924
- const cacheMisses = this.settingsManager.getShowCacheMissNotices()
2925
- ? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRegistry)
2926
- : new Map();
2927
2874
  if (options.updateFooter) {
2928
2875
  this.footer.invalidate();
2929
2876
  this.updateEditorBorderColor();
@@ -2961,11 +2908,6 @@ export class InteractiveMode {
2961
2908
  }
2962
2909
  }
2963
2910
  }
2964
- if (message.stopReason !== "aborted" && message.stopReason !== "error") {
2965
- const miss = cacheMisses.get(message);
2966
- if (miss)
2967
- this.addCacheMissNotice(miss);
2968
- }
2969
2911
  }
2970
2912
  else if (message.role === "toolResult") {
2971
2913
  // Match tool results to pending tool components
@@ -3000,35 +2942,6 @@ export class InteractiveMode {
3000
2942
  });
3001
2943
  this.renderSessionItems(items, options);
3002
2944
  }
3003
- /**
3004
- * Show a transcript notice when a completed assistant message paid for a
3005
- * significant cache miss. Only states observable facts: the miss itself,
3006
- * a model switch, or an idle gap past the cache TTL.
3007
- */
3008
- maybeShowCacheMissNotice(message) {
3009
- if (!this.settingsManager.getShowCacheMissNotices())
3010
- return;
3011
- // Entries don't contain `message` yet: message_end fires before persistence.
3012
- const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRegistry);
3013
- if (miss)
3014
- this.addCacheMissNotice(miss);
3015
- }
3016
- addCacheMissNotice(miss) {
3017
- if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
3018
- return;
3019
- const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
3020
- const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
3021
- let label = "Cache miss";
3022
- if (miss.modelChanged) {
3023
- label = "Cache miss after model switch";
3024
- }
3025
- else if (miss.idleMs >= CACHE_TTL_MS) {
3026
- label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
3027
- }
3028
- const text = theme.fg("warning", `${label}: ${reBilled}`);
3029
- this.chatContainer.addChild(new Spacer(1));
3030
- this.chatContainer.addChild(new Text(text, 1, 0));
3031
- }
3032
2945
  renderInitialMessages() {
3033
2946
  const entries = this.sessionManager.buildContextEntries();
3034
2947
  this.renderSessionEntries(entries, {
@@ -3683,7 +3596,6 @@ export class InteractiveMode {
3683
3596
  doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
3684
3597
  treeFilterMode: this.settingsManager.getTreeFilterMode(),
3685
3598
  showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
3686
- showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
3687
3599
  defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
3688
3600
  editorPaddingX: this.settingsManager.getEditorPaddingX(),
3689
3601
  outputPad: this.settingsManager.getOutputPad(),
@@ -3760,10 +3672,6 @@ export class InteractiveMode {
3760
3672
  this.chatContainer.clear();
3761
3673
  this.rebuildChatFromMessages();
3762
3674
  },
3763
- onShowCacheMissNoticesChange: (shown) => {
3764
- this.settingsManager.setShowCacheMissNotices(shown);
3765
- this.rebuildChatFromMessages();
3766
- },
3767
3675
  onCollapseChangelogChange: (collapsed) => {
3768
3676
  this.settingsManager.setCollapseChangelog(collapsed);
3769
3677
  },
@@ -4067,17 +3975,19 @@ export class InteractiveMode {
4067
3975
  const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
4068
3976
  this.showSelector((done) => {
4069
3977
  const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
4070
- done();
4071
3978
  try {
4072
3979
  const result = await this.runtimeHost.fork(entryId);
4073
3980
  if (result.cancelled) {
3981
+ done();
4074
3982
  this.ui.requestRender();
4075
3983
  return;
4076
3984
  }
4077
3985
  this.editor.setText(result.selectedText ?? "");
3986
+ done();
4078
3987
  this.showStatus("Forked to new session");
4079
3988
  }
4080
3989
  catch (error) {
3990
+ done();
4081
3991
  this.showError(error instanceof Error ? error.message : String(error));
4082
3992
  }
4083
3993
  }, () => {
@@ -4306,82 +4216,13 @@ export class InteractiveMode {
4306
4216
  }
4307
4217
  return options.sort((a, b) => a.name.localeCompare(b.name));
4308
4218
  }
4309
- findLoginProviderOptions(providerRef) {
4310
- const normalizedProviderRef = providerRef.trim().toLowerCase();
4311
- if (!normalizedProviderRef) {
4312
- return [];
4313
- }
4314
- return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
4315
- provider.name.toLowerCase() === normalizedProviderRef);
4316
- }
4317
- async handleLoginCommand(providerRef) {
4318
- if (!providerRef) {
4319
- this.showLoginAuthTypeSelector();
4320
- return;
4321
- }
4322
- const providerOptions = this.findLoginProviderOptions(providerRef);
4323
- if (providerOptions.length === 1) {
4324
- await this.startProviderLogin(providerOptions[0]);
4325
- return;
4326
- }
4327
- if (providerOptions.length > 1) {
4328
- const providerIds = new Set(providerOptions.map((provider) => provider.id));
4329
- if (providerIds.size === 1) {
4330
- this.showLoginAuthTypeSelector(providerOptions);
4331
- return;
4332
- }
4333
- }
4334
- this.showLoginProviderSelector(undefined, providerRef);
4335
- }
4336
- async startProviderLogin(providerOption) {
4337
- if (providerOption.authType === "oauth") {
4338
- await this.showLoginDialog(providerOption.id, providerOption.name);
4339
- }
4340
- else if (providerOption.id === BEDROCK_PROVIDER_ID) {
4341
- this.showBedrockSetupDialog(providerOption.id, providerOption.name);
4342
- }
4343
- else {
4344
- await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
4345
- }
4346
- }
4347
- showLoginAuthTypeSelector(providerOptions) {
4219
+ showLoginAuthTypeSelector() {
4348
4220
  const subscriptionLabel = "Use a subscription";
4349
4221
  const apiKeyLabel = "Use an API key";
4350
- const availableAuthTypes = providerOptions
4351
- ? new Set(providerOptions.map((provider) => provider.authType))
4352
- : new Set(["oauth", "api_key"]);
4353
- const options = [];
4354
- if (availableAuthTypes.has("oauth")) {
4355
- options.push(subscriptionLabel);
4356
- }
4357
- if (availableAuthTypes.has("api_key")) {
4358
- options.push(apiKeyLabel);
4359
- }
4360
- if (options.length === 0) {
4361
- this.showStatus("No login methods available.");
4362
- return;
4363
- }
4364
- if (providerOptions && options.length === 1) {
4365
- const providerOption = providerOptions[0];
4366
- if (providerOption) {
4367
- void this.startProviderLogin(providerOption);
4368
- }
4369
- return;
4370
- }
4371
- const title = providerOptions?.[0]
4372
- ? `Select authentication method for ${providerOptions[0].name}:`
4373
- : "Select authentication method:";
4374
4222
  this.showSelector((done) => {
4375
- const selector = new ExtensionSelectorComponent(title, options, (option) => {
4223
+ const selector = new ExtensionSelectorComponent("Select authentication method:", [subscriptionLabel, apiKeyLabel], (option) => {
4376
4224
  done();
4377
4225
  const authType = option === subscriptionLabel ? "oauth" : "api_key";
4378
- if (providerOptions) {
4379
- const providerOption = providerOptions.find((provider) => provider.authType === authType);
4380
- if (providerOption) {
4381
- void this.startProviderLogin(providerOption);
4382
- }
4383
- return;
4384
- }
4385
4226
  this.showLoginProviderSelector(authType);
4386
4227
  }, () => {
4387
4228
  done();
@@ -4390,34 +4231,32 @@ export class InteractiveMode {
4390
4231
  return { component: selector, focus: selector };
4391
4232
  });
4392
4233
  }
4393
- showLoginProviderSelector(authType, initialSearchInput) {
4234
+ showLoginProviderSelector(authType) {
4394
4235
  const providerOptions = this.getLoginProviderOptions(authType);
4395
4236
  if (providerOptions.length === 0) {
4396
- const message = authType === "oauth"
4397
- ? "No subscription providers available."
4398
- : authType === "api_key"
4399
- ? "No API key providers available."
4400
- : "No login providers available.";
4401
- this.showStatus(message);
4237
+ this.showStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
4402
4238
  return;
4403
4239
  }
4404
4240
  this.showSelector((done) => {
4405
- const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId, selectedAuthType) => {
4241
+ const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
4406
4242
  done();
4407
- const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
4243
+ const providerOption = providerOptions.find((provider) => provider.id === providerId);
4408
4244
  if (!providerOption) {
4409
4245
  return;
4410
4246
  }
4411
- await this.startProviderLogin(providerOption);
4412
- }, () => {
4413
- done();
4414
- if (authType) {
4415
- this.showLoginAuthTypeSelector();
4247
+ if (providerOption.authType === "oauth") {
4248
+ await this.showLoginDialog(providerOption.id, providerOption.name);
4249
+ }
4250
+ else if (providerOption.id === BEDROCK_PROVIDER_ID) {
4251
+ this.showBedrockSetupDialog(providerOption.id, providerOption.name);
4416
4252
  }
4417
4253
  else {
4418
- this.ui.requestRender();
4254
+ await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
4419
4255
  }
4420
- }, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId), initialSearchInput);
4256
+ }, () => {
4257
+ done();
4258
+ this.showLoginAuthTypeSelector();
4259
+ }, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
4421
4260
  return { component: selector, focus: selector };
4422
4261
  });
4423
4262
  }
@@ -4678,7 +4517,7 @@ export class InteractiveMode {
4678
4517
  const borderColor = (s) => theme.fg("border", s);
4679
4518
  reloadBox.addChild(new DynamicBorder(borderColor));
4680
4519
  reloadBox.addChild(new Spacer(1));
4681
- reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
4520
+ reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
4682
4521
  reloadBox.addChild(new Spacer(1));
4683
4522
  reloadBox.addChild(new DynamicBorder(borderColor));
4684
4523
  const previousEditor = this.editor;
@@ -4742,8 +4581,8 @@ export class InteractiveMode {
4742
4581
  this.showError(`models.json error: ${modelsJsonError}`);
4743
4582
  }
4744
4583
  this.showStatus(savedImplicitProjectTrust
4745
- ? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
4746
- : "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
4584
+ ? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
4585
+ : "Reloaded keybindings, extensions, skills, prompts, themes");
4747
4586
  dismissReloadBox(this.editor);
4748
4587
  reloadBoxDismissed = true;
4749
4588
  }
@@ -4964,70 +4803,7 @@ export class InteractiveMode {
4964
4803
  handleSessionCommand() {
4965
4804
  const stats = this.session.getSessionStats();
4966
4805
  const sessionName = this.sessionManager.getSessionName();
4967
- const entries = this.sessionManager.getEntries();
4968
- const cacheWaste = computeCacheWaste(entries, this.session.modelRegistry);
4969
- // Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
4970
- // resolves to a concrete responseModel), sorted by cost descending.
4971
- const perModelMap = new Map();
4972
- for (const entry of entries) {
4973
- if (entry.type !== "message" || entry.message.role !== "assistant")
4974
- continue;
4975
- const message = entry.message;
4976
- const usage = message.usage;
4977
- const key = `${message.provider}/${message.responseModel ?? message.model}`;
4978
- let bucket = perModelMap.get(key);
4979
- if (!bucket) {
4980
- bucket = { key, cost: 0, tokens: 0 };
4981
- perModelMap.set(key, bucket);
4982
- }
4983
- bucket.cost += usage.cost.total;
4984
- bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
4985
- }
4986
- const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
4987
- let info = `${theme.bold("Session Info")}\n\n`;
4988
- if (sessionName) {
4989
- info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
4990
- }
4991
- info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
4992
- info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
4993
- info += `${theme.bold("Messages")}\n`;
4994
- info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
4995
- info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
4996
- info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
4997
- info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
4998
- info += `${theme.bold("Tokens")}\n`;
4999
- // "Input" is the full prompt volume. With cache activity, split it into
5000
- // cached (served from cache) vs uncached (everything else) - the only
5001
- // provider-independent split. Cache writes, where reported, are a detail
5002
- // of the uncached portion.
5003
- const { input, cacheRead, cacheWrite } = stats.tokens;
5004
- const promptTokens = input + cacheRead + cacheWrite;
5005
- info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
5006
- if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
5007
- const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
5008
- info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
5009
- const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
5010
- info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
5011
- }
5012
- info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
5013
- info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
5014
- if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
5015
- info += `\n${theme.bold("Cost")}\n`;
5016
- info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
5017
- if (perModel.length > 1) {
5018
- for (const entry of perModel) {
5019
- info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
5020
- }
5021
- }
5022
- if (cacheWaste.missedTokens > 0) {
5023
- const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
5024
- const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
5025
- info +=
5026
- cacheWaste.missedCost >= 0.0001
5027
- ? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
5028
- : `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
5029
- }
5030
- }
4806
+ const info = formatSessionInfo(stats, sessionName);
5031
4807
  this.chatContainer.addChild(new Spacer(1));
5032
4808
  this.chatContainer.addChild(new Text(info, 1, 0));
5033
4809
  this.ui.requestRender();