@oh-my-pi/pi-coding-agent 15.9.5 → 15.10.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 +98 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +10 -2
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +43 -7
- package/dist/types/edit/file-snapshot-store.d.ts +1 -1
- package/dist/types/eval/backend.d.ts +6 -6
- package/dist/types/eval/bridge-timeout.d.ts +27 -0
- package/dist/types/eval/idle-timeout.d.ts +16 -14
- package/dist/types/eval/js/executor.d.ts +3 -3
- package/dist/types/eval/py/executor.d.ts +2 -2
- package/dist/types/eval/py/spawn-options.d.ts +58 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +5 -0
- package/dist/types/modes/components/copy-selector.d.ts +22 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/command-controller.d.ts +0 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -2
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/modes/utils/copy-targets.d.ts +53 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -0
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +33 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui/code-cell.d.ts +6 -0
- package/dist/types/tui/output-block.d.ts +11 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/autoresearch/dashboard.ts +11 -21
- package/src/cli/args.ts +2 -2
- package/src/cli/claude-trace-cli.ts +13 -1
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +68 -2
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +29 -6
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/debug/raw-sse.ts +18 -4
- package/src/edit/file-snapshot-store.ts +1 -1
- package/src/edit/index.ts +1 -1
- package/src/edit/renderer.ts +43 -55
- package/src/edit/streaming.ts +1 -1
- package/src/eval/__tests__/agent-bridge.test.ts +102 -58
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/idle-timeout.test.ts +26 -12
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/llm-bridge.test.ts +10 -10
- package/src/eval/agent-bridge.ts +38 -12
- package/src/eval/backend.ts +6 -6
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/idle-timeout.ts +33 -15
- package/src/eval/js/executor.ts +10 -10
- package/src/eval/llm-bridge.ts +4 -5
- package/src/eval/py/executor.ts +6 -6
- package/src/eval/py/kernel.ts +11 -1
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/export/ttsr.ts +9 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/lsp/client.ts +179 -52
- package/src/lsp/index.ts +38 -4
- package/src/lsp/render.ts +3 -3
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +47 -52
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/agent-dashboard.ts +13 -4
- package/src/modes/components/assistant-message.ts +22 -1
- package/src/modes/components/copy-selector.ts +249 -0
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/extensions/extension-list.ts +17 -8
- package/src/modes/components/history-search.ts +19 -11
- package/src/modes/components/model-selector.ts +125 -29
- package/src/modes/components/oauth-selector.ts +28 -12
- package/src/modes/components/session-observer-overlay.ts +13 -15
- package/src/modes/components/session-selector.ts +24 -13
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +83 -24
- package/src/modes/components/tree-selector.ts +19 -7
- package/src/modes/components/user-message-selector.ts +25 -14
- package/src/modes/controllers/command-controller.ts +13 -118
- package/src/modes/controllers/event-controller.ts +26 -10
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +40 -3
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/theme/theme.ts +46 -10
- package/src/modes/types.ts +2 -2
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +254 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +13 -9
- package/src/slash-commands/builtin-registry.ts +4 -12
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +37 -11
- package/src/telemetry-export.ts +25 -7
- package/src/tools/bash.ts +18 -8
- package/src/tools/browser/render.ts +5 -4
- package/src/tools/debug.ts +3 -3
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +28 -10
- package/src/tools/eval.ts +19 -23
- package/src/tools/fetch.ts +99 -89
- package/src/tools/read.ts +7 -7
- package/src/tools/render-utils.ts +63 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/ssh.ts +21 -8
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +39 -9
- package/src/tui/code-cell.ts +19 -4
- package/src/tui/output-block.ts +14 -0
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +42 -57
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/heartbeat.d.ts +0 -45
- package/src/eval/__tests__/heartbeat.test.ts +0 -84
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
- package/src/eval/heartbeat.ts +0 -74
- /package/dist/types/eval/__tests__/{heartbeat.test.d.ts → bridge-timeout.test.d.ts} +0 -0
- /package/dist/types/eval/__tests__/{shared-executors.test.d.ts → kernel-spawn.test.d.ts} +0 -0
|
@@ -4,34 +4,49 @@ const MODEL_ID_SEGMENT_PATTERN = /[a-z0-9.:-]+/g;
|
|
|
4
4
|
const MODEL_FAMILY_PREFIX_PATTERN =
|
|
5
5
|
/^(claude|gemini|gpt|grok|glm|qwen|deepseek|kimi|mimo|doubao|ernie|gpt-oss|gemma|minimax|step|command|jamba|llama|o[1345])/i;
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
return
|
|
7
|
+
function normalizeModelIdWhitespace(value: string): string {
|
|
8
|
+
return value.trim().replace(/\s+/g, " ");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** Ordering for model-like segments: longest first, ties broken lexicographically. */
|
|
11
12
|
function compareSegmentPreference(left: string, right: string): number {
|
|
12
|
-
|
|
13
|
-
return right.length - left.length;
|
|
14
|
-
}
|
|
15
|
-
return left.localeCompare(right);
|
|
13
|
+
return left.length !== right.length ? right.length - left.length : left.localeCompare(right);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
export function getModelLikeIdSegments(modelId: string): string[] {
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
-
const segments = (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return unique;
|
|
17
|
+
const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
|
|
18
|
+
if (!matches) return [];
|
|
19
|
+
const segments = new Set<string>();
|
|
20
|
+
for (const segment of matches) {
|
|
21
|
+
if (MODEL_FAMILY_PREFIX_PATTERN.test(segment) && /\d/.test(segment)) segments.add(segment);
|
|
22
|
+
}
|
|
23
|
+
return [...segments].sort(compareSegmentPreference);
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export function getLongestModelLikeIdSegment(modelId: string): string | undefined {
|
|
30
|
-
|
|
27
|
+
const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
|
|
28
|
+
if (!matches) return undefined;
|
|
29
|
+
let best: string | undefined;
|
|
30
|
+
for (const segment of matches) {
|
|
31
|
+
if (
|
|
32
|
+
MODEL_FAMILY_PREFIX_PATTERN.test(segment) &&
|
|
33
|
+
/\d/.test(segment) &&
|
|
34
|
+
(best === undefined || compareSegmentPreference(segment, best) < 0)
|
|
35
|
+
) {
|
|
36
|
+
best = segment;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return best;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
function
|
|
34
|
-
|
|
42
|
+
function hasBracketAffixMarker(value: string): boolean {
|
|
43
|
+
for (let index = 0; index < value.length; index++) {
|
|
44
|
+
const code = value.charCodeAt(index);
|
|
45
|
+
if (code === 91 || code === 93 || code === 0x3010 || code === 0x3011) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
|
@@ -39,18 +54,20 @@ function normalizeModelIdWhitespace(value: string): string {
|
|
|
39
54
|
* upstream model id, e.g.
|
|
40
55
|
* "[Kiro] claude-opus-4-8" -> "claude-opus-4-8"
|
|
41
56
|
* "[gcli转] gemini-3.1-pro-preview [假流]" -> "gemini-3.1-pro-preview"
|
|
57
|
+
*
|
|
58
|
+
* Candidates are returned most-stripped first: both ends, then leading-only, then trailing-only.
|
|
42
59
|
*/
|
|
43
60
|
export function getBracketStrippedModelIdCandidates(modelId: string): string[] {
|
|
61
|
+
if (!hasBracketAffixMarker(modelId)) return [];
|
|
44
62
|
const normalized = normalizeModelIdWhitespace(modelId);
|
|
45
63
|
if (!normalized) return [];
|
|
46
64
|
|
|
47
|
-
const
|
|
48
|
-
const withoutLeading = normalizeModelIdWhitespace(
|
|
65
|
+
const strippedLeading = normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "");
|
|
66
|
+
const withoutLeading = normalizeModelIdWhitespace(strippedLeading);
|
|
49
67
|
const withoutTrailing = normalizeModelIdWhitespace(normalized.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
50
|
-
const withoutBoth = normalizeModelIdWhitespace(
|
|
51
|
-
normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "").replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""),
|
|
52
|
-
);
|
|
68
|
+
const withoutBoth = normalizeModelIdWhitespace(strippedLeading.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
53
69
|
|
|
70
|
+
const candidates = new Set<string>();
|
|
54
71
|
for (const candidate of [withoutBoth, withoutLeading, withoutTrailing]) {
|
|
55
72
|
if (candidate && candidate !== normalized) {
|
|
56
73
|
candidates.add(candidate);
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
|
|
3
|
+
import { readModelCache } from "@oh-my-pi/pi-ai/model-cache";
|
|
4
|
+
import { createModelManager, type ModelManagerOptions, type ModelRefreshStrategy } from "@oh-my-pi/pi-ai/model-manager";
|
|
5
|
+
import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking";
|
|
6
|
+
import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-ai/models";
|
|
2
7
|
import {
|
|
3
|
-
type Api,
|
|
4
|
-
type AssistantMessageEventStream,
|
|
5
|
-
type Context,
|
|
6
|
-
createModelManager,
|
|
7
|
-
enrichModelThinking,
|
|
8
|
-
getBundledModels,
|
|
9
|
-
getBundledProviders,
|
|
10
8
|
googleAntigravityModelManagerOptions,
|
|
11
9
|
googleGeminiCliModelManagerOptions,
|
|
12
|
-
type Model,
|
|
13
|
-
type ModelManagerOptions,
|
|
14
|
-
type ModelRefreshStrategy,
|
|
15
10
|
openaiCodexModelManagerOptions,
|
|
16
11
|
PROVIDER_DESCRIPTORS,
|
|
17
|
-
readModelCache,
|
|
18
|
-
registerCustomApi,
|
|
19
|
-
type SimpleStreamOptions,
|
|
20
|
-
type ThinkingConfig,
|
|
21
12
|
UNK_CONTEXT_WINDOW,
|
|
22
13
|
UNK_MAX_TOKENS,
|
|
23
|
-
|
|
24
|
-
} from "@oh-my-pi/pi-ai";
|
|
14
|
+
} from "@oh-my-pi/pi-ai/provider-models";
|
|
15
|
+
import type { Api, Context, Model, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
|
|
16
|
+
import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
|
|
25
17
|
|
|
26
18
|
// Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
|
|
27
19
|
// any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
|
|
@@ -2609,6 +2601,14 @@ export class ModelRegistry {
|
|
|
2609
2601
|
}
|
|
2610
2602
|
return true;
|
|
2611
2603
|
}
|
|
2604
|
+
|
|
2605
|
+
/**
|
|
2606
|
+
* Clear all cooldown suppressions recorded via {@link suppressSelector}.
|
|
2607
|
+
* Used to reset retry-fallback cooldown state without a full {@link refresh}.
|
|
2608
|
+
*/
|
|
2609
|
+
clearSuppressedSelectors(): void {
|
|
2610
|
+
this.#suppressedSelectors.clear();
|
|
2611
|
+
}
|
|
2612
2612
|
}
|
|
2613
2613
|
|
|
2614
2614
|
/**
|
|
@@ -902,6 +902,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
902
902
|
"Maximum wait between retries, in ms. When the provider asks us to wait longer than this and no credential or model fallback succeeds, the request fails fast instead of sleeping (e.g. 3-hour Anthropic rate-limit windows).",
|
|
903
903
|
},
|
|
904
904
|
},
|
|
905
|
+
"retry.modelFallback": {
|
|
906
|
+
type: "boolean",
|
|
907
|
+
default: true,
|
|
908
|
+
ui: {
|
|
909
|
+
tab: "model",
|
|
910
|
+
label: "Retry Model Fallback",
|
|
911
|
+
description: "Allow retry recovery to switch to configured fallback models",
|
|
912
|
+
},
|
|
913
|
+
},
|
|
905
914
|
"retry.fallbackChains": { type: "record", default: {} as Record<string, string[]> },
|
|
906
915
|
"retry.fallbackRevertPolicy": {
|
|
907
916
|
type: "enum",
|
|
@@ -1855,7 +1864,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1855
1864
|
tab: "editing",
|
|
1856
1865
|
label: "Hash Lines",
|
|
1857
1866
|
description:
|
|
1858
|
-
"Include snapshot-tag headers and line numbers in read output for hashline edit mode (
|
|
1867
|
+
"Include snapshot-tag headers and line numbers in read output for hashline edit mode ([PATH#TAG] plus LINE:content)",
|
|
1859
1868
|
},
|
|
1860
1869
|
},
|
|
1861
1870
|
|
|
@@ -3050,13 +3059,26 @@ export const SETTINGS_SCHEMA = {
|
|
|
3050
3059
|
],
|
|
3051
3060
|
},
|
|
3052
3061
|
},
|
|
3053
|
-
"providers.
|
|
3054
|
-
type: "
|
|
3055
|
-
|
|
3062
|
+
"providers.fetch": {
|
|
3063
|
+
type: "enum",
|
|
3064
|
+
values: ["auto", "native", "trafilatura", "lynx", "parallel", "jina"] as const,
|
|
3065
|
+
default: "auto",
|
|
3056
3066
|
ui: {
|
|
3057
3067
|
tab: "providers",
|
|
3058
|
-
label: "
|
|
3059
|
-
description: "
|
|
3068
|
+
label: "Fetch Provider",
|
|
3069
|
+
description: "Reader backend priority for the fetch/read URL tool",
|
|
3070
|
+
options: [
|
|
3071
|
+
{
|
|
3072
|
+
value: "auto",
|
|
3073
|
+
label: "Auto",
|
|
3074
|
+
description: "Priority: native > trafilatura > lynx > parallel > jina",
|
|
3075
|
+
},
|
|
3076
|
+
{ value: "native", label: "Native", description: "In-process HTML→Markdown converter (always available)" },
|
|
3077
|
+
{ value: "trafilatura", label: "Trafilatura", description: "Auto-installs via uv/pip" },
|
|
3078
|
+
{ value: "lynx", label: "Lynx", description: "Requires lynx system package" },
|
|
3079
|
+
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
3080
|
+
{ value: "jina", label: "Jina", description: "Uses r.jina.ai reader (JINA_API_KEY optional)" },
|
|
3081
|
+
],
|
|
3060
3082
|
},
|
|
3061
3083
|
},
|
|
3062
3084
|
"provider.appendOnlyContext": {
|
|
@@ -3307,6 +3329,7 @@ export interface RetrySettings {
|
|
|
3307
3329
|
maxRetries: number;
|
|
3308
3330
|
baseDelayMs: number;
|
|
3309
3331
|
maxDelayMs: number;
|
|
3332
|
+
modelFallback: boolean;
|
|
3310
3333
|
}
|
|
3311
3334
|
|
|
3312
3335
|
export interface MemoriesSettings {
|
package/src/config/settings.ts
CHANGED
|
@@ -712,6 +712,17 @@ export class Settings {
|
|
|
712
712
|
}
|
|
713
713
|
}
|
|
714
714
|
|
|
715
|
+
// providers.parallelFetch (boolean) replaced by the providers.fetch reader
|
|
716
|
+
// priority enum. The new default ("auto") supersedes both old values —
|
|
717
|
+
// Parallel is now a deep fallback in the auto chain rather than the first
|
|
718
|
+
// choice — so drop the legacy key (flat and nested) and let the enum
|
|
719
|
+
// default apply.
|
|
720
|
+
const providersObj = raw.providers as Record<string, unknown> | undefined;
|
|
721
|
+
if (providersObj && "parallelFetch" in providersObj) {
|
|
722
|
+
delete providersObj.parallelFetch;
|
|
723
|
+
}
|
|
724
|
+
delete raw["providers.parallelFetch"];
|
|
725
|
+
|
|
715
726
|
// Map legacy `memories.enabled` boolean to the explicit `memory.backend`
|
|
716
727
|
// enum if the latter hasn't been set yet. Idempotent: subsequent
|
|
717
728
|
// migrations are no-ops once memory.backend is materialised.
|
package/src/dap/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { isEnoent, logger, ptree } from "@oh-my-pi/pi-utils";
|
|
2
3
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
3
4
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
4
5
|
import type {
|
|
@@ -165,19 +166,7 @@ export class DapClient {
|
|
|
165
166
|
detached: true,
|
|
166
167
|
});
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
await waitForCondition(
|
|
170
|
-
() => {
|
|
171
|
-
try {
|
|
172
|
-
Bun.file(socketPath).size;
|
|
173
|
-
return true;
|
|
174
|
-
} catch {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
10_000,
|
|
179
|
-
proc,
|
|
180
|
-
);
|
|
169
|
+
await waitForCondition(() => isUnixSocketReady(socketPath), 10_000, proc);
|
|
181
170
|
|
|
182
171
|
const { readable, writeSink, socket } = await connectSocket({ unix: socketPath });
|
|
183
172
|
const client = new DapClient(adapter, cwd, proc, { readable, writeSink, socket });
|
|
@@ -553,15 +542,24 @@ export class DapClient {
|
|
|
553
542
|
}
|
|
554
543
|
}
|
|
555
544
|
|
|
545
|
+
async function isUnixSocketReady(socketPath: string): Promise<boolean> {
|
|
546
|
+
try {
|
|
547
|
+
return (await fs.stat(socketPath)).isSocket();
|
|
548
|
+
} catch (error) {
|
|
549
|
+
if (isEnoent(error)) return false;
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
556
554
|
/** Poll a condition until it returns true, or timeout/process exit. */
|
|
557
555
|
async function waitForCondition(
|
|
558
|
-
check: () => boolean
|
|
556
|
+
check: () => boolean | Promise<boolean>,
|
|
559
557
|
timeoutMs: number,
|
|
560
558
|
proc: { exitCode: number | null },
|
|
561
559
|
): Promise<void> {
|
|
562
560
|
const deadline = Date.now() + timeoutMs;
|
|
563
561
|
while (Date.now() < deadline) {
|
|
564
|
-
if (check()) return;
|
|
562
|
+
if (await check()) return;
|
|
565
563
|
if (proc.exitCode !== null) {
|
|
566
564
|
throw new Error("Adapter process exited before socket was ready");
|
|
567
565
|
}
|
package/src/debug/raw-sse.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
matchesKey,
|
|
4
|
+
padding,
|
|
5
|
+
replaceTabs,
|
|
6
|
+
ScrollView,
|
|
7
|
+
truncateToWidth,
|
|
8
|
+
visibleWidth,
|
|
9
|
+
} from "@oh-my-pi/pi-tui";
|
|
2
10
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
11
|
import { theme } from "../modes/theme/theme";
|
|
4
12
|
import { copyToClipboard } from "../utils/clipboard";
|
|
@@ -146,14 +154,20 @@ export class RawSseViewerComponent implements Component {
|
|
|
146
154
|
const innerWidth = Math.max(1, this.#lastRenderWidth - 2);
|
|
147
155
|
const bodyHeight = this.#bodyHeight();
|
|
148
156
|
const rawLines = this.#renderRawLines(innerWidth);
|
|
149
|
-
const
|
|
150
|
-
|
|
157
|
+
const sv = new ScrollView(rawLines.slice(this.#scrollOffset, this.#scrollOffset + bodyHeight), {
|
|
158
|
+
height: bodyHeight,
|
|
159
|
+
scrollbar: "auto",
|
|
160
|
+
totalRows: rawLines.length,
|
|
161
|
+
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
162
|
+
});
|
|
163
|
+
sv.setScrollOffset(this.#scrollOffset);
|
|
164
|
+
const bodyRows = sv.render(innerWidth);
|
|
151
165
|
|
|
152
166
|
return [
|
|
153
167
|
this.#frameTop(innerWidth),
|
|
154
168
|
this.#frameLine(this.#summaryText(), innerWidth),
|
|
155
169
|
this.#frameSeparator(innerWidth),
|
|
156
|
-
...
|
|
170
|
+
...bodyRows.map(line => this.#frameLine(line, innerWidth)),
|
|
157
171
|
this.#frameLine(this.#statusText(), innerWidth),
|
|
158
172
|
this.#frameBottom(innerWidth),
|
|
159
173
|
];
|
|
@@ -14,7 +14,7 @@ import { normalizeToLF } from "./normalize";
|
|
|
14
14
|
/**
|
|
15
15
|
* Upper bound on the file size we snapshot. A section tag is a content hash of
|
|
16
16
|
* the *whole* file, so minting one means holding the full normalized text in
|
|
17
|
-
* the store. Files above this cap emit no
|
|
17
|
+
* the store. Files above this cap emit no `[path#tag]` header — line-anchored
|
|
18
18
|
* editing of multi-megabyte files is out of scope under the full-content model.
|
|
19
19
|
*/
|
|
20
20
|
export const SNAPSHOT_MAX_BYTES = 4 * 1024 * 1024;
|
package/src/edit/index.ts
CHANGED
|
@@ -275,7 +275,7 @@ function extractApprovalPath(args: unknown): string {
|
|
|
275
275
|
const record = args && typeof args === "object" ? (args as Record<string, unknown>) : {};
|
|
276
276
|
const input = typeof record.input === "string" ? record.input : undefined;
|
|
277
277
|
if (input) {
|
|
278
|
-
const hashlineMatch =
|
|
278
|
+
const hashlineMatch = /^\[([^#\r\n]+)(?:#[0-9a-fA-F]{4})?\]/m.exec(input);
|
|
279
279
|
if (hashlineMatch?.[1]) return hashlineMatch[1];
|
|
280
280
|
|
|
281
281
|
const applyPatchMatch = /^\*\*\* (?:Add|Update|Delete) File:\s*(.+)$/m.exec(input);
|
package/src/edit/renderer.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Edit tool renderer and LSP batching helpers.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { HL_FILE_PREFIX } from "@oh-my-pi/hashline";
|
|
5
|
+
import { HL_FILE_PREFIX, HL_FILE_SUFFIX } from "@oh-my-pi/hashline";
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { Text, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
@@ -179,11 +179,6 @@ function countEditFiles(edits: EditRenderEntry[]): number {
|
|
|
179
179
|
return new Set(edits.map(edit => filePathFromEditEntry(edit.path)).filter(Boolean)).size;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
function countLines(text: string): number {
|
|
183
|
-
if (!text) return 0;
|
|
184
|
-
return text.split("\n").length;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
182
|
function getOperationTitle(op: Operation | undefined): string {
|
|
188
183
|
return op === "create" ? "Create" : op === "delete" ? "Delete" : "Edit";
|
|
189
184
|
}
|
|
@@ -233,19 +228,22 @@ function renderPlainTextPreview(text: string, uiTheme: Theme, filePath?: string)
|
|
|
233
228
|
return preview.trimEnd();
|
|
234
229
|
}
|
|
235
230
|
|
|
236
|
-
function formatStreamingDiff(
|
|
231
|
+
function formatStreamingDiff(
|
|
232
|
+
diff: string,
|
|
233
|
+
rawPath: string,
|
|
234
|
+
uiTheme: Theme,
|
|
235
|
+
expanded: boolean,
|
|
236
|
+
label = "streaming",
|
|
237
|
+
): string {
|
|
237
238
|
if (!diff) return "";
|
|
238
|
-
// "Cursor" tail window: pin the last
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
// stuttered, and the earlier high-water fix traded that for a half-empty
|
|
245
|
-
// rectangle. A strict fixed-height window keeps the box steady and always
|
|
246
|
-
// full of real diff context instead of blank padding.
|
|
239
|
+
// Collapsed uses a "Cursor" tail window: pin the last
|
|
240
|
+
// EDIT_STREAMING_PREVIEW_LINES rows to the bottom so freshly streamed changes
|
|
241
|
+
// stay on screen. The whole-file diff is recomputed on every streamed chunk
|
|
242
|
+
// and its Myers alignment is not monotonic in payload length, so a hunk-aware
|
|
243
|
+
// window stutters as rows move between hunks. Expanded deliberately lifts that
|
|
244
|
+
// cap for the approval-time full view.
|
|
247
245
|
const allLines = diff.replace(/\n+$/u, "").split("\n");
|
|
248
|
-
const hiddenLines = Math.max(0, allLines.length - EDIT_STREAMING_PREVIEW_LINES);
|
|
246
|
+
const hiddenLines = expanded ? 0 : Math.max(0, allLines.length - EDIT_STREAMING_PREVIEW_LINES);
|
|
249
247
|
const visible = hiddenLines > 0 ? allLines.slice(hiddenLines) : allLines;
|
|
250
248
|
let text = "\n\n";
|
|
251
249
|
if (hiddenLines > 0) {
|
|
@@ -256,19 +254,11 @@ function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, labe
|
|
|
256
254
|
text += `${uiTheme.fg("dim", `… (${remainder.join(", ")} above)`)}\n`;
|
|
257
255
|
}
|
|
258
256
|
text += renderDiffColored(visible.join("\n"), { filePath: rawPath });
|
|
259
|
-
text += uiTheme.fg("dim", `\n(${label})`);
|
|
257
|
+
if (!expanded || label !== "preview") text += uiTheme.fg("dim", `\n(${label})`);
|
|
260
258
|
return text;
|
|
261
259
|
}
|
|
262
260
|
|
|
263
|
-
function
|
|
264
|
-
const icon = uiTheme.getLangIcon(language);
|
|
265
|
-
if (lineCount !== null) {
|
|
266
|
-
return uiTheme.fg("dim", `${icon} ${lineCount} lines`);
|
|
267
|
-
}
|
|
268
|
-
return uiTheme.fg("dim", `${icon}`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: Theme): string {
|
|
261
|
+
function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: Theme, expanded: boolean): string {
|
|
272
262
|
const parts: string[] = [];
|
|
273
263
|
for (const preview of previews) {
|
|
274
264
|
if (!preview.diff && !preview.error) continue;
|
|
@@ -278,7 +268,7 @@ function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: T
|
|
|
278
268
|
continue;
|
|
279
269
|
}
|
|
280
270
|
if (preview.diff) {
|
|
281
|
-
parts.push(`${header}${formatStreamingDiff(preview.diff, preview.path, uiTheme, "preview")}`);
|
|
271
|
+
parts.push(`${header}${formatStreamingDiff(preview.diff, preview.path, uiTheme, expanded, "preview")}`);
|
|
282
272
|
}
|
|
283
273
|
}
|
|
284
274
|
return parts.join("");
|
|
@@ -289,16 +279,17 @@ function getCallPreview(
|
|
|
289
279
|
rawPath: string,
|
|
290
280
|
uiTheme: Theme,
|
|
291
281
|
renderContext: EditRenderContext | undefined,
|
|
282
|
+
expanded: boolean,
|
|
292
283
|
): string {
|
|
293
284
|
const multi = renderContext?.perFileDiffPreview;
|
|
294
285
|
if (multi && multi.length > 1 && multi.some(p => p.diff || p.error)) {
|
|
295
|
-
return formatMultiFileStreamingDiff(multi, uiTheme);
|
|
286
|
+
return formatMultiFileStreamingDiff(multi, uiTheme, expanded);
|
|
296
287
|
}
|
|
297
288
|
if (args.previewDiff) {
|
|
298
|
-
return formatStreamingDiff(args.previewDiff, rawPath, uiTheme, "preview");
|
|
289
|
+
return formatStreamingDiff(args.previewDiff, rawPath, uiTheme, expanded, "preview");
|
|
299
290
|
}
|
|
300
291
|
if (args.diff && args.op) {
|
|
301
|
-
return formatStreamingDiff(args.diff, rawPath, uiTheme);
|
|
292
|
+
return formatStreamingDiff(args.diff, rawPath, uiTheme, expanded);
|
|
302
293
|
}
|
|
303
294
|
if (args.diff) {
|
|
304
295
|
return renderPlainTextPreview(args.diff, uiTheme, rawPath);
|
|
@@ -328,12 +319,12 @@ function normalizeHashlineInputPreviewPath(rawPath: string): string {
|
|
|
328
319
|
}
|
|
329
320
|
|
|
330
321
|
function parseHashlineInputPreviewHeader(line: string): string | null {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const body =
|
|
322
|
+
const trimmed = line.trimEnd();
|
|
323
|
+
if (!trimmed.startsWith(HL_FILE_PREFIX)) return null;
|
|
324
|
+
// Keep streaming previews tolerant while the closing bracket is still
|
|
325
|
+
// being generated; the parser enforces the final `[path#TAG]` shape.
|
|
326
|
+
const bodyEnd = trimmed.endsWith(HL_FILE_SUFFIX) ? trimmed.length - HL_FILE_SUFFIX.length : trimmed.length;
|
|
327
|
+
const body = trimmed.slice(HL_FILE_PREFIX.length, bodyEnd).trim();
|
|
337
328
|
const previewPath = normalizeHashlineInputPreviewPath(body);
|
|
338
329
|
return previewPath.length > 0 ? previewPath : null;
|
|
339
330
|
}
|
|
@@ -383,6 +374,13 @@ function getApplyPatchRenderSummary(
|
|
|
383
374
|
}
|
|
384
375
|
}
|
|
385
376
|
|
|
377
|
+
function formatDiffStatsSuffix(diff: string, uiTheme: Theme): string {
|
|
378
|
+
const { added, removed, hunks } = getDiffStats(diff);
|
|
379
|
+
const stats = formatDiffStats(added, removed, hunks, uiTheme);
|
|
380
|
+
if (!stats) return "";
|
|
381
|
+
return ` ${uiTheme.fg("dim", uiTheme.format.bracketLeft)}${stats}${uiTheme.fg("dim", uiTheme.format.bracketRight)}`;
|
|
382
|
+
}
|
|
383
|
+
|
|
386
384
|
function renderDiffSection(
|
|
387
385
|
diff: string,
|
|
388
386
|
rawPath: string,
|
|
@@ -390,15 +388,6 @@ function renderDiffSection(
|
|
|
390
388
|
uiTheme: Theme,
|
|
391
389
|
renderDiffFn: (t: string, o?: { filePath?: string }) => string,
|
|
392
390
|
): string {
|
|
393
|
-
let text = "";
|
|
394
|
-
const diffStats = getDiffStats(diff);
|
|
395
|
-
text += `\n${uiTheme.fg("dim", uiTheme.format.bracketLeft)}${formatDiffStats(
|
|
396
|
-
diffStats.added,
|
|
397
|
-
diffStats.removed,
|
|
398
|
-
diffStats.hunks,
|
|
399
|
-
uiTheme,
|
|
400
|
-
)}${uiTheme.fg("dim", uiTheme.format.bracketRight)}`;
|
|
401
|
-
|
|
402
391
|
const {
|
|
403
392
|
text: truncatedDiff,
|
|
404
393
|
hiddenHunks,
|
|
@@ -407,7 +396,7 @@ function renderDiffSection(
|
|
|
407
396
|
? { text: diff, hiddenHunks: 0, hiddenLines: 0 }
|
|
408
397
|
: truncateDiffByHunk(diff, PREVIEW_LIMITS.DIFF_COLLAPSED_HUNKS, PREVIEW_LIMITS.DIFF_COLLAPSED_LINES);
|
|
409
398
|
|
|
410
|
-
text
|
|
399
|
+
let text = `\n${renderDiffFn(truncatedDiff, { filePath: rawPath })}`;
|
|
411
400
|
if (!expanded && (hiddenHunks > 0 || hiddenLines > 0)) {
|
|
412
401
|
const remainder: string[] = [];
|
|
413
402
|
if (hiddenHunks > 0) remainder.push(`${hiddenHunks} more hunks`);
|
|
@@ -481,7 +470,7 @@ export const editToolRenderer = {
|
|
|
481
470
|
if (fileCount > 1) {
|
|
482
471
|
text += uiTheme.fg("dim", ` (+${fileCount - 1} more)`);
|
|
483
472
|
}
|
|
484
|
-
text += getCallPreview(editArgs, rawPath, uiTheme, renderContext);
|
|
473
|
+
text += getCallPreview(editArgs, rawPath, uiTheme, renderContext, options.expanded);
|
|
485
474
|
if (applyPatchSummary?.error) {
|
|
486
475
|
text += `\n\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchSummary.error, rawPath), CALL_TEXT_PREVIEW_WIDTH))}`;
|
|
487
476
|
}
|
|
@@ -528,11 +517,6 @@ function renderSingleFileResult(
|
|
|
528
517
|
"";
|
|
529
518
|
const op = args?.op || firstEdit?.op || details?.op;
|
|
530
519
|
const rename = args?.rename || firstEdit?.rename || firstEdit?.move || details?.move;
|
|
531
|
-
const { language } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
532
|
-
|
|
533
|
-
const editTextSource = args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch;
|
|
534
|
-
const metadataLineCount = editTextSource ? countLines(editTextSource) : null;
|
|
535
|
-
const metadataLine = op !== "delete" ? `\n${formatMetadataLine(metadataLineCount, language, uiTheme)}` : "";
|
|
536
520
|
|
|
537
521
|
const displayErrorText = isError && details && "displayErrorText" in details ? details.displayErrorText : undefined;
|
|
538
522
|
const errorText = isError
|
|
@@ -556,6 +540,11 @@ function renderSingleFileResult(
|
|
|
556
540
|
(details && !isError ? details.firstChangedLine : undefined);
|
|
557
541
|
const { description } = formatEditDescription(rawPath, uiTheme, { rename, firstChangedLine });
|
|
558
542
|
|
|
543
|
+
// Change stats ride inline on the header next to the path rather than a separate row.
|
|
544
|
+
const previewDiff = editDiffPreview && !("error" in editDiffPreview) ? editDiffPreview.diff : undefined;
|
|
545
|
+
const headerDiff = isError ? undefined : details?.diff || previewDiff;
|
|
546
|
+
const statsSuffix = headerDiff ? formatDiffStatsSuffix(headerDiff, uiTheme) : "";
|
|
547
|
+
|
|
559
548
|
const header = renderStatusLine(
|
|
560
549
|
{
|
|
561
550
|
icon: isError ? "error" : "success",
|
|
@@ -564,8 +553,7 @@ function renderSingleFileResult(
|
|
|
564
553
|
},
|
|
565
554
|
uiTheme,
|
|
566
555
|
);
|
|
567
|
-
let text = header;
|
|
568
|
-
text += metadataLine;
|
|
556
|
+
let text = header + statsSuffix;
|
|
569
557
|
|
|
570
558
|
if (isError) {
|
|
571
559
|
if (errorText) {
|
package/src/edit/streaming.ts
CHANGED
|
@@ -424,7 +424,7 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
424
424
|
return previews.length > 0 ? previews : null;
|
|
425
425
|
},
|
|
426
426
|
renderStreamingFallback() {
|
|
427
|
-
// Never leak raw hashline syntax (`64:`, `|payload`,
|
|
427
|
+
// Never leak raw hashline syntax (`64:`, `|payload`, `[path#hash]`)
|
|
428
428
|
// to the user — the streaming preview already projects every
|
|
429
429
|
// parseable op onto the real file via applyPartialTo, and an
|
|
430
430
|
// unparseable trailing chunk renders as "no preview yet" rather
|