Overhaul Feynman harness: streamline agents, prompts, and extensions
Remove legacy chains, skills, and config modules. Add citation agent, SYSTEM.md, modular research-tools extension, and web-access layer. Add ralph-wiggum to Pi package stack for long-running loops. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
||||
import { getUserName as getAlphaUserName, isLoggedIn as isAlphaLoggedIn } from "@companion-ai/alpha-hub/lib";
|
||||
|
||||
import {
|
||||
FEYNMAN_CONFIG_PATH,
|
||||
formatWebSearchDoctorLines,
|
||||
getWebSearchStatus,
|
||||
loadFeynmanConfig,
|
||||
} from "../config/feynman-config.js";
|
||||
import { formatPiWebAccessDoctorLines, getPiWebAccessStatus } from "../pi/web-access.js";
|
||||
import { BROWSER_FALLBACK_PATHS, PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
||||
import { readJson } from "../pi/settings.js";
|
||||
import { validatePiInstallation } from "../pi/runtime.js";
|
||||
@@ -32,11 +27,9 @@ export type FeynmanStatusSnapshot = {
|
||||
modelGuidance: string[];
|
||||
alphaLoggedIn: boolean;
|
||||
alphaUser?: string;
|
||||
webProviderLabel: string;
|
||||
webConfigured: boolean;
|
||||
webRouteLabel: string;
|
||||
previewConfigured: boolean;
|
||||
sessionDir: string;
|
||||
configPath: string;
|
||||
pandocReady: boolean;
|
||||
browserReady: boolean;
|
||||
piReady: boolean;
|
||||
@@ -44,11 +37,10 @@ export type FeynmanStatusSnapshot = {
|
||||
};
|
||||
|
||||
export function collectStatusSnapshot(options: DoctorOptions): FeynmanStatusSnapshot {
|
||||
const config = loadFeynmanConfig();
|
||||
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
||||
const browserPath = process.env.PUPPETEER_EXECUTABLE_PATH ?? resolveExecutable("google-chrome", BROWSER_FALLBACK_PATHS);
|
||||
const missingPiBits = validatePiInstallation(options.appRoot);
|
||||
const webStatus = getWebSearchStatus(config.webSearch ?? {});
|
||||
const webStatus = getPiWebAccessStatus();
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(options.authPath),
|
||||
getAvailableModelRecords(options.authPath),
|
||||
@@ -65,11 +57,9 @@ export function collectStatusSnapshot(options: DoctorOptions): FeynmanStatusSnap
|
||||
modelGuidance: modelStatus.guidance,
|
||||
alphaLoggedIn: isAlphaLoggedIn(),
|
||||
alphaUser: isAlphaLoggedIn() ? getAlphaUserName() ?? undefined : undefined,
|
||||
webProviderLabel: webStatus.selected.label,
|
||||
webConfigured: webStatus.perplexityConfigured || webStatus.geminiApiConfigured || webStatus.selected.id === "gemini-browser",
|
||||
previewConfigured: Boolean(config.preview?.lastSetupAt),
|
||||
webRouteLabel: webStatus.routeLabel,
|
||||
previewConfigured: Boolean(pandocPath),
|
||||
sessionDir: options.sessionDir,
|
||||
configPath: FEYNMAN_CONFIG_PATH,
|
||||
pandocReady: Boolean(pandocPath),
|
||||
browserReady: Boolean(browserPath),
|
||||
piReady: missingPiBits.length === 0,
|
||||
@@ -89,11 +79,10 @@ export function runStatus(options: DoctorOptions): void {
|
||||
printInfo(`Authenticated providers: ${snapshot.authenticatedProviderCount}`);
|
||||
printInfo(`Recommended model: ${snapshot.recommendedModel ?? "not available"}`);
|
||||
printInfo(`alphaXiv: ${snapshot.alphaLoggedIn ? snapshot.alphaUser ?? "configured" : "not configured"}`);
|
||||
printInfo(`Web research: ${snapshot.webConfigured ? snapshot.webProviderLabel : "not configured"}`);
|
||||
printInfo(`Web access: pi-web-access (${snapshot.webRouteLabel})`);
|
||||
printInfo(`Preview: ${snapshot.previewConfigured ? "configured" : "not configured"}`);
|
||||
|
||||
printSection("Paths");
|
||||
printInfo(`Config: ${snapshot.configPath}`);
|
||||
printInfo(`Sessions: ${snapshot.sessionDir}`);
|
||||
|
||||
printSection("Runtime");
|
||||
@@ -115,7 +104,6 @@ export function runStatus(options: DoctorOptions): void {
|
||||
|
||||
export function runDoctor(options: DoctorOptions): void {
|
||||
const settings = readJson(options.settingsPath);
|
||||
const config = loadFeynmanConfig();
|
||||
const modelRegistry = new ModelRegistry(AuthStorage.create(options.authPath));
|
||||
const availableModels = modelRegistry.getAvailable();
|
||||
const pandocPath = resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS);
|
||||
@@ -127,7 +115,6 @@ export function runDoctor(options: DoctorOptions): void {
|
||||
]);
|
||||
console.log(`working dir: ${options.workingDir}`);
|
||||
console.log(`session dir: ${options.sessionDir}`);
|
||||
console.log(`config path: ${FEYNMAN_CONFIG_PATH}`);
|
||||
console.log("");
|
||||
console.log(`alphaXiv auth: ${isAlphaLoggedIn() ? "ok" : "missing"}`);
|
||||
if (isAlphaLoggedIn()) {
|
||||
@@ -159,8 +146,7 @@ export function runDoctor(options: DoctorOptions): void {
|
||||
}
|
||||
console.log(`pandoc: ${pandocPath ?? "missing"}`);
|
||||
console.log(`browser preview runtime: ${browserPath ?? "missing"}`);
|
||||
console.log(`configured session dir: ${config.sessionDir ?? "not set"}`);
|
||||
for (const line of formatWebSearchDoctorLines(config.webSearch ?? {})) {
|
||||
for (const line of formatPiWebAccessDoctorLines()) {
|
||||
console.log(line);
|
||||
}
|
||||
console.log(`quiet startup: ${settings.quietStartup === true ? "enabled" : "disabled"}`);
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
import { isLoggedIn as isAlphaLoggedIn, login as loginAlpha } from "@companion-ai/alpha-hub/lib";
|
||||
|
||||
import {
|
||||
DEFAULT_WEB_SEARCH_PROVIDER,
|
||||
FEYNMAN_CONFIG_PATH,
|
||||
WEB_SEARCH_PROVIDERS,
|
||||
configureWebSearchProvider,
|
||||
getConfiguredWebSearchProvider,
|
||||
getWebSearchStatus,
|
||||
hasConfiguredWebProvider,
|
||||
loadFeynmanConfig,
|
||||
saveFeynmanConfig,
|
||||
} from "../config/feynman-config.js";
|
||||
import { getFeynmanHome } from "../config/paths.js";
|
||||
import { getDefaultSessionDir, getFeynmanHome } from "../config/paths.js";
|
||||
import { getPiWebAccessStatus, getPiWebSearchConfigPath } from "../pi/web-access.js";
|
||||
import { normalizeFeynmanSettings } from "../pi/settings.js";
|
||||
import type { ThinkingLevel } from "../pi/settings.js";
|
||||
import { getCurrentModelSpec, runModelSetup } from "../model/commands.js";
|
||||
import { buildModelStatusSnapshotFromRecords, getAvailableModelRecords, getSupportedModelRecords } from "../model/catalog.js";
|
||||
import { promptChoice, promptText } from "./prompts.js";
|
||||
import { PANDOC_FALLBACK_PATHS, resolveExecutable } from "../system/executables.js";
|
||||
import { promptText } from "./prompts.js";
|
||||
import { setupPreviewDependencies } from "./preview.js";
|
||||
import { runDoctor } from "./doctor.js";
|
||||
import { printInfo, printPanel, printSection, printSuccess } from "../ui/terminal.js";
|
||||
|
||||
type SetupOptions = {
|
||||
section: string | undefined;
|
||||
settingsPath: string;
|
||||
bundledSettingsPath: string;
|
||||
authPath: string;
|
||||
@@ -32,62 +22,18 @@ type SetupOptions = {
|
||||
defaultThinkingLevel?: ThinkingLevel;
|
||||
};
|
||||
|
||||
async function setupWebProvider(): Promise<void> {
|
||||
const config = loadFeynmanConfig();
|
||||
const current = getConfiguredWebSearchProvider(config.webSearch ?? {});
|
||||
const preferredSelectionId = config.webSearch?.feynmanWebProvider ?? DEFAULT_WEB_SEARCH_PROVIDER;
|
||||
const choices = [
|
||||
...WEB_SEARCH_PROVIDERS.map((provider) => `${provider.label} — ${provider.description}`),
|
||||
"Skip",
|
||||
];
|
||||
const defaultIndex = WEB_SEARCH_PROVIDERS.findIndex((provider) => provider.id === preferredSelectionId);
|
||||
const selection = await promptChoice(
|
||||
"Choose a web search provider for Feynman:",
|
||||
choices,
|
||||
defaultIndex >= 0 ? defaultIndex : 0,
|
||||
);
|
||||
|
||||
if (selection === WEB_SEARCH_PROVIDERS.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = WEB_SEARCH_PROVIDERS[selection] ?? WEB_SEARCH_PROVIDERS[0];
|
||||
let nextWebConfig = { ...(config.webSearch ?? {}) };
|
||||
|
||||
if (selected.id === "perplexity") {
|
||||
const key = await promptText(
|
||||
"Perplexity API key",
|
||||
typeof nextWebConfig.perplexityApiKey === "string" ? nextWebConfig.perplexityApiKey : "",
|
||||
);
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id, { apiKey: key });
|
||||
} else if (selected.id === "gemini-api") {
|
||||
const key = await promptText(
|
||||
"Gemini API key",
|
||||
typeof nextWebConfig.geminiApiKey === "string" ? nextWebConfig.geminiApiKey : "",
|
||||
);
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id, { apiKey: key });
|
||||
} else if (selected.id === "gemini-browser") {
|
||||
const profile = await promptText(
|
||||
"Chrome profile (optional)",
|
||||
typeof nextWebConfig.chromeProfile === "string" ? nextWebConfig.chromeProfile : "",
|
||||
);
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id, { chromeProfile: profile });
|
||||
} else {
|
||||
nextWebConfig = configureWebSearchProvider(nextWebConfig, selected.id);
|
||||
}
|
||||
|
||||
saveFeynmanConfig({
|
||||
...config,
|
||||
webSearch: nextWebConfig,
|
||||
});
|
||||
printSuccess(`Saved web search provider: ${selected.label}`);
|
||||
if (selected.id === "gemini-browser") {
|
||||
printInfo("Gemini Browser relies on a signed-in Chromium profile through pi-web-access.");
|
||||
}
|
||||
async function explainWebAccess(): Promise<void> {
|
||||
const status = getPiWebAccessStatus();
|
||||
printSection("Web Access");
|
||||
printInfo("Feynman uses the bundled `pi-web-access` package directly.");
|
||||
printInfo("Default v1 path: sign into gemini.google.com in a supported Chromium browser.");
|
||||
printInfo(`Current search route: ${status.routeLabel}`);
|
||||
printInfo(`Pi config path: ${status.configPath}`);
|
||||
printInfo("Advanced users can edit the Pi config directly if they want API keys or a different route.");
|
||||
}
|
||||
|
||||
function isPreviewConfigured() {
|
||||
return Boolean(loadFeynmanConfig().preview?.lastSetupAt);
|
||||
return Boolean(resolveExecutable("pandoc", PANDOC_FALLBACK_PATHS));
|
||||
}
|
||||
|
||||
function isInteractiveTerminal(): boolean {
|
||||
@@ -100,13 +46,10 @@ function printNonInteractiveSetupGuidance(): void {
|
||||
]);
|
||||
printInfo("Use the explicit commands instead of the interactive setup wizard:");
|
||||
printInfo(" feynman status");
|
||||
printInfo(" feynman model providers");
|
||||
printInfo(" feynman model login <provider>");
|
||||
printInfo(" feynman model list");
|
||||
printInfo(" feynman model recommend");
|
||||
printInfo(" feynman model set <provider/model>");
|
||||
printInfo(" feynman search providers");
|
||||
printInfo(" feynman search set <provider> [value]");
|
||||
printInfo(" feynman search status");
|
||||
printInfo(` edit ${getPiWebSearchConfigPath()} # optional advanced web config`);
|
||||
printInfo(" feynman alpha login");
|
||||
printInfo(" feynman doctor");
|
||||
printInfo(" feynman # Pi's /login flow still works inside chat if you prefer it");
|
||||
@@ -115,25 +58,16 @@ function printNonInteractiveSetupGuidance(): void {
|
||||
async function runPreviewSetup(): Promise<void> {
|
||||
const result = setupPreviewDependencies();
|
||||
printSuccess(result.message);
|
||||
saveFeynmanConfig({
|
||||
...loadFeynmanConfig(),
|
||||
preview: {
|
||||
lastSetupAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function printConfigurationLocation(appRoot: string): void {
|
||||
printSection("Configuration Location");
|
||||
printInfo(`Config file: ${FEYNMAN_CONFIG_PATH}`);
|
||||
printInfo(`Data folder: ${getFeynmanHome()}`);
|
||||
printInfo(`Sessions: ${getDefaultSessionDir()}`);
|
||||
printInfo(`Install dir: ${appRoot}`);
|
||||
printInfo("You can edit config.json directly or use `feynman config` commands.");
|
||||
}
|
||||
|
||||
function printSetupSummary(settingsPath: string, authPath: string): void {
|
||||
const config = loadFeynmanConfig();
|
||||
const webStatus = getWebSearchStatus(config.webSearch ?? {});
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(authPath),
|
||||
getAvailableModelRecords(authPath),
|
||||
@@ -144,46 +78,24 @@ function printSetupSummary(settingsPath: string, authPath: string): void {
|
||||
printInfo(`Model valid: ${modelStatus.currentValid ? "yes" : "no"}`);
|
||||
printInfo(`Recommended model: ${modelStatus.recommended ?? "not available"}`);
|
||||
printInfo(`alphaXiv: ${isAlphaLoggedIn() ? "configured" : "missing"}`);
|
||||
printInfo(`Web research: ${hasConfiguredWebProvider(config.webSearch ?? {}) ? webStatus.selected.label : "not configured"}`);
|
||||
printInfo(`Web access: pi-web-access (${getPiWebAccessStatus().routeLabel})`);
|
||||
printInfo(`Preview: ${isPreviewConfigured() ? "configured" : "not configured"}`);
|
||||
for (const line of modelStatus.guidance) {
|
||||
printInfo(line);
|
||||
}
|
||||
}
|
||||
|
||||
async function runSetupSection(section: "model" | "alpha" | "web" | "preview", options: SetupOptions): Promise<void> {
|
||||
if (section === "model") {
|
||||
await runModelSetup(options.settingsPath, options.authPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === "alpha") {
|
||||
if (!isAlphaLoggedIn()) {
|
||||
await loginAlpha();
|
||||
printSuccess("alphaXiv login complete");
|
||||
} else {
|
||||
printInfo("alphaXiv login already configured");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === "web") {
|
||||
await setupWebProvider();
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === "preview") {
|
||||
await runPreviewSetup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function runFullSetup(options: SetupOptions): Promise<void> {
|
||||
printConfigurationLocation(options.appRoot);
|
||||
await runSetupSection("model", options);
|
||||
await runSetupSection("alpha", options);
|
||||
await runSetupSection("web", options);
|
||||
await runSetupSection("preview", options);
|
||||
await runModelSetup(options.settingsPath, options.authPath);
|
||||
if (!isAlphaLoggedIn()) {
|
||||
await loginAlpha();
|
||||
printSuccess("alphaXiv login complete");
|
||||
} else {
|
||||
printInfo("alphaXiv login already configured");
|
||||
}
|
||||
await explainWebAccess();
|
||||
await runPreviewSetup();
|
||||
normalizeFeynmanSettings(
|
||||
options.settingsPath,
|
||||
options.bundledSettingsPath,
|
||||
@@ -200,49 +112,7 @@ async function runFullSetup(options: SetupOptions): Promise<void> {
|
||||
printSetupSummary(options.settingsPath, options.authPath);
|
||||
}
|
||||
|
||||
async function runQuickSetup(options: SetupOptions): Promise<void> {
|
||||
printSection("Quick Setup");
|
||||
let changed = false;
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(options.authPath),
|
||||
getAvailableModelRecords(options.authPath),
|
||||
getCurrentModelSpec(options.settingsPath),
|
||||
);
|
||||
|
||||
if (!modelStatus.current || !modelStatus.currentValid) {
|
||||
await runSetupSection("model", options);
|
||||
changed = true;
|
||||
}
|
||||
if (!isAlphaLoggedIn()) {
|
||||
await runSetupSection("alpha", options);
|
||||
changed = true;
|
||||
}
|
||||
if (!hasConfiguredWebProvider(loadFeynmanConfig().webSearch ?? {})) {
|
||||
await runSetupSection("web", options);
|
||||
changed = true;
|
||||
}
|
||||
if (!isPreviewConfigured()) {
|
||||
await runSetupSection("preview", options);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
printSuccess("Everything already looks configured.");
|
||||
printInfo("Run `feynman setup` and choose Full Setup if you want to reconfigure everything.");
|
||||
return;
|
||||
}
|
||||
|
||||
normalizeFeynmanSettings(
|
||||
options.settingsPath,
|
||||
options.bundledSettingsPath,
|
||||
options.defaultThinkingLevel ?? "medium",
|
||||
options.authPath,
|
||||
);
|
||||
printSetupSummary(options.settingsPath, options.authPath);
|
||||
}
|
||||
|
||||
function hasExistingSetup(settingsPath: string, authPath: string): boolean {
|
||||
const config = loadFeynmanConfig();
|
||||
const modelStatus = buildModelStatusSnapshotFromRecords(
|
||||
getSupportedModelRecords(authPath),
|
||||
getAvailableModelRecords(authPath),
|
||||
@@ -252,8 +122,7 @@ function hasExistingSetup(settingsPath: string, authPath: string): boolean {
|
||||
modelStatus.current ||
|
||||
modelStatus.availableModels.length > 0 ||
|
||||
isAlphaLoggedIn() ||
|
||||
hasConfiguredWebProvider(config.webSearch ?? {}) ||
|
||||
config.preview?.lastSetupAt,
|
||||
isPreviewConfigured(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -267,13 +136,11 @@ async function runDefaultInteractiveSetup(options: SetupOptions): Promise<void>
|
||||
if (existing) {
|
||||
printSection("Full Setup");
|
||||
printInfo("Existing configuration detected. Rerunning the full guided setup.");
|
||||
printInfo("Use `feynman setup quick` if you only want to fill missing items.");
|
||||
} else {
|
||||
printInfo("We'll walk you through:");
|
||||
printInfo(" 1. Model Selection");
|
||||
printInfo(" 2. alphaXiv Login");
|
||||
printInfo(" 3. Web Research Provider");
|
||||
printInfo(" 4. Preview Dependencies");
|
||||
printInfo(" 3. Preview Dependencies");
|
||||
}
|
||||
printInfo("Press Enter to begin, or Ctrl+C to exit.");
|
||||
await promptText("Press Enter to start");
|
||||
@@ -286,31 +153,5 @@ export async function runSetup(options: SetupOptions): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options.section) {
|
||||
await runDefaultInteractiveSetup(options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.section === "model") {
|
||||
await runSetupSection("model", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "alpha") {
|
||||
await runSetupSection("alpha", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "web") {
|
||||
await runSetupSection("web", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "preview") {
|
||||
await runSetupSection("preview", options);
|
||||
return;
|
||||
}
|
||||
if (options.section === "quick") {
|
||||
await runQuickSetup(options);
|
||||
return;
|
||||
}
|
||||
|
||||
await runFullSetup(options);
|
||||
await runDefaultInteractiveSetup(options);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user