jeo-code 0.12.1 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -15
- package/README.ja.md +26 -5
- package/README.ko.md +25 -5
- package/README.md +25 -5
- package/README.zh.md +26 -5
- package/package.json +1 -1
- package/src/agent/antigravity-cli.ts +499 -0
- package/src/agent/session.ts +25 -0
- package/src/agent/task-tool.ts +9 -3
- package/src/ai/model-cache.ts +7 -2
- package/src/ai/model-catalog-compat.ts +5 -1
- package/src/ai/model-catalog.ts +17 -16
- package/src/ai/model-discovery.ts +57 -20
- package/src/ai/model-manager.ts +7 -0
- package/src/ai/model-registry.ts +4 -4
- package/src/ai/provider-ranking.ts +2 -2
- package/src/ai/provider-status.ts +21 -2
- package/src/ai/providers/custom-providers.ts +1 -1
- package/src/ai/types.ts +1 -1
- package/src/commands/doctor.ts +18 -1
- package/src/commands/launch/agy-turn.ts +60 -0
- package/src/commands/launch/route-slash.ts +8 -3
- package/src/commands/launch.ts +139 -15
- package/src/util/provider-error.ts +3 -0
package/src/commands/launch.ts
CHANGED
|
@@ -54,6 +54,10 @@ import chalk from "chalk";
|
|
|
54
54
|
import { callLlm, type Message } from "../agent/loop";
|
|
55
55
|
import { friendlyProviderError } from "../util/provider-error";
|
|
56
56
|
import { readGlobalConfig, saveConfigPatch, resolveWikiRoot } from "../agent/state";
|
|
57
|
+
import { AgyCliError, type AgyTurnResult } from "../agent/antigravity-cli";
|
|
58
|
+
import { runPreToolHooks, runPostTurnHooks } from "../agent/hooks";
|
|
59
|
+
import { runAgySessionTurn } from "./launch/agy-turn";
|
|
60
|
+
import { listProviderModels } from "../ai/model-discovery";
|
|
57
61
|
import { rememberModelPatch, recentModelsForDisplay } from "../agent/model-recency";
|
|
58
62
|
import { describeModel, describeAllProviders, describeProvider, resolveProvider, thinkingMaxTokens, resolveMaxOutputTokens, thinkingToReasoningEffort, discoverModels, flattenModels, resolveSelection, catalogMetadata, catalogByProvider, liveProviderCatalogModels, resolveRoleModel, CODEX_MODELS, qualifyModelId, modelServableWithConfig, isLocalProviderReachable } from "../ai";
|
|
59
63
|
import { rehydrateLiveModels, writeModelCache } from "../ai/model-cache";
|
|
@@ -350,7 +354,7 @@ export function providerPickEntries(live: ProviderModelsResult[], want: Provider
|
|
|
350
354
|
if (fromLive.length) return fromLive;
|
|
351
355
|
// An OpenAI or Antigravity empty/error response is authoritative for this picker.
|
|
352
356
|
// Offering the static capability catalog here would fabricate account availability.
|
|
353
|
-
if (want === "openai" || want === "antigravity") return [];
|
|
357
|
+
if (want === "openai" || want === "antigravity" || want === "agy") return [];
|
|
354
358
|
const catalog = catalogByProvider(want);
|
|
355
359
|
if (catalog.length) {
|
|
356
360
|
return catalog.map((m, i) => ({ index: i + 1, provider: want, model: qualifyModelId(m.providerModel, want) }));
|
|
@@ -420,9 +424,18 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
420
424
|
} catch {}
|
|
421
425
|
const cfg = await readGlobalConfig();
|
|
422
426
|
const defaultModel = cfg.defaultModel;
|
|
423
|
-
|
|
427
|
+
let initialSessionModel =
|
|
424
428
|
flags.model ??
|
|
425
429
|
(flags.modelRole ? resolveRoleModel(flags.modelRole, cfg) : flags.provider ? providerDefaultModel(flags.provider) : undefined);
|
|
430
|
+
if (flags.provider === "agy" && !flags.model && !flags.modelRole) {
|
|
431
|
+
const available = await listProviderModels("agy", { config: cfg });
|
|
432
|
+
if (!available.ok || !available.models.length) {
|
|
433
|
+
console.error(sanitizeForTerminal(`error: ${available.error ?? "the official agy CLI returned no available models"}`));
|
|
434
|
+
process.exitCode = 1;
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
initialSessionModel = available.models[0];
|
|
438
|
+
}
|
|
426
439
|
if (flags.provider && initialSessionModel) {
|
|
427
440
|
const { provider } = await describeModel(initialSessionModel);
|
|
428
441
|
if (provider !== flags.provider) {
|
|
@@ -955,6 +968,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
955
968
|
const subagentRegistry = new SubagentRegistry();
|
|
956
969
|
const jobRegistry = new JobRegistry();
|
|
957
970
|
let activeTui: LaunchTui | null = null;
|
|
971
|
+
let activeAgyController: AbortController | undefined;
|
|
972
|
+
let activeAgyTurn: Promise<AgyTurnResult> | undefined;
|
|
958
973
|
const monitorRegistry = new MonitorRegistry({
|
|
959
974
|
onLine: (record, line) => {
|
|
960
975
|
const notice = `[monitor ${record.id}] ${line}`;
|
|
@@ -968,6 +983,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
968
983
|
const cleanupSession = async (): Promise<void> => {
|
|
969
984
|
if (cleanedUp) return;
|
|
970
985
|
cleanedUp = true;
|
|
986
|
+
activeAgyController?.abort();
|
|
987
|
+
await activeAgyTurn?.catch(() => undefined);
|
|
971
988
|
subagentRegistry.cancelAll();
|
|
972
989
|
jobRegistry.cancelAll();
|
|
973
990
|
monitorRegistry.cancelAll();
|
|
@@ -1048,6 +1065,96 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1048
1065
|
// gjc-style); a string → show that compact label instead of the raw input.
|
|
1049
1066
|
opts?: { userCard?: string | null },
|
|
1050
1067
|
): Promise<{ done: boolean; steps: number; reply: string; rendered: boolean; usage: string }> => {
|
|
1068
|
+
const selectedModel = sessionModel || defaultModel;
|
|
1069
|
+
if (selectedModel.startsWith("agy/")) {
|
|
1070
|
+
const oneShot = !!flags.print || flags.message.length > 0 || !process.stdin.isTTY;
|
|
1071
|
+
if (flags.noTools || flags.tools || flags.maxSteps > 0 || flags.systemPrompt || flags.appendSystemPrompt || flags.thinking) {
|
|
1072
|
+
if (oneShot) process.exitCode = 1;
|
|
1073
|
+
throw new AgyCliError("The official agy agent owns its tools, system prompt, step budget, and reasoning settings. Remove jeo's raw-model tool/system/step/thinking flags and select an exact agy model slug instead.", "protocol", 1);
|
|
1074
|
+
}
|
|
1075
|
+
const cliTui = useTui ? new LaunchTui({ model: selectedModel, provider: "agy", sessionId, maxSteps: 0, cwd, branch }) : null;
|
|
1076
|
+
const notice = (message: string): void => {
|
|
1077
|
+
const safe = sanitizeForTerminal(message);
|
|
1078
|
+
if (cliTui) cliTui.events().onNotice?.(safe);
|
|
1079
|
+
else console.error(safe);
|
|
1080
|
+
};
|
|
1081
|
+
let hardExit = false;
|
|
1082
|
+
const harness = createInFlightAbortHarness({
|
|
1083
|
+
captureEsc: !!cliTui,
|
|
1084
|
+
onAbortNotice: notice,
|
|
1085
|
+
onNoise: () => cliTui?.repaint(),
|
|
1086
|
+
pasteActive: () => queueBusyPasteActive?.() ?? false,
|
|
1087
|
+
onBufferedInput: chunk => {
|
|
1088
|
+
if (queueBusyInput?.(chunk) && jeoEnv("NO_LIVE_DRAFT") !== "1") {
|
|
1089
|
+
cliTui?.setLivePromptInput(expandSentinel(queueBusySnapshot?.().text ?? ""));
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
onHardExit: () => { hardExit = true; },
|
|
1093
|
+
});
|
|
1094
|
+
activeAgyController = harness.controller;
|
|
1095
|
+
activeTui = cliTui;
|
|
1096
|
+
interactiveTurnActive = !!cliTui;
|
|
1097
|
+
lastUserInput = userInput;
|
|
1098
|
+
lastTurnReasoning = "";
|
|
1099
|
+
lastTurnArtifacts = [];
|
|
1100
|
+
try {
|
|
1101
|
+
cliTui?.start();
|
|
1102
|
+
cliTui?.setTurnTitle(userInput);
|
|
1103
|
+
const card = opts?.userCard === undefined ? userInput : opts.userCard;
|
|
1104
|
+
if (card?.trim()) cliTui?.flushUserCard(card);
|
|
1105
|
+
notice("Official agy agent: uses its own tools, sandbox, and permissions; jeo approvals do not mediate its inner tool calls.");
|
|
1106
|
+
const pre = await runPreToolHooks(cwd, "agy", { model: selectedModel, prompt: userInput }, harness.controller.signal, notice);
|
|
1107
|
+
if (pre.vetoed) throw new AgyCliError(pre.error ?? "Official CLI delegation blocked by a jeo hook", "upstream", 1);
|
|
1108
|
+
sessionNotifyEndpoint?.sendContextUpdate("turn_start", notifyRedact ? "[redacted]" : userInput.slice(0, 200), { model: selectedModel });
|
|
1109
|
+
let streamed = "";
|
|
1110
|
+
activeAgyTurn = runAgySessionTurn({
|
|
1111
|
+
model: selectedModel, prompt: userInput, cwd, history, sessionId, images,
|
|
1112
|
+
signal: harness.controller.signal,
|
|
1113
|
+
mode: flags.modelRole === "plan" ? "plan" : undefined,
|
|
1114
|
+
onNotice: notice,
|
|
1115
|
+
onText: cliTui ? text => {
|
|
1116
|
+
streamed += sanitizeForTerminal(text);
|
|
1117
|
+
cliTui.events().onModelStream?.(streamed);
|
|
1118
|
+
} : undefined,
|
|
1119
|
+
});
|
|
1120
|
+
const result = await activeAgyTurn;
|
|
1121
|
+
const post = await runPostTurnHooks(cwd, "agy", { model: selectedModel, prompt: userInput }, true, result.response, harness.controller.signal, notice);
|
|
1122
|
+
if (post.diags.length) throw new AgyCliError(`Official CLI completed, but a jeo post-turn hook failed: ${post.diags.map(diag => diag.output).join("\n")}`, "upstream", 1);
|
|
1123
|
+
const reply = sanitizeForTerminal(result.response);
|
|
1124
|
+
lastReply = result.response;
|
|
1125
|
+
lastActiveModel = selectedModel;
|
|
1126
|
+
if (result.usage) {
|
|
1127
|
+
sessionUsage.inputTokens += result.usage.inputTokens;
|
|
1128
|
+
sessionUsage.outputTokens += result.usage.outputTokens;
|
|
1129
|
+
cliTui?.events().onUsage?.(result.usage);
|
|
1130
|
+
}
|
|
1131
|
+
sessionUsage.turns++;
|
|
1132
|
+
const usage = result.usage ? `\n${formatUsage(result.usage)}` : "";
|
|
1133
|
+
sessionNotifyEndpoint?.sendContextUpdate("turn_end", notifyRedact ? "[redacted]" : reply.slice(0, 200), { model: selectedModel, usage });
|
|
1134
|
+
sessionNotifyEndpoint?.sendTurnStream(notifyRedact ? "[redacted]" : reply);
|
|
1135
|
+
cliTui?.events().onStep?.(result.steps);
|
|
1136
|
+
cliTui?.finish(reply);
|
|
1137
|
+
return { done: true, steps: result.steps, reply, rendered: !!cliTui, usage };
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
if (oneShot) {
|
|
1140
|
+
process.exitCode = error instanceof AgyCliError
|
|
1141
|
+
? (error.kind === "cancelled" ? 130 : Math.max(1, error.exitCode ?? 1))
|
|
1142
|
+
: 1;
|
|
1143
|
+
}
|
|
1144
|
+
cliTui?.finish(`! ${friendlyProviderError(error)}`, { ok: false });
|
|
1145
|
+
throw error;
|
|
1146
|
+
} finally {
|
|
1147
|
+
harness.dispose();
|
|
1148
|
+
activeAgyTurn = undefined;
|
|
1149
|
+
activeAgyController = undefined;
|
|
1150
|
+
activeTui = null;
|
|
1151
|
+
interactiveTurnActive = false;
|
|
1152
|
+
if (hardExit) {
|
|
1153
|
+
process.exitCode = 130;
|
|
1154
|
+
if (!oneShot) forceExitFromCtrlC();
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1051
1158
|
const turnConfig = await readGlobalConfig();
|
|
1052
1159
|
// Fall back to the model resolved at THIS session's start (frozen snapshot), NOT
|
|
1053
1160
|
// the live global default. A concurrent `jeo` session running `/model` persists the
|
|
@@ -1552,7 +1659,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1552
1659
|
const fullTools = {
|
|
1553
1660
|
...DEFAULT_TOOLS,
|
|
1554
1661
|
task: createTaskTool({
|
|
1555
|
-
|
|
1662
|
+
// Subagents inherit the parent's EFFECTIVE routing state (`/route on|off`
|
|
1663
|
+
// override ?? config) — task-tool.ts gates its failover reroute on it.
|
|
1664
|
+
config: { ...turnConfig, defaultModel: activeModel, routing: { ...turnConfig.routing, enabled: routingEnabled } },
|
|
1665
|
+
|
|
1556
1666
|
signal: ac.signal,
|
|
1557
1667
|
steer: drainSteer,
|
|
1558
1668
|
registry: subagentRegistry,
|
|
@@ -1603,12 +1713,13 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1603
1713
|
// mutated in place (`.add`) as the fallback loop below tries more models — this
|
|
1604
1714
|
// closure always reads its live contents, never a stale snapshot.
|
|
1605
1715
|
const attemptedRouteModels = new Set<string>([activeModel]);
|
|
1606
|
-
//
|
|
1607
|
-
//
|
|
1608
|
-
//
|
|
1609
|
-
//
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1716
|
+
// The synthesized decision below is only the BASE for `equivalentRouteFallback`'s
|
|
1717
|
+
// tier/pool search when the pre-call router produced no decision (routing on but
|
|
1718
|
+
// fail-open, or a /model pin with `/route on`). It is inert when routing is off:
|
|
1719
|
+
// the reroute loop and the engine's bail predicates are both gated on
|
|
1720
|
+
// `routingEnabled`, so `/route off` means a FIXED model with no failover.
|
|
1721
|
+
// (`inferTierForModel` gives the same-size-class tier/pool to search.)
|
|
1722
|
+
|
|
1612
1723
|
let fallbackBaseDecision: RouteDecision = routed ?? {
|
|
1613
1724
|
model: activeModel,
|
|
1614
1725
|
thinking: activeThinking,
|
|
@@ -1678,8 +1789,10 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1678
1789
|
signal: ac.signal,
|
|
1679
1790
|
sessionKey: cacheKey,
|
|
1680
1791
|
steer: drainSteer,
|
|
1681
|
-
|
|
1682
|
-
|
|
1792
|
+
// Withheld when routing is off: the bail predicates only make sense paired
|
|
1793
|
+
// with the reroute loop below, which `routingEnabled` also gates.
|
|
1794
|
+
...(routingEnabled ? { rateLimitFallbackAvailable, safetyFallbackAvailable } : {}),
|
|
1795
|
+
|
|
1683
1796
|
events: wrapEvents(withStepPersistence({ ...withToolDetailCapture(tui ? tui.events() : streamEvents), onBeforeDone }, persistTurnTail), opik),
|
|
1684
1797
|
});
|
|
1685
1798
|
const routeFailureReason = (doneReason: unknown): string | null => {
|
|
@@ -1759,7 +1872,8 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1759
1872
|
const routeFallbackMax = Number.isFinite(routeFallbackMaxRaw) && routeFallbackMaxRaw >= 0
|
|
1760
1873
|
? Math.floor(routeFallbackMaxRaw)
|
|
1761
1874
|
: 3;
|
|
1762
|
-
for (let routeFallbackAttempt = 0; routeFallbackAttempt < routeFallbackMax; routeFallbackAttempt++) {
|
|
1875
|
+
for (let routeFallbackAttempt = 0; routingEnabled && routeFallbackAttempt < routeFallbackMax; routeFallbackAttempt++) {
|
|
1876
|
+
|
|
1763
1877
|
// A loop guard is a local safety boundary, not a provider failure. A
|
|
1764
1878
|
// fresh loop on an equivalent model loses the guard state and could
|
|
1765
1879
|
// re-execute a previously skipped mutation.
|
|
@@ -1855,10 +1969,20 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1855
1969
|
}
|
|
1856
1970
|
// A completed turn with an empty done-reason must NOT masquerade as a step-limit
|
|
1857
1971
|
// failure ("reached the 3-step limit" after the model called done at step 3).
|
|
1858
|
-
const
|
|
1972
|
+
const baseReply = stripSafetyFallbackTag(result.doneReason ?? "")
|
|
1859
1973
|
|| (result.done
|
|
1860
1974
|
? `(done in ${result.steps} step${result.steps === 1 ? "" : "s"} — the model returned no summary)`
|
|
1861
1975
|
: `(reached the ${result.steps}-step limit without signaling done)`);
|
|
1976
|
+
// Routing off = the model is fixed and nothing fails over. When the turn died on
|
|
1977
|
+
// an account-level limit, say so and name the one switch that re-enables failover.
|
|
1978
|
+
const failoverHint = !routingEnabled && !result.done && (() => {
|
|
1979
|
+
const failure = new Error(result.doneReason ?? "");
|
|
1980
|
+
return isRateLimitError(failure) || isUsageLimitError(failure);
|
|
1981
|
+
})()
|
|
1982
|
+
? "\n(routing is off — the model is fixed and no equivalent-model failover was attempted. Run '/route on' to allow failover.)"
|
|
1983
|
+
: "";
|
|
1984
|
+
const reply = `${baseReply}${failoverHint}`;
|
|
1985
|
+
|
|
1862
1986
|
if (sessionNotifyEndpoint) {
|
|
1863
1987
|
const usageStr = result.usage ? formatUsage(result.usage) : undefined;
|
|
1864
1988
|
if (notifyRedact) {
|
|
@@ -1965,7 +2089,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
1965
2089
|
// > 0) and stays silent (no stdout noise) since one-shot output may be
|
|
1966
2090
|
// piped/parsed by a script.
|
|
1967
2091
|
const maybeDistillOneShot = async (): Promise<void> => {
|
|
1968
|
-
if (sessionUsage.turns > 0) {
|
|
2092
|
+
if (sessionUsage.turns > 0 && !(sessionModel || defaultModel).startsWith("agy/")) {
|
|
1969
2093
|
await spawnDetachedDistill(history, cwd, sessionModel || defaultModel);
|
|
1970
2094
|
}
|
|
1971
2095
|
};
|
|
@@ -5152,7 +5276,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
|
|
|
5152
5276
|
// hermes-style experience distill (plan/gjc-inheritance.md B6) — now handed to a
|
|
5153
5277
|
// DETACHED child (round-16): /exit and ^C^C return the shell IMMEDIATELY instead
|
|
5154
5278
|
// of blocking up to 20s on a final LLM call. The child writes MEMORY.md atomically.
|
|
5155
|
-
if (sessionUsage.turns > 0) {
|
|
5279
|
+
if (sessionUsage.turns > 0 && !(sessionModel || defaultModel).startsWith("agy/")) {
|
|
5156
5280
|
const spawned = await spawnDetachedDistill(history, cwd, sessionModel || defaultModel);
|
|
5157
5281
|
if (spawned) console.log(chalk.gray("(session memory distilling in background)"));
|
|
5158
5282
|
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import { isUsageLimitError, isRefusalError, isConnectionError, ConnectionContextError } from "./retry";
|
|
8
8
|
import { companyLabel } from "../ai/model-catalog";
|
|
9
9
|
import { ProviderHttpError, ProviderStreamError } from "../ai/providers/errors";
|
|
10
|
+
import { AgyCliError } from "../agent/antigravity-cli";
|
|
11
|
+
import { sanitizeForTerminal } from "../tui/components/code-view";
|
|
10
12
|
// Re-export: engine.ts and callers import the refusal predicate from here; the
|
|
11
13
|
// definition lives in retry.ts so defaultRetryable can fail fast without a cycle.
|
|
12
14
|
export { isRefusalError } from "./retry";
|
|
@@ -71,6 +73,7 @@ function retryAfterMsOf(err: unknown): number | undefined {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export function friendlyProviderError(err: unknown): string {
|
|
76
|
+
if (err instanceof AgyCliError) return sanitizeForTerminal(err.message);
|
|
74
77
|
if (err instanceof ConnectionContextError) {
|
|
75
78
|
const provider = companyLabel(err.provider);
|
|
76
79
|
const target = err.baseUrl ? ` at ${err.baseUrl}` : "";
|