@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.
- package/CLAUDE.md +21 -0
- package/CONTEXT.md +53 -0
- package/README.md +75 -0
- package/docs/adr/0001-use-an-isolated-antigravity-home.md +5 -0
- package/node_modules/@mrclrchtr/supi-core/README.md +118 -0
- package/node_modules/@mrclrchtr/supi-core/package.json +76 -0
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +40 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config.ts +232 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/prompt-surface.ts +363 -0
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +12 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-provider-registry.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/context/context-tag.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/context.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-identity.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-registry.ts +308 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug-timing.ts +120 -0
- package/node_modules/@mrclrchtr/supi-core/src/debug.ts +14 -0
- package/node_modules/@mrclrchtr/supi-core/src/evidence-badge.ts +41 -0
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +57 -0
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +34 -0
- package/node_modules/@mrclrchtr/supi-core/src/llm.ts +201 -0
- package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +44 -0
- package/node_modules/@mrclrchtr/supi-core/src/path.ts +2 -0
- package/node_modules/@mrclrchtr/supi-core/src/project-roots.ts +170 -0
- package/node_modules/@mrclrchtr/supi-core/src/project.ts +15 -0
- package/node_modules/@mrclrchtr/supi-core/src/prompt-surface.ts +4 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +93 -0
- package/node_modules/@mrclrchtr/supi-core/src/report.ts +121 -0
- package/node_modules/@mrclrchtr/supi-core/src/session-utils.ts +71 -0
- package/node_modules/@mrclrchtr/supi-core/src/session.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +105 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-schema.ts +453 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings.ts +36 -0
- package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/node_modules/@mrclrchtr/supi-core/src/terminal.ts +60 -0
- package/package.json +79 -0
- package/scripts/live-probe.ts +161 -0
- package/src/activity.ts +22 -0
- package/src/availability.ts +231 -0
- package/src/catalogue.ts +21 -0
- package/src/config.ts +26 -0
- package/src/conversation/handles.ts +183 -0
- package/src/extension.ts +22 -0
- package/src/isolated-home.ts +346 -0
- package/src/process/environment.ts +33 -0
- package/src/process/event-values.ts +279 -0
- package/src/process/events.ts +365 -0
- package/src/process/hooks.ts +219 -0
- package/src/process/ndjson.ts +120 -0
- package/src/process/protocol.ts +69 -0
- package/src/process/runner.ts +147 -0
- package/src/process/subprocess.ts +278 -0
- package/src/process/usage.ts +50 -0
- package/src/runtime.ts +118 -0
- package/src/settings.ts +38 -0
- package/src/structured-output.ts +58 -0
- package/src/tool/antigravity_run/evidence.ts +169 -0
- package/src/tool/antigravity_run/execute.ts +225 -0
- package/src/tool/antigravity_run/guidance.ts +3 -0
- package/src/tool/antigravity_run/input.ts +106 -0
- package/src/tool/antigravity_run/register.ts +36 -0
- package/src/tool/antigravity_run/render.ts +236 -0
- package/src/tool/antigravity_run/result.ts +186 -0
- package/src/tool/antigravity_run/spec.ts +20 -0
- package/src/types.ts +107 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, normalize as normalizePath, relative, resolve } from "node:path";
|
|
3
|
+
import { hashEvidence } from "../../process/event-values.ts";
|
|
4
|
+
import type {
|
|
5
|
+
AntigravityAnswer,
|
|
6
|
+
AntigravityExecutionFacts,
|
|
7
|
+
ClassifiedSource,
|
|
8
|
+
ClassifiedWorkspaceEvidence,
|
|
9
|
+
} from "../../types.ts";
|
|
10
|
+
|
|
11
|
+
/** Evidence split and safe warnings produced from one Antigravity answer. */
|
|
12
|
+
export interface ClassifiedEvidence {
|
|
13
|
+
observedSources: ClassifiedSource[];
|
|
14
|
+
claimedSources: ClassifiedSource[];
|
|
15
|
+
observedWorkspaceEvidence: ClassifiedWorkspaceEvidence[];
|
|
16
|
+
claimedWorkspaceEvidence: ClassifiedWorkspaceEvidence[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Validate references, classify observed facts, and omit unsafe references. */
|
|
21
|
+
export function classifyAnswerEvidence(
|
|
22
|
+
answer: AntigravityAnswer,
|
|
23
|
+
facts: Pick<AntigravityExecutionFacts, "observedSourceHashes" | "observedWorkspacePathHashes">,
|
|
24
|
+
workspaceDirectory: string,
|
|
25
|
+
): ClassifiedEvidence {
|
|
26
|
+
const sources = classifySources(answer.sources, new Set(facts.observedSourceHashes));
|
|
27
|
+
const workspace = classifyWorkspaceEvidence(
|
|
28
|
+
answer.workspaceEvidence,
|
|
29
|
+
new Set(facts.observedWorkspacePathHashes),
|
|
30
|
+
workspaceDirectory,
|
|
31
|
+
);
|
|
32
|
+
return {
|
|
33
|
+
observedSources: sources.observed,
|
|
34
|
+
claimedSources: sources.claimed,
|
|
35
|
+
observedWorkspaceEvidence: workspace.observed,
|
|
36
|
+
claimedWorkspaceEvidence: workspace.claimed,
|
|
37
|
+
warnings: [...new Set([...sources.warnings, ...workspace.warnings])],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ClassifiedSourceLists {
|
|
42
|
+
observed: ClassifiedSource[];
|
|
43
|
+
claimed: ClassifiedSource[];
|
|
44
|
+
warnings: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function classifySources(
|
|
48
|
+
sources: readonly AntigravityAnswer["sources"][number][],
|
|
49
|
+
observedHashes: ReadonlySet<string>,
|
|
50
|
+
): ClassifiedSourceLists {
|
|
51
|
+
const result: ClassifiedSourceLists = { observed: [], claimed: [], warnings: [] };
|
|
52
|
+
for (const source of sources) {
|
|
53
|
+
const url = source.url.trim();
|
|
54
|
+
if (!isHttpUrl(url) || !source.title.trim()) {
|
|
55
|
+
result.warnings.push("Some source references were omitted because they were invalid.");
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const classified: ClassifiedSource = {
|
|
59
|
+
title: source.title.trim(),
|
|
60
|
+
url,
|
|
61
|
+
evidence: observedHashes.has(hashEvidence(url)) ? "observed" : "claimed",
|
|
62
|
+
};
|
|
63
|
+
addClassified(result, classified);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function classifyWorkspaceEvidence(
|
|
69
|
+
evidence: readonly AntigravityAnswer["workspaceEvidence"][number][],
|
|
70
|
+
observedHashes: ReadonlySet<string>,
|
|
71
|
+
workspaceDirectory: string,
|
|
72
|
+
): {
|
|
73
|
+
observed: ClassifiedWorkspaceEvidence[];
|
|
74
|
+
claimed: ClassifiedWorkspaceEvidence[];
|
|
75
|
+
warnings: string[];
|
|
76
|
+
} {
|
|
77
|
+
const result = { observed: [], claimed: [], warnings: [] } as {
|
|
78
|
+
observed: ClassifiedWorkspaceEvidence[];
|
|
79
|
+
claimed: ClassifiedWorkspaceEvidence[];
|
|
80
|
+
warnings: string[];
|
|
81
|
+
};
|
|
82
|
+
for (const item of evidence) {
|
|
83
|
+
if (!isSafeWorkspacePath(item.path, workspaceDirectory) || !item.summary.trim()) {
|
|
84
|
+
result.warnings.push(
|
|
85
|
+
"Some workspace references were omitted because they were outside the selected workspace.",
|
|
86
|
+
);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const normalizedPath = normalizeWorkspacePath(item.path);
|
|
90
|
+
const classified: ClassifiedWorkspaceEvidence = {
|
|
91
|
+
path: normalizedPath,
|
|
92
|
+
summary: item.summary.trim(),
|
|
93
|
+
evidence: observedHashes.has(hashEvidence(normalizedPath)) ? "observed" : "claimed",
|
|
94
|
+
};
|
|
95
|
+
if (classified.evidence === "observed") result.observed.push(classified);
|
|
96
|
+
else result.claimed.push(classified);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function addClassified(result: ClassifiedSourceLists, source: ClassifiedSource): void {
|
|
102
|
+
if (source.evidence === "observed") result.observed.push(source);
|
|
103
|
+
else result.claimed.push(source);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Accept only HTTP and HTTPS source URLs. */
|
|
107
|
+
export function isHttpUrl(value: string): boolean {
|
|
108
|
+
if (hasControlCharacters(value)) return false;
|
|
109
|
+
try {
|
|
110
|
+
const url = new URL(value);
|
|
111
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Check that a returned path is relative and contained by a workspace. */
|
|
118
|
+
export function isSafeWorkspacePath(value: string, workspaceDirectory: string): boolean {
|
|
119
|
+
if (
|
|
120
|
+
!value ||
|
|
121
|
+
hasControlCharacters(value) ||
|
|
122
|
+
value.startsWith("~") ||
|
|
123
|
+
isAbsolute(value) ||
|
|
124
|
+
/^[A-Za-z]:[\\/]/.test(value)
|
|
125
|
+
) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const candidate = normalizeWorkspacePath(value);
|
|
129
|
+
if (candidate === "." || candidate === ".." || candidate.startsWith("../")) return false;
|
|
130
|
+
const resolvedWorkspace = safeRealpath(workspaceDirectory);
|
|
131
|
+
const resolvedCandidate = resolve(resolvedWorkspace, candidate);
|
|
132
|
+
if (!isContainedPath(resolvedWorkspace, resolvedCandidate)) return false;
|
|
133
|
+
const existingAncestor = findExistingAncestor(resolvedCandidate);
|
|
134
|
+
return isContainedPath(resolvedWorkspace, safeRealpath(existingAncestor));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function hasControlCharacters(value: string): boolean {
|
|
138
|
+
return [...value].some((character) => {
|
|
139
|
+
const code = character.charCodeAt(0);
|
|
140
|
+
return code < 32 || code === 127;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeWorkspacePath(value: string): string {
|
|
145
|
+
return normalizePath(value.replaceAll("\\", "/")).replace(/^\.\//, "");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function safeRealpath(value: string): string {
|
|
149
|
+
try {
|
|
150
|
+
return realpathSync(value);
|
|
151
|
+
} catch {
|
|
152
|
+
return resolve(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function findExistingAncestor(value: string): string {
|
|
157
|
+
let candidate = value;
|
|
158
|
+
while (!existsSync(candidate)) {
|
|
159
|
+
const parent = dirname(candidate);
|
|
160
|
+
if (parent === candidate) return value;
|
|
161
|
+
candidate = parent;
|
|
162
|
+
}
|
|
163
|
+
return candidate;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isContainedPath(workspace: string, candidate: string): boolean {
|
|
167
|
+
const distance = relative(workspace, candidate);
|
|
168
|
+
return distance !== ".." && !distance.startsWith("../") && !isAbsolute(distance);
|
|
169
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import type {
|
|
3
|
+
AgentToolUpdateCallback,
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import {
|
|
8
|
+
appendHandleCreated,
|
|
9
|
+
appendHandleRetired,
|
|
10
|
+
type ConversationHandleStore,
|
|
11
|
+
} from "../../conversation/handles.ts";
|
|
12
|
+
import {
|
|
13
|
+
type IsolatedAntigravityPaths,
|
|
14
|
+
prepareIsolatedAntigravityHome,
|
|
15
|
+
} from "../../isolated-home.ts";
|
|
16
|
+
import { probeProjectHooks } from "../../process/hooks.ts";
|
|
17
|
+
import { AntigravityProcessError, runAntigravityConversation } from "../../process/runner.ts";
|
|
18
|
+
import { ANTIGRAVITY_ANSWER_SCHEMA } from "../../structured-output.ts";
|
|
19
|
+
import type { CuratedModel } from "../../types.ts";
|
|
20
|
+
import {
|
|
21
|
+
type AntigravityRunInput,
|
|
22
|
+
type ContinueAntigravityInput,
|
|
23
|
+
parseAntigravityRunInput,
|
|
24
|
+
} from "./input.ts";
|
|
25
|
+
import { buildAntigravityResult } from "./result.ts";
|
|
26
|
+
|
|
27
|
+
/** Dependencies captured by one dynamically registered Antigravity tool. */
|
|
28
|
+
export interface AntigravityRunDependencies {
|
|
29
|
+
pi: ExtensionAPI;
|
|
30
|
+
paths: IsolatedAntigravityPaths;
|
|
31
|
+
catalogue: readonly CuratedModel[];
|
|
32
|
+
cliVersion: string;
|
|
33
|
+
handles: ConversationHandleStore;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type AntigravityExecute = NonNullable<Parameters<ExtensionAPI["registerTool"]>[0]["execute"]>;
|
|
37
|
+
|
|
38
|
+
/** Build the execute function for one immutable availability snapshot. */
|
|
39
|
+
export function makeAntigravityRunExecute(
|
|
40
|
+
dependencies: AntigravityRunDependencies,
|
|
41
|
+
): AntigravityExecute {
|
|
42
|
+
// biome-ignore lint/complexity/useMaxParams: Pi execute contract requires five parameters.
|
|
43
|
+
return async (_toolCallId, params, signal, onUpdate, ctx) => {
|
|
44
|
+
const input = parseAntigravityRunInput(params, dependencies.catalogue);
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
if (input.new) {
|
|
47
|
+
return runNew({ input, dependencies, signal, onUpdate, ctx, startedAt });
|
|
48
|
+
}
|
|
49
|
+
if (!input.continue)
|
|
50
|
+
throw new Error("antigravity_run requires exactly one of new or continue.");
|
|
51
|
+
return runContinue({
|
|
52
|
+
continuation: input.continue,
|
|
53
|
+
prompt: input.prompt,
|
|
54
|
+
dependencies,
|
|
55
|
+
signal,
|
|
56
|
+
onUpdate,
|
|
57
|
+
ctx,
|
|
58
|
+
startedAt,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface CommonRunOptions {
|
|
64
|
+
dependencies: AntigravityRunDependencies;
|
|
65
|
+
signal: AbortSignal | undefined;
|
|
66
|
+
onUpdate: AgentToolUpdateCallback<unknown> | undefined;
|
|
67
|
+
ctx: ExtensionContext;
|
|
68
|
+
startedAt: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface NewRunOptions extends CommonRunOptions {
|
|
72
|
+
input: Extract<AntigravityRunInput, { new: object }>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function runNew(options: NewRunOptions): Promise<ReturnType<typeof buildAntigravityResult>> {
|
|
76
|
+
const { input, dependencies, signal, onUpdate, ctx, startedAt } = options;
|
|
77
|
+
const workspaceAccess = input.new.workspace;
|
|
78
|
+
await prepareIsolatedAntigravityHome(dependencies.paths);
|
|
79
|
+
const cwd = await resolveWorkingDirectory(workspaceAccess, ctx.cwd, dependencies.paths);
|
|
80
|
+
const hookWarning = await prepareWorkspaceRun({
|
|
81
|
+
workspaceAccess,
|
|
82
|
+
cwd,
|
|
83
|
+
paths: dependencies.paths,
|
|
84
|
+
signal,
|
|
85
|
+
ctx,
|
|
86
|
+
});
|
|
87
|
+
const progress = createProgressReporter(onUpdate, input.new.model, workspaceAccess);
|
|
88
|
+
progress("starting Antigravity");
|
|
89
|
+
|
|
90
|
+
const facts = await runAntigravityConversation({
|
|
91
|
+
paths: dependencies.paths,
|
|
92
|
+
cwd,
|
|
93
|
+
prompt: input.prompt,
|
|
94
|
+
model: input.new.model,
|
|
95
|
+
workspaceDirectory: workspaceAccess ? cwd : undefined,
|
|
96
|
+
schema: ANTIGRAVITY_ANSWER_SCHEMA as Record<string, unknown>,
|
|
97
|
+
signal,
|
|
98
|
+
onActivity: progress,
|
|
99
|
+
});
|
|
100
|
+
const record = dependencies.handles.create({
|
|
101
|
+
rawAntigravityId: facts.conversationId,
|
|
102
|
+
model: input.new.model,
|
|
103
|
+
canonicalWorkingDirectory: cwd,
|
|
104
|
+
workspaceAccess,
|
|
105
|
+
cliVersion: dependencies.cliVersion,
|
|
106
|
+
});
|
|
107
|
+
appendHandleCreated(dependencies.pi, record);
|
|
108
|
+
return buildAntigravityResult({
|
|
109
|
+
facts,
|
|
110
|
+
handle: record,
|
|
111
|
+
durationMs: Date.now() - startedAt,
|
|
112
|
+
...(hookWarning ? { hookWarning } : {}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface ContinueRunOptions extends CommonRunOptions {
|
|
117
|
+
continuation: ContinueAntigravityInput;
|
|
118
|
+
prompt: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function runContinue(
|
|
122
|
+
options: ContinueRunOptions,
|
|
123
|
+
): Promise<ReturnType<typeof buildAntigravityResult>> {
|
|
124
|
+
const { continuation, prompt, dependencies, signal, onUpdate, ctx, startedAt } = options;
|
|
125
|
+
const record = dependencies.handles.acquire(continuation.handle);
|
|
126
|
+
let processStarted = false;
|
|
127
|
+
try {
|
|
128
|
+
const cwd = record.canonicalWorkingDirectory;
|
|
129
|
+
const hookWarning = await prepareWorkspaceRun({
|
|
130
|
+
workspaceAccess: record.workspaceAccess,
|
|
131
|
+
cwd,
|
|
132
|
+
paths: dependencies.paths,
|
|
133
|
+
signal,
|
|
134
|
+
ctx,
|
|
135
|
+
onProcessStart: () => {
|
|
136
|
+
processStarted = true;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
const progress = createProgressReporter(onUpdate, record.model, record.workspaceAccess);
|
|
140
|
+
await prepareIsolatedAntigravityHome(dependencies.paths);
|
|
141
|
+
progress("starting Antigravity follow-up");
|
|
142
|
+
const facts = await runAntigravityConversation({
|
|
143
|
+
paths: dependencies.paths,
|
|
144
|
+
cwd,
|
|
145
|
+
prompt,
|
|
146
|
+
model: record.model,
|
|
147
|
+
conversationId: record.rawAntigravityId,
|
|
148
|
+
workspaceDirectory: record.workspaceAccess ? cwd : undefined,
|
|
149
|
+
schema: ANTIGRAVITY_ANSWER_SCHEMA as Record<string, unknown>,
|
|
150
|
+
signal,
|
|
151
|
+
onActivity: progress,
|
|
152
|
+
onProcessStart: () => {
|
|
153
|
+
processStarted = true;
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
if (facts.conversationId !== record.rawAntigravityId) {
|
|
157
|
+
throw new Error("Antigravity returned a different conversation for this handle.");
|
|
158
|
+
}
|
|
159
|
+
return buildAntigravityResult({
|
|
160
|
+
facts,
|
|
161
|
+
handle: record,
|
|
162
|
+
durationMs: Date.now() - startedAt,
|
|
163
|
+
...(hookWarning ? { hookWarning } : {}),
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (processStarted) {
|
|
167
|
+
const retired = dependencies.handles.retire(record.handle);
|
|
168
|
+
if (retired) appendHandleRetired(dependencies.pi, retired, errorMessage(error));
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
} finally {
|
|
172
|
+
dependencies.handles.release(record.handle);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function resolveWorkingDirectory(
|
|
177
|
+
workspaceAccess: boolean,
|
|
178
|
+
currentDirectory: string,
|
|
179
|
+
paths: IsolatedAntigravityPaths,
|
|
180
|
+
): Promise<string> {
|
|
181
|
+
return realpath(workspaceAccess ? currentDirectory : paths.consultationWorkspace);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function prepareWorkspaceRun(options: {
|
|
185
|
+
workspaceAccess: boolean;
|
|
186
|
+
cwd: string;
|
|
187
|
+
paths: IsolatedAntigravityPaths;
|
|
188
|
+
signal: AbortSignal | undefined;
|
|
189
|
+
ctx: ExtensionContext;
|
|
190
|
+
onProcessStart?: () => void;
|
|
191
|
+
}): Promise<string | undefined> {
|
|
192
|
+
if (!options.workspaceAccess) return undefined;
|
|
193
|
+
const hookProbe = await probeProjectHooks({
|
|
194
|
+
paths: options.paths,
|
|
195
|
+
cwd: options.cwd,
|
|
196
|
+
signal: options.signal,
|
|
197
|
+
onProcessStart: options.onProcessStart,
|
|
198
|
+
});
|
|
199
|
+
if (!hookProbe.warning) return undefined;
|
|
200
|
+
options.ctx.ui.notify(hookProbe.warning, "warning");
|
|
201
|
+
return hookProbe.warning;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createProgressReporter(
|
|
205
|
+
onUpdate: AgentToolUpdateCallback<unknown> | undefined,
|
|
206
|
+
model: CuratedModel,
|
|
207
|
+
workspaceAccess: boolean,
|
|
208
|
+
): (activity: string) => void {
|
|
209
|
+
return (activity: string) => {
|
|
210
|
+
onUpdate?.({
|
|
211
|
+
content: [{ type: "text", text: "Antigravity is working…" }],
|
|
212
|
+
details: {
|
|
213
|
+
model,
|
|
214
|
+
workingDirectoryKind: workspaceAccess ? "workspace" : "consultation",
|
|
215
|
+
workspaceAccess,
|
|
216
|
+
latestActivity: activity,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function errorMessage(error: unknown): string {
|
|
223
|
+
if (error instanceof AntigravityProcessError) return `Antigravity ${error.kind} failure`;
|
|
224
|
+
return "Antigravity follow-up failed";
|
|
225
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { type TSchema, Type } from "typebox";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
|
+
import { buildModelCatalogueEnum, isAvailableCuratedModel } from "../../catalogue.ts";
|
|
4
|
+
import type { CuratedModel } from "../../types.ts";
|
|
5
|
+
|
|
6
|
+
/** Maximum prompt length accepted by one Antigravity Run. */
|
|
7
|
+
const MAX_PROMPT_CHARS = 32_000;
|
|
8
|
+
/** Maximum opaque handle length accepted from a follow-up. */
|
|
9
|
+
const MAX_HANDLE_CHARS = 128;
|
|
10
|
+
|
|
11
|
+
/** Input for a new Antigravity Run. */
|
|
12
|
+
export interface NewAntigravityInput {
|
|
13
|
+
workspace: boolean;
|
|
14
|
+
model: CuratedModel;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Input for a Conversation Handle follow-up. */
|
|
18
|
+
export interface ContinueAntigravityInput {
|
|
19
|
+
handle: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The exact public antigravity_run input contract. */
|
|
23
|
+
export type AntigravityRunInput =
|
|
24
|
+
| { prompt: string; new: NewAntigravityInput; continue?: never }
|
|
25
|
+
| { prompt: string; new?: never; continue: ContinueAntigravityInput };
|
|
26
|
+
|
|
27
|
+
/** Build the dynamic tool schema from the immutable Model Catalogue. */
|
|
28
|
+
export function buildAntigravityRunSchema(catalogue: readonly CuratedModel[]): TSchema {
|
|
29
|
+
if (catalogue.length === 0) {
|
|
30
|
+
throw new Error("Cannot register antigravity_run without an available model.");
|
|
31
|
+
}
|
|
32
|
+
const prompt = Type.String({
|
|
33
|
+
minLength: 1,
|
|
34
|
+
maxLength: MAX_PROMPT_CHARS,
|
|
35
|
+
description: "The bounded request to send to Antigravity.",
|
|
36
|
+
});
|
|
37
|
+
const newInput = Type.Object(
|
|
38
|
+
{
|
|
39
|
+
prompt,
|
|
40
|
+
new: Type.Object(
|
|
41
|
+
{
|
|
42
|
+
workspace: Type.Boolean({
|
|
43
|
+
description:
|
|
44
|
+
"Expose the current PI workspace, or use the empty Consultation Workspace.",
|
|
45
|
+
}),
|
|
46
|
+
model: buildModelCatalogueEnum(catalogue),
|
|
47
|
+
},
|
|
48
|
+
{ additionalProperties: false },
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
{ additionalProperties: false },
|
|
52
|
+
);
|
|
53
|
+
const continueInput = Type.Object(
|
|
54
|
+
{
|
|
55
|
+
prompt,
|
|
56
|
+
continue: Type.Object(
|
|
57
|
+
{
|
|
58
|
+
handle: Type.String({ minLength: 1, maxLength: MAX_HANDLE_CHARS }),
|
|
59
|
+
},
|
|
60
|
+
{ additionalProperties: false },
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
{ additionalProperties: false },
|
|
64
|
+
);
|
|
65
|
+
return Type.Union([newInput, continueInput]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Validate exact-one input and the model against the same catalogue. */
|
|
69
|
+
export function parseAntigravityRunInput(
|
|
70
|
+
value: unknown,
|
|
71
|
+
catalogue: readonly CuratedModel[],
|
|
72
|
+
): AntigravityRunInput {
|
|
73
|
+
if (!isRecord(value)) throw new Error("Invalid antigravity_run input.");
|
|
74
|
+
const hasNew = value.new !== undefined;
|
|
75
|
+
const hasContinue = value.continue !== undefined;
|
|
76
|
+
if (hasNew === hasContinue) {
|
|
77
|
+
throw new Error("antigravity_run requires exactly one of new or continue.");
|
|
78
|
+
}
|
|
79
|
+
const schema = buildAntigravityRunSchema(catalogue);
|
|
80
|
+
if (!Value.Check(schema, value)) throw new Error("Invalid antigravity_run input.");
|
|
81
|
+
const newInput = isRecord(value.new) ? value.new : undefined;
|
|
82
|
+
const continueInput = isRecord(value.continue) ? value.continue : undefined;
|
|
83
|
+
if (newInput) {
|
|
84
|
+
if (
|
|
85
|
+
typeof newInput.workspace !== "boolean" ||
|
|
86
|
+
!isAvailableCuratedModel(newInput.model, catalogue)
|
|
87
|
+
) {
|
|
88
|
+
throw new Error("The selected Antigravity model is not available in the Model Catalogue.");
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
prompt: value.prompt as string,
|
|
92
|
+
new: { workspace: newInput.workspace, model: newInput.model },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!continueInput || typeof continueInput.handle !== "string") {
|
|
96
|
+
throw new Error("Invalid Antigravity Conversation Handle.");
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
prompt: value.prompt as string,
|
|
100
|
+
continue: { handle: continueInput.handle },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
106
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { ConversationHandleStore } from "../../conversation/handles.ts";
|
|
3
|
+
import type { IsolatedAntigravityPaths } from "../../isolated-home.ts";
|
|
4
|
+
import type { CuratedModel } from "../../types.ts";
|
|
5
|
+
import { makeAntigravityRunExecute } from "./execute.ts";
|
|
6
|
+
import { toolDescription } from "./guidance.ts";
|
|
7
|
+
import { renderAntigravityCall, renderAntigravityResult } from "./render.ts";
|
|
8
|
+
import { antigravityRunSpec, buildAntigravityRunParameters } from "./spec.ts";
|
|
9
|
+
|
|
10
|
+
/** Dependencies needed by the dynamically registered tool. */
|
|
11
|
+
export interface RegisterAntigravityRunOptions {
|
|
12
|
+
pi: ExtensionAPI;
|
|
13
|
+
paths: IsolatedAntigravityPaths;
|
|
14
|
+
catalogue: readonly CuratedModel[];
|
|
15
|
+
cliVersion: string;
|
|
16
|
+
handles: ConversationHandleStore;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Register antigravity_run from one immutable Model Catalogue. */
|
|
20
|
+
export function registerAntigravityRunTool(options: RegisterAntigravityRunOptions): void {
|
|
21
|
+
const parameters = buildAntigravityRunParameters(options.catalogue);
|
|
22
|
+
options.pi.registerTool({
|
|
23
|
+
...antigravityRunSpec,
|
|
24
|
+
description: toolDescription,
|
|
25
|
+
parameters,
|
|
26
|
+
renderCall: renderAntigravityCall,
|
|
27
|
+
renderResult: renderAntigravityResult,
|
|
28
|
+
execute: makeAntigravityRunExecute({
|
|
29
|
+
pi: options.pi,
|
|
30
|
+
paths: options.paths,
|
|
31
|
+
catalogue: options.catalogue,
|
|
32
|
+
cliVersion: options.cliVersion,
|
|
33
|
+
handles: options.handles,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
}
|