@oh-my-pi/pi-coding-agent 17.2.1 → 17.2.3
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 +61 -0
- package/dist/{CHANGELOG-dj46zzrm.md → CHANGELOG-c5hpqt9r.md} +61 -0
- package/dist/cli.js +3856 -3850
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +16 -5
- package/dist/types/edit/hashline/block-resolver.d.ts +5 -5
- package/dist/types/edit/hashline/diff.d.ts +4 -4
- package/dist/types/eval/executor-base.d.ts +27 -0
- package/dist/types/eval/js/context-manager.d.ts +7 -0
- package/dist/types/eval/js/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +64 -5
- package/dist/types/extensibility/extensions/types.d.ts +15 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +1 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
- package/dist/types/live/visualizer.d.ts +3 -1
- package/dist/types/modes/acp/acp-agent.d.ts +3 -3
- package/dist/types/modes/components/ask-dialog.d.ts +0 -1
- package/dist/types/modes/components/keybinding-hints.d.ts +1 -1
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +13 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/secrets/index.d.ts +22 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/session/codex-auto-reset.d.ts +182 -64
- package/dist/types/session/session-storage.d.ts +5 -2
- package/dist/types/session/session-tools.d.ts +2 -1
- package/dist/types/session/turn-recovery.d.ts +7 -1
- package/dist/types/system-prompt.d.ts +8 -0
- package/dist/types/task/executor.d.ts +27 -0
- package/dist/types/task/persisted-revive.d.ts +7 -0
- package/dist/types/tools/bash-interceptor.d.ts +1 -1
- package/dist/types/tools/browser/attach.d.ts +9 -1
- package/dist/types/tools/browser/launch.d.ts +50 -1
- package/dist/types/tools/browser/registry.d.ts +7 -0
- package/dist/types/tools/browser/shared-daemon.d.ts +26 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +8 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/shell-tokenize.d.ts +11 -0
- package/dist/types/utils/image-loading.d.ts +2 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/src/commit/agentic/index.ts +2 -1
- package/src/commit/pipeline.ts +3 -2
- package/src/config/keybindings.ts +6 -1
- package/src/config/settings-schema.ts +16 -3
- package/src/edit/hashline/block-resolver.ts +5 -5
- package/src/edit/hashline/diff.ts +22 -15
- package/src/edit/hashline/execute.ts +14 -13
- package/src/edit/renderer.ts +3 -3
- package/src/eval/executor-base.ts +39 -0
- package/src/eval/jl/executor.ts +13 -2
- package/src/eval/js/context-manager.ts +97 -14
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/index.ts +1 -0
- package/src/eval/py/executor.ts +55 -15
- package/src/eval/rb/executor.ts +8 -1
- package/src/extensibility/extensions/runner.ts +121 -4
- package/src/extensibility/extensions/types.ts +15 -0
- package/src/extensibility/extensions/wrapper.ts +19 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +5 -5
- package/src/launch/broker.ts +59 -41
- package/src/live/visualizer.ts +8 -1
- package/src/lsp/index.ts +51 -11
- package/src/main.ts +3 -0
- package/src/modes/acp/acp-agent.ts +16 -5
- package/src/modes/components/ask-dialog.ts +65 -23
- package/src/modes/components/assistant-message.ts +5 -10
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/keybinding-hints.ts +4 -13
- package/src/modes/components/status-line/segments.ts +4 -5
- package/src/modes/components/tool-execution.ts +4 -5
- package/src/modes/controllers/input-controller.ts +18 -40
- package/src/modes/controllers/live-command-controller.ts +1 -0
- package/src/modes/controllers/mcp-command-controller.ts +28 -11
- package/src/modes/interactive-mode.ts +59 -2
- package/src/modes/theme/theme.ts +13 -6
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/system/plan-mode-active.md +5 -5
- package/src/sdk.ts +47 -16
- package/src/secrets/index.ts +83 -3
- package/src/secrets/obfuscator.ts +70 -15
- package/src/session/agent-session-types.ts +7 -1
- package/src/session/agent-session.ts +394 -113
- package/src/session/codex-auto-reset.ts +594 -123
- package/src/session/eval-runner.ts +2 -0
- package/src/session/messages.ts +1 -1
- package/src/session/session-maintenance.ts +50 -3
- package/src/session/session-manager.ts +4 -3
- package/src/session/session-stats.ts +14 -3
- package/src/session/session-storage.ts +47 -15
- package/src/session/session-tools.ts +30 -2
- package/src/session/turn-recovery.ts +15 -2
- package/src/slash-commands/builtin-registry.ts +21 -15
- package/src/slash-commands/helpers/reset-usage.ts +2 -0
- package/src/system-prompt.ts +13 -1
- package/src/task/executor.ts +168 -15
- package/src/task/persisted-revive.ts +26 -1
- package/src/telemetry-export.ts +8 -4
- package/src/tools/bash-interceptor.ts +85 -8
- package/src/tools/bash.ts +12 -6
- package/src/tools/browser/attach.ts +35 -10
- package/src/tools/browser/launch.ts +110 -16
- package/src/tools/browser/registry.ts +140 -31
- package/src/tools/browser/shared-daemon.ts +190 -0
- package/src/tools/browser/tab-protocol.ts +8 -0
- package/src/tools/browser/tab-supervisor.ts +31 -4
- package/src/tools/browser/tab-worker.ts +32 -2
- package/src/tools/browser.ts +1 -1
- package/src/tools/output-meta.ts +16 -1
- package/src/tools/shell-tokenize.ts +101 -0
- package/src/utils/image-loading.ts +8 -3
- package/src/utils/shell-snapshot.ts +31 -21
- package/src/web/search/providers/codex.ts +31 -6
- package/src/web/search/providers/duckduckgo.ts +183 -15
- package/src/web/search/types.ts +6 -1
|
@@ -2,6 +2,15 @@ import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
|
2
2
|
import type { SearchResponse } from "../../../web/search/types.js";
|
|
3
3
|
import type { SearchParams } from "./base.js";
|
|
4
4
|
import { SearchProvider } from "./base.js";
|
|
5
|
+
/**
|
|
6
|
+
* Map a parsed `lang:` locale onto DuckDuckGo's documented `kl` values.
|
|
7
|
+
*
|
|
8
|
+
* Shared queries use `language-region` order while DDG generally uses
|
|
9
|
+
* `region-language`. Provider-specific exceptions resolve through
|
|
10
|
+
* {@link DDG_LOCALE_ALIASES}; all other values must survive the documented
|
|
11
|
+
* allowlist after swapping or the caller keeps its default region.
|
|
12
|
+
*/
|
|
13
|
+
export declare function localeToKl(lang: string | undefined): string | undefined;
|
|
5
14
|
/** Execute a DuckDuckGo web search via the no-JS HTML frontend. */
|
|
6
15
|
export declare function searchDuckDuckGo(params: SearchParams): Promise<SearchResponse>;
|
|
7
16
|
/** Search provider for DuckDuckGo (no API key required). */
|
|
@@ -26,7 +26,7 @@ export declare const SEARCH_PROVIDER_OPTIONS: readonly [{
|
|
|
26
26
|
}, {
|
|
27
27
|
readonly value: "xai";
|
|
28
28
|
readonly label: "xAI";
|
|
29
|
-
readonly description: "Grok web search via xAI Responses API (
|
|
29
|
+
readonly description: "Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)";
|
|
30
30
|
}, {
|
|
31
31
|
readonly value: "zai";
|
|
32
32
|
readonly label: "Z.AI";
|
|
@@ -128,7 +128,7 @@ export declare const SEARCH_PROVIDER_CHOICES: ({
|
|
|
128
128
|
} | {
|
|
129
129
|
readonly value: "xai";
|
|
130
130
|
readonly label: "xAI";
|
|
131
|
-
readonly description: "Grok web search via xAI Responses API (
|
|
131
|
+
readonly description: "Grok web search via xAI Responses API (uses SuperGrok/X Premium+ OAuth via /login xai-oauth, or XAI_API_KEY)";
|
|
132
132
|
} | {
|
|
133
133
|
readonly value: "zai";
|
|
134
134
|
readonly label: "Z.AI";
|
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": "17.2.
|
|
4
|
+
"version": "17.2.3",
|
|
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",
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
53
53
|
"@babel/parser": "^7.29.7",
|
|
54
54
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@oh-my-pi/hashline": "17.2.
|
|
56
|
-
"@oh-my-pi/omp-stats": "17.2.
|
|
57
|
-
"@oh-my-pi/pi-agent-core": "17.2.
|
|
58
|
-
"@oh-my-pi/pi-ai": "17.2.
|
|
59
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
60
|
-
"@oh-my-pi/pi-mnemopi": "17.2.
|
|
61
|
-
"@oh-my-pi/pi-natives": "17.2.
|
|
62
|
-
"@oh-my-pi/pi-tui": "17.2.
|
|
63
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
64
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
65
|
-
"@oh-my-pi/snapcompact": "17.2.
|
|
55
|
+
"@oh-my-pi/hashline": "17.2.3",
|
|
56
|
+
"@oh-my-pi/omp-stats": "17.2.3",
|
|
57
|
+
"@oh-my-pi/pi-agent-core": "17.2.3",
|
|
58
|
+
"@oh-my-pi/pi-ai": "17.2.3",
|
|
59
|
+
"@oh-my-pi/pi-catalog": "17.2.3",
|
|
60
|
+
"@oh-my-pi/pi-mnemopi": "17.2.3",
|
|
61
|
+
"@oh-my-pi/pi-natives": "17.2.3",
|
|
62
|
+
"@oh-my-pi/pi-tui": "17.2.3",
|
|
63
|
+
"@oh-my-pi/pi-utils": "17.2.3",
|
|
64
|
+
"@oh-my-pi/pi-wire": "17.2.3",
|
|
65
|
+
"@oh-my-pi/snapcompact": "17.2.3",
|
|
66
66
|
"@opentelemetry/api": "^1.9.1",
|
|
67
67
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
68
68
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
@@ -9,7 +9,7 @@ import { resolvePrimaryModel, resolveSmolModel } from "../../commit/model-select
|
|
|
9
9
|
import type { CommitCommandArgs, ConventionalAnalysis, NumstatEntry } from "../../commit/types";
|
|
10
10
|
import { ModelRegistry } from "../../config/model-registry";
|
|
11
11
|
import { Settings } from "../../config/settings";
|
|
12
|
-
import { discoverAuthStorage, discoverContextFiles } from "../../sdk";
|
|
12
|
+
import { discoverAuthStorage, discoverContextFiles, loadCliExtensionProviders } from "../../sdk";
|
|
13
13
|
import * as git from "../../utils/git";
|
|
14
14
|
import { type ExistingChangelogEntries, runCommitAgentSession } from "./agent";
|
|
15
15
|
import { generateFallbackProposal } from "./fallback";
|
|
@@ -32,6 +32,7 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
32
32
|
process.stdout.write("● Resolving model...\n");
|
|
33
33
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
34
34
|
await modelRegistry.refresh();
|
|
35
|
+
await loadCliExtensionProviders(modelRegistry, settings, cwd);
|
|
35
36
|
const stagedFilesPromise = (async () => {
|
|
36
37
|
let stagedFiles = await git.diff.changedFiles(cwd, { cached: true });
|
|
37
38
|
if (stagedFiles.length === 0) {
|
package/src/commit/pipeline.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
|
4
4
|
import { getProjectDir, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { ModelRegistry } from "../config/model-registry";
|
|
6
6
|
import { Settings } from "../config/settings";
|
|
7
|
-
import { discoverAuthStorage } from "../sdk";
|
|
7
|
+
import { discoverAuthStorage, loadCliExtensionProviders } from "../sdk";
|
|
8
8
|
import { loadProjectContextFiles } from "../system-prompt";
|
|
9
9
|
import * as git from "../utils/git";
|
|
10
10
|
import { runAgenticCommit } from "./agentic";
|
|
@@ -40,11 +40,12 @@ export async function runCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
|
40
40
|
|
|
41
41
|
async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
|
|
42
42
|
const cwd = getProjectDir();
|
|
43
|
-
const settings = await Settings.init();
|
|
43
|
+
const settings = await Settings.init({ cwd });
|
|
44
44
|
const commitSettings = settings.getGroup("commit");
|
|
45
45
|
const authStorage = await discoverAuthStorage();
|
|
46
46
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
47
47
|
await modelRegistry.refresh();
|
|
48
|
+
await loadCliExtensionProviders(modelRegistry, settings, cwd);
|
|
48
49
|
|
|
49
50
|
const {
|
|
50
51
|
model: primaryModel,
|
|
@@ -53,6 +53,7 @@ interface AppKeybindings {
|
|
|
53
53
|
"app.plan.toggle": true;
|
|
54
54
|
"app.history.search": true;
|
|
55
55
|
"app.stt.toggle": true;
|
|
56
|
+
"app.live.toggle": true;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export type AppKeybinding = keyof AppKeybindings;
|
|
@@ -92,7 +93,7 @@ export const KEYBINDINGS = {
|
|
|
92
93
|
description: "Suspend application",
|
|
93
94
|
},
|
|
94
95
|
"app.display.reset": {
|
|
95
|
-
defaultKeys: "
|
|
96
|
+
defaultKeys: "alt+l",
|
|
96
97
|
description: "Reset terminal display",
|
|
97
98
|
},
|
|
98
99
|
"app.thinking.cycle": {
|
|
@@ -222,6 +223,10 @@ export const KEYBINDINGS = {
|
|
|
222
223
|
defaultKeys: [],
|
|
223
224
|
description: "Toggle speech-to-text (default gesture: hold Space)",
|
|
224
225
|
},
|
|
226
|
+
"app.live.toggle": {
|
|
227
|
+
defaultKeys: "ctrl+l",
|
|
228
|
+
description: "Start or stop live voice mode (/live)",
|
|
229
|
+
},
|
|
225
230
|
} as const satisfies KeybindingDefinitions;
|
|
226
231
|
|
|
227
232
|
/**
|
|
@@ -5289,7 +5289,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
5289
5289
|
group: "Services",
|
|
5290
5290
|
label: "Codex Auto-Redeem Saved Resets",
|
|
5291
5291
|
description:
|
|
5292
|
-
"
|
|
5292
|
+
"Spend saved Codex rate-limit resets automatically: restore an account blocked by an exhausted 5h or weekly window when a turn is stuck and no other account can take over, and salvage credits that are about to expire. unset asks before the first spend, yes spends without prompting, and no disables both checks.",
|
|
5293
5293
|
options: [
|
|
5294
5294
|
{
|
|
5295
5295
|
value: "unset",
|
|
@@ -5309,7 +5309,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
5309
5309
|
group: "Services",
|
|
5310
5310
|
label: "Codex Auto-Redeem Min Block",
|
|
5311
5311
|
description:
|
|
5312
|
-
"Only auto-redeem when the natural
|
|
5312
|
+
"Only auto-redeem when the natural unblock — the latest reset among the exhausted 5h/weekly windows — is at least this many minutes away (don't spend a scarce credit to save a short wait). Raise it (e.g. 360) to ignore 5h-only blocks.",
|
|
5313
5313
|
},
|
|
5314
5314
|
},
|
|
5315
5315
|
"codexResets.keepCredits": {
|
|
@@ -5319,7 +5319,19 @@ export const SETTINGS_SCHEMA = {
|
|
|
5319
5319
|
tab: "providers",
|
|
5320
5320
|
group: "Services",
|
|
5321
5321
|
label: "Codex Auto-Redeem Reserve",
|
|
5322
|
-
description:
|
|
5322
|
+
description:
|
|
5323
|
+
"Never auto-spend below this many saved resets (0 = the last credit may be spent automatically). Credits about to expire are exempt — a reserved credit that expires preserves nothing.",
|
|
5324
|
+
},
|
|
5325
|
+
},
|
|
5326
|
+
"codexResets.salvageHorizonHours": {
|
|
5327
|
+
type: "number",
|
|
5328
|
+
default: 12,
|
|
5329
|
+
ui: {
|
|
5330
|
+
tab: "providers",
|
|
5331
|
+
group: "Services",
|
|
5332
|
+
label: "Codex Reset Salvage Horizon",
|
|
5333
|
+
description:
|
|
5334
|
+
"Spend a saved Codex reset automatically when it would otherwise expire within this many hours and either chat window (5h or weekly) has meaningful usage to restore (0 disables expiry salvage).",
|
|
5323
5335
|
},
|
|
5324
5336
|
},
|
|
5325
5337
|
"provider.appendOnlyContext": {
|
|
@@ -5784,6 +5796,7 @@ export interface CodexResetsSettings {
|
|
|
5784
5796
|
autoRedeem: CodexAutoRedeemMode;
|
|
5785
5797
|
minBlockedMinutes: number;
|
|
5786
5798
|
keepCredits: number;
|
|
5799
|
+
salvageHorizonHours: number;
|
|
5787
5800
|
}
|
|
5788
5801
|
|
|
5789
5802
|
export interface GcSettings {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tree-sitter-backed {@link BlockResolver} for the hashline
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Tree-sitter-backed {@link BlockResolver} for the hashline `N*` locator.
|
|
3
|
+
* Bridges the pure hashline seam to the native `blockRangeAt` primitive in
|
|
4
|
+
* `@oh-my-pi/pi-natives`, which infers the language from the file path and
|
|
5
|
+
* returns the 1-indexed line span of the syntactic block beginning on the
|
|
6
|
+
* requested line (or `null` when none can be resolved).
|
|
7
7
|
*/
|
|
8
8
|
import type { BlockResolver } from "@oh-my-pi/hashline";
|
|
9
9
|
import { blockRangeAt } from "@oh-my-pi/pi-natives";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type Cursor,
|
|
18
18
|
computeFileHash,
|
|
19
19
|
type Edit,
|
|
20
|
+
forkClipboard,
|
|
20
21
|
Patch as HashlinePatch,
|
|
21
22
|
hasBlockEdit,
|
|
22
23
|
MismatchError,
|
|
@@ -53,10 +54,10 @@ export interface HashlineDiffOptions {
|
|
|
53
54
|
skipHashValidation?: boolean;
|
|
54
55
|
/**
|
|
55
56
|
* Clipboard register shared across the sections of one patch preview.
|
|
56
|
-
* `CUT` in an earlier section feeds `
|
|
57
|
-
* matches apply. Multi-section previews MUST thread one
|
|
58
|
-
* sections in patch order; omitted, each section gets a
|
|
59
|
-
* private register (same-file cut/
|
|
57
|
+
* `CUT` in an earlier section feeds a register-backed `PUT` in a later one,
|
|
58
|
+
* so the preview matches apply. Multi-section previews MUST thread one
|
|
59
|
+
* register through sections in patch order; omitted, each section gets a
|
|
60
|
+
* private register (same-file cut/put still previews correctly).
|
|
60
61
|
*/
|
|
61
62
|
clipboard?: Clipboard;
|
|
62
63
|
}
|
|
@@ -153,9 +154,11 @@ async function readSectionForPreview(
|
|
|
153
154
|
|
|
154
155
|
function hasAnchorScopedEdit(edits: readonly Edit[]): boolean {
|
|
155
156
|
return edits.some(edit => {
|
|
156
|
-
if (edit.kind === "delete") return true;
|
|
157
|
-
if (edit.kind === "
|
|
158
|
-
|
|
157
|
+
if (edit.kind === "delete" || edit.kind === "block" || edit.kind === "cut") return true;
|
|
158
|
+
if (edit.kind === "paste") {
|
|
159
|
+
if (edit.at.kind === "span") return true;
|
|
160
|
+
return edit.at.cursor.kind === "before_anchor" || edit.at.cursor.kind === "after_anchor";
|
|
161
|
+
}
|
|
159
162
|
return edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor";
|
|
160
163
|
});
|
|
161
164
|
}
|
|
@@ -279,19 +282,23 @@ function buildStreamingSectionDiff(
|
|
|
279
282
|
onUnresolved: "drop",
|
|
280
283
|
});
|
|
281
284
|
const fileLines = normalized.split("\n");
|
|
282
|
-
// Expand
|
|
283
|
-
// a
|
|
284
|
-
// capture (out-of-range while the range digits are still
|
|
285
|
-
// transient sequencing violations) throws AFTER earlier
|
|
286
|
-
// landed, and leaking those into the shared register
|
|
287
|
-
// section preview a
|
|
288
|
-
//
|
|
289
|
-
const scratch
|
|
285
|
+
// Expand register-backed PUT ops so moved rows appear in the preview.
|
|
286
|
+
// Resolve into a transactional register and publish it only on success: a
|
|
287
|
+
// mid-typed capture (out-of-range while the range digits are still
|
|
288
|
+
// streaming, transient sequencing violations) throws AFTER earlier
|
|
289
|
+
// captures may have landed, and leaking those into the shared register
|
|
290
|
+
// would let a later section preview a PUT that never happened. On failure,
|
|
291
|
+
// filter the clipboard edits out of this section's frame.
|
|
292
|
+
const scratch = forkClipboard(clipboard);
|
|
290
293
|
let resolved: readonly Edit[];
|
|
291
294
|
try {
|
|
292
295
|
resolved = resolveClipboardEdits(blockResolved, fileLines, scratch, { onEmptyPaste: "drop" });
|
|
293
296
|
if (scratch.lines === undefined) delete clipboard.lines;
|
|
294
297
|
else clipboard.lines = scratch.lines;
|
|
298
|
+
if (scratch.named === undefined) delete clipboard.named;
|
|
299
|
+
else clipboard.named = scratch.named;
|
|
300
|
+
if (scratch.pendingAnonCuts === undefined) delete clipboard.pendingAnonCuts;
|
|
301
|
+
else clipboard.pendingAnonCuts = scratch.pendingAnonCuts;
|
|
295
302
|
} catch {
|
|
296
303
|
resolved = blockResolved.filter(edit => edit.kind !== "cut" && edit.kind !== "paste");
|
|
297
304
|
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
Patcher,
|
|
22
22
|
type PatchSectionResult,
|
|
23
23
|
type PreparedSection,
|
|
24
|
+
startClipboardBatch,
|
|
24
25
|
} from "@oh-my-pi/hashline";
|
|
25
26
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
26
27
|
import type { FileDiagnosticsResult, WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
|
|
@@ -48,7 +49,7 @@ export interface ExecuteHashlineSingleOptions {
|
|
|
48
49
|
|
|
49
50
|
function noChangeDiagnostic(path: string): string {
|
|
50
51
|
// The patch parsed and applied cleanly but produced no change — the
|
|
51
|
-
//
|
|
52
|
+
// `+TEXT` body rows matched the file content at the targeted lines
|
|
52
53
|
// byte-for-byte. The model usually misreads this as "wrong anchor, try
|
|
53
54
|
// again with a bigger payload" and starts duplicating content; the
|
|
54
55
|
// message below names the cause directly so the next turn can re-read
|
|
@@ -103,14 +104,14 @@ interface RenderedSection {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
const BLOCK_OP_LABELS: Record<BlockResolution["op"], string> = {
|
|
106
|
-
replace: "
|
|
107
|
-
insert_after: "
|
|
108
|
-
cut: "CUT
|
|
109
|
-
paste_after: "
|
|
107
|
+
replace: "PUT N*:",
|
|
108
|
+
insert_after: "PUT >N*:",
|
|
109
|
+
cut: "CUT N*",
|
|
110
|
+
paste_after: "PUT >N*",
|
|
110
111
|
};
|
|
111
112
|
|
|
112
113
|
function formatBlockResolution(resolution: BlockResolution): string {
|
|
113
|
-
const op = BLOCK_OP_LABELS[resolution.op];
|
|
114
|
+
const op = BLOCK_OP_LABELS[resolution.op].replace("N", String(resolution.anchorLine));
|
|
114
115
|
const lines = resolution.end - resolution.start + 1;
|
|
115
116
|
const span =
|
|
116
117
|
resolution.start === resolution.end ? `line ${resolution.start}` : `lines ${resolution.start}-${resolution.end}`;
|
|
@@ -120,7 +121,7 @@ function formatBlockResolution(resolution: BlockResolution): string {
|
|
|
120
121
|
: resolution.op === "paste_after"
|
|
121
122
|
? `; clipboard lands after line ${resolution.end}`
|
|
122
123
|
: "";
|
|
123
|
-
return `${op}
|
|
124
|
+
return `${op} → resolved ${span} (${lines} line${lines === 1 ? "" : "s"})${suffix}`;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
function renderSection(
|
|
@@ -229,11 +230,11 @@ export async function executeHashlineSingle(
|
|
|
229
230
|
const enforceSeenLines = options.session.settings.get("edit.enforceSeenLines");
|
|
230
231
|
const patcher = new Patcher({ fs, snapshots, blockResolver: nativeBlockResolver, enforceSeenLines });
|
|
231
232
|
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// only after writes land.
|
|
233
|
+
// Named registers persist across edit calls; the anonymous register is
|
|
234
|
+
// batch-local. Each batch starts without anonymous state and publishes
|
|
235
|
+
// named registers only after writes land.
|
|
235
236
|
const sessionClipboard = getEditClipboard(options.session);
|
|
236
|
-
const clipboard =
|
|
237
|
+
const clipboard = startClipboardBatch(sessionClipboard);
|
|
237
238
|
|
|
238
239
|
// Single-section fast path: prepare, commit, render.
|
|
239
240
|
const inputHash = hashPatchInput(options.input);
|
|
@@ -254,8 +255,8 @@ export async function executeHashlineSingle(
|
|
|
254
255
|
}
|
|
255
256
|
|
|
256
257
|
// Multi-section: prepare every section up front so we fail fast before
|
|
257
|
-
// any write hits the filesystem. One
|
|
258
|
-
// so `CUT` in one section feeds `
|
|
258
|
+
// any write hits the filesystem. One batch-local register spans the batch,
|
|
259
|
+
// so `CUT` in one section feeds a register-backed `PUT` in a later one.
|
|
259
260
|
const prepared: PreparedSection[] = [];
|
|
260
261
|
// Register state after each section's prepare. Commits are non-atomic: a
|
|
261
262
|
// mid-batch write failure leaves earlier sections on disk, so the session
|
package/src/edit/renderer.ts
CHANGED
|
@@ -140,7 +140,7 @@ interface HashlineInputEntry {
|
|
|
140
140
|
path: string;
|
|
141
141
|
op?: Operation;
|
|
142
142
|
rename?: string;
|
|
143
|
-
/** A
|
|
143
|
+
/** A PUT/CUT line edit precedes the file op — keeps a move framed. */
|
|
144
144
|
hasLineEdits?: boolean;
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -568,9 +568,9 @@ function parseHashlineInputPreviewHeader(line: string): string | null {
|
|
|
568
568
|
return previewPath.length > 0 ? previewPath : null;
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
// Line-editing op headers (
|
|
571
|
+
// Line-editing op headers (PUT/CUT family), distinct from file-level
|
|
572
572
|
// REM/MV ops. Body rows are `+TEXT`, so this only matches real headers.
|
|
573
|
-
const HL_LINE_OP_HEADER = /^(?:
|
|
573
|
+
const HL_LINE_OP_HEADER = /^(?:PUT|CUT)\b/;
|
|
574
574
|
|
|
575
575
|
/**
|
|
576
576
|
* Walk a (possibly mid-stream) hashline payload into per-section descriptors:
|
|
@@ -313,6 +313,45 @@ export function attachSessionOwner(
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/** Owner registry shared by every language's live/starting session records. */
|
|
317
|
+
export interface SessionOwners {
|
|
318
|
+
ownerIds: Set<string>;
|
|
319
|
+
hasFallbackOwner: boolean;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Resolve the session key an owner's eval cell runs on, forking `reset` away
|
|
324
|
+
* from shared kernels.
|
|
325
|
+
*
|
|
326
|
+
* Eval sessions are shared across agents by design (subagents inherit the
|
|
327
|
+
* parent's eval session id), so honoring `reset` on a co-owned kernel would
|
|
328
|
+
* destroy every other agent's state — including cells executing at that
|
|
329
|
+
* moment. When the requester does not exclusively own the live base session,
|
|
330
|
+
* its reset resolves to a deterministic per-owner fork key: the requester
|
|
331
|
+
* starts a fresh private kernel while co-owners keep the shared one. Once
|
|
332
|
+
* forked, the owner keeps resolving to its fork, and per-owner dispose reaps
|
|
333
|
+
* the fork since the requester is its only registered owner.
|
|
334
|
+
*/
|
|
335
|
+
export function resolveOwnerScopedSessionKey(options: {
|
|
336
|
+
baseKey: string;
|
|
337
|
+
ownerId: string | undefined;
|
|
338
|
+
reset: boolean;
|
|
339
|
+
/** True when a live or starting session exists under `key`. */
|
|
340
|
+
hasSession: (key: string) => boolean;
|
|
341
|
+
/** Owner registry for the session under `key`, when inspectable. */
|
|
342
|
+
getOwners: (key: string) => SessionOwners | undefined;
|
|
343
|
+
}): string {
|
|
344
|
+
const { baseKey, ownerId } = options;
|
|
345
|
+
if (ownerId === undefined) return baseKey;
|
|
346
|
+
const forkKey = `${baseKey}\0fork\0${ownerId}`;
|
|
347
|
+
if (options.hasSession(forkKey)) return forkKey;
|
|
348
|
+
if (!options.reset) return baseKey;
|
|
349
|
+
const base = options.getOwners(baseKey);
|
|
350
|
+
if (!base) return baseKey;
|
|
351
|
+
const exclusive = !base.hasFallbackOwner && base.ownerIds.size === 1 && base.ownerIds.has(ownerId);
|
|
352
|
+
return exclusive ? baseKey : forkKey;
|
|
353
|
+
}
|
|
354
|
+
|
|
316
355
|
// ---------------------------------------------------------------------------
|
|
317
356
|
// Base executor implementation
|
|
318
357
|
// ---------------------------------------------------------------------------
|
package/src/eval/jl/executor.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import type { ToolSession } from "../../tools";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
attachSessionOwner,
|
|
6
|
+
createCancelledKernelResult,
|
|
7
|
+
executeWithKernelBase,
|
|
8
|
+
resolveOwnerScopedSessionKey,
|
|
9
|
+
} from "../executor-base";
|
|
5
10
|
import { ensurePyToolBridge, type PyToolBridgeInfo } from "../py/tool-bridge";
|
|
6
11
|
import type { EvalDisplayOutput, EvalStatusEvent } from "../types";
|
|
7
12
|
import {
|
|
@@ -451,7 +456,13 @@ async function ensureToolBridge(options: JuliaExecutorOptions): Promise<void> {
|
|
|
451
456
|
|
|
452
457
|
async function executeOnSession(code: string, cwd: string, options: JuliaExecutorOptions): Promise<JuliaResult> {
|
|
453
458
|
const sessionId = options.sessionId ?? `session:${cwd}`;
|
|
454
|
-
const sessionKey =
|
|
459
|
+
const sessionKey = resolveOwnerScopedSessionKey({
|
|
460
|
+
baseKey: buildSessionKey(sessionId, cwd, options.interpreter),
|
|
461
|
+
ownerId: options.kernelOwnerId,
|
|
462
|
+
reset: options.reset === true,
|
|
463
|
+
hasSession: key => sessions.has(key) || startingSessions.has(key),
|
|
464
|
+
getOwners: key => sessions.get(key) ?? startingSessions.get(key),
|
|
465
|
+
});
|
|
455
466
|
if (options.bridge && !options.bridgeSessionId) {
|
|
456
467
|
options.bridgeSessionId = sessionId;
|
|
457
468
|
}
|