refactor: remove legacy autonomous aliases
This commit is contained in:
parent
861c4b6cf6
commit
46db1e95ef
18 changed files with 33 additions and 102 deletions
|
|
@ -6,22 +6,16 @@ describe("parseArgs", () => {
|
|||
it("parses optional-value extension flags with implicit and explicit values", () => {
|
||||
const extensionFlags = new Map([
|
||||
["genai-proxy", { type: "string" as const, allowNoValue: true }],
|
||||
["gemini-cli-proxy", { type: "string" as const, allowNoValue: true }],
|
||||
]);
|
||||
const defaultFlagArgs = parseArgs(["--genai-proxy"], extensionFlags);
|
||||
const explicitFlagArgs = parseArgs(["--genai-proxy=8080"], extensionFlags);
|
||||
const legacyFlagArgs = parseArgs(
|
||||
["--gemini-cli-proxy=3001"],
|
||||
extensionFlags,
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
[
|
||||
defaultFlagArgs.unknownFlags.get("genai-proxy"),
|
||||
explicitFlagArgs.unknownFlags.get("genai-proxy"),
|
||||
legacyFlagArgs.unknownFlags.get("gemini-cli-proxy"),
|
||||
],
|
||||
[true, "8080", "3001"],
|
||||
[true, "8080"],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -588,9 +588,7 @@ async function runHeadlessFromAutonomous(
|
|||
}
|
||||
|
||||
// `sf autonomous [args...]` — shorthand for headless autonomous mode (#2732).
|
||||
// The legacy `sf auto` spelling is still accepted for compatibility, but all
|
||||
// generated prompts use `/sf autonomous`.
|
||||
if (cliFlags.messages[0] === "auto" || cliFlags.messages[0] === "autonomous") {
|
||||
if (cliFlags.messages[0] === "autonomous") {
|
||||
await runHeadlessFromAutonomous([
|
||||
"autonomous",
|
||||
...cliFlags.messages.slice(1),
|
||||
|
|
@ -878,10 +876,7 @@ if (!cliFlags.worktree && !isPrintMode) {
|
|||
// the TUI cannot render and the process hangs. Redirect to headless mode
|
||||
// which handles non-interactive output gracefully.
|
||||
// ---------------------------------------------------------------------------
|
||||
if (
|
||||
(cliFlags.messages[0] === "auto" || cliFlags.messages[0] === "autonomous") &&
|
||||
!process.stdout.isTTY
|
||||
) {
|
||||
if (cliFlags.messages[0] === "autonomous" && !process.stdout.isTTY) {
|
||||
process.stderr.write(
|
||||
"[forge] stdout is not a terminal — running autonomous mode in headless mode.\n",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ export function parseHeadlessArgs(argv: string[]): HeadlessOptions {
|
|||
options.bare = true;
|
||||
}
|
||||
} else if (!commandSeen) {
|
||||
if (arg === "autonomous" || arg === "auto") {
|
||||
if (arg === "autonomous") {
|
||||
options.command = "autonomous";
|
||||
options.auto = true; // autonomous subcommand implies --auto
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { createProxyServer } from "./proxy-server.js";
|
|||
|
||||
const PROXY_COMMAND_NAME = "genai-proxy";
|
||||
const PROXY_FLAG_NAME = "genai-proxy";
|
||||
const LEGACY_PROXY_FLAG_NAME = "gemini-cli-proxy";
|
||||
const DEFAULT_PROXY_PORT = 3000;
|
||||
export function installGenaiProxyExtension(api, dependencies) {
|
||||
let proxyServer = null;
|
||||
|
|
@ -31,12 +30,6 @@ export function installGenaiProxyExtension(api, dependencies) {
|
|||
allowNoValue: true,
|
||||
onStartup: startProxyFromFlag,
|
||||
});
|
||||
api.registerFlag(LEGACY_PROXY_FLAG_NAME, {
|
||||
description: "Legacy alias for --genai-proxy",
|
||||
type: "string",
|
||||
allowNoValue: true,
|
||||
onStartup: startProxyFromFlag,
|
||||
});
|
||||
api.registerCommand(PROXY_COMMAND_NAME, {
|
||||
description: "Manage the GenAI proxy server",
|
||||
handler: async (args, context) => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ export {
|
|||
INFRA_ERROR_CODES,
|
||||
isInfrastructureError,
|
||||
} from "./auto/infra-errors.js";
|
||||
export { autoLoop, runLegacyAutoLoop, runUokKernelLoop } from "./auto/loop.js";
|
||||
export {
|
||||
autoLoop,
|
||||
runStandardAutoLoop,
|
||||
runUokKernelLoop,
|
||||
} from "./auto/loop.js";
|
||||
export {
|
||||
_hasPendingResolve,
|
||||
_resetPendingResolve,
|
||||
|
|
|
|||
|
|
@ -337,11 +337,7 @@ export {
|
|||
function registerSigtermHandler(currentBasePath) {
|
||||
const prefs = loadEffectiveSFPreferences()?.preferences;
|
||||
const flags = resolveUokFlags(prefs);
|
||||
const pathLabel = flags.legacyFallback
|
||||
? "legacy-fallback"
|
||||
: flags.enabled
|
||||
? "uok-kernel"
|
||||
: "legacy-wrapper";
|
||||
const pathLabel = flags.enabled ? "uok-kernel" : "standard-loop";
|
||||
const onSignal = () => {
|
||||
// Write UOK parity exit heartbeat before process.exit(0) bypasses
|
||||
// the finally block in runAutoLoopWithUok. Fixes the enter/exit
|
||||
|
|
@ -1736,7 +1732,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|||
s,
|
||||
deps: buildLoopDeps(),
|
||||
runKernelLoop: runUokKernelLoop,
|
||||
runLegacyLoop: autoLoop,
|
||||
runStandardLoop: autoLoop,
|
||||
});
|
||||
cleanupAfterLoopExit(ctx);
|
||||
return;
|
||||
|
|
@ -1785,7 +1781,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|||
s,
|
||||
deps: buildLoopDeps(),
|
||||
runKernelLoop: runUokKernelLoop,
|
||||
runLegacyLoop: autoLoop,
|
||||
runStandardLoop: autoLoop,
|
||||
});
|
||||
cleanupAfterLoopExit(ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ async function runUnitPhaseViaContract(
|
|||
loopState,
|
||||
sidecarItem,
|
||||
) {
|
||||
if (dispatchContract === "legacy-direct") {
|
||||
if (dispatchContract === "standard-direct") {
|
||||
return runUnitPhase(ic, iterData, loopState, sidecarItem);
|
||||
}
|
||||
const scheduler = new ExecutionGraphScheduler();
|
||||
|
|
@ -308,7 +308,7 @@ async function enforceMinRequestInterval(s, prefs) {
|
|||
* dispatchNextUnit → handleAgentEnd → dispatchNextUnit chain.
|
||||
*/
|
||||
export async function autoLoop(ctx, pi, s, deps, options) {
|
||||
const dispatchContract = options?.dispatchContract ?? "legacy-direct";
|
||||
const dispatchContract = options?.dispatchContract ?? "standard-direct";
|
||||
debugLog("autoLoop", { phase: "enter" });
|
||||
let iteration = 0;
|
||||
// Load persisted stuck state so counters survive session restarts (#3704)
|
||||
|
|
@ -1119,6 +1119,6 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|||
export async function runUokKernelLoop(ctx, pi, s, deps) {
|
||||
return autoLoop(ctx, pi, s, deps, { dispatchContract: "uok-scheduler" });
|
||||
}
|
||||
export async function runLegacyAutoLoop(ctx, pi, s, deps) {
|
||||
return autoLoop(ctx, pi, s, deps, { dispatchContract: "legacy-direct" });
|
||||
export async function runStandardAutoLoop(ctx, pi, s, deps) {
|
||||
return autoLoop(ctx, pi, s, deps, { dispatchContract: "standard-direct" });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ import { guardRemoteSession, projectRoot } from "../context.js";
|
|||
/**
|
||||
* Parse --yolo flag and optional file path from the autonomous command string.
|
||||
* Supports: `/sf autonomous --yolo path/to/file.md` or
|
||||
* `/sf autonomous -y path/to/file.md`. The legacy `/sf auto` spelling is still
|
||||
* normalized for compatibility, but is not a distinct user-facing mode.
|
||||
* `/sf autonomous -y path/to/file.md`.
|
||||
*/
|
||||
function parseYoloFlag(trimmed) {
|
||||
const yoloRe = /(?:--yolo|-y)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/;
|
||||
|
|
@ -51,7 +50,7 @@ export function parseMilestoneTarget(input) {
|
|||
* Dispatch entry point for the autonomous command family.
|
||||
*
|
||||
* Handles `/sf autonomous`, `/sf next`, `/sf stop`, `/sf pause`, and their flag
|
||||
* variants. Legacy `/sf auto` is accepted as a hidden compatibility spelling.
|
||||
* variants.
|
||||
* Returns `true` when the command was recognised and routed (caller stops
|
||||
* searching), `false` when the command isn't autonomous-related.
|
||||
*
|
||||
|
|
@ -69,8 +68,6 @@ export function parseMilestoneTarget(input) {
|
|||
export async function handleAutoCommand(trimmed, ctx, pi) {
|
||||
const isAutonomousVerb =
|
||||
trimmed === "autonomous" || trimmed.startsWith("autonomous ");
|
||||
const isLegacyAutoVerb = trimmed === "auto" || trimmed.startsWith("auto ");
|
||||
const isAutonomousFamily = isAutonomousVerb || isLegacyAutoVerb;
|
||||
/**
|
||||
* Route an autonomous launch through either the headless (in-process) or
|
||||
* detached (spawned subprocess) entry point depending on `SF_HEADLESS`.
|
||||
|
|
@ -114,9 +111,8 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
|
|||
});
|
||||
return true;
|
||||
}
|
||||
if (isAutonomousFamily) {
|
||||
const normalized = trimmed.replace(/^(?:auto|autonomous)\b/, "autonomous");
|
||||
const { yoloSeedFile, rest: afterYolo } = parseYoloFlag(normalized);
|
||||
if (isAutonomousVerb) {
|
||||
const { yoloSeedFile, rest: afterYolo } = parseYoloFlag(trimmed);
|
||||
const { milestoneId, rest: afterMilestone } =
|
||||
parseMilestoneTarget(afterYolo);
|
||||
const verboseMode = afterMilestone.includes("--verbose");
|
||||
|
|
|
|||
|
|
@ -210,8 +210,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|||
|
||||
- `uok`: orchestration kernel controls. Keys:
|
||||
- `enabled`: boolean — enable kernel wrappers and contract observers. Default: `true`.
|
||||
- `legacy_fallback.enabled`: boolean — emergency release fallback that forces legacy orchestration behavior even when `uok.enabled` is `true`. Default: `false`.
|
||||
- Runtime override: set `SF_UOK_FORCE_LEGACY=1` (or `SF_UOK_LEGACY_FALLBACK=1`) to force legacy behavior for the current process.
|
||||
- `gates.enabled`: boolean — route checks through the unified gate runner and persist `gate_runs`. Default: `true`.
|
||||
- `model_policy.enabled`: boolean — enforce policy filtering before model capability scoring. Default: `true`.
|
||||
- `execution_graph.enabled`: boolean — enable DAG scheduler facade/adapters for execution. Default: `true`.
|
||||
|
|
|
|||
|
|
@ -240,7 +240,6 @@ export function validatePreferences(preferences) {
|
|||
valid[normalizedTargetKey] = parsed;
|
||||
}
|
||||
};
|
||||
parseEnabledBlock("legacy_fallback");
|
||||
parseEnabledBlock("gates");
|
||||
parseEnabledBlock("model_policy");
|
||||
parseEnabledBlock("execution_graph");
|
||||
|
|
@ -297,7 +296,6 @@ export function validatePreferences(preferences) {
|
|||
}
|
||||
const knownUokKeys = new Set([
|
||||
"enabled",
|
||||
"legacy_fallback",
|
||||
"gates",
|
||||
"model_policy",
|
||||
"execution_graph",
|
||||
|
|
|
|||
|
|
@ -440,13 +440,6 @@ function mergePreferences(base, override) {
|
|||
base.uok || override.uok
|
||||
? {
|
||||
enabled: override.uok?.enabled ?? base.uok?.enabled,
|
||||
legacy_fallback:
|
||||
base.uok?.legacy_fallback || override.uok?.legacy_fallback
|
||||
? {
|
||||
...(base.uok?.legacy_fallback ?? {}),
|
||||
...(override.uok?.legacy_fallback ?? {}),
|
||||
}
|
||||
: undefined,
|
||||
gates:
|
||||
base.uok?.gates || override.uok?.gates
|
||||
? { ...(base.uok?.gates ?? {}), ...(override.uok?.gates ?? {}) }
|
||||
|
|
|
|||
|
|
@ -63,8 +63,6 @@ dynamic_routing:
|
|||
hooks:
|
||||
uok:
|
||||
enabled: true
|
||||
legacy_fallback:
|
||||
enabled: false
|
||||
gates:
|
||||
enabled: true
|
||||
model_policy:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|||
import { join } from "node:path";
|
||||
/**
|
||||
* Read the last UOK parity report from <basePath>/.sf/runtime/uok-parity-report.json
|
||||
* and surface any divergences/fallbacks via ctx.ui?.notify?.().
|
||||
* and surface any divergences or errors via ctx.ui?.notify?.().
|
||||
*
|
||||
* Never throws — all errors are swallowed so session_start is never blocked.
|
||||
*/
|
||||
|
|
@ -17,12 +17,10 @@ export async function summarizeParityReport(basePath, ctx, pi) {
|
|||
return;
|
||||
}
|
||||
const mismatches = report.criticalMismatches?.length ?? 0;
|
||||
const fallbacks = report.fallbackInvocations ?? 0;
|
||||
const errors = report.statuses?.error ?? 0;
|
||||
if (mismatches > 0 || fallbacks > 0 || errors > 0) {
|
||||
if (mismatches > 0 || errors > 0) {
|
||||
const msg =
|
||||
`UOK parity report shows ${mismatches} critical mismatch${mismatches === 1 ? "" : "es"}, ` +
|
||||
`${fallbacks} fallback invocation${fallbacks === 1 ? "" : "s"}, ` +
|
||||
`${errors} error${errors === 1 ? "" : "s"} since ${report.generatedAt}. ` +
|
||||
`Inspect .sf/runtime/uok-parity-report.json.`;
|
||||
ctx.ui?.notify?.(msg, "warning");
|
||||
|
|
|
|||
|
|
@ -1,25 +1,10 @@
|
|||
import { loadEffectiveSFPreferences } from "../preferences.js";
|
||||
|
||||
function envForcesLegacyFallback() {
|
||||
const raw =
|
||||
process.env.SF_UOK_FORCE_LEGACY ?? process.env.SF_UOK_LEGACY_FALLBACK;
|
||||
if (!raw) return false;
|
||||
const normalized = raw.trim().toLowerCase();
|
||||
return (
|
||||
normalized === "1" ||
|
||||
normalized === "true" ||
|
||||
normalized === "yes" ||
|
||||
normalized === "on"
|
||||
);
|
||||
}
|
||||
export function resolveUokFlags(prefs) {
|
||||
const uok = prefs?.uok;
|
||||
const legacyFallback =
|
||||
uok?.legacy_fallback?.enabled === true || envForcesLegacyFallback();
|
||||
const enabledByPreference = uok?.enabled ?? true;
|
||||
return {
|
||||
enabled: enabledByPreference && !legacyFallback,
|
||||
legacyFallback,
|
||||
enabled: enabledByPreference,
|
||||
gates: uok?.gates?.enabled ?? true,
|
||||
modelPolicy: uok?.model_policy?.enabled ?? true,
|
||||
executionGraph: uok?.execution_graph?.enabled ?? true,
|
||||
|
|
|
|||
|
|
@ -21,11 +21,10 @@ function refreshParityReport(basePath) {
|
|||
}
|
||||
}
|
||||
function resolveKernelPathLabel(flags) {
|
||||
if (flags.legacyFallback) return "legacy-fallback";
|
||||
return flags.enabled ? "uok-kernel" : "legacy-wrapper";
|
||||
return flags.enabled ? "uok-kernel" : "standard-loop";
|
||||
}
|
||||
export async function runAutoLoopWithUok(args) {
|
||||
const { ctx, pi, s, deps, runKernelLoop, runLegacyLoop } = args;
|
||||
const { ctx, pi, s, deps, runKernelLoop, runStandardLoop } = args;
|
||||
const prefs = deps.loadEffectiveSFPreferences()?.preferences;
|
||||
const flags = resolveUokFlags(prefs);
|
||||
const previousReport = refreshParityReport(s.basePath);
|
||||
|
|
@ -73,10 +72,10 @@ export async function runAutoLoopWithUok(args) {
|
|||
let status = "ok";
|
||||
let error;
|
||||
try {
|
||||
if (flags.enabled && !flags.legacyFallback) {
|
||||
if (flags.enabled) {
|
||||
await runKernelLoop(ctx, pi, s, decoratedDeps);
|
||||
} else {
|
||||
await runLegacyLoop(ctx, pi, s, deps);
|
||||
await runStandardLoop(ctx, pi, s, deps);
|
||||
}
|
||||
} catch (err) {
|
||||
status = "error";
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ export function buildParityReport(events, sourcePath) {
|
|||
const paths = {};
|
||||
const statuses = {};
|
||||
const criticalMismatches = [];
|
||||
let fallbackInvocations = 0;
|
||||
let enterEvents = 0;
|
||||
let exitEvents = 0;
|
||||
let totalDiffs = 0;
|
||||
|
|
@ -75,13 +74,12 @@ export function buildParityReport(events, sourcePath) {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
// Legacy heartbeat event
|
||||
// Kernel heartbeat event
|
||||
const heartbeat = event;
|
||||
increment(paths, heartbeat.path);
|
||||
increment(statuses, heartbeat.status);
|
||||
if (heartbeat.phase === "enter") enterEvents += 1;
|
||||
if (heartbeat.phase === "exit") exitEvents += 1;
|
||||
if (heartbeat.path === "legacy-fallback") fallbackInvocations += 1;
|
||||
if (heartbeat.status === "error") {
|
||||
criticalMismatches.push(heartbeat.error ?? "parity event reported error");
|
||||
}
|
||||
|
|
@ -99,7 +97,6 @@ export function buildParityReport(events, sourcePath) {
|
|||
paths,
|
||||
statuses,
|
||||
criticalMismatches,
|
||||
fallbackInvocations,
|
||||
enterEvents,
|
||||
exitEvents,
|
||||
missingExitEvents,
|
||||
|
|
|
|||
|
|
@ -34,14 +34,14 @@ const EXPLICIT_SUBCOMMANDS = new Set([
|
|||
* Detect whether the current subcommand should be auto-redirected
|
||||
* to headless mode when stdout is not a TTY.
|
||||
*
|
||||
* Returns true when: the subcommand is "autonomous", or legacy "auto", AND stdout is piped.
|
||||
* Returns true when the subcommand is "autonomous" and stdout is piped.
|
||||
*/
|
||||
function shouldRedirectAutoToHeadless(
|
||||
subcommand: string | undefined,
|
||||
stdoutIsTTY: boolean,
|
||||
): boolean {
|
||||
if (stdoutIsTTY) return false;
|
||||
return subcommand === "auto" || subcommand === "autonomous";
|
||||
return subcommand === "autonomous";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -65,19 +65,12 @@ function isExplicitSubcommand(subcommand: string | undefined): boolean {
|
|||
|
||||
// ─── shouldRedirectAutoToHeadless ─────────────────────────────────────────
|
||||
|
||||
test("redirects legacy 'auto' to headless when stdout is piped", () => {
|
||||
assert.ok(shouldRedirectAutoToHeadless("auto", false));
|
||||
});
|
||||
|
||||
test("redirects 'autonomous' to headless when stdout is piped", () => {
|
||||
assert.ok(shouldRedirectAutoToHeadless("autonomous", false));
|
||||
});
|
||||
|
||||
test("does NOT redirect legacy 'auto' when stdout is a TTY", () => {
|
||||
assert.ok(!shouldRedirectAutoToHeadless("auto", true));
|
||||
});
|
||||
|
||||
test("does NOT redirect non-auto subcommands when stdout is piped", () => {
|
||||
assert.ok(!shouldRedirectAutoToHeadless("auto", false));
|
||||
assert.ok(!shouldRedirectAutoToHeadless("headless", false));
|
||||
assert.ok(!shouldRedirectAutoToHeadless("config", false));
|
||||
assert.ok(!shouldRedirectAutoToHeadless("update", false));
|
||||
|
|
@ -114,7 +107,7 @@ test("identifies explicitly handled subcommands", () => {
|
|||
assert.ok(isExplicitSubcommand("web"));
|
||||
});
|
||||
|
||||
test("does NOT identify legacy 'auto' as explicit subcommand", () => {
|
||||
test("does NOT identify 'auto' as explicit subcommand", () => {
|
||||
assert.ok(!isExplicitSubcommand("auto"));
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ function parseHeadlessArgs(argv: string[]): HeadlessOptions {
|
|||
options.bare = true;
|
||||
}
|
||||
} else if (!commandSeen) {
|
||||
if (arg === "autonomous" || arg === "auto") {
|
||||
if (arg === "autonomous") {
|
||||
options.command = "autonomous";
|
||||
options.auto = true;
|
||||
} else {
|
||||
|
|
@ -190,12 +190,6 @@ test("autonomous command preserves command arguments", () => {
|
|||
assert.deepEqual(opts.commandArgs, ["M001", "extra-context"]);
|
||||
});
|
||||
|
||||
test("legacy auto command normalizes to autonomous", () => {
|
||||
const opts = parseHeadlessArgs(["node", "sf", "headless", "auto", "M001"]);
|
||||
assert.equal(opts.command, "autonomous");
|
||||
assert.deepEqual(opts.commandArgs, ["M001"]);
|
||||
});
|
||||
|
||||
test("invalid --output-format value throws", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue