@herbertgao/pi-extensions 2026.9.11 → 2026.9.13
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/README.md +4 -4
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +5 -4
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +39 -16
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +21 -4
- package/node_modules/@narumitw/pi-btw/dist/index.ts +1668 -958
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +10 -2
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +17 -79
- package/node_modules/@narumitw/pi-btw/src/conversation-context.ts +74 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +196 -7
- package/node_modules/@narumitw/pi-btw/src/main-thread-updates.ts +40 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +49 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +9 -1
- package/node_modules/@narumitw/pi-btw/src/workspace-layout.ts +559 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/node_modules/pi-multi-account/CHANGELOG.md +23 -0
- package/node_modules/pi-multi-account/README.md +38 -11
- package/node_modules/pi-multi-account/index.ts +371 -96
- package/node_modules/pi-multi-account/package.json +5 -4
- package/node_modules/pi-multi-account/provider-payload-stream.ts +36 -28
- package/node_modules/pi-multi-account/usage.ts +31 -2
- package/node_modules/pi-typesafe/README.md +3 -1
- package/node_modules/pi-typesafe/dist/auth.d.ts +10 -3
- package/node_modules/pi-typesafe/dist/auth.js +13 -7
- package/node_modules/pi-typesafe/dist/backends.d.ts +31 -0
- package/node_modules/pi-typesafe/dist/backends.js +33 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +3 -9
- package/node_modules/pi-typesafe/dist/client.js +63 -39
- package/node_modules/pi-typesafe/dist/credentials.d.ts +11 -5
- package/node_modules/pi-typesafe/dist/credentials.js +15 -8
- package/node_modules/pi-typesafe/dist/extension.js +4 -1
- package/node_modules/pi-typesafe/dist/index.d.ts +1 -1
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/dist/login.d.ts +13 -7
- package/node_modules/pi-typesafe/dist/login.js +17 -8
- package/node_modules/pi-typesafe/dist/schema.js +19 -13
- package/node_modules/pi-typesafe/package.json +1 -1
- package/package.json +11 -10
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
type Component,
|
|
4
|
+
type Focusable,
|
|
5
|
+
HStack,
|
|
6
|
+
isFocusable,
|
|
7
|
+
ScrollView,
|
|
8
|
+
type TuiMouseEvent,
|
|
9
|
+
type TuiMouseEventResult,
|
|
10
|
+
truncateToWidth,
|
|
11
|
+
visibleWidth,
|
|
12
|
+
} from "@earendil-works/pi-tui";
|
|
13
|
+
import { BtwPasteGuard } from "./keybindings.js";
|
|
14
|
+
import {
|
|
15
|
+
type BtwLayout,
|
|
16
|
+
DEFAULT_BTW_SIDE_PANE_RATIO,
|
|
17
|
+
MAX_BTW_SIDE_PANE_RATIO,
|
|
18
|
+
MIN_BTW_SIDE_PANE_RATIO,
|
|
19
|
+
} from "./settings.js";
|
|
20
|
+
|
|
21
|
+
export const MIN_BTW_SPLIT_COLUMNS = 80;
|
|
22
|
+
const PANE_DIVIDER_COLUMNS = 1;
|
|
23
|
+
// biome-ignore lint/complexity/useRegexLiterals: the constructor keeps a raw ESC control character out of source.
|
|
24
|
+
const SGR_MOUSE_PRESS_PATTERN = new RegExp("^\\u001b\\[<(\\d+);(\\d+);\\d+M$");
|
|
25
|
+
type BtwActivePane = "side" | "main";
|
|
26
|
+
|
|
27
|
+
export interface BtwFullscreenLayoutComponent extends Component {
|
|
28
|
+
getFullscreenLayout(): Component;
|
|
29
|
+
getPrimaryScrollView?(): ScrollView;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class BtwMainThreadInput implements Component, Focusable {
|
|
33
|
+
private target: Component | undefined;
|
|
34
|
+
private _focused = false;
|
|
35
|
+
private disposed = false;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
initialTarget: Component | null,
|
|
39
|
+
private readonly resolveTarget: () => Component | null,
|
|
40
|
+
private readonly requestRender: () => void,
|
|
41
|
+
) {
|
|
42
|
+
this.target = hasInput(initialTarget) ? initialTarget : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get focused(): boolean {
|
|
46
|
+
return this._focused;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set focused(value: boolean) {
|
|
50
|
+
this._focused = value;
|
|
51
|
+
if (this.target && isFocusable(this.target)) this.target.focused = value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get wantsKeyRelease(): boolean {
|
|
55
|
+
return this.target?.wantsKeyRelease ?? false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
render(): string[] {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handleInput(data: string): void {
|
|
63
|
+
if (this.disposed) return;
|
|
64
|
+
this.refreshTarget();
|
|
65
|
+
this.target?.handleInput?.(data);
|
|
66
|
+
this.refreshTarget();
|
|
67
|
+
this.requestRender();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
refreshTarget(): void {
|
|
71
|
+
if (this.disposed) return;
|
|
72
|
+
const next = this.resolveTarget();
|
|
73
|
+
if (!hasInput(next) || next === this.target) return;
|
|
74
|
+
if (this._focused && this.target && isFocusable(this.target)) this.target.focused = false;
|
|
75
|
+
this.target = next;
|
|
76
|
+
if (this._focused && isFocusable(next)) next.focused = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
invalidate(): void {}
|
|
80
|
+
|
|
81
|
+
dispose(): void {
|
|
82
|
+
if (this.disposed) return;
|
|
83
|
+
this.disposed = true;
|
|
84
|
+
if (this._focused && this.target && isFocusable(this.target)) this.target.focused = false;
|
|
85
|
+
this.target = undefined;
|
|
86
|
+
this._focused = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface BtwSplitPaneOptions {
|
|
91
|
+
sideComponent: Component;
|
|
92
|
+
sideLayout: Component;
|
|
93
|
+
mainThread: Component;
|
|
94
|
+
mainLayout?: Component;
|
|
95
|
+
mainInput: Component;
|
|
96
|
+
sideScrollView?: ScrollView;
|
|
97
|
+
layout: Exclude<BtwLayout, "fullscreen">;
|
|
98
|
+
theme: Theme;
|
|
99
|
+
terminalColumns(): number;
|
|
100
|
+
terminalRows(): number;
|
|
101
|
+
hasFocusedOverlay(): boolean;
|
|
102
|
+
setFocus(component: Component): void;
|
|
103
|
+
setViewportTarget(scrollView: ScrollView | undefined): void;
|
|
104
|
+
requestRender(): void;
|
|
105
|
+
sidePaneRatio?: number;
|
|
106
|
+
persistSidePaneRatio?(ratio: number): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class BtwSplitPane implements BtwFullscreenLayoutComponent {
|
|
110
|
+
private readonly pasteGuard = new BtwPasteGuard();
|
|
111
|
+
private readonly mainPane: MainThreadPane;
|
|
112
|
+
private readonly layoutRoot: HStack;
|
|
113
|
+
private readonly viewportRouter: PaneViewportRouter;
|
|
114
|
+
private activePane: BtwActivePane = "side";
|
|
115
|
+
private sidePaneRatio: number;
|
|
116
|
+
private persistedSidePaneRatio: number;
|
|
117
|
+
private dividerDragStartRatio: number | undefined;
|
|
118
|
+
private focusGeneration = 0;
|
|
119
|
+
private ratioSaveGeneration = 0;
|
|
120
|
+
private confirmedRatioSaveGeneration = 0;
|
|
121
|
+
private failedRatioSaveGenerationDuringDrag: number | undefined;
|
|
122
|
+
private disposed = false;
|
|
123
|
+
|
|
124
|
+
constructor(private readonly options: BtwSplitPaneOptions) {
|
|
125
|
+
this.sidePaneRatio = clampSidePaneRatio(options.sidePaneRatio ?? DEFAULT_BTW_SIDE_PANE_RATIO);
|
|
126
|
+
this.persistedSidePaneRatio = this.sidePaneRatio;
|
|
127
|
+
this.mainPane = new MainThreadPane(options.mainThread, options.mainLayout, options.theme, options.terminalRows);
|
|
128
|
+
this.viewportRouter = new PaneViewportRouter(
|
|
129
|
+
options.sideScrollView ?? findPrimaryScrollView(options.sideLayout),
|
|
130
|
+
this.mainPane.getPrimaryScrollView(),
|
|
131
|
+
options.setViewportTarget,
|
|
132
|
+
);
|
|
133
|
+
this.viewportRouter.activate("side");
|
|
134
|
+
const separator: Component = {
|
|
135
|
+
render: (width) =>
|
|
136
|
+
Array.from({ length: Math.max(1, options.terminalRows()) }, () => truncateToWidth(this.renderDivider(), width)),
|
|
137
|
+
handleMouse: (event) => this.handleDividerMouse(event),
|
|
138
|
+
invalidate() {},
|
|
139
|
+
};
|
|
140
|
+
const side = options.sideLayout;
|
|
141
|
+
const main = this.mainPane.getLayout();
|
|
142
|
+
this.layoutRoot =
|
|
143
|
+
options.layout === "left-pane"
|
|
144
|
+
? new ResponsivePaneRow(
|
|
145
|
+
side,
|
|
146
|
+
separator,
|
|
147
|
+
main,
|
|
148
|
+
0,
|
|
149
|
+
() => this.sidePaneRatio,
|
|
150
|
+
(width) => this.handleViewportWidth(width),
|
|
151
|
+
)
|
|
152
|
+
: new ResponsivePaneRow(
|
|
153
|
+
main,
|
|
154
|
+
separator,
|
|
155
|
+
side,
|
|
156
|
+
2,
|
|
157
|
+
() => this.sidePaneRatio,
|
|
158
|
+
(width) => this.handleViewportWidth(width),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getFullscreenLayout(): Component {
|
|
163
|
+
return this.layoutRoot;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
handleTerminalInput(data: string): boolean {
|
|
167
|
+
if (this.disposed) return false;
|
|
168
|
+
const pasted = this.pasteGuard.consume(data);
|
|
169
|
+
if (this.options.hasFocusedOverlay()) return false;
|
|
170
|
+
const width = this.terminalColumns();
|
|
171
|
+
if (width < MIN_BTW_SPLIT_COLUMNS && this.activePane !== "side") this.activatePane("side");
|
|
172
|
+
if (pasted) {
|
|
173
|
+
this.forwardPastedInput(data);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (width < MIN_BTW_SPLIT_COLUMNS) return false;
|
|
177
|
+
const pane = paneForMouseClick(data, width, this.options.layout, this.sidePaneRatio);
|
|
178
|
+
if (pane) this.queuePaneFocus(pane);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
render(width: number): string[] {
|
|
183
|
+
if (width <= 0) return [];
|
|
184
|
+
const safeWidth = Math.max(1, width);
|
|
185
|
+
this.handleViewportWidth(safeWidth);
|
|
186
|
+
if (safeWidth < MIN_BTW_SPLIT_COLUMNS) {
|
|
187
|
+
return this.options.sideComponent.render(safeWidth).map((line) => truncateToWidth(line, safeWidth));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const { sideWidth, mainWidth } = paneWidths(safeWidth, this.options.layout, this.sidePaneRatio);
|
|
191
|
+
const sideLines = this.options.sideComponent.render(sideWidth);
|
|
192
|
+
const mainLines = this.mainPane.render(mainWidth);
|
|
193
|
+
const rows = Math.max(1, this.options.terminalRows());
|
|
194
|
+
const separator = this.renderDivider();
|
|
195
|
+
const lines: string[] = [];
|
|
196
|
+
for (let index = 0; index < rows; index += 1) {
|
|
197
|
+
const sideLine = padLine(sideLines[index] ?? "", sideWidth);
|
|
198
|
+
const mainLine = padLine(mainLines[index] ?? "", mainWidth);
|
|
199
|
+
lines.push(
|
|
200
|
+
this.options.layout === "left-pane"
|
|
201
|
+
? `${sideLine}${separator}${mainLine}`
|
|
202
|
+
: `${mainLine}${separator}${sideLine}`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return lines.map((line) => truncateToWidth(line, safeWidth));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
invalidate(): void {
|
|
209
|
+
this.layoutRoot.invalidate();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
dispose(): void {
|
|
213
|
+
if (this.disposed) return;
|
|
214
|
+
this.disposed = true;
|
|
215
|
+
this.focusGeneration += 1;
|
|
216
|
+
this.dividerDragStartRatio = undefined;
|
|
217
|
+
this.failedRatioSaveGenerationDuringDrag = undefined;
|
|
218
|
+
this.viewportRouter.dispose();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private renderDivider(): string {
|
|
222
|
+
return this.options.theme.fg("borderMuted", "│");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private handleDividerMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
226
|
+
if (this.disposed) return undefined;
|
|
227
|
+
if (this.options.hasFocusedOverlay()) {
|
|
228
|
+
return { handled: true, render: this.cancelDividerDrag() };
|
|
229
|
+
}
|
|
230
|
+
if (event.type === "press" && event.button === "left") {
|
|
231
|
+
if (this.terminalColumns() < MIN_BTW_SPLIT_COLUMNS) return undefined;
|
|
232
|
+
this.focusGeneration += 1;
|
|
233
|
+
this.dividerDragStartRatio = this.sidePaneRatio;
|
|
234
|
+
return { handled: true, capture: true, render: false };
|
|
235
|
+
}
|
|
236
|
+
if (this.dividerDragStartRatio === undefined) return undefined;
|
|
237
|
+
if (event.type === "drag") {
|
|
238
|
+
const changed = this.updateSidePaneRatio(event.screenX);
|
|
239
|
+
return { handled: true, render: changed };
|
|
240
|
+
}
|
|
241
|
+
if (event.type === "release") {
|
|
242
|
+
const changed = this.updateSidePaneRatio(event.screenX);
|
|
243
|
+
const startRatio = this.dividerDragStartRatio;
|
|
244
|
+
const failedDuringDrag = this.failedRatioSaveGenerationDuringDrag === this.ratioSaveGeneration;
|
|
245
|
+
this.dividerDragStartRatio = undefined;
|
|
246
|
+
this.failedRatioSaveGenerationDuringDrag = undefined;
|
|
247
|
+
if (failedDuringDrag && this.sidePaneRatio === this.persistedSidePaneRatio) {
|
|
248
|
+
return { handled: true, render: changed };
|
|
249
|
+
}
|
|
250
|
+
if (this.sidePaneRatio !== startRatio) {
|
|
251
|
+
this.persistCurrentSidePaneRatio();
|
|
252
|
+
return { handled: true, render: changed };
|
|
253
|
+
}
|
|
254
|
+
const restored = failedDuringDrag && this.restorePersistedSidePaneRatio();
|
|
255
|
+
return { handled: true, render: changed || restored };
|
|
256
|
+
}
|
|
257
|
+
return { handled: true, render: false };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private updateSidePaneRatio(dividerColumn: number): boolean {
|
|
261
|
+
const width = this.terminalColumns();
|
|
262
|
+
if (width < MIN_BTW_SPLIT_COLUMNS) return false;
|
|
263
|
+
const contentWidth = width - PANE_DIVIDER_COLUMNS;
|
|
264
|
+
const leftWidth = clamp(Math.floor(dividerColumn), 1, contentWidth - 1);
|
|
265
|
+
const sideWidth = this.options.layout === "left-pane" ? leftWidth : contentWidth - leftWidth;
|
|
266
|
+
const ratio = clampSidePaneRatio(roundPaneRatio(sideWidth / contentWidth));
|
|
267
|
+
if (ratio === this.sidePaneRatio) return false;
|
|
268
|
+
this.sidePaneRatio = ratio;
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private cancelDividerDrag(): boolean {
|
|
273
|
+
const startRatio = this.dividerDragStartRatio;
|
|
274
|
+
if (startRatio === undefined) return false;
|
|
275
|
+
const failedDuringDrag = this.failedRatioSaveGenerationDuringDrag === this.ratioSaveGeneration;
|
|
276
|
+
this.dividerDragStartRatio = undefined;
|
|
277
|
+
this.failedRatioSaveGenerationDuringDrag = undefined;
|
|
278
|
+
return this.setSidePaneRatio(failedDuringDrag ? this.persistedSidePaneRatio : startRatio);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private persistCurrentSidePaneRatio(): void {
|
|
282
|
+
const persist = this.options.persistSidePaneRatio;
|
|
283
|
+
const ratio = this.sidePaneRatio;
|
|
284
|
+
if (!persist) {
|
|
285
|
+
this.persistedSidePaneRatio = ratio;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.failedRatioSaveGenerationDuringDrag = undefined;
|
|
289
|
+
const saveGeneration = ++this.ratioSaveGeneration;
|
|
290
|
+
void Promise.resolve()
|
|
291
|
+
.then(() => persist(ratio))
|
|
292
|
+
.then(() => {
|
|
293
|
+
if (saveGeneration <= this.confirmedRatioSaveGeneration) return;
|
|
294
|
+
this.confirmedRatioSaveGeneration = saveGeneration;
|
|
295
|
+
this.persistedSidePaneRatio = ratio;
|
|
296
|
+
})
|
|
297
|
+
.catch(() => {
|
|
298
|
+
if (this.disposed || saveGeneration !== this.ratioSaveGeneration) return;
|
|
299
|
+
const dragActive = this.dividerDragStartRatio !== undefined;
|
|
300
|
+
const changed = this.restorePersistedSidePaneRatio();
|
|
301
|
+
if (dragActive) this.failedRatioSaveGenerationDuringDrag = saveGeneration;
|
|
302
|
+
if (changed) this.options.requestRender();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private restorePersistedSidePaneRatio(): boolean {
|
|
307
|
+
return this.setSidePaneRatio(this.persistedSidePaneRatio);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private setSidePaneRatio(ratio: number): boolean {
|
|
311
|
+
if (ratio === this.sidePaneRatio) return false;
|
|
312
|
+
this.sidePaneRatio = ratio;
|
|
313
|
+
this.layoutRoot.invalidate();
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private queuePaneFocus(pane: BtwActivePane): void {
|
|
318
|
+
const generation = ++this.focusGeneration;
|
|
319
|
+
queueMicrotask(() => {
|
|
320
|
+
if (this.disposed || generation !== this.focusGeneration || this.options.hasFocusedOverlay()) return;
|
|
321
|
+
this.activatePane(this.terminalColumns() < MIN_BTW_SPLIT_COLUMNS ? "side" : pane);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private terminalColumns(): number {
|
|
326
|
+
return Math.max(1, Math.floor(this.options.terminalColumns()));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private handleViewportWidth(width: number): void {
|
|
330
|
+
if (
|
|
331
|
+
this.disposed ||
|
|
332
|
+
width >= MIN_BTW_SPLIT_COLUMNS ||
|
|
333
|
+
this.activePane === "side" ||
|
|
334
|
+
this.options.hasFocusedOverlay()
|
|
335
|
+
) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
this.activatePane("side");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private activatePane(pane: BtwActivePane): void {
|
|
342
|
+
if (this.disposed) return;
|
|
343
|
+
this.activePane = pane;
|
|
344
|
+
this.viewportRouter.activate(pane);
|
|
345
|
+
this.options.setFocus(pane === "side" ? this.options.sideComponent : this.options.mainInput);
|
|
346
|
+
this.options.requestRender();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private forwardPastedInput(data: string): void {
|
|
350
|
+
const target = this.activePane === "side" ? this.options.sideComponent : this.options.mainInput;
|
|
351
|
+
target.handleInput?.(data);
|
|
352
|
+
this.options.requestRender();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// HStack has no percentage basis, so refresh exact ratio-based widths from its
|
|
357
|
+
// viewport callback before each layout pass and let the side pane fill narrow views.
|
|
358
|
+
class ResponsivePaneRow extends HStack {
|
|
359
|
+
constructor(
|
|
360
|
+
left: Component,
|
|
361
|
+
separator: Component,
|
|
362
|
+
right: Component,
|
|
363
|
+
private readonly sideIndex: 0 | 2,
|
|
364
|
+
private readonly sidePaneRatio: () => number,
|
|
365
|
+
private readonly onViewportWidth: (width: number) => void,
|
|
366
|
+
) {
|
|
367
|
+
super([
|
|
368
|
+
{ component: left, basis: 1, grow: 0, shrink: 0, minSize: 1 },
|
|
369
|
+
{
|
|
370
|
+
component: separator,
|
|
371
|
+
basis: PANE_DIVIDER_COLUMNS,
|
|
372
|
+
grow: 0,
|
|
373
|
+
shrink: 0,
|
|
374
|
+
minSize: PANE_DIVIDER_COLUMNS,
|
|
375
|
+
},
|
|
376
|
+
{ component: right, basis: 1, grow: 0, shrink: 0, minSize: 1 },
|
|
377
|
+
]);
|
|
378
|
+
for (const [index, entry] of this.entries.entries()) {
|
|
379
|
+
entry.visible = (viewport) => {
|
|
380
|
+
if (index === 0) this.resize(viewport.width);
|
|
381
|
+
return index === this.sideIndex || viewport.width >= MIN_BTW_SPLIT_COLUMNS;
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private resize(width: number): void {
|
|
387
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
388
|
+
this.onViewportWidth(safeWidth);
|
|
389
|
+
if (safeWidth < MIN_BTW_SPLIT_COLUMNS) {
|
|
390
|
+
for (const [index, entry] of this.entries.entries()) {
|
|
391
|
+
entry.basis = index === this.sideIndex ? safeWidth : 1;
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const layout = this.sideIndex === 0 ? "left-pane" : "right-pane";
|
|
396
|
+
const { leftWidth, rightWidth } = paneWidths(safeWidth, layout, this.sidePaneRatio());
|
|
397
|
+
const left = this.entries[0];
|
|
398
|
+
const separator = this.entries[1];
|
|
399
|
+
const right = this.entries[2];
|
|
400
|
+
if (left) left.basis = leftWidth;
|
|
401
|
+
if (separator) separator.basis = PANE_DIVIDER_COLUMNS;
|
|
402
|
+
if (right) right.basis = rightWidth;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
class MainThreadPane {
|
|
407
|
+
private readonly body: Component;
|
|
408
|
+
private readonly layout: Component;
|
|
409
|
+
private readonly scroll: ScrollView | undefined;
|
|
410
|
+
|
|
411
|
+
constructor(
|
|
412
|
+
mainThread: Component,
|
|
413
|
+
mainLayout: Component | undefined,
|
|
414
|
+
theme: Theme,
|
|
415
|
+
private readonly terminalRows: () => number,
|
|
416
|
+
) {
|
|
417
|
+
this.body = {
|
|
418
|
+
render: (width) => mainThread.render(Math.max(1, width)).map((line) => truncateToWidth(line, Math.max(1, width))),
|
|
419
|
+
invalidate() {},
|
|
420
|
+
};
|
|
421
|
+
if (mainLayout) {
|
|
422
|
+
this.layout = mainLayout;
|
|
423
|
+
this.scroll = findPrimaryScrollView(mainLayout);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
this.scroll = new ScrollView(this.body, {
|
|
427
|
+
follow: "end",
|
|
428
|
+
scrollbar: "auto",
|
|
429
|
+
scrollbarTrackStyle: (text) => theme.fg("borderMuted", text),
|
|
430
|
+
scrollbarThumbStyle: (text) => theme.fg("muted", text),
|
|
431
|
+
});
|
|
432
|
+
this.layout = this.scroll;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
getLayout(): Component {
|
|
436
|
+
return this.layout;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
getPrimaryScrollView(): ScrollView | undefined {
|
|
440
|
+
return this.scroll;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
render(width: number): string[] {
|
|
444
|
+
if (width <= 0) return [];
|
|
445
|
+
const safeWidth = Math.max(1, width);
|
|
446
|
+
const rows = Math.max(1, this.terminalRows());
|
|
447
|
+
const visible = this.body.render(safeWidth).slice(-rows);
|
|
448
|
+
return [...Array.from({ length: Math.max(0, rows - visible.length) }, () => ""), ...visible];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
class PaneViewportRouter {
|
|
453
|
+
private readonly originalPrimary = new Map<ScrollView, boolean>();
|
|
454
|
+
private disposed = false;
|
|
455
|
+
|
|
456
|
+
constructor(
|
|
457
|
+
private readonly side: ScrollView | undefined,
|
|
458
|
+
private readonly main: ScrollView | undefined,
|
|
459
|
+
private readonly setViewportTarget: (scrollView: ScrollView | undefined) => void,
|
|
460
|
+
) {
|
|
461
|
+
for (const scrollView of [side, main]) {
|
|
462
|
+
if (scrollView) this.originalPrimary.set(scrollView, scrollView.primary);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
activate(pane: BtwActivePane): void {
|
|
467
|
+
if (this.disposed) return;
|
|
468
|
+
const target = pane === "side" ? this.side : this.main;
|
|
469
|
+
for (const scrollView of this.originalPrimary.keys()) setScrollViewPrimary(scrollView, scrollView === target);
|
|
470
|
+
this.setViewportTarget(target);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
dispose(): void {
|
|
474
|
+
if (this.disposed) return;
|
|
475
|
+
this.disposed = true;
|
|
476
|
+
for (const [scrollView, primary] of this.originalPrimary) setScrollViewPrimary(scrollView, primary);
|
|
477
|
+
this.setViewportTarget(undefined);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Pi exposes primary as a constructor-time ScrollView option but has no public
|
|
482
|
+
// active-pane viewport router. Preserve and restore this runtime field so Pi's
|
|
483
|
+
// native search, prompt navigation, and scrolling keep their standard behavior.
|
|
484
|
+
function setScrollViewPrimary(scrollView: ScrollView, primary: boolean): void {
|
|
485
|
+
Reflect.set(scrollView, "primary", primary);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function findPrimaryScrollView(root: Component): ScrollView | undefined {
|
|
489
|
+
const visited = new Set<Component>();
|
|
490
|
+
let fallback: ScrollView | undefined;
|
|
491
|
+
let primary: ScrollView | undefined;
|
|
492
|
+
const visit = (component: Component) => {
|
|
493
|
+
if (visited.has(component)) return;
|
|
494
|
+
visited.add(component);
|
|
495
|
+
if (component instanceof ScrollView) {
|
|
496
|
+
fallback ??= component;
|
|
497
|
+
if (component.primary) primary = component;
|
|
498
|
+
}
|
|
499
|
+
if (!("children" in component) || !Array.isArray(component.children)) return;
|
|
500
|
+
for (const child of component.children) visit(child);
|
|
501
|
+
};
|
|
502
|
+
visit(root);
|
|
503
|
+
return primary ?? fallback;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function paneForMouseClick(
|
|
507
|
+
data: string,
|
|
508
|
+
terminalColumns: number,
|
|
509
|
+
layout: Exclude<BtwLayout, "fullscreen">,
|
|
510
|
+
sidePaneRatio: number,
|
|
511
|
+
): BtwActivePane | undefined {
|
|
512
|
+
const match = SGR_MOUSE_PRESS_PATTERN.exec(data);
|
|
513
|
+
if (!match) return undefined;
|
|
514
|
+
const button = Number.parseInt(match[1] ?? "", 10);
|
|
515
|
+
if ((button & 32) !== 0 || (button & 64) !== 0 || (button & 3) !== 0) return undefined;
|
|
516
|
+
const column = Number.parseInt(match[2] ?? "", 10) - 1;
|
|
517
|
+
if (!Number.isFinite(column) || column < 0 || column >= terminalColumns) return undefined;
|
|
518
|
+
const { leftWidth } = paneWidths(terminalColumns, layout, sidePaneRatio);
|
|
519
|
+
if (column >= leftWidth && column < leftWidth + PANE_DIVIDER_COLUMNS) return undefined;
|
|
520
|
+
const clickedLeft = column < leftWidth;
|
|
521
|
+
if (layout === "left-pane") return clickedLeft ? "side" : "main";
|
|
522
|
+
return clickedLeft ? "main" : "side";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function paneWidths(width: number, layout: Exclude<BtwLayout, "fullscreen">, sidePaneRatio: number) {
|
|
526
|
+
const contentWidth = Math.max(2, width - PANE_DIVIDER_COLUMNS);
|
|
527
|
+
const sideWidth = clamp(
|
|
528
|
+
Math.round(contentWidth * clampSidePaneRatio(sidePaneRatio)),
|
|
529
|
+
1,
|
|
530
|
+
Math.max(1, contentWidth - 1),
|
|
531
|
+
);
|
|
532
|
+
const mainWidth = Math.max(1, contentWidth - sideWidth);
|
|
533
|
+
const leftWidth = layout === "left-pane" ? sideWidth : mainWidth;
|
|
534
|
+
const rightWidth = layout === "left-pane" ? mainWidth : sideWidth;
|
|
535
|
+
return { leftWidth, rightWidth, sideWidth, mainWidth };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function clampSidePaneRatio(ratio: number): number {
|
|
539
|
+
return Number.isFinite(ratio)
|
|
540
|
+
? clamp(ratio, MIN_BTW_SIDE_PANE_RATIO, MAX_BTW_SIDE_PANE_RATIO)
|
|
541
|
+
: DEFAULT_BTW_SIDE_PANE_RATIO;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function roundPaneRatio(ratio: number): number {
|
|
545
|
+
return Math.round(ratio * 10_000) / 10_000;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function clamp(value: number, minimum: number, maximum: number): number {
|
|
549
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function hasInput(component: Component | null): component is Component {
|
|
553
|
+
return component !== null && typeof component.handleInput === "function";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function padLine(line: string, width: number): string {
|
|
557
|
+
const truncated = truncateToWidth(line, width);
|
|
558
|
+
return `${truncated}${" ".repeat(Math.max(0, width - visibleWidth(truncated)))}`;
|
|
559
|
+
}
|
|
@@ -7,6 +7,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.36.0] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
### Highlights
|
|
13
|
+
|
|
14
|
+
- Set up TypeSafe semantic search from Pi with `/mcp jev setup`.
|
|
15
|
+
- Search every enabled MCP tool automatically when a TypeSafe key is available.
|
|
16
|
+
- Use regex safety checks reliably on Windows with both native and Java backends.
|
|
17
|
+
- Get clearer, non-duplicated guidance when tool catalogs are large or semantic search finds no match.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `/mcp jev setup` checks for a TypeSafe credential, lets you restrict which MCP servers may share semantic-search data, saves the project policy, and reloads Pi automatically.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- A valid TypeSafe key now enables semantic search across every enabled MCP tool by default, while script evaluation remains opt-in. Search now explains when an allowlist permits no servers, when permitted servers have no cached tools, and when none of the available tools match the request.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Regex safety checks on Windows resolve recheck's native executable and JAR fallback correctly. `recheck` is intentionally pinned to `4.6.0-beta.3` until a stable fixed release is available. Thanks to [@LCubero](https://github.com/LCubero) for reporting [#623](https://github.com/nicobailon/pi-mcp-adapter/issues/623), and [@Kristinita](https://github.com/Kristinita) and [@makenowjust](https://github.com/makenowjust) for the upstream reproduction and fix.
|
|
30
|
+
- Large direct-tool advisories now use Pi's renderer in interactive sessions, avoiding raw console output and duplicate warnings. Thanks to [@grivper](https://github.com/grivper) for issue [#633](https://github.com/nicobailon/pi-mcp-adapter/issues/633).
|
|
31
|
+
- Windows contributors can run `npm test` again; the runner now launches npm through `cross-spawn` so hardened Node versions can execute `npm.cmd`. Thanks @insuffer for the fix.
|
|
32
|
+
|
|
33
|
+
## [2.35.0] - 2026-09-20
|
|
34
|
+
|
|
35
|
+
### Highlights
|
|
36
|
+
|
|
37
|
+
- Describe what you want to do and let Jev find the MCP tools that best match your request.
|
|
38
|
+
- Run long-lived MCP Tasks with progress polling, interactive input, and cancellation.
|
|
39
|
+
- Edit shared MCP configuration without leaving Pi and approve a server for the rest of the session.
|
|
40
|
+
- Reconnect to OAuth and bearer-token servers more reliably, including after expired credentials or authorization failures.
|
|
41
|
+
- Find and use tools more accurately across CJK queries, namespaced catalogs, structured results, and ambiguous names.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Opt-in TypeSafe Jev support can understand a request, rank MCP tools by how well they match, and evaluate intermediate `mcpScript` results. Credentials stay in the OS keyring or environment, sharing MCP data requires an explicit server allowlist, and requests have configurable limits. In a live test across 95 local tools and resources, Jev chose the expected result first in 10 of 11 answerable cases and second once, compared with 5 first-place matches from regular text search. Part of [#611](https://github.com/nicobailon/pi-mcp-adapter/issues/611).
|
|
46
|
+
- `/mcp edit [project|global]` opens the shared MCP config in an editor (Ctrl+G opens `$EDITOR`), refuses text that is not a JSONC object, and reloads after a save. Closes #593. Thanks to [@turisanapo](https://github.com/turisanapo) for PR #594.
|
|
47
|
+
- Support for MCP Tasks. Long-running tool calls are polled to completion, interactive questions use the normal Pi interface, cancellation is forwarded to the server, and failures are reported like ordinary tool-call errors. Task support activates only when the server advertises it and can be disabled per server with `tasks: false`. Thanks to [@rgarcia](https://github.com/rgarcia) for PR #620.
|
|
48
|
+
- Users can grant runtime-only approval for all tools and arguments on a server for the current session. Thanks to [@derdossi](https://github.com/derdossi) for PR #618.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Development and peer dependency coverage now includes Pi 0.86.
|
|
53
|
+
- Self-namespaced MCP tools no longer receive duplicate prefixes, and proxy calls now resolve unique canonical-name candidates while failing closed on collisions and ambiguity. Fixes [#609](https://github.com/nicobailon/pi-mcp-adapter/issues/609). Thanks to [@elkaix](https://github.com/elkaix) for the report.
|
|
54
|
+
- Namespace proxy tools can now be disabled with `settings.namespaceProxyTools: false`. Thanks to [@k03mad](https://github.com/k03mad) for PR #592.
|
|
55
|
+
- The bundled `mcp-scripting` skill is now discovered alongside the default-on `mcpScript` tool and hidden with it when `settings.scriptMode` is `false`. Thanks to [@zhangyoufu](https://github.com/zhangyoufu) for PR #583.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- HTTP streams authenticated with `requestHeadersCommand` remain cancellable after garbage collection. Thanks to [@zaini](https://github.com/zaini) for PR #619.
|
|
60
|
+
- Bearer-token and TypeSafe key storage now retry revoked Linux session-keyring operations through the packaged `keyctl` helper, without special launch commands or plaintext fallback. Set `PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY=1` to disable recovery. Thanks to [@magoz](https://github.com/magoz) for PR #621.
|
|
61
|
+
- OAuth-enabled MCP servers now reconnect reliably after explicit OAuth, stored-token, and 401 authentication paths. Thanks to [@jaresty](https://github.com/jaresty) for PR #624.
|
|
62
|
+
- Direct tools now recover stringified array and object arguments declared through type arrays and schema unions without coercing values that are valid strings. Thanks to [@sashkachan](https://github.com/sashkachan) for issue [#606](https://github.com/nicobailon/pi-mcp-adapter/issues/606).
|
|
63
|
+
- Default tool search now supports CJK text, including unseparated mixed-script queries and configured search keywords, while retaining bounded lexical matching. Thanks to [@wjunhere](https://github.com/wjunhere) for issue [#607](https://github.com/nicobailon/pi-mcp-adapter/issues/607).
|
|
64
|
+
- Command-backed bearer tokens now refresh through a TTL cache, and keep-alive bearer connections reconnect after a 401. Thanks to [@kesor](https://github.com/kesor) for PR #608.
|
|
65
|
+
- Tool results now preserve `structuredContent` alongside ordinary content in direct and proxy calls. Thanks to [@civcode](https://github.com/civcode) for issue #588.
|
|
66
|
+
- Restored the MCP footer status during cache-backed deferred startup without eagerly loading or connecting the runtime. Thanks to [@pkulyn](https://github.com/pkulyn) for issue #586.
|
|
67
|
+
- Oversized object `structuredContent` summaries now identify themselves as omitted and account for preserved and dropped fields, so extension consumers do not mistake a partial preview for an empty payload. Thanks to [@Batchputz](https://github.com/Batchputz) for issue #585.
|
|
68
|
+
- Server-scoped tool describe and call requests now fail closed when a name exactly identifies different displayed and upstream tools. Thanks to [@sheurich](https://github.com/sheurich) for PR #587.
|
|
69
|
+
- Config writes now preserve resolvable existing symlinks by atomically replacing their targets. Thanks to [@peedrr](https://github.com/peedrr) for #597.
|
|
70
|
+
- Server-returned MCP tool errors no longer include misleading input-schema guidance, while invalid proxy arguments are rejected before dispatch. Thanks to [@jaresty](https://github.com/jaresty) for PR #596.
|
|
71
|
+
|
|
10
72
|
## [2.34.0] - 2026-09-14
|
|
11
73
|
|
|
12
74
|
### Highlights
|