@zigai/pi-mode 0.3.1 → 0.4.1
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/README.md +3 -21
- package/package.json +5 -9
- package/src/index.ts +1 -46
- package/src/constants.ts +0 -22
- package/src/editor.ts +0 -101
- package/src/mode-state.ts +0 -1090
- package/src/settings.ts +0 -323
- package/src/storage.ts +0 -128
- package/src/types.ts +0 -55
package/src/settings.ts
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getAgentDir,
|
|
3
|
-
SettingsManager,
|
|
4
|
-
type ExtensionContext,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
closeSync,
|
|
8
|
-
existsSync,
|
|
9
|
-
mkdirSync,
|
|
10
|
-
openSync,
|
|
11
|
-
readFileSync,
|
|
12
|
-
renameSync,
|
|
13
|
-
statSync,
|
|
14
|
-
unlinkSync,
|
|
15
|
-
writeFileSync,
|
|
16
|
-
} from "node:fs";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
18
|
-
import { Type, type TSchema } from "typebox";
|
|
19
|
-
import { Value } from "typebox/value";
|
|
20
|
-
|
|
21
|
-
export const SHOW_MODE_NAME_SETTINGS_KEY = "modeShowName";
|
|
22
|
-
export const USE_THINKING_BORDER_COLORS_SETTINGS_KEY = "modeUseThinkingBorderColors";
|
|
23
|
-
|
|
24
|
-
const SETTINGS_LOCK_TIMEOUT_MS = 5_000;
|
|
25
|
-
const STALE_SETTINGS_LOCK_MS = 30_000;
|
|
26
|
-
const SettingsObjectSchema = Type.Object({});
|
|
27
|
-
const BooleanSettingSchema = Type.Boolean();
|
|
28
|
-
|
|
29
|
-
type SettingsReadContext = {
|
|
30
|
-
cwd: string;
|
|
31
|
-
projectTrusted: boolean;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
let settingsReadContext: SettingsReadContext | undefined;
|
|
35
|
-
let cachedSettings:
|
|
36
|
-
| {
|
|
37
|
-
mtimeKey: string;
|
|
38
|
-
showModeName: boolean;
|
|
39
|
-
useThinkingBorderColors: boolean;
|
|
40
|
-
}
|
|
41
|
-
| undefined;
|
|
42
|
-
|
|
43
|
-
type ProjectTrustContext = ExtensionContext & {
|
|
44
|
-
isProjectTrusted?: () => boolean;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
function isProjectTrusted(ctx: ExtensionContext): boolean {
|
|
48
|
-
return (ctx as ProjectTrustContext).isProjectTrusted?.() ?? true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function setSettingsContext(ctx: ExtensionContext): void {
|
|
52
|
-
const next: SettingsReadContext = {
|
|
53
|
-
cwd: ctx.cwd,
|
|
54
|
-
projectTrusted: isProjectTrusted(ctx),
|
|
55
|
-
};
|
|
56
|
-
if (
|
|
57
|
-
settingsReadContext?.cwd !== next.cwd ||
|
|
58
|
-
settingsReadContext.projectTrusted !== next.projectTrusted
|
|
59
|
-
) {
|
|
60
|
-
settingsReadContext = next;
|
|
61
|
-
cachedSettings = undefined;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getSettingsPath(): string {
|
|
66
|
-
return join(getAgentDir(), "settings.json");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getProjectSettingsPath(): string | undefined {
|
|
70
|
-
if (settingsReadContext === undefined || !settingsReadContext.projectTrusted) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
return join(settingsReadContext.cwd, ".pi", "settings.json");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function getErrorCode(error: unknown): string | undefined {
|
|
77
|
-
if (!(error instanceof Error)) return undefined;
|
|
78
|
-
const code = (error as NodeJS.ErrnoException).code;
|
|
79
|
-
if (typeof code === "string") return code;
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function throwError(error: unknown): never {
|
|
84
|
-
if (error instanceof Error) throw error;
|
|
85
|
-
throw new Error(String(error));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function sleepSync(ms: number): void {
|
|
89
|
-
const buffer = new SharedArrayBuffer(4);
|
|
90
|
-
Atomics.wait(new Int32Array(buffer), 0, 0, ms);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function parseOptionalBoolean(schema: TSchema, value: unknown): boolean | undefined {
|
|
94
|
-
if (value === undefined) return undefined;
|
|
95
|
-
if (!Value.Check(schema, value)) return undefined;
|
|
96
|
-
const parsed: unknown = Value.Parse(schema, value);
|
|
97
|
-
if (typeof parsed === "boolean") return parsed;
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function withSettingsLock<T>(settingsPath: string, fn: () => T): T {
|
|
102
|
-
const lockPath = `${settingsPath}.lock`;
|
|
103
|
-
mkdirSync(dirname(lockPath), { recursive: true });
|
|
104
|
-
|
|
105
|
-
const start = Date.now();
|
|
106
|
-
while (true) {
|
|
107
|
-
try {
|
|
108
|
-
const fd = openSync(lockPath, "wx");
|
|
109
|
-
try {
|
|
110
|
-
writeFileSync(
|
|
111
|
-
fd,
|
|
112
|
-
`${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() })}\n`,
|
|
113
|
-
"utf8",
|
|
114
|
-
);
|
|
115
|
-
} catch {
|
|
116
|
-
// Ignore best-effort lock metadata.
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
return fn();
|
|
121
|
-
} finally {
|
|
122
|
-
try {
|
|
123
|
-
closeSync(fd);
|
|
124
|
-
} catch {
|
|
125
|
-
// Ignore cleanup failures.
|
|
126
|
-
}
|
|
127
|
-
try {
|
|
128
|
-
unlinkSync(lockPath);
|
|
129
|
-
} catch {
|
|
130
|
-
// Ignore cleanup failures.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
} catch (error: unknown) {
|
|
134
|
-
if (getErrorCode(error) !== "EEXIST") throwError(error);
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const stat = statSync(lockPath);
|
|
138
|
-
if (Date.now() - stat.mtimeMs > STALE_SETTINGS_LOCK_MS) {
|
|
139
|
-
unlinkSync(lockPath);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
} catch {
|
|
143
|
-
// Ignore stale-lock checks.
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (Date.now() - start > SETTINGS_LOCK_TIMEOUT_MS) {
|
|
147
|
-
throw new Error(`Timed out waiting for lock: ${lockPath}`);
|
|
148
|
-
}
|
|
149
|
-
sleepSync(40 + Math.random() * 80);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function atomicWriteUtf8Sync(filePath: string, content: string): void {
|
|
155
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
156
|
-
|
|
157
|
-
const tempPath = join(
|
|
158
|
-
dirname(filePath),
|
|
159
|
-
`.${filePath.split(/[\\/]/).pop() ?? "settings.json"}.tmp.${process.pid}.${Math.random()
|
|
160
|
-
.toString(16)
|
|
161
|
-
.slice(2)}`,
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
writeFileSync(tempPath, content, "utf8");
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
renameSync(tempPath, filePath);
|
|
168
|
-
} catch (error: unknown) {
|
|
169
|
-
const code = getErrorCode(error);
|
|
170
|
-
if (code === "EEXIST" || code === "EPERM") {
|
|
171
|
-
try {
|
|
172
|
-
unlinkSync(filePath);
|
|
173
|
-
} catch {
|
|
174
|
-
// Ignore missing target before retrying the rename.
|
|
175
|
-
}
|
|
176
|
-
renameSync(tempPath, filePath);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
unlinkSync(tempPath);
|
|
181
|
-
} catch {
|
|
182
|
-
// Ignore cleanup failures.
|
|
183
|
-
}
|
|
184
|
-
throwError(error);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function getFileMtimeMs(filePath: string | undefined): number | null {
|
|
189
|
-
if (filePath === undefined) return null;
|
|
190
|
-
try {
|
|
191
|
-
if (!existsSync(filePath)) return null;
|
|
192
|
-
return statSync(filePath).mtimeMs;
|
|
193
|
-
} catch {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function getSettingsMtimeKey(): string {
|
|
199
|
-
return `${getFileMtimeMs(getSettingsPath())}:${getFileMtimeMs(getProjectSettingsPath())}`;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function formatSchemaPath(instancePath: string): string {
|
|
203
|
-
if (instancePath.length === 0) return "root";
|
|
204
|
-
return instancePath
|
|
205
|
-
.slice(1)
|
|
206
|
-
.split("/")
|
|
207
|
-
.map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"))
|
|
208
|
-
.join(".");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function parseSettingsObject(value: unknown, settingsPath: string): Record<string, unknown> {
|
|
212
|
-
const errors = [...Value.Errors(SettingsObjectSchema, value)];
|
|
213
|
-
if (errors.length > 0) {
|
|
214
|
-
const messages = errors
|
|
215
|
-
.slice(0, 5)
|
|
216
|
-
.map((error) => `${formatSchemaPath(error.instancePath)} ${error.message}`);
|
|
217
|
-
let suffix = "";
|
|
218
|
-
if (errors.length > messages.length) {
|
|
219
|
-
suffix = `; and ${errors.length - messages.length} more`;
|
|
220
|
-
}
|
|
221
|
-
throw new Error(
|
|
222
|
-
`${settingsPath} must contain a JSON object: ${messages.join("; ")}${suffix}`,
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
return { ...(Value.Parse(SettingsObjectSchema, value) as Record<string, unknown>) };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function readSettingsObject(options?: { throwOnInvalid?: boolean }): Record<string, unknown> {
|
|
229
|
-
const settingsPath = getSettingsPath();
|
|
230
|
-
try {
|
|
231
|
-
const raw = readFileSync(settingsPath, "utf8");
|
|
232
|
-
return parseSettingsObject(JSON.parse(raw), settingsPath);
|
|
233
|
-
} catch (error: unknown) {
|
|
234
|
-
if (getErrorCode(error) === "ENOENT") return {};
|
|
235
|
-
if (options?.throwOnInvalid === true) throwError(error);
|
|
236
|
-
// Ignore malformed settings files while reading and fall back to defaults.
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return {};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function updateSettingsObject(update: (settings: Record<string, unknown>) => void): void {
|
|
243
|
-
const settingsPath = getSettingsPath();
|
|
244
|
-
withSettingsLock(settingsPath, () => {
|
|
245
|
-
const settings = readSettingsObject({ throwOnInvalid: true });
|
|
246
|
-
update(settings);
|
|
247
|
-
atomicWriteUtf8Sync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function applyBooleanSetting(
|
|
252
|
-
settings: Record<string, unknown>,
|
|
253
|
-
key: string,
|
|
254
|
-
fallback: boolean,
|
|
255
|
-
): boolean {
|
|
256
|
-
const parsed = parseOptionalBoolean(BooleanSettingSchema, settings[key]);
|
|
257
|
-
if (parsed === undefined) return fallback;
|
|
258
|
-
return parsed;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function readModeSettings(): {
|
|
262
|
-
showModeName: boolean;
|
|
263
|
-
useThinkingBorderColors: boolean;
|
|
264
|
-
} {
|
|
265
|
-
const mtimeKey = getSettingsMtimeKey();
|
|
266
|
-
if (cachedSettings?.mtimeKey === mtimeKey) {
|
|
267
|
-
return cachedSettings;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const context = settingsReadContext ?? { cwd: process.cwd(), projectTrusted: false };
|
|
271
|
-
const manager = SettingsManager.create(context.cwd, getAgentDir(), {
|
|
272
|
-
projectTrusted: context.projectTrusted,
|
|
273
|
-
});
|
|
274
|
-
let showModeName = false;
|
|
275
|
-
let useThinkingBorderColors = false;
|
|
276
|
-
|
|
277
|
-
const globalSettings = manager.getGlobalSettings() as Record<string, unknown>;
|
|
278
|
-
showModeName = applyBooleanSetting(globalSettings, SHOW_MODE_NAME_SETTINGS_KEY, showModeName);
|
|
279
|
-
useThinkingBorderColors = applyBooleanSetting(
|
|
280
|
-
globalSettings,
|
|
281
|
-
USE_THINKING_BORDER_COLORS_SETTINGS_KEY,
|
|
282
|
-
useThinkingBorderColors,
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
const projectSettings = manager.getProjectSettings() as Record<string, unknown>;
|
|
286
|
-
showModeName = applyBooleanSetting(projectSettings, SHOW_MODE_NAME_SETTINGS_KEY, showModeName);
|
|
287
|
-
useThinkingBorderColors = applyBooleanSetting(
|
|
288
|
-
projectSettings,
|
|
289
|
-
USE_THINKING_BORDER_COLORS_SETTINGS_KEY,
|
|
290
|
-
useThinkingBorderColors,
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
cachedSettings = {
|
|
294
|
-
mtimeKey,
|
|
295
|
-
showModeName,
|
|
296
|
-
useThinkingBorderColors,
|
|
297
|
-
};
|
|
298
|
-
return cachedSettings;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export function shouldShowModeName(): boolean {
|
|
302
|
-
return readModeSettings().showModeName;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function shouldUseThinkingBorderColors(): boolean {
|
|
306
|
-
return readModeSettings().useThinkingBorderColors;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export function setShowModeName(show: boolean): void {
|
|
310
|
-
updateSettingsObject((settings) => {
|
|
311
|
-
settings[SHOW_MODE_NAME_SETTINGS_KEY] = show;
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
cachedSettings = undefined;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function setUseThinkingBorderColors(useThinkingBorderColors: boolean): void {
|
|
318
|
-
updateSettingsObject((settings) => {
|
|
319
|
-
settings[USE_THINKING_BORDER_COLORS_SETTINGS_KEY] = useThinkingBorderColors;
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
cachedSettings = undefined;
|
|
323
|
-
}
|
package/src/storage.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
export function getGlobalAgentDir(): string {
|
|
6
|
-
return getAgentDir();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function getGlobalModesPath(): string {
|
|
10
|
-
return path.join(getGlobalAgentDir(), "modes.json");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getProjectModesPath(cwd: string): string {
|
|
14
|
-
return path.join(cwd, ".pi", "modes.json");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function fileExists(filePath: string): Promise<boolean> {
|
|
18
|
-
try {
|
|
19
|
-
await fs.stat(filePath);
|
|
20
|
-
return true;
|
|
21
|
-
} catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function ensureDirForFile(filePath: string): Promise<void> {
|
|
27
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function getMtimeMs(filePath: string): Promise<number | null> {
|
|
31
|
-
try {
|
|
32
|
-
const stat = await fs.stat(filePath);
|
|
33
|
-
return stat.mtimeMs;
|
|
34
|
-
} catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function sleep(ms: number): Promise<void> {
|
|
40
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getErrorCode(error: unknown): string | undefined {
|
|
44
|
-
if (!(error instanceof Error)) return undefined;
|
|
45
|
-
const code = (error as NodeJS.ErrnoException).code;
|
|
46
|
-
if (typeof code === "string") return code;
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function throwError(error: unknown): never {
|
|
51
|
-
if (error instanceof Error) throw error;
|
|
52
|
-
throw new Error(String(error));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getLockPathForFile(filePath: string): string {
|
|
56
|
-
return `${filePath}.lock`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function withFileLock<T>(filePath: string, fn: () => Promise<T>): Promise<T> {
|
|
60
|
-
const lockPath = getLockPathForFile(filePath);
|
|
61
|
-
await ensureDirForFile(lockPath);
|
|
62
|
-
|
|
63
|
-
const start = Date.now();
|
|
64
|
-
while (true) {
|
|
65
|
-
try {
|
|
66
|
-
const handle = await fs.open(lockPath, "wx");
|
|
67
|
-
try {
|
|
68
|
-
await handle.writeFile(
|
|
69
|
-
JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }) +
|
|
70
|
-
"\n",
|
|
71
|
-
"utf8",
|
|
72
|
-
);
|
|
73
|
-
} catch {
|
|
74
|
-
// ignore best-effort lock metadata
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
return await fn();
|
|
79
|
-
} finally {
|
|
80
|
-
await handle.close().catch(() => {});
|
|
81
|
-
await fs.unlink(lockPath).catch(() => {});
|
|
82
|
-
}
|
|
83
|
-
} catch (error: unknown) {
|
|
84
|
-
if (getErrorCode(error) !== "EEXIST") throwError(error);
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const stat = await fs.stat(lockPath);
|
|
88
|
-
if (Date.now() - stat.mtimeMs > 30_000) {
|
|
89
|
-
await fs.unlink(lockPath);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
} catch {
|
|
93
|
-
// ignore stale-lock checks
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (Date.now() - start > 5_000) {
|
|
97
|
-
throw new Error(`Timed out waiting for lock: ${lockPath}`);
|
|
98
|
-
}
|
|
99
|
-
await sleep(40 + Math.random() * 80);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function atomicWriteUtf8(filePath: string, content: string): Promise<void> {
|
|
105
|
-
await ensureDirForFile(filePath);
|
|
106
|
-
|
|
107
|
-
const dir = path.dirname(filePath);
|
|
108
|
-
const base = path.basename(filePath);
|
|
109
|
-
const tempPath = path.join(
|
|
110
|
-
dir,
|
|
111
|
-
`.${base}.tmp.${process.pid}.${Math.random().toString(16).slice(2)}`,
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
await fs.writeFile(tempPath, content, "utf8");
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
await fs.rename(tempPath, filePath);
|
|
118
|
-
} catch (error: unknown) {
|
|
119
|
-
const code = getErrorCode(error);
|
|
120
|
-
if (code === "EEXIST" || code === "EPERM") {
|
|
121
|
-
await fs.unlink(filePath).catch(() => {});
|
|
122
|
-
await fs.rename(tempPath, filePath);
|
|
123
|
-
} else {
|
|
124
|
-
await fs.unlink(tempPath).catch(() => {});
|
|
125
|
-
throwError(error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
|
|
4
|
-
export type ModeName = string;
|
|
5
|
-
|
|
6
|
-
export type ModeSpec = {
|
|
7
|
-
provider?: string;
|
|
8
|
-
modelId?: string;
|
|
9
|
-
thinkingLevel?: ThinkingLevel;
|
|
10
|
-
/**
|
|
11
|
-
* Optional theme color token to use for the editor border.
|
|
12
|
-
* If unset, the default editor border is used unless thinking-derived
|
|
13
|
-
* border colors are enabled in settings.
|
|
14
|
-
*/
|
|
15
|
-
color?: ThemeColor;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type ModesFile = {
|
|
19
|
-
version: 1;
|
|
20
|
-
currentMode: ModeName;
|
|
21
|
-
modes: Record<ModeName, ModeSpec>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type ModeSpecPatch = {
|
|
25
|
-
provider?: string | null;
|
|
26
|
-
modelId?: string | null;
|
|
27
|
-
thinkingLevel?: ThinkingLevel | null;
|
|
28
|
-
color?: ThemeColor | null;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type ModesPatch = {
|
|
32
|
-
currentMode?: ModeName;
|
|
33
|
-
modes?: Record<ModeName, ModeSpecPatch | null>;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type ModeRuntime = {
|
|
37
|
-
filePath: string;
|
|
38
|
-
fileMtimeMs: number | null;
|
|
39
|
-
/**
|
|
40
|
-
* Snapshot of what we last loaded or synced from disk. Used to compute patches
|
|
41
|
-
* so multiple running pi processes do not clobber each other's edits.
|
|
42
|
-
*/
|
|
43
|
-
baseline: ModesFile | null;
|
|
44
|
-
data: ModesFile;
|
|
45
|
-
/**
|
|
46
|
-
* Last non-overlay mode. Used as cycle base while in the overlay custom mode.
|
|
47
|
-
*/
|
|
48
|
-
lastRealMode: string;
|
|
49
|
-
/**
|
|
50
|
-
* The effective current mode. Can temporarily be the overlay custom mode,
|
|
51
|
-
* which is not persisted and not selectable via /mode.
|
|
52
|
-
*/
|
|
53
|
-
currentMode: string;
|
|
54
|
-
applying: boolean;
|
|
55
|
-
};
|