@litfamily/litopencode 1.0.20 → 1.0.21

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.
@@ -1,8 +1,14 @@
1
1
  import { effectiveAgentConfig } from "./config.js";
2
- import { defaultOpenAiRoutes, newGenerationOpenAiModels, openAiModelEfforts, previousGenerationOpenAiModels, previousGpt6OpenAiModels, stripFastSuffix } from "./cli/model-catalog.js";
3
- const safeLunaEfforts = new Set(openAiModelEfforts(previousGenerationOpenAiModels.luna));
4
- const safeTerraEfforts = new Set(openAiModelEfforts(previousGenerationOpenAiModels.terra));
2
+ import { defaultOpenAiRoutes, newGenerationOpenAiModels, openAiModelEfforts, previousGenerationOpenAiModels, previousGpt6OpenAiModels, replacedRetiredEffort, stripFastSuffix } from "./cli/model-catalog.js";
5
3
  const safeAstraEfforts = new Set(openAiModelEfforts(defaultOpenAiRoutes.lead.model));
4
+ // The managed rows whose effort is checked; other GPT-5.6 ids stay user-owned as before.
5
+ const guardedModels = new Set([
6
+ newGenerationOpenAiModels.sol,
7
+ previousGpt6OpenAiModels.sol,
8
+ newGenerationOpenAiModels.luna,
9
+ previousGenerationOpenAiModels.luna,
10
+ previousGenerationOpenAiModels.terra
11
+ ]);
6
12
  function dispatchedModel(route) {
7
13
  if (route.model === undefined)
8
14
  return undefined;
@@ -53,41 +59,17 @@ export function diagnoseModelRoutes(routes) {
53
59
  }
54
60
  continue;
55
61
  }
56
- if (modelId === newGenerationOpenAiModels.sol || modelId === previousGpt6OpenAiModels.sol || modelId === newGenerationOpenAiModels.luna) {
57
- const supported = new Set(openAiModelEfforts(modelId));
58
- const variantUnsafe = route.variant !== undefined && !supported.has(route.variant);
59
- const reasoningUnsafe = route.reasoningEffort !== undefined && !supported.has(route.reasoningEffort);
60
- if (variantUnsafe || reasoningUnsafe || (route.variant === undefined && route.reasoningEffort === undefined)) {
61
- diagnostics.push({ agentId, code: "model_effort_unsupported", model, effort });
62
- }
63
- if (route.variant !== undefined && route.reasoningEffort !== undefined && route.variant !== route.reasoningEffort) {
64
- diagnostics.push({ agentId, code: "model_effort_conflict", model, effort });
65
- }
66
- continue;
67
- }
68
- if (modelId === previousGenerationOpenAiModels.luna) {
69
- const hasXhigh = route.variant === "xhigh" || route.reasoningEffort === "xhigh";
70
- if ((route.variant !== undefined && route.variant !== "xhigh" && !safeLunaEfforts.has(route.variant)) ||
71
- (route.reasoningEffort !== undefined && route.reasoningEffort !== "xhigh" && !safeLunaEfforts.has(route.reasoningEffort)) ||
72
- (route.variant === undefined && route.reasoningEffort === undefined)) {
73
- diagnostics.push({ agentId, code: "luna_effort_below_high", model, effort });
74
- }
75
- if (hasXhigh)
76
- diagnostics.push({ agentId, code: "luna_xhigh_forbidden", model, effort });
77
- if (route.variant !== undefined && route.reasoningEffort !== undefined && route.variant !== route.reasoningEffort) {
78
- diagnostics.push({ agentId, code: "luna_effort_conflict", model, effort });
79
- }
62
+ // Each guarded row accepts exactly the efforts OpenCode offers for it.
63
+ if (!guardedModels.has(modelId))
80
64
  continue;
81
- }
82
- if (modelId !== previousGenerationOpenAiModels.terra)
83
- continue;
84
- if ((route.variant !== undefined && !safeTerraEfforts.has(route.variant)) ||
85
- (route.reasoningEffort !== undefined && !safeTerraEfforts.has(route.reasoningEffort)) ||
86
- (route.variant === undefined && route.reasoningEffort === undefined)) {
87
- diagnostics.push({ agentId, code: "terra_effort_below_high", model, effort });
65
+ const supported = new Set(openAiModelEfforts(modelId));
66
+ const variantUnsafe = route.variant !== undefined && !supported.has(route.variant);
67
+ const reasoningUnsafe = route.reasoningEffort !== undefined && !supported.has(route.reasoningEffort);
68
+ if (variantUnsafe || reasoningUnsafe || (route.variant === undefined && route.reasoningEffort === undefined)) {
69
+ diagnostics.push({ agentId, code: "model_effort_unsupported", model, effort });
88
70
  }
89
71
  if (route.variant !== undefined && route.reasoningEffort !== undefined && route.variant !== route.reasoningEffort) {
90
- diagnostics.push({ agentId, code: "terra_effort_conflict", model, effort });
72
+ diagnostics.push({ agentId, code: "model_effort_conflict", model, effort });
91
73
  }
92
74
  }
93
75
  return diagnostics;
@@ -114,22 +96,12 @@ export class UnsafeModelRouteError extends Error {
114
96
  const summary = diagnostics
115
97
  .map((diagnostic) => {
116
98
  switch (diagnostic.code) {
117
- case "luna_effort_below_high":
118
- return `${diagnostic.agentId}: Luna requires high or max (${diagnostic.model}, ${diagnostic.effort})`;
119
- case "luna_xhigh_forbidden":
120
- return `${diagnostic.agentId}: Luna plus xhigh is forbidden (${diagnostic.model}, ${diagnostic.effort})`;
121
- case "luna_effort_conflict":
122
- return `${diagnostic.agentId}: Luna effort fields conflict (${diagnostic.model}, ${diagnostic.effort})`;
123
- case "terra_effort_below_high":
124
- return `${diagnostic.agentId}: TERRA requires high or xhigh (${diagnostic.model}, ${diagnostic.effort})`;
125
- case "terra_effort_conflict":
126
- return `${diagnostic.agentId}: TERRA effort fields conflict (${diagnostic.model}, ${diagnostic.effort})`;
127
99
  case "astra_effort_unsupported":
128
- return `${diagnostic.agentId}: Astra requires low, medium, high, xhigh, or max (${diagnostic.model}, ${diagnostic.effort})`;
100
+ return `${diagnostic.agentId}: Astra requires ${[...safeAstraEfforts].join(", ")} (${diagnostic.model}, ${diagnostic.effort})`;
129
101
  case "astra_effort_conflict":
130
102
  return `${diagnostic.agentId}: Astra effort fields conflict (${diagnostic.model}, ${diagnostic.effort})`;
131
103
  case "model_effort_unsupported":
132
- return `${diagnostic.agentId}: ${diagnostic.model} accepts a supported catalog effort (${diagnostic.model}, ${diagnostic.effort})`;
104
+ return `${diagnostic.agentId}: ${diagnostic.model} accepts ${openAiModelEfforts(unqualifiedModel(diagnostic.model)).join(", ")} (${diagnostic.model}, ${diagnostic.effort})`;
133
105
  case "model_effort_conflict":
134
106
  return `${diagnostic.agentId}: ${diagnostic.model} effort fields conflict (${diagnostic.model}, ${diagnostic.effort})`;
135
107
  }
@@ -144,3 +116,30 @@ export function assertSafeEffectiveAgentRoutes(config, agentIds) {
144
116
  if (diagnostics.length > 0)
145
117
  throw new UnsafeModelRouteError(diagnostics);
146
118
  }
119
+ function isRouteRecord(value) {
120
+ return typeof value === "object" && value !== null && !Array.isArray(value);
121
+ }
122
+ /** The saved routes in a litopencode.json object that still name an effort OpenCode no longer offers. */
123
+ export function retiredEffortRoutes(config) {
124
+ const found = [];
125
+ if (!isRouteRecord(config))
126
+ return found;
127
+ for (const section of ["categories", "agents"]) {
128
+ const entries = config[section];
129
+ if (!isRouteRecord(entries))
130
+ continue;
131
+ for (const [id, route] of Object.entries(entries)) {
132
+ if (!isRouteRecord(route) || typeof route.model !== "string")
133
+ continue;
134
+ const provider = typeof route.provider === "string" ? route.provider : undefined;
135
+ const effort = (value) => (typeof value === "string" ? value : undefined);
136
+ if (replacedRetiredEffort(provider, route.model, effort(route.variant)) === undefined && replacedRetiredEffort(provider, route.model, effort(route.reasoningEffort)) === undefined)
137
+ continue;
138
+ found.push({ section, id, model: route.model.slice(route.model.indexOf("/") + 1) });
139
+ }
140
+ }
141
+ return found;
142
+ }
143
+ export function retiredEffortWarning(route) {
144
+ return `ultra is no longer offered by OpenCode for ${route.model} (${route.section}.${route.id}); the next install moves it to max.`;
145
+ }
@@ -25,6 +25,8 @@ export type CardInputs = {
25
25
  readonly view: MissionView;
26
26
  readonly frame: MotionFrame;
27
27
  readonly chips: readonly SkillChip[];
28
+ /** Whether the session's turn is running; false lets a lit card rest at idle. Unknown keeps it working. */
29
+ readonly busy?: boolean;
28
30
  readonly shimmer?: ChipShimmer;
29
31
  readonly figures: CardFigures;
30
32
  /** The project's last goal, shown on the track while the session has no lit mission. */
@@ -82,16 +82,23 @@ function ruled(lead, left, right, tail, width, border) {
82
82
  return undefined;
83
83
  return [{ text: lead, fg: border }, ...l, { text: "─".repeat(fill), fg: border }, ...r, { text: tail, fg: border }];
84
84
  }
85
- function borderColor(view, frame, palette) {
86
- if (!view.active)
85
+ /**
86
+ * A lit session whose turn has ended rests: nothing is running, so the card stops showing a lit step.
87
+ * A finished goal and an open warning still show, since they are news after the turn.
88
+ */
89
+ function resting(view, busy) {
90
+ return view.active && busy === false && !view.complete && view.warnings.length === 0;
91
+ }
92
+ function borderColor(view, frame, palette, rest) {
93
+ if (!view.active || rest)
87
94
  return palette.borderActive;
88
95
  const enter = frame.chipEnter ? ease.outCubic(seg(frame.t, 0, motionTiming.entry)) : 1;
89
96
  const done = view.complete ? (frame.celebrate === undefined ? 1 : ease.inOutCubic(seg(frame.celebrate, 0, motionTiming.crossfade))) : 0;
90
97
  return mixHex(palette.borderActive, mixHex(mixHex(palette.accent, palette.warning, frame.warn), palette.success, done), enter);
91
98
  }
92
99
  /** The phase on the right of the title: always a glyph plus words. */
93
- function phaseLabel(view, frame, palette) {
94
- if (!view.active)
100
+ function phaseLabel(view, frame, palette, rest) {
101
+ if (!view.active || rest)
95
102
  return { text: "○ IDLE", fg: palette.muted };
96
103
  const status = missionStatus(view);
97
104
  if (status.tone === "success")
@@ -100,10 +107,10 @@ function phaseLabel(view, frame, palette) {
100
107
  return { text: `⚑ ${status.text}`, fg: mixHex(palette.info, palette.warning, Math.max(frame.warn, 0.5)), bold: true };
101
108
  return { text: `● ${view.phase.toUpperCase()}`, fg: palette.info, bold: true };
102
109
  }
103
- function topLine(view, frame, palette, width, border) {
110
+ function topLine(view, frame, palette, width, border, rest) {
104
111
  const brand = [{ text: "🔥 ", fg: palette.text }, { text: "LIT", fg: shine(palette.primary, palette, 0.5, shimmerAt(frame), 0.6), bold: true }];
105
112
  const name = view.active ? view.discipline : "ready";
106
- const right = [phaseLabel(view, frame, palette)];
113
+ const right = [phaseLabel(view, frame, palette, rest)];
107
114
  const withName = (room) => [...brand, { text: " · ", fg: palette.muted }, { text: truncateCells(name, room), fg: view.active ? palette.text : palette.muted }];
108
115
  const nameRoom = width - cellWidth("╭─") - lineWidth(withName(0)) - 2 - (lineWidth(right) + 2) - cellWidth("─╮") - 1;
109
116
  const attempts = [];
@@ -245,12 +252,15 @@ export function skillRow(chips, shimmer, palette, width) {
245
252
  * the prompt never moves. Each row sheds its own low-priority parts as the terminal narrows.
246
253
  */
247
254
  export function statusCard(input, palette, width) {
248
- const { view, frame } = input;
249
- const border = borderColor(view, frame, palette);
255
+ const { frame } = input;
256
+ const rest = resting(input.view, input.busy);
257
+ // At rest the track keeps what is done and unlights the step that was running.
258
+ const view = rest ? { ...input.view, steps: input.view.steps.map((step) => (step.state === "now" ? { ...step, state: "todo" } : step)) } : input.view;
259
+ const border = borderColor(view, frame, palette, rest);
250
260
  const inner = width - 4;
251
261
  const row = (content) => (inner < 0 ? fitLine([], width, palette) : [{ text: "│ ", fg: border }, ...fitLine(content, inner, palette), { text: " │", fg: border }]);
252
262
  return [
253
- topLine(view, frame, palette, width, border),
263
+ topLine(view, frame, palette, width, border, rest),
254
264
  row(trackRow(view, frame, palette, Math.max(0, inner), input.last)),
255
265
  row(skillRow(input.chips, input.shimmer, palette, Math.max(0, inner))),
256
266
  bottomLine(view, input.figures, palette, width, border)
@@ -60,9 +60,14 @@ export declare function createLedgerReader(): (root: string | undefined) => unkn
60
60
  export declare function readMissionInput(state: MissionStateSource, sessionID: string, ledger: unknown, now: number): MissionInput;
61
61
  export type CardInput = {
62
62
  readonly chips: readonly SkillChip[];
63
+ readonly busy: boolean;
63
64
  readonly contextPercent: number | null;
64
65
  readonly branch?: string;
65
66
  };
66
- /** What the band card shows beside the mission: the session's skill chips, context use and git branch. */
67
+ /**
68
+ * What the band card shows beside the mission: the session's skill chips, whether a turn is running,
69
+ * context use and git branch. OpenCode keeps each session's last `session.status` ({ type: "busy" },
70
+ * "retry" or "idle"); a session it has not seen run has none, which counts as not running.
71
+ */
67
72
  export declare function readCardInput(state: MissionStateSource, sessionID: string): CardInput;
68
73
  export {};
@@ -87,7 +87,11 @@ export function readMissionInput(state, sessionID, ledger, now) {
87
87
  now
88
88
  };
89
89
  }
90
- /** What the band card shows beside the mission: the session's skill chips, context use and git branch. */
90
+ /**
91
+ * What the band card shows beside the mission: the session's skill chips, whether a turn is running,
92
+ * context use and git branch. OpenCode keeps each session's last `session.status` ({ type: "busy" },
93
+ * "retry" or "idle"); a session it has not seen run has none, which counts as not running.
94
+ */
91
95
  export function readCardInput(state, sessionID) {
92
96
  const messages = state.session.messages(sessionID);
93
97
  const uses = [];
@@ -103,9 +107,11 @@ export function readCardInput(state, sessionID) {
103
107
  }
104
108
  }
105
109
  const status = state.session.status(sessionID)?.type;
110
+ const busy = status === "busy" || status === "retry";
106
111
  const branch = state.vcs?.branch?.trim();
107
112
  return {
108
- chips: skillChips(uses, { since, busy: status === "busy" || status === "retry" }),
113
+ chips: skillChips(uses, { since, busy }),
114
+ busy,
109
115
  contextPercent: contextPercent(state, messages),
110
116
  ...(branch === undefined || branch === "" ? {} : { branch })
111
117
  };
package/dist/tui.js CHANGED
@@ -289,6 +289,7 @@ const tui = async (api, options) => {
289
289
  view: snapshot.view,
290
290
  frame: snapshot.frame,
291
291
  chips: snapshot.card.chips,
292
+ busy: snapshot.card.busy,
292
293
  ...(shimmer === undefined ? {} : { shimmer }),
293
294
  figures: { contextPercent: snapshot.card.contextPercent, ...(snapshot.card.branch === undefined ? {} : { branch: snapshot.card.branch }), inLane: snapshot.inLane },
294
295
  ...(last === undefined ? {} : { last })
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.20"><title>release: 1.0.20</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.20</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.21"><title>release: 1.0.21</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.21</text></g></svg>
package/docs/migration.md CHANGED
@@ -23,7 +23,7 @@ This guide describes the supported migration shape for moving an OpenCode workfl
23
23
  - Use `litopencode doctor --root <workspace>` to inspect `litopencode.json` route validity and effective `lit-plan` / `lit-loop` settings without writing files.
24
24
  - Use `litopencode install --dry-run --root <workspace>` to preview the `opencode.json` plugin mutation without writing files.
25
25
  - Route fields include top-level `permissionMode` (`safe`, `balanced`, or `yolo`) plus per-agent/per-category `provider`, `model`, `variant`, `category`, `reasoningEffort`, `textVerbosity`, `thinking`, `temperature`, `topP`, `maxTokens`, `promptAppend`, `tools`, `permission`, and `providerOptions`; generated route files use `variant` as the primary effort knob while `reasoningEffort` stays available for explicit provider-level overrides. LitOpenCode keeps `topP` as its config spelling and projects it to OpenCode's native `top_p` field; this preserves nonreasoning model sampling while Astra's native Responses capability omits unsupported sampling fields.
26
- - Global route config loads first, project-local route config merges afterward, and effective per-agent fields override category fields. Fresh/reset lead routes use Astra with native `reasoningEffort`. GPT-6 Astra and Sol accept `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`. GPT-6 Luna accepts `low`, `medium`, `high`, `xhigh`, and `max`, but not `ultra`. Previous-generation GPT-5.6 Luna accepts only `high` and `max`; `xhigh` remains unsupported for that model. Astra has no invented `-fast` alias. No-selection installs preserve user route bytes but fail before success output when a managed route is unsafe. Other model IDs remain user-owned custom routes. Existing host entries named `momus` or `litwork-reviewer` preserve explicit routes and receive Astra/xhigh only when route fields are absent. LitOpenCode does not expose Hermes-specific per-subagent overrides.
26
+ - Global route config loads first, project-local route config merges afterward, and effective per-agent fields override category fields. Fresh/reset lead routes use Astra with native `reasoningEffort`. Each model accepts exactly the efforts OpenCode lists for it: GPT-6 Astra and GPT-6.1 Sol accept `low`, `medium`, `high`, `xhigh`, and `max`; GPT-6 Luna, GPT-6 Sol, and the GPT-5.6 rows accept `none`, `low`, `medium`, `high`, `xhigh`, and `max`. No model accepts `ultra`. A saved route set to `ultra` keeps running as `max`, `litopencode doctor` warns about it, and the next install rewrites it to `max` with a one-line notice after copying the file to `litopencode.json.litopencode-backup`. Astra has no invented `-fast` alias. No-selection installs preserve user route bytes but fail before success output when a managed route is unsafe. Other model IDs remain user-owned custom routes. Existing host entries named `momus` or `litwork-reviewer` preserve explicit routes and receive Astra/xhigh only when route fields are absent. LitOpenCode does not expose Hermes-specific per-subagent overrides.
27
27
  - Use `litopencode install --permission-prompt`, `--permission-mode balanced`, or `--yolo` only when you explicitly want LitOpenCode to reduce OpenCode approval prompts. Default installs do not relax permissions. Balanced mode allows routine automation while keeping dangerous bash patterns on ask; YOLO mode writes `permissionMode: "yolo"` and the plugin config hook allows OpenCode `bash`, `edit`, `webfetch`, and `external_directory` globally. In both balanced and YOLO modes, `task` delegation is granted only to execution-capable primary agents; subagents and `lit-plan` stay task-denied so delegation never recurses and the planner cannot select an implementation child. Existing global string/object and per-primary task policies are preserved unless they would weaken those hard deny guards; absent global task policy is not invented, and the runtime lineage guard blocks child task calls even under an explicit global allow. `lit-plan` remains agent-level edit/bash/task `deny`.
28
28
  - Restart OpenCode after changing `permissionMode`; OpenCode loads config at startup and running sessions keep the already-loaded permission policy.
29
29
  - Planning routes now produce proportionate objective-achievable checklists rather than relying on a later review pass to discover missing execution detail. Each retained item carries action/output/verification, while risky or multi-stage work receives SDD-like gates only when justified. `/review-work` accepts both draft plans (read-only `PASS`/`ITERATE`/`NEEDS-CONTEXT`) and completed work (the existing five-lane review); it never implements a draft plan.
@@ -89,11 +89,14 @@ CI와 비대화형 터미널에서는 자동 질문을 생략하며, 명시한
89
89
  새로 설치하거나 초기화하면 계획·검토 및 `lit-loop`는 `openai/gpt-6-astra`/`xhigh`,
90
90
  실행·연구 helper는 새 기본값인 `openai/gpt-6-luna`/`max`를 사용합니다. `gpt-6.1-sol`/`xhigh`는
91
91
  코딩 리드 대안입니다. `gpt-6-sol`은 이전 세대로 계속 선택할 수 있어 이 모델을 쓰는 기존 설정도 그대로 동작하며,
92
- 같은 추론 수준을 지원하고 `-fast` 별칭은 없습니다. GPT-6 Astra와 GPT-6 Sol은 `low`, `medium`, `high`, `xhigh`, `max`, `ultra`를
93
- 모두 지원합니다. GPT-6 Luna는 `low`, `medium`, `high`, `xhigh`, `max`를 지원하지만 `ultra`는
94
- 지원하지 않습니다. 이전 세대 `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`는 계속 선택할 수 있고,
95
- 현재 호스트 카탈로그에는 이 모델들의 지원 종료일이 등록되어 있지 않습니다. GPT-5.6 Luna는
96
- `high`와 `max`만 지원하고 `xhigh`는 허용하지 않습니다. 이 제한은 GPT-6 Luna에는 적용되지 않습니다.
92
+ `-fast` 별칭은 없습니다. 모델마다 OpenCode가 그 모델에 내놓는 추론 수준을 그대로 고를 수 있습니다
93
+ (`opencode models openai --verbose`의 `variants`, OpenCode 1.18.35에서 확인). GPT-6 Astra와 GPT-6.1 Sol은
94
+ `low`, `medium`, `high`, `xhigh`, `max`를 지원합니다. GPT-6 Luna, GPT-6 Sol, GPT-5.6 계열은 `none`, `low`,
95
+ `medium`, `high`, `xhigh`, `max`를 지원합니다. `ultra`를 지원하는 모델은 없습니다. `ultra`로 저장된 설정은 `max`로 동작하고, doctor가 `retiredEfforts`에 알려 주며,
96
+ 다음 설치가 백업을 남긴 뒤 `max`로 바꿉니다. 선택 메뉴는 모델을 고른
97
+ 뒤 그 역할의 추론 수준을 묻고, 모델이 지원하는 수준을 모두 보여 주며, 추천 수준에 표시를 붙여 기본값으로
98
+ 둡니다. 지금 설정의 수준이 여전히 지원되면 그 수준이 기본값입니다. 이전 세대 `gpt-5.6-sol`, `gpt-5.6-terra`,
99
+ `gpt-5.6-luna`는 계속 선택할 수 있고, 현재 호스트 카탈로그에는 이 모델들의 지원 종료일이 등록되어 있지 않습니다.
97
100
  일반 설치와
98
101
  업데이트는 이미 설정된 모델을 보존합니다. 명시적인 `--model-prompt`만 관리 모델 키를 다시 씁니다.
99
102
  기존 GPT-5.6 계열의 `-fast` 의미는 유지하지만 `gpt-6-astra-fast` 별칭은 추가하지 않습니다.
@@ -267,7 +270,8 @@ OpenCode 1.18은 `tui.json`이나 `tui.jsonc`의 `plugin` 배열에 적힌 TUI
267
270
  아래를 모두 가로지릅니다. lit을 쓰든 안 쓰든 세션 내내 보이고, 쉴 때나 일할 때나 똑같이 네 줄이라
268
271
  입력창이 움직이지 않습니다.
269
272
  - 위 테두리 왼쪽에 `🔥 LIT · <작업 이름>`, 오른쪽에 단계가 붙습니다(`● WORK`, `⚑ review blocked`,
270
- `✓ COMPLETE`, lit 턴 전에는 `○ IDLE`).
273
+ `✓ COMPLETE`, lit 턴 전과 세션이 턴을 돌리지 않는 동안에는 `○ IDLE`. 이때는 OpenCode의 `session.status`를
274
+ 읽고, 밝게 켜진 단계가 없습니다).
271
275
  - 첫째 줄은 칩과 같은 알약으로 그린 단계 줄 `PLAN ━ WORK ━ REVIEW`, 1/8칸 막대, 목표의
272
276
  `n/m criteria`, 다음 할 일입니다. 경고가 열려 있는 동안에는 `▸ 다음 일` 자리에 `⚑ <내용>`이
273
277
  들어갑니다. 쉬는 세션에서는 흐린 단계 줄 옆에 프로젝트의 마지막 목표를, 목표가 없으면
@@ -362,7 +366,7 @@ OpenCode는 세션이 바쁜 동안 들어온 `summarize` 호출을 줄 세웠
362
366
 
363
367
  ### Doctor
364
368
 
365
- `litopencode doctor`의 출력에 `autoHandoff` 블록이 추가됩니다. 실제로 적용되는 스위치와 퍼센트, 각각의 출처, 상태 파일 경로, 두 변수 이름, 호스트의 압축 지점, `warnings`가 들어 있습니다. 호스트 지점은 LitOpenCode가 설치하는 창(372,000토큰, 압축은 334,800토큰, 곧 90%)에서만 알 수 있습니다. 그 지점과 같거나 높은 퍼센트는 OpenCode가 먼저 압축하므로 경고가 납니다. 다른 모델은 창과 여유분을 호스트가 정하므로 doctor가 숫자를 내놓지 않습니다.
369
+ `litopencode doctor`의 출력에 `autoHandoff` 블록이 추가됩니다. 플러그인과 같은 프로젝트(작업 폴더 위의 git 루트, 없으면 그 폴더)에서 읽습니다. 실제로 적용되는 스위치와 퍼센트, 각각의 출처, 프로젝트, 상태 파일 경로, 두 변수 이름, 호스트의 압축 지점, `warnings`가 들어 있습니다. 호스트 지점은 LitOpenCode가 설치하는 창(372,000토큰, 압축은 334,800토큰, 곧 90%)에서만 알 수 있습니다. 그 지점과 같거나 높은 퍼센트는 OpenCode가 먼저 압축하므로 경고가 납니다. 다른 모델은 창과 여유분을 호스트가 정하므로 doctor가 숫자를 내놓지 않습니다.
366
370
 
367
371
  ### 디스크에 남는 것
368
372
 
package/docs/reference.md CHANGED
@@ -91,11 +91,15 @@ may use a different provider. New installs default to GPT-6 Astra/xhigh for lead
91
91
  review roles; GPT-6.1 Sol/xhigh (`gpt-6.1-sol`) is the coding-lead alternative, and GPT-6 Luna/max is the helper
92
92
  default for execution, research, and ordinary workers. `gpt-6-sol` stays selectable as the previous
93
93
  generation, so existing configs that name it keep working; it accepts the same efforts and has no `-fast` alias.
94
- GPT-6 Astra and Sol accept `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`. GPT-6 Luna accepts
95
- `low`, `medium`, `high`, `xhigh`, and `max`, but not `ultra`. The previous-generation
94
+ Each model offers exactly the reasoning efforts OpenCode lists for it (the model's `variants` in
95
+ `opencode models openai --verbose`, checked on OpenCode 1.18.35). GPT-6 Astra and GPT-6.1 Sol accept
96
+ `low`, `medium`, `high`, `xhigh`, and `max`. GPT-6 Luna, GPT-6 Sol, and the GPT-5.6 rows accept `none`,
97
+ `low`, `medium`, `high`, `xhigh`, and `max`. No model accepts `ultra`; a saved `ultra` route runs as `max`,
98
+ doctor lists it under `retiredEfforts`, and the next install rewrites it to `max` after a backup. After each model the picker
99
+ asks for that role's effort, lists every effort the model accepts, marks the recommended one, and
100
+ defaults to it, or to the current route's effort when that is still offered. The previous-generation
96
101
  `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` remain selectable, and the current host catalog
97
- has no retirement metadata for them. GPT-5.6 Luna accepts only `high` and `max`; `xhigh` remains
98
- unsupported for that model. This previous-generation restriction does not apply to GPT-6 Luna.
102
+ has no retirement metadata for them.
99
103
  xAI rows are the reasoning-capable Grok models from the host catalog, `grok-4.6` first.
100
104
  `--subagent-model <provider/id>` and `--subagent-effort <e>` set the helper route from flags.
101
105
  Legacy OpenAI `<id>-fast` aliases remain accepted and policy-checked as their base ids;
@@ -370,7 +374,8 @@ What it draws:
370
374
  session and the sidebar alike. It is on screen for the whole session, lit or not, and keeps the
371
375
  same four rows idle or working, so the prompt never moves:
372
376
  - the top border carries `🔥 LIT · <discipline>` on the left and the phase on the right
373
- (`● WORK`, `⚑ review blocked`, `✓ COMPLETE`, or `○ IDLE` before any lit turn);
377
+ (`● WORK`, `⚑ review blocked`, `✓ COMPLETE`, or `○ IDLE` before any lit turn and whenever the
378
+ session is not running a turn, read from OpenCode's own `session.status`; at rest no step is lit);
374
379
  - row one is the phase track `PLAN ━ WORK ━ REVIEW` in the chip's pills, an eighth-block bar,
375
380
  `n/m criteria` for the goal and the next task. While a warning is open, `⚑ <text>` takes the
376
381
  place of `▸ next`. An idle session shows the dim track with the project's last goal, or
@@ -471,7 +476,7 @@ The order is environment, then saved setting, then config file, then the default
471
476
 
472
477
  ### Doctor
473
478
 
474
- `litopencode doctor` adds an `autoHandoff` block: the effective switch and percent, where each came from, the state file path, the two variable names, the host compaction point, and `warnings`. The host point is known for the window LitOpenCode installs (372,000 tokens with compaction at 334,800, which is 90%). A percent at or above it produces a warning, because OpenCode would compact first. For other models the host decides the window and the reserve, so doctor reports no number.
479
+ `litopencode doctor` adds an `autoHandoff` block, read from the same project the plugin uses (the git root above the working directory, or the directory itself): the effective switch and percent, where each came from, the project, the state file path, the two variable names, the host compaction point, and `warnings`. The host point is known for the window LitOpenCode installs (372,000 tokens with compaction at 334,800, which is 90%). A percent at or above it produces a warning, because OpenCode would compact first. For other models the host decides the window and the reserve, so doctor reports no number.
475
480
 
476
481
  ### What is written
477
482
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/litopencode",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "Workflow plugin for OpenCode: add lit to a prompt and the work is planned, built, checked and written down so the next session can pick it up.",
5
5
  "keywords": [
6
6
  "opencode",