@oh-my-pi/pi-coding-agent 17.2.5 → 17.2.7
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 +35 -1
- package/dist/{CHANGELOG-q2w43aw3.md → CHANGELOG-1p8yvde7.md} +35 -1
- package/dist/cli.js +3340 -3339
- package/dist/types/advisor/advise-tool.d.ts +10 -4
- package/dist/types/autoresearch/tools/init-experiment.d.ts +19 -7
- package/dist/types/autoresearch/tools/log-experiment.d.ts +19 -11
- package/dist/types/autoresearch/tools/run-experiment.d.ts +4 -2
- package/dist/types/autoresearch/tools/update-notes.d.ts +5 -2
- package/dist/types/cli/gc-cli.d.ts +1 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +5 -2
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +5 -2
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +6 -2
- package/dist/types/commit/agentic/tools/git-overview.d.ts +5 -2
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +29 -7
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +14 -4
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +4 -2
- package/dist/types/commit/agentic/tools/schemas.d.ts +7 -3
- package/dist/types/commit/agentic/tools/split-commit.d.ts +37 -10
- package/dist/types/commit/changelog/generate.d.ts +13 -3
- package/dist/types/commit/shared-llm.d.ts +13 -4
- package/dist/types/config/config-file.d.ts +1 -1
- package/dist/types/config/models-config-schema-bundle.d.ts +1992 -981
- package/dist/types/config/models-config-schema.d.ts +1201 -580
- package/dist/types/config/models-config.d.ts +355 -371
- package/dist/types/edit/hashline/params.d.ts +4 -2
- package/dist/types/edit/modes/apply-patch.d.ts +4 -2
- package/dist/types/edit/modes/patch.d.ts +16 -5
- package/dist/types/edit/modes/replace.d.ts +8 -3
- package/dist/types/eval/bridge-timeout.d.ts +5 -2
- package/dist/types/eval/completion-bridge.d.ts +13 -0
- package/dist/types/eval/py/tool-bridge.d.ts +12 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +4 -4
- package/dist/types/extensibility/custom-tools/types.d.ts +4 -4
- package/dist/types/extensibility/extensions/types.d.ts +4 -4
- package/dist/types/extensibility/hooks/types.d.ts +4 -4
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
- package/dist/types/extensibility/legacy-typebox.d.ts +8 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +6 -13
- package/dist/types/goals/tools/goal-tool.d.ts +12 -4
- package/dist/types/launch/client.d.ts +9 -1
- package/dist/types/launch/protocol.d.ts +17 -0
- package/dist/types/lsp/index.d.ts +15 -5
- package/dist/types/lsp/types.d.ts +15 -5
- package/dist/types/modes/components/btw-panel.d.ts +3 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/security/contracts/schemas.d.ts +1115 -537
- package/dist/types/security/publication.d.ts +92 -34
- package/dist/types/session/agent-session-types.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +26 -2
- package/dist/types/session/launch-completion.d.ts +10 -0
- package/dist/types/session/session-entries.d.ts +15 -1
- package/dist/types/session/session-manager.d.ts +7 -0
- package/dist/types/session/yield-queue.d.ts +6 -1
- package/dist/types/task/types.d.ts +63 -18
- package/dist/types/tools/ask.d.ts +34 -8
- package/dist/types/tools/ast-edit.d.ts +16 -4
- package/dist/types/tools/ast-grep.d.ts +12 -4
- package/dist/types/tools/bash.d.ts +20 -11
- package/dist/types/tools/browser.d.ts +64 -20
- package/dist/types/tools/checkpoint.d.ts +16 -8
- package/dist/types/tools/computer.d.ts +1 -1
- package/dist/types/tools/debug.d.ts +118 -48
- package/dist/types/tools/eval.d.ts +11 -5
- package/dist/types/tools/gh.d.ts +74 -26
- package/dist/types/tools/glob.d.ts +16 -6
- package/dist/types/tools/grep.d.ts +18 -6
- package/dist/types/tools/hub/index.d.ts +116 -46
- package/dist/types/tools/image-gen.d.ts +25 -8
- package/dist/types/tools/index.d.ts +9 -0
- package/dist/types/tools/inspect-image.d.ts +10 -4
- package/dist/types/tools/learn.d.ts +26 -8
- package/dist/types/tools/manage-skill.d.ts +16 -6
- package/dist/types/tools/memory-edit.d.ts +18 -6
- package/dist/types/tools/memory-recall.d.ts +8 -4
- package/dist/types/tools/memory-reflect.d.ts +10 -4
- package/dist/types/tools/memory-retain.d.ts +14 -4
- package/dist/types/tools/read.d.ts +8 -4
- package/dist/types/tools/security-scan.d.ts +82 -30
- package/dist/types/tools/todo.d.ts +30 -10
- package/dist/types/tools/tts.d.ts +12 -5
- package/dist/types/tools/vibe.d.ts +42 -18
- package/dist/types/tools/write.d.ts +10 -4
- package/dist/types/web/search/index.d.ts +20 -6
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +2 -2
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/config.ts +1 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -2
- package/src/autoresearch/tools/log-experiment.ts +1 -2
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/cli/gc-cli.ts +641 -21
- package/src/commit/agentic/tools/analyze-file.ts +1 -1
- package/src/commit/agentic/tools/git-file-diff.ts +1 -1
- package/src/commit/agentic/tools/git-hunk.ts +1 -1
- package/src/commit/agentic/tools/git-overview.ts +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +1 -1
- package/src/commit/agentic/tools/propose-commit.ts +1 -1
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -1
- package/src/commit/agentic/tools/split-commit.ts +1 -1
- package/src/commit/analysis/summary.ts +1 -1
- package/src/commit/changelog/generate.ts +1 -1
- package/src/config/config-file.ts +2 -2
- package/src/config/model-discovery.ts +14 -14
- package/src/config/model-registry.ts +10 -8
- package/src/config/models-config-schema-bundle.ts +1 -8
- package/src/config/settings.ts +1 -1
- package/src/discovery/codex.ts +22 -9
- package/src/edit/hashline/params.ts +1 -1
- package/src/edit/modes/apply-patch.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/modes/replace.ts +2 -1
- package/src/eval/agent-bridge.ts +4 -2
- package/src/eval/bridge-timeout.ts +5 -2
- package/src/eval/completion-bridge.ts +3 -2
- package/src/eval/executor-base.ts +20 -1
- package/src/eval/js/context-manager.ts +94 -6
- package/src/eval/py/tool-bridge.ts +29 -9
- package/src/export/html/index.ts +10 -3
- package/src/export/share.ts +4 -0
- package/src/extensibility/custom-commands/loader.ts +3 -3
- package/src/extensibility/custom-commands/types.ts +4 -4
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/types.ts +5 -4
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/types.ts +5 -4
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/types.ts +4 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +2 -2
- package/src/extensibility/legacy-typebox.ts +101 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +13 -22
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/launch/broker.ts +222 -6
- package/src/launch/client.ts +161 -9
- package/src/launch/protocol.ts +52 -0
- package/src/lsp/types.ts +1 -1
- package/src/main.ts +13 -6
- package/src/mcp/config-writer.ts +1 -1
- package/src/modes/components/btw-panel.ts +41 -4
- package/src/modes/controllers/btw-controller.ts +55 -7
- package/src/modes/controllers/command-controller.ts +31 -0
- package/src/modes/controllers/input-controller.ts +24 -7
- package/src/modes/interactive-mode.ts +16 -2
- package/src/modes/theme/theme.ts +3 -3
- package/src/modes/types.ts +8 -1
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/sdk.ts +15 -0
- package/src/security/contracts/schemas.ts +202 -184
- package/src/security/contracts/validation.ts +6 -2
- package/src/security/publication.ts +1 -1
- package/src/security/store.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +190 -14
- package/src/session/launch-completion.ts +37 -0
- package/src/session/session-context.ts +26 -0
- package/src/session/session-entries.ts +17 -1
- package/src/session/session-manager.ts +21 -0
- package/src/session/yield-queue.ts +121 -16
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +1 -1
- package/src/tools/bash.ts +61 -3
- package/src/tools/browser/cmux/cmux-tab.ts +92 -4
- package/src/tools/browser.ts +1 -1
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/computer.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval.ts +1 -1
- package/src/tools/gh.ts +1 -2
- package/src/tools/glob.ts +1 -1
- package/src/tools/grep.ts +1 -1
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/launch.ts +122 -6
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +9 -0
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/learn.ts +1 -1
- package/src/tools/manage-skill.ts +1 -1
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +1 -1
- package/src/tools/memory-retain.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/tools/security-scan.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +1 -1
- package/src/tools/vibe.ts +2 -1
- package/src/tools/write.ts +1 -1
- package/src/web/search/index.ts +2 -1
- package/dist/types/config/file-lock.d.ts +0 -29
- package/dist/types/extensibility/typebox.d.ts +0 -181
- package/src/config/file-lock.ts +0 -164
- package/src/extensibility/typebox.ts +0 -958
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
1
2
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
2
|
-
import { type } from "arktype";
|
|
3
3
|
import analyzeFilePrompt from "../../../commit/agentic/prompts/analyze-file.md" with { type: "text" };
|
|
4
4
|
import type { CommitAgentState } from "../../../commit/agentic/state";
|
|
5
5
|
import type { NumstatEntry } from "../../../commit/types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type } from "
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
2
2
|
import type { CommitAgentState } from "../../../commit/agentic/state";
|
|
3
3
|
import type { CustomTool } from "../../../extensibility/custom-tools/types";
|
|
4
4
|
import * as git from "../../../utils/git";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type } from "
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
2
2
|
import type { CommitAgentState, GitOverviewSnapshot } from "../../../commit/agentic/state";
|
|
3
3
|
import { extractScopeCandidates } from "../../../commit/analysis/scope";
|
|
4
4
|
import type { CustomTool } from "../../../extensibility/custom-tools/types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type } from "
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
2
2
|
import type { CommitAgentState } from "../../../commit/agentic/state";
|
|
3
3
|
import { CHANGELOG_CATEGORIES, type ChangelogCategory } from "../../../commit/types";
|
|
4
4
|
import type { CustomTool } from "../../../extensibility/custom-tools/types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type } from "
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
2
2
|
import type { CommitAgentState, SplitCommitGroup, SplitCommitPlan } from "../../../commit/agentic/state";
|
|
3
3
|
import { computeDependencyOrder } from "../../../commit/agentic/topo-sort";
|
|
4
4
|
import {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
1
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import { type } from "arktype";
|
|
6
6
|
import summarySystemPrompt from "../../commit/prompts/summary-system.md" with { type: "text" };
|
|
7
7
|
import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
|
|
8
8
|
import type { CommitSummary } from "../../commit/types";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
1
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
3
|
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import { type } from "arktype";
|
|
6
6
|
import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
|
|
7
7
|
import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
|
|
8
8
|
import type { ChangelogGenerationResult } from "../../commit/types";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { OmpErrors, type Type } from "@oh-my-pi/omptype";
|
|
3
4
|
import { getAgentDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
4
|
-
import { ArkErrors, type Type } from "arktype";
|
|
5
5
|
import { JSONC, YAML } from "bun";
|
|
6
6
|
|
|
7
7
|
/** Minimal subset of the AJV ConfigSchemaError shape this module actually relies on. */
|
|
@@ -252,7 +252,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
const checked = this.schema(parsed);
|
|
255
|
-
if (checked instanceof
|
|
255
|
+
if (checked instanceof OmpErrors) {
|
|
256
256
|
const schemaErrors: ConfigSchemaError[] = checked.map(error => ({
|
|
257
257
|
instancePath: error.path.length === 0 ? "root" : error.path.join("."),
|
|
258
258
|
message: error.problem,
|
|
@@ -38,25 +38,25 @@ export const DISCOVERY_DEFAULT_CONTEXT_WINDOW = OPENAI_COMPAT_DISCOVERY_DEFAULT_
|
|
|
38
38
|
export const DISCOVERY_DEFAULT_MAX_TOKENS = OPENAI_COMPAT_DISCOVERY_DEFAULT_MAX_TOKENS;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Run `fn` with
|
|
42
|
-
*
|
|
41
|
+
* Run `fn` with a hard deadline while also signalling cooperative transports
|
|
42
|
+
* to abort. The independent rejection keeps discovery bounded when a runtime
|
|
43
|
+
* leaves its fetch promise pending after `AbortSignal.abort()` (observed with
|
|
44
|
+
* Windows localhost probes).
|
|
43
45
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* abort-signal timeout (e.g. discovery against a mocked fetch that resolves
|
|
48
|
-
* instantly) fires seconds later and sets its abort `reason` — which crashed
|
|
49
|
-
* Bun's concurrent GC while it marked the signal's wrapped reason during an
|
|
50
|
-
* unrelated allocation (`JSAbortSignal::visitAdditionalChildren`).
|
|
46
|
+
* The backing timer is cleared as soon as `fn` settles, unlike
|
|
47
|
+
* `AbortSignal.timeout()`, whose delayed reason previously crashed Bun's
|
|
48
|
+
* concurrent GC during an unrelated allocation.
|
|
51
49
|
*/
|
|
52
50
|
async function withTimeoutSignal<T>(timeoutMs: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T> {
|
|
53
51
|
const controller = new AbortController();
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const timeout = Promise.withResolvers<never>();
|
|
53
|
+
const timer = setTimeout(() => {
|
|
54
|
+
const error = new DOMException("The operation timed out.", "TimeoutError");
|
|
55
|
+
controller.abort(error);
|
|
56
|
+
timeout.reject(error);
|
|
57
|
+
}, timeoutMs);
|
|
58
58
|
try {
|
|
59
|
-
return await fn(controller.signal);
|
|
59
|
+
return await Promise.race([fn(controller.signal), timeout.promise]);
|
|
60
60
|
} finally {
|
|
61
61
|
clearTimeout(timer);
|
|
62
62
|
}
|
|
@@ -68,6 +68,7 @@ import type { ApiKeyResolver, FetchImpl } from "@oh-my-pi/pi-ai";
|
|
|
68
68
|
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
69
69
|
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
|
|
70
70
|
import { setCodexAttestationProvider } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
71
|
+
import { getProviderDefinition } from "@oh-my-pi/pi-ai/registry";
|
|
71
72
|
import {
|
|
72
73
|
getBundledModelReferenceIndex,
|
|
73
74
|
inheritReferenceThinking,
|
|
@@ -2011,14 +2012,15 @@ export class ModelRegistry {
|
|
|
2011
2012
|
this.#providerOverrides.has(descriptor.providerId) ||
|
|
2012
2013
|
this.#keylessProviders.has(descriptor.providerId));
|
|
2013
2014
|
if (isAuthenticated(apiKey) || descriptor.allowUnauthenticated || hasExplicitVllmConfig) {
|
|
2014
|
-
const
|
|
2015
|
-
|
|
2016
|
-
descriptor.
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2015
|
+
const discoveryConfig = {
|
|
2016
|
+
apiKey: isDiscoveryBearerApiKey(apiKey) ? apiKey : undefined,
|
|
2017
|
+
baseUrl: this.#descriptorBaseUrl(descriptor.providerId),
|
|
2018
|
+
fetch: this.#fetch,
|
|
2019
|
+
};
|
|
2020
|
+
const preparedConfig =
|
|
2021
|
+
getProviderDefinition(descriptor.providerId)?.prepareModelDiscovery?.(discoveryConfig) ??
|
|
2022
|
+
discoveryConfig;
|
|
2023
|
+
options.push(descriptor.createModelManagerOptions(preparedConfig));
|
|
2022
2024
|
}
|
|
2023
2025
|
}
|
|
2024
2026
|
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
1
2
|
import { once } from "@oh-my-pi/pi-utils";
|
|
2
|
-
import { scope } from "arktype";
|
|
3
3
|
|
|
4
4
|
export const getModelsConfigSchemaBundle = once(() => {
|
|
5
|
-
// Config schemas validate at most a handful of times per process (on config
|
|
6
|
-
// load), so the eager JIT codegen ArkType runs at definition time is pure
|
|
7
|
-
// startup tax. A local jitless scope skips that codegen and falls back to
|
|
8
|
-
// interpreted traversal — ~65% cheaper to construct, validation correctness
|
|
9
|
-
// unchanged. (No `name`: duplicate module instances would collide.)
|
|
10
|
-
const { type } = scope({}, { jitless: true });
|
|
11
|
-
|
|
12
5
|
const OpenRouterRoutingSchema = type({
|
|
13
6
|
"only?": "string[]",
|
|
14
7
|
"order?": "string[]",
|
package/src/config/settings.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
setWorktreesDir,
|
|
31
31
|
toError,
|
|
32
32
|
} from "@oh-my-pi/pi-utils";
|
|
33
|
+
import { withFileLock } from "@oh-my-pi/pi-utils/file-lock";
|
|
33
34
|
import { JSONC, YAML } from "bun";
|
|
34
35
|
import { invalidate as invalidateCapabilityFsCache } from "../capability/fs";
|
|
35
36
|
import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
|
|
@@ -41,7 +42,6 @@ import { AUTO_IMAGE_PROVIDER_ORDER, isImageProviderId } from "../tools/image-pro
|
|
|
41
42
|
import { type EditMode, normalizeEditMode } from "../utils/edit-mode";
|
|
42
43
|
import { INSPECT_IMAGE_MODES } from "../utils/inspect-image-mode";
|
|
43
44
|
import { isSearchProviderId, SEARCH_PROVIDER_ORDER } from "../web/search/types";
|
|
44
|
-
import { withFileLock } from "./file-lock";
|
|
45
45
|
import {
|
|
46
46
|
type BashInterceptorRule,
|
|
47
47
|
type GroupPrefix,
|
package/src/discovery/codex.ts
CHANGED
|
@@ -87,23 +87,28 @@ async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>>
|
|
|
87
87
|
]);
|
|
88
88
|
|
|
89
89
|
const items: MCPServer[] = [];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
// Capability dedupe is first-wins, including suppressed items claiming their
|
|
91
|
+
// key. Load project entries first so a project `enabled = false` keeps a
|
|
92
|
+
// same-named user server disabled.
|
|
93
|
+
if (projectConfig) {
|
|
94
|
+
const servers = extractMCPServersFromToml(projectConfig, path.dirname(projectConfigPath));
|
|
95
|
+
for (const name in servers) {
|
|
96
|
+
const config = servers[name];
|
|
93
97
|
items.push({
|
|
94
98
|
name,
|
|
95
99
|
...config,
|
|
96
|
-
_source: createSourceMeta(PROVIDER_ID,
|
|
100
|
+
_source: createSourceMeta(PROVIDER_ID, projectConfigPath, "project"),
|
|
97
101
|
});
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
|
-
if (
|
|
101
|
-
const servers = extractMCPServersFromToml(
|
|
102
|
-
for (const
|
|
104
|
+
if (userConfig) {
|
|
105
|
+
const servers = extractMCPServersFromToml(userConfig, path.dirname(userConfigPath));
|
|
106
|
+
for (const name in servers) {
|
|
107
|
+
const config = servers[name];
|
|
103
108
|
items.push({
|
|
104
109
|
name,
|
|
105
110
|
...config,
|
|
106
|
-
_source: createSourceMeta(PROVIDER_ID,
|
|
111
|
+
_source: createSourceMeta(PROVIDER_ID, userConfigPath, "user"),
|
|
107
112
|
});
|
|
108
113
|
}
|
|
109
114
|
}
|
|
@@ -125,6 +130,7 @@ async function loadTomlConfig(_ctx: LoadContext, path: string): Promise<Record<s
|
|
|
125
130
|
|
|
126
131
|
/** Codex MCP server config format (from config.toml) */
|
|
127
132
|
interface CodexMCPConfig {
|
|
133
|
+
enabled?: boolean;
|
|
128
134
|
command?: string;
|
|
129
135
|
args?: string[];
|
|
130
136
|
env?: Record<string, string>;
|
|
@@ -152,13 +158,20 @@ function extractMCPServersFromToml(
|
|
|
152
158
|
const codexServers = toml.mcp_servers as Record<string, CodexMCPConfig>;
|
|
153
159
|
const result: Record<string, Partial<MCPServer>> = {};
|
|
154
160
|
|
|
155
|
-
for (const
|
|
161
|
+
for (const name in codexServers) {
|
|
162
|
+
const config = codexServers[name];
|
|
156
163
|
// Root relative cwd/command against the Codex config directory. Codex
|
|
157
164
|
// spawns the process with the resolved cwd, so a relative command is
|
|
158
165
|
// resolved by the OS from there — pass "cwd" so e.g. cwd="server",
|
|
159
166
|
// command="./bin/mcp" resolves to <configDir>/server/bin/mcp.
|
|
160
167
|
const rooted = resolvePluginStdioPaths({ command: config.command, cwd: config.cwd }, configDir, "cwd");
|
|
161
168
|
const server: Partial<MCPServer> = {
|
|
169
|
+
// Carry `enabled: false` through rather than dropping the entry: the
|
|
170
|
+
// central MCP loader (`loadAllMCPConfigs`) suppresses disabled servers
|
|
171
|
+
// so they still claim their dedupe key (keeping a same-named,
|
|
172
|
+
// lower-priority source disabled) and remain overridable via the user
|
|
173
|
+
// force-enable allowlist. Dropping here would defeat both.
|
|
174
|
+
...(config.enabled === false && { enabled: false }),
|
|
162
175
|
...(rooted.command !== undefined && { command: rooted.command }),
|
|
163
176
|
args: config.args,
|
|
164
177
|
url: config.url,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* deliberately permissive (allows extra keys) so providers can attach extra
|
|
4
4
|
* keys without rejection; only `input` is required.
|
|
5
5
|
*/
|
|
6
|
-
import { type } from "
|
|
6
|
+
import { type } from "@oh-my-pi/omptype";
|
|
7
7
|
|
|
8
8
|
export const hashlineEditParamsSchema = type({
|
|
9
9
|
input: "string",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the `patch` mode.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { type } from "
|
|
11
|
+
import { type } from "@oh-my-pi/omptype";
|
|
12
12
|
import { parseApplyPatch, parseApplyPatchStreaming } from "../apply-patch/parser";
|
|
13
13
|
import { ApplyPatchError } from "../diff";
|
|
14
14
|
import type { PatchEditEntry } from "./patch";
|
package/src/edit/modes/patch.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as path from "node:path";
|
|
10
|
+
import { type } from "@oh-my-pi/omptype";
|
|
10
11
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
11
12
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
12
|
-
import { type } from "arktype";
|
|
13
13
|
import {
|
|
14
14
|
type FileDiagnosticsResult,
|
|
15
15
|
flushLspWritethroughBatch,
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Provides both character-level and line-level fuzzy matching with progressive
|
|
5
5
|
* fallback strategies for finding text in files.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import { type } from "@oh-my-pi/omptype";
|
|
7
9
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
8
|
-
import { type } from "arktype";
|
|
9
10
|
import type { FileDiagnosticsResult, WritethroughCallback, WritethroughDeferredHandle } from "../../lsp";
|
|
10
11
|
import type { ToolSession } from "../../tools";
|
|
11
12
|
import { routeWriteThroughBridge } from "../../tools/acp-bridge";
|
package/src/eval/agent-bridge.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host-side handler for the eval `agent()` helper.
|
|
3
3
|
*/
|
|
4
|
-
import { type } from "
|
|
4
|
+
import { type } from "@oh-my-pi/omptype";
|
|
5
5
|
import {
|
|
6
6
|
buildStructuredSubagentRecoveryHint,
|
|
7
7
|
runStructuredSubagent,
|
|
@@ -155,7 +155,9 @@ export async function runEvalAgent(args: unknown, options: EvalAgentBridgeOption
|
|
|
155
155
|
...(isolation ? { isolation } : {}),
|
|
156
156
|
...(parsed.handle ? { retainArtifacts: true } : {}),
|
|
157
157
|
keepAlive: false,
|
|
158
|
-
maxRuntimeMs:
|
|
158
|
+
// `maxRuntimeMs` is intentionally omitted: the executor then inherits
|
|
159
|
+
// `task.maxRuntimeMs`, matching the task tool. Pinning it to 0 here
|
|
160
|
+
// silently overrode the user's wall-clock cap for eval fan-outs.
|
|
159
161
|
shareEvalSession: false,
|
|
160
162
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
161
163
|
...(options.emitStatus
|
|
@@ -29,8 +29,11 @@ export function isEvalTimeoutControlEvent(event: JsStatusEvent): boolean {
|
|
|
29
29
|
/** Optional behavior for a timeout pause around a host bridge call. */
|
|
30
30
|
export interface BridgeTimeoutPauseOptions {
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Holds an external eval abort back from the *kernel* until this bridge call
|
|
33
|
+
* settles, so the runtime is never torn down mid-phase (`agent()` isolation
|
|
34
|
+
* worktree setup and merge/cherry-pick). It does not shield the delegated
|
|
35
|
+
* work itself: the bridge hands subagents the caller's real signal, so a
|
|
36
|
+
* turn cancel still stops them immediately.
|
|
34
37
|
*/
|
|
35
38
|
deferExternalAbort?: boolean;
|
|
36
39
|
}
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* The call is oneshot and toolless from the model's perspective — pure text
|
|
12
12
|
* in, text (or, with `schema`, a structured object) out.
|
|
13
13
|
*/
|
|
14
|
+
|
|
15
|
+
import { type } from "@oh-my-pi/omptype";
|
|
14
16
|
import { instrumentedCompleteSimple, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
15
17
|
import { type Api, Effort, type Model, type Tool } from "@oh-my-pi/pi-ai";
|
|
16
18
|
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
17
|
-
import { type } from "arktype";
|
|
18
19
|
import { extractTextContent, extractToolCall, parseJsonPayload } from "../commit/utils";
|
|
19
20
|
|
|
20
21
|
import {
|
|
@@ -46,7 +47,7 @@ const completionArgsSchema = type({
|
|
|
46
47
|
prompt: "string>0",
|
|
47
48
|
"model?": "'smol'|'default'|'slow'",
|
|
48
49
|
"system?": "string",
|
|
49
|
-
"schema?": "
|
|
50
|
+
"schema?": { "[string]": "unknown" },
|
|
50
51
|
});
|
|
51
52
|
|
|
52
53
|
export interface EvalCompletionBridgeOptions {
|
|
@@ -158,6 +158,15 @@ export async function waitForPromiseWithCancellation<T>(
|
|
|
158
158
|
return await resultPromise;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Derived abort signal for the kernel, holding back an external abort while a
|
|
163
|
+
* bridge call marked `deferExternalAbort` is in flight.
|
|
164
|
+
*
|
|
165
|
+
* Scope is deliberately narrow: only `kernel.execute` consumes
|
|
166
|
+
* {@link BridgeAbortShield.signal}. Work dispatched through the tool bridge
|
|
167
|
+
* keeps the caller's real signal so a turn cancel reaches spawned subagents
|
|
168
|
+
* immediately — deferral protects the runtime, never the delegated work.
|
|
169
|
+
*/
|
|
161
170
|
interface BridgeAbortShield {
|
|
162
171
|
signal: AbortSignal | undefined;
|
|
163
172
|
abortRequested: boolean;
|
|
@@ -428,11 +437,21 @@ export async function executeWithKernelBase<
|
|
|
428
437
|
const emitStatus: (event: JsStatusEvent) => void =
|
|
429
438
|
options?.emitStatus ?? (event => collectDisplay({ type: "status", event }));
|
|
430
439
|
const runId = `${runIdPrefix}-${crypto.randomUUID()}`;
|
|
440
|
+
// Two aborts cross the bridge, and conflating them is what let a cancelled
|
|
441
|
+
// turn keep working. Delegated work (above all the subagents `agent()`
|
|
442
|
+
// spawns) gets the caller's real signal so it dies with the turn — shielding
|
|
443
|
+
// it here made Python/Ruby/Julia fan-outs outlive a cancel indefinitely,
|
|
444
|
+
// while JS cells, which never route through this shield, stopped fine. The
|
|
445
|
+
// shielded signal only governs how long the host waits on a call, holding
|
|
446
|
+
// the cell open across a critical phase (isolation worktree setup,
|
|
447
|
+
// merge/cherry-pick) so a cancel can't settle it on top of a half-applied
|
|
448
|
+
// git operation.
|
|
431
449
|
const unregisterBridge =
|
|
432
450
|
options?.toolSession && options?.bridgeSessionId
|
|
433
451
|
? registerPyToolBridge(options.bridgeSessionId, runId, {
|
|
434
452
|
toolSession: options.toolSession,
|
|
435
|
-
signal:
|
|
453
|
+
signal: options.signal,
|
|
454
|
+
shieldedSignal: abortShield.signal,
|
|
436
455
|
emitStatus,
|
|
437
456
|
abortRequested: () => {
|
|
438
457
|
return abortShield.abortRequested;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import type { ToolSession } from "../../tools";
|
|
9
9
|
import { ToolAbortError, ToolError } from "../../tools/tool-errors";
|
|
10
10
|
import { safeSend as safeSendIpc } from "../../utils/ipc";
|
|
11
|
+
import { EVAL_TIMEOUT_PAUSE_OP, EVAL_TIMEOUT_RESUME_OP } from "../bridge-timeout";
|
|
11
12
|
import { attachSessionOwner, resolveOwnerScopedSessionKey, type SessionOwners } from "../executor-base";
|
|
12
13
|
import { shouldDetachKernel } from "../py/spawn-options";
|
|
13
14
|
import { callSessionTool, type JsStatusEvent } from "./tool-bridge";
|
|
@@ -48,6 +49,26 @@ interface PendingRun {
|
|
|
48
49
|
resolve(value: { value: unknown }): void;
|
|
49
50
|
reject(error: Error): void;
|
|
50
51
|
toolCalls: Map<string, AbortController>;
|
|
52
|
+
/**
|
|
53
|
+
* Host calls currently inside a `deferExternalAbort` phase — `agent()`
|
|
54
|
+
* isolation worktree setup and merge/cherry-pick, which ignore their abort
|
|
55
|
+
* once started. Settling the run while one is live would return the cell on
|
|
56
|
+
* top of a git operation still rewriting the repo, so the abort path drains
|
|
57
|
+
* this first. Mirrors the Python bridge's shielded-signal contract.
|
|
58
|
+
*/
|
|
59
|
+
deferDepth: number;
|
|
60
|
+
/** Resolves once {@link deferDepth} falls back to zero. */
|
|
61
|
+
deferDrained?: PromiseWithResolvers<void>;
|
|
62
|
+
/** Set once the turn was cancelled; blocks new bridge calls during the drain. */
|
|
63
|
+
aborted: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* A worker `result` withheld because the cell still has bridge calls in
|
|
66
|
+
* flight. `#runOne` reports a finished run without awaiting its pending
|
|
67
|
+
* tools, so a floated or caught `agent()` would otherwise settle the run —
|
|
68
|
+
* tearing down the abort listener — while the subagent kept going with
|
|
69
|
+
* nothing left able to cancel it. Delivered once the last call drains.
|
|
70
|
+
*/
|
|
71
|
+
heldResult?: Extract<WorkerOutbound, { type: "result" }>;
|
|
51
72
|
settled: boolean;
|
|
52
73
|
}
|
|
53
74
|
|
|
@@ -267,6 +288,8 @@ async function runOnce(
|
|
|
267
288
|
resolve,
|
|
268
289
|
reject,
|
|
269
290
|
toolCalls: new Map(),
|
|
291
|
+
deferDepth: 0,
|
|
292
|
+
aborted: false,
|
|
270
293
|
settled: false,
|
|
271
294
|
};
|
|
272
295
|
session.pending.set(runId, pending);
|
|
@@ -274,9 +297,21 @@ async function runOnce(
|
|
|
274
297
|
const onAbort = (): void => {
|
|
275
298
|
const reason = options.runState.signal?.reason;
|
|
276
299
|
const abortError = reasonToError(reason, "Execution aborted");
|
|
277
|
-
//
|
|
300
|
+
// Stop delegated work at once — this is what kills spawned subagents —
|
|
301
|
+
// and refuse further bridge calls so the drain below stays bounded to
|
|
302
|
+
// phases that had already started.
|
|
303
|
+
pending.aborted = true;
|
|
278
304
|
for (const ctrl of pending.toolCalls.values()) ctrl.abort(abortError);
|
|
279
|
-
//
|
|
305
|
+
// A critical host phase ignores its abort once started (isolation
|
|
306
|
+
// worktree setup, merge/cherry-pick). Killing the worker now would
|
|
307
|
+
// settle the cell on top of a git operation still in progress, so wait
|
|
308
|
+
// for it. Hard-kill is still the only way to interrupt synchronous user
|
|
309
|
+
// code, hence it stays the terminal step either way.
|
|
310
|
+
const drained = pending.deferDepth > 0 ? pending.deferDrained?.promise : undefined;
|
|
311
|
+
if (drained) {
|
|
312
|
+
void drained.then(() => killSessionFor(session, abortError, { force: true }));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
280
315
|
void killSessionFor(session, abortError, { force: true });
|
|
281
316
|
};
|
|
282
317
|
|
|
@@ -458,6 +493,25 @@ function handleSessionMessage(session: JsSession, msg: WorkerOutbound): void {
|
|
|
458
493
|
}
|
|
459
494
|
}
|
|
460
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Maintain {@link PendingRun.deferDepth} from the bridge's pause/resume status
|
|
498
|
+
* events so an abort can wait out a critical `agent()` phase instead of
|
|
499
|
+
* settling the cell over a half-applied merge.
|
|
500
|
+
*/
|
|
501
|
+
function trackDeferPhase(pending: PendingRun, event: JsStatusEvent): void {
|
|
502
|
+
if (event.deferExternalAbort !== true) return;
|
|
503
|
+
if (event.op === EVAL_TIMEOUT_PAUSE_OP) {
|
|
504
|
+
pending.deferDepth++;
|
|
505
|
+
pending.deferDrained ??= Promise.withResolvers<void>();
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (event.op !== EVAL_TIMEOUT_RESUME_OP || pending.deferDepth === 0) return;
|
|
509
|
+
pending.deferDepth--;
|
|
510
|
+
if (pending.deferDepth > 0) return;
|
|
511
|
+
pending.deferDrained?.resolve();
|
|
512
|
+
pending.deferDrained = undefined;
|
|
513
|
+
}
|
|
514
|
+
|
|
461
515
|
async function handleToolCall(session: JsSession, msg: Extract<WorkerOutbound, { type: "tool-call" }>): Promise<void> {
|
|
462
516
|
const pending = session.pending.get(msg.runId);
|
|
463
517
|
if (!pending) {
|
|
@@ -468,26 +522,42 @@ async function handleToolCall(session: JsSession, msg: Extract<WorkerOutbound, {
|
|
|
468
522
|
});
|
|
469
523
|
return;
|
|
470
524
|
}
|
|
525
|
+
if (pending.aborted) {
|
|
526
|
+
safeSend(session, {
|
|
527
|
+
type: "tool-reply",
|
|
528
|
+
id: msg.id,
|
|
529
|
+
reply: { ok: false, error: { message: "Run was interrupted" } },
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
471
533
|
const ctrl = new AbortController();
|
|
472
534
|
pending.toolCalls.set(msg.id, ctrl);
|
|
473
535
|
try {
|
|
474
536
|
const value = await callSessionTool(msg.name, msg.args, {
|
|
475
537
|
session: pending.toolSession,
|
|
476
538
|
signal: ctrl.signal,
|
|
477
|
-
emitStatus: (event: JsStatusEvent) =>
|
|
539
|
+
emitStatus: (event: JsStatusEvent) => {
|
|
540
|
+
trackDeferPhase(pending, event);
|
|
541
|
+
pending.runState.onDisplay?.({ type: "status", event });
|
|
542
|
+
},
|
|
478
543
|
});
|
|
479
544
|
safeSend(session, { type: "tool-reply", id: msg.id, reply: { ok: true, value } });
|
|
480
545
|
} catch (error) {
|
|
481
546
|
safeSend(session, { type: "tool-reply", id: msg.id, reply: { ok: false, error: toErrorPayload(error) } });
|
|
482
547
|
} finally {
|
|
483
548
|
pending.toolCalls.delete(msg.id);
|
|
549
|
+
// Last call of a run whose worker result was withheld: settle it now.
|
|
550
|
+
const held = pending.heldResult;
|
|
551
|
+
if (held && !pending.settled && !pending.aborted && pending.toolCalls.size === 0) {
|
|
552
|
+
finishPending(pending, held);
|
|
553
|
+
}
|
|
484
554
|
}
|
|
485
555
|
}
|
|
486
556
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
if (!pending || pending.settled) return;
|
|
557
|
+
/** Deliver a worker `result` to the waiting {@link runOnce}. */
|
|
558
|
+
function finishPending(pending: PendingRun, msg: Extract<WorkerOutbound, { type: "result" }>): void {
|
|
490
559
|
pending.settled = true;
|
|
560
|
+
pending.heldResult = undefined;
|
|
491
561
|
if (msg.ok) {
|
|
492
562
|
pending.resolve({ value: undefined });
|
|
493
563
|
return;
|
|
@@ -495,6 +565,24 @@ function settlePending(session: JsSession, msg: Extract<WorkerOutbound, { type:
|
|
|
495
565
|
pending.reject(errorFromPayload(msg.error));
|
|
496
566
|
}
|
|
497
567
|
|
|
568
|
+
function settlePending(session: JsSession, msg: Extract<WorkerOutbound, { type: "result" }>): void {
|
|
569
|
+
const pending = session.pending.get(msg.runId);
|
|
570
|
+
if (!pending || pending.settled) return;
|
|
571
|
+
// Once the turn is cancelled the scheduled kill is the sole settler, so a
|
|
572
|
+
// late worker result can't cut the abort drain short.
|
|
573
|
+
if (pending.aborted) return;
|
|
574
|
+
// A cell owns every bridge call it starts. The worker finishes a run without
|
|
575
|
+
// awaiting its outstanding tool calls, so `agent(...)` that is floated or
|
|
576
|
+
// caught would settle the run here — `runOnce` then drops the abort listener
|
|
577
|
+
// and the pending entry, leaving the subagent running with nothing able to
|
|
578
|
+
// cancel it. Hold the result until the last call drains.
|
|
579
|
+
if (pending.toolCalls.size > 0) {
|
|
580
|
+
pending.heldResult = msg;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
finishPending(pending, msg);
|
|
584
|
+
}
|
|
585
|
+
|
|
498
586
|
async function killSessionFor(session: JsSession, error: Error, options: { force: boolean }): Promise<void> {
|
|
499
587
|
if (sessions.get(session.sessionKey) === session) {
|
|
500
588
|
sessions.delete(session.sessionKey);
|