@litfamily/lithermes 1.0.6 → 1.0.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/README.md +12 -12
- package/README_Ko-KR.md +12 -12
- package/assets/lithermes-plugin/diagnostics.py +70 -15
- package/assets/lithermes-plugin/payload-version.json +6 -6
- package/assets/lithermes-plugin/plugin.yaml +1 -1
- package/assets/lithermes-plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -2
- package/assets/lithermes-plugin/skills/visual-qa/references/complete-contract.md +2 -2
- package/package.json +1 -1
- package/readme-assets/badge-version.svg +1 -1
- package/src/lib/check.js +2 -2
- package/src/lib/install.js +2 -3
- package/src/lib/modelConfig.js +30 -9
- package/src/lib/modelConfigBoundary.js +5 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
2
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion.webp" width="100%" alt="LitHermes cover art. A lime signal moves through orbital paths above the HERMES AGENT wordmark; a still frame from this motion cover appears when reduced motion is preferred." /></picture></p>
|
|
2
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover.webp" width="100%" alt="Five armored robots, one for each LitFamily product, beneath the words KEEP THE WORK LIT." /></p>
|
|
3
3
|
|
|
4
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
4
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ascii-readme.svg" width="480" alt="LIT ASCII B mark" /></p>
|
|
5
5
|
|
|
6
6
|
<details>
|
|
7
7
|
<summary>Copy ASCII logo</summary>
|
|
@@ -31,19 +31,19 @@
|
|
|
31
31
|
|
|
32
32
|
</details>
|
|
33
33
|
|
|
34
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
35
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
34
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-wordmark.svg" width="480" alt="LITHERMES display type" /></p>
|
|
35
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-clay-icon.png" width="160" alt="LitHermes clay mark" /></p>
|
|
36
36
|
|
|
37
37
|
<p align="center">
|
|
38
|
-
<a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
39
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
38
|
+
<a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-version.svg" alt="1.0.8" /></a>
|
|
39
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-license.svg" alt="MIT license" /></a>
|
|
40
40
|
</p>
|
|
41
41
|
|
|
42
42
|
<p align="center">
|
|
43
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
43
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-book-open.svg" width="16" alt="" /> Docs</a>
|
|
44
44
|
<a href="#install">Install</a>
|
|
45
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
46
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
45
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition</a>
|
|
46
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
49
|
# LitHermes
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
|
|
53
53
|
**What it is:** LitHermes connects planning, execution, review, and handoff inside **Hermes Agent**.
|
|
54
54
|
|
|
55
|
-
[한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
55
|
+
[한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/README_Ko-KR.md) · [npm](https://www.npmjs.com/package/@litfamily/lithermes) · [GitHub](https://github.com/wjgoarxiv/lithermes)
|
|
56
56
|
|
|
57
57
|
## Install
|
|
58
58
|
|
|
59
|
-
Source version: `@litfamily/lithermes@1.0.
|
|
59
|
+
Source version: `@litfamily/lithermes@1.0.8`. Have Hermes Agent and Node.js 18+ installed, with write access to your Hermes home (normally `~/.hermes`).
|
|
60
60
|
|
|
61
61
|
The installer targets your Hermes home. For an isolated trial, set `HERMES_HOME` to a new empty directory before installation and use that same value when starting Hermes. Keep your existing home and settings. `--no-patch-installed-hermes` also prevents compatibility edits to the detected host installation outside that profile.
|
|
62
62
|
|
package/README_Ko-KR.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
2
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover-motion.webp" width="100%" alt="LitHermes 표지. 라임색 신호가 궤도를 지나 HERMES AGENT 워드마크로 이어집니다. 모션을 줄이도록 설정하면 이 모션 표지에서 추출한 정지 프레임을 표시합니다." /></picture></p>
|
|
2
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/cover.webp" width="100%" alt="LitFamily 다섯 제품을 나타내는 장갑 로봇 패널과 KEEP THE WORK LIT. 문구" /></p>
|
|
3
3
|
|
|
4
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
4
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ascii-readme.svg" width="480" alt="LIT ASCII B 마크" /></p>
|
|
5
5
|
|
|
6
6
|
<details>
|
|
7
7
|
<summary>ASCII 로고 복사</summary>
|
|
@@ -31,19 +31,19 @@
|
|
|
31
31
|
|
|
32
32
|
</details>
|
|
33
33
|
|
|
34
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
35
|
-
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
34
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-wordmark.svg" width="480" alt="LITHERMES 디스플레이 서체" /></p>
|
|
35
|
+
<p align="center"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lithermes-clay-icon.png" width="160" alt="LitHermes 클레이 마크" /></p>
|
|
36
36
|
|
|
37
37
|
<p align="center">
|
|
38
|
-
<a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
39
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
38
|
+
<a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-version.svg" alt="1.0.8" /></a>
|
|
39
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/badge-license.svg" alt="MIT license" /></a>
|
|
40
40
|
</p>
|
|
41
41
|
|
|
42
42
|
<p align="center">
|
|
43
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
43
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-book-open.svg" width="16" alt="" /> 문서</a>
|
|
44
44
|
<a href="#설치">설치</a>
|
|
45
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
46
|
-
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
45
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition</a>
|
|
46
|
+
<a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
49
|
# LitHermes
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
|
|
53
53
|
**무엇인가요:** LitHermes는 **Hermes Agent**에서 계획, 실행, 검토, 인계를 이어갑니다.
|
|
54
54
|
|
|
55
|
-
[English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.
|
|
55
|
+
[English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.8/README.md) · [npm](https://www.npmjs.com/package/@litfamily/lithermes) · [GitHub](https://github.com/wjgoarxiv/lithermes)
|
|
56
56
|
|
|
57
57
|
## 설치
|
|
58
58
|
|
|
59
|
-
소스 기준 버전은 `@litfamily/lithermes@1.0.
|
|
59
|
+
소스 기준 버전은 `@litfamily/lithermes@1.0.8`입니다. Hermes Agent와 Node.js 18+가 필요하며, Hermes 홈(기본 `~/.hermes`)에 쓰기 권한이 있어야 합니다.
|
|
60
60
|
|
|
61
61
|
설치기는 Hermes 홈을 대상으로 합니다. 별도 체험을 하려면 설치 전에 `HERMES_HOME`을 새 빈 디렉터리로 지정하고 Hermes를 시작할 때도 같은 값을 사용하세요. 기존 홈과 설정은 보관합니다. `--no-patch-installed-hermes`는 해당 프로필 밖에서 감지된 호스트 설치본에 호환성 패치를 적용하지 못하게 합니다.
|
|
62
62
|
|
|
@@ -84,6 +84,27 @@ _OPENAI_CATALOG_EFFORTS = {
|
|
|
84
84
|
}
|
|
85
85
|
_REVIEWER_ROUTE_NAME = "".join(chr(code) for code in (109, 111, 109, 117, 115))
|
|
86
86
|
SUPPORTED_HERMES_HOST_VERSIONS = frozenset(("0.17.0", "0.19.0"))
|
|
87
|
+
_MINIMUM_HERMES_HOST_VERSION = (0, 17, 0)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _classify_hermes_host_version(host_version: str) -> str:
|
|
91
|
+
if re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", host_version) is None:
|
|
92
|
+
return "unsupported"
|
|
93
|
+
version = tuple(int(part) for part in host_version.split("."))
|
|
94
|
+
if version < _MINIMUM_HERMES_HOST_VERSION:
|
|
95
|
+
return "unsupported"
|
|
96
|
+
if host_version in SUPPORTED_HERMES_HOST_VERSIONS:
|
|
97
|
+
return "verified"
|
|
98
|
+
return "beyond-verified"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _beyond_verified_host_note(host_version: str) -> str | None:
|
|
102
|
+
if _classify_hermes_host_version(host_version) != "beyond-verified":
|
|
103
|
+
return None
|
|
104
|
+
return (
|
|
105
|
+
f"[NOTE] Hermes host {host_version} is beyond the verified matrix "
|
|
106
|
+
"(0.17.0, 0.19.0); diagnostics are read-only"
|
|
107
|
+
)
|
|
87
108
|
|
|
88
109
|
|
|
89
110
|
@dataclass(frozen=True, slots=True)
|
|
@@ -94,6 +115,7 @@ class ModelCapabilities:
|
|
|
94
115
|
child_effort: str
|
|
95
116
|
concurrency: int
|
|
96
117
|
async_batches: int
|
|
118
|
+
shared_async_cap: bool
|
|
97
119
|
spawn_depth: int
|
|
98
120
|
flat: bool
|
|
99
121
|
approved: bool
|
|
@@ -149,14 +171,31 @@ def _host_config_path() -> Path:
|
|
|
149
171
|
return home / "config.yaml"
|
|
150
172
|
|
|
151
173
|
|
|
174
|
+
def _is_secret_looking_scalar(value: object) -> bool:
|
|
175
|
+
if not isinstance(value, str):
|
|
176
|
+
return False
|
|
177
|
+
trimmed = value.strip()
|
|
178
|
+
if not trimmed or re.fullmatch(r"(?:true|false|null)", trimmed, re.IGNORECASE):
|
|
179
|
+
return False
|
|
180
|
+
if re.fullmatch(r"[A-Z][A-Z0-9_]*", trimmed) and "_" in trimmed:
|
|
181
|
+
return False
|
|
182
|
+
if re.fullmatch(r"\$\{[A-Z][A-Z0-9_]*\}", trimmed):
|
|
183
|
+
return False
|
|
184
|
+
return not re.search(r"(?:^|[-_])(redacted|placeholder)(?:[-_]|$)", trimmed, re.IGNORECASE)
|
|
185
|
+
|
|
186
|
+
|
|
152
187
|
def _has_credential_key(value: object) -> bool:
|
|
153
188
|
if isinstance(value, dict):
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
|
|
189
|
+
for key, child in value.items():
|
|
190
|
+
normalized = re.sub(
|
|
191
|
+
r"([a-z0-9])([A-Z])", r"\1_\2", str(key or "")
|
|
192
|
+
).replace("-", "_")
|
|
193
|
+
if (
|
|
194
|
+
_CREDENTIAL_KEY_RE.search(normalized)
|
|
195
|
+
and _is_secret_looking_scalar(child)
|
|
196
|
+
) or _has_credential_key(child):
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
160
199
|
if isinstance(value, list):
|
|
161
200
|
return any(_has_credential_key(child) for child in value)
|
|
162
201
|
return False
|
|
@@ -203,7 +242,7 @@ def _cataloged_route(model: object, effort: object) -> bool:
|
|
|
203
242
|
|
|
204
243
|
|
|
205
244
|
def _parse_capabilities(host_version: str) -> CapabilityResult:
|
|
206
|
-
if host_version
|
|
245
|
+
if _classify_hermes_host_version(host_version) == "unsupported":
|
|
207
246
|
return UnavailableCapabilities("unsupported Hermes host version")
|
|
208
247
|
try:
|
|
209
248
|
text = _host_config_path().read_text(encoding="utf-8")
|
|
@@ -221,7 +260,8 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
|
|
|
221
260
|
return UnavailableCapabilities("malformed host config")
|
|
222
261
|
if _has_credential_key(config):
|
|
223
262
|
return UnavailableCapabilities("credential-risk host config")
|
|
224
|
-
|
|
263
|
+
schema_version = config.get("_config_version")
|
|
264
|
+
if type(schema_version) is not int or schema_version < 30:
|
|
225
265
|
return UnavailableCapabilities("unknown host config schema")
|
|
226
266
|
for key in _SAFE_CHILDREN:
|
|
227
267
|
if key in config and not isinstance(config[key], dict):
|
|
@@ -256,7 +296,8 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
|
|
|
256
296
|
child_effort = effort if child_effort_raw is None or child_effort_raw == "" else child_effort_raw
|
|
257
297
|
child_provider = provider if child_provider_raw is None or child_provider_raw == "" else child_provider_raw
|
|
258
298
|
concurrency_raw = delegation_config.get("max_concurrent_children", "")
|
|
259
|
-
|
|
299
|
+
shared_async_cap = schema_version >= 33
|
|
300
|
+
async_raw = 1 if shared_async_cap else delegation_config.get("max_async_children", 3)
|
|
260
301
|
depth_raw = delegation_config.get("max_spawn_depth", 1)
|
|
261
302
|
orchestrator_enabled = delegation_config.get("orchestrator_enabled", True)
|
|
262
303
|
delegation_provider = delegation_config.get("provider") or provider
|
|
@@ -355,6 +396,7 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
|
|
|
355
396
|
child_effort=child_effort,
|
|
356
397
|
concurrency=concurrency,
|
|
357
398
|
async_batches=async_batches,
|
|
399
|
+
shared_async_cap=shared_async_cap,
|
|
358
400
|
spawn_depth=spawn_depth,
|
|
359
401
|
flat=flat,
|
|
360
402
|
approved=approved,
|
|
@@ -369,16 +411,22 @@ def _recursion_detail(spawn_depth: int, flat: bool) -> str:
|
|
|
369
411
|
return f"hard (configured depth {spawn_depth}, flat via orchestrator kill switch)"
|
|
370
412
|
|
|
371
413
|
|
|
414
|
+
def _concurrency_detail(concurrency: int, async_batches: int, shared_async_cap: bool) -> str:
|
|
415
|
+
if shared_async_cap:
|
|
416
|
+
return f"per batch {concurrency}; background cap {concurrency}; potential children {concurrency}"
|
|
417
|
+
return f"per batch {concurrency}; async batches {async_batches}; potential children {concurrency * async_batches}"
|
|
418
|
+
|
|
419
|
+
|
|
372
420
|
def _capability_status_lines(host_version: str) -> list[str]:
|
|
373
421
|
result = _parse_capabilities(host_version)
|
|
374
422
|
match result:
|
|
375
|
-
case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, spawn_depth=spawn_depth, flat=flat):
|
|
423
|
+
case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, shared_async_cap=shared_async_cap, spawn_depth=spawn_depth, flat=flat):
|
|
376
424
|
lines = [
|
|
377
425
|
f"parent model: {parent_model}",
|
|
378
426
|
f"parent effort: {parent_effort}",
|
|
379
427
|
f"global child route: configured ({child_model}, effort {child_effort}, provider inherited)",
|
|
380
428
|
"child execution proof: unavailable (delegate_task receipt required)",
|
|
381
|
-
f"concurrency: hard (
|
|
429
|
+
f"concurrency: hard ({_concurrency_detail(concurrency, async_batches, shared_async_cap)})",
|
|
382
430
|
f"recursion: {_recursion_detail(spawn_depth, flat)}",
|
|
383
431
|
"auto-compaction: unavailable (ratio-only compression; no exact 650K schema)",
|
|
384
432
|
f"runtime: hard ({_OPENAI_PROVIDER} {_RESPONSES_MODE})",
|
|
@@ -408,7 +456,7 @@ def _capability_status_lines(host_version: str) -> list[str]:
|
|
|
408
456
|
def status_report(host_version: str | None = None) -> str:
|
|
409
457
|
version = host_version or hermes_host_version()
|
|
410
458
|
skills = _skill_names()
|
|
411
|
-
|
|
459
|
+
lines = [
|
|
412
460
|
f"LitHermes plugin {plugin_version()} (Hermes host {version})",
|
|
413
461
|
f"plugin dir: {PLUGIN_ROOT}",
|
|
414
462
|
f"hooks ({len(HOOKS)}): {', '.join(HOOKS)}",
|
|
@@ -424,7 +472,11 @@ def status_report(host_version: str | None = None) -> str:
|
|
|
424
472
|
f"litgoal evidence kinds: {', '.join(_evidence_kinds())}",
|
|
425
473
|
f"scientific visualization: {_science.dependency_summary()}",
|
|
426
474
|
*_capability_status_lines(version),
|
|
427
|
-
]
|
|
475
|
+
]
|
|
476
|
+
host_note = _beyond_verified_host_note(version)
|
|
477
|
+
if host_note:
|
|
478
|
+
lines.insert(1, host_note)
|
|
479
|
+
return "\n".join(lines)
|
|
428
480
|
|
|
429
481
|
|
|
430
482
|
def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
|
|
@@ -463,14 +515,17 @@ def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
|
|
|
463
515
|
lines.append(f"[OK] Hermes host detected (v{version})")
|
|
464
516
|
else:
|
|
465
517
|
lines.append("[NOTE] Hermes host version unknown (running outside Hermes?)")
|
|
518
|
+
host_note = _beyond_verified_host_note(version)
|
|
519
|
+
if host_note:
|
|
520
|
+
lines.append(host_note)
|
|
466
521
|
capabilities = _parse_capabilities(version)
|
|
467
522
|
match capabilities:
|
|
468
|
-
case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, spawn_depth=spawn_depth, flat=flat):
|
|
523
|
+
case ModelCapabilities(parent_model=parent_model, parent_effort=parent_effort, child_model=child_model, child_effort=child_effort, concurrency=concurrency, async_batches=async_batches, shared_async_cap=shared_async_cap, spawn_depth=spawn_depth, flat=flat):
|
|
469
524
|
lines.extend([
|
|
470
525
|
f"[OK] parent model {parent_model} / effort {parent_effort}",
|
|
471
526
|
f"[NOTE] global child route configured ({child_model}, effort {child_effort}, provider inherited)",
|
|
472
527
|
"[NOTE] child execution proof unavailable (delegate_task receipt required)",
|
|
473
|
-
f"[OK] concurrency hard (
|
|
528
|
+
f"[OK] concurrency hard ({_concurrency_detail(concurrency, async_batches, shared_async_cap)})",
|
|
474
529
|
f"[{'OK' if flat else 'WARN'}] recursion {_recursion_detail(spawn_depth, flat)}",
|
|
475
530
|
"[OK] auto-compaction unavailable (ratio-only compression; no exact 650K schema)",
|
|
476
531
|
f"[OK] runtime hard ({_OPENAI_PROVIDER} {_RESPONSES_MODE})",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-09-
|
|
2
|
+
"syncedAt": "2026-09-24T16:39:30.023Z",
|
|
3
3
|
"source": "bundled-payload",
|
|
4
|
-
"sourceHash": "
|
|
4
|
+
"sourceHash": "95a5f8d9b4057baaa3d73a74a48bc99d953dd8a31403b7b80126c1e98340c091",
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "NOTICE.md",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"path": "diagnostics.py",
|
|
88
|
-
"sha256": "
|
|
88
|
+
"sha256": "fc4c902009704378e31fe511e7d85ad48474fc3198706417f90ac6eb2956b9e8"
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "handoff.py",
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"path": "plugin.yaml",
|
|
160
|
-
"sha256": "
|
|
160
|
+
"sha256": "200f3ae31a7b4623a2a09f3a9fcb1af87e7638db16ddb19a6f64ef03e0864d04"
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"path": "provider_metrics.py",
|
|
@@ -1245,7 +1245,7 @@
|
|
|
1245
1245
|
},
|
|
1246
1246
|
{
|
|
1247
1247
|
"path": "skills/frontend-ui-ux/references/complete-contract.md",
|
|
1248
|
-
"sha256": "
|
|
1248
|
+
"sha256": "50973cdab59eeea6bc699d1da32856ce20b810c56fcb3ceb14d09b387f4782d2"
|
|
1249
1249
|
},
|
|
1250
1250
|
{
|
|
1251
1251
|
"path": "skills/frontend-ui-ux/references/composition.md",
|
|
@@ -1969,7 +1969,7 @@
|
|
|
1969
1969
|
},
|
|
1970
1970
|
{
|
|
1971
1971
|
"path": "skills/visual-qa/references/complete-contract.md",
|
|
1972
|
-
"sha256": "
|
|
1972
|
+
"sha256": "6df5e613de62f49e3aa7d78251bcd916d2dea419a679598a0099a761d5f68fcd"
|
|
1973
1973
|
},
|
|
1974
1974
|
{
|
|
1975
1975
|
"path": "skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json",
|
|
@@ -431,7 +431,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
|
|
|
431
431
|
|
|
432
432
|
```text
|
|
433
433
|
$ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
|
|
434
|
-
Installed LitHermes 1.0.
|
|
434
|
+
Installed LitHermes 1.0.8
|
|
435
435
|
plugin: $ISOLATED/plugins/lithermes
|
|
436
436
|
model config: updated
|
|
437
437
|
|
|
@@ -448,7 +448,7 @@ installed payload: PASS
|
|
|
448
448
|
enabled config: PASS
|
|
449
449
|
|
|
450
450
|
$ hermes lithermes doctor
|
|
451
|
-
[OK] plugin.yaml readable (version 1.0.
|
|
451
|
+
[OK] plugin.yaml readable (version 1.0.8)
|
|
452
452
|
[OK] skills bundled: 32
|
|
453
453
|
[OK] litgoal durable runtime importable
|
|
454
454
|
```
|
|
@@ -485,7 +485,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
|
|
|
485
485
|
|
|
486
486
|
```text
|
|
487
487
|
$ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
|
|
488
|
-
Installed LitHermes 1.0.
|
|
488
|
+
Installed LitHermes 1.0.8
|
|
489
489
|
plugin: $ISOLATED/plugins/lithermes
|
|
490
490
|
model config: updated
|
|
491
491
|
|
|
@@ -502,7 +502,7 @@ installed payload: PASS
|
|
|
502
502
|
enabled config: PASS
|
|
503
503
|
|
|
504
504
|
$ hermes lithermes doctor
|
|
505
|
-
[OK] plugin.yaml readable (version 1.0.
|
|
505
|
+
[OK] plugin.yaml readable (version 1.0.8)
|
|
506
506
|
[OK] skills bundled: 32
|
|
507
507
|
[OK] litgoal durable runtime importable
|
|
508
508
|
```
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.8"><title>release: 1.0.8</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.8</text></g></svg>
|
package/src/lib/check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const path = require("node:path");
|
|
3
3
|
const { spawnSync } = require("node:child_process");
|
|
4
|
-
const { configHasLitHermes, inspectHermesCapabilities, readConfig, readExternalSkillDirsConfig } = require("./config");
|
|
4
|
+
const { configHasLitHermes, formatConcurrencyCapability, inspectHermesCapabilities, readConfig, readExternalSkillDirsConfig } = require("./config");
|
|
5
5
|
const { detectHermesRuntimeRepo, detectHermesVersion, ensureHermesHome, expandHome, inspectHermesHostCapabilities, LitHermesError } = require("./hermesDiscovery");
|
|
6
6
|
const { formatManagedRoute } = require("./modelRoutePolicy");
|
|
7
7
|
const { inspectPythonCache } = require("./files");
|
|
@@ -271,7 +271,7 @@ function doctorLitHermes(flags = {}) {
|
|
|
271
271
|
`skill shadow check: ${skillShadows.length ? "WARNING" : "PASS"}`,
|
|
272
272
|
...skillShadows.map(formatSkillShadowWarning),
|
|
273
273
|
`auto-compaction: ${auto.status}${auto.reason ? ` (${auto.reason})` : ""}`,
|
|
274
|
-
`concurrency: ${
|
|
274
|
+
`concurrency: ${formatConcurrencyCapability(concurrency)}`,
|
|
275
275
|
`recursion: ${recursion.status}${recursion.status === "hard" ? recursion.flatBy === "depth" ? ` (depth ${recursion.limit}, flat)` : ` (configured depth ${recursion.limit}, flat via orchestrator kill switch)` : ` (${recursion.reason})`}`,
|
|
276
276
|
`model route safety: ${modelSafety.status}${modelSafety.code ? ` (${modelSafety.code}; ${modelSafety.reason})` : ` (${modelSafety.reason})`}`,
|
|
277
277
|
`runtime: ${runtime.status}${runtime.status === "hard" ? ` (${runtime.provider} ${runtime.apiMode})` : ` (${runtime.reason})`}`,
|
package/src/lib/install.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
configHasLitHermes,
|
|
6
6
|
disableLitHermesConfig,
|
|
7
7
|
enableLitHermesConfig,
|
|
8
|
+
formatConcurrencyCapability,
|
|
8
9
|
inspectHermesCapabilities,
|
|
9
10
|
planModelConfig,
|
|
10
11
|
readConfig,
|
|
@@ -245,9 +246,7 @@ function capabilityLines(plan, { dryRun = false } = {}) {
|
|
|
245
246
|
const auto = autoCompaction.status === "hard"
|
|
246
247
|
? `hard (${autoCompaction.limit} tokens)`
|
|
247
248
|
: `unavailable (${autoCompaction.reason.replace(/^Hermes 0\.17\.0 exposes /, "")})`;
|
|
248
|
-
const sync = concurrency
|
|
249
|
-
? `hard (per batch ${concurrency.limit}; async batches ${concurrency.asyncBatchLimit}; potential children ${concurrency.potentialChildCeiling})`
|
|
250
|
-
: `${concurrency.status} (${concurrency.reason})`;
|
|
249
|
+
const sync = formatConcurrencyCapability(concurrency);
|
|
251
250
|
const nesting = recursion.status === "hard"
|
|
252
251
|
? recursion.flatBy === "depth"
|
|
253
252
|
? `hard (depth ${recursion.limit}, flat)`
|
package/src/lib/modelConfig.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require("node:fs");
|
|
2
2
|
const path = require("node:path");
|
|
3
3
|
const { writeFileAtomic } = require("./files");
|
|
4
|
-
const { OPENAI_PROVIDER, RESPONSES_MODE, classifyHostVersion,
|
|
4
|
+
const { OPENAI_PROVIDER, RESPONSES_MODE, VERIFIED_CONFIG_VERSION, classifyHostVersion,
|
|
5
5
|
exactHostVersion, parseBoundary, sourceHash } = require("./modelConfigBoundary");
|
|
6
6
|
const {
|
|
7
7
|
ASTRA_MODEL,
|
|
@@ -138,6 +138,14 @@ function unavailableCapabilities(reason) {
|
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
function formatConcurrencyCapability(concurrency) {
|
|
142
|
+
if (concurrency.status !== "hard") return `${concurrency.status} (${concurrency.reason})`;
|
|
143
|
+
if (concurrency.sharedAsyncCap) {
|
|
144
|
+
return `hard (per batch ${concurrency.limit}; background cap ${concurrency.backgroundCap}; potential children ${concurrency.potentialChildCeiling})`;
|
|
145
|
+
}
|
|
146
|
+
return `hard (per batch ${concurrency.limit}; async batches ${concurrency.asyncBatchLimit}; potential children ${concurrency.potentialChildCeiling})`;
|
|
147
|
+
}
|
|
148
|
+
|
|
141
149
|
function safeOriginalDispatchId(dispatch) {
|
|
142
150
|
if (typeof dispatch !== "string" || !/^[A-Za-z0-9._:/-]{1,128}$/.test(dispatch)) return null;
|
|
143
151
|
return dispatch;
|
|
@@ -199,9 +207,11 @@ function inspectHermesCapabilities(text, options = {}) {
|
|
|
199
207
|
}
|
|
200
208
|
const concurrency = boundary.document.getIn(["delegation", "max_concurrent_children"]);
|
|
201
209
|
const asyncBatches = boundary.document.getIn(["delegation", "max_async_children"]);
|
|
210
|
+
const configVersion = boundary.document.get("_config_version");
|
|
211
|
+
const sharedAsyncCap = configVersion >= 33;
|
|
202
212
|
const spawnDepth = boundary.document.getIn(["delegation", "max_spawn_depth"]);
|
|
203
213
|
const orchestratorEnabled = boundary.document.getIn(["delegation", "orchestrator_enabled"]);
|
|
204
|
-
const effectiveAsyncBatches = Number.isInteger(asyncBatches) ? asyncBatches : 3;
|
|
214
|
+
const effectiveAsyncBatches = sharedAsyncCap ? 1 : Number.isInteger(asyncBatches) ? asyncBatches : 3;
|
|
205
215
|
const effectiveSpawnDepth = Number.isInteger(spawnDepth) ? spawnDepth : 1;
|
|
206
216
|
const flatDelegation = effectiveSpawnDepth === 1 || orchestratorEnabled === false;
|
|
207
217
|
const contextLength = boundary.document.getIn(["model", "context_length"]);
|
|
@@ -234,12 +244,15 @@ function inspectHermesCapabilities(text, options = {}) {
|
|
|
234
244
|
concurrency: concurrency === 20
|
|
235
245
|
? {
|
|
236
246
|
status: "hard",
|
|
237
|
-
reason:
|
|
247
|
+
reason: sharedAsyncCap
|
|
248
|
+
? "effective concurrency and background work share the configured cap of 20"
|
|
249
|
+
: "effective synchronous concurrency is configured to 20",
|
|
238
250
|
observedSource: "delegation.max_concurrent_children",
|
|
239
251
|
limit: 20,
|
|
240
252
|
scope: "per-batch",
|
|
241
|
-
|
|
242
|
-
|
|
253
|
+
...(sharedAsyncCap
|
|
254
|
+
? { sharedAsyncCap: true, backgroundCap: 20, potentialChildCeiling: 20 }
|
|
255
|
+
: { asyncBatchLimit: effectiveAsyncBatches, potentialChildCeiling: 20 * effectiveAsyncBatches }),
|
|
243
256
|
}
|
|
244
257
|
: {
|
|
245
258
|
status: "unavailable",
|
|
@@ -379,15 +392,22 @@ function planModelConfig(text, options = {}) {
|
|
|
379
392
|
document.setIn(["delegation", "max_spawn_depth"], 1);
|
|
380
393
|
document.setIn(["delegation", "orchestrator_enabled"], false);
|
|
381
394
|
const plannedText = document.toString({ lineWidth: 0 });
|
|
395
|
+
const beyondVerifiedReasons = [];
|
|
396
|
+
if (classifyHostVersion(options.hostVersion).status === "beyond-verified") {
|
|
397
|
+
beyondVerifiedReasons.push(`Hermes ${request.hostVersion} is beyond the verified matrix; source and runtime markers verified, model schema written`);
|
|
398
|
+
}
|
|
399
|
+
const configVersion = document.get("_config_version");
|
|
400
|
+
if (configVersion > VERIFIED_CONFIG_VERSION) {
|
|
401
|
+
beyondVerifiedReasons.push(`config schema ${configVersion} is beyond the verified ceiling ${VERIFIED_CONFIG_VERSION}`);
|
|
402
|
+
}
|
|
382
403
|
return {
|
|
383
404
|
action: "write",
|
|
384
405
|
backupAllowed: true,
|
|
385
406
|
capabilities: inspectHermesCapabilities(plannedText, { ...options, ...route }),
|
|
386
407
|
classification,
|
|
387
|
-
reason:
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
: `verified Hermes ${request.hostVersion} model schema`,
|
|
408
|
+
reason: beyondVerifiedReasons.length
|
|
409
|
+
? beyondVerifiedReasons.join("; ")
|
|
410
|
+
: `verified Hermes ${request.hostVersion} model schema`,
|
|
391
411
|
request,
|
|
392
412
|
sourceHash: sourceHash(String(text || "")),
|
|
393
413
|
text: plannedText,
|
|
@@ -429,6 +449,7 @@ function applyModelConfigPlan(hermesHome, plan) {
|
|
|
429
449
|
module.exports = {
|
|
430
450
|
applyModelConfigPlan,
|
|
431
451
|
classifyModelConfig,
|
|
452
|
+
formatConcurrencyCapability,
|
|
432
453
|
inspectHermesCapabilities,
|
|
433
454
|
planModelConfig,
|
|
434
455
|
revalidateModelConfigPlan,
|
|
@@ -17,6 +17,7 @@ const MINIMUM_HOST_VERSION = "0.17.0";
|
|
|
17
17
|
// malformed, which must still be reported as unsupported.
|
|
18
18
|
const NOT_PROBED_HOST_VERSION = "__lithermes_dry_run_not_probed__";
|
|
19
19
|
const CONFIG_VERSION = 30;
|
|
20
|
+
const VERIFIED_CONFIG_VERSION = 45;
|
|
20
21
|
const CREDENTIAL_KEY = /(^|[_-])(api[_-]?key|client[_-]?secret|auth[_-]?token|(?:aws[_-]?)?access[_-]?key(?:[_-]?id)?|(?:ssh[_-]?)?private[_-]?key|token|password|passphrase|secret|credential|authorization)([_-]|$)/i;
|
|
21
22
|
const MAP_PATHS = ["model", "agent", "delegation", "compression"];
|
|
22
23
|
const SCALAR_PATHS = [
|
|
@@ -165,8 +166,9 @@ function parseBoundary(text, options = {}) {
|
|
|
165
166
|
return { error: "malformed host config" };
|
|
166
167
|
}
|
|
167
168
|
const credentialKey = findCredentialRiskKey(document);
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
const configVersion = document.get("_config_version");
|
|
170
|
+
if (!Number.isInteger(configVersion) || configVersion < CONFIG_VERSION) {
|
|
171
|
+
return { error: `unknown host config schema; expected integer version >= ${CONFIG_VERSION}` };
|
|
170
172
|
}
|
|
171
173
|
for (const key of MAP_PATHS) {
|
|
172
174
|
const node = document.get(key, true);
|
|
@@ -208,6 +210,7 @@ module.exports = {
|
|
|
208
210
|
NOT_PROBED_HOST_VERSION,
|
|
209
211
|
OPENAI_PROVIDER,
|
|
210
212
|
RESPONSES_MODE,
|
|
213
|
+
VERIFIED_CONFIG_VERSION,
|
|
211
214
|
classifyHostVersion,
|
|
212
215
|
exactHostVersion,
|
|
213
216
|
parseBoundary,
|