@oh-my-pi/pi-coding-agent 16.0.5 → 16.0.6
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 +53 -0
- package/dist/cli.js +1927 -1376
- package/dist/types/advisor/advise-tool.d.ts +22 -19
- package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
- package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
- package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
- package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
- package/dist/types/cli/ttsr-cli.d.ts +39 -0
- package/dist/types/commands/ttsr.d.ts +57 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
- package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
- package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
- package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
- package/dist/types/commit/changelog/generate.d.ts +12 -13
- package/dist/types/commit/shared-llm.d.ts +10 -37
- package/dist/types/config/config-file.d.ts +4 -4
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +625 -990
- package/dist/types/config/models-config.d.ts +229 -217
- package/dist/types/config/settings-schema.d.ts +53 -23
- package/dist/types/edit/hashline/params.d.ts +7 -11
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/edit/modes/apply-patch.d.ts +4 -5
- package/dist/types/edit/modes/patch.d.ts +15 -24
- package/dist/types/edit/modes/replace.d.ts +16 -17
- package/dist/types/eval/js/index.d.ts +1 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
- package/dist/types/extensibility/extensions/types.d.ts +6 -3
- package/dist/types/extensibility/hooks/types.d.ts +7 -4
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
- package/dist/types/extensibility/typebox.d.ts +80 -58
- package/dist/types/goals/tools/goal-tool.d.ts +11 -24
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lsp/index.d.ts +11 -26
- package/dist/types/lsp/types.d.ts +12 -28
- package/dist/types/mcp/client.d.ts +8 -0
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -1
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/setup-wizard/index.d.ts +1 -0
- package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/startup-splash.d.ts +12 -0
- package/dist/types/task/types.d.ts +47 -48
- package/dist/types/tools/ask.d.ts +26 -27
- package/dist/types/tools/ast-edit.d.ts +17 -17
- package/dist/types/tools/ast-grep.d.ts +12 -13
- package/dist/types/tools/bash.d.ts +20 -17
- package/dist/types/tools/browser.d.ts +46 -71
- package/dist/types/tools/checkpoint.d.ts +14 -15
- package/dist/types/tools/debug.d.ts +82 -145
- package/dist/types/tools/eval.d.ts +30 -40
- package/dist/types/tools/find.d.ts +17 -18
- package/dist/types/tools/gh.d.ts +49 -78
- package/dist/types/tools/image-gen.d.ts +20 -36
- package/dist/types/tools/inspect-image.d.ts +10 -11
- package/dist/types/tools/irc.d.ts +22 -33
- package/dist/types/tools/job.d.ts +11 -12
- package/dist/types/tools/learn.d.ts +21 -28
- package/dist/types/tools/manage-skill.d.ts +13 -22
- package/dist/types/tools/memory-edit.d.ts +15 -24
- package/dist/types/tools/memory-recall.d.ts +7 -8
- package/dist/types/tools/memory-reflect.d.ts +9 -10
- package/dist/types/tools/memory-retain.d.ts +13 -14
- package/dist/types/tools/read.d.ts +7 -8
- package/dist/types/tools/resolve.d.ts +11 -18
- package/dist/types/tools/review.d.ts +9 -15
- package/dist/types/tools/search-tool-bm25.d.ts +9 -10
- package/dist/types/tools/search.d.ts +16 -17
- package/dist/types/tools/ssh.d.ts +14 -15
- package/dist/types/tools/todo.d.ts +27 -43
- package/dist/types/tools/tts.d.ts +8 -9
- package/dist/types/tools/write.d.ts +9 -10
- package/dist/types/tui/index.d.ts +1 -0
- package/dist/types/tui/width-aware-text.d.ts +23 -0
- package/dist/types/utils/markit.d.ts +10 -1
- package/dist/types/web/search/index.d.ts +17 -28
- package/dist/types/web/search/providers/perplexity.d.ts +0 -2
- package/dist/types/web/search/types.d.ts +32 -26
- package/package.json +14 -13
- package/scripts/omp +1 -1
- package/src/advisor/__tests__/advisor.test.ts +44 -1
- package/src/advisor/advise-tool.ts +34 -11
- package/src/autoresearch/tools/init-experiment.ts +13 -16
- package/src/autoresearch/tools/log-experiment.ts +15 -18
- package/src/autoresearch/tools/run-experiment.ts +3 -3
- package/src/autoresearch/tools/update-notes.ts +4 -4
- package/src/cli/ttsr-cli.ts +995 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +7 -1
- package/src/commands/ttsr.ts +125 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -4
- package/src/commit/agentic/tools/git-file-diff.ts +4 -4
- package/src/commit/agentic/tools/git-hunk.ts +7 -5
- package/src/commit/agentic/tools/git-overview.ts +4 -4
- package/src/commit/agentic/tools/propose-changelog.ts +18 -15
- package/src/commit/agentic/tools/propose-commit.ts +6 -6
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/schemas.ts +8 -20
- package/src/commit/agentic/tools/split-commit.ts +19 -23
- package/src/commit/analysis/summary.ts +7 -5
- package/src/commit/changelog/generate.ts +15 -11
- package/src/commit/shared-llm.ts +17 -24
- package/src/config/config-file.ts +13 -15
- package/src/config/keybindings.ts +6 -0
- package/src/config/models-config-schema.ts +206 -179
- package/src/config/settings-schema.ts +34 -0
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-import-type.md +2 -2
- package/src/discovery/builtin-rules/ts-no-any.md +11 -2
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
- package/src/edit/hashline/params.ts +12 -11
- package/src/edit/index.ts +5 -4
- package/src/edit/modes/apply-patch.ts +4 -4
- package/src/edit/modes/patch.ts +15 -18
- package/src/edit/modes/replace.ts +13 -17
- package/src/edit/renderer.ts +0 -1
- package/src/eval/agent-bridge.ts +11 -13
- package/src/eval/completion-bridge.ts +25 -17
- package/src/eval/js/context-manager.ts +17 -2
- package/src/eval/js/index.ts +1 -1
- package/src/eval/py/executor.ts +2 -2
- package/src/extensibility/custom-commands/loader.ts +5 -3
- package/src/extensibility/custom-commands/types.ts +6 -3
- package/src/extensibility/custom-tools/loader.ts +4 -2
- package/src/extensibility/custom-tools/types.ts +8 -5
- package/src/extensibility/extensions/loader.ts +4 -2
- package/src/extensibility/extensions/types.ts +6 -3
- package/src/extensibility/hooks/loader.ts +5 -2
- package/src/extensibility/hooks/types.ts +7 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
- package/src/extensibility/tool-proxy.ts +4 -1
- package/src/extensibility/typebox.ts +778 -251
- package/src/goals/guided-setup.ts +12 -3
- package/src/goals/tools/goal-tool.ts +6 -6
- package/src/index.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +11 -9
- package/src/lsp/types.ts +13 -27
- package/src/main.ts +19 -18
- package/src/mcp/client.ts +38 -13
- package/src/mcp/render.ts +102 -89
- package/src/modes/components/agent-hub.ts +11 -4
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/custom-editor.ts +18 -0
- package/src/modes/components/status-line/component.ts +8 -1
- package/src/modes/components/tool-execution.ts +17 -10
- package/src/modes/controllers/btw-controller.ts +69 -1
- package/src/modes/controllers/input-controller.ts +29 -0
- package/src/modes/interactive-mode.ts +38 -8
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/theme/theme.ts +133 -143
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/context-usage.ts +9 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk.ts +21 -4
- package/src/session/agent-session.ts +160 -33
- package/src/session/session-history-format.ts +11 -2
- package/src/session/snapcompact-inline.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +4 -11
- package/src/startup-splash.ts +19 -0
- package/src/task/executor.ts +11 -6
- package/src/task/types.ts +44 -41
- package/src/tool-discovery/tool-index.ts +17 -4
- package/src/tools/ask.ts +14 -14
- package/src/tools/ast-edit.ts +17 -14
- package/src/tools/ast-grep.ts +10 -9
- package/src/tools/bash.ts +15 -10
- package/src/tools/browser/launch.ts +13 -0
- package/src/tools/browser.ts +26 -32
- package/src/tools/checkpoint.ts +7 -7
- package/src/tools/debug.ts +72 -69
- package/src/tools/eval.ts +18 -19
- package/src/tools/find.ts +20 -13
- package/src/tools/gh.ts +29 -49
- package/src/tools/image-gen.ts +27 -32
- package/src/tools/inspect-image.ts +8 -9
- package/src/tools/irc.ts +12 -12
- package/src/tools/job.ts +6 -6
- package/src/tools/learn.ts +11 -14
- package/src/tools/manage-skill.ts +19 -23
- package/src/tools/memory-edit.ts +8 -8
- package/src/tools/memory-recall.ts +4 -4
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +9 -11
- package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
- package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
- package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
- package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
- package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
- package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
- package/src/tools/read.ts +169 -13
- package/src/tools/report-tool-issue.ts +6 -6
- package/src/tools/resolve.ts +6 -6
- package/src/tools/review.ts +10 -12
- package/src/tools/search-tool-bm25.ts +5 -5
- package/src/tools/search.ts +20 -29
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +16 -19
- package/src/tools/tts.ts +16 -15
- package/src/tools/write.ts +5 -5
- package/src/tui/index.ts +1 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/markit.ts +17 -2
- package/src/web/search/index.ts +9 -9
- package/src/web/search/providers/perplexity.ts +373 -126
- package/src/web/search/types.ts +28 -48
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
35
35
|
import { toolRenderers } from "../../tools/renderers";
|
|
36
36
|
import { TODO_STRIKE_TOTAL_FRAMES } from "../../tools/todo";
|
|
37
|
-
import { isFramedBlockComponent, renderStatusLine } from "../../tui";
|
|
37
|
+
import { isFramedBlockComponent, renderStatusLine, WidthAwareText } from "../../tui";
|
|
38
38
|
import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
39
39
|
import { renderDiff } from "./diff";
|
|
40
40
|
|
|
@@ -169,7 +169,7 @@ let toolExecutionInstanceSeq = 0;
|
|
|
169
169
|
*/
|
|
170
170
|
export class ToolExecutionComponent extends Container implements NativeScrollbackLiveRegion {
|
|
171
171
|
#contentBox: Box; // Used for custom tools and bash visual truncation
|
|
172
|
-
#contentText:
|
|
172
|
+
#contentText: WidthAwareText; // Generic fallback (no custom/built-in renderer)
|
|
173
173
|
#multiFileBoxes: (Box | Spacer)[] = []; // Extra boxes for multi-file edit results
|
|
174
174
|
#imageComponents: Image[] = [];
|
|
175
175
|
#imageSpacers: Spacer[] = [];
|
|
@@ -272,6 +272,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
272
272
|
this.#ui = ui;
|
|
273
273
|
this.#cwd = cwd;
|
|
274
274
|
this.#args = args;
|
|
275
|
+
this.#editMode = resolveEditModeForTool(toolName, tool);
|
|
275
276
|
|
|
276
277
|
// Always create both - contentBox for custom tools/bash/tools with renderers, contentText for other built-ins.
|
|
277
278
|
// paddingY is 1 so background-tinted blocks (custom/extension tools and the
|
|
@@ -279,7 +280,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
279
280
|
// strips PLAIN-blank edges, so framed/minimal blocks (no bg set) drop these
|
|
280
281
|
// lines and keep their tight spacing — only tinted lines survive.
|
|
281
282
|
this.#contentBox = new Box(0, 1);
|
|
282
|
-
this.#contentText = new
|
|
283
|
+
this.#contentText = new WidthAwareText(contentWidth => this.#formatToolExecution(contentWidth), 1, 1);
|
|
283
284
|
|
|
284
285
|
// Use Box for custom tools or built-in tools that have renderers
|
|
285
286
|
const hasRenderer = toolName in toolRenderers;
|
|
@@ -289,8 +290,9 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
289
290
|
} else {
|
|
290
291
|
this.addChild(this.#contentText);
|
|
291
292
|
}
|
|
292
|
-
|
|
293
|
-
|
|
293
|
+
// Tool blocks are visually distinct cards (background-tinted or framed),
|
|
294
|
+
// so keep their horizontal padding even when the user enables tight layout.
|
|
295
|
+
this.setIgnoreTight(true);
|
|
294
296
|
|
|
295
297
|
this.#updateDisplay();
|
|
296
298
|
this.#editDiffInFlight = this.#runPreviewDiff();
|
|
@@ -922,9 +924,11 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
922
924
|
}
|
|
923
925
|
}
|
|
924
926
|
} else {
|
|
925
|
-
//
|
|
927
|
+
// Generic fallback (no custom/built-in renderer). WidthAwareText
|
|
928
|
+
// reformats at render time so output fills the actual terminal width
|
|
929
|
+
// instead of a fixed column cap.
|
|
926
930
|
this.#contentText.setCustomBgFn(stateBgFn);
|
|
927
|
-
this.#contentText.
|
|
931
|
+
this.#contentText.invalidate();
|
|
928
932
|
}
|
|
929
933
|
|
|
930
934
|
// Handle images (same for both custom and built-in)
|
|
@@ -1076,14 +1080,17 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
1076
1080
|
/**
|
|
1077
1081
|
* Format a generic tool execution (fallback for tools without custom renderers)
|
|
1078
1082
|
*/
|
|
1079
|
-
#formatToolExecution(): string {
|
|
1083
|
+
#formatToolExecution(contentWidth: number): string {
|
|
1080
1084
|
const lines: string[] = [];
|
|
1081
1085
|
const icon = this.#isPartial ? "pending" : this.#result?.isError ? "error" : "done";
|
|
1082
1086
|
lines.push(renderStatusLine({ icon, title: this.#toolLabel }, theme));
|
|
1083
1087
|
|
|
1084
1088
|
const argsObject = this.#args && typeof this.#args === "object" ? (this.#args as Record<string, unknown>) : null;
|
|
1085
1089
|
if (!this.#expanded && argsObject && Object.keys(argsObject).length > 0) {
|
|
1086
|
-
|
|
1090
|
+
// Budget the inline preview against the render width, leaving room for
|
|
1091
|
+
// the ` └─ ` connector prefix instead of a fixed cap.
|
|
1092
|
+
const inlineBudget = Math.max(20, contentWidth - Bun.stringWidth(theme.tree.last) - 2);
|
|
1093
|
+
const preview = formatArgsInline(argsObject, inlineBudget);
|
|
1087
1094
|
if (preview) {
|
|
1088
1095
|
lines.push(` ${theme.fg("dim", theme.tree.last)} ${theme.fg("dim", preview)}`);
|
|
1089
1096
|
}
|
|
@@ -1143,7 +1150,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
1143
1150
|
const displayLines = outputLines.slice(0, maxOutputLines);
|
|
1144
1151
|
|
|
1145
1152
|
for (const line of displayLines) {
|
|
1146
|
-
lines.push(theme.fg("toolOutput", truncateToWidth(replaceTabs(line),
|
|
1153
|
+
lines.push(theme.fg("toolOutput", truncateToWidth(replaceTabs(line), contentWidth)));
|
|
1147
1154
|
}
|
|
1148
1155
|
|
|
1149
1156
|
if (outputLines.length > maxOutputLines) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
1
2
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import btwUserPrompt from "../../prompts/system/btw-user.md" with { type: "text" };
|
|
3
4
|
import { BtwPanelComponent } from "../components/btw-panel";
|
|
@@ -7,10 +8,37 @@ interface BtwRequest {
|
|
|
7
8
|
component: BtwPanelComponent;
|
|
8
9
|
abortController: AbortController;
|
|
9
10
|
question: string;
|
|
11
|
+
leafId: string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function assistantMessageWithReplyText(assistantMessage: AssistantMessage, replyText: string): AssistantMessage {
|
|
15
|
+
const content: AssistantMessage["content"] = [];
|
|
16
|
+
let replacedText = false;
|
|
17
|
+
for (const part of assistantMessage.content) {
|
|
18
|
+
if (part.type === "thinking") {
|
|
19
|
+
content.push({ type: "thinking", thinking: part.thinking });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (part.type === "redactedThinking") continue;
|
|
23
|
+
if (part.type !== "text") {
|
|
24
|
+
content.push(part);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (replacedText) continue;
|
|
28
|
+
content.push({ type: "text", text: replyText });
|
|
29
|
+
replacedText = true;
|
|
30
|
+
}
|
|
31
|
+
if (!replacedText) content.push({ type: "text", text: replyText });
|
|
32
|
+
return { ...assistantMessage, content, providerPayload: undefined };
|
|
10
33
|
}
|
|
11
34
|
|
|
12
35
|
export class BtwController {
|
|
13
36
|
#activeRequest: BtwRequest | undefined;
|
|
37
|
+
#lastQuestion: string | undefined;
|
|
38
|
+
#lastReplyText: string | undefined;
|
|
39
|
+
#lastAssistantMessage: AssistantMessage | undefined;
|
|
40
|
+
#lastLeafId: string | null | undefined;
|
|
41
|
+
#branchInFlight = false;
|
|
14
42
|
|
|
15
43
|
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
16
44
|
|
|
@@ -18,6 +46,29 @@ export class BtwController {
|
|
|
18
46
|
return this.#activeRequest !== undefined;
|
|
19
47
|
}
|
|
20
48
|
|
|
49
|
+
canBranch(): boolean {
|
|
50
|
+
return (
|
|
51
|
+
!this.#branchInFlight &&
|
|
52
|
+
this.#activeRequest?.component.isBranchable() === true &&
|
|
53
|
+
this.#lastQuestion !== undefined &&
|
|
54
|
+
this.#lastReplyText !== undefined &&
|
|
55
|
+
this.#lastAssistantMessage !== undefined &&
|
|
56
|
+
this.#lastLeafId !== null &&
|
|
57
|
+
this.#lastLeafId === this.ctx.sessionManager.getLeafId()
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async handleBranch(): Promise<boolean> {
|
|
62
|
+
if (!this.canBranch() || !this.#lastQuestion || !this.#lastAssistantMessage) return false;
|
|
63
|
+
this.#branchInFlight = true;
|
|
64
|
+
try {
|
|
65
|
+
await this.ctx.handleBtwBranch(this.#lastQuestion, this.#lastAssistantMessage);
|
|
66
|
+
return true;
|
|
67
|
+
} finally {
|
|
68
|
+
this.#branchInFlight = false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
21
72
|
handleEscape(): boolean {
|
|
22
73
|
if (!this.#activeRequest) return false;
|
|
23
74
|
this.#closeActiveRequest({ abort: this.#activeRequest.abortController.signal.aborted === false });
|
|
@@ -47,6 +98,7 @@ export class BtwController {
|
|
|
47
98
|
component: new BtwPanelComponent({ question: trimmedQuestion, tui: this.ctx.ui }),
|
|
48
99
|
abortController: new AbortController(),
|
|
49
100
|
question: trimmedQuestion,
|
|
101
|
+
leafId: this.ctx.sessionManager.getLeafId(),
|
|
50
102
|
};
|
|
51
103
|
this.ctx.btwContainer.clear();
|
|
52
104
|
this.ctx.btwContainer.addChild(request.component);
|
|
@@ -58,7 +110,7 @@ export class BtwController {
|
|
|
58
110
|
async #runRequest(request: BtwRequest): Promise<void> {
|
|
59
111
|
try {
|
|
60
112
|
const promptText = prompt.render(btwUserPrompt, { question: request.question });
|
|
61
|
-
const { replyText } = await this.ctx.session.runEphemeralTurn({
|
|
113
|
+
const { replyText, assistantMessage } = await this.ctx.session.runEphemeralTurn({
|
|
62
114
|
promptText,
|
|
63
115
|
onTextDelta: delta => {
|
|
64
116
|
if (this.#isActiveRequest(request)) {
|
|
@@ -75,6 +127,14 @@ export class BtwController {
|
|
|
75
127
|
request.component.setAnswer(replyText);
|
|
76
128
|
}
|
|
77
129
|
request.component.markComplete();
|
|
130
|
+
if (request.component.isBranchable()) {
|
|
131
|
+
this.#lastQuestion = request.question;
|
|
132
|
+
this.#lastReplyText = replyText;
|
|
133
|
+
this.#lastAssistantMessage = assistantMessageWithReplyText(assistantMessage, replyText);
|
|
134
|
+
this.#lastLeafId = request.leafId;
|
|
135
|
+
} else {
|
|
136
|
+
this.#clearBranchState();
|
|
137
|
+
}
|
|
78
138
|
} catch (error) {
|
|
79
139
|
if (!this.#isActiveRequest(request)) {
|
|
80
140
|
return;
|
|
@@ -91,6 +151,7 @@ export class BtwController {
|
|
|
91
151
|
const request = this.#activeRequest;
|
|
92
152
|
if (!request) return;
|
|
93
153
|
this.#activeRequest = undefined;
|
|
154
|
+
this.#clearBranchState();
|
|
94
155
|
if (options.abort) {
|
|
95
156
|
request.abortController.abort();
|
|
96
157
|
}
|
|
@@ -99,6 +160,13 @@ export class BtwController {
|
|
|
99
160
|
this.ctx.ui.requestRender();
|
|
100
161
|
}
|
|
101
162
|
|
|
163
|
+
#clearBranchState(): void {
|
|
164
|
+
this.#lastQuestion = undefined;
|
|
165
|
+
this.#lastReplyText = undefined;
|
|
166
|
+
this.#lastAssistantMessage = undefined;
|
|
167
|
+
this.#lastLeafId = undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
102
170
|
#isActiveRequest(request: BtwRequest): boolean {
|
|
103
171
|
return this.#activeRequest === request;
|
|
104
172
|
}
|
|
@@ -78,6 +78,7 @@ export class InputController {
|
|
|
78
78
|
|
|
79
79
|
#enhancedPaste?: EnhancedPasteController;
|
|
80
80
|
#focusedLeftTapListenerInstalled = false;
|
|
81
|
+
#btwBranchListenerInstalled = false;
|
|
81
82
|
// Tap counter for the double-← gesture; reset whenever a quiet gap
|
|
82
83
|
// (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
|
|
83
84
|
// #detectLeftDoubleTap.
|
|
@@ -143,6 +144,16 @@ export class InputController {
|
|
|
143
144
|
return { consume: true };
|
|
144
145
|
});
|
|
145
146
|
}
|
|
147
|
+
if (!this.#btwBranchListenerInstalled) {
|
|
148
|
+
this.#btwBranchListenerInstalled = true;
|
|
149
|
+
this.ctx.ui.addInputListener(data => {
|
|
150
|
+
if (!matchesKey(data, "b")) return undefined;
|
|
151
|
+
if (!this.ctx.canBranchBtw()) return undefined;
|
|
152
|
+
if (this.ctx.editor.getText().trim()) return undefined;
|
|
153
|
+
void this.ctx.handleBtwBranchKey();
|
|
154
|
+
return { consume: true };
|
|
155
|
+
});
|
|
156
|
+
}
|
|
146
157
|
this.ctx.editor.onEscape = () => {
|
|
147
158
|
// Active context maintenance owns Esc: auto/manual compaction,
|
|
148
159
|
// handoff generation, and auto-retry backoff all advertise
|
|
@@ -304,6 +315,8 @@ export class InputController {
|
|
|
304
315
|
this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
|
|
305
316
|
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
|
306
317
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
318
|
+
this.ctx.editor.setActionKeys("app.retry", this.ctx.keybindings.getKeys("app.retry"));
|
|
319
|
+
this.ctx.editor.onRetry = () => void this.handleRetry();
|
|
307
320
|
this.ctx.editor.clearCustomKeyHandlers();
|
|
308
321
|
// Wire up extension shortcuts
|
|
309
322
|
this.registerExtensionShortcuts();
|
|
@@ -925,6 +938,22 @@ export class InputController {
|
|
|
925
938
|
return true;
|
|
926
939
|
}
|
|
927
940
|
|
|
941
|
+
async handleRetry(): Promise<void> {
|
|
942
|
+
if (this.ctx.collabGuest) {
|
|
943
|
+
this.ctx.showStatus("/retry is host-only during a collab session");
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const didRetry = await this.ctx.viewSession.retry();
|
|
947
|
+
if (didRetry) {
|
|
948
|
+
this.ctx.editor.setText("");
|
|
949
|
+
this.ctx.pendingImages = [];
|
|
950
|
+
this.ctx.pendingImageLinks = [];
|
|
951
|
+
this.ctx.editor.imageLinks = undefined;
|
|
952
|
+
} else {
|
|
953
|
+
this.ctx.showStatus("Nothing to retry");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
928
957
|
/** Send editor text as a follow-up message (queued behind current stream). */
|
|
929
958
|
async handleFollowUp(): Promise<void> {
|
|
930
959
|
let text = this.ctx.editor.getText().trim();
|
|
@@ -854,14 +854,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
854
854
|
}),
|
|
855
855
|
);
|
|
856
856
|
// Set up theme file watcher
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
857
|
+
this.#eventBusUnsubscribers.push(
|
|
858
|
+
onThemeChange(() => {
|
|
859
|
+
this.#clearWorkingMessageAccentCache();
|
|
860
|
+
clearRenderCache();
|
|
861
|
+
clearMermaidCache();
|
|
862
|
+
this.ui.invalidate();
|
|
863
|
+
this.updateEditorBorderColor();
|
|
864
|
+
this.ui.requestRender();
|
|
865
|
+
}),
|
|
866
|
+
);
|
|
865
867
|
|
|
866
868
|
// Subscribe to terminal dark/light appearance changes.
|
|
867
869
|
// The terminal queries background color via OSC 11 at startup and on
|
|
@@ -3722,6 +3724,34 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3722
3724
|
return this.#btwController.handleEscape();
|
|
3723
3725
|
}
|
|
3724
3726
|
|
|
3727
|
+
canBranchBtw(): boolean {
|
|
3728
|
+
return this.#btwController.canBranch();
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
handleBtwBranchKey(): Promise<boolean> {
|
|
3732
|
+
return this.#btwController.handleBranch();
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
async handleBtwBranch(question: string, assistantMessage: AssistantMessage): Promise<void> {
|
|
3736
|
+
try {
|
|
3737
|
+
const result = await this.session.branchFromBtw(question, assistantMessage);
|
|
3738
|
+
if (result.cancelled) {
|
|
3739
|
+
this.showStatus("/btw branch cancelled", { dim: true });
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
this.#btwController.dispose();
|
|
3743
|
+
this.#omfgController.dispose();
|
|
3744
|
+
this.chatContainer.clear();
|
|
3745
|
+
this.renderInitialMessages({ clearTerminalHistory: true });
|
|
3746
|
+
this.updateEditorBorderColor();
|
|
3747
|
+
this.showStatus(
|
|
3748
|
+
result.sessionFile ? `Branched /btw to ${path.basename(result.sessionFile)}` : "Branched /btw",
|
|
3749
|
+
);
|
|
3750
|
+
} catch (error) {
|
|
3751
|
+
this.showError(`Cannot branch /btw: ${error instanceof Error ? error.message : String(error)}`);
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3725
3755
|
handleOmfgCommand(complaint: string): Promise<void> {
|
|
3726
3756
|
return this.#omfgController.start(complaint);
|
|
3727
3757
|
}
|
|
@@ -9,6 +9,7 @@ import { SetupWizardComponent } from "./wizard-overlay";
|
|
|
9
9
|
|
|
10
10
|
export type { SetupScene, SetupSceneController, SetupSceneHost, SetupSceneResult } from "./scenes/types";
|
|
11
11
|
|
|
12
|
+
export { runStartupSplash } from "./startup-splash";
|
|
12
13
|
export { CURRENT_SETUP_VERSION };
|
|
13
14
|
|
|
14
15
|
export const ALL_SCENES = [
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { PASTE_CODE_LOGIN_PROVIDERS } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type Component,
|
|
6
|
+
type Focusable,
|
|
7
|
+
Input,
|
|
8
|
+
matchesKey,
|
|
9
|
+
type SgrMouseEvent,
|
|
10
|
+
wrapTextWithAnsi,
|
|
11
|
+
} from "@oh-my-pi/pi-tui";
|
|
5
12
|
import { getAgentDbPath } from "@oh-my-pi/pi-utils";
|
|
13
|
+
import { copyToClipboard } from "../../../utils/clipboard";
|
|
6
14
|
import { OAuthSelectorComponent } from "../../components/oauth-selector";
|
|
7
15
|
import { theme } from "../../theme/theme";
|
|
8
16
|
import type { SetupSceneHost, SetupTab } from "./types";
|
|
@@ -11,10 +19,52 @@ function loginUrlLink(url: string): string {
|
|
|
11
19
|
return `\x1b]8;;${url}\x07Open login URL\x1b]8;;\x07`;
|
|
12
20
|
}
|
|
13
21
|
|
|
22
|
+
function loginCopyHint(): string {
|
|
23
|
+
return theme.fg("dim", "(clipboard copy attempted; Alt+C retries)");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class CopyablePromptInput implements Component, Focusable {
|
|
27
|
+
#input: Input;
|
|
28
|
+
#onCopy: () => void;
|
|
29
|
+
|
|
30
|
+
constructor(input: Input, onCopy: () => void) {
|
|
31
|
+
this.#input = input;
|
|
32
|
+
this.#onCopy = onCopy;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get focused(): boolean {
|
|
36
|
+
return this.#input.focused;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
set focused(value: boolean) {
|
|
40
|
+
this.#input.focused = value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setUseTerminalCursor(useTerminalCursor: boolean): void {
|
|
44
|
+
this.#input.setUseTerminalCursor(useTerminalCursor);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
render(width: number): readonly string[] {
|
|
48
|
+
return this.#input.render(width);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
handleInput(data: string): void {
|
|
52
|
+
if (matchesKey(data, "alt+c")) {
|
|
53
|
+
this.#onCopy();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.#input.handleInput(data);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
invalidate(): void {
|
|
60
|
+
this.#input.invalidate();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
14
64
|
interface PromptState {
|
|
15
65
|
message: string;
|
|
16
66
|
placeholder?: string;
|
|
17
|
-
input:
|
|
67
|
+
input: CopyablePromptInput;
|
|
18
68
|
}
|
|
19
69
|
|
|
20
70
|
/**
|
|
@@ -62,6 +112,10 @@ export class SignInTab implements SetupTab {
|
|
|
62
112
|
|
|
63
113
|
handleInput(data: string): void {
|
|
64
114
|
if (this.#loggingInProvider) {
|
|
115
|
+
if (this.#authUrl && (matchesKey(data, "alt+c") || (data === "c" && !this.#prompt))) {
|
|
116
|
+
void this.#copyAuthUrl();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
65
119
|
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c")) {
|
|
66
120
|
this.#loginAbort?.abort();
|
|
67
121
|
}
|
|
@@ -88,7 +142,10 @@ export class SignInTab implements SetupTab {
|
|
|
88
142
|
|
|
89
143
|
const urlLines = this.#authUrl ? wrapTextWithAnsi(theme.fg("dim", this.#authUrl), width) : [];
|
|
90
144
|
if (this.#authUrl) {
|
|
91
|
-
lines.push(
|
|
145
|
+
lines.push(
|
|
146
|
+
theme.fg("accent", `Browser login: ${loginUrlLink(this.#authUrl)} ${loginCopyHint()}`),
|
|
147
|
+
...urlLines.slice(0, 2),
|
|
148
|
+
);
|
|
92
149
|
}
|
|
93
150
|
if (this.#prompt) {
|
|
94
151
|
lines.push(theme.fg("warning", this.#prompt.message));
|
|
@@ -140,6 +197,7 @@ export class SignInTab implements SetupTab {
|
|
|
140
197
|
if (useManualInput) {
|
|
141
198
|
this.#statusLines.push(theme.fg("dim", "Paste the returned code or redirect URL when prompted."));
|
|
142
199
|
}
|
|
200
|
+
void this.#copyAuthUrl();
|
|
143
201
|
this.host.ctx.openInBrowser(info.url);
|
|
144
202
|
this.host.requestRender();
|
|
145
203
|
},
|
|
@@ -184,12 +242,26 @@ export class SignInTab implements SetupTab {
|
|
|
184
242
|
}
|
|
185
243
|
}
|
|
186
244
|
|
|
245
|
+
async #copyAuthUrl(): Promise<void> {
|
|
246
|
+
const url = this.#authUrl;
|
|
247
|
+
if (!url) return;
|
|
248
|
+
try {
|
|
249
|
+
await copyToClipboard(url);
|
|
250
|
+
} catch {
|
|
251
|
+
// Clipboard integration is best-effort; the full URL remains rendered below.
|
|
252
|
+
}
|
|
253
|
+
this.host.requestRender();
|
|
254
|
+
}
|
|
255
|
+
|
|
187
256
|
#showPrompt(prompt: { message: string; placeholder?: string }): Promise<string> {
|
|
188
257
|
this.#resolvePrompt("");
|
|
189
258
|
const input = new Input();
|
|
259
|
+
const focusInput = new CopyablePromptInput(input, () => {
|
|
260
|
+
void this.#copyAuthUrl();
|
|
261
|
+
});
|
|
190
262
|
const pending = Promise.withResolvers<string>();
|
|
191
263
|
this.#promptResolve = pending.resolve;
|
|
192
|
-
this.#prompt = { message: prompt.message, placeholder: prompt.placeholder, input };
|
|
264
|
+
this.#prompt = { message: prompt.message, placeholder: prompt.placeholder, input: focusInput };
|
|
193
265
|
input.onSubmit = value => {
|
|
194
266
|
this.#resolvePrompt(value);
|
|
195
267
|
};
|
|
@@ -197,7 +269,7 @@ export class SignInTab implements SetupTab {
|
|
|
197
269
|
this.#loginAbort?.abort();
|
|
198
270
|
this.#resolvePrompt("");
|
|
199
271
|
};
|
|
200
|
-
this.host.setFocus(
|
|
272
|
+
this.host.setFocus(focusInput);
|
|
201
273
|
this.host.requestRender();
|
|
202
274
|
return pending.promise;
|
|
203
275
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type Component, matchesKey, type OverlayFocusOwner } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import type { InteractiveModeContext } from "../types";
|
|
3
|
+
import { renderSetupSplash, SETUP_SPLASH_MS, SETUP_TICK_MS } from "./scenes/splash";
|
|
4
|
+
|
|
5
|
+
export interface RunStartupSplashOptions {
|
|
6
|
+
readonly durationMs?: number;
|
|
7
|
+
readonly tickMs?: number;
|
|
8
|
+
readonly now?: () => number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class StartupSplashComponent implements Component, OverlayFocusOwner {
|
|
12
|
+
#phaseStartedAt = 0;
|
|
13
|
+
#timer: NodeJS.Timeout | undefined;
|
|
14
|
+
#done = Promise.withResolvers<void>();
|
|
15
|
+
#disposed = false;
|
|
16
|
+
readonly #durationMs: number;
|
|
17
|
+
readonly #tickMs: number;
|
|
18
|
+
readonly #now: () => number;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
readonly ctx: InteractiveModeContext,
|
|
22
|
+
options: RunStartupSplashOptions = {},
|
|
23
|
+
) {
|
|
24
|
+
this.#durationMs = options.durationMs ?? SETUP_SPLASH_MS;
|
|
25
|
+
this.#tickMs = options.tickMs ?? SETUP_TICK_MS;
|
|
26
|
+
this.#now = options.now ?? (() => performance.now());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
run(): Promise<void> {
|
|
30
|
+
this.#phaseStartedAt = this.#now();
|
|
31
|
+
this.#startTimer();
|
|
32
|
+
this.ctx.ui.requestRender();
|
|
33
|
+
return this.#done.promise;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
dispose(): void {
|
|
37
|
+
this.#disposed = true;
|
|
38
|
+
this.#stopTimer();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ownsOverlayFocusTarget(component: Component): boolean {
|
|
42
|
+
return component === this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
handleInput(data: string): void {
|
|
46
|
+
if (
|
|
47
|
+
matchesKey(data, "enter") ||
|
|
48
|
+
matchesKey(data, "return") ||
|
|
49
|
+
matchesKey(data, "space") ||
|
|
50
|
+
matchesKey(data, "escape")
|
|
51
|
+
) {
|
|
52
|
+
this.#complete();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render(width: number): readonly string[] {
|
|
57
|
+
const elapsedMs = Math.min(this.#durationMs, Math.max(0, this.#now() - this.#phaseStartedAt));
|
|
58
|
+
return renderSetupSplash(Math.max(1, width), Math.max(1, this.ctx.ui.terminal.rows), elapsedMs);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#startTimer(): void {
|
|
62
|
+
if (this.#timer) return;
|
|
63
|
+
this.#timer = setInterval(() => {
|
|
64
|
+
if (this.#disposed) return;
|
|
65
|
+
const elapsed = this.#now() - this.#phaseStartedAt;
|
|
66
|
+
if (elapsed >= this.#durationMs) {
|
|
67
|
+
this.#complete();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.ctx.ui.requestRender();
|
|
71
|
+
}, this.#tickMs);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#stopTimer(): void {
|
|
75
|
+
if (!this.#timer) return;
|
|
76
|
+
clearInterval(this.#timer);
|
|
77
|
+
this.#timer = undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#complete(): void {
|
|
81
|
+
if (this.#disposed) return;
|
|
82
|
+
this.#stopTimer();
|
|
83
|
+
this.#done.resolve();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function runStartupSplash(
|
|
88
|
+
ctx: InteractiveModeContext,
|
|
89
|
+
options: RunStartupSplashOptions = {},
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
const component = new StartupSplashComponent(ctx, options);
|
|
92
|
+
const overlay = ctx.ui.showOverlay(component, {
|
|
93
|
+
width: "100%",
|
|
94
|
+
maxHeight: "100%",
|
|
95
|
+
anchor: "top-left",
|
|
96
|
+
margin: 0,
|
|
97
|
+
fullscreen: true,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
ctx.ui.setFocus(component);
|
|
101
|
+
await component.run();
|
|
102
|
+
} finally {
|
|
103
|
+
component.dispose();
|
|
104
|
+
ctx.ui.setFocus(component);
|
|
105
|
+
overlay.hide();
|
|
106
|
+
}
|
|
107
|
+
}
|