@estebanforge/pi-ask-antigravity 1.2.8 → 1.2.9
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/CHANGELOG.md +7 -0
- package/extensions/index.ts +197 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.9] - 2026-09-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Plan-mode delegations enforce read-only instead of promising it.** Port of the bridge fix one commit earlier in the stack: the prompt-only guard failed in the field (a plan delegation behind an env-injected skip flag still edited files), so plan-mode runs now stage a temporary agy agent — `tools:` limited to `view_file` and `run_command`, `commandExecutionPolicy: auto`, `inheritMcp: false` — under the shared `~/.gemini/config/agents` discovery root and pass `--agent`. No file-editing tool in the toolset means no edit call is possible, with or without `--dangerously-skip-permissions` (probed on agy 1.2.12); the policy is what lets analysis commands run headless for users with no allow rules. With the agent staged, the skip flag returns under the shared `skipPermissions` knob; staging failure falls back fail-closed to no agent, no flag, and the strict all-commands-forbidden guard, with an env-injected skip flag stripped from `AGY_EXTRA_ARGS` on that path. Includes the standalone sweep for orphaned agent dirs (pid-marker doctrine), duplicated rather than extracted: the standalone stays self-contained per repo convention. Tool descriptions state the residual honestly: file-editing tools are hard-blocked, arbitrary shell execution under the flag is prompt-guarded alone.
|
|
10
|
+
- **Headless plan prompts say what they cannot do.** Same hardening as the bridge: plan prompts append an explicit no-commands constraint and a state-what-is-missing instruction, converting the soft-denied empty-output death of a shell-command attempt into a visible missing-content request. On the enforced path the guard yields to the mutation-forbidding variant; the command-forbidding rule remains on the fallback path.
|
|
11
|
+
|
|
5
12
|
## [1.2.8] - 2026-09-25
|
|
6
13
|
|
|
7
14
|
### Fixed
|
package/extensions/index.ts
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { spawn } from "node:child_process";
|
|
32
|
+
import { randomBytes } from "node:crypto";
|
|
32
33
|
import * as fs from "node:fs";
|
|
33
34
|
import * as os from "node:os";
|
|
34
35
|
import * as path from "node:path";
|
|
@@ -156,7 +157,7 @@ TWO MODES (you choose):
|
|
|
156
157
|
- **Continued conversation**: pass the conversationId returned in the PREVIOUS call's details (details.conversationId). agy resumes that conversation with full context intact — use for follow-ups, multi-turn refinement, or when the user says "ask agy to follow up / continue / now do X based on what you just did". Thread the id from each result into the next call.
|
|
157
158
|
|
|
158
159
|
EXECUTION MODES (param: mode):
|
|
159
|
-
- **plan**: agy reviews and plans without writing. Use for cross-review and read-only tasks. Enforced: plan runs
|
|
160
|
+
- **plan**: agy reviews and plans without writing. Use for cross-review and read-only tasks. Enforced: plan runs execute in a temporary restricted agent whose toolset has NO file-editing tools (hard block); the reviewer may still execute arbitrary shell commands, kept read-only by the prompt guard alone. Inline the material to review - a plan run cannot fetch it.
|
|
160
161
|
- **accept-edits** (default): agy applies edits directly inside the workspace.
|
|
161
162
|
- For agy's orthogonal \`--sandbox\` shell-containment flag, set the \`AGY_EXTRA_ARGS=--sandbox\` env var.
|
|
162
163
|
|
|
@@ -282,6 +283,150 @@ function saveConfig(patch: Partial<Config>): SaveResult {
|
|
|
282
283
|
return { path: targetPath, routedToProject: projectShadows };
|
|
283
284
|
}
|
|
284
285
|
|
|
286
|
+
// --- Prompt assembly -------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
/** Appended to every headless plan-mode prompt. `agy -p` cannot answer
|
|
289
|
+
* permission prompts: in plan mode a run_command attempt is soft-denied and
|
|
290
|
+
* the turn ends AT the denial (exit 0, empty stdout, notice only on stderr -
|
|
291
|
+
* the empty-output branch in execute). Probed 2026-09-28 on agy 1.2.12:
|
|
292
|
+
* allow rules ARE consulted (a verbatim allow-listed `git log` runs), but
|
|
293
|
+
* --sandbox does NOT relax the command gate, so the only input every user
|
|
294
|
+
* is guaranteed is the prompt itself. The guard steers the model to answer
|
|
295
|
+
* from that material. accept-edits runs keep their tools and never get it. */
|
|
296
|
+
export const PLAN_HEADLESS_GUARD = [
|
|
297
|
+
"",
|
|
298
|
+
"--- Headless session constraints ---",
|
|
299
|
+
"- Do not run shell commands. Command execution is denied in this session, and any attempt ends the session immediately with no answer.",
|
|
300
|
+
"- Work only from the material provided in this prompt.",
|
|
301
|
+
"- If information you need is missing, state exactly what is missing in your answer instead of trying to fetch it.",
|
|
302
|
+
].join("\n");
|
|
303
|
+
|
|
304
|
+
/** Guard for plan runs backed by the restricted reviewer agent. Difference
|
|
305
|
+
* to PLAN_HEADLESS_GUARD: commands are the analysis capability here (the
|
|
306
|
+
* agent's commandExecutionPolicy auto-approves them headless, probed
|
|
307
|
+
* 2026-09-28), so the guard forbids FILE MUTATION - by tool or by shell -
|
|
308
|
+
* and keeps the answer-from-material discipline. */
|
|
309
|
+
export const AGENT_REVIEW_GUARD = [
|
|
310
|
+
"",
|
|
311
|
+
"--- Review constraints ---",
|
|
312
|
+
"- Read-only review. Do not create, modify, or delete any files, including through shell commands (no redirects, tee, rm, mv, git commit).",
|
|
313
|
+
"- Read-only commands (git log, git diff, git show, rg, ls, cat, test runners) are allowed for analysis.",
|
|
314
|
+
"- Work from the material provided in this prompt; if information you need is missing, state exactly what is missing in your answer.",
|
|
315
|
+
].join("\n");
|
|
316
|
+
|
|
317
|
+
/** Assemble the prompt sent to agy: digest marker first (existing behavior),
|
|
318
|
+
* then the caller's prompt, then the plan-mode guard last - the position
|
|
319
|
+
* the model reads with the most recency. No user-config mutation: this is
|
|
320
|
+
* the only plan-mode lever the tool itself owns. */
|
|
321
|
+
export function buildFinalPrompt(
|
|
322
|
+
prompt: string,
|
|
323
|
+
mode: Mode,
|
|
324
|
+
digest: boolean,
|
|
325
|
+
/** True when the restricted reviewer agent is staged: its empty edit
|
|
326
|
+
* toolset is the hard block, so the prompt may ALLOW read-only commands. */
|
|
327
|
+
agentEnforced = false,
|
|
328
|
+
): string {
|
|
329
|
+
let out = digest ? `(Use compact digests, not full file contents.)\n${prompt}` : prompt;
|
|
330
|
+
if (mode === "plan") out += agentEnforced ? AGENT_REVIEW_GUARD : PLAN_HEADLESS_GUARD;
|
|
331
|
+
return out;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// --- Plan-mode reviewer agent ----------------------------------------------
|
|
335
|
+
|
|
336
|
+
const ASK_AGENT_PREFIX = "pi-bridge-ask-";
|
|
337
|
+
const ASK_AGENT_TOOLS = ["view_file", "run_command"];
|
|
338
|
+
|
|
339
|
+
/** Temp agent dirs share the ~/.gemini/config/agents discovery root; the
|
|
340
|
+
* AGY_AGENTS_ROOT override exists for tests and sandboxes. */
|
|
341
|
+
export function askAgentsRoot(): string {
|
|
342
|
+
return process.env.AGY_AGENTS_ROOT ?? path.join(os.homedir(), ".gemini", "config", "agents");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** agent.md for the plan-mode reviewer. The tools list is the ENFORCEMENT:
|
|
346
|
+
* with no file-editing tool present the model cannot emit an edit call at
|
|
347
|
+
* all (probed 2026-09-28 on agy 1.2.12: the toolset reports "none" for
|
|
348
|
+
* edits, with and without the skip flag). commandExecutionPolicy auto is
|
|
349
|
+
* what lets read commands run headless with no user allow rules. Never add
|
|
350
|
+
* a write-capable tool here. */
|
|
351
|
+
export function reviewerAgentMd(name: string): string {
|
|
352
|
+
return [
|
|
353
|
+
"---",
|
|
354
|
+
`name: ${name}`,
|
|
355
|
+
"description: Temporary Pi plan-mode reviewer",
|
|
356
|
+
"mainAgent: true",
|
|
357
|
+
"subagent: false",
|
|
358
|
+
"model: inherit",
|
|
359
|
+
"excludeDefaultComponents: true",
|
|
360
|
+
"inheritCustomizations: false",
|
|
361
|
+
"inheritMcp: false",
|
|
362
|
+
"commandExecutionPolicy: auto",
|
|
363
|
+
"tools:",
|
|
364
|
+
...ASK_AGENT_TOOLS.map((t) => ` - ${t}`),
|
|
365
|
+
"skills: []",
|
|
366
|
+
"rules: []",
|
|
367
|
+
"agents: []",
|
|
368
|
+
"mcpServers: []",
|
|
369
|
+
"---",
|
|
370
|
+
"",
|
|
371
|
+
"You are a strict read-only code reviewer. Review, analyze, and plan; never modify anything.",
|
|
372
|
+
"",
|
|
373
|
+
].join("\n");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Stage one unique reviewer agent (agent.md + pid marker) under root. Same
|
|
377
|
+
* hygiene doctrine as the web delegates: nonce-named, cleaned in finally,
|
|
378
|
+
* orphans swept by pid marker at registration. */
|
|
379
|
+
export function stageReviewerAgent(root: string): { name: string; dir: string } {
|
|
380
|
+
fs.mkdirSync(root, { recursive: true });
|
|
381
|
+
const name = `${ASK_AGENT_PREFIX}${process.pid}-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
|
|
382
|
+
const dir = path.join(root, name);
|
|
383
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
384
|
+
fs.writeFileSync(path.join(dir, "agent.md"), reviewerAgentMd(name), { mode: 0o600 });
|
|
385
|
+
fs.writeFileSync(path.join(dir, ".pid"), `${process.pid}\n`, { mode: 0o600 });
|
|
386
|
+
return { name, dir };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** Remove leftover reviewer agent dirs: dead-pid markers always, marker-less
|
|
390
|
+
* dirs only after the grace period (a live sibling may sit between mkdir and
|
|
391
|
+
* its pid write). Never touches foreign agent dirs. */
|
|
392
|
+
export function sweepStaleAskAgents(
|
|
393
|
+
root: string = askAgentsRoot(),
|
|
394
|
+
now = Date.now(),
|
|
395
|
+
prefix: string = ASK_AGENT_PREFIX,
|
|
396
|
+
): void {
|
|
397
|
+
let entries: string[];
|
|
398
|
+
try {
|
|
399
|
+
entries = fs.readdirSync(root);
|
|
400
|
+
} catch {
|
|
401
|
+
return; // no agents dir yet: nothing to sweep
|
|
402
|
+
}
|
|
403
|
+
for (const entry of entries) {
|
|
404
|
+
if (!entry.startsWith(prefix)) continue;
|
|
405
|
+
const dir = path.join(root, entry);
|
|
406
|
+
try {
|
|
407
|
+
let stale: boolean;
|
|
408
|
+
try {
|
|
409
|
+
const pid = Number.parseInt(fs.readFileSync(path.join(dir, ".pid"), "utf8").trim(), 10);
|
|
410
|
+
stale = Number.isInteger(pid) && pid > 0 ? !pidAlive(pid) : now - fs.statSync(dir).mtimeMs > 24 * 60 * 60 * 1000;
|
|
411
|
+
} catch {
|
|
412
|
+
stale = now - fs.statSync(dir).mtimeMs > 24 * 60 * 60 * 1000;
|
|
413
|
+
}
|
|
414
|
+
if (stale) fs.rmSync(dir, { recursive: true, force: true });
|
|
415
|
+
} catch {
|
|
416
|
+
// vanished mid-sweep: nothing to remove
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function pidAlive(pid: number): boolean {
|
|
422
|
+
try {
|
|
423
|
+
process.kill(pid, 0);
|
|
424
|
+
return true;
|
|
425
|
+
} catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
285
430
|
// --- Model parsing + alias resolution --------------------------------------
|
|
286
431
|
|
|
287
432
|
/** Parse one `agy models` line into a structured entry. */
|
|
@@ -776,6 +921,14 @@ export default async function (pi: ExtensionAPI) {
|
|
|
776
921
|
// before (standalone tool).
|
|
777
922
|
if (isBridgeInstalled()) return;
|
|
778
923
|
|
|
924
|
+
// Orphan sweep for the plan-mode reviewer agents (SIGKILL can skip the
|
|
925
|
+
// run's finally): same pid-marker doctrine as the bridge's web delegates.
|
|
926
|
+
try {
|
|
927
|
+
sweepStaleAskAgents();
|
|
928
|
+
} catch {
|
|
929
|
+
// a sweep failure must never block registration
|
|
930
|
+
}
|
|
931
|
+
|
|
779
932
|
const binary = resolveAgy();
|
|
780
933
|
// Discovered once at load; frozen for the session. Run /reload after an
|
|
781
934
|
// `agy update` to refresh. Failure is non-fatal: resolveModel falls back
|
|
@@ -954,7 +1107,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
954
1107
|
],
|
|
955
1108
|
{
|
|
956
1109
|
description:
|
|
957
|
-
"agy execution mode. 'plan' = review-only
|
|
1110
|
+
"agy execution mode. 'plan' = review-only (--mode plan in a temporary restricted agent: file-editing tools are hard-blocked; arbitrary shell commands remain prompt-guarded only; with skipPermissions on, commands still run for analysis). 'accept-edits' = agy applies edits directly (--mode accept-edits, default). For agy's orthogonal --sandbox shell-containment flag, set the AGY_EXTRA_ARGS env var.",
|
|
958
1111
|
default: "accept-edits",
|
|
959
1112
|
},
|
|
960
1113
|
),
|
|
@@ -1162,6 +1315,19 @@ export default async function (pi: ExtensionAPI) {
|
|
|
1162
1315
|
const snapshot = isContinuation ? null : snapshotConversations(CONVERSATIONS_DIR);
|
|
1163
1316
|
|
|
1164
1317
|
const mode: Mode = (params.mode as Mode | undefined) ?? "accept-edits";
|
|
1318
|
+
// Plan runs stage a restricted reviewer agent: its tools list has NO
|
|
1319
|
+
// file-editing tool, which is the hard edit block (the prompt guard
|
|
1320
|
+
// alone was observed failing once - a sub-agent still edited files).
|
|
1321
|
+
// Staging failure degrades to the legacy fallback: no agent, no skip
|
|
1322
|
+
// flag, the stricter command-forbidding guard.
|
|
1323
|
+
let reviewerAgent: { name: string; dir: string } | null = null;
|
|
1324
|
+
if (mode === "plan") {
|
|
1325
|
+
try {
|
|
1326
|
+
reviewerAgent = stageReviewerAgent(askAgentsRoot());
|
|
1327
|
+
} catch {
|
|
1328
|
+
reviewerAgent = null;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1165
1331
|
// digest default: on for plan (review-only contexts where full file
|
|
1166
1332
|
// contents are noise), off for accept-edits (agy applies edits and
|
|
1167
1333
|
// may need richer context for diffs).
|
|
@@ -1169,9 +1335,12 @@ export default async function (pi: ExtensionAPI) {
|
|
|
1169
1335
|
typeof params.digest === "boolean"
|
|
1170
1336
|
? params.digest
|
|
1171
1337
|
: mode === "plan";
|
|
1172
|
-
const finalPrompt: string =
|
|
1173
|
-
|
|
1174
|
-
|
|
1338
|
+
const finalPrompt: string = buildFinalPrompt(
|
|
1339
|
+
params.prompt,
|
|
1340
|
+
mode,
|
|
1341
|
+
useDigest,
|
|
1342
|
+
reviewerAgent !== null,
|
|
1343
|
+
);
|
|
1175
1344
|
|
|
1176
1345
|
// Opt-in full-context export (isolated stays the default).
|
|
1177
1346
|
let contextFile: string | null = null;
|
|
@@ -1197,22 +1366,30 @@ export default async function (pi: ExtensionAPI) {
|
|
|
1197
1366
|
: finalPrompt;
|
|
1198
1367
|
|
|
1199
1368
|
const args: string[] = ["--add-dir", cwd];
|
|
1200
|
-
const
|
|
1369
|
+
const extraRaw = extraArgs();
|
|
1370
|
+
// Fail-closed on the fallback path: AGY_EXTRA_ARGS lands before the
|
|
1371
|
+
// mode flags, so an env-injected skip flag would re-arm what the
|
|
1372
|
+
// plan-mode rule strips. Only the enforced agent makes the flag safe.
|
|
1373
|
+
const extra =
|
|
1374
|
+
mode === "plan" && !reviewerAgent
|
|
1375
|
+
? extraRaw.filter((a) => a !== "--dangerously-skip-permissions")
|
|
1376
|
+
: extraRaw;
|
|
1201
1377
|
if (extra.length) args.push(...extra);
|
|
1202
1378
|
if (resolved.model) args.push("--model", resolved.model);
|
|
1203
1379
|
if (resolved.effort) args.push("--effort", resolved.effort);
|
|
1204
1380
|
args.push("--mode", mode);
|
|
1381
|
+
if (reviewerAgent) args.push("--agent", reviewerAgent.name);
|
|
1205
1382
|
// accept-edits auto-approves file edits but NOT shell commands, so a
|
|
1206
1383
|
// run_command would hang on an unanswerable y/n prompt in non-interactive
|
|
1207
1384
|
// -p mode. Honor the shared permissions setting (same knob as the bridge).
|
|
1208
|
-
// Plan
|
|
1209
|
-
//
|
|
1210
|
-
//
|
|
1211
|
-
//
|
|
1212
|
-
//
|
|
1213
|
-
//
|
|
1214
|
-
//
|
|
1215
|
-
if (mode !== "plan"
|
|
1385
|
+
// Plan runs WITH the reviewer agent: the flag is safe again because the
|
|
1386
|
+
// agent's toolset has no file-editing tool - the 2026-09-25 write
|
|
1387
|
+
// incident rode edit tools that existed then; probed 2026-09-28 the
|
|
1388
|
+
// restricted toolset reports "none" for edits even with the flag. The
|
|
1389
|
+
// flag is what lets analysis commands (builds, test runners) run for
|
|
1390
|
+
// users without allow rules. Plan fallback (staging failed): strict
|
|
1391
|
+
// legacy behavior, no flag - the prompt guard is all that is left.
|
|
1392
|
+
if (config.skipPermissions && (mode !== "plan" || reviewerAgent !== null)) {
|
|
1216
1393
|
args.push("--dangerously-skip-permissions");
|
|
1217
1394
|
}
|
|
1218
1395
|
if (isContinuation) args.push("--conversation", rawConvId as string);
|
|
@@ -1454,7 +1631,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
1454
1631
|
const note = [
|
|
1455
1632
|
"agy exited cleanly but produced no output.",
|
|
1456
1633
|
details.stderr.trim() ? `stderr: ${details.stderr.trim()}` : null,
|
|
1457
|
-
"Common cause: a tool call needed a permission that headless mode cannot prompt for (typically the command gate in plan mode), so it was auto-denied
|
|
1634
|
+
"Common cause: a tool call needed a permission that headless mode cannot prompt for (typically the command gate in plan mode), so it was auto-denied and the turn ended with no answer. Recovery: retry in plan mode with all needed content inlined in the prompt - plan runs cannot fetch it, commands are denied. Or rerun outside plan mode with skipPermissions, or add your own permissions.allow rules in ~/.gemini/antigravity-cli/settings.json.",
|
|
1458
1635
|
]
|
|
1459
1636
|
.filter(Boolean)
|
|
1460
1637
|
.join(" ");
|
|
@@ -1495,6 +1672,11 @@ export default async function (pi: ExtensionAPI) {
|
|
|
1495
1672
|
fs.unlinkSync(contextFile);
|
|
1496
1673
|
} catch {}
|
|
1497
1674
|
}
|
|
1675
|
+
if (reviewerAgent) {
|
|
1676
|
+
try {
|
|
1677
|
+
fs.rmSync(reviewerAgent.dir, { recursive: true, force: true });
|
|
1678
|
+
} catch {}
|
|
1679
|
+
}
|
|
1498
1680
|
}
|
|
1499
1681
|
},
|
|
1500
1682
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estebanforge/pi-ask-antigravity",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "Pi tool that delegates a self-contained sub-task to Google Antigravity's agy CLI (the CLI for Gemini). Friendly model aliases (flash/pro/gemini), configurable default model + thinking, and the AskAntigravity delegation tool.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|