@cluesmith/codev 3.1.6 → 3.1.8
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/dashboard-dist/assets/{index-5eO1yGoy.js → index-DM7OU-EI.js} +43 -41
- package/dashboard-dist/assets/index-DM7OU-EI.js.map +1 -0
- package/dashboard-dist/index.html +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +8 -2
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +53 -9
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/tower.d.ts +12 -0
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +11 -3
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts +4 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +12 -5
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +26 -145
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +76 -9
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/resolved-enrichment-cache.d.ts +55 -0
- package/dist/agent-farm/servers/resolved-enrichment-cache.d.ts.map +1 -0
- package/dist/agent-farm/servers/resolved-enrichment-cache.js +55 -0
- package/dist/agent-farm/servers/resolved-enrichment-cache.js.map +1 -0
- package/dist/agent-farm/servers/tower-routes.d.ts +4 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +99 -2
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +6 -0
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +14 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +95 -9
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +42 -5
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/types.d.ts +13 -3
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/commands/consult/index.d.ts +51 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +310 -51
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/usage-extractor.d.ts +12 -10
- package/dist/commands/consult/usage-extractor.d.ts.map +1 -1
- package/dist/commands/consult/usage-extractor.js +16 -88
- package/dist/commands/consult/usage-extractor.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +101 -25
- package/dist/commands/doctor.js.map +1 -1
- package/dist/lib/forge-contracts.d.ts +7 -0
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge.js +1 -1
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/github.d.ts +13 -0
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +21 -0
- package/dist/lib/github.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +8 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +8 -1
- package/dist/terminal/pty-manager.js.map +1 -1
- package/package.json +24 -23
- package/scripts/forge/gitea/auth-status.sh +0 -0
- package/scripts/forge/gitea/issue-comment.sh +0 -0
- package/scripts/forge/gitea/issue-list.sh +0 -0
- package/scripts/forge/gitea/issue-search.sh +28 -0
- package/scripts/forge/gitea/issue-view.sh +0 -0
- package/scripts/forge/gitea/pr-exists.sh +0 -0
- package/scripts/forge/gitea/pr-list.sh +0 -0
- package/scripts/forge/gitea/pr-merge.sh +0 -0
- package/scripts/forge/gitea/pr-view.sh +0 -0
- package/scripts/forge/gitea/recently-closed.sh +0 -0
- package/scripts/forge/gitea/recently-merged.sh +0 -0
- package/scripts/forge/gitea/repo-archive.sh +0 -0
- package/scripts/forge/gitea/user-identity.sh +0 -0
- package/scripts/forge/github/auth-status.sh +0 -0
- package/scripts/forge/github/issue-comment.sh +0 -0
- package/scripts/forge/github/issue-list.sh +0 -0
- package/scripts/forge/github/issue-search.sh +13 -0
- package/scripts/forge/github/issue-view.sh +0 -0
- package/scripts/forge/github/on-it-timestamps.sh +0 -0
- package/scripts/forge/github/pr-diff.sh +0 -0
- package/scripts/forge/github/pr-exists.sh +0 -0
- package/scripts/forge/github/pr-list.sh +0 -0
- package/scripts/forge/github/pr-merge.sh +0 -0
- package/scripts/forge/github/pr-search.sh +0 -0
- package/scripts/forge/github/pr-view.sh +0 -0
- package/scripts/forge/github/recently-closed.sh +0 -0
- package/scripts/forge/github/recently-merged.sh +0 -0
- package/scripts/forge/github/repo-archive.sh +0 -0
- package/scripts/forge/github/team-activity.sh +0 -0
- package/scripts/forge/github/user-identity.sh +0 -0
- package/scripts/forge/gitlab/auth-status.sh +0 -0
- package/scripts/forge/gitlab/issue-comment.sh +0 -0
- package/scripts/forge/gitlab/issue-list.sh +0 -0
- package/scripts/forge/gitlab/issue-search.sh +19 -0
- package/scripts/forge/gitlab/issue-view.sh +0 -0
- package/scripts/forge/gitlab/pr-diff.sh +0 -0
- package/scripts/forge/gitlab/pr-exists.sh +0 -0
- package/scripts/forge/gitlab/pr-list.sh +0 -0
- package/scripts/forge/gitlab/pr-merge.sh +0 -0
- package/scripts/forge/gitlab/pr-search.sh +0 -0
- package/scripts/forge/gitlab/pr-view.sh +0 -0
- package/scripts/forge/gitlab/recently-closed.sh +0 -0
- package/scripts/forge/gitlab/recently-merged.sh +0 -0
- package/scripts/forge/gitlab/repo-archive.sh +0 -0
- package/scripts/forge/gitlab/user-identity.sh +0 -0
- package/scripts/forge/linear/auth-status.sh +0 -0
- package/scripts/forge/linear/issue-comment.sh +0 -0
- package/scripts/forge/linear/issue-list.sh +0 -0
- package/scripts/forge/linear/issue-search.sh +49 -0
- package/scripts/forge/linear/issue-view.sh +0 -0
- package/scripts/forge/linear/recently-closed.sh +0 -0
- package/scripts/forge/linear/user-identity.sh +0 -0
- package/skeleton/.claude/skills/consult/SKILL.md +1 -1
- package/skeleton/DEPENDENCIES.md +17 -6
- package/skeleton/protocols/aspir/protocol.json +5 -5
- package/skeleton/protocols/spir/protocol.json +5 -5
- package/skeleton/resources/commands/codev.md +1 -1
- package/skeleton/resources/commands/consult.md +21 -6
- package/LICENSE +0 -201
- package/dashboard-dist/assets/index-5eO1yGoy.js.map +0 -1
|
@@ -16,6 +16,7 @@ interface ModelConfig {
|
|
|
16
16
|
envVar: string | null;
|
|
17
17
|
}
|
|
18
18
|
declare const MODEL_CONFIGS: Record<string, ModelConfig>;
|
|
19
|
+
declare const MODEL_ALIASES: Record<string, string>;
|
|
19
20
|
export interface ConsultOptions {
|
|
20
21
|
model: string;
|
|
21
22
|
prompt?: string;
|
|
@@ -63,6 +64,55 @@ declare function computePersistentOutputPath(state: BuilderProjectState, model:
|
|
|
63
64
|
* Mirrors runClaudeConsultation() — streams events, captures usage, records metrics.
|
|
64
65
|
*/
|
|
65
66
|
export declare function runCodexConsultation(queryText: string, role: string, workspaceRoot: string, outputPath?: string, metricsCtx?: MetricsContext): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Build the env passed to the Claude Agent SDK subprocess for a consultation.
|
|
69
|
+
*
|
|
70
|
+
* Copies the given environment, but when a Claude subscription/OAuth token
|
|
71
|
+
* (`CLAUDE_CODE_OAUTH_TOKEN`) is present, strips `ANTHROPIC_API_KEY` and
|
|
72
|
+
* `ANTHROPIC_AUTH_TOKEN` from the *copy* (never the global `process.env`).
|
|
73
|
+
* The Agent SDK prioritizes the API key over the OAuth token, so leaving the
|
|
74
|
+
* key in would silently route CMAP/review traffic to the metered Opus API
|
|
75
|
+
* instead of the Claude subscription (issue #985).
|
|
76
|
+
*
|
|
77
|
+
* When no OAuth token is set, the API key is preserved so CI / key-only
|
|
78
|
+
* environments continue to authenticate.
|
|
79
|
+
*
|
|
80
|
+
* The deletion is scoped to this subprocess env only — other callers that need
|
|
81
|
+
* the API key (persona, dev:local) are unaffected.
|
|
82
|
+
*/
|
|
83
|
+
export declare function buildClaudeConsultEnv(processEnv: NodeJS.ProcessEnv): Record<string, string>;
|
|
84
|
+
export declare const AGY_OAUTH_MARKERS: string[];
|
|
85
|
+
/**
|
|
86
|
+
* Verify a path is the real headless `agy` CLI, not the Antigravity IDE
|
|
87
|
+
* launcher. The IDE ships `~/.antigravity/.../bin/agy` as a symlink to the
|
|
88
|
+
* Electron app binary (`Antigravity.app/.../antigravity`); resolving it and
|
|
89
|
+
* launching it would open the IDE, never produce a `--print` review. We reject
|
|
90
|
+
* by realpath WITHOUT executing anything (no risk of launching the GUI).
|
|
91
|
+
*/
|
|
92
|
+
export declare function isRealAgyCli(p: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the real `agy` CLI binary deterministically — never trust a bare
|
|
95
|
+
* PATH lookup (a stale shell or the IDE symlink shadows the CLI). Prefers the
|
|
96
|
+
* official installer path, then a PATH `agy` verified not to be the IDE.
|
|
97
|
+
* Returns null if no valid headless CLI is found.
|
|
98
|
+
*/
|
|
99
|
+
/**
|
|
100
|
+
* Positively verify a candidate behaves like the real headless agy CLI by
|
|
101
|
+
* running `--version` (read-only, fast). `isRealAgyCli` rejects the IDE launcher
|
|
102
|
+
* by realpath; this adds behavioral verification for an *untrusted* PATH
|
|
103
|
+
* candidate so we only run a binary proven to be the CLI.
|
|
104
|
+
*/
|
|
105
|
+
export declare function agyRespondsToVersion(bin: string): boolean;
|
|
106
|
+
export declare function resolveAgyBin(): string | null;
|
|
107
|
+
/** Non-blocking skip artifact: porch's verdict parser treats COMMENT as non-blocking. */
|
|
108
|
+
declare function agySkipContent(reason: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Run the `gemini` consult lane via the Antigravity CLI (`agy --print`).
|
|
111
|
+
* Preserves agentic file-reading (--sandbox --add-dir), folds the role into the
|
|
112
|
+
* prompt, and NEVER blocks the run: a missing/unauthed/invalid CLI or a
|
|
113
|
+
* timeout/error produces a non-blocking COMMENT skip instead of throwing.
|
|
114
|
+
*/
|
|
115
|
+
declare function runAgyConsultation(queryText: string, role: string, workspaceRoot: string, outputPath?: string, metricsCtx?: MetricsContext): Promise<void>;
|
|
66
116
|
/**
|
|
67
117
|
* Get a compact diff stat summary and list of changed files.
|
|
68
118
|
*
|
|
@@ -115,5 +165,5 @@ declare function buildPlanQuery(plan: ContentRef, spec: ContentRef | null): stri
|
|
|
115
165
|
* Main consult entry point
|
|
116
166
|
*/
|
|
117
167
|
export declare function consult(options: ConsultOptions): Promise<void>;
|
|
118
|
-
export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, buildPRQuery as _buildPRQuery, composePRQueryText as _composePRQueryText, computePersistentOutputPath as _computePersistentOutputPath, MODEL_CONFIGS as _MODEL_CONFIGS, };
|
|
168
|
+
export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, buildPRQuery as _buildPRQuery, composePRQueryText as _composePRQueryText, computePersistentOutputPath as _computePersistentOutputPath, MODEL_CONFIGS as _MODEL_CONFIGS, MODEL_ALIASES as _MODEL_ALIASES, runAgyConsultation as _runAgyConsultation, agySkipContent as _agySkipContent, };
|
|
119
169
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/consult/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,UAAU,WAAW;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,QAAA,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/consult/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import * as fs from 'node:fs';
|
|
10
10
|
import * as path from 'node:path';
|
|
11
11
|
import { spawn, execSync, execFileSync } from 'node:child_process';
|
|
12
|
-
import { tmpdir } from 'node:os';
|
|
12
|
+
import { tmpdir, homedir } from 'node:os';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import { query as claudeQuery } from '@anthropic-ai/claude-agent-sdk';
|
|
15
15
|
import { Codex } from '@openai/codex-sdk';
|
|
@@ -20,7 +20,10 @@ import { MetricsDB } from './metrics.js';
|
|
|
20
20
|
import { extractUsage, extractReviewText } from './usage-extractor.js';
|
|
21
21
|
import { executeForgeCommandSync } from '../../lib/forge.js';
|
|
22
22
|
const MODEL_CONFIGS = {
|
|
23
|
-
gemini
|
|
23
|
+
// gemini dispatches to the Antigravity CLI (`agy`) via runAgyConsultation —
|
|
24
|
+
// this entry exists only for model validation and the `pro` alias; its
|
|
25
|
+
// cli/args are NOT used for dispatch (agy's binary path is resolved at runtime).
|
|
26
|
+
gemini: { cli: 'agy', args: [], envVar: null },
|
|
24
27
|
hermes: { cli: 'hermes', args: ['chat', '-q'], envVar: null },
|
|
25
28
|
};
|
|
26
29
|
// Models that use an Agent SDK instead of CLI subprocess
|
|
@@ -381,6 +384,35 @@ export async function runCodexConsultation(queryText, role, workspaceRoot, outpu
|
|
|
381
384
|
}
|
|
382
385
|
}
|
|
383
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Build the env passed to the Claude Agent SDK subprocess for a consultation.
|
|
389
|
+
*
|
|
390
|
+
* Copies the given environment, but when a Claude subscription/OAuth token
|
|
391
|
+
* (`CLAUDE_CODE_OAUTH_TOKEN`) is present, strips `ANTHROPIC_API_KEY` and
|
|
392
|
+
* `ANTHROPIC_AUTH_TOKEN` from the *copy* (never the global `process.env`).
|
|
393
|
+
* The Agent SDK prioritizes the API key over the OAuth token, so leaving the
|
|
394
|
+
* key in would silently route CMAP/review traffic to the metered Opus API
|
|
395
|
+
* instead of the Claude subscription (issue #985).
|
|
396
|
+
*
|
|
397
|
+
* When no OAuth token is set, the API key is preserved so CI / key-only
|
|
398
|
+
* environments continue to authenticate.
|
|
399
|
+
*
|
|
400
|
+
* The deletion is scoped to this subprocess env only — other callers that need
|
|
401
|
+
* the API key (persona, dev:local) are unaffected.
|
|
402
|
+
*/
|
|
403
|
+
export function buildClaudeConsultEnv(processEnv) {
|
|
404
|
+
const env = {};
|
|
405
|
+
for (const [key, value] of Object.entries(processEnv)) {
|
|
406
|
+
if (value !== undefined) {
|
|
407
|
+
env[key] = value;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (env.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
411
|
+
delete env.ANTHROPIC_API_KEY;
|
|
412
|
+
delete env.ANTHROPIC_AUTH_TOKEN;
|
|
413
|
+
}
|
|
414
|
+
return env;
|
|
415
|
+
}
|
|
384
416
|
/**
|
|
385
417
|
* Run Claude consultation via Agent SDK.
|
|
386
418
|
* Uses the SDK's query() function instead of CLI subprocess.
|
|
@@ -397,12 +429,7 @@ async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath,
|
|
|
397
429
|
// the nesting guard. Restore it after the SDK call.
|
|
398
430
|
const savedClaudeCode = process.env.CLAUDECODE;
|
|
399
431
|
delete process.env.CLAUDECODE;
|
|
400
|
-
const env =
|
|
401
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
402
|
-
if (value !== undefined) {
|
|
403
|
-
env[key] = value;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
432
|
+
const env = buildClaudeConsultEnv(process.env);
|
|
406
433
|
try {
|
|
407
434
|
const session = claudeQuery({
|
|
408
435
|
prompt: queryText,
|
|
@@ -474,6 +501,265 @@ async function runClaudeConsultation(queryText, role, workspaceRoot, outputPath,
|
|
|
474
501
|
}
|
|
475
502
|
}
|
|
476
503
|
}
|
|
504
|
+
// ── Antigravity CLI (`agy`) backend for the `gemini` lane ──────────────────
|
|
505
|
+
// Replaces the retiring Gemini CLI. agy is an agent (reads files from disk via
|
|
506
|
+
// --add-dir under --sandbox), OAuth-only, default model = Flash, plain-text
|
|
507
|
+
// output (no usage JSON). See spec/plan 778.
|
|
508
|
+
// Markers that indicate agy is NOT authenticated (it prints an OAuth URL and
|
|
509
|
+
// waits ~30s for an interactive login that can't complete headlessly). When
|
|
510
|
+
// seen, we terminate early and emit a non-blocking COMMENT skip.
|
|
511
|
+
export const AGY_OAUTH_MARKERS = [
|
|
512
|
+
'accounts.google.com/o/oauth2',
|
|
513
|
+
'Authentication required',
|
|
514
|
+
'paste the authorization code',
|
|
515
|
+
'Waiting for authentication',
|
|
516
|
+
];
|
|
517
|
+
const AGY_PRINT_TIMEOUT = '5m'; // passed to `agy --print-timeout`
|
|
518
|
+
const AGY_TIMEOUT_MS = 6 * 60 * 1000; // Codev-owned hard cap (> agy's own timeout)
|
|
519
|
+
// OAuth banner appears before any review text; only scan the early stream.
|
|
520
|
+
const AGY_MARKER_SCAN_LIMIT = 8192;
|
|
521
|
+
// agy's own print-timeout message: on an agentic task that outruns --print-timeout,
|
|
522
|
+
// it returns this (often with a "monitoring the task" note) instead of a review.
|
|
523
|
+
// Treat it as a non-response → non-blocking skip rather than a garbage "review".
|
|
524
|
+
const AGY_NONRESPONSE_MARKER = 'timed out waiting for response';
|
|
525
|
+
/**
|
|
526
|
+
* Verify a path is the real headless `agy` CLI, not the Antigravity IDE
|
|
527
|
+
* launcher. The IDE ships `~/.antigravity/.../bin/agy` as a symlink to the
|
|
528
|
+
* Electron app binary (`Antigravity.app/.../antigravity`); resolving it and
|
|
529
|
+
* launching it would open the IDE, never produce a `--print` review. We reject
|
|
530
|
+
* by realpath WITHOUT executing anything (no risk of launching the GUI).
|
|
531
|
+
*/
|
|
532
|
+
export function isRealAgyCli(p) {
|
|
533
|
+
try {
|
|
534
|
+
if (!fs.existsSync(p))
|
|
535
|
+
return false;
|
|
536
|
+
const real = fs.realpathSync(p);
|
|
537
|
+
if (real.includes('Antigravity.app'))
|
|
538
|
+
return false; // IDE app bundle
|
|
539
|
+
if (/[/\\]antigravity(\.exe)?$/.test(real))
|
|
540
|
+
return false; // IDE launcher binary
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Resolve the real `agy` CLI binary deterministically — never trust a bare
|
|
549
|
+
* PATH lookup (a stale shell or the IDE symlink shadows the CLI). Prefers the
|
|
550
|
+
* official installer path, then a PATH `agy` verified not to be the IDE.
|
|
551
|
+
* Returns null if no valid headless CLI is found.
|
|
552
|
+
*/
|
|
553
|
+
/**
|
|
554
|
+
* Positively verify a candidate behaves like the real headless agy CLI by
|
|
555
|
+
* running `--version` (read-only, fast). `isRealAgyCli` rejects the IDE launcher
|
|
556
|
+
* by realpath; this adds behavioral verification for an *untrusted* PATH
|
|
557
|
+
* candidate so we only run a binary proven to be the CLI.
|
|
558
|
+
*/
|
|
559
|
+
export function agyRespondsToVersion(bin) {
|
|
560
|
+
try {
|
|
561
|
+
const out = execSync(`"${bin}" --version 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
562
|
+
return out.length > 0;
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
export function resolveAgyBin() {
|
|
569
|
+
// Explicit override (advanced users / tests): use it if valid, never silently
|
|
570
|
+
// fall back to a different binary the user didn't ask for.
|
|
571
|
+
const override = process.env.CODEV_AGY_BIN;
|
|
572
|
+
if (override)
|
|
573
|
+
return isRealAgyCli(override) ? override : null;
|
|
574
|
+
// Canonical install path — trusted location; realpath-reject the IDE only.
|
|
575
|
+
const preferred = path.join(homedir(), '.local', 'bin', 'agy');
|
|
576
|
+
if (isRealAgyCli(preferred))
|
|
577
|
+
return preferred;
|
|
578
|
+
// A bare PATH `agy` is untrusted: require it to NOT be the IDE (realpath) AND
|
|
579
|
+
// to behave like the headless CLI (`--version`) before we'll run it.
|
|
580
|
+
try {
|
|
581
|
+
const found = execSync('command -v agy 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
582
|
+
if (found && isRealAgyCli(found) && agyRespondsToVersion(found))
|
|
583
|
+
return found;
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
// not on PATH
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
/** Non-blocking skip artifact: porch's verdict parser treats COMMENT as non-blocking. */
|
|
591
|
+
function agySkipContent(reason) {
|
|
592
|
+
return [
|
|
593
|
+
'---',
|
|
594
|
+
'VERDICT: COMMENT',
|
|
595
|
+
`SUMMARY: Gemini lane skipped — ${reason}`,
|
|
596
|
+
'CONFIDENCE: LOW',
|
|
597
|
+
'---',
|
|
598
|
+
'',
|
|
599
|
+
`The Gemini (Antigravity \`agy\`) reviewer was skipped: ${reason}.`,
|
|
600
|
+
'This is a non-blocking skip; the remaining reviewers still apply. To enable the',
|
|
601
|
+
'Gemini lane, install the CLI (https://antigravity.google/cli/install.sh) and run',
|
|
602
|
+
'`agy` once to sign in.',
|
|
603
|
+
].join('\n');
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Per-process sandbox temp dir for consult artifacts (the PR diff written by
|
|
607
|
+
* buildPRQuery, and the large-prompt file written by runAgyConsultation).
|
|
608
|
+
*
|
|
609
|
+
* Created once per CLI invocation (each `consult` run is its own process), so the
|
|
610
|
+
* sandboxed `agy` reviewer can be granted exactly this directory via `--add-dir`
|
|
611
|
+
* instead of the entire OS temp dir — keeping the grant scoped to the artifacts
|
|
612
|
+
* this flow creates. `mkdtempSync` yields a private, user-owned dir; callers still
|
|
613
|
+
* write with mode 0o600 / flag 'wx' to defeat symlink/clobber races.
|
|
614
|
+
*/
|
|
615
|
+
let _consultSandboxDir = null;
|
|
616
|
+
function consultSandboxDir() {
|
|
617
|
+
if (!_consultSandboxDir) {
|
|
618
|
+
_consultSandboxDir = fs.mkdtempSync(path.join(tmpdir(), 'codev-consult-'));
|
|
619
|
+
}
|
|
620
|
+
return _consultSandboxDir;
|
|
621
|
+
}
|
|
622
|
+
function writeConsultOutput(outputPath, content) {
|
|
623
|
+
if (!outputPath || content.length === 0)
|
|
624
|
+
return;
|
|
625
|
+
const outputDir = path.dirname(outputPath);
|
|
626
|
+
if (!fs.existsSync(outputDir))
|
|
627
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
628
|
+
fs.writeFileSync(outputPath, content);
|
|
629
|
+
console.error(`\nOutput written to: ${outputPath}`);
|
|
630
|
+
}
|
|
631
|
+
function recordAgyMetrics(metricsCtx, startTime, exitCode, errorMessage) {
|
|
632
|
+
if (!metricsCtx)
|
|
633
|
+
return;
|
|
634
|
+
recordMetrics(metricsCtx, {
|
|
635
|
+
durationSeconds: (Date.now() - startTime) / 1000,
|
|
636
|
+
// agy --print emits plain text, no token usage → cost rows degrade gracefully (null).
|
|
637
|
+
inputTokens: null,
|
|
638
|
+
cachedInputTokens: null,
|
|
639
|
+
outputTokens: null,
|
|
640
|
+
costUsd: null,
|
|
641
|
+
exitCode,
|
|
642
|
+
errorMessage,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Run the `gemini` consult lane via the Antigravity CLI (`agy --print`).
|
|
647
|
+
* Preserves agentic file-reading (--sandbox --add-dir), folds the role into the
|
|
648
|
+
* prompt, and NEVER blocks the run: a missing/unauthed/invalid CLI or a
|
|
649
|
+
* timeout/error produces a non-blocking COMMENT skip instead of throwing.
|
|
650
|
+
*/
|
|
651
|
+
async function runAgyConsultation(queryText, role, workspaceRoot, outputPath, metricsCtx) {
|
|
652
|
+
const startTime = Date.now();
|
|
653
|
+
const bin = resolveAgyBin();
|
|
654
|
+
if (!bin) {
|
|
655
|
+
const reason = 'agy CLI not found (install: https://antigravity.google/cli/install.sh)';
|
|
656
|
+
const content = agySkipContent(reason);
|
|
657
|
+
process.stdout.write(content);
|
|
658
|
+
writeConsultOutput(outputPath, content);
|
|
659
|
+
recordAgyMetrics(metricsCtx, startTime, 0, reason);
|
|
660
|
+
console.error(`\n[gemini (agy) skipped: ${reason}]`);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
// agy has no system-prompt flag — fold the role into the prompt (hermes precedent).
|
|
664
|
+
const prompt = `${role}\n\n---\n\n${queryText}`;
|
|
665
|
+
// Grant the sandboxed agent read access to the workspace AND the dedicated consult
|
|
666
|
+
// sandbox dir (where buildPRQuery writes the diff and, below, a large-prompt file
|
|
667
|
+
// lands) — NOT the entire OS temp dir, which would over-expose unrelated /tmp files.
|
|
668
|
+
const addDirs = [workspaceRoot, consultSandboxDir()];
|
|
669
|
+
let tempFile = null;
|
|
670
|
+
let promptArg = prompt;
|
|
671
|
+
// Large prompts can exceed ARG_MAX (E2BIG) — write to a temp file and point agy at it.
|
|
672
|
+
if (prompt.length > CLI_PROMPT_INLINE_MAX_CHARS) {
|
|
673
|
+
tempFile = path.join(consultSandboxDir(), `codev-consult-prompt-${Date.now()}.md`);
|
|
674
|
+
fs.writeFileSync(tempFile, prompt);
|
|
675
|
+
promptArg = [
|
|
676
|
+
`Read the full consultation prompt from this file: ${tempFile}`,
|
|
677
|
+
'You have file access. Read files directly from disk to review code.',
|
|
678
|
+
].join('\n\n');
|
|
679
|
+
}
|
|
680
|
+
const args = ['--print', '--sandbox', '--print-timeout', AGY_PRINT_TIMEOUT];
|
|
681
|
+
for (const d of addDirs)
|
|
682
|
+
args.push('--add-dir', d);
|
|
683
|
+
args.push(promptArg);
|
|
684
|
+
const cleanup = () => {
|
|
685
|
+
if (tempFile && fs.existsSync(tempFile)) {
|
|
686
|
+
try {
|
|
687
|
+
fs.unlinkSync(tempFile);
|
|
688
|
+
}
|
|
689
|
+
catch { /* best-effort */ }
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
return new Promise((resolve) => {
|
|
693
|
+
const proc = spawn(bin, args, {
|
|
694
|
+
cwd: workspaceRoot,
|
|
695
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
696
|
+
});
|
|
697
|
+
const outChunks = [];
|
|
698
|
+
let scanBuf = '';
|
|
699
|
+
let settled = false;
|
|
700
|
+
const settleSkip = (reason, exitCode = 0) => {
|
|
701
|
+
if (settled)
|
|
702
|
+
return;
|
|
703
|
+
settled = true;
|
|
704
|
+
clearTimeout(timer);
|
|
705
|
+
try {
|
|
706
|
+
proc.kill('SIGTERM');
|
|
707
|
+
}
|
|
708
|
+
catch { /* already gone */ }
|
|
709
|
+
cleanup();
|
|
710
|
+
const content = agySkipContent(reason);
|
|
711
|
+
process.stdout.write(content);
|
|
712
|
+
writeConsultOutput(outputPath, content);
|
|
713
|
+
recordAgyMetrics(metricsCtx, startTime, exitCode, reason);
|
|
714
|
+
console.error(`\n[gemini (agy) skipped: ${reason}]`);
|
|
715
|
+
resolve();
|
|
716
|
+
};
|
|
717
|
+
const timer = setTimeout(() => settleSkip('agy timed out (no response)', 1), AGY_TIMEOUT_MS);
|
|
718
|
+
const watch = (buf, isStdout) => {
|
|
719
|
+
if (isStdout)
|
|
720
|
+
outChunks.push(buf);
|
|
721
|
+
if (scanBuf.length < AGY_MARKER_SCAN_LIMIT) {
|
|
722
|
+
scanBuf += buf.toString('utf-8');
|
|
723
|
+
if (AGY_OAUTH_MARKERS.some((m) => scanBuf.includes(m))) {
|
|
724
|
+
settleSkip('agy not authenticated — run `agy` once to sign in (OAuth)', 1);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
proc.stdout?.on('data', (b) => watch(b, true));
|
|
729
|
+
proc.stderr?.on('data', (b) => watch(b, false));
|
|
730
|
+
proc.on('error', (err) => {
|
|
731
|
+
settleSkip(`agy failed to start: ${err.message}`, 1);
|
|
732
|
+
});
|
|
733
|
+
proc.on('close', (code) => {
|
|
734
|
+
if (settled)
|
|
735
|
+
return;
|
|
736
|
+
settled = true;
|
|
737
|
+
clearTimeout(timer);
|
|
738
|
+
cleanup();
|
|
739
|
+
const raw = Buffer.concat(outChunks).toString('utf-8').trim();
|
|
740
|
+
if (code !== 0 || raw.length === 0 || raw.includes(AGY_NONRESPONSE_MARKER)) {
|
|
741
|
+
const reason = code !== 0
|
|
742
|
+
? `agy exited with code ${code}`
|
|
743
|
+
: raw.includes(AGY_NONRESPONSE_MARKER)
|
|
744
|
+
? 'agy timed out producing the review'
|
|
745
|
+
: 'agy produced no review output';
|
|
746
|
+
const content = agySkipContent(reason);
|
|
747
|
+
process.stdout.write(content);
|
|
748
|
+
writeConsultOutput(outputPath, content);
|
|
749
|
+
recordAgyMetrics(metricsCtx, startTime, code ?? 1, reason);
|
|
750
|
+
console.error(`\n[gemini (agy) skipped: ${reason}]`);
|
|
751
|
+
resolve();
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
// Plain-text stdout IS the review.
|
|
755
|
+
process.stdout.write(raw);
|
|
756
|
+
writeConsultOutput(outputPath, raw);
|
|
757
|
+
recordAgyMetrics(metricsCtx, startTime, 0, null);
|
|
758
|
+
console.error(`\n[gemini (agy) completed in ${((Date.now() - startTime) / 1000).toFixed(1)}s]`);
|
|
759
|
+
resolve();
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
}
|
|
477
763
|
/**
|
|
478
764
|
* Run the consultation — dispatches to the correct model runner.
|
|
479
765
|
*/
|
|
@@ -495,6 +781,14 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
495
781
|
console.error(`\n[${model} completed in ${duration.toFixed(1)}s]`);
|
|
496
782
|
return;
|
|
497
783
|
}
|
|
784
|
+
// gemini lane → Antigravity CLI (`agy`); handles its own logging, metrics,
|
|
785
|
+
// and non-blocking skip (see runAgyConsultation).
|
|
786
|
+
if (model === 'gemini') {
|
|
787
|
+
const startTime = Date.now();
|
|
788
|
+
await runAgyConsultation(query, role, workspaceRoot, outputPath, metricsCtx);
|
|
789
|
+
logQuery(workspaceRoot, model, query, (Date.now() - startTime) / 1000);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
498
792
|
const config = MODEL_CONFIGS[model];
|
|
499
793
|
if (!config) {
|
|
500
794
|
throw new Error(`Unknown model: ${model}`);
|
|
@@ -504,31 +798,8 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
504
798
|
throw new Error(`${config.cli} not found. Please install it first.`);
|
|
505
799
|
}
|
|
506
800
|
let tempFile = null;
|
|
507
|
-
const env = {};
|
|
508
801
|
let cmd;
|
|
509
|
-
|
|
510
|
-
// Used for gemini to avoid V8 heap exhaustion on large prompts (#680).
|
|
511
|
-
let stdinPayload = null;
|
|
512
|
-
if (model === 'gemini') {
|
|
513
|
-
// Gemini uses GEMINI_SYSTEM_MD env var for role
|
|
514
|
-
tempFile = path.join(tmpdir(), `codev-role-${Date.now()}.md`);
|
|
515
|
-
fs.writeFileSync(tempFile, role);
|
|
516
|
-
env['GEMINI_SYSTEM_MD'] = tempFile;
|
|
517
|
-
// Bugfix #680: gemini-cli v0.37.x crashes on large PR diffs (>500KB) due to
|
|
518
|
-
// V8 old-space exhaustion in the spawned subprocess. Mitigations:
|
|
519
|
-
// 1. Bump heap via NODE_OPTIONS (survives gemini-cli's internal relaunch).
|
|
520
|
-
// 2. Pipe the prompt via stdin instead of argv — avoids ARG_MAX and keeps
|
|
521
|
-
// V8 from holding the full prompt buffer twice.
|
|
522
|
-
env['NODE_OPTIONS'] = [process.env.NODE_OPTIONS ?? '', '--max-old-space-size=8192']
|
|
523
|
-
.join(' ')
|
|
524
|
-
.trim();
|
|
525
|
-
stdinPayload = query;
|
|
526
|
-
// Use --output-format json to capture token usage/cost in structured output.
|
|
527
|
-
// Never use --yolo — it allows Gemini to write files (#370).
|
|
528
|
-
// No positional query arg: prompt arrives on stdin (triggers non-interactive mode).
|
|
529
|
-
cmd = [config.cli, '--output-format', 'json', ...config.args];
|
|
530
|
-
}
|
|
531
|
-
else if (model === 'hermes') {
|
|
802
|
+
if (model === 'hermes') {
|
|
532
803
|
// Hermes does not have a dedicated system prompt flag for single-shot mode.
|
|
533
804
|
// Include role context at the top of the prompt.
|
|
534
805
|
const hermesPrompt = `${role}\n\n---\n\n${query}`;
|
|
@@ -551,27 +822,14 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
|
|
|
551
822
|
else {
|
|
552
823
|
throw new Error(`Unknown model: ${model}`);
|
|
553
824
|
}
|
|
554
|
-
// Execute with passthrough stdio.
|
|
555
|
-
//
|
|
556
|
-
// Use 'pipe' when we need to stream the prompt in (e.g. gemini, see #680).
|
|
557
|
-
const fullEnv = { ...process.env, ...env };
|
|
825
|
+
// Execute with passthrough stdio. stdin is 'ignore' (hermes passes its prompt
|
|
826
|
+
// via argv) — prevents blocking when spawned as a subprocess.
|
|
558
827
|
const startTime = Date.now();
|
|
559
|
-
const stdinMode = stdinPayload !== null ? 'pipe' : 'ignore';
|
|
560
828
|
return new Promise((resolve, reject) => {
|
|
561
829
|
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
562
830
|
cwd: workspaceRoot,
|
|
563
|
-
|
|
564
|
-
stdio: [stdinMode, 'pipe', 'inherit'],
|
|
831
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
565
832
|
});
|
|
566
|
-
if (stdinPayload !== null && proc.stdin) {
|
|
567
|
-
proc.stdin.on('error', (err) => {
|
|
568
|
-
// EPIPE can happen if the child exits before reading all input — not fatal.
|
|
569
|
-
if (err.code !== 'EPIPE') {
|
|
570
|
-
reject(err);
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
proc.stdin.end(stdinPayload, 'utf-8');
|
|
574
|
-
}
|
|
575
833
|
const chunks = [];
|
|
576
834
|
if (proc.stdout) {
|
|
577
835
|
proc.stdout.on('data', (chunk) => {
|
|
@@ -774,9 +1032,10 @@ function buildPRQuery(prId) {
|
|
|
774
1032
|
const prData = fetchPRData(prId);
|
|
775
1033
|
const diff = fetchPRDiff(prId);
|
|
776
1034
|
// Private-per-user dir to avoid world-readable /tmp diffs + symlink/clobber
|
|
777
|
-
// races:
|
|
1035
|
+
// races: consultSandboxDir() is a fresh mkdtempSync dir owned by us (and the
|
|
1036
|
+
// only temp dir granted to the sandboxed agy reviewer); writeFileSync with
|
|
778
1037
|
// flag 'wx' refuses to follow a symlink or overwrite an existing file.
|
|
779
|
-
const diffDir =
|
|
1038
|
+
const diffDir = consultSandboxDir();
|
|
780
1039
|
const diffPath = path.join(diffDir, `pr-${prId}.diff`);
|
|
781
1040
|
fs.writeFileSync(diffPath, diff, { encoding: 'utf-8', mode: 0o600, flag: 'wx' });
|
|
782
1041
|
const diffBytes = Buffer.byteLength(diff, 'utf-8');
|
|
@@ -1400,5 +1659,5 @@ export async function consult(options) {
|
|
|
1400
1659
|
await runConsultation(model, query, workspaceRoot, role, outputPath, metricsCtx, isGeneralMode);
|
|
1401
1660
|
}
|
|
1402
1661
|
// Exported for testing
|
|
1403
|
-
export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, buildPRQuery as _buildPRQuery, composePRQueryText as _composePRQueryText, computePersistentOutputPath as _computePersistentOutputPath, MODEL_CONFIGS as _MODEL_CONFIGS, };
|
|
1662
|
+
export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, buildPRQuery as _buildPRQuery, composePRQueryText as _composePRQueryText, computePersistentOutputPath as _computePersistentOutputPath, MODEL_CONFIGS as _MODEL_CONFIGS, MODEL_ALIASES as _MODEL_ALIASES, runAgyConsultation as _runAgyConsultation, agySkipContent as _agySkipContent, };
|
|
1404
1663
|
//# sourceMappingURL=index.js.map
|