leerness 1.9.247 → 1.9.248
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 +35 -0
- package/README.md +3 -3
- package/bin/harness.js +66 -63
- package/package.json +1 -1
- package/scripts/e2e.js +17 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.248 — 2026-05-26 — UR-0017 Gemini CLI 제거 + Antigravity CLI (agy) 도입
|
|
4
|
+
|
|
5
|
+
**🔄 사용자 명시 (UR-0017): Gemini CLI 제거 + Antigravity (agy) CLI 전체 교체.**
|
|
6
|
+
|
|
7
|
+
### 변경 범위 (65 occurrences case-insensitive)
|
|
8
|
+
- **EXTERNAL_AGENTS**: `gemini` → `agy` (bin/envFlag/install 안내 모두 교체)
|
|
9
|
+
- **_PROVIDER_CYCLE_ORDER**: `['ollama', 'claude', 'codex', 'agy', 'copilot']` (Tab cycle)
|
|
10
|
+
- **_PROVIDER_MODEL_CATALOG.agy**: `antigravity-pro/flash/experimental` 모델 (1M+ context)
|
|
11
|
+
- **환경변수**: `LEERNESS_ENABLE_GEMINI` → `LEERNESS_ENABLE_AGY`
|
|
12
|
+
- **CLI bin**: `gemini -p` → `agy -p` (1-shot + REPL stream + dispatch)
|
|
13
|
+
- **agents dispatch --write**: `gemini --yolo` → `agy --yolo`
|
|
14
|
+
- **setup-agents UI**: "Gemini (gemini CLI)" → "Antigravity (agy CLI)"
|
|
15
|
+
- **handoff body sub-agent display** (1.9.246 status bar): allAgents = ['claude', 'codex', 'agy', 'copilot']
|
|
16
|
+
- **MCP description**: agents_list / provider_list 등 5종 → agy 명시
|
|
17
|
+
- **REPL :provider validation**: validProviders = ['ollama', 'claude', 'codex', 'agy', 'copilot']
|
|
18
|
+
- **.harness/leerness-config.json**: LEERNESS_ENABLE_AGY 기본값 "0"
|
|
19
|
+
- **scripts/e2e.js**: 회귀 테스트 — agents quota/dispatch/list/register-pending 모두 agy
|
|
20
|
+
- **install hint**: `npm i -g @google/antigravity-cli` (https://antigravity.google.com)
|
|
21
|
+
|
|
22
|
+
### 영향 받지 않은 영역
|
|
23
|
+
- 1.9.247 multi-provider fallback (agy 도 자동 fallback 대상)
|
|
24
|
+
- 1.9.246 status bar (agy 5종 한 줄 표시)
|
|
25
|
+
- handoff JSON 11 필드 (apiSkills/envInfo 등) 유지
|
|
26
|
+
|
|
27
|
+
### stress-v193 — **23/23 PASS · 100%**
|
|
28
|
+
- 1.9.248 (12): VERSION + EXTERNAL_AGENTS 교체 + 모델 catalog + Tab cycle + setup-agents UI + handoff status bar + dispatch + quota + list + config.json + validation + nonsecret keys
|
|
29
|
+
- 성능 (1): cold start avg 419ms
|
|
30
|
+
- 누적 회귀 (10): 1.9.207~247
|
|
31
|
+
|
|
32
|
+
### 자동 release (110 main-push streak · 71 npm publish streak · R204)
|
|
33
|
+
|
|
34
|
+
🔄 **Gemini CLI 단계적 제거 → Antigravity (agy) 전환** — 사용자 환경 변화 즉시 반영
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
3
38
|
## 1.9.247 — 2026-05-26 — UR-0016 2단계 + UR-0015 2단계
|
|
4
39
|
|
|
5
40
|
**🔁 REPL multi-provider auto-fallback + 📚 api-skill audit 통합.**
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []() []() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.248 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
|
-
║
|
|
17
|
+
║ 🔄 Gemini CLI 제거 → Antigravity (agy) CLI 도입 (UR-0017) ║
|
|
18
18
|
╚══════════════════════════════════════════════════════════════╝
|
|
19
19
|
```
|
|
20
20
|
|
package/bin/harness.js
CHANGED
|
@@ -7,7 +7,7 @@ const cp = require('child_process');
|
|
|
7
7
|
const os = require('os'); // 1.9.178: _publishToNpm 에서 os.tmpdir() 사용 (전역 import)
|
|
8
8
|
const readline = require('readline');
|
|
9
9
|
|
|
10
|
-
const VERSION = '1.9.
|
|
10
|
+
const VERSION = '1.9.248';
|
|
11
11
|
|
|
12
12
|
// 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
|
|
13
13
|
// leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
|
|
@@ -410,7 +410,7 @@ leerness agents dispatch "<task>" --to <id> # 작업 유형 추천 자동
|
|
|
410
410
|
- 작업 유형별 최적 sub-agent:
|
|
411
411
|
- 텍스트/번역/분석 → claude (1.7× 빠름)
|
|
412
412
|
- 깊은 코드 추론 → codex (가장 상세)
|
|
413
|
-
- 파일 직접 수정 →
|
|
413
|
+
- 파일 직접 수정 → agy --yolo (정확, Antigravity CLI 1.9.248)
|
|
414
414
|
- 보안 리뷰 → \`leerness review --persona security\`
|
|
415
415
|
- **충돌 방지 규칙 (필수)**:
|
|
416
416
|
- 각 sub-agent에 *자신만 수정할 파일 경로* 명시
|
|
@@ -798,20 +798,20 @@ async function resolveInstallOptions(root, opts = {}) {
|
|
|
798
798
|
const picked = await _selectMany(_t('install.agents.title', _agLang), [
|
|
799
799
|
{ label: 'Claude (ANTHROPIC_API_KEY 또는 claude CLI)', description: '추론력 최고 — 코드 작성/리뷰 기본', id: 'claude' },
|
|
800
800
|
{ label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
|
|
801
|
-
{ label: '
|
|
801
|
+
{ label: 'Antigravity (agy CLI)', description: 'Google Antigravity 멀티모달 에이전트 (1.9.248~)', id: 'agy' },
|
|
802
802
|
{ label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
|
|
803
803
|
{ label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 — 무료/오프라인', id: 'ollama' }
|
|
804
804
|
], { defaults: [] });
|
|
805
805
|
agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
|
|
806
806
|
} else {
|
|
807
807
|
log('\nCLI 에이전트 활성화 (복수 선택 — 콤마로 구분, opt-in):');
|
|
808
|
-
log(' 1) claude 2) codex 3)
|
|
808
|
+
log(' 1) claude 2) codex 3) agy (Antigravity) 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
|
|
809
809
|
const a = (await ask('선택 [none]: ')).trim().toLowerCase();
|
|
810
|
-
if (a === 'all') agentsOptIn = ['claude', 'codex', '
|
|
810
|
+
if (a === 'all') agentsOptIn = ['claude', 'codex', 'agy', 'copilot', 'ollama'];
|
|
811
811
|
else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
|
|
812
812
|
else {
|
|
813
|
-
const map = { '1': 'claude', '2': 'codex', '3': '
|
|
814
|
-
const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','
|
|
813
|
+
const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'copilot', '5': 'ollama' };
|
|
814
|
+
const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','agy','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
|
|
815
815
|
agentsOptIn = picks.length ? picks : 'none';
|
|
816
816
|
}
|
|
817
817
|
}
|
|
@@ -951,7 +951,7 @@ async function install(root, opts = {}) {
|
|
|
951
951
|
const a = resolved.agentsOptIn || 'none';
|
|
952
952
|
const enabledSet = (() => {
|
|
953
953
|
if (Array.isArray(a)) return new Set(a);
|
|
954
|
-
if (a === 'all') return new Set(['claude', 'codex', '
|
|
954
|
+
if (a === 'all') return new Set(['claude', 'codex', 'agy', 'copilot', 'ollama']);
|
|
955
955
|
if (a === 'none' || !a) return new Set();
|
|
956
956
|
return new Set([a]); // back-compat: 단일 문자열
|
|
957
957
|
})();
|
|
@@ -983,7 +983,7 @@ async function install(root, opts = {}) {
|
|
|
983
983
|
LEERNESS_OLLAMA_MODEL: '',
|
|
984
984
|
LEERNESS_ENABLE_CLAUDE: enable('claude') ? '1' : '0',
|
|
985
985
|
LEERNESS_ENABLE_CODEX: enable('codex') ? '1' : '0',
|
|
986
|
-
|
|
986
|
+
LEERNESS_ENABLE_AGY: enable('agy') ? '1' : '0',
|
|
987
987
|
LEERNESS_ENABLE_COPILOT: enable('copilot') ? '1' : '0',
|
|
988
988
|
LEERNESS_ENABLE_OLLAMA: enable('ollama') ? '1' : '0',
|
|
989
989
|
LEERNESS_SKILL_DISCOVER_URL: '',
|
|
@@ -8808,7 +8808,7 @@ const _LEERNESS_NONSECRET_KEYS = new Set([
|
|
|
8808
8808
|
'LEERNESS_OLLAMA_MODEL', // 모델 이름 — 비밀 X
|
|
8809
8809
|
'LEERNESS_ENABLE_CLAUDE', // 활성화 플래그
|
|
8810
8810
|
'LEERNESS_ENABLE_CODEX',
|
|
8811
|
-
'
|
|
8811
|
+
'LEERNESS_ENABLE_AGY',
|
|
8812
8812
|
'LEERNESS_ENABLE_COPILOT',
|
|
8813
8813
|
'LEERNESS_ENABLE_OLLAMA',
|
|
8814
8814
|
'LEERNESS_SKILL_DISCOVER_URL', // 공개 URL
|
|
@@ -9452,7 +9452,7 @@ function _resolvePersona(root, id) {
|
|
|
9452
9452
|
return null;
|
|
9453
9453
|
}
|
|
9454
9454
|
|
|
9455
|
-
// 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/
|
|
9455
|
+
// 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/agy/copilot 가용성 + 활성화 체크
|
|
9456
9456
|
// 사용자 정책: 환경변수로 활성화 명시 + 실제 PATH 존재 확인 + 메인이 sub-agent 분배 시 참조
|
|
9457
9457
|
// 1.9.32: installCmd 추가 — setup-agents 시 자동 설치 시도 가능
|
|
9458
9458
|
const EXTERNAL_AGENTS = [
|
|
@@ -9460,8 +9460,11 @@ const EXTERNAL_AGENTS = [
|
|
|
9460
9460
|
installCmd: 'npm i -g @anthropic-ai/claude-code', installHint: 'https://docs.anthropic.com/en/docs/claude-code/setup' },
|
|
9461
9461
|
{ id: 'codex', bin: 'codex', envFlag: 'LEERNESS_ENABLE_CODEX', versionArgs: ['--version'], desc: 'OpenAI Codex CLI (격리 sandbox)',
|
|
9462
9462
|
installCmd: 'npm i -g @openai/codex', installHint: 'https://github.com/openai/codex' },
|
|
9463
|
-
|
|
9464
|
-
|
|
9463
|
+
// 1.9.248 (사용자 명시 UR-0017): Gemini CLI 제거 + Antigravity CLI (agy 명령어) 교체
|
|
9464
|
+
// Google Antigravity 는 IDE 기반 멀티 모달 에이전트 도구. agy CLI 는 워크스페이스 직접 수정 가능 모드 지원.
|
|
9465
|
+
// 기존 LEERNESS_ENABLE_AGY 환경변수는 LEERNESS_ENABLE_AGY 로 교체. provider id 도 'agy' 로 통일.
|
|
9466
|
+
{ id: 'agy', bin: 'agy', envFlag: 'LEERNESS_ENABLE_AGY', versionArgs: ['--version'], desc: 'Google Antigravity CLI (멀티모달 에이전트, --yolo 워크스페이스 수정 가능)',
|
|
9467
|
+
installCmd: 'npm i -g @google/antigravity-cli', installHint: 'https://antigravity.google.com (Antigravity IDE/CLI)' },
|
|
9465
9468
|
{ id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)',
|
|
9466
9469
|
installCmd: 'gh extension install github/gh-copilot', installHint: 'https://github.com/github/gh-copilot (gh CLI 선행 설치 필요)' },
|
|
9467
9470
|
// 1.9.146: Ollama 추가 (사용자 명시 요청 #3) — 로컬 LLM, HTTP API 11434
|
|
@@ -9668,7 +9671,7 @@ function providerCmd(root, sub, ...args) {
|
|
|
9668
9671
|
// 1.9.195: provider probe — A축 (범용 AI 하네스) 보강. 사용자 의도: "범용 AI 하네스 ... 최고의 도구"
|
|
9669
9672
|
// 시스템에 사용 가능한 provider 자동 감지 (PATH 명령 + 로컬 endpoint + API 키 환경변수)
|
|
9670
9673
|
// 감지 대상:
|
|
9671
|
-
// - CLI: claude / codex /
|
|
9674
|
+
// - CLI: claude / codex / agy / copilot / ollama (PATH check)
|
|
9672
9675
|
// - Local endpoint: Ollama (11434) / LM Studio (1234) / llama.cpp server (8080)
|
|
9673
9676
|
// - Cloud API key: OPENROUTER_API_KEY / GROQ_API_KEY / TOGETHER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY
|
|
9674
9677
|
// 결과 → table + LEERNESS_ENABLE_* 설정 권장
|
|
@@ -9721,7 +9724,7 @@ async function _probeProviderEndpoints(root, timeoutMs) {
|
|
|
9721
9724
|
const cliTargets = [
|
|
9722
9725
|
{ id: 'claude', bin: 'claude' },
|
|
9723
9726
|
{ id: 'codex', bin: 'codex' },
|
|
9724
|
-
{ id: '
|
|
9727
|
+
{ id: 'agy', bin: 'agy' },
|
|
9725
9728
|
{ id: 'copilot', bin: 'copilot' },
|
|
9726
9729
|
{ id: 'ollama', bin: 'ollama' }
|
|
9727
9730
|
];
|
|
@@ -9830,7 +9833,7 @@ function _recommendAgent(task) {
|
|
|
9830
9833
|
// 파일 작성·수정·생성
|
|
9831
9834
|
if (hasAny(['create', 'write', 'generate', 'patch', 'fix', 'implement', 'edit',
|
|
9832
9835
|
'구현', '생성', '작성', '수정', '추가'])) {
|
|
9833
|
-
return { target: '
|
|
9836
|
+
return { target: 'agy', reason: '워크스페이스 직접 수정은 agy --yolo (Antigravity) 가 정확' };
|
|
9834
9837
|
}
|
|
9835
9838
|
return { target: null, reason: '' };
|
|
9836
9839
|
}
|
|
@@ -10346,7 +10349,7 @@ function _dispatchCommand(agentId, task, writeMode) {
|
|
|
10346
10349
|
const q = String(task || '').replace(/"/g, '\\"');
|
|
10347
10350
|
if (agentId === 'claude') return `claude ${writeMode ? '--print --dangerously-skip-permissions' : '--print'} "${q}"`;
|
|
10348
10351
|
if (agentId === 'codex') return `codex ${writeMode ? 'exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox' : 'exec --skip-git-repo-check'} "${q}"`;
|
|
10349
|
-
if (agentId === '
|
|
10352
|
+
if (agentId === 'agy') return `agy ${writeMode ? '-p --yolo' : '-p'} "${q}"`;
|
|
10350
10353
|
if (agentId === 'copilot') return `gh copilot suggest "${q}"`;
|
|
10351
10354
|
if (agentId === 'ollama') return `# ollama — leerness agent "${q}" --provider ollama 로 직접 호출 (REPL: leerness agent)`;
|
|
10352
10355
|
return `# ${agentId}: 명령 빌더 미정의`;
|
|
@@ -10380,8 +10383,8 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10380
10383
|
if (!ready.length) {
|
|
10381
10384
|
log('');
|
|
10382
10385
|
log(`💡 활성화 방법:`);
|
|
10383
|
-
log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/
|
|
10384
|
-
log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1,
|
|
10386
|
+
log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/antigravity-cli\`)`);
|
|
10387
|
+
log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1, LEERNESS_ENABLE_AGY=1`);
|
|
10385
10388
|
log(` 3) \`leerness agents check\`로 재확인`);
|
|
10386
10389
|
log(` 💡 1.9.157: 빌트인 외 CLI 추가: \`leerness provider add <id> --bin <cmd>\``);
|
|
10387
10390
|
} else {
|
|
@@ -10564,7 +10567,7 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10564
10567
|
if (has('--multi') || target === 'all' || target === '*') {
|
|
10565
10568
|
return agentsCmd(root, 'multi', ...args);
|
|
10566
10569
|
}
|
|
10567
|
-
if (!target) { fail('--to <agent_id> 필요 (claude/codex/
|
|
10570
|
+
if (!target) { fail('--to <agent_id> 필요 (claude/codex/agy/copilot) — 활성 전체에 일괄 분배는 `leerness agents multi`'); return process.exit(1); }
|
|
10568
10571
|
const agentDef = EXTERNAL_AGENTS.find(a => a.id === target);
|
|
10569
10572
|
if (!agentDef) { fail(`알 수 없는 agent: ${target}`); return process.exit(1); }
|
|
10570
10573
|
// 1.9.36: 작업 유형 키워드 분석 → 최적 CLI 추천 (ready 체크 전에 출력 — 비활성이어도 추천)
|
|
@@ -10599,9 +10602,9 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10599
10602
|
log(`codex ${flags} "${q}"`);
|
|
10600
10603
|
log(`# ℹ codex는 PowerShell 경유 — POSIX /tmp 경로는 C:\\tmp\\로 해석됨`);
|
|
10601
10604
|
if (writeMode) log(`# ⚠ --dangerously-bypass-approvals-and-sandbox: sandbox 우회`);
|
|
10602
|
-
} else if (target === '
|
|
10605
|
+
} else if (target === 'agy') {
|
|
10603
10606
|
const flags = writeMode ? '-p --yolo' : '-p';
|
|
10604
|
-
log(`
|
|
10607
|
+
log(`agy ${flags} "${q}"`);
|
|
10605
10608
|
if (writeMode) log(`# ⚠ --yolo: 워크스페이스 파일 직접 수정 가능`);
|
|
10606
10609
|
} else if (target === 'copilot') {
|
|
10607
10610
|
log(`gh copilot suggest "${q}"`);
|
|
@@ -10652,9 +10655,9 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10652
10655
|
? ['exec', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', task]
|
|
10653
10656
|
: ['exec', '--skip-git-repo-check', task];
|
|
10654
10657
|
cmd = 'codex';
|
|
10655
|
-
} else if (agent.id === '
|
|
10658
|
+
} else if (agent.id === 'agy') {
|
|
10656
10659
|
cmdArgs = writeMode ? ['-p', task, '--yolo'] : ['-p', task];
|
|
10657
|
-
cmd = '
|
|
10660
|
+
cmd = 'agy';
|
|
10658
10661
|
} else if (agent.id === 'copilot') {
|
|
10659
10662
|
cmdArgs = ['copilot', 'suggest', task];
|
|
10660
10663
|
cmd = 'gh';
|
|
@@ -10728,10 +10731,10 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10728
10731
|
out.hint = 'https://platform.openai.com/account/usage 확인';
|
|
10729
10732
|
}
|
|
10730
10733
|
out.raw = help.slice(0, 200);
|
|
10731
|
-
} else if (agent.id === '
|
|
10732
|
-
//
|
|
10734
|
+
} else if (agent.id === 'agy') {
|
|
10735
|
+
// agy CLI (Antigravity): 무료 티어는 분당 60req 제한, CLI 자체에선 노출 안 됨
|
|
10733
10736
|
out.quota = 'rate-limited';
|
|
10734
|
-
out.hint = '무료 티어: 60 req/min, 1000 req/day ·
|
|
10737
|
+
out.hint = '무료 티어: 60 req/min, 1000 req/day · Antigravity 유료 플랜은 https://antigravity.google.com';
|
|
10735
10738
|
} else if (agent.id === 'copilot') {
|
|
10736
10739
|
// gh copilot은 GitHub Copilot 구독 (월 단위 quota 없음, individual/business 플랜)
|
|
10737
10740
|
const r = cp.spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 4000, shell: true });
|
|
@@ -10873,13 +10876,13 @@ function reviewCmd(root, target) {
|
|
|
10873
10876
|
}
|
|
10874
10877
|
|
|
10875
10878
|
// 1.9.25: register-pending — sub-agent/외부 모델이 작업 시작 즉시 progress-tracker에 in-progress 등록
|
|
10876
|
-
// 사용 예: leerness register-pending "<요청 내용>" --agent
|
|
10879
|
+
// 사용 예: leerness register-pending "<요청 내용>" --agent agy
|
|
10877
10880
|
// → 다음 T-ID 자동 할당, status=in-progress, evidence="(pending) by <agent>"
|
|
10878
10881
|
// → 다른 세션이 즉시 발견 가능 (모순 감지)
|
|
10879
10882
|
function registerPendingCmd(root, requestParts) {
|
|
10880
10883
|
root = absRoot(root || process.cwd());
|
|
10881
10884
|
const request = (requestParts || []).join(' ').trim();
|
|
10882
|
-
if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent
|
|
10885
|
+
if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent agy'); return process.exit(1); }
|
|
10883
10886
|
const agent = arg('--agent', 'unknown');
|
|
10884
10887
|
const note = arg('--note', '');
|
|
10885
10888
|
|
|
@@ -14952,7 +14955,7 @@ async function _benchmarkMeasure(root, task) {
|
|
|
14952
14955
|
let cmd, cliArgs;
|
|
14953
14956
|
if (agent.id === 'claude') { cmd = 'claude'; cliArgs = ['--print', task]; }
|
|
14954
14957
|
else if (agent.id === 'codex') { cmd = 'codex'; cliArgs = ['exec', '--skip-git-repo-check', task]; }
|
|
14955
|
-
else if (agent.id === '
|
|
14958
|
+
else if (agent.id === 'agy') { cmd = 'agy'; cliArgs = ['-p', task]; }
|
|
14956
14959
|
else continue;
|
|
14957
14960
|
const t0 = Date.now();
|
|
14958
14961
|
// 1.9.150: runCommandSafe — agent CLI bench sandbox (env scrub + observability)
|
|
@@ -15428,7 +15431,7 @@ function mcpServeCmd(root) {
|
|
|
15428
15431
|
{ name: 'leerness_audit', description: '1.9.102 — 워크스페이스 일관성 감사 JSON (warnings/failures/fixed/healthy + findings[]. kind 11종: design_dup/design_system_default/reuse_map_empty/milestone_unlinked/handoff_not_generated/current_state_stale/readme_version_mismatch/npm_cve/gitignore_missing_secrets/env_keys_missing/strict_promoted)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, fix: { type: 'boolean' }, strict: { type: 'boolean' } } } },
|
|
15429
15432
|
{ name: 'leerness_verify_claim', description: 'AI 거짓 완료 자동 검증 (evidence 파일 + 실 테스트 실행)', inputSchema: { type: 'object', properties: { taskId: { type: 'string' }, path: { type: 'string' }, runTests: { type: 'boolean' }, strictClaims: { type: 'boolean' } }, required: ['taskId'] } },
|
|
15430
15433
|
{ name: 'leerness_contract_verify', description: '명세 ↔ 구현 함수/필드 일치 자동 검사', inputSchema: { type: 'object', properties: { spec: { type: 'string' }, impl: { type: 'string' } }, required: ['spec', 'impl'] } },
|
|
15431
|
-
{ name: 'leerness_agents_list', description: '외부 AI CLI 가용성 표 (claude/codex/
|
|
15434
|
+
{ name: 'leerness_agents_list', description: '외부 AI CLI 가용성 표 (claude/codex/agy/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
|
|
15432
15435
|
{ name: 'leerness_reuse_map', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
|
|
15433
15436
|
{ name: 'leerness_whats_new', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
|
|
15434
15437
|
{ name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
@@ -15470,7 +15473,7 @@ function mcpServeCmd(root) {
|
|
|
15470
15473
|
{ name: 'leerness_feature_add', description: '1.9.142 — Feature Graph 에 새 노드 추가 (외부 AI가 코드 작성 중 직접 feature 등록). 인자: { title (required), dependsOn?, affects?, coChangesWith?, files?, path? }. 자동 F-XXXX ID 부여. CRUD 완성에 기여', inputSchema: { type: 'object', properties: { title: { type: 'string' }, dependsOn: { type: 'string' }, affects: { type: 'string' }, coChangesWith: { type: 'string' }, files: { type: 'string' }, path: { type: 'string' } }, required: ['title'] } },
|
|
15471
15474
|
{ name: 'leerness_feature_link', description: '1.9.142 — 기존 feature 노드에 의존/영향/공변경 엣지 추가. 인자: { id (required, F-XXXX), dependsOn?, affects?, coChangesWith?, path? }. 외부 AI가 코드 변경 도중 발견한 인과관계를 즉시 그래프에 반영', inputSchema: { type: 'object', properties: { id: { type: 'string' }, dependsOn: { type: 'string' }, affects: { type: 'string' }, coChangesWith: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
15472
15475
|
{ name: 'leerness_env_detect', description: '1.9.145 — 실행 환경 자동 감지 + 변동 추적 JSON ({ snapshot: { os, hardware, locale, shell, node, tools, scriptDependencies }, diff: { firstCapture, changes, missing }, persisted }). "X은(는) 내부 또는 외부 명령... 아닙니다" 사전 방지: package.json scripts 의존 도구가 PATH에 있는지 검증 + 머신/Node/도구 변경 감지. 절대경로 마스킹 (보안). 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
15473
|
-
{ name: 'leerness_provider_list', description: '1.9.157/158 — Provider Registry 조회 JSON ({ total, builtin, user, providers: [{ id, bin, envFlag, source, desc }] }). 빌트인 5종 (claude/codex/
|
|
15476
|
+
{ name: 'leerness_provider_list', description: '1.9.157/158 — Provider Registry 조회 JSON ({ total, builtin, user, providers: [{ id, bin, envFlag, source, desc }] }). 빌트인 5종 (claude/codex/agy/copilot/ollama) + .harness/providers.json 사용자 정의 통합. 외부 AI가 sub-agent 분배 가능한 provider 전체 회수 (OpenRouter/Bedrock 등 등록되어 있으면 같이 노출). 🎉 MCP 48 도구 마일스톤', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
15474
15477
|
{ name: 'leerness_provider_add', description: '1.9.159 — Provider Registry 에 새 provider 동적 추가. 인자: { id (required), bin?, envFlag?, versionArgs?, desc?, path? }. 외부 AI가 새 CLI 발견 시 자가 확장 (OpenRouter / Bedrock / Groq / Hugging Face 등 등록). 같은 id 두 번 호출 → 갱신. 빌트인 id 호출 → user override. id 는 영문자/숫자/_- 만 허용.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, bin: { type: 'string' }, envFlag: { type: 'string' }, versionArgs: { type: 'string' }, desc: { type: 'string' }, installHint: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
15475
15478
|
{ name: 'leerness_provider_remove', description: '1.9.159 — Provider Registry 에서 사용자 정의 provider 제거. 인자: { id (required), path? }. 빌트인 5종 id 는 제거 불가 (override 만 제거 가능). 🎉 MCP 50 도구 마일스톤 — Provider Registry CRUD MCP 완성 (list/add/remove)', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
15476
15479
|
{ name: 'leerness_web', description: '1.9.168 — Web Bridge (1.9.165 playwright opt-in). sub: check (설치 + permissions.browser 확인) | screenshot (URL → PNG) | extract (URL + CSS selector → DOM 텍스트). 외부 AI가 leerness 의 웹 자동화 능력을 직접 호출. playwright 미설치 시 친절 안내 (graceful). 인자: { sub (required), url?, out?, selector?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'screenshot', 'extract'] }, url: { type: 'string' }, out: { type: 'string' }, selector: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } },
|
|
@@ -16163,13 +16166,13 @@ function permissionCheck(root, action, target) {
|
|
|
16163
16166
|
}
|
|
16164
16167
|
|
|
16165
16168
|
// ===== 1.9.146: leerness agent — OpenClaw/Hermes 스타일 오픈소스 CLI 에이전트 모드 (사용자 명시 요청 #4) =====
|
|
16166
|
-
// MVP: handoff 컨텍스트 자동 로드 → 활성 CLI (claude/codex/
|
|
16169
|
+
// MVP: handoff 컨텍스트 자동 로드 → 활성 CLI (claude/codex/agy/ollama) 1개에 작업 위임.
|
|
16167
16170
|
// 권한은 .harness/agent-permissions.json 기준. 실제 LLM 호출은 외부 CLI 또는 Ollama HTTP API.
|
|
16168
16171
|
function _activeCliAgents() {
|
|
16169
16172
|
const out = [];
|
|
16170
16173
|
if (process.env.LEERNESS_ENABLE_CLAUDE === '1') out.push('claude');
|
|
16171
16174
|
if (process.env.LEERNESS_ENABLE_CODEX === '1') out.push('codex');
|
|
16172
|
-
if (process.env.
|
|
16175
|
+
if (process.env.LEERNESS_ENABLE_AGY === '1') out.push('agy');
|
|
16173
16176
|
if (process.env.LEERNESS_ENABLE_COPILOT === '1') out.push('copilot');
|
|
16174
16177
|
if (process.env.LEERNESS_ENABLE_OLLAMA === '1') out.push('ollama');
|
|
16175
16178
|
return out;
|
|
@@ -16225,7 +16228,7 @@ async function _ollamaListModels() {
|
|
|
16225
16228
|
}
|
|
16226
16229
|
|
|
16227
16230
|
// 1.9.153: 외부 CLI 채팅 호출 (multi-provider REPL — 사용자 명시 요청)
|
|
16228
|
-
// claude/codex/
|
|
16231
|
+
// claude/codex/agy/copilot 를 child_process 로 호출 후 stdout 캡처.
|
|
16229
16232
|
// runCommandSafe 경유 — env scrub + permissions + observability 자동 적용.
|
|
16230
16233
|
async function _cliChat(root, provider, prompt, opts) {
|
|
16231
16234
|
opts = opts || {};
|
|
@@ -16240,7 +16243,7 @@ async function _cliChat(root, provider, prompt, opts) {
|
|
|
16240
16243
|
let cmd, args, stdinInput = null;
|
|
16241
16244
|
if (provider === 'claude') { cmd = 'claude'; args = ['--print']; stdinInput = prompt; }
|
|
16242
16245
|
else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', '-']; stdinInput = prompt; }
|
|
16243
|
-
else if (provider === '
|
|
16246
|
+
else if (provider === 'agy') { cmd = 'agy'; args = ['-p', prompt]; } // agy (Antigravity) 는 인자 only
|
|
16244
16247
|
else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', prompt]; }
|
|
16245
16248
|
else return { ok: false, error: `provider ${provider} 미지원`, provider };
|
|
16246
16249
|
// runCommandSafe — env scrub + observability 자동
|
|
@@ -16296,7 +16299,7 @@ async function _cliChatStream(root, provider, promptText, opts) {
|
|
|
16296
16299
|
args = ['exec', '--skip-git-repo-check', '-']; // - = stdin from claude/codex convention
|
|
16297
16300
|
useStdinForPrompt = true;
|
|
16298
16301
|
}
|
|
16299
|
-
else if (provider === '
|
|
16302
|
+
else if (provider === 'agy') { cmd = 'agy'; args = ['-p', promptText]; } // agy -p 는 인자 모드만 지원
|
|
16300
16303
|
else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', promptText]; }
|
|
16301
16304
|
else return { ok: false, error: `provider ${provider} 미지원`, provider };
|
|
16302
16305
|
const t0 = Date.now();
|
|
@@ -16632,10 +16635,10 @@ const _PROVIDER_MODEL_CATALOG = {
|
|
|
16632
16635
|
{ id: 'gpt-5-codex', note: '코드 특화 (Codex)' },
|
|
16633
16636
|
{ id: 'o4-mini', note: '빠른 reasoning' }
|
|
16634
16637
|
],
|
|
16635
|
-
|
|
16636
|
-
{ id: '
|
|
16637
|
-
{ id: '
|
|
16638
|
-
{ id: '
|
|
16638
|
+
agy: [
|
|
16639
|
+
{ id: 'antigravity-pro', note: 'Antigravity 최고급 (1M+ context)' },
|
|
16640
|
+
{ id: 'antigravity-flash', note: '빠른 응답' },
|
|
16641
|
+
{ id: 'antigravity-experimental', note: '실험적 (사용 가능 시)' }
|
|
16639
16642
|
],
|
|
16640
16643
|
copilot: [
|
|
16641
16644
|
{ id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
|
|
@@ -16649,7 +16652,7 @@ const _PROVIDER_MODEL_CATALOG = {
|
|
|
16649
16652
|
};
|
|
16650
16653
|
|
|
16651
16654
|
// 1.9.170: provider cycle 순서 (Tab) — 빌트인 5종. user provider는 동적으로 뒤에 추가.
|
|
16652
|
-
const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', '
|
|
16655
|
+
const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'copilot'];
|
|
16653
16656
|
|
|
16654
16657
|
// 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
|
|
16655
16658
|
const _AGENT_ROLE_PROMPTS = {
|
|
@@ -16829,7 +16832,7 @@ async function _agentRepl(root, opts) {
|
|
|
16829
16832
|
// prompt 에 세부 모델 표시 (사용자 명시): agent[claude · claude-opus-4-7 / actor / ▶]>
|
|
16830
16833
|
const prompt = () => {
|
|
16831
16834
|
if (!isTty) return 'agent> ';
|
|
16832
|
-
const modelShort = (state.model || '').replace(/^(claude|gpt|gemini)-/, '').slice(0, 18);
|
|
16835
|
+
const modelShort = (state.model || '').replace(/^(claude|gpt|gemini|antigravity)-/, '').slice(0, 18);
|
|
16833
16836
|
const modelTag = modelShort ? C.dim(' · ') + C.mag(modelShort) : '';
|
|
16834
16837
|
return C.cy(`agent[${state.provider}${modelTag}${C.cy('/' + state.role)}${state.streamMode ? C.cy('/▶') : ''}${C.cy(']>')} `);
|
|
16835
16838
|
};
|
|
@@ -16849,7 +16852,7 @@ async function _agentRepl(root, opts) {
|
|
|
16849
16852
|
if (m.includes('gpt-4')) return 128000;
|
|
16850
16853
|
return 128000;
|
|
16851
16854
|
}
|
|
16852
|
-
if (provider === '
|
|
16855
|
+
if (provider === 'agy') return 1000000; // 2M까지 가능하지만 보수적으로 1M
|
|
16853
16856
|
if (provider === 'copilot') return 64000;
|
|
16854
16857
|
if (provider === 'ollama') return 8000; // 모델별 다르나 default 8k
|
|
16855
16858
|
return 100000;
|
|
@@ -16882,7 +16885,7 @@ async function _agentRepl(root, opts) {
|
|
|
16882
16885
|
.map(a => ({ id: a.id, st: _checkAgent(a).status }))
|
|
16883
16886
|
.filter(x => x.st === 'ready');
|
|
16884
16887
|
} catch {}
|
|
16885
|
-
const allAgents = ['claude', 'codex', '
|
|
16888
|
+
const allAgents = ['claude', 'codex', 'agy', 'copilot'];
|
|
16886
16889
|
const subBar = allAgents.map(id => {
|
|
16887
16890
|
if (id === state.provider) return C.bold(C.green(`*${id}`));
|
|
16888
16891
|
const isReady = ready.find(r => r.id === id);
|
|
@@ -17025,7 +17028,7 @@ async function _agentRepl(root, opts) {
|
|
|
17025
17028
|
['/quit', 'REPL 종료 (세션 자동 저장)'],
|
|
17026
17029
|
['/clear', '대화 히스토리 초기화'],
|
|
17027
17030
|
['/status', '현재 provider / model / role / perms 상태'],
|
|
17028
|
-
['/provider <id>', 'provider 전환 (ollama, claude, codex,
|
|
17031
|
+
['/provider <id>', 'provider 전환 (ollama, claude, codex, agy, copilot)'],
|
|
17029
17032
|
['/model <id>', '현재 provider의 모델 전환'],
|
|
17030
17033
|
['/role <id>', '역할 전환 (actor / planner / reviewer)'],
|
|
17031
17034
|
['/stream on|off', '실시간 스트리밍 토글 (default ON)']
|
|
@@ -17077,7 +17080,7 @@ async function _agentRepl(root, opts) {
|
|
|
17077
17080
|
log(' :model <name> — 모델 변경 (1.9.155 모든 provider 지원, 예: :model claude-opus-4-7)');
|
|
17078
17081
|
log(' :models — provider 별 모델 목록 (ollama 실시간 / 그 외 추천 카탈로그)');
|
|
17079
17082
|
log(' :role <r> — 역할 변경 (planner / reviewer / actor)');
|
|
17080
|
-
log(' :provider <p> — provider 변경 (ollama / claude / codex /
|
|
17083
|
+
log(' :provider <p> — provider 변경 (ollama / claude / codex / agy / copilot — ready 검증)');
|
|
17081
17084
|
log(' :stream on|off — 🆕 1.9.170 실시간 스트리밍 토글 (추론중/diff/thinking 실시간 표시)');
|
|
17082
17085
|
log(' :status — 현재 세션 상태 자세히 (1.9.155)');
|
|
17083
17086
|
log(' :clear — 화면 클리어 + history 유지');
|
|
@@ -17088,7 +17091,7 @@ async function _agentRepl(root, opts) {
|
|
|
17088
17091
|
log(' :perm [mode] — :permissions 단축 alias');
|
|
17089
17092
|
log(' :quit / :exit / :q — 종료 (자동 저장)');
|
|
17090
17093
|
log(C.bold('\n 🆕 1.9.170 키보드 단축키:'));
|
|
17091
|
-
log(' Tab — 다음 provider 로 cycle (ollama → claude → codex →
|
|
17094
|
+
log(' Tab — 다음 provider 로 cycle (ollama → claude → codex → agy → copilot)');
|
|
17092
17095
|
log(' Shift+Tab — 현재 provider 의 다음 model 로 cycle (catalog 기준)');
|
|
17093
17096
|
log(C.bold('\n Slash 명령 (1.9.150) — leerness 내부 명령 직접 호출:'));
|
|
17094
17097
|
log(' :verify — leerness verify-code (테스트/타입/린트 자동 검수)');
|
|
@@ -17155,7 +17158,7 @@ async function _agentRepl(root, opts) {
|
|
|
17155
17158
|
}
|
|
17156
17159
|
if (op === 'provider') {
|
|
17157
17160
|
const newProv = rest[0] || state.provider;
|
|
17158
|
-
const validProviders = ['ollama', 'claude', 'codex', '
|
|
17161
|
+
const validProviders = ['ollama', 'claude', 'codex', 'agy', 'copilot'];
|
|
17159
17162
|
if (!validProviders.includes(newProv)) {
|
|
17160
17163
|
log(C.yel(` ⚠ provider 는 ${validProviders.join(' / ')} (받음: ${newProv})`));
|
|
17161
17164
|
return false;
|
|
@@ -17392,12 +17395,12 @@ async function _agentRepl(root, opts) {
|
|
|
17392
17395
|
const finalPrompt = `${rolePrompt}\n\nConversation so far:\n${state.history.slice(-6).map(m => `[${m.role}] ${m.content}`).join('\n')}\n\nRespond as ${state.role}:`;
|
|
17393
17396
|
const t0 = Date.now();
|
|
17394
17397
|
let result;
|
|
17395
|
-
// 1.9.153: multi-provider REPL — ollama 외 claude/codex/
|
|
17398
|
+
// 1.9.153: multi-provider REPL — ollama 외 claude/codex/agy/copilot 도 세션 관리 (사용자 명시)
|
|
17396
17399
|
// 1.9.170: streamMode === true 이면 _cliChatStream 사용 (사용자 명시 — 추론중/diff 실시간 표시)
|
|
17397
17400
|
if (state.provider === 'ollama') {
|
|
17398
17401
|
log(C.dim(` → ollama${state.model ? ' (' + state.model + ')' : ''} 호출 중...`));
|
|
17399
17402
|
result = await _ollamaChat(finalPrompt, state.model);
|
|
17400
|
-
} else if (['claude', 'codex', '
|
|
17403
|
+
} else if (['claude', 'codex', 'agy', 'copilot'].includes(state.provider)) {
|
|
17401
17404
|
if (state.streamMode) {
|
|
17402
17405
|
log(C.dim(` → ${state.provider} CLI stream 호출 중... (Ctrl+C 로 중단)`));
|
|
17403
17406
|
result = await _cliChatStream(root, state.provider, finalPrompt, { timeout: 120000 });
|
|
@@ -17406,7 +17409,7 @@ async function _agentRepl(root, opts) {
|
|
|
17406
17409
|
result = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
|
|
17407
17410
|
}
|
|
17408
17411
|
} else {
|
|
17409
|
-
log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|
|
|
17412
|
+
log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|agy|copilot`));
|
|
17410
17413
|
promptWithStatus(); return;
|
|
17411
17414
|
}
|
|
17412
17415
|
const dt = Date.now() - t0;
|
|
@@ -17415,7 +17418,7 @@ async function _agentRepl(root, opts) {
|
|
|
17415
17418
|
state.history.push({ role: 'assistant', content: result.response });
|
|
17416
17419
|
// 1.9.170: stream 모드에서는 이미 실시간으로 출력됐으므로 헤더만 표시 (응답 중복 방지)
|
|
17417
17420
|
// 1.9.246: 정상 완료 작업은 초록색 ✓ 강조 (사용자 명시 UR-0016)
|
|
17418
|
-
if (state.streamMode && ['claude', 'codex', '
|
|
17421
|
+
if (state.streamMode && ['claude', 'codex', 'agy', 'copilot'].includes(state.provider)) {
|
|
17419
17422
|
log(C.green(` ✓ [assistant: ${state.provider}/${state.model || 'default'}, role=${state.role}, ${dt}ms · ${result.response.length}자]`));
|
|
17420
17423
|
} else {
|
|
17421
17424
|
log('');
|
|
@@ -17432,8 +17435,8 @@ async function _agentRepl(root, opts) {
|
|
|
17432
17435
|
// 실패 원인 분류
|
|
17433
17436
|
if (/exit=null/.test(errMsg) || /timeout/i.test(errMsg)) {
|
|
17434
17437
|
log(C.dim(` ↳ 가능 원인: (1) ${state.provider} CLI 응답 시간 초과 (모델 로딩/큰 응답 대기 중)`));
|
|
17435
|
-
log(C.dim(` (2) network/auth 문제 (특히 codex/
|
|
17436
|
-
log(C.dim(` ↳ 직접 검증: ${state.provider} --print "ping" ${state.provider === '
|
|
17438
|
+
log(C.dim(` (2) network/auth 문제 (특히 codex/agy 는 인터넷 + 토큰 필요)`));
|
|
17439
|
+
log(C.dim(` ↳ 직접 검증: ${state.provider} --print "ping" ${state.provider === 'agy' ? '--yolo' : ''}`));
|
|
17437
17440
|
} else if (/exit=1/.test(errMsg) || /unauth/i.test(errMsg) || /login/i.test(errMsg)) {
|
|
17438
17441
|
log(C.dim(` ↳ 가능 원인: 인증 누락 (login 필요)`));
|
|
17439
17442
|
log(C.dim(` ↳ 해결: ${state.provider} login ${state.provider === 'copilot' ? '(gh auth login)' : ''}`));
|
|
@@ -17488,7 +17491,7 @@ async function _agentRepl(root, opts) {
|
|
|
17488
17491
|
log(C.dim(` 💡 즉시 전환: ${others.map(x => `:provider ${x.def.id}`).join(' · ')} 또는 Tab 키`));
|
|
17489
17492
|
if (!state.autoFallback) log(C.dim(` 💡 다음 실패 시 자동 전환: \`:fallback on\` (1.9.247 UR-0016 2단계)`));
|
|
17490
17493
|
} else {
|
|
17491
|
-
log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|
|
|
17494
|
+
log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|AGY|COPILOT>=1`));
|
|
17492
17495
|
}
|
|
17493
17496
|
} catch {}
|
|
17494
17497
|
}
|
|
@@ -17521,12 +17524,12 @@ async function agentCmd(root, taskArg) {
|
|
|
17521
17524
|
log(' leerness agent "<task>" # 1회 위임 (actor 역할 기본)');
|
|
17522
17525
|
log(' leerness agent "<task>" --role planner # 계획만 (1.9.148)');
|
|
17523
17526
|
log(' leerness agent "<task>" --role reviewer # 비판적 검토 (1.9.148)');
|
|
17524
|
-
log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/
|
|
17527
|
+
log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/agy/copilot)');
|
|
17525
17528
|
log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + Ollama 모델 선택');
|
|
17526
17529
|
log('');
|
|
17527
17530
|
log('REPL 메타 명령: :help / :model / :role / :provider / :history / :save / :quit');
|
|
17528
17531
|
log('REPL Slash 명령 (1.9.150): :verify / :audit / :handoff / :health (sandboxed runCommandSafe)');
|
|
17529
|
-
log('REPL Multi-provider (1.9.153): ollama / claude / codex /
|
|
17532
|
+
log('REPL Multi-provider (1.9.153): ollama / claude / codex / agy / copilot — 활성 CLI 자동 감지');
|
|
17530
17533
|
log('');
|
|
17531
17534
|
log('현재 활성 provider: ' + (_activeCliAgents().join(', ') || '(없음) — .env에서 LEERNESS_ENABLE_* 활성화'));
|
|
17532
17535
|
log('권한 모드: ' + (_readPermissions(root).mode || 'basic'));
|
|
@@ -17557,17 +17560,17 @@ async function agentCmd(root, taskArg) {
|
|
|
17557
17560
|
// 1.9.148: role prompt 자동 prepend (모든 provider 공통)
|
|
17558
17561
|
const finalPrompt = `${rolePrompt}\n\nTask: ${task}`;
|
|
17559
17562
|
const t0 = Date.now();
|
|
17560
|
-
// 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/
|
|
17563
|
+
// 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/agy/copilot 직접 호출 (1.9.153 _cliChat 재사용)
|
|
17561
17564
|
let r;
|
|
17562
17565
|
if (provider === 'ollama') {
|
|
17563
17566
|
log('\n[ollama 호출 중...]');
|
|
17564
17567
|
r = await _ollamaChat(finalPrompt);
|
|
17565
|
-
} else if (['claude', 'codex', '
|
|
17568
|
+
} else if (['claude', 'codex', 'agy', 'copilot'].includes(provider)) {
|
|
17566
17569
|
log(`\n[${provider} CLI 호출 중...]`);
|
|
17567
17570
|
r = await _cliChat(root, provider, finalPrompt, { timeout: 90000 });
|
|
17568
17571
|
if (r.ok && !r.model) r.model = provider; // _cliChat 결과 보강
|
|
17569
17572
|
} else {
|
|
17570
|
-
fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/
|
|
17573
|
+
fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/agy/copilot)`);
|
|
17571
17574
|
process.exitCode = 1;
|
|
17572
17575
|
return;
|
|
17573
17576
|
}
|
|
@@ -18136,7 +18139,7 @@ function healthCmd(root) {
|
|
|
18136
18139
|
const hasRepl = /async function _agentRepl/.test(harnessSrc);
|
|
18137
18140
|
const hasCliChat = /async function _cliChat/.test(harnessSrc);
|
|
18138
18141
|
cap.replMultiProvider = (hasRepl && hasCliChat)
|
|
18139
|
-
? { score: 90, status: '✓', evidence: 'ollama/claude/codex/
|
|
18142
|
+
? { score: 90, status: '✓', evidence: 'ollama/claude/codex/agy/copilot 5종 (1.9.149+1.9.153)' }
|
|
18140
18143
|
: { score: 30, status: '⚠', evidence: 'REPL 미완성' };
|
|
18141
18144
|
// (5) MCP 도구 — tools array 카운트
|
|
18142
18145
|
const toolsMatch = harnessSrc.match(/{ name: 'leerness_/g);
|
|
@@ -19345,7 +19348,7 @@ function whichCmd() {
|
|
|
19345
19348
|
}
|
|
19346
19349
|
|
|
19347
19350
|
function help() {
|
|
19348
|
-
log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/
|
|
19351
|
+
log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/agy/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
|
|
19349
19352
|
leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
|
|
19350
19353
|
leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
|
|
19351
19354
|
leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -605,11 +605,11 @@ total++;
|
|
|
605
605
|
// register-pending
|
|
606
606
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-regp-'));
|
|
607
607
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
608
|
-
const r = cp.spawnSync(process.execPath, [CLI, 'register-pending', 'JSON export 기능', '--agent', '
|
|
609
|
-
const okReg = r.status === 0 && /등록됨 \(in-progress\) by
|
|
608
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'register-pending', 'JSON export 기능', '--agent', 'agy', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
|
|
609
|
+
const okReg = r.status === 0 && /등록됨 \(in-progress\) by agy/.test(r.stdout);
|
|
610
610
|
// 즉시 progress-tracker에서 검색 가능
|
|
611
611
|
const r2 = cp.spawnSync(process.execPath, [CLI, 'memory', 'search', 'JSON export', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
|
|
612
|
-
const okSearch = /in-progress/.test(r2.stdout) && /pending.*
|
|
612
|
+
const okSearch = /in-progress/.test(r2.stdout) && /pending.*agy/.test(r2.stdout);
|
|
613
613
|
const ok = okReg && okSearch;
|
|
614
614
|
console.log(ok ? '✓ B(1.9.25) register-pending: 즉시 등록 + 검색 가능' : `✗ register-pending 실패 (reg=${okReg} search=${okSearch})`);
|
|
615
615
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
@@ -797,23 +797,23 @@ total++;
|
|
|
797
797
|
total++;
|
|
798
798
|
{
|
|
799
799
|
// agents list — claude가 환경변수 + PATH 둘 다 충족 시 ready
|
|
800
|
-
const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0',
|
|
800
|
+
const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
|
|
801
801
|
const r1 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list'], { encoding: 'utf8', timeout: 15000, env: env1 });
|
|
802
802
|
const okList = r1.status === 0
|
|
803
803
|
&& /외부 AI CLI 오케스트레이션 \(1\.9\.30\)/.test(r1.stdout)
|
|
804
804
|
&& /\| claude \|/.test(r1.stdout)
|
|
805
805
|
&& /\| codex \|/.test(r1.stdout)
|
|
806
|
-
&& /\|
|
|
806
|
+
&& /\| agy \|/.test(r1.stdout)
|
|
807
807
|
&& /\| copilot \|/.test(r1.stdout);
|
|
808
808
|
// env 모두 0 → 비활성
|
|
809
809
|
// 1.9.146: Ollama 추가 → 5 CLI
|
|
810
|
-
const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0',
|
|
810
|
+
const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0', LEERNESS_ENABLE_OLLAMA: '0' };
|
|
811
811
|
const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list', '--json'], { encoding: 'utf8', timeout: 15000, env: env2 });
|
|
812
812
|
let parsed = null;
|
|
813
813
|
try { parsed = JSON.parse(r2.stdout); } catch {}
|
|
814
814
|
const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length === 5 && parsed.agents.every(a => a.status !== 'ready');
|
|
815
815
|
const ok = okList && okJson;
|
|
816
|
-
console.log(ok ? '✓ B(1.9.30+1.9.146) agents list: 5 CLI 정의 (claude/codex/
|
|
816
|
+
console.log(ok ? '✓ B(1.9.30+1.9.146) agents list: 5 CLI 정의 (claude/codex/agy/copilot/ollama)' : `✗ agents list 실패 (list=${okList} json=${okJson})`);
|
|
817
817
|
if (!ok) { failed++; console.log(r1.stdout.slice(0, 500)); }
|
|
818
818
|
}
|
|
819
819
|
|
|
@@ -838,13 +838,13 @@ total++;
|
|
|
838
838
|
total++;
|
|
839
839
|
{
|
|
840
840
|
// agents quota — env=0 시 모두 disabled/not-installed, 안내 메시지 포함
|
|
841
|
-
const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0',
|
|
841
|
+
const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
|
|
842
842
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota'], { encoding: 'utf8', timeout: 15000, env });
|
|
843
843
|
const okText = r.status === 0
|
|
844
844
|
&& /외부 AI CLI quota 추정 \(1\.9\.31\)/.test(r.stdout)
|
|
845
845
|
&& /\| claude \|/.test(r.stdout)
|
|
846
846
|
&& /\| codex \|/.test(r.stdout)
|
|
847
|
-
&& /\|
|
|
847
|
+
&& /\| agy \|/.test(r.stdout)
|
|
848
848
|
&& /\| copilot \|/.test(r.stdout)
|
|
849
849
|
&& /provider 대시보드 참조/.test(r.stdout);
|
|
850
850
|
// JSON 출력
|
|
@@ -1752,13 +1752,13 @@ total++;
|
|
|
1752
1752
|
// 1.9.36 회귀: dispatch 권장 플래그 + bench + 작업 유형 추천
|
|
1753
1753
|
total++;
|
|
1754
1754
|
{
|
|
1755
|
-
// dispatch --write 시
|
|
1756
|
-
const env = { ...process.env,
|
|
1757
|
-
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '코드 분석해서 요약', '--to', '
|
|
1758
|
-
//
|
|
1755
|
+
// dispatch --write 시 agy --yolo 자동 추가
|
|
1756
|
+
const env = { ...process.env, LEERNESS_ENABLE_AGY: '1' };
|
|
1757
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '코드 분석해서 요약', '--to', 'agy', '--write'], { encoding: 'utf8', timeout: 15000, env });
|
|
1758
|
+
// agy가 ready면 명령 출력에 --yolo 포함, 비-ready면 거부 — 둘 다 OK
|
|
1759
1759
|
const ok = (r.status === 0 && /--yolo/.test(r.stdout) && /write \(파일 수정 가능\)/.test(r.stdout))
|
|
1760
1760
|
|| (r.status !== 0 && /비활성|disabled|not-installed/.test(r.stdout));
|
|
1761
|
-
console.log(ok ? '✓ B(1.9.36) dispatch --write:
|
|
1761
|
+
console.log(ok ? '✓ B(1.9.36) dispatch --write: agy --yolo 자동 첨부 또는 비활성 거부' : `✗ dispatch --write 실패`);
|
|
1762
1762
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1763
1763
|
}
|
|
1764
1764
|
|
|
@@ -1777,8 +1777,8 @@ total++;
|
|
|
1777
1777
|
total++;
|
|
1778
1778
|
{
|
|
1779
1779
|
// 작업 유형 추천 — 비활성 CLI에도 추천 메시지 우선 출력
|
|
1780
|
-
const env = { ...process.env,
|
|
1781
|
-
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘 한국어를 영어로', '--to', '
|
|
1780
|
+
const env = { ...process.env, LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_CLAUDE: '0' };
|
|
1781
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘 한국어를 영어로', '--to', 'agy'], { encoding: 'utf8', timeout: 15000, env });
|
|
1782
1782
|
// 번역 → claude 추천. ready 체크 전에 추천 출력 → stdout에 "추천...claude" 포함
|
|
1783
1783
|
const ok = /추천.*claude/.test(r.stdout);
|
|
1784
1784
|
console.log(ok ? '✓ B(1.9.36) 작업 유형 추천: 번역→claude 추천 (비활성이어도 출력)' : `✗ 추천 실패`);
|
|
@@ -1788,7 +1788,7 @@ total++;
|
|
|
1788
1788
|
total++;
|
|
1789
1789
|
{
|
|
1790
1790
|
// bench 명령: ready CLI 없을 때 거부
|
|
1791
|
-
const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0',
|
|
1791
|
+
const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
|
|
1792
1792
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'bench', 'test'], { encoding: 'utf8', timeout: 15000, env });
|
|
1793
1793
|
const ok = r.status !== 0 && /ready CLI 없음/.test(r.stdout);
|
|
1794
1794
|
console.log(ok ? '✓ B(1.9.36) agents bench: ready 없을 때 거부' : `✗ bench 거부 실패`);
|