cdsa-harness 0.22.0 → 0.22.2
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/package.json +1 -1
- package/src/builtins.js +1 -1
- package/src/cli.js +2 -2
- package/src/llm.js +63 -5
package/package.json
CHANGED
package/src/builtins.js
CHANGED
package/src/cli.js
CHANGED
|
@@ -492,11 +492,11 @@ function printHelp() {
|
|
|
492
492
|
"",
|
|
493
493
|
c.bold("대화"),
|
|
494
494
|
` ${c.cyan("/new")}(=/clear) 새 대화 · ${c.cyan("/compact")} 대화를 모델이 요약해 압축 · ${c.cyan("/resume")} 지난 대화 이어가기`,
|
|
495
|
-
` ${c.cyan("/undo")}
|
|
495
|
+
` ${c.cyan("/undo")} ${c.cyan("/redo")} 변경 되돌리기/재적용 · ${c.cyan("/todos")} 에이전트 할일 · ${c.cyan("/context")} 컨텍스트`,
|
|
496
496
|
"",
|
|
497
497
|
c.bold("프로젝트"),
|
|
498
498
|
` ${c.cyan("/init")} 모델이 폴더를 파악해 AGENT.md(프로젝트 규칙) 생성 · ${c.cyan("/memory")} 규칙 파일 보기`,
|
|
499
|
-
` ${c.cyan("/workspace")} <폴더>
|
|
499
|
+
` ${c.cyan("/workspace")} <폴더> 작업 폴더 · ${c.cyan("/agent")} 모드(build/plan/커스텀) · ${c.cyan("!")}cmd 셸 직접실행`,
|
|
500
500
|
"",
|
|
501
501
|
c.bold("모델·설정"),
|
|
502
502
|
` ${c.cyan("/setup")} 연결 마법사 · ${c.cyan("/model")} <이름> 변경 · ${c.cyan("/models")} 목록(ollama 는 설치분) · ${c.cyan("/provider")} 전환`,
|
package/src/llm.js
CHANGED
|
@@ -68,11 +68,7 @@ export class LLMClient {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
_netErrorMessage(e) {
|
|
71
|
-
|
|
72
|
-
if (this.provider === "ollama") {
|
|
73
|
-
msg += "\n ↳ Ollama 가 실행 중인지 확인하세요: `ollama serve` (모델 설치: `ollama pull qwen2.5:7b`)";
|
|
74
|
-
}
|
|
75
|
-
return msg;
|
|
71
|
+
return describeNetError(e, this.provider, this.timeout);
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
async _post(url, headers, body) {
|
|
@@ -232,6 +228,68 @@ export class LLMClient {
|
|
|
232
228
|
}
|
|
233
229
|
}
|
|
234
230
|
|
|
231
|
+
// Node 내장 fetch(undici)는 실패하면 겉면 메시지가 "fetch failed" 하나뿐이고,
|
|
232
|
+
// 진짜 원인(DNS 실패·프록시·인증서 등)은 e.cause 체인 안에 숨어 있다.
|
|
233
|
+
// 여기서 원인을 끝까지 파고들어 사용자에게 코드와 해결 힌트까지 보여준다.
|
|
234
|
+
export function describeNetError(e, provider = "", timeout = 0) {
|
|
235
|
+
// 자체 타임아웃(AbortController)에 걸린 경우: fetch 는 AbortError 를 던진다.
|
|
236
|
+
if (e?.name === "AbortError" || e?.name === "TimeoutError") {
|
|
237
|
+
return `네트워크 오류: 응답 시간 초과(${timeout}ms). 네트워크가 느리거나 요청이 너무 클 수 있어요.`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// cause 체인(+AggregateError.errors)을 평탄화해서 원인 오류들을 모두 수집.
|
|
241
|
+
const causes = [];
|
|
242
|
+
const seen = new Set();
|
|
243
|
+
const walk = (err) => {
|
|
244
|
+
if (!err || typeof err !== "object" || seen.has(err)) return;
|
|
245
|
+
seen.add(err);
|
|
246
|
+
causes.push(err);
|
|
247
|
+
if (Array.isArray(err.errors)) err.errors.forEach(walk);
|
|
248
|
+
walk(err.cause);
|
|
249
|
+
};
|
|
250
|
+
walk(e?.cause);
|
|
251
|
+
|
|
252
|
+
const codes = [...new Set(causes.map((c) => c.code).filter(Boolean))];
|
|
253
|
+
const detail = causes.map((c) => c.message).filter(Boolean).slice(-1)[0] || "";
|
|
254
|
+
|
|
255
|
+
let msg = `네트워크 오류: ${e?.message || e}`;
|
|
256
|
+
if (detail && detail !== e?.message) msg += `\n ↳ 원인: ${detail}${codes.length ? ` [${codes.join(", ")}]` : ""}`;
|
|
257
|
+
else if (codes.length) msg += `\n ↳ 원인 코드: ${codes.join(", ")}`;
|
|
258
|
+
|
|
259
|
+
const has = (...cs) => cs.some((c) => codes.includes(c));
|
|
260
|
+
if (has("ENOTFOUND", "EAI_AGAIN")) {
|
|
261
|
+
msg += "\n ↳ DNS 조회 실패예요. 인터넷 연결을 확인하세요. 사내망이라면 프록시 뒤일 가능성이 큽니다(아래 프록시 안내 참고).";
|
|
262
|
+
}
|
|
263
|
+
if (has("ECONNREFUSED", "ETIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ENETUNREACH", "UND_ERR_CONNECT_TIMEOUT")) {
|
|
264
|
+
msg += "\n ↳ 서버까지 연결이 안 돼요. 방화벽·VPN·보안 프로그램이 막고 있거나 프록시가 필요한 환경일 수 있습니다.";
|
|
265
|
+
}
|
|
266
|
+
if (
|
|
267
|
+
has(
|
|
268
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
269
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
270
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
271
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
272
|
+
"CERT_HAS_EXPIRED",
|
|
273
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
274
|
+
)
|
|
275
|
+
) {
|
|
276
|
+
msg +=
|
|
277
|
+
"\n ↳ TLS 인증서 검증 실패 — 회사 보안 장비가 HTTPS 를 가로채는(SSL 인터셉트) 환경으로 보여요. " +
|
|
278
|
+
"사내 루트 CA 인증서(pem)를 받아 `NODE_EXTRA_CA_CERTS=<CA 파일 경로>` 환경변수를 설정하고 다시 실행하세요.";
|
|
279
|
+
}
|
|
280
|
+
// Node 의 fetch 는 브라우저와 달리 시스템/환경변수 프록시 설정을 기본으로 무시한다.
|
|
281
|
+
// 프록시가 원인일 수 있는 실패라면 사용법을 알려준다.
|
|
282
|
+
if (codes.length === 0 || has("ENOTFOUND", "EAI_AGAIN", "ECONNREFUSED", "ETIMEDOUT", "ECONNRESET", "UND_ERR_CONNECT_TIMEOUT")) {
|
|
283
|
+
msg +=
|
|
284
|
+
"\n ↳ 프록시 환경이라면: Node 의 fetch 는 HTTPS_PROXY 설정을 기본으로 무시해요. " +
|
|
285
|
+
"`NODE_USE_ENV_PROXY=1` 과 `HTTPS_PROXY=http://<프록시주소:포트>` 환경변수를 함께 설정하고 다시 실행하세요(Node 22.18+).";
|
|
286
|
+
}
|
|
287
|
+
if (provider === "ollama") {
|
|
288
|
+
msg += "\n ↳ Ollama 가 실행 중인지 확인하세요: `ollama serve` (모델 설치: `ollama pull qwen2.5:7b`)";
|
|
289
|
+
}
|
|
290
|
+
return msg;
|
|
291
|
+
}
|
|
292
|
+
|
|
235
293
|
function trim(s) {
|
|
236
294
|
s = String(s || "");
|
|
237
295
|
return s.length > 400 ? s.slice(0, 400) + " …" : s;
|