@oh-my-pi/pi-coding-agent 15.11.6 → 15.11.8
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 +57 -1
- package/dist/cli.js +431 -381
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +78 -0
- package/dist/types/collab/crypto.d.ts +12 -0
- package/dist/types/collab/guest.d.ts +21 -0
- package/dist/types/collab/host.d.ts +13 -0
- package/dist/types/collab/protocol.d.ts +100 -0
- package/dist/types/collab/relay-client.d.ts +22 -0
- package/dist/types/commands/bench.d.ts +29 -0
- package/dist/types/commands/join.d.ts +12 -0
- package/dist/types/config/model-resolver.d.ts +3 -2
- package/dist/types/config/settings-schema.d.ts +93 -1
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/extensibility/slash-commands.d.ts +1 -11
- package/dist/types/modes/components/agent-hub.d.ts +13 -0
- package/dist/types/modes/components/collab-prompt-message.d.ts +10 -0
- package/dist/types/modes/components/hook-selector.d.ts +4 -6
- package/dist/types/modes/components/oauth-selector.d.ts +10 -1
- package/dist/types/modes/components/segment-track.d.ts +11 -6
- package/dist/types/modes/components/settings-selector.d.ts +8 -1
- package/dist/types/modes/components/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/modes/components/status-line/component.d.ts +4 -1
- package/dist/types/modes/components/status-line/types.d.ts +9 -0
- package/dist/types/modes/components/tool-execution.d.ts +13 -9
- package/dist/types/modes/interactive-mode.d.ts +7 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +3 -0
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +10 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +3 -0
- package/dist/types/modes/types.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +11 -0
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/snapcompact-inline.d.ts +8 -3
- package/dist/types/slash-commands/builtin-registry.d.ts +9 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/renderers.d.ts +13 -0
- package/dist/types/tools/ssh.d.ts +1 -0
- package/package.json +14 -12
- package/scripts/bench-guard.ts +71 -0
- package/src/cli/args.ts +2 -0
- package/src/cli/bench-cli.ts +437 -0
- package/src/cli-commands.ts +2 -0
- package/src/collab/crypto.ts +57 -0
- package/src/collab/guest.ts +421 -0
- package/src/collab/host.ts +494 -0
- package/src/collab/protocol.ts +191 -0
- package/src/collab/relay-client.ts +216 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/join.ts +39 -0
- package/src/config/model-registry.ts +74 -19
- package/src/config/model-resolver.ts +36 -5
- package/src/config/settings-schema.ts +119 -1
- package/src/edit/renderer.ts +5 -0
- package/src/extensibility/slash-commands.ts +1 -97
- package/src/hindsight/client.ts +26 -1
- package/src/hindsight/state.ts +6 -2
- package/src/internal-urls/docs-index.generated.ts +4 -3
- package/src/main.ts +11 -2
- package/src/mcp/transports/stdio.ts +81 -7
- package/src/modes/components/agent-hub.ts +119 -22
- package/src/modes/components/assistant-message.ts +126 -6
- package/src/modes/components/collab-prompt-message.ts +30 -0
- package/src/modes/components/hook-selector.ts +4 -5
- package/src/modes/components/oauth-selector.ts +67 -7
- package/src/modes/components/segment-track.ts +44 -7
- package/src/modes/components/settings-selector.ts +27 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +11 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/status-line/component.ts +21 -1
- package/src/modes/components/status-line/presets.ts +1 -1
- package/src/modes/components/status-line/segments.ts +13 -0
- package/src/modes/components/status-line/types.ts +10 -0
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +18 -10
- package/src/modes/controllers/input-controller.ts +80 -12
- package/src/modes/controllers/selector-controller.ts +6 -2
- package/src/modes/controllers/streaming-reveal.ts +7 -0
- package/src/modes/interactive-mode.ts +36 -4
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +24 -6
- package/src/modes/setup-wizard/scenes/providers.ts +36 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +10 -1
- package/src/modes/setup-wizard/scenes/theme.ts +28 -1
- package/src/modes/setup-wizard/scenes/types.ts +10 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +22 -6
- package/src/modes/setup-wizard/wizard-overlay.ts +38 -1
- package/src/modes/types.ts +8 -0
- package/src/modes/utils/context-usage.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +7 -0
- package/src/prompts/bench.md +7 -0
- package/src/sdk.ts +240 -36
- package/src/session/agent-session.ts +22 -0
- package/src/session/session-manager.ts +44 -0
- package/src/session/snapcompact-inline.ts +20 -22
- package/src/slash-commands/builtin-registry.ts +210 -0
- package/src/tools/bash.ts +3 -0
- package/src/tools/eval-render.ts +4 -0
- package/src/tools/read.ts +38 -5
- package/src/tools/renderers.ts +13 -0
- package/src/tools/ssh.ts +3 -0
- package/src/tools/write.ts +13 -42
|
@@ -3,8 +3,11 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
5
5
|
import { setNextRequestDebugPath } from "@oh-my-pi/pi-ai/utils/request-debug";
|
|
6
|
+
import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { Snowflake, setProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import { $ } from "bun";
|
|
9
|
+
import { COLLAB_GUEST_ALLOWED_COMMANDS, CollabGuestLink } from "../collab/guest";
|
|
10
|
+
import { CollabHost } from "../collab/host";
|
|
8
11
|
import type { SettingPath, SettingValue } from "../config/settings";
|
|
9
12
|
import { settings } from "../config/settings";
|
|
10
13
|
import {
|
|
@@ -42,6 +45,7 @@ import type {
|
|
|
42
45
|
SlashCommandResult,
|
|
43
46
|
SlashCommandRuntime,
|
|
44
47
|
SlashCommandSpec,
|
|
48
|
+
SubcommandDef,
|
|
45
49
|
TuiSlashCommandRuntime,
|
|
46
50
|
} from "./types";
|
|
47
51
|
|
|
@@ -442,6 +446,115 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
442
446
|
runtime.ctx.editor.setText("");
|
|
443
447
|
},
|
|
444
448
|
},
|
|
449
|
+
{
|
|
450
|
+
name: "collab",
|
|
451
|
+
description: "Share this session live via a relay",
|
|
452
|
+
inlineHint: "[start|stop|status] [relayUrl]",
|
|
453
|
+
allowArgs: true,
|
|
454
|
+
handleTui: async (command, runtime) => {
|
|
455
|
+
const ctx = runtime.ctx;
|
|
456
|
+
ctx.editor.setText("");
|
|
457
|
+
const args = command.args.trim();
|
|
458
|
+
const [first = ""] = args.split(/\s+/, 1);
|
|
459
|
+
if (first === "stop") {
|
|
460
|
+
if (!ctx.collabHost) {
|
|
461
|
+
ctx.showStatus("Not hosting a collab session");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
await ctx.collabHost.stop("host stopped");
|
|
465
|
+
ctx.showStatus("Collab stopped");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (first === "status") {
|
|
469
|
+
if (ctx.collabHost) {
|
|
470
|
+
const names = ctx.collabHost.participants.map(p => (p.role === "host" ? `${p.name} (host)` : p.name));
|
|
471
|
+
ctx.showStatus(`Collab: ${names.join(", ")} — ${ctx.collabHost.link}`);
|
|
472
|
+
} else if (ctx.collabGuest) {
|
|
473
|
+
ctx.showStatus("In a collab session as a guest (/leave to exit)");
|
|
474
|
+
} else {
|
|
475
|
+
ctx.showStatus("Not in a collab session");
|
|
476
|
+
}
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (ctx.collabGuest) {
|
|
480
|
+
ctx.showError("Already in a collab session as a guest (/leave first)");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (ctx.collabHost) {
|
|
484
|
+
ctx.session.emitNotice("info", `Collab link: ${ctx.collabHost.link}`, "collab");
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const explicitUrl = first === "start" ? args.slice("start".length).trim() : args;
|
|
488
|
+
const relayInput = explicitUrl || ctx.settings.get("collab.relayUrl") || "";
|
|
489
|
+
if (!relayInput) {
|
|
490
|
+
ctx.showError(
|
|
491
|
+
"No relay configured. Set collab.relayUrl in /settings or pass one: /collab relay.example.com",
|
|
492
|
+
);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
// Scheme-less relay args default to wss (ws:// must be spelled out for localhost).
|
|
496
|
+
const relayUrl = relayInput.includes("://") ? relayInput : `wss://${relayInput}`;
|
|
497
|
+
const host = new CollabHost(ctx);
|
|
498
|
+
try {
|
|
499
|
+
await host.start(relayUrl);
|
|
500
|
+
} catch (err) {
|
|
501
|
+
ctx.showError(`Failed to start collab session: ${errorMessage(err)}`);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
ctx.collabHost = host;
|
|
505
|
+
ctx.session.emitNotice(
|
|
506
|
+
"info",
|
|
507
|
+
`Collab link: ${host.link}\nAnyone with this link can read the session and prompt the agent.`,
|
|
508
|
+
"collab",
|
|
509
|
+
);
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "join",
|
|
514
|
+
description: "Join a shared collab session",
|
|
515
|
+
inlineHint: "<link>",
|
|
516
|
+
allowArgs: true,
|
|
517
|
+
handleTui: async (command, runtime) => {
|
|
518
|
+
const ctx = runtime.ctx;
|
|
519
|
+
ctx.editor.setText("");
|
|
520
|
+
const link = command.args.trim();
|
|
521
|
+
if (!link) {
|
|
522
|
+
ctx.showError("Usage: /join <link>");
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (ctx.collabHost) {
|
|
526
|
+
ctx.showError("Stop hosting first (/collab stop)");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (ctx.collabGuest) {
|
|
530
|
+
ctx.showError("Already in a collab session (/leave first)");
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
await new CollabGuestLink(ctx).join(link);
|
|
535
|
+
} catch (err) {
|
|
536
|
+
ctx.showError(`Failed to join collab session: ${errorMessage(err)}`);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "leave",
|
|
542
|
+
description: "Leave the collab session",
|
|
543
|
+
handleTui: async (_command, runtime) => {
|
|
544
|
+
const ctx = runtime.ctx;
|
|
545
|
+
ctx.editor.setText("");
|
|
546
|
+
if (ctx.collabGuest) {
|
|
547
|
+
await ctx.collabGuest.leave("left");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (ctx.collabHost) {
|
|
551
|
+
await ctx.collabHost.stop("host stopped");
|
|
552
|
+
ctx.showStatus("Collab stopped");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
ctx.showStatus("Not in a collab session");
|
|
556
|
+
},
|
|
557
|
+
},
|
|
445
558
|
{
|
|
446
559
|
name: "browser",
|
|
447
560
|
description: "Toggle browser headless vs visible mode",
|
|
@@ -1853,6 +1966,70 @@ for (const command of BUILTIN_SLASH_COMMAND_REGISTRY) {
|
|
|
1853
1966
|
|
|
1854
1967
|
export const BUILTIN_SLASH_COMMAND_RESERVED_NAMES: ReadonlySet<string> = new Set(BUILTIN_SLASH_COMMAND_LOOKUP.keys());
|
|
1855
1968
|
|
|
1969
|
+
/**
|
|
1970
|
+
* Build getArgumentCompletions from declarative subcommand definitions.
|
|
1971
|
+
* Returns subcommand names filtered by prefix in the dropdown.
|
|
1972
|
+
*/
|
|
1973
|
+
function buildArgumentCompletions(subcommands: SubcommandDef[]): (prefix: string) => AutocompleteItem[] | null {
|
|
1974
|
+
return (argumentPrefix: string) => {
|
|
1975
|
+
if (argumentPrefix.includes(" ")) return null; // past the subcommand
|
|
1976
|
+
const lower = argumentPrefix.toLowerCase();
|
|
1977
|
+
const matches = subcommands
|
|
1978
|
+
.filter(s => s.name.startsWith(lower))
|
|
1979
|
+
.map(s => ({
|
|
1980
|
+
value: `${s.name} `,
|
|
1981
|
+
label: s.name,
|
|
1982
|
+
description: s.description,
|
|
1983
|
+
hint: s.usage,
|
|
1984
|
+
}));
|
|
1985
|
+
return matches.length > 0 ? matches : null;
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* Build getInlineHint from declarative subcommand definitions.
|
|
1991
|
+
* Shows remaining completion + usage as dim ghost text after cursor.
|
|
1992
|
+
*/
|
|
1993
|
+
function buildSubcommandInlineHint(subcommands: SubcommandDef[]): (argumentText: string) => string | null {
|
|
1994
|
+
return (argumentText: string) => {
|
|
1995
|
+
const trimmed = argumentText.trimStart();
|
|
1996
|
+
const spaceIndex = trimmed.indexOf(" ");
|
|
1997
|
+
|
|
1998
|
+
if (spaceIndex === -1) {
|
|
1999
|
+
// Still typing subcommand name — show remaining chars + usage
|
|
2000
|
+
const prefix = trimmed.toLowerCase();
|
|
2001
|
+
if (prefix.length === 0) return null;
|
|
2002
|
+
const match = subcommands.find(s => s.name.startsWith(prefix));
|
|
2003
|
+
if (!match) return null;
|
|
2004
|
+
const remaining = match.name.slice(prefix.length);
|
|
2005
|
+
return remaining + (match.usage ? ` ${match.usage}` : "");
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Subcommand typed — show remaining usage params
|
|
2009
|
+
const subName = trimmed.slice(0, spaceIndex).toLowerCase();
|
|
2010
|
+
const afterSub = trimmed.slice(spaceIndex + 1);
|
|
2011
|
+
const sub = subcommands.find(s => s.name === subName);
|
|
2012
|
+
if (!sub?.usage) return null;
|
|
2013
|
+
|
|
2014
|
+
if (afterSub.length > 0) {
|
|
2015
|
+
const usageParts = sub.usage.split(" ");
|
|
2016
|
+
const inputParts = afterSub.trim().split(/\s+/);
|
|
2017
|
+
const remaining = usageParts.slice(inputParts.length);
|
|
2018
|
+
return remaining.length > 0 ? remaining.join(" ") : null;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
return sub.usage;
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* Build getInlineHint for commands with a simple static hint string.
|
|
2027
|
+
* Shows the hint only when no arguments have been typed yet.
|
|
2028
|
+
*/
|
|
2029
|
+
function buildStaticInlineHint(hint: string): (argumentText: string) => string | null {
|
|
2030
|
+
return (argumentText: string) => (argumentText.trim().length === 0 ? hint : null);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1856
2033
|
/** Builtin command metadata used for slash-command autocomplete and help text. */
|
|
1857
2034
|
export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = BUILTIN_SLASH_COMMAND_REGISTRY.map(
|
|
1858
2035
|
command => ({
|
|
@@ -1864,6 +2041,32 @@ export const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand> = BU
|
|
|
1864
2041
|
}),
|
|
1865
2042
|
);
|
|
1866
2043
|
|
|
2044
|
+
/**
|
|
2045
|
+
* Materialized builtin slash commands with completion functions derived from
|
|
2046
|
+
* declarative subcommand/hint definitions.
|
|
2047
|
+
*/
|
|
2048
|
+
export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<
|
|
2049
|
+
BuiltinSlashCommand & {
|
|
2050
|
+
getArgumentCompletions?: (prefix: string) => AutocompleteItem[] | null;
|
|
2051
|
+
getInlineHint?: (argumentText: string) => string | null;
|
|
2052
|
+
}
|
|
2053
|
+
> = BUILTIN_SLASH_COMMAND_DEFS.map(cmd => {
|
|
2054
|
+
if (cmd.subcommands) {
|
|
2055
|
+
return {
|
|
2056
|
+
...cmd,
|
|
2057
|
+
getArgumentCompletions: buildArgumentCompletions(cmd.subcommands),
|
|
2058
|
+
getInlineHint: buildSubcommandInlineHint(cmd.subcommands),
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
if (cmd.inlineHint) {
|
|
2062
|
+
return {
|
|
2063
|
+
...cmd,
|
|
2064
|
+
getInlineHint: buildStaticInlineHint(cmd.inlineHint),
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
return cmd;
|
|
2068
|
+
});
|
|
2069
|
+
|
|
1867
2070
|
/**
|
|
1868
2071
|
* Unified registry exposed for cross-mode tooling. Each spec carries at least
|
|
1869
2072
|
* one of `handle` / `handleTui`. The TUI dispatcher prefers `handleTui`; the
|
|
@@ -1890,6 +2093,13 @@ export async function executeBuiltinSlashCommand(
|
|
|
1890
2093
|
if (parsed.args.length > 0 && !command.allowArgs) {
|
|
1891
2094
|
return false;
|
|
1892
2095
|
}
|
|
2096
|
+
// Collab guests run a read-mostly replica: session-mutating builtins are
|
|
2097
|
+
// host-only; the allowlist covers purely local/read-only commands.
|
|
2098
|
+
if (runtime.ctx.collabGuest && !COLLAB_GUEST_ALLOWED_COMMANDS[command.name]) {
|
|
2099
|
+
runtime.ctx.showStatus(`/${command.name} is host-only during a collab session`);
|
|
2100
|
+
runtime.ctx.editor.setText("");
|
|
2101
|
+
return true;
|
|
2102
|
+
}
|
|
1893
2103
|
if (command.handleTui) {
|
|
1894
2104
|
const result = await command.handleTui(parsed, runtime);
|
|
1895
2105
|
if (result && typeof result === "object" && "prompt" in result) return result.prompt;
|
package/src/tools/bash.ts
CHANGED
|
@@ -1385,6 +1385,9 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1385
1385
|
},
|
|
1386
1386
|
mergeCallAndResult: true,
|
|
1387
1387
|
inline: true,
|
|
1388
|
+
// Pending preview caps the command to a viewport-sized tail window that
|
|
1389
|
+
// shifts while args stream; keep it out of native scrollback mid-run.
|
|
1390
|
+
provisionalPendingPreview: true,
|
|
1388
1391
|
};
|
|
1389
1392
|
}
|
|
1390
1393
|
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -754,4 +754,8 @@ export const evalToolRenderer = {
|
|
|
754
754
|
|
|
755
755
|
mergeCallAndResult: true,
|
|
756
756
|
inline: true,
|
|
757
|
+
// Pending preview shows tail-window code cells; the result render
|
|
758
|
+
// interleaves each cell's output under its code, re-laying-out every row
|
|
759
|
+
// below the first cell. Keep the preview out of native scrollback mid-run.
|
|
760
|
+
provisionalPendingPreview: true,
|
|
757
761
|
};
|
package/src/tools/read.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { glob, type SummaryResult, summarizeCode } from "@oh-my-pi/pi-natives";
|
|
|
8
8
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
9
9
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
10
10
|
import { getRemoteDir, logger, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { LRUCache } from "lru-cache/raw";
|
|
11
12
|
import * as z from "zod/v4";
|
|
12
13
|
import {
|
|
13
14
|
canonicalSnapshotKey,
|
|
@@ -100,6 +101,28 @@ import {
|
|
|
100
101
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
101
102
|
import { toolResult } from "./tool-result";
|
|
102
103
|
|
|
104
|
+
// Per-session memo for tree-sitter summaries. `summarizeCode` is a pure function
|
|
105
|
+
// of (code, path, fold settings) but costs ~12-18ms for a ~1500-line file, and a
|
|
106
|
+
// repeat summary read of the same unchanged file re-parses from scratch. Key on
|
|
107
|
+
// the content hash of the freshly-read bytes (+ path + fold settings): the file
|
|
108
|
+
// is still read fresh on every call, so a hit only reuses the deterministic
|
|
109
|
+
// parse — there is no staleness window and no stat guard is needed. Bounded LRU,
|
|
110
|
+
// aged out with the session via WeakMap.
|
|
111
|
+
// Unusable results (not parsed, or nothing elided) are memoized as `false`: the
|
|
112
|
+
// full SummaryResult embeds the whole source in kept segments, and the caller
|
|
113
|
+
// only ever renders `parsed && elided` summaries — caching the segments would
|
|
114
|
+
// retain up to 48 near-2MiB sources just to remember "no summary".
|
|
115
|
+
const SUMMARY_CACHE_MAX = 48;
|
|
116
|
+
const summaryParseCaches = new WeakMap<object, LRUCache<string, SummaryResult | false>>();
|
|
117
|
+
function getSummaryParseCache(session: object): LRUCache<string, SummaryResult | false> {
|
|
118
|
+
let cache = summaryParseCaches.get(session);
|
|
119
|
+
if (!cache) {
|
|
120
|
+
cache = new LRUCache<string, SummaryResult | false>({ max: SUMMARY_CACHE_MAX });
|
|
121
|
+
summaryParseCaches.set(session, cache);
|
|
122
|
+
}
|
|
123
|
+
return cache;
|
|
124
|
+
}
|
|
125
|
+
|
|
103
126
|
// Document types converted to markdown via markit.
|
|
104
127
|
const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
|
|
105
128
|
|
|
@@ -1614,15 +1637,25 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1614
1637
|
if (lineCount > MAX_SUMMARY_LINES) return null;
|
|
1615
1638
|
if (lineCount < this.session.settings.get("read.summarize.minTotalLines")) return null;
|
|
1616
1639
|
|
|
1640
|
+
const minBodyLines = this.session.settings.get("read.summarize.minBodyLines");
|
|
1641
|
+
const minCommentLines = this.session.settings.get("read.summarize.minCommentLines");
|
|
1642
|
+
const unfoldUntilLines = this.session.settings.get("read.summarize.unfoldUntil");
|
|
1643
|
+
const unfoldLimitLines = this.session.settings.get("read.summarize.unfoldLimit");
|
|
1644
|
+
const cache = getSummaryParseCache(this.session);
|
|
1645
|
+
const cacheKey = `${absolutePath}\0${Bun.hash(code)}\0${minBodyLines},${minCommentLines},${unfoldUntilLines},${unfoldLimitLines}`;
|
|
1646
|
+
const memoized = cache.get(cacheKey);
|
|
1647
|
+
if (memoized !== undefined) return memoized || null;
|
|
1617
1648
|
const result = summarizeCode({
|
|
1618
1649
|
code,
|
|
1619
1650
|
path: absolutePath,
|
|
1620
|
-
minBodyLines
|
|
1621
|
-
minCommentLines
|
|
1622
|
-
unfoldUntilLines
|
|
1623
|
-
unfoldLimitLines
|
|
1651
|
+
minBodyLines,
|
|
1652
|
+
minCommentLines,
|
|
1653
|
+
unfoldUntilLines,
|
|
1654
|
+
unfoldLimitLines,
|
|
1624
1655
|
});
|
|
1625
|
-
|
|
1656
|
+
const usable = result.parsed && result.elided ? result : false;
|
|
1657
|
+
cache.set(cacheKey, usable);
|
|
1658
|
+
return usable || null;
|
|
1626
1659
|
} catch {
|
|
1627
1660
|
return null;
|
|
1628
1661
|
}
|
package/src/tools/renderers.ts
CHANGED
|
@@ -43,6 +43,19 @@ export type ToolRenderer = {
|
|
|
43
43
|
mergeCallAndResult?: boolean;
|
|
44
44
|
/** Render without background box, inline in the response flow */
|
|
45
45
|
inline?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Collapsed pending preview is provisional — a tail-window or otherwise
|
|
48
|
+
* re-anchored view the result render replaces wholesale (an edit's
|
|
49
|
+
* streamed-diff tail, bash/ssh command caps, eval cells whose outputs
|
|
50
|
+
* interleave under each cell). Its rows must never commit to native
|
|
51
|
+
* scrollback mid-run; see
|
|
52
|
+
* `ToolExecutionComponent.isTranscriptBlockCommitStable`. Absent = the
|
|
53
|
+
* pending preview streams top-anchored append-shaped rows the result
|
|
54
|
+
* render preserves (task context/assignment, write content), which stay
|
|
55
|
+
* commit-eligible so a call taller than the viewport scrolls into history
|
|
56
|
+
* instead of reading as cut off.
|
|
57
|
+
*/
|
|
58
|
+
provisionalPendingPreview?: boolean;
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
export const toolRenderers: Record<string, ToolRenderer> = {
|
package/src/tools/ssh.ts
CHANGED
|
@@ -346,4 +346,7 @@ export const sshToolRenderer = {
|
|
|
346
346
|
});
|
|
347
347
|
},
|
|
348
348
|
mergeCallAndResult: true,
|
|
349
|
+
// Pending preview caps the command to a viewport-sized tail window that
|
|
350
|
+
// shifts while args stream; keep it out of native scrollback mid-run.
|
|
351
|
+
provisionalPendingPreview: true,
|
|
349
352
|
};
|
package/src/tools/write.ts
CHANGED
|
@@ -583,9 +583,10 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
583
583
|
|
|
584
584
|
/**
|
|
585
585
|
* Resolve a single `conflict://<N>` write by splicing the recorded
|
|
586
|
-
* marker region in the registered file with `replacementContent
|
|
587
|
-
*
|
|
588
|
-
*
|
|
586
|
+
* marker region in the registered file with `replacementContent`.
|
|
587
|
+
* The write deliberately bypasses the LSP writethrough: the file may
|
|
588
|
+
* still hold other unresolved marker blocks, so formatting could
|
|
589
|
+
* corrupt them and diagnostics would be marker-noise anyway.
|
|
589
590
|
*
|
|
590
591
|
* Entry ids are session-stable: they keep working even after later
|
|
591
592
|
* writes resolve other blocks in the same file. The recorded range
|
|
@@ -597,7 +598,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
597
598
|
replacementContent: string,
|
|
598
599
|
stripped: boolean,
|
|
599
600
|
signal: AbortSignal | undefined,
|
|
600
|
-
context: AgentToolContext | undefined,
|
|
601
601
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
602
602
|
const absolutePath = entry.absolutePath;
|
|
603
603
|
if (!(await fs.exists(absolutePath))) {
|
|
@@ -608,8 +608,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
608
608
|
const originalText = await Bun.file(absolutePath).text();
|
|
609
609
|
const newContent = spliceConflict(originalText, entry, expanded);
|
|
610
610
|
|
|
611
|
-
|
|
612
|
-
const diagnostics = await this.#writethrough(absolutePath, newContent, signal, undefined, batchRequest);
|
|
611
|
+
await writethroughNoop(absolutePath, newContent, signal);
|
|
613
612
|
invalidateFsScanAfterWrite(absolutePath);
|
|
614
613
|
this.session.bumpFileMutationVersion?.(absolutePath);
|
|
615
614
|
this.session.fileSnapshotStore?.invalidate(absolutePath);
|
|
@@ -643,21 +642,9 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
643
642
|
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
644
643
|
}
|
|
645
644
|
|
|
646
|
-
if (!diagnostics) {
|
|
647
|
-
return {
|
|
648
|
-
content: [{ type: "text", text: resultText }],
|
|
649
|
-
details: { resolvedPath: absolutePath },
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
645
|
return {
|
|
653
646
|
content: [{ type: "text", text: resultText }],
|
|
654
|
-
details: {
|
|
655
|
-
resolvedPath: absolutePath,
|
|
656
|
-
diagnostics,
|
|
657
|
-
meta: outputMeta()
|
|
658
|
-
.diagnostics(diagnostics.summary, diagnostics.messages ?? [])
|
|
659
|
-
.get(),
|
|
660
|
-
},
|
|
647
|
+
details: { resolvedPath: absolutePath },
|
|
661
648
|
};
|
|
662
649
|
}
|
|
663
650
|
|
|
@@ -670,7 +657,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
670
657
|
replacementContent: string,
|
|
671
658
|
stripped: boolean,
|
|
672
659
|
signal: AbortSignal | undefined,
|
|
673
|
-
context: AgentToolContext | undefined,
|
|
674
660
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
675
661
|
const entry = getConflictHistory(this.session).get(id);
|
|
676
662
|
if (!entry) {
|
|
@@ -678,7 +664,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
678
664
|
`Conflict #${id} not found. Conflict ids are registered when \`read\` surfaces a marker block; re-read the file to get a current id.`,
|
|
679
665
|
);
|
|
680
666
|
}
|
|
681
|
-
return this.#resolveConflict(entry, replacementContent, stripped, signal
|
|
667
|
+
return this.#resolveConflict(entry, replacementContent, stripped, signal);
|
|
682
668
|
}
|
|
683
669
|
|
|
684
670
|
/**
|
|
@@ -700,7 +686,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
700
686
|
replacementContent: string,
|
|
701
687
|
stripped: boolean,
|
|
702
688
|
signal: AbortSignal | undefined,
|
|
703
|
-
context: AgentToolContext | undefined,
|
|
704
689
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
705
690
|
const history = getConflictHistory(this.session);
|
|
706
691
|
const allEntries = history.entries();
|
|
@@ -717,8 +702,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
717
702
|
byFile.set(entry.absolutePath, bucket);
|
|
718
703
|
}
|
|
719
704
|
|
|
720
|
-
const batchRequest = getLspBatchRequest(context?.toolCall);
|
|
721
|
-
const allDiagnostics: FileDiagnosticsResult[] = [];
|
|
722
705
|
const succeededFiles: { displayPath: string; count: number; header?: string }[] = [];
|
|
723
706
|
const failedFiles: { displayPath: string; count: number; error: string }[] = [];
|
|
724
707
|
let totalResolvedIds = 0;
|
|
@@ -776,7 +759,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
776
759
|
continue;
|
|
777
760
|
}
|
|
778
761
|
|
|
779
|
-
|
|
762
|
+
await writethroughNoop(absolutePath, text, signal);
|
|
780
763
|
invalidateFsScanAfterWrite(absolutePath);
|
|
781
764
|
this.session.bumpFileMutationVersion?.(absolutePath);
|
|
782
765
|
this.session.fileSnapshotStore?.invalidate(absolutePath);
|
|
@@ -785,7 +768,6 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
785
768
|
const header = maybeWriteSnapshotHeader(this.session, absolutePath, text);
|
|
786
769
|
succeededFiles.push({ displayPath: sample.displayPath, count: resolvedEntries.length, header });
|
|
787
770
|
totalResolvedIds += resolvedEntries.length;
|
|
788
|
-
if (diagnostics) allDiagnostics.push(diagnostics);
|
|
789
771
|
}
|
|
790
772
|
|
|
791
773
|
const summaryLines: string[] = [];
|
|
@@ -819,23 +801,12 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
819
801
|
}
|
|
820
802
|
const resultText = summaryLines.join("\n");
|
|
821
803
|
|
|
822
|
-
if (
|
|
823
|
-
|
|
824
|
-
throw new ToolError(resultText);
|
|
825
|
-
}
|
|
826
|
-
return {
|
|
827
|
-
content: [{ type: "text", text: resultText }],
|
|
828
|
-
details: {},
|
|
829
|
-
isError: failedFiles.length > 0 ? true : undefined,
|
|
830
|
-
};
|
|
804
|
+
if (failedFiles.length > 0 && succeededFiles.length === 0) {
|
|
805
|
+
throw new ToolError(resultText);
|
|
831
806
|
}
|
|
832
|
-
const mergedSummary = allDiagnostics.map(d => d.summary).join("\n");
|
|
833
|
-
const mergedMessages = allDiagnostics.flatMap(d => d.messages ?? []);
|
|
834
807
|
return {
|
|
835
808
|
content: [{ type: "text", text: resultText }],
|
|
836
|
-
details: {
|
|
837
|
-
meta: outputMeta().diagnostics(mergedSummary, mergedMessages).get(),
|
|
838
|
-
},
|
|
809
|
+
details: {},
|
|
839
810
|
isError: failedFiles.length > 0 ? true : undefined,
|
|
840
811
|
};
|
|
841
812
|
}
|
|
@@ -885,8 +856,8 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
885
856
|
}
|
|
886
857
|
const result =
|
|
887
858
|
conflictUri.id === "*"
|
|
888
|
-
? await this.#resolveAllConflicts(cleanContent, stripped, signal
|
|
889
|
-
: await this.#resolveSingleConflictById(conflictUri.id, cleanContent, stripped, signal
|
|
859
|
+
? await this.#resolveAllConflicts(cleanContent, stripped, signal)
|
|
860
|
+
: await this.#resolveSingleConflictById(conflictUri.id, cleanContent, stripped, signal);
|
|
890
861
|
if (conflictUri.recoveredPrefix !== undefined) {
|
|
891
862
|
appendNoteToResult(
|
|
892
863
|
result,
|