@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.0
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 +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
|
@@ -7,7 +7,6 @@ import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-m
|
|
|
7
7
|
import { getRoleInfo } from "../../config/model-registry";
|
|
8
8
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
9
9
|
import { settings } from "../../config/settings";
|
|
10
|
-
import { DebugSelectorComponent } from "../../debug";
|
|
11
10
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
12
11
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
13
12
|
import {
|
|
@@ -37,9 +36,11 @@ import {
|
|
|
37
36
|
setPreferredSearchProvider,
|
|
38
37
|
} from "../../tools";
|
|
39
38
|
import { shortenPath } from "../../tools/render-utils";
|
|
39
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
40
40
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
41
41
|
import { AgentDashboard } from "../components/agent-dashboard";
|
|
42
42
|
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
43
|
+
import { CopySelectorComponent } from "../components/copy-selector";
|
|
43
44
|
import { ExtensionDashboard } from "../components/extensions";
|
|
44
45
|
import { HistorySearchComponent } from "../components/history-search";
|
|
45
46
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
@@ -52,6 +53,8 @@ import { ToolExecutionComponent } from "../components/tool-execution";
|
|
|
52
53
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
53
54
|
import { UserMessageSelectorComponent } from "../components/user-message-selector";
|
|
54
55
|
import type { SessionObserverRegistry } from "../session-observer-registry";
|
|
56
|
+
import { computeContextBreakdown } from "../utils/context-usage";
|
|
57
|
+
import { buildCopyTargets } from "../utils/copy-targets";
|
|
55
58
|
|
|
56
59
|
const CALLBACK_SERVER_PROVIDERS = new Set<OAuthProvider>([
|
|
57
60
|
"anthropic",
|
|
@@ -407,6 +410,7 @@ export class SelectorController {
|
|
|
407
410
|
}
|
|
408
411
|
|
|
409
412
|
showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
413
|
+
const currentContextTokens = computeContextBreakdown(this.ctx.session).usedTokens;
|
|
410
414
|
this.showSelector(done => {
|
|
411
415
|
const selector = new ModelSelectorComponent(
|
|
412
416
|
this.ctx.ui,
|
|
@@ -470,7 +474,7 @@ export class SelectorController {
|
|
|
470
474
|
done();
|
|
471
475
|
this.ctx.ui.requestRender();
|
|
472
476
|
},
|
|
473
|
-
options,
|
|
477
|
+
{ ...options, currentContextTokens },
|
|
474
478
|
);
|
|
475
479
|
return { component: selector, focus: selector };
|
|
476
480
|
});
|
|
@@ -598,6 +602,38 @@ export class SelectorController {
|
|
|
598
602
|
});
|
|
599
603
|
}
|
|
600
604
|
|
|
605
|
+
showCopySelector(): void {
|
|
606
|
+
const targets = buildCopyTargets(this.ctx.session);
|
|
607
|
+
if (targets.length === 0) {
|
|
608
|
+
this.ctx.showStatus("Nothing to copy yet.");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
613
|
+
const done = () => {
|
|
614
|
+
overlayHandle?.hide();
|
|
615
|
+
this.ctx.ui.requestRender();
|
|
616
|
+
};
|
|
617
|
+
const selector = new CopySelectorComponent(targets, {
|
|
618
|
+
onPick: target => {
|
|
619
|
+
done();
|
|
620
|
+
if (target.content === undefined) return;
|
|
621
|
+
void copyToClipboard(target.content);
|
|
622
|
+
this.ctx.showStatus(target.copyMessage ?? "Copied to clipboard");
|
|
623
|
+
},
|
|
624
|
+
onCancel: done,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
overlayHandle = this.ctx.ui.showOverlay(selector, {
|
|
628
|
+
anchor: "bottom-center",
|
|
629
|
+
width: "100%",
|
|
630
|
+
maxHeight: "100%",
|
|
631
|
+
margin: 0,
|
|
632
|
+
});
|
|
633
|
+
this.ctx.ui.setFocus(selector);
|
|
634
|
+
this.ctx.ui.requestRender();
|
|
635
|
+
}
|
|
636
|
+
|
|
601
637
|
showTreeSelector(): void {
|
|
602
638
|
const tree = this.ctx.sessionManager.getTree();
|
|
603
639
|
const realLeafId = this.ctx.sessionManager.getLeafId();
|
|
@@ -1043,7 +1079,8 @@ export class SelectorController {
|
|
|
1043
1079
|
});
|
|
1044
1080
|
}
|
|
1045
1081
|
|
|
1046
|
-
showDebugSelector(): void {
|
|
1082
|
+
async showDebugSelector(): Promise<void> {
|
|
1083
|
+
const { DebugSelectorComponent } = await import("../../debug");
|
|
1047
1084
|
this.showSelector(done => {
|
|
1048
1085
|
const selector = new DebugSelectorComponent(this.ctx, done);
|
|
1049
1086
|
return { component: selector, focus: selector };
|
package/src/modes/index.ts
CHANGED
|
@@ -2,11 +2,13 @@ import { emergencyTerminalRestore } from "@oh-my-pi/pi-tui";
|
|
|
2
2
|
import { postmortem } from "@oh-my-pi/pi-utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Interactive mode and embeddable RPC client exports for the coding agent.
|
|
6
|
+
*
|
|
7
|
+
* Branch-specific runners live in their concrete modules so importing this
|
|
8
|
+
* barrel does not pull print, RPC server, or ACP server mode into the normal
|
|
9
|
+
* TUI graph.
|
|
6
10
|
*/
|
|
7
|
-
export { runAcpMode } from "./acp";
|
|
8
11
|
export { InteractiveMode, type InteractiveModeOptions } from "./interactive-mode";
|
|
9
|
-
export { type PrintModeOptions, runPrintMode } from "./print-mode";
|
|
10
12
|
export {
|
|
11
13
|
defineRpcClientTool,
|
|
12
14
|
type ModelInfo,
|
|
@@ -17,7 +19,6 @@ export {
|
|
|
17
19
|
type RpcClientToolResult,
|
|
18
20
|
type RpcEventListener,
|
|
19
21
|
} from "./rpc/rpc-client";
|
|
20
|
-
export { runRpcMode } from "./rpc/rpc-mode";
|
|
21
22
|
export type {
|
|
22
23
|
RpcCommand,
|
|
23
24
|
RpcHostToolCallRequest,
|
|
@@ -250,6 +250,20 @@ export interface InteractiveModeOptions {
|
|
|
250
250
|
initialMessages?: string[];
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Plan-review preview block. Once rendered it is static (a one-shot Markdown of
|
|
255
|
+
* the plan file), so even while it sits as the live bottom block beneath the
|
|
256
|
+
* approval selector its scrolled-off head is safe to commit to native
|
|
257
|
+
* scrollback. Reporting append-only lets an over-tall plan + selector commit the
|
|
258
|
+
* plan's head instead of clipping it — without this a plain {@link Container} is
|
|
259
|
+
* deferred and a long plan is cut off the top on ED3-risk terminals.
|
|
260
|
+
*/
|
|
261
|
+
class PlanReviewBlock extends Container {
|
|
262
|
+
isTranscriptBlockAppendOnly(): boolean {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
253
267
|
export class InteractiveMode implements InteractiveModeContext {
|
|
254
268
|
session: AgentSession;
|
|
255
269
|
sessionManager: SessionManager;
|
|
@@ -1680,7 +1694,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1680
1694
|
#renderPlanPreview(planContent: string, options?: { append?: boolean }): void {
|
|
1681
1695
|
const existingContainer = this.#planReviewContainer;
|
|
1682
1696
|
const replaceExisting = options?.append !== true && existingContainer !== undefined;
|
|
1683
|
-
const planReviewContainer = replaceExisting ? existingContainer : new
|
|
1697
|
+
const planReviewContainer = replaceExisting ? existingContainer : new PlanReviewBlock();
|
|
1684
1698
|
planReviewContainer.clear();
|
|
1685
1699
|
planReviewContainer.addChild(new Spacer(1));
|
|
1686
1700
|
planReviewContainer.addChild(new DynamicBorder());
|
|
@@ -2700,10 +2714,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2700
2714
|
return this.#commandController.handleShareCommand();
|
|
2701
2715
|
}
|
|
2702
2716
|
|
|
2703
|
-
handleCopyCommand(sub?: string) {
|
|
2704
|
-
return this.#commandController.handleCopyCommand(sub);
|
|
2705
|
-
}
|
|
2706
|
-
|
|
2707
2717
|
handleTodoCommand(args: string): Promise<void> {
|
|
2708
2718
|
return this.#todoCommandController.handleTodoCommand(args);
|
|
2709
2719
|
}
|
|
@@ -2848,8 +2858,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2848
2858
|
}
|
|
2849
2859
|
}
|
|
2850
2860
|
|
|
2851
|
-
showDebugSelector(): void {
|
|
2852
|
-
this.#selectorController.showDebugSelector();
|
|
2861
|
+
async showDebugSelector(): Promise<void> {
|
|
2862
|
+
await this.#selectorController.showDebugSelector();
|
|
2853
2863
|
}
|
|
2854
2864
|
|
|
2855
2865
|
showSessionObserver(): void {
|
|
@@ -2936,6 +2946,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2936
2946
|
this.#selectorController.showUserMessageSelector();
|
|
2937
2947
|
}
|
|
2938
2948
|
|
|
2949
|
+
showCopySelector(): void {
|
|
2950
|
+
this.#selectorController.showCopySelector();
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2939
2953
|
showTreeSelector(): void {
|
|
2940
2954
|
this.#selectorController.showTreeSelector();
|
|
2941
2955
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup version the wizard advances a fresh install to. Bump it whenever a new
|
|
3
|
+
* setup scene lands (or an existing scene raises its `minVersion`).
|
|
4
|
+
*
|
|
5
|
+
* Kept in its own dependency-free module so the cold-launch gate in `main.ts`
|
|
6
|
+
* can answer "is the stored setup version stale?" without statically importing
|
|
7
|
+
* the full wizard — every scene (sign-in/OAuth, web search, theme previews) plus
|
|
8
|
+
* the overlay component and their TUI deps. MUST equal `max(scene.minVersion)`
|
|
9
|
+
* across `ALL_SCENES`; the `setup-wizard` barrel and test suite guard it.
|
|
10
|
+
*/
|
|
11
|
+
export const CURRENT_SETUP_VERSION = 1;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Settings } from "../../config/settings";
|
|
2
|
+
import { CURRENT_SETUP_VERSION } from "../setup-version";
|
|
2
3
|
import type { InteractiveModeContext } from "../types";
|
|
3
4
|
import { glyphSetupScene } from "./scenes/glyph";
|
|
4
5
|
import { providersSetupScene } from "./scenes/providers";
|
|
@@ -8,14 +9,14 @@ import { SetupWizardComponent } from "./wizard-overlay";
|
|
|
8
9
|
|
|
9
10
|
export type { SetupScene, SetupSceneController, SetupSceneHost, SetupSceneResult } from "./scenes/types";
|
|
10
11
|
|
|
12
|
+
export { CURRENT_SETUP_VERSION };
|
|
13
|
+
|
|
11
14
|
export const ALL_SCENES = [
|
|
12
15
|
providersSetupScene,
|
|
13
16
|
glyphSetupScene,
|
|
14
17
|
themeSetupScene,
|
|
15
18
|
] as const satisfies readonly SetupScene[];
|
|
16
19
|
|
|
17
|
-
export const CURRENT_SETUP_VERSION = ALL_SCENES.reduce((max, scene) => Math.max(max, scene.minVersion), 0);
|
|
18
|
-
|
|
19
20
|
export interface SetupSceneSelectionOptions {
|
|
20
21
|
resuming?: boolean;
|
|
21
22
|
isTTY?: boolean;
|
|
@@ -19,7 +19,8 @@ type Availability = "checking" | boolean;
|
|
|
19
19
|
/**
|
|
20
20
|
* "Web search" panel: picks the provider the web_search tool should prefer and
|
|
21
21
|
* reports whether the highlighted provider is ready to use given current
|
|
22
|
-
* credentials (env keys or OAuth sign-ins from the Sign in tab)
|
|
22
|
+
* credentials (env keys or OAuth sign-ins from the Sign in tab) or an
|
|
23
|
+
* unauthenticated fallback.
|
|
23
24
|
*/
|
|
24
25
|
export class WebSearchTab implements SetupTab {
|
|
25
26
|
readonly id = "web-search";
|
|
@@ -91,7 +92,7 @@ export class WebSearchTab implements SetupTab {
|
|
|
91
92
|
let ready = false;
|
|
92
93
|
try {
|
|
93
94
|
const provider = await getSearchProvider(id);
|
|
94
|
-
ready = await provider.
|
|
95
|
+
ready = await provider.isExplicitlyAvailable(this.host.ctx.session.modelRegistry.authStorage);
|
|
95
96
|
} catch {
|
|
96
97
|
ready = false;
|
|
97
98
|
}
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import type { EditorTheme, MarkdownTheme, SelectListTheme, SymbolTheme } from "@oh-my-pi/pi-tui";
|
|
13
13
|
import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
|
|
14
14
|
import chalk from "chalk";
|
|
15
|
+
import { LRUCache } from "lru-cache/raw";
|
|
15
16
|
import * as z from "zod/v4";
|
|
16
17
|
// Embed theme JSON files at build time
|
|
17
18
|
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
@@ -2429,17 +2430,54 @@ function getHighlightColors(t: Theme): NativeHighlightColors {
|
|
|
2429
2430
|
return cachedHighlightColors;
|
|
2430
2431
|
}
|
|
2431
2432
|
|
|
2433
|
+
/**
|
|
2434
|
+
* Memoized native syntax highlight. Returns the joined ANSI string, or `null`
|
|
2435
|
+
* when the native tokenizer throws so callers can apply their own fallback.
|
|
2436
|
+
*
|
|
2437
|
+
* Keyed on `(lang, code)` and reset whenever the active `theme` instance
|
|
2438
|
+
* changes — the ANSI colors are baked into the highlighted output, so a theme
|
|
2439
|
+
* switch (which always reassigns `theme`) must invalidate every entry.
|
|
2440
|
+
*
|
|
2441
|
+
* Why this exists: animated tool blocks (eval/bash) repaint their box on every
|
|
2442
|
+
* ~16ms border-shimmer frame, and markdown re-lexes on every streamed delta.
|
|
2443
|
+
* Without memoization each frame re-tokenizes an unchanged code body through the
|
|
2444
|
+
* Rust FFI — ~26ms for 100 lines, ~40ms for 150 — overrunning the 16ms frame
|
|
2445
|
+
* budget and starving the spinner/render timers (the "TUI freeze").
|
|
2446
|
+
*/
|
|
2447
|
+
const HIGHLIGHT_CACHE_MAX = 256;
|
|
2448
|
+
const highlightCache = new LRUCache<string, string>({ max: HIGHLIGHT_CACHE_MAX });
|
|
2449
|
+
let highlightCacheTheme: Theme | undefined;
|
|
2450
|
+
|
|
2451
|
+
function highlightCached(code: string, validLang: string | undefined): string | null {
|
|
2452
|
+
if (highlightCacheTheme !== theme) {
|
|
2453
|
+
highlightCache.clear();
|
|
2454
|
+
highlightCacheTheme = theme;
|
|
2455
|
+
}
|
|
2456
|
+
const key = `${validLang ?? ""}\x00${code}`;
|
|
2457
|
+
const hit = highlightCache.get(key);
|
|
2458
|
+
if (hit !== undefined) {
|
|
2459
|
+
return hit;
|
|
2460
|
+
}
|
|
2461
|
+
let highlighted: string;
|
|
2462
|
+
try {
|
|
2463
|
+
highlighted = nativeHighlightCode(code, validLang, getHighlightColors(theme));
|
|
2464
|
+
} catch {
|
|
2465
|
+
return null;
|
|
2466
|
+
}
|
|
2467
|
+
highlightCache.set(key, highlighted);
|
|
2468
|
+
return highlighted;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2432
2471
|
/**
|
|
2433
2472
|
* Highlight code with syntax coloring based on file extension or language.
|
|
2434
2473
|
* Returns array of highlighted lines.
|
|
2435
2474
|
*/
|
|
2436
2475
|
export function highlightCode(code: string, lang?: string): string[] {
|
|
2437
2476
|
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
}
|
|
2477
|
+
const highlighted = highlightCached(code, validLang);
|
|
2478
|
+
// Always return a fresh array: callers (e.g. renderCodeCell) push extra lines
|
|
2479
|
+
// onto the result, which would corrupt the cached string otherwise.
|
|
2480
|
+
return (highlighted ?? code).split("\n");
|
|
2443
2481
|
}
|
|
2444
2482
|
|
|
2445
2483
|
export function getSymbolTheme(): SymbolTheme {
|
|
@@ -2484,11 +2522,9 @@ export function getMarkdownTheme(): MarkdownTheme {
|
|
|
2484
2522
|
resolveMermaidAscii,
|
|
2485
2523
|
highlightCode: (code: string, lang?: string): string[] => {
|
|
2486
2524
|
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
|
|
2491
|
-
}
|
|
2525
|
+
const highlighted = highlightCached(code, validLang);
|
|
2526
|
+
if (highlighted !== null) return highlighted.split("\n");
|
|
2527
|
+
return code.split("\n").map(line => theme.fg("mdCodeBlock", line));
|
|
2492
2528
|
},
|
|
2493
2529
|
};
|
|
2494
2530
|
cachedMarkdownTheme = markdownTheme;
|
package/src/modes/types.ts
CHANGED
|
@@ -222,7 +222,6 @@ export interface InteractiveModeContext {
|
|
|
222
222
|
// Command handling
|
|
223
223
|
handleExportCommand(text: string): Promise<void>;
|
|
224
224
|
handleShareCommand(): Promise<void>;
|
|
225
|
-
handleCopyCommand(sub?: string): void;
|
|
226
225
|
handleTodoCommand(args: string): Promise<void>;
|
|
227
226
|
handleSessionCommand(): Promise<void>;
|
|
228
227
|
handleJobsCommand(): Promise<void>;
|
|
@@ -263,13 +262,14 @@ export interface InteractiveModeContext {
|
|
|
263
262
|
showModelSelector(options?: { temporaryOnly?: boolean }): void;
|
|
264
263
|
showPluginSelector(mode?: "install" | "uninstall"): void;
|
|
265
264
|
showUserMessageSelector(): void;
|
|
265
|
+
showCopySelector(): void;
|
|
266
266
|
showTreeSelector(): void;
|
|
267
267
|
showSessionSelector(): void;
|
|
268
268
|
handleResumeSession(sessionPath: string): Promise<void>;
|
|
269
269
|
handleSessionDeleteCommand(): Promise<void>;
|
|
270
270
|
showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;
|
|
271
271
|
showHookConfirm(title: string, message: string): Promise<boolean>;
|
|
272
|
-
showDebugSelector(): void
|
|
272
|
+
showDebugSelector(): Promise<void>;
|
|
273
273
|
showSessionObserver(): void;
|
|
274
274
|
resetObserverRegistry(): void;
|
|
275
275
|
|
|
@@ -37,6 +37,9 @@ export interface ContextBreakdown {
|
|
|
37
37
|
freeTokens: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
const EMPTY_STRING_PARTS: readonly string[] = [];
|
|
41
|
+
const EMPTY_TOOLS: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">> = [];
|
|
42
|
+
|
|
40
43
|
export function estimateSkillsTokens(skills: readonly Skill[]): number {
|
|
41
44
|
const fragments: string[] = [];
|
|
42
45
|
for (const skill of skills) {
|
|
@@ -75,15 +78,16 @@ export function estimateToolSchemaTokens(
|
|
|
75
78
|
* messages walked incrementally as new entries append.
|
|
76
79
|
*/
|
|
77
80
|
export function computeNonMessageTokens(session: AgentSession): number {
|
|
78
|
-
const
|
|
79
|
-
|
|
81
|
+
const systemPromptParts = session.systemPrompt ?? EMPTY_STRING_PARTS;
|
|
82
|
+
const tools = session.agent?.state?.tools ?? EMPTY_TOOLS;
|
|
83
|
+
return countTokens(systemPromptParts) + estimateToolSchemaTokens(tools);
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
/**
|
|
83
|
-
* Shared helper for the four non-message token totals
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
+
* Shared helper for the four non-message token totals used by
|
|
88
|
+
* `computeContextBreakdown` (/context panel). Keep this category split stable:
|
|
89
|
+
* the status-line fast path intentionally uses the equivalent collapsed total
|
|
90
|
+
* in `computeNonMessageTokens`.
|
|
87
91
|
*/
|
|
88
92
|
function computeNonMessageBreakdown(session: AgentSession): {
|
|
89
93
|
skillsTokens: number;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { ToolCall } from "@oh-my-pi/pi-ai";
|
|
3
|
+
|
|
4
|
+
/** A fenced code block extracted from assistant markdown. */
|
|
5
|
+
export interface CodeBlock {
|
|
6
|
+
/** Info string after the opening fence (language id), trimmed. */
|
|
7
|
+
lang: string;
|
|
8
|
+
/** Block body with the trailing newline stripped. */
|
|
9
|
+
code: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A runnable command found in the transcript. */
|
|
13
|
+
export interface LastCommand {
|
|
14
|
+
kind: "bash" | "eval";
|
|
15
|
+
code: string;
|
|
16
|
+
/** Highlight language: "bash" for bash, "python"/"javascript" for eval. */
|
|
17
|
+
language: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A node in the `/copy` picker tree. Leaves carry `content` (placed on the
|
|
22
|
+
* clipboard) plus `copyMessage` (the status shown afterwards); groups carry
|
|
23
|
+
* `children` to drill into.
|
|
24
|
+
*/
|
|
25
|
+
export interface CopyTarget {
|
|
26
|
+
/** Stable identifier (e.g. "msg:1", "msg:1:code:0", "msg:1:all", "cmd:1"). */
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
/** Dim annotation: line/block counts, language, or tool name. */
|
|
30
|
+
hint?: string;
|
|
31
|
+
/** Full text rendered in the preview pane. */
|
|
32
|
+
preview: string;
|
|
33
|
+
/** Highlight language for code/command previews (undefined = plain/markdown). */
|
|
34
|
+
language?: string;
|
|
35
|
+
/** Leaf: text copied to the clipboard. */
|
|
36
|
+
content?: string;
|
|
37
|
+
/** Leaf: status message shown after copying. */
|
|
38
|
+
copyMessage?: string;
|
|
39
|
+
/** Group: nested targets to drill into. */
|
|
40
|
+
children?: CopyTarget[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Minimal session surface needed to assemble copy targets (eases testing). */
|
|
44
|
+
export interface CopySource {
|
|
45
|
+
readonly messages: readonly AgentMessage[];
|
|
46
|
+
getLastVisibleHandoffText(): string | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Cap on how many recent assistant messages the picker lists. */
|
|
50
|
+
const MAX_MESSAGES = 50;
|
|
51
|
+
|
|
52
|
+
const CODE_BLOCK_RE = /^```([^\n]*)\n([\s\S]*?)^```/gm;
|
|
53
|
+
|
|
54
|
+
/** Extract fenced code blocks from assistant markdown, in document order. */
|
|
55
|
+
export function extractCodeBlocks(text: string): CodeBlock[] {
|
|
56
|
+
const blocks: CodeBlock[] = [];
|
|
57
|
+
for (const match of text.matchAll(CODE_BLOCK_RE)) {
|
|
58
|
+
blocks.push({ lang: match[1].trim(), code: match[2].replace(/\n$/, "") });
|
|
59
|
+
}
|
|
60
|
+
return blocks;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractEvalCode(args: unknown): { code: string; language: string } | undefined {
|
|
64
|
+
if (!args || typeof args !== "object") return undefined;
|
|
65
|
+
const cells = (args as { cells?: unknown }).cells;
|
|
66
|
+
if (!Array.isArray(cells)) return undefined;
|
|
67
|
+
|
|
68
|
+
const codeBlocks: string[] = [];
|
|
69
|
+
let language = "python";
|
|
70
|
+
let languageResolved = false;
|
|
71
|
+
for (const cell of cells) {
|
|
72
|
+
if (!cell || typeof cell !== "object") continue;
|
|
73
|
+
const code = (cell as { code?: unknown }).code;
|
|
74
|
+
if (typeof code !== "string" || code.length === 0) continue;
|
|
75
|
+
codeBlocks.push(code);
|
|
76
|
+
if (!languageResolved) {
|
|
77
|
+
language = (cell as { language?: unknown }).language === "js" ? "javascript" : "python";
|
|
78
|
+
languageResolved = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return codeBlocks.length > 0 ? { code: codeBlocks.join("\n\n"), language } : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function commandFromToolCall(tc: ToolCall): LastCommand | undefined {
|
|
86
|
+
if (tc.name === "bash" && typeof tc.arguments.command === "string") {
|
|
87
|
+
return { kind: "bash", code: tc.arguments.command, language: "bash" };
|
|
88
|
+
}
|
|
89
|
+
if (tc.name === "eval") {
|
|
90
|
+
const evalResult = extractEvalCode(tc.arguments);
|
|
91
|
+
if (evalResult) return { kind: "eval", code: evalResult.code, language: evalResult.language };
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Walk the transcript backwards for the most recent bash command or eval code. */
|
|
97
|
+
export function extractLastCommand(messages: readonly AgentMessage[]): LastCommand | undefined {
|
|
98
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
99
|
+
const msg = messages[i];
|
|
100
|
+
if (msg.role !== "assistant") continue;
|
|
101
|
+
const toolCalls = msg.content.filter((c): c is ToolCall => c.type === "toolCall");
|
|
102
|
+
for (let j = toolCalls.length - 1; j >= 0; j--) {
|
|
103
|
+
const command = commandFromToolCall(toolCalls[j]!);
|
|
104
|
+
if (command) return command;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Concatenated visible text of an assistant message, or undefined when empty. */
|
|
111
|
+
function assistantText(msg: AgentMessage): string | undefined {
|
|
112
|
+
if (msg.role !== "assistant") return undefined;
|
|
113
|
+
let text = "";
|
|
114
|
+
for (const content of msg.content) {
|
|
115
|
+
if (content.type === "text") text += content.text;
|
|
116
|
+
}
|
|
117
|
+
return text.trim() || undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function pluralLines(text: string): string {
|
|
121
|
+
const count = text.length === 0 ? 0 : text.split("\n").length;
|
|
122
|
+
return `${count} line${count === 1 ? "" : "s"}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function blockHint(block: CodeBlock): string {
|
|
126
|
+
const lines = pluralLines(block.code);
|
|
127
|
+
return block.lang ? `${block.lang} · ${lines}` : lines;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** First non-empty line, whitespace-collapsed, used as a message label. */
|
|
131
|
+
function firstLine(text: string): string {
|
|
132
|
+
for (const line of text.split("\n")) {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (trimmed) return trimmed.replace(/\s+/g, " ");
|
|
135
|
+
}
|
|
136
|
+
return text.trim().replace(/\s+/g, " ");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Build the target node for one assistant message: a leaf when it has no code
|
|
140
|
+
* blocks, otherwise a group exposing the full message, each block, and "all". */
|
|
141
|
+
function messageTarget(text: string, rank: number): CopyTarget {
|
|
142
|
+
const id = `msg:${rank}`;
|
|
143
|
+
const label = firstLine(text);
|
|
144
|
+
const blocks = extractCodeBlocks(text);
|
|
145
|
+
const hint = blocks.length > 0 ? `${pluralLines(text)} · ${blocks.length} code` : pluralLines(text);
|
|
146
|
+
const messageCopy = rank === 1 ? "Copied last message to clipboard" : "Copied message to clipboard";
|
|
147
|
+
|
|
148
|
+
if (blocks.length === 0) {
|
|
149
|
+
return { id, label, hint, preview: text, content: text, copyMessage: messageCopy };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The message node itself copies the full message; its code blocks are
|
|
153
|
+
// child copy targets you can expand into.
|
|
154
|
+
const children: CopyTarget[] = blocks.map((block, j) => ({
|
|
155
|
+
id: `${id}:code:${j}`,
|
|
156
|
+
label: `Block ${j + 1}`,
|
|
157
|
+
hint: blockHint(block),
|
|
158
|
+
preview: block.code,
|
|
159
|
+
language: block.lang || undefined,
|
|
160
|
+
content: block.code,
|
|
161
|
+
copyMessage: `Copied code block ${j + 1} to clipboard`,
|
|
162
|
+
}));
|
|
163
|
+
if (blocks.length > 1) {
|
|
164
|
+
const combined = blocks.map(b => b.code).join("\n\n");
|
|
165
|
+
children.push({
|
|
166
|
+
id: `${id}:all`,
|
|
167
|
+
label: `All ${blocks.length} blocks`,
|
|
168
|
+
hint: pluralLines(combined),
|
|
169
|
+
preview: combined,
|
|
170
|
+
content: combined,
|
|
171
|
+
copyMessage: `Copied ${blocks.length} code blocks to clipboard`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { id, label, hint, preview: text, content: text, copyMessage: messageCopy, children };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function commandTitle(command: LastCommand): string {
|
|
179
|
+
return command.kind === "bash" ? "Bash command" : "Eval code";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function commandTarget(command: LastCommand, rank: number): CopyTarget {
|
|
183
|
+
const title = commandTitle(command);
|
|
184
|
+
return {
|
|
185
|
+
id: `cmd:${rank}`,
|
|
186
|
+
label: firstLine(command.code) || title,
|
|
187
|
+
hint: `${command.kind} · ${pluralLines(command.code)}`,
|
|
188
|
+
preview: command.code,
|
|
189
|
+
language: command.language,
|
|
190
|
+
content: command.code,
|
|
191
|
+
copyMessage: `Copied ${command.kind === "bash" ? "bash command" : "eval code"} to clipboard`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Assemble the unified `/copy` target tree: recent assistant messages
|
|
197
|
+
* (most recent first, each drillable into its code blocks), runnable command
|
|
198
|
+
* targets interleaved after the assistant message that issued them, and a
|
|
199
|
+
* fresh-handoff fallback when no assistant message exists yet.
|
|
200
|
+
*/
|
|
201
|
+
export function buildCopyTargets(source: CopySource): CopyTarget[] {
|
|
202
|
+
const targets: CopyTarget[] = [];
|
|
203
|
+
const pendingCommands: LastCommand[] = [];
|
|
204
|
+
let messageRank = 0;
|
|
205
|
+
let commandRank = 0;
|
|
206
|
+
|
|
207
|
+
const appendCommands = (commands: readonly LastCommand[]) => {
|
|
208
|
+
for (const command of commands) {
|
|
209
|
+
commandRank += 1;
|
|
210
|
+
targets.push(commandTarget(command, commandRank));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
for (let i = source.messages.length - 1; i >= 0 && messageRank < MAX_MESSAGES; i--) {
|
|
215
|
+
const msg = source.messages[i];
|
|
216
|
+
if (msg.role !== "assistant") continue;
|
|
217
|
+
|
|
218
|
+
const toolCalls = msg.content.filter((c): c is ToolCall => c.type === "toolCall");
|
|
219
|
+
const commands: LastCommand[] = [];
|
|
220
|
+
for (let j = toolCalls.length - 1; j >= 0; j--) {
|
|
221
|
+
const command = commandFromToolCall(toolCalls[j]!);
|
|
222
|
+
if (command) commands.push(command);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const text = assistantText(msg);
|
|
226
|
+
if (!text) {
|
|
227
|
+
pendingCommands.push(...commands);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
messageRank += 1;
|
|
232
|
+
targets.push(messageTarget(text, messageRank));
|
|
233
|
+
appendCommands(pendingCommands);
|
|
234
|
+
appendCommands(commands);
|
|
235
|
+
pendingCommands.length = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (messageRank === 0) {
|
|
239
|
+
const handoff = source.getLastVisibleHandoffText();
|
|
240
|
+
if (handoff) {
|
|
241
|
+
targets.unshift({
|
|
242
|
+
id: "handoff",
|
|
243
|
+
label: "Handoff context",
|
|
244
|
+
hint: pluralLines(handoff),
|
|
245
|
+
preview: handoff,
|
|
246
|
+
content: handoff,
|
|
247
|
+
copyMessage: "Copied handoff context to clipboard",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
appendCommands(pendingCommands);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return targets;
|
|
254
|
+
}
|
|
@@ -37,6 +37,7 @@ export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string
|
|
|
37
37
|
`| \`${appKey(bindings, "app.clear")}\` | Clear editor (first) / exit (second) |`,
|
|
38
38
|
`| \`${appKey(bindings, "app.exit")}\` | Exit (when editor is empty) |`,
|
|
39
39
|
`| \`${appKey(bindings, "app.suspend")}\` | Suspend to background |`,
|
|
40
|
+
`| \`${appKey(bindings, "app.display.reset")}\` | Reset terminal display |`,
|
|
40
41
|
`| \`${appKey(bindings, "app.thinking.cycle")}\` | Cycle thinking level |`,
|
|
41
42
|
`| \`${appKey(bindings, "app.model.cycleForward")}\` | Cycle role models (slow/default/smol) |`,
|
|
42
43
|
`| \`${appKey(bindings, "app.model.cycleBackward")}\` | Cycle role models (backward) |`,
|
|
@@ -14,7 +14,7 @@ Performs structural AST-aware rewrites via native ast-grep.
|
|
|
14
14
|
</instruction>
|
|
15
15
|
|
|
16
16
|
<output>
|
|
17
|
-
- Replacement summary, per-file replacement counts, and change diffs as
|
|
17
|
+
- Replacement summary, per-file replacement counts, and change diffs as `[src/foo.ts#1A2B]`, `-12:before`, `+12:after` lines in hashline mode
|
|
18
18
|
- Parse issues when files cannot be processed
|
|
19
19
|
</output>
|
|
20
20
|
|
|
@@ -18,7 +18,7 @@ Performs structural code search using AST matching via native ast-grep.
|
|
|
18
18
|
|
|
19
19
|
<output>
|
|
20
20
|
- Grouped matches with file path, byte range, line/column ranges, metavariable captures
|
|
21
|
-
- Match lines are numbered under a file snapshot tag header in hashline mode:
|
|
21
|
+
- Match lines are numbered under a file snapshot tag header in hashline mode: `[src/foo.ts#1A2B]`, `*42:content` for the matched line, ` 43:content` for context
|
|
22
22
|
- Summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
|
|
23
23
|
</output>
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ Append `:<sel>` to `path`. The bare path falls back to the default mode.
|
|
|
28
28
|
|
|
29
29
|
- Reading a directory path returns a depth-limited dirent listing.
|
|
30
30
|
{{#if IS_HL_MODE}}
|
|
31
|
-
- Reading a file with an explicit selector emits a file snapshot tag header and numbered lines:
|
|
31
|
+
- Reading a file with an explicit selector emits a file snapshot tag header and numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy the `[PATH#TAG]` header for anchored edits; ops use bare line numbers. NEVER fabricate the tag.
|
|
32
32
|
{{else}}
|
|
33
33
|
{{#if IS_LINE_NUMBER_MODE}}
|
|
34
34
|
- Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`.
|