@selesai/code 0.13.26 → 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.
- package/CHANGELOG.md +74 -0
- package/dist/cli/args.js +1 -0
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/core/agent-session-auto-handoff.test.js +2 -1
- package/dist/core/agent-session-length-continuation.test.js +2 -1
- package/dist/core/agent-session.d.ts +58 -7
- package/dist/core/agent-session.js +334 -167
- package/dist/core/bug-report-upload.d.ts +11 -0
- package/dist/core/bug-report-upload.js +21 -0
- package/dist/core/bug-report.d.ts +175 -0
- package/dist/core/bug-report.js +291 -0
- package/dist/core/cache-stats.js +12 -1
- package/dist/core/cache-warmer.d.ts +101 -0
- package/dist/core/cache-warmer.js +341 -0
- package/dist/core/compaction/branch-summarization.js +2 -2
- package/dist/core/compaction/compaction.d.ts +6 -5
- package/dist/core/compaction/compaction.js +49 -42
- package/dist/core/crash-log.d.ts +21 -0
- package/dist/core/crash-log.js +70 -0
- package/dist/core/experimental.d.ts +0 -4
- package/dist/core/experimental.js +0 -4
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/jiti-loader.d.ts +1 -0
- package/dist/core/extensions/jiti-loader.js +3 -0
- package/dist/core/extensions/jiti-static-loader.d.ts +1 -0
- package/dist/core/extensions/jiti-static-loader.js +3 -0
- package/dist/core/extensions/loader.js +39 -49
- package/dist/core/extensions/runner.d.ts +10 -7
- package/dist/core/extensions/runner.js +85 -67
- package/dist/core/extensions/types.d.ts +66 -52
- package/dist/core/extensions/virtual-modules.d.ts +2 -0
- package/dist/core/extensions/virtual-modules.js +39 -0
- package/dist/core/extensions/wrapper.js +1 -20
- package/dist/core/index.d.ts +2 -1
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.js +1 -1
- package/dist/core/messages.js +1 -0
- package/dist/core/model-config.d.ts +101 -20
- package/dist/core/model-config.js +25 -18
- package/dist/core/model-resolver.js +2 -1
- package/dist/core/model-runtime.js +5 -3
- package/dist/core/provider-composer.d.ts +7 -2
- package/dist/core/provider-composer.js +10 -5
- package/dist/core/radius.d.ts +3 -0
- package/dist/core/radius.js +6 -0
- package/dist/core/sdk.js +60 -38
- package/dist/core/session-export.d.ts +6 -2
- package/dist/core/session-export.js +14 -13
- package/dist/core/session-format.test.d.ts +1 -0
- package/dist/core/session-format.test.js +96 -0
- package/dist/core/session-manager.d.ts +29 -6
- package/dist/core/session-manager.js +159 -96
- package/dist/core/settings-manager.d.ts +22 -5
- package/dist/core/settings-manager.js +56 -23
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/system-prompt.d.ts +49 -4
- package/dist/core/system-prompt.js +155 -102
- package/dist/core/system-prompt.test.js +61 -23
- package/dist/core/tools/bash.d.ts +2 -1
- package/dist/core/tools/bash.js +10 -4
- package/dist/core/tools/edit.js +1 -2
- package/dist/core/tools/read.js +1 -2
- package/dist/core/tools/renderers/bash.js +9 -1
- package/dist/core/tools/write.js +1 -2
- package/dist/core/usage-totals.d.ts +1 -1
- package/dist/core/usage-totals.js +5 -1
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +14 -6
- package/dist/extensions/pi-web-agent/src/extension.ts +3 -1
- package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +7 -3
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +3 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +2 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +4 -2
- package/dist/extensions/pi-web-agent/src/search/tokenin.ts +4 -1
- package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +2 -1
- package/dist/extensions/pi-web-agent/src/types.ts +4 -0
- package/dist/index.d.ts +3 -2
- package/dist/main.js +13 -9
- package/dist/migrations.js +2 -2
- package/dist/modes/interactive/bug-report.d.ts +15 -0
- package/dist/modes/interactive/bug-report.js +205 -0
- package/dist/modes/interactive/chat-viewport.js +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +12 -5
- package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
- package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
- package/dist/modes/interactive/components/extension-editor.js +7 -2
- package/dist/modes/interactive/components/extension-input.d.ts +2 -0
- package/dist/modes/interactive/components/extension-input.js +6 -0
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/session-selector.d.ts +5 -5
- package/dist/modes/interactive/components/session-selector.js +74 -48
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.js +11 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
- package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
- package/dist/modes/interactive/components/status-indicator.js +7 -7
- package/dist/modes/interactive/components/tool-execution.js +18 -9
- package/dist/modes/interactive/components/tree-selector.js +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts +15 -2
- package/dist/modes/interactive/interactive-mode.js +257 -81
- package/dist/modes/interactive/session-share.d.ts +2 -0
- package/dist/modes/interactive/session-share.js +8 -4
- package/dist/modes/interactive/tui-renderer.js +2 -2
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/utils/clipboard-command.d.ts +6 -0
- package/dist/utils/clipboard-command.js +44 -0
- package/dist/utils/clipboard-image.js +55 -89
- package/dist/utils/clipboard.d.ts +1 -1
- package/dist/utils/clipboard.js +118 -108
- package/dist/utils/wsl.d.ts +2 -0
- package/dist/utils/wsl.js +14 -0
- package/dist/utils/zip.d.ts +6 -0
- package/dist/utils/zip.js +59 -0
- package/package.json +9 -9
- package/dist/utils/clipboard-native.d.ts +0 -10
- package/dist/utils/clipboard-native.js +0 -19
|
@@ -28,23 +28,27 @@ function joinReasons(reasons: string[]) {
|
|
|
28
28
|
return `${reasons.slice(0, -1).join(', ')}, and ${reasons.at(-1)}`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function caveatText(partial: boolean, caveatReasons: EvidenceCaveatReason[] = []) {
|
|
31
|
+
function caveatText(partial: boolean, caveatReasons: EvidenceCaveatReason[] = [], failureReason?: string) {
|
|
32
32
|
if (!partial) return undefined;
|
|
33
33
|
const specificReasons = caveatReasons.map(sentenceForReason);
|
|
34
34
|
if (specificReasons.length > 0) {
|
|
35
35
|
return `Evidence is partial: ${joinReasons(specificReasons)}.`;
|
|
36
36
|
}
|
|
37
|
+
// With nothing to show, the budget line is a cover-up: say what actually failed.
|
|
38
|
+
if (failureReason) return `No usable evidence: ${failureReason}`;
|
|
37
39
|
return 'Evidence is partial, so this answer is based on the strongest source found within the bounded research budget.';
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export function synthesizeAnswer({
|
|
41
43
|
evidence,
|
|
42
44
|
partial,
|
|
43
|
-
caveatReasons = []
|
|
45
|
+
caveatReasons = [],
|
|
46
|
+
failureReason
|
|
44
47
|
}: {
|
|
45
48
|
evidence: ResearchEvidence[];
|
|
46
49
|
partial: boolean;
|
|
47
50
|
caveatReasons?: EvidenceCaveatReason[];
|
|
51
|
+
failureReason?: string;
|
|
48
52
|
}) {
|
|
49
53
|
const findings = evidence.slice(0, 5).map((item) => {
|
|
50
54
|
const summary = normalizeSummary(item.summary);
|
|
@@ -55,6 +59,6 @@ export function synthesizeAnswer({
|
|
|
55
59
|
|
|
56
60
|
return {
|
|
57
61
|
findings,
|
|
58
|
-
caveat: caveatText(partial, caveatReasons)
|
|
62
|
+
caveat: caveatText(partial, caveatReasons, failureReason)
|
|
59
63
|
};
|
|
60
64
|
}
|
|
@@ -136,6 +136,9 @@ function buildMetadata({
|
|
|
136
136
|
headlessAttempts,
|
|
137
137
|
exhaustedBudget,
|
|
138
138
|
caveatReasons,
|
|
139
|
+
// The first gap is why the run came up dry; it was collected and then thrown away, which
|
|
140
|
+
// is how a DNS-blocked DuckDuckGo surfaced as a contentless "No usable evidence found".
|
|
141
|
+
failureReason: allEvidence.length === 0 ? allGaps[0]?.message : undefined,
|
|
139
142
|
fanoutProviders,
|
|
140
143
|
fanoutSkipped
|
|
141
144
|
};
|
|
@@ -48,6 +48,8 @@ export type ResearchRunMetadata = {
|
|
|
48
48
|
fetchedPages: number;
|
|
49
49
|
headlessAttempts: number;
|
|
50
50
|
exhaustedBudget: boolean;
|
|
51
|
+
caveatReasons?: string[];
|
|
52
|
+
failureReason?: string;
|
|
51
53
|
fanoutProviders?: SearchProviderName[];
|
|
52
54
|
fanoutSkipped?: SearchProviderName[];
|
|
53
55
|
};
|
|
@@ -34,7 +34,7 @@ export function buildExplorePresentation(result: WebExploreResponse): Presentati
|
|
|
34
34
|
const hasEvidence = result.findings.length > 0 || result.sources.length > 0;
|
|
35
35
|
const evidenceLines = hasEvidence
|
|
36
36
|
? result.findings.map((finding, index) => `- [${internalReaderLabel(result.sources[index]?.method)}] ${finding}`)
|
|
37
|
-
: ['No usable evidence found.'];
|
|
37
|
+
: [result.metadata?.failureReason ? `No usable evidence: ${result.metadata.failureReason}` : 'No usable evidence found.'];
|
|
38
38
|
const preview = [
|
|
39
39
|
...evidenceLines,
|
|
40
40
|
internalSummary ? `\n${internalSummary}` : undefined
|
|
@@ -55,7 +55,9 @@ export function buildExplorePresentation(result: WebExploreResponse): Presentati
|
|
|
55
55
|
|
|
56
56
|
const compact = hasEvidence
|
|
57
57
|
? `Reviewed ${result.sources.length} sources · synthesized answer with ${result.findings.length} findings`
|
|
58
|
-
:
|
|
58
|
+
: result.metadata?.failureReason
|
|
59
|
+
? `No usable evidence: ${result.metadata.failureReason}`
|
|
60
|
+
: 'No usable evidence found';
|
|
59
61
|
|
|
60
62
|
return {
|
|
61
63
|
mode: 'compact',
|
|
@@ -6,7 +6,10 @@ import type { SearchResult, WebSearchResponse } from '../types.js';
|
|
|
6
6
|
|
|
7
7
|
export const TOKENIN_SEARCH_TOOL_NAME = 'firecrawl';
|
|
8
8
|
export const TOKENIN_DEFAULT_BASE_URL = 'https://lite.andlet.me/v1';
|
|
9
|
-
|
|
9
|
+
// ponytail: measured 20-36s per query through the LiteLLM proxy, so the old 10s ceiling
|
|
10
|
+
// aborted every search and quietly punted to the fallback chain. Ceiling: a dead proxy now
|
|
11
|
+
// costs 45s before falling back. Lower it if your proxy is local; raise it if still timing out.
|
|
12
|
+
const TOKENIN_SEARCH_TIMEOUT_MS = 45_000;
|
|
10
13
|
|
|
11
14
|
type TokenInAccount = { id: string; label: string; apiKey: string; baseUrl?: string };
|
|
12
15
|
type TokenInAuth = { accounts: TokenInAccount[]; activeId: string | null };
|
|
@@ -52,7 +52,8 @@ export function createWebExploreTool({
|
|
|
52
52
|
const synthesized = synthesizeAnswer({
|
|
53
53
|
evidence: result.evidence,
|
|
54
54
|
partial: result.decision.action !== 'answer',
|
|
55
|
-
caveatReasons: result.metadata?.caveatReasons as EvidenceCaveatReason[] | undefined
|
|
55
|
+
caveatReasons: result.metadata?.caveatReasons as EvidenceCaveatReason[] | undefined,
|
|
56
|
+
failureReason: result.metadata?.failureReason
|
|
56
57
|
});
|
|
57
58
|
|
|
58
59
|
const shaped: WebExploreResponse = {
|
|
@@ -95,6 +95,10 @@ export type WebExploreResponse = {
|
|
|
95
95
|
headlessAttempts: number;
|
|
96
96
|
exhaustedBudget: boolean;
|
|
97
97
|
caveatReasons?: string[];
|
|
98
|
+
// Why nothing came back, when nothing came back. Carried from the research pass's first
|
|
99
|
+
// gap so the empty result reads as "DuckDuckGo search request failed: fetch failed"
|
|
100
|
+
// instead of a bare "No usable evidence found".
|
|
101
|
+
failureReason?: string;
|
|
98
102
|
fanoutProviders?: SearchProviderName[];
|
|
99
103
|
fanoutSkipped?: SearchProviderName[];
|
|
100
104
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,11 @@ export { type Args, parseArgs } from "./cli/args.ts";
|
|
|
2
2
|
export { CONFIG_DIR_NAME, getAgentDir, getDocsPath, getModelsPath, getSettingsPath, getExamplesPath, getPackageDir, getReadmePath, VERSION, } from "./config.ts";
|
|
3
3
|
export { AgentSession, type AgentSessionConfig, type AgentSessionEvent, type AgentSessionEventListener, type ModelCycleResult, type ParsedSkillBlock, type ParsedSkillBlocks, type PromptOptions, parseSkillBlock, parseSkillBlocks, type SessionStats, } from "./core/agent-session.ts";
|
|
4
4
|
export { readStoredCredential } from "./core/auth-storage.ts";
|
|
5
|
+
export type { CacheWarmingDecision, CacheWarmingStatus } from "./core/cache-warmer.ts";
|
|
5
6
|
export { ensureTool, getToolPath, type ManagedTool } from "./utils/tools-manager.ts";
|
|
6
7
|
export { type BranchPreparation, type BranchSummaryResult, type CollectEntriesResult, type CompactionResult, type CutPointResult, calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, type FileOperations, findCutPoint, findTurnStartIndex, type GenerateBranchSummaryOptions, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.ts";
|
|
7
8
|
export { createEventBus, type EventBus, type EventBusController } from "./core/event-bus.ts";
|
|
8
|
-
export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CompactOptions, ContextEvent, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, ResolvedSkillInfo, SessionBeforeCompactEvent, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UIPromptEndEvent, UIPromptKind, UIPromptStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
|
|
9
|
+
export type { AfterProviderResponseEvent, AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CacheWarmingDecisionEvent, CacheWarmingDecisionEventResult, CompactOptions, ContextEvent, ContextEventResult, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MessageEndEvent, MessageEndEventResult, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ModelSelectEvent, ModelSelectSource, NormalizedBuildSystemPromptOptions, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, ResolvedSkillInfo, ResourcesDiscoverEvent, ResourcesDiscoverResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeForkEvent, SessionBeforeForkResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionCompactEvent, SessionCompactFailedEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ThinkingLevelSelectEvent, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TurnEndEvent, TurnStartEvent, UIPromptEndEvent, UIPromptKind, UIPromptStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
|
|
9
10
|
export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts";
|
|
10
11
|
export type { ReadonlyFooterDataProvider } from "./core/footer-data-provider.ts";
|
|
11
12
|
export { convertToLlm } from "./core/messages.ts";
|
|
@@ -18,7 +19,7 @@ export type { ResourceCollision, ResourceDiagnostic, ResourceLoader } from "./co
|
|
|
18
19
|
export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.ts";
|
|
19
20
|
export { AgentSessionRuntime, type AgentSessionRuntimeDiagnostic, type AgentSessionServices, type CreateAgentSessionFromServicesOptions, type CreateAgentSessionOptions, type CreateAgentSessionResult, type CreateAgentSessionRuntimeFactory, type CreateAgentSessionRuntimeResult, type CreateAgentSessionServicesOptions, createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, type PromptTemplate, } from "./core/sdk.ts";
|
|
20
21
|
export { type BranchSummaryEntry, buildContextEntries, buildSessionContext, type CompactionEntry, CURRENT_SESSION_VERSION, type CustomEntry, type CustomMessageEntry, type FileEntry, getLatestCompactionEntry, type ModelChangeEntry, migrateSessionEntries, type NewSessionOptions, parseSessionEntries, type SessionContext, type SessionEntry, type SessionEntryBase, type SessionHeader, type SessionInfo, type SessionInfoEntry, SessionManager, type SessionMessageEntry, type SessionTreeNode, sessionEntryToContextMessages, type ThinkingLevelChangeEntry, } from "./core/session-manager.ts";
|
|
21
|
-
export { type AutoHandoffSettings, type CompactionSettings, type DefaultProjectTrust, type FullscreenExitOutput, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
|
|
22
|
+
export { type AutoHandoffSettings, type CacheWarmingMode, type CompactionModelOverride, type CompactionSettings, type DefaultProjectTrust, type FullscreenExitOutput, type ImageSettings, type PackageSource, type RetrySettings, SettingsManager, type SettingsManagerCreateOptions, type TuiMode, } from "./core/settings-manager.ts";
|
|
22
23
|
export { formatSkillsForPrompt, type LoadSkillsFromDirOptions, type LoadSkillsResult, loadSkills, loadSkillsFromDir, type Skill, type SkillFrontmatter, } from "./core/skills.ts";
|
|
23
24
|
export { createSyntheticSourceInfo } from "./core/source-info.ts";
|
|
24
25
|
export { type EditDiffResult, generateDiffString, generateUnifiedPatch } from "./core/tools/edit-diff.ts";
|
package/dist/main.js
CHANGED
|
@@ -151,19 +151,23 @@ async function prepareInitialMessage(parsed, autoResizeImages, stdinContent) {
|
|
|
151
151
|
* Resolve a session argument to a file path.
|
|
152
152
|
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
|
|
153
153
|
*/
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
return localMatch ? { type: "local", path: localMatch.path } : undefined;
|
|
154
|
+
function findLocalSessionByExactId(sessionId, cwd, sessionDir) {
|
|
155
|
+
const path = SessionManager.findById(cwd, sessionId, sessionDir);
|
|
156
|
+
return path ? { type: "local", path } : undefined;
|
|
158
157
|
}
|
|
159
158
|
async function resolveSessionPath(sessionArg, cwd, sessionDir) {
|
|
160
159
|
// If it looks like a file path, resolve it before handing it to the session manager.
|
|
161
160
|
if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
|
|
162
161
|
return { type: "path", path: resolvePath(sessionArg, cwd) };
|
|
163
162
|
}
|
|
164
|
-
//
|
|
163
|
+
// Exact IDs only require reading session headers. Fall back to the full
|
|
164
|
+
// metadata listing for prefix matches.
|
|
165
|
+
const exactLocalMatch = findLocalSessionByExactId(sessionArg, cwd, sessionDir);
|
|
166
|
+
if (exactLocalMatch) {
|
|
167
|
+
return exactLocalMatch;
|
|
168
|
+
}
|
|
165
169
|
const localSessions = await SessionManager.list(cwd, sessionDir);
|
|
166
|
-
const localMatch = localSessions.find((s) => s.id
|
|
170
|
+
const localMatch = localSessions.find((s) => s.id.startsWith(sessionArg));
|
|
167
171
|
if (localMatch) {
|
|
168
172
|
return { type: "local", path: localMatch.path };
|
|
169
173
|
}
|
|
@@ -250,7 +254,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
|
250
254
|
}
|
|
251
255
|
if (parsed.fork) {
|
|
252
256
|
if (parsed.sessionId) {
|
|
253
|
-
const existingTarget =
|
|
257
|
+
const existingTarget = findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
|
|
254
258
|
if (existingTarget) {
|
|
255
259
|
console.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));
|
|
256
260
|
process.exit(1);
|
|
@@ -289,7 +293,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
|
289
293
|
}
|
|
290
294
|
if (parsed.resume) {
|
|
291
295
|
try {
|
|
292
|
-
const selectedPath = await selectSession((onProgress) => SessionManager.list(cwd, sessionDir, onProgress), (onProgress) => SessionManager.listAll(sessionDir, onProgress), settingsManager);
|
|
296
|
+
const selectedPath = await selectSession((onProgress, signal) => SessionManager.list(cwd, sessionDir, onProgress, signal), (onProgress, signal) => SessionManager.listAll(sessionDir, onProgress, signal), settingsManager);
|
|
293
297
|
if (!selectedPath) {
|
|
294
298
|
console.log(chalk.dim("No session selected"));
|
|
295
299
|
process.exit(0);
|
|
@@ -304,7 +308,7 @@ async function createSessionManager(parsed, cwd, sessionDir, settingsManager) {
|
|
|
304
308
|
return SessionManager.continueRecent(cwd, sessionDir);
|
|
305
309
|
}
|
|
306
310
|
if (parsed.sessionId) {
|
|
307
|
-
const existingSession =
|
|
311
|
+
const existingSession = findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
|
|
308
312
|
if (existingSession) {
|
|
309
313
|
return SessionManager.open(existingSession.path, sessionDir);
|
|
310
314
|
}
|
package/dist/migrations.js
CHANGED
|
@@ -6,8 +6,8 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, w
|
|
|
6
6
|
import { dirname, join } from "path";
|
|
7
7
|
import { CONFIG_DIR_NAME, getAgentDir, getBinDir } from "./config.js";
|
|
8
8
|
import { migrateKeybindingsConfig } from "./core/keybindings.js";
|
|
9
|
-
const MIGRATION_GUIDE_URL = "https://github.com/earendil-works/pi
|
|
10
|
-
const EXTENSIONS_DOC_URL = "https://github.com/earendil-works/pi
|
|
9
|
+
const MIGRATION_GUIDE_URL = "https://github.com/earendil-works/pi/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
|
|
10
|
+
const EXTENSIONS_DOC_URL = "https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md";
|
|
11
11
|
/**
|
|
12
12
|
* Migrate legacy oauth.json and settings.json apiKeys to auth.json.
|
|
13
13
|
*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Container, EditorComponent, TUI } from "@earendil-works/pi-tui";
|
|
2
|
+
import type { AgentSession } from "../../core/agent-session.ts";
|
|
3
|
+
import type { KeybindingsManager } from "../../core/keybindings.ts";
|
|
4
|
+
interface BugReportContext {
|
|
5
|
+
session: AgentSession;
|
|
6
|
+
ui: TUI;
|
|
7
|
+
editorContainer: Container;
|
|
8
|
+
editor: EditorComponent;
|
|
9
|
+
keybindings: KeybindingsManager;
|
|
10
|
+
showStatus: (message: string) => void;
|
|
11
|
+
showError: (message: string) => void;
|
|
12
|
+
}
|
|
13
|
+
/** Run the `/bug` flow: consent, optional summary, then upload or export. */
|
|
14
|
+
export declare function reportBug(context: BugReportContext, initialHint?: string): Promise<void>;
|
|
15
|
+
export {};
|
|
@@ -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:
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
3
|
+
import type { StatusIndicator } from "./status-indicator.ts";
|
|
4
4
|
export type CustomEditorOptions = EditorOptions & {
|
|
5
|
-
/** Render
|
|
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:
|
|
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?:
|
|
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(),
|
|
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));
|
|
@@ -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());
|