@selesai/code 0.13.25 → 0.13.27

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 (124) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/cli/args.js +1 -0
  3. package/dist/cli/session-picker.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/core/agent-session-auto-handoff.test.js +14 -4
  6. package/dist/core/agent-session-length-continuation.test.js +2 -1
  7. package/dist/core/agent-session.d.ts +58 -7
  8. package/dist/core/agent-session.js +339 -169
  9. package/dist/core/bug-report-upload.d.ts +11 -0
  10. package/dist/core/bug-report-upload.js +21 -0
  11. package/dist/core/bug-report.d.ts +175 -0
  12. package/dist/core/bug-report.js +291 -0
  13. package/dist/core/cache-stats.js +12 -1
  14. package/dist/core/cache-warmer.d.ts +101 -0
  15. package/dist/core/cache-warmer.js +341 -0
  16. package/dist/core/compaction/branch-summarization.js +2 -2
  17. package/dist/core/compaction/compaction.d.ts +6 -5
  18. package/dist/core/compaction/compaction.js +49 -42
  19. package/dist/core/crash-log.d.ts +21 -0
  20. package/dist/core/crash-log.js +70 -0
  21. package/dist/core/experimental.d.ts +0 -4
  22. package/dist/core/experimental.js +0 -4
  23. package/dist/core/extensions/index.d.ts +1 -1
  24. package/dist/core/extensions/jiti-loader.d.ts +1 -0
  25. package/dist/core/extensions/jiti-loader.js +3 -0
  26. package/dist/core/extensions/jiti-static-loader.d.ts +1 -0
  27. package/dist/core/extensions/jiti-static-loader.js +3 -0
  28. package/dist/core/extensions/loader.js +39 -49
  29. package/dist/core/extensions/runner.d.ts +10 -7
  30. package/dist/core/extensions/runner.js +85 -67
  31. package/dist/core/extensions/types.d.ts +66 -52
  32. package/dist/core/extensions/virtual-modules.d.ts +2 -0
  33. package/dist/core/extensions/virtual-modules.js +39 -0
  34. package/dist/core/extensions/wrapper.js +1 -20
  35. package/dist/core/handoff.d.ts +1 -0
  36. package/dist/core/handoff.js +1 -0
  37. package/dist/core/index.d.ts +2 -1
  38. package/dist/core/keybindings.d.ts +1 -1
  39. package/dist/core/keybindings.js +1 -1
  40. package/dist/core/messages.js +1 -0
  41. package/dist/core/model-config.d.ts +101 -20
  42. package/dist/core/model-config.js +25 -18
  43. package/dist/core/model-resolver.js +2 -1
  44. package/dist/core/model-runtime.js +5 -3
  45. package/dist/core/provider-composer.d.ts +7 -2
  46. package/dist/core/provider-composer.js +10 -5
  47. package/dist/core/radius.d.ts +3 -0
  48. package/dist/core/radius.js +6 -0
  49. package/dist/core/sdk.js +60 -38
  50. package/dist/core/session-export.d.ts +6 -2
  51. package/dist/core/session-export.js +14 -13
  52. package/dist/core/session-format.test.d.ts +1 -0
  53. package/dist/core/session-format.test.js +96 -0
  54. package/dist/core/session-manager.d.ts +29 -6
  55. package/dist/core/session-manager.js +159 -96
  56. package/dist/core/settings-manager.d.ts +22 -5
  57. package/dist/core/settings-manager.js +56 -23
  58. package/dist/core/slash-commands.js +1 -0
  59. package/dist/core/system-prompt.d.ts +49 -4
  60. package/dist/core/system-prompt.js +155 -102
  61. package/dist/core/system-prompt.test.js +61 -23
  62. package/dist/core/tools/bash.d.ts +2 -1
  63. package/dist/core/tools/bash.js +10 -4
  64. package/dist/core/tools/edit.js +1 -2
  65. package/dist/core/tools/read.js +1 -2
  66. package/dist/core/tools/renderers/bash.js +9 -1
  67. package/dist/core/tools/write.js +1 -2
  68. package/dist/core/usage-totals.d.ts +1 -1
  69. package/dist/core/usage-totals.js +5 -1
  70. package/dist/defaults/models.json +2 -72
  71. package/dist/extensions/handoff-new.test.ts +13 -13
  72. package/dist/extensions/handoff-new.ts +17 -6
  73. package/dist/extensions/pi-web-agent/src/backends/factory.ts +14 -6
  74. package/dist/extensions/pi-web-agent/src/extension.ts +3 -1
  75. package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +7 -3
  76. package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +3 -0
  77. package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +2 -0
  78. package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +4 -2
  79. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +4 -1
  80. package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +2 -1
  81. package/dist/extensions/pi-web-agent/src/types.ts +4 -0
  82. package/dist/index.d.ts +3 -2
  83. package/dist/main.js +13 -9
  84. package/dist/migrations.js +2 -2
  85. package/dist/modes/interactive/bug-report.d.ts +15 -0
  86. package/dist/modes/interactive/bug-report.js +205 -0
  87. package/dist/modes/interactive/chat-viewport.js +1 -1
  88. package/dist/modes/interactive/components/branch-summary-message.js +12 -5
  89. package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
  90. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  91. package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
  92. package/dist/modes/interactive/components/extension-editor.js +7 -2
  93. package/dist/modes/interactive/components/extension-input.d.ts +2 -0
  94. package/dist/modes/interactive/components/extension-input.js +6 -0
  95. package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
  96. package/dist/modes/interactive/components/extension-selector.js +4 -0
  97. package/dist/modes/interactive/components/footer.js +4 -1
  98. package/dist/modes/interactive/components/session-selector.d.ts +5 -5
  99. package/dist/modes/interactive/components/session-selector.js +74 -48
  100. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  101. package/dist/modes/interactive/components/settings-selector.js +11 -0
  102. package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
  103. package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
  104. package/dist/modes/interactive/components/status-indicator.js +7 -7
  105. package/dist/modes/interactive/components/tool-execution.js +18 -9
  106. package/dist/modes/interactive/components/tree-selector.js +2 -0
  107. package/dist/modes/interactive/interactive-mode.d.ts +15 -2
  108. package/dist/modes/interactive/interactive-mode.js +257 -81
  109. package/dist/modes/interactive/session-share.d.ts +2 -0
  110. package/dist/modes/interactive/session-share.js +8 -4
  111. package/dist/modes/interactive/tui-renderer.js +2 -2
  112. package/dist/modes/rpc/rpc-mode.js +7 -2
  113. package/dist/utils/clipboard-command.d.ts +6 -0
  114. package/dist/utils/clipboard-command.js +44 -0
  115. package/dist/utils/clipboard-image.js +55 -89
  116. package/dist/utils/clipboard.d.ts +1 -1
  117. package/dist/utils/clipboard.js +118 -108
  118. package/dist/utils/wsl.d.ts +2 -0
  119. package/dist/utils/wsl.js +14 -0
  120. package/dist/utils/zip.d.ts +6 -0
  121. package/dist/utils/zip.js +59 -0
  122. package/package.json +9 -9
  123. package/dist/utils/clipboard-native.d.ts +0 -10
  124. package/dist/utils/clipboard-native.js +0 -19
@@ -0,0 +1,205 @@
1
+ import * as path from "node:path";
2
+ import { getAuthCredential } from "../../cli/auth-command.js";
3
+ import { BUG_REPORT_CUSTOM_ENTRY_TYPE, bugReportArchiveFileName, collectBugReportDiagnostics, collectBugReportMetadata, writeBugReportArchive, } from "../../core/bug-report.js";
4
+ import { uploadBugReport } from "../../core/bug-report-upload.js";
5
+ import { clearCrashLog, readCrashLog } from "../../core/crash-log.js";
6
+ import { getRadiusGatewayUrl, RADIUS_PROVIDER_ID } from "../../core/radius.js";
7
+ import { serializeSessionBranch } from "../../core/session-export.js";
8
+ import { BorderedLoader } from "./components/bordered-loader.js";
9
+ import { ExtensionEditorComponent } from "./components/extension-editor.js";
10
+ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
11
+ import { createShareTrailingEntries } from "./session-share.js";
12
+ import { theme } from "./theme/theme.js";
13
+ const DISCLAIMER = "This report goes to the Pi developers (Earendil) and is not shared publicly. It includes your pi version, operating system, the current model and provider configuration (without API keys), loaded extensions, settings, and provider error diagnostics from this session.";
14
+ const TRANSCRIPT_NOTE = "The transcript contains your messages, model output, tool calls and their results, including file contents and command output read during this session.";
15
+ /** Run the `/bug` flow: consent, optional summary, then upload or export. */
16
+ export async function reportBug(context, initialHint) {
17
+ const options = await promptForOptions(context, initialHint);
18
+ if (!options) {
19
+ context.showStatus("Bug report cancelled");
20
+ return;
21
+ }
22
+ let summary;
23
+ if (options.includeSummary) {
24
+ const loader = showLoader(context, `Writing summary with ${context.session.model?.name ?? "the current model"}...`);
25
+ try {
26
+ summary = await context.session.summarizeForBugReport({ hint: options.hint, signal: loader.signal });
27
+ }
28
+ catch (error) {
29
+ restoreEditor(context, loader);
30
+ if (loader.signal.aborted)
31
+ context.showStatus("Bug report cancelled");
32
+ else
33
+ context.showError(`Failed to write bug report summary: ${errorMessage(error)}`);
34
+ return;
35
+ }
36
+ restoreEditor(context, loader);
37
+ if (loader.signal.aborted) {
38
+ context.showStatus("Bug report cancelled");
39
+ return;
40
+ }
41
+ }
42
+ let bundle;
43
+ try {
44
+ bundle = buildBundle(context.session, options, summary);
45
+ }
46
+ catch (error) {
47
+ context.showError(`Failed to build bug report: ${errorMessage(error)}`);
48
+ return;
49
+ }
50
+ if (options.delivery === "upload") {
51
+ const failure = await upload(context, bundle);
52
+ if (failure === undefined)
53
+ return;
54
+ const fallback = await choose(context, "Upload failed", ["Export as Zip", "Cancel"], `${failure}\n\nExport the report as a zip archive instead?`);
55
+ if (fallback !== "Export as Zip") {
56
+ context.showStatus("Bug report cancelled");
57
+ return;
58
+ }
59
+ }
60
+ await exportZip(context, bundle);
61
+ }
62
+ async function promptForOptions(context, initialHint) {
63
+ const hint = await input(context, "Report a bug", `${DISCLAIMER}\n\nWhat went wrong? (optional)`, initialHint);
64
+ if (hint === null)
65
+ return undefined;
66
+ const transcript = await choose(context, "Include the session transcript?", ["Yes, include the transcript", "No"], TRANSCRIPT_NOTE);
67
+ if (!transcript)
68
+ return undefined;
69
+ const includeSession = transcript !== "No";
70
+ let includeSummary = false;
71
+ if (!includeSession) {
72
+ const model = context.session.model;
73
+ const summary = await choose(context, `Attach a summary written by ${model?.name ?? "the current model"} instead?`, ["Yes, generate a summary", "No"], `The transcript is sent to ${model?.provider ?? "your provider"} with your credentials and tokens. Only the generated summary is attached; the transcript stays on your machine.`);
74
+ if (!summary)
75
+ return undefined;
76
+ includeSummary = summary !== "No";
77
+ }
78
+ const description = hint.trim();
79
+ const delivery = await choose(context, "Bug report", ["Upload Report", "Export as Zip", "Cancel"], `Description: ${description || "none"}\nTranscript: ${includeSession ? "included" : "not included"}\nSummary: ${includeSummary ? `written by ${context.session.model?.name ?? "the current model"}` : "none"}\n\nUpload sends the report to ${new URL(getRadiusGatewayUrl()).host}. Export writes a zip archive to the current directory instead.`);
80
+ if (!delivery || delivery === "Cancel")
81
+ return undefined;
82
+ return {
83
+ hint: description || undefined,
84
+ includeSession,
85
+ includeSummary,
86
+ delivery: delivery === "Upload Report" ? "upload" : "zip",
87
+ };
88
+ }
89
+ function buildBundle(session, options, summary) {
90
+ const extensions = session.resourceLoader.getExtensions();
91
+ return {
92
+ metadata: collectBugReportMetadata({
93
+ hint: options.hint,
94
+ sessionId: session.sessionId,
95
+ cwd: session.sessionManager.getCwd(),
96
+ includeSession: options.includeSession,
97
+ includeSummary: summary !== undefined,
98
+ messageCount: session.messages.length,
99
+ model: session.model,
100
+ modelRuntime: session.modelRuntime,
101
+ thinkingLevel: session.thinkingLevel,
102
+ extensions: extensions.extensions,
103
+ extensionErrors: extensions.errors,
104
+ globalSettings: session.settingsManager.getGlobalSettings(),
105
+ projectSettings: session.settingsManager.getProjectSettings(),
106
+ }),
107
+ diagnostics: collectBugReportDiagnostics(session.sessionManager, readCrashLog()),
108
+ summary,
109
+ sessionJsonl: options.includeSession
110
+ ? serializeSessionBranch(session.sessionManager, (parentId, timestamp) => createShareTrailingEntries(session, parentId, timestamp))
111
+ : undefined,
112
+ };
113
+ }
114
+ async function upload(context, bundle) {
115
+ const loader = showLoader(context, "Uploading bug report...");
116
+ try {
117
+ const provider = context.session.modelRuntime.getProvider(RADIUS_PROVIDER_ID);
118
+ const token = provider
119
+ ? getAuthCredential(await context.session.modelRuntime.getAuth(RADIUS_PROVIDER_ID, { minOAuthValidityMs: 5 * 60_000 }))
120
+ : undefined;
121
+ const result = await uploadBugReport(bundle, { token, signal: loader.signal });
122
+ restoreEditor(context, loader);
123
+ recordInSession(context.session, bundle, { delivery: "upload" });
124
+ context.showStatus(`Bug report uploaded. Report ID: ${result.id}`);
125
+ return undefined;
126
+ }
127
+ catch (error) {
128
+ restoreEditor(context, loader);
129
+ if (loader.signal.aborted) {
130
+ context.showStatus("Bug report cancelled");
131
+ return undefined;
132
+ }
133
+ return errorMessage(error);
134
+ }
135
+ }
136
+ async function exportZip(context, bundle) {
137
+ const archivePath = path.join(process.cwd(), bugReportArchiveFileName(bundle.metadata.id));
138
+ try {
139
+ await writeBugReportArchive(bundle, archivePath);
140
+ }
141
+ catch (error) {
142
+ context.showError(`Failed to write bug report: ${errorMessage(error)}`);
143
+ return;
144
+ }
145
+ recordInSession(context.session, bundle, { delivery: "zip", path: archivePath });
146
+ context.showStatus(`Bug report exported to: ${archivePath}\nReport ID: ${bundle.metadata.id}`);
147
+ }
148
+ function recordInSession(session, bundle, delivery) {
149
+ session.sessionManager.appendCustomEntry(BUG_REPORT_CUSTOM_ENTRY_TYPE, {
150
+ id: bundle.metadata.id,
151
+ createdAt: bundle.metadata.createdAt,
152
+ hint: bundle.metadata.hint,
153
+ sessionIncluded: bundle.metadata.session.included,
154
+ summaryIncluded: bundle.metadata.session.summaryIncluded,
155
+ ...delivery,
156
+ });
157
+ if (bundle.diagnostics.crashes.length > 0)
158
+ clearCrashLog();
159
+ }
160
+ function input(context, title, description, initialValue) {
161
+ return new Promise((resolve) => {
162
+ let component;
163
+ const finish = (value) => {
164
+ restoreEditor(context, component);
165
+ resolve(value);
166
+ };
167
+ component = new ExtensionEditorComponent(context.ui, context.keybindings, title, initialValue, (value) => finish(value), () => finish(null), { description }, context.session.settingsManager.getExternalEditorCommand());
168
+ showOverlay(context, component);
169
+ });
170
+ }
171
+ function choose(context, title, options, description) {
172
+ return new Promise((resolve) => {
173
+ let component;
174
+ const finish = (value) => {
175
+ restoreEditor(context, component);
176
+ resolve(value);
177
+ };
178
+ component = new ExtensionSelectorComponent(title, options, finish, () => finish(), {
179
+ tui: context.ui,
180
+ description,
181
+ });
182
+ showOverlay(context, component);
183
+ });
184
+ }
185
+ function showLoader(context, message) {
186
+ const loader = new BorderedLoader(context.ui, theme, message);
187
+ showOverlay(context, loader);
188
+ return loader;
189
+ }
190
+ function showOverlay(context, component) {
191
+ context.editorContainer.clear();
192
+ context.editorContainer.addChild(component);
193
+ context.ui.setFocus(component);
194
+ context.ui.requestRender();
195
+ }
196
+ function restoreEditor(context, component) {
197
+ component.dispose?.();
198
+ context.editorContainer.clear();
199
+ context.editorContainer.addChild(context.editor);
200
+ context.ui.setFocus(context.editor);
201
+ context.ui.requestRender();
202
+ }
203
+ function errorMessage(error) {
204
+ return error instanceof Error ? error.message : "Unknown error";
205
+ }
@@ -15,7 +15,7 @@ export function createChatViewport(options) {
15
15
  ...(options.widgetsAbove === undefined ? [] : [{ component: options.widgetsAbove, shrink: 1, minSize: 0 }]),
16
16
  { component: options.editor, shrink: 1, minSize: 3 },
17
17
  ...(options.widgetsBelow === undefined ? [] : [{ component: options.widgetsBelow, shrink: 1, minSize: 0 }]),
18
- { component: options.footer, shrink: 1, minSize: 1 },
18
+ { component: options.footer, shrink: 1, minSize: 0 },
19
19
  ]);
20
20
  return {
21
21
  transcript,
@@ -1,4 +1,4 @@
1
- import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
1
+ import { Box, Container, Markdown, MouseRegion, Spacer, Text } from "@earendil-works/pi-tui";
2
2
  import { getMarkdownTheme, theme } from "../theme/theme.js";
3
3
  import { keyText } from "./keybinding-hints.js";
4
4
  /**
@@ -25,19 +25,26 @@ export class BranchSummaryMessageComponent extends Box {
25
25
  }
26
26
  updateDisplay() {
27
27
  this.clear();
28
+ const content = new Container();
28
29
  const label = theme.fg("customMessageLabel", `\x1b[1m[branch]\x1b[22m`);
29
- this.addChild(new Text(label, 0, 0));
30
- this.addChild(new Spacer(1));
30
+ content.addChild(new Text(label, 0, 0));
31
+ content.addChild(new Spacer(1));
31
32
  if (this.expanded) {
32
33
  const header = "**Branch Summary**\n\n";
33
- this.addChild(new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
34
+ content.addChild(new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
34
35
  color: (text) => theme.fg("customMessageText", text),
35
36
  }));
36
37
  }
37
38
  else {
38
- this.addChild(new Text(theme.fg("customMessageText", "Branch summary (") +
39
+ content.addChild(new Text(theme.fg("customMessageText", "Branch summary (") +
39
40
  theme.fg("dim", keyText("app.tools.expand")) +
40
41
  theme.fg("customMessageText", " to expand)"), 0, 0));
41
42
  }
43
+ this.addChild(new MouseRegion(content, (event) => {
44
+ if (event.type !== "click" || event.button !== "left")
45
+ return undefined;
46
+ this.setExpanded(!this.expanded);
47
+ return { handled: true };
48
+ }));
42
49
  }
43
50
  }
@@ -1,4 +1,4 @@
1
- import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
1
+ import { Box, Container, Markdown, MouseRegion, Spacer, Text } from "@earendil-works/pi-tui";
2
2
  import { getMarkdownTheme, theme } from "../theme/theme.js";
3
3
  import { keyText } from "./keybinding-hints.js";
4
4
  /**
@@ -25,20 +25,27 @@ export class CompactionSummaryMessageComponent extends Box {
25
25
  }
26
26
  updateDisplay() {
27
27
  this.clear();
28
+ const content = new Container();
28
29
  const tokenStr = this.message.tokensBefore.toLocaleString();
29
30
  const label = theme.fg("customMessageLabel", `\x1b[1m[compaction]\x1b[22m`);
30
- this.addChild(new Text(label, 0, 0));
31
- this.addChild(new Spacer(1));
31
+ content.addChild(new Text(label, 0, 0));
32
+ content.addChild(new Spacer(1));
32
33
  if (this.expanded) {
33
34
  const header = `**Compacted from ${tokenStr} tokens**\n\n`;
34
- this.addChild(new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
35
+ content.addChild(new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
35
36
  color: (text) => theme.fg("customMessageText", text),
36
37
  }));
37
38
  }
38
39
  else {
39
- this.addChild(new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
40
+ content.addChild(new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
40
41
  theme.fg("dim", keyText("app.tools.expand")) +
41
42
  theme.fg("customMessageText", " to expand)"), 0, 0));
42
43
  }
44
+ this.addChild(new MouseRegion(content, (event) => {
45
+ if (event.type !== "click" || event.button !== "left")
46
+ return undefined;
47
+ this.setExpanded(!this.expanded);
48
+ return { handled: true };
49
+ }));
43
50
  }
44
51
  }
@@ -1,8 +1,8 @@
1
1
  import { Editor, type EditorOptions, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
2
2
  import type { AppKeybinding, KeybindingsManager } from "../../../core/keybindings.ts";
3
- import type { WorkingStatusIndicator } from "./status-indicator.ts";
3
+ import type { StatusIndicator } from "./status-indicator.ts";
4
4
  export type CustomEditorOptions = EditorOptions & {
5
- /** Render the streaming working status in the editor's top border. */
5
+ /** Render working, compaction, summarization, and retry status in the editor's top border. */
6
6
  embedWorkingStatus?: boolean;
7
7
  };
8
8
  /**
@@ -19,7 +19,7 @@ export declare class CustomEditor extends Editor {
19
19
  /** Handler for extension-registered shortcuts. Returns true if handled. */
20
20
  onExtensionShortcut?: (data: string) => boolean;
21
21
  constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions);
22
- setWorkingStatusIndicator(indicator: WorkingStatusIndicator | undefined): void;
22
+ setWorkingStatusIndicator(indicator: StatusIndicator | undefined): void;
23
23
  protected renderTopBorder(width: number, hiddenLineCount: number): string;
24
24
  /**
25
25
  * Register a handler for an app action.
@@ -4,6 +4,9 @@
4
4
  */
5
5
  import { Container, type EditorOptions, type Focusable, type TUI } from "@earendil-works/pi-tui";
6
6
  import type { KeybindingsManager } from "../../../core/keybindings.ts";
7
+ export interface ExtensionEditorOptions extends EditorOptions {
8
+ description?: string;
9
+ }
7
10
  export declare class ExtensionEditorComponent extends Container implements Focusable {
8
11
  private editor;
9
12
  private onSubmitCallback;
@@ -14,7 +17,7 @@ export declare class ExtensionEditorComponent extends Container implements Focus
14
17
  private _focused;
15
18
  get focused(): boolean;
16
19
  set focused(value: boolean);
17
- constructor(tui: TUI, keybindings: KeybindingsManager, title: string, prefill: string | undefined, onSubmit: (value: string) => void, onCancel: () => void, options?: EditorOptions, externalEditorCommand?: string);
20
+ constructor(tui: TUI, keybindings: KeybindingsManager, title: string, prefill: string | undefined, onSubmit: (value: string) => void, onCancel: () => void, options?: ExtensionEditorOptions, externalEditorCommand?: string);
18
21
  handleInput(keyData: string): void;
19
22
  private handleOpenExternalEditor;
20
23
  }
@@ -33,14 +33,19 @@ export class ExtensionEditorComponent extends Container {
33
33
  (process.platform === "win32" ? "notepad" : "nano");
34
34
  this.onSubmitCallback = onSubmit;
35
35
  this.onCancelCallback = onCancel;
36
+ const { description, ...editorOptions } = options ?? {};
36
37
  // Add top border
37
38
  this.addChild(new DynamicBorder());
38
39
  this.addChild(new Spacer(1));
39
- // Add title
40
+ // Add title and optional description
40
41
  this.addChild(new Text(theme.fg("accent", title), 1, 0));
42
+ if (description) {
43
+ this.addChild(new Spacer(1));
44
+ this.addChild(new Text(theme.fg("text", description), 1, 0));
45
+ }
41
46
  this.addChild(new Spacer(1));
42
47
  // Create editor
43
- this.editor = new Editor(tui, getEditorTheme(), options);
48
+ this.editor = new Editor(tui, getEditorTheme(), editorOptions);
44
49
  if (prefill) {
45
50
  this.editor.setText(prefill);
46
51
  }
@@ -5,6 +5,8 @@ import { Container, type Focusable, type TUI } from "@earendil-works/pi-tui";
5
5
  export interface ExtensionInputOptions {
6
6
  tui?: TUI;
7
7
  timeout?: number;
8
+ initialValue?: string;
9
+ description?: string;
8
10
  }
9
11
  export declare class ExtensionInputComponent extends Container implements Focusable {
10
12
  private input;
@@ -31,11 +31,17 @@ export class ExtensionInputComponent extends Container {
31
31
  this.addChild(new Spacer(1));
32
32
  this.titleText = new Text(theme.fg("accent", title), 1, 0);
33
33
  this.addChild(this.titleText);
34
+ if (opts?.description) {
35
+ this.addChild(new Spacer(1));
36
+ this.addChild(new Text(theme.fg("text", opts.description), 1, 0));
37
+ }
34
38
  this.addChild(new Spacer(1));
35
39
  if (opts?.timeout && opts.timeout > 0 && opts.tui) {
36
40
  this.countdown = new CountdownTimer(opts.timeout, opts.tui, (s) => this.titleText.setText(theme.fg("accent", `${this.baseTitle} (${s}s)`)), () => this.onCancelCallback());
37
41
  }
38
42
  this.input = new Input();
43
+ if (opts?.initialValue)
44
+ this.input.setValue(opts.initialValue);
39
45
  this.addChild(this.input);
40
46
  this.addChild(new Spacer(1));
41
47
  this.addChild(new Text(`${keyHint("tui.select.confirm", "submit")} ${keyHint("tui.select.cancel", "cancel")}`, 1, 0));
@@ -7,6 +7,7 @@ export interface ExtensionSelectorOptions {
7
7
  tui?: TUI;
8
8
  timeout?: number;
9
9
  onToggleToolsExpanded?: () => void;
10
+ description?: string;
10
11
  }
11
12
  export declare class ExtensionSelectorComponent extends Container {
12
13
  private options;
@@ -28,6 +28,10 @@ export class ExtensionSelectorComponent extends Container {
28
28
  this.addChild(new Spacer(1));
29
29
  this.titleText = new Text(theme.fg("accent", theme.bold(title)), 1, 0);
30
30
  this.addChild(this.titleText);
31
+ if (opts?.description) {
32
+ this.addChild(new Spacer(1));
33
+ this.addChild(new Text(theme.fg("text", opts.description), 1, 0));
34
+ }
31
35
  this.addChild(new Spacer(1));
32
36
  if (opts?.timeout && opts.timeout > 0 && opts.tui) {
33
37
  this.countdown = new CountdownTimer(opts.timeout, opts.tui, (s) => this.titleText.setText(theme.fg("accent", theme.bold(`${this.baseTitle} (${s}s)`))), () => this.onCancelCallback());
@@ -78,7 +78,10 @@ export class FooterComponent {
78
78
  const usageTotals = createUsageTotals();
79
79
  let latestCacheHitRate;
80
80
  for (const entry of this.session.sessionManager.getEntries()) {
81
- if (entry.type === "message" && entry.message.role === "assistant") {
81
+ if (entry.type === "usage") {
82
+ addUsageToTotals(usageTotals, entry.usage);
83
+ }
84
+ else if (entry.type === "message" && entry.message.role === "assistant") {
82
85
  addUsageToTotals(usageTotals, entry.message.usage);
83
86
  const latestPromptTokens = entry.message.usage.input + entry.message.usage.cacheRead + entry.message.usage.cacheWrite;
84
87
  latestCacheHitRate =
@@ -10,6 +10,7 @@ declare class SessionList implements Component, Focusable {
10
10
  private allSessions;
11
11
  private filteredSessions;
12
12
  private selectedIndex;
13
+ private selectionTouched;
13
14
  private searchInput;
14
15
  private showCwd;
15
16
  private sortMode;
@@ -46,7 +47,7 @@ declare class SessionList implements Component, Focusable {
46
47
  private buildTreePrefix;
47
48
  handleInput(keyData: string): void;
48
49
  }
49
- type SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;
50
+ type SessionsLoader = (onProgress?: SessionListProgress, signal?: AbortSignal) => Promise<SessionInfo[]>;
50
51
  /**
51
52
  * Component that renders a session selector
52
53
  */
@@ -65,9 +66,8 @@ export declare class SessionSelectorComponent extends Container implements Focus
65
66
  private allSessionsLoader;
66
67
  private requestRender;
67
68
  private renameSession?;
68
- private currentLoading;
69
- private allLoading;
70
- private allLoadSeq;
69
+ private currentLoad;
70
+ private allLoad;
71
71
  private mode;
72
72
  private renameInput;
73
73
  private renameTargetPath;
@@ -80,7 +80,7 @@ export declare class SessionSelectorComponent extends Container implements Focus
80
80
  showRenameHint?: boolean;
81
81
  keybindings?: KeybindingsManager;
82
82
  }, currentSessionFilePath?: string);
83
- private loadCurrentSessions;
83
+ private cancelLoads;
84
84
  private enterRenameMode;
85
85
  private exitRenameMode;
86
86
  private confirmRename;