@litfamily/litopencode 1.0.13 → 1.0.15
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 +11 -0
- package/README-Ko-KR.md +11 -15
- package/README.md +11 -15
- package/dist/activation.d.ts +2 -0
- package/dist/activation.js +20 -0
- package/dist/auto-handoff-hooks.d.ts +31 -0
- package/dist/auto-handoff-hooks.js +313 -0
- package/dist/auto-handoff.d.ts +73 -0
- package/dist/auto-handoff.js +322 -0
- package/dist/cli/doctor.js +21 -1
- package/dist/cli/host-limits.d.ts +5 -0
- package/dist/cli/host-limits.js +12 -0
- package/dist/cli/model-catalog.d.ts +10 -1
- package/dist/cli/model-catalog.js +9 -2
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +22 -0
- package/dist/config-parser.js +23 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +5 -0
- package/dist/features.js +25 -2
- package/dist/index.d.ts +1 -1
- package/dist/model-route-policy.js +2 -2
- package/dist/server.js +12 -0
- package/dist/skills.js +3 -2
- package/dist/state.d.ts +1 -0
- package/dist/state.js +1 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/reference-Ko-KR.md +39 -2
- package/docs/reference.md +39 -2
- package/package.json +1 -1
- package/skills/lit-handoff/SKILL.md +30 -1
- package/tools/check-pack-payload.mjs +2 -2
- package/docs/ab/S11/baseline-desktop.webp +0 -0
- package/docs/ab/S11/baseline-phone.webp +0 -0
- package/docs/ab/S11/lit-desktop.webp +0 -0
- package/docs/ab/S11/lit-phone.webp +0 -0
- package/docs/ab/S3/baseline-desktop.webp +0 -0
- package/docs/ab/S3/baseline-phone.webp +0 -0
- package/docs/ab/S3/lit-desktop.webp +0 -0
- package/docs/ab/S3/lit-phone.webp +0 -0
- package/docs/ab/S4/baseline-desktop.webp +0 -0
- package/docs/ab/S4/baseline-phone.webp +0 -0
- package/docs/ab/S4/lit-desktop.webp +0 -0
- package/docs/ab/S4/lit-phone.webp +0 -0
- package/docs/ab/S5/lit-slides.webp +0 -0
- package/docs/ab/S7/lit-diagram.webp +0 -0
- package/docs/ab/S8/baseline-slides.webp +0 -0
- package/docs/ab/S8/lit-slides.webp +0 -0
- package/docs/ab/S9/lit-pages.webp +0 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export const autoHandoffEnvEnabled = "LITOPENCODE_AUTO_HANDOFF";
|
|
5
|
+
export const autoHandoffEnvPercent = "LITOPENCODE_AUTO_HANDOFF_PERCENT";
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
export function isValidAutoHandoffPercent(value) {
|
|
10
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 99;
|
|
11
|
+
}
|
|
12
|
+
function percentFromText(raw) {
|
|
13
|
+
return /^\d+$/u.test(raw) ? Number(raw) : Number.NaN;
|
|
14
|
+
}
|
|
15
|
+
function parseSwitch(raw) {
|
|
16
|
+
const value = raw?.trim().toLowerCase();
|
|
17
|
+
if (value === undefined || value === "")
|
|
18
|
+
return undefined;
|
|
19
|
+
if (value === "1" || value === "true" || value === "on")
|
|
20
|
+
return true;
|
|
21
|
+
if (value === "0" || value === "false" || value === "off")
|
|
22
|
+
return false;
|
|
23
|
+
return "invalid";
|
|
24
|
+
}
|
|
25
|
+
const sourceLabel = Object.freeze({
|
|
26
|
+
env: autoHandoffEnvPercent,
|
|
27
|
+
command: "The saved auto-handoff setting",
|
|
28
|
+
config: "The autoHandoff.percent value in the config file"
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* Environment beats the saved command setting, which beats the config file, which beats the default
|
|
32
|
+
* (OFF, no percent). A percent that is not a whole number from 1 to 99 switches the feature OFF and
|
|
33
|
+
* says which source carried it; nothing falls back to a quieter source behind the user's back.
|
|
34
|
+
*/
|
|
35
|
+
export function resolveAutoHandoff(input) {
|
|
36
|
+
const warnings = [];
|
|
37
|
+
if (input.stateProblem !== undefined)
|
|
38
|
+
warnings.push(input.stateProblem);
|
|
39
|
+
let invalid = false;
|
|
40
|
+
let enabled = input.config.enabled;
|
|
41
|
+
let enabledSource = input.config.enabled ? "config" : "default";
|
|
42
|
+
if (input.state !== undefined) {
|
|
43
|
+
enabled = input.state.enabled;
|
|
44
|
+
enabledSource = "command";
|
|
45
|
+
}
|
|
46
|
+
const envSwitch = parseSwitch(input.env[autoHandoffEnvEnabled]);
|
|
47
|
+
if (envSwitch === "invalid") {
|
|
48
|
+
warnings.push(`${autoHandoffEnvEnabled} must be 1 or 0 (on, off, true and false also work). Auto-handoff stays OFF.`);
|
|
49
|
+
invalid = true;
|
|
50
|
+
}
|
|
51
|
+
else if (envSwitch !== undefined) {
|
|
52
|
+
enabled = envSwitch;
|
|
53
|
+
enabledSource = "env";
|
|
54
|
+
}
|
|
55
|
+
const envPercentRaw = input.env[autoHandoffEnvPercent]?.trim() ?? "";
|
|
56
|
+
const candidates = [];
|
|
57
|
+
if (envPercentRaw !== "")
|
|
58
|
+
candidates.push({ source: "env", value: envPercentRaw });
|
|
59
|
+
if (input.state?.percent !== undefined && input.state.percent !== null) {
|
|
60
|
+
candidates.push({ source: "command", value: input.state.percent });
|
|
61
|
+
}
|
|
62
|
+
if (input.config.percent !== null)
|
|
63
|
+
candidates.push({ source: "config", value: input.config.percent });
|
|
64
|
+
let percent = null;
|
|
65
|
+
let percentSource = "none";
|
|
66
|
+
const first = candidates[0];
|
|
67
|
+
if (first !== undefined) {
|
|
68
|
+
const numeric = typeof first.value === "string" ? percentFromText(first.value) : first.value;
|
|
69
|
+
if (isValidAutoHandoffPercent(numeric)) {
|
|
70
|
+
percent = numeric;
|
|
71
|
+
percentSource = first.source;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
warnings.push(`${sourceLabel[first.source]} is ${String(first.value)}, which is not a whole number from 1 to 99. Auto-handoff stays OFF.`);
|
|
75
|
+
invalid = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let lastPercent = null;
|
|
79
|
+
for (const candidate of [input.state?.percent, input.config.percent, percentFromText(envPercentRaw)]) {
|
|
80
|
+
if (isValidAutoHandoffPercent(candidate)) {
|
|
81
|
+
lastPercent = candidate;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (enabled && percent === null && !invalid) {
|
|
86
|
+
warnings.push(`Auto-handoff is switched on but no percent is set, so it stays OFF. Set one with lit-handoff auto on <percent> or ${autoHandoffEnvPercent}.`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
enabled: enabled && !invalid && percent !== null,
|
|
90
|
+
percent,
|
|
91
|
+
lastPercent,
|
|
92
|
+
warnings,
|
|
93
|
+
sources: { enabled: enabledSource, percent: percentSource }
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export async function readAutoHandoffState(stateFile) {
|
|
97
|
+
let raw;
|
|
98
|
+
try {
|
|
99
|
+
raw = await fs.readFile(stateFile, "utf8");
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
103
|
+
return {};
|
|
104
|
+
return { problem: `The saved auto-handoff setting at ${stateFile} cannot be read, so it is ignored.` };
|
|
105
|
+
}
|
|
106
|
+
const unreadable = { problem: `The saved auto-handoff setting at ${stateFile} is not valid, so it is ignored.` };
|
|
107
|
+
let parsed;
|
|
108
|
+
try {
|
|
109
|
+
parsed = JSON.parse(raw);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return unreadable;
|
|
113
|
+
}
|
|
114
|
+
if (!isRecord(parsed) || typeof parsed.enabled !== "boolean")
|
|
115
|
+
return unreadable;
|
|
116
|
+
if (Object.keys(parsed).some((key) => key !== "enabled" && key !== "percent"))
|
|
117
|
+
return unreadable;
|
|
118
|
+
const percent = parsed.percent === undefined ? null : parsed.percent;
|
|
119
|
+
if (percent !== null && typeof percent !== "number")
|
|
120
|
+
return unreadable;
|
|
121
|
+
return { state: { enabled: parsed.enabled, percent } };
|
|
122
|
+
}
|
|
123
|
+
export async function writeAutoHandoffState(stateFile, state) {
|
|
124
|
+
await fs.mkdir(path.dirname(stateFile), { recursive: true });
|
|
125
|
+
const tempPath = path.join(path.dirname(stateFile), `.${path.basename(stateFile)}.tmp-${randomUUID()}`);
|
|
126
|
+
try {
|
|
127
|
+
await fs.writeFile(tempPath, `${JSON.stringify({ enabled: state.enabled, percent: state.percent })}\n`, {
|
|
128
|
+
encoding: "utf8",
|
|
129
|
+
flag: "wx",
|
|
130
|
+
mode: 0o600
|
|
131
|
+
});
|
|
132
|
+
await fs.rename(tempPath, stateFile);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
await fs.rm(tempPath, { force: true });
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Slash arguments arrive without the command name (`auto on 70`); a chat message carries it
|
|
141
|
+
* (`lit-handoff auto on 70`) and must be that whole single line, so a sentence that merely mentions
|
|
142
|
+
* the route never changes a setting.
|
|
143
|
+
*/
|
|
144
|
+
export function parseAutoHandoffRoute(text, mode) {
|
|
145
|
+
const trimmed = text.trim();
|
|
146
|
+
if (trimmed === "" || /[\r\n]/u.test(trimmed))
|
|
147
|
+
return undefined;
|
|
148
|
+
let tokens = trimmed.split(/\s+/u);
|
|
149
|
+
if (mode === "chat") {
|
|
150
|
+
if (tokens[0]?.toLowerCase() !== "lit-handoff")
|
|
151
|
+
return undefined;
|
|
152
|
+
tokens = tokens.slice(1);
|
|
153
|
+
}
|
|
154
|
+
if (tokens[0]?.toLowerCase() !== "auto")
|
|
155
|
+
return undefined;
|
|
156
|
+
const action = tokens[1]?.toLowerCase();
|
|
157
|
+
if (action === "on" && tokens.length <= 3)
|
|
158
|
+
return { action: "on", percent: tokens[2] };
|
|
159
|
+
if ((action === "off" || action === "status") && tokens.length === 2)
|
|
160
|
+
return { action };
|
|
161
|
+
return { action: "invalid" };
|
|
162
|
+
}
|
|
163
|
+
const usageLine = "Use lit-handoff auto on <percent>, lit-handoff auto off, or lit-handoff auto status. The percent is a whole number from 1 to 99.";
|
|
164
|
+
function describe(resolution) {
|
|
165
|
+
const lines = [];
|
|
166
|
+
if (resolution.enabled) {
|
|
167
|
+
lines.push(`Auto-handoff is ON at ${resolution.percent}%. When a conversation reaches ${resolution.percent}% of the model's context window, LitOpenCode asks the model to save a handoff, compacts the conversation, and loads the handoff back.`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
lines.push(resolution.percent === null
|
|
171
|
+
? "Auto-handoff is OFF."
|
|
172
|
+
: `Auto-handoff is OFF. The last percent, ${resolution.percent}%, is kept for the next time you switch it on.`);
|
|
173
|
+
}
|
|
174
|
+
return lines.join(" ");
|
|
175
|
+
}
|
|
176
|
+
function sourceName(field, source) {
|
|
177
|
+
switch (source) {
|
|
178
|
+
case "env": return `the environment variable ${field === "enabled" ? autoHandoffEnvEnabled : autoHandoffEnvPercent}`;
|
|
179
|
+
case "command": return "the saved setting";
|
|
180
|
+
case "config": return "the config file";
|
|
181
|
+
case "default": return "the default";
|
|
182
|
+
case "none": return "nowhere yet";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function overrideNotes(resolution, requested, env) {
|
|
186
|
+
const notes = [];
|
|
187
|
+
if (resolution.sources.enabled === "env" && resolution.enabled !== requested.enabled) {
|
|
188
|
+
notes.push(`${autoHandoffEnvEnabled}=${env[autoHandoffEnvEnabled]?.trim() ?? ""} is set in the environment and takes priority over this command. Unset it to use the saved setting.`);
|
|
189
|
+
}
|
|
190
|
+
if (resolution.sources.percent === "env" && requested.percent !== null && resolution.percent !== requested.percent) {
|
|
191
|
+
notes.push(`${autoHandoffEnvPercent}=${env[autoHandoffEnvPercent]?.trim() ?? ""} is set in the environment and takes priority over the percent in this command.`);
|
|
192
|
+
}
|
|
193
|
+
return notes;
|
|
194
|
+
}
|
|
195
|
+
export function autoHandoffSettingText(reply) {
|
|
196
|
+
return [
|
|
197
|
+
"<lit-handoff-auto-setting>",
|
|
198
|
+
reply,
|
|
199
|
+
"Tell the user this result in one or two plain sentences. Do not write a handoff now.",
|
|
200
|
+
"</lit-handoff-auto-setting>"
|
|
201
|
+
].join("\n");
|
|
202
|
+
}
|
|
203
|
+
export function createAutoHandoffSettings(options) {
|
|
204
|
+
const currentEnv = () => options.env ?? process.env;
|
|
205
|
+
const resolve = async () => {
|
|
206
|
+
const { state, problem } = await readAutoHandoffState(options.stateFile);
|
|
207
|
+
return resolveAutoHandoff({
|
|
208
|
+
config: options.config,
|
|
209
|
+
state,
|
|
210
|
+
env: currentEnv(),
|
|
211
|
+
...(problem === undefined ? {} : { stateProblem: problem })
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
const route = async (text, mode) => {
|
|
215
|
+
const parsed = parseAutoHandoffRoute(text, mode);
|
|
216
|
+
if (parsed === undefined)
|
|
217
|
+
return undefined;
|
|
218
|
+
if (parsed.action === "invalid")
|
|
219
|
+
return usageLine;
|
|
220
|
+
const before = await resolve();
|
|
221
|
+
if (parsed.action === "status") {
|
|
222
|
+
const sources = `The switch comes from ${sourceName("enabled", before.sources.enabled)} and the percent from ${sourceName("percent", before.sources.percent)}.`;
|
|
223
|
+
return [
|
|
224
|
+
describe(before),
|
|
225
|
+
...before.warnings,
|
|
226
|
+
sources,
|
|
227
|
+
"Change it with lit-handoff auto on <percent> or lit-handoff auto off. Run litopencode doctor to see how the percent compares with OpenCode's own compaction point."
|
|
228
|
+
].join(" ");
|
|
229
|
+
}
|
|
230
|
+
let requested;
|
|
231
|
+
if (parsed.action === "off") {
|
|
232
|
+
requested = { enabled: false, percent: before.lastPercent };
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
let percent;
|
|
236
|
+
if (parsed.percent === undefined) {
|
|
237
|
+
percent = before.lastPercent;
|
|
238
|
+
if (percent === null) {
|
|
239
|
+
return "Auto-handoff stays OFF because no percent has been set yet. Tell me the percent to use, a whole number from 1 to 99, for example lit-handoff auto on 70.";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
const match = /^(\d{1,3})%?$/u.exec(parsed.percent);
|
|
244
|
+
percent = match === null ? Number.NaN : Number(match[1]);
|
|
245
|
+
if (!isValidAutoHandoffPercent(percent)) {
|
|
246
|
+
return `${parsed.percent} is not a usable percent. Use a whole number from 1 to 99, for example lit-handoff auto on 70. The saved setting is unchanged.`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
requested = { enabled: true, percent };
|
|
250
|
+
}
|
|
251
|
+
await writeAutoHandoffState(options.stateFile, requested);
|
|
252
|
+
const after = await resolve();
|
|
253
|
+
return [describe(after), ...overrideNotes(after, requested, currentEnv()), ...after.warnings].join(" ");
|
|
254
|
+
};
|
|
255
|
+
return { resolve, route };
|
|
256
|
+
}
|
|
257
|
+
function tokenNumber(value) {
|
|
258
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
259
|
+
}
|
|
260
|
+
/** The host's own count: its total when present, otherwise input + output + cache read + cache write. */
|
|
261
|
+
export function assistantTokenTotal(info) {
|
|
262
|
+
if (!isRecord(info) || !isRecord(info.tokens))
|
|
263
|
+
return 0;
|
|
264
|
+
const tokens = info.tokens;
|
|
265
|
+
const total = tokenNumber(tokens.total);
|
|
266
|
+
if (total !== undefined && total > 0)
|
|
267
|
+
return total;
|
|
268
|
+
const cache = isRecord(tokens.cache) ? tokens.cache : {};
|
|
269
|
+
const parts = [tokens.input, tokens.output, cache.read, cache.write].map(tokenNumber);
|
|
270
|
+
return parts.every((part) => part !== undefined) ? parts.reduce((sum, part) => sum + part, 0) : 0;
|
|
271
|
+
}
|
|
272
|
+
export function autoHandoffMarker(sessionID, firedAt) {
|
|
273
|
+
return `Auto-handoff marker: ${sessionID} ${new Date(firedAt).toISOString()}`;
|
|
274
|
+
}
|
|
275
|
+
export function autoHandoffDirective(percent, marker) {
|
|
276
|
+
return [
|
|
277
|
+
`Automatic handoff: this conversation has reached about ${percent}% of the context window. Write the handoff now.`,
|
|
278
|
+
"Follow the handoff contract in the system message. Put this exact line directly under the title of the handoff file:",
|
|
279
|
+
marker,
|
|
280
|
+
"Keep the rest of the handoff as detailed as the contract asks. When the file is saved, reply with one short sentence that names its path. The conversation is compacted right after your reply, so finish the handoff first."
|
|
281
|
+
].join("\n\n");
|
|
282
|
+
}
|
|
283
|
+
const handoffCandidates = Object.freeze(["HANDOFF.md", path.join(".handoff", "HANDOFF.md")]);
|
|
284
|
+
const handoffSizeLimitBytes = 1_048_576;
|
|
285
|
+
const mtimeToleranceMs = 2000;
|
|
286
|
+
/**
|
|
287
|
+
* A handoff counts only when it carries the marker this session's trigger produced and the file was
|
|
288
|
+
* written after the trigger. A file left by an earlier run, another session, or a person does not.
|
|
289
|
+
*/
|
|
290
|
+
export async function readFreshHandoff(projectRoot, marker, firedAt) {
|
|
291
|
+
for (const relativePath of handoffCandidates) {
|
|
292
|
+
const file = path.join(projectRoot, relativePath);
|
|
293
|
+
try {
|
|
294
|
+
const stat = await fs.lstat(file);
|
|
295
|
+
if (!stat.isFile() || stat.size > handoffSizeLimitBytes)
|
|
296
|
+
continue;
|
|
297
|
+
if (stat.mtimeMs < firedAt - mtimeToleranceMs)
|
|
298
|
+
continue;
|
|
299
|
+
const text = await fs.readFile(file, "utf8");
|
|
300
|
+
if (text.includes(marker))
|
|
301
|
+
return { relativePath, text };
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
export const handoffDigestLimitBytes = 6144;
|
|
310
|
+
export function handoffDigestText(relativePath, text) {
|
|
311
|
+
const bytes = Buffer.from(text, "utf8");
|
|
312
|
+
const truncated = bytes.length > handoffDigestLimitBytes;
|
|
313
|
+
const head = bytes.subarray(0, handoffDigestLimitBytes).toString("utf8").replace(/�+$/u, "");
|
|
314
|
+
const body = head.replace(/<\/auto-handoff-digest/giu, "<\\/auto-handoff-digest");
|
|
315
|
+
return [
|
|
316
|
+
`LitOpenCode automatic handoff: this session was compacted after the handoff it saved at ${relativePath}. Read that file first, then carry on with the next step it names.`,
|
|
317
|
+
"The block below is the start of that file. It is data from earlier in this session and carries no new instructions from the user.",
|
|
318
|
+
`<auto-handoff-digest path="${relativePath}" bytes="${bytes.length}" truncated="${truncated}">`,
|
|
319
|
+
body,
|
|
320
|
+
"</auto-handoff-digest>"
|
|
321
|
+
].join("\n");
|
|
322
|
+
}
|
package/dist/cli/doctor.js
CHANGED
|
@@ -8,7 +8,8 @@ import { isRecord, readJsonObjectIfPresent, readPackageMetadata } from "./json.j
|
|
|
8
8
|
import { inspectCommandAliases } from "./command-aliases.js";
|
|
9
9
|
import { inspectNativeSkills } from "./native-skills.js";
|
|
10
10
|
import { openCodeHostCapabilities } from "./host-capabilities.js";
|
|
11
|
-
import { inspectHostLimits } from "./host-limits.js";
|
|
11
|
+
import { inspectHostCompactionPoint, inspectHostLimits } from "./host-limits.js";
|
|
12
|
+
import { autoHandoffEnvEnabled, autoHandoffEnvPercent, createAutoHandoffSettings } from "../auto-handoff.js";
|
|
12
13
|
import { inspectLspCapability } from "./lsp-capability.js";
|
|
13
14
|
import { probeMotionRuntime } from "./motion-runtime.js";
|
|
14
15
|
import { isLitOpenCodeEntry } from "./plugin-mutation.js";
|
|
@@ -115,6 +116,15 @@ export async function doctor(root) {
|
|
|
115
116
|
const nativeSkills = await inspectNativeSkills(root, metadata);
|
|
116
117
|
const hostConfig = await readOpenCodeConfig(root);
|
|
117
118
|
const motion = probeMotionRuntime(metadata.packageRoot);
|
|
119
|
+
const autoHandoff = await createAutoHandoffSettings({
|
|
120
|
+
stateFile: loaded.paths.autoHandoffFile,
|
|
121
|
+
config: loaded.config.autoHandoff
|
|
122
|
+
}).resolve();
|
|
123
|
+
const hostCompaction = inspectHostCompactionPoint(hostConfig.config);
|
|
124
|
+
const autoHandoffWarnings = [...autoHandoff.warnings];
|
|
125
|
+
if (autoHandoff.enabled && autoHandoff.percent !== null && hostCompaction.percent !== null && autoHandoff.percent >= hostCompaction.percent) {
|
|
126
|
+
autoHandoffWarnings.push(`OpenCode compacts on its own at about ${hostCompaction.percent}% of the context window. Your ${autoHandoff.percent}% is at or above that, so the host compacts first and the handoff has no time to run. Choose a percent below ${hostCompaction.percent}.`);
|
|
127
|
+
}
|
|
118
128
|
return {
|
|
119
129
|
exitCode: 0,
|
|
120
130
|
stdout: JSON.stringify({
|
|
@@ -122,6 +132,16 @@ export async function doctor(root) {
|
|
|
122
132
|
capabilities: openCodeHostCapabilities(),
|
|
123
133
|
hostLimits: { path: hostConfig.path, ...inspectHostLimits(hostConfig.config) },
|
|
124
134
|
lsp: inspectLspCapability(hostConfig.config),
|
|
135
|
+
autoHandoff: {
|
|
136
|
+
enabled: autoHandoff.enabled,
|
|
137
|
+
percent: autoHandoff.percent,
|
|
138
|
+
sources: autoHandoff.sources,
|
|
139
|
+
stateFile: loaded.paths.autoHandoffFile,
|
|
140
|
+
env: { enabled: autoHandoffEnvEnabled, percent: autoHandoffEnvPercent },
|
|
141
|
+
host: { autoCompaction: hostCompaction.autoCompaction, compactionPercent: hostCompaction.percent },
|
|
142
|
+
compaction: "The plugin starts compaction itself after the handoff is saved; the user does not need to run /compact.",
|
|
143
|
+
warnings: autoHandoffWarnings
|
|
144
|
+
},
|
|
125
145
|
motion,
|
|
126
146
|
jevSkillHint: jevSkillHintDoctorLine(process.env),
|
|
127
147
|
config: {
|
|
@@ -18,4 +18,9 @@ export type HostLimitState = {
|
|
|
18
18
|
export declare function applyHostLimits(config: Record<string, unknown> | null): Record<string, unknown>;
|
|
19
19
|
export declare function describeHostLimitMutation(config: Record<string, unknown> | null, reconfigure: boolean): HostLimitMutation;
|
|
20
20
|
export declare function inspectHostLimits(config: Record<string, unknown> | null): HostLimitState;
|
|
21
|
+
export type HostCompactionPoint = {
|
|
22
|
+
readonly autoCompaction: boolean;
|
|
23
|
+
readonly percent: number | null;
|
|
24
|
+
};
|
|
25
|
+
export declare function inspectHostCompactionPoint(config: Record<string, unknown> | null): HostCompactionPoint;
|
|
21
26
|
export declare function ensureHostLimits(configPath: string, config: Record<string, unknown> | null, reconfigure: boolean): Promise<void>;
|
package/dist/cli/host-limits.js
CHANGED
|
@@ -73,6 +73,18 @@ export function inspectHostLimits(config) {
|
|
|
73
73
|
compactionReserveTokens
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
// Only the window LitOpenCode itself configures gives a known percent; for any other model the host
|
|
77
|
+
// decides the window and the reserve, so no number is claimed.
|
|
78
|
+
export function inspectHostCompactionPoint(config) {
|
|
79
|
+
const compaction = config !== null && isRecord(config.compaction) ? config.compaction : {};
|
|
80
|
+
if (compaction.auto === false)
|
|
81
|
+
return { autoCompaction: false, percent: null };
|
|
82
|
+
const configured = inspectHostLimits(config).status === "configured";
|
|
83
|
+
return {
|
|
84
|
+
autoCompaction: true,
|
|
85
|
+
percent: configured ? Math.floor((compactionTriggerTokens * 100) / contextCeilingTokens) : null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
76
88
|
export async function ensureHostLimits(configPath, config, reconfigure) {
|
|
77
89
|
if (!reconfigure)
|
|
78
90
|
return;
|
|
@@ -29,9 +29,12 @@ export declare const defaultOpenAiRoutes: Readonly<{
|
|
|
29
29
|
}>;
|
|
30
30
|
export declare const newGenerationOpenAiModels: Readonly<{
|
|
31
31
|
readonly astra: "gpt-6-astra";
|
|
32
|
-
readonly sol: "gpt-6-sol";
|
|
32
|
+
readonly sol: "gpt-6.1-sol";
|
|
33
33
|
readonly luna: "gpt-6-luna";
|
|
34
34
|
}>;
|
|
35
|
+
export declare const previousGpt6OpenAiModels: Readonly<{
|
|
36
|
+
readonly sol: "gpt-6-sol";
|
|
37
|
+
}>;
|
|
35
38
|
export declare const previousGenerationOpenAiModels: Readonly<{
|
|
36
39
|
readonly sol: "gpt-5.6-sol";
|
|
37
40
|
readonly terra: "gpt-5.6-terra";
|
|
@@ -49,6 +52,12 @@ export declare const openaiProviderModels: Readonly<{
|
|
|
49
52
|
temperature: false;
|
|
50
53
|
tool_call: true;
|
|
51
54
|
}>;
|
|
55
|
+
"gpt-6.1-sol": Readonly<{
|
|
56
|
+
name: "GPT-6.1 Sol";
|
|
57
|
+
reasoning: true;
|
|
58
|
+
temperature: false;
|
|
59
|
+
tool_call: true;
|
|
60
|
+
}>;
|
|
52
61
|
"gpt-6-sol": Readonly<{
|
|
53
62
|
name: "GPT-6 Sol";
|
|
54
63
|
reasoning: true;
|
|
@@ -14,9 +14,14 @@ export const defaultOpenAiRoutes = Object.freeze({
|
|
|
14
14
|
});
|
|
15
15
|
export const newGenerationOpenAiModels = Object.freeze({
|
|
16
16
|
astra: defaultOpenAiRoutes.lead.model,
|
|
17
|
-
sol: "gpt-6-sol",
|
|
17
|
+
sol: "gpt-6.1-sol",
|
|
18
18
|
luna: defaultOpenAiRoutes.helper.model
|
|
19
19
|
});
|
|
20
|
+
// gpt-6-sol stays accepted for existing configs. Like the GPT-5.6 ids it is a
|
|
21
|
+
// previous generation, but it keeps exact-id semantics: no inferred -fast alias.
|
|
22
|
+
export const previousGpt6OpenAiModels = Object.freeze({
|
|
23
|
+
sol: "gpt-6-sol"
|
|
24
|
+
});
|
|
20
25
|
export const previousGenerationOpenAiModels = Object.freeze({
|
|
21
26
|
sol: "gpt-5.6-sol",
|
|
22
27
|
terra: "gpt-5.6-terra",
|
|
@@ -41,6 +46,7 @@ export const openaiRows = [
|
|
|
41
46
|
{ provider: "openai", model: defaultOpenAiRoutes.lead.model, effort: defaultOpenAiRoutes.lead.effort, efforts: installModelEfforts, hint: "frontier reasoning (recommended lead)" },
|
|
42
47
|
{ provider: "openai", model: newGenerationOpenAiModels.sol, effort: "xhigh", efforts: installModelEfforts, hint: "coding lead (recommended alternative)" },
|
|
43
48
|
{ provider: "openai", model: defaultOpenAiRoutes.helper.model, effort: defaultOpenAiRoutes.helper.effort, efforts: installModelEfforts.slice(0, 5), hint: "balanced (recommended helper)" },
|
|
49
|
+
{ provider: "openai", model: previousGpt6OpenAiModels.sol, effort: "xhigh", efforts: installModelEfforts, hint: "coding lead, previous generation" },
|
|
44
50
|
{ provider: "openai", model: previousGenerationOpenAiModels.sol, effort: "xhigh", efforts: ["high", "xhigh", "max"], hint: "deepest reasoning, previous generation" },
|
|
45
51
|
{ provider: "openai", model: previousGenerationOpenAiModels.luna, effort: "max", efforts: ["high", "max"], hint: "balanced, previous generation" },
|
|
46
52
|
{ provider: "openai", model: previousGenerationOpenAiModels.family, effort: "high", efforts: ["high", "xhigh", "max"], hint: "previous generation" },
|
|
@@ -48,7 +54,8 @@ export const openaiRows = [
|
|
|
48
54
|
];
|
|
49
55
|
export const openaiProviderModels = Object.freeze({
|
|
50
56
|
[newGenerationOpenAiModels.astra]: Object.freeze({ name: "GPT-6 Astra", reasoning: true, temperature: false, tool_call: true }),
|
|
51
|
-
[newGenerationOpenAiModels.sol]: Object.freeze({ name: "GPT-6 Sol", reasoning: true, temperature: false, tool_call: true }),
|
|
57
|
+
[newGenerationOpenAiModels.sol]: Object.freeze({ name: "GPT-6.1 Sol", reasoning: true, temperature: false, tool_call: true }),
|
|
58
|
+
[previousGpt6OpenAiModels.sol]: Object.freeze({ name: "GPT-6 Sol", reasoning: true, temperature: false, tool_call: true }),
|
|
52
59
|
[newGenerationOpenAiModels.luna]: Object.freeze({ name: "GPT-6 Luna", reasoning: true, temperature: false, tool_call: true })
|
|
53
60
|
});
|
|
54
61
|
export function openAiModelRow(model) {
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Hooks } from "@opencode-ai/plugin";
|
|
2
2
|
import type { BoundedAuthorityOptions } from "./bounded-authority.ts";
|
|
3
|
+
import { type AutoHandoffRouteMode } from "./auto-handoff.ts";
|
|
3
4
|
export type LitOpenCodeCommandId = "lit" | "lit-loop" | "litwork" | "lit-work" | "start-work" | "review-work" | "lit-plan" | "litgoal" | "lit-goal" | "litresearch" | "lit-research" | "lit-init" | "lit-crucible" | "lit-burnoff-file" | "lit-fetch" | "refactor" | "lit-burnoff" | "lit-code" | "debugging" | "lit-commit" | "lsp" | "lsp-setup" | "rules" | "deep-interview" | "structural-search" | "browser-drive" | "lit-recap" | "lit-comprehend" | "lit-handoff" | "lit-scientific-visualization" | "lit-humanizer" | "autoresearch" | "autoresearch-debug" | "autoresearch-fix" | "autoresearch-learn" | "autoresearch-plan" | "autoresearch-predict" | "autoresearch-reason" | "autoresearch-scenario" | "autoresearch-security" | "autoresearch-ship" | "autoconference" | "autoconference-analyze" | "autoconference-debate" | "autoconference-plan" | "autoconference-resume" | "autoconference-ship" | "autoconference-survey" | "wikify-init" | "wikify-ingest" | "wikify-query" | "wikify-save" | "wikify-lint";
|
|
4
5
|
export type LitOpenCodeCommand = {
|
|
5
6
|
readonly id: LitOpenCodeCommandId;
|
|
@@ -16,6 +17,7 @@ export type CommandActivationOptions = {
|
|
|
16
17
|
readonly commandAliasRoot?: string;
|
|
17
18
|
readonly boundedAuthority?: BoundedAuthorityOptions;
|
|
18
19
|
readonly knowledgeCaptureEnabled?: boolean;
|
|
20
|
+
readonly autoHandoffRoute?: (text: string, mode: AutoHandoffRouteMode) => Promise<string | undefined>;
|
|
19
21
|
readonly onScientificVisualizationActivation?: (sessionID: string) => void;
|
|
20
22
|
readonly onSkillActivation?: (sessionID: string, skillId: string) => void | Promise<void>;
|
|
21
23
|
};
|
package/dist/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ import { comprehendPromptInjection, debuggingPromptInjection, deepInterviewPromp
|
|
|
4
4
|
import { createLitGoalOperations } from "./ledger.js";
|
|
5
5
|
import { commandAliasOwnership } from "./cli/command-alias-ownership.js";
|
|
6
6
|
import { applyTrustedStartWorkDirective, parseStartWorkLifecycleDirective } from "./bounded-authority-hooks.js";
|
|
7
|
+
import { autoHandoffSettingText } from "./auto-handoff.js";
|
|
7
8
|
import { formatResolvedPlanNotice, formatSavedPlanNotice, parsePlanSaveArgument, persistApprovedPlan, resolveLatestDurablePlan } from "./durable-plan.js";
|
|
8
9
|
import { isWorkflowFamilyMode, workflowFamilyCommandDefinitions } from "./workflow-families.js";
|
|
9
10
|
import { captureKnowledgeEvent, inspectKnowledge, queryKnowledge, recoverKnowledge, reviewKnowledgeRecord } from "./knowledge.js";
|
|
@@ -436,6 +437,27 @@ export function createCommandActivationHook(projectRoot, options = {}) {
|
|
|
436
437
|
await commandAliasOwnership(options.commandAliasRoot, input.command.trim().replace(/^\//, "").toLowerCase()) === "preserved")
|
|
437
438
|
return;
|
|
438
439
|
await applyWikifyKnowledgeCommand(projectRoot, input, output, command, options.knowledgeCaptureEnabled);
|
|
440
|
+
if (command.id === "lit-handoff" && options.autoHandoffRoute !== undefined) {
|
|
441
|
+
const reply = await options.autoHandoffRoute(input.arguments, "command");
|
|
442
|
+
if (reply !== undefined) {
|
|
443
|
+
// The installed command file carries the full handoff prompt; a settings change must not send it.
|
|
444
|
+
for (let index = output.parts.length - 1; index >= 0; index -= 1) {
|
|
445
|
+
const part = output.parts[index];
|
|
446
|
+
if (part?.type === "text" && part.text.startsWith(command.banner))
|
|
447
|
+
output.parts.splice(index, 1);
|
|
448
|
+
}
|
|
449
|
+
output.parts.push({
|
|
450
|
+
id: "prt_litopencode_lit_handoff_auto_setting",
|
|
451
|
+
sessionID: input.sessionID,
|
|
452
|
+
messageID: "msg_litopencode_lit_handoff_auto_setting",
|
|
453
|
+
type: "text",
|
|
454
|
+
text: autoHandoffSettingText(reply),
|
|
455
|
+
synthetic: true,
|
|
456
|
+
metadata: { litopencodeAutoHandoff: { surface: "command.execute.before" } }
|
|
457
|
+
});
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
439
461
|
if (command.id === "start-work") {
|
|
440
462
|
if (/^save-plan(?:\s|$)/u.test(input.arguments.trim())) {
|
|
441
463
|
const request = parsePlanSaveArgument(input.arguments);
|
package/dist/config-parser.js
CHANGED
|
@@ -84,6 +84,26 @@ function parseKnowledge(value, filePath) {
|
|
|
84
84
|
const capture = optionalBoolean(value.capture, filePath, "knowledge.capture");
|
|
85
85
|
return capture === undefined ? {} : { capture };
|
|
86
86
|
}
|
|
87
|
+
// The percent is checked for type here and for range where the setting is resolved, so a mistyped
|
|
88
|
+
// number turns the feature OFF with a doctor warning instead of keeping the whole plugin from loading.
|
|
89
|
+
function parseAutoHandoff(value, filePath) {
|
|
90
|
+
if (value === undefined || value === null)
|
|
91
|
+
return undefined;
|
|
92
|
+
if (!isRecord(value)) {
|
|
93
|
+
throw new LitOpenCodeConfigError(`Malformed LitOpenCode config at ${filePath}: autoHandoff must be an object.`);
|
|
94
|
+
}
|
|
95
|
+
for (const key of Object.keys(value)) {
|
|
96
|
+
if (key !== "enabled" && key !== "percent") {
|
|
97
|
+
throw new LitOpenCodeConfigError(`Malformed LitOpenCode config at ${filePath}: autoHandoff.${key} is not a supported setting.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const enabled = optionalBoolean(value.enabled, filePath, "autoHandoff.enabled");
|
|
101
|
+
const percent = value.percent === null ? null : optionalNumber(value.percent, filePath, "autoHandoff.percent");
|
|
102
|
+
return {
|
|
103
|
+
...(enabled === undefined ? {} : { enabled }),
|
|
104
|
+
...(percent === undefined ? {} : { percent })
|
|
105
|
+
};
|
|
106
|
+
}
|
|
87
107
|
function optionalReasoningEffort(value, filePath, field) {
|
|
88
108
|
if (value === undefined || value === null)
|
|
89
109
|
return undefined;
|
|
@@ -249,6 +269,9 @@ function parseConfig(value, filePath) {
|
|
|
249
269
|
if ("knowledge" in value) {
|
|
250
270
|
config.knowledge = parseKnowledge(value.knowledge, filePath);
|
|
251
271
|
}
|
|
272
|
+
if ("autoHandoff" in value) {
|
|
273
|
+
config.autoHandoff = parseAutoHandoff(value.autoHandoff, filePath);
|
|
274
|
+
}
|
|
252
275
|
config.categories = parseNamedMap(value.categories, filePath, "categories", parseCategoryConfig);
|
|
253
276
|
config.agents = parseNamedMap(value.agents, filePath, "agents", parseAgentModelConfig);
|
|
254
277
|
return config;
|
package/dist/config.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export type BoundedAuthorityConfig = {
|
|
|
15
15
|
export type KnowledgeConfig = {
|
|
16
16
|
readonly capture: boolean;
|
|
17
17
|
};
|
|
18
|
+
export type AutoHandoffConfig = {
|
|
19
|
+
readonly enabled: boolean;
|
|
20
|
+
readonly percent: number | null;
|
|
21
|
+
};
|
|
18
22
|
export type JsonScalar = string | number | boolean | null;
|
|
19
23
|
export type JsonObject = {
|
|
20
24
|
readonly [key: string]: JsonValue;
|
|
@@ -48,6 +52,7 @@ export type LitOpenCodeConfig = {
|
|
|
48
52
|
outputStyle: string;
|
|
49
53
|
boundedAuthority: BoundedAuthorityConfig;
|
|
50
54
|
knowledge: KnowledgeConfig;
|
|
55
|
+
autoHandoff: AutoHandoffConfig;
|
|
51
56
|
agents: Record<string, LitOpenCodeAgentModelConfig>;
|
|
52
57
|
categories: Record<string, LitOpenCodeCategoryConfig>;
|
|
53
58
|
};
|
package/dist/config.js
CHANGED
|
@@ -22,6 +22,7 @@ export const defaultConfig = Object.freeze({
|
|
|
22
22
|
maxContextBytes: 65_536
|
|
23
23
|
}),
|
|
24
24
|
knowledge: Object.freeze({ capture: true }),
|
|
25
|
+
autoHandoff: Object.freeze({ enabled: false, percent: null }),
|
|
25
26
|
categories: Object.freeze({
|
|
26
27
|
planning: Object.freeze({
|
|
27
28
|
provider: defaultOpenAiRoutes.lead.provider,
|
|
@@ -107,6 +108,10 @@ export function mergeConfigs(base, override) {
|
|
|
107
108
|
...base.knowledge,
|
|
108
109
|
...(override.knowledge ?? {})
|
|
109
110
|
},
|
|
111
|
+
autoHandoff: {
|
|
112
|
+
...base.autoHandoff,
|
|
113
|
+
...(override.autoHandoff ?? {})
|
|
114
|
+
},
|
|
110
115
|
categories,
|
|
111
116
|
agents
|
|
112
117
|
};
|
package/dist/features.js
CHANGED
|
@@ -1015,7 +1015,25 @@ export const litOpenCodeFeatures = Object.freeze([
|
|
|
1015
1015
|
kind: "hook",
|
|
1016
1016
|
id: "chat.message",
|
|
1017
1017
|
surface: "OpenCode exact bare handoff route",
|
|
1018
|
-
description: "Activates only when an entire non-code chat message is handoff."
|
|
1018
|
+
description: "Activates only when an entire non-code chat message is handoff. The single line lit-handoff auto on <percent>, off or status changes the automatic-handoff setting instead."
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
kind: "hook",
|
|
1022
|
+
id: "event",
|
|
1023
|
+
surface: "OpenCode event hook",
|
|
1024
|
+
description: "Reads assistant token counts, asks for a handoff once the user's percent is reached and the turn is over, starts compaction after a verified handoff, and notes the compaction."
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
kind: "hook",
|
|
1028
|
+
id: "experimental.chat.system.transform",
|
|
1029
|
+
surface: "OpenCode system prompt hook",
|
|
1030
|
+
description: "Hands the verified handoff back once after the compaction; a stale or foreign handoff is refused."
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
kind: "config",
|
|
1034
|
+
id: "autoHandoff",
|
|
1035
|
+
surface: "litopencode.json and .litopencode/config.json",
|
|
1036
|
+
description: "Holds the OFF-by-default switch and the user's percent; LITOPENCODE_AUTO_HANDOFF and LITOPENCODE_AUTO_HANDOFF_PERCENT override it and doctor reports it."
|
|
1019
1037
|
},
|
|
1020
1038
|
{
|
|
1021
1039
|
kind: "config",
|
|
@@ -1024,7 +1042,12 @@ export const litOpenCodeFeatures = Object.freeze([
|
|
|
1024
1042
|
description: "Preserves the adapter and package-vendored exact source, eval, example, and template with hash-aware doctor checks."
|
|
1025
1043
|
}
|
|
1026
1044
|
],
|
|
1027
|
-
verification: [
|
|
1045
|
+
verification: [
|
|
1046
|
+
"node --test test/lit-handoff.test.mjs",
|
|
1047
|
+
"node --test test/auto-handoff.test.mjs",
|
|
1048
|
+
"node --test test/auto-handoff-wiring.test.mjs",
|
|
1049
|
+
"node --test test/runtime-skills.test.mjs"
|
|
1050
|
+
]
|
|
1028
1051
|
},
|
|
1029
1052
|
{
|
|
1030
1053
|
id: "lit-scientific-visualization",
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { LifecycleConflictError, LifecycleSafetyError, boundedAuthoritySchemaVer
|
|
|
3
3
|
export { applyTrustedStartWorkDirective, createBoundedAuthorityEventHook, parseStartWorkLifecycleDirective, type StartWorkLifecycleDirective } from "./bounded-authority-hooks.ts";
|
|
4
4
|
export { appendLedgerEvent, createLitGoalOperations, initializeLitGoal, readLedgerEvents, readLedgerEventsLenient, recoverLedgerTemps, LedgerIoError, LedgerParseError, type JsonValue, type LedgerAppendResult, type LedgerEvent, type LedgerReadDiagnostic, type LedgerRecoveryReport, type LenientLedgerReadResult, type LitGoalInitialization, type LitGoalOperations } from "./ledger.ts";
|
|
5
5
|
export { containsStandaloneLitTrigger, createChatMessageActivationHook, detectChatActivationMode, litLoopPromptInjection, litPlanPromptInjection } from "./activation.ts";
|
|
6
|
-
export { defaultGlobalConfigFile, defaultGlobalConfigJson, effectiveAgentConfig, litOpenCodeConfigSchema, loadConfig, mergeAgentModelConfig, mergeConfigs, readLitOpenCodeConfigFile, type LitOpenCodeAgentModelConfig, type BoundedAuthorityConfig, type KnowledgeConfig, type LitOpenCodeCategoryConfig, type LitOpenCodeConfig, type LitOpenCodeConfigSource, type LitOpenCodePermissionMode, type LoadedConfig, type ReasoningEffort, type TextVerbosity } from "./config.ts";
|
|
6
|
+
export { defaultGlobalConfigFile, defaultGlobalConfigJson, effectiveAgentConfig, litOpenCodeConfigSchema, loadConfig, mergeAgentModelConfig, mergeConfigs, readLitOpenCodeConfigFile, type AutoHandoffConfig, type LitOpenCodeAgentModelConfig, type BoundedAuthorityConfig, type KnowledgeConfig, type LitOpenCodeCategoryConfig, type LitOpenCodeConfig, type LitOpenCodeConfigSource, type LitOpenCodePermissionMode, type LoadedConfig, type ReasoningEffort, type TextVerbosity } from "./config.ts";
|
|
7
7
|
export { captureKnowledgeEvent, inspectKnowledge, knowledgeKinds, knowledgePaths, knowledgeStates, queryKnowledge, readKnowledgeClaims, recoverKnowledge, reviewKnowledgeRecord, KnowledgeStoreError, type KnowledgeCaptureResult, type KnowledgeEvent, type KnowledgeKind, type KnowledgePaths, type KnowledgeQueryResult, type KnowledgeRecord, type KnowledgeReviewResult, type KnowledgeState } from "./knowledge.ts";
|
|
8
8
|
export { litActivationBanner, litOpenCodeCommands } from "./commands.ts";
|
|
9
9
|
export { createToolExecuteAfterHook, createToolExecuteBeforeHook } from "./hooks.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { effectiveAgentConfig } from "./config.js";
|
|
2
|
-
import { defaultOpenAiRoutes, newGenerationOpenAiModels, openAiModelEfforts, previousGenerationOpenAiModels, stripFastSuffix } from "./cli/model-catalog.js";
|
|
2
|
+
import { defaultOpenAiRoutes, newGenerationOpenAiModels, openAiModelEfforts, previousGenerationOpenAiModels, previousGpt6OpenAiModels, stripFastSuffix } from "./cli/model-catalog.js";
|
|
3
3
|
const safeLunaEfforts = new Set(openAiModelEfforts(previousGenerationOpenAiModels.luna));
|
|
4
4
|
const safeTerraEfforts = new Set(openAiModelEfforts(previousGenerationOpenAiModels.terra));
|
|
5
5
|
const safeAstraEfforts = new Set(openAiModelEfforts(defaultOpenAiRoutes.lead.model));
|
|
@@ -53,7 +53,7 @@ export function diagnoseModelRoutes(routes) {
|
|
|
53
53
|
}
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
if (modelId === newGenerationOpenAiModels.sol || modelId === newGenerationOpenAiModels.luna) {
|
|
56
|
+
if (modelId === newGenerationOpenAiModels.sol || modelId === previousGpt6OpenAiModels.sol || modelId === newGenerationOpenAiModels.luna) {
|
|
57
57
|
const supported = new Set(openAiModelEfforts(modelId));
|
|
58
58
|
const variantUnsafe = route.variant !== undefined && !supported.has(route.variant);
|
|
59
59
|
const reasoningUnsafe = route.reasoningEffort !== undefined && !supported.has(route.reasoningEffort);
|