@litfamily/litopencode 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 (49) hide show
  1. package/CHANGELOG.md +11 -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/cli/model-catalog.d.ts +10 -1
  14. package/dist/cli/model-catalog.js +9 -2
  15. package/dist/commands.d.ts +2 -0
  16. package/dist/commands.js +22 -0
  17. package/dist/config-parser.js +23 -0
  18. package/dist/config.d.ts +5 -0
  19. package/dist/config.js +5 -0
  20. package/dist/features.js +25 -2
  21. package/dist/index.d.ts +1 -1
  22. package/dist/model-route-policy.js +2 -2
  23. package/dist/server.js +12 -0
  24. package/dist/skills.js +3 -2
  25. package/dist/state.d.ts +1 -0
  26. package/dist/state.js +1 -0
  27. package/docs/assets/readme/badge-version.svg +1 -1
  28. package/docs/reference-Ko-KR.md +39 -2
  29. package/docs/reference.md +39 -2
  30. package/package.json +1 -1
  31. package/skills/lit-handoff/SKILL.md +30 -1
  32. package/tools/check-pack-payload.mjs +2 -2
  33. package/docs/ab/S11/baseline-desktop.webp +0 -0
  34. package/docs/ab/S11/baseline-phone.webp +0 -0
  35. package/docs/ab/S11/lit-desktop.webp +0 -0
  36. package/docs/ab/S11/lit-phone.webp +0 -0
  37. package/docs/ab/S3/baseline-desktop.webp +0 -0
  38. package/docs/ab/S3/baseline-phone.webp +0 -0
  39. package/docs/ab/S3/lit-desktop.webp +0 -0
  40. package/docs/ab/S3/lit-phone.webp +0 -0
  41. package/docs/ab/S4/baseline-desktop.webp +0 -0
  42. package/docs/ab/S4/baseline-phone.webp +0 -0
  43. package/docs/ab/S4/lit-desktop.webp +0 -0
  44. package/docs/ab/S4/lit-phone.webp +0 -0
  45. package/docs/ab/S5/lit-slides.webp +0 -0
  46. package/docs/ab/S7/lit-diagram.webp +0 -0
  47. package/docs/ab/S8/baseline-slides.webp +0 -0
  48. package/docs/ab/S8/lit-slides.webp +0 -0
  49. package/docs/ab/S9/lit-pages.webp +0 -0
package/dist/server.js CHANGED
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { createChatMessageActivationHook, showActivationToast, showJevSkillHintToast } from "./activation.js";
4
4
  import { registerLitOpenCodeAgents } from "./agents.js";
5
+ import { createAutoHandoff } from "./auto-handoff-hooks.js";
5
6
  import { createBoundedAuthorityEventHook } from "./bounded-authority-hooks.js";
6
7
  import { createCommandActivationHook } from "./commands.js";
7
8
  import { loadConfig } from "./config.js";
@@ -48,6 +49,13 @@ export function createLitOpenCodePlugin(autoUpdateRunner = runPluginAutoUpdate)
48
49
  const logger = createLogger(loaded.paths);
49
50
  const getSession = createSessionLookup(input?.client);
50
51
  const ignitionState = createIgnitionState();
52
+ const autoHandoff = createAutoHandoff({
53
+ projectRoot: root,
54
+ paths: loaded.paths,
55
+ client: input?.client,
56
+ config: loaded.config.autoHandoff,
57
+ ...(getSession === undefined ? {} : { getSession })
58
+ });
51
59
  const deliverableHedgeGuard = createDeliverableHedgeGuard({ projectRoot: root });
52
60
  const recordSkillActivation = async (sessionID, skillId, appendedLit = false) => {
53
61
  showActivationToast(input?.client, skillId);
@@ -65,6 +73,7 @@ export function createLitOpenCodePlugin(autoUpdateRunner = runPluginAutoUpdate)
65
73
  commandAliasRoot: defaultOpenCodeConfigRoot(),
66
74
  boundedAuthority: loaded.config.boundedAuthority,
67
75
  knowledgeCaptureEnabled: loaded.config.knowledge.capture,
76
+ autoHandoffRoute: autoHandoff.route,
68
77
  onSkillActivation: recordSkillActivation
69
78
  });
70
79
  const hooks = {
@@ -79,12 +88,14 @@ export function createLitOpenCodePlugin(autoUpdateRunner = runPluginAutoUpdate)
79
88
  trustedCommandActivations.delete(eventInput.event.properties.info.id);
80
89
  }
81
90
  await boundedAuthorityEvent(eventInput);
91
+ await autoHandoff.event(eventInput);
82
92
  },
83
93
  tool: litOpenCodeTools,
84
94
  "chat.message": createChatMessageActivationHook(root, {
85
95
  getSession,
86
96
  boundedAuthority: loaded.config.boundedAuthority,
87
97
  knowledgeCaptureEnabled: loaded.config.knowledge.capture,
98
+ autoHandoffRoute: autoHandoff.route,
88
99
  onRootUserTurn: (sessionID) => {
89
100
  ignitionState.resetDiscipline(sessionID);
90
101
  },
@@ -122,6 +133,7 @@ export function createLitOpenCodePlugin(autoUpdateRunner = runPluginAutoUpdate)
122
133
  bundledRulesDir: fileURLToPath(new URL("../rules/bundled-rules", import.meta.url)),
123
134
  outputStyle: loaded.config.outputStyle
124
135
  }, transformInput, transformOutput);
136
+ await autoHandoff.systemTransform(transformInput, transformOutput);
125
137
  },
126
138
  "experimental.session.compacting": async (compactInput, compactOutput) => {
127
139
  await applyCompactionRuleReset(compactInput, compactOutput);
package/dist/skills.js CHANGED
@@ -359,10 +359,11 @@ export const litOpenCodeRuntimeSkills = Object.freeze([
359
359
  title: "Lit Handoff",
360
360
  summary: "Create a resumable project handoff from live evidence using the complete embedded Handoff contract and template.",
361
361
  featureIds: ["lit-handoff", "doctor-install"],
362
- discovery: "Run /lit-handoff, type exact bare handoff, or inspect skills/lit-handoff/SKILL.md.",
362
+ discovery: "Run /lit-handoff, type exact bare handoff, or inspect skills/lit-handoff/SKILL.md. Run /lit-handoff auto on <percent> to have the handoff written automatically.",
363
363
  safety: [
364
364
  "Read the complete embedded original contract and template before choosing a destination.",
365
- "Verify live workspace facts and preserve a non-managed installed skill collision."
365
+ "Verify live workspace facts and preserve a non-managed installed skill collision.",
366
+ "Automatic handoff is off until the user switches it on with a percent of their own; it never compacts without a verified handoff."
366
367
  ]
367
368
  },
368
369
  {
package/dist/state.d.ts CHANGED
@@ -17,6 +17,7 @@ export type RuntimePaths = {
17
17
  lifecycleLockDir: string;
18
18
  knowledgeDir: string;
19
19
  knowledgeClaimsFile: string;
20
+ autoHandoffFile: string;
20
21
  opencodeConfigFile: string;
21
22
  };
22
23
  export declare const runtimeDirectoryName = ".litopencode";
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.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>
@@ -87,8 +87,9 @@ CI와 비대화형 터미널에서는 자동 질문을 생략하며, 명시한
87
87
  `NO_COLOR`에서도 출력 스타일을 직접 선택할 수 있지만 색상·커서 이스케이프는 출력하지
88
88
  않습니다. `TERM=dumb`이나 UTF-8이 아닌 로케일에서는 ANSI 장식 없이 `LIT` 문자를 표시합니다.
89
89
  새로 설치하거나 초기화하면 계획·검토 및 `lit-loop`는 `openai/gpt-6-astra`/`xhigh`,
90
- 실행·연구 helper는 새 기본값인 `openai/gpt-6-luna`/`max`를 사용합니다. `gpt-6-sol`/`xhigh`는
91
- 코딩 리드 대안입니다. GPT-6 Astra와 GPT-6 Sol은 `low`, `medium`, `high`, `xhigh`, `max`, `ultra`를
90
+ 실행·연구 helper는 새 기본값인 `openai/gpt-6-luna`/`max`를 사용합니다. `gpt-6.1-sol`/`xhigh`는
91
+ 코딩 리드 대안입니다. `gpt-6-sol`은 이전 세대로 계속 선택할 수 있어 이 모델을 쓰는 기존 설정도 그대로 동작하며,
92
+ 같은 추론 수준을 지원하고 `-fast` 별칭은 없습니다. GPT-6 Astra와 GPT-6 Sol은 `low`, `medium`, `high`, `xhigh`, `max`, `ultra`를
92
93
  모두 지원합니다. GPT-6 Luna는 `low`, `medium`, `high`, `xhigh`, `max`를 지원하지만 `ultra`는
93
94
  지원하지 않습니다. 이전 세대 `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`는 계속 선택할 수 있고,
94
95
  현재 호스트 카탈로그에는 이 모델들의 지원 종료일이 등록되어 있지 않습니다. GPT-5.6 Luna는
@@ -241,6 +242,42 @@ npm uninstall -g @litfamily/litopencode
241
242
  프로젝트의 `.litopencode/`는 별도 ledger와 receipt입니다. 나중에 재개할 계획이 없다면
242
243
  그 상태를 버려도 되는지 확인한 뒤 직접 정리합니다.
243
244
 
245
+ ## 자동 인수인계
246
+
247
+ 자동 인수인계는 직접 켜야 하며 기본은 꺼져 있습니다. 기본 퍼센트는 없습니다. 플러그인의 `event` 훅과 `experimental.chat.system.transform` 훅에서 동작하고, 대화 내용은 읽지 않으며 assistant 메시지의 토큰 수만 봅니다.
248
+
249
+ ### 한 번의 흐름
250
+
251
+ 1. 루트 세션에서 끝난 assistant 메시지에는 토큰 수가 들어 있습니다. 플러그인은 입력, 출력, 캐시 읽기, 캐시 쓰기를 더하고(호스트가 합계를 주면 그 값을 씁니다) `config.providers`에서 얻은 모델의 컨텍스트 창과 비교합니다. 압축 요약, 실패한 호출, 자식 세션의 메시지는 세지 않습니다.
252
+ 2. 합계가 정한 퍼센트에 닿으면 플러그인은 차례가 끝나기를 기다립니다. 차례나 도구 호출이 진행되는 중에는 아무것도 보내지 않습니다.
253
+ 3. 유휴 이벤트가 오면 같은 에이전트와 모델로 `promptAsync` 요청을 한 번 보냅니다. 요청 본문은 인수인계를 써 달라고 하면서 표식 줄 `Auto-handoff marker: <세션 id> <ISO 시각>`을 알려 줍니다. lit-handoff 계약 전체는 요청의 system 필드로 전달되므로 화면에 보이는 메시지는 짧게 유지됩니다.
254
+ 4. 모델은 lit-handoff 계약이 정하는 위치에 인수인계를 씁니다. 루트 `HANDOFF.md`가 있으면 그 파일에, 없으면 git 프로젝트에서 `.handoff/HANDOFF.md`에 씁니다. 표식 줄은 제목 바로 아래에 둡니다.
255
+ 5. 답이 끝나 세션이 다시 유휴가 되면 플러그인은 세션이 유휴인지(`session.status`) 확인하고 후보 파일 두 곳을 읽습니다. 이 세션의 표식이 들어 있고 요청 뒤에 쓰인 인수인계만 인정합니다. 없으면 압축하지 않고 알림을 띄웁니다.
256
+ 6. 그다음 마지막 assistant 메시지의 모델로 `session.summarize`를 백그라운드에서 호출하고 "Handoff saved. Compacting the conversation now."를 보여 줍니다. 호출이 실패하면 "Handoff saved. Run /compact now."를 한 번 보여 줍니다.
257
+ 7. `session.compacted` 뒤에는(플러그인이 시작했든, 사용자가 `/compact`를 직접 실행했든, OpenCode의 자동 압축이든) 다음 시스템 프롬프트에 `<auto-handoff-digest>`가 붙습니다. 파일 위치와 앞 6144바이트를 데이터로 표시해 담으며, 한 번만 붙습니다. 인수인계가 없거나 오래됐거나 다른 세션 것이면 아무것도 붙이지 않습니다.
258
+ 8. 세션이 다시 발동하려면 사용량이 퍼센트 아래로 내려가야 합니다. 그래서 한 번 닿을 때 요청은 한 번만 나가고, 요청 전에 압축이 일어나면 대기 중이던 발동은 취소됩니다.
259
+
260
+ OpenCode는 세션이 바쁜 동안 들어온 `summarize` 호출을 줄 세웠다가 진행 중인 차례가 끝난 뒤에 응답합니다(OpenCode 1.18.33에서 확인했습니다. 6초짜리 차례 도중의 호출은 6.5초 뒤에 돌아왔고 그 뒤에 압축이 일어났습니다). 그래서 플러그인은 유휴일 때만 호출합니다.
261
+
262
+ ### 설정
263
+
264
+ | 위치 | 내용 | 참고 |
265
+ | --- | --- | --- |
266
+ | 채팅 `lit-handoff auto on <percent>`, `lit-handoff auto off`, `lit-handoff auto status` | 저장된 설정 | 메시지 전체가 그 한 줄이어야 합니다. `/lit-handoff auto ...`도 같습니다. 숫자 없는 `on`은 마지막 퍼센트를 다시 쓰고, 없으면 물어봅니다 |
267
+ | `.litopencode/auto-handoff.json` | `{"enabled": true, "percent": 70}` | 위 경로가 쓰고 사용할 때마다 읽습니다 |
268
+ | `litopencode.json` 또는 `.litopencode/config.json`의 `autoHandoff` | `{"enabled": false, "percent": null}` | 알 수 없는 키와 잘못된 타입은 설정을 읽을 때 거부합니다 |
269
+ | `LITOPENCODE_AUTO_HANDOFF`, `LITOPENCODE_AUTO_HANDOFF_PERCENT` | `1` 또는 `0`, 1~99 정수 | 우선순위가 가장 높습니다 |
270
+
271
+ 우선순위는 환경 변수, 저장된 설정, 설정 파일, 기본값(꺼짐, 퍼센트 없음) 순서입니다. 1~99 밖의 퍼센트, 1과 0이 아닌 스위치 값, 퍼센트 없이 켜진 스위치는 기능을 끄고, 이유가 상태 응답과 `litopencode doctor`에 나옵니다.
272
+
273
+ ### Doctor
274
+
275
+ `litopencode doctor`의 출력에 `autoHandoff` 블록이 추가됩니다. 실제로 적용되는 스위치와 퍼센트, 각각의 출처, 상태 파일 경로, 두 변수 이름, 호스트의 압축 지점, `warnings`가 들어 있습니다. 호스트 지점은 LitOpenCode가 설치하는 창(372,000토큰, 압축은 334,800토큰, 곧 90%)에서만 알 수 있습니다. 그 지점과 같거나 높은 퍼센트는 OpenCode가 먼저 압축하므로 경고가 납니다. 다른 모델은 창과 여유분을 호스트가 정하므로 doctor가 숫자를 내놓지 않습니다.
276
+
277
+ ### 디스크에 남는 것
278
+
279
+ 경로는 `.litopencode/auto-handoff.json`(스위치와 퍼센트)을 씁니다. 인수인계 파일은 모델이 씁니다. 그 밖에는 저장하지 않습니다. 표식은 세션 동안 메모리에만 있으므로, 요청과 압축 사이에 OpenCode를 다시 시작하면 다시 불러오기는 일어나지 않습니다.
280
+
244
281
  ## 안전과 업데이트
245
282
 
246
283
  - `lit-plan`은 계획 전용입니다. `edit`, `bash`, `task` 권한을 허용하지 않습니다.
package/docs/reference.md CHANGED
@@ -88,8 +88,9 @@ The interactive picker (`--model-prompt`, or a fresh TTY install) asks for a pro
88
88
  (the OpenAI GPT-6 family or the previous GPT-5.6 generation, or xAI Grok), a LEAD model for the
89
89
  planning/review categories, and a HELPER model for the execution/research categories; the helper
90
90
  may use a different provider. New installs default to GPT-6 Astra/xhigh for lead, planning, and
91
- review roles; GPT-6 Sol/xhigh is the coding-lead alternative, and GPT-6 Luna/max is the helper
92
- default for execution, research, and ordinary workers.
91
+ review roles; GPT-6.1 Sol/xhigh (`gpt-6.1-sol`) is the coding-lead alternative, and GPT-6 Luna/max is the helper
92
+ default for execution, research, and ordinary workers. `gpt-6-sol` stays selectable as the previous
93
+ generation, so existing configs that name it keep working; it accepts the same efforts and has no `-fast` alias.
93
94
  GPT-6 Astra and Sol accept `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`. GPT-6 Luna accepts
94
95
  `low`, `medium`, `high`, `xhigh`, and `max`, but not `ultra`. The previous-generation
95
96
  `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` remain selectable, and the current host catalog
@@ -341,6 +342,42 @@ OpenCode's managed `opencode.jsonc` (or the custom root's `opencode.json`). Remo
341
342
  Project `.litopencode/` state is separate; preserve it for later resume or remove it only
342
343
  when discarding that project's ledger and receipts is intentional.
343
344
 
345
+ ## Automatic handoff
346
+
347
+ 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.
348
+
349
+ ### How one cycle runs
350
+
351
+ 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.
352
+ 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.
353
+ 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.
354
+ 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.
355
+ 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.
356
+ 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.
357
+ 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.
358
+ 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.
359
+
360
+ 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.
361
+
362
+ ### Settings
363
+
364
+ | Where | What | Notes |
365
+ | --- | --- | --- |
366
+ | 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 |
367
+ | `.litopencode/auto-handoff.json` | `{"enabled": true, "percent": 70}` | Written by the route above, read on every use |
368
+ | `autoHandoff` in `litopencode.json` or `.litopencode/config.json` | `{"enabled": false, "percent": null}` | Unknown keys and wrong types are rejected when the config loads |
369
+ | `LITOPENCODE_AUTO_HANDOFF`, `LITOPENCODE_AUTO_HANDOFF_PERCENT` | `1` or `0`, and a whole number from 1 to 99 | Highest priority |
370
+
371
+ 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`.
372
+
373
+ ### Doctor
374
+
375
+ `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.
376
+
377
+ ### What is written
378
+
379
+ 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.
380
+
344
381
  ## Safety and updates
345
382
 
346
383
  - `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.15",
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