Fix workflow continuation and provider setup gaps

This commit is contained in:
Advait Paliwal
2026-04-17 09:47:38 -07:00
parent d30506c82a
commit 9841342866
28 changed files with 359 additions and 36 deletions

View File

@@ -69,3 +69,31 @@ test("deepresearch workflow requires durable artifacts even when blocked", () =>
assert.match(deepResearchPrompt, /Verification: BLOCKED/i);
assert.match(deepResearchPrompt, /Never end with only an explanation in chat/i);
});
test("workflow prompts do not introduce implicit confirmation gates", () => {
const workflowPrompts = [
"audit.md",
"compare.md",
"deepresearch.md",
"draft.md",
"lit.md",
"review.md",
"summarize.md",
"watch.md",
];
const bannedConfirmationGates = [
/Do you want to proceed/i,
/Wait for confirmation/i,
/wait for user confirmation/i,
/give them a brief chance/i,
/request changes before proceeding/i,
];
for (const fileName of workflowPrompts) {
const content = readFileSync(join(repoRoot, "prompts", fileName), "utf8");
assert.match(content, /continue (immediately|automatically)/i, `${fileName} should keep running after planning`);
for (const pattern of bannedConfirmationGates) {
assert.doesNotMatch(content, pattern, `${fileName} contains confirmation gate ${pattern}`);
}
}
});

View File

@@ -88,6 +88,15 @@ test("resolveModelProviderForCommand supports LM Studio as a first-class local p
assert.equal(resolved?.id, "lm-studio");
});
test("resolveModelProviderForCommand supports LiteLLM as a first-class proxy provider", () => {
const authPath = createAuthPath({});
const resolved = resolveModelProviderForCommand(authPath, "litellm");
assert.equal(resolved?.kind, "api-key");
assert.equal(resolved?.id, "litellm");
});
test("resolveModelProviderForCommand prefers OAuth when a provider supports both auth modes", () => {
const authPath = createAuthPath({});

View File

@@ -30,3 +30,45 @@ test("upsertProviderConfig creates models.json and merges provider config", () =
assert.equal(parsed.providers.custom.authHeader, true);
assert.deepEqual(parsed.providers.custom.models, [{ id: "llama3.1:8b" }]);
});
test("upsertProviderConfig writes LiteLLM proxy config with master key", () => {
const dir = mkdtempSync(join(tmpdir(), "feynman-litellm-"));
const modelsPath = join(dir, "models.json");
const result = upsertProviderConfig(modelsPath, "litellm", {
baseUrl: "http://localhost:4000/v1",
apiKey: "LITELLM_MASTER_KEY",
api: "openai-completions",
authHeader: true,
models: [{ id: "gpt-4o" }],
});
assert.deepEqual(result, { ok: true });
const parsed = JSON.parse(readFileSync(modelsPath, "utf8")) as any;
assert.equal(parsed.providers.litellm.baseUrl, "http://localhost:4000/v1");
assert.equal(parsed.providers.litellm.apiKey, "LITELLM_MASTER_KEY");
assert.equal(parsed.providers.litellm.api, "openai-completions");
assert.equal(parsed.providers.litellm.authHeader, true);
assert.deepEqual(parsed.providers.litellm.models, [{ id: "gpt-4o" }]);
});
test("upsertProviderConfig writes LiteLLM proxy config without master key", () => {
const dir = mkdtempSync(join(tmpdir(), "feynman-litellm-"));
const modelsPath = join(dir, "models.json");
const result = upsertProviderConfig(modelsPath, "litellm", {
baseUrl: "http://localhost:4000/v1",
apiKey: "local",
api: "openai-completions",
authHeader: false,
models: [{ id: "llama3" }],
});
assert.deepEqual(result, { ok: true });
const parsed = JSON.parse(readFileSync(modelsPath, "utf8")) as any;
assert.equal(parsed.providers.litellm.baseUrl, "http://localhost:4000/v1");
assert.equal(parsed.providers.litellm.apiKey, "local");
assert.equal(parsed.providers.litellm.api, "openai-completions");
assert.equal(parsed.providers.litellm.authHeader, false);
assert.deepEqual(parsed.providers.litellm.models, [{ id: "llama3" }]);
});

View File

@@ -188,6 +188,46 @@ test("installPackageSources skips native packages on unsupported Node majors bef
}
});
test("installPackageSources disables inherited npm dry-run config for child installs", async () => {
const root = mkdtempSync(join(tmpdir(), "feynman-package-ops-"));
const workingDir = resolve(root, "project");
const agentDir = resolve(root, "agent");
const markerPath = resolve(root, "install-env-ok.txt");
mkdirSync(workingDir, { recursive: true });
const scriptPath = writeFakeNpmScript(root, [
`import { writeFileSync } from "node:fs";`,
`if (process.env.npm_config_dry_run !== "false" || process.env.NPM_CONFIG_DRY_RUN !== "false") process.exit(42);`,
`writeFileSync(${JSON.stringify(markerPath)}, "ok\\n", "utf8");`,
"process.exit(0);",
].join("\n"));
writeSettings(agentDir, {
npmCommand: [process.execPath, scriptPath],
});
const originalLower = process.env.npm_config_dry_run;
const originalUpper = process.env.NPM_CONFIG_DRY_RUN;
process.env.npm_config_dry_run = "true";
process.env.NPM_CONFIG_DRY_RUN = "true";
try {
const result = await installPackageSources(workingDir, agentDir, ["npm:test-package"]);
assert.deepEqual(result.installed, ["npm:test-package"]);
assert.equal(existsSync(markerPath), true);
} finally {
if (originalLower === undefined) {
delete process.env.npm_config_dry_run;
} else {
process.env.npm_config_dry_run = originalLower;
}
if (originalUpper === undefined) {
delete process.env.NPM_CONFIG_DRY_RUN;
} else {
process.env.NPM_CONFIG_DRY_RUN = originalUpper;
}
}
});
test("updateConfiguredPackages batches multiple npm updates into a single install per scope", async () => {
const root = mkdtempSync(join(tmpdir(), "feynman-package-ops-"));
const workingDir = resolve(root, "project");

View File

@@ -1,7 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
import { patchPiSubagentsSource } from "../scripts/lib/pi-subagents-patch.mjs";
import { patchPiSubagentsSource, stripPiSubagentBuiltinModelSource } from "../scripts/lib/pi-subagents-patch.mjs";
const CASES = [
{
@@ -140,3 +140,22 @@ test("patchPiSubagentsSource rewrites modern agents.ts discovery paths", () => {
assert.ok(!patched.includes('loadChainsFromDir(userDirNew, "user")'));
assert.ok(!patched.includes('fs.existsSync(userDirNew) ? userDirNew : userDirOld'));
});
test("stripPiSubagentBuiltinModelSource removes built-in model pins", () => {
const input = [
"---",
"name: researcher",
"description: Web researcher",
"model: anthropic/claude-sonnet-4-6",
"tools: read, web_search",
"---",
"",
"Body",
].join("\n");
const patched = stripPiSubagentBuiltinModelSource(input);
assert.ok(!patched.includes("model: anthropic/claude-sonnet-4-6"));
assert.match(patched, /name: researcher/);
assert.match(patched, /tools: read, web_search/);
});