jeopi-cli 16.4.5 → 16.5.0
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 +10 -0
- package/dist/cli.js +3194 -3193
- package/dist/types/config/settings-schema.d.ts +27 -6
- package/dist/types/session/blob-store.d.ts +10 -1
- package/dist/types/session/exit-diagnostics.d.ts +11 -0
- package/package.json +14 -14
- package/src/cli/config-cli.ts +23 -7
- package/src/config/settings-schema.ts +32 -7
- package/src/config/settings.ts +43 -2
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +1 -0
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +2 -0
- package/src/internal-urls/docs-index.generated.txt +2 -2
- package/src/sdk.ts +30 -3
- package/src/session/agent-session.ts +16 -1
- package/src/session/blob-store.ts +19 -2
- package/src/session/exit-diagnostics.ts +106 -0
|
@@ -73,33 +73,44 @@ interface UiString extends UiBase {
|
|
|
73
73
|
export type AnyUiMetadata = UiBase & {
|
|
74
74
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
75
75
|
};
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Marks a setting whose value is a credential.
|
|
78
|
+
*
|
|
79
|
+
* Lives at the top level rather than inside `ui` so it can also describe a
|
|
80
|
+
* setting the settings panel never shows and therefore cannot carry
|
|
81
|
+
* `ui.secret`. Read it through `isCredential`, which is the single accessor
|
|
82
|
+
* both the CLI and the settings panel consult.
|
|
83
|
+
*/
|
|
84
|
+
interface CredentialMarker {
|
|
85
|
+
credential?: true;
|
|
86
|
+
}
|
|
87
|
+
interface BooleanDef extends CredentialMarker {
|
|
77
88
|
type: "boolean";
|
|
78
89
|
default: boolean | undefined;
|
|
79
90
|
ui?: UiBoolean;
|
|
80
91
|
}
|
|
81
|
-
interface StringDef {
|
|
92
|
+
interface StringDef extends CredentialMarker {
|
|
82
93
|
type: "string";
|
|
83
94
|
default: string | undefined;
|
|
84
95
|
ui?: UiString;
|
|
85
96
|
}
|
|
86
|
-
interface NumberDef {
|
|
97
|
+
interface NumberDef extends CredentialMarker {
|
|
87
98
|
type: "number";
|
|
88
99
|
default: number | undefined;
|
|
89
100
|
ui?: UiNumber;
|
|
90
101
|
}
|
|
91
|
-
interface EnumDef<T extends readonly string[]> {
|
|
102
|
+
interface EnumDef<T extends readonly string[]> extends CredentialMarker {
|
|
92
103
|
type: "enum";
|
|
93
104
|
values: T;
|
|
94
105
|
default: T[number];
|
|
95
106
|
ui?: UiEnum<T>;
|
|
96
107
|
}
|
|
97
|
-
interface ArrayDef<T> {
|
|
108
|
+
interface ArrayDef<T> extends CredentialMarker {
|
|
98
109
|
type: "array";
|
|
99
110
|
default: T[];
|
|
100
111
|
ui?: UiBase;
|
|
101
112
|
}
|
|
102
|
-
interface RecordDef<T> {
|
|
113
|
+
interface RecordDef<T> extends CredentialMarker {
|
|
103
114
|
type: "record";
|
|
104
115
|
default: Record<string, T>;
|
|
105
116
|
ui?: UiBase;
|
|
@@ -127,6 +138,7 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
127
138
|
readonly "auth.broker.token": {
|
|
128
139
|
readonly type: "string";
|
|
129
140
|
readonly default: undefined;
|
|
141
|
+
readonly credential: true;
|
|
130
142
|
};
|
|
131
143
|
readonly autoResume: {
|
|
132
144
|
readonly type: "boolean";
|
|
@@ -5336,6 +5348,7 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
5336
5348
|
readonly "searxng.token": {
|
|
5337
5349
|
readonly type: "string";
|
|
5338
5350
|
readonly default: undefined;
|
|
5351
|
+
readonly credential: true;
|
|
5339
5352
|
};
|
|
5340
5353
|
readonly "searxng.basicUsername": {
|
|
5341
5354
|
readonly type: "string";
|
|
@@ -5344,6 +5357,7 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
5344
5357
|
readonly "searxng.basicPassword": {
|
|
5345
5358
|
readonly type: "string";
|
|
5346
5359
|
readonly default: undefined;
|
|
5360
|
+
readonly credential: true;
|
|
5347
5361
|
};
|
|
5348
5362
|
readonly "searxng.categories": {
|
|
5349
5363
|
readonly type: "string";
|
|
@@ -5400,6 +5414,7 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
5400
5414
|
readonly "dev.autoqaPush.token": {
|
|
5401
5415
|
readonly type: "string";
|
|
5402
5416
|
readonly default: undefined;
|
|
5417
|
+
readonly credential: true;
|
|
5403
5418
|
};
|
|
5404
5419
|
/**
|
|
5405
5420
|
* User decision on sharing automatic `report_tool_issue` grievances.
|
|
@@ -5494,6 +5509,12 @@ export declare function getDefault<P extends SettingPath>(path: P): SettingValue
|
|
|
5494
5509
|
export declare function hasUi(path: SettingPath): boolean;
|
|
5495
5510
|
/** Get UI metadata for a path (undefined if no UI) */
|
|
5496
5511
|
export declare function getUi(path: SettingPath): AnyUiMetadata | undefined;
|
|
5512
|
+
/**
|
|
5513
|
+
* True when a setting holds a credential and must never be printed in a bulk
|
|
5514
|
+
* dump. The single accessor every surface consults, so the CLI and the settings
|
|
5515
|
+
* panel cannot diverge on what counts as a secret.
|
|
5516
|
+
*/
|
|
5517
|
+
export declare function isCredential(path: SettingPath): boolean;
|
|
5497
5518
|
/** Get all paths for a specific tab */
|
|
5498
5519
|
export declare function getPathsForTab(tab: SettingTab): SettingPath[];
|
|
5499
5520
|
/** Get the type of a setting */
|
|
@@ -34,7 +34,16 @@ export declare class BlobStore {
|
|
|
34
34
|
}
|
|
35
35
|
/** Check if a data string is a blob reference. */
|
|
36
36
|
export declare function isBlobRef(data: string): boolean;
|
|
37
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Extract the SHA-256 hash from a blob reference string.
|
|
39
|
+
*
|
|
40
|
+
* Returns null when the string is not a blob ref, or when the suffix is not a
|
|
41
|
+
* canonical 64-char lowercase hex hash. Rejecting non-hash suffixes here is the
|
|
42
|
+
* single choke point that keeps every resolution path confined to the blob dir:
|
|
43
|
+
* `get`/`getSync`/`has` feed this value into `path.join(this.dir, hash)`, so an
|
|
44
|
+
* unvalidated `../` suffix in a crafted session file would otherwise escape the
|
|
45
|
+
* store and read arbitrary files.
|
|
46
|
+
*/
|
|
38
47
|
export declare function parseBlobRef(data: string): string | null;
|
|
39
48
|
/** Identify provider transport image data URLs so persistence can externalize and restore them losslessly. */
|
|
40
49
|
export declare function isImageDataUrl(data: string): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AssistantMessage } from "jeopi-ai";
|
|
1
2
|
import type { SessionEntry } from "./session-entries";
|
|
2
3
|
export declare const TOOL_EXECUTION_START_CUSTOM_TYPE = "tool_execution_start";
|
|
3
4
|
export declare const SESSION_EXIT_CUSTOM_TYPE = "session_exit";
|
|
@@ -35,6 +36,16 @@ export interface SessionExitData {
|
|
|
35
36
|
recordedAt: string;
|
|
36
37
|
pendingToolCalls?: PendingToolCallDiagnostic[];
|
|
37
38
|
}
|
|
39
|
+
export interface AssistantModelMetadata {
|
|
40
|
+
api: AssistantMessage["api"];
|
|
41
|
+
provider: string;
|
|
42
|
+
model: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* createInterruptedTurnAbortMessage returns a terminal assistant record when
|
|
46
|
+
* the latest persisted process exit follows a non-terminal conversation tail.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createInterruptedTurnAbortMessage(entries: readonly SessionEntry[], fallbackModel?: AssistantModelMetadata): AssistantMessage | undefined;
|
|
38
49
|
/**
|
|
39
50
|
* Project full tool-call arguments down to the fields the pending-tool-call
|
|
40
51
|
* resume warning actually renders (`command`/`path`), truncated. Returns
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-cli",
|
|
4
|
-
"version": "16.
|
|
4
|
+
"version": "16.5.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
58
58
|
"@babel/parser": "^7.29.7",
|
|
59
59
|
"@mozilla/readability": "^0.6.0",
|
|
60
|
-
"jeopi-hashline": "16.
|
|
61
|
-
"jeopi-stats": "16.
|
|
62
|
-
"jeopi-agent-core": "16.
|
|
63
|
-
"jeopi-ai": "16.
|
|
64
|
-
"jeopi-catalog": "16.
|
|
65
|
-
"jeopi-mnemopi": "16.
|
|
66
|
-
"jeopi-natives": "16.
|
|
67
|
-
"jeopi-tui": "16.
|
|
68
|
-
"jeopi-utils": "16.
|
|
69
|
-
"jeopi-wire": "16.
|
|
70
|
-
"jeopi-snapcompact": "16.
|
|
60
|
+
"jeopi-hashline": "16.5.0",
|
|
61
|
+
"jeopi-stats": "16.5.0",
|
|
62
|
+
"jeopi-agent-core": "16.5.0",
|
|
63
|
+
"jeopi-ai": "16.5.0",
|
|
64
|
+
"jeopi-catalog": "16.5.0",
|
|
65
|
+
"jeopi-mnemopi": "16.5.0",
|
|
66
|
+
"jeopi-natives": "16.5.0",
|
|
67
|
+
"jeopi-tui": "16.5.0",
|
|
68
|
+
"jeopi-utils": "16.5.0",
|
|
69
|
+
"jeopi-wire": "16.5.0",
|
|
70
|
+
"jeopi-snapcompact": "16.5.0",
|
|
71
71
|
"@opentelemetry/api": "^1.9.1",
|
|
72
72
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
73
73
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@puppeteer/browsers": "^3.0.4",
|
|
78
78
|
"@types/turndown": "5.0.6",
|
|
79
79
|
"@xterm/headless": "^6.0.0",
|
|
80
|
-
"arktype": "
|
|
80
|
+
"arktype": "2.2.3",
|
|
81
81
|
"chalk": "^5.6.2",
|
|
82
82
|
"diff": "^9.0.0",
|
|
83
83
|
"fast-xml-parser": "^5.9.0",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"lru-cache": "11.5.1",
|
|
87
87
|
"mammoth": "^1.12.0",
|
|
88
88
|
"mupdf": "^1.27.0",
|
|
89
|
-
"puppeteer-core": "
|
|
89
|
+
"puppeteer-core": "25.4.0",
|
|
90
90
|
"turndown": "7.2.4",
|
|
91
91
|
"turndown-plugin-gfm": "1.0.2",
|
|
92
92
|
"zod": "^4"
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getEnumValues,
|
|
13
13
|
getType,
|
|
14
14
|
getUi,
|
|
15
|
+
isCredential,
|
|
15
16
|
type SettingPath,
|
|
16
17
|
Settings,
|
|
17
18
|
type SettingValue,
|
|
@@ -50,6 +51,9 @@ type CliSettingDef = {
|
|
|
50
51
|
|
|
51
52
|
const ALL_SETTING_PATHS = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
|
|
52
53
|
|
|
54
|
+
/** Printed instead of a credential value in human output only. */
|
|
55
|
+
const REDACTED = "********";
|
|
56
|
+
|
|
53
57
|
/** Find setting definition by path */
|
|
54
58
|
function findSettingDef(path: string): CliSettingDef | undefined {
|
|
55
59
|
if (!(path in SETTINGS_SCHEMA)) return undefined;
|
|
@@ -278,13 +282,20 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
278
282
|
const defs = ALL_SETTING_PATHS.map(path => findSettingDef(path)).filter((def): def is CliSettingDef => !!def);
|
|
279
283
|
|
|
280
284
|
if (flags.json) {
|
|
281
|
-
|
|
285
|
+
// A redacted entry omits `value` and says so, rather than substituting a
|
|
286
|
+
// placeholder string: a consumer cannot tell a stand-in from a real value
|
|
287
|
+
// and could write it back as the credential.
|
|
288
|
+
//
|
|
289
|
+
// Redaction is driven by the value, not by classification alone. Marking an
|
|
290
|
+
// unset credential as redacted would report every fresh install as having
|
|
291
|
+
// one configured, which leaks the opposite of what redaction is for.
|
|
292
|
+
const result: Record<string, { value?: unknown; redacted?: true; type: string; description: string }> = {};
|
|
282
293
|
for (const def of defs) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
294
|
+
const value = settings.get(def.path);
|
|
295
|
+
result[def.path] =
|
|
296
|
+
isCredential(def.path) && value
|
|
297
|
+
? { redacted: true, type: def.type, description: def.description }
|
|
298
|
+
: { value, type: def.type, description: def.description };
|
|
288
299
|
}
|
|
289
300
|
console.log(JSON.stringify(result, null, 2));
|
|
290
301
|
return;
|
|
@@ -309,8 +320,13 @@ function handleList(flags: { json?: boolean }): void {
|
|
|
309
320
|
for (const group of sortedGroups) {
|
|
310
321
|
console.log(chalk.bold.blue(`[${group}]`));
|
|
311
322
|
for (const def of groups[group]) {
|
|
323
|
+
// `list` dumps every value without anyone asking for a specific
|
|
324
|
+
// credential, so redact here. `get <path>` stays an explicit
|
|
325
|
+
// single-value request and is left alone. An unset or cleared ("")
|
|
326
|
+
// credential keeps its ordinary rendering: masking it would imply one
|
|
327
|
+
// is configured.
|
|
312
328
|
const value = settings.get(def.path);
|
|
313
|
-
const valueStr = formatValue(value);
|
|
329
|
+
const valueStr = isCredential(def.path) && value ? REDACTED : formatValue(value);
|
|
314
330
|
const typeStr = getTypeDisplay(def);
|
|
315
331
|
console.log(` ${chalk.white(def.path)} = ${valueStr} ${chalk.dim(typeStr)}`);
|
|
316
332
|
}
|
|
@@ -219,38 +219,50 @@ export type AnyUiMetadata = UiBase & {
|
|
|
219
219
|
options?: ReadonlyArray<SubmenuOption> | "runtime";
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Marks a setting whose value is a credential.
|
|
224
|
+
*
|
|
225
|
+
* Lives at the top level rather than inside `ui` so it can also describe a
|
|
226
|
+
* setting the settings panel never shows and therefore cannot carry
|
|
227
|
+
* `ui.secret`. Read it through `isCredential`, which is the single accessor
|
|
228
|
+
* both the CLI and the settings panel consult.
|
|
229
|
+
*/
|
|
230
|
+
interface CredentialMarker {
|
|
231
|
+
credential?: true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface BooleanDef extends CredentialMarker {
|
|
223
235
|
type: "boolean";
|
|
224
236
|
default: boolean | undefined;
|
|
225
237
|
ui?: UiBoolean;
|
|
226
238
|
}
|
|
227
239
|
|
|
228
|
-
interface StringDef {
|
|
240
|
+
interface StringDef extends CredentialMarker {
|
|
229
241
|
type: "string";
|
|
230
242
|
default: string | undefined;
|
|
231
243
|
ui?: UiString;
|
|
232
244
|
}
|
|
233
245
|
|
|
234
|
-
interface NumberDef {
|
|
246
|
+
interface NumberDef extends CredentialMarker {
|
|
235
247
|
type: "number";
|
|
236
248
|
default: number | undefined;
|
|
237
249
|
ui?: UiNumber;
|
|
238
250
|
}
|
|
239
251
|
|
|
240
|
-
interface EnumDef<T extends readonly string[]> {
|
|
252
|
+
interface EnumDef<T extends readonly string[]> extends CredentialMarker {
|
|
241
253
|
type: "enum";
|
|
242
254
|
values: T;
|
|
243
255
|
default: T[number];
|
|
244
256
|
ui?: UiEnum<T>;
|
|
245
257
|
}
|
|
246
258
|
|
|
247
|
-
interface ArrayDef<T> {
|
|
259
|
+
interface ArrayDef<T> extends CredentialMarker {
|
|
248
260
|
type: "array";
|
|
249
261
|
default: T[];
|
|
250
262
|
ui?: UiBase;
|
|
251
263
|
}
|
|
252
264
|
|
|
253
|
-
interface RecordDef<T> {
|
|
265
|
+
interface RecordDef<T> extends CredentialMarker {
|
|
254
266
|
type: "record";
|
|
255
267
|
default: Record<string, T>;
|
|
256
268
|
ui?: UiBase;
|
|
@@ -344,7 +356,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
344
356
|
// Env (`JEOPI_AUTH_BROKER_URL` / `JEOPI_AUTH_BROKER_TOKEN`) takes precedence so
|
|
345
357
|
// per-machine overrides remain trivial.
|
|
346
358
|
"auth.broker.url": { type: "string", default: undefined },
|
|
347
|
-
"auth.broker.token": { type: "string", default: undefined },
|
|
359
|
+
"auth.broker.token": { type: "string", default: undefined, credential: true },
|
|
348
360
|
|
|
349
361
|
autoResume: {
|
|
350
362
|
type: "boolean",
|
|
@@ -4889,6 +4901,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
4889
4901
|
"searxng.token": {
|
|
4890
4902
|
type: "string",
|
|
4891
4903
|
default: undefined,
|
|
4904
|
+
credential: true,
|
|
4892
4905
|
},
|
|
4893
4906
|
|
|
4894
4907
|
"searxng.basicUsername": {
|
|
@@ -4899,6 +4912,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
4899
4912
|
"searxng.basicPassword": {
|
|
4900
4913
|
type: "string",
|
|
4901
4914
|
default: undefined,
|
|
4915
|
+
credential: true,
|
|
4902
4916
|
},
|
|
4903
4917
|
|
|
4904
4918
|
"searxng.categories": {
|
|
@@ -4948,6 +4962,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
4948
4962
|
"dev.autoqaPush.token": {
|
|
4949
4963
|
type: "string",
|
|
4950
4964
|
default: undefined,
|
|
4965
|
+
credential: true,
|
|
4951
4966
|
},
|
|
4952
4967
|
|
|
4953
4968
|
/**
|
|
@@ -5036,6 +5051,16 @@ export function getUi(path: SettingPath): AnyUiMetadata | undefined {
|
|
|
5036
5051
|
return "ui" in def ? (def.ui as AnyUiMetadata) : undefined;
|
|
5037
5052
|
}
|
|
5038
5053
|
|
|
5054
|
+
/**
|
|
5055
|
+
* True when a setting holds a credential and must never be printed in a bulk
|
|
5056
|
+
* dump. The single accessor every surface consults, so the CLI and the settings
|
|
5057
|
+
* panel cannot diverge on what counts as a secret.
|
|
5058
|
+
*/
|
|
5059
|
+
export function isCredential(path: SettingPath): boolean {
|
|
5060
|
+
const def = SETTINGS_SCHEMA[path];
|
|
5061
|
+
return "credential" in def && def.credential === true;
|
|
5062
|
+
}
|
|
5063
|
+
|
|
5039
5064
|
/** Get all paths for a specific tab */
|
|
5040
5065
|
export function getPathsForTab(tab: SettingTab): SettingPath[] {
|
|
5041
5066
|
return (Object.keys(SETTINGS_SCHEMA) as SettingPath[]).filter(path => {
|
package/src/config/settings.ts
CHANGED
|
@@ -701,6 +701,46 @@ export class Settings {
|
|
|
701
701
|
}
|
|
702
702
|
}
|
|
703
703
|
|
|
704
|
+
/**
|
|
705
|
+
* Read the config file for the read-modify-write save path.
|
|
706
|
+
*
|
|
707
|
+
* Unlike {@link Settings.#loadYaml}, a present-but-unusable file is NEVER
|
|
708
|
+
* reported as empty settings. `#saveNow` merges the modified keys into
|
|
709
|
+
* whatever this returns and writes the result back, so returning `{}` for a
|
|
710
|
+
* malformed or temporarily unreadable file would replace the user's entire
|
|
711
|
+
* configuration — broker tokens, model roles, provider entries — with the one
|
|
712
|
+
* key that happened to change. A missing file is the only safe empty base.
|
|
713
|
+
*
|
|
714
|
+
* An invalid file is moved aside first so the next save starts from a clean
|
|
715
|
+
* slate instead of failing forever, and the original bytes survive for
|
|
716
|
+
* recovery. Both failure modes throw; the caller re-queues the write.
|
|
717
|
+
*/
|
|
718
|
+
async #loadYamlForWrite(filePath: string): Promise<RawSettings> {
|
|
719
|
+
let content: string;
|
|
720
|
+
try {
|
|
721
|
+
content = await Bun.file(filePath).text();
|
|
722
|
+
} catch (error) {
|
|
723
|
+
if (isEnoent(error)) return {};
|
|
724
|
+
throw new Error(`Failed to read settings config ${filePath}: ${String(error)}`);
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const parsed = YAML.parse(content);
|
|
728
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
729
|
+
return this.#migrateRawSettings(parsed as RawSettings);
|
|
730
|
+
} catch (error) {
|
|
731
|
+
const backupPath = `${filePath}.broken-${Date.now()}-${process.pid}`;
|
|
732
|
+
try {
|
|
733
|
+
await fs.promises.rename(filePath, backupPath);
|
|
734
|
+
} catch (renameError) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
`Settings config is invalid and could not be moved aside: ${filePath}; refusing to overwrite it: ${String(renameError)}`,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
logger.warn("Settings: moved invalid config aside", { path: filePath, backupPath, error: String(error) });
|
|
740
|
+
throw new Error(`Settings config is invalid: ${filePath} (moved to ${backupPath}): ${String(error)}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
704
744
|
async #loadProjectSettings(): Promise<RawSettings> {
|
|
705
745
|
try {
|
|
706
746
|
const result = await loadCapability(settingsCapability.id, { cwd: this.#cwd });
|
|
@@ -1282,8 +1322,9 @@ export class Settings {
|
|
|
1282
1322
|
|
|
1283
1323
|
try {
|
|
1284
1324
|
await withFileLock(configPath, async () => {
|
|
1285
|
-
// Re-read to preserve external changes
|
|
1286
|
-
|
|
1325
|
+
// Re-read to preserve external changes. Never treat an unreadable or
|
|
1326
|
+
// malformed file as empty here — that would overwrite it wholesale.
|
|
1327
|
+
const current = await this.#loadYamlForWrite(configPath);
|
|
1287
1328
|
|
|
1288
1329
|
// Apply only our modified paths
|
|
1289
1330
|
for (const modPath of modifiedPaths) {
|
|
@@ -111,6 +111,7 @@ export const BUNDLED_PI_REGISTRY_KEYS: ReadonlySet<string> = new Set([
|
|
|
111
111
|
"@oh-my-pi/pi-ai/utils/deterministic-id",
|
|
112
112
|
"@oh-my-pi/pi-ai/utils/empty-completion-retry",
|
|
113
113
|
"@oh-my-pi/pi-ai/utils/event-stream",
|
|
114
|
+
"@oh-my-pi/pi-ai/utils/fences",
|
|
114
115
|
"@oh-my-pi/pi-ai/utils/foundry",
|
|
115
116
|
"@oh-my-pi/pi-ai/utils/google-validation",
|
|
116
117
|
"@oh-my-pi/pi-ai/utils/http-inspector",
|
|
@@ -135,6 +135,7 @@ import * as bundledPiAiUtilsBlockSymbols from "jeopi-ai/utils/block-symbols";
|
|
|
135
135
|
import * as bundledPiAiUtilsDeterministicId from "jeopi-ai/utils/deterministic-id";
|
|
136
136
|
import * as bundledPiAiUtilsEmptyCompletionRetry from "jeopi-ai/utils/empty-completion-retry";
|
|
137
137
|
import * as bundledPiAiUtilsEventStream from "jeopi-ai/utils/event-stream";
|
|
138
|
+
import * as bundledPiAiUtilsFences from "jeopi-ai/utils/fences";
|
|
138
139
|
import * as bundledPiAiUtilsFoundry from "jeopi-ai/utils/foundry";
|
|
139
140
|
import * as bundledPiAiUtilsGoogleValidation from "jeopi-ai/utils/google-validation";
|
|
140
141
|
import * as bundledPiAiUtilsHarmonyLeak from "jeopi-ai/utils/harmony-leak";
|
|
@@ -1265,6 +1266,7 @@ export const BUNDLED_PI_REGISTRY: Readonly<Record<string, Readonly<Record<string
|
|
|
1265
1266
|
Record<string, unknown>
|
|
1266
1267
|
>,
|
|
1267
1268
|
"@oh-my-pi/pi-ai/utils/event-stream": bundledPiAiUtilsEventStream as unknown as Readonly<Record<string, unknown>>,
|
|
1269
|
+
"@oh-my-pi/pi-ai/utils/fences": bundledPiAiUtilsFences as unknown as Readonly<Record<string, unknown>>,
|
|
1268
1270
|
"@oh-my-pi/pi-ai/utils/foundry": bundledPiAiUtilsFoundry as unknown as Readonly<Record<string, unknown>>,
|
|
1269
1271
|
"@oh-my-pi/pi-ai/utils/google-validation": bundledPiAiUtilsGoogleValidation as unknown as Readonly<
|
|
1270
1272
|
Record<string, unknown>
|