@litfamily/litopencode 1.0.28 → 1.0.30
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 +28 -0
- package/README-Ko-KR.md +7 -7
- package/README.md +7 -7
- package/dist/activation-primary-prompts.js +15 -6
- package/dist/activation-prompt-utils.d.ts +2 -2
- package/dist/activation-prompt-utils.js +9 -3
- package/dist/activation-reentry.d.ts +29 -0
- package/dist/activation-reentry.js +71 -0
- package/dist/activation-routing.d.ts +1 -1
- package/dist/activation-routing.js +40 -7
- package/dist/activation-workflow-prompts.d.ts +2 -0
- package/dist/activation-workflow-prompts.js +32 -7
- package/dist/activation.d.ts +6 -1
- package/dist/activation.js +34 -7
- package/dist/cli/args.js +14 -0
- package/dist/cli/auto-update.js +2 -4
- package/dist/cli/command-plan.d.ts +40 -0
- package/dist/cli/command-plan.js +62 -0
- package/dist/cli/doctor.d.ts +4 -1
- package/dist/cli/doctor.js +65 -4
- package/dist/cli/host-detect.d.ts +58 -0
- package/dist/cli/host-detect.js +199 -0
- package/dist/cli/install-report.js +3 -1
- package/dist/cli/install.d.ts +9 -1
- package/dist/cli/install.js +134 -31
- package/dist/cli/managed-skill-assets.d.ts +1 -1
- package/dist/cli/managed-skill-assets.js +5 -3
- package/dist/cli/office-dependencies.d.ts +2 -0
- package/dist/cli/office-dependencies.js +7 -3
- package/dist/cli/scientific-visualization-dependencies.js +1 -1
- package/dist/cli/tui-config.d.ts +27 -2
- package/dist/cli/tui-config.js +77 -2
- package/dist/cli/types.d.ts +17 -0
- package/dist/cli.js +9 -6
- package/dist/commands.d.ts +10 -1
- package/dist/commands.js +17 -3
- package/dist/deliverable-hedge-guard.js +21 -9
- package/dist/features.d.ts +27 -1
- package/dist/features.js +40 -8
- package/dist/host/server.d.ts +7 -0
- package/dist/host/server.js +12 -0
- package/dist/host/tui.d.ts +7 -0
- package/dist/host/tui.js +12 -0
- package/dist/server.js +11 -1
- package/dist/skill-renames.d.ts +1 -1
- package/dist/skills.d.ts +9 -2
- package/dist/skills.js +22 -8
- package/dist/tui/mission-source-v2.d.ts +51 -0
- package/dist/tui/mission-source-v2.js +52 -0
- package/dist/v2/agents.d.ts +105 -0
- package/dist/v2/agents.js +185 -0
- package/dist/v2/commands.d.ts +44 -0
- package/dist/v2/commands.js +65 -0
- package/dist/v2/context.d.ts +67 -0
- package/dist/v2/context.js +17 -0
- package/dist/v2/events.d.ts +55 -0
- package/dist/v2/events.js +193 -0
- package/dist/v2/hooks.d.ts +39 -0
- package/dist/v2/hooks.js +61 -0
- package/dist/v2/server.d.ts +2 -0
- package/dist/v2/server.js +68 -0
- package/dist/v2/session-hooks.d.ts +64 -0
- package/dist/v2/session-hooks.js +230 -0
- package/dist/v2/tools.d.ts +26 -0
- package/dist/v2/tools.js +49 -0
- package/dist/v2/tui.d.ts +63 -0
- package/dist/v2/tui.js +259 -0
- package/docs/assets/cover-motion-still.webp +0 -0
- package/docs/assets/cover-motion.webp +0 -0
- package/docs/assets/cover.webp +0 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/assets/readme/litfamily-machines.png +0 -0
- package/docs/migration.md +4 -3
- package/docs/reference-Ko-KR.md +109 -7
- package/docs/reference.md +122 -9
- package/package.json +10 -5
- package/rules/bundled-rules/lit-humanizer.md +2 -0
- package/skills/deep-interview/SKILL.md +1 -1
- package/skills/lit-brainstorm/SKILL.md +299 -0
- package/skills/lit-brainstorm/references/handoff-briefs.md +72 -0
- package/skills/lit-brainstorm/references/lenses.md +106 -0
- package/skills/lit-brainstorm/references/scoring-and-convergence.md +93 -0
- package/skills/lit-brainstorm/references/session-notes-and-lanes.md +122 -0
- package/skills/lit-docx/SKILL.md +1 -1
- package/skills/lit-docx/run.py +42 -4
- package/skills/lit-docx/scripts/convert_md_to_pdf.py +38 -0
- package/skills/lit-docx/templates/latex/acs/template.tex +2 -0
- package/skills/lit-docx/templates/latex/elsevier/template.tex +2 -0
- package/skills/lit-docx/templates/latex/ieee/template.tex +2 -0
- package/skills/lit-docx/templates/latex/korean-generic/template.tex +2 -0
- package/skills/lit-docx/templates/latex/nature/template.tex +2 -0
- package/skills/lit-humanizer/SKILL.md +4 -0
- package/skills/lit-humanizer/scripts/core.mjs +81 -1
- package/skills/lit-humanizer/scripts/detect.mjs +4 -3
- package/skills/lit-humanizer/scripts/guard-scan.mjs +12 -6
- package/skills/lit-pptx/SKILL.md +1 -1
- package/skills/lit-pptx/command.mjs +94 -0
- package/skills/lit-pptx/run.mjs +16 -6
- package/skills/lit-pptx/scripts/compile-deck.js +2 -2
- package/skills/lit-typographic-motion/SKILL.md +5 -2
- package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +3 -0
- package/skills/lit-typographic-motion/engine/browser.mjs +3 -9
- package/skills/lit-typographic-motion/engine/command.mjs +94 -0
- package/skills/lit-typographic-motion/engine/constants.mjs +29 -1
- package/skills/lit-typographic-motion/engine/cover/assets.mjs +91 -0
- package/skills/lit-typographic-motion/engine/cover/capture.mjs +173 -0
- package/skills/lit-typographic-motion/engine/cover/decode.mjs +173 -0
- package/skills/lit-typographic-motion/engine/cover/frame-worker.mjs +19 -0
- package/skills/lit-typographic-motion/engine/cover/gate.mjs +261 -0
- package/skills/lit-typographic-motion/engine/cover/image.mjs +138 -0
- package/skills/lit-typographic-motion/engine/cover/render.mjs +323 -0
- package/skills/lit-typographic-motion/engine/cover/sheet.mjs +65 -0
- package/skills/lit-typographic-motion/engine/cover/treatment.mjs +110 -0
- package/skills/lit-typographic-motion/engine/encode.mjs +1 -1
- package/skills/lit-typographic-motion/engine/look.mjs +113 -10
- package/skills/lit-typographic-motion/engine/sound.mjs +1 -1
- package/skills/lit-typographic-motion/engine/stage/capture.mjs +26 -18
- package/skills/lit-typographic-motion/engine/stage/cover-init.js +38 -0
- package/skills/lit-typographic-motion/engine/stage/loop-kit.js +102 -0
- package/skills/lit-typographic-motion/engine/stage/render.mjs +4 -4
- package/skills/lit-typographic-motion/engine/treatment.mjs +1 -0
- package/skills/lit-typographic-motion/fonts/JetBrainsMono-OFL.txt +93 -0
- package/skills/lit-typographic-motion/fonts/JetBrainsMono-wght-latin.woff2 +0 -0
- package/skills/lit-typographic-motion/fonts/pins.json +3 -1
- package/skills/lit-typographic-motion/gate.mjs +34 -1
- package/skills/lit-typographic-motion/probe.mjs +8 -3
- package/skills/lit-typographic-motion/recipes/negatives/card-scenes/expected.json +16 -0
- package/skills/lit-typographic-motion/recipes/negatives/card-scenes/index.html +35 -0
- package/skills/lit-typographic-motion/recipes/negatives/reset-sweep/expected.json +16 -0
- package/skills/lit-typographic-motion/recipes/negatives/reset-sweep/index.html +29 -0
- package/skills/lit-typographic-motion/recipes/negatives/still-glow/expected.json +17 -0
- package/skills/lit-typographic-motion/recipes/negatives/still-glow/index.html +29 -0
- package/skills/lit-typographic-motion/recipes/negatives/title-slam/expected.json +17 -0
- package/skills/lit-typographic-motion/recipes/negatives/title-slam/index.html +35 -0
- package/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +62 -0
- package/skills/lit-typographic-motion/recipes/orbit-loop/index.html +100 -0
- package/skills/lit-typographic-motion/recipes/painted-light/expected.json +95 -0
- package/skills/lit-typographic-motion/recipes/painted-light/index.html +232 -0
- package/skills/lit-typographic-motion/recipes/weight-tide/expected.json +62 -0
- package/skills/lit-typographic-motion/recipes/weight-tide/index.html +81 -0
- package/skills/lit-typographic-motion/references/cover.md +423 -0
- package/skills/lit-typographic-motion/references/craft-loop.md +2 -0
- package/skills/lit-typographic-motion/references/runtime.md +17 -2
- package/skills/lit-typographic-motion/references/stage.md +3 -0
- package/skills/lit-typographic-motion/references/treatment.md +4 -0
- package/skills/lit-typographic-motion/render.mjs +30 -6
- package/skills/lit-typographic-motion/runtime.mjs +12 -13
- package/skills/{durable-litgoal → litgoal}/SKILL.md +31 -12
- package/skills/managed-skill-manifest.json +175 -31
- package/skills/review-work/SKILL.md +27 -18
- package/skills/skill-rename-aliases.json +2 -1
- package/tools/check-pack-payload.mjs +4 -1
- package/tools/payload-substance-allowlist.json +1 -1
- package/tools/payload-substance-parity.json +6 -4
- package/tools/qa-cover-recipes.mjs +167 -0
- package/tools/version-manifests.json +6 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
export type ToolCallContextLike = {
|
|
3
|
+
readonly sessionID: string;
|
|
4
|
+
readonly agent: string;
|
|
5
|
+
readonly messageID: string;
|
|
6
|
+
readonly signal: AbortSignal;
|
|
7
|
+
};
|
|
8
|
+
export type ToolResultLike = {
|
|
9
|
+
readonly content?: string;
|
|
10
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
export type ToolInfoLike = {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly description: string;
|
|
15
|
+
readonly input: Record<string, unknown>;
|
|
16
|
+
readonly options: {
|
|
17
|
+
readonly codemode: false;
|
|
18
|
+
};
|
|
19
|
+
readonly execute: (input: unknown, context: ToolCallContextLike) => Promise<ToolResultLike>;
|
|
20
|
+
};
|
|
21
|
+
export type ToolEditorLike = {
|
|
22
|
+
add(tool: ToolInfoLike): void;
|
|
23
|
+
};
|
|
24
|
+
export declare function adaptTool(name: string, definition: ToolDefinition, projectRoot: string): ToolInfoLike;
|
|
25
|
+
export declare function litToolsForProject(projectRoot: string): readonly ToolInfoLike[];
|
|
26
|
+
export declare function addLitTools(editor: ToolEditorLike, tools: readonly ToolInfoLike[]): void;
|
package/dist/v2/tools.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// LitOpenCode's five tools on OpenCode 2. The tool bodies are the OpenCode 1 ones; this adapter gives
|
|
2
|
+
// each a closed JSON Schema input, validates the call before it runs, hands the body an OpenCode 1
|
|
3
|
+
// style context whose directory is the plugin's project, and returns `{ content, metadata }`. Each tool
|
|
4
|
+
// sits on the model's own tool list (`codemode: false`), as it did on OpenCode 1, so lit-plan, which has
|
|
5
|
+
// no Code Mode `execute`, still has them.
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { litOpenCodeTools } from "../tools.js";
|
|
8
|
+
function resultToV2(result) {
|
|
9
|
+
if (typeof result === "string")
|
|
10
|
+
return { content: result };
|
|
11
|
+
return {
|
|
12
|
+
content: result.output,
|
|
13
|
+
metadata: { ...(result.metadata ?? {}), ...(result.title === undefined ? {} : { title: result.title }) }
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function adaptTool(name, definition, projectRoot) {
|
|
17
|
+
const schema = z.strictObject(definition.args);
|
|
18
|
+
const { $schema: _dialect, ...input } = z.toJSONSchema(schema, { io: "input" });
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
description: definition.description,
|
|
22
|
+
input,
|
|
23
|
+
options: { codemode: false },
|
|
24
|
+
async execute(raw, context) {
|
|
25
|
+
const args = schema.parse(raw ?? {});
|
|
26
|
+
const v1Context = {
|
|
27
|
+
sessionID: context.sessionID,
|
|
28
|
+
messageID: context.messageID,
|
|
29
|
+
agent: context.agent,
|
|
30
|
+
directory: projectRoot,
|
|
31
|
+
worktree: projectRoot,
|
|
32
|
+
abort: context.signal,
|
|
33
|
+
metadata: () => undefined,
|
|
34
|
+
// No tool body asks today; a future one must not be approved without the user seeing a prompt.
|
|
35
|
+
ask: async () => {
|
|
36
|
+
throw new Error(`${name}: asking for permission from inside a tool is not supported on OpenCode 2`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return resultToV2(await definition.execute(args, v1Context));
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function litToolsForProject(projectRoot) {
|
|
44
|
+
return Object.entries(litOpenCodeTools).map(([name, definition]) => adaptTool(name, definition, projectRoot));
|
|
45
|
+
}
|
|
46
|
+
export function addLitTools(editor, tools) {
|
|
47
|
+
for (const tool of tools)
|
|
48
|
+
editor.add(tool);
|
|
49
|
+
}
|
package/dist/v2/tui.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type MissionDataV2 } from "../tui/mission-source-v2.ts";
|
|
2
|
+
import { type MissionPalette } from "../tui/mission-text.ts";
|
|
3
|
+
export type TuiRuntime = {
|
|
4
|
+
readonly jsx: (type: string, props?: Record<string, unknown>) => unknown;
|
|
5
|
+
readonly createSignal: <T>(value: T) => readonly [() => T, (value: T) => T];
|
|
6
|
+
readonly createEffect: (fn: () => void) => void;
|
|
7
|
+
readonly createRoot: <T>(fn: (dispose: () => void) => T) => T;
|
|
8
|
+
};
|
|
9
|
+
type PromptFooterInput = {
|
|
10
|
+
readonly sessionID?: string;
|
|
11
|
+
readonly mode: "normal" | "shell";
|
|
12
|
+
readonly showDetails: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type SlotClaimLike = {
|
|
15
|
+
readonly append: "sidebar.content";
|
|
16
|
+
readonly render: (input: {
|
|
17
|
+
readonly sessionID: string;
|
|
18
|
+
}) => unknown;
|
|
19
|
+
} | {
|
|
20
|
+
readonly append: "prompt.footer.status";
|
|
21
|
+
readonly render: (input: PromptFooterInput) => unknown;
|
|
22
|
+
};
|
|
23
|
+
export type TuiContextLike = {
|
|
24
|
+
readonly location: {
|
|
25
|
+
readonly directory: string;
|
|
26
|
+
} | undefined;
|
|
27
|
+
readonly theme: unknown;
|
|
28
|
+
readonly themeMode: "dark" | "light";
|
|
29
|
+
readonly ui: {
|
|
30
|
+
readonly slot: (claim: SlotClaimLike) => () => void;
|
|
31
|
+
readonly toast: {
|
|
32
|
+
readonly show: (options: {
|
|
33
|
+
readonly title?: string;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
readonly variant?: "info" | "success" | "warning" | "error";
|
|
36
|
+
readonly duration?: number;
|
|
37
|
+
readonly sessionID?: string;
|
|
38
|
+
}) => void;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
readonly storage: {
|
|
42
|
+
readonly store: <Value extends object>(key: string, options: {
|
|
43
|
+
readonly initial: Value;
|
|
44
|
+
}) => readonly [Value, unknown];
|
|
45
|
+
};
|
|
46
|
+
readonly data: MissionDataV2 & {
|
|
47
|
+
readonly listen: (handler: (event: {
|
|
48
|
+
readonly details: {
|
|
49
|
+
readonly type: string;
|
|
50
|
+
};
|
|
51
|
+
}) => void) => () => void;
|
|
52
|
+
readonly location: {
|
|
53
|
+
default(): {
|
|
54
|
+
readonly directory: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** OpenCode 2 theme tokens under the names the mission palette reads. */
|
|
60
|
+
export declare function paletteFromV2Theme(theme: unknown, mode: "dark" | "light"): MissionPalette;
|
|
61
|
+
export declare function ignitionLine(name: string): string;
|
|
62
|
+
export declare function setupLitOpenCodeTui(context: TuiContextLike, runtime?: TuiRuntime): Promise<() => Promise<void>>;
|
|
63
|
+
export {};
|
package/dist/v2/tui.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// LitOpenCode's OpenCode 2 TUI plugin: the mission panel in the session sidebar, the ignition line under
|
|
2
|
+
// the prompt (`🔥 LIT IGNITED · <discipline>`, the OpenCode 2 home of the OpenCode 1 completion mark),
|
|
3
|
+
// and toasts. OpenCode 2 loads it on its own for an active server plugin. The server cannot raise toasts
|
|
4
|
+
// on OpenCode 2, so the ignition toast is inferred here from the lit mark the server writes into the user
|
|
5
|
+
// message metadata. It only reads state. OpenTUI and Solid come from the host at run time; tests pass a
|
|
6
|
+
// stand-in runtime.
|
|
7
|
+
import { activationDiscipline } from "../activation-probe.js";
|
|
8
|
+
import { micro, supportsMarkGlyphs } from "../lit-mark.js";
|
|
9
|
+
import { buildMissionView, leadingNotice, missionNotices } from "../tui/mission-model.js";
|
|
10
|
+
import { idleMotion, motionTiming, planMotion, sampleMotion } from "../tui/mission-motion.js";
|
|
11
|
+
import { latestLitMarkV2, missionStateFromV2 } from "../tui/mission-source-v2.js";
|
|
12
|
+
import { createLedgerReader, createWorkReader, missionProjectRoot, readLatestResearchRun, readMissionInput } from "../tui/mission-source.js";
|
|
13
|
+
import { missionPanel, paletteFromTheme, panelWidth } from "../tui/mission-text.js";
|
|
14
|
+
const refreshDelayMs = 80;
|
|
15
|
+
const ledgerPollMs = 2000;
|
|
16
|
+
const toastMs = 4000;
|
|
17
|
+
const ignitionToastMs = 6000;
|
|
18
|
+
const trackedSessionLimit = 4;
|
|
19
|
+
// A lit mark older than the moment a session is first drawn is history being loaded, not a new turn.
|
|
20
|
+
const historySlackMs = 2000;
|
|
21
|
+
async function hostRuntime() {
|
|
22
|
+
const [jsxRuntime, solid] = await Promise.all([import("@opentui/solid/jsx-runtime"), import("solid-js")]);
|
|
23
|
+
return { jsx: jsxRuntime.jsx, createSignal: solid.createSignal, createEffect: solid.createEffect, createRoot: solid.createRoot };
|
|
24
|
+
}
|
|
25
|
+
/** OpenCode 2 theme tokens under the names the mission palette reads. */
|
|
26
|
+
export function paletteFromV2Theme(theme, mode) {
|
|
27
|
+
const tokens = (typeof theme === "object" && theme !== null ? theme : {});
|
|
28
|
+
return paletteFromTheme({
|
|
29
|
+
text: tokens.text?.base,
|
|
30
|
+
textMuted: tokens.text?.muted,
|
|
31
|
+
borderActive: tokens.border?.base,
|
|
32
|
+
borderSubtle: tokens.border?.base,
|
|
33
|
+
background: tokens.background?.base,
|
|
34
|
+
backgroundPanel: tokens.background?.raised?.base,
|
|
35
|
+
backgroundElement: tokens.background?.raised?.high
|
|
36
|
+
}, mode);
|
|
37
|
+
}
|
|
38
|
+
export function ignitionLine(name) {
|
|
39
|
+
return `🔥 LIT IGNITED · ${activationDiscipline(name)}`;
|
|
40
|
+
}
|
|
41
|
+
export async function setupLitOpenCodeTui(context, runtime) {
|
|
42
|
+
const ui = runtime ?? await hostRuntime();
|
|
43
|
+
const [settings] = context.storage.store("settings", { initial: { motion: true } });
|
|
44
|
+
const motionEnabled = settings.motion !== false;
|
|
45
|
+
const directory = context.location?.directory ?? context.data.location.default().directory;
|
|
46
|
+
const root = missionProjectRoot({ directory });
|
|
47
|
+
const readLedger = createLedgerReader();
|
|
48
|
+
const readWork = createWorkReader();
|
|
49
|
+
const sessions = new Map();
|
|
50
|
+
const ignitions = new Map();
|
|
51
|
+
const trackedAt = new Map();
|
|
52
|
+
const tracked = [];
|
|
53
|
+
// Each session's store watcher lives in its own root, so it outlasts the slot render that started it
|
|
54
|
+
// and ends only on eviction or cleanup. After cleanup nothing may arm a timer again.
|
|
55
|
+
const watchers = new Map();
|
|
56
|
+
let disposed = false;
|
|
57
|
+
const [revision, setRevision] = ui.createSignal(0);
|
|
58
|
+
const [clock, setClock] = ui.createSignal(Date.now());
|
|
59
|
+
let frameTimer;
|
|
60
|
+
let refreshTimer;
|
|
61
|
+
let pollTimer;
|
|
62
|
+
const palette = () => paletteFromV2Theme(context.theme, context.themeMode);
|
|
63
|
+
function span(segment) {
|
|
64
|
+
return ui.jsx("span", {
|
|
65
|
+
style: { fg: segment.fg, ...(segment.bg === undefined ? {} : { bg: segment.bg }), ...(segment.bold === true ? { bold: true } : {}) },
|
|
66
|
+
children: segment.text
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function line(segments) {
|
|
70
|
+
return ui.jsx("text", { wrapMode: "none", children: segments.length === 0 ? " " : segments.map(span) });
|
|
71
|
+
}
|
|
72
|
+
function viewFor(sessionID, now) {
|
|
73
|
+
const source = missionStateFromV2(context.data, directory);
|
|
74
|
+
const records = { work: readWork(root, sessionID), research: readLatestResearchRun(root) };
|
|
75
|
+
return buildMissionView(readMissionInput(source, sessionID, readLedger(root), now, {
|
|
76
|
+
...(records.work === undefined ? {} : { work: records.work }),
|
|
77
|
+
...(records.research === undefined ? {} : { research: records.research })
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
function anyMoving(now) {
|
|
81
|
+
for (const entry of sessions.values())
|
|
82
|
+
if (sampleMotion(entry.motion, now).moving)
|
|
83
|
+
return true;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function ensureFrames() {
|
|
87
|
+
if (disposed || frameTimer !== undefined || !anyMoving(Date.now()))
|
|
88
|
+
return;
|
|
89
|
+
frameTimer = setInterval(() => {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
setClock(now);
|
|
92
|
+
if (!anyMoving(now)) {
|
|
93
|
+
clearInterval(frameTimer);
|
|
94
|
+
frameTimer = undefined;
|
|
95
|
+
}
|
|
96
|
+
}, motionTiming.frameMs);
|
|
97
|
+
}
|
|
98
|
+
function ignitionToast(sessionID, name) {
|
|
99
|
+
const label = `${ignitionLine(name)} 🔥`;
|
|
100
|
+
const message = supportsMarkGlyphs() ? [...micro.map((row) => row.replace(/ +$/u, "")), label].join("\n") : label;
|
|
101
|
+
context.ui.toast.show({ title: "🔥 LIT IGNITED", message, variant: "warning", duration: ignitionToastMs, sessionID });
|
|
102
|
+
}
|
|
103
|
+
function refresh(sessionID) {
|
|
104
|
+
if (disposed)
|
|
105
|
+
return;
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const mark = latestLitMarkV2(context.data, sessionID);
|
|
108
|
+
if (ignitions.get(sessionID) !== mark?.messageID) {
|
|
109
|
+
ignitions.set(sessionID, mark?.messageID);
|
|
110
|
+
if (mark !== undefined && mark.at >= (trackedAt.get(sessionID) ?? 0) - historySlackMs)
|
|
111
|
+
ignitionToast(sessionID, mark.name);
|
|
112
|
+
}
|
|
113
|
+
const next = viewFor(sessionID, now);
|
|
114
|
+
const entry = sessions.get(sessionID);
|
|
115
|
+
if (entry !== undefined && JSON.stringify(entry.view) === JSON.stringify(next))
|
|
116
|
+
return;
|
|
117
|
+
if (entry !== undefined) {
|
|
118
|
+
const notice = leadingNotice(missionNotices(entry.view, next));
|
|
119
|
+
if (notice !== undefined)
|
|
120
|
+
context.ui.toast.show({ variant: notice.variant, title: notice.title, message: notice.message, duration: toastMs, sessionID });
|
|
121
|
+
}
|
|
122
|
+
sessions.set(sessionID, { view: next, motion: planMotion(entry?.view, next, now, entry?.motion ?? idleMotion, motionEnabled) });
|
|
123
|
+
setClock(now);
|
|
124
|
+
setRevision(revision() + 1);
|
|
125
|
+
ensureFrames();
|
|
126
|
+
}
|
|
127
|
+
function syncPoll() {
|
|
128
|
+
if (disposed)
|
|
129
|
+
return;
|
|
130
|
+
const active = [...sessions.values()].some((entry) => entry.view.active);
|
|
131
|
+
if (active && pollTimer === undefined)
|
|
132
|
+
pollTimer = setInterval(refreshTracked, ledgerPollMs);
|
|
133
|
+
if (!active && pollTimer !== undefined) {
|
|
134
|
+
clearInterval(pollTimer);
|
|
135
|
+
pollTimer = undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function refreshTracked() {
|
|
139
|
+
refreshTimer = undefined;
|
|
140
|
+
if (disposed)
|
|
141
|
+
return;
|
|
142
|
+
for (const sessionID of tracked)
|
|
143
|
+
refresh(sessionID);
|
|
144
|
+
syncPoll();
|
|
145
|
+
}
|
|
146
|
+
function scheduleRefresh() {
|
|
147
|
+
if (!disposed && refreshTimer === undefined)
|
|
148
|
+
refreshTimer = setTimeout(refreshTracked, refreshDelayMs);
|
|
149
|
+
}
|
|
150
|
+
// A session is tracked from its first render. Its existing history raises no toast; only a lit mark
|
|
151
|
+
// that appears after that does.
|
|
152
|
+
function track(sessionID) {
|
|
153
|
+
if (disposed || tracked.includes(sessionID))
|
|
154
|
+
return;
|
|
155
|
+
tracked.push(sessionID);
|
|
156
|
+
if (tracked.length > trackedSessionLimit) {
|
|
157
|
+
const dropped = tracked.shift() ?? "";
|
|
158
|
+
watchers.get(dropped)?.();
|
|
159
|
+
watchers.delete(dropped);
|
|
160
|
+
sessions.delete(dropped);
|
|
161
|
+
ignitions.delete(dropped);
|
|
162
|
+
trackedAt.delete(dropped);
|
|
163
|
+
}
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
trackedAt.set(sessionID, now);
|
|
166
|
+
ignitions.set(sessionID, latestLitMarkV2(context.data, sessionID)?.messageID);
|
|
167
|
+
sessions.set(sessionID, { view: viewFor(sessionID, now), motion: idleMotion });
|
|
168
|
+
queueMicrotask(syncPoll);
|
|
169
|
+
// Opening a session loads its messages over HTTP, which raises no event. Reading the host's reactive
|
|
170
|
+
// store here re-runs this effect as they arrive, so a resumed session lights up too.
|
|
171
|
+
const dispose = ui.createRoot((release) => {
|
|
172
|
+
ui.createEffect(() => {
|
|
173
|
+
for (const message of context.data.session.message.list(sessionID) ?? [])
|
|
174
|
+
void message.id;
|
|
175
|
+
void context.data.session.status(sessionID);
|
|
176
|
+
scheduleRefresh();
|
|
177
|
+
});
|
|
178
|
+
return release;
|
|
179
|
+
});
|
|
180
|
+
watchers.set(sessionID, dispose);
|
|
181
|
+
}
|
|
182
|
+
const unlisten = context.data.listen((event) => {
|
|
183
|
+
const type = event.details.type;
|
|
184
|
+
if (type.startsWith("session.") || type.startsWith("permission.") || type.startsWith("form."))
|
|
185
|
+
scheduleRefresh();
|
|
186
|
+
});
|
|
187
|
+
const releases = [
|
|
188
|
+
context.ui.slot({
|
|
189
|
+
append: "prompt.footer.status",
|
|
190
|
+
render(input) {
|
|
191
|
+
const sessionID = input.sessionID;
|
|
192
|
+
if (sessionID === undefined)
|
|
193
|
+
return null;
|
|
194
|
+
track(sessionID);
|
|
195
|
+
return () => {
|
|
196
|
+
revision();
|
|
197
|
+
const mark = latestLitMarkV2(context.data, sessionID);
|
|
198
|
+
if (mark === undefined)
|
|
199
|
+
return null;
|
|
200
|
+
return ui.jsx("box", {
|
|
201
|
+
flexShrink: 0,
|
|
202
|
+
children: line([{ text: ignitionLine(mark.name), fg: palette().primary, bold: true }])
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}),
|
|
207
|
+
context.ui.slot({
|
|
208
|
+
append: "sidebar.content",
|
|
209
|
+
render(input) {
|
|
210
|
+
track(input.sessionID);
|
|
211
|
+
return () => {
|
|
212
|
+
revision();
|
|
213
|
+
const entry = sessions.get(input.sessionID);
|
|
214
|
+
if (entry === undefined || !entry.view.active)
|
|
215
|
+
return null;
|
|
216
|
+
const panel = missionPanel(entry.view, sampleMotion(entry.motion, clock()), palette());
|
|
217
|
+
if (panel === undefined)
|
|
218
|
+
return null;
|
|
219
|
+
return ui.jsx("box", {
|
|
220
|
+
border: true,
|
|
221
|
+
borderStyle: "rounded",
|
|
222
|
+
width: panelWidth,
|
|
223
|
+
flexShrink: 0,
|
|
224
|
+
flexDirection: "column",
|
|
225
|
+
paddingLeft: 1,
|
|
226
|
+
paddingRight: 1,
|
|
227
|
+
titleAlignment: "left",
|
|
228
|
+
bottomTitleAlignment: "right",
|
|
229
|
+
borderColor: panel.border,
|
|
230
|
+
title: panel.title,
|
|
231
|
+
titleColor: panel.titleColor,
|
|
232
|
+
bottomTitle: panel.bottomTitle ?? "",
|
|
233
|
+
backgroundColor: panel.background ?? "transparent",
|
|
234
|
+
children: panel.rows.map(line)
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
];
|
|
240
|
+
return async () => {
|
|
241
|
+
disposed = true;
|
|
242
|
+
for (const dispose of watchers.values())
|
|
243
|
+
dispose();
|
|
244
|
+
watchers.clear();
|
|
245
|
+
tracked.length = 0;
|
|
246
|
+
unlisten();
|
|
247
|
+
for (const release of releases)
|
|
248
|
+
release();
|
|
249
|
+
if (frameTimer !== undefined)
|
|
250
|
+
clearInterval(frameTimer);
|
|
251
|
+
if (refreshTimer !== undefined)
|
|
252
|
+
clearTimeout(refreshTimer);
|
|
253
|
+
if (pollTimer !== undefined)
|
|
254
|
+
clearInterval(pollTimer);
|
|
255
|
+
sessions.clear();
|
|
256
|
+
ignitions.clear();
|
|
257
|
+
trackedAt.clear();
|
|
258
|
+
};
|
|
259
|
+
}
|
|
Binary file
|
|
Binary file
|
package/docs/assets/cover.webp
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.30"><title>release: 1.0.30</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.30</text></g></svg>
|
|
Binary file
|
package/docs/migration.md
CHANGED
|
@@ -52,10 +52,10 @@ Use LitOpenCode vocabulary in docs, config, runtime output, and issue reports:
|
|
|
52
52
|
- npm package and OpenCode package-loading entry: `@litfamily/litopencode`; native namespace and binary: `litopencode`
|
|
53
53
|
- activation commands/tools: `lit`, `litwork`, `start-work`, `review-work`, `lit-recap`, `lit-korean`, `text-neutralization`
|
|
54
54
|
- durable loop: `lit-loop`
|
|
55
|
-
- durable goal manager: `litgoal`
|
|
55
|
+
- durable goal manager: `litgoal` (sets and keeps the finish line; `lit-loop` and `start-work` run toward it)
|
|
56
56
|
- current planning/research workflow names: `lit-plan`, `lit-crucible`, `litresearch`, `start-work`, `review-work`
|
|
57
57
|
- `lit-plan` creates a planning-only objective-achievable checklist; `lit-crucible` adversarially tests assumptions before handing surviving insights to `lit-plan`.
|
|
58
|
-
- `litresearch` runs the root-owned evidence workflow; `start-work` executes an approved plan through `lit-implement`; `review-work`
|
|
58
|
+
- `litresearch` runs the root-owned evidence workflow; `start-work` executes an approved plan through `lit-implement`; `review-work` runs plan review after `lit-plan` or work review after `lit-loop` or `start-work`, picked from what exists or forced with `lit review plan` / `lit review work`, without inventing another workflow surface.
|
|
59
59
|
|
|
60
60
|
Legacy vocabulary must not appear in tracked files or release payloads. Do not introduce old package names, host claims, or environment prefixes into new product docs, tests, source, fixtures, or release artifacts.
|
|
61
61
|
|
|
@@ -72,7 +72,8 @@ Legacy vocabulary must not appear in tracked files or release payloads. Do not i
|
|
|
72
72
|
## Skill ID renames
|
|
73
73
|
|
|
74
74
|
Use `lit-crucible`, `lit-init`, `lit-commit`, `lit-burnoff`, `lit-burnoff-file`,
|
|
75
|
-
`lit-korean`, `lit-fetch`,
|
|
75
|
+
`lit-korean`, `lit-fetch`, `lit-code`, and `litgoal` (formerly `durable-litgoal`, so the skill,
|
|
76
|
+
the `/litgoal` command and the chat route now share one name). The Unreleased changelog records the old-to-new
|
|
76
77
|
mapping. Old bare-word invocations select the new skill and inject exactly one line:
|
|
77
78
|
``Note: `<old>` was renamed to `<new>`; the old name is removed in the next minor.``
|
|
78
79
|
Quoted text, code fences, and compound-word
|
package/docs/reference-Ko-KR.md
CHANGED
|
@@ -137,7 +137,9 @@ litopencode install
|
|
|
137
137
|
| `/litwork` | 제한된 작업 루프 진입 |
|
|
138
138
|
| `/lit-plan` | 읽기 전용 구현 계획 작성 |
|
|
139
139
|
| `/start-work` | 승인된 계획을 `lit-implement`로 실행 |
|
|
140
|
-
| `/review-work` |
|
|
140
|
+
| `/review-work` | `lit-plan` 뒤에는 계획 검토, `lit-loop`나 `/start-work` 뒤에는 작업 검토. 있는 것을 보고 고르며 `/review-work plan`, `/review-work work`로 직접 정할 수 있음 |
|
|
141
|
+
| `/litgoal` | 끝나는 지점을 정하고 지킴: 확인 가능한 기준이 붙은 목표 하나를 디스크에 남김. `lit-loop`와 `/start-work`가 그 목표를 향해 일함 |
|
|
142
|
+
| `/lit-brainstorm` | 열린 질문을 여러 선택지로 펼친 뒤 짧은 목록으로 좁히고, 고른 것을 `deep-interview`나 `lit-plan`에 넘김. 채팅에서는 `lit brainstorm`, `lit brainstorming`, `lit ideation`, `lit ideate`, `lit idea`, `lit ideas`. 직접 만들지는 않음 |
|
|
141
143
|
| `/litresearch` 또는 `/lit-research` | 순차 대체 경로를 포함한 근거 기반 연구 |
|
|
142
144
|
| `/lit-handoff` | 이어서 작업할 수 있는 핸드오프 작성 |
|
|
143
145
|
| `/lit-recap` | 로컬 ledger와 세션 맥락으로 짧은 리캡 작성 |
|
|
@@ -150,6 +152,17 @@ litopencode install
|
|
|
150
152
|
`/autoresearch`, `/autoconference`, `/wikify-*` 경로가 있습니다. 이 경로는 안내 또는 제한된
|
|
151
153
|
로컬 동작을 추가합니다. 호스트 권한은 늘리지 않습니다.
|
|
152
154
|
|
|
155
|
+
### `lit` 반복 입력
|
|
156
|
+
|
|
157
|
+
메시지마다 `lit`를 붙여도 안전합니다. 세션의 첫 `lit`는 전체 활성화 안내를 보냅니다. 같은 discipline의 `lit`
|
|
158
|
+
(예: 실행 중에 보낸 두 번째 `lit fix ...`)는 전체 안내 대신 "이미 활성, 이어서 진행" 짧은 메모만 보내며, 기존
|
|
159
|
+
계획, 기준, ledger를 건드리지 않습니다. 다른 discipline(루프 중 `lit plan ...`)은 그 discipline의 전체
|
|
160
|
+
활성화 안내를 한 번 보냅니다. `lit new ...`와 `lit restart ...`는 새로 시작합니다. 승인된 계획이나 진행 중인
|
|
161
|
+
`/start-work` 기록이 있으면 단독 `lit` 또는 `lit continue`가 그 작업을 이어 가며 새 계획을 만들지 않습니다.
|
|
162
|
+
작업 내용도 없고 진행 중인 것도 없는 단독 `lit`는 무엇을 할지 되묻습니다. 컴팩션, 새 세션, OpenCode 재시작
|
|
163
|
+
뒤에는 활성 discipline을 잊으므로 다음 `lit`는 다시 전체 안내를 보냅니다. `lit-loop`, `lit-plan`,
|
|
164
|
+
`lit-humanizer`는 스킬 본문을 붙이지 않고 설치된 스킬을 가리킵니다.
|
|
165
|
+
|
|
153
166
|
### Durable loop 상태
|
|
154
167
|
|
|
155
168
|
LitOpenCode는 `.litopencode/litgoal/lit-loop/`에 goal 상태를 저장합니다. append-only
|
|
@@ -245,15 +258,104 @@ npm uninstall -g @litfamily/litopencode
|
|
|
245
258
|
프로젝트의 `.litopencode/`는 별도 ledger와 receipt입니다. 나중에 재개할 계획이 없다면
|
|
246
259
|
그 상태를 버려도 되는지 확인한 뒤 직접 정리합니다.
|
|
247
260
|
|
|
248
|
-
TUI 패널 항목은 `tui.json`이나 `tui.jsonc`에
|
|
249
|
-
|
|
250
|
-
`<file>.litopencode-backup`으로 복사합니다.
|
|
261
|
+
TUI 패널 항목은 `tui.json`이나 `tui.jsonc`에 있고, OpenCode 2에서는 `cli.json`에 고정된 항목이 있을 수도
|
|
262
|
+
있습니다. `litopencode tui remove [--root <dir>]`는 세 파일에서 LitOpenCode 항목만 지우고, 다른 플러그인과
|
|
263
|
+
주석은 그대로 둡니다. 바뀌는 파일은 먼저 `<file>.litopencode-backup`으로 복사합니다. 쓰기 전에 모든 파일을
|
|
264
|
+
읽고 확인하므로, 깨진 파일이나 지워질 주석이 있으면 아무것도 바꾸지 않고 멈춥니다. `--host v1`이면
|
|
265
|
+
`cli.json`은 건드리지 않습니다. `--dry-run`은 같은 결과를 보여 주기만 하고 아무것도 쓰지 않습니다.
|
|
266
|
+
|
|
267
|
+
## OpenCode 2
|
|
268
|
+
|
|
269
|
+
LitOpenCode는 OpenCode 1(1.18.29 이상. 패키지가 `engines.opencode >=1.18.29`를 선언하고 OpenCode 1이 이를
|
|
270
|
+
확인합니다)과 OpenCode 2에서 모두 돌아갑니다. 두 호스트를 함께 설치할 수 있습니다. OpenCode 1은 `opencode`
|
|
271
|
+
명령을 그대로 쓰고, OpenCode 2(npm `@opencode/cli`)는 `opencode2`로 실행합니다. 둘 다 같은
|
|
272
|
+
`~/.config/opencode`를 읽으므로 한 번 설치하면 양쪽에 적용됩니다.
|
|
273
|
+
|
|
274
|
+
**설치.** `litopencode install`은 무엇을 쓰기 전에 호스트부터 찾고, 그때 어느 호스트도 실행하지 않습니다.
|
|
275
|
+
`opencode`와 `opencode2` 뒤에 있는 npm 패키지를 읽습니다(`opencode-ai`는 OpenCode 1, `@opencode/cli`는 OpenCode 2.
|
|
276
|
+
Windows에서는 npm `.cmd` 실행 파일을 글자로 읽습니다). 뒤에 npm 패키지가 없는 `opencode`(curl 설치 스크립트,
|
|
277
|
+
Homebrew, mise)는 일단 OpenCode 1로 봅니다. 어떻게 설치했든 OpenCode 1 설정이 유지되도록 하기 위해서입니다.
|
|
278
|
+
`opencode2`와 같은 파일인 `opencode`는 OpenCode 2로 보고, `opencode2`는 언제나 OpenCode 2로 봅니다. OpenCode 1에는
|
|
279
|
+
`opencode2`가 없기 때문입니다. 실제 설치는 OpenCode 1로 짐작한 `opencode`에 `opencode plugin`을 부르기 전에
|
|
280
|
+
`opencode --version`을 묻고, 2.x라고 답하면 그 호스트에 OpenCode 2 설정을 합니다(dry run은 아무것도 실행하지 않으므로
|
|
281
|
+
처음 짐작을 보여 줍니다).
|
|
282
|
+
PATH에 두 명령이 모두 없으면 `tui.json`(OpenCode 1)과 `cli.json`(OpenCode 2)을 근거로 삼고, 둘 다 없으면
|
|
283
|
+
OpenCode 1 설정을 유지합니다. `--host v1`이나 `--host both`인데 `opencode`가 OpenCode 2뿐이면 아무것도 실행하기
|
|
284
|
+
전에 멈춥니다. 그 상태에서 `opencode plugin`을 부르면 OpenCode 2 설정이 바뀌기 때문입니다.
|
|
285
|
+
기본값인 `--host auto`는 찾은 호스트를 모두 설정하고, `--host v1`, `--host v2`, `--host both`로 직접 정할 수
|
|
286
|
+
있습니다. dry-run 결과의 `host`에 판단이 나옵니다. OpenCode 1 쪽은 그대로입니다. `opencode plugin`이 패키지를
|
|
287
|
+
등록하고 TUI 항목은 `tui.json`에 들어갑니다. OpenCode 2 쪽에서 설치 프로그램은 다음을 합니다.
|
|
288
|
+
|
|
289
|
+
- 두 호스트가 함께 읽는 `opencode.json(c)`의 `plugin` 목록에 항목 하나만 둡니다. OpenCode 2 실행 파일로
|
|
290
|
+
`opencode plugin`을 부르지 않습니다. OpenCode 2는 처음 불러올 때 패키지를 직접 설치합니다.
|
|
291
|
+
- OpenCode 2 전용 `plugins` 키에 있는 LitOpenCode 항목을 지웁니다. 같은 id의 플러그인이 둘이면 OpenCode 2가
|
|
292
|
+
뒤의 것을 거부하기 때문입니다.
|
|
293
|
+
- `cli.json`의 LitOpenCode 항목을 지우고 `cli.json.litopencode-backup`을 남깁니다. OpenCode 2는 활성
|
|
294
|
+
서버 플러그인의 TUI를 스스로 불러오는데, 거기에 고정된 항목이 있으면 그것이 새 TUI를 가리기 때문입니다.
|
|
295
|
+
- 공유 설정에 OpenCode 2 키 `permissions`나 `policies`를 절대 쓰지 않습니다. OpenCode 1은 그 자리에서
|
|
296
|
+
`permissions`를 보면 시작하지 않습니다.
|
|
297
|
+
|
|
298
|
+
두 호스트에 설치한 뒤 `opencode2` TUI를 처음 열면 OpenCode 2가 `tui.json`의 플러그인 목록을 새 `cli.json`으로
|
|
299
|
+
복사하고, 여기에 LitOpenCode 항목도 따라 들어갑니다. 설치 프로그램은 이 복사를 막을 수 없습니다. doctor가 그
|
|
300
|
+
항목을 알려 주고, `litopencode install`이나 `litopencode tui remove`를 다시 실행하면 지워집니다.
|
|
301
|
+
|
|
302
|
+
**Doctor.** `litopencode doctor`는 `host` 블록(찾은 호스트, 근거 파일, 공유 설정의 `autoupdate`에서 읽은
|
|
303
|
+
OpenCode 2 플러그인 업데이트 방식)을 보여 주고, `plugin`과 `plugins`에 걸친 LitOpenCode 항목 수를 세며,
|
|
304
|
+
`cli.json`에 남은 항목과 OpenCode 1도 읽는 설정에 들어간 `permissions`를 알려 줍니다. doctor는 호스트를
|
|
305
|
+
실행하지 않습니다. OpenCode 2가 대상이면 `lsp` 블록에 OpenCode 2는 `lsp` 설정을 받아 두기만 하고 언어 서버를
|
|
306
|
+
돌리지 않는다는 설명이 붙습니다.
|
|
307
|
+
|
|
308
|
+
**똑같이 동작하는 것.** 에이전트(기본은 `lit-loop`, `build`와 `plan`은 숨김), 도구 다섯 개, 단독 `lit` 활성화,
|
|
309
|
+
정적 규칙과 독자용 계약, 도구 가드, 플래너 잠금, bounded-authority 진행 기록, 자동 인수인계,
|
|
310
|
+
`~/.config/opencode/skills`의 스킬이 모두 OpenCode 2에서 돌아갑니다. OpenCode 2는 도구 두 개의 이름을 바꿨기
|
|
311
|
+
때문에 권한 규칙은 `bash` 대신 `shell`, `task` 대신 `subagent`를 쓰고, `write`와 `patch`는 `edit`에 들어갑니다.
|
|
312
|
+
그곳에서는 마지막에 맞는 규칙이 이기므로 `lit-plan`은 어떤 권한 모드에서도 `edit`, `shell`, `subagent`,
|
|
313
|
+
`execute` 거부로 끝나고, 권한 훅이 다른 규칙과 상관없이 한 번 더 거부합니다. `execute`는 OpenCode 2의 Code Mode
|
|
314
|
+
도구로, 모델이 쓴 JavaScript를 `fetch`와 다른 도구와 함께 실행합니다. OpenCode 1에는 없던 도구이고 계획만 하는
|
|
315
|
+
에이전트가 가져서는 안 됩니다. 이렇게 거부된 도구는 요청에서 빠집니다. LitOpenCode 도구 다섯 개는 OpenCode 1에서처럼
|
|
316
|
+
Code Mode 밖, 모델의 도구 목록에 바로 있으므로 `lit-plan`도 계속 씁니다. OpenCode 1에서 전역 권한을 넓히는 balanced와 yolo
|
|
317
|
+
모드는 OpenCode 2에서는 각 LitOpenCode 에이전트 규칙의 앞부분에 들어갑니다. 플러그인이 그곳의 전역 규칙을
|
|
318
|
+
바꿀 수 없기 때문입니다. 추론 강도 같은 에이전트별 요청 설정은 모델 요청마다 함께 보냅니다. OpenCode 2는
|
|
319
|
+
에이전트의 요청 설정을 저장만 하고 아직 보내지 않습니다.
|
|
320
|
+
|
|
321
|
+
**OpenCode 2에서 달라지는 것.**
|
|
322
|
+
|
|
323
|
+
- *슬래시 명령.* 설치 프로그램은 두 호스트에 같은 `command/<id>.md` 파일을 쓰고, OpenCode 2에서는 이 파일이
|
|
324
|
+
같은 이름의 플러그인 명령을 대신합니다. 그래서 모든 lit 명령은 파일에서 오고 목록에 한 번만 나옵니다.
|
|
325
|
+
플러그인은 파일이 없는 명령만 직접 추가합니다(예를 들어 설치 프로그램 없이 `opencode plugin add`만 한 경우).
|
|
326
|
+
OpenCode 2에서 파일로 실행한 명령 차례에는 LitOpenCode 표시가 붙지 않습니다. 플러그인이 직접 보낸 명령에는
|
|
327
|
+
한 번만 쓰는 확인값이 든 표시가 붙어, 손으로 입력한 프롬프트가 흉내 낼 수 없습니다. `/lit-crucible`과
|
|
328
|
+
`/deep-interview`(`lit-plan`), `/start-work`(`lit-implement`)처럼 에이전트가 정해진 플러그인 명령은 명령 파일의
|
|
329
|
+
`agent:` 줄과 같이 먼저 세션을 그 에이전트로 바꿉니다.
|
|
330
|
+
- *모델 경로.* OpenCode 2는 자기 모델이 없는 세션을 호스트의 기본 모델로 돌립니다. 에이전트의 모델은 세션을
|
|
331
|
+
만드는 클라이언트만 읽는데, TUI는 읽고 `opencode2 run`은 읽지 않습니다. 그래서 그런 세션의 첫 프롬프트에서
|
|
332
|
+
세션을 에이전트의 경로로 바꿉니다(에이전트가 없으면 `lit-loop`). 그 모델이 카탈로그에 있을 때만 바꿉니다.
|
|
333
|
+
사용자가 고른 모델과, 호스트가 이미 에이전트 모델로 시작하는 하위 세션은 그대로 둡니다. OpenCode 1과 같이
|
|
334
|
+
LitOpenCode가 쓰는 OpenAI 모델이 호스트 카탈로그에 없으면 `openai` 카탈로그에 추가합니다.
|
|
335
|
+
- *사용자 메시지로 가는 프로젝트 데이터.* 로컬 지식 기록, start-work 재개 영수증, 자동 인수인계 설정 응답은
|
|
336
|
+
요청의 마지막 사용자 메시지에 붙습니다. OpenCode 1에서도 사용자 메시지의 일부였습니다. OpenCode 2의 system
|
|
337
|
+
부분은 지시를 위한 자리라서, LitOpenCode의 활성화 문구와 규칙, 독자용 계약만 그곳에 들어갑니다.
|
|
338
|
+
- *따옴표 프롬프트.* `opencode2 run "lit fix it"`은 단어를 큰따옴표로 감싸 보내므로, 메시지 전체가 따옴표 한 쌍이면
|
|
339
|
+
따옴표를 벗겨 읽습니다. 서버는 이것과 TUI에서 통째로 따옴표 안에 입력한 프롬프트를 구별하지 못해서 그런 입력도
|
|
340
|
+
활성화됩니다. OpenCode 1에서는 활성화되지 않았습니다.
|
|
341
|
+
- *점화 표시.* OpenCode 2에는 끝난 답변을 고치는 훅이 없어서 표시가 TUI로 옮겨 갑니다. 세션의 마지막 프롬프트가
|
|
342
|
+
lit 프롬프트이면 프롬프트 아래 줄(`prompt.footer.status`)에 `🔥 LIT IGNITED · <discipline>`이 나옵니다.
|
|
343
|
+
OpenCode 1에서는 표시가 예전 자리에 그대로 있습니다.
|
|
344
|
+
- *토스트.* OpenCode 2에서는 서버가 토스트를 띄울 수 없습니다. 서버가 사용자 메시지 메타데이터에 남긴 lit 표시를
|
|
345
|
+
TUI가 보고 점화 토스트를 직접 띄웁니다. 서버만 알던 자동 인수인계 알림과 스킬 힌트는 그곳에 나오지 않습니다.
|
|
346
|
+
- *TUI.* 미션 패널은 전처럼 세션 사이드바에 있습니다. OpenCode 2는 서버 플러그인과 함께 오는 TUI에 옵션을
|
|
347
|
+
넘기지 않고 할 일 목록도 두지 않으므로, 미션 진행은 ledger와 작업 기록과 계획에서 읽습니다. 세션 아래 상태 띠와
|
|
348
|
+
홈 화면 한 줄은 OpenCode 1에서만 그립니다.
|
|
349
|
+
- *업데이트.* OpenCode 2에서는 플러그인 안의 자동 업데이트가 없습니다. `opencode plugin update`를 실행하거나
|
|
350
|
+
OpenCode 2의 업데이트 방식에 맡기면 됩니다.
|
|
251
351
|
|
|
252
352
|
## TUI 미션 패널
|
|
253
353
|
|
|
254
354
|
OpenCode 1.18은 `tui.json`이나 `tui.jsonc`의 `plugin` 배열에 적힌 TUI 플러그인을 불러옵니다.
|
|
255
|
-
이 패키지는 TUI 모듈을 서버 플러그인과 따로 `@litfamily/litopencode/tui`(`dist/tui.js`, 플러그인 id
|
|
256
|
-
`litopencode`)로 내보냅니다. OpenCode가 두 대상을 한 모듈에 섞는 것을 허용하지 않기 때문입니다.
|
|
355
|
+
이 패키지는 TUI 모듈을 서버 플러그인과 따로 `@litfamily/litopencode/tui`(`dist/host/tui.js`, 플러그인 id
|
|
356
|
+
`litopencode`)로 내보냅니다. OpenCode가 두 대상을 한 모듈에 섞는 것을 허용하지 않기 때문입니다. OpenCode 2는
|
|
357
|
+
활성 서버 플러그인을 위해 같은 모듈을 스스로 불러옵니다(위 OpenCode 2 참고). `./server`와 `./tui`의 타입 선언은
|
|
358
|
+
각 진입점을 모양으로만 적으므로, 어느 호스트의 플러그인 패키지 없이도 해석됩니다.
|
|
257
359
|
|
|
258
360
|
화면에 그리는 것:
|
|
259
361
|
|
|
@@ -461,7 +563,7 @@ OpenCode는 세션이 바쁜 동안 들어온 `summarize` 호출을 줄 세웠
|
|
|
461
563
|
## 스킬 이름 변경
|
|
462
564
|
|
|
463
565
|
새 명령은 `/lit-crucible`, `/lit-init`, `/lit-commit`, `/lit-burnoff`, `/lit-burnoff-file`,
|
|
464
|
-
`/lit-korean`, `/lit-fetch`, `/lit-code
|
|
566
|
+
`/lit-korean`, `/lit-fetch`, `/lit-code`, `/litgoal`(이전 `durable-litgoal`)입니다. 이전 이름은 한 릴리스 동안 새 명령으로
|
|
465
567
|
연결되며, 이름 변경 안내가 한 줄 표시됩니다. 다음 마이너 버전에서 이전 이름을 제거합니다.
|
|
466
568
|
설치·업데이트는 새 복사본을 검증한 뒤 설치 도구가 관리하는 이전 이름의 스킬 디렉터리를
|
|
467
569
|
제거합니다. 사용자 소유 파일은 보존합니다. 자세한 내용은 [마이그레이션 안내](migration.md#skill-id-renames)를 참고하세요.
|