@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.10
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 +26 -0
- package/dist/types/config/model-registry.d.ts +13 -0
- package/dist/types/config/settings-schema.d.ts +0 -9
- package/dist/types/debug/terminal-info.d.ts +0 -1
- package/dist/types/modes/components/transcript-container.d.ts +12 -26
- package/dist/types/task/discovery.d.ts +1 -2
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +2 -0
- package/package.json +9 -9
- package/src/cli/list-models.ts +5 -11
- package/src/config/model-registry.ts +91 -20
- package/src/config/settings-schema.ts +0 -10
- package/src/debug/terminal-info.ts +0 -3
- package/src/edit/diff.ts +48 -15
- package/src/eval/js/shared/rewrite-imports.ts +9 -1
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/modes/components/model-selector.ts +62 -52
- package/src/modes/components/transcript-container.ts +134 -107
- package/src/modes/controllers/event-controller.ts +0 -45
- package/src/modes/controllers/input-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +0 -4
- package/src/modes/interactive-mode.ts +2 -10
- package/src/prompts/system/system-prompt.md +3 -3
- package/src/prompts/tools/bash.md +3 -3
- package/src/prompts/tools/todo.md +5 -1
- package/src/task/discovery.ts +17 -24
- package/src/tiny/title-client.ts +6 -3
- package/src/tools/todo.ts +16 -7
- package/src/web/search/providers/anthropic.ts +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [15.10.10] - 2026-06-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a read-only `view` op to the `todo` tool that echoes the current list without mutating state, so the agent can recover exact task text instead of guessing it from memory.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Rewrote the bash tool's coreutils guidance (tool prompt and system prompt) around an explicit litmus: pipelines that compute a new fact (`wc -l`, `sort | uniq -c`, `comm`, `diff`) are legitimate bash, while commands that merely move, page, or trim bytes a dedicated tool can fetch remain banned — output trimming destroys data the `artifact://` capture would have saved.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed the model selector dropping an immediate Enter when cached models were available but the selector's offline refresh was still pending.
|
|
18
|
+
- Fixed dynamic `import(...)` inside functions passed to the browser tool's `tab.evaluate`/`page.evaluate` failing with `__omp_import__ is not defined`. The eval/browser JS runtime rewrites dynamic-import callees to the worker-injected `__omp_import__` helper, but puppeteer serializes evaluate callbacks with `Function.prototype.toString()` and re-runs them inside the page, where the helper does not exist. The rewriter now substitutes a guarded shim that falls back to native dynamic import when the helper is absent, so serialized code works in the page realm while in-worker imports keep resolving against the session cwd.
|
|
19
|
+
- Transcript block freezing is now unconditional instead of gated on ED3-risk terminal detection: every finalized block replays its frozen snapshot once it crosses out of the live region, on all terminals including Windows, because the rewritten renderer's committed scrollback is immutable everywhere. Still-mutating blocks (pending tools, streaming messages, async thinking renderers) anchor the live region and keep repainting until they finalize, which structurally fixes stale/duplicated output from late async expansions ([#1823](https://github.com/can1357/oh-my-pi/issues/1823)).
|
|
20
|
+
- Fixed the edit tool's post-edit diff preview occasionally echoing a context line twice with out-of-order numbering. Block-boundary context injection classified space-prefixed diff rows as old-file-only, so an unchanged line sitting in a net-offset region (old N / new N+k) was missing from the new file's visibility window; `findBlockContextLines` then re-surfaced it under its post-edit number and the row was spliced in after the adjacent change run. New-file boundary lines are now translated back to pre-edit numbers (the compact-preview renumbering contract) and merged into a single old-numbered insertion pass — also fixing closers below a net-offset edit being dropped or renumbered incorrectly.
|
|
21
|
+
- Fixed the Anthropic web-search provider claiming the Claude Code identity on API-key requests: the CC billing header + system instruction were injected whenever the model wasn't Haiku 3.5, regardless of auth mode. Injection is now OAuth-gated like the streaming path, and OAuth search requests patch the billing header's `cch` attestation (via `wrapFetchForCch`) instead of shipping the `cch=00000` placeholder.
|
|
22
|
+
- Fixed long streamed content appearing cut off mid-run: scrolled-off rows were erased from the viewport without ever being appended to terminal history. The transcript's commit boundary (`deriveLiveCommitState`) was all-or-nothing per block — one perpetually rewriting row (a task tool's ticking progress tree, per-agent cost/tool counters, spinner stats) suspended scrollback commits for the entire block, so once the block outgrew the viewport its static head (e.g. a task's prompt/context markdown) was neither committed nor on screen until the tool sealed, and was lost outright if the session ended mid-run. A stable-prefix ratchet now promotes leading rows that stayed visibly identical for a full 30-frame window as commit-safe, so the settled head reaches native scrollback while only the genuinely volatile tail stays deferred; a rewrite above the promoted run retreats the boundary and the engine audit recommits (duplication, never loss).
|
|
23
|
+
- Fixed local tiny-title worker stdout/stderr leaking raw native model output such as `</title>` and cache/status lines into the interactive TUI scrollback ([#2206](https://github.com/can1357/oh-my-pi/issues/2206)).
|
|
24
|
+
- Fixed task-agent discovery advertising Claude Code custom agents from `.claude/agents/*.md` as OMP subagents; direct task-agent discovery now only loads OMP-native `.omp` agent roots, while Claude marketplace plugin agents keep their existing provider path ([#2209](https://github.com/can1357/oh-my-pi/issues/2209)).
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- Removed the `clearOnShrink` setting and its `PI_CLEAR_ON_SHRINK` environment variable: the rewritten renderer always clears shrunken rows exactly, so the flicker/perf tradeoff the setting controlled no longer exists. Existing config entries are ignored.
|
|
29
|
+
- Removed the prompt-submit native-scrollback reconciliation checkpoint and the eager streaming render mode from the interactive controllers — the renderer's append-only contract made both obsolete.
|
|
30
|
+
|
|
5
31
|
## [15.10.9] - 2026-06-09
|
|
6
32
|
|
|
7
33
|
### Fixed
|
|
@@ -262,6 +262,11 @@ export interface CanonicalModelQueryOptions {
|
|
|
262
262
|
availableOnly?: boolean;
|
|
263
263
|
candidates?: readonly Model<Api>[];
|
|
264
264
|
}
|
|
265
|
+
/** A canonical record (with query-filtered variants) plus the variant model selected for it. */
|
|
266
|
+
export interface CanonicalModelSelection {
|
|
267
|
+
record: CanonicalModelRecord;
|
|
268
|
+
model: Model<Api>;
|
|
269
|
+
}
|
|
265
270
|
/**
|
|
266
271
|
* Model registry - loads and manages models, resolves API keys via AuthStorage.
|
|
267
272
|
*/
|
|
@@ -306,6 +311,14 @@ export declare class ModelRegistry {
|
|
|
306
311
|
*/
|
|
307
312
|
getAll(): Model<Api>[];
|
|
308
313
|
getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[];
|
|
314
|
+
/**
|
|
315
|
+
* One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
|
|
316
|
+
* record. The per-query state (candidate-selector set, availability memo,
|
|
317
|
+
* provider rank, candidate order) is built once, so the whole catalog
|
|
318
|
+
* resolves in O(records + candidates) instead of O(records × candidates).
|
|
319
|
+
* This is the path the model selector hydrates from synchronously on open.
|
|
320
|
+
*/
|
|
321
|
+
getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[];
|
|
309
322
|
getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[];
|
|
310
323
|
resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined;
|
|
311
324
|
getCanonicalId(model: Model<Api>): string | undefined;
|
|
@@ -716,15 +716,6 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
716
716
|
readonly description: "Show terminal cursor for IME support";
|
|
717
717
|
};
|
|
718
718
|
};
|
|
719
|
-
readonly clearOnShrink: {
|
|
720
|
-
readonly type: "boolean";
|
|
721
|
-
readonly default: false;
|
|
722
|
-
readonly ui: {
|
|
723
|
-
readonly tab: "appearance";
|
|
724
|
-
readonly label: "Clear on Shrink";
|
|
725
|
-
readonly description: "Clear empty rows when content shrinks (may cause flicker)";
|
|
726
|
-
};
|
|
727
|
-
};
|
|
728
719
|
readonly defaultThinkingLevel: {
|
|
729
720
|
readonly type: "enum";
|
|
730
721
|
readonly values: readonly [...import("@oh-my-pi/pi-ai").Effort[], "auto"];
|
|
@@ -1,25 +1,18 @@
|
|
|
1
1
|
import { Container, type NativeScrollbackLiveRegion } from "@oh-my-pi/pi-tui";
|
|
2
2
|
/**
|
|
3
|
-
* Transcript container that
|
|
4
|
-
* the
|
|
5
|
-
*
|
|
3
|
+
* Transcript container that always renders every block's current content and
|
|
4
|
+
* reports the live-region seam (`NativeScrollbackLiveRegion`) that gates the
|
|
5
|
+
* engine's append-only scrollback commits.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* This container provides that guarantee: a block's render is snapshotted while
|
|
17
|
-
* it is the live (bottom-most) block, and once a newer block is appended it
|
|
18
|
-
* replays the snapshot instead of recomputing. Mutations after a block leaves
|
|
19
|
-
* live are intentionally deferred until the next checkpoint {@link thaw} (prompt
|
|
20
|
-
* submit → native-scrollback rebuild), where the whole transcript is replayed
|
|
21
|
-
* and any drift reconciles safely. On terminals that can rebuild history this
|
|
22
|
-
* freezing is unnecessary, so it renders every block live for full fidelity.
|
|
7
|
+
* The engine never rewrites committed history: rows above the seam that have
|
|
8
|
+
* entered the tape keep whatever bytes they were committed with ("let the
|
|
9
|
+
* history be"), while the visible window always repaints from each block's
|
|
10
|
+
* latest render — a late tool result, a post-finalize error pin, or an expand
|
|
11
|
+
* toggle is always reflected on screen. Blocks that are still mutating (an
|
|
12
|
+
* unfinalized tool, a streaming assistant message) stay below the seam so
|
|
13
|
+
* their rows do not enter history while they can still change; a streaming
|
|
14
|
+
* block whose render grows append-only deepens the seam through its settled
|
|
15
|
+
* head so a long reply's scrolled-off rows still reach scrollback mid-stream.
|
|
23
16
|
*/
|
|
24
17
|
export declare class TranscriptContainer extends Container implements NativeScrollbackLiveRegion {
|
|
25
18
|
#private;
|
|
@@ -27,13 +20,6 @@ export declare class TranscriptContainer extends Container implements NativeScro
|
|
|
27
20
|
clear(): void;
|
|
28
21
|
getNativeScrollbackLiveRegionStart(): number | undefined;
|
|
29
22
|
getNativeScrollbackCommitSafeEnd(): number | undefined;
|
|
30
|
-
/**
|
|
31
|
-
* Retire all frozen snapshots so the next render reflects each block's current
|
|
32
|
-
* state. Call at reconciliation checkpoints (prompt submit) where the whole
|
|
33
|
-
* transcript is replayed into native scrollback and any drift a frozen block
|
|
34
|
-
* accumulated is reconciled.
|
|
35
|
-
*/
|
|
36
|
-
thaw(): void;
|
|
37
23
|
render(width: number): string[];
|
|
38
24
|
}
|
|
39
25
|
/**
|
|
@@ -7,8 +7,7 @@ export interface DiscoveryResult {
|
|
|
7
7
|
/**
|
|
8
8
|
* Discover agents from filesystem and merge with bundled agents.
|
|
9
9
|
*
|
|
10
|
-
* Precedence (highest wins): .omp
|
|
11
|
-
*
|
|
10
|
+
* Precedence (highest wins): project .omp, user .omp, Claude plugin agents, then bundled
|
|
12
11
|
* @param cwd - Current working directory for project agent discovery
|
|
13
12
|
*/
|
|
14
13
|
export declare function discoverAgents(cwd: string, home?: string): Promise<DiscoveryResult>;
|
|
@@ -32,7 +32,7 @@ export declare const TINY_WORKER_ARG = "--tiny-worker";
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function tinyWorkerEnvOverlay(env: Record<string, string | undefined>, deviceSetting: string | undefined, dtypeSetting: string | undefined): Record<string, string>;
|
|
34
34
|
interface SpawnedSubprocess {
|
|
35
|
-
proc: Subprocess<"ignore", "
|
|
35
|
+
proc: Subprocess<"ignore", "ignore", "ignore">;
|
|
36
36
|
inbound: Set<(message: TinyTitleWorkerOutbound) => void>;
|
|
37
37
|
errors: Set<(error: Error) => void>;
|
|
38
38
|
/**
|
|
@@ -40,6 +40,7 @@ declare const todoSchema: z.ZodObject<{
|
|
|
40
40
|
note: "note";
|
|
41
41
|
rm: "rm";
|
|
42
42
|
start: "start";
|
|
43
|
+
view: "view";
|
|
43
44
|
}>;
|
|
44
45
|
list: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
45
46
|
phase: z.ZodString;
|
|
@@ -113,6 +114,7 @@ export declare class TodoTool implements AgentTool<typeof todoSchema, TodoToolDe
|
|
|
113
114
|
note: "note";
|
|
114
115
|
rm: "rm";
|
|
115
116
|
start: "start";
|
|
117
|
+
view: "view";
|
|
116
118
|
}>;
|
|
117
119
|
list: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
118
120
|
phase: z.ZodString;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "15.10.
|
|
4
|
+
"version": "15.10.10",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"@agentclientprotocol/sdk": "0.22.1",
|
|
48
48
|
"@babel/parser": "^7.29.7",
|
|
49
49
|
"@mozilla/readability": "^0.6.0",
|
|
50
|
-
"@oh-my-pi/hashline": "15.10.
|
|
51
|
-
"@oh-my-pi/omp-stats": "15.10.
|
|
52
|
-
"@oh-my-pi/pi-agent-core": "15.10.
|
|
53
|
-
"@oh-my-pi/pi-ai": "15.10.
|
|
54
|
-
"@oh-my-pi/pi-mnemopi": "15.10.
|
|
55
|
-
"@oh-my-pi/pi-natives": "15.10.
|
|
56
|
-
"@oh-my-pi/pi-tui": "15.10.
|
|
57
|
-
"@oh-my-pi/pi-utils": "15.10.
|
|
50
|
+
"@oh-my-pi/hashline": "15.10.10",
|
|
51
|
+
"@oh-my-pi/omp-stats": "15.10.10",
|
|
52
|
+
"@oh-my-pi/pi-agent-core": "15.10.10",
|
|
53
|
+
"@oh-my-pi/pi-ai": "15.10.10",
|
|
54
|
+
"@oh-my-pi/pi-mnemopi": "15.10.10",
|
|
55
|
+
"@oh-my-pi/pi-natives": "15.10.10",
|
|
56
|
+
"@oh-my-pi/pi-tui": "15.10.10",
|
|
57
|
+
"@oh-my-pi/pi-utils": "15.10.10",
|
|
58
58
|
"@opentelemetry/api": "^1.9.1",
|
|
59
59
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
60
60
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
package/src/cli/list-models.ts
CHANGED
|
@@ -64,22 +64,16 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const filteredCanonical = modelRegistry
|
|
67
|
-
.
|
|
68
|
-
.map(
|
|
69
|
-
|
|
70
|
-
availableOnly: true,
|
|
71
|
-
candidates: filteredModels,
|
|
72
|
-
});
|
|
73
|
-
if (!selected) return undefined;
|
|
74
|
-
return {
|
|
67
|
+
.getCanonicalModelSelections({ availableOnly: true, candidates: filteredModels })
|
|
68
|
+
.map(
|
|
69
|
+
({ record, model: selected }): CanonicalRow => ({
|
|
75
70
|
canonical: record.id,
|
|
76
71
|
selected: `${selected.provider}/${selected.id}`,
|
|
77
72
|
variants: String(record.variants.length),
|
|
78
73
|
context: formatNumber(selected.contextWindow),
|
|
79
74
|
maxOut: formatNumber(selected.maxTokens),
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.filter((row): row is CanonicalRow => row !== undefined)
|
|
75
|
+
}),
|
|
76
|
+
)
|
|
83
77
|
.sort((left, right) => left.canonical.localeCompare(right.canonical));
|
|
84
78
|
|
|
85
79
|
if (filteredModels.length === 0 && filteredCanonical.length === 0) {
|
|
@@ -428,6 +428,12 @@ export interface CanonicalModelQueryOptions {
|
|
|
428
428
|
candidates?: readonly Model<Api>[];
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
/** A canonical record (with query-filtered variants) plus the variant model selected for it. */
|
|
432
|
+
export interface CanonicalModelSelection {
|
|
433
|
+
record: CanonicalModelRecord;
|
|
434
|
+
model: Model<Api>;
|
|
435
|
+
}
|
|
436
|
+
|
|
431
437
|
/** Result of loading custom models from models.json */
|
|
432
438
|
interface CustomModelsResult {
|
|
433
439
|
models?: CustomModelOverlay[];
|
|
@@ -2217,48 +2223,81 @@ export class ModelRegistry {
|
|
|
2217
2223
|
return this.#models;
|
|
2218
2224
|
}
|
|
2219
2225
|
|
|
2220
|
-
|
|
2226
|
+
/**
|
|
2227
|
+
* Availability predicate with per-provider memoization. Auth lookups
|
|
2228
|
+
* (`authStorage.hasAuth`) and the disabled-provider set are resolved once
|
|
2229
|
+
* per provider instead of once per model, which matters when filtering the
|
|
2230
|
+
* full bundled catalog (thousands of models, ~50 providers).
|
|
2231
|
+
*/
|
|
2232
|
+
#createAvailabilityCheck(): (model: Model<Api>) => boolean {
|
|
2221
2233
|
const disabledProviders = getDisabledProviderIdsFromSettings();
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2234
|
+
const byProvider = new Map<string, boolean>();
|
|
2235
|
+
return model => {
|
|
2236
|
+
let available = byProvider.get(model.provider);
|
|
2237
|
+
if (available === undefined) {
|
|
2238
|
+
available =
|
|
2239
|
+
!disabledProviders.has(model.provider) &&
|
|
2240
|
+
(this.#keylessProviders.has(model.provider) || this.authStorage.hasAuth(model.provider));
|
|
2241
|
+
byProvider.set(model.provider, available);
|
|
2242
|
+
}
|
|
2243
|
+
return available;
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
/**
|
|
2248
|
+
* Build the shared per-query filter state for canonical model queries.
|
|
2249
|
+
* Hoisted out of the per-record loop: building the candidate-selector set
|
|
2250
|
+
* and availability memo once per query instead of once per record is what
|
|
2251
|
+
* keeps `getCanonicalModelSelections` linear instead of O(records × candidates).
|
|
2252
|
+
*/
|
|
2253
|
+
#canonicalQueryFilters(options: CanonicalModelQueryOptions | undefined): {
|
|
2254
|
+
candidateKeys: Set<string> | undefined;
|
|
2255
|
+
isAvailable: ((model: Model<Api>) => boolean) | undefined;
|
|
2256
|
+
} {
|
|
2257
|
+
return {
|
|
2258
|
+
candidateKeys: options?.candidates
|
|
2259
|
+
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
2260
|
+
: undefined,
|
|
2261
|
+
isAvailable: options?.availableOnly ? this.#createAvailabilityCheck() : undefined,
|
|
2262
|
+
};
|
|
2226
2263
|
}
|
|
2227
2264
|
|
|
2228
2265
|
#filterCanonicalVariants(
|
|
2229
2266
|
record: CanonicalModelRecord,
|
|
2230
|
-
|
|
2267
|
+
candidateKeys: ReadonlySet<string> | undefined,
|
|
2268
|
+
isAvailable: ((model: Model<Api>) => boolean) | undefined,
|
|
2231
2269
|
): CanonicalModelVariant[] {
|
|
2232
|
-
const candidateKeys = options?.candidates
|
|
2233
|
-
? new Set(options.candidates.map(candidate => formatCanonicalVariantSelector(candidate)))
|
|
2234
|
-
: undefined;
|
|
2235
2270
|
return record.variants.filter(variant => {
|
|
2236
2271
|
if (candidateKeys && !candidateKeys.has(variant.selector)) {
|
|
2237
2272
|
return false;
|
|
2238
2273
|
}
|
|
2239
|
-
if (
|
|
2274
|
+
if (isAvailable && !isAvailable(variant.model)) {
|
|
2240
2275
|
return false;
|
|
2241
2276
|
}
|
|
2242
2277
|
return true;
|
|
2243
2278
|
});
|
|
2244
2279
|
}
|
|
2245
2280
|
|
|
2281
|
+
#buildModelOrder(candidates: readonly Model<Api>[]): Map<string, number> {
|
|
2282
|
+
const modelOrder = new Map<string, number>();
|
|
2283
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
2284
|
+
modelOrder.set(formatCanonicalVariantSelector(candidates[index]!), index);
|
|
2285
|
+
}
|
|
2286
|
+
return modelOrder;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2246
2289
|
#providerRank(): Map<string, number> {
|
|
2247
2290
|
return buildModelProviderPriorityRank(getConfiguredProviderOrderFromSettings());
|
|
2248
2291
|
}
|
|
2249
2292
|
|
|
2250
2293
|
#resolveCanonicalVariant(
|
|
2251
2294
|
variants: readonly CanonicalModelVariant[],
|
|
2252
|
-
|
|
2295
|
+
modelOrder: ReadonlyMap<string, number>,
|
|
2296
|
+
providerRank: ReadonlyMap<string, number>,
|
|
2253
2297
|
): CanonicalModelVariant | undefined {
|
|
2254
2298
|
if (variants.length === 0) {
|
|
2255
2299
|
return undefined;
|
|
2256
2300
|
}
|
|
2257
|
-
const providerRank = this.#providerRank();
|
|
2258
|
-
const modelOrder = new Map<string, number>();
|
|
2259
|
-
for (let index = 0; index < allCandidates.length; index += 1) {
|
|
2260
|
-
modelOrder.set(formatCanonicalVariantSelector(allCandidates[index]!), index);
|
|
2261
|
-
}
|
|
2262
2301
|
const sourceRank: Record<CanonicalModelVariant["source"], number> = {
|
|
2263
2302
|
override: 1,
|
|
2264
2303
|
bundled: 1,
|
|
@@ -2289,9 +2328,10 @@ export class ModelRegistry {
|
|
|
2289
2328
|
}
|
|
2290
2329
|
|
|
2291
2330
|
getCanonicalModels(options?: CanonicalModelQueryOptions): CanonicalModelRecord[] {
|
|
2331
|
+
const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
|
|
2292
2332
|
const records: CanonicalModelRecord[] = [];
|
|
2293
2333
|
for (const record of this.#canonicalIndex.records) {
|
|
2294
|
-
const variants = this.#filterCanonicalVariants(record,
|
|
2334
|
+
const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
|
|
2295
2335
|
if (variants.length === 0) {
|
|
2296
2336
|
continue;
|
|
2297
2337
|
}
|
|
@@ -2304,12 +2344,43 @@ export class ModelRegistry {
|
|
|
2304
2344
|
return records;
|
|
2305
2345
|
}
|
|
2306
2346
|
|
|
2347
|
+
/**
|
|
2348
|
+
* One-pass equivalent of `getCanonicalModels` + `resolveCanonicalModel` per
|
|
2349
|
+
* record. The per-query state (candidate-selector set, availability memo,
|
|
2350
|
+
* provider rank, candidate order) is built once, so the whole catalog
|
|
2351
|
+
* resolves in O(records + candidates) instead of O(records × candidates).
|
|
2352
|
+
* This is the path the model selector hydrates from synchronously on open.
|
|
2353
|
+
*/
|
|
2354
|
+
getCanonicalModelSelections(options?: CanonicalModelQueryOptions): CanonicalModelSelection[] {
|
|
2355
|
+
const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
|
|
2356
|
+
const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
|
|
2357
|
+
const modelOrder = this.#buildModelOrder(candidates);
|
|
2358
|
+
const providerRank = this.#providerRank();
|
|
2359
|
+
const selections: CanonicalModelSelection[] = [];
|
|
2360
|
+
for (const record of this.#canonicalIndex.records) {
|
|
2361
|
+
const variants = this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
|
|
2362
|
+
if (variants.length === 0) {
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
const resolved = this.#resolveCanonicalVariant(variants, modelOrder, providerRank);
|
|
2366
|
+
if (!resolved) {
|
|
2367
|
+
continue;
|
|
2368
|
+
}
|
|
2369
|
+
selections.push({
|
|
2370
|
+
record: { id: record.id, name: record.name, variants },
|
|
2371
|
+
model: resolved.model,
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
return selections;
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2307
2377
|
getCanonicalVariants(canonicalId: string, options?: CanonicalModelQueryOptions): CanonicalModelVariant[] {
|
|
2308
2378
|
const record = this.#canonicalIndex.byId.get(canonicalId.trim().toLowerCase());
|
|
2309
2379
|
if (!record) {
|
|
2310
2380
|
return [];
|
|
2311
2381
|
}
|
|
2312
|
-
|
|
2382
|
+
const { candidateKeys, isAvailable } = this.#canonicalQueryFilters(options);
|
|
2383
|
+
return this.#filterCanonicalVariants(record, candidateKeys, isAvailable);
|
|
2313
2384
|
}
|
|
2314
2385
|
|
|
2315
2386
|
resolveCanonicalModel(canonicalId: string, options?: CanonicalModelQueryOptions): Model<Api> | undefined {
|
|
@@ -2318,7 +2389,7 @@ export class ModelRegistry {
|
|
|
2318
2389
|
return undefined;
|
|
2319
2390
|
}
|
|
2320
2391
|
const candidates = options?.candidates ?? (options?.availableOnly ? this.getAvailable() : this.getAll());
|
|
2321
|
-
return this.#resolveCanonicalVariant(variants, candidates)?.model;
|
|
2392
|
+
return this.#resolveCanonicalVariant(variants, this.#buildModelOrder(candidates), this.#providerRank())?.model;
|
|
2322
2393
|
}
|
|
2323
2394
|
|
|
2324
2395
|
getCanonicalId(model: Model<Api>): string | undefined {
|
|
@@ -2330,7 +2401,7 @@ export class ModelRegistry {
|
|
|
2330
2401
|
* This is a fast check that doesn't refresh OAuth tokens.
|
|
2331
2402
|
*/
|
|
2332
2403
|
getAvailable(): Model<Api>[] {
|
|
2333
|
-
return this.#models.filter(
|
|
2404
|
+
return this.#models.filter(this.#createAvailabilityCheck());
|
|
2334
2405
|
}
|
|
2335
2406
|
|
|
2336
2407
|
/**
|
|
@@ -686,16 +686,6 @@ export const SETTINGS_SCHEMA = {
|
|
|
686
686
|
ui: { tab: "appearance", label: "Show Hardware Cursor", description: "Show terminal cursor for IME support" },
|
|
687
687
|
},
|
|
688
688
|
|
|
689
|
-
clearOnShrink: {
|
|
690
|
-
type: "boolean",
|
|
691
|
-
default: false,
|
|
692
|
-
ui: {
|
|
693
|
-
tab: "appearance",
|
|
694
|
-
label: "Clear on Shrink",
|
|
695
|
-
description: "Clear empty rows when content shrinks (may cause flicker)",
|
|
696
|
-
},
|
|
697
|
-
},
|
|
698
|
-
|
|
699
689
|
// ────────────────────────────────────────────────────────────────────────
|
|
700
690
|
// Model
|
|
701
691
|
// ────────────────────────────────────────────────────────────────────────
|
|
@@ -36,7 +36,6 @@ export interface TerminalStateInfo {
|
|
|
36
36
|
hyperlinks: boolean;
|
|
37
37
|
deccara: boolean;
|
|
38
38
|
screenToScrollback: boolean;
|
|
39
|
-
eagerEraseScrollbackRisk: boolean;
|
|
40
39
|
synchronizedOutput: boolean;
|
|
41
40
|
multiplexer: string | null;
|
|
42
41
|
env: { TERM?: string; TERM_PROGRAM?: string; TERM_PROGRAM_VERSION?: string; COLORTERM?: string };
|
|
@@ -82,7 +81,6 @@ export function collectTerminalState(runtime: TerminalRuntimeState): TerminalSta
|
|
|
82
81
|
hyperlinks: TERMINAL.hyperlinks,
|
|
83
82
|
deccara: TERMINAL.deccara,
|
|
84
83
|
screenToScrollback: TERMINAL.supportsScreenToScrollback,
|
|
85
|
-
eagerEraseScrollbackRisk: TERMINAL.eagerEraseScrollbackRisk,
|
|
86
84
|
synchronizedOutput: runtime.synchronizedOutput,
|
|
87
85
|
multiplexer: detectMultiplexer(env),
|
|
88
86
|
env: {
|
|
@@ -115,7 +113,6 @@ export function formatTerminalState(info: TerminalStateInfo): string {
|
|
|
115
113
|
"",
|
|
116
114
|
"Scrollback",
|
|
117
115
|
` Screen->history clear: ${info.screenToScrollback ? "CSI 22 J" : "CSI 2 J (redraw)"}`,
|
|
118
|
-
` Eager-erase risk: ${yesNo(info.eagerEraseScrollbackRisk)} (ED3 may yank scrolled readers)`,
|
|
119
116
|
"",
|
|
120
117
|
"Detection signals",
|
|
121
118
|
` TERM: ${info.env.TERM ?? "(unset)"}`,
|
package/src/edit/diff.ts
CHANGED
|
@@ -55,13 +55,10 @@ function formatNumberedDiffLine(prefix: "+" | "-" | " ", lineNum: number, conten
|
|
|
55
55
|
return `${prefix}${lineNum}|${content}`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
type DiffSource = "old" | "new";
|
|
59
|
-
|
|
60
58
|
interface ParsedNumberedDiffRow {
|
|
61
59
|
prefix: "+" | "-" | " ";
|
|
62
60
|
lineNumber: number;
|
|
63
61
|
content: string;
|
|
64
|
-
source: DiffSource;
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
function parseNumberedDiffRow(row: string): ParsedNumberedDiffRow | undefined {
|
|
@@ -70,12 +67,7 @@ function parseNumberedDiffRow(row: string): ParsedNumberedDiffRow | undefined {
|
|
|
70
67
|
const prefix = match[1] as "+" | "-" | " ";
|
|
71
68
|
const lineNumber = Number.parseInt(match[2], 10);
|
|
72
69
|
if (!Number.isFinite(lineNumber)) return undefined;
|
|
73
|
-
return {
|
|
74
|
-
prefix,
|
|
75
|
-
lineNumber,
|
|
76
|
-
content: match[3] ?? "",
|
|
77
|
-
source: prefix === "+" ? "new" : "old",
|
|
78
|
-
};
|
|
70
|
+
return { prefix, lineNumber, content: match[3] ?? "" };
|
|
79
71
|
}
|
|
80
72
|
|
|
81
73
|
function isDiffChangeRow(row: string | undefined): boolean {
|
|
@@ -92,7 +84,6 @@ function adjustedContextInsertIndex(rows: readonly string[], index: number): num
|
|
|
92
84
|
|
|
93
85
|
function insertBracketContextRows(
|
|
94
86
|
rows: string[],
|
|
95
|
-
source: DiffSource,
|
|
96
87
|
contextLines: ReadonlyMap<number, string>,
|
|
97
88
|
seenRows: Set<string>,
|
|
98
89
|
): void {
|
|
@@ -106,7 +97,7 @@ function insertBracketContextRows(
|
|
|
106
97
|
let nextSourceLine: number | undefined;
|
|
107
98
|
for (let i = 0; i < rows.length; i++) {
|
|
108
99
|
const parsed = parseNumberedDiffRow(rows[i]);
|
|
109
|
-
if (!parsed || parsed.
|
|
100
|
+
if (!parsed || parsed.prefix === "+") continue;
|
|
110
101
|
if (parsed.lineNumber < lineNumber) {
|
|
111
102
|
previousSourceLine = parsed.lineNumber;
|
|
112
103
|
continue;
|
|
@@ -127,6 +118,16 @@ function insertBracketContextRows(
|
|
|
127
118
|
}
|
|
128
119
|
}
|
|
129
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Insert off-window block-boundary rows (enclosing header, matching closing
|
|
123
|
+
* bracket, …) into a numbered diff. Context rows carry pre-edit line numbers —
|
|
124
|
+
* the renumbering contract of `buildCompactDiffPreview` — so boundary lines
|
|
125
|
+
* discovered in the new file are translated back to their pre-edit numbers
|
|
126
|
+
* and merged with the old-file pass before a single insertion sweep. Without
|
|
127
|
+
* the translation, a context line sitting in a net-offset region would be
|
|
128
|
+
* re-inserted under its post-edit number: duplicated, out of order, and
|
|
129
|
+
* renumbered incorrectly by the preview.
|
|
130
|
+
*/
|
|
130
131
|
function addMatchingBracketContextRows(
|
|
131
132
|
rows: string[],
|
|
132
133
|
oldLines: readonly string[],
|
|
@@ -136,16 +137,48 @@ function addMatchingBracketContextRows(
|
|
|
136
137
|
const oldVisible: number[] = [];
|
|
137
138
|
const newVisible: number[] = [];
|
|
138
139
|
const seenRows = new Set(rows);
|
|
140
|
+
// Change positions in new-file coordinates, used to translate an unchanged
|
|
141
|
+
// new-file line number back to its pre-edit equivalent.
|
|
142
|
+
const changes: { newPos: number; delta: 1 | -1 }[] = [];
|
|
143
|
+
let offset = 0;
|
|
139
144
|
|
|
140
145
|
for (const row of rows) {
|
|
141
146
|
const parsed = parseNumberedDiffRow(row);
|
|
142
147
|
if (!parsed) continue;
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
switch (parsed.prefix) {
|
|
149
|
+
case "-":
|
|
150
|
+
oldVisible.push(parsed.lineNumber);
|
|
151
|
+
changes.push({ newPos: parsed.lineNumber + offset, delta: -1 });
|
|
152
|
+
offset--;
|
|
153
|
+
break;
|
|
154
|
+
case "+":
|
|
155
|
+
newVisible.push(parsed.lineNumber);
|
|
156
|
+
changes.push({ newPos: parsed.lineNumber, delta: 1 });
|
|
157
|
+
offset++;
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
// Context rows are visible in BOTH files: pre-edit number as
|
|
161
|
+
// written, post-edit number shifted by the net change so far.
|
|
162
|
+
oldVisible.push(parsed.lineNumber);
|
|
163
|
+
newVisible.push(parsed.lineNumber + offset);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
145
166
|
}
|
|
146
167
|
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
const toOldLineNumber = (newLineNumber: number): number => {
|
|
169
|
+
let shift = 0;
|
|
170
|
+
for (const change of changes) {
|
|
171
|
+
if (change.newPos <= newLineNumber) shift += change.delta;
|
|
172
|
+
}
|
|
173
|
+
return newLineNumber - shift;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const contextRows = findBlockContextLines(oldLines, oldVisible, source);
|
|
177
|
+
for (const [lineNumber, text] of findBlockContextLines(newLines, newVisible, source)) {
|
|
178
|
+
const oldLineNumber = toOldLineNumber(lineNumber);
|
|
179
|
+
if (!contextRows.has(oldLineNumber)) contextRows.set(oldLineNumber, text);
|
|
180
|
+
}
|
|
181
|
+
insertBracketContextRows(rows, contextRows, seenRows);
|
|
149
182
|
}
|
|
150
183
|
|
|
151
184
|
/**
|
|
@@ -82,6 +82,14 @@ function parseProgram(code: string): { program: { body: ReadonlyArray<BabelProgr
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Callee substituted for dynamic `import(...)` calls. Functions handed to puppeteer
|
|
86
|
+
// (`tab.evaluate`, `page.evaluate`, `waitForFunction`, `$$eval`, ...) are serialized with
|
|
87
|
+
// `Function.prototype.toString()` and re-evaluated inside the browser page, where the
|
|
88
|
+
// worker-injected `__omp_import__` global does not exist. The swap therefore guards on the
|
|
89
|
+
// helper's presence and falls back to native dynamic import, so serialized code keeps
|
|
90
|
+
// working in foreign realms while in-worker code still resolves against the session cwd.
|
|
91
|
+
const DYNAMIC_IMPORT_CALLEE = '(typeof __omp_import__ === "function" ? __omp_import__ : (s, o) => import(s, o))';
|
|
92
|
+
|
|
85
93
|
function buildOmpImportCall(sourceLiteral: string, optionsLiteral: string | undefined): string {
|
|
86
94
|
// Route every static import through the worker-injected `__omp_import__` helper so the
|
|
87
95
|
// specifier resolves against the session cwd (and `with`-attribute imports keep working).
|
|
@@ -180,7 +188,7 @@ export function rewriteImports(code: string): string {
|
|
|
180
188
|
const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
|
|
181
189
|
const callee = call.callee;
|
|
182
190
|
if (callee?.type !== "Import" || typeof callee.start !== "number" || typeof callee.end !== "number") return;
|
|
183
|
-
edits.push({ start: callee.start, end: callee.end, text:
|
|
191
|
+
edits.push({ start: callee.start, end: callee.end, text: DYNAMIC_IMPORT_CALLEE });
|
|
184
192
|
});
|
|
185
193
|
|
|
186
194
|
if (edits.length === 0) return code;
|