agentlas 0.9.6 → 0.9.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/CHANGELOG.md +30 -0
- package/engine/agentlas-capabilities.cjs +22 -16
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas.cjs +195 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.8 — 2026-07-25
|
|
4
|
+
|
|
5
|
+
- No more silent keyword fallback. When the connected model can't judge a route
|
|
6
|
+
(no runtime, or the model timed out / returned junk), Agentlas no longer picks
|
|
7
|
+
a specialist by keyword — it answers with the plain assistant and says why. The
|
|
8
|
+
note distinguishes "no model connected" (connect one) from "the model didn't
|
|
9
|
+
answer in time" (retry / check it), so a transient timeout isn't mistaken for a
|
|
10
|
+
missing model. Image-capability routing is likewise model-only: a keyword guess
|
|
11
|
+
never hijacks which runtime an agent runs on.
|
|
12
|
+
- The embedded Agentlas OS runtime's own judge (content-guard, pipeline,
|
|
13
|
+
research, privacy) now uses this host's connected model too, via a universal
|
|
14
|
+
callback — so provider/CLI users, not only local Ollama, get real judgment
|
|
15
|
+
there, with no model hardcoded. Pins Agentlas OS v1.1.62.
|
|
16
|
+
|
|
17
|
+
## 0.9.7 — 2026-07-25
|
|
18
|
+
|
|
19
|
+
- Fix: the resident judge now reaches API/Ollama/BYOK runtimes, not only CLI
|
|
20
|
+
subprocess runtimes. Previously, when your connected runtime was Ollama or a
|
|
21
|
+
BYOK API model, every route, intent, and classification silently used the
|
|
22
|
+
deterministic wordlist fallback because the judge was wired to null — so a
|
|
23
|
+
non-English request the keyword lists could not read never got a model
|
|
24
|
+
verdict. The judge now runs on whatever runtime you actually have connected,
|
|
25
|
+
and its timeout signal aborts the underlying request cleanly.
|
|
26
|
+
- Fix: the judge is installed at startup, before the first routing decision.
|
|
27
|
+
It was previously wired only inside the run turn, which happens after routing,
|
|
28
|
+
so the very first auto-route in a one-shot always fell back.
|
|
29
|
+
- Routing gives the model a more generous deadline (a one-shot pre-run gate), so
|
|
30
|
+
a slower local model is judged rather than frequently falling back. When it
|
|
31
|
+
still cannot answer in time, the route receipt says so explicitly.
|
|
32
|
+
|
|
3
33
|
## 0.9.6 — 2026-07-25
|
|
4
34
|
|
|
5
35
|
- Agent and App Builder routing is now decided by the connected model: lexical
|
|
@@ -63,9 +63,10 @@ const IMAGE_TOOL_MARKERS = [/nano-?banana/i, /\bimagen\b/i, /gpt-image/i, /grok\
|
|
|
63
63
|
// 겹치는 정규식 여러 개를 동시에 때려도 1클러스터다.
|
|
64
64
|
const MIN_BODY_IMAGE_SENTENCES = 3;
|
|
65
65
|
const BODY_SCAN_CAP = 16000; // 로컬 임포트 상한과 동일 — 클라우드 무제한 프롬프트의 전문 스캔 방지
|
|
66
|
-
// 어휘
|
|
67
|
-
//
|
|
68
|
-
//
|
|
66
|
+
// 어휘 스코어 — IMAGE_HINTS 단어목록에 대한 참고용 스코어러. 하우스 룰(2026-07-25):
|
|
67
|
+
// 단어목록은 최종 결정을 내리지 않는다. resolveNeedsImage가 IMAGE_HINTS를 힌트로만 모델에
|
|
68
|
+
// 넘기고, 연결 모델이 없으면 이미지 여부를 판정하지 않는다(어휘 폴백 제거). 이 함수는 힌트
|
|
69
|
+
// 품질을 고정하는 참조 스코어러로만 남는다(회귀 테스트가 정밀도를 검증).
|
|
69
70
|
function needsImageLexical(agent) {
|
|
70
71
|
if (!agent) return false;
|
|
71
72
|
if (NON_IMAGE_ROLES.has(String(agent.role || "").toLowerCase())) return false;
|
|
@@ -93,7 +94,7 @@ function needsImageLexical(agent) {
|
|
|
93
94
|
// ── 상주 판정 서비스 배선 — 모델이 의미로 최종 판정, IMAGE_HINTS는 참고 힌트 ──────
|
|
94
95
|
// needsImage 호출자(REPL 배지·autoRuntimeFor·routingNote)는 동기라서 warm-cache 패턴:
|
|
95
96
|
// 비동기 경로(resolveNeedsImage)가 먼저 판정해 캐시를 데우고, 동기 needsImage는 캐시만
|
|
96
|
-
// 읽는다. 캐시 미스 = 어휘 폴백
|
|
97
|
+
// 읽는다. 캐시 미스 = "이미지 아님"으로 처리(어휘 폴백 없음) — 모델 판정만 이미지로 인정한다.
|
|
97
98
|
const IMAGE_VERDICT_CACHE_MAX = 200;
|
|
98
99
|
const imageVerdicts = new Map();
|
|
99
100
|
function imageJudgeInput(agent) {
|
|
@@ -109,17 +110,20 @@ function imageJudgeVetoed(agent) {
|
|
|
109
110
|
return false;
|
|
110
111
|
}
|
|
111
112
|
// 이 에이전트의 직무가 이미지 생산인지를 연결 모델이 의미로 판정한다.
|
|
112
|
-
//
|
|
113
|
+
// 하우스 룰(2026-07-25): 연결 모델이 없으면 단어목록으로 이미지 여부를 결정하지 않는다.
|
|
114
|
+
// 러너 없음/타임아웃/정크 → source:"unavailable"(판정 불가)로 반환하고, 호출자는 안전
|
|
115
|
+
// 기본값(세션 런타임 유지, gemini/codex 하이재킹 금지)을 지킨다. 이미지 판정은 저위험
|
|
116
|
+
// 능력 추론이라 실패-닫힘이 아니라 "판정 안 함"으로 정직하게 둔다.
|
|
113
117
|
async function resolveNeedsImage(agent) {
|
|
114
|
-
|
|
115
|
-
if (imageJudgeVetoed(agent)) return { image:
|
|
118
|
+
// 팀/역할 하드 가드는 구조적 판단(키워드 아님) — 판정 대상에서 제외하고 결정적으로 not-image.
|
|
119
|
+
if (imageJudgeVetoed(agent)) return { image: false, source: "deterministic" };
|
|
116
120
|
let judgment;
|
|
117
121
|
try {
|
|
118
122
|
judgment = require("./agentlas-judgment.cjs");
|
|
119
123
|
} catch {
|
|
120
124
|
judgment = null;
|
|
121
125
|
}
|
|
122
|
-
if (!judgment || !judgment.hasJudgmentRunner()) return { image:
|
|
126
|
+
if (!judgment || !judgment.hasJudgmentRunner()) return { image: false, source: "unavailable", decided: false };
|
|
123
127
|
const input = imageJudgeInput(agent);
|
|
124
128
|
const cached = imageVerdicts.get(input);
|
|
125
129
|
if (cached) return cached;
|
|
@@ -136,9 +140,10 @@ async function resolveNeedsImage(agent) {
|
|
|
136
140
|
"producing them: builders, orchestrators, PMs, curators, and coordination brains that commission or " +
|
|
137
141
|
"delegate image work are 'not-image'. Refusals or prohibitions ('never generate images') declare the " +
|
|
138
142
|
"opposite of a capability.",
|
|
139
|
-
fallback: [
|
|
143
|
+
fallback: [],
|
|
140
144
|
});
|
|
141
|
-
|
|
145
|
+
// 모델이 판정을 못 냈다 → 어휘 폴백 없이 "판정 불가". 캐시하지 않아 이후 모델 연결 시 재판정한다.
|
|
146
|
+
if (verdict.source !== "llm" || !verdict.labels.length) return { image: false, source: "unavailable", decided: false };
|
|
142
147
|
const out = { image: verdict.labels[0] === "image", source: "llm", reason: verdict.reason || "" };
|
|
143
148
|
imageVerdicts.set(input, out);
|
|
144
149
|
if (imageVerdicts.size > IMAGE_VERDICT_CACHE_MAX) {
|
|
@@ -148,14 +153,15 @@ async function resolveNeedsImage(agent) {
|
|
|
148
153
|
return out;
|
|
149
154
|
}
|
|
150
155
|
// Does this agent's job involve generating/handling images? Sync surface for badges and
|
|
151
|
-
// autoRuntimeFor
|
|
156
|
+
// autoRuntimeFor. House rule (2026-07-25): only a warm MODEL verdict counts as "image".
|
|
157
|
+
// A cache miss (no model judgment / not warmed) is treated as "not specifically an image
|
|
158
|
+
// agent" so a keyword guess can never hijack the runtime to gemini/codex. The lexical
|
|
159
|
+
// scorer (needsImageLexical) survives only as the hint source for the judge, not a decision.
|
|
152
160
|
function needsImage(agent) {
|
|
153
161
|
if (!agent) return false;
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
return needsImageLexical(agent);
|
|
162
|
+
if (imageJudgeVetoed(agent)) return false;
|
|
163
|
+
const cached = imageVerdicts.get(imageJudgeInput(agent));
|
|
164
|
+
return cached ? cached.image : false;
|
|
159
165
|
}
|
|
160
166
|
// 라벨용 — 이 에이전트의 현재 이미지 판정이 모델("llm")인지 결정적 경로("deterministic")인지.
|
|
161
167
|
function imageJudgmentSource(agent) {
|
|
Binary file
|
package/engine/agentlas.cjs
CHANGED
|
@@ -573,6 +573,34 @@ function directRouteChoice(lang) {
|
|
|
573
573
|
: "this is a general request that needs no specialist agent",
|
|
574
574
|
};
|
|
575
575
|
}
|
|
576
|
+
// 연결 모델 없음 직답 — 하우스 룰: 모델이 없으면 어휘(단어목록)로 전문 에이전트를 고르지
|
|
577
|
+
// 않는다. 어떤 전문 에이전트가 맞는지 "판단하지 못한다"고 정직하게 밝히고 기본 어시스턴트로
|
|
578
|
+
// 답한다. routeSource는 "deterministic"(=판정 없음)으로 유지하되 noModel 플래그로 구분해
|
|
579
|
+
// 사용자에게 "모델 연결" 안내를 띄운다. 이 경로가 조용히 키워드로 결정하던 것이 사고의 근본.
|
|
580
|
+
// reason 구분: "no_runtime"=연결된 런타임이 아예 없음(드묾), "model_unavailable"=런타임은
|
|
581
|
+
// 연결됐지만 그 한 번의 판정이 타임아웃/불량 응답으로 실패(현실적 케이스). 모델이 거의 항상
|
|
582
|
+
// 연결돼 있으므로 후자를 "연결 안 됨"으로 오인시키지 않도록 문구를 다르게 준다.
|
|
583
|
+
function noModelRouteChoice(lang, reason = "no_runtime") {
|
|
584
|
+
const resolvedLang = lang || prefsLang();
|
|
585
|
+
const message = reason === "model_unavailable"
|
|
586
|
+
? (resolvedLang === "ko"
|
|
587
|
+
? "연결된 모델이 제때 응답하지 않아 어떤 전문 에이전트가 맞는지 판단하지 못했습니다. 잠시 후 다시 시도하거나 모델 상태를 확인해 주세요. 지금은 기본 어시스턴트로 답합니다"
|
|
588
|
+
: "the connected model didn't answer in time, so I couldn't judge which specialist agent fits; retry in a moment or check the model, and I'll answer with the default assistant for now")
|
|
589
|
+
: (resolvedLang === "ko"
|
|
590
|
+
? "연결된 모델이 없어 어떤 전문 에이전트가 맞는지 판단하지 못했습니다. 모델을 연결하면 자동 라우팅이 됩니다. 지금은 기본 어시스턴트로 답합니다"
|
|
591
|
+
: "no model is connected, so I couldn't judge which specialist agent fits; connect a model to enable auto-routing, and I'll answer with the default assistant for now");
|
|
592
|
+
return {
|
|
593
|
+
direct: true,
|
|
594
|
+
agent: null,
|
|
595
|
+
score: 0,
|
|
596
|
+
terms: [],
|
|
597
|
+
strong: false,
|
|
598
|
+
noModel: true,
|
|
599
|
+
noModelReason: reason,
|
|
600
|
+
routeSource: "deterministic",
|
|
601
|
+
reason: message,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
576
604
|
// 직답 모드 시스템 프롬프트 — 페르소나·라우팅 오염 없이 현재 런타임 그대로 답한다.
|
|
577
605
|
function directSystemPrompt(lang) {
|
|
578
606
|
const resolvedLang = lang || prefsLang();
|
|
@@ -630,8 +658,10 @@ function autoRouteAgent(db, prompt, lang) {
|
|
|
630
658
|
// direct) 중에서 의미로 하나를 고른다 — 어휘 점수 0점인 에이전트(아랍어 등 어떤 언어의
|
|
631
659
|
// 요청이든)도 모델은 뽑을 수 있다. 닫힌형 가드는 결정적으로 유지: 잡담 short-circuit
|
|
632
660
|
// (isTrivialRoutePrompt)과 경로 스트리핑(ROUTE_PATH_RE)은 모델 호출 전에 그대로 적용.
|
|
633
|
-
// 러너 없음/타임아웃/정크 →
|
|
634
|
-
//
|
|
661
|
+
// 러너 없음/타임아웃/정크 → 어휘로 전문 에이전트를 고르지 않는다. 연결 모델이 없으면
|
|
662
|
+
// "판단 못 함"을 정직하게 밝히고 기본 어시스턴트로 직답한다(noModelRouteChoice) —
|
|
663
|
+
// 어휘 스코어러(autoRouteAgent/rankRouteAgents)는 후보 모집 전용이지 반환 결정이 아니다.
|
|
664
|
+
// routeSource:"deterministic"은 "판정 없음"을 뜻하는 기계 플래그로만 남는다(키워드 픽 아님).
|
|
635
665
|
const ROUTE_JUDGE_CANDIDATE_CAP = 30;
|
|
636
666
|
const ROUTE_JUDGE_DIRECT_LABEL = "direct";
|
|
637
667
|
const ROUTE_JUDGE_META_LABEL = "meta-builder";
|
|
@@ -639,20 +669,22 @@ const ROUTE_JUDGE_APP_LABEL = "app-builder";
|
|
|
639
669
|
const APP_BUILDER_ROUTE_SLUG = "agentlas-app-builder";
|
|
640
670
|
async function resolveAutoRoute(db, prompt, lang) {
|
|
641
671
|
const resolvedLang = lang || prefsLang();
|
|
642
|
-
|
|
672
|
+
ensureJudgmentRunnerInstalled(db);
|
|
643
673
|
let judgment;
|
|
644
674
|
try {
|
|
645
675
|
judgment = require("./agentlas-judgment.cjs");
|
|
646
676
|
} catch {
|
|
647
677
|
judgment = null;
|
|
648
678
|
}
|
|
649
|
-
|
|
679
|
+
// 연결 모델 없음 → 어휘로 전문 에이전트를 고르지 않는다. 정직하게 "판단 못 함" + 모델 연결 안내.
|
|
680
|
+
if (!judgment || !judgment.hasJudgmentRunner()) return noModelRouteChoice(resolvedLang, "no_runtime");
|
|
650
681
|
const promptText = routeNormalize(routeStripPaths(prompt));
|
|
651
|
-
// 잡담("hi")
|
|
652
|
-
if (!promptText.trim() || isTrivialRoutePrompt(promptText)) return { ...
|
|
682
|
+
// 잡담("hi")은 닫힌형 결정적 가드로 직답 — 모델은 연결돼 있으니 "모델 연결" 안내는 필요 없다.
|
|
683
|
+
if (!promptText.trim() || isTrivialRoutePrompt(promptText)) return { ...directRouteChoice(resolvedLang), routeSource: "deterministic" };
|
|
653
684
|
const ranked = rankRouteAgents(db, prompt, resolvedLang);
|
|
654
685
|
const meta = resolveMetaBuilder(db);
|
|
655
|
-
|
|
686
|
+
// 라우팅할 후보 자체가 없음(설치 에이전트 0) → 직답. 모델은 연결돼 있으니 일반 직답 사유.
|
|
687
|
+
if (!ranked.length && !meta) return { ...directRouteChoice(resolvedLang), routeSource: "deterministic" };
|
|
656
688
|
// App Builder는 합성 라벨로만 제시한다(동의 핸드셰이크가 걸린 특수 라우트임을 모델에 명시).
|
|
657
689
|
const appBuilder = ranked.find((r) => r.agent.slug === APP_BUILDER_ROUTE_SLUG) || null;
|
|
658
690
|
const candidates = ranked.filter((r) => r.agent.slug !== APP_BUILDER_ROUTE_SLUG).slice(0, ROUTE_JUDGE_CANDIDATE_CAP);
|
|
@@ -700,8 +732,15 @@ async function resolveAutoRoute(db, prompt, lang) {
|
|
|
700
732
|
].join("\n"),
|
|
701
733
|
multi: false,
|
|
702
734
|
fallback: [],
|
|
735
|
+
// Routing is a one-shot pre-run gate: correctness matters more than latency,
|
|
736
|
+
// and a local 30B model with a full roster can need well over the 20s default.
|
|
737
|
+
// The judge's abort signal is threaded into the request, so a genuinely hung
|
|
738
|
+
// model still aborts cleanly at this deadline rather than hanging forever.
|
|
739
|
+
timeoutMs: 40000,
|
|
703
740
|
});
|
|
704
|
-
|
|
741
|
+
// 모델이 판정을 못 냈다(러너 실패/타임아웃/정크 응답) → 어휘 픽으로 떨어지지 않는다.
|
|
742
|
+
// 연결 모델이 사실상 판단하지 못한 것이므로 "판단 못 함" 직답으로 정직하게 종결한다.
|
|
743
|
+
if (verdict.source !== "llm" || !verdict.labels.length) return noModelRouteChoice(resolvedLang, "model_unavailable");
|
|
705
744
|
const picked = verdict.labels[0];
|
|
706
745
|
const reason =
|
|
707
746
|
verdict.reason ||
|
|
@@ -711,15 +750,24 @@ async function resolveAutoRoute(db, prompt, lang) {
|
|
|
711
750
|
return { agent: meta, score: 1000, strong: true, terms: [], reason, routeSource: "llm" };
|
|
712
751
|
}
|
|
713
752
|
const chosen = picked === ROUTE_JUDGE_APP_LABEL ? appBuilder : bySlug.get(picked);
|
|
714
|
-
|
|
753
|
+
// 모델이 라벨을 골랐지만 설치 에이전트로 해석되지 않음(경계 케이스) → 어휘 픽 대신 직답.
|
|
754
|
+
if (!chosen) return { ...directRouteChoice(resolvedLang), routeSource: "deterministic" };
|
|
715
755
|
// 모델 확답은 strong 계약을 충족한다 — 어휘 근거(terms/score)는 참고로 보존.
|
|
716
756
|
return { ...chosen, strong: true, reason, routeSource: "llm" };
|
|
717
757
|
}
|
|
718
758
|
// 라우트 영수증 라벨 — 누가 최종 판정했는지 반드시 찍는다(조용한 폴백 금지 하우스 룰).
|
|
759
|
+
// noModel 경로는 사유(reason)에 이미 "모델 연결" 안내가 담겨 있으니 짧은 기계 라벨만 덧붙인다.
|
|
760
|
+
// 그 외 deterministic(잡담·후보 없음: 모델은 연결됨)은 사유가 이미 설명하므로 라벨을 붙이지 않는다.
|
|
719
761
|
function routeJudgeSourceNote(choice, lang) {
|
|
720
762
|
if (!choice || !choice.routeSource) return "";
|
|
721
763
|
if (choice.routeSource === "llm") return lang === "ko" ? " (판정: 연결 모델)" : " (judged by the connected model)";
|
|
722
|
-
|
|
764
|
+
if (choice.noModel) {
|
|
765
|
+
if (choice.noModelReason === "model_unavailable") {
|
|
766
|
+
return lang === "ko" ? " (판정 없음 — 모델 응답 없음)" : " (no judgment — model did not answer)";
|
|
767
|
+
}
|
|
768
|
+
return lang === "ko" ? " (판정 없음 — 연결 모델 없음)" : " (no judgment — no model connected)";
|
|
769
|
+
}
|
|
770
|
+
return "";
|
|
723
771
|
}
|
|
724
772
|
function autoRouteNote(choice, lang) {
|
|
725
773
|
const resolvedLang = lang || prefsLang();
|
|
@@ -8617,8 +8665,9 @@ const RUNTIME_BIN = {
|
|
|
8617
8665
|
* Wire the resident judgment service to whatever runtime the user actually has connected.
|
|
8618
8666
|
* This is what lets classification be decided by MEANING instead of by a wordlist: the
|
|
8619
8667
|
* judge asks the connected model, and the old keyword maps are passed only as hints. It is
|
|
8620
|
-
* invisible to the user (no output, read-only, no tools)
|
|
8621
|
-
* to the
|
|
8668
|
+
* invisible to the user (no output, read-only, no tools). When no runtime answers, judged
|
|
8669
|
+
* callers do NOT keyword-decide: routing/intent go to the plain assistant with a "connect a
|
|
8670
|
+
* model" note, capability inference returns undecided, and gates fail closed.
|
|
8622
8671
|
*/
|
|
8623
8672
|
function installJudgmentRunner(db, rt) {
|
|
8624
8673
|
let judgment;
|
|
@@ -8627,32 +8676,129 @@ function installJudgmentRunner(db, rt) {
|
|
|
8627
8676
|
} catch {
|
|
8628
8677
|
return;
|
|
8629
8678
|
}
|
|
8630
|
-
|
|
8631
|
-
|
|
8679
|
+
// CLI runtime (claude-code/codex/gemini): the judge runs a silent native turn.
|
|
8680
|
+
if (rt && rt.mode === "cli" && RUNTIME_BIN[rt.kind]) {
|
|
8681
|
+
judgment.setJudgmentRunner(async ({ system, prompt, signal }) => {
|
|
8682
|
+
const { runNativeTurn } = require("./agentlas-native-host.cjs");
|
|
8683
|
+
const { Ui } = require("./agentlas-ui.cjs");
|
|
8684
|
+
const silent = new Ui({ lang: prefsLang(), quiet: true });
|
|
8685
|
+
for (const method of ["write", "warn", "info", "tool", "result", "status", "line"]) {
|
|
8686
|
+
if (typeof silent[method] === "function") silent[method] = () => {};
|
|
8687
|
+
}
|
|
8688
|
+
const res = await runNativeTurn({
|
|
8689
|
+
kind: rt.kind,
|
|
8690
|
+
bin: RUNTIME_BIN[rt.kind],
|
|
8691
|
+
prompt,
|
|
8692
|
+
systemPrompt: system,
|
|
8693
|
+
cwd: projectCwd(),
|
|
8694
|
+
permission: "read",
|
|
8695
|
+
session: {},
|
|
8696
|
+
ui: silent,
|
|
8697
|
+
env: process.env,
|
|
8698
|
+
signal,
|
|
8699
|
+
});
|
|
8700
|
+
if (res && res.error) return "";
|
|
8701
|
+
return (res && res.text) || "";
|
|
8702
|
+
});
|
|
8632
8703
|
return;
|
|
8633
8704
|
}
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8705
|
+
|
|
8706
|
+
// API runtime (BYOK / Ollama): the judge uses the same one-shot API path the
|
|
8707
|
+
// rest of the terminal uses, so "the connected model decides" holds for the
|
|
8708
|
+
// user's actual runtime — not only CLI subprocess runtimes. Without this, an
|
|
8709
|
+
// Ollama/BYOK user silently gets the deterministic wordlist fallback for every
|
|
8710
|
+
// route, intent, and classification decision.
|
|
8711
|
+
if (rt && rt.mode === "api" && rt.backend) {
|
|
8712
|
+
judgment.setJudgmentRunner(async ({ system, prompt, signal }) => {
|
|
8713
|
+
// Thread the judge's timeout signal into the actual HTTP request so a slow
|
|
8714
|
+
// model aborts the fetch instead of hanging past the deadline.
|
|
8715
|
+
const baseFetch = globalThis.fetch;
|
|
8716
|
+
const fetchImpl = typeof baseFetch === "function" && signal
|
|
8717
|
+
? (url, init) => baseFetch(url, { ...(init || {}), signal })
|
|
8718
|
+
: baseFetch;
|
|
8719
|
+
try {
|
|
8720
|
+
return await runApi(rt.backend, rt.model, system, prompt, { fetch: fetchImpl });
|
|
8721
|
+
} catch {
|
|
8722
|
+
return "";
|
|
8723
|
+
}
|
|
8652
8724
|
});
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8725
|
+
return;
|
|
8726
|
+
}
|
|
8727
|
+
|
|
8728
|
+
judgment.setJudgmentRunner(null);
|
|
8729
|
+
}
|
|
8730
|
+
|
|
8731
|
+
// Make sure the resident judge can reach the connected model BEFORE the first
|
|
8732
|
+
// judged decision (routing, image capability, task class). The only other
|
|
8733
|
+
// install site is inside executeOnce, which runs AFTER routing — so a one-shot
|
|
8734
|
+
// `run`/auto-route would always fall to the deterministic label without this.
|
|
8735
|
+
// Uses the active runtime (CLI or API/Ollama/BYOK); a pinned override still
|
|
8736
|
+
// reinstalls in executeOnce for the actual task turn.
|
|
8737
|
+
function ensureJudgmentRunnerInstalled(db) {
|
|
8738
|
+
let judgment;
|
|
8739
|
+
try {
|
|
8740
|
+
judgment = require("./agentlas-judgment.cjs");
|
|
8741
|
+
} catch {
|
|
8742
|
+
return;
|
|
8743
|
+
}
|
|
8744
|
+
if (judgment.hasJudgmentRunner()) return;
|
|
8745
|
+
try {
|
|
8746
|
+
const ar = activeRuntime(db);
|
|
8747
|
+
let rt = null;
|
|
8748
|
+
if (ar && RUNTIME_BIN[ar.kind]) rt = { mode: "cli", kind: ar.kind, model: ar.model || null };
|
|
8749
|
+
else if (ar && ar.kind === "byok" && ar.backend) rt = { mode: "api", backend: ar.backend, model: ar.model };
|
|
8750
|
+
else if (ar && ar.kind === "ollama") rt = { mode: "api", backend: "ollama", model: ar.model };
|
|
8751
|
+
if (rt) installJudgmentRunner(db, rt);
|
|
8752
|
+
} catch {
|
|
8753
|
+
// No resolvable runtime → leave the runner unset; judged sites use their
|
|
8754
|
+
// labeled deterministic fallback rather than crashing.
|
|
8755
|
+
}
|
|
8756
|
+
}
|
|
8757
|
+
|
|
8758
|
+
// Hand the embedded Agentlas OS runtime a UNIVERSAL judge callback so ITS
|
|
8759
|
+
// resident judge (content-guard, pipeline, research, privacy) decides by meaning
|
|
8760
|
+
// for EVERY connected runtime — Claude/Codex/Gemini CLIs, all BYOK providers, and
|
|
8761
|
+
// local models alike — with no model hardcoded and no provider code duplicated in
|
|
8762
|
+
// Python. The OS engine stays BYOC: it never calls a model on its own; it only
|
|
8763
|
+
// invokes this host command, which runs the user's own connected runtime. If no
|
|
8764
|
+
// runtime resolves, the env is left unset and OS judged sites honestly report
|
|
8765
|
+
// "connect a model" instead of keyword-deciding.
|
|
8766
|
+
function exportOsJudgeRuntimeEnv(db) {
|
|
8767
|
+
try {
|
|
8768
|
+
const ar = activeRuntime(db);
|
|
8769
|
+
if (!ar) return;
|
|
8770
|
+
const config = {
|
|
8771
|
+
kind: "host-cmd",
|
|
8772
|
+
cmd: [process.execPath, path.resolve(__dirname, "..", "bin", "agentlas.cjs"), "__judge"],
|
|
8773
|
+
};
|
|
8774
|
+
process.env.AGENTLAS_JUDGE_RUNTIME = JSON.stringify(config);
|
|
8775
|
+
} catch {
|
|
8776
|
+
// Leave the env unset — OS judged sites then report "connect a model" rather
|
|
8777
|
+
// than keyword-deciding.
|
|
8778
|
+
}
|
|
8779
|
+
}
|
|
8780
|
+
|
|
8781
|
+
// Hidden helper the embedded OS runtime calls for one-shot classification. Reads
|
|
8782
|
+
// {"system","prompt"} JSON on stdin, runs the connected runtime (any provider/CLI)
|
|
8783
|
+
// as a silent read-only turn, prints the reply text on stdout. Judgment is a
|
|
8784
|
+
// lightweight task, so this uses whatever model the user already has connected —
|
|
8785
|
+
// no hardcoded model, no separate credentials.
|
|
8786
|
+
async function cmdInternalJudge(db) {
|
|
8787
|
+
let raw = "";
|
|
8788
|
+
try {
|
|
8789
|
+
raw = await readStdin();
|
|
8790
|
+
const { system, prompt } = JSON.parse(raw || "{}");
|
|
8791
|
+
if (!prompt || !prompt.trim()) { process.stdout.write(""); return; }
|
|
8792
|
+
let judgment;
|
|
8793
|
+
try { judgment = require("./agentlas-judgment.cjs"); } catch { judgment = null; }
|
|
8794
|
+
ensureJudgmentRunnerInstalled(db);
|
|
8795
|
+
if (!judgment || !judgment.hasJudgmentRunner()) { process.stdout.write(""); return; }
|
|
8796
|
+
// Reuse the installed runner directly for an opaque system+prompt turn.
|
|
8797
|
+
const text = await judgment.runRaw(String(system || ""), String(prompt), 40000);
|
|
8798
|
+
process.stdout.write(text || "");
|
|
8799
|
+
} catch {
|
|
8800
|
+
process.stdout.write("");
|
|
8801
|
+
}
|
|
8656
8802
|
}
|
|
8657
8803
|
|
|
8658
8804
|
function resolveRuntime(db, override) {
|
|
@@ -11878,6 +12024,17 @@ async function main() {
|
|
|
11878
12024
|
// Agentlas 아키텍처 빌트인 에이전트를 보장(앱과 동일, 멱등·버전 게이팅). 스키마가 준비됐을 때만.
|
|
11879
12025
|
try { seedBuiltins(db); } catch { /* best-effort */ }
|
|
11880
12026
|
|
|
12027
|
+
// Wire the resident judge to the connected runtime up front, so the FIRST
|
|
12028
|
+
// routing / image-capability / task-class decision is judged by the model —
|
|
12029
|
+
// in the REPL and in one-shot commands alike — instead of silently falling to
|
|
12030
|
+
// the deterministic label until executeOnce eventually installs it.
|
|
12031
|
+
ensureJudgmentRunnerInstalled(db);
|
|
12032
|
+
// Hidden judge-callback for the embedded OS runtime — handle before exporting
|
|
12033
|
+
// the env or dispatching, and never re-enter the OS from here.
|
|
12034
|
+
if (cmd === "__judge") return cmdInternalJudge(db);
|
|
12035
|
+
// Hand the same connected model to the embedded Agentlas OS runtime's judge.
|
|
12036
|
+
exportOsJudgeRuntimeEnv(db);
|
|
12037
|
+
|
|
11881
12038
|
// 인자 없이 `agentlas` → 에이전트 1개면 바로 대화형, 아니면 목록 + 사용법
|
|
11882
12039
|
if (cmd === "") {
|
|
11883
12040
|
const agents = listAgents(db);
|
|
@@ -12140,6 +12297,9 @@ module.exports = {
|
|
|
12140
12297
|
autoRouteNote,
|
|
12141
12298
|
autoRoutePreamble,
|
|
12142
12299
|
directSystemPrompt,
|
|
12300
|
+
// Test bridge: prove the resident judge is wired to API/Ollama/BYOK runtimes,
|
|
12301
|
+
// not only CLI subprocess runtimes.
|
|
12302
|
+
installJudgmentRunner,
|
|
12143
12303
|
// Hub 플러그인 설치 회귀 테스트 표면 — 레포 URL을 MCP 서버로 등록하지 않는 규칙 검증용.
|
|
12144
12304
|
pluginMcpRowCli,
|
|
12145
12305
|
planPluginMcpInstallCli,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentlas": "bin/agentlas.cjs"
|