chore(vendor): import pi 1.3.0

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
2026-10-15 17:58:48 -04:00
parent 719370c8f7
commit 6081e3e325
1743 changed files with 205460 additions and 131705 deletions

View File

@@ -62,10 +62,11 @@ import { SettingsManager } from "./core/settings-manager.ts";
import { printTimings, resetTimings, time } from "./core/timings.ts";
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "./core/trust-manager.ts";
import { builtInExtensions } from "./extensions/index.ts";
import { loadMcpCommand } from "./extensions/mcp/cli.lazy.ts";
import { runMigrations, showDeprecationWarnings } from "./migrations.ts";
import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.ts";
import { initTheme, setThemeJsonValidator, stopThemeWatcher } from "./modes/interactive/theme/theme.ts";
import { validateThemeJson } from "./modes/interactive/theme/theme-json.ts";
import { validateThemeJson } from "./modes/interactive/theme/theme-schema.ts";
import { cleanupManagedInstall, handleConfigCommand, handlePackageCommand } from "./package-manager-cli.ts";
import { isLocalPath, normalizePath, resolvePath } from "./utils/paths.ts";
import { cleanupWindowsSelfUpdateQuarantine } from "./utils/windows-self-update.ts";
@@ -209,7 +210,6 @@ async function runAuthCommand(args: string[]): Promise<boolean> {
async function prepareInitialMessage(
parsed: Args,
autoResizeImages: boolean,
stdinContent?: string,
): Promise<{
initialMessage?: string;
@@ -219,7 +219,8 @@ async function prepareInitialMessage(
return buildInitialMessage({ parsed, stdinContent });
}
const { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });
// AgentSession resizes these after extension hooks select the request model.
const { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages: false });
return buildInitialMessage({
parsed,
fileText: text,
@@ -239,14 +240,13 @@ type ResolvedSession =
* Resolve a session argument to a file path.
* If it looks like a path, use as-is. Otherwise try to match as session ID prefix.
*/
async function findLocalSessionByExactId(
function findLocalSessionByExactId(
sessionId: string,
cwd: string,
sessionDir?: string,
): Promise<{ type: "local"; path: string } | undefined> {
const localSessions = await SessionManager.list(cwd, sessionDir);
const localMatch = localSessions.find((s) => s.id === sessionId);
return localMatch ? { type: "local", path: localMatch.path } : undefined;
): { type: "local"; path: string } | undefined {
const path = SessionManager.findById(cwd, sessionId, sessionDir);
return path ? { type: "local", path } : undefined;
}
async function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {
@@ -255,10 +255,15 @@ async function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?:
return { type: "path", path: resolvePath(sessionArg, cwd) };
}
// Try to match as session ID in current project first
// Exact IDs only require reading session headers. Fall back to the full
// metadata listing for prefix matches.
const exactLocalMatch = findLocalSessionByExactId(sessionArg, cwd, sessionDir);
if (exactLocalMatch) {
return exactLocalMatch;
}
const localSessions = await SessionManager.list(cwd, sessionDir);
const localMatch =
localSessions.find((s) => s.id === sessionArg) ?? localSessions.find((s) => s.id.startsWith(sessionArg));
const localMatch = localSessions.find((s) => s.id.startsWith(sessionArg));
if (localMatch) {
return { type: "local", path: localMatch.path };
@@ -362,7 +367,7 @@ export async function createSessionManager(
if (parsed.fork) {
if (parsed.sessionId) {
const existingTarget = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
const existingTarget = findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
if (existingTarget) {
console.error(chalk.red(`Session already exists with id '${parsed.sessionId}'`));
process.exit(1);
@@ -410,8 +415,8 @@ export async function createSessionManager(
if (parsed.resume) {
try {
const selectedPath = await selectSession(
(onProgress) => SessionManager.list(cwd, sessionDir, onProgress),
(onProgress) => SessionManager.listAll(sessionDir, onProgress),
(onProgress, signal) => SessionManager.list(cwd, sessionDir, onProgress, signal),
(onProgress, signal) => SessionManager.listAll(sessionDir, onProgress, signal),
settingsManager,
);
if (!selectedPath) {
@@ -429,7 +434,7 @@ export async function createSessionManager(
}
if (parsed.sessionId) {
const existingSession = await findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
const existingSession = findLocalSessionByExactId(parsed.sessionId, cwd, sessionDir);
if (existingSession) {
return SessionManager.open(existingSession.path, sessionDir);
}
@@ -461,11 +466,18 @@ function buildSessionOptions(
// Model from CLI
// - supports --provider <name> --model <pattern>
// - supports --model <provider>/<pattern>
if (parsed.provider && !parsed.model) {
diagnostics.push({
type: "error",
message: `--provider requires --model (for example: --provider ${parsed.provider} --model <pattern>)`,
});
}
if (parsed.model) {
const resolved = resolveCliModel({
cliProvider: parsed.provider,
cliModel: parsed.model,
cliThinking: parsed.thinking,
inlineApiKey: Boolean(parsed.apiKey),
modelRuntime,
});
if (resolved.warning) {
@@ -561,6 +573,8 @@ export interface MainOptions {
export async function main(args: string[], options?: MainOptions) {
resetTimings();
// Commands and startup selectors can load user-authored themes before runtime creation.
setThemeJsonValidator(validateThemeJson);
const extensionFactories = [...builtInExtensions, ...(options?.extensionFactories ?? [])];
const offlineMode = args.includes("--offline") || isTruthyEnvFlag(process.env.PI_OFFLINE);
if (offlineMode) {
@@ -600,6 +614,12 @@ export async function main(args: string[], options?: MainOptions) {
return;
}
if (args[0] === "mcp") {
const { runMcpCommand } = await loadMcpCommand();
process.exitCode = await runMcpCommand(args.slice(1), { cwd, agentDir });
return;
}
const parsed = parseArgs(args);
if (parsed.diagnostics.length > 0) {
for (const d of parsed.diagnostics) {
@@ -765,6 +785,7 @@ export async function main(args: string[], options?: MainOptions) {
additionalPromptTemplatePaths: resolvedPromptTemplatePaths,
additionalThemePaths: resolvedThemePaths,
noExtensions: parsed.noExtensions,
disabledBuiltinExtensions: parsed.noMcp ? ["mcp"] : undefined,
noSkills: parsed.noSkills,
noPromptTemplates: parsed.noPromptTemplates,
noThemes: parsed.noThemes,
@@ -783,6 +804,10 @@ export async function main(args: string[], options?: MainOptions) {
type: "error" as const,
message: `Failed to load extension "${path}": ${error}`,
})),
...(resourceLoader.getExtensions().warnings ?? []).map(({ path, warning }) => ({
type: "warning" as const,
message: `Extension package "${path}": ${warning}`,
})),
];
const modelPatterns = parsed.models ?? settingsManager.getEnabledModels();
@@ -876,14 +901,8 @@ export async function main(args: string[], options?: MainOptions) {
}
time("readPipedStdin");
const { initialMessage, initialImages } = await prepareInitialMessage(
parsed,
settingsManager.getImageAutoResize(),
stdinContent,
);
const { initialMessage, initialImages } = await prepareInitialMessage(parsed, stdinContent);
time("prepareInitialMessage");
// pi reads user-authored themes, so it opts into full validation before any theme loads.
setThemeJsonValidator(validateThemeJson);
initTheme(settingsManager.getTheme(), appMode === "interactive");
time("initTheme");