@mrclrchtr/supi-antigravity 6.4.0

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.
Files changed (67) hide show
  1. package/CLAUDE.md +21 -0
  2. package/CONTEXT.md +53 -0
  3. package/README.md +75 -0
  4. package/docs/adr/0001-use-an-isolated-antigravity-home.md +5 -0
  5. package/node_modules/@mrclrchtr/supi-core/README.md +118 -0
  6. package/node_modules/@mrclrchtr/supi-core/package.json +76 -0
  7. package/node_modules/@mrclrchtr/supi-core/src/api.ts +40 -0
  8. package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +232 -0
  9. package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +363 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/config.ts +12 -0
  11. package/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
  12. package/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
  13. package/node_modules/@mrclrchtr/supi-core/src/context.ts +8 -0
  14. package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
  15. package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +308 -0
  16. package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +120 -0
  17. package/node_modules/@mrclrchtr/supi-core/src/debug.ts +14 -0
  18. package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +41 -0
  19. package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +57 -0
  20. package/node_modules/@mrclrchtr/supi-core/src/index.ts +34 -0
  21. package/node_modules/@mrclrchtr/supi-core/src/llm.ts +201 -0
  22. package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
  23. package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +44 -0
  24. package/node_modules/@mrclrchtr/supi-core/src/path.ts +2 -0
  25. package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
  26. package/node_modules/@mrclrchtr/supi-core/src/project.ts +15 -0
  27. package/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
  28. package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +93 -0
  29. package/node_modules/@mrclrchtr/supi-core/src/report.ts +121 -0
  30. package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +71 -0
  31. package/node_modules/@mrclrchtr/supi-core/src/session.ts +8 -0
  32. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +105 -0
  33. package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +453 -0
  34. package/node_modules/@mrclrchtr/supi-core/src/settings.ts +36 -0
  35. package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
  36. package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
  37. package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
  38. package/package.json +79 -0
  39. package/scripts/live-probe.ts +161 -0
  40. package/src/activity.ts +22 -0
  41. package/src/availability.ts +231 -0
  42. package/src/catalogue.ts +21 -0
  43. package/src/config.ts +26 -0
  44. package/src/conversation/handles.ts +183 -0
  45. package/src/extension.ts +22 -0
  46. package/src/isolated-home.ts +346 -0
  47. package/src/process/environment.ts +33 -0
  48. package/src/process/event-values.ts +279 -0
  49. package/src/process/events.ts +365 -0
  50. package/src/process/hooks.ts +219 -0
  51. package/src/process/ndjson.ts +120 -0
  52. package/src/process/protocol.ts +69 -0
  53. package/src/process/runner.ts +147 -0
  54. package/src/process/subprocess.ts +278 -0
  55. package/src/process/usage.ts +50 -0
  56. package/src/runtime.ts +118 -0
  57. package/src/settings.ts +38 -0
  58. package/src/structured-output.ts +58 -0
  59. package/src/tool/antigravity_run/evidence.ts +169 -0
  60. package/src/tool/antigravity_run/execute.ts +225 -0
  61. package/src/tool/antigravity_run/guidance.ts +3 -0
  62. package/src/tool/antigravity_run/input.ts +106 -0
  63. package/src/tool/antigravity_run/register.ts +36 -0
  64. package/src/tool/antigravity_run/render.ts +236 -0
  65. package/src/tool/antigravity_run/result.ts +186 -0
  66. package/src/tool/antigravity_run/spec.ts +20 -0
  67. package/src/types.ts +107 -0
@@ -0,0 +1,278 @@
1
+ import { type ChildProcess, spawn } from "node:child_process";
2
+ import { buildAntigravityEnvironment } from "./environment.ts";
3
+ import {
4
+ BoundedLineParser,
5
+ BoundedStderrCapture,
6
+ MAX_STDOUT_BYTES,
7
+ MAX_STDOUT_LINE_BYTES,
8
+ } from "./ndjson.ts";
9
+
10
+ const COMMAND = "agy";
11
+ const TERMINATION_GRACE_MS = 500;
12
+
13
+ /** Safe outcomes from a bounded non-stream Antigravity probe. */
14
+ export interface AntigravityProbeResult {
15
+ exitCode: number | null;
16
+ signal: NodeJS.Signals | null;
17
+ stdout: string;
18
+ stderr: string;
19
+ }
20
+
21
+ /** Error raised for a failed Antigravity process or protocol. */
22
+ export class AntigravityProcessError extends Error {
23
+ readonly kind: "missing" | "timeout" | "cancelled" | "stream" | "process" | "protocol";
24
+ readonly exitCode: number | null | undefined;
25
+ /** Bounded stderr retained for an in-memory caller, never put in result details. */
26
+ readonly stderr: string | undefined;
27
+
28
+ constructor(
29
+ message: string,
30
+ kind: AntigravityProcessError["kind"],
31
+ options: { cause?: unknown; exitCode?: number | null; stderr?: string } = {},
32
+ ) {
33
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
34
+ this.name = "AntigravityProcessError";
35
+ this.kind = kind;
36
+ this.exitCode = options.exitCode;
37
+ this.stderr = options.stderr;
38
+ }
39
+ }
40
+
41
+ /** Options for one directly spawned, bounded agy process. */
42
+ export interface BoundedChildProcessOptions {
43
+ args: string[];
44
+ cwd: string;
45
+ homeDir: string;
46
+ prompt?: string;
47
+ signal?: AbortSignal;
48
+ timeoutMs: number;
49
+ onLine: (line: string) => void;
50
+ maxStdoutBytes?: number;
51
+ allowNonZero?: boolean;
52
+ onProcessStart?: () => void;
53
+ }
54
+
55
+ /** Spawn agy without a shell and apply stream, timeout, and process-group limits. */
56
+ export function runBoundedChildProcess(
57
+ options: BoundedChildProcessOptions,
58
+ ): Promise<AntigravityProbeResult> {
59
+ if (process.platform !== "darwin" && process.platform !== "linux") {
60
+ return Promise.reject(
61
+ new AntigravityProcessError("Antigravity is supported on macOS and Linux only.", "process"),
62
+ );
63
+ }
64
+ if (options.signal?.aborted) {
65
+ return Promise.reject(
66
+ new AntigravityProcessError("Antigravity run was canceled.", "cancelled"),
67
+ );
68
+ }
69
+
70
+ return new Promise((resolve, reject) => {
71
+ const parser = new BoundedLineParser({
72
+ maxLineBytes: MAX_STDOUT_LINE_BYTES,
73
+ maxTotalBytes: options.maxStdoutBytes ?? MAX_STDOUT_BYTES,
74
+ });
75
+ const stderr = new BoundedStderrCapture();
76
+ let child: ChildProcess | undefined;
77
+ let closed = false;
78
+ let settled = false;
79
+ let failure: Error | undefined;
80
+ let closeResolve: (() => void) | undefined;
81
+ const closePromise = new Promise<void>((resolveClose) => {
82
+ closeResolve = resolveClose;
83
+ });
84
+
85
+ const finishReject = (error: Error): void => {
86
+ if (settled) return;
87
+ settled = true;
88
+ reject(error);
89
+ };
90
+
91
+ const terminateAndReject = (error: Error): void => {
92
+ if (settled || failure) return;
93
+ failure = error;
94
+ void terminateProcess(child, closePromise, () => closed).then(() => {
95
+ finishReject(error);
96
+ });
97
+ };
98
+
99
+ const timer = setTimeout(() => {
100
+ terminateAndReject(new AntigravityProcessError("Antigravity process timed out.", "timeout"));
101
+ }, options.timeoutMs);
102
+ const onAbort = (): void => {
103
+ terminateAndReject(new AntigravityProcessError("Antigravity run was canceled.", "cancelled"));
104
+ };
105
+ options.signal?.addEventListener("abort", onAbort, { once: true });
106
+
107
+ try {
108
+ child = spawn(COMMAND, options.args, {
109
+ cwd: options.cwd,
110
+ env: buildAntigravityEnvironment(options.homeDir),
111
+ detached: true,
112
+ shell: false,
113
+ stdio: ["pipe", "pipe", "pipe"],
114
+ });
115
+ } catch (error) {
116
+ clearTimeout(timer);
117
+ options.signal?.removeEventListener("abort", onAbort);
118
+ terminateAndReject(classifySpawnError(error));
119
+ return;
120
+ }
121
+
122
+ child.stdout?.on("data", (chunk: Buffer | string) => {
123
+ try {
124
+ parser.feed(toBuffer(chunk), options.onLine);
125
+ } catch (error) {
126
+ terminateAndReject(
127
+ error instanceof Error
128
+ ? new AntigravityProcessError(error.message, "stream", { cause: error })
129
+ : new AntigravityProcessError("Antigravity stdout exceeded its limits.", "stream"),
130
+ );
131
+ }
132
+ });
133
+ child.stderr?.on("data", (chunk: Buffer | string) => stderr.feed(toBuffer(chunk)));
134
+ child.stdin?.on("error", (error) => terminateAndReject(classifySpawnError(error)));
135
+ child.on("error", (error) => terminateAndReject(classifySpawnError(error)));
136
+ child.on("close", (exitCode, signal) => {
137
+ handleChildClose(exitCode, signal, {
138
+ parser,
139
+ options,
140
+ stderr,
141
+ setClosed: () => {
142
+ closed = true;
143
+ closeResolve?.();
144
+ },
145
+ clearTimer: () => clearTimeout(timer),
146
+ removeAbortListener: () => options.signal?.removeEventListener("abort", onAbort),
147
+ getFailure: () => failure,
148
+ isSettled: () => settled,
149
+ resolve: (result) => {
150
+ settled = true;
151
+ resolve(result);
152
+ },
153
+ fail: terminateAndReject,
154
+ });
155
+ });
156
+
157
+ try {
158
+ options.onProcessStart?.();
159
+ if (options.prompt !== undefined) {
160
+ const userEvent = JSON.stringify({
161
+ event: "user",
162
+ message: { role: "user", content: options.prompt },
163
+ });
164
+ child.stdin?.end(`${userEvent}\n`);
165
+ } else {
166
+ child.stdin?.end();
167
+ }
168
+ } catch (error) {
169
+ terminateAndReject(classifySpawnError(error));
170
+ }
171
+ });
172
+ }
173
+
174
+ interface ChildCloseState {
175
+ parser: BoundedLineParser;
176
+ options: BoundedChildProcessOptions;
177
+ stderr: BoundedStderrCapture;
178
+ setClosed: () => void;
179
+ clearTimer: () => void;
180
+ removeAbortListener: () => void;
181
+ getFailure: () => Error | undefined;
182
+ isSettled: () => boolean;
183
+ resolve: (result: AntigravityProbeResult) => void;
184
+ fail: (error: Error) => void;
185
+ }
186
+
187
+ function handleChildClose(
188
+ exitCode: number | null,
189
+ signal: NodeJS.Signals | null,
190
+ state: ChildCloseState,
191
+ ): void {
192
+ state.setClosed();
193
+ state.clearTimer();
194
+ state.removeAbortListener();
195
+ if (state.getFailure()) return;
196
+ try {
197
+ state.parser.finish(state.options.onLine);
198
+ ensureSuccessfulExit(exitCode, signal, state.options.allowNonZero, state.stderr.text());
199
+ if (!state.isSettled()) {
200
+ state.resolve({ exitCode, signal, stdout: "", stderr: state.stderr.text() });
201
+ }
202
+ } catch (error) {
203
+ state.fail(toProtocolOrProcessError(error));
204
+ }
205
+ }
206
+
207
+ function ensureSuccessfulExit(
208
+ exitCode: number | null,
209
+ signal: NodeJS.Signals | null,
210
+ allowNonZero: boolean | undefined,
211
+ stderr: string,
212
+ ): void {
213
+ if (allowNonZero || (exitCode === 0 && !signal)) return;
214
+ throw new AntigravityProcessError(processFailureMessage(exitCode, signal), "process", {
215
+ exitCode,
216
+ stderr,
217
+ });
218
+ }
219
+
220
+ function toProtocolOrProcessError(error: unknown): AntigravityProcessError {
221
+ if (error instanceof AntigravityProcessError) return error;
222
+ return new AntigravityProcessError("Antigravity returned an invalid stream.", "protocol", {
223
+ cause: error,
224
+ });
225
+ }
226
+
227
+ async function terminateProcess(
228
+ child: ChildProcess | undefined,
229
+ closePromise: Promise<void>,
230
+ isClosed: () => boolean,
231
+ ): Promise<void> {
232
+ if (!child || isClosed()) return;
233
+ sendProcessGroupSignal(child, "SIGTERM");
234
+ await waitForClose(closePromise, TERMINATION_GRACE_MS);
235
+ if (!isClosed()) {
236
+ sendProcessGroupSignal(child, "SIGKILL");
237
+ await waitForClose(closePromise, TERMINATION_GRACE_MS);
238
+ }
239
+ }
240
+
241
+ function sendProcessGroupSignal(child: ChildProcess, signal: NodeJS.Signals): void {
242
+ if (typeof child.pid !== "number") return;
243
+ try {
244
+ process.kill(-child.pid, signal);
245
+ return;
246
+ } catch {
247
+ try {
248
+ child.kill(signal);
249
+ } catch {
250
+ // The process may have exited between the two kill attempts.
251
+ }
252
+ }
253
+ }
254
+
255
+ async function waitForClose(closePromise: Promise<void>, timeoutMs: number): Promise<void> {
256
+ await Promise.race([
257
+ closePromise,
258
+ new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),
259
+ ]);
260
+ }
261
+
262
+ function toBuffer(chunk: Buffer | string): Buffer {
263
+ return Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
264
+ }
265
+
266
+ function classifySpawnError(error: unknown): AntigravityProcessError {
267
+ const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
268
+ return new AntigravityProcessError(
269
+ code === "ENOENT" ? "Antigravity CLI was not found." : "Could not start Antigravity CLI.",
270
+ code === "ENOENT" ? "missing" : "process",
271
+ { cause: error },
272
+ );
273
+ }
274
+
275
+ function processFailureMessage(exitCode: number | null, signal: NodeJS.Signals | null): string {
276
+ if (signal) return `Antigravity process stopped with ${signal}.`;
277
+ return `Antigravity process exited with code ${String(exitCode)}.`;
278
+ }
@@ -0,0 +1,50 @@
1
+ import type { AntigravityUsage } from "../types.ts";
2
+ import { isRecord } from "./event-values.ts";
3
+
4
+ /** Read bounded token usage from one normalized Antigravity event. */
5
+ export function readUsage(event: Record<string, unknown>): AntigravityUsage | undefined {
6
+ const value = isRecord(event.usage)
7
+ ? event.usage
8
+ : isRecord(event.token_usage)
9
+ ? event.token_usage
10
+ : isRecord(event.tokenUsage)
11
+ ? event.tokenUsage
12
+ : undefined;
13
+ if (!value) return undefined;
14
+ const inputTokens = finiteToken(value.input_tokens ?? value.inputTokens ?? value.prompt_tokens);
15
+ const outputTokens = finiteToken(
16
+ value.output_tokens ?? value.outputTokens ?? value.completion_tokens,
17
+ );
18
+ const totalTokens = finiteToken(value.total_tokens ?? value.totalTokens ?? value.total);
19
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined)
20
+ return undefined;
21
+ return {
22
+ ...(inputTokens === undefined ? {} : { inputTokens }),
23
+ ...(outputTokens === undefined ? {} : { outputTokens }),
24
+ ...(totalTokens === undefined ? {} : { totalTokens }),
25
+ };
26
+ }
27
+
28
+ /** Merge usage fields without retaining any provider event payload. */
29
+ export function mergeUsage(
30
+ left: AntigravityUsage | undefined,
31
+ right: AntigravityUsage | undefined,
32
+ ): AntigravityUsage | undefined {
33
+ if (!left) return right;
34
+ if (!right) return left;
35
+ return {
36
+ ...(left.inputTokens === undefined && right.inputTokens === undefined
37
+ ? {}
38
+ : { inputTokens: right.inputTokens ?? left.inputTokens }),
39
+ ...(left.outputTokens === undefined && right.outputTokens === undefined
40
+ ? {}
41
+ : { outputTokens: right.outputTokens ?? left.outputTokens }),
42
+ ...(left.totalTokens === undefined && right.totalTokens === undefined
43
+ ? {}
44
+ : { totalTokens: right.totalTokens ?? left.totalTokens }),
45
+ };
46
+ }
47
+
48
+ function finiteToken(value: unknown): number | undefined {
49
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
50
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,118 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import { type AntigravityAvailability, discoverAntigravityAvailability } from "./availability.ts";
3
+ import { loadAntigravityConfig } from "./config.ts";
4
+ import { ConversationHandleStore } from "./conversation/handles.ts";
5
+ import { getIsolatedAntigravityPaths, type IsolatedAntigravityPaths } from "./isolated-home.ts";
6
+ import { registerAntigravityRunTool } from "./tool/antigravity_run/register.ts";
7
+ import { ANTIGRAVITY_RUN_TOOL_NAME } from "./tool/antigravity_run/spec.ts";
8
+ import type { CuratedModel } from "./types.ts";
9
+
10
+ /** Context needed to show an availability warning. */
11
+ export type AntigravityRefreshContext = { ui: Pick<ExtensionContext["ui"], "notify"> } | undefined;
12
+
13
+ /** Session runtime for immutable availability and Conversation Handle state. */
14
+ export class AntigravityRuntime {
15
+ readonly paths: IsolatedAntigravityPaths;
16
+ readonly handles = new ConversationHandleStore();
17
+ #pi: ExtensionAPI;
18
+ #homeDir: string | undefined;
19
+ #discover: typeof discoverAntigravityAvailability;
20
+ #availability: AntigravityAvailability | undefined;
21
+ #refreshGeneration = 0;
22
+ #refreshAbort: AbortController | undefined;
23
+ #toolRegistered = false;
24
+
25
+ constructor(options: {
26
+ pi: ExtensionAPI;
27
+ paths?: IsolatedAntigravityPaths;
28
+ homeDir?: string;
29
+ discover?: typeof discoverAntigravityAvailability;
30
+ }) {
31
+ this.#pi = options.pi;
32
+ this.paths = options.paths ?? getIsolatedAntigravityPaths();
33
+ this.#homeDir = options.homeDir;
34
+ this.#discover = options.discover ?? discoverAntigravityAvailability;
35
+ }
36
+
37
+ /** The immutable discovery result, if session discovery has completed. */
38
+ get availability(): AntigravityAvailability | undefined {
39
+ return this.#availability;
40
+ }
41
+
42
+ /** Rebuild handles from the current PI branch. */
43
+ rebuildHandles(branch: readonly unknown[]): void {
44
+ this.handles.rebuild(branch);
45
+ }
46
+
47
+ /** Discover or reuse availability and synchronize the active tool. */
48
+ async refresh(cwd: string, context?: AntigravityRefreshContext): Promise<void> {
49
+ const generation = ++this.#refreshGeneration;
50
+ this.#refreshAbort?.abort();
51
+ const abortController = new AbortController();
52
+ this.#refreshAbort = abortController;
53
+ const config = loadAntigravityConfig(cwd, this.#homeDir);
54
+ if (!config.agentToolEnabled) {
55
+ this.#deactivateTool();
56
+ return;
57
+ }
58
+
59
+ let availability = this.#availability;
60
+ if (!availability) {
61
+ try {
62
+ availability = await this.#discover({
63
+ paths: this.paths,
64
+ signal: abortController.signal,
65
+ });
66
+ } catch {
67
+ availability = {
68
+ status: "unavailable",
69
+ reason: "discovery",
70
+ warning:
71
+ "Antigravity availability discovery failed. Check the installation, then reload PI.",
72
+ };
73
+ }
74
+ }
75
+ if (generation !== this.#refreshGeneration || abortController.signal.aborted) return;
76
+ this.#availability = availability;
77
+ if (availability.status === "available") {
78
+ this.#activateTool(availability.catalogue, availability.cliVersion);
79
+ return;
80
+ }
81
+ this.#deactivateTool();
82
+ context?.ui.notify(availability.warning, "warning");
83
+ }
84
+
85
+ /** Stop in-flight discovery and clear session-local state. */
86
+ async shutdown(): Promise<void> {
87
+ this.#refreshGeneration += 1;
88
+ this.#refreshAbort?.abort();
89
+ this.#refreshAbort = undefined;
90
+ this.#deactivateTool();
91
+ this.handles.clear();
92
+ await Promise.resolve();
93
+ }
94
+
95
+ #activateTool(catalogue: readonly CuratedModel[], cliVersion: string): void {
96
+ if (!this.#toolRegistered) {
97
+ registerAntigravityRunTool({
98
+ pi: this.#pi,
99
+ paths: this.paths,
100
+ catalogue,
101
+ cliVersion,
102
+ handles: this.handles,
103
+ });
104
+ this.#toolRegistered = true;
105
+ }
106
+ const activeTools = this.#pi.getActiveTools();
107
+ if (!activeTools.includes(ANTIGRAVITY_RUN_TOOL_NAME)) {
108
+ this.#pi.setActiveTools([...activeTools, ANTIGRAVITY_RUN_TOOL_NAME]);
109
+ }
110
+ }
111
+
112
+ #deactivateTool(): void {
113
+ const activeTools = this.#pi.getActiveTools();
114
+ if (activeTools.includes(ANTIGRAVITY_RUN_TOOL_NAME)) {
115
+ this.#pi.setActiveTools(activeTools.filter((name) => name !== ANTIGRAVITY_RUN_TOOL_NAME));
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,38 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { defineConfigSettings, registerSettings } from "@mrclrchtr/supi-core/settings";
3
+ import { ANTIGRAVITY_CONFIG_SECTION, ANTIGRAVITY_DEFAULTS } from "./config.ts";
4
+ import type { AntigravityRuntime } from "./runtime.ts";
5
+
6
+ /** Register the Antigravity setting with an awaited availability refresh. */
7
+ export function registerAntigravitySettings(
8
+ pi: ExtensionAPI,
9
+ runtime: AntigravityRuntime,
10
+ homeDir?: string,
11
+ ): void {
12
+ const fixedSettings = defineConfigSettings({
13
+ id: ANTIGRAVITY_CONFIG_SECTION,
14
+ label: "Antigravity",
15
+ section: ANTIGRAVITY_CONFIG_SECTION,
16
+ defaults: ANTIGRAVITY_DEFAULTS,
17
+ fields: [
18
+ {
19
+ kind: "boolean" as const,
20
+ key: "agentToolEnabled",
21
+ label: "Antigravity Run tool",
22
+ description: "Enable antigravity_run and its availability checks.",
23
+ },
24
+ ],
25
+ ...(homeDir ? { homeDir } : {}),
26
+ });
27
+
28
+ registerSettings(pi, {
29
+ ...fixedSettings,
30
+ apply: async (request) => {
31
+ const result = await fixedSettings.apply(request);
32
+ if (request.fieldKey === "agentToolEnabled") {
33
+ await runtime.refresh(request.cwd, request.ctx ? { ui: request.ctx.ui } : undefined);
34
+ }
35
+ return result;
36
+ },
37
+ });
38
+ }
@@ -0,0 +1,58 @@
1
+ import { type TSchema, Type } from "typebox";
2
+ import { Value } from "typebox/value";
3
+ import type { AntigravityAnswer } from "./types.ts";
4
+
5
+ /** Maximum length of Antigravity's final answer text. */
6
+ export const MAX_ANSWER_CHARS = 16_000;
7
+ /** Maximum source references accepted from one answer. */
8
+ export const MAX_SOURCES = 8;
9
+ /** Maximum workspace references accepted from one answer. */
10
+ export const MAX_WORKSPACE_EVIDENCE = 8;
11
+ /** Maximum source title length. */
12
+ export const MAX_SOURCE_TITLE_CHARS = 240;
13
+ /** Maximum source URL length. */
14
+ export const MAX_SOURCE_URL_CHARS = 1_024;
15
+ /** Maximum workspace evidence path length. */
16
+ export const MAX_WORKSPACE_PATH_CHARS = 512;
17
+ /** Maximum workspace evidence summary length. */
18
+ export const MAX_EVIDENCE_SUMMARY_CHARS = 1_000;
19
+
20
+ /** JSON Schema passed to agy for every paid Antigravity Run. */
21
+ export const ANTIGRAVITY_ANSWER_SCHEMA: TSchema = Type.Object(
22
+ {
23
+ answer: Type.String({
24
+ minLength: 1,
25
+ maxLength: MAX_ANSWER_CHARS,
26
+ description: "The answer to the user's request.",
27
+ }),
28
+ sources: Type.Array(
29
+ Type.Object(
30
+ {
31
+ title: Type.String({ minLength: 1, maxLength: MAX_SOURCE_TITLE_CHARS }),
32
+ url: Type.String({ minLength: 1, maxLength: MAX_SOURCE_URL_CHARS }),
33
+ },
34
+ { additionalProperties: false },
35
+ ),
36
+ { maxItems: MAX_SOURCES },
37
+ ),
38
+ workspaceEvidence: Type.Array(
39
+ Type.Object(
40
+ {
41
+ path: Type.String({ minLength: 1, maxLength: MAX_WORKSPACE_PATH_CHARS }),
42
+ summary: Type.String({ minLength: 1, maxLength: MAX_EVIDENCE_SUMMARY_CHARS }),
43
+ },
44
+ { additionalProperties: false },
45
+ ),
46
+ { maxItems: MAX_WORKSPACE_EVIDENCE },
47
+ ),
48
+ },
49
+ { additionalProperties: false },
50
+ );
51
+
52
+ /** Validate and return the exact structured answer shape. */
53
+ export function validateAntigravityAnswer(value: unknown): AntigravityAnswer {
54
+ if (!Value.Check(ANTIGRAVITY_ANSWER_SCHEMA, value)) {
55
+ throw new Error("Antigravity returned invalid structured output.");
56
+ }
57
+ return value as AntigravityAnswer;
58
+ }