@litfamily/litopencode 1.0.21 → 1.0.22
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 +8 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/tui/mission-band.d.ts +11 -0
- package/dist/tui/mission-band.js +44 -16
- package/dist/tui/mission-model.d.ts +69 -3
- package/dist/tui/mission-model.js +155 -29
- package/dist/tui/mission-motion.d.ts +10 -1
- package/dist/tui/mission-motion.js +17 -7
- package/dist/tui/mission-source.d.ts +30 -5
- package/dist/tui/mission-source.js +159 -11
- package/dist/tui/mission-text.d.ts +34 -9
- package/dist/tui/mission-text.js +169 -74
- package/dist/tui.js +37 -5
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/reference-Ko-KR.md +70 -19
- package/docs/reference.md +81 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ are tracked separately from this product history.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 1.0.22 - 2026-10-13
|
|
9
|
+
|
|
10
|
+
- The LIT card, the chip beside the prompt and the sidebar now show where you are in the workflow, not only which skill ran. A map runs `interview › plan › work › review › done` and lights the step you are on with its count, such as `WORK 3/7` while `/start-work` works through a saved plan. A lit-loop goal counts its criteria instead (`CRITERION 1/3`), and a litresearch run follows its own steps from `decompose` through each `wave` to `synthesis`. A step the request went past without doing shows `–`. The sidebar draws the map top to bottom with a line that fills as you move forward.
|
|
11
|
+
- While a tool runs, its name and running time sit beside the map, like `⠹ bash · 0:48`. Only the tool's name and start time are read, never its command or arguments, and the step disappears when the turn ends.
|
|
12
|
+
- A finished mission can now tell you so. Add `{ "notify": true }` to the LitOpenCode entry in `tui.json` for a desktop notification and the `done` sound, or turn either one off with `{ "notify": { "sound": false } }` or `{ "notify": { "desktop": false } }`. It is off until you add it, and OpenCode plays nothing unless its own `"attention": { "enabled": true }` is set too.
|
|
13
|
+
- The README pictures of the status card are new captures from OpenCode showing the workflow map, the live tool step and the card after the turn, in English and Korean.
|
|
14
|
+
- A lit-loop turn shows its criteria even when the agent writes `goals.json` by hand, as one goal at the top or nested under `goal`, with `completed` or `done` counted as passed. A goal marked complete by hand passed no gate, so review stays at `–`; only a goal completed through the ledger, whose gate includes review, marks it done. In the criterion view the card leaves out the separate criteria count, so the live step keeps its room on the row.
|
|
15
|
+
|
|
8
16
|
## 1.0.21 - 2026-10-12
|
|
9
17
|
|
|
10
18
|
- The README demo on GitHub is a new film of one real OpenCode turn. It plays without music, and a numbered label names each part LitOpenCode adds as it appears: the `lit` word, the status card switching to WORK, the light mode for a small fix, the reply that reads before editing, the fix with its test, the real test run and the closing summary.
|
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.22/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/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.22/docs/assets/readme/badge-version.svg" alt="1.0.22" /></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.22/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/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.22/docs/reference-Ko-KR.md) · [English reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/docs/reference.md)
|
|
61
|
+
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/CHANGELOG.md) · [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/docs/privacy.md)
|
|
62
|
+
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/LICENSE) · [ASCII 글꼴 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/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.22/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/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.22/docs/assets/readme/badge-version.svg" alt="1.0.22" /></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.22/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/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.22/docs/reference.md)
|
|
61
|
+
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/CHANGELOG.md) · [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/docs/privacy.md)
|
|
62
|
+
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/LICENSE) · [ASCII font license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.22/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>Copy ASCII logo</summary>
|
|
@@ -11,6 +11,17 @@ export type BandOptions = {
|
|
|
11
11
|
* `band: { card: true }` adds the detail card under the status card. Anything else keeps the defaults.
|
|
12
12
|
*/
|
|
13
13
|
export declare function readBandOptions(options: unknown): BandOptions;
|
|
14
|
+
export type NotifyOptions = {
|
|
15
|
+
readonly enabled: boolean;
|
|
16
|
+
readonly desktop: boolean;
|
|
17
|
+
readonly sound: boolean;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Reads the finish alert switch from the plugin's tui.json options. It is off unless `notify` is true
|
|
21
|
+
* (desktop notification and the host's `done` sound) or an object that turns either part off with
|
|
22
|
+
* `desktop: false` or `sound: false`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readNotifyOptions(options: unknown): NotifyOptions;
|
|
14
25
|
/**
|
|
15
26
|
* The skill chips row, exactly `width` cells: bright accent pills for the current request first, then
|
|
16
27
|
* dim recent names, and `+N` for the ones that do not fit. The label goes before any chip does.
|
package/dist/tui/mission-band.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// It reuses the chip's pills, the panel's bar and the shared motion frame, so the chip, the panel and
|
|
5
5
|
// the band always move together.
|
|
6
6
|
import { ease, mixHex, motionTiming, seg } from "./mission-motion.js";
|
|
7
|
-
import { contextWarnPercent, missionStatus } from "./mission-model.js";
|
|
8
|
-
import { cellWidth, chipSparkCells, chipSparkWidth, fade, lineWidth, missionPanel, progressBar, shimmerAt, shine,
|
|
7
|
+
import { contextWarnPercent, currentStepIndex, missionStatus, stepName } from "./mission-model.js";
|
|
8
|
+
import { cellWidth, chipSparkCells, chipSparkWidth, crumb, fade, lineWidth, linkColor, liveSegments, missionPanel, progressBar, shimmerAt, shine, stageFlash, statusSegments, truncateCells } from "./mission-text.js";
|
|
9
9
|
/**
|
|
10
10
|
* Reads the band switches from the plugin's tui.json options: `band: false` turns the band off,
|
|
11
11
|
* `band: { card: true }` adds the detail card under the status card. Anything else keeps the defaults.
|
|
@@ -17,12 +17,31 @@ export function readBandOptions(options) {
|
|
|
17
17
|
const card = typeof band === "object" && band !== null && band.card === true;
|
|
18
18
|
return { enabled: true, card };
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Reads the finish alert switch from the plugin's tui.json options. It is off unless `notify` is true
|
|
22
|
+
* (desktop notification and the host's `done` sound) or an object that turns either part off with
|
|
23
|
+
* `desktop: false` or `sound: false`.
|
|
24
|
+
*/
|
|
25
|
+
export function readNotifyOptions(options) {
|
|
26
|
+
const notify = typeof options === "object" && options !== null ? options.notify : undefined;
|
|
27
|
+
if (notify === true)
|
|
28
|
+
return { enabled: true, desktop: true, sound: true };
|
|
29
|
+
if (typeof notify !== "object" || notify === null)
|
|
30
|
+
return { enabled: false, desktop: false, sound: false };
|
|
31
|
+
const record = notify;
|
|
32
|
+
const desktop = record.desktop !== false;
|
|
33
|
+
const sound = record.sound !== false;
|
|
34
|
+
return { enabled: desktop || sound, desktop, sound };
|
|
35
|
+
}
|
|
20
36
|
const minimumText = 12;
|
|
21
37
|
function elapsedText(minutes) {
|
|
22
38
|
const total = Math.max(0, minutes ?? 0);
|
|
23
39
|
return total < 60 ? `${total}m` : `${Math.floor(total / 60)}h${String(total % 60).padStart(2, "0")}`;
|
|
24
40
|
}
|
|
25
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* The text after the bar: a warning (⚑ and words) replaces the live step and the next task; a finished
|
|
43
|
+
* mission shows COMPLETE. Otherwise the live step, then the next task when it still has room.
|
|
44
|
+
*/
|
|
26
45
|
function trailing(view, frame, palette, room) {
|
|
27
46
|
if (view.complete) {
|
|
28
47
|
// Before COMPLETE pops in, the slot stays empty rather than flashing the header's "● active".
|
|
@@ -37,9 +56,12 @@ function trailing(view, frame, palette, room) {
|
|
|
37
56
|
const color = warning.kind === "retry" ? mixHex(palette.warning, palette.error, 0.5) : palette.warning;
|
|
38
57
|
return [{ text: " ⚑ ", fg: color, bold: true }, { text: truncateCells(warning.text, room - 4), fg: color }];
|
|
39
58
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
const live = liveSegments(view, frame, palette, Math.min(28, room - 2));
|
|
60
|
+
const lead = live.length === 0 ? [] : [{ text: " ", fg: palette.muted }, ...live];
|
|
61
|
+
const left = room - lineWidth(lead);
|
|
62
|
+
if (view.next === undefined || left < minimumText)
|
|
63
|
+
return lead;
|
|
64
|
+
return [...lead, { text: " ▸ ", fg: palette.muted }, { text: truncateCells(view.next, left - 4), fg: palette.text }];
|
|
43
65
|
}
|
|
44
66
|
/** Cuts or pads a line to exactly `width` cells, keeping wide glyphs whole. */
|
|
45
67
|
function fitLine(line, width, palette) {
|
|
@@ -105,7 +127,7 @@ function phaseLabel(view, frame, palette, rest) {
|
|
|
105
127
|
return { text: "✓ COMPLETE", fg: palette.success, bold: true };
|
|
106
128
|
if (status.tone === "warning")
|
|
107
129
|
return { text: `⚑ ${status.text}`, fg: mixHex(palette.info, palette.warning, Math.max(frame.warn, 0.5)), bold: true };
|
|
108
|
-
return { text: `● ${view.phase.toUpperCase()}`, fg: palette.info, bold: true };
|
|
130
|
+
return { text: `● ${stepName(view.phase, view).toUpperCase()}`, fg: palette.info, bold: true };
|
|
109
131
|
}
|
|
110
132
|
function topLine(view, frame, palette, width, border, rest) {
|
|
111
133
|
const brand = [{ text: "🔥 ", fg: palette.text }, { text: "LIT", fg: shine(palette.primary, palette, 0.5, shimmerAt(frame), 0.6), bold: true }];
|
|
@@ -148,17 +170,22 @@ function bottomLine(view, figures, palette, width, border) {
|
|
|
148
170
|
}
|
|
149
171
|
return fitLine([{ text: "╰" + "─".repeat(Math.max(0, width)), fg: border }], width, palette);
|
|
150
172
|
}
|
|
173
|
+
/** The whole stage map as a breadcrumb, `plan ✓ ━ WORK 1/3 ─ review ─ done`, or only the running stage when tight. */
|
|
151
174
|
function trackPills(view, frame, palette, tight) {
|
|
152
175
|
const alpha = (index) => (frame.chipEnter ? ease.outCubic(seg(frame.t, index * motionTiming.stagger, motionTiming.entry)) : 1);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
176
|
+
const reached = currentStepIndex(view);
|
|
177
|
+
const current = Math.min(view.steps.length - 1, Math.max(0, reached));
|
|
178
|
+
const flash = stageFlash(frame);
|
|
179
|
+
if (tight)
|
|
180
|
+
return crumb(view, current, frame, palette, 0, flash).map((segment) => fade(segment, alpha(0), palette));
|
|
157
181
|
const out = [];
|
|
158
|
-
view.steps.forEach((
|
|
159
|
-
if (index > 0)
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
view.steps.forEach((_step, index) => {
|
|
183
|
+
if (index > 0) {
|
|
184
|
+
const color = linkColor(view, index, frame, palette, palette.borderSubtle);
|
|
185
|
+
out.push(fade({ text: color === palette.borderSubtle ? " ─ " : " ━ ", fg: color }, alpha(index), palette));
|
|
186
|
+
}
|
|
187
|
+
for (const segment of crumb(view, index, frame, palette, 0, index === current && reached >= 0 ? flash : -1))
|
|
188
|
+
out.push(fade(segment, alpha(index), palette));
|
|
162
189
|
});
|
|
163
190
|
return out;
|
|
164
191
|
}
|
|
@@ -186,7 +213,8 @@ function trackRow(view, frame, palette, width, last) {
|
|
|
186
213
|
for (const plan of trackPlans) {
|
|
187
214
|
const pills = trackPills(view, frame, palette, plan.tight);
|
|
188
215
|
const bar = plan.bar > 0 && view.progress !== undefined ? [gap, ...progressBar(frame.barRatio, plan.bar, palette, sweep)] : [];
|
|
189
|
-
|
|
216
|
+
// In the criterion loop the stage itself counts criteria, so the count is not repeated.
|
|
217
|
+
const goal = view.family === "loop" ? undefined : view.goal;
|
|
190
218
|
const criteria = plan.criteria && goal !== undefined ? [gap, { text: `${goal.passed}/${goal.total} criteria`, fg: goal.total > 0 && goal.passed === goal.total ? palette.success : palette.muted }] : [];
|
|
191
219
|
const fixed = lineWidth(pills) + lineWidth(bar) + lineWidth(criteria);
|
|
192
220
|
if (fixed > width)
|
|
@@ -9,6 +9,34 @@ export type TodoInput = {
|
|
|
9
9
|
readonly content: string;
|
|
10
10
|
readonly status: string;
|
|
11
11
|
};
|
|
12
|
+
/** The plan file's `- [ ]` / `- [x]` rows: how many are ticked, how many exist, and the first open one. */
|
|
13
|
+
export type PlanChecklist = {
|
|
14
|
+
readonly done: number;
|
|
15
|
+
readonly total: number;
|
|
16
|
+
readonly next?: string;
|
|
17
|
+
};
|
|
18
|
+
/** This session's bounded work from work-schema-3.json: its status, recorded progress turns and plan. */
|
|
19
|
+
export type WorkInput = {
|
|
20
|
+
readonly status: string;
|
|
21
|
+
readonly iterations: number;
|
|
22
|
+
readonly plan?: PlanChecklist;
|
|
23
|
+
};
|
|
24
|
+
export type ResearchStage = "decompose" | "wave" | "expand" | "verify" | "synthesis";
|
|
25
|
+
/** The latest litresearch run folder: what its journal, claim graph and report say so far. */
|
|
26
|
+
export type ResearchInput = {
|
|
27
|
+
readonly stage: ResearchStage;
|
|
28
|
+
readonly waves: number;
|
|
29
|
+
readonly claims: number;
|
|
30
|
+
readonly verified: number;
|
|
31
|
+
readonly reported: boolean;
|
|
32
|
+
/** Last write to the run folder, in ms; a run older than the session's first lit turn is earlier work. */
|
|
33
|
+
readonly touchedAt: number;
|
|
34
|
+
};
|
|
35
|
+
/** The tool the session is running now: its name and start time only, never its input. */
|
|
36
|
+
export type LiveStep = {
|
|
37
|
+
readonly tool: string;
|
|
38
|
+
readonly start: number;
|
|
39
|
+
};
|
|
12
40
|
export type MissionInput = {
|
|
13
41
|
readonly marks: readonly LitMark[];
|
|
14
42
|
readonly agent?: string;
|
|
@@ -19,9 +47,17 @@ export type MissionInput = {
|
|
|
19
47
|
readonly pendingPermissions?: number;
|
|
20
48
|
readonly pendingQuestions?: number;
|
|
21
49
|
readonly contextPercent?: number | null;
|
|
50
|
+
readonly work?: WorkInput;
|
|
51
|
+
readonly research?: ResearchInput;
|
|
52
|
+
readonly live?: LiveStep;
|
|
22
53
|
readonly now: number;
|
|
23
54
|
};
|
|
24
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Which stage table the map follows: the main chain, lit-loop's criterion loop (the same stages with
|
|
57
|
+
* the work stage counting criteria), or a litresearch run.
|
|
58
|
+
*/
|
|
59
|
+
export type MissionFamily = "main" | "loop" | "research";
|
|
60
|
+
export type StepId = "interview" | "plan" | "work" | "review" | "done" | ResearchStage;
|
|
25
61
|
export type StepState = "done" | "skip" | "now" | "todo" | "warn";
|
|
26
62
|
export type MissionStep = {
|
|
27
63
|
readonly id: StepId;
|
|
@@ -50,13 +86,22 @@ export type MissionEvent = {
|
|
|
50
86
|
export type MissionView = {
|
|
51
87
|
readonly active: boolean;
|
|
52
88
|
readonly discipline: string;
|
|
53
|
-
readonly
|
|
89
|
+
readonly family: MissionFamily;
|
|
90
|
+
readonly phase: StepId;
|
|
54
91
|
readonly steps: readonly MissionStep[];
|
|
55
92
|
readonly progress?: {
|
|
56
93
|
readonly done: number;
|
|
57
94
|
readonly total: number;
|
|
58
95
|
};
|
|
59
96
|
readonly next?: string;
|
|
97
|
+
/** The lead of the detail line: `task 3`, or a research run's claim counts. */
|
|
98
|
+
readonly detail?: string;
|
|
99
|
+
/** Recorded turns of the current loop: bounded work progress, or lit-loop goal checkpoints. */
|
|
100
|
+
readonly iteration?: number;
|
|
101
|
+
/** How many tasks the saved plan holds, shown beside the plan stage. */
|
|
102
|
+
readonly planTasks?: number;
|
|
103
|
+
readonly wave?: number;
|
|
104
|
+
readonly live?: LiveStep;
|
|
60
105
|
readonly goal?: MissionGoal;
|
|
61
106
|
readonly blockers: readonly {
|
|
62
107
|
readonly id: string;
|
|
@@ -77,9 +122,28 @@ export type MissionNotice = {
|
|
|
77
122
|
readonly celebrate?: boolean;
|
|
78
123
|
};
|
|
79
124
|
export declare const contextWarnPercent = 75;
|
|
125
|
+
export declare const familySteps: Readonly<Record<MissionFamily, readonly StepId[]>>;
|
|
80
126
|
export declare const idleMission: MissionView;
|
|
81
127
|
/** Reads one part's `metadata.litopencode` mark. Anything without that shape is not a mark. */
|
|
82
128
|
export declare function readLitMark(metadata: unknown, at: number): LitMark | undefined;
|
|
129
|
+
/** The name a stage shows: the loop counts criteria at its work stage and a research wave carries its number. */
|
|
130
|
+
export declare function stepName(id: StepId, view: Pick<MissionView, "family" | "wave">): string;
|
|
131
|
+
/** Index of the stage that is running (or flagged); the step count once complete; -1 while idle. */
|
|
132
|
+
export declare function currentStepIndex(view: Pick<MissionView, "steps" | "complete" | "active">): number;
|
|
133
|
+
/**
|
|
134
|
+
* Counts a plan's checkbox rows at column zero (`- [ ] 1. title — …`). The first open row, cut at its
|
|
135
|
+
* em dash, becomes the next item. Indented rows are not tasks.
|
|
136
|
+
*/
|
|
137
|
+
export declare function planChecklist(text: string): PlanChecklist | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Lenient read of work-schema-3.json for one session: undefined when it is absent, another session's,
|
|
140
|
+
* cancelled, or not the schema-3 shape. Iterations count the recorded `work.progressed` turns.
|
|
141
|
+
*/
|
|
142
|
+
export declare function readWorkState(raw: unknown, sessionID: string): {
|
|
143
|
+
readonly status: string;
|
|
144
|
+
readonly iterations: number;
|
|
145
|
+
readonly planPath: string;
|
|
146
|
+
} | undefined;
|
|
83
147
|
type LedgerCriterion = {
|
|
84
148
|
readonly id: string;
|
|
85
149
|
readonly scenario: string;
|
|
@@ -106,8 +170,10 @@ type LedgerGoal = {
|
|
|
106
170
|
readonly resolved: boolean;
|
|
107
171
|
readonly needsUserDecision: boolean;
|
|
108
172
|
}[];
|
|
173
|
+
/** Written by the ledger CLI, whose completion gate checks evidence; a hand-written goal passed none. */
|
|
174
|
+
readonly gated: boolean;
|
|
109
175
|
};
|
|
110
|
-
/** Lenient read of goals.json: the
|
|
176
|
+
/** Lenient read of goals.json: the goal it names, or undefined when there is none. */
|
|
111
177
|
export declare function readActiveGoal(raw: unknown): LedgerGoal | undefined;
|
|
112
178
|
export declare function formatAgo(at: string, now: number): string;
|
|
113
179
|
export declare function buildMissionView(input: MissionInput): MissionView;
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
// The mission view model for the OpenCode TUI. It reads only what LitOpenCode already leaves behind:
|
|
2
2
|
// the `metadata.litopencode` marks the plugin attaches to user-message parts on activation and on
|
|
3
|
-
// commands, the host todo list
|
|
4
|
-
//
|
|
3
|
+
// commands, the host todo list and running tool, the evidence ledger in
|
|
4
|
+
// .litopencode/litgoal/lit-loop/goals.json, the bounded work record work-schema-3.json with its plan
|
|
5
|
+
// checklist, and litresearch run folders. No new state is written for the TUI.
|
|
5
6
|
export const contextWarnPercent = 75;
|
|
6
7
|
const timelineLimit = 4;
|
|
7
8
|
const staleGoalSlackMs = 60_000;
|
|
9
|
+
export const familySteps = Object.freeze({
|
|
10
|
+
main: Object.freeze(["interview", "plan", "work", "review", "done"]),
|
|
11
|
+
loop: Object.freeze(["interview", "plan", "work", "review", "done"]),
|
|
12
|
+
research: Object.freeze(["decompose", "wave", "expand", "verify", "synthesis"])
|
|
13
|
+
});
|
|
8
14
|
export const idleMission = Object.freeze({
|
|
9
15
|
active: false,
|
|
10
16
|
discipline: "",
|
|
17
|
+
family: "main",
|
|
11
18
|
phase: "plan",
|
|
12
|
-
steps: Object.freeze(
|
|
13
|
-
{ id: "plan", state: "todo" },
|
|
14
|
-
{ id: "work", state: "todo" },
|
|
15
|
-
{ id: "review", state: "todo" }
|
|
16
|
-
]),
|
|
19
|
+
steps: Object.freeze(familySteps.main.map((id) => Object.freeze({ id, state: "todo" }))),
|
|
17
20
|
blockers: Object.freeze([]),
|
|
18
21
|
warnings: Object.freeze([]),
|
|
19
22
|
timeline: Object.freeze([]),
|
|
@@ -46,29 +49,108 @@ export function readLitMark(metadata, at) {
|
|
|
46
49
|
function stepOf(name) {
|
|
47
50
|
if (name === "")
|
|
48
51
|
return undefined;
|
|
52
|
+
if (name === "deep-interview" || name.endsWith("-interview"))
|
|
53
|
+
return "interview";
|
|
49
54
|
if (name === "review-work" || name.includes("review"))
|
|
50
55
|
return "review";
|
|
51
|
-
if (name === "lit-plan" || name.endsWith("-plan") || name === "lit-crucible"
|
|
56
|
+
if (name === "lit-plan" || name.endsWith("-plan") || name === "lit-crucible")
|
|
52
57
|
return "plan";
|
|
53
58
|
return "work";
|
|
54
59
|
}
|
|
55
|
-
|
|
60
|
+
function isResearchName(name) {
|
|
61
|
+
return name === "litresearch" || name === "lit-research";
|
|
62
|
+
}
|
|
63
|
+
/** The name a stage shows: the loop counts criteria at its work stage and a research wave carries its number. */
|
|
64
|
+
export function stepName(id, view) {
|
|
65
|
+
if (id === "work" && view.family === "loop")
|
|
66
|
+
return "criterion";
|
|
67
|
+
if (id === "wave")
|
|
68
|
+
return `wave ${Math.max(1, view.wave ?? 1)}`;
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
/** Index of the stage that is running (or flagged); the step count once complete; -1 while idle. */
|
|
72
|
+
export function currentStepIndex(view) {
|
|
73
|
+
if (!view.active)
|
|
74
|
+
return -1;
|
|
75
|
+
const index = view.steps.findIndex((step) => step.state === "now" || step.state === "warn");
|
|
76
|
+
return index >= 0 ? index : view.complete ? view.steps.length : -1;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Counts a plan's checkbox rows at column zero (`- [ ] 1. title — …`). The first open row, cut at its
|
|
80
|
+
* em dash, becomes the next item. Indented rows are not tasks.
|
|
81
|
+
*/
|
|
82
|
+
export function planChecklist(text) {
|
|
83
|
+
let done = 0;
|
|
84
|
+
let total = 0;
|
|
85
|
+
let next;
|
|
86
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
87
|
+
const match = /^- \[( |x|X)\] +(.*)$/u.exec(line);
|
|
88
|
+
if (match === null)
|
|
89
|
+
continue;
|
|
90
|
+
total += 1;
|
|
91
|
+
if (match[1] !== " ")
|
|
92
|
+
done += 1;
|
|
93
|
+
else if (next === undefined)
|
|
94
|
+
next = match[2].split(" — ")[0].trim();
|
|
95
|
+
}
|
|
96
|
+
if (total === 0)
|
|
97
|
+
return undefined;
|
|
98
|
+
return { done, total, ...(next === undefined || next === "" ? {} : { next }) };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Lenient read of work-schema-3.json for one session: undefined when it is absent, another session's,
|
|
102
|
+
* cancelled, or not the schema-3 shape. Iterations count the recorded `work.progressed` turns.
|
|
103
|
+
*/
|
|
104
|
+
export function readWorkState(raw, sessionID) {
|
|
105
|
+
if (!isRecord(raw) || raw.schemaVersion !== 3 || raw.sessionID !== sessionID)
|
|
106
|
+
return undefined;
|
|
107
|
+
const status = text(raw.status);
|
|
108
|
+
if (status === "" || status === "cancelled")
|
|
109
|
+
return undefined;
|
|
110
|
+
const history = Array.isArray(raw.history) ? raw.history.filter(isRecord) : [];
|
|
111
|
+
const plan = isRecord(raw.plan) ? text(raw.plan.path) : "";
|
|
112
|
+
return { status, iterations: history.filter((entry) => entry.type === "work.progressed").length, planPath: plan };
|
|
113
|
+
}
|
|
114
|
+
// The ledger CLI accepts `passed`, `failed` and `completed` as spellings of its own statuses; a goals.json
|
|
115
|
+
// the agent writes by hand also marks a finished criterion `completed` or `done`.
|
|
116
|
+
const criterionStatusSpellings = { passed: "pass", completed: "pass", complete: "pass", done: "pass", failed: "fail" };
|
|
117
|
+
const goalStatusSpellings = { completed: "complete", done: "complete" };
|
|
118
|
+
/**
|
|
119
|
+
* Picks the goal out of goals.json: the ledger's active goal, else what an agent writes by hand —
|
|
120
|
+
* a goal nested under `goal`, the last entry of a `goals` list, or one goal object at the top.
|
|
121
|
+
*/
|
|
122
|
+
function pickGoal(raw) {
|
|
123
|
+
const hasCriteria = (value) => isRecord(value) && Array.isArray(value.criteria);
|
|
124
|
+
if (Array.isArray(raw.goals)) {
|
|
125
|
+
if (raw.activeGoalId !== undefined) {
|
|
126
|
+
const active = raw.goals.find((candidate) => isRecord(candidate) && candidate.id === raw.activeGoalId);
|
|
127
|
+
return isRecord(active) ? active : undefined;
|
|
128
|
+
}
|
|
129
|
+
return raw.goals.filter(hasCriteria).at(-1);
|
|
130
|
+
}
|
|
131
|
+
if (hasCriteria(raw.goal))
|
|
132
|
+
return raw.goal;
|
|
133
|
+
return hasCriteria(raw) ? raw : undefined;
|
|
134
|
+
}
|
|
135
|
+
/** Lenient read of goals.json: the goal it names, or undefined when there is none. */
|
|
56
136
|
export function readActiveGoal(raw) {
|
|
57
|
-
if (!isRecord(raw)
|
|
137
|
+
if (!isRecord(raw))
|
|
58
138
|
return undefined;
|
|
59
|
-
const goal =
|
|
60
|
-
if (
|
|
139
|
+
const goal = pickGoal(raw);
|
|
140
|
+
if (goal === undefined)
|
|
61
141
|
return undefined;
|
|
62
142
|
const list = (value) => (Array.isArray(value) ? value.filter(isRecord) : []);
|
|
143
|
+
const status = text(goal.status);
|
|
63
144
|
return {
|
|
145
|
+
gated: goal !== raw && Array.isArray(raw.goals) && raw.activeGoalId !== undefined,
|
|
64
146
|
title: text(goal.title) || text(goal.objective),
|
|
65
|
-
status:
|
|
147
|
+
status: goalStatusSpellings[status] ?? status,
|
|
66
148
|
createdAt: text(goal.createdAt),
|
|
67
149
|
updatedAt: text(goal.updatedAt),
|
|
68
150
|
criteria: list(goal.criteria).map((criterion) => ({
|
|
69
151
|
id: text(criterion.id),
|
|
70
152
|
scenario: text(criterion.scenario),
|
|
71
|
-
status: text(criterion.status),
|
|
153
|
+
status: criterionStatusSpellings[text(criterion.status)] ?? text(criterion.status),
|
|
72
154
|
evidence: list(criterion.evidence).map((entry) => ({ kind: text(entry.kind), ref: text(entry.ref), at: text(entry.at) }))
|
|
73
155
|
})),
|
|
74
156
|
checkpoints: list(goal.checkpoints).map((entry) => ({ summary: text(entry.summary), at: text(entry.at) })),
|
|
@@ -131,16 +213,27 @@ export function buildMissionView(input) {
|
|
|
131
213
|
const planSaved = planMark !== undefined && planMark.blocked !== true;
|
|
132
214
|
const planBlocked = planMark?.blocked === true;
|
|
133
215
|
const discipline = latest?.mode && latest.mode !== "bounded-authority-lifecycle" ? latest.mode : latest?.command ?? input.agent ?? "lit";
|
|
216
|
+
// A research run counts when this session started it or wrote to it; an older folder is earlier work.
|
|
217
|
+
const research = input.research !== undefined && !(input.research.touchedAt < marks[0].at - staleGoalSlackMs) ? input.research : undefined;
|
|
218
|
+
const researching = isResearchName(discipline) || (research !== undefined && !research.reported);
|
|
219
|
+
const work = input.work;
|
|
134
220
|
const counted = input.todos.filter((todo) => todo.status !== "cancelled");
|
|
221
|
+
const planList = work?.plan;
|
|
135
222
|
const progress = counted.length > 0
|
|
136
223
|
? { done: counted.filter((todo) => todo.status === "completed").length, total: counted.length }
|
|
137
|
-
:
|
|
138
|
-
? { done:
|
|
139
|
-
: undefined
|
|
224
|
+
: planList !== undefined
|
|
225
|
+
? { done: planList.done, total: planList.total }
|
|
226
|
+
: goal !== undefined && goal.criteria.length > 0
|
|
227
|
+
? { done: goal.criteria.filter((criterion) => criterion.status === "pass").length, total: goal.criteria.length }
|
|
228
|
+
: undefined;
|
|
229
|
+
const countsTasks = counted.length > 0 || planList !== undefined;
|
|
140
230
|
const nextTodo = counted.find((todo) => todo.status === "in_progress") ?? counted.find((todo) => todo.status === "pending");
|
|
141
231
|
const nextCriterion = goal?.criteria.find((criterion) => criterion.status !== "pass");
|
|
232
|
+
const family = researching ? "research" : !countsTasks && goal !== undefined && goal.criteria.length > 0 && /lit-?loop|litgoal/u.test(discipline) ? "loop" : "main";
|
|
142
233
|
const blockers = (goal?.reviewBlockers ?? []).filter((blocker) => !blocker.resolved);
|
|
143
|
-
const complete =
|
|
234
|
+
const complete = researching
|
|
235
|
+
? research?.reported === true
|
|
236
|
+
: goal?.status === "complete" || (progress !== undefined && progress.total > 0 && progress.done === progress.total && goal === undefined && countsTasks);
|
|
144
237
|
let phase = latest === undefined ? agentStep ?? "work" : stepOf(latest.command ?? latest.mode ?? "") ?? "work";
|
|
145
238
|
if (latest?.operation === "save-plan" && !planBlocked)
|
|
146
239
|
phase = "work";
|
|
@@ -148,6 +241,8 @@ export function buildMissionView(input) {
|
|
|
148
241
|
const workComplete = progress === undefined || progress.done === progress.total;
|
|
149
242
|
if (blockers.length > 0 || goal?.status === "review_blocked")
|
|
150
243
|
phase = workComplete ? "review" : phase === "review" ? "review" : "work";
|
|
244
|
+
if (researching)
|
|
245
|
+
phase = research?.stage ?? "decompose";
|
|
151
246
|
if (complete)
|
|
152
247
|
phase = "done";
|
|
153
248
|
const warnings = [];
|
|
@@ -168,23 +263,51 @@ export function buildMissionView(input) {
|
|
|
168
263
|
if (typeof input.contextPercent === "number" && input.contextPercent >= contextWarnPercent) {
|
|
169
264
|
warnings.push({ kind: "context", text: `ctx ${Math.round(input.contextPercent)}%` });
|
|
170
265
|
}
|
|
171
|
-
const order = [
|
|
172
|
-
const position =
|
|
173
|
-
const
|
|
266
|
+
const order = familySteps[family];
|
|
267
|
+
const position = complete ? order.length : order.indexOf(phase);
|
|
268
|
+
const visited = (id) => {
|
|
269
|
+
if (id === "interview")
|
|
270
|
+
return agentStep === "interview" || marks.some((mark) => stepOf(mark.command ?? mark.mode ?? "") === "interview");
|
|
271
|
+
if (id === "plan")
|
|
272
|
+
return planMark !== undefined || work !== undefined || agentStep === "plan" || marks.some((mark) => stepOf(mark.command ?? mark.mode ?? "") === "plan");
|
|
273
|
+
// Finished todos alone prove no review; a review command, a review blocker or the goal ledger's
|
|
274
|
+
// completion gate does.
|
|
275
|
+
if (id === "review")
|
|
276
|
+
return (goal?.status === "complete" && goal.gated) || blockers.length > 0 || (goal?.reviewBlockers.length ?? 0) > 0 || marks.some((mark) => stepOf(mark.command ?? mark.mode ?? "") === "review");
|
|
277
|
+
return true;
|
|
278
|
+
};
|
|
174
279
|
const steps = order.map((id, index) => {
|
|
175
280
|
if (index < position)
|
|
176
|
-
return { id, state: id
|
|
281
|
+
return { id, state: visited(id) ? "done" : "skip" };
|
|
177
282
|
if (index > position)
|
|
178
283
|
return { id, state: "todo" };
|
|
179
284
|
return { id, state: warnings.length > 0 ? "warn" : "now" };
|
|
180
285
|
});
|
|
286
|
+
const iteration = work !== undefined && work.iterations > 0 ? work.iterations : family === "loop" && goal !== undefined && goal.checkpoints.length > 0 ? goal.checkpoints.length : undefined;
|
|
287
|
+
const nextPlan = nextTodo === undefined ? planList?.next : undefined;
|
|
288
|
+
const detail = family === "research"
|
|
289
|
+
? research === undefined ? undefined : `${research.claims} claims · ${research.verified} verified`
|
|
290
|
+
: countsTasks && progress !== undefined && progress.done < progress.total ? `task ${progress.done + 1}` : undefined;
|
|
291
|
+
const live = input.live !== undefined && (input.sessionStatus === "busy" || input.sessionStatus === "retry") ? input.live : undefined;
|
|
181
292
|
return {
|
|
182
293
|
active: true,
|
|
183
294
|
discipline,
|
|
295
|
+
family,
|
|
184
296
|
phase,
|
|
185
297
|
steps,
|
|
186
|
-
...(progress === undefined ? {} : { progress }),
|
|
187
|
-
...(
|
|
298
|
+
...(family === "research" || progress === undefined ? {} : { progress }),
|
|
299
|
+
...(family === "research"
|
|
300
|
+
? {}
|
|
301
|
+
: nextTodo !== undefined
|
|
302
|
+
? { next: nextTodo.content }
|
|
303
|
+
: nextPlan !== undefined
|
|
304
|
+
? { next: nextPlan }
|
|
305
|
+
: nextCriterion !== undefined ? { next: `${nextCriterion.id} ${nextCriterion.scenario}`.trim() } : {}),
|
|
306
|
+
...(detail === undefined ? {} : { detail }),
|
|
307
|
+
...(iteration === undefined ? {} : { iteration }),
|
|
308
|
+
...(planList === undefined ? {} : { planTasks: planList.total }),
|
|
309
|
+
...(family === "research" && research !== undefined && research.waves > 0 ? { wave: research.waves } : {}),
|
|
310
|
+
...(live === undefined ? {} : { live }),
|
|
188
311
|
...(goal === undefined
|
|
189
312
|
? {}
|
|
190
313
|
: {
|
|
@@ -287,14 +410,17 @@ export function missionRowKeys(view) {
|
|
|
287
410
|
const keys = ["h1", "h2", "b1"];
|
|
288
411
|
if (view.goal !== undefined)
|
|
289
412
|
keys.push("title");
|
|
290
|
-
keys.push(
|
|
413
|
+
view.steps.forEach((step, index) => keys.push(...(index === 0 ? [] : [`link:${index}`]), `step:${step.id}`));
|
|
291
414
|
if (view.progress !== undefined)
|
|
292
415
|
keys.push("bar");
|
|
293
|
-
const
|
|
294
|
-
|
|
416
|
+
const line = (view.next !== undefined || view.detail !== undefined) && !view.complete;
|
|
417
|
+
const live = view.live !== undefined && !view.complete;
|
|
418
|
+
if (line || live || view.goal !== undefined || view.warnings.length > 0)
|
|
295
419
|
keys.push("b2");
|
|
296
|
-
if (
|
|
297
|
-
keys.push("
|
|
420
|
+
if (line)
|
|
421
|
+
keys.push("detail");
|
|
422
|
+
if (live)
|
|
423
|
+
keys.push("live");
|
|
298
424
|
if (view.goal !== undefined)
|
|
299
425
|
keys.push("crit");
|
|
300
426
|
view.warnings.forEach((warning) => keys.push(`warn:${warning.kind}:${warning.text}`));
|
|
@@ -41,6 +41,10 @@ export type MotionState = {
|
|
|
41
41
|
readonly warnFrom: number;
|
|
42
42
|
readonly warnTo: number;
|
|
43
43
|
readonly celebrate: boolean;
|
|
44
|
+
/** The stage index the map moved forward from; the connectors below it fill and the chip flashes once. */
|
|
45
|
+
readonly stageFrom?: number;
|
|
46
|
+
/** The stage that was running when the mission completed; it and the stages after it flip to done in turn. */
|
|
47
|
+
readonly celebrateFrom?: number;
|
|
44
48
|
};
|
|
45
49
|
export type MotionFrame = {
|
|
46
50
|
/** Milliseconds since the last change; Infinity once settled or when motion is off. */
|
|
@@ -52,6 +56,11 @@ export type MotionFrame = {
|
|
|
52
56
|
readonly warn: number;
|
|
53
57
|
/** Milliseconds into the completion moment while it plays, otherwise undefined. */
|
|
54
58
|
readonly celebrate?: number;
|
|
59
|
+
readonly celebrateFrom?: number;
|
|
60
|
+
/** While a stage change plays: the stage index the map moved forward from. */
|
|
61
|
+
readonly stageFrom?: number;
|
|
62
|
+
/** The clock this frame was sampled at, for the live step's elapsed time. */
|
|
63
|
+
readonly now?: number;
|
|
55
64
|
readonly moving: boolean;
|
|
56
65
|
};
|
|
57
66
|
export declare const idleMotion: MotionState;
|
|
@@ -63,4 +72,4 @@ export declare function rowEntryStart(state: Pick<MotionState, "entering" | "chi
|
|
|
63
72
|
*/
|
|
64
73
|
export declare function planMotion(prev: MissionView | undefined, next: MissionView, now: number, state: MotionState, enabled?: boolean): MotionState;
|
|
65
74
|
export declare function sampleMotion(state: MotionState, now: number): MotionFrame;
|
|
66
|
-
export declare function restingFrameFor(view: MissionView): MotionFrame;
|
|
75
|
+
export declare function restingFrameFor(view: MissionView, now?: number): MotionFrame;
|