@litfamily/litopencode 1.0.13 → 1.0.14

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 (46) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README-Ko-KR.md +11 -15
  3. package/README.md +11 -15
  4. package/dist/activation.d.ts +2 -0
  5. package/dist/activation.js +20 -0
  6. package/dist/auto-handoff-hooks.d.ts +31 -0
  7. package/dist/auto-handoff-hooks.js +313 -0
  8. package/dist/auto-handoff.d.ts +73 -0
  9. package/dist/auto-handoff.js +322 -0
  10. package/dist/cli/doctor.js +21 -1
  11. package/dist/cli/host-limits.d.ts +5 -0
  12. package/dist/cli/host-limits.js +12 -0
  13. package/dist/commands.d.ts +2 -0
  14. package/dist/commands.js +22 -0
  15. package/dist/config-parser.js +23 -0
  16. package/dist/config.d.ts +5 -0
  17. package/dist/config.js +5 -0
  18. package/dist/features.js +25 -2
  19. package/dist/index.d.ts +1 -1
  20. package/dist/server.js +12 -0
  21. package/dist/skills.js +3 -2
  22. package/dist/state.d.ts +1 -0
  23. package/dist/state.js +1 -0
  24. package/docs/assets/readme/badge-version.svg +1 -1
  25. package/docs/reference-Ko-KR.md +36 -0
  26. package/docs/reference.md +36 -0
  27. package/package.json +1 -1
  28. package/skills/lit-handoff/SKILL.md +30 -1
  29. package/tools/check-pack-payload.mjs +2 -2
  30. package/docs/ab/S11/baseline-desktop.webp +0 -0
  31. package/docs/ab/S11/baseline-phone.webp +0 -0
  32. package/docs/ab/S11/lit-desktop.webp +0 -0
  33. package/docs/ab/S11/lit-phone.webp +0 -0
  34. package/docs/ab/S3/baseline-desktop.webp +0 -0
  35. package/docs/ab/S3/baseline-phone.webp +0 -0
  36. package/docs/ab/S3/lit-desktop.webp +0 -0
  37. package/docs/ab/S3/lit-phone.webp +0 -0
  38. package/docs/ab/S4/baseline-desktop.webp +0 -0
  39. package/docs/ab/S4/baseline-phone.webp +0 -0
  40. package/docs/ab/S4/lit-desktop.webp +0 -0
  41. package/docs/ab/S4/lit-phone.webp +0 -0
  42. package/docs/ab/S5/lit-slides.webp +0 -0
  43. package/docs/ab/S7/lit-diagram.webp +0 -0
  44. package/docs/ab/S8/baseline-slides.webp +0 -0
  45. package/docs/ab/S8/lit-slides.webp +0 -0
  46. package/docs/ab/S9/lit-pages.webp +0 -0
package/dist/state.js CHANGED
@@ -33,6 +33,7 @@ export function createRuntimePaths(projectRoot) {
33
33
  lifecycleLockDir: path.join(litLoopDir, ".work-schema-3.lock"),
34
34
  knowledgeDir: path.join(runtimeDir, "knowledge"),
35
35
  knowledgeClaimsFile: path.join(runtimeDir, "knowledge", "claims.jsonl"),
36
+ autoHandoffFile: path.join(runtimeDir, "auto-handoff.json"),
36
37
  opencodeConfigFile: path.join(root, "opencode.json")
37
38
  };
38
39
  }
@@ -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.14"><title>release: 1.0.14</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.14</text></g></svg>
@@ -241,6 +241,42 @@ npm uninstall -g @litfamily/litopencode
241
241
  프로젝트의 `.litopencode/`는 별도 ledger와 receipt입니다. 나중에 재개할 계획이 없다면
242
242
  그 상태를 버려도 되는지 확인한 뒤 직접 정리합니다.
243
243
 
244
+ ## 자동 인수인계
245
+
246
+ 자동 인수인계는 직접 켜야 하며 기본은 꺼져 있습니다. 기본 퍼센트는 없습니다. 플러그인의 `event` 훅과 `experimental.chat.system.transform` 훅에서 동작하고, 대화 내용은 읽지 않으며 assistant 메시지의 토큰 수만 봅니다.
247
+
248
+ ### 한 번의 흐름
249
+
250
+ 1. 루트 세션에서 끝난 assistant 메시지에는 토큰 수가 들어 있습니다. 플러그인은 입력, 출력, 캐시 읽기, 캐시 쓰기를 더하고(호스트가 합계를 주면 그 값을 씁니다) `config.providers`에서 얻은 모델의 컨텍스트 창과 비교합니다. 압축 요약, 실패한 호출, 자식 세션의 메시지는 세지 않습니다.
251
+ 2. 합계가 정한 퍼센트에 닿으면 플러그인은 차례가 끝나기를 기다립니다. 차례나 도구 호출이 진행되는 중에는 아무것도 보내지 않습니다.
252
+ 3. 유휴 이벤트가 오면 같은 에이전트와 모델로 `promptAsync` 요청을 한 번 보냅니다. 요청 본문은 인수인계를 써 달라고 하면서 표식 줄 `Auto-handoff marker: <세션 id> <ISO 시각>`을 알려 줍니다. lit-handoff 계약 전체는 요청의 system 필드로 전달되므로 화면에 보이는 메시지는 짧게 유지됩니다.
253
+ 4. 모델은 lit-handoff 계약이 정하는 위치에 인수인계를 씁니다. 루트 `HANDOFF.md`가 있으면 그 파일에, 없으면 git 프로젝트에서 `.handoff/HANDOFF.md`에 씁니다. 표식 줄은 제목 바로 아래에 둡니다.
254
+ 5. 답이 끝나 세션이 다시 유휴가 되면 플러그인은 세션이 유휴인지(`session.status`) 확인하고 후보 파일 두 곳을 읽습니다. 이 세션의 표식이 들어 있고 요청 뒤에 쓰인 인수인계만 인정합니다. 없으면 압축하지 않고 알림을 띄웁니다.
255
+ 6. 그다음 마지막 assistant 메시지의 모델로 `session.summarize`를 백그라운드에서 호출하고 "Handoff saved. Compacting the conversation now."를 보여 줍니다. 호출이 실패하면 "Handoff saved. Run /compact now."를 한 번 보여 줍니다.
256
+ 7. `session.compacted` 뒤에는(플러그인이 시작했든, 사용자가 `/compact`를 직접 실행했든, OpenCode의 자동 압축이든) 다음 시스템 프롬프트에 `<auto-handoff-digest>`가 붙습니다. 파일 위치와 앞 6144바이트를 데이터로 표시해 담으며, 한 번만 붙습니다. 인수인계가 없거나 오래됐거나 다른 세션 것이면 아무것도 붙이지 않습니다.
257
+ 8. 세션이 다시 발동하려면 사용량이 퍼센트 아래로 내려가야 합니다. 그래서 한 번 닿을 때 요청은 한 번만 나가고, 요청 전에 압축이 일어나면 대기 중이던 발동은 취소됩니다.
258
+
259
+ OpenCode는 세션이 바쁜 동안 들어온 `summarize` 호출을 줄 세웠다가 진행 중인 차례가 끝난 뒤에 응답합니다(OpenCode 1.18.33에서 확인했습니다. 6초짜리 차례 도중의 호출은 6.5초 뒤에 돌아왔고 그 뒤에 압축이 일어났습니다). 그래서 플러그인은 유휴일 때만 호출합니다.
260
+
261
+ ### 설정
262
+
263
+ | 위치 | 내용 | 참고 |
264
+ | --- | --- | --- |
265
+ | 채팅 `lit-handoff auto on <percent>`, `lit-handoff auto off`, `lit-handoff auto status` | 저장된 설정 | 메시지 전체가 그 한 줄이어야 합니다. `/lit-handoff auto ...`도 같습니다. 숫자 없는 `on`은 마지막 퍼센트를 다시 쓰고, 없으면 물어봅니다 |
266
+ | `.litopencode/auto-handoff.json` | `{"enabled": true, "percent": 70}` | 위 경로가 쓰고 사용할 때마다 읽습니다 |
267
+ | `litopencode.json` 또는 `.litopencode/config.json`의 `autoHandoff` | `{"enabled": false, "percent": null}` | 알 수 없는 키와 잘못된 타입은 설정을 읽을 때 거부합니다 |
268
+ | `LITOPENCODE_AUTO_HANDOFF`, `LITOPENCODE_AUTO_HANDOFF_PERCENT` | `1` 또는 `0`, 1~99 정수 | 우선순위가 가장 높습니다 |
269
+
270
+ 우선순위는 환경 변수, 저장된 설정, 설정 파일, 기본값(꺼짐, 퍼센트 없음) 순서입니다. 1~99 밖의 퍼센트, 1과 0이 아닌 스위치 값, 퍼센트 없이 켜진 스위치는 기능을 끄고, 이유가 상태 응답과 `litopencode doctor`에 나옵니다.
271
+
272
+ ### Doctor
273
+
274
+ `litopencode doctor`의 출력에 `autoHandoff` 블록이 추가됩니다. 실제로 적용되는 스위치와 퍼센트, 각각의 출처, 상태 파일 경로, 두 변수 이름, 호스트의 압축 지점, `warnings`가 들어 있습니다. 호스트 지점은 LitOpenCode가 설치하는 창(372,000토큰, 압축은 334,800토큰, 곧 90%)에서만 알 수 있습니다. 그 지점과 같거나 높은 퍼센트는 OpenCode가 먼저 압축하므로 경고가 납니다. 다른 모델은 창과 여유분을 호스트가 정하므로 doctor가 숫자를 내놓지 않습니다.
275
+
276
+ ### 디스크에 남는 것
277
+
278
+ 경로는 `.litopencode/auto-handoff.json`(스위치와 퍼센트)을 씁니다. 인수인계 파일은 모델이 씁니다. 그 밖에는 저장하지 않습니다. 표식은 세션 동안 메모리에만 있으므로, 요청과 압축 사이에 OpenCode를 다시 시작하면 다시 불러오기는 일어나지 않습니다.
279
+
244
280
  ## 안전과 업데이트
245
281
 
246
282
  - `lit-plan`은 계획 전용입니다. `edit`, `bash`, `task` 권한을 허용하지 않습니다.
package/docs/reference.md CHANGED
@@ -341,6 +341,42 @@ OpenCode's managed `opencode.jsonc` (or the custom root's `opencode.json`). Remo
341
341
  Project `.litopencode/` state is separate; preserve it for later resume or remove it only
342
342
  when discarding that project's ledger and receipts is intentional.
343
343
 
344
+ ## Automatic handoff
345
+
346
+ Automatic handoff is opt-in and OFF by default. There is no default percent. It works in the plugin's `event` hook and its `experimental.chat.system.transform` hook, and it reads nothing from the conversation except each assistant message's token counts.
347
+
348
+ ### How one cycle runs
349
+
350
+ 1. Each completed assistant message in a root session carries token counts. The plugin adds input, output, cache read and cache write (or uses the host's total when it is present) and compares the sum with the model's context window from `config.providers`. A compaction summary, a failed call, and a message from a child session are ignored.
351
+ 2. When the sum reaches the user's percent, the plugin waits for the turn to end. It never sends anything during a turn or a tool call.
352
+ 3. At the idle event it sends one `promptAsync` request to the same agent and model. The request text asks for a handoff and names a marker line, `Auto-handoff marker: <session id> <ISO time>`. The complete lit-handoff contract travels in the request's system field, so the visible message stays short.
353
+ 4. The model writes the handoff at the destination the lit-handoff contract resolves: root `HANDOFF.md` when it exists, otherwise `.handoff/HANDOFF.md` in a git project. It puts the marker line directly under the title.
354
+ 5. When the session is idle again after the reply, the plugin checks that the session is idle (`session.status`) and reads both candidate files. A handoff counts only if it contains this session's marker and was written after the request. Without one the plugin does not compact and shows a toast.
355
+ 6. The plugin then calls `session.summarize` with the model of the last assistant message, in the background, and shows "Handoff saved. Compacting the conversation now." If that call fails it shows "Handoff saved. Run /compact now." once.
356
+ 7. After `session.compacted` (from the plugin, from the user's own `/compact`, or from OpenCode's automatic compaction), the next system prompt carries `<auto-handoff-digest>`: a pointer to the file plus the first 6144 bytes, framed as data. It is added once. A missing, stale or foreign handoff adds nothing.
357
+ 8. Usage must fall below the percent before the session can fire again, so a crossing produces one request, and a compaction that happens before the request cancels a pending crossing.
358
+
359
+ OpenCode queues a `summarize` call made while a session is busy and answers it only after the running turn ends (checked on OpenCode 1.18.33: a call made during a six-second turn returned after six and a half seconds and compacted afterwards). The plugin therefore calls it only at idle.
360
+
361
+ ### Settings
362
+
363
+ | Where | What | Notes |
364
+ | --- | --- | --- |
365
+ | Chat `lit-handoff auto on <percent>`, `lit-handoff auto off`, `lit-handoff auto status` | Saved setting | The whole message must be that one line. `/lit-handoff auto ...` does the same. `on` without a number reuses the last percent and asks for one when none exists |
366
+ | `.litopencode/auto-handoff.json` | `{"enabled": true, "percent": 70}` | Written by the route above, read on every use |
367
+ | `autoHandoff` in `litopencode.json` or `.litopencode/config.json` | `{"enabled": false, "percent": null}` | Unknown keys and wrong types are rejected when the config loads |
368
+ | `LITOPENCODE_AUTO_HANDOFF`, `LITOPENCODE_AUTO_HANDOFF_PERCENT` | `1` or `0`, and a whole number from 1 to 99 | Highest priority |
369
+
370
+ The order is environment, then saved setting, then config file, then the default (OFF, no percent). A percent outside 1 to 99, a switch value other than 1 or 0, or an enabled switch without any percent turns the feature OFF, and the reason appears in the status reply and in `litopencode doctor`.
371
+
372
+ ### Doctor
373
+
374
+ `litopencode doctor` adds an `autoHandoff` block: the effective switch and percent, where each came from, the state file path, the two variable names, the host compaction point, and `warnings`. The host point is known for the window LitOpenCode installs (372,000 tokens with compaction at 334,800, which is 90%). A percent at or above it produces a warning, because OpenCode would compact first. For other models the host decides the window and the reserve, so doctor reports no number.
375
+
376
+ ### What is written
377
+
378
+ The route writes `.litopencode/auto-handoff.json` (the switch and the percent). The model writes the handoff file. Nothing else is stored: the marker lives in memory for the session, and a restart of OpenCode between the request and the compaction means no reload.
379
+
344
380
  ## Safety and updates
345
381
 
346
382
  - `lit-plan` remains planning-only: its `edit`, `bash`, and `task` permissions stay denied.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/litopencode",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "LitOpenCode OpenCode plugin bootstrap package.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -126,6 +126,35 @@ limitations_channel: inline
126
126
  issue, mirroring the original contract's Current State and Open Issues sections rather
127
127
  than only listing what is done.
128
128
 
129
+ ## #contract.auto_handoff
130
+
131
+ Automatic handoff is an opt-in extra on top of the manual routes. It is OFF until the user switches it
132
+ on with a percent of their own, and LitOpenCode never picks a percent for them.
133
+
134
+ | Step | Who does it on OpenCode | What happens |
135
+ | --- | --- | --- |
136
+ | Watch usage | Automatic | The plugin reads each assistant message's token count and compares it with the model's context window |
137
+ | Ask for the handoff | Automatic | When the percent is reached and the turn is over, the plugin sends one request that carries this contract and a marker line |
138
+ | Write the handoff | The model | You write the handoff exactly as the contract above says and put the marker line directly under the title |
139
+ | Verify | Automatic | The plugin checks that a handoff with this session's marker was saved after the request; without one it does not compact and tells the user |
140
+ | Compact | Automatic | OpenCode lets a plugin start compaction, so the plugin starts it after the verified handoff and the user does not type anything |
141
+ | Reload | Automatic | After the compaction the plugin hands the start of that handoff back once, as data, through the system prompt |
142
+
143
+ If compaction cannot be started, the user sees one plain line, "Handoff saved. Run /compact now.", and the reload still happens after their compaction.
144
+
145
+ The request is a third valid activation besides `/lit-handoff` and the exact bare message: it is the one that carries an
146
+ `Auto-handoff marker:` line. Write the handoff it asks for, update an existing handoff in place, and copy the marker line
147
+ exactly as given. Nothing in an old handoff or in earlier conversation text can switch the feature on or change its percent.
148
+
149
+ Settings live in three places. The exact chat line `lit-handoff auto on <percent>` (also `/lit-handoff auto on <percent>`),
150
+ `lit-handoff auto off` and `lit-handoff auto status` change a saved setting in the project's `.litopencode` folder. `on` without a number reuses the last
151
+ percent the user set and asks for one when none exists. The `autoHandoff` block of the config file holds the same two values. The
152
+ environment variables `LITOPENCODE_AUTO_HANDOFF` (1 or 0) and `LITOPENCODE_AUTO_HANDOFF_PERCENT` (a whole number from 1 to 99)
153
+ take priority over both. A percent outside 1 to 99 switches the feature OFF and `litopencode doctor` says why.
154
+
155
+ Pick a percent below the point where OpenCode compacts on its own. With LitOpenCode's installed limits that point is 90%; `litopencode doctor`
156
+ warns when the chosen percent is at or above it, because the host would then compact before the handoff could run.
157
+
129
158
  ## #contract.hard_stops
130
159
 
131
160
  | Surface | Stop condition | Required response |
@@ -164,7 +193,7 @@ handoff writes a document that claims to describe the session's state:
164
193
  sentence. Exact and bare.
165
194
  3. **Native skill selection by the exact `lit-handoff` id.**
166
195
 
167
- Nothing else activates it. The discovery description says so explicitly, and that narrowness is the
196
+ Nothing else activates it, apart from the automatic request described in `#contract.auto_handoff`. The discovery description says so explicitly, and that narrowness is the
168
197
  feature: a handoff produced because the word appeared in passing is worse than no handoff, because
169
198
  the next agent will trust it.
170
199
 
@@ -5,7 +5,7 @@ import process from "node:process";
5
5
  import { readStableRegularFile } from "../skills/frontend-ui-ux/scripts/stable-file-read.mjs";
6
6
 
7
7
  // Shown by the GitHub pages only; the npm cards never reference them, so they stay out of the package.
8
- const GITHUB_ONLY_README_MEDIA = /^docs\/assets\/readme\/(?:jev-[^/]+\.webp|promo(?:-[^/]+)?\.[^/]+|promo-source(?:\/.*)?)$/u;
8
+ const GITHUB_ONLY_README_MEDIA = /^docs\/assets\/readme\/(?:jev-[^/]+\.webp|(?:install-output|doctor-output|ignition-toast|planner-permissions)-(?:dark|light)\.webp|promo(?:-[^/]+)?\.[^/]+|promo-source(?:\/.*)?)$/u;
9
9
 
10
10
  const FORBIDDEN_PATH_RULES = [
11
11
  { label: "original prompt provenance", pattern: /^INITIAL_PROMPT\.md$/u },
@@ -14,7 +14,7 @@ const FORBIDDEN_PATH_RULES = [
14
14
  { label: "local asset generator", pattern: /^generate_cover\.py$/u },
15
15
  { label: "repository cover PNG", pattern: /^cover\.png$/u },
16
16
  { label: "repository cover vector", pattern: /^docs\/assets\/cover\.svg$/u },
17
- { label: "GitHub-only README media (Jev snapshots, promo film)", pattern: GITHUB_ONLY_README_MEDIA },
17
+ { label: "GitHub-only README media (Jev snapshots, on-screen pictures, promo film)", pattern: GITHUB_ONLY_README_MEDIA },
18
18
  { label: "unapproved README presentation asset", pattern: /^docs\/assets\/readme\/(?!(?:ascii-readme|badge-version|badge-license|lucide-book-open|lucide-play|lucide-shield-check)\.svg$|litopencode-wordmark\.svg$|litopencode-clay-icon\.png$|(?:litfamily-machines|poster)\.png$|ignition-film\.mp4$|ignition-readme\.gif$|(?:Lucide-LICENSE|JetBrainsMono-OFL)\.txt$)/u },
19
19
  { label: "maintainer release checklist", pattern: /^docs\/release-checklist\.md$/u },
20
20
  { label: "npm README source (packed as README.md / README-Ko-KR.md)", pattern: /^README-npm(?:-Ko-KR)?\.md$/u },
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file