@oh-my-pi/pi-coding-agent 15.7.1 → 15.7.2
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 +20 -0
- package/dist/types/auto-thinking/classifier.d.ts +35 -0
- package/dist/types/config/settings-schema.d.ts +24 -4
- package/dist/types/edit/hashline/diff.d.ts +6 -0
- package/dist/types/modes/components/model-selector.d.ts +3 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/sdk.d.ts +2 -1
- package/dist/types/session/agent-session.d.ts +22 -9
- package/dist/types/thinking.d.ts +39 -1
- package/dist/types/tiny/device.d.ts +3 -3
- package/dist/types/tiny/models.d.ts +19 -0
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +180 -0
- package/src/config/settings-schema.ts +24 -4
- package/src/edit/hashline/diff.ts +10 -2
- package/src/edit/streaming.ts +17 -6
- package/src/eval/__tests__/shared-executors.test.ts +32 -0
- package/src/eval/js/shared/local-module-loader.ts +75 -10
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +6 -1
- package/src/modes/acp/acp-agent.ts +13 -3
- package/src/modes/components/footer.ts +10 -3
- package/src/modes/components/model-selector.ts +20 -11
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/settings-selector.ts +4 -1
- package/src/modes/components/status-line/segments.ts +13 -5
- package/src/modes/controllers/event-controller.ts +5 -1
- package/src/modes/controllers/selector-controller.ts +20 -6
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +12 -0
- package/src/sdk.ts +25 -7
- package/src/session/agent-session.ts +193 -32
- package/src/thinking.ts +73 -1
- package/src/tiny/device.ts +4 -10
- package/src/tiny/models.ts +24 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { TASK_SIMPLE_MODES } from "../task/simple-mode";
|
|
3
|
-
import { getThinkingLevelMetadata } from "../thinking";
|
|
3
|
+
import { AUTO_THINKING, getConfiguredThinkingLevelMetadata, getThinkingLevelMetadata } from "../thinking";
|
|
4
4
|
import {
|
|
5
5
|
TINY_MODEL_DEVICE_DEFAULT,
|
|
6
6
|
TINY_MODEL_DEVICE_SETTING_OPTIONS,
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
TINY_MODEL_DTYPE_SETTING_VALUES,
|
|
13
13
|
} from "../tiny/dtype";
|
|
14
14
|
import {
|
|
15
|
+
AUTO_THINKING_MODEL_OPTIONS,
|
|
16
|
+
AUTO_THINKING_MODEL_VALUES,
|
|
17
|
+
ONLINE_AUTO_THINKING_MODEL_KEY,
|
|
15
18
|
ONLINE_MEMORY_MODEL_KEY,
|
|
16
19
|
ONLINE_TINY_TITLE_MODEL_KEY,
|
|
17
20
|
TINY_MEMORY_MODEL_OPTIONS,
|
|
@@ -671,13 +674,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
671
674
|
// Reasoning and prompts
|
|
672
675
|
defaultThinkingLevel: {
|
|
673
676
|
type: "enum",
|
|
674
|
-
values: THINKING_EFFORTS,
|
|
677
|
+
values: [...THINKING_EFFORTS, AUTO_THINKING],
|
|
675
678
|
default: "high",
|
|
676
679
|
ui: {
|
|
677
680
|
tab: "model",
|
|
678
681
|
label: "Thinking Level",
|
|
679
682
|
description: "Reasoning depth for thinking-capable models",
|
|
680
|
-
options: [
|
|
683
|
+
options: [
|
|
684
|
+
getConfiguredThinkingLevelMetadata(AUTO_THINKING),
|
|
685
|
+
...THINKING_EFFORTS.map(getThinkingLevelMetadata),
|
|
686
|
+
],
|
|
681
687
|
},
|
|
682
688
|
},
|
|
683
689
|
|
|
@@ -2954,7 +2960,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
2954
2960
|
tab: "providers",
|
|
2955
2961
|
label: "Tiny Model Device",
|
|
2956
2962
|
description:
|
|
2957
|
-
"ONNX execution provider for local tiny models (titles + memory). Default
|
|
2963
|
+
"ONNX execution provider for local tiny models (titles + memory). Default uses CPU-only inference. The PI_TINY_DEVICE env var overrides this.",
|
|
2958
2964
|
options: TINY_MODEL_DEVICE_SETTING_OPTIONS,
|
|
2959
2965
|
},
|
|
2960
2966
|
},
|
|
@@ -2984,6 +2990,20 @@ export const SETTINGS_SCHEMA = {
|
|
|
2984
2990
|
},
|
|
2985
2991
|
},
|
|
2986
2992
|
|
|
2993
|
+
"providers.autoThinkingModel": {
|
|
2994
|
+
type: "enum",
|
|
2995
|
+
values: AUTO_THINKING_MODEL_VALUES,
|
|
2996
|
+
default: ONLINE_AUTO_THINKING_MODEL_KEY,
|
|
2997
|
+
ui: {
|
|
2998
|
+
tab: "model",
|
|
2999
|
+
label: "Auto Thinking Model",
|
|
3000
|
+
description:
|
|
3001
|
+
"Difficulty classifier for the `auto` thinking level: online smol by default, or a local on-device model",
|
|
3002
|
+
condition: "autoThinkingActive",
|
|
3003
|
+
options: AUTO_THINKING_MODEL_OPTIONS,
|
|
3004
|
+
},
|
|
3005
|
+
},
|
|
3006
|
+
|
|
2987
3007
|
"providers.kimiApiFormat": {
|
|
2988
3008
|
type: "enum",
|
|
2989
3009
|
values: ["openai", "anthropic"] as const,
|
|
@@ -31,6 +31,12 @@ export interface HashlineDiffOptions {
|
|
|
31
31
|
* preview path only.
|
|
32
32
|
*/
|
|
33
33
|
streaming?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Skip snapshot-tag validation. Streaming previews use this so transient
|
|
36
|
+
* stale/missing tags do not flash re-read errors while the model is still
|
|
37
|
+
* authoring input; the final apply path still validates through Patcher.
|
|
38
|
+
*/
|
|
39
|
+
skipHashValidation?: boolean;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
async function readSectionText(absolutePath: string, sectionPath: string): Promise<string> {
|
|
@@ -73,8 +79,10 @@ export async function computeHashlineSectionDiff(
|
|
|
73
79
|
const rawContent = await readSectionText(absolutePath, section.path);
|
|
74
80
|
const { text: content } = stripBom(rawContent);
|
|
75
81
|
const normalized = normalizeToLF(content);
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
if (!options.skipHashValidation) {
|
|
83
|
+
const hashError = validateSectionHash(section, absolutePath, normalized, snapshots);
|
|
84
|
+
if (hashError) return { error: hashError };
|
|
85
|
+
}
|
|
78
86
|
const result = options.streaming
|
|
79
87
|
? section.applyPartialTo(normalized, nativeBlockResolver)
|
|
80
88
|
: section.applyTo(normalized, nativeBlockResolver);
|
package/src/edit/streaming.ts
CHANGED
|
@@ -314,8 +314,14 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
314
314
|
},
|
|
315
315
|
async computeDiffPreview(args, ctx) {
|
|
316
316
|
if (typeof args.input !== "string" || args.input.length === 0) return null;
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
// Unlike apply_patch, hashline previews flow through `applyPartialTo`,
|
|
318
|
+
// whose streaming-tolerant parser (`parsePatchStreaming` → `endStreaming`)
|
|
319
|
+
// drops a payload-less trailing op and projects a partially-typed payload
|
|
320
|
+
// line onto the file as it grows. Trimming the trailing partial line here
|
|
321
|
+
// would instead strip the sole payload of a single-op `replace`/`insert`
|
|
322
|
+
// for almost the entire stream, collapsing the preview to "No changes" and
|
|
323
|
+
// rendering a blank box. Feed the raw in-flight text straight through.
|
|
324
|
+
const input = args.input;
|
|
319
325
|
ctx.signal.throwIfAborted();
|
|
320
326
|
|
|
321
327
|
let sections: readonly HashlineInputSection[];
|
|
@@ -347,12 +353,17 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
347
353
|
const section = sectionsToProcess[i];
|
|
348
354
|
const result = await computeHashlineSectionDiff(section, ctx.cwd, ctx.snapshots, {
|
|
349
355
|
streaming: ctx.isStreaming,
|
|
356
|
+
skipHashValidation: ctx.isStreaming === true,
|
|
350
357
|
});
|
|
351
358
|
ctx.signal.throwIfAborted();
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
359
|
+
// Ignore parse/apply errors from the trailing (actively-typed)
|
|
360
|
+
// section while streaming: a mid-typed op may transiently resolve to
|
|
361
|
+
// "No changes" or an out-of-bounds anchor, and surfacing that would
|
|
362
|
+
// wipe the already-stable previews (or, for a lone section, the prior
|
|
363
|
+
// good frame). Returning no entry preserves the last preview. Earlier
|
|
364
|
+
// sections, and every section once args are complete, stay rendered so
|
|
365
|
+
// real errors still reach the model.
|
|
366
|
+
if ((ctx.isStreaming || sectionsToProcess.length > 1) && i === trailingProcessedIndex && "error" in result) {
|
|
356
367
|
continue;
|
|
357
368
|
}
|
|
358
369
|
previews.push(toPerFilePreview(section.path, result));
|
|
@@ -492,6 +492,38 @@ display({"label": "A"})`,
|
|
|
492
492
|
expect(reloaded.output.trim()).toBe("2");
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
+
it("links a cyclic local module graph without crashing", async () => {
|
|
496
|
+
// Regression: the loader used to link()+evaluate() each local module individually
|
|
497
|
+
// inside the recursive linker callback. On any import cycle that re-entered Bun's
|
|
498
|
+
// node:vm linker mid-instantiation and segfaulted the process (SIGTRAP,
|
|
499
|
+
// getImportedModule on a null record) — e.g. `await import("…/edit/streaming.ts")`,
|
|
500
|
+
// whose relative-import subtree is cyclic. The graph must now link in a single pass.
|
|
501
|
+
using tempDir = TempDir.createSync("@omp-eval-js-cycle-");
|
|
502
|
+
const sessionFile = path.join(tempDir.path(), "session.jsonl");
|
|
503
|
+
const sessionId = `js-cycle:${crypto.randomUUID()}`;
|
|
504
|
+
const session = createToolSession(tempDir.path(), sessionFile);
|
|
505
|
+
const alphaPath = path.join(tempDir.path(), "alpha.ts");
|
|
506
|
+
const betaPath = path.join(tempDir.path(), "beta.ts");
|
|
507
|
+
const alphaSpec = JSON.stringify(alphaPath);
|
|
508
|
+
const betaSpec = JSON.stringify(betaPath);
|
|
509
|
+
await Bun.write(
|
|
510
|
+
alphaPath,
|
|
511
|
+
'import { betaName } from "./beta.ts";\nexport const alphaName = "alpha";\nexport function combined() { return alphaName + ":" + betaName; }\n',
|
|
512
|
+
);
|
|
513
|
+
await Bun.write(
|
|
514
|
+
betaPath,
|
|
515
|
+
'import { alphaName } from "./alpha.ts";\nexport const betaName = "beta";\nexport function viaAlpha() { return alphaName; }\n',
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
const result = await executeJs(
|
|
519
|
+
`const a = await import(${alphaSpec});\nconst b = await import(${betaSpec});\nreturn [a.combined(), b.viaAlpha()].join("|");`,
|
|
520
|
+
{ sessionId, session, sessionFile },
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
expect(result.exitCode).toBe(0);
|
|
524
|
+
expect(result.output.trim()).toBe("alpha:beta|alpha");
|
|
525
|
+
});
|
|
526
|
+
|
|
495
527
|
it("loads TypeScript type-only imports in cells and local modules", async () => {
|
|
496
528
|
using tempDir = TempDir.createSync("@omp-eval-js-type-imports-");
|
|
497
529
|
const sessionFile = path.join(tempDir.path(), "session.jsonl");
|
|
@@ -9,6 +9,8 @@ interface LocalModuleEntry {
|
|
|
9
9
|
version: number;
|
|
10
10
|
identifier: string;
|
|
11
11
|
module: vm.SourceTextModule;
|
|
12
|
+
/** Memoized link+evaluate of this module as a graph root; set lazily by `#loadLocalModule`. */
|
|
13
|
+
loaded?: Promise<void>;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export type LocalImportResolution = { mode: "local"; value: unknown } | { mode: "external"; target: string };
|
|
@@ -26,6 +28,7 @@ export class LocalModuleLoader {
|
|
|
26
28
|
#moduleBuilds = new Map<string, Promise<LocalModuleEntry>>();
|
|
27
29
|
#externalModules = new Map<string, Promise<vm.Module>>();
|
|
28
30
|
#requireCache = new Map<string, NodeJS.Require>();
|
|
31
|
+
#modulePaths = new WeakMap<vm.Module, string>();
|
|
29
32
|
|
|
30
33
|
constructor(sessionId: string) {
|
|
31
34
|
this.#context = vm.createContext(globalThis);
|
|
@@ -68,8 +71,8 @@ export class LocalModuleLoader {
|
|
|
68
71
|
async #resolveFromBase(baseDir: string, source: string): Promise<LocalImportResolution> {
|
|
69
72
|
const resolved = resolveImportSpecifier(baseDir, source);
|
|
70
73
|
if (isLocalPathSpecifier(source) && isManagedLocalModulePath(resolved)) {
|
|
71
|
-
const
|
|
72
|
-
return { mode: "local", value:
|
|
74
|
+
const module = await this.#loadLocalModule(resolved);
|
|
75
|
+
return { mode: "local", value: module.namespace };
|
|
73
76
|
}
|
|
74
77
|
return { mode: "external", target: normalizeImportTarget(resolved) };
|
|
75
78
|
}
|
|
@@ -86,6 +89,11 @@ export class LocalModuleLoader {
|
|
|
86
89
|
return await buildPromise;
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
// Construct (parse + register) a local module WITHOUT linking or evaluating it.
|
|
93
|
+
// Linking and evaluation are driven once from the graph root in `#linkAndEvaluate`;
|
|
94
|
+
// doing them per-module inside the recursive linker re-enters Bun's node:vm linker
|
|
95
|
+
// mid-instantiation, which segfaults JSC (getImportedModule on a null record) whenever
|
|
96
|
+
// the local graph contains an import cycle.
|
|
89
97
|
async #buildLocalModule(modulePath: string): Promise<LocalModuleEntry> {
|
|
90
98
|
const rawSource = fs.readFileSync(modulePath, "utf8");
|
|
91
99
|
const stripped = stripTypeScriptSyntax(rawSource, {
|
|
@@ -116,28 +124,85 @@ export class LocalModuleLoader {
|
|
|
116
124
|
(meta as { url?: string; path?: string; dir?: string }).dir = moduleDir;
|
|
117
125
|
},
|
|
118
126
|
importModuleDynamically: async specifier => {
|
|
119
|
-
return await this.#
|
|
127
|
+
return await this.#resolveDynamicImport(modulePath, String(specifier));
|
|
120
128
|
},
|
|
121
129
|
});
|
|
130
|
+
this.#modulePaths.set(module, modulePath);
|
|
122
131
|
const entry: LocalModuleEntry = { version, identifier, module };
|
|
123
132
|
this.#moduleEntries.set(modulePath, entry);
|
|
133
|
+
return entry;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Construct (if needed) then link+evaluate a local module as a graph root, returning
|
|
137
|
+
// the evaluated module. Link and evaluate run exactly once over the whole reachable
|
|
138
|
+
// graph; the static linker only constructs dependencies, letting node:vm instantiate
|
|
139
|
+
// cyclic graphs in a single pass.
|
|
140
|
+
async #loadLocalModule(modulePath: string): Promise<vm.SourceTextModule> {
|
|
141
|
+
const entry = await this.#ensureLocalModule(modulePath);
|
|
142
|
+
entry.loaded ??= this.#linkAndEvaluate(entry, modulePath);
|
|
143
|
+
await entry.loaded;
|
|
144
|
+
return entry.module;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async #linkAndEvaluate(entry: LocalModuleEntry, modulePath: string): Promise<void> {
|
|
148
|
+
const { module } = entry;
|
|
124
149
|
try {
|
|
125
|
-
|
|
126
|
-
await module.evaluate();
|
|
127
|
-
return entry;
|
|
150
|
+
if (module.status === "unlinked") await module.link(this.#linkResolve);
|
|
151
|
+
if (module.status === "linked") await module.evaluate();
|
|
128
152
|
} catch (error) {
|
|
129
|
-
this.#
|
|
153
|
+
this.#invalidateFailedLoad(modulePath);
|
|
130
154
|
throw error;
|
|
131
155
|
}
|
|
156
|
+
if (module.status === "errored") {
|
|
157
|
+
this.#invalidateFailedLoad(modulePath);
|
|
158
|
+
throw module.error;
|
|
159
|
+
}
|
|
132
160
|
}
|
|
133
161
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
162
|
+
// Shared static-link resolver for `module.link()`. node:vm passes the referencing
|
|
163
|
+
// module and reuses this one resolver for the entire graph, so the referrer path is
|
|
164
|
+
// recovered from `#modulePaths`. Local dependencies are constructed but NOT linked or
|
|
165
|
+
// evaluated here (the root drives that); externals are loaded eagerly — they carry no
|
|
166
|
+
// imports and cannot participate in a cycle.
|
|
167
|
+
#linkResolve = async (specifier: string, referencingModule: vm.Module): Promise<vm.Module> => {
|
|
168
|
+
const referrerPath = this.#modulePaths.get(referencingModule);
|
|
169
|
+
if (referrerPath === undefined) {
|
|
170
|
+
throw new Error(`local module loader: unknown referrer while linking "${specifier}"`);
|
|
171
|
+
}
|
|
172
|
+
const resolved = resolveImportSpecifier(path.dirname(referrerPath), specifier);
|
|
137
173
|
if (isLocalPathSpecifier(specifier) && isManagedLocalModulePath(resolved)) {
|
|
138
174
|
return (await this.#ensureLocalModule(resolved)).module;
|
|
139
175
|
}
|
|
140
176
|
return await this.#ensureExternalModule(normalizeImportTarget(resolved));
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Resolver for runtime `import()` inside evaluated module code: the result must be a
|
|
180
|
+
// fully linked+evaluated module, so local targets are loaded as graph roots.
|
|
181
|
+
async #resolveDynamicImport(referrerPath: string, specifier: string): Promise<vm.Module> {
|
|
182
|
+
const resolved = resolveImportSpecifier(path.dirname(referrerPath), specifier);
|
|
183
|
+
if (isLocalPathSpecifier(specifier) && isManagedLocalModulePath(resolved)) {
|
|
184
|
+
return await this.#loadLocalModule(resolved);
|
|
185
|
+
}
|
|
186
|
+
return await this.#ensureExternalModule(normalizeImportTarget(resolved));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// A failed link/evaluate can leave a partial graph cached. Drop every reachable module
|
|
190
|
+
// that is not fully evaluated so the next attempt reconstructs it; fully evaluated
|
|
191
|
+
// modules keep valid namespaces and stay cached.
|
|
192
|
+
#invalidateFailedLoad(rootPath: string): void {
|
|
193
|
+
const stack = [rootPath];
|
|
194
|
+
const seen = new Set<string>();
|
|
195
|
+
while (stack.length > 0) {
|
|
196
|
+
const current = stack.pop();
|
|
197
|
+
if (current === undefined || seen.has(current)) continue;
|
|
198
|
+
seen.add(current);
|
|
199
|
+
const entry = this.#moduleEntries.get(current);
|
|
200
|
+
if (entry && entry.module.status === "evaluated") continue;
|
|
201
|
+
this.#moduleEntries.delete(current);
|
|
202
|
+
this.#moduleBuilds.delete(current);
|
|
203
|
+
const deps = this.#moduleDeps.get(current);
|
|
204
|
+
if (deps) for (const dep of deps) stack.push(dep);
|
|
205
|
+
}
|
|
141
206
|
}
|
|
142
207
|
|
|
143
208
|
async #ensureExternalModule(target: string): Promise<vm.Module> {
|