@uzysjung/agent-harness 26.136.0 → 26.139.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,14 +4,22 @@
4
4
  # on stdout, wrapped in <untrusted-gemini-output> tags; progress → stderr.
5
5
  #
6
6
  # Usage:
7
- # gemini-ask.sh [-m "MODEL"] "PROMPT" # text (Korean polish / persona review)
8
- # gemini-ask.sh [-m "MODEL"] <<'EOF' # multi-line prompt via stdin
7
+ # gemini-ask.sh [-t TIER] [-m MODEL] "PROMPT" # text (Korean polish / persona review)
8
+ # gemini-ask.sh [-t TIER] [-m MODEL] <<'EOF' # multi-line prompt via stdin
9
9
  # ...multi-line prompt...
10
10
  # EOF
11
- # gemini-ask.sh -g OUT_DIR [-m "MODEL"] "PROMPT" # image gen: files copied to OUT_DIR
12
- # gemini-ask.sh -- "-leading-dash prompt" # `--` ends option parsing
11
+ # gemini-ask.sh -g OUT_DIR [-t TIER] "PROMPT" # image gen: files copied to OUT_DIR
12
+ # gemini-ask.sh -- "-leading-dash prompt" # `--` ends option parsing
13
13
  #
14
- # Exit codes: 2 usage · 3 agy missing · 4 secret-shaped prompt refused ·
14
+ # TIER (default: pro) selects a model FAMILY; the concrete model is resolved
15
+ # from `agy models` at call time, so no version string is pinned in here:
16
+ # pro — gemini-*-pro-* : every Gemini call this skill makes
17
+ # claude — claude-* : a non-Gemini reply, or the Gemini quota is spent
18
+ # `-m SLUG` skips tier resolution entirely — pass an exact slug from
19
+ # `agy models`, or a custom model configured in agy's settings.
20
+ #
21
+ # Exit codes: 2 usage · 3 agy missing / no model matches TIER ·
22
+ # 4 secret-shaped prompt refused ·
15
23
  # 5 image mode produced no files · 124 timed out ·
16
24
  # otherwise agy's own exit code.
17
25
  #
@@ -25,20 +33,22 @@
25
33
  set -euo pipefail
26
34
 
27
35
  AGY="${AGY_BIN:-$(command -v agy || echo "$HOME/.local/bin/agy")}"
28
- # The "gemini 3.1 pro" the user asked for. Override with -m or GEMINI_CONSULT_MODEL.
29
- # See `agy models` for the full list (e.g. "Gemini 3.1 Pro (Low)" is faster).
30
- MODEL="${GEMINI_CONSULT_MODEL:-Gemini 3.1 Pro (High)}"
36
+ # Pick a FAMILY (see resolution below), never a version. Empty MODEL means
37
+ # "resolve TIER against `agy models`"; a non-empty one is used verbatim.
38
+ TIER="${GEMINI_CONSULT_TIER:-pro}"
39
+ MODEL="${GEMINI_CONSULT_MODEL:-}"
31
40
  TIMEOUT_S="${GEMINI_CONSULT_TIMEOUT:-300}"
32
41
  # Where agy drops generated artifacts (images land here, not in cwd, because
33
42
  # headless mode denies the shell "command" permission a cwd-save would need).
34
43
  BRAIN_DIR="${GEMINI_CONSULT_BRAIN:-$HOME/.gemini/antigravity-cli/brain}"
35
44
  OUTDIR=""
36
45
 
37
- while getopts "m:g:" opt; do
46
+ while getopts "m:g:t:" opt; do
38
47
  case "$opt" in
39
48
  m) MODEL="$OPTARG" ;;
40
49
  g) OUTDIR="$OPTARG" ;;
41
- *) echo 'usage: gemini-ask.sh [-m "MODEL"] [-g OUT_DIR] "PROMPT"' >&2; exit 2 ;;
50
+ t) TIER="$OPTARG" ;;
51
+ *) echo 'usage: gemini-ask.sh [-t pro|claude] [-m MODEL] [-g OUT_DIR] "PROMPT"' >&2; exit 2 ;;
42
52
  esac
43
53
  done
44
54
  shift $((OPTIND - 1))
@@ -99,9 +109,62 @@ fi
99
109
  # Neutral cwd → agy won't pull the current repo into its workspace context.
100
110
  WORKDIR="$(mktemp -d)"
101
111
  MSGFILE="$(mktemp)"
102
- trap 'rm -rf "$WORKDIR" "$MSGFILE" ${MARKER:+"$MARKER"}' EXIT
112
+ MODELS_ERR=""
113
+ trap 'rm -rf "$WORKDIR" "$MSGFILE" ${MARKER:+"$MARKER"} ${MODELS_ERR:+"$MODELS_ERR"}' EXIT
103
114
  cd "$WORKDIR"
104
115
 
116
+ # Tier → concrete model, resolved from `agy models` on every call. Pinning a
117
+ # version here rots: agy retires slugs (an unknown one dies with "invalid model
118
+ # selection", exit 1), and the families do NOT share a version line — measured
119
+ # 2026-07-26, a flash line was 3.6 while pro was still 3.1 — so neither "newest" nor
120
+ # "best" can be spelled as a constant.
121
+ if [ -z "$MODEL" ]; then
122
+ case "$TIER" in
123
+ pro) TIER_RE='^gemini-.*-pro(-|$)' ;;
124
+ claude) TIER_RE='^claude-' ;;
125
+ *) echo "gemini-ask.sh: unknown tier '$TIER' (expected pro | claude; use -m for an exact slug)" >&2; exit 2 ;;
126
+ esac
127
+ MODELS_ERR="$(mktemp)"
128
+ MODELS="$(env -i PATH="$PATH" HOME="$HOME" TERM="${TERM:-dumb}" "$AGY" models 2>"$MODELS_ERR")" || MODELS=""
129
+ # Rank candidates: version desc, then capability (opus > sonnet), then effort
130
+ # (high/thinking > medium > low). Digits are read positionally so both slug
131
+ # shapes rank alike — `gemini-3.1-pro-high` and `claude-opus-4-6-thinking`.
132
+ # Fixed-width keys mean a plain `sort -r` suffices (BSD sort has no -V), and
133
+ # `awk NR==1` (not `head -1`) avoids a SIGPIPE under `set -o pipefail`.
134
+ MODEL="$(printf '%s\n' "$MODELS" | grep -E "$TIER_RE" | awk -F- '
135
+ {
136
+ maj = 0; min = 0; qual = 0; eff = 0
137
+ for (i = 1; i <= NF; i++) {
138
+ if ($i ~ /^[0-9]+(\.[0-9]+)?$/) {
139
+ if (maj == 0) { split($i, v, "."); maj = v[1] + 0; min = v[2] + 0 }
140
+ else if (min == 0) { min = $i + 0 }
141
+ }
142
+ if ($i == "opus") qual = 2
143
+ else if ($i == "sonnet") qual = 1
144
+ if ($i == "high" || $i == "thinking") eff = 3
145
+ else if ($i == "medium") eff = 2
146
+ else if ($i == "low") eff = 1
147
+ }
148
+ printf "%03d%03d%d%d %s\n", maj, min, qual, eff, $0
149
+ }' | sort -r | awk 'NR == 1 { print $2 }')" || MODEL=""
150
+ if [ -z "$MODEL" ]; then
151
+ # Never silently fall back to some other tier: the caller asked for this
152
+ # family for a reason, and a wrong-family answer looks like a right one.
153
+ echo "gemini-ask.sh: no '$TIER' model offered by \`agy models\` — available:" >&2
154
+ printf '%s\n' "$MODELS" | sed 's/^/ /' >&2
155
+ if [ -s "$MODELS_ERR" ]; then
156
+ # Surface agy's own stderr — an auth/network failure here looks exactly
157
+ # like "tier not offered" if you only see the empty list.
158
+ echo "gemini-ask.sh: \`agy models\` stderr:" >&2
159
+ cat "$MODELS_ERR" >&2
160
+ fi
161
+ exit 3
162
+ fi
163
+ echo "gemini-ask.sh: tier '$TIER' → model '$MODEL'" >&2
164
+ else
165
+ echo "gemini-ask.sh: model '$MODEL' (explicit)" >&2
166
+ fi
167
+
105
168
  # Env allowlist: don't hand agy ambient tokens (GH_TOKEN, AWS_*, ...). HOME
106
169
  # stays so agy auth (~/.gemini) keeps working.
107
170
  # NOTE: --model must precede -p, and the prompt must be the value right after
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ui-visual-review
3
- description: "Captures screenshots of key UI flows after E2E tests pass, runs an agent-side first-pass diff (regressions, console errors, layout shifts), then surfaces a checklist for the user's final approval. Use after E2E tests pass on a UI track (csr-*, ssr-*, full)."
3
+ description: "Captures screenshots of key UI flows after E2E tests pass, runs an agent-side first-pass diff (regressions, console errors, layout shifts), then surfaces a checklist for the user's final approval. Also owns the browser-launch procedure the `playwright-launch` rule delegates here: use it whenever a browser must be opened for a human to drive or for automated capture — manual E2E checks, UX/fidelity comparison against a reference product, or a one-time OAuth login. Use after E2E tests pass on a UI track (csr-*, ssr-*, full)."
4
4
  ---
5
5
 
6
6
  # UI Visual Review
@@ -31,6 +31,56 @@ E2E 테스트가 PASS 했어도 시각적 회귀(layout shift, 색상/간격 변
31
31
  - 앱이 로컬에서 기동 가능 (예: `pnpm dev`, `docker-compose up`)
32
32
  - 핵심 화면 URL 리스트가 정의됨 (없으면 본 skill 첫 실행 시 사용자 질의)
33
33
 
34
+ ## 브라우저를 띄우는 법 (영속 profile)
35
+
36
+ `playwright-launch` 룰이 **금지**를 소유하고, 여기가 **절차**를 소유한다. 금지문은 상주해야
37
+ 하지만(위반은 작업 도중에 일어난다) 아래 절차는 실제로 브라우저를 띄울 때만 필요하다.
38
+
39
+ 핵심은 셋이다. ⓐ **영속 profile dir** — 프로젝트별로 분리해 매 iteration 재사용한다. cookie ·
40
+ IndexedDB · Service Worker 가 보존돼야 재로그인이 사라진다. ⓑ **Chrome for Testing 별도 binary**
41
+ (`npx playwright install chromium`) — 사용자의 일반 Chrome 과 완전히 분리한다. ⓒ **사용자가 키를
42
+ 입력하는 동안 자동화 layer 0** — main session 은 창을 띄우기만 하고, capture·검증은 입력이 끝난 뒤
43
+ 별도 process 에서 돈다.
44
+
45
+ ```js
46
+ import { chromium } from 'playwright';
47
+
48
+ const PROFILE_DIR = process.env.PROJECT_PROFILE_DIR || `${process.env.HOME}/.<project>-audit-profile`;
49
+ const TARGET = process.env.PROJECT_URL || 'http://localhost:<port>';
50
+
51
+ const ctx = await chromium.launchPersistentContext(PROFILE_DIR, {
52
+ headless: false,
53
+ viewport: { width: 1440, height: 900 },
54
+ args: ['--disable-blink-features=AutomationControlled', '--no-first-run', '--no-default-browser-check'],
55
+ });
56
+ const page = ctx.pages()[0] ?? (await ctx.newPage());
57
+
58
+ // (선택) dev bypass auth — OAuth 의 webdriver 차단을 피한다. same-origin cookie 로 저장된다.
59
+ await page.goto(`${TARGET}/`, { waitUntil: 'domcontentloaded' });
60
+ await ctx.request.post(`${TARGET}/api/v1/_dev/test-login`, {
61
+ data: { key: process.env.E2E_TEST_KEY ?? 'e2e-dev-key', email: 'e2e@example.local' },
62
+ });
63
+ await page.reload({ waitUntil: 'networkidle' });
64
+
65
+ // 창을 띄운 채 자동화는 detach — 사용자가 직접 쓴다. ctx.close() 를 부르지 않는다.
66
+ ```
67
+
68
+ **두 가지 사용 형태**
69
+
70
+ 0. **기존 launcher 를 먼저 정리한다** — `pkill -f "playwright|Chrome for Testing"`. 같은 profile
71
+ dir 을 잡고 있는 창이 살아 있으면 `launchPersistentContext` 가 기동하지 못한다. 첫 실행은 되고
72
+ **2회차부터** 실패하기 때문에 프로필 잠금이 아니라 스크립트 버그로 오진하기 쉽다.
73
+ 1. **사용자가 직접 보는 경우** — `node scripts/<project>-launch.mjs` 를 포그라운드로 띄우고
74
+ "URL / 로그인됨 / 직접 쓰세요"를 보고한 뒤, 추가 navigation·evaluate 를 **하지 않는다**.
75
+ 사용자 입력과 충돌한다.
76
+ 2. **자동 capture (fidelity·audit)** — 같은 `launchPersistentContext` 로 시나리오를 자동 click 하고
77
+ 산출물을 `docs/research/<area>_audit_<sprint>/iter_<N>/` 에 남긴다.
78
+
79
+ > **이 launcher 로 띄운 창은 Playwright API 로 캡처한다**(`page.screenshot({ fullPage: true })`,
80
+ > 콘솔은 `page.on('console')`). 아래 §2·§4 의 chrome-devtools MCP 예시는 launcher 없이 MCP 로
81
+ > 작업하는 경우다 — Playwright 는 CDP 를 파이프로 물어 외부 프로세스가 붙을 엔드포인트를 열지
82
+ > 않으므로, 이 둘을 한 세션에서 섞으려 하면 붙을 대상이 없다. **하나를 고르고 끝까지 그걸로 간다.**
83
+
34
84
  ## Process
35
85
 
36
86
  ### 1. 화면 리스트업 (한 번만)