gaslamp 2.2.0 → 2.2.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.
- package/README.md +13 -7
- package/bin/gaslamp.mjs +2 -0
- package/package.json +1 -1
- package/src/consult.mjs +79 -18
- package/src/fleet.mjs +3 -2
- package/src/setup.mjs +1 -1
- package/src/stdin.mjs +28 -0
package/README.md
CHANGED
|
@@ -288,13 +288,18 @@ threads, `--schema`, fleets — with these differences:
|
|
|
288
288
|
kernel sandbox (`--sandbox read-only`: Seatbelt on macOS, Landlock on Linux;
|
|
289
289
|
writes only to `~/.grok` and temp dirs). Grok's permission layer is not a
|
|
290
290
|
substitute: headless `--permission-mode default` denied its write tool but
|
|
291
|
-
ran an arbitrary shell `touch`.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
291
|
+
ran an arbitrary shell `touch`. On macOS, a read-only gemini consult runs
|
|
292
|
+
agy under Seatbelt (`sandbox-exec`): it can write its own state directory
|
|
293
|
+
(`~/.gemini/antigravity-cli`, minus the files that configure it) and temp
|
|
294
|
+
dirs, nothing else, whatever Antigravity's permission settings say — so a
|
|
295
|
+
user can keep `always-proceed` / `agentMode: auto` for interactive work, and
|
|
296
|
+
search and read-only commands still run. Elsewhere (Linux, or inside a
|
|
297
|
+
Codex sandbox, where Seatbelt does not nest) the guarantee is Antigravity's
|
|
298
|
+
`request-review` preset: the consult refuses to start when its permission
|
|
299
|
+
files grant more than reads, read-only git and the end_session off-ramp, or
|
|
300
|
+
set a lax `toolPermission` or `agentMode: auto` (which approves writes and
|
|
301
|
+
commands under any preset, unreported), and fails if init doesn't confirm
|
|
302
|
+
the preset. That path is read-capable rather than airtight: an allowed
|
|
298
303
|
`git diff` still accepts git's own `--output=<file>`.
|
|
299
304
|
Antigravity has no headless `workspace-write` (`--mode accept-edits` did
|
|
300
305
|
not enable writes in print mode), so that is a usage error there.
|
|
@@ -335,6 +340,7 @@ it. This is loop prevention, not a security boundary. Set
|
|
|
335
340
|
| `GASLAMP_DEBUG` | unset | verbose stderr (spawn argv) |
|
|
336
341
|
| `CLAUDE_BIN` / `CODEX_BIN` / `AGY_BIN` / `GROK_BIN` | autodetected | backend binaries |
|
|
337
342
|
| `GROK_HOME` | `~/.grok` | where grok keeps sessions (the system prompt hash is read from there) |
|
|
343
|
+
| `GASLAMP_GEMINI_SEATBELT` | on (macOS) | `0` runs read-only gemini consults on Antigravity's preset check instead of Seatbelt |
|
|
338
344
|
| `CODEX_HOME` | `~/.codex` | where `setup --skill` puts Codex's copy |
|
|
339
345
|
|
|
340
346
|
Exit codes: `0` ok · `1` consult failed/killed · `2` usage · `3` nested
|
package/bin/gaslamp.mjs
CHANGED
|
@@ -120,6 +120,8 @@ Env knobs:
|
|
|
120
120
|
GASLAMP_DEBUG verbose stderr (spawn argv)
|
|
121
121
|
CLAUDE_BIN / CODEX_BIN / AGY_BIN / GROK_BIN
|
|
122
122
|
backend binaries (autodetected otherwise)
|
|
123
|
+
GASLAMP_GEMINI_SEATBELT 0 = read-only gemini consults rely on Antigravity's
|
|
124
|
+
preset check instead of Seatbelt (macOS default)
|
|
123
125
|
|
|
124
126
|
Every job record (meta.json) carries provenance: provider, harness version,
|
|
125
127
|
the model the backend reported (or unknown), the vendor system prompt's hash
|
package/package.json
CHANGED
package/src/consult.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
newJobId, readMeta, renderTrailer, writeMeta,
|
|
48
48
|
} from "./jobs.mjs";
|
|
49
49
|
import { readThread, validThreadName, writeThread } from "./threads.mjs";
|
|
50
|
+
import { readStdin } from "./stdin.mjs";
|
|
50
51
|
|
|
51
52
|
// Tools permitted under the claude `read-only` override (advisory reviewer).
|
|
52
53
|
// The git reads matter: a reviewer that can't run `git diff`/`git log` is
|
|
@@ -81,9 +82,14 @@ const RETRIEVAL_TOOLS = {
|
|
|
81
82
|
claude: /^(WebSearch|WebFetch)$/,
|
|
82
83
|
codex: /^web_search/,
|
|
83
84
|
gemini: /^(search_web|read_url_content|open_browser_url|read_browser_page|browser_)/,
|
|
84
|
-
grok: /^(web_search|web_fetch|
|
|
85
|
+
grok: /^(web_search|web_fetch|x_)/,
|
|
85
86
|
};
|
|
86
87
|
|
|
88
|
+
// Grok's X search is a backend tool: init never lists it, and a call arrives
|
|
89
|
+
// as a tool_use named "X search:" ({"variant":"XSearch","backend":true}) whose
|
|
90
|
+
// tool_result names the tool that ran (x_keyword_search, x_semantic_search).
|
|
91
|
+
const toolKey = (name) => String(name).toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
92
|
+
|
|
87
93
|
// Variables a third-party harness has no business inheriting from the calling
|
|
88
94
|
// agent: Claude Code's session id, messaging socket token, account and OAuth
|
|
89
95
|
// details; Codex's and the API keys' equivalents. They reach the consultee's
|
|
@@ -113,9 +119,36 @@ function backendEnv(backend) {
|
|
|
113
119
|
if (backend === "gemini" || backend === "grok")
|
|
114
120
|
for (const k of Object.keys(env)) if (FOREIGN_ENV.test(k)) delete env[k];
|
|
115
121
|
if (backend === "grok") Object.assign(env, GROK_COMPAT_OFF, { GROK_DISABLE_AUTOUPDATER: "1" });
|
|
122
|
+
if (backend === "gemini") env.AGY_CLI_DISABLE_AUTO_UPDATE = "true";
|
|
116
123
|
return env;
|
|
117
124
|
}
|
|
118
125
|
|
|
126
|
+
// Read-only for a gemini consult, on macOS: agy runs under Seatbelt, writing
|
|
127
|
+
// nothing but its own state directory and temp — the guarantee no longer
|
|
128
|
+
// depends on Antigravity's permission settings, which a user may keep lax
|
|
129
|
+
// for interactive work (toolPermission always-proceed, agentMode auto) and
|
|
130
|
+
// which no flag overrides per run. Inside that state directory the files
|
|
131
|
+
// that configure agy stay write-denied (later deny rules win in SBPL).
|
|
132
|
+
// (bin/ stays writable: agy extracts a helper, bin/agentapi, before running
|
|
133
|
+
// any shell command, and denying it breaks read-only commands like git diff.)
|
|
134
|
+
// Elsewhere — Linux, or inside a sandbox of Codex's own, where Seatbelt does
|
|
135
|
+
// not nest — the preset check below is the guarantee.
|
|
136
|
+
const SANDBOX_EXEC = "/usr/bin/sandbox-exec";
|
|
137
|
+
const useSeatbelt = () => process.platform === "darwin" && existsSync(SANDBOX_EXEC)
|
|
138
|
+
&& !process.env.CODEX_SANDBOX && process.env.GASLAMP_GEMINI_SEATBELT !== "0";
|
|
139
|
+
function agySeatbeltProfile() {
|
|
140
|
+
const q = (p) => '"' + p.replace(/[\\"]/g, (c) => "\\" + c) + '"';
|
|
141
|
+
const state = join(homedir(), ".gemini", "antigravity-cli");
|
|
142
|
+
return [
|
|
143
|
+
"(version 1)",
|
|
144
|
+
"(allow default)",
|
|
145
|
+
"(deny file-write*)",
|
|
146
|
+
`(allow file-write* (subpath ${q(state)}) (subpath "/private/tmp") (subpath "/private/var/folders") (subpath "/dev"))`,
|
|
147
|
+
`(deny file-write* ${["settings.json", "keybindings.json"].map((f) => `(literal ${q(join(state, f))})`).join(" ")} ` +
|
|
148
|
+
`${["rules", "plugins", "skills", "updater"].map((d) => `(subpath ${q(join(state, d))})`).join(" ")})`,
|
|
149
|
+
].join("\n");
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
// What a read-only gemini consult may find pre-allowed in Antigravity's
|
|
120
153
|
// permission files: reads, read-only git (read-capable like the claude read
|
|
121
154
|
// set — git's own --output flags aside), and the end_session off-ramp. Any
|
|
@@ -124,6 +157,10 @@ function backendEnv(backend) {
|
|
|
124
157
|
// presence refuses the consult rather than letting "read-only" mean less.
|
|
125
158
|
const AGY_READ_GRANT = /^(read_file|read_url)\(|^command\(git (diff|log|show|status)\)$|^mcp\(end-conversation\/(\*|end_session)\)$/;
|
|
126
159
|
const AGY_LAX_PRESETS = new Set(["always-proceed", "proceed-in-sandbox"]);
|
|
160
|
+
// agentMode "auto" approves file edits and commands itself: under it a print
|
|
161
|
+
// run wrote a file and ran `touch` while init still reported request-review
|
|
162
|
+
// (1.4.3), so the init check cannot see it. accept-edits is refused likewise.
|
|
163
|
+
const AGY_LAX_MODES = new Set(["auto", "accept-edits"]);
|
|
127
164
|
|
|
128
165
|
// Allow rules and the tool-permission preset from every file the CLI merges:
|
|
129
166
|
// its settings.json, the settings shared with the Antigravity app, and the
|
|
@@ -135,20 +172,21 @@ function agyPermissionState() {
|
|
|
135
172
|
for (const f of readdirSync(join(root, "config", "projects")))
|
|
136
173
|
if (f.endsWith(".json")) files.push(join(root, "config", "projects", f));
|
|
137
174
|
} catch {}
|
|
138
|
-
const grants = [], presets = [];
|
|
175
|
+
const grants = [], presets = [], modes = [];
|
|
139
176
|
const walk = (node, file) => {
|
|
140
177
|
if (Array.isArray(node)) return node.forEach((n) => walk(n, file));
|
|
141
178
|
if (!node || typeof node !== "object") return;
|
|
142
179
|
for (const [k, v] of Object.entries(node)) {
|
|
143
180
|
if (k === "allow" && Array.isArray(v)) for (const r of v) if (typeof r === "string") grants.push({ rule: r, file });
|
|
144
181
|
if (k === "toolPermission" && typeof v === "string") presets.push({ preset: v, file });
|
|
182
|
+
if (k === "agentMode" && typeof v === "string") modes.push({ mode: v, file });
|
|
145
183
|
walk(v, file);
|
|
146
184
|
}
|
|
147
185
|
};
|
|
148
186
|
for (const f of files) {
|
|
149
187
|
try { walk(JSON.parse(readFileSync(f, "utf8")), f); } catch {}
|
|
150
188
|
}
|
|
151
|
-
return { grants, presets };
|
|
189
|
+
return { grants, presets, modes };
|
|
152
190
|
}
|
|
153
191
|
|
|
154
192
|
// The consult preamble: the one piece of framing a consulted agent needs.
|
|
@@ -486,12 +524,12 @@ export function runConsult(backend, argv) {
|
|
|
486
524
|
let prompt = o.prompt;
|
|
487
525
|
if (prompt == null || prompt === "-") {
|
|
488
526
|
if (prompt == null && process.stdin.isTTY) die(2, "no prompt — pass one as an argument or pipe it on stdin");
|
|
489
|
-
prompt =
|
|
527
|
+
prompt = readStdin();
|
|
490
528
|
} else if (!process.stdin.isTTY) {
|
|
491
529
|
// Prompt on argv AND piped stdin: stdin is evidence — the diff, the log,
|
|
492
530
|
// the failing output — appended as a <stdin> block. This is codex exec's
|
|
493
531
|
// own convention for exactly this case, applied uniformly to both backends.
|
|
494
|
-
const evidence =
|
|
532
|
+
const evidence = readStdin();
|
|
495
533
|
if (evidence.trim())
|
|
496
534
|
prompt += "\n\n<stdin>\n" + (evidence.endsWith("\n") ? evidence : evidence + "\n") + "</stdin>";
|
|
497
535
|
}
|
|
@@ -517,16 +555,28 @@ export function runConsult(backend, argv) {
|
|
|
517
555
|
// must not strand either.
|
|
518
556
|
const bin = resolveBin(backend);
|
|
519
557
|
const env = backendEnv(backend);
|
|
520
|
-
let grants = null;
|
|
558
|
+
let grants = null, agySettings = null, wrap = null;
|
|
559
|
+
let enforcement = sandbox === "read-only" ? {
|
|
560
|
+
claude: "permission-mode default + read allowlist", codex: "codex read-only sandbox",
|
|
561
|
+
grok: "grok --sandbox read-only (kernel)", gemini: null,
|
|
562
|
+
}[backend] : null;
|
|
521
563
|
if (backend === "gemini" && sandbox === "read-only") {
|
|
522
564
|
const state = agyPermissionState();
|
|
565
|
+
grants = state.grants.map((g) => g.rule);
|
|
566
|
+
agySettings = [...state.presets.map((p) => `toolPermission ${p.preset}`), ...state.modes.map((m) => `agentMode ${m.mode}`)];
|
|
523
567
|
const lax = state.presets.filter((p) => AGY_LAX_PRESETS.has(p.preset));
|
|
568
|
+
const modes = state.modes.filter((m) => AGY_LAX_MODES.has(m.mode));
|
|
524
569
|
const open = state.grants.filter((g) => !AGY_READ_GRANT.test(g.rule));
|
|
525
|
-
if (
|
|
570
|
+
if (useSeatbelt()) {
|
|
571
|
+
wrap = [SANDBOX_EXEC, "-p", agySeatbeltProfile()];
|
|
572
|
+
enforcement = "seatbelt (sandbox-exec)";
|
|
573
|
+
} else if (lax.length || modes.length || open.length)
|
|
526
574
|
die(1, "Antigravity's settings would let a read-only consult act unprompted — " +
|
|
527
|
-
[...lax.map((p) => `toolPermission "${p.preset}" (${p.file})`),
|
|
575
|
+
[...lax.map((p) => `toolPermission "${p.preset}" (${p.file})`),
|
|
576
|
+
...modes.map((m) => `agentMode "${m.mode}" (${m.file})`),
|
|
577
|
+
...open.map((g) => `${g.rule} (${g.file})`)].join(", ") +
|
|
528
578
|
". Remove them, or pass --sandbox danger-full-access to consult without the guarantee.");
|
|
529
|
-
|
|
579
|
+
else enforcement = "agy request-review preset";
|
|
530
580
|
}
|
|
531
581
|
const model = resolveModel(backend, o.model, bin, env);
|
|
532
582
|
const version = harnessVersion(bin, env);
|
|
@@ -550,8 +600,8 @@ export function runConsult(backend, argv) {
|
|
|
550
600
|
// `model` is what the backend reported answering with; unknown until it
|
|
551
601
|
// does (codex never reports one in its event stream).
|
|
552
602
|
model: "unknown", modelRequested: o.model ?? null, modelPassed: model,
|
|
553
|
-
permissionMode: null, sandbox,
|
|
554
|
-
...(grants ? { grants } : {}),
|
|
603
|
+
permissionMode: null, sandbox, enforcement,
|
|
604
|
+
...(grants ? { grants, agySettings } : {}),
|
|
555
605
|
systemPrompt: { known: false },
|
|
556
606
|
retrieval: { available: null, used: [] },
|
|
557
607
|
gaslamp: { version: PKG_VERSION, revision: gaslampRevision() },
|
|
@@ -576,10 +626,13 @@ export function runConsult(backend, argv) {
|
|
|
576
626
|
: backend === "gemini" ? geminiArgs(o, resumeSid, schemaText, model, sandbox)
|
|
577
627
|
: grokArgs(o, resumeSid, schemaText, sandbox);
|
|
578
628
|
|
|
579
|
-
|
|
629
|
+
// sandbox-exec applies the profile and execs the harness in place (same
|
|
630
|
+
// pid), so the group, the signals and the process name are agy's own.
|
|
631
|
+
const [cmd, argv0] = wrap ? [wrap[0], [...wrap.slice(1), bin, ...args]] : [bin, args];
|
|
632
|
+
log("spawn", cmd, JSON.stringify(argv0));
|
|
580
633
|
// detached → the child leads its own process group, so signal forwarding can
|
|
581
634
|
// kill the whole tree (backend + whatever it spawned), never just the top.
|
|
582
|
-
const child = spawn(
|
|
635
|
+
const child = spawn(cmd, argv0, { cwd: meta.cwd, env, stdio: ["pipe", "pipe", "pipe"], detached: true });
|
|
583
636
|
meta.childPid = child.pid ?? null;
|
|
584
637
|
writeMeta(meta);
|
|
585
638
|
const killGroup = (sig) => {
|
|
@@ -672,12 +725,20 @@ export function runConsult(backend, argv) {
|
|
|
672
725
|
if (j.model) prov.model = j.model;
|
|
673
726
|
prov.permissionMode = j.permissionMode ?? null;
|
|
674
727
|
if (Array.isArray(j.tools)) prov.retrieval.available = j.tools.filter((t) => RETRIEVAL_TOOLS[backend].test(t));
|
|
728
|
+
// Grok's backend X search is never listed but is there (see toolKey).
|
|
729
|
+
if (backend === "grok" && prov.retrieval.available) prov.retrieval.available.push("x_search");
|
|
675
730
|
writeMeta(meta);
|
|
676
731
|
} else if (claudeShaped && j.type === "assistant") {
|
|
677
732
|
for (const c of j.message?.content ?? []) {
|
|
678
|
-
if (c.type === "tool_use" || c.type === "server_tool_use") noteTool(c.name);
|
|
733
|
+
if (c.type === "tool_use" || c.type === "server_tool_use") noteTool(toolKey(c.name));
|
|
679
734
|
if (c.type === "tool_use" && c.name === "use_tool") noteTool(c.input?.tool_name);
|
|
680
735
|
}
|
|
736
|
+
} else if (backend === "grok" && j.type === "user") {
|
|
737
|
+
for (const c of j.message?.content ?? []) {
|
|
738
|
+
if (c.type !== "tool_result" || typeof c.content !== "string") continue;
|
|
739
|
+
let r; try { r = JSON.parse(c.content); } catch { continue; }
|
|
740
|
+
if (typeof r?.name === "string") noteTool(r.name);
|
|
741
|
+
}
|
|
681
742
|
} else if (backend === "gemini" && j.event === "init") {
|
|
682
743
|
initSeen = true;
|
|
683
744
|
const init = j.init ?? {};
|
|
@@ -687,9 +748,9 @@ export function runConsult(backend, argv) {
|
|
|
687
748
|
if (Array.isArray(init.tools)) prov.retrieval.available = [...new Set(init.tools
|
|
688
749
|
.filter((t) => RETRIEVAL_TOOLS.gemini.test(t)).map((t) => t.startsWith("browser_") ? "browser_*" : t))];
|
|
689
750
|
writeMeta(meta);
|
|
690
|
-
//
|
|
691
|
-
// a laxer one would let the consult act unprompted.
|
|
692
|
-
if (sandbox === "read-only" && !["request-review", "strict"].includes(prov.permissionMode))
|
|
751
|
+
// Without Seatbelt, read-only rests on Antigravity's request-review (or
|
|
752
|
+
// strict) preset; a laxer one would let the consult act unprompted.
|
|
753
|
+
if (sandbox === "read-only" && !wrap && !["request-review", "strict"].includes(prov.permissionMode))
|
|
693
754
|
refuse(`agy reports permission mode "${prov.permissionMode}", not request-review — refusing a read-only consult it would not enforce`);
|
|
694
755
|
else initValidated = true;
|
|
695
756
|
} else if (backend === "gemini" && j.event === "step_update") {
|
|
@@ -808,7 +869,7 @@ export function runConsult(backend, argv) {
|
|
|
808
869
|
: structuredOut !== undefined ? JSON.stringify(structuredOut) : resultText;
|
|
809
870
|
|
|
810
871
|
let status = killedBy ? "killed" : (refusal || code !== 0 || resultErr) ? "failed" : "done";
|
|
811
|
-
if (status === "done" && backend === "gemini" && sandbox === "read-only" && !initValidated) {
|
|
872
|
+
if (status === "done" && backend === "gemini" && sandbox === "read-only" && !wrap && !initValidated) {
|
|
812
873
|
status = "failed";
|
|
813
874
|
refusal = "agy reported no init event, so its permission mode — the read-only guarantee — went unconfirmed";
|
|
814
875
|
writeSync(2, `[gaslamp] ${refusal}\n`);
|
package/src/fleet.mjs
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
} from "./jobs.mjs";
|
|
44
44
|
import { BACKEND_NAMES, isBackend } from "./backends.mjs";
|
|
45
45
|
import { readThread } from "./threads.mjs";
|
|
46
|
+
import { readStdin } from "./stdin.mjs";
|
|
46
47
|
|
|
47
48
|
const GASLAMP_BIN = join(dirname(fileURLToPath(import.meta.url)), "..", "bin", "gaslamp.mjs");
|
|
48
49
|
// Quota-shaped, not CPU-shaped: the bottleneck is API rate limits, not cores
|
|
@@ -116,7 +117,7 @@ function buildTasks(backend, o) {
|
|
|
116
117
|
let raw;
|
|
117
118
|
const stdinRequested = o.stdin || (o.prompt == null && !process.stdin.isTTY);
|
|
118
119
|
if (stdinRequested) {
|
|
119
|
-
const lines =
|
|
120
|
+
const lines = readStdin().split("\n").map((l) => l.trim()).filter(Boolean);
|
|
120
121
|
if (!lines.length) die(2, "no tasks on stdin");
|
|
121
122
|
raw = lines.map((line, i) => norm(line.startsWith("{") ? parseJsonLine(line, i) : line, i));
|
|
122
123
|
} else {
|
|
@@ -125,7 +126,7 @@ function buildTasks(backend, o) {
|
|
|
125
126
|
// <stdin> convention as a single consult (git diff | gaslamp fleet …).
|
|
126
127
|
let prompt = o.prompt;
|
|
127
128
|
if (!process.stdin.isTTY) {
|
|
128
|
-
const evidence =
|
|
129
|
+
const evidence = readStdin();
|
|
129
130
|
if (evidence.trim())
|
|
130
131
|
prompt += "\n\n<stdin>\n" + (evidence.endsWith("\n") ? evidence : evidence + "\n") + "</stdin>";
|
|
131
132
|
}
|
package/src/setup.mjs
CHANGED
|
@@ -121,7 +121,7 @@ Omit \`--model\` to use ${Other}'s own configured default. Match the tier to the
|
|
|
121
121
|
|
|
122
122
|
Same contract as above — threads, \`--schema\`, fleets, records — with two differences:
|
|
123
123
|
|
|
124
|
-
- **Read-only unless you say otherwise.** An omitted \`--sandbox\` means read-only, enforced
|
|
124
|
+
- **Read-only unless you say otherwise.** An omitted \`--sandbox\` means read-only, kernel-enforced where possible: Grok runs in its own sandbox, and on macOS gaslamp runs Antigravity under Seatbelt, so neither writes outside its own state and temp dirs (Antigravity elsewhere relies on its request-review preset, and gaslamp refuses lax settings). Inside that they read local files and search — Google search on Gemini, web and X search on Grok — so for a search consult send the question and its constraints and let them fetch; for a review of material you already hold, pipe it in. What fails is writing: a write or a writing shell command errors and the turn goes on, except on Antigravity's preset path, where writes and shell commands are refused outright and can end the turn with nothing to say (gaslamp marks that failed), and its URL fetch is not offered at all. Gemini has no \`workspace-write\`; \`danger-full-access\` exists on both.
|
|
125
125
|
- **Provenance on every job.** \`meta.json\` records the model the harness reported (Grok also shows the model it billed), the harness version, a hash of the vendor system prompt where the harness exposes it (Grok) or \`known: false\`, and which web/X retrieval tools the consultee had and actually used.
|
|
126
126
|
|
|
127
127
|
\`gaslamp gemini --model\` takes \`argon\` (the default) or \`flash\`, resolved to the newest \`gemini-<version>-<tier>\` in Antigravity's own catalog; Antigravity also serves Claude and GPT models under the same account, so the target always pins a Gemini unless you pass another id. \`gaslamp grok\` uses the account's default model unless \`--model\` names one (\`grok models\` lists them).
|
package/src/stdin.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// stdin.mjs — read all of stdin, cat-like, even when it is non-blocking.
|
|
2
|
+
//
|
|
3
|
+
// readFileSync(0) throws EAGAIN instead of waiting when the caller hands
|
|
4
|
+
// gaslamp a stdin with O_NONBLOCK set (seen from an agent harness's shell),
|
|
5
|
+
// and the consult died before reading its evidence. Reading in a loop that
|
|
6
|
+
// naps on EAGAIN waits for the data and the EOF the way a blocking read would;
|
|
7
|
+
// on macOS reopening /dev/stdin is no escape, since it dups the same
|
|
8
|
+
// non-blocking file description.
|
|
9
|
+
|
|
10
|
+
import { readSync } from "node:fs";
|
|
11
|
+
|
|
12
|
+
export function readStdin() {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
const buf = Buffer.alloc(1 << 16);
|
|
15
|
+
const nap = new Int32Array(new SharedArrayBuffer(4));
|
|
16
|
+
for (;;) {
|
|
17
|
+
let n;
|
|
18
|
+
try { n = readSync(0, buf, 0, buf.length, null); }
|
|
19
|
+
catch (e) {
|
|
20
|
+
if (e.code === "EAGAIN") { Atomics.wait(nap, 0, 0, 10); continue; }
|
|
21
|
+
if (e.code === "EOF") break;
|
|
22
|
+
throw e;
|
|
23
|
+
}
|
|
24
|
+
if (n === 0) break;
|
|
25
|
+
chunks.push(Buffer.from(buf.subarray(0, n)));
|
|
26
|
+
}
|
|
27
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
28
|
+
}
|