leerness 1.9.153 → 1.9.155
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 +91 -0
- package/README.md +2 -2
- package/bin/harness.js +191 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.155 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**REPL UX 대폭 개선 + provider 모델 카탈로그 + orchestrate consensus 강화 + 5능력 점검 보고서 (사용자 명시).**
|
|
6
|
+
|
|
7
|
+
자율 모드 85 라운드. 사용자 명시 점검 요청 — sub-agent 2개 병렬로 비교/실측 후 정직한 보고서 산출 + 발견 gap 1건 즉시 보강.
|
|
8
|
+
|
|
9
|
+
### Added — REPL UX (사용자 명시: 선택한 CLI 모델 변경 가능)
|
|
10
|
+
- **`:model <name>` 모든 provider 지원** — claude/codex/gemini/copilot/ollama 모두 가능 (1.9.149 → 1.9.155)
|
|
11
|
+
- **`_PROVIDER_MODEL_CATALOG`** — provider 별 추천 모델 카탈로그
|
|
12
|
+
- claude: `claude-opus-4-5` / `claude-sonnet-4-5` / `claude-haiku-4-5`
|
|
13
|
+
- codex: `gpt-5` / `gpt-5-codex` / `o4-mini`
|
|
14
|
+
- gemini: `gemini-2.5-pro` / `gemini-2.5-flash`
|
|
15
|
+
- copilot: `default` (모델 선택 불가)
|
|
16
|
+
- ollama: `llama3` / `qwen2.5-coder` / `gpt-oss` (실시간 조회 권장)
|
|
17
|
+
- **`:models`** — provider 별 분기 (ollama 실시간 / 그 외 카탈로그)
|
|
18
|
+
- **`:status`** — 현재 세션 상태 자세히 (provider/model/role/permissions/history/sessionId/activeCli)
|
|
19
|
+
- **REPL 진입 시 handoff context 자동 노출** — 매번 `:handoff` 안 해도 즉시 컨텍스트 인지
|
|
20
|
+
- `:help` 에 1.9.155 명령 안내 통합
|
|
21
|
+
|
|
22
|
+
### Added — orchestrate consensus 강화 (sub-agent 점검 발견 gap 보강)
|
|
23
|
+
- 기존: "응답 토큰 수가 가장 긴 응답 = best" 임시 휴리스틱
|
|
24
|
+
- 변경: **multi-signal scoring**
|
|
25
|
+
- `tokensNorm` (0~1) — 정보 밀도
|
|
26
|
+
- `overlap` — 다른 응답과의 단어 교집합 비율 평균 (합의도)
|
|
27
|
+
- `lengthFit` — 평균 길이 z-score 기반 적정 가중 (`|z| <= 1.5` 가산)
|
|
28
|
+
- `score = 0.4*tokens + 0.4*overlap + 0.2*lengthFit`
|
|
29
|
+
- 출력: best 1위 + others 2-4위 점수 표시 (투명성)
|
|
30
|
+
|
|
31
|
+
### Verified — 5능력 점검 보고서 (`_reports/1.9.155-capability-audit.md`)
|
|
32
|
+
sub-agent 2 병렬 분석 (약 110초):
|
|
33
|
+
|
|
34
|
+
**경쟁 비교 (sub-agent #1)** — OpenClaw / Hermes Agent / OpenCode WebSearch 실측:
|
|
35
|
+
- leerness 비교우위: 한국어+신뢰도 하네스, MCP 47도구+Memory 5종, Feature Causality Graph, 의존성 0, 자율 release
|
|
36
|
+
- 부족 영역: 모델/프로바이더 폭(5종 vs 200+), LSP 통합 미흡, GUI/멀티채널 부재
|
|
37
|
+
- 권고: LSP 어댑터, Provider Registry MCP, i18n+영어 docs
|
|
38
|
+
|
|
39
|
+
**5능력 매트릭스 (sub-agent #2 코드 실측)**:
|
|
40
|
+
| 영역 | 평가 | 완성도 |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| 웹 띄워 검수 | ❌ | 5% (permissions.browser=toggle만, 실 코드 0) |
|
|
43
|
+
| 권한 따른 PC 조작 | ❌ | 5% (mouse/keyboard/admin 필드만, 실 사용처 0) |
|
|
44
|
+
| 멀티 에이전트 오케스트레이션 | ⚠ → ✓ (1.9.155 보강) | 70% → 80% |
|
|
45
|
+
| REPL multi-provider | ✓ | 90% |
|
|
46
|
+
| MCP 47도구 일관성 | ✓ | 100% |
|
|
47
|
+
|
|
48
|
+
**종합 ~55% — "검수·기억·드리프트 방지 하네스" 본질은 95%, "범용 PC 자동화"는 30%**. 정직한 포지셔닝.
|
|
49
|
+
|
|
50
|
+
### Pending — 보고서가 제안한 다음 4 라운드 후보
|
|
51
|
+
1. 1.9.156 — `agents multi --execute` (단순 명령 출력 → 실제 spawn + consensus 통합)
|
|
52
|
+
2. 1.9.157 — LSP 어댑터 MVP (TypeScript LSP 먼저)
|
|
53
|
+
3. 1.9.158 — Provider Registry MCP 도구 (OpenRouter/Bedrock 흡수)
|
|
54
|
+
4. 1.9.159 — playwright/computer-use bridge (permissions.browser/mouse 실 동작 — opt-in)
|
|
55
|
+
|
|
56
|
+
### Verified
|
|
57
|
+
- e2e 217/217 ✓
|
|
58
|
+
- stress-v100: 18/18 (REPL :model 3종 + :status/UX 3종 + consensus 2종 + 점검 보고서 3종 + 누적 회귀 7종) 🎉 **100번째 stress 마일스톤**
|
|
59
|
+
- VERSION = 1.9.155 / autonomous-rounds = 85
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 1.9.154 — 2026-05-20
|
|
64
|
+
|
|
65
|
+
**agent 1-shot multi-provider + REPL `:provider` 활성 검증 (1.9.153 후속, 일관성 강화).**
|
|
66
|
+
|
|
67
|
+
자율 모드 84 라운드.
|
|
68
|
+
|
|
69
|
+
### Added — `leerness agent "<task>" --provider <p>` 1-shot multi-provider
|
|
70
|
+
- 기존: 1-shot 모드는 Ollama 만 호출, 다른 CLI 는 `agents dispatch` 안내만
|
|
71
|
+
- 변경: **claude / codex / gemini / copilot 도 직접 호출** (1.9.153 `_cliChat` 재사용)
|
|
72
|
+
- `_recordRun` observability — provider/model 필드 동적 (`agent_one_shot` kind)
|
|
73
|
+
- task-log 기록도 provider 동적 (`leerness agent (claude:claude, role=actor)` 형식)
|
|
74
|
+
- 실패 시 provider 별 friendly 안내 (Ollama: BASE_URL 확인 / 외부 CLI: `LEERNESS_ENABLE_<X>=1` + 설치)
|
|
75
|
+
|
|
76
|
+
### Added — REPL `:provider <p>` 전환 시 활성 사전 검증
|
|
77
|
+
- validProviders 화이트리스트 5종 — 알 수 없는 provider 거부
|
|
78
|
+
- **비활성 (`ready` 아님) provider 전환 시 즉시 거부** — 실제 호출 시 실패 방지
|
|
79
|
+
- `_checkAgent` 결과로 status/installed/enabled 종합 판정
|
|
80
|
+
- Ollama 는 `LEERNESS_OLLAMA_BASE_URL` 미설정 시 친절한 안내 (블록 아님 — fallback URL 시도)
|
|
81
|
+
- 전환 성공 시 `rl.setPrompt(prompt())` 으로 프롬프트 즉시 갱신
|
|
82
|
+
|
|
83
|
+
### Verified — setup-agents `_selectMany` 일관성 (회귀 방지)
|
|
84
|
+
- 1.9.34 이래 setup-agents 이미 `_selectMany` 사용 중 — 1.9.151 install 흐름과 일관
|
|
85
|
+
- stress-v99 회귀 테스트 추가 (사용자 명시 요청 일관성 보장)
|
|
86
|
+
|
|
87
|
+
### Verified
|
|
88
|
+
- e2e 217/217 ✓
|
|
89
|
+
- stress-v99: 18/18 (1-shot multi-provider 6종 + REPL :provider 검증 4종 + setup-agents 1종 + 누적 회귀 7종)
|
|
90
|
+
- VERSION = 1.9.154 / autonomous-rounds = 84
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
3
94
|
## 1.9.153 — 2026-05-20
|
|
4
95
|
|
|
5
96
|
**`.env` 직접 생성/마이그레이션 + REPL 배너 leerness 고유 문구 + multi-provider REPL (사용자 명시 3종).**
|
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,7 +12,7 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.155 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
package/bin/harness.js
CHANGED
|
@@ -6,7 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const cp = require('child_process');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
|
|
9
|
-
const VERSION = '1.9.
|
|
9
|
+
const VERSION = '1.9.155';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -4158,9 +4158,43 @@ async function orchestrateCmd(root, goalParts) {
|
|
|
4158
4158
|
log(` 평균 latency: ${avgElapsed.toFixed(0)}ms · wall-clock 총: ${totalElapsedWallClock}ms (병렬 효과 ${(avgElapsed * ok.length / totalElapsedWallClock).toFixed(1)}x)`);
|
|
4159
4159
|
|
|
4160
4160
|
log('');
|
|
4161
|
-
log(`## 최고 응답 (
|
|
4162
|
-
|
|
4163
|
-
|
|
4161
|
+
log(`## 최고 응답 (1.9.155 multi-signal consensus — 토큰 + 단어중복도 + 길이 정규화)`);
|
|
4162
|
+
// 1.9.155: 단순 token-max → multi-signal scoring
|
|
4163
|
+
// 1) responseTokens (information density) — 정규화 0~1
|
|
4164
|
+
// 2) wordOverlap (agreement with others) — 다른 응답과의 단어 교집합 비율 평균
|
|
4165
|
+
// 3) lengthZ (적정 길이 가중) — 평균에서 너무 짧지/길지 않음
|
|
4166
|
+
const tokenizer = (s) => new Set(String(s || '').toLowerCase().match(/[\w가-힣]{3,}/g) || []);
|
|
4167
|
+
const wordsOf = ok.map(o => tokenizer(o.reply));
|
|
4168
|
+
const maxTokens = Math.max(...ok.map(o => o.responseTokens), 1);
|
|
4169
|
+
const avgLen = ok.reduce((s, o) => s + o.reply.length, 0) / ok.length;
|
|
4170
|
+
const stdLen = Math.sqrt(ok.reduce((s, o) => s + (o.reply.length - avgLen) ** 2, 0) / ok.length) || 1;
|
|
4171
|
+
const scored = ok.map((o, i) => {
|
|
4172
|
+
const tokensNorm = o.responseTokens / maxTokens;
|
|
4173
|
+
// 다른 응답들과 단어 교집합 비율 평균
|
|
4174
|
+
const myWords = wordsOf[i];
|
|
4175
|
+
let overlapSum = 0;
|
|
4176
|
+
for (let j = 0; j < wordsOf.length; j++) {
|
|
4177
|
+
if (i === j) continue;
|
|
4178
|
+
const other = wordsOf[j];
|
|
4179
|
+
if (!myWords.size || !other.size) continue;
|
|
4180
|
+
let inter = 0;
|
|
4181
|
+
for (const w of myWords) if (other.has(w)) inter++;
|
|
4182
|
+
overlapSum += inter / Math.max(myWords.size, 1);
|
|
4183
|
+
}
|
|
4184
|
+
const overlap = (ok.length > 1) ? overlapSum / (ok.length - 1) : 0;
|
|
4185
|
+
// length z-score (적정 길이 가산: |z| <= 1.5 일 때 가산)
|
|
4186
|
+
const z = Math.abs((o.reply.length - avgLen) / stdLen);
|
|
4187
|
+
const lengthFit = z <= 1.5 ? (1 - z / 1.5) : 0;
|
|
4188
|
+
const score = 0.4 * tokensNorm + 0.4 * overlap + 0.2 * lengthFit;
|
|
4189
|
+
return { ...o, score, tokensNorm, overlap, lengthFit };
|
|
4190
|
+
});
|
|
4191
|
+
scored.sort((a, b) => b.score - a.score);
|
|
4192
|
+
const best = scored[0];
|
|
4193
|
+
log(` best: agent ${best.agent} · score=${best.score.toFixed(3)} (tokens=${best.tokensNorm.toFixed(2)} · overlap=${best.overlap.toFixed(2)} · lengthFit=${best.lengthFit.toFixed(2)})`);
|
|
4194
|
+
log(` → ${best.responseTokens} 응답 토큰 · ${best.elapsed}ms`);
|
|
4195
|
+
if (scored.length > 1) {
|
|
4196
|
+
log(` others: ${scored.slice(1, 4).map(s => `${s.agent}=${s.score.toFixed(2)}`).join(', ')}`);
|
|
4197
|
+
}
|
|
4164
4198
|
log(` --- 처음 600자 ---`);
|
|
4165
4199
|
log(best.reply.slice(0, 600));
|
|
4166
4200
|
}
|
|
@@ -10300,6 +10334,32 @@ function runsShowCmd(root, id) {
|
|
|
10300
10334
|
if (!exists(fp)) return fail(`run 없음: ${id}`);
|
|
10301
10335
|
log(read(fp));
|
|
10302
10336
|
}
|
|
10337
|
+
// 1.9.155: provider 별 추천 모델 카탈로그 — REPL :models 명령에서 노출 (실제 가용성은 사용자 CLI 가 결정)
|
|
10338
|
+
const _PROVIDER_MODEL_CATALOG = {
|
|
10339
|
+
claude: [
|
|
10340
|
+
{ id: 'claude-opus-4-5', note: '최고 추론 (Anthropic)' },
|
|
10341
|
+
{ id: 'claude-sonnet-4-5', note: '균형형 (속도/품질)' },
|
|
10342
|
+
{ id: 'claude-haiku-4-5', note: '빠름' }
|
|
10343
|
+
],
|
|
10344
|
+
codex: [
|
|
10345
|
+
{ id: 'gpt-5', note: 'OpenAI 최신' },
|
|
10346
|
+
{ id: 'gpt-5-codex', note: '코드 특화' },
|
|
10347
|
+
{ id: 'o4-mini', note: '빠른 reasoning' }
|
|
10348
|
+
],
|
|
10349
|
+
gemini: [
|
|
10350
|
+
{ id: 'gemini-2.5-pro', note: 'Google 최고급' },
|
|
10351
|
+
{ id: 'gemini-2.5-flash', note: '빠른 응답' }
|
|
10352
|
+
],
|
|
10353
|
+
copilot: [
|
|
10354
|
+
{ id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
|
|
10355
|
+
],
|
|
10356
|
+
ollama: [
|
|
10357
|
+
{ id: 'llama3', note: 'Meta — :models 로 실시간 조회 권장' },
|
|
10358
|
+
{ id: 'qwen2.5-coder', note: 'Alibaba — 코드 특화' },
|
|
10359
|
+
{ id: 'gpt-oss', note: 'OpenAI 오픈소스' }
|
|
10360
|
+
]
|
|
10361
|
+
};
|
|
10362
|
+
|
|
10303
10363
|
// 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
|
|
10304
10364
|
const _AGENT_ROLE_PROMPTS = {
|
|
10305
10365
|
planner: '역할: planner. task를 step 3-6개로 분해, 각 step의 입출력/검증 방법 명시. 코드 작성 금지, 계획만.',
|
|
@@ -10379,9 +10439,23 @@ async function _agentRepl(root, opts) {
|
|
|
10379
10439
|
}
|
|
10380
10440
|
}
|
|
10381
10441
|
log('');
|
|
10382
|
-
log(C.dim(' 메타 명령: :help | :model <m> | :role <r> | :provider <p> | :clear | :save | :history | :quit'));
|
|
10442
|
+
log(C.dim(' 메타 명령: :help | :model <m> | :role <r> | :provider <p> | :status | :clear | :save | :history | :quit'));
|
|
10383
10443
|
log(C.dim(' Slash 명령 (1.9.150): :verify | :audit | :handoff | :health'));
|
|
10384
|
-
log(C.dim(` 현재 — provider=${state.provider} model=${state.model || '(
|
|
10444
|
+
log(C.dim(` 현재 — provider=${state.provider} model=${state.model || '(기본)'} role=${state.role} permissions=${_readPermissions(root).mode}`));
|
|
10445
|
+
// 1.9.155: REPL 진입 시 handoff 컨텍스트 자동 노출 (UX 개선 — 사용자가 매번 :handoff 안 해도 컨텍스트 인지)
|
|
10446
|
+
try {
|
|
10447
|
+
const hf = cp.spawnSync(process.execPath, [__filename, 'handoff', root, '--compact', '--no-drift-check', '--no-headline'], {
|
|
10448
|
+
encoding: 'utf8', timeout: 8000,
|
|
10449
|
+
env: { ...process.env, LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1', LEERNESS_NO_LESSONS: '1' }
|
|
10450
|
+
});
|
|
10451
|
+
if (hf.status === 0 && hf.stdout) {
|
|
10452
|
+
const preview = hf.stdout.split('\n').slice(0, 4).map(l => l.replace(/^\s+/, '')).filter(Boolean).slice(0, 3).join(' · ');
|
|
10453
|
+
if (preview) {
|
|
10454
|
+
log('');
|
|
10455
|
+
log(C.dim(` 📍 context: ${preview.slice(0, 220)}${preview.length > 220 ? '…' : ''}`));
|
|
10456
|
+
}
|
|
10457
|
+
}
|
|
10458
|
+
} catch {}
|
|
10385
10459
|
log('');
|
|
10386
10460
|
const prompt = () => isTty ? C.cy(`agent[${state.role}]> `) : 'agent> ';
|
|
10387
10461
|
rl.setPrompt(prompt());
|
|
@@ -10394,12 +10468,13 @@ async function _agentRepl(root, opts) {
|
|
|
10394
10468
|
rl.close(); return true;
|
|
10395
10469
|
}
|
|
10396
10470
|
if (op === 'help' || op === '?') {
|
|
10397
|
-
log(C.bold('\n 메타
|
|
10471
|
+
log(C.bold('\n 메타 명령 (provider/모델/역할 전환):'));
|
|
10398
10472
|
log(' :help / :? — 이 도움말');
|
|
10399
|
-
log(' :model <name> — 모델 변경 (예: :model
|
|
10400
|
-
log(' :models —
|
|
10473
|
+
log(' :model <name> — 모델 변경 (1.9.155 모든 provider 지원, 예: :model claude-opus-4-5)');
|
|
10474
|
+
log(' :models — provider 별 모델 목록 (ollama 실시간 / 그 외 추천 카탈로그)');
|
|
10401
10475
|
log(' :role <r> — 역할 변경 (planner / reviewer / actor)');
|
|
10402
|
-
log(' :provider <p> — provider 변경 (ollama / claude / codex / gemini)');
|
|
10476
|
+
log(' :provider <p> — provider 변경 (ollama / claude / codex / gemini / copilot — ready 검증)');
|
|
10477
|
+
log(' :status — 현재 세션 상태 자세히 (1.9.155)');
|
|
10403
10478
|
log(' :clear — 화면 클리어 + history 유지');
|
|
10404
10479
|
log(' :reset — history 초기화');
|
|
10405
10480
|
log(' :history — 대화 history 표시');
|
|
@@ -10413,11 +10488,37 @@ async function _agentRepl(root, opts) {
|
|
|
10413
10488
|
log(' :health — leerness health --json (종합 헬스 체크)');
|
|
10414
10489
|
return false;
|
|
10415
10490
|
}
|
|
10416
|
-
if (op === 'model') {
|
|
10491
|
+
if (op === 'model') {
|
|
10492
|
+
// 1.9.155: provider 별 모델 명시 (사용자 명시 요청 — 선택한 CLI 모델 변경 가능)
|
|
10493
|
+
const m = rest.join(' ').trim();
|
|
10494
|
+
if (!m) {
|
|
10495
|
+
log(C.dim(` 현재 model: ${state.model || '(provider 기본)'} · provider: ${state.provider}`));
|
|
10496
|
+
log(C.dim(` 사용: :model <name> — 예: :model qwen2.5-coder (ollama) / :model claude-opus-4-5 (claude) / :model gpt-5 (codex)`));
|
|
10497
|
+
return false;
|
|
10498
|
+
}
|
|
10499
|
+
state.model = m;
|
|
10500
|
+
log(C.green(` model = ${state.model} (provider: ${state.provider})`));
|
|
10501
|
+
log(C.dim(` ※ 다음 메시지부터 새 모델로 호출. provider 가 ${state.provider} 인지 :provider 로 재확인 권장.`));
|
|
10502
|
+
return false;
|
|
10503
|
+
}
|
|
10417
10504
|
if (op === 'models') {
|
|
10418
|
-
|
|
10419
|
-
if (
|
|
10420
|
-
|
|
10505
|
+
// 1.9.155: provider 별 모델 목록 — ollama 는 실시간 조회, 그 외는 추천 카탈로그 노출
|
|
10506
|
+
if (state.provider === 'ollama') {
|
|
10507
|
+
const r = await _ollamaListModels();
|
|
10508
|
+
if (r.ok && r.models.length) {
|
|
10509
|
+
log(C.green(` [ollama 실시간] ${r.models.length}개 모델:`));
|
|
10510
|
+
r.models.forEach(m => log(' • ' + m));
|
|
10511
|
+
} else log(C.yel(' ⚠ Ollama 미가동 — ollama serve + ollama pull <model>'));
|
|
10512
|
+
} else {
|
|
10513
|
+
const catalog = _PROVIDER_MODEL_CATALOG[state.provider] || [];
|
|
10514
|
+
if (catalog.length) {
|
|
10515
|
+
log(C.green(` [${state.provider} 추천 모델 카탈로그]`));
|
|
10516
|
+
catalog.forEach(m => log(` • ${m.id}${m.note ? ' — ' + m.note : ''}`));
|
|
10517
|
+
log(C.dim(' ※ 실제 가용 모델은 해당 CLI 의 --help / 공식 문서 참고. :model <name> 으로 변경.'));
|
|
10518
|
+
} else {
|
|
10519
|
+
log(C.dim(` ${state.provider} 의 추천 모델 카탈로그 없음 — :model <name> 으로 직접 지정`));
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10421
10522
|
return false;
|
|
10422
10523
|
}
|
|
10423
10524
|
if (op === 'role') {
|
|
@@ -10425,7 +10526,36 @@ async function _agentRepl(root, opts) {
|
|
|
10425
10526
|
if (!['planner', 'reviewer', 'actor'].includes(r)) { log(C.yel(` ⚠ role 은 planner/reviewer/actor`)); return false; }
|
|
10426
10527
|
state.role = r; rl.setPrompt(prompt()); log(C.green(` role = ${r}`)); return false;
|
|
10427
10528
|
}
|
|
10428
|
-
if (op === 'provider') {
|
|
10529
|
+
if (op === 'provider') {
|
|
10530
|
+
const newProv = rest[0] || state.provider;
|
|
10531
|
+
const validProviders = ['ollama', 'claude', 'codex', 'gemini', 'copilot'];
|
|
10532
|
+
if (!validProviders.includes(newProv)) {
|
|
10533
|
+
log(C.yel(` ⚠ provider 는 ${validProviders.join(' / ')} (받음: ${newProv})`));
|
|
10534
|
+
return false;
|
|
10535
|
+
}
|
|
10536
|
+
// 1.9.154: provider 전환 시 활성 ready 사전 검증 — 비활성/미설치이면 친절한 안내 후 거부 (실제 호출 시 실패 방지)
|
|
10537
|
+
if (newProv === 'ollama') {
|
|
10538
|
+
// Ollama 는 HTTP 기반 — 단순히 LEERNESS_OLLAMA_BASE_URL 확인
|
|
10539
|
+
const url = process.env.LEERNESS_OLLAMA_BASE_URL || '';
|
|
10540
|
+
if (!url) {
|
|
10541
|
+
log(C.yel(` ⚠ ollama base URL 미설정 (LEERNESS_OLLAMA_BASE_URL) — 기본 http://localhost:11434 시도`));
|
|
10542
|
+
}
|
|
10543
|
+
} else {
|
|
10544
|
+
const agent = EXTERNAL_AGENTS.find(a => a.id === newProv);
|
|
10545
|
+
if (agent) {
|
|
10546
|
+
const st = _checkAgent(agent);
|
|
10547
|
+
if (st.status !== 'ready') {
|
|
10548
|
+
log(C.yel(` ⚠ ${newProv} 비활성 (${st.status}) — .env 에서 LEERNESS_ENABLE_${newProv.toUpperCase()}=1 + CLI 설치 필요`));
|
|
10549
|
+
log(C.dim(` (leerness agents list 로 상태 확인) — provider 전환 취소`));
|
|
10550
|
+
return false;
|
|
10551
|
+
}
|
|
10552
|
+
}
|
|
10553
|
+
}
|
|
10554
|
+
state.provider = newProv;
|
|
10555
|
+
rl.setPrompt(prompt());
|
|
10556
|
+
log(C.green(` provider = ${state.provider}`));
|
|
10557
|
+
return false;
|
|
10558
|
+
}
|
|
10429
10559
|
if (op === 'clear') { process.stdout.write('\x1b[2J\x1b[H'); return false; }
|
|
10430
10560
|
if (op === 'reset') { state.history = []; log(C.dim(' history 초기화됨')); return false; }
|
|
10431
10561
|
if (op === 'history') {
|
|
@@ -10435,6 +10565,23 @@ async function _agentRepl(root, opts) {
|
|
|
10435
10565
|
}
|
|
10436
10566
|
if (op === 'save') { saveSession(); log(C.dim(` → ${rel(root, sessionPath())}`)); return false; }
|
|
10437
10567
|
if (op === 'permissions') { permissionsListCmd(root); return false; }
|
|
10568
|
+
if (op === 'status') {
|
|
10569
|
+
// 1.9.155: REPL 안에서 현재 세션 상태 자세히 (provider/model/role/permissions/history/runs)
|
|
10570
|
+
log(C.bold('\n 📊 REPL 세션 상태 (1.9.155)'));
|
|
10571
|
+
log(` provider: ${state.provider}`);
|
|
10572
|
+
log(` model: ${state.model || '(기본)'}`);
|
|
10573
|
+
log(` role: ${state.role} (${_AGENT_ROLE_PROMPTS[state.role]?.slice(0, 60) || ''}...)`);
|
|
10574
|
+
log(` permissions: ${_readPermissions(root).mode || 'basic'}`);
|
|
10575
|
+
log(` history: ${state.history.length}턴 (마지막: ${state.history[state.history.length - 1]?.role || '-'})`);
|
|
10576
|
+
log(` session ID: ${state.sessionId}`);
|
|
10577
|
+
log(` started: ${state.startedAt}`);
|
|
10578
|
+
try {
|
|
10579
|
+
const ready = EXTERNAL_AGENTS.map(a => _checkAgent(a)).filter(c => c.status === 'ready');
|
|
10580
|
+
log(` 활성 CLI: ${ready.length ? ready.map(c => c.id).join(', ') : '(없음)'}`);
|
|
10581
|
+
} catch {}
|
|
10582
|
+
log('');
|
|
10583
|
+
return false;
|
|
10584
|
+
}
|
|
10438
10585
|
// 1.9.150: leerness 내부 명령 slash-commands — :verify / :audit / :handoff / :health
|
|
10439
10586
|
if (op === 'verify' || op === 'audit' || op === 'handoff' || op === 'health') {
|
|
10440
10587
|
const subArgs = {
|
|
@@ -10558,32 +10705,39 @@ async function agentCmd(root, taskArg) {
|
|
|
10558
10705
|
} catch {}
|
|
10559
10706
|
if (dryRun) { log('\n(dry-run) LLM 호출 스킵 — provider/권한/컨텍스트만 출력'); return; }
|
|
10560
10707
|
if (!provider) { fail('활성 provider 없음 — .env 에서 LEERNESS_ENABLE_OLLAMA=1 또는 LEERNESS_ENABLE_CLAUDE=1 활성화'); process.exitCode = 1; return; }
|
|
10561
|
-
//
|
|
10708
|
+
// 1.9.148: role prompt 자동 prepend (모든 provider 공통)
|
|
10709
|
+
const finalPrompt = `${rolePrompt}\n\nTask: ${task}`;
|
|
10710
|
+
const t0 = Date.now();
|
|
10711
|
+
// 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/gemini/copilot 직접 호출 (1.9.153 _cliChat 재사용)
|
|
10712
|
+
let r;
|
|
10562
10713
|
if (provider === 'ollama') {
|
|
10563
10714
|
log('\n[ollama 호출 중...]');
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
log('\n[response (model=' + r.model + ', role=' + role + ', ' + dt + 'ms)]\n' + r.response);
|
|
10573
|
-
try {
|
|
10574
|
-
const tlp = taskLogPath(root);
|
|
10575
|
-
const block = `\n## ${today()} leerness agent (ollama:${r.model}, role=${role})\n- task: ${task.slice(0, 200)}\n- response (preview): ${r.response.slice(0, 240).replace(/\n+/g, ' ')}\n`;
|
|
10576
|
-
append(tlp, block);
|
|
10577
|
-
} catch {}
|
|
10578
|
-
} else {
|
|
10579
|
-
fail(`ollama 호출 실패: ${r.error || 'unknown'}`);
|
|
10580
|
-
log(` → ollama serve 실행 + LEERNESS_OLLAMA_BASE_URL 확인`);
|
|
10581
|
-
process.exitCode = 1;
|
|
10582
|
-
}
|
|
10715
|
+
r = await _ollamaChat(finalPrompt);
|
|
10716
|
+
} else if (['claude', 'codex', 'gemini', 'copilot'].includes(provider)) {
|
|
10717
|
+
log(`\n[${provider} CLI 호출 중...]`);
|
|
10718
|
+
r = await _cliChat(root, provider, finalPrompt, { timeout: 90000 });
|
|
10719
|
+
if (r.ok && !r.model) r.model = provider; // _cliChat 결과 보강
|
|
10720
|
+
} else {
|
|
10721
|
+
fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/gemini/copilot)`);
|
|
10722
|
+
process.exitCode = 1;
|
|
10583
10723
|
return;
|
|
10584
10724
|
}
|
|
10585
|
-
|
|
10586
|
-
|
|
10725
|
+
const dt = Date.now() - t0;
|
|
10726
|
+
// 1.9.149: observability 기록
|
|
10727
|
+
_recordRun(root, { kind: 'agent_one_shot', provider, model: r.model || provider, role, durationMs: dt, ok: r.ok, error: r.error, task: task.slice(0, 200), responseChars: (r.response || '').length });
|
|
10728
|
+
if (r.ok) {
|
|
10729
|
+
log(`\n[response (provider=${provider}, model=${r.model || provider}, role=${role}, ${dt}ms)]\n${r.response}`);
|
|
10730
|
+
try {
|
|
10731
|
+
const tlp = taskLogPath(root);
|
|
10732
|
+
const block = `\n## ${today()} leerness agent (${provider}:${r.model || provider}, role=${role})\n- task: ${task.slice(0, 200)}\n- response (preview): ${(r.response || '').slice(0, 240).replace(/\n+/g, ' ')}\n`;
|
|
10733
|
+
append(tlp, block);
|
|
10734
|
+
} catch {}
|
|
10735
|
+
} else {
|
|
10736
|
+
fail(`${provider} 호출 실패: ${r.error || 'unknown'}`);
|
|
10737
|
+
if (provider === 'ollama') log(` → ollama serve 실행 + LEERNESS_OLLAMA_BASE_URL 확인`);
|
|
10738
|
+
else log(` → .env 에서 LEERNESS_ENABLE_${provider.toUpperCase()}=1 + CLI 설치 확인 (leerness agents list)`);
|
|
10739
|
+
process.exitCode = 1;
|
|
10740
|
+
}
|
|
10587
10741
|
}
|
|
10588
10742
|
|
|
10589
10743
|
// ===== 1.9.147: 자동 유지보수 시스템 (사용자 명시 요청) =====
|