@litfamily/lithermes 1.0.13 → 1.0.15

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.
Files changed (28) hide show
  1. package/README.md +11 -27
  2. package/README_Ko-KR.md +11 -27
  3. package/assets/lithermes-plugin/README.md +35 -4
  4. package/assets/lithermes-plugin/__init__.py +25 -2
  5. package/assets/lithermes-plugin/auto_handoff.py +733 -0
  6. package/assets/lithermes-plugin/core.py +10 -0
  7. package/assets/lithermes-plugin/diagnostics.py +12 -2
  8. package/assets/lithermes-plugin/handoff.py +13 -2
  9. package/assets/lithermes-plugin/payload-version.json +15 -11
  10. package/assets/lithermes-plugin/plugin.yaml +1 -1
  11. package/assets/lithermes-plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -2
  12. package/assets/lithermes-plugin/skills/lit-handoff/SKILL.md +7 -0
  13. package/assets/lithermes-plugin/skills/visual-qa/references/complete-contract.md +2 -2
  14. package/package.json +1 -1
  15. package/readme-assets/badge-version.svg +1 -1
  16. package/src/lib/modelRoutePolicy.js +9 -1
  17. package/readme-assets/ab-simple/s11-ui-baseline-desktop.webp +0 -0
  18. package/readme-assets/ab-simple/s11-ui-lithermes-desktop.webp +0 -0
  19. package/readme-assets/ab-simple/s3-ui-baseline-desktop.webp +0 -0
  20. package/readme-assets/ab-simple/s3-ui-lithermes-desktop.webp +0 -0
  21. package/readme-assets/ab-simple/s4-ui-baseline-desktop.webp +0 -0
  22. package/readme-assets/ab-simple/s4-ui-lithermes-desktop.webp +0 -0
  23. package/readme-assets/ab-simple/s5-office-baseline-slides.webp +0 -0
  24. package/readme-assets/ab-simple/s5-office-lithermes-slides.webp +0 -0
  25. package/readme-assets/ab-simple/s7-lithermes-diagram.webp +0 -0
  26. package/readme-assets/ab-simple/s8-office-baseline-slides.webp +0 -0
  27. package/readme-assets/ab-simple/s8-office-lithermes-slides.webp +0 -0
  28. package/readme-assets/ab-simple/s9-office-lithermes-pages.webp +0 -0
@@ -76,6 +76,11 @@ try:
76
76
  except (ImportError, ModuleNotFoundError):
77
77
  import auto_update as _auto_update
78
78
 
79
+ try:
80
+ from . import auto_handoff as _auto_handoff
81
+ except (ImportError, ModuleNotFoundError):
82
+ import auto_handoff as _auto_handoff
83
+
79
84
  try:
80
85
  from . import output_styles as _output_styles
81
86
  except (ImportError, ModuleNotFoundError):
@@ -155,6 +160,10 @@ def post_api_request(**kwargs: Any) -> None:
155
160
  fields, but the measurement module intentionally ignores them. Returning
156
161
  ``None`` preserves the observer-only hook contract.
157
162
  """
163
+ try:
164
+ _auto_handoff.post_api_request(**kwargs)
165
+ except Exception: # noqa: BLE001 - the optional handoff reading never costs a model call
166
+ pass
158
167
  return _provider_metrics.post_api_request(**kwargs)
159
168
 
160
169
 
@@ -191,6 +200,7 @@ def on_session_start(**kwargs: Any) -> None:
191
200
  release_browser_drive_state(kwargs.get("session_id"))
192
201
  _deliverable_hedges.release_session(kwargs.get("session_id"))
193
202
  _rules.begin_session(str(kwargs.get("session_id") or ""))
203
+ _auto_handoff.begin_session(kwargs.get("session_id"))
194
204
  _print_agents_nudge(str(kwargs.get("platform") or ""))
195
205
  return None
196
206
 
@@ -26,6 +26,11 @@ try:
26
26
  except (ImportError, ModuleNotFoundError):
27
27
  import jev_hint as _jev_hint # type: ignore
28
28
 
29
+ try:
30
+ from . import auto_handoff as _auto_handoff
31
+ except (ImportError, ModuleNotFoundError):
32
+ import auto_handoff as _auto_handoff # type: ignore
33
+
29
34
  try:
30
35
  from .core_contract import KOREAN_PROSE_ALIASES, KOREAN_PROSE_COMMANDS
31
36
  except (ImportError, ModuleNotFoundError):
@@ -74,7 +79,8 @@ _RUNTIME_STEM = "".join(("co", "dex"))
74
79
  _OPENAI_PROVIDER = f"openai-{_RUNTIME_STEM}"
75
80
  _RESPONSES_MODE = f"{_RUNTIME_STEM}_responses"
76
81
  _ASTRA_MODEL = "gpt-6-astra"
77
- _SOL_MODEL = "gpt-6-sol"
82
+ _SOL_MODEL = "gpt-6.1-sol"
83
+ _PREVIOUS_SOL_MODEL = "gpt-6-sol"
78
84
  _LUNA_MODEL = "gpt-6-luna"
79
85
  _LEGACY_SOL_MODEL = "gpt-5.6-sol"
80
86
  _TERRA_MODEL = "gpt-5.6-terra"
@@ -89,6 +95,7 @@ _ASTRA_EFFORTS = _GPT6_LEAD_EFFORTS
89
95
  _OPENAI_CATALOG_EFFORTS = {
90
96
  _ASTRA_MODEL: _ASTRA_EFFORTS,
91
97
  _SOL_MODEL: _GPT6_LEAD_EFFORTS,
98
+ _PREVIOUS_SOL_MODEL: _GPT6_LEAD_EFFORTS,
92
99
  _LUNA_MODEL: _GPT6_LUNA_EFFORTS,
93
100
  _LEGACY_SOL_MODEL: _LEGACY_SOL_EFFORTS,
94
101
  _TERRA_MODEL: _TERRA_EFFORTS,
@@ -352,7 +359,7 @@ def _parse_capabilities(host_version: str) -> CapabilityResult:
352
359
  astra = (astra_parent or astra_child) and cataloged_parent and cataloged_child
353
360
  sol_lead = (
354
361
  provider == _OPENAI_PROVIDER
355
- and model in (_SOL_MODEL, _LEGACY_SOL_MODEL)
362
+ and model in (_SOL_MODEL, _PREVIOUS_SOL_MODEL, _LEGACY_SOL_MODEL)
356
363
  and _cataloged_route(model, effort)
357
364
  and child_model in _LUNA_MODELS
358
365
  and _cataloged_route(child_model, child_effort)
@@ -483,6 +490,7 @@ def status_report(host_version: str | None = None) -> str:
483
490
  _knowledge.status_line(Path.cwd()),
484
491
  _provider_metrics.capability_line(),
485
492
  _jev_hint.status_line(),
493
+ _auto_handoff.status_line(),
486
494
  f"litgoal evidence kinds: {', '.join(_evidence_kinds())}",
487
495
  f"scientific visualization: {_science.dependency_summary()}",
488
496
  *_motion_runtime_lines(),
@@ -571,6 +579,8 @@ def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
571
579
  jev_state = _jev_hint.status()
572
580
  jev_tag = {"on": "OK", "off": "NOTE"}.get(jev_state, "WARN")
573
581
  lines.append(f"[{jev_tag}] {_jev_hint.status_line()}")
582
+ handoff_tag, handoff_text = _auto_handoff.doctor_line()
583
+ lines.append(f"[{handoff_tag}] {handoff_text}")
574
584
  if version != "unknown":
575
585
  lines.append(f"[OK] Hermes host detected (v{version})")
576
586
  else:
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import html
4
+ import os
4
5
  import re
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
8
9
  try:
10
+ from . import auto_handoff
9
11
  from .core_contract import (
10
12
  reader_facing_contract_block,
11
13
  reader_facing_mode_from_user_request,
@@ -14,6 +16,7 @@ try:
14
16
  from .redaction import redact_text
15
17
  from .session_context import is_delegate_child_platform
16
18
  except (ImportError, ModuleNotFoundError):
19
+ import auto_handoff # type: ignore
17
20
  from core_contract import ( # type: ignore
18
21
  reader_facing_contract_block,
19
22
  reader_facing_mode_from_user_request,
@@ -65,8 +68,16 @@ def _agent_message(raw_args: str) -> str:
65
68
  )
66
69
 
67
70
 
68
- def command_lit_handoff(raw_args: str) -> dict[str, str]:
69
- """Build side-effect-free model context for the native slash command."""
71
+ def command_lit_handoff(raw_args: str) -> dict[str, str] | str:
72
+ """Build model context for the native slash command.
73
+
74
+ ``auto on <percent> | off | status`` is the user's switch for the automatic
75
+ handoff; it answers in plain text and sends nothing to the model. Every
76
+ other argument is a focus for a normal handoff and stays side-effect-free.
77
+ """
78
+ switch = auto_handoff.parse_route(raw_args)
79
+ if switch is not None:
80
+ return auto_handoff.run_command(switch, session_id=os.environ.get(auto_handoff.SESSION_ENV, "").strip())
70
81
  return {
71
82
  "display": f"{acknowledgement('lit-handoff', color=True)}\nHandoff source loaded; inspect live state before writing.",
72
83
  "agent_message": _agent_message(raw_args),
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-09-29T15:21:29.113Z",
2
+ "syncedAt": "2026-09-30T13:06:01.009Z",
3
3
  "source": "bundled-payload",
4
- "sourceHash": "552f6dd7aa356b13feb1509ca0cc045eab8be46c83d86d484e1c5a63da94e7ca",
4
+ "sourceHash": "92b298c9a38364d94b56fb497641d44835bb3bda3230338eb6a769647a5f61b5",
5
5
  "files": [
6
6
  {
7
7
  "path": "NOTICE.md",
@@ -9,11 +9,15 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "1802ff4904f841e7da8db7ce0dbb4e8fd867ef1d94493e96dfb40091f7c74df8"
12
+ "sha256": "234ed5a80d74dec9953f5168b85f666de656b3511c23a74e26194748a4f3b766"
13
13
  },
14
14
  {
15
15
  "path": "__init__.py",
16
- "sha256": "f5dc1b38aa13977312c8c773dcd8b31f9df878826f9210737d76f9d19dc9dcb1"
16
+ "sha256": "76cf72655600b82544e9047fab1f5f4030927803c0cfbc08fca311204f37d32c"
17
+ },
18
+ {
19
+ "path": "auto_handoff.py",
20
+ "sha256": "7c0e5385373cc83b202669a9ab500d46e04d79c5a188ed2ffdea93f863ec7dab"
17
21
  },
18
22
  {
19
23
  "path": "auto_update.py",
@@ -29,7 +33,7 @@
29
33
  },
30
34
  {
31
35
  "path": "core.py",
32
- "sha256": "80a2b6706ddd8381c82a3748c9b25ff958af1023d182ed3a641ad0b734217aee"
36
+ "sha256": "32b7ef2c93e3f208e39e26a6eca4a03f48804b7ddf6b7a16eaf4cf038fbf4e21"
33
37
  },
34
38
  {
35
39
  "path": "core_commands.py",
@@ -85,11 +89,11 @@
85
89
  },
86
90
  {
87
91
  "path": "diagnostics.py",
88
- "sha256": "07d7b9b2399475481a6c593761e0e34a8a94a3a17c86ebc55a2afbd4d51cfc9d"
92
+ "sha256": "bd797eadf291ec9cd6c02926595305532e235e21794e4467e157c03b0311fe54"
89
93
  },
90
94
  {
91
95
  "path": "handoff.py",
92
- "sha256": "0e364d64981b84e50b437326c34965121928ef826e231d3822fbbfdb4009f6aa"
96
+ "sha256": "f34788ee4b56b6156e6503b546ea416fd886856df2c8f9e60006eb2b0289a027"
93
97
  },
94
98
  {
95
99
  "path": "humanizer_detector.py",
@@ -177,7 +181,7 @@
177
181
  },
178
182
  {
179
183
  "path": "plugin.yaml",
180
- "sha256": "38db753fc650eadebd74928c31e39bc90ab4226bc417617ae133556a2ef4e3b8"
184
+ "sha256": "b086300c1f74bf85aca8be93bf6fdf9112e35536f050ff109456584952c1ec78"
181
185
  },
182
186
  {
183
187
  "path": "provider_metrics.py",
@@ -1281,7 +1285,7 @@
1281
1285
  },
1282
1286
  {
1283
1287
  "path": "skills/frontend-ui-ux/references/complete-contract.md",
1284
- "sha256": "1959f22c99c35ae6496f6609fb5a2bc0b8d872878fed0345f110e928292b7fe4"
1288
+ "sha256": "a42439ee181e68b729f969e5b8243205566a64e6f212894f2284735f2737809c"
1285
1289
  },
1286
1290
  {
1287
1291
  "path": "skills/frontend-ui-ux/references/composition.md",
@@ -3157,7 +3161,7 @@
3157
3161
  },
3158
3162
  {
3159
3163
  "path": "skills/lit-handoff/SKILL.md",
3160
- "sha256": "c50767e13d4d062d2677d1d8b43891b2dfdb704b6ba557b427ffd6ddecfa8edd"
3164
+ "sha256": "d49141a84537cfa5dc36be0adb72fc57fb6ab4af97c1a75e82b84b4fa5983dd6"
3161
3165
  },
3162
3166
  {
3163
3167
  "path": "skills/lit-humanizer/NOTICE",
@@ -4149,7 +4153,7 @@
4149
4153
  },
4150
4154
  {
4151
4155
  "path": "skills/visual-qa/references/complete-contract.md",
4152
- "sha256": "f51865082a9b60ee743c8e0844d23dcec7890d631ed2982d8142fc66f84a2749"
4156
+ "sha256": "65b9754533bf3a07fc32d2e6a4b479d885e4bc7bcd25030c1dd36418db748eeb"
4153
4157
  },
4154
4158
  {
4155
4159
  "path": "skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json",
@@ -1,5 +1,5 @@
1
1
  name: lithermes
2
- version: 1.0.13
2
+ version: 1.0.15
3
3
  description: "Hermes-native workflow toolkit: litgoal durable runtime, 5-lane review orchestrator, Litwork commands, skills, and prompt steering."
4
4
  author: "LitHermes contributors"
5
5
  kind: standalone
@@ -445,7 +445,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
445
445
 
446
446
  ```text
447
447
  $ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
448
- Installed LitHermes 1.0.13
448
+ Installed LitHermes 1.0.15
449
449
  plugin: $ISOLATED/plugins/lithermes
450
450
  model config: updated
451
451
 
@@ -462,7 +462,7 @@ installed payload: PASS
462
462
  enabled config: PASS
463
463
 
464
464
  $ hermes lithermes doctor
465
- [OK] plugin.yaml readable (version 1.0.13)
465
+ [OK] plugin.yaml readable (version 1.0.15)
466
466
  [OK] skills bundled: 36
467
467
  [OK] litgoal durable runtime importable
468
468
  ```
@@ -62,6 +62,7 @@ on its own, before any other reply content:
62
62
  | direct skill | explicit `lithermes:lit-handoff` | Read this adapter and then the original source in full. | Do not invent state that was not inspected. |
63
63
  | slash command | `/lit-handoff [focus]` | Inject the exact source plus a redacted focus as inert data. | The command handler itself must not write workspace files. |
64
64
  | exact natural route | the complete user message is `handoff` | Use the same source and named banner exactly once for that turn. | Near-misses, code spans, paths, and child-agent messages must not activate. |
65
+ | automatic handoff | the user switched it on with `/lit-handoff auto on <percent>` or `LITHERMES_AUTO_HANDOFF=1` plus `LITHERMES_AUTO_HANDOFF_PERCENT`, and a model call passed that percent | `auto_handoff.py` adds one `<lithermes-auto-handoff>` block to the next turn. Follow the original source, put the `auto-handoff-id:` line from the block near the top of the file, and tell the user the one plain line the block names. | Off by default, no built-in percent, one directive per crossing, delegate children never receive it. After a compaction the next turn gets a bounded digest, and only a file that carries this session's id and was written after the block is loaded. |
65
66
 
66
67
  ## #contract.procedure
67
68
 
@@ -138,6 +139,12 @@ limitations_channel: inline
138
139
 
139
140
  # Hermes Adapter Notes
140
141
 
142
+ `auto_handoff.py` owns the switch and the automatic path: `/lit-handoff auto on
143
+ <percent>`, `auto off` and `auto status` answer in plain text without a model
144
+ call, and `status` and `doctor` print an `Automatic handoff` line. A plugin
145
+ cannot start compaction on Hermes, so the user runs the compact command, or
146
+ Hermes compacts on its own threshold.
147
+
141
148
  `handoff.py` resolves the installed source root at runtime, reads the source
142
149
  without rewriting it, and injects it as authoritative bundled guidance. The
143
150
  slash command and exact natural route are side-effect-free until the model has
@@ -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.13
488
+ Installed LitHermes 1.0.15
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.13)
505
+ [OK] plugin.yaml readable (version 1.0.15)
506
506
  [OK] skills bundled: 36
507
507
  [OK] litgoal durable runtime importable
508
508
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/lithermes",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "npx/bunx installer for the LitHermes Hermes plugin",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.13"><title>release: 1.0.13</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.13</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.15"><title>release: 1.0.15</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.15</text></g></svg>
@@ -3,7 +3,8 @@ const GPT6_LEAD_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max", "ult
3
3
  const ASTRA_EFFORTS = GPT6_LEAD_EFFORTS;
4
4
  const PARENT_MODEL = ASTRA_MODEL;
5
5
  const PARENT_EFFORT = "xhigh";
6
- const SOL_MODEL = "gpt-6-sol";
6
+ const SOL_MODEL = "gpt-6.1-sol";
7
+ const PREVIOUS_SOL_MODEL = "gpt-6-sol";
7
8
  const CHILD_MODEL = "gpt-6-luna";
8
9
  const LEGACY_SOL_MODEL = "gpt-5.6-sol";
9
10
  const LEGACY_CHILD_MODEL = "gpt-5.6-luna";
@@ -18,6 +19,7 @@ const LEGACY_SOL_EFFORTS = new Set(["high", "xhigh"]);
18
19
  const MODEL_EFFORTS = new Map([
19
20
  [ASTRA_MODEL, GPT6_LEAD_EFFORTS],
20
21
  [SOL_MODEL, GPT6_LEAD_EFFORTS],
22
+ [PREVIOUS_SOL_MODEL, GPT6_LEAD_EFFORTS],
21
23
  [CHILD_MODEL, GPT6_LUNA_EFFORTS],
22
24
  [LEGACY_SOL_MODEL, LEGACY_SOL_EFFORTS],
23
25
  [LEGACY_CHILD_MODEL, LEGACY_LUNA_EFFORTS],
@@ -51,6 +53,11 @@ const ROUTE_CATALOG = {
51
53
  model: SOL_MODEL,
52
54
  note: effort === PARENT_EFFORT ? "coding lead alternative" : "Sol Responses effort",
53
55
  })),
56
+ ...["low", "medium", "high", "xhigh", "max", "ultra"].map((effort) => ({
57
+ effort,
58
+ model: PREVIOUS_SOL_MODEL,
59
+ note: "previous-generation Sol lead route",
60
+ })),
54
61
  ...["low", "medium", "high", "xhigh", "max"].map((effort) => ({
55
62
  effort,
56
63
  model: CHILD_MODEL,
@@ -403,6 +410,7 @@ module.exports = {
403
410
  MANAGED_PROVIDER_MODELS,
404
411
  MODEL_IDS,
405
412
  OPENAI_PROVIDER_ID,
413
+ PREVIOUS_SOL_MODEL,
406
414
  SOL_MODEL,
407
415
  PROVIDER_IDS,
408
416
  ROUTE_CATALOG,