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 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
- // 하우스 룰: 단어목록은 최종 결정을 내리지 않는다. 최종 판정은 resolveNeedsImage가
68
- // 상주 판정 서비스(judgeLabels) 묻고, 이 함수는 그 폴백으로만 살아남는다.
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
- // 읽는다. 캐시 미스 = 어휘 폴백 그대로imageJudgmentSource가 어느 쪽이었는지 라벨한다.
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
- // 러너 없음/타임아웃/정크 어휘 판정을 "fallback"으로 라벨해 반환 (조용한 회귀 금지).
113
+ // 하우스 룰(2026-07-25): 연결 모델이 없으면 단어목록으로 이미지 여부를 결정하지 않는다.
114
+ // 러너 없음/타임아웃/정크 → source:"unavailable"(판정 불가)로 반환하고, 호출자는 안전
115
+ // 기본값(세션 런타임 유지, gemini/codex 하이재킹 금지)을 지킨다. 이미지 판정은 저위험
116
+ // 능력 추론이라 실패-닫힘이 아니라 "판정 안 함"으로 정직하게 둔다.
113
117
  async function resolveNeedsImage(agent) {
114
- const lexical = needsImageLexical(agent);
115
- if (imageJudgeVetoed(agent)) return { image: lexical, source: "deterministic" };
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: lexical, source: "fallback" };
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: [lexical ? "image" : "not-image"],
143
+ fallback: [],
140
144
  });
141
- if (verdict.source !== "llm" || !verdict.labels.length) return { image: lexical, source: "fallback" };
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: model verdict from the warm cache wins; miss = deterministic lexical fallback.
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 (!imageJudgeVetoed(agent)) {
155
- const cached = imageVerdicts.get(imageJudgeInput(agent));
156
- if (cached) return cached.image;
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
- if (typeof D.runModel === "function") return normalizeModelText(await D.runModel({ runtime, system, prompt, context }));
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, system, prompt, {
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, system, prompt));
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 = { cwd, permission, env, modelPin: ctx.modelPin || null, effortPin: ctx.effortPin, authorityMode: "no-authority" };
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;
@@ -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
- // 러너 없음/타임아웃/정크 → 기존 어휘 라우팅(autoRouteAgent)을 routeSource:"deterministic"
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
- if (!judgment || !judgment.hasJudgmentRunner()) return { ...prefilter, routeSource: "deterministic" };
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 { ...prefilter, routeSource: "deterministic" };
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
- if (!ranked.length && !meta) return { ...prefilter, routeSource: "deterministic" };
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
- if (verdict.source !== "llm" || !verdict.labels.length) return { ...prefilter, routeSource: "deterministic" };
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
- if (!chosen) return { ...prefilter, routeSource: "deterministic" };
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
- return lang === "ko" ? " (판정: 결정적 폴백 — 연결 모델 없음)" : " (deterministic fallback — no connected-model verdict)";
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
- function cliMemoryContext(db, projectPath, agentId = null) {
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) and every judged call falls back
8627
- * to the deterministic prefilter when no runtime answers.
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.7",
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"