@litfamily/litopencode 1.0.36 → 1.0.37
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 +6 -0
- package/README-Ko-KR.md +6 -6
- package/README.md +6 -6
- package/dist/tui/mission-skills.js +3 -1
- package/dist/tui/mission-source-v2.d.ts +23 -0
- package/dist/tui/mission-source-v2.js +29 -4
- package/dist/tui/mission-source.d.ts +1 -0
- package/dist/tui/mission-source.js +3 -2
- package/dist/v2/tui-notices.d.ts +7 -0
- package/dist/v2/tui-notices.js +45 -0
- package/dist/v2/tui.d.ts +37 -1
- package/dist/v2/tui.js +309 -31
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/reference-Ko-KR.md +13 -5
- package/docs/reference.md +17 -7
- package/package.json +1 -1
- package/skills/lit-typographic-motion/engine/cover/render.mjs +1 -1
- package/skills/lit-typographic-motion/engine/stage/capture.mjs +3 -2
- package/skills/lit-typographic-motion/engine/stage/gate.mjs +1 -0
- package/skills/lit-typographic-motion/engine/stage/render.mjs +1 -0
- package/skills/lit-typographic-motion/engine/stage/scan.mjs +109 -5
- package/skills/lit-typographic-motion/engine/stage/stage-kit.js +39 -1
- package/skills/lit-typographic-motion/references/cover.md +2 -1
- package/skills/lit-typographic-motion/references/runtime.md +1 -1
- package/skills/lit-typographic-motion/references/stage.md +48 -2
- package/skills/managed-skill-manifest.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ are tracked separately from this product history.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 1.0.37 - 2026-11-01
|
|
9
|
+
|
|
10
|
+
- `lit-typographic-motion` films can now carry real keyed footage, such as a reaction clip or a spinning-cat loop shot on a green screen. Key it outside the renderer into 8-bit RGBA PNG frames, put them in `stage/clips/<folder>/` and list them in `stage/clips/clips.json` (`id`, `dir`, `count`, `fps`). The page loads `/lit/clips.js`, registers a clip once with `LitStage.clip({ parent, id, box })` and shows it from `render(t)`; the frame is picked from film time, so a cue is plain arithmetic and the determinism replay holds. A declared clip is checked against its bytes (12 clips, 360 frames each and 1500 in all, 40 MB each and 128 MB in all, 720 px on the long side, up to 30 fps, real transparency) and is exempt from the flipbook rule; any frame sequence you did not declare is still refused with exit 17. A cover takes no clips. The flash audit and the text checks run on the final pixels, and the report lists each clip. `LitStage.clip.circle`, `wipe` and `iris` keep working.
|
|
11
|
+
- On OpenCode 2 the lit HUD is as complete as on OpenCode 1. The status card (discipline, workflow map, bar, live step, skill chips, lanes in parallel, context use and branch) now sits under every session, the stage chip appears beside the ignition line under the prompt, and the home screen shows the last goal. Until now only the ignition line and the sidebar panel were drawn there. The card reads OpenCode 2's own tool names, so a `skill` call (`id`) lights its chip and `subagent` calls show as lanes.
|
|
12
|
+
- OpenCode 2 also gets pop-ups for the workflow stage moving, a discipline switching and a lane starting or finishing, next to the plan, goal, blocker and completion pop-ups it already had. The restart notice, the `lit adhd` suggestion and the opt-in finish alert (`notify` in the plugin options) work there too.
|
|
13
|
+
|
|
8
14
|
## 1.0.36 - 2026-10-31
|
|
9
15
|
|
|
10
16
|
- On OpenCode 2.2 and later, `lit loop`, `litwork` and `/start-work` set OpenCode's native `/goal` for you. The goal carries your task plus what done means here (tests pass, a real-surface check ran, temporary files are cleaned up, the ledger is closed), and each change prints one `LitOpenCode goal:` line. LitOpenCode manages only goals it created: it resumes one that a server restart paused, clears one after OpenCode's verdict and the ledger both say done, and reports a goal you made as left alone without touching it. When OpenCode says the goal is met but the ledger is open, you get a conflict line and no done. Planning, research, review and bare `lit` never set a goal. OpenCode 1 and older OpenCode 2 builds have no `/goal`, so the ledger carries the work and `native-goal-unavailable` is recorded once.
|
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.37/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/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.37/docs/assets/readme/badge-version.svg" alt="1.0.37" /></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.37/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/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.37/docs/reference-Ko-KR.md) · [English reference](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/reference.md)
|
|
61
|
+
- [마이그레이션 안내](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/CHANGELOG.md) · [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/privacy.md)
|
|
62
|
+
- [LitFamily 허브](https://github.com/wjgoarxiv/litfamily) · [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/LICENSE) · [상표 정책](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/TRADEMARK.md) · [ASCII 글꼴 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/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.37/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: seven 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.37/docs/assets/readme/badge-version.svg" alt="1.0.37" /></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.37/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/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.37/docs/reference.md)
|
|
61
|
+
- [Migration notes](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/migration.md) · [Changelog](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/CHANGELOG.md) · [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/privacy.md)
|
|
62
|
+
- [LitFamily hub](https://github.com/wjgoarxiv/litfamily) · [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/LICENSE) · [Trademark policy](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/TRADEMARK.md) · [ASCII font license](https://cdn.jsdelivr.net/npm/@litfamily/litopencode@1.0.37/docs/assets/readme/JetBrainsMono-OFL.txt)
|
|
63
63
|
|
|
64
64
|
<details>
|
|
65
65
|
<summary>Copy ASCII logo</summary>
|
|
@@ -25,7 +25,9 @@ export function readSkillUse(part, at) {
|
|
|
25
25
|
const input = isRecord(state.input) ? state.input : {};
|
|
26
26
|
const start = isRecord(state.time) && typeof state.time.start === "number" ? state.time.start : at;
|
|
27
27
|
if (part.tool === "skill") {
|
|
28
|
-
|
|
28
|
+
// OpenCode 1's skill tool takes `name`; OpenCode 2's takes `id`.
|
|
29
|
+
const skill = typeof input.name === "string" ? input.name : typeof input.id === "string" ? input.id : undefined;
|
|
30
|
+
return skill !== undefined && ownSkills.has(skill) ? { name: skill, at: start } : undefined;
|
|
29
31
|
}
|
|
30
32
|
const name = ownTools[part.tool];
|
|
31
33
|
if (name === undefined || input.action === "status")
|
|
@@ -15,16 +15,39 @@ type V2Message = {
|
|
|
15
15
|
readonly content?: ReadonlyArray<{
|
|
16
16
|
readonly type: string;
|
|
17
17
|
readonly name?: string;
|
|
18
|
+
readonly text?: string;
|
|
18
19
|
readonly state?: {
|
|
19
20
|
readonly status?: string;
|
|
21
|
+
readonly input?: unknown;
|
|
22
|
+
readonly metadata?: unknown;
|
|
20
23
|
};
|
|
21
24
|
readonly time?: {
|
|
22
25
|
readonly created?: number;
|
|
23
26
|
readonly ran?: number;
|
|
27
|
+
readonly completed?: number;
|
|
24
28
|
};
|
|
25
29
|
}>;
|
|
26
30
|
};
|
|
31
|
+
type V2Model = {
|
|
32
|
+
readonly providerID: string;
|
|
33
|
+
readonly modelID: string;
|
|
34
|
+
readonly limit?: {
|
|
35
|
+
readonly context?: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
27
38
|
export type MissionDataV2 = {
|
|
39
|
+
readonly location?: {
|
|
40
|
+
readonly vcs?: {
|
|
41
|
+
info(): {
|
|
42
|
+
readonly branch?: {
|
|
43
|
+
readonly current?: string;
|
|
44
|
+
};
|
|
45
|
+
} | undefined;
|
|
46
|
+
};
|
|
47
|
+
readonly model?: {
|
|
48
|
+
list(): ReadonlyArray<V2Model> | undefined;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
28
51
|
readonly session: {
|
|
29
52
|
status(sessionID: string): "idle" | "running";
|
|
30
53
|
readonly message: {
|
|
@@ -5,11 +5,23 @@ function isMessage(value) {
|
|
|
5
5
|
export function sessionMessagesV2(data, sessionID) {
|
|
6
6
|
return (data.session.message.list(sessionID) ?? []).filter(isMessage).filter((message) => message.type === "user" || message.type === "assistant");
|
|
7
7
|
}
|
|
8
|
+
/** The provider table the context figure reads: each model's context window, from the host's model list. */
|
|
9
|
+
function providersV2(data) {
|
|
10
|
+
const byProvider = new Map();
|
|
11
|
+
for (const model of data.location?.model?.list() ?? []) {
|
|
12
|
+
const models = byProvider.get(model.providerID) ?? {};
|
|
13
|
+
models[model.modelID] = { limit: { context: model.limit?.context } };
|
|
14
|
+
byProvider.set(model.providerID, models);
|
|
15
|
+
}
|
|
16
|
+
return [...byProvider].map(([id, models]) => ({ id, models }));
|
|
17
|
+
}
|
|
8
18
|
export function missionStateFromV2(data, directory) {
|
|
9
19
|
const index = new Map();
|
|
20
|
+
const branch = data.location?.vcs?.info()?.branch?.current?.trim();
|
|
10
21
|
return {
|
|
11
22
|
path: directory === undefined ? {} : { directory },
|
|
12
|
-
|
|
23
|
+
...(branch === undefined || branch === "" ? {} : { vcs: { branch } }),
|
|
24
|
+
provider: providersV2(data),
|
|
13
25
|
session: {
|
|
14
26
|
messages(sessionID) {
|
|
15
27
|
return sessionMessagesV2(data, sessionID).map((message) => {
|
|
@@ -35,9 +47,22 @@ export function missionStateFromV2(data, directory) {
|
|
|
35
47
|
return [];
|
|
36
48
|
if (message.type === "user")
|
|
37
49
|
return message.metadata === undefined ? [] : [{ metadata: message.metadata }];
|
|
38
|
-
return (message.content ?? [])
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
return (message.content ?? []).flatMap((item) => {
|
|
51
|
+
if (item.type === "text" && typeof item.text === "string")
|
|
52
|
+
return [{ type: "text", text: item.text }];
|
|
53
|
+
if (item.type !== "tool" || typeof item.name !== "string")
|
|
54
|
+
return [];
|
|
55
|
+
return [{
|
|
56
|
+
type: "tool",
|
|
57
|
+
tool: item.name,
|
|
58
|
+
state: {
|
|
59
|
+
status: item.state?.status,
|
|
60
|
+
input: item.state?.input,
|
|
61
|
+
metadata: item.state?.metadata,
|
|
62
|
+
time: { start: item.time?.ran ?? item.time?.created, ...(item.time?.completed === undefined ? {} : { end: item.time.completed }) }
|
|
63
|
+
}
|
|
64
|
+
}];
|
|
65
|
+
});
|
|
41
66
|
}
|
|
42
67
|
};
|
|
43
68
|
}
|
|
@@ -52,6 +52,7 @@ export type MissionStateSource = {
|
|
|
52
52
|
readonly tool?: unknown;
|
|
53
53
|
readonly state?: unknown;
|
|
54
54
|
readonly metadata?: unknown;
|
|
55
|
+
readonly text?: unknown;
|
|
55
56
|
}>;
|
|
56
57
|
};
|
|
57
58
|
/** The same root the server plugin uses: the worktree unless it is the filesystem root, else the directory. */
|
|
@@ -330,7 +330,8 @@ function taskSources(state, sessionID, now) {
|
|
|
330
330
|
const assistants = state.session.messages(sessionID).filter((message) => message.role === "assistant").slice(-recentAssistantMessages);
|
|
331
331
|
for (const message of assistants) {
|
|
332
332
|
for (const part of state.part(message.id)) {
|
|
333
|
-
|
|
333
|
+
// OpenCode 1 delegates through `task`, OpenCode 2 through `subagent`.
|
|
334
|
+
if (part.type !== "tool" || (part.tool !== "task" && part.tool !== "subagent") || !isRecord(part.state))
|
|
334
335
|
continue;
|
|
335
336
|
const toolState = part.state;
|
|
336
337
|
const status = toolState.status;
|
|
@@ -348,7 +349,7 @@ function taskSources(state, sessionID, now) {
|
|
|
348
349
|
children.add(child);
|
|
349
350
|
const todos = child === undefined ? [] : state.session.todo(child).filter((todo) => todo.status !== "cancelled");
|
|
350
351
|
const done = todos.filter((todo) => todo.status === "completed").length;
|
|
351
|
-
const name = typeof input.description === "string" && input.description.trim() !== "" ? input.description.trim() : typeof input.subagent_type === "string" ? input.subagent_type : "subagent";
|
|
352
|
+
const name = typeof input.description === "string" && input.description.trim() !== "" ? input.description.trim() : typeof input.subagent_type === "string" ? input.subagent_type : typeof input.agent === "string" ? input.agent : "subagent";
|
|
352
353
|
// A running task last moved when its own session did; its start time alone would read a long, busy run as idle.
|
|
353
354
|
const moved = child === undefined ? undefined : lastActivity(state, child);
|
|
354
355
|
sources.push({
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type MissionNotice, type MissionView } from "../tui/mission-model.ts";
|
|
2
|
+
import type { ThreadRow } from "../tui/mission-threads.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Stage, discipline and lane changes between two views. A first view never toasts its history. A lane
|
|
5
|
+
* counts as started when its key is new and working, and as finished when a working lane is gone or done.
|
|
6
|
+
*/
|
|
7
|
+
export declare function v2StageNotices(prev: MissionView | undefined, next: MissionView, before: readonly ThreadRow[], after: readonly ThreadRow[]): readonly MissionNotice[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// The toasts OpenCode 2 raises beyond the shared gate events (plan saved, goal set, criterion passed,
|
|
2
|
+
// blockers, complete): the workflow stage moving, a discipline switching, a lane starting or finishing.
|
|
3
|
+
// They are pure: the TUI hands in the previous and next view and thread rows and gets notices back,
|
|
4
|
+
// which leadingNotice folds into one toast with a `+N more` tail.
|
|
5
|
+
import { stepName } from "../tui/mission-model.js";
|
|
6
|
+
const maxNamed = 1;
|
|
7
|
+
function runningStage(view) {
|
|
8
|
+
const step = view.steps.find((candidate) => candidate.state === "now");
|
|
9
|
+
if (step === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const count = view.progress !== undefined && view.progress.total > 0 ? ` ${view.progress.done}/${view.progress.total}` : "";
|
|
12
|
+
return { id: `${view.family}:${step.id}`, label: `${stepName(step.id, view)}${count}` };
|
|
13
|
+
}
|
|
14
|
+
function laneNotice(rows, verb) {
|
|
15
|
+
if (rows.length === 0)
|
|
16
|
+
return undefined;
|
|
17
|
+
const message = rows.length > maxNamed ? `${rows.length} lanes ${verb}` : `lane ${verb} · ${rows[0].name}`;
|
|
18
|
+
return { variant: "info", title: "🔥 Lane", message };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Stage, discipline and lane changes between two views. A first view never toasts its history. A lane
|
|
22
|
+
* counts as started when its key is new and working, and as finished when a working lane is gone or done.
|
|
23
|
+
*/
|
|
24
|
+
export function v2StageNotices(prev, next, before, after) {
|
|
25
|
+
if (prev === undefined || !next.active)
|
|
26
|
+
return [];
|
|
27
|
+
const notices = [];
|
|
28
|
+
if (prev.active && prev.discipline !== next.discipline && next.discipline !== "") {
|
|
29
|
+
notices.push({ variant: "info", title: `🔥 ${prev.discipline} → ${next.discipline}`, message: "Discipline switched." });
|
|
30
|
+
}
|
|
31
|
+
else if (prev.active && !next.complete) {
|
|
32
|
+
const was = runningStage(prev);
|
|
33
|
+
const now = runningStage(next);
|
|
34
|
+
if (was !== undefined && now !== undefined && was.id !== now.id)
|
|
35
|
+
notices.push({ variant: "info", title: "🔥 Stage", message: now.label });
|
|
36
|
+
}
|
|
37
|
+
const priorKeys = new Set(before.map((row) => row.key));
|
|
38
|
+
const nextByKey = new Map(after.map((row) => [row.key, row]));
|
|
39
|
+
const started = after.filter((row) => row.status === "working" && !priorKeys.has(row.key));
|
|
40
|
+
const finished = before.filter((row) => row.status === "working" && (nextByKey.get(row.key) === undefined || nextByKey.get(row.key)?.status === "done"));
|
|
41
|
+
for (const notice of [laneNotice(started, "started"), laneNotice(finished, "finished")])
|
|
42
|
+
if (notice !== undefined)
|
|
43
|
+
notices.push(notice);
|
|
44
|
+
return notices;
|
|
45
|
+
}
|
package/dist/v2/tui.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export type TuiRuntime = {
|
|
|
5
5
|
readonly createSignal: <T>(value: T) => readonly [() => T, (value: T) => T];
|
|
6
6
|
readonly createEffect: (fn: () => void) => void;
|
|
7
7
|
readonly createRoot: <T>(fn: (dispose: () => void) => T) => T;
|
|
8
|
+
/** The host's terminal size, reactive; a stand-in runtime may leave it out. */
|
|
9
|
+
readonly useTerminalDimensions?: () => () => {
|
|
10
|
+
readonly width: number;
|
|
11
|
+
readonly height: number;
|
|
12
|
+
};
|
|
8
13
|
};
|
|
9
14
|
type PromptFooterInput = {
|
|
10
15
|
readonly sessionID?: string;
|
|
@@ -19,15 +24,38 @@ export type SlotClaimLike = {
|
|
|
19
24
|
} | {
|
|
20
25
|
readonly append: "prompt.footer.status";
|
|
21
26
|
readonly render: (input: PromptFooterInput) => unknown;
|
|
27
|
+
} | {
|
|
28
|
+
readonly append: "app";
|
|
29
|
+
readonly render: (input: Record<string, never>) => unknown;
|
|
30
|
+
} | {
|
|
31
|
+
readonly append: "home.footer";
|
|
32
|
+
readonly render: (input: Record<string, never>) => unknown;
|
|
22
33
|
};
|
|
23
34
|
export type TuiContextLike = {
|
|
35
|
+
readonly options?: unknown;
|
|
24
36
|
readonly location: {
|
|
25
37
|
readonly directory: string;
|
|
26
38
|
} | undefined;
|
|
27
39
|
readonly theme: unknown;
|
|
28
40
|
readonly themeMode: "dark" | "light";
|
|
41
|
+
readonly attention?: {
|
|
42
|
+
readonly notify: (options: {
|
|
43
|
+
readonly title?: string;
|
|
44
|
+
readonly message: string;
|
|
45
|
+
readonly notification?: boolean;
|
|
46
|
+
readonly sound?: boolean | {
|
|
47
|
+
readonly name?: "done";
|
|
48
|
+
};
|
|
49
|
+
}) => Promise<unknown>;
|
|
50
|
+
};
|
|
29
51
|
readonly ui: {
|
|
30
52
|
readonly slot: (claim: SlotClaimLike) => () => void;
|
|
53
|
+
readonly router?: {
|
|
54
|
+
current(): {
|
|
55
|
+
readonly type: string;
|
|
56
|
+
readonly sessionID?: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
31
59
|
readonly toast: {
|
|
32
60
|
readonly show: (options: {
|
|
33
61
|
readonly title?: string;
|
|
@@ -49,11 +77,19 @@ export type TuiContextLike = {
|
|
|
49
77
|
readonly type: string;
|
|
50
78
|
};
|
|
51
79
|
}) => void) => () => void;
|
|
52
|
-
readonly location: {
|
|
80
|
+
readonly location: MissionDataV2["location"] & {
|
|
53
81
|
default(): {
|
|
54
82
|
readonly directory: string;
|
|
55
83
|
};
|
|
56
84
|
};
|
|
85
|
+
readonly session: MissionDataV2["session"] & {
|
|
86
|
+
get?(sessionID: string): {
|
|
87
|
+
readonly parentID?: string;
|
|
88
|
+
readonly time?: {
|
|
89
|
+
readonly created?: number;
|
|
90
|
+
};
|
|
91
|
+
} | undefined;
|
|
92
|
+
};
|
|
57
93
|
};
|
|
58
94
|
};
|
|
59
95
|
/** OpenCode 2 theme tokens under the names the mission palette reads. */
|