@litfamily/litopencode 1.0.25 → 1.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/activation-routing.js +11 -1
- package/dist/activation.d.ts +0 -10
- package/dist/activation.js +0 -54
- package/dist/agents/defaults.js +2 -1
- package/dist/cli/auto-update.d.ts +27 -1
- package/dist/cli/auto-update.js +112 -13
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.js +71 -10
- package/dist/cli/managed-skill-assets.js +2 -2
- package/dist/deliverable-hedge-guard.js +176 -10
- package/dist/server.js +1 -17
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/privacy.md +1 -12
- package/docs/reference-Ko-KR.md +6 -44
- package/docs/reference.md +6 -48
- package/package.json +1 -1
- package/skills/lit-docx/SKILL.md +1 -1
- package/skills/lit-humanizer/SKILL.md +1 -1
- package/skills/lit-humanizer/references/taxonomy.md +2 -2
- package/skills/lit-humanizer/rules.json +10 -7
- package/skills/lit-humanizer/scripts/core.mjs +13 -2
- package/skills/lit-pptx/SKILL.md +1 -1
- package/skills/managed-skill-manifest.json +3 -3
- package/tools/check-pack-payload.mjs +2 -2
- package/dist/jev-skill-hint.d.ts +0 -50
- package/dist/jev-skill-hint.js +0 -318
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ are tracked separately from this product history.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 1.0.27 - 2026-10-18
|
|
9
|
+
|
|
10
|
+
- When a startup update fails and the rollback leaves the old setup working, OpenCode no longer reruns the snapshot and npm install on every start. Startup leaves that same version alone for a day; an explicit install still runs it, and a newer release is tried at once. The receipt names the failed step, a timeout or a missing command, without command output. `litopencode doctor` now reports the update state, its cause, what the next start will run ("installed X, running Y") and a remedy for each cause, and says it cannot see inside a running OpenCode.
|
|
11
|
+
|
|
12
|
+
- The optional Jev skill hint is gone. In testing it improved skill choice by no more than run-to-run noise, and in real use the model followed none of the 45 hints it received. LitOpenCode no longer contacts TypeSafe, adds no hint line or toast, and `litopencode doctor` no longer reports it. `LITOPENCODE_JEV`, the other `LITOPENCODE_JEV_*` variables and `TYPESAFE_API_KEY` are now ignored and can be deleted, and so can an old debug trace at `.litopencode/logs/jev-skill-hint.jsonl`.
|
|
13
|
+
|
|
14
|
+
- lit-loop now loads the skill that matches a request before it inspects the workspace; plain questions and requests no listed skill covers still load none. The review-work trigger now names reviewing a patch, diff, PR or open changes, with Korean cues, and the workflow-loop trigger names carrying one feature, fix or ticket through build, tests, real-surface checks and cleanup.
|
|
15
|
+
|
|
16
|
+
- A request whose deliverable is a Markdown or plain-text file, such as `report.md` or `slides.md`, now gets ordinary Markdown. lit-docx and lit-pptx no longer write their converter source (YAML front matter and `:::` blocks) into those files or start a DOCX or PPTX build; they keep that source format only for files they render.
|
|
17
|
+
|
|
18
|
+
## 1.0.26 - 2026-10-16
|
|
19
|
+
|
|
20
|
+
- The write guard now refuses more of the lines that tell a reader what a document is not for, such as `This table is not a ranking.` or `이 문서는 제품을 비교하려는 것이 아닙니다.` These forms came from real sessions. Bold-label callouts such as `> **주의:** ...` are now read too, while ordinary quoted text is still skipped, and lines from manuals and lab reports that only look similar stay allowed. Files saved through `apply_patch` are now checked before they land, the same way the write and edit tools already were.
|
|
21
|
+
|
|
8
22
|
## 1.0.25 - 2026-10-15
|
|
9
23
|
|
|
10
24
|
- The write guard now refuses sentences that say what a document is not for, such as `동일 조건의 독립 성능 비교로 쓰지 않음.` or an English line saying a result is not a benchmark. When the model writes one with the write or edit tool, the call is refused and the model rewrites it. Three new rules cover Korean pledges, a Korean pledge repeated through a document, and the English forms, and the status-negation warning now also reads `쓰지 않음`, the polite endings and bold-wrapped endings. Legal and financial notices, safety warnings, operating rules, method lines and real negative findings stay untouched, and the rules still finish in milliseconds on very long runs of spaces. The lit-docx and lit-pptx skills now ask for a real limit once, as a fact beside the number it affects, and keep anything about what the document is not for in the reply.
|
package/README-Ko-KR.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitOpenCode 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
|
|
2
2
|
|
|
3
3
|
# LitOpenCode
|
|
4
4
|
|
|
5
5
|
**LitOpenCode는 OpenCode용 워크플로 플러그인입니다. 프롬프트에 `lit`만 붙이면 계획하고, 만들고, 확인하고, 기록까지 남겨서 다음 세션이 어디서 멈췄는지 바로 압니다.**
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/readme/badge-version.svg" alt="1.0.27" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://img.shields.io/npm/dm/@litfamily/litopencode?label=downloads&color=D7F75B" alt="월간 npm 다운로드 수" /></a>
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/readme/badge-license.svg" alt="MIT 라이선스" /></a>
|
|
11
11
|
<a href="https://github.com/wjgoarxiv/litopencode"><img src="https://img.shields.io/github/stars/wjgoarxiv/litopencode?style=social" alt="GitHub 스타" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -57,9 +57,9 @@ npm uninstall -g @litfamily/litopencode
|
|
|
57
57
|
## 더 보기
|
|
58
58
|
|
|
59
59
|
- [GitHub의 사용 안내](https://github.com/wjgoarxiv/litopencode/blob/main/docs/guide-Ko-KR.md): 설치 옵션, 자동 인수인계, 문제 해결, 제거
|
|
60
|
-
- [워크플로 상세 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
61
|
-
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
62
|
-
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
60
|
+
- [워크플로 상세 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/reference-Ko-KR.md) · [English reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/reference.md)
|
|
61
|
+
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/CHANGELOG.md) · [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/privacy.md)
|
|
62
|
+
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/LICENSE) · [ASCII 글꼴 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>ASCII 로고 복사</summary>
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
1
|
+
<p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitOpenCode robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
|
|
2
2
|
|
|
3
3
|
# LitOpenCode
|
|
4
4
|
|
|
5
5
|
**LitOpenCode is a workflow plugin for OpenCode. Add `lit` to a prompt and the work gets planned, built, checked and written down, so the next session knows where you stopped.**
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
8
|
+
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/readme/badge-version.svg" alt="1.0.27" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@litfamily/litopencode"><img src="https://img.shields.io/npm/dm/@litfamily/litopencode?label=downloads&color=D7F75B" alt="npm downloads per month" /></a>
|
|
10
|
-
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
10
|
+
<a href="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
|
|
11
11
|
<a href="https://github.com/wjgoarxiv/litopencode"><img src="https://img.shields.io/github/stars/wjgoarxiv/litopencode?style=social" alt="GitHub stars" /></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -57,9 +57,9 @@ npm uninstall -g @litfamily/litopencode
|
|
|
57
57
|
## More
|
|
58
58
|
|
|
59
59
|
- [Guide on GitHub](https://github.com/wjgoarxiv/litopencode/blob/main/docs/guide.md): install options, automatic handoff, troubleshooting and removal.
|
|
60
|
-
- [Workflow reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
61
|
-
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
62
|
-
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.
|
|
60
|
+
- [Workflow reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/reference.md)
|
|
61
|
+
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/CHANGELOG.md) · [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/privacy.md)
|
|
62
|
+
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/LICENSE) · [ASCII font license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.27/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>Copy ASCII logo</summary>
|
|
@@ -67,6 +67,11 @@ const diagramCreationIntent = /^\s*(?:lit[,:]?\s+)?(?:please\s+)?(?:create|draw|
|
|
|
67
67
|
const officeAuthoringVerb = /(?<![a-z])(?:make|create|write|draft|prepare|produce|generate|convert|edit|build)(?![a-z])|(?<![a-z])turn(?=(?:[^.!?\n]|\.(?=\S))*?(?<![a-z])into(?![a-z])(?:[^.!?\n]|\.(?=\S))*?(?<![a-z])(?:slides?|decks?|presentations?|pptx?|reports?|documents?|docx|word)(?![a-z]))|만들|작성|써줘|제작|변환|편집/u;
|
|
68
68
|
const reportNoun = /(?<![a-z])(?:reports?|documents?|docx|word)(?![a-z])|보고서|리포트|기획서|제안서|문서|워드/u;
|
|
69
69
|
const slidesNoun = /(?<![a-z])(?:slides?|decks?|presentations?|pptx?)(?![a-z])|발표자료|발표|슬라이드|피피티|덱/u;
|
|
70
|
+
// A deliverable the user asked to receive as Markdown or plain text ("saved as report.md", "in Markdown",
|
|
71
|
+
// "as plain text"). An input file such as "turn notes.md into slides" is not one. A named Office or PDF
|
|
72
|
+
// output keeps the skill route, because the skill then renders that file from its own source.
|
|
73
|
+
const markdownDeliverable = /(?<![a-z])(?:save|saved|saving|write|written|output|deliver|export)\s+(?:(?:it|them|this|both|each)\s+)?(?:as|to|in|into)\s+`?[\w./-]*\.(?:md|markdown|txt)(?![a-z])|(?<![a-z])(?:as|in)\s+(?:plain\s+)?markdown(?![a-z])|(?<![a-z])plain[- ]text(?![a-z])|마크다운(?:으로|\s*파일로|\s*형식)|\.md\s*(?:로|으로|파일로)\s*(?:저장|작성)/u;
|
|
74
|
+
const officeFileFormat = /(?<![a-z])(?:docx|pptx?|pdf|word|powerpoint)(?![a-z])|워드|파워포인트|피피티/u;
|
|
70
75
|
// lit-typographic-motion (MO-C-18..24). The trigger is a creation verb plus a compound motion-video
|
|
71
76
|
// noun, or plus a bare video noun that none of the five exclusions claims. Bare 모션/motion and
|
|
72
77
|
// 인트로/intro never trigger alone. The verbs are the office/UI making verbs plus a small motion set
|
|
@@ -378,7 +383,12 @@ export function promptForChatActivationMode(mode, triggerText = "") {
|
|
|
378
383
|
After the workspace check, use OpenCode's native skill tool to load lit-diagram-drawer before authoring the requested conceptual diagram. This is the one narrowly relevant skill for this bounded task. Resolve resources from the skill location returned by OpenCode, follow its verification and export steps, and inspect the final PNG. If the skill is unavailable, report that limit instead of claiming it ran.
|
|
379
384
|
</lit-task-mode>`);
|
|
380
385
|
}
|
|
381
|
-
if (mode === "lit-task" && officeAuthoringVerb.test(raw)) {
|
|
386
|
+
if (mode === "lit-task" && officeAuthoringVerb.test(raw) && markdownDeliverable.test(raw) && !officeFileFormat.test(raw) && (reportNoun.test(raw) || slidesNoun.test(raw))) {
|
|
387
|
+
targetedPrompt = targetedPrompt.replace("</lit-task-mode>", `
|
|
388
|
+
The requested deliverable is a Markdown or plain-text file, not an Office file. Write each named file as ordinary Markdown that a reader opens directly: headings, paragraphs, lists and tables, with no YAML front matter, no \`:::\` directive blocks and no tonality, layout or title-treatment keys. Do not build a DOCX or PPTX and do not load lit-docx or lit-pptx for it; their Markdown is converter source and belongs only beside a file that skill renders.
|
|
389
|
+
</lit-task-mode>`);
|
|
390
|
+
}
|
|
391
|
+
else if (mode === "lit-task" && officeAuthoringVerb.test(raw)) {
|
|
382
392
|
const skills = [reportNoun.test(raw) ? "lit-docx" : undefined, slidesNoun.test(raw) ? "lit-pptx" : undefined].filter(Boolean);
|
|
383
393
|
if (skills.length > 0)
|
|
384
394
|
targetedPrompt = targetedPrompt.replace("</lit-task-mode>", `
|
package/dist/activation.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin";
|
|
|
2
2
|
import type { SessionLookup } from "./session-lineage.ts";
|
|
3
3
|
import type { BoundedAuthorityOptions } from "./bounded-authority.ts";
|
|
4
4
|
import { type AutoHandoffRouteMode } from "./auto-handoff.ts";
|
|
5
|
-
import { type JevSkillHintPart } from "./jev-skill-hint.ts";
|
|
6
5
|
export { litHandoffPromptInjection, scientificVisualizationPromptInjection } from "./activation-managed-prompts.ts";
|
|
7
6
|
export { litActivationBanner } from "./activation-prompt-utils.ts";
|
|
8
7
|
export { litLoopPromptInjection, litPlanPromptInjection } from "./activation-primary-prompts.ts";
|
|
@@ -11,12 +10,6 @@ export { comprehendPromptInjection, debuggingPromptInjection, deepInterviewPromp
|
|
|
11
10
|
type ChatMessageHook = NonNullable<Hooks["chat.message"]>;
|
|
12
11
|
type ActivationToastClient = Pick<PluginInput["client"], "tui">;
|
|
13
12
|
export declare function showActivationToast(client: ActivationToastClient | undefined, skillId: string, env?: NodeJS.ProcessEnv): void;
|
|
14
|
-
/**
|
|
15
|
-
* One quiet `info` toast per hinted turn; notes, `none` and silent turns never reach it. On the first
|
|
16
|
-
* eligible turn of a session (`announce`) the standout ON notice goes out instead, with that turn's hint
|
|
17
|
-
* as its body: the TUI keeps a single toast, so a second one would replace the notice at once.
|
|
18
|
-
*/
|
|
19
|
-
export declare function showJevSkillHintToast(client: ActivationToastClient | undefined, hint: JevSkillHintPart | undefined, announce?: boolean): void;
|
|
20
13
|
export type ChatMessageActivationOptions = {
|
|
21
14
|
readonly getSession?: SessionLookup;
|
|
22
15
|
readonly hostArgv?: readonly string[];
|
|
@@ -30,8 +23,5 @@ export type ChatMessageActivationOptions = {
|
|
|
30
23
|
readonly trustedInjectedSkill?: (sessionID: string) => string | undefined;
|
|
31
24
|
/** Restores that skill after the root-turn reset; the command already showed its activation toast. */
|
|
32
25
|
readonly onTrustedSkillResume?: (sessionID: string, skillId: string) => void | Promise<void>;
|
|
33
|
-
/** Consumes the mark `command.execute.before` left for this session's next message. */
|
|
34
|
-
readonly isCommandTurn?: (sessionID: string) => boolean;
|
|
35
|
-
readonly skillHint?: (sessionID: string, promptText: string) => Promise<JevSkillHintPart | undefined>;
|
|
36
26
|
};
|
|
37
27
|
export declare function createChatMessageActivationHook(projectRoot: string, options?: ChatMessageActivationOptions): ChatMessageHook;
|
package/dist/activation.js
CHANGED
|
@@ -8,7 +8,6 @@ import { isReadOnlyWorkflowFamilyPlan } from "./workflow-families.js";
|
|
|
8
8
|
import { queryKnowledge } from "./knowledge.js";
|
|
9
9
|
import { activationDiscipline } from "./activation-probe.js";
|
|
10
10
|
import { micro, supportsMarkGlyphs } from "./lit-mark.js";
|
|
11
|
-
import { jevSkillHintAwarenessText, jevSkillHintToastText } from "./jev-skill-hint.js";
|
|
12
11
|
// Prompt modules preserve minimum-first, single-task or few-task planning, avoidable custom code review,
|
|
13
12
|
// and external-source safety; this module only coordinates their unchanged activation surfaces.
|
|
14
13
|
export { litHandoffPromptInjection, scientificVisualizationPromptInjection } from "./activation-managed-prompts.js";
|
|
@@ -35,32 +34,6 @@ export function showActivationToast(client, skillId, env = process.env) {
|
|
|
35
34
|
// The activation prompt is the primary surface; this toast is best-effort UI feedback.
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
|
-
/**
|
|
39
|
-
* One quiet `info` toast per hinted turn; notes, `none` and silent turns never reach it. On the first
|
|
40
|
-
* eligible turn of a session (`announce`) the standout ON notice goes out instead, with that turn's hint
|
|
41
|
-
* as its body: the TUI keeps a single toast, so a second one would replace the notice at once.
|
|
42
|
-
*/
|
|
43
|
-
export function showJevSkillHintToast(client, hint, announce = false) {
|
|
44
|
-
const hinted = hint?.kind === "hint" ? jevSkillHintToastText(hint.skillId, hint.latencyMs) : undefined;
|
|
45
|
-
let body;
|
|
46
|
-
if (announce) {
|
|
47
|
-
body = hinted === undefined
|
|
48
|
-
? { message: jevSkillHintAwarenessText, variant: "warning" }
|
|
49
|
-
: { title: jevSkillHintAwarenessText, message: hinted, variant: "warning" };
|
|
50
|
-
}
|
|
51
|
-
else if (hinted !== undefined) {
|
|
52
|
-
body = { message: hinted, variant: "info" };
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const pending = client?.tui?.showToast({ body });
|
|
59
|
-
void pending?.catch(() => undefined);
|
|
60
|
-
}
|
|
61
|
-
catch { // no-excuse-ok: catch -- a missing or failing TUI must never block the user message.
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
37
|
const knownSubagentAgentNames = new Set([
|
|
65
38
|
...litOpenCodeAgents.filter((agent) => agent.mode !== "primary").map((agent) => agent.id),
|
|
66
39
|
"build",
|
|
@@ -92,9 +65,6 @@ export function createChatMessageActivationHook(projectRoot, options = {}) {
|
|
|
92
65
|
if (processedOutputs.has(output))
|
|
93
66
|
return;
|
|
94
67
|
processedOutputs.add(output);
|
|
95
|
-
// Command marks are keyed by session and consumed here, on every turn: the host hands this hook a
|
|
96
|
-
// new parts array, so the array `command.execute.before` saw can never be matched by identity.
|
|
97
|
-
const commandTurn = options.isCommandTurn?.(input.sessionID) === true;
|
|
98
68
|
const trustedInjectedSkill = options.trustedInjectedSkill?.(input.sessionID);
|
|
99
69
|
const agent = input.agent ?? (typeof output.message.agent === "string" ? output.message.agent : undefined);
|
|
100
70
|
const childSession = await isChildSessionMessage(input.sessionID, agent, options.getSession);
|
|
@@ -161,30 +131,6 @@ export function createChatMessageActivationHook(projectRoot, options = {}) {
|
|
|
161
131
|
// A malformed or unreadable knowledge store stays silent instead of inventing context.
|
|
162
132
|
}
|
|
163
133
|
}
|
|
164
|
-
// The optional skill hint only speaks on root turns that no command or deterministic route claimed.
|
|
165
|
-
if (!childSession && mode === undefined && options.skillHint !== undefined && !commandTurn) {
|
|
166
|
-
const promptText = nonEmptyTextParts
|
|
167
|
-
.filter((part) => part.synthetic !== true)
|
|
168
|
-
.map((part) => part.text)
|
|
169
|
-
.join("\n");
|
|
170
|
-
try {
|
|
171
|
-
const hint = await options.skillHint(input.sessionID, promptText);
|
|
172
|
-
if (hint !== undefined) {
|
|
173
|
-
output.parts.push({
|
|
174
|
-
id: `prt_litopencode_skill_hint_${idSuffix(input.messageID ?? output.message.id)}`,
|
|
175
|
-
sessionID: input.sessionID,
|
|
176
|
-
messageID: input.messageID ?? output.message.id,
|
|
177
|
-
type: "text",
|
|
178
|
-
text: hint.text,
|
|
179
|
-
// Sent to the model like any text part, but not shown or copied as the user's own words.
|
|
180
|
-
synthetic: true,
|
|
181
|
-
metadata: { litopencodeSkillHint: { kind: hint.kind, source: "chat.message" } }
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
catch { // no-excuse-ok: catch -- the optional hint must never block the user message.
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
134
|
if (mode === undefined || childSession)
|
|
189
135
|
return;
|
|
190
136
|
const trigger = mode === "lit-handoff" ? "handoff" : mode === "lit-scientific-visualization" ? mode : "lit";
|
package/dist/agents/defaults.js
CHANGED
|
@@ -64,7 +64,8 @@ const litLoopPrompt = [
|
|
|
64
64
|
interfaceWorkHandoff,
|
|
65
65
|
"Do not call the task, lit, litwork, start-work, or review-work tools, and do not delegate for that path.",
|
|
66
66
|
"Do not create a TODO plan, durable goal or ledger, or evidence tree. Do not preload the workflow skills or agent roster. The activation hook has already handled bare lit.",
|
|
67
|
-
"
|
|
67
|
+
"On that bounded path, use the supplied acceptance criteria directly; consult at most one narrowly relevant skill only if inspection reveals a specific gap it can resolve.",
|
|
68
|
+
"Otherwise, when the request matches a skill in the available skills list, load that skill with the skill tool first, then inspect the workspace under its guidance. Plain questions, explanations, and requests no listed skill covers need no skill.",
|
|
68
69
|
"Use the durable loop only when the user explicitly asks for tracked multi-turn work, or the task genuinely needs resumption across turns or independent workstreams.",
|
|
69
70
|
"For a long workflow, delegate only independent slices that materially reduce work. Treat at most 20 concurrent subagents as an advisory ceiling; OpenCode exposes no verified numeric hard limit, so never claim host enforcement.",
|
|
70
71
|
"When delegation is used, require each worker DoneClaim to name changed files, exact tests, real-surface evidence, risks, and a cleanup receipt. An independent verifier must confirm it before the slice becomes FullyDone.",
|
|
@@ -6,6 +6,8 @@ export declare const autoUpdateLockTimeoutMs = 2000;
|
|
|
6
6
|
/** Keep a live owner protected for the whole bounded transaction plus one command window. */
|
|
7
7
|
export declare const autoUpdateInstallLockStaleMs: number;
|
|
8
8
|
export declare const autoUpdateRecursionEnv = "LITOPENCODE_AUTO_UPDATE_IN_PROGRESS";
|
|
9
|
+
/** After a safe rollback, OpenCode starts leave the same target alone for this long. */
|
|
10
|
+
export declare const autoUpdateFailureBackoffMs: number;
|
|
9
11
|
type CommandOutput = {
|
|
10
12
|
readonly stdout?: string;
|
|
11
13
|
readonly stderr?: string;
|
|
@@ -62,6 +64,11 @@ type DoctorReceipt = {
|
|
|
62
64
|
};
|
|
63
65
|
readonly error?: string;
|
|
64
66
|
};
|
|
67
|
+
export type AutoUpdateFailure = {
|
|
68
|
+
readonly step: "prepare" | "npm-install" | "install" | "doctor";
|
|
69
|
+
readonly timedOut: boolean;
|
|
70
|
+
readonly missingCommand: boolean;
|
|
71
|
+
};
|
|
65
72
|
type AutoUpdateReceipt = {
|
|
66
73
|
readonly schemaVersion: 1;
|
|
67
74
|
readonly status: AutoUpdateRunResult["status"];
|
|
@@ -82,6 +89,7 @@ type AutoUpdateReceipt = {
|
|
|
82
89
|
readonly ok: boolean;
|
|
83
90
|
readonly error?: string;
|
|
84
91
|
};
|
|
92
|
+
readonly failure?: AutoUpdateFailure;
|
|
85
93
|
};
|
|
86
94
|
/**
|
|
87
95
|
* The staged CLI needs Node. Inside OpenCode the plugin runs on a Bun-compiled
|
|
@@ -92,6 +100,7 @@ export declare function childNodeExecutable(execPath?: string, versions?: NodeJS
|
|
|
92
100
|
export declare function autoUpdateInstallLockPath(homeDir?: string): string;
|
|
93
101
|
export declare function autoUpdateJournalPath(homeDir?: string): string;
|
|
94
102
|
export declare function autoUpdateReceiptPath(homeDir?: string): string;
|
|
103
|
+
export declare function autoUpdateCheckPath(homeDir?: string): string;
|
|
95
104
|
export declare function isAutoUpdateDisabled(env?: NodeJS.ProcessEnv, argv?: readonly string[]): boolean;
|
|
96
105
|
export declare function shouldRunInteractiveAutoUpdate(input: InteractiveAutoUpdateGate): boolean;
|
|
97
106
|
/**
|
|
@@ -115,11 +124,28 @@ export type AutoUpdateReceiptSummary = {
|
|
|
115
124
|
readonly stagedStateKnown: boolean;
|
|
116
125
|
readonly rollbackOk: boolean;
|
|
117
126
|
readonly finishedAt: number;
|
|
127
|
+
readonly currentVersion?: string;
|
|
128
|
+
readonly targetVersion?: string;
|
|
129
|
+
readonly failure?: AutoUpdateFailure;
|
|
118
130
|
};
|
|
119
131
|
/** Read only the bounded safety summary used by doctor; never echo command output. */
|
|
120
132
|
export declare function readAutoUpdateReceipt(homeDir?: string): Promise<AutoUpdateReceiptSummary | {
|
|
121
133
|
readonly status: "unreadable";
|
|
122
134
|
} | undefined>;
|
|
123
|
-
export
|
|
135
|
+
export type AutoUpdateCheckRecord = {
|
|
136
|
+
readonly schemaVersion: 1;
|
|
137
|
+
readonly at: number;
|
|
138
|
+
/** The plugin version this OpenCode start loaded, which keeps running until the next start. */
|
|
139
|
+
readonly runningVersion: string;
|
|
140
|
+
readonly status: AutoUpdateRunResult["status"];
|
|
141
|
+
readonly reason?: string;
|
|
142
|
+
readonly targetVersion?: string;
|
|
143
|
+
};
|
|
144
|
+
/** Best effort: a state root that is not safe to write never stops the plugin. */
|
|
145
|
+
export declare function recordAutoUpdateCheck(homeDir: string, runningVersion: string, result: AutoUpdateRunResult): Promise<void>;
|
|
146
|
+
export declare function readAutoUpdateCheck(homeDir?: string): Promise<AutoUpdateCheckRecord | undefined>;
|
|
147
|
+
export declare function runPluginAutoUpdate(options?: {
|
|
148
|
+
readonly homeDir?: string;
|
|
149
|
+
}): Promise<AutoUpdateRunResult>;
|
|
124
150
|
export declare function runInteractiveAutoUpdate(input: InteractiveAutoUpdateGate): Promise<AutoUpdateRunResult>;
|
|
125
151
|
export {};
|
package/dist/cli/auto-update.js
CHANGED
|
@@ -15,10 +15,13 @@ export const autoUpdateLockTimeoutMs = 2_000;
|
|
|
15
15
|
/** Keep a live owner protected for the whole bounded transaction plus one command window. */
|
|
16
16
|
export const autoUpdateInstallLockStaleMs = autoUpdateTransactionBoundMs + autoUpdateTimeoutMs;
|
|
17
17
|
export const autoUpdateRecursionEnv = "LITOPENCODE_AUTO_UPDATE_IN_PROGRESS";
|
|
18
|
+
/** After a safe rollback, OpenCode starts leave the same target alone for this long. */
|
|
19
|
+
export const autoUpdateFailureBackoffMs = 24 * 60 * 60 * 1000;
|
|
18
20
|
const stateRootName = ".litopencode";
|
|
19
21
|
const receiptFileName = "auto-update-receipt.json";
|
|
20
22
|
const journalFileName = "auto-update-journal.jsonl";
|
|
21
23
|
const installLockFileName = "auto-update-install.lock";
|
|
24
|
+
const checkFileName = "auto-update-check.json";
|
|
22
25
|
const maxCommandOutputBytes = 4 * 1024 * 1024;
|
|
23
26
|
const execFileAsync = promisify(execFile);
|
|
24
27
|
/**
|
|
@@ -43,6 +46,9 @@ export function autoUpdateJournalPath(homeDir = os.homedir()) {
|
|
|
43
46
|
export function autoUpdateReceiptPath(homeDir = os.homedir()) {
|
|
44
47
|
return path.join(stateRoot(homeDir), receiptFileName);
|
|
45
48
|
}
|
|
49
|
+
export function autoUpdateCheckPath(homeDir = os.homedir()) {
|
|
50
|
+
return path.join(stateRoot(homeDir), checkFileName);
|
|
51
|
+
}
|
|
46
52
|
function truthyEnvironmentValue(value) {
|
|
47
53
|
return value !== undefined;
|
|
48
54
|
}
|
|
@@ -110,6 +116,16 @@ function commandFailure(error) {
|
|
|
110
116
|
.filter(Boolean)
|
|
111
117
|
.join("; ");
|
|
112
118
|
}
|
|
119
|
+
/** Classify a failure by kind only; command output never reaches the receipt. */
|
|
120
|
+
function failureKind(step, error) {
|
|
121
|
+
const candidate = (typeof error === "object" && error !== null ? error : {});
|
|
122
|
+
const message = typeof candidate.message === "string" ? candidate.message : "";
|
|
123
|
+
return {
|
|
124
|
+
step,
|
|
125
|
+
timedOut: candidate.killed === true || /automatic update command exceeded/u.test(message),
|
|
126
|
+
missingCommand: candidate.code === "ENOENT"
|
|
127
|
+
};
|
|
128
|
+
}
|
|
113
129
|
async function defaultRunCommand(command, args, options) {
|
|
114
130
|
return await execFileAsync(command, [...args], options);
|
|
115
131
|
}
|
|
@@ -400,6 +416,8 @@ async function updateTransaction(options, homeDir, currentVersion, targetVersion
|
|
|
400
416
|
// resulting host state unknown until the snapshot is restored and verified.
|
|
401
417
|
let stagedStateKnown = true;
|
|
402
418
|
let priorStateVerified = false;
|
|
419
|
+
let step = "prepare";
|
|
420
|
+
let failure;
|
|
403
421
|
try {
|
|
404
422
|
await fs.mkdir(stageRoot, { recursive: true, mode: 0o700 });
|
|
405
423
|
await fs.writeFile(path.join(stageRoot, "package.json"), '{"private":true}\n', { encoding: "utf8", mode: 0o600 });
|
|
@@ -423,16 +441,19 @@ async function updateTransaction(options, homeDir, currentVersion, targetVersion
|
|
|
423
441
|
"--no-save",
|
|
424
442
|
spec
|
|
425
443
|
];
|
|
444
|
+
step = "npm-install";
|
|
426
445
|
await bounded(() => runCommand("npm", npmArgs, { cwd: stageRoot, env: childEnv, timeout: autoUpdateTimeoutMs, maxBuffer: maxCommandOutputBytes }), autoUpdateTimeoutMs);
|
|
427
446
|
await appendJournal(homeDir, "npm-install", { spec });
|
|
428
447
|
const entrypoint = await packageEntrypoint(stageRoot, targetVersion);
|
|
429
448
|
const nodeExecutable = options.nodeExecutable ?? childNodeExecutable();
|
|
430
449
|
const installArgs = [entrypoint, "install", "--root", options.configRoot, "--no-model-prompt", "--no-permission-prompt", "--no-auto-update"];
|
|
450
|
+
step = "install";
|
|
431
451
|
stagedStateKnown = false;
|
|
432
452
|
await bounded(() => runCommand(nodeExecutable, installArgs, { cwd: options.configRoot, env: childEnv, timeout: autoUpdateTimeoutMs, maxBuffer: maxCommandOutputBytes }), autoUpdateTimeoutMs);
|
|
433
453
|
stagedStateKnown = true;
|
|
434
454
|
await appendJournal(homeDir, "install", { spec });
|
|
435
455
|
const doctorArgs = [entrypoint, "doctor", "--root", options.configRoot, "--json", "--no-auto-update"];
|
|
456
|
+
step = "doctor";
|
|
436
457
|
stagedStateKnown = false;
|
|
437
458
|
const doctorOutput = await bounded(() => runCommand(nodeExecutable, doctorArgs, { cwd: options.configRoot, env: childEnv, timeout: autoUpdateTimeoutMs, maxBuffer: maxCommandOutputBytes }), autoUpdateTimeoutMs);
|
|
438
459
|
doctor = parseDoctorOutput(doctorOutput, targetVersion);
|
|
@@ -444,8 +465,8 @@ async function updateTransaction(options, homeDir, currentVersion, targetVersion
|
|
|
444
465
|
await appendJournal(homeDir, "updated", { spec });
|
|
445
466
|
}
|
|
446
467
|
catch (error) {
|
|
447
|
-
|
|
448
|
-
await tryAppendJournal(homeDir, "failure", { error:
|
|
468
|
+
failure = failureKind(step, error);
|
|
469
|
+
await tryAppendJournal(homeDir, "failure", { error: commandFailure(error) });
|
|
449
470
|
try {
|
|
450
471
|
await restoreSnapshot(options.configRoot, snapshot);
|
|
451
472
|
priorStateVerified = await verifySnapshot(options.configRoot, snapshot);
|
|
@@ -482,7 +503,8 @@ async function updateTransaction(options, homeDir, currentVersion, targetVersion
|
|
|
482
503
|
priorStateVerified: status === "updated" ? true : priorStateVerified,
|
|
483
504
|
stagedStateKnown,
|
|
484
505
|
doctor,
|
|
485
|
-
rollback
|
|
506
|
+
rollback,
|
|
507
|
+
...(failure === undefined ? {} : { failure })
|
|
486
508
|
};
|
|
487
509
|
await writeReceipt(homeDir, receipt);
|
|
488
510
|
return {
|
|
@@ -497,6 +519,20 @@ async function updateTransaction(options, homeDir, currentVersion, targetVersion
|
|
|
497
519
|
...(status === "rolled-back" || status === "failed" ? { rollback } : {})
|
|
498
520
|
};
|
|
499
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* A rollback that left the old setup proven in place is safe to wait on. Without
|
|
524
|
+
* this, a failing update re-ran its snapshot and npm install on every OpenCode
|
|
525
|
+
* start. Anything less certain keeps retrying, because a retry is the repair path.
|
|
526
|
+
*/
|
|
527
|
+
async function inFailureBackoff(homeDir, targetVersion) {
|
|
528
|
+
const last = await readAutoUpdateReceipt(homeDir);
|
|
529
|
+
if (last === undefined || last.status !== "rolled-back")
|
|
530
|
+
return false;
|
|
531
|
+
if (!last.rollbackOk || !last.priorStateVerified || !last.stagedStateKnown || last.targetVersion !== targetVersion)
|
|
532
|
+
return false;
|
|
533
|
+
const age = Date.now() - last.finishedAt;
|
|
534
|
+
return age >= 0 && age < autoUpdateFailureBackoffMs;
|
|
535
|
+
}
|
|
500
536
|
export async function runAutoUpdate(options) {
|
|
501
537
|
const env = options.env ?? process.env;
|
|
502
538
|
if (isAutoUpdateDisabled(env, options.argv))
|
|
@@ -517,6 +553,9 @@ export async function runAutoUpdate(options) {
|
|
|
517
553
|
const comparison = compareStableVersions(targetVersion, options.currentVersion);
|
|
518
554
|
if (comparison !== 1)
|
|
519
555
|
return { status: "skipped", currentVersion: options.currentVersion, targetVersion, reason: "up-to-date" };
|
|
556
|
+
if (options.reason === "plugin" && (await inFailureBackoff(homeDir, targetVersion))) {
|
|
557
|
+
return { status: "skipped", currentVersion: options.currentVersion, targetVersion, reason: "backoff" };
|
|
558
|
+
}
|
|
520
559
|
let lock;
|
|
521
560
|
try {
|
|
522
561
|
lock = await acquireInstallLock(homeDir);
|
|
@@ -551,6 +590,14 @@ export function autoUpdateDiagnostic(result) {
|
|
|
551
590
|
const backup = result.backupPath === undefined ? "" : ` Retained backup: ${result.backupPath}.`;
|
|
552
591
|
return `Automatic update stopped without claiming success (${result.status}). Inspect the receipt at ${receipt}.${backup}`;
|
|
553
592
|
}
|
|
593
|
+
function isFailureRecord(value) {
|
|
594
|
+
if (typeof value !== "object" || value === null)
|
|
595
|
+
return false;
|
|
596
|
+
const record = value;
|
|
597
|
+
return ((record.step === "prepare" || record.step === "npm-install" || record.step === "install" || record.step === "doctor") &&
|
|
598
|
+
typeof record.timedOut === "boolean" &&
|
|
599
|
+
typeof record.missingCommand === "boolean");
|
|
600
|
+
}
|
|
554
601
|
/** Read only the bounded safety summary used by doctor; never echo command output. */
|
|
555
602
|
export async function readAutoUpdateReceipt(homeDir = os.homedir()) {
|
|
556
603
|
const receiptPath = autoUpdateReceiptPath(homeDir);
|
|
@@ -580,7 +627,10 @@ export async function readAutoUpdateReceipt(homeDir = os.homedir()) {
|
|
|
580
627
|
priorStateVerified: parsed.priorStateVerified,
|
|
581
628
|
stagedStateKnown: parsed.stagedStateKnown,
|
|
582
629
|
rollbackOk: parsed.rollback.ok,
|
|
583
|
-
finishedAt: parsed.finishedAt
|
|
630
|
+
finishedAt: parsed.finishedAt,
|
|
631
|
+
...(typeof parsed.currentVersion === "string" ? { currentVersion: parsed.currentVersion } : {}),
|
|
632
|
+
...(typeof parsed.targetVersion === "string" ? { targetVersion: parsed.targetVersion } : {}),
|
|
633
|
+
...(isFailureRecord(parsed.failure) ? { failure: { step: parsed.failure.step, timedOut: parsed.failure.timedOut, missingCommand: parsed.failure.missingCommand } } : {})
|
|
584
634
|
};
|
|
585
635
|
}
|
|
586
636
|
catch (error) {
|
|
@@ -598,16 +648,65 @@ async function isLocalCheckout(packageRoot) {
|
|
|
598
648
|
return false;
|
|
599
649
|
}
|
|
600
650
|
}
|
|
601
|
-
|
|
651
|
+
/** Best effort: a state root that is not safe to write never stops the plugin. */
|
|
652
|
+
export async function recordAutoUpdateCheck(homeDir, runningVersion, result) {
|
|
653
|
+
try {
|
|
654
|
+
await ensurePrivateStateRoot(homeDir);
|
|
655
|
+
const record = {
|
|
656
|
+
schemaVersion: 1,
|
|
657
|
+
at: Date.now(),
|
|
658
|
+
runningVersion,
|
|
659
|
+
status: result.status,
|
|
660
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
661
|
+
...(result.targetVersion === undefined ? {} : { targetVersion: result.targetVersion })
|
|
662
|
+
};
|
|
663
|
+
const checkPath = autoUpdateCheckPath(homeDir);
|
|
664
|
+
const temporary = `${checkPath}.${randomUUID()}.tmp`;
|
|
665
|
+
await fs.writeFile(temporary, `${JSON.stringify(record)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
666
|
+
await fs.rename(temporary, checkPath);
|
|
667
|
+
}
|
|
668
|
+
catch {
|
|
669
|
+
// The record only feeds doctor.
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
export async function readAutoUpdateCheck(homeDir = os.homedir()) {
|
|
673
|
+
try {
|
|
674
|
+
const checkPath = autoUpdateCheckPath(homeDir);
|
|
675
|
+
const stat = await fs.lstat(checkPath);
|
|
676
|
+
if (stat.isSymbolicLink() || !stat.isFile() || (stat.mode & 0o077) !== 0)
|
|
677
|
+
return undefined;
|
|
678
|
+
if (typeof process.getuid === "function" && stat.uid !== process.getuid())
|
|
679
|
+
return undefined;
|
|
680
|
+
const parsed = JSON.parse(await fs.readFile(checkPath, "utf8"));
|
|
681
|
+
if (parsed.schemaVersion !== 1 || typeof parsed.at !== "number" || typeof parsed.runningVersion !== "string" || typeof parsed.status !== "string")
|
|
682
|
+
return undefined;
|
|
683
|
+
return {
|
|
684
|
+
schemaVersion: 1,
|
|
685
|
+
at: parsed.at,
|
|
686
|
+
runningVersion: parsed.runningVersion,
|
|
687
|
+
status: parsed.status,
|
|
688
|
+
...(typeof parsed.reason === "string" ? { reason: parsed.reason } : {}),
|
|
689
|
+
...(typeof parsed.targetVersion === "string" ? { targetVersion: parsed.targetVersion } : {})
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
export async function runPluginAutoUpdate(options = {}) {
|
|
602
697
|
const metadata = await readPackageMetadata();
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
698
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
699
|
+
const result = (await isLocalCheckout(metadata.packageRoot))
|
|
700
|
+
? { status: "skipped", reason: "local-checkout" }
|
|
701
|
+
: await runAutoUpdate({
|
|
702
|
+
reason: "plugin",
|
|
703
|
+
homeDir,
|
|
704
|
+
configRoot: defaultOpenCodeConfigRoot(),
|
|
705
|
+
currentVersion: metadata.version,
|
|
706
|
+
env: process.env
|
|
707
|
+
});
|
|
708
|
+
await recordAutoUpdateCheck(homeDir, metadata.version, result);
|
|
709
|
+
return result;
|
|
611
710
|
}
|
|
612
711
|
export async function runInteractiveAutoUpdate(input) {
|
|
613
712
|
if (!shouldRunInteractiveAutoUpdate(input))
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { CliResult } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Doctor reads the receipt and the record each OpenCode start leaves behind. It
|
|
4
|
+
* cannot see inside a running OpenCode, so "running" means the last start seen.
|
|
5
|
+
*/
|
|
6
|
+
export declare function inspectAutoUpdate(input: {
|
|
7
|
+
readonly homeDir?: string;
|
|
8
|
+
readonly env: NodeJS.ProcessEnv;
|
|
9
|
+
readonly pinnedVersion: string | null;
|
|
10
|
+
}): Promise<Record<string, unknown>>;
|
|
2
11
|
/**
|
|
3
12
|
* The project the plugin would use from here: OpenCode's worktree is the git root above the working
|
|
4
13
|
* directory, and without one the directory itself.
|