@oh-my-pi/pi-coding-agent 17.3.4 → 17.3.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 +88 -0
- package/dist/{CHANGELOG-trcc215s.md → CHANGELOG-1hmwwt45.md} +88 -0
- package/dist/cli.js +3156 -3163
- package/dist/docs-index.generated.txt +1 -1
- package/dist/types/cli/stats-cli.d.ts +1 -6
- package/dist/types/commands/stats.d.ts +4 -0
- package/dist/types/config/model-discovery.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +7 -2
- package/dist/types/extensibility/extensions/loader.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +24 -6
- package/dist/types/extensibility/extensions/types.d.ts +60 -0
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +4 -0
- package/dist/types/launch/presence.d.ts +11 -0
- package/dist/types/lsp/client.d.ts +22 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +2 -0
- package/dist/types/mcp/config.d.ts +3 -1
- package/dist/types/modes/components/tool-execution.d.ts +3 -1
- package/dist/types/registry/agent-lifecycle.d.ts +11 -0
- package/dist/types/registry/agent-registry.d.ts +4 -0
- package/dist/types/session/agent-session-types.d.ts +0 -2
- package/dist/types/session/agent-session.d.ts +20 -0
- package/dist/types/session/date-cwd-reminder.d.ts +22 -0
- package/dist/types/session/irc-bridge.d.ts +2 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-loader.d.ts +18 -11
- package/dist/types/session/session-maintenance.d.ts +6 -2
- package/dist/types/session/session-manager.d.ts +19 -3
- package/dist/types/session/session-tools.d.ts +0 -1
- package/dist/types/session/turn-recovery.d.ts +7 -7
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +1 -0
- package/dist/types/tools/browser/attach.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +2 -2
- package/dist/types/tools/browser.d.ts +1 -1
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/file-write-fallback.d.ts +124 -0
- package/dist/types/tools/hub/jobs.d.ts +6 -0
- package/dist/types/tools/hub/types.d.ts +6 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/path-utils.d.ts +23 -0
- package/dist/types/vibe/runtime.d.ts +3 -3
- package/package.json +13 -13
- package/src/auto-thinking/classifier.ts +37 -23
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/stats-cli.ts +6 -72
- package/src/commands/stats.ts +7 -4
- package/src/commit/analysis/conventional.ts +15 -9
- package/src/commit/analysis/summary.ts +15 -9
- package/src/commit/changelog/generate.ts +15 -9
- package/src/commit/map-reduce/map-phase.ts +3 -19
- package/src/commit/map-reduce/reduce-phase.ts +15 -9
- package/src/config/model-discovery.ts +3 -3
- package/src/config/model-registry.ts +29 -16
- package/src/config/settings-schema.ts +13 -0
- package/src/debug/report-bundle.ts +25 -59
- package/src/discovery/claude-plugins.ts +2 -1
- package/src/discovery/claude.ts +4 -2
- package/src/discovery/helpers.ts +5 -0
- package/src/edit/hashline/filesystem.ts +9 -3
- package/src/edit/modes/patch.ts +31 -5
- package/src/edit/renderer.ts +48 -19
- package/src/extensibility/extensions/loader.ts +20 -5
- package/src/extensibility/extensions/runner.ts +239 -24
- package/src/extensibility/extensions/types.ts +62 -0
- package/src/extensibility/extensions/wrapper.ts +26 -11
- package/src/extensibility/legacy-pi-ai-shim.ts +4 -0
- package/src/extensibility/plugins/marketplace/manager.ts +15 -18
- package/src/irc/bus.ts +1 -1
- package/src/launch/broker.ts +8 -1
- package/src/launch/presence.ts +77 -1
- package/src/lsp/client.ts +35 -3
- package/src/lsp/clients/biome-client.ts +47 -93
- package/src/lsp/servers.ts +17 -11
- package/src/lsp/tool.ts +2 -4
- package/src/lsp/workspace-diagnostics.ts +24 -2
- package/src/lsp/writethrough.ts +20 -10
- package/src/mcp/config.ts +50 -6
- package/src/memories/index.ts +29 -25
- package/src/mnemopi/backend.ts +13 -11
- package/src/modes/components/ask-dialog.ts +25 -5
- package/src/modes/components/tool-execution.ts +10 -6
- package/src/modes/components/welcome.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +52 -32
- package/src/modes/controllers/selector-controller.ts +6 -0
- package/src/modes/controllers/tan-command-controller.ts +1 -0
- package/src/modes/interactive-mode.ts +49 -10
- package/src/modes/theme/defaults/birch.json +2 -2
- package/src/prompts/system/checkpoint-active-notice.md +5 -0
- package/src/prompts/system/date-cwd-reminder.md +3 -0
- package/src/prompts/system/project-prompt.md +0 -1
- package/src/prompts/system/rewind-report.md +1 -3
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/rewind.md +1 -13
- package/src/registry/agent-lifecycle.ts +34 -0
- package/src/registry/agent-registry.ts +27 -2
- package/src/registry/persisted-agents.ts +40 -20
- package/src/sdk.ts +32 -2
- package/src/session/agent-session-types.ts +0 -2
- package/src/session/agent-session.ts +178 -46
- package/src/session/date-cwd-reminder.ts +77 -0
- package/src/session/irc-bridge.ts +5 -0
- package/src/session/messages.ts +5 -1
- package/src/session/session-loader.ts +106 -64
- package/src/session/session-maintenance.ts +189 -115
- package/src/session/session-manager.ts +142 -35
- package/src/session/session-persistence.ts +4 -4
- package/src/session/session-storage.ts +18 -5
- package/src/session/session-tools.ts +5 -10
- package/src/session/turn-recovery.ts +102 -20
- package/src/session/unexpected-stop-classifier.ts +33 -21
- package/src/slash-commands/builtin-session.ts +1 -1
- package/src/slash-commands/helpers/stats-dashboard.ts +23 -9
- package/src/system-prompt.ts +0 -5
- package/src/task/executor.ts +4 -15
- package/src/task/persisted-revive.ts +3 -6
- package/src/tools/ask.ts +10 -3
- package/src/tools/browser/attach.ts +80 -25
- package/src/tools/browser/launch.ts +44 -15
- package/src/tools/browser/relay/daemon.ts +3 -7
- package/src/tools/browser/render.ts +1 -1
- package/src/tools/browser/shared-daemon.ts +3 -7
- package/src/tools/browser.ts +5 -5
- package/src/tools/builtin-names.ts +7 -3
- package/src/tools/checkpoint.ts +1 -7
- package/src/tools/file-write-fallback.ts +467 -0
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +20 -3
- package/src/tools/hub/types.ts +6 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/path-utils.ts +79 -0
- package/src/tools/render-utils.ts +53 -21
- package/src/tools/think.ts +15 -3
- package/src/tts/speech-enhancer.ts +19 -14
- package/src/utils/commit-message-generator.ts +14 -12
- package/src/utils/title-generator.ts +23 -19
- package/src/vibe/runtime.ts +32 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type } from "@oh-my-pi/omptype";
|
|
2
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
4
|
-
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { completeSimple, retryTransientCompletion, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
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" };
|
|
@@ -52,16 +52,22 @@ export async function generateSummary({
|
|
|
52
52
|
stat,
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
const response = await
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
const response = await retryTransientCompletion(() =>
|
|
56
|
+
completeSimple(
|
|
57
|
+
model,
|
|
58
|
+
{
|
|
59
|
+
systemPrompt: [systemPrompt],
|
|
60
|
+
messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }],
|
|
61
|
+
tools: [SummaryTool],
|
|
62
|
+
},
|
|
63
|
+
{ apiKey, maxTokens: 200, reasoning: toReasoningEffort(thinkingLevel) },
|
|
64
|
+
),
|
|
63
65
|
);
|
|
64
66
|
|
|
67
|
+
if (response.stopReason === "error") {
|
|
68
|
+
throw new Error(response.errorMessage ?? "provider error");
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
return parseSummaryFromResponse(response, commitType, scope);
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type } from "@oh-my-pi/omptype";
|
|
2
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
4
|
-
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { completeSimple, retryTransientCompletion, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
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" };
|
|
@@ -55,16 +55,22 @@ export async function generateChangelogEntries({
|
|
|
55
55
|
stat,
|
|
56
56
|
diff,
|
|
57
57
|
});
|
|
58
|
-
const response = await
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
const response = await retryTransientCompletion(() =>
|
|
59
|
+
completeSimple(
|
|
60
|
+
model,
|
|
61
|
+
{
|
|
62
|
+
systemPrompt: [prompt.render(changelogSystemPrompt)],
|
|
63
|
+
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
|
64
|
+
tools: [changelogTool],
|
|
65
|
+
},
|
|
66
|
+
{ apiKey, maxTokens: 1200, reasoning: toReasoningEffort(thinkingLevel) },
|
|
67
|
+
),
|
|
66
68
|
);
|
|
67
69
|
|
|
70
|
+
if (response.stopReason === "error") {
|
|
71
|
+
throw new Error(response.errorMessage ?? "provider error");
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
const parsed = parseChangelogResponse(response);
|
|
69
75
|
return { entries: dedupeEntries(parsed.entries) };
|
|
70
76
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Api, ApiKey, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { completeSimple, retryTransientCompletion } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import fileObserverSystemPrompt from "../../commit/prompts/file-observer-system.md" with { type: "text" };
|
|
6
6
|
import fileObserverUserPrompt from "../../commit/prompts/file-observer-user.md" with { type: "text" };
|
|
@@ -66,7 +66,7 @@ export async function runMapPhase({
|
|
|
66
66
|
messages: [{ role: "user", content: userContent, timestamp: Date.now() }] as Message[],
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
const response = await
|
|
69
|
+
const response = await retryTransientCompletion(
|
|
70
70
|
() =>
|
|
71
71
|
completeSimple(model, request, {
|
|
72
72
|
apiKey,
|
|
@@ -74,8 +74,7 @@ export async function runMapPhase({
|
|
|
74
74
|
reasoning: toReasoningEffort(thinkingLevel),
|
|
75
75
|
signal: AbortSignal.timeout(timeoutMs),
|
|
76
76
|
}),
|
|
77
|
-
maxRetries,
|
|
78
|
-
retryBackoffMs,
|
|
77
|
+
{ maxAttempts: maxRetries, baseDelayMs: retryBackoffMs },
|
|
79
78
|
);
|
|
80
79
|
|
|
81
80
|
const observations = parseObservations(response);
|
|
@@ -176,18 +175,3 @@ async function runWithConcurrency<T, R>(
|
|
|
176
175
|
await Promise.all(runners);
|
|
177
176
|
return results;
|
|
178
177
|
}
|
|
179
|
-
|
|
180
|
-
async function withRetry<T>(fn: () => Promise<T>, attempts: number, backoffMs: number): Promise<T> {
|
|
181
|
-
let lastError: unknown;
|
|
182
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
183
|
-
try {
|
|
184
|
-
return await fn();
|
|
185
|
-
} catch (error) {
|
|
186
|
-
lastError = error;
|
|
187
|
-
if (attempt < attempts - 1) {
|
|
188
|
-
await Bun.sleep(backoffMs * (attempt + 1));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
throw lastError;
|
|
193
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { completeSimple, retryTransientCompletion } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { type: "text" };
|
|
6
6
|
import reduceUserPrompt from "../../commit/prompts/reduce-user.md" with { type: "text" };
|
|
@@ -35,15 +35,21 @@ export async function runReducePhase({
|
|
|
35
35
|
stat,
|
|
36
36
|
scope_candidates: scopeCandidates,
|
|
37
37
|
});
|
|
38
|
-
const response = await
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
const response = await retryTransientCompletion(() =>
|
|
39
|
+
completeSimple(
|
|
40
|
+
model,
|
|
41
|
+
{
|
|
42
|
+
systemPrompt: [prompt.render(reduceSystemPrompt)],
|
|
43
|
+
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
|
44
|
+
tools: [ReduceTool],
|
|
45
|
+
},
|
|
46
|
+
{ apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
|
|
47
|
+
),
|
|
46
48
|
);
|
|
47
49
|
|
|
50
|
+
if (response.stopReason === "error") {
|
|
51
|
+
throw new Error(response.errorMessage ?? "provider error");
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
return parseConventionalAnalysisResponse(response, ReduceTool);
|
|
49
55
|
}
|
|
@@ -645,7 +645,7 @@ export async function discoverLlamaCppModels(
|
|
|
645
645
|
name: id,
|
|
646
646
|
api: providerConfig.api,
|
|
647
647
|
provider: providerConfig.provider,
|
|
648
|
-
baseUrl,
|
|
648
|
+
baseUrl: ensureLlamaCppV1BaseUrl(baseUrl),
|
|
649
649
|
reasoning: false,
|
|
650
650
|
input: item.input ?? serverMetadata?.input ?? ["text"],
|
|
651
651
|
imageInputDecoder: "stb",
|
|
@@ -1018,7 +1018,7 @@ export async function discoverProxyModels(
|
|
|
1018
1018
|
return discovered;
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
|
-
function normalizeLlamaCppBaseUrl(baseUrl?: string): string {
|
|
1021
|
+
export function normalizeLlamaCppBaseUrl(baseUrl?: string): string {
|
|
1022
1022
|
const defaultBaseUrl = "http://127.0.0.1:8080";
|
|
1023
1023
|
const raw = baseUrl || defaultBaseUrl;
|
|
1024
1024
|
try {
|
|
@@ -1033,7 +1033,7 @@ function normalizeLlamaCppBaseUrl(baseUrl?: string): string {
|
|
|
1033
1033
|
// ensureLlamaCppV1BaseUrl appends the OpenAI-compatible `/v1` prefix a
|
|
1034
1034
|
// chat-completions request needs; native discovery keeps the bare root, which
|
|
1035
1035
|
// serves `/models` and `/props` but not `/chat/completions`.
|
|
1036
|
-
function ensureLlamaCppV1BaseUrl(baseUrl: string): string {
|
|
1036
|
+
export function ensureLlamaCppV1BaseUrl(baseUrl: string): string {
|
|
1037
1037
|
return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
|
1038
1038
|
}
|
|
1039
1039
|
|
|
@@ -61,9 +61,11 @@ import {
|
|
|
61
61
|
type DiscoveryProviderConfig,
|
|
62
62
|
discoverLlamaCppModelRuntimeMetadata,
|
|
63
63
|
discoverModelsByProviderType,
|
|
64
|
+
ensureLlamaCppV1BaseUrl,
|
|
64
65
|
getImplicitOllamaBaseUrl,
|
|
65
66
|
getOllamaContextLengthOverride,
|
|
66
67
|
normalizeLiteLLMDiscoveryBaseUrl,
|
|
68
|
+
normalizeLlamaCppBaseUrl,
|
|
67
69
|
} from "./model-discovery";
|
|
68
70
|
import {
|
|
69
71
|
AUTHORITATIVE_RUNTIME_CATALOG_PROVIDERS,
|
|
@@ -577,7 +579,7 @@ export class ModelRegistry {
|
|
|
577
579
|
const withConfigModels = this.#mergeCustomModels(resolvedDefaults, select(this.#customModelOverlays));
|
|
578
580
|
const combined = this.#mergeCustomModels(withConfigModels, select(this.#runtimeModelOverlays));
|
|
579
581
|
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
580
|
-
return this.#
|
|
582
|
+
return this.#applyLlamaCppModelFixups(this.#applyRuntimeProviderOverrides(withModelOverrides));
|
|
581
583
|
}
|
|
582
584
|
|
|
583
585
|
#composeStaticModels(providerFilter?: ReadonlySet<string>): Model<Api>[] {
|
|
@@ -1064,9 +1066,7 @@ export class ModelRegistry {
|
|
|
1064
1066
|
const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
|
|
1065
1067
|
const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
|
|
1066
1068
|
const withModelOverrides = this.#applyModelOverrides(collapseBuiltModelVariants(combined), this.#modelOverrides);
|
|
1067
|
-
this.#unprojectedModels = this.#
|
|
1068
|
-
this.#applyRuntimeProviderOverrides(withModelOverrides),
|
|
1069
|
-
);
|
|
1069
|
+
this.#unprojectedModels = this.#applyLlamaCppModelFixups(this.#applyRuntimeProviderOverrides(withModelOverrides));
|
|
1070
1070
|
this.#models = this.#applyRuntimeModelModifiers(this.#unprojectedModels);
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
@@ -1446,20 +1446,28 @@ export class ModelRegistry {
|
|
|
1446
1446
|
});
|
|
1447
1447
|
}
|
|
1448
1448
|
|
|
1449
|
-
// #
|
|
1450
|
-
//
|
|
1451
|
-
//
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
|
|
1455
|
-
// cached row would otherwise leave the old spec in place.
|
|
1456
|
-
#applyLlamaCppQwenThinkingToModels(models: Model<Api>[]): Model<Api>[] {
|
|
1449
|
+
// #applyLlamaCppModelFixups is the outermost transform for llama.cpp-provider
|
|
1450
|
+
// models, after discovery merges, cache fallbacks, and provider/transport
|
|
1451
|
+
// overrides have run. It applies Qwen-specific fixes (api, reasoning, compat)
|
|
1452
|
+
// and ensures all non-transport models have the `/v1` prefix in their baseUrl,
|
|
1453
|
+
// even when a configured override or stale cache row would strip it.
|
|
1454
|
+
#applyLlamaCppModelFixups(models: Model<Api>[]): Model<Api>[] {
|
|
1457
1455
|
const llamaCppProviders = new Set<string>();
|
|
1458
1456
|
for (const provider of this.#discoverableProviders) {
|
|
1459
1457
|
if (provider.discovery.type === "llama.cpp") llamaCppProviders.add(provider.provider);
|
|
1460
1458
|
}
|
|
1461
1459
|
if (llamaCppProviders.size === 0) return models;
|
|
1462
|
-
return models.map(model =>
|
|
1460
|
+
return models.map(model => {
|
|
1461
|
+
if (!llamaCppProviders.has(model.provider)) return model;
|
|
1462
|
+
const withFixups = applyLlamaCppQwenThinking(model);
|
|
1463
|
+
if (!withFixups.transport && !withFixups.baseUrl.endsWith("/v1")) {
|
|
1464
|
+
return buildModel({
|
|
1465
|
+
...withFixups,
|
|
1466
|
+
baseUrl: ensureLlamaCppV1BaseUrl(normalizeLlamaCppBaseUrl(withFixups.baseUrl)),
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
return withFixups;
|
|
1470
|
+
});
|
|
1463
1471
|
}
|
|
1464
1472
|
|
|
1465
1473
|
#mergeProviderOverride(baseOverride: ProviderOverride | undefined, override: ProviderOverride): ProviderOverride {
|
|
@@ -1667,17 +1675,22 @@ export class ModelRegistry {
|
|
|
1667
1675
|
*
|
|
1668
1676
|
* Side-effect-free and synchronous: a command-backed key (`!cmd`) counts as
|
|
1669
1677
|
* configured by its presence alone — the program is NOT executed — and OAuth
|
|
1670
|
-
* tokens are NOT refreshed (`authStorage.
|
|
1678
|
+
* tokens are NOT refreshed (`authStorage.hasResolvableAuth`). This is what keeps the
|
|
1671
1679
|
* model-switch pre-flight off the event loop's hot path; the real key
|
|
1672
1680
|
* (command execution + OAuth refresh) is resolved lazily per request via
|
|
1673
1681
|
* {@link ModelRegistry.resolver}.
|
|
1682
|
+
*
|
|
1683
|
+
* Cross-provider env aliases count here (`xai-oauth` can borrow `XAI_API_KEY`)
|
|
1684
|
+
* so an explicit `xai-oauth/…` selector does not fail with "No API key".
|
|
1685
|
+
* Default-model availability still uses {@link AuthStorage.hasAuth}, which
|
|
1686
|
+
* ignores that alias so SuperGrok is not auto-selected from a paid key.
|
|
1674
1687
|
*/
|
|
1675
1688
|
hasConfiguredAuth(model: Model<Api>): boolean {
|
|
1676
1689
|
const keyConfig = this.#customProviderApiKeys.get(model.provider);
|
|
1677
1690
|
return (
|
|
1678
1691
|
isCommandConfigValue(keyConfig) ||
|
|
1679
1692
|
this.#keylessProviders.has(model.provider) ||
|
|
1680
|
-
this.authStorage.
|
|
1693
|
+
this.authStorage.hasResolvableAuth(model.provider)
|
|
1681
1694
|
);
|
|
1682
1695
|
}
|
|
1683
1696
|
|
|
@@ -2097,7 +2110,7 @@ export class ModelRegistry {
|
|
|
2097
2110
|
transportOverride,
|
|
2098
2111
|
);
|
|
2099
2112
|
this.#runtimeProviderOverrides.set(providerName, nextRuntimeOverride);
|
|
2100
|
-
this.#unprojectedModels = this.#
|
|
2113
|
+
this.#unprojectedModels = this.#applyLlamaCppModelFixups(
|
|
2101
2114
|
this.#unprojectedModels.map(model => {
|
|
2102
2115
|
if (model.provider !== providerName) return model;
|
|
2103
2116
|
return this.#applyProviderTransportOverrideToModel(model, transportOverride);
|
|
@@ -152,6 +152,7 @@ export const TAB_GROUPS: Record<SettingTab, readonly string[]> = {
|
|
|
152
152
|
"Output Limits",
|
|
153
153
|
"Execution",
|
|
154
154
|
"Discovery & MCP",
|
|
155
|
+
"Extensions",
|
|
155
156
|
"Developer",
|
|
156
157
|
],
|
|
157
158
|
tasks: ["Modes", "Subagents", "Isolation", "Commands & Skills"],
|
|
@@ -5494,6 +5495,18 @@ export const SETTINGS_SCHEMA = {
|
|
|
5494
5495
|
|
|
5495
5496
|
"commit.changelogMaxDiffChars": { type: "number", default: 120000 },
|
|
5496
5497
|
|
|
5498
|
+
"extensionHandlers.toolCallTimeoutMs": {
|
|
5499
|
+
type: "number",
|
|
5500
|
+
default: 30_000,
|
|
5501
|
+
ui: {
|
|
5502
|
+
tab: "tools",
|
|
5503
|
+
group: "Extensions",
|
|
5504
|
+
label: "Tool Call Handler Timeout (ms)",
|
|
5505
|
+
description:
|
|
5506
|
+
"Positive finite active-work timeout for extension tool_call handlers; invalid values use 30000ms, and time awaiting OMP-owned dialogs does not count",
|
|
5507
|
+
},
|
|
5508
|
+
},
|
|
5509
|
+
|
|
5497
5510
|
"dev.autoqa": {
|
|
5498
5511
|
type: "boolean",
|
|
5499
5512
|
default: true,
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Creates a .tar.gz archive with session data, logs, system info, and optional profiling data.
|
|
5
5
|
*/
|
|
6
|
+
|
|
7
|
+
import type { Dirent } from "node:fs";
|
|
6
8
|
import * as fs from "node:fs/promises";
|
|
7
9
|
import * as path from "node:path";
|
|
8
10
|
import type { WorkProfile } from "@oh-my-pi/pi-natives";
|
|
@@ -66,8 +68,8 @@ export interface DebugLogSource {
|
|
|
66
68
|
*
|
|
67
69
|
* Bundle contents:
|
|
68
70
|
* - session.jsonl: Current session transcript
|
|
69
|
-
* - artifacts/:
|
|
70
|
-
*
|
|
71
|
+
* - artifacts/: Current session's artifacts subtree (recursive), including any
|
|
72
|
+
* subagent session transcripts nested under it
|
|
71
73
|
* - logs.txt: Recent log entries
|
|
72
74
|
* - system.json: OS, arch, CPU, memory, versions
|
|
73
75
|
* - env.json: Sanitized environment variables
|
|
@@ -130,14 +132,12 @@ export async function createReportBundle(options: ReportBundleOptions): Promise<
|
|
|
130
132
|
// Session file might not exist yet
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
// Artifacts
|
|
135
|
+
// Artifacts subtree (same path without .jsonl). Recursing captures the
|
|
136
|
+
// current session's nested subagent transcripts and their artifacts while
|
|
137
|
+
// staying inside this session's own directory — unrelated co-located
|
|
138
|
+
// sessions in the sessions root are never touched (#8648).
|
|
134
139
|
const artifactsDir = options.sessionFile.slice(0, -6);
|
|
135
140
|
await addDirectoryToArchive(data, files, artifactsDir, "artifacts");
|
|
136
|
-
|
|
137
|
-
// Look for subagent sessions in the same directory
|
|
138
|
-
const sessionDir = path.dirname(options.sessionFile);
|
|
139
|
-
const sessionBasename = path.basename(options.sessionFile, ".jsonl");
|
|
140
|
-
await addSubagentSessions(data, files, sessionDir, sessionBasename);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// CPU profile
|
|
@@ -172,68 +172,34 @@ export async function createReportBundle(options: ReportBundleOptions): Promise<
|
|
|
172
172
|
return { path: outputPath, files };
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
/**
|
|
175
|
+
/** Recursively add every file under a directory to the archive. */
|
|
176
176
|
async function addDirectoryToArchive(
|
|
177
177
|
data: Record<string, string>,
|
|
178
178
|
files: string[],
|
|
179
179
|
dirPath: string,
|
|
180
180
|
archivePrefix: string,
|
|
181
181
|
): Promise<void> {
|
|
182
|
+
let entries: Dirent[];
|
|
182
183
|
try {
|
|
183
|
-
|
|
184
|
-
for (const entry of entries) {
|
|
185
|
-
if (!entry.isFile()) continue;
|
|
186
|
-
const filePath = path.join(dirPath, entry.name);
|
|
187
|
-
const archivePath = `${archivePrefix}/${entry.name}`;
|
|
188
|
-
try {
|
|
189
|
-
const content = await Bun.file(filePath).text();
|
|
190
|
-
data[archivePath] = content;
|
|
191
|
-
files.push(archivePath);
|
|
192
|
-
} catch {
|
|
193
|
-
// Skip files we can't read
|
|
194
|
-
}
|
|
195
|
-
}
|
|
184
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
196
185
|
} catch {
|
|
197
186
|
// Directory doesn't exist
|
|
187
|
+
return;
|
|
198
188
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const entries = await fs.readdir(sessionDir, { withFileTypes: true });
|
|
213
|
-
const sessionFiles = entries
|
|
214
|
-
.filter(e => e.isFile() && e.name.endsWith(".jsonl") && e.name !== `${parentBasename}.jsonl`)
|
|
215
|
-
.map(e => e.name);
|
|
216
|
-
|
|
217
|
-
// Limit to most recent 10 subagent sessions
|
|
218
|
-
const sortedFiles = sessionFiles.sort().slice(-10);
|
|
219
|
-
|
|
220
|
-
for (const filename of sortedFiles) {
|
|
221
|
-
const filePath = path.join(sessionDir, filename);
|
|
222
|
-
const archivePath = `subagents/${filename}`;
|
|
223
|
-
try {
|
|
224
|
-
const content = await Bun.file(filePath).text();
|
|
225
|
-
data[archivePath] = content;
|
|
226
|
-
files.push(archivePath);
|
|
227
|
-
|
|
228
|
-
// Also add artifacts for this subagent session
|
|
229
|
-
const artifactsDir = filePath.slice(0, -6);
|
|
230
|
-
await addDirectoryToArchive(data, files, artifactsDir, `subagents/${filename.slice(0, -6)}`);
|
|
231
|
-
} catch {
|
|
232
|
-
// Skip files we can't read
|
|
233
|
-
}
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
191
|
+
const archivePath = `${archivePrefix}/${entry.name}`;
|
|
192
|
+
if (entry.isDirectory()) {
|
|
193
|
+
await addDirectoryToArchive(data, files, entryPath, archivePath);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (!entry.isFile()) continue;
|
|
197
|
+
try {
|
|
198
|
+
data[archivePath] = await Bun.file(entryPath).text();
|
|
199
|
+
files.push(archivePath);
|
|
200
|
+
} catch {
|
|
201
|
+
// Skip files we can't read
|
|
234
202
|
}
|
|
235
|
-
} catch {
|
|
236
|
-
// Directory doesn't exist
|
|
237
203
|
}
|
|
238
204
|
}
|
|
239
205
|
|
|
@@ -379,8 +379,9 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
379
379
|
roots.map(async root => {
|
|
380
380
|
const toolsDir = path.join(root.path, "tools");
|
|
381
381
|
return loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, root.scope, {
|
|
382
|
+
extensions: ["ts", "js"],
|
|
382
383
|
transform: (name, _content, filePath, source) => {
|
|
383
|
-
const toolName = name.replace(/\.(ts|js
|
|
384
|
+
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
384
385
|
return {
|
|
385
386
|
name: toolName,
|
|
386
387
|
path: filePath,
|
package/src/discovery/claude.ts
CHANGED
|
@@ -406,8 +406,9 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
406
406
|
const userToolsDir = path.join(userBase, "tools");
|
|
407
407
|
|
|
408
408
|
const userResult = await loadFilesFromDir<CustomTool>(ctx, userToolsDir, PROVIDER_ID, "user", {
|
|
409
|
+
extensions: ["ts", "js"],
|
|
409
410
|
transform: (name, _content, path, source) => {
|
|
410
|
-
const toolName = name.replace(/\.(ts|js
|
|
411
|
+
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
411
412
|
return {
|
|
412
413
|
name: toolName,
|
|
413
414
|
path,
|
|
@@ -425,8 +426,9 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
425
426
|
const projectToolsDir = path.join(projectBase, "tools");
|
|
426
427
|
|
|
427
428
|
const projectResult = await loadFilesFromDir<CustomTool>(ctx, projectToolsDir, PROVIDER_ID, "project", {
|
|
429
|
+
extensions: ["ts", "js"],
|
|
428
430
|
transform: (name, _content, path, source) => {
|
|
429
|
-
const toolName = name.replace(/\.(ts|js
|
|
431
|
+
const toolName = name.replace(/\.(ts|js)$/, "");
|
|
430
432
|
return {
|
|
431
433
|
name: toolName,
|
|
432
434
|
path,
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -512,6 +512,11 @@ export async function loadFilesFromDir<T>(
|
|
|
512
512
|
gitignore: true,
|
|
513
513
|
hidden: false,
|
|
514
514
|
fileType: FileType.File,
|
|
515
|
+
// Thread the caller's non-recursive intent explicitly: the native glob
|
|
516
|
+
// defaults `recursive` to true and rewrites `*.{ts,js}` -> `**/*.{ts,js}`,
|
|
517
|
+
// which would walk the entire subtree (e.g. a venv's site-packages under
|
|
518
|
+
// ~/.codex/tools) and import arbitrary frontend assets as tools (#8552).
|
|
519
|
+
recursive,
|
|
515
520
|
});
|
|
516
521
|
matches = result.matches;
|
|
517
522
|
} catch {
|
|
@@ -25,6 +25,7 @@ import { FileChangeType, notifyWorkspaceWatchedFiles } from "../../lsp/client";
|
|
|
25
25
|
import type { ToolSession } from "../../tools";
|
|
26
26
|
import { routeWriteThroughBridge } from "../../tools/acp-bridge";
|
|
27
27
|
import { assertEditableFileContent } from "../../tools/auto-generated-guard";
|
|
28
|
+
import { deleteFileWithFallback, writeFileWithFallback } from "../../tools/file-write-fallback";
|
|
28
29
|
import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
|
|
29
30
|
import { isInternalUrlPath } from "../../tools/path-utils";
|
|
30
31
|
import { enforcePlanModeWrite, resolvePlanPath, targetsLocalSandbox } from "../../tools/plan-mode-guard";
|
|
@@ -153,7 +154,7 @@ export class HashlineFilesystem extends Filesystem {
|
|
|
153
154
|
enforcePlanModeWrite(this.session, relativePath, { op: "delete" });
|
|
154
155
|
const absolutePath = this.resolveAbsolute(relativePath);
|
|
155
156
|
try {
|
|
156
|
-
await
|
|
157
|
+
await deleteFileWithFallback(absolutePath);
|
|
157
158
|
} catch (error) {
|
|
158
159
|
if (isEnoent(error)) throw new NotFoundError(relativePath, error);
|
|
159
160
|
throw error;
|
|
@@ -173,8 +174,13 @@ export class HashlineFilesystem extends Filesystem {
|
|
|
173
174
|
const fromAbsolute = this.resolveAbsolute(fromRelative);
|
|
174
175
|
const toAbsolute = this.resolveAbsolute(toRelative);
|
|
175
176
|
if (content !== undefined) {
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
// The one `edit` write that does not pass through the writethrough, so it
|
|
178
|
+
// routes to the fallback seam directly. `patcher.ts` always supplies
|
|
179
|
+
// `content` for a hashline `MV`, making this the live branch. The source
|
|
180
|
+
// unlink is a separate primitive with its own seam, so a move out of the
|
|
181
|
+
// workspace and a move out of denied territory both complete.
|
|
182
|
+
await writeFileWithFallback(toAbsolute, content);
|
|
183
|
+
await deleteFileWithFallback(fromAbsolute);
|
|
178
184
|
} else {
|
|
179
185
|
await fs.rename(fromAbsolute, toAbsolute);
|
|
180
186
|
}
|
package/src/edit/modes/patch.ts
CHANGED
|
@@ -20,6 +20,12 @@ import { FileChangeType, notifyWorkspaceWatchedFiles } from "../../lsp/client";
|
|
|
20
20
|
import type { ToolSession } from "../../tools";
|
|
21
21
|
import { routeWriteThroughBridge } from "../../tools/acp-bridge";
|
|
22
22
|
import { assertEditableFile } from "../../tools/auto-generated-guard";
|
|
23
|
+
import {
|
|
24
|
+
deleteFileWithFallback,
|
|
25
|
+
hasFileWriteFallback,
|
|
26
|
+
isPermissionDeniedError,
|
|
27
|
+
writeFileWithFallback,
|
|
28
|
+
} from "../../tools/file-write-fallback";
|
|
23
29
|
import {
|
|
24
30
|
invalidateFsScanAfterDelete,
|
|
25
31
|
invalidateFsScanAfterRename,
|
|
@@ -111,6 +117,26 @@ export interface ApplyPatchOptions {
|
|
|
111
117
|
// Default File System
|
|
112
118
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
113
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Create a patch target's parent directory, tolerating a permission denial when a
|
|
122
|
+
* file-write fallback is registered.
|
|
123
|
+
*
|
|
124
|
+
* `apply_patch` mkdirs the parent before writing, so under a sandbox that denies
|
|
125
|
+
* the out-of-tree path this throws before the write — and therefore before
|
|
126
|
+
* {@link writeFileWithFallback} — is ever reached, leaving the fallback unable to
|
|
127
|
+
* broker a `create` or a rename-move into a new directory. Swallowing only a
|
|
128
|
+
* permission denial, and only with a handler installed, hands control to the write,
|
|
129
|
+
* which reports the denial through the seam. Without a handler the error propagates
|
|
130
|
+
* exactly as before.
|
|
131
|
+
*/
|
|
132
|
+
async function mkdirAllowingFallback(dir: string): Promise<void> {
|
|
133
|
+
try {
|
|
134
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!hasFileWriteFallback() || !isPermissionDeniedError(error)) throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
114
140
|
/** Default filesystem implementation using Bun APIs */
|
|
115
141
|
export const defaultFileSystem: FileSystem = {
|
|
116
142
|
async exists(path: string): Promise<boolean> {
|
|
@@ -123,13 +149,13 @@ export const defaultFileSystem: FileSystem = {
|
|
|
123
149
|
return fs.promises.readFile(path);
|
|
124
150
|
},
|
|
125
151
|
async write(path: string, content: string): Promise<void> {
|
|
126
|
-
await
|
|
152
|
+
await writeFileWithFallback(path, await serializeEditFileText(path, path, content));
|
|
127
153
|
},
|
|
128
154
|
async delete(path: string): Promise<void> {
|
|
129
|
-
await
|
|
155
|
+
await deleteFileWithFallback(path);
|
|
130
156
|
},
|
|
131
157
|
async mkdir(path: string): Promise<void> {
|
|
132
|
-
await
|
|
158
|
+
await mkdirAllowingFallback(path);
|
|
133
159
|
},
|
|
134
160
|
};
|
|
135
161
|
|
|
@@ -1753,7 +1779,7 @@ class LspFileSystem implements FileSystem {
|
|
|
1753
1779
|
}
|
|
1754
1780
|
|
|
1755
1781
|
async delete(path: string): Promise<void> {
|
|
1756
|
-
await this.#getFile(path)
|
|
1782
|
+
await deleteFileWithFallback(path, this.#getFile(path));
|
|
1757
1783
|
if (this.session.enableLsp ?? true) {
|
|
1758
1784
|
await notifyWorkspaceWatchedFiles(
|
|
1759
1785
|
this.session.cwd,
|
|
@@ -1764,7 +1790,7 @@ class LspFileSystem implements FileSystem {
|
|
|
1764
1790
|
}
|
|
1765
1791
|
|
|
1766
1792
|
async mkdir(path: string): Promise<void> {
|
|
1767
|
-
await
|
|
1793
|
+
await mkdirAllowingFallback(path);
|
|
1768
1794
|
}
|
|
1769
1795
|
|
|
1770
1796
|
getDiagnostics(): FileDiagnosticsResult | undefined {
|