jeo-code 0.12.0 → 0.12.2

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.
@@ -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
- const initialSessionModel =
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
@@ -1965,7 +2072,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
1965
2072
  // > 0) and stays silent (no stdout noise) since one-shot output may be
1966
2073
  // piped/parsed by a script.
1967
2074
  const maybeDistillOneShot = async (): Promise<void> => {
1968
- if (sessionUsage.turns > 0) {
2075
+ if (sessionUsage.turns > 0 && !(sessionModel || defaultModel).startsWith("agy/")) {
1969
2076
  await spawnDetachedDistill(history, cwd, sessionModel || defaultModel);
1970
2077
  }
1971
2078
  };
@@ -5152,7 +5259,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
5152
5259
  // hermes-style experience distill (plan/gjc-inheritance.md B6) — now handed to a
5153
5260
  // DETACHED child (round-16): /exit and ^C^C return the shell IMMEDIATELY instead
5154
5261
  // of blocking up to 20s on a final LLM call. The child writes MEMORY.md atomically.
5155
- if (sessionUsage.turns > 0) {
5262
+ if (sessionUsage.turns > 0 && !(sessionModel || defaultModel).startsWith("agy/")) {
5156
5263
  const spawned = await spawnDetachedDistill(history, cwd, sessionModel || defaultModel);
5157
5264
  if (spawned) console.log(chalk.gray("(session memory distilling in background)"));
5158
5265
  }
@@ -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}` : "";