@prismatic-io/lux 0.0.2-preview.27 → 0.0.2-preview.28
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/lib/authoring.d.ts +1 -0
- package/lib/authoring.d.ts.map +1 -1
- package/lib/authoring.js +1 -0
- package/lib/authoring.js.map +1 -1
- package/lib/container/driver.d.ts +27 -0
- package/lib/container/driver.d.ts.map +1 -0
- package/lib/container/driver.js +197 -0
- package/lib/container/driver.js.map +1 -0
- package/lib/container/protocol.d.ts +12 -0
- package/lib/container/protocol.d.ts.map +1 -0
- package/lib/container/protocol.js +38 -0
- package/lib/container/protocol.js.map +1 -0
- package/lib/container/resources.d.ts +12 -0
- package/lib/container/resources.d.ts.map +1 -0
- package/lib/container/resources.js +133 -0
- package/lib/container/resources.js.map +1 -0
- package/lib/container/worker.d.ts +2 -0
- package/lib/container/worker.d.ts.map +1 -0
- package/lib/container/worker.js +85 -0
- package/lib/container/worker.js.map +1 -0
- package/lib/core/case.d.ts +80 -0
- package/lib/core/case.d.ts.map +1 -1
- package/lib/core/case.js +8 -1
- package/lib/core/case.js.map +1 -1
- package/lib/core/driver-execution.d.ts +83 -0
- package/lib/core/driver-execution.d.ts.map +1 -0
- package/lib/core/driver-execution.js +72 -0
- package/lib/core/driver-execution.js.map +1 -0
- package/lib/core/driver.d.ts +5 -1
- package/lib/core/driver.d.ts.map +1 -1
- package/lib/core/driver.js +5 -1
- package/lib/core/driver.js.map +1 -1
- package/lib/core/harness-matrix.d.ts +84 -0
- package/lib/core/harness-matrix.d.ts.map +1 -1
- package/lib/core/harness-matrix.js +11 -1
- package/lib/core/harness-matrix.js.map +1 -1
- package/lib/core/run.d.ts +40 -0
- package/lib/core/run.d.ts.map +1 -1
- package/lib/drivers/antigravity/artifacts.d.ts +2 -0
- package/lib/drivers/antigravity/artifacts.d.ts.map +1 -0
- package/lib/drivers/antigravity/artifacts.js +33 -0
- package/lib/drivers/antigravity/artifacts.js.map +1 -0
- package/lib/drivers/antigravity/config.d.ts +27 -0
- package/lib/drivers/antigravity/config.d.ts.map +1 -1
- package/lib/drivers/antigravity/config.js +35 -0
- package/lib/drivers/antigravity/config.js.map +1 -1
- package/lib/drivers/antigravity/index.d.ts +35 -0
- package/lib/drivers/antigravity/index.d.ts.map +1 -1
- package/lib/drivers/antigravity/index.js +42 -20
- package/lib/drivers/antigravity/index.js.map +1 -1
- package/lib/drivers/antigravity/runtime.d.ts +7 -0
- package/lib/drivers/antigravity/runtime.d.ts.map +1 -0
- package/lib/drivers/antigravity/runtime.js +78 -0
- package/lib/drivers/antigravity/runtime.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/orchestrator/config.d.ts +122 -1
- package/lib/orchestrator/config.d.ts.map +1 -1
- package/lib/orchestrator/config.js +2 -1
- package/lib/orchestrator/config.js.map +1 -1
- package/lib/orchestrator/doctor.d.ts.map +1 -1
- package/lib/orchestrator/doctor.js +3 -1
- package/lib/orchestrator/doctor.js.map +1 -1
- package/lib/orchestrator/driver-capabilities.d.ts +2 -1
- package/lib/orchestrator/driver-capabilities.d.ts.map +1 -1
- package/lib/orchestrator/driver-capabilities.js +5 -2
- package/lib/orchestrator/driver-capabilities.js.map +1 -1
- package/lib/orchestrator/driver-identity.d.ts.map +1 -1
- package/lib/orchestrator/driver-identity.js +11 -1
- package/lib/orchestrator/driver-identity.js.map +1 -1
- package/lib/orchestrator/experiment-context.d.ts +34 -0
- package/lib/orchestrator/experiment-context.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.d.ts.map +1 -1
- package/lib/orchestrator/orchestrator.js +16 -3
- package/lib/orchestrator/orchestrator.js.map +1 -1
- package/package.json +1 -1
- package/skills/lux-answerer/SKILL.md +1 -1
- package/src/authoring.ts +6 -0
- package/src/container/driver.ts +211 -0
- package/src/container/protocol.ts +48 -0
- package/src/container/resources.ts +153 -0
- package/src/container/worker.ts +107 -0
- package/src/core/case.ts +8 -1
- package/src/core/driver-execution.ts +84 -0
- package/src/core/driver.ts +9 -1
- package/src/core/harness-matrix.ts +19 -1
- package/src/drivers/antigravity/README.md +108 -10
- package/src/drivers/antigravity/artifacts.ts +37 -0
- package/src/drivers/antigravity/config.ts +35 -0
- package/src/drivers/antigravity/index.ts +41 -22
- package/src/drivers/antigravity/runtime.ts +88 -0
- package/src/index.ts +6 -0
- package/src/orchestrator/config.ts +4 -1
- package/src/orchestrator/doctor.ts +3 -1
- package/src/orchestrator/driver-capabilities.ts +6 -1
- package/src/orchestrator/driver-identity.ts +11 -1
- package/src/orchestrator/orchestrator.ts +19 -3
package/src/core/case.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import type { AssertionPlugin } from "./assertion.js";
|
|
3
3
|
import { collectEvalCaseExtensions, setEvalCaseExtensions } from "./case-authoring-state.js";
|
|
4
4
|
import { DriverCapabilitySchema } from "./driver.js";
|
|
5
|
+
import { DriverExecutionSchema } from "./driver-execution.js";
|
|
5
6
|
import { EvaluationMetadataSchema } from "./evaluation-clusters.js";
|
|
6
7
|
import { HarnessMatrixSchema } from "./harness-matrix.js";
|
|
7
8
|
import type { LifecycleHooksPlugin } from "./lifecycle-hooks.js";
|
|
@@ -20,7 +21,13 @@ export type FixturesRef = z.infer<typeof FixturesRefSchema>;
|
|
|
20
21
|
// `defineEvalCase` discriminate the config type on `name`.
|
|
21
22
|
export const DriverRefSchema = z.union([
|
|
22
23
|
z.string(),
|
|
23
|
-
z
|
|
24
|
+
z
|
|
25
|
+
.object({
|
|
26
|
+
name: z.string(),
|
|
27
|
+
config: z.unknown().optional(),
|
|
28
|
+
execution: DriverExecutionSchema.optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict(),
|
|
24
31
|
]);
|
|
25
32
|
export type DriverRef = z.infer<typeof DriverRefSchema>;
|
|
26
33
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const homePath = z
|
|
4
|
+
.string()
|
|
5
|
+
.min(1)
|
|
6
|
+
.refine(
|
|
7
|
+
(value) =>
|
|
8
|
+
!value.startsWith("/") &&
|
|
9
|
+
!value.includes("\\") &&
|
|
10
|
+
value.split("/").every((part) => part !== ".." && part !== "." && part !== ""),
|
|
11
|
+
"must be a relative path within the container home",
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export const ContainerExecutionSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
kind: z.literal("container"),
|
|
17
|
+
/** A locally available image containing Lux and the selected Linux harness. */
|
|
18
|
+
image: z.string().min(1),
|
|
19
|
+
command: z.string().min(1).optional(),
|
|
20
|
+
/** The worker shipped with the same Lux build as the host. */
|
|
21
|
+
worker: z.string().min(1).optional(),
|
|
22
|
+
/** Optional module in the image exporting a custom DriverPlugin as default. */
|
|
23
|
+
driverModule: z.string().min(1).optional(),
|
|
24
|
+
mounts: z
|
|
25
|
+
.array(
|
|
26
|
+
z
|
|
27
|
+
.object({
|
|
28
|
+
source: z.string().min(1),
|
|
29
|
+
target: z.string().min(1).optional(),
|
|
30
|
+
readOnly: z.boolean().optional(),
|
|
31
|
+
})
|
|
32
|
+
.strict(),
|
|
33
|
+
)
|
|
34
|
+
.optional(),
|
|
35
|
+
/** Only these host environment variables enter the container. */
|
|
36
|
+
passEnv: z.array(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/)).optional(),
|
|
37
|
+
env: z.record(z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), z.string()).optional(),
|
|
38
|
+
/** Private copies, never host-home mounts; deleted after the run. */
|
|
39
|
+
homeFiles: z
|
|
40
|
+
.array(
|
|
41
|
+
z
|
|
42
|
+
.object({
|
|
43
|
+
destination: homePath,
|
|
44
|
+
source: z.discriminatedUnion("kind", [
|
|
45
|
+
z.object({ kind: z.literal("file"), path: z.string().min(1) }).strict(),
|
|
46
|
+
z
|
|
47
|
+
.object({
|
|
48
|
+
kind: z.literal("keychain"),
|
|
49
|
+
service: z.string().min(1),
|
|
50
|
+
account: z.string().min(1),
|
|
51
|
+
encoding: z.enum(["utf8", "go-keyring-base64"]).optional(),
|
|
52
|
+
})
|
|
53
|
+
.strict(),
|
|
54
|
+
]),
|
|
55
|
+
})
|
|
56
|
+
.strict(),
|
|
57
|
+
)
|
|
58
|
+
.max(32)
|
|
59
|
+
.optional(),
|
|
60
|
+
network: z.enum(["bridge", "none"]).optional(),
|
|
61
|
+
user: z.string().min(1).optional(),
|
|
62
|
+
memory: z
|
|
63
|
+
.string()
|
|
64
|
+
.regex(/^[1-9][0-9]*[bkmgt]?$/i)
|
|
65
|
+
.optional(),
|
|
66
|
+
cpus: z.number().positive().optional(),
|
|
67
|
+
pidsLimit: z.number().int().positive().optional(),
|
|
68
|
+
startupTimeoutMs: z.number().int().positive().optional(),
|
|
69
|
+
})
|
|
70
|
+
.strict();
|
|
71
|
+
|
|
72
|
+
export const DriverExecutionSchema = z.discriminatedUnion("kind", [
|
|
73
|
+
z.object({ kind: z.literal("host") }).strict(),
|
|
74
|
+
ContainerExecutionSchema,
|
|
75
|
+
]);
|
|
76
|
+
export type DriverExecution = z.infer<typeof DriverExecutionSchema>;
|
|
77
|
+
export type ContainerExecution = z.infer<typeof ContainerExecutionSchema>;
|
|
78
|
+
|
|
79
|
+
export const driverExecutionOf = (reference: unknown): DriverExecution | undefined => {
|
|
80
|
+
if (!reference || typeof reference !== "object" || !("execution" in reference)) return undefined;
|
|
81
|
+
return reference.execution === undefined
|
|
82
|
+
? undefined
|
|
83
|
+
: DriverExecutionSchema.parse(reference.execution);
|
|
84
|
+
};
|
package/src/core/driver.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { DriverExecution } from "./driver-execution.js";
|
|
2
3
|
import type { EventHandler } from "./emitter.js";
|
|
3
4
|
import type { Answer, Done, ErrorEvent, Interrupt, Progress, ReadyState } from "./events.js";
|
|
4
5
|
import type { ReasoningEffort } from "./harness-catalog.js";
|
|
@@ -28,6 +29,8 @@ export type ResolvedFixtures = z.infer<typeof ResolvedFixturesSchema>;
|
|
|
28
29
|
export type StartContext = {
|
|
29
30
|
/** Stable selected case id, available to drivers that multiplex scenarios. */
|
|
30
31
|
caseId?: string;
|
|
32
|
+
/** Private home owned by an outer execution boundary; never the caller’s home. */
|
|
33
|
+
isolatedHome?: string;
|
|
31
34
|
prompt: string;
|
|
32
35
|
fixtures?: ResolvedFixtures | undefined;
|
|
33
36
|
/** Authored fixture values selected for this case. */
|
|
@@ -139,4 +142,9 @@ export const defineDriver = <S extends z.ZodType>(
|
|
|
139
142
|
export const driverRef = <S extends z.ZodType>(
|
|
140
143
|
plugin: { name: string; configSchema: S },
|
|
141
144
|
config: z.input<S>,
|
|
142
|
-
|
|
145
|
+
execution?: DriverExecution,
|
|
146
|
+
): { name: string; config: z.input<S>; execution?: DriverExecution } => ({
|
|
147
|
+
name: plugin.name,
|
|
148
|
+
config,
|
|
149
|
+
...(execution ? { execution } : {}),
|
|
150
|
+
});
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { DriverRef } from "./case.js";
|
|
3
3
|
import type { DriverPlugin } from "./driver.js";
|
|
4
|
+
import {
|
|
5
|
+
type DriverExecution,
|
|
6
|
+
DriverExecutionSchema,
|
|
7
|
+
driverExecutionOf,
|
|
8
|
+
} from "./driver-execution.js";
|
|
4
9
|
import {
|
|
5
10
|
type CatalogModels,
|
|
6
11
|
catalogEfforts,
|
|
@@ -24,6 +29,7 @@ type CatalogEntry<C extends HarnessCatalog> = {
|
|
|
24
29
|
models: C["models"] extends CatalogModels ? (keyof C["models"] & string)[] : string[];
|
|
25
30
|
efforts?: C["efforts"][number][];
|
|
26
31
|
config?: unknown;
|
|
32
|
+
execution?: DriverExecution;
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
export type BuiltInHarnessMatrixEntry = {
|
|
@@ -37,6 +43,7 @@ export type CustomHarnessMatrixEntry = {
|
|
|
37
43
|
models?: string[];
|
|
38
44
|
efforts?: ReasoningEffort[];
|
|
39
45
|
config?: unknown;
|
|
46
|
+
execution?: DriverExecution;
|
|
40
47
|
};
|
|
41
48
|
|
|
42
49
|
export type HarnessMatrixEntry = BuiltInHarnessMatrixEntry | CustomHarnessMatrixEntry;
|
|
@@ -54,6 +61,7 @@ const catalogEntrySchema = (catalog: HarnessCatalog) => {
|
|
|
54
61
|
models: z.array(models).min(1),
|
|
55
62
|
efforts: z.array(nonEmptyEnum(catalog.efforts)).min(1).optional(),
|
|
56
63
|
config: z.unknown().optional(),
|
|
64
|
+
execution: DriverExecutionSchema.optional(),
|
|
57
65
|
})
|
|
58
66
|
.strict()
|
|
59
67
|
.superRefine((entry, ctx) => {
|
|
@@ -86,6 +94,7 @@ const CustomHarnessMatrixEntrySchema = z
|
|
|
86
94
|
models: z.array(z.string().min(1)).min(1).optional(),
|
|
87
95
|
efforts: z.array(ReasoningEffortSchema).min(1).optional(),
|
|
88
96
|
config: z.unknown().optional(),
|
|
97
|
+
execution: DriverExecutionSchema.optional(),
|
|
89
98
|
})
|
|
90
99
|
.strict();
|
|
91
100
|
|
|
@@ -109,6 +118,7 @@ export type HarnessPermutation = {
|
|
|
109
118
|
model?: string | undefined;
|
|
110
119
|
effort?: ReasoningEffort | undefined;
|
|
111
120
|
config?: unknown;
|
|
121
|
+
execution?: DriverExecution;
|
|
112
122
|
};
|
|
113
123
|
|
|
114
124
|
export const permutationId = (permutation: HarnessPermutation): string =>
|
|
@@ -125,6 +135,7 @@ const entryPermutation = (
|
|
|
125
135
|
...(model !== undefined ? { model } : {}),
|
|
126
136
|
...(effort !== undefined ? { effort } : {}),
|
|
127
137
|
...(entry.config !== undefined ? { config: entry.config } : {}),
|
|
138
|
+
...(entry.execution !== undefined ? { execution: entry.execution } : {}),
|
|
128
139
|
});
|
|
129
140
|
|
|
130
141
|
const expandEntry = (entry: HarnessMatrixEntry): HarnessPermutation[] => {
|
|
@@ -294,5 +305,12 @@ export const permutationDriverRef = (
|
|
|
294
305
|
}
|
|
295
306
|
config = plugin.selectHarness(config, { model: selected.model, effort: selected.effort });
|
|
296
307
|
}
|
|
297
|
-
|
|
308
|
+
const execution =
|
|
309
|
+
selected.execution ??
|
|
310
|
+
(fallback && driverNameOf(fallback) === selected.driver
|
|
311
|
+
? driverExecutionOf(fallback)
|
|
312
|
+
: undefined);
|
|
313
|
+
return config === undefined && execution === undefined
|
|
314
|
+
? selected.driver
|
|
315
|
+
: { name: selected.driver, config, ...(execution ? { execution } : {}) };
|
|
298
316
|
};
|
|
@@ -32,7 +32,7 @@ response is also retained as message evidence when it was not already streamed.
|
|
|
32
32
|
Both `DONE` and `ERROR` tool steps close the corresponding tool call; failed
|
|
33
33
|
steps retain their error message. A result with `denied_actions` is an error even
|
|
34
34
|
when Antigravity reports `SUCCESS`. `WAITING` and other non-success statuses are
|
|
35
|
-
also errors. Configure the required scoped permissions
|
|
35
|
+
also errors. Configure the required scoped permissions for the
|
|
36
36
|
headless evaluation; Lux does not retry a denial with broader permissions.
|
|
37
37
|
|
|
38
38
|
Lux explicitly selects `mode: "accept-edits"` for coding tasks. Set `mode: "plan"`
|
|
@@ -41,14 +41,61 @@ workspaces read-only even when native file tools can edit them and scoped write
|
|
|
41
41
|
grants exist. `accept-edits` permits workspace writes within the terminal sandbox;
|
|
42
42
|
it does not disable that sandbox or authorize outside-sandbox execution.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
## Run-scoped configuration
|
|
45
|
+
|
|
46
|
+
Lux can own the CLI settings for a run:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
{
|
|
50
|
+
name: "antigravity",
|
|
51
|
+
config: {
|
|
52
|
+
model: "gemini-3.1-pro",
|
|
53
|
+
reasoningEffort: "low",
|
|
54
|
+
sandbox: true,
|
|
55
|
+
permissionMode: "proceed-in-sandbox",
|
|
56
|
+
permissions: { allow: ["read_url(registry.npmjs.org)"] },
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This mode gives only the child process a private home directory. Lux writes
|
|
62
|
+
`enableTerminalSandbox: true`, `toolPermission: "proceed-in-sandbox"`, and the
|
|
63
|
+
configured `permissions` there. It does not edit the user's settings, copy their
|
|
64
|
+
history/plugins, or reuse their project records. Concurrent runs get distinct
|
|
65
|
+
homes. `permissionMode: "inherit"` is the default and preserves the previous
|
|
66
|
+
behavior. Scoped mode requires `sandbox: true`, rejects `skipPermissions: true`
|
|
67
|
+
and a host `project`, and supports scoped `allow`, `deny`, and `ask` lists.
|
|
68
|
+
|
|
69
|
+
Authentication must work in that private home. An inherited `GEMINI_API_KEY` is
|
|
70
|
+
passed to the CLI. For file-based OAuth, Lux copies only the CLI's
|
|
71
|
+
`antigravity-oauth-token` into the private runtime, using the normal CLI token
|
|
72
|
+
path by default or an explicit `authTokenFile`. The copy has mode 0600 inside
|
|
73
|
+
a mode-0700 runtime directory; token refresh cannot modify the source file.
|
|
74
|
+
On macOS, `reuseKeychain: true` explicitly opts into reading Antigravity’s own
|
|
75
|
+
Keychain entry (service `gemini`, account `antigravity`) when the default token
|
|
76
|
+
file is absent. Lux writes that credential only to the private runtime and
|
|
77
|
+
removes it on close. This option defaults to false. Do not put secret values in
|
|
78
|
+
committed Lux configuration.
|
|
79
|
+
|
|
80
|
+
Before deleting a private runtime, Lux snapshots native deliverables from its
|
|
81
|
+
`brain` directory under `antigravity-artifacts/` in the task artifacts. This keeps
|
|
82
|
+
handoff documents available to graders even when the final response only links
|
|
83
|
+
to them. Hidden CLI logs, revision copies, metadata, and symlinks are excluded;
|
|
84
|
+
collection fails explicitly above 500 files or 32 MiB. Existing user-home brain
|
|
85
|
+
history is never scanned in inherited mode.
|
|
86
|
+
|
|
87
|
+
The runtime is outside task artifacts and removed on driver close, collection,
|
|
88
|
+
and startup failure. A force-killed Lux process cannot run cleanup; private
|
|
89
|
+
runtime directories have the `lux-antigravity-home-` prefix in the OS temp root.
|
|
90
|
+
This is configuration separation, not a complete OS security boundary; the
|
|
91
|
+
driver continues declaring `isolation: false`.
|
|
92
|
+
|
|
46
93
|
Grant scoped file and documentation access as needed, including read access to
|
|
47
|
-
language runtimes installed outside system directories.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
94
|
+
language runtimes installed outside system directories. `command(...)` allow
|
|
95
|
+
rules can authorize outside-sandbox execution too. Keep them absent when that
|
|
96
|
+
execution must remain unapproved; `unsandboxed(...)` rules were ignored in the
|
|
97
|
+
observed macOS CLI. The terminal sandbox and native file-tool permissions are
|
|
98
|
+
separate. Extra CLI arguments remain caller-controlled.
|
|
52
99
|
|
|
53
100
|
Each run creates a fresh Antigravity project and passes its canonical working
|
|
54
101
|
directory through `--add-dir` and an explicit task-workspace prompt preamble.
|
|
@@ -60,14 +107,65 @@ permissions: Antigravity 1.2.10 can still deny workspace writes in headless mode
|
|
|
60
107
|
bypass tool permission checks.
|
|
61
108
|
|
|
62
109
|
The headless protocol carries no user questions or permission prompts, so the
|
|
63
|
-
driver is not interactive
|
|
64
|
-
|
|
110
|
+
driver is not interactive. In inherited mode, Antigravity still reads user
|
|
111
|
+
settings and conversation history. Controlled experiments are not
|
|
65
112
|
declared.
|
|
66
113
|
|
|
67
114
|
Regression tests cover the `ERROR` tool step followed by `SUCCESS` with denied
|
|
68
115
|
actions observed in an authenticated Antigravity 1.2.10 session.
|
|
69
116
|
|
|
117
|
+
## Live readiness checks
|
|
118
|
+
|
|
119
|
+
From a Lux checkout, after installing locked dependencies and authenticating `agy`:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
LUX_ANTIGRAVITY_MODEL=gemini-3.1-pro mise run smoke:antigravity
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This opt-in smoke test consumes model usage. It runs four bounded checks in
|
|
126
|
+
fresh artifact workspaces: a tool-free protocol response, a Node write/read
|
|
127
|
+
check, native handoff-document capture, and a local HTTP server/client check. It stops on the first failure and
|
|
128
|
+
retains events, fixtures, and results under `.lux-runs/antigravity-smoke/`.
|
|
129
|
+
After building, select one stage with
|
|
130
|
+
`LUX_ANTIGRAVITY_MODEL=gemini-3.1-pro node scripts/smoke-antigravity.mjs protocol`
|
|
131
|
+
(or `workspace` / `artifact` / `loopback`); `--list` makes no model calls.
|
|
132
|
+
|
|
133
|
+
The script uses `permissionMode: "proceed-in-sandbox"` with scoped read access to
|
|
134
|
+
the running Node installation, plus localhost grants for the loopback stage.
|
|
135
|
+
It never changes global settings. Authenticate
|
|
136
|
+
with an inherited `GEMINI_API_KEY`, a normal CLI OAuth token file, or
|
|
137
|
+
`LUX_ANTIGRAVITY_AUTH_TOKEN_FILE` pointing to such a file. To opt into macOS
|
|
138
|
+
Keychain reuse, set `LUX_ANTIGRAVITY_REUSE_KEYCHAIN=1`. On macOS with CLI 1.2.12,
|
|
139
|
+
private-runtime authentication and sandboxed file writes passed on 2026-09-29.
|
|
140
|
+
The loopback check failed with `connect EPERM 127.0.0.1`; its missing fixture
|
|
141
|
+
evidence correctly failed the smoke test despite normal agent completion.
|
|
142
|
+
|
|
143
|
+
`--sandbox` alone does not select an execution policy: a native 1.2.12 probe on
|
|
144
|
+
macOS with only `enableTerminalSandbox: true` denied the command, then returned
|
|
145
|
+
`SUCCESS` with empty text and `denied_actions`. Lux correctly classifies that
|
|
146
|
+
result as an error. A separate tool-free live probe passed in inherited mode;
|
|
147
|
+
it does not establish private-runtime authentication.
|
|
148
|
+
|
|
149
|
+
A passing protocol check establishes authentication and streaming only.
|
|
150
|
+
Workspace and loopback checks require fixture-produced evidence; a completed
|
|
151
|
+
agent response alone cannot pass them. These checks do not establish skill
|
|
152
|
+
quality, package-install access, or complete sandbox isolation. Scoped access
|
|
153
|
+
to a runtime outside system directories may also be necessary.
|
|
154
|
+
|
|
155
|
+
On macOS, native 1.2.11 probes could write/build in editing mode but could
|
|
156
|
+
not connect to their own loopback HTTP server: direct connections returned
|
|
157
|
+
`EPERM`, and proxy requests rejected literal IPs or nonstandard HTTP ports.
|
|
158
|
+
That failure occurred without Lux. Recheck the installed CLI with `loopback`
|
|
159
|
+
before running suites that depend on local HTTP fixtures. Domain grants alone
|
|
160
|
+
have not been shown to resolve this restriction. Keep a native reproduction
|
|
161
|
+
separate from driver traces when diagnosing it; do not treat it as skill failure
|
|
162
|
+
or bypass the sandbox to obtain a passing score.
|
|
163
|
+
|
|
70
164
|
Protocol references:
|
|
71
165
|
|
|
72
166
|
- [Antigravity CLI headless mode](https://antigravity.google/docs/cli/headless/)
|
|
73
167
|
- [Antigravity CLI](https://github.com/google-antigravity/antigravity-cli)
|
|
168
|
+
|
|
169
|
+
- [Antigravity terminal sandbox](https://antigravity.google/docs/sandbox?tab=cli)
|
|
170
|
+
- [CLI settings reference](https://antigravity.google/docs/cli/reference/)
|
|
171
|
+
- [Native artifacts](https://antigravity.google/docs/cli/artifacts/)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
+
import { join, sep } from "node:path";
|
|
3
|
+
import { writeArtifactSnapshot } from "../../core/artifact-snapshot.js";
|
|
4
|
+
import { walkArtifacts } from "../shared/artifacts.js";
|
|
5
|
+
|
|
6
|
+
export const captureAntigravityArtifacts = async (
|
|
7
|
+
home: string,
|
|
8
|
+
destination: string,
|
|
9
|
+
): Promise<void> => {
|
|
10
|
+
const brain = join(home, ".gemini/antigravity-cli/brain");
|
|
11
|
+
let root: string;
|
|
12
|
+
try {
|
|
13
|
+
root = await realpath(brain);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
if (!root.startsWith(`${await realpath(home)}${sep}`))
|
|
19
|
+
throw new Error("Antigravity artifact directory escaped its private runtime");
|
|
20
|
+
// Native revisions and CLI bookkeeping are not authored deliverables.
|
|
21
|
+
const artifacts = (await walkArtifacts(brain)).filter(
|
|
22
|
+
({ path }) => !/\.(?:metadata\.json|resolved(?:\.\d+)?)$/.test(path),
|
|
23
|
+
);
|
|
24
|
+
if (!artifacts.length) return;
|
|
25
|
+
const maxBytes = 32 * 1024 * 1024;
|
|
26
|
+
if (artifacts.reduce((total, artifact) => total + (artifact.size ?? 0), 0) > maxBytes)
|
|
27
|
+
throw new Error("Antigravity native artifacts exceed the 32 MiB collection limit");
|
|
28
|
+
const files = Object.fromEntries(
|
|
29
|
+
await Promise.all(artifacts.map(async ({ path }) => [path, await readFile(join(brain, path))])),
|
|
30
|
+
);
|
|
31
|
+
await writeArtifactSnapshot({
|
|
32
|
+
artifactsDir: destination,
|
|
33
|
+
name: "antigravity-artifacts",
|
|
34
|
+
files,
|
|
35
|
+
maxBytes,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
@@ -21,6 +21,20 @@ export const AntigravityDriverConfigSchema = z
|
|
|
21
21
|
skipPermissions: z.boolean().default(false),
|
|
22
22
|
/** Enable Antigravity's terminal sandbox restrictions. */
|
|
23
23
|
sandbox: z.boolean().default(false),
|
|
24
|
+
/** Own the CLI settings in a private runtime home instead of changing user settings. */
|
|
25
|
+
permissionMode: z.enum(["inherit", "proceed-in-sandbox"]).default("inherit"),
|
|
26
|
+
permissions: z
|
|
27
|
+
.object({
|
|
28
|
+
allow: z.array(z.string().min(1)).default([]),
|
|
29
|
+
deny: z.array(z.string().min(1)).default([]),
|
|
30
|
+
ask: z.array(z.string().min(1)).default([]),
|
|
31
|
+
})
|
|
32
|
+
.strict()
|
|
33
|
+
.optional(),
|
|
34
|
+
/** Optional CLI-format OAuth token file for the private runtime; never logged. */
|
|
35
|
+
authTokenFile: z.string().min(1).optional(),
|
|
36
|
+
/** Explicitly authorize copying this application's macOS Keychain credential. */
|
|
37
|
+
reuseKeychain: z.boolean().default(false),
|
|
24
38
|
cwd: z.string().optional(),
|
|
25
39
|
env: z.record(z.string(), z.string()).optional(),
|
|
26
40
|
extraArgs: z.array(z.string()).optional(),
|
|
@@ -28,6 +42,27 @@ export const AntigravityDriverConfigSchema = z
|
|
|
28
42
|
maxLineBytes: z.number().positive().default(10_000_000),
|
|
29
43
|
})
|
|
30
44
|
.strict()
|
|
45
|
+
.superRefine((config, ctx) => {
|
|
46
|
+
if (config.permissionMode === "proceed-in-sandbox") {
|
|
47
|
+
if (!config.sandbox || config.skipPermissions)
|
|
48
|
+
ctx.addIssue({
|
|
49
|
+
code: "custom",
|
|
50
|
+
message: "proceed-in-sandbox requires sandbox: true and skipPermissions: false",
|
|
51
|
+
});
|
|
52
|
+
if (config.project)
|
|
53
|
+
ctx.addIssue({
|
|
54
|
+
code: "custom",
|
|
55
|
+
path: ["project"],
|
|
56
|
+
message: "A private Antigravity runtime cannot reuse a host project",
|
|
57
|
+
});
|
|
58
|
+
} else if (config.permissions || config.authTokenFile || config.reuseKeychain) {
|
|
59
|
+
ctx.addIssue({
|
|
60
|
+
code: "custom",
|
|
61
|
+
message:
|
|
62
|
+
"permissions, authTokenFile, and reuseKeychain require permissionMode: proceed-in-sandbox",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
})
|
|
31
66
|
.transform(applyCatalogEffort(antigravityCatalog));
|
|
32
67
|
|
|
33
68
|
export type AntigravityDriverConfig = z.infer<typeof AntigravityDriverConfigSchema>;
|
|
@@ -19,12 +19,15 @@ import {
|
|
|
19
19
|
formatAntigravityPrompt,
|
|
20
20
|
} from "./config.js";
|
|
21
21
|
import { makeParseEvent } from "./events.js";
|
|
22
|
+
import { prepareAntigravityRuntime } from "./runtime.js";
|
|
22
23
|
|
|
23
24
|
class AntigravityDriver implements AgentDriver {
|
|
24
25
|
private inner: AgentDriver | null = null;
|
|
25
26
|
private pendingAttach: Array<(inner: AgentDriver) => void> = [];
|
|
26
27
|
private cwd: string | null = null;
|
|
27
28
|
private artifactsDir: string | null = null;
|
|
29
|
+
private runtime: Awaited<ReturnType<typeof prepareAntigravityRuntime>> | null = null;
|
|
30
|
+
private started = false;
|
|
28
31
|
private readonly config: AntigravityDriverConfig;
|
|
29
32
|
|
|
30
33
|
constructor(config: AntigravityDriverConfig) {
|
|
@@ -42,30 +45,37 @@ class AntigravityDriver implements AgentDriver {
|
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
async start(ctx: StartContext): Promise<ReadyState> {
|
|
45
|
-
if (this.
|
|
48
|
+
if (this.started) throw new Error("Antigravity driver cannot be started twice");
|
|
49
|
+
this.started = true;
|
|
46
50
|
this.cwd = await realpath(this.config.cwd ?? ctx.artifactsDir);
|
|
47
51
|
this.artifactsDir = ctx.artifactsDir;
|
|
48
52
|
const cliVersion = await discoverCliVersion(this.config.command);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
this.runtime = await prepareAntigravityRuntime(this.config, ctx.isolatedHome);
|
|
54
|
+
try {
|
|
55
|
+
const inner = await subprocessDriver.create({
|
|
56
|
+
command: this.config.command,
|
|
57
|
+
args: buildAntigravityArgs(this.config, this.cwd),
|
|
58
|
+
cwd: this.cwd,
|
|
59
|
+
env: this.runtime.env,
|
|
60
|
+
protocol: "custom",
|
|
61
|
+
parseEvent: makeParseEvent(this.config, cliVersion),
|
|
62
|
+
formatPrompt: (prompt) =>
|
|
63
|
+
formatAntigravityPrompt(
|
|
64
|
+
`The task workspace is ${JSON.stringify(this.cwd)}. Treat that directory as the current directory for this task and create task artifacts there.\n\n${prompt}`,
|
|
65
|
+
),
|
|
66
|
+
closeStdinAfterPrompt: true,
|
|
67
|
+
idleTimeout: this.config.idleTimeout,
|
|
68
|
+
maxLineBytes: this.config.maxLineBytes,
|
|
69
|
+
maxInterrupts: 1,
|
|
70
|
+
});
|
|
71
|
+
for (const attach of this.pendingAttach) attach(inner);
|
|
72
|
+
this.pendingAttach = [];
|
|
73
|
+
this.inner = inner;
|
|
74
|
+
return await inner.start(ctx);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
await this.close();
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
async respond(_answer: Answer): Promise<void> {
|
|
@@ -79,6 +89,11 @@ class AntigravityDriver implements AgentDriver {
|
|
|
79
89
|
async collect(): Promise<Artifact[]> {
|
|
80
90
|
await this.quiesce();
|
|
81
91
|
if (!this.cwd) return [];
|
|
92
|
+
try {
|
|
93
|
+
await this.runtime?.collect(this.cwd);
|
|
94
|
+
} finally {
|
|
95
|
+
await this.runtime?.close();
|
|
96
|
+
}
|
|
82
97
|
const artifacts = await walkArtifacts(this.cwd);
|
|
83
98
|
return this.cwd === this.artifactsDir
|
|
84
99
|
? artifacts
|
|
@@ -86,7 +101,11 @@ class AntigravityDriver implements AgentDriver {
|
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
async close(): Promise<void> {
|
|
89
|
-
|
|
104
|
+
try {
|
|
105
|
+
await this.inner?.close();
|
|
106
|
+
} finally {
|
|
107
|
+
await this.runtime?.close();
|
|
108
|
+
}
|
|
90
109
|
}
|
|
91
110
|
}
|
|
92
111
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { chmod, copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { temporaryDirectory } from "tempy";
|
|
5
|
+
import { exec } from "../../core/exec.js";
|
|
6
|
+
import { captureAntigravityArtifacts } from "./artifacts.js";
|
|
7
|
+
import type { AntigravityDriverConfig } from "./config.js";
|
|
8
|
+
|
|
9
|
+
export const prepareAntigravityRuntime = async (
|
|
10
|
+
config: AntigravityDriverConfig,
|
|
11
|
+
isolatedHome?: string,
|
|
12
|
+
): Promise<{
|
|
13
|
+
env: Record<string, string> | undefined;
|
|
14
|
+
collect: (destination: string) => Promise<void>;
|
|
15
|
+
close: () => Promise<void>;
|
|
16
|
+
}> => {
|
|
17
|
+
if (config.permissionMode === "inherit")
|
|
18
|
+
return {
|
|
19
|
+
env: config.env,
|
|
20
|
+
collect: async (destination) => {
|
|
21
|
+
if (isolatedHome) await captureAntigravityArtifacts(isolatedHome, destination);
|
|
22
|
+
},
|
|
23
|
+
close: async () => {},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const runtimeHome = temporaryDirectory({ prefix: "lux-antigravity-home-" });
|
|
27
|
+
const close = () => rm(runtimeHome, { recursive: true, force: true });
|
|
28
|
+
try {
|
|
29
|
+
await chmod(runtimeHome, 0o700);
|
|
30
|
+
const cliRoot = join(runtimeHome, ".gemini", "antigravity-cli");
|
|
31
|
+
await mkdir(cliRoot, { recursive: true, mode: 0o700 });
|
|
32
|
+
await writeFile(
|
|
33
|
+
join(cliRoot, "settings.json"),
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
enableTerminalSandbox: true,
|
|
36
|
+
toolPermission: "proceed-in-sandbox",
|
|
37
|
+
permissions: config.permissions,
|
|
38
|
+
}),
|
|
39
|
+
{ mode: 0o600 },
|
|
40
|
+
);
|
|
41
|
+
const source =
|
|
42
|
+
config.authTokenFile ??
|
|
43
|
+
join(config.env?.HOME ?? homedir(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
|
|
44
|
+
try {
|
|
45
|
+
// Copy only the CLI's own token, never settings, conversations, or plugins.
|
|
46
|
+
// A private copy prevents token refresh from mutating the caller's login.
|
|
47
|
+
const target = join(cliRoot, "antigravity-oauth-token");
|
|
48
|
+
await copyFile(source, target);
|
|
49
|
+
await chmod(target, 0o600);
|
|
50
|
+
// Reject empty files early without including credential contents in diagnostics.
|
|
51
|
+
if ((await readFile(target)).length === 0) throw new Error("Antigravity token file is empty");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (config.authTokenFile || (error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
54
|
+
if (config.reuseKeychain) {
|
|
55
|
+
if (platform() !== "darwin") throw new Error("Antigravity Keychain reuse requires macOS");
|
|
56
|
+
const credential = await exec(
|
|
57
|
+
"/usr/bin/security",
|
|
58
|
+
["find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"],
|
|
59
|
+
{ timeoutMs: 10_000, maxBuffer: 1_000_000 },
|
|
60
|
+
);
|
|
61
|
+
if (credential.exitCode !== 0 || credential.stdoutTruncated)
|
|
62
|
+
throw new Error("Could not read the authorized Antigravity Keychain credential");
|
|
63
|
+
let token = credential.stdout.trim();
|
|
64
|
+
if (token.startsWith("go-keyring-base64:"))
|
|
65
|
+
token = Buffer.from(token.slice("go-keyring-base64:".length), "base64").toString("utf8");
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(token);
|
|
68
|
+
if (!parsed?.token?.refresh_token || !parsed.auth_method) throw new Error();
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error("Antigravity Keychain credential has an unsupported format");
|
|
71
|
+
}
|
|
72
|
+
await writeFile(join(cliRoot, "antigravity-oauth-token"), token, { mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
env: {
|
|
77
|
+
...config.env,
|
|
78
|
+
HOME: runtimeHome,
|
|
79
|
+
...(process.platform === "win32" ? { USERPROFILE: runtimeHome } : {}),
|
|
80
|
+
},
|
|
81
|
+
collect: (destination) => captureAntigravityArtifacts(runtimeHome, destination),
|
|
82
|
+
close,
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
await close();
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -265,6 +265,12 @@ export {
|
|
|
265
265
|
type StartContext,
|
|
266
266
|
selectModelAndReasoningEffort,
|
|
267
267
|
} from "./core/driver.js";
|
|
268
|
+
export {
|
|
269
|
+
type ContainerExecution,
|
|
270
|
+
ContainerExecutionSchema,
|
|
271
|
+
type DriverExecution,
|
|
272
|
+
DriverExecutionSchema,
|
|
273
|
+
} from "./core/driver-execution.js";
|
|
268
274
|
export {
|
|
269
275
|
type DriverMetric,
|
|
270
276
|
type DriverMetricDescriptor,
|
|
@@ -74,7 +74,7 @@ const LifecycleHooksPluginSchema = z.custom<LifecycleHooksPlugin>().superRefine(
|
|
|
74
74
|
if (issue) ctx.addIssue({ code: "custom", message: issue });
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
const LuxConfigSchema = z
|
|
77
|
+
export const LuxConfigSchema = z
|
|
78
78
|
.object({
|
|
79
79
|
environment: EnvironmentDeclarationSchema.optional(),
|
|
80
80
|
casesRoot: z.string().optional(),
|
|
@@ -194,6 +194,9 @@ const builtinDrivers: DriverPlugin<never>[] = [
|
|
|
194
194
|
subprocessDriver,
|
|
195
195
|
mcpProbeDriver,
|
|
196
196
|
];
|
|
197
|
+
export const isBuiltinDriver = (plugin: DriverPlugin): boolean =>
|
|
198
|
+
builtinDrivers.some((builtin) => builtin === plugin);
|
|
199
|
+
|
|
197
200
|
const builtinAnswerers: AnswererPlugin<never>[] = [
|
|
198
201
|
personaAnswerer,
|
|
199
202
|
claudeCodeAnswerer,
|