Merge branch 'main' into bigrefactor
This commit is contained in:
1
.github/APPROVED_CONTRIBUTORS
vendored
1
.github/APPROVED_CONTRIBUTORS
vendored
@@ -4,6 +4,7 @@
|
|||||||
# issue future issues stay open
|
# issue future issues stay open
|
||||||
# pr future issues and PRs stay open
|
# pr future issues and PRs stay open
|
||||||
|
|
||||||
|
julien-c pr
|
||||||
barapa pr
|
barapa pr
|
||||||
alasano pr
|
alasano pr
|
||||||
aadishv pr
|
aadishv pr
|
||||||
|
|||||||
@@ -61,22 +61,21 @@ export interface ReadToolOptions {
|
|||||||
operations?: ReadOperations;
|
operations?: ReadOperations;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatReadCall(
|
type ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };
|
||||||
args: { path?: string; file_path?: string; offset?: number; limit?: number } | undefined,
|
|
||||||
theme: Theme,
|
function formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {
|
||||||
): string {
|
if (args?.offset === undefined && args?.limit === undefined) return "";
|
||||||
|
const startLine = args.offset ?? 1;
|
||||||
|
const endLine = args.limit !== undefined ? startLine + args.limit - 1 : "";
|
||||||
|
return theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatReadCall(args: ReadRenderArgs | undefined, theme: Theme): string {
|
||||||
const rawPath = str(args?.file_path ?? args?.path);
|
const rawPath = str(args?.file_path ?? args?.path);
|
||||||
const path = rawPath !== null ? shortenPath(rawPath) : null;
|
const path = rawPath !== null ? shortenPath(rawPath) : null;
|
||||||
const offset = args?.offset;
|
|
||||||
const limit = args?.limit;
|
|
||||||
const invalidArg = invalidArgText(theme);
|
const invalidArg = invalidArgText(theme);
|
||||||
let pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
|
const pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
|
||||||
if (offset !== undefined || limit !== undefined) {
|
return `${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}${formatReadLineRange(args, theme)}`;
|
||||||
const startLine = offset ?? 1;
|
|
||||||
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
||||||
pathDisplay += theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
||||||
}
|
|
||||||
return `${theme.fg("toolTitle", theme.bold("read"))} ${pathDisplay}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function trimTrailingEmptyLines(lines: string[]): string[] {
|
function trimTrailingEmptyLines(lines: string[]): string[] {
|
||||||
@@ -118,7 +117,7 @@ function getPiDocsClassification(absolutePath: string): CompactReadClassificatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCompactReadClassification(
|
function getCompactReadClassification(
|
||||||
args: { path?: string; file_path?: string } | undefined,
|
args: ReadRenderArgs | undefined,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
): CompactReadClassification | undefined {
|
): CompactReadClassification | undefined {
|
||||||
const rawPath = str(args?.file_path ?? args?.path);
|
const rawPath = str(args?.file_path ?? args?.path);
|
||||||
@@ -140,12 +139,18 @@ function getCompactReadClassification(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCompactReadCall(classification: CompactReadClassification, theme: Theme): string {
|
function formatCompactReadCall(
|
||||||
|
classification: CompactReadClassification,
|
||||||
|
args: ReadRenderArgs | undefined,
|
||||||
|
theme: Theme,
|
||||||
|
): string {
|
||||||
|
const expandHint = theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`);
|
||||||
if (classification.kind === "skill") {
|
if (classification.kind === "skill") {
|
||||||
return (
|
return (
|
||||||
theme.fg("customMessageLabel", `\x1b[1m[skill]\x1b[22m `) +
|
theme.fg("customMessageLabel", `\x1b[1m[skill]\x1b[22m `) +
|
||||||
theme.fg("customMessageText", classification.label) +
|
theme.fg("customMessageText", classification.label) +
|
||||||
theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`)
|
formatReadLineRange(args, theme) +
|
||||||
|
expandHint
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,12 +158,13 @@ function formatCompactReadCall(classification: CompactReadClassification, theme:
|
|||||||
theme.fg("toolTitle", theme.bold(`read ${classification.kind}`)) +
|
theme.fg("toolTitle", theme.bold(`read ${classification.kind}`)) +
|
||||||
" " +
|
" " +
|
||||||
theme.fg("accent", classification.label) +
|
theme.fg("accent", classification.label) +
|
||||||
theme.fg("dim", ` (${keyText("app.tools.expand")} to expand)`)
|
formatReadLineRange(args, theme) +
|
||||||
|
expandHint
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatReadResult(
|
function formatReadResult(
|
||||||
args: { path?: string; file_path?: string; offset?: number; limit?: number } | undefined,
|
args: ReadRenderArgs | undefined,
|
||||||
result: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },
|
result: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },
|
||||||
options: ToolRenderResultOptions,
|
options: ToolRenderResultOptions,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
@@ -336,7 +342,9 @@ export function createReadToolDefinition(
|
|||||||
renderCall(args, theme, context) {
|
renderCall(args, theme, context) {
|
||||||
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
||||||
const classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;
|
const classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;
|
||||||
text.setText(classification ? formatCompactReadCall(classification, theme) : formatReadCall(args, theme));
|
text.setText(
|
||||||
|
classification ? formatCompactReadCall(classification, args, theme) : formatReadCall(args, theme),
|
||||||
|
);
|
||||||
return text;
|
return text;
|
||||||
},
|
},
|
||||||
renderResult(result, options, theme, context) {
|
renderResult(result, options, theme, context) {
|
||||||
|
|||||||
@@ -163,6 +163,16 @@ type CompactionQueuedMessage = {
|
|||||||
mode: "steer" | "followUp";
|
mode: "steer" | "followUp";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
||||||
|
|
||||||
|
function isDeadTerminalError(error: unknown): boolean {
|
||||||
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const code = (error as NodeJS.ErrnoException).code;
|
||||||
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
||||||
|
}
|
||||||
|
|
||||||
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING =
|
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING =
|
||||||
"Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
|
"Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
|
||||||
|
|
||||||
@@ -3223,6 +3233,15 @@ export class InteractiveMode {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private emergencyTerminalExit(): never {
|
||||||
|
this.isShuttingDown = true;
|
||||||
|
this.unregisterSignalHandlers();
|
||||||
|
killTrackedDetachedChildren();
|
||||||
|
// The terminal is gone. Do not run normal shutdown because TUI and
|
||||||
|
// extension cleanup can write restore sequences and re-trigger EIO.
|
||||||
|
process.exit(129);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if shutdown was requested and perform shutdown if so.
|
* Check if shutdown was requested and perform shutdown if so.
|
||||||
*/
|
*/
|
||||||
@@ -3241,12 +3260,26 @@ export class InteractiveMode {
|
|||||||
|
|
||||||
for (const signal of signals) {
|
for (const signal of signals) {
|
||||||
const handler = () => {
|
const handler = () => {
|
||||||
|
if (signal === "SIGHUP") {
|
||||||
|
this.emergencyTerminalExit();
|
||||||
|
}
|
||||||
killTrackedDetachedChildren();
|
killTrackedDetachedChildren();
|
||||||
void this.shutdown();
|
void this.shutdown();
|
||||||
};
|
};
|
||||||
process.on(signal, handler);
|
process.prependListener(signal, handler);
|
||||||
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
this.signalCleanupHandlers.push(() => process.off(signal, handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const terminalErrorHandler = (error: Error) => {
|
||||||
|
if (isDeadTerminalError(error)) {
|
||||||
|
this.emergencyTerminalExit();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
process.stdout.on("error", terminalErrorHandler);
|
||||||
|
process.stderr.on("error", terminalErrorHandler);
|
||||||
|
this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
|
||||||
|
this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
private unregisterSignalHandlers(): void {
|
private unregisterSignalHandlers(): void {
|
||||||
|
|||||||
@@ -384,4 +384,25 @@ describe("ToolExecutionComponent parity", () => {
|
|||||||
expect(expanded).toContain(scenario.hidden);
|
expect(expanded).toContain(scenario.hidden);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const scenario of [
|
||||||
|
{ title: "SKILL.md", path: join(process.cwd(), "attio", "SKILL.md"), compact: "[skill] attio:120-329" },
|
||||||
|
{ title: "Pi documentation", path: getReadmePath(), compact: "read docs README.md:120-329" },
|
||||||
|
] as const) {
|
||||||
|
test(`shows the read line range in compact ${scenario.title} reads before the expand hint`, () => {
|
||||||
|
const component = new ToolExecutionComponent(
|
||||||
|
"read",
|
||||||
|
`tool-compact-range-${scenario.title}`,
|
||||||
|
{ path: scenario.path, offset: 120, limit: 210 },
|
||||||
|
{},
|
||||||
|
createReadToolDefinition(process.cwd()),
|
||||||
|
createFakeTui(),
|
||||||
|
process.cwd(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const collapsed = stripAnsi(component.render(120).join("\n"));
|
||||||
|
expect(collapsed).toContain(scenario.compact);
|
||||||
|
expect(collapsed.indexOf(":120-329")).toBeLessThan(collapsed.indexOf("to expand"));
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ if [[ "$NO_ENV" == "true" ]]; then
|
|||||||
unset COPILOT_GITHUB_TOKEN
|
unset COPILOT_GITHUB_TOKEN
|
||||||
unset GH_TOKEN
|
unset GH_TOKEN
|
||||||
unset GITHUB_TOKEN
|
unset GITHUB_TOKEN
|
||||||
|
unset HF_TOKEN
|
||||||
unset GOOGLE_APPLICATION_CREDENTIALS
|
unset GOOGLE_APPLICATION_CREDENTIALS
|
||||||
unset GOOGLE_CLOUD_PROJECT
|
unset GOOGLE_CLOUD_PROJECT
|
||||||
unset GCLOUD_PROJECT
|
unset GCLOUD_PROJECT
|
||||||
|
|||||||
Reference in New Issue
Block a user