agentlas 0.9.7 → 0.9.9
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 +39 -0
- package/README.md +19 -0
- package/engine/agentlas-capabilities.cjs +22 -16
- package/engine/agentlas-judgment.cjs +0 -0
- package/engine/agentlas-workforce.cjs +18 -4
- package/engine/agentlas.cjs +156 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.9 — 2026-07-26
|
|
4
|
+
|
|
5
|
+
- In an explicitly initialized project, ordinary runs, firms, Stormbreaker, and
|
|
6
|
+
Workforce now receive the same local Context Slice from Agentlas OS v1.1.66
|
|
7
|
+
after the task is concrete. The slice carries inherited goals and constraints
|
|
8
|
+
plus exact definitions, backlinks, and structurally related files without
|
|
9
|
+
sending the project map to Hub or Cloud.
|
|
10
|
+
- Read, write, and full task permission no longer doubles as consent to create
|
|
11
|
+
`.agentlas/` or edit `.gitignore`. `agentlas project status` is passive, and
|
|
12
|
+
only the explicit `agentlas project init` boundary creates private project
|
|
13
|
+
state after announcing the side effects.
|
|
14
|
+
- Workload-routing receipts now conform to the public v1 nested schema, treat a
|
|
15
|
+
missing model/runtime pair as unresolved, and fail closed on unknown required
|
|
16
|
+
metadata or ambiguous duplicate model identifiers.
|
|
17
|
+
- The shared architecture seeder is monotonic: it preserves newer or
|
|
18
|
+
unparseable shared state, upgrades older state atomically, and repairs only
|
|
19
|
+
missing built-ins at an equal version instead of flipping the Desktop-shared
|
|
20
|
+
database back to an older Terminal bundle.
|
|
21
|
+
- Interactive output is append-only while a task runs, preserves long streamed
|
|
22
|
+
output and scrollback through resize/cancellation, wraps command meaning at
|
|
23
|
+
narrow widths, and keeps saved runtime, permission, lifecycle, and Korean/
|
|
24
|
+
English status text truthful.
|
|
25
|
+
- Release validation targets the exact Agentlas OS v1.1.66 contract commit
|
|
26
|
+
`3f6f9ac3929b9238330de18c758ba200fb371017`.
|
|
27
|
+
|
|
28
|
+
## 0.9.8 — 2026-07-25
|
|
29
|
+
|
|
30
|
+
- No more silent keyword fallback. When the connected model can't judge a route
|
|
31
|
+
(no runtime, or the model timed out / returned junk), Agentlas no longer picks
|
|
32
|
+
a specialist by keyword — it answers with the plain assistant and says why. The
|
|
33
|
+
note distinguishes "no model connected" (connect one) from "the model didn't
|
|
34
|
+
answer in time" (retry / check it), so a transient timeout isn't mistaken for a
|
|
35
|
+
missing model. Image-capability routing is likewise model-only: a keyword guess
|
|
36
|
+
never hijacks which runtime an agent runs on.
|
|
37
|
+
- The embedded Agentlas OS runtime's own judge (content-guard, pipeline,
|
|
38
|
+
research, privacy) now uses this host's connected model too, via a universal
|
|
39
|
+
callback — so provider/CLI users, not only local Ollama, get real judgment
|
|
40
|
+
there, with no model hardcoded. Pins Agentlas OS v1.1.62.
|
|
41
|
+
|
|
3
42
|
## 0.9.7 — 2026-07-25
|
|
4
43
|
|
|
5
44
|
- Fix: the resident judge now reaches API/Ollama/BYOK runtimes, not only CLI
|
package/README.md
CHANGED
|
@@ -116,6 +116,25 @@ agentlas import <폴더> # 로컬 에이전트/팀 임포트
|
|
|
116
116
|
agentlas list # 설치된 에이전트/회사 + 활성 런타임
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
**프로젝트 의존관계 지도**
|
|
120
|
+
```sh
|
|
121
|
+
agentlas project status
|
|
122
|
+
agentlas project init
|
|
123
|
+
agentlas context refresh
|
|
124
|
+
agentlas context refs resolveHubEntityKind
|
|
125
|
+
agentlas context slice --task "entity kind 라우팅 수정" \
|
|
126
|
+
--target src/package-kind.ts --render
|
|
127
|
+
agentlas context impact --changed src/package-kind.ts
|
|
128
|
+
agentlas context verify --changed src/package-kind.ts \
|
|
129
|
+
--reviewed src/register/route.ts
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`agentlas project init`으로 명시적으로 초기화한 프로젝트에서만 일반
|
|
133
|
+
실행, 팀, Stormbreaker, Workforce가 작업이 구체화된 뒤 같은 로컬 Context
|
|
134
|
+
Slice를 자동으로 받는다. 읽기·쓰기·전체 권한만으로는 `.agentlas/`나
|
|
135
|
+
`.gitignore`를 만들거나 수정하지 않는다. Hub/Cloud 검색에는 코드맵,
|
|
136
|
+
소스 경로, 프로젝트 파일 내용이 전송되지 않는다.
|
|
137
|
+
|
|
119
138
|
**Portable Experience Bundle과 Variant**
|
|
120
139
|
```sh
|
|
121
140
|
agentlas experience list
|
|
@@ -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
|
|
@@ -1643,7 +1643,13 @@ function create(deps = {}) {
|
|
|
1643
1643
|
}
|
|
1644
1644
|
|
|
1645
1645
|
async function runModel(runtime, system, prompt, context) {
|
|
1646
|
-
|
|
1646
|
+
const localContextSlice = typeof D.projectContextSlice === "function"
|
|
1647
|
+
? D.projectContextSlice(context.cwd, context.task || "")
|
|
1648
|
+
: "";
|
|
1649
|
+
const effectiveSystem = localContextSlice
|
|
1650
|
+
? `${system}\n\n${localContextSlice}`
|
|
1651
|
+
: system;
|
|
1652
|
+
if (typeof D.runModel === "function") return normalizeModelText(await D.runModel({ runtime, system: effectiveSystem, prompt, context }));
|
|
1647
1653
|
if (runtime.mode === "cli") {
|
|
1648
1654
|
const authorityMode = context.authorityMode || "no-authority";
|
|
1649
1655
|
if (runtime.kind === "codex" && authorityMode === "no-authority") {
|
|
@@ -1658,7 +1664,7 @@ function create(deps = {}) {
|
|
|
1658
1664
|
"Gemini CLI workforce execution is blocked until this host proves an empty built-in and MCP tool inventory",
|
|
1659
1665
|
);
|
|
1660
1666
|
}
|
|
1661
|
-
return normalizeModelText(await D.captureRuntime(runtime.kind,
|
|
1667
|
+
return normalizeModelText(await D.captureRuntime(runtime.kind, effectiveSystem, prompt, {
|
|
1662
1668
|
cwd: context.cwd,
|
|
1663
1669
|
env: context.env,
|
|
1664
1670
|
permission: context.permission,
|
|
@@ -1667,7 +1673,7 @@ function create(deps = {}) {
|
|
|
1667
1673
|
authorityMode,
|
|
1668
1674
|
}));
|
|
1669
1675
|
}
|
|
1670
|
-
return normalizeModelText(await D.runApi(runtime.backend, context.modelPin || runtime.model,
|
|
1676
|
+
return normalizeModelText(await D.runApi(runtime.backend, context.modelPin || runtime.model, effectiveSystem, prompt));
|
|
1671
1677
|
}
|
|
1672
1678
|
|
|
1673
1679
|
async function callHubTool(name, args) {
|
|
@@ -1888,7 +1894,15 @@ function create(deps = {}) {
|
|
|
1888
1894
|
const env = typeof D.buildChildEnv === "function" ? await D.buildChildEnv(db, {
|
|
1889
1895
|
projectPath: ctx.projectPath || null, permission, cwd, lang: ui.lang,
|
|
1890
1896
|
}) : process.env;
|
|
1891
|
-
const modelContext = {
|
|
1897
|
+
const modelContext = {
|
|
1898
|
+
cwd,
|
|
1899
|
+
permission,
|
|
1900
|
+
env,
|
|
1901
|
+
modelPin: ctx.modelPin || null,
|
|
1902
|
+
effortPin: ctx.effortPin,
|
|
1903
|
+
authorityMode: "no-authority",
|
|
1904
|
+
task,
|
|
1905
|
+
};
|
|
1892
1906
|
const prompts = buildPrompts(task, identity);
|
|
1893
1907
|
const runId = `workforce-run:${crypto.randomUUID()}`;
|
|
1894
1908
|
const provider = runtime.mode === "cli" ? runtime.kind : runtime.backend;
|
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,7 +669,6 @@ 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
|
-
const prefilter = autoRouteAgent(db, prompt, resolvedLang);
|
|
643
672
|
ensureJudgmentRunnerInstalled(db);
|
|
644
673
|
let judgment;
|
|
645
674
|
try {
|
|
@@ -647,13 +676,15 @@ async function resolveAutoRoute(db, prompt, lang) {
|
|
|
647
676
|
} catch {
|
|
648
677
|
judgment = null;
|
|
649
678
|
}
|
|
650
|
-
|
|
679
|
+
// 연결 모델 없음 → 어휘로 전문 에이전트를 고르지 않는다. 정직하게 "판단 못 함" + 모델 연결 안내.
|
|
680
|
+
if (!judgment || !judgment.hasJudgmentRunner()) return noModelRouteChoice(resolvedLang, "no_runtime");
|
|
651
681
|
const promptText = routeNormalize(routeStripPaths(prompt));
|
|
652
|
-
// 잡담("hi")
|
|
653
|
-
if (!promptText.trim() || isTrivialRoutePrompt(promptText)) return { ...
|
|
682
|
+
// 잡담("hi")은 닫힌형 결정적 가드로 직답 — 모델은 연결돼 있으니 "모델 연결" 안내는 필요 없다.
|
|
683
|
+
if (!promptText.trim() || isTrivialRoutePrompt(promptText)) return { ...directRouteChoice(resolvedLang), routeSource: "deterministic" };
|
|
654
684
|
const ranked = rankRouteAgents(db, prompt, resolvedLang);
|
|
655
685
|
const meta = resolveMetaBuilder(db);
|
|
656
|
-
|
|
686
|
+
// 라우팅할 후보 자체가 없음(설치 에이전트 0) → 직답. 모델은 연결돼 있으니 일반 직답 사유.
|
|
687
|
+
if (!ranked.length && !meta) return { ...directRouteChoice(resolvedLang), routeSource: "deterministic" };
|
|
657
688
|
// App Builder는 합성 라벨로만 제시한다(동의 핸드셰이크가 걸린 특수 라우트임을 모델에 명시).
|
|
658
689
|
const appBuilder = ranked.find((r) => r.agent.slug === APP_BUILDER_ROUTE_SLUG) || null;
|
|
659
690
|
const candidates = ranked.filter((r) => r.agent.slug !== APP_BUILDER_ROUTE_SLUG).slice(0, ROUTE_JUDGE_CANDIDATE_CAP);
|
|
@@ -707,7 +738,9 @@ async function resolveAutoRoute(db, prompt, lang) {
|
|
|
707
738
|
// model still aborts cleanly at this deadline rather than hanging forever.
|
|
708
739
|
timeoutMs: 40000,
|
|
709
740
|
});
|
|
710
|
-
|
|
741
|
+
// 모델이 판정을 못 냈다(러너 실패/타임아웃/정크 응답) → 어휘 픽으로 떨어지지 않는다.
|
|
742
|
+
// 연결 모델이 사실상 판단하지 못한 것이므로 "판단 못 함" 직답으로 정직하게 종결한다.
|
|
743
|
+
if (verdict.source !== "llm" || !verdict.labels.length) return noModelRouteChoice(resolvedLang, "model_unavailable");
|
|
711
744
|
const picked = verdict.labels[0];
|
|
712
745
|
const reason =
|
|
713
746
|
verdict.reason ||
|
|
@@ -717,15 +750,24 @@ async function resolveAutoRoute(db, prompt, lang) {
|
|
|
717
750
|
return { agent: meta, score: 1000, strong: true, terms: [], reason, routeSource: "llm" };
|
|
718
751
|
}
|
|
719
752
|
const chosen = picked === ROUTE_JUDGE_APP_LABEL ? appBuilder : bySlug.get(picked);
|
|
720
|
-
|
|
753
|
+
// 모델이 라벨을 골랐지만 설치 에이전트로 해석되지 않음(경계 케이스) → 어휘 픽 대신 직답.
|
|
754
|
+
if (!chosen) return { ...directRouteChoice(resolvedLang), routeSource: "deterministic" };
|
|
721
755
|
// 모델 확답은 strong 계약을 충족한다 — 어휘 근거(terms/score)는 참고로 보존.
|
|
722
756
|
return { ...chosen, strong: true, reason, routeSource: "llm" };
|
|
723
757
|
}
|
|
724
758
|
// 라우트 영수증 라벨 — 누가 최종 판정했는지 반드시 찍는다(조용한 폴백 금지 하우스 룰).
|
|
759
|
+
// noModel 경로는 사유(reason)에 이미 "모델 연결" 안내가 담겨 있으니 짧은 기계 라벨만 덧붙인다.
|
|
760
|
+
// 그 외 deterministic(잡담·후보 없음: 모델은 연결됨)은 사유가 이미 설명하므로 라벨을 붙이지 않는다.
|
|
725
761
|
function routeJudgeSourceNote(choice, lang) {
|
|
726
762
|
if (!choice || !choice.routeSource) return "";
|
|
727
763
|
if (choice.routeSource === "llm") return lang === "ko" ? " (판정: 연결 모델)" : " (judged by the connected model)";
|
|
728
|
-
|
|
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 "";
|
|
729
771
|
}
|
|
730
772
|
function autoRouteNote(choice, lang) {
|
|
731
773
|
const resolvedLang = lang || prefsLang();
|
|
@@ -8292,7 +8334,39 @@ function ensureTerminalProjectForExecutionCli(db, projectPath, permission = PERM
|
|
|
8292
8334
|
if (permission === "read") return activeProjectPath(db, { projectPath: root });
|
|
8293
8335
|
return activeProjectPath(db, { projectPath: root, activate: true, reason });
|
|
8294
8336
|
}
|
|
8295
|
-
|
|
8337
|
+
|
|
8338
|
+
function cliProjectContextSlice(projectPath, task) {
|
|
8339
|
+
if (!projectPath || !String(task || "").trim()) return "";
|
|
8340
|
+
try {
|
|
8341
|
+
const coreRoot = resolveCoreRuntimeRoot();
|
|
8342
|
+
if (!coreRoot) return "";
|
|
8343
|
+
const result = captureCoreJsonSync(
|
|
8344
|
+
"agentlas_cloud",
|
|
8345
|
+
[
|
|
8346
|
+
"context", "slice",
|
|
8347
|
+
"--project", projectPath,
|
|
8348
|
+
"--task-stdin",
|
|
8349
|
+
"--no-refresh",
|
|
8350
|
+
"--render",
|
|
8351
|
+
],
|
|
8352
|
+
{
|
|
8353
|
+
cwd: projectPath,
|
|
8354
|
+
input: String(task).slice(0, 12_000),
|
|
8355
|
+
timeout: 4_000,
|
|
8356
|
+
},
|
|
8357
|
+
coreRoot,
|
|
8358
|
+
);
|
|
8359
|
+
return result
|
|
8360
|
+
&& result.schemaVersion === "agentlas.context-slice.v1"
|
|
8361
|
+
&& typeof result.rendered === "string"
|
|
8362
|
+
? result.rendered.trim()
|
|
8363
|
+
: "";
|
|
8364
|
+
} catch {
|
|
8365
|
+
return "";
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8368
|
+
|
|
8369
|
+
function cliMemoryContext(db, projectPath, agentId = null, task = "") {
|
|
8296
8370
|
const sections = [];
|
|
8297
8371
|
const arch = loadArch();
|
|
8298
8372
|
ensureMemoryContextColumn(db);
|
|
@@ -8305,6 +8379,8 @@ function cliMemoryContext(db, projectPath, agentId = null) {
|
|
|
8305
8379
|
if (s.trim()) sections.push(`### Project memory (${projectPath})\n${s.trim()}`);
|
|
8306
8380
|
}
|
|
8307
8381
|
} catch { /* ignore */ }
|
|
8382
|
+
const contextSlice = cliProjectContextSlice(projectPath, task);
|
|
8383
|
+
if (contextSlice) sections.push(contextSlice);
|
|
8308
8384
|
}
|
|
8309
8385
|
if (tableExists(db, "memory_entries")) {
|
|
8310
8386
|
try {
|
|
@@ -8472,7 +8548,7 @@ function augmentSystem(db, baseSystem, ctx, withEmitter, request = "") {
|
|
|
8472
8548
|
sys = langDirective(lang) + (sys ? "\n\n" + sys : "");
|
|
8473
8549
|
const connectionSkill = loadGlobalConnectionSkill();
|
|
8474
8550
|
if (connectionSkill) sys += "\n\n" + connectionSkill;
|
|
8475
|
-
const mem = cliMemoryContext(db, ctx && ctx.projectPath, ctx && ctx.agentId);
|
|
8551
|
+
const mem = cliMemoryContext(db, ctx && ctx.projectPath, ctx && ctx.agentId, request);
|
|
8476
8552
|
if (mem) sys += "\n\n" + mem;
|
|
8477
8553
|
if (withEmitter) {
|
|
8478
8554
|
sys += "\n\n" + memoryEmitterPromptFor(request, arch, ctx && ctx.turnId, ctx && ctx.permission);
|
|
@@ -8623,8 +8699,9 @@ const RUNTIME_BIN = {
|
|
|
8623
8699
|
* Wire the resident judgment service to whatever runtime the user actually has connected.
|
|
8624
8700
|
* This is what lets classification be decided by MEANING instead of by a wordlist: the
|
|
8625
8701
|
* judge asks the connected model, and the old keyword maps are passed only as hints. It is
|
|
8626
|
-
* invisible to the user (no output, read-only, no tools)
|
|
8627
|
-
* to the
|
|
8702
|
+
* invisible to the user (no output, read-only, no tools). When no runtime answers, judged
|
|
8703
|
+
* callers do NOT keyword-decide: routing/intent go to the plain assistant with a "connect a
|
|
8704
|
+
* model" note, capability inference returns undecided, and gates fail closed.
|
|
8628
8705
|
*/
|
|
8629
8706
|
function installJudgmentRunner(db, rt) {
|
|
8630
8707
|
let judgment;
|
|
@@ -8712,6 +8789,52 @@ function ensureJudgmentRunnerInstalled(db) {
|
|
|
8712
8789
|
}
|
|
8713
8790
|
}
|
|
8714
8791
|
|
|
8792
|
+
// Hand the embedded Agentlas OS runtime a UNIVERSAL judge callback so ITS
|
|
8793
|
+
// resident judge (content-guard, pipeline, research, privacy) decides by meaning
|
|
8794
|
+
// for EVERY connected runtime — Claude/Codex/Gemini CLIs, all BYOK providers, and
|
|
8795
|
+
// local models alike — with no model hardcoded and no provider code duplicated in
|
|
8796
|
+
// Python. The OS engine stays BYOC: it never calls a model on its own; it only
|
|
8797
|
+
// invokes this host command, which runs the user's own connected runtime. If no
|
|
8798
|
+
// runtime resolves, the env is left unset and OS judged sites honestly report
|
|
8799
|
+
// "connect a model" instead of keyword-deciding.
|
|
8800
|
+
function exportOsJudgeRuntimeEnv(db) {
|
|
8801
|
+
try {
|
|
8802
|
+
const ar = activeRuntime(db);
|
|
8803
|
+
if (!ar) return;
|
|
8804
|
+
const config = {
|
|
8805
|
+
kind: "host-cmd",
|
|
8806
|
+
cmd: [process.execPath, path.resolve(__dirname, "..", "bin", "agentlas.cjs"), "__judge"],
|
|
8807
|
+
};
|
|
8808
|
+
process.env.AGENTLAS_JUDGE_RUNTIME = JSON.stringify(config);
|
|
8809
|
+
} catch {
|
|
8810
|
+
// Leave the env unset — OS judged sites then report "connect a model" rather
|
|
8811
|
+
// than keyword-deciding.
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
|
|
8815
|
+
// Hidden helper the embedded OS runtime calls for one-shot classification. Reads
|
|
8816
|
+
// {"system","prompt"} JSON on stdin, runs the connected runtime (any provider/CLI)
|
|
8817
|
+
// as a silent read-only turn, prints the reply text on stdout. Judgment is a
|
|
8818
|
+
// lightweight task, so this uses whatever model the user already has connected —
|
|
8819
|
+
// no hardcoded model, no separate credentials.
|
|
8820
|
+
async function cmdInternalJudge(db) {
|
|
8821
|
+
let raw = "";
|
|
8822
|
+
try {
|
|
8823
|
+
raw = await readStdin();
|
|
8824
|
+
const { system, prompt } = JSON.parse(raw || "{}");
|
|
8825
|
+
if (!prompt || !prompt.trim()) { process.stdout.write(""); return; }
|
|
8826
|
+
let judgment;
|
|
8827
|
+
try { judgment = require("./agentlas-judgment.cjs"); } catch { judgment = null; }
|
|
8828
|
+
ensureJudgmentRunnerInstalled(db);
|
|
8829
|
+
if (!judgment || !judgment.hasJudgmentRunner()) { process.stdout.write(""); return; }
|
|
8830
|
+
// Reuse the installed runner directly for an opaque system+prompt turn.
|
|
8831
|
+
const text = await judgment.runRaw(String(system || ""), String(prompt), 40000);
|
|
8832
|
+
process.stdout.write(text || "");
|
|
8833
|
+
} catch {
|
|
8834
|
+
process.stdout.write("");
|
|
8835
|
+
}
|
|
8836
|
+
}
|
|
8837
|
+
|
|
8715
8838
|
function resolveRuntime(db, override) {
|
|
8716
8839
|
const ar = activeRuntime(db);
|
|
8717
8840
|
const activeCli = ar && RUNTIME_BIN[ar.kind]
|
|
@@ -9714,7 +9837,7 @@ function buildHelpers(db) {
|
|
|
9714
9837
|
autoRouteNote: (choice, lang) => autoRouteNote(choice, lang),
|
|
9715
9838
|
autoRoutePreamble: (choice, lang) => autoRoutePreamble(choice, lang),
|
|
9716
9839
|
directSystemPrompt: (lang) => directSystemPrompt(lang),
|
|
9717
|
-
cliMemoryContext: (db_, pp, agentId) => cliMemoryContext(db_, pp, agentId),
|
|
9840
|
+
cliMemoryContext: (db_, pp, agentId, task) => cliMemoryContext(db_, pp, agentId, task),
|
|
9718
9841
|
importLocal: (db_, p) => importLocalFolderCli(db_, p),
|
|
9719
9842
|
// REPL-safe public Hub install: fail()(process.exit) 대신 Error를 throw 해 REPL이 직접 렌더하게 한다.
|
|
9720
9843
|
cloudInstall: async (db_, slug) => {
|
|
@@ -10196,6 +10319,7 @@ function workforce() {
|
|
|
10196
10319
|
fetchHub: (url, init) => fetchHubCli(url, init),
|
|
10197
10320
|
listWorkforceTools: listWorkforceToolsCli,
|
|
10198
10321
|
supportsWorkforceToolAuthority: async () => false,
|
|
10322
|
+
projectContextSlice: cliProjectContextSlice,
|
|
10199
10323
|
prefsLang,
|
|
10200
10324
|
out,
|
|
10201
10325
|
});
|
|
@@ -11860,6 +11984,7 @@ function cmdHelp() {
|
|
|
11860
11984
|
" research <sub> Research Engine: status|gather|search|read|plan",
|
|
11861
11985
|
" career-graph <sub> source routing index: status|list|add",
|
|
11862
11986
|
" ontology <sub> project knowledge: status|list|add (REPL: /ontology)",
|
|
11987
|
+
" context <sub> project map: refresh|locate|refs|slice|impact|verify",
|
|
11863
11988
|
" journal <sub> Stormbreaker run journal: status|verify|repair|gate",
|
|
11864
11989
|
"",
|
|
11865
11990
|
hdr("ACCOUNT & OPS"),
|
|
@@ -11940,6 +12065,11 @@ async function main() {
|
|
|
11940
12065
|
// in the REPL and in one-shot commands alike — instead of silently falling to
|
|
11941
12066
|
// the deterministic label until executeOnce eventually installs it.
|
|
11942
12067
|
ensureJudgmentRunnerInstalled(db);
|
|
12068
|
+
// Hidden judge-callback for the embedded OS runtime — handle before exporting
|
|
12069
|
+
// the env or dispatching, and never re-enter the OS from here.
|
|
12070
|
+
if (cmd === "__judge") return cmdInternalJudge(db);
|
|
12071
|
+
// Hand the same connected model to the embedded Agentlas OS runtime's judge.
|
|
12072
|
+
exportOsJudgeRuntimeEnv(db);
|
|
11943
12073
|
|
|
11944
12074
|
// 인자 없이 `agentlas` → 에이전트 1개면 바로 대화형, 아니면 목록 + 사용법
|
|
11945
12075
|
if (cmd === "") {
|
|
@@ -11969,6 +12099,17 @@ async function main() {
|
|
|
11969
12099
|
case "memory":
|
|
11970
12100
|
// Phase 1b: 기존 마크다운 메모리 → 공유 agentlas.sqlite 이관(dry-run 기본, --apply).
|
|
11971
12101
|
return require("./agentlas-memory-import.cjs").cmdMemory({ db, args: rest.slice(1), out, fail });
|
|
12102
|
+
case "context": {
|
|
12103
|
+
if (!rest[1]) return fail("usage: agentlas context refresh|locate|refs|slice|impact|verify [options]");
|
|
12104
|
+
const cwd = projectCwd();
|
|
12105
|
+
ensureTerminalProjectForExecutionCli(db, cwd, PERMISSION, "terminal-context");
|
|
12106
|
+
const contextArgs = rest.slice(1);
|
|
12107
|
+
const hasProject = contextArgs.includes("--project");
|
|
12108
|
+
return parity().cmdHep(
|
|
12109
|
+
db,
|
|
12110
|
+
["context", ...contextArgs, ...(hasProject ? [] : ["--project", cwd])],
|
|
12111
|
+
);
|
|
12112
|
+
}
|
|
11972
12113
|
case "evolve":
|
|
11973
12114
|
// Phase 2/2+: 데스크탑 트리거가 만든 성장 제안 검토·적용·되돌리기(공유 DB).
|
|
11974
12115
|
return require("./agentlas-evolution.cjs").cmdEvolve({ db, args: rest.slice(1), out, fail, agentFolder });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
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"
|