@oh-my-pi/pi-coding-agent 16.5.0 → 16.5.2
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 +91 -0
- package/dist/cli.js +3556 -3504
- package/dist/types/advisor/advise-tool.d.ts +12 -1
- package/dist/types/advisor/runtime.d.ts +41 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-cli.d.ts +3 -0
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +8 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +22 -0
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +3 -3
- package/dist/types/eval/bridge-timeout.d.ts +9 -1
- package/dist/types/eval/js/context-manager.d.ts +5 -3
- package/dist/types/eval/js/process-entry.d.ts +6 -0
- package/dist/types/eval/js/worker-core.d.ts +15 -1
- package/dist/types/eval/py/spawn-options.d.ts +10 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +3 -6
- package/dist/types/extensibility/plugins/bun-git-cache.d.ts +3 -0
- package/dist/types/goals/guided-setup.d.ts +12 -0
- package/dist/types/internal-urls/history-protocol.d.ts +3 -2
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -7
- package/dist/types/internal-urls/registry-helpers.d.ts +19 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -0
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +10 -0
- package/dist/types/modes/components/dynamic-border.d.ts +5 -3
- package/dist/types/modes/components/login-dialog.d.ts +2 -0
- package/dist/types/modes/components/mcp-add-wizard.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +0 -2
- package/dist/types/modes/components/transcript-container.d.ts +3 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +26 -6
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +12 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/exit-diagnostics.d.ts +11 -0
- package/dist/types/session/messages.d.ts +15 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +11 -0
- package/dist/types/subprocess/worker-client.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -0
- package/dist/types/tools/grep.d.ts +0 -2
- package/dist/types/tools/read.d.ts +0 -4
- package/dist/types/web/search/provider.d.ts +10 -3
- package/dist/types/web/search/providers/codex.d.ts +5 -4
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +917 -42
- package/src/advisor/advise-tool.ts +17 -1
- package/src/advisor/runtime.ts +270 -67
- package/src/autolearn/controller.ts +15 -3
- package/src/autoresearch/dashboard.ts +2 -2
- package/src/cli/args.ts +12 -0
- package/src/cli/auth-broker-cli.ts +30 -11
- package/src/cli/auth-gateway-cli.ts +5 -1
- package/src/cli/config-cli.ts +15 -3
- package/src/cli/dry-balance-cli.ts +14 -4
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +62 -11
- package/src/cli/usage-cli.ts +58 -5
- package/src/cli/usage-error.ts +7 -0
- package/src/cli.ts +23 -1
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commands/token.ts +3 -1
- package/src/config/api-key-resolver.ts +12 -3
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +21 -7
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +10 -0
- package/src/cursor.ts +2 -0
- package/src/discovery/claude-plugins.ts +9 -3
- package/src/discovery/omp-plugins.ts +6 -2
- package/src/discovery/substitute-plugin-root.ts +32 -0
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +106 -0
- package/src/eval/__tests__/js-context-manager.test.ts +158 -1
- package/src/eval/__tests__/kernel-spawn.test.ts +12 -0
- package/src/eval/__tests__/prelude-agent.test.ts +20 -0
- package/src/eval/__tests__/process-entry-import.test.ts +27 -0
- package/src/eval/agent-bridge.ts +121 -116
- package/src/eval/backend.ts +3 -3
- package/src/eval/bridge-timeout.ts +20 -2
- package/src/eval/executor-base.ts +85 -7
- package/src/eval/jl/kernel.ts +2 -1
- package/src/eval/js/context-manager.ts +109 -32
- package/src/eval/js/process-entry.ts +27 -0
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/js/worker-core.ts +70 -9
- package/src/eval/js/worker-entry.ts +1 -1
- package/src/eval/py/__tests__/prelude.test.ts +72 -0
- package/src/eval/py/kernel.ts +2 -1
- package/src/eval/py/prelude.py +28 -1
- package/src/eval/py/spawn-options.ts +13 -0
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/eval/rb/kernel.ts +2 -1
- package/src/exec/bash-executor.ts +30 -43
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/extensions/wrapper.ts +18 -18
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +32 -16
- package/src/extensibility/plugins/manager.ts +28 -7
- package/src/goals/guided-setup.ts +29 -1
- package/src/internal-urls/history-protocol.ts +95 -15
- package/src/internal-urls/memory-protocol.ts +13 -9
- package/src/internal-urls/registry-helpers.ts +50 -1
- package/src/launch/broker.ts +38 -25
- package/src/lsp/client.ts +7 -1
- package/src/main.ts +29 -0
- package/src/mcp/oauth-discovery.ts +20 -1
- package/src/mcp/oauth-flow.ts +3 -1
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/modes/components/__tests__/dynamic-border.test.ts +55 -0
- package/src/modes/components/agent-dashboard.ts +2 -2
- package/src/modes/components/agent-hub.ts +15 -2
- package/src/modes/components/agent-transcript-viewer.ts +2 -2
- package/src/modes/components/chat-transcript-builder.ts +26 -4
- package/src/modes/components/dynamic-border.ts +9 -6
- package/src/modes/components/extensions/extension-list.ts +2 -2
- package/src/modes/components/hook-selector.ts +10 -4
- package/src/modes/components/login-dialog.ts +5 -0
- package/src/modes/components/mcp-add-wizard.ts +5 -0
- package/src/modes/components/plan-review-overlay.ts +11 -11
- package/src/modes/components/read-tool-group.ts +1 -8
- package/src/modes/components/status-line/component.ts +10 -1
- package/src/modes/components/transcript-container.ts +110 -7
- package/src/modes/controllers/command-controller.ts +12 -4
- package/src/modes/controllers/event-controller.ts +80 -15
- package/src/modes/controllers/input-controller.ts +4 -2
- package/src/modes/controllers/mcp-command-controller.ts +6 -7
- package/src/modes/controllers/selector-controller.ts +24 -5
- package/src/modes/controllers/todo-command-controller.ts +18 -14
- package/src/modes/interactive-mode.ts +7 -3
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/prompt-action-autocomplete.ts +6 -1
- package/src/modes/rpc/host-tools.ts +15 -0
- package/src/modes/rpc/rpc-mode.ts +123 -48
- package/src/modes/types.ts +1 -1
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +54 -0
- package/src/modes/utils/ui-helpers.ts +27 -1
- package/src/modes/workflow.ts +6 -5
- package/src/prompts/advisor/system.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/grep.md +1 -2
- package/src/prompts/tools/read.md +2 -4
- package/src/sdk.ts +61 -34
- package/src/session/agent-session.ts +283 -39
- package/src/session/exit-diagnostics.ts +108 -0
- package/src/session/messages.test.ts +66 -0
- package/src/session/messages.ts +37 -0
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/helpers/active-oauth-account.ts +22 -2
- package/src/slash-commands/helpers/logout.ts +23 -3
- package/src/slash-commands/helpers/usage-report.ts +14 -2
- package/src/subprocess/worker-client.ts +9 -2
- package/src/system-prompt.test.ts +36 -0
- package/src/system-prompt.ts +1 -1
- package/src/task/executor.ts +8 -0
- package/src/task/render.test.ts +36 -0
- package/src/task/render.ts +55 -43
- package/src/tools/bash-skill-urls.ts +4 -1
- package/src/tools/bash.ts +1 -0
- package/src/tools/browser/registry.ts +17 -3
- package/src/tools/eval.ts +14 -9
- package/src/tools/gh.ts +3 -1
- package/src/tools/grep.ts +5 -45
- package/src/tools/path-utils.ts +7 -1
- package/src/tools/read.ts +23 -74
- package/src/tools/write.ts +82 -9
- package/src/tools/yield.ts +29 -1
- package/src/utils/title-generator.ts +10 -6
- package/src/web/search/index.ts +39 -22
- package/src/web/search/provider.ts +33 -16
- package/src/web/search/providers/codex.ts +68 -21
- package/src/web/search/providers/perplexity-auth.ts +20 -11
- package/src/web/search/providers/perplexity.ts +14 -2
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -15,8 +15,10 @@ import type {
|
|
|
15
15
|
CustomToolResult,
|
|
16
16
|
RenderResultOptions,
|
|
17
17
|
} from "../extensibility/custom-tools/types";
|
|
18
|
+
import { resolveLocalUrlToFile } from "../internal-urls/local-protocol";
|
|
18
19
|
import type { Theme } from "../modes/theme/theme";
|
|
19
20
|
import type { OutputMeta } from "../tools/output-meta";
|
|
21
|
+
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
20
22
|
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
21
23
|
import { callTool } from "./client";
|
|
22
24
|
import { renderMCPCall, renderMCPResult } from "./render";
|
|
@@ -104,13 +106,62 @@ function stripHarnessIntent(args: MCPToolArgs, inputSchema: MCPToolDefinition["i
|
|
|
104
106
|
return rest;
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
async function resolveOutboundLocalUrlArgs(
|
|
110
|
+
value: unknown,
|
|
111
|
+
context: CustomToolContext,
|
|
112
|
+
seen: WeakSet<object> = new WeakSet(),
|
|
113
|
+
): Promise<unknown> {
|
|
114
|
+
if (typeof value === "string") {
|
|
115
|
+
const normalized = normalizeLocalScheme(value);
|
|
116
|
+
if (!normalized.startsWith("local://")) return value;
|
|
117
|
+
const localFile = await resolveLocalUrlToFile(normalized, {
|
|
118
|
+
cwd: context.sessionManager?.getCwd?.(),
|
|
119
|
+
settings: context.settings,
|
|
120
|
+
localProtocolOptions: context.localProtocolOptions,
|
|
121
|
+
});
|
|
122
|
+
return localFile?.path ?? value;
|
|
123
|
+
}
|
|
124
|
+
if (typeof value !== "object" || value === null) return value;
|
|
125
|
+
if (seen.has(value)) return value;
|
|
126
|
+
seen.add(value);
|
|
127
|
+
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
let resolved: unknown[] | undefined;
|
|
130
|
+
for (let index = 0; index < value.length; index++) {
|
|
131
|
+
const item = value[index];
|
|
132
|
+
const next = await resolveOutboundLocalUrlArgs(item, context, seen);
|
|
133
|
+
if (next === item && !resolved) continue;
|
|
134
|
+
resolved ??= value.slice();
|
|
135
|
+
resolved[index] = next;
|
|
136
|
+
}
|
|
137
|
+
return resolved ?? value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const input = value as Record<string, unknown>;
|
|
141
|
+
let resolved: Record<string, unknown> | undefined;
|
|
142
|
+
for (const key in input) {
|
|
143
|
+
const item = input[key];
|
|
144
|
+
const next = await resolveOutboundLocalUrlArgs(item, context, seen);
|
|
145
|
+
if (next === item && !resolved) continue;
|
|
146
|
+
resolved ??= { ...input };
|
|
147
|
+
resolved[key] = next;
|
|
148
|
+
}
|
|
149
|
+
return resolved ?? value;
|
|
150
|
+
}
|
|
151
|
+
|
|
107
152
|
/**
|
|
108
153
|
* Normalize raw tool params into the outbound `tools/call` arguments: strip
|
|
109
|
-
* the harness intent field,
|
|
110
|
-
* declares but doesn't require
|
|
154
|
+
* the harness intent field, drop optional empty placeholders the server
|
|
155
|
+
* declares but doesn't require, then translate session-local files to paths
|
|
156
|
+
* external MCP servers can read.
|
|
111
157
|
*/
|
|
112
|
-
function prepareOutboundArgs(
|
|
113
|
-
|
|
158
|
+
async function prepareOutboundArgs(
|
|
159
|
+
params: unknown,
|
|
160
|
+
inputSchema: MCPToolDefinition["inputSchema"],
|
|
161
|
+
context: CustomToolContext,
|
|
162
|
+
): Promise<MCPToolArgs> {
|
|
163
|
+
const args = omitUnusedOptionalArgs(stripHarnessIntent(normalizeToolArgs(params), inputSchema), inputSchema);
|
|
164
|
+
return (await resolveOutboundLocalUrlArgs(args, context)) as MCPToolArgs;
|
|
114
165
|
}
|
|
115
166
|
|
|
116
167
|
/** Details included in MCP tool results for rendering */
|
|
@@ -316,7 +367,7 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
316
367
|
signal?: AbortSignal,
|
|
317
368
|
): Promise<CustomToolResult<MCPToolDetails>> {
|
|
318
369
|
throwIfAborted(signal);
|
|
319
|
-
const args = prepareOutboundArgs(params, this.tool.inputSchema);
|
|
370
|
+
const args = await prepareOutboundArgs(params, this.tool.inputSchema, _ctx);
|
|
320
371
|
const provider = this.connection._source?.provider;
|
|
321
372
|
const providerName = this.connection._source?.providerName;
|
|
322
373
|
|
|
@@ -415,7 +466,7 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
415
466
|
signal?: AbortSignal,
|
|
416
467
|
): Promise<CustomToolResult<MCPToolDetails>> {
|
|
417
468
|
throwIfAborted(signal);
|
|
418
|
-
const args = prepareOutboundArgs(params, this.tool.inputSchema);
|
|
469
|
+
const args = await prepareOutboundArgs(params, this.tool.inputSchema, _ctx);
|
|
419
470
|
const provider = this.#fallbackProvider;
|
|
420
471
|
const providerName = this.#fallbackProviderName;
|
|
421
472
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { getThemeByName, setThemeInstance, theme } from "../../theme/theme";
|
|
6
|
+
import { DynamicBorder } from "../dynamic-border";
|
|
7
|
+
|
|
8
|
+
const componentPath = path.resolve(import.meta.dir, "../dynamic-border.ts");
|
|
9
|
+
|
|
10
|
+
describe("DynamicBorder", () => {
|
|
11
|
+
// Regression for #5366: extensions importing legacy pi UI components get a
|
|
12
|
+
// second `src` module graph whose module-level `theme` is never assigned by
|
|
13
|
+
// host startup. `render()` must degrade to plain glyphs instead of throwing
|
|
14
|
+
// "undefined is not an object (evaluating 'theme.boxRound')" and killing the
|
|
15
|
+
// TUI. Other test files in the same process initialize the theme module, so
|
|
16
|
+
// the uninitialized state is only reachable in a fresh module graph — a
|
|
17
|
+
// subprocess reproduces the actual reported scenario deterministically.
|
|
18
|
+
it("renders plain glyphs when the module-level theme is uninitialized", async () => {
|
|
19
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-dynamic-border-"));
|
|
20
|
+
try {
|
|
21
|
+
const script = [
|
|
22
|
+
`import { DynamicBorder } from ${JSON.stringify(componentPath)};`,
|
|
23
|
+
'const custom = new DynamicBorder(str => "<" + str + ">");',
|
|
24
|
+
`if (JSON.stringify(custom.render(4)) !== JSON.stringify(["<────>"])) process.exit(1);`,
|
|
25
|
+
"const plain = new DynamicBorder();",
|
|
26
|
+
`if (JSON.stringify(plain.render(3)) !== JSON.stringify(["───"])) process.exit(2);`,
|
|
27
|
+
"",
|
|
28
|
+
].join("\n");
|
|
29
|
+
const file = path.join(dir, "uninitialized-theme.ts");
|
|
30
|
+
await Bun.write(file, script);
|
|
31
|
+
const proc = Bun.spawn(["bun", file], { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
|
|
32
|
+
const [exitCode, stderr] = await Promise.all([
|
|
33
|
+
proc.exited,
|
|
34
|
+
new Response(proc.stderr as ReadableStream<Uint8Array>).text(),
|
|
35
|
+
]);
|
|
36
|
+
expect(stderr).toBe("");
|
|
37
|
+
expect(exitCode).toBe(0);
|
|
38
|
+
} finally {
|
|
39
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("paints with theme.boxRound.horizontal once the theme is initialized", async () => {
|
|
44
|
+
const previous = theme;
|
|
45
|
+
const loaded = await getThemeByName("dark");
|
|
46
|
+
if (!loaded) throw new Error("theme unavailable");
|
|
47
|
+
setThemeInstance(loaded);
|
|
48
|
+
try {
|
|
49
|
+
const border = new DynamicBorder(str => `<${str}>`);
|
|
50
|
+
expect(border.render(3)).toEqual([`<${loaded.boxRound.horizontal.repeat(3)}>`]);
|
|
51
|
+
} finally {
|
|
52
|
+
if (previous) setThemeInstance(previous);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -1142,11 +1142,11 @@ export class AgentDashboard extends Container {
|
|
|
1142
1142
|
return;
|
|
1143
1143
|
}
|
|
1144
1144
|
|
|
1145
|
-
if (matchesSelectUp(data) || data
|
|
1145
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
1146
1146
|
this.#moveSelection(-1);
|
|
1147
1147
|
return;
|
|
1148
1148
|
}
|
|
1149
|
-
if (matchesSelectDown(data) || data
|
|
1149
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
1150
1150
|
this.#moveSelection(1);
|
|
1151
1151
|
return;
|
|
1152
1152
|
}
|
|
@@ -340,6 +340,19 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
340
340
|
this.#handleTableInput(keyData);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Seed the table's left-left close detector with the current time so a single
|
|
345
|
+
* subsequent `←` (within {@link LEFT_TAP_WINDOW_MS}) dismisses the hub.
|
|
346
|
+
*
|
|
347
|
+
* The editor's own double-tap detector consumes the `←←` that opens the hub,
|
|
348
|
+
* leaving this detector at its fresh `0` — without this handoff the user would
|
|
349
|
+
* have to press `←←` a second time to escape. Called by the opener when the hub
|
|
350
|
+
* was raised by that gesture.
|
|
351
|
+
*/
|
|
352
|
+
armCloseTap(): void {
|
|
353
|
+
this.#lastLeftTap = Date.now();
|
|
354
|
+
}
|
|
355
|
+
|
|
343
356
|
/**
|
|
344
357
|
* Open the fullscreen transcript viewer for an agent id (public for table Enter
|
|
345
358
|
* and tests). Mounts {@link AgentTranscriptViewer} as a `fullscreen` overlay so it
|
|
@@ -573,14 +586,14 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
573
586
|
}
|
|
574
587
|
return;
|
|
575
588
|
}
|
|
576
|
-
if (keyData
|
|
589
|
+
if (matchesKey(keyData, "j") || matchesSelectDown(keyData)) {
|
|
577
590
|
if (this.#rows.length > 0) {
|
|
578
591
|
this.#selectedRow = Math.min(this.#selectedRow + 1, this.#rows.length - 1);
|
|
579
592
|
}
|
|
580
593
|
this.#requestRender();
|
|
581
594
|
return;
|
|
582
595
|
}
|
|
583
|
-
if (keyData
|
|
596
|
+
if (matchesKey(keyData, "k") || matchesSelectUp(keyData)) {
|
|
584
597
|
if (this.#rows.length > 0) {
|
|
585
598
|
this.#selectedRow = Math.max(this.#selectedRow - 1, 0);
|
|
586
599
|
}
|
|
@@ -496,9 +496,9 @@ export class AgentTranscriptViewer implements Component {
|
|
|
496
496
|
this.deps.requestRender();
|
|
497
497
|
return true;
|
|
498
498
|
}
|
|
499
|
-
if (data
|
|
499
|
+
if (matchesKey(data, "j") || matchesSelectDown(data)) {
|
|
500
500
|
this.#scrollView.scroll(1);
|
|
501
|
-
} else if (data
|
|
501
|
+
} else if (matchesKey(data, "k") || matchesSelectUp(data)) {
|
|
502
502
|
this.#scrollView.scroll(-1);
|
|
503
503
|
} else if (data === "g") {
|
|
504
504
|
this.#scrollView.scrollToTop();
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
buildIrcMessageCard,
|
|
36
36
|
normalizeToolArgs,
|
|
37
37
|
resolveAssistantErrorPresentation,
|
|
38
|
+
splitAssistantMessageToolTimeline,
|
|
38
39
|
} from "../utils/transcript-render-helpers";
|
|
39
40
|
import { createAdvisorMessageCard } from "./advisor-message";
|
|
40
41
|
import { AssistantMessageComponent } from "./assistant-message";
|
|
@@ -273,12 +274,13 @@ export class ChatTranscriptBuilder {
|
|
|
273
274
|
#appendAssistantMessage(message: Extract<AgentMessage, { role: "assistant" }>): void {
|
|
274
275
|
const hideThinkingBlock = this.deps.hideThinkingBlock?.() ?? false;
|
|
275
276
|
const proseOnlyThinking = this.deps.proseOnlyThinking ? this.deps.proseOnlyThinking() : true;
|
|
277
|
+
const timeline = splitAssistantMessageToolTimeline(message);
|
|
276
278
|
const assistantComponent = new AssistantMessageComponent(
|
|
277
|
-
|
|
279
|
+
timeline.beforeTools,
|
|
278
280
|
hideThinkingBlock,
|
|
279
281
|
() => this.deps.requestRender(),
|
|
280
282
|
this.deps.getMessageRenderer ? undefined : [], // placeholder for thinkingRenderers
|
|
281
|
-
|
|
283
|
+
this.deps.ui.imageBudget,
|
|
282
284
|
proseOnlyThinking,
|
|
283
285
|
);
|
|
284
286
|
this.container.addChild(assistantComponent);
|
|
@@ -301,11 +303,24 @@ export class ChatTranscriptBuilder {
|
|
|
301
303
|
const errorPresentation = resolveAssistantErrorPresentation(message);
|
|
302
304
|
const hasErrorStop = errorPresentation.kind === "full";
|
|
303
305
|
const errorMessage = hasErrorStop ? errorPresentation.text : null;
|
|
306
|
+
const appendAssistantSegment = (segment: Extract<AgentMessage, { role: "assistant" }> | undefined) => {
|
|
307
|
+
if (!segment || !assistantHasVisibleContent(segment)) return;
|
|
308
|
+
const component = new AssistantMessageComponent(
|
|
309
|
+
segment,
|
|
310
|
+
hideThinkingBlock,
|
|
311
|
+
() => this.deps.requestRender(),
|
|
312
|
+
this.deps.getMessageRenderer ? undefined : [],
|
|
313
|
+
undefined,
|
|
314
|
+
proseOnlyThinking,
|
|
315
|
+
);
|
|
316
|
+
this.container.addChild(component);
|
|
317
|
+
};
|
|
304
318
|
|
|
305
319
|
for (const content of message.content) {
|
|
306
320
|
if (content.type !== "toolCall") continue;
|
|
307
321
|
this.#resolveWaitingPoll(content.name);
|
|
308
322
|
|
|
323
|
+
const afterToolSegment = timeline.afterToolCalls.get(content.id);
|
|
309
324
|
if (
|
|
310
325
|
content.name === "read" &&
|
|
311
326
|
readArgsHaveTarget(content.arguments) &&
|
|
@@ -319,10 +334,15 @@ export class ChatTranscriptBuilder {
|
|
|
319
334
|
false,
|
|
320
335
|
content.id,
|
|
321
336
|
);
|
|
337
|
+
} else if (afterToolSegment) {
|
|
338
|
+
const group = this.#ensureReadGroup();
|
|
339
|
+
group.updateArgs(content.arguments, content.id);
|
|
340
|
+
this.#pendingTools.set(content.id, group);
|
|
322
341
|
} else {
|
|
323
342
|
const normalizedArgs = normalizeToolArgs(content.arguments);
|
|
324
343
|
this.#readArgs.set(content.id, normalizedArgs);
|
|
325
344
|
}
|
|
345
|
+
appendAssistantSegment(afterToolSegment);
|
|
326
346
|
continue;
|
|
327
347
|
}
|
|
328
348
|
|
|
@@ -332,8 +352,9 @@ export class ChatTranscriptBuilder {
|
|
|
332
352
|
content.name,
|
|
333
353
|
content.arguments,
|
|
334
354
|
{
|
|
335
|
-
//
|
|
336
|
-
|
|
355
|
+
// Stable ids and Kitty placeholder cells keep images anchored
|
|
356
|
+
// while the transcript viewport scrolls and reflows.
|
|
357
|
+
showImages: settings.get("terminal.showImages"),
|
|
337
358
|
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
338
359
|
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
339
360
|
liveRegion: this.container,
|
|
@@ -355,6 +376,7 @@ export class ChatTranscriptBuilder {
|
|
|
355
376
|
} else {
|
|
356
377
|
this.#pendingTools.set(content.id, component);
|
|
357
378
|
}
|
|
379
|
+
appendAssistantSegment(afterToolSegment);
|
|
358
380
|
}
|
|
359
381
|
|
|
360
382
|
this.#pendingUsage =
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { theme } from "../../modes/theme/theme";
|
|
2
|
+
import { fgOrPlain, theme } from "../../modes/theme/theme";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Dynamic border component that adjusts to viewport width.
|
|
6
6
|
*
|
|
7
|
-
* Note:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Note: the module-level `theme` may be `undefined` — when loaded through jiti
|
|
8
|
+
* (separate module cache) or from a second `src` module graph in npm-package
|
|
9
|
+
* installs, where the host bundle assigns `theme` but this copy never sees it
|
|
10
|
+
* (issue #5366). Both the default color and `render()` degrade to plain,
|
|
11
|
+
* unstyled output instead of crashing the TUI.
|
|
10
12
|
*/
|
|
11
13
|
export class DynamicBorder implements Component {
|
|
12
14
|
#color: (str: string) => string;
|
|
13
15
|
#cachedWidth = -1;
|
|
14
16
|
#cachedLines: string[] | undefined;
|
|
15
17
|
|
|
16
|
-
constructor(color: (str: string) => string = str =>
|
|
18
|
+
constructor(color: (str: string) => string = str => fgOrPlain("border", str)) {
|
|
17
19
|
this.#color = color;
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -26,7 +28,8 @@ export class DynamicBorder implements Component {
|
|
|
26
28
|
if (this.#cachedLines && this.#cachedWidth === width) {
|
|
27
29
|
return this.#cachedLines;
|
|
28
30
|
}
|
|
29
|
-
const
|
|
31
|
+
const horizontal = typeof theme === "undefined" ? "─" : theme.boxRound.horizontal;
|
|
32
|
+
const lines = [this.#color(horizontal.repeat(Math.max(1, width)))];
|
|
30
33
|
this.#cachedWidth = width;
|
|
31
34
|
this.#cachedLines = lines;
|
|
32
35
|
return lines;
|
|
@@ -449,12 +449,12 @@ export class ExtensionList implements Component {
|
|
|
449
449
|
|
|
450
450
|
handleInput(data: string): void {
|
|
451
451
|
// Navigation
|
|
452
|
-
if (matchesSelectUp(data) || data
|
|
452
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
453
453
|
this.#moveSelectionUp();
|
|
454
454
|
return;
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
if (matchesSelectDown(data) || data
|
|
457
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
458
458
|
this.#moveSelectionDown();
|
|
459
459
|
return;
|
|
460
460
|
}
|
|
@@ -652,17 +652,23 @@ export class HookSelectorComponent extends Container {
|
|
|
652
652
|
return;
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
-
if (matchesSelectUp(keyData) || (!this.#isSearchEnabled() && keyData
|
|
655
|
+
if (matchesSelectUp(keyData) || (!this.#isSearchEnabled() && matchesKey(keyData, "k"))) {
|
|
656
656
|
this.#moveSelection(-1);
|
|
657
|
-
} else if (matchesSelectDown(keyData) || (!this.#isSearchEnabled() && keyData
|
|
657
|
+
} else if (matchesSelectDown(keyData) || (!this.#isSearchEnabled() && matchesKey(keyData, "j"))) {
|
|
658
658
|
this.#moveSelection(1);
|
|
659
659
|
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
660
660
|
const selected = this.#filteredOptions[this.#selectedIndex];
|
|
661
661
|
if (selected && !this.#isDisabled(selected.index)) this.#onSelectCallback(selected.option.label);
|
|
662
|
-
} else if (
|
|
662
|
+
} else if (
|
|
663
|
+
matchesKey(keyData, "left") ||
|
|
664
|
+
(this.#slider && !this.#isSearchEnabled() && matchesKey(keyData, "h"))
|
|
665
|
+
) {
|
|
663
666
|
if (this.#slider) this.#moveSlider(-1);
|
|
664
667
|
else this.#onLeftCallback?.();
|
|
665
|
-
} else if (
|
|
668
|
+
} else if (
|
|
669
|
+
matchesKey(keyData, "right") ||
|
|
670
|
+
(this.#slider && !this.#isSearchEnabled() && matchesKey(keyData, "l"))
|
|
671
|
+
) {
|
|
666
672
|
if (this.#slider) this.#moveSlider(1);
|
|
667
673
|
else this.#onRightCallback?.();
|
|
668
674
|
} else if (this.#onExternalEditorCallback && matchesAppExternalEditor(keyData)) {
|
|
@@ -164,6 +164,11 @@ export class LoginDialogComponent extends Container {
|
|
|
164
164
|
this.#tui.requestRender();
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/** Route non-bracketed paste transports into the active login input. */
|
|
168
|
+
pasteText(text: string): void {
|
|
169
|
+
this.#input.pasteText(text);
|
|
170
|
+
}
|
|
171
|
+
|
|
167
172
|
handleInput(data: string): void {
|
|
168
173
|
const kb = getKeybindings();
|
|
169
174
|
|
|
@@ -63,6 +63,7 @@ export interface MCPAddWizardOAuthResult {
|
|
|
63
63
|
interface MCPAddWizardOAuthOptions {
|
|
64
64
|
serverUrl?: string;
|
|
65
65
|
resource?: string;
|
|
66
|
+
registrationUrl?: string;
|
|
66
67
|
/**
|
|
67
68
|
* External cancellation source. Aborting it tears down the in-flight OAuth
|
|
68
69
|
* flow and surfaces a neutral cancellation error. The wizard wires its own
|
|
@@ -82,6 +83,7 @@ interface WizardState {
|
|
|
82
83
|
authMethod: AuthMethod;
|
|
83
84
|
oauthAuthUrl: string;
|
|
84
85
|
oauthTokenUrl: string;
|
|
86
|
+
oauthRegistrationUrl: string;
|
|
85
87
|
oauthClientId: string;
|
|
86
88
|
oauthClientSecret: string;
|
|
87
89
|
oauthScopes: string;
|
|
@@ -113,6 +115,7 @@ export class MCPAddWizard extends Container {
|
|
|
113
115
|
authMethod: "none",
|
|
114
116
|
oauthAuthUrl: "",
|
|
115
117
|
oauthTokenUrl: "",
|
|
118
|
+
oauthRegistrationUrl: "",
|
|
116
119
|
oauthClientId: "",
|
|
117
120
|
oauthClientSecret: "",
|
|
118
121
|
oauthScopes: "",
|
|
@@ -1026,6 +1029,7 @@ export class MCPAddWizard extends Container {
|
|
|
1026
1029
|
if (oauth) {
|
|
1027
1030
|
this.#state.oauthAuthUrl = oauth.authorizationUrl;
|
|
1028
1031
|
this.#state.oauthTokenUrl = oauth.tokenUrl;
|
|
1032
|
+
this.#state.oauthRegistrationUrl = oauth.registrationUrl || "";
|
|
1029
1033
|
this.#state.oauthClientId = oauth.clientId || "";
|
|
1030
1034
|
this.#state.oauthScopes = oauth.scopes || "";
|
|
1031
1035
|
this.#state.oauthResource = oauth.resource || (this.#state.transport === "stdio" ? "" : this.#state.url);
|
|
@@ -1196,6 +1200,7 @@ export class MCPAddWizard extends Container {
|
|
|
1196
1200
|
this.#state.oauthScopes,
|
|
1197
1201
|
{
|
|
1198
1202
|
serverUrl: this.#state.url || undefined,
|
|
1203
|
+
registrationUrl: this.#state.oauthRegistrationUrl || undefined,
|
|
1199
1204
|
resource: oauthResource || undefined,
|
|
1200
1205
|
abortSignal: this.#oauthAbort.signal,
|
|
1201
1206
|
},
|
|
@@ -400,8 +400,8 @@ export class PlanReviewOverlay implements Component {
|
|
|
400
400
|
// Left/right always drive the slider. The sidebar sits beside the body
|
|
401
401
|
// (above this row), not the slider, so stealing left for it would strand
|
|
402
402
|
// the operator unable to step the model tier back — reach the ToC via Tab.
|
|
403
|
-
const isLeft = matchesKey(data, "left") || (this.#slider !== undefined && data
|
|
404
|
-
const isRight = matchesKey(data, "right") || (this.#slider !== undefined && data
|
|
403
|
+
const isLeft = matchesKey(data, "left") || (this.#slider !== undefined && matchesKey(data, "h"));
|
|
404
|
+
const isRight = matchesKey(data, "right") || (this.#slider !== undefined && matchesKey(data, "l"));
|
|
405
405
|
if (isLeft) {
|
|
406
406
|
this.#moveSlider(-1);
|
|
407
407
|
return;
|
|
@@ -410,12 +410,12 @@ export class PlanReviewOverlay implements Component {
|
|
|
410
410
|
this.#moveSlider(1);
|
|
411
411
|
return;
|
|
412
412
|
}
|
|
413
|
-
if (matchesSelectUp(data) || data
|
|
413
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
414
414
|
if (this.#selectedIndex === this.#firstEnabledIndex()) this.#setFocus("body");
|
|
415
415
|
else this.#moveSelection(-1);
|
|
416
416
|
return;
|
|
417
417
|
}
|
|
418
|
-
if (matchesSelectDown(data) || data
|
|
418
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
419
419
|
this.#moveSelection(1);
|
|
420
420
|
return;
|
|
421
421
|
}
|
|
@@ -427,13 +427,13 @@ export class PlanReviewOverlay implements Component {
|
|
|
427
427
|
}
|
|
428
428
|
|
|
429
429
|
#handleBody(data: string): void {
|
|
430
|
-
if (matchesKey(data, "left") || data
|
|
430
|
+
if (matchesKey(data, "left") || matchesKey(data, "h")) {
|
|
431
431
|
if (this.#sidebarShown) this.#setFocus("toc");
|
|
432
432
|
return;
|
|
433
433
|
}
|
|
434
434
|
if (
|
|
435
435
|
matchesKey(data, "right") ||
|
|
436
|
-
data
|
|
436
|
+
matchesKey(data, "l") ||
|
|
437
437
|
matchesKey(data, "enter") ||
|
|
438
438
|
matchesKey(data, "return") ||
|
|
439
439
|
data === "\n"
|
|
@@ -444,12 +444,12 @@ export class PlanReviewOverlay implements Component {
|
|
|
444
444
|
// Vertical nav flows between regions at the edges: scrolling off the bottom
|
|
445
445
|
// drops into the actions ("next step"); scrolling off the top steps back up
|
|
446
446
|
// to the ToC.
|
|
447
|
-
if (matchesSelectUp(data) || data
|
|
447
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
448
448
|
if (this.#scrollView.getScrollOffset() <= 0 && this.#sidebarShown) this.#setFocus("toc");
|
|
449
449
|
else this.#scrollView.scroll(-1);
|
|
450
450
|
return;
|
|
451
451
|
}
|
|
452
|
-
if (matchesSelectDown(data) || data
|
|
452
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
453
453
|
if (this.#scrollView.getScrollOffset() >= this.#scrollView.getMaxScrollOffset()) this.#setFocus("actions");
|
|
454
454
|
else this.#scrollView.scroll(1);
|
|
455
455
|
return;
|
|
@@ -470,11 +470,11 @@ export class PlanReviewOverlay implements Component {
|
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
#handleToc(data: string): void {
|
|
473
|
-
if (matchesSelectUp(data) || data
|
|
473
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
474
474
|
this.#moveTocCursor(-1);
|
|
475
475
|
return;
|
|
476
476
|
}
|
|
477
|
-
if (matchesSelectDown(data) || data
|
|
477
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) {
|
|
478
478
|
// Past the last section, fall through to the actions ("next step").
|
|
479
479
|
if (this.#tocCursor >= this.#toc.length - 1) this.#setFocus("actions");
|
|
480
480
|
else this.#moveTocCursor(1);
|
|
@@ -482,7 +482,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
482
482
|
}
|
|
483
483
|
if (
|
|
484
484
|
matchesKey(data, "right") ||
|
|
485
|
-
data
|
|
485
|
+
matchesKey(data, "l") ||
|
|
486
486
|
matchesKey(data, "enter") ||
|
|
487
487
|
matchesKey(data, "return") ||
|
|
488
488
|
data === "\n"
|
|
@@ -37,9 +37,6 @@ export function readArgsTargetInternalUrl(args: unknown): boolean {
|
|
|
37
37
|
type ReadRenderArgs = {
|
|
38
38
|
path?: string;
|
|
39
39
|
file_path?: string;
|
|
40
|
-
selector?: string;
|
|
41
|
-
// Legacy field from the old schema; tolerated for rebuilt transcripts.
|
|
42
|
-
sel?: string;
|
|
43
40
|
};
|
|
44
41
|
|
|
45
42
|
type ReadToolSuffixResolution = {
|
|
@@ -344,11 +341,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
344
341
|
|
|
345
342
|
updateArgs(args: ReadRenderArgs, toolCallId?: string): void {
|
|
346
343
|
if (!toolCallId) return;
|
|
347
|
-
const
|
|
348
|
-
const rawSelector =
|
|
349
|
-
typeof args.selector === "string" ? args.selector : typeof args.sel === "string" ? args.sel : undefined;
|
|
350
|
-
const selector = rawSelector?.trim().replace(/^:+/, "");
|
|
351
|
-
const rawPath = selector && selector.length > 0 ? `${basePath}:${selector}` : basePath;
|
|
344
|
+
const rawPath = args.file_path || args.path || "";
|
|
352
345
|
const entry: ReadEntry = this.#entries.get(toolCallId) ?? {
|
|
353
346
|
toolCallId,
|
|
354
347
|
path: rawPath,
|
|
@@ -780,7 +780,16 @@ export class StatusLineComponent implements Component {
|
|
|
780
780
|
const activeProvider = session.state.model?.provider ?? session.model?.provider ?? "";
|
|
781
781
|
if (!activeProvider) return "";
|
|
782
782
|
const identity = session.modelRegistry?.authStorage?.getOAuthAccountIdentity(activeProvider, session.sessionId);
|
|
783
|
-
|
|
783
|
+
// orgId is part of the key: rotating between two same-email Anthropic
|
|
784
|
+
// subscriptions must invalidate the cached usage immediately instead of
|
|
785
|
+
// showing the previous org's quota for the rest of the cache TTL.
|
|
786
|
+
return [
|
|
787
|
+
activeProvider,
|
|
788
|
+
identity?.accountId ?? "",
|
|
789
|
+
identity?.email ?? "",
|
|
790
|
+
identity?.projectId ?? "",
|
|
791
|
+
identity?.orgId ?? "",
|
|
792
|
+
].join("\0");
|
|
784
793
|
}
|
|
785
794
|
|
|
786
795
|
/**
|