@oh-my-pi/pi-coding-agent 14.0.2 → 14.0.4
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 +30 -0
- package/package.json +7 -7
- package/src/config/model-registry.ts +17 -3
- package/src/edit/index.ts +2 -0
- package/src/edit/modes/chunk.ts +37 -13
- package/src/edit/modes/hashline.ts +2 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +34 -13
- package/src/ipy/executor.ts +3 -7
- package/src/ipy/kernel.ts +3 -3
- package/src/lsp/config.ts +18 -2
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/utils.ts +1 -143
- package/src/modes/components/session-observer-overlay.ts +21 -12
- package/src/modes/theme/theme.ts +2 -161
- package/src/prompts/review-request.md +6 -0
- package/src/prompts/system/system-prompt.md +40 -2
- package/src/prompts/tools/chunk-edit.md +46 -13
- package/src/prompts/tools/read-chunk.md +6 -0
- package/src/sdk.ts +2 -1
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/index.ts +2 -3
- package/src/tools/read.ts +2 -0
- package/src/tools/report-tool-issue.ts +2 -2
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/sixel.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.0.4] - 2026-04-10
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `PI_CHUNK_AUTOINDENT` environment variable to control whether chunk read/edit tools normalize indentation to canonical tabs or preserve literal file whitespace
|
|
9
|
+
- Added dynamic chunk tool prompts that automatically adjust guidance based on `PI_CHUNK_AUTOINDENT` setting without exposing a tool parameter
|
|
10
|
+
- Added `<instruction-priority>`, `<output-contract>`, `<default-follow-through>`, `<tool-persistence>`, and `<completeness-contract>` sections to system prompt for improved long-horizon agent workflows
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Updated chunk edit tool to apply `normalizeIndent` setting during edit operations, enabling literal whitespace preservation when `PI_CHUNK_AUTOINDENT=0`
|
|
15
|
+
- Refactored environment variable parsing to use `$flag()` and `$envpos()` utilities from pi-utils for consistent boolean and integer handling across codebase
|
|
16
|
+
- Updated system prompt communication guidelines to emphasize conciseness and information density, and added guidance on avoiding repetition of user requests
|
|
17
|
+
- Enhanced system prompt with explicit rules for design integrity, verification before yielding, and handling of missing context via tool-based retrieval
|
|
18
|
+
- Added `PI_CHUNK_AUTOINDENT` to control whether chunk read/edit tools normalize indentation, and updated chunk prompts to switch guidance automatically based on that setting
|
|
19
|
+
- Refined the default system prompt with explicit instruction-priority, output-contract, tool-persistence, completeness, and verification rules for long-horizon GPT-5.4-style agent workflows
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed typo in system prompt: 'backwards compatibiltity' → 'backwards compatibility'
|
|
24
|
+
|
|
25
|
+
## [14.0.3] - 2026-04-09
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed cached Ollama discovery rows so upgraded installs switch to the OpenAI Responses transport instead of staying on the old completions transport
|
|
30
|
+
|
|
5
31
|
## [14.0.2] - 2026-04-09
|
|
6
32
|
### Added
|
|
7
33
|
|
|
@@ -258,6 +284,10 @@
|
|
|
258
284
|
- `/autoresearch` toggles like `/plan` when empty; slash completion no longer suggests `off`/`clear` on an empty prefix after the command
|
|
259
285
|
- Chunk-mode read/edit edge cases (zero-width gap replaces, stale batch diagnostics, grouped Go receivers, line-count headers, parse error locations)
|
|
260
286
|
|
|
287
|
+
### Added
|
|
288
|
+
|
|
289
|
+
- `/review` command now accepts inline args as custom instructions appended to the generated prompt for all structured review modes (PR-style, uncommitted, specific commit). When inline args are provided, option 4 (editor) is suppressed from the menu. The no-UI (Task tool) path forwards args as a focus hint.
|
|
290
|
+
|
|
261
291
|
## [13.19.0] - 2026-04-05
|
|
262
292
|
|
|
263
293
|
### Added
|
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": "14.0.
|
|
4
|
+
"version": "14.0.4",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
48
48
|
"@mozilla/readability": "^0.6",
|
|
49
|
-
"@oh-my-pi/omp-stats": "14.0.
|
|
50
|
-
"@oh-my-pi/pi-agent-core": "14.0.
|
|
51
|
-
"@oh-my-pi/pi-ai": "14.0.
|
|
52
|
-
"@oh-my-pi/pi-natives": "14.0.
|
|
53
|
-
"@oh-my-pi/pi-tui": "14.0.
|
|
54
|
-
"@oh-my-pi/pi-utils": "14.0.
|
|
49
|
+
"@oh-my-pi/omp-stats": "14.0.4",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "14.0.4",
|
|
51
|
+
"@oh-my-pi/pi-ai": "14.0.4",
|
|
52
|
+
"@oh-my-pi/pi-natives": "14.0.4",
|
|
53
|
+
"@oh-my-pi/pi-tui": "14.0.4",
|
|
54
|
+
"@oh-my-pi/pi-utils": "14.0.4",
|
|
55
55
|
"@sinclair/typebox": "^0.34",
|
|
56
56
|
"@xterm/headless": "^6.0",
|
|
57
57
|
"ajv": "^8.18",
|
|
@@ -947,7 +947,10 @@ export class ModelRegistry {
|
|
|
947
947
|
}
|
|
948
948
|
const models = this.#applyProviderModelOverrides(
|
|
949
949
|
providerConfig.provider,
|
|
950
|
-
this.#
|
|
950
|
+
this.#normalizeDiscoverableModels(
|
|
951
|
+
providerConfig,
|
|
952
|
+
this.#applyProviderCompat(providerConfig.compat, cache.models),
|
|
953
|
+
),
|
|
951
954
|
);
|
|
952
955
|
cachedModels.push(...models);
|
|
953
956
|
this.#providerDiscoveryStates.set(providerConfig.provider, {
|
|
@@ -967,11 +970,19 @@ export class ModelRegistry {
|
|
|
967
970
|
return models.map(model => ({ ...model, compat: mergeCompat(model.compat, compat) }));
|
|
968
971
|
}
|
|
969
972
|
|
|
973
|
+
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
974
|
+
if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
|
|
975
|
+
return models;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
return models.map(model => (model.api === "openai-completions" ? { ...model, api: "openai-responses" } : model));
|
|
979
|
+
}
|
|
980
|
+
|
|
970
981
|
#addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
|
|
971
982
|
if (!configuredProviders.has("ollama")) {
|
|
972
983
|
this.#discoverableProviders.push({
|
|
973
984
|
provider: "ollama",
|
|
974
|
-
api: "openai-
|
|
985
|
+
api: "openai-responses",
|
|
975
986
|
baseUrl: Bun.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434",
|
|
976
987
|
discovery: { type: "ollama" },
|
|
977
988
|
optional: true,
|
|
@@ -1203,7 +1214,10 @@ export class ModelRegistry {
|
|
|
1203
1214
|
}
|
|
1204
1215
|
return this.#applyProviderModelOverrides(
|
|
1205
1216
|
providerId,
|
|
1206
|
-
this.#
|
|
1217
|
+
this.#normalizeDiscoverableModels(
|
|
1218
|
+
providerConfig,
|
|
1219
|
+
this.#applyProviderCompat(providerConfig.compat, result.models),
|
|
1220
|
+
),
|
|
1207
1221
|
);
|
|
1208
1222
|
}
|
|
1209
1223
|
|
package/src/edit/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
executeChunkMode,
|
|
20
20
|
isChunkParams,
|
|
21
21
|
resolveAnchorStyle,
|
|
22
|
+
resolveChunkAutoIndent,
|
|
22
23
|
} from "./modes/chunk";
|
|
23
24
|
import { executeHashlineMode, type HashlineParams, hashlineEditParamsSchema, isHashlineParams } from "./modes/hashline";
|
|
24
25
|
import { executePatchMode, isPatchParams, type PatchParams, patchEditSchema } from "./modes/patch";
|
|
@@ -197,6 +198,7 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
197
198
|
description: (session: ToolSession) =>
|
|
198
199
|
prompt.render(chunkEditDescription, {
|
|
199
200
|
anchorStyle: resolveAnchorStyle(session.settings),
|
|
201
|
+
chunkAutoIndent: resolveChunkAutoIndent(),
|
|
200
202
|
}),
|
|
201
203
|
parameters: chunkEditParamsSchema,
|
|
202
204
|
invalidParamsMessage: "Invalid edit parameters for chunk mode.",
|
package/src/edit/modes/chunk.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ChunkState,
|
|
12
12
|
type EditOperation as NativeEditOperation,
|
|
13
13
|
} from "@oh-my-pi/pi-natives";
|
|
14
|
+
import { $envpos } from "@oh-my-pi/pi-utils";
|
|
14
15
|
import { type Static, Type } from "@sinclair/typebox";
|
|
15
16
|
import type { BunFile } from "bun";
|
|
16
17
|
import { LRUCache } from "lru-cache";
|
|
@@ -63,6 +64,34 @@ const validAnchorStyles: Record<string, ChunkAnchorStyle> = {
|
|
|
63
64
|
bare: ChunkAnchorStyle.Bare,
|
|
64
65
|
};
|
|
65
66
|
|
|
67
|
+
export function resolveChunkAutoIndent(rawValue = Bun.env.PI_CHUNK_AUTOINDENT): boolean {
|
|
68
|
+
if (!rawValue) return true;
|
|
69
|
+
const normalized = rawValue.trim().toLowerCase();
|
|
70
|
+
switch (normalized) {
|
|
71
|
+
case "1":
|
|
72
|
+
case "true":
|
|
73
|
+
case "yes":
|
|
74
|
+
case "on":
|
|
75
|
+
return true;
|
|
76
|
+
case "0":
|
|
77
|
+
case "false":
|
|
78
|
+
case "no":
|
|
79
|
+
case "off":
|
|
80
|
+
return false;
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(`Invalid PI_CHUNK_AUTOINDENT: ${rawValue}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getChunkRenderIndentOptions(): {
|
|
87
|
+
normalizeIndent: boolean;
|
|
88
|
+
tabReplacement: string;
|
|
89
|
+
} {
|
|
90
|
+
return resolveChunkAutoIndent()
|
|
91
|
+
? { normalizeIndent: true, tabReplacement: " " }
|
|
92
|
+
: { normalizeIndent: false, tabReplacement: "\t" };
|
|
93
|
+
}
|
|
94
|
+
|
|
66
95
|
export function resolveAnchorStyle(settings?: Settings): ChunkAnchorStyle {
|
|
67
96
|
const envStyle = Bun.env.PI_ANCHOR_STYLE;
|
|
68
97
|
return (
|
|
@@ -72,16 +101,8 @@ export function resolveAnchorStyle(settings?: Settings): ChunkAnchorStyle {
|
|
|
72
101
|
);
|
|
73
102
|
}
|
|
74
103
|
|
|
75
|
-
const readEnvInt = (name: string, defaultValue: number): number => {
|
|
76
|
-
const value = Bun.env[name];
|
|
77
|
-
if (!value) return defaultValue;
|
|
78
|
-
const parsed = Number.parseInt(value, 10);
|
|
79
|
-
if (Number.isNaN(parsed) || parsed <= 0) return defaultValue;
|
|
80
|
-
return parsed;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
104
|
const chunkStateCache = new LRUCache<string, ChunkCacheEntry>({
|
|
84
|
-
max:
|
|
105
|
+
max: $envpos("PI_CHUNK_CACHE_MAX_ENTRIES", 200),
|
|
85
106
|
});
|
|
86
107
|
|
|
87
108
|
export function invalidateChunkCache(filePath: string): void {
|
|
@@ -215,6 +236,7 @@ export async function formatChunkedRead(params: {
|
|
|
215
236
|
const normalizedLanguage = normalizeLanguage(language);
|
|
216
237
|
const { state } = await loadChunkStateForFile(filePath, normalizedLanguage);
|
|
217
238
|
const displayPath = displayPathForFile(filePath, cwd);
|
|
239
|
+
const renderIndentOptions = getChunkRenderIndentOptions();
|
|
218
240
|
const result = state.renderRead({
|
|
219
241
|
readPath,
|
|
220
242
|
displayPath,
|
|
@@ -224,8 +246,8 @@ export async function formatChunkedRead(params: {
|
|
|
224
246
|
absoluteLineRange: absoluteLineRange
|
|
225
247
|
? { startLine: absoluteLineRange.startLine, endLine: absoluteLineRange.endLine ?? absoluteLineRange.startLine }
|
|
226
248
|
: undefined,
|
|
227
|
-
tabReplacement:
|
|
228
|
-
normalizeIndent:
|
|
249
|
+
tabReplacement: renderIndentOptions.tabReplacement,
|
|
250
|
+
normalizeIndent: renderIndentOptions.normalizeIndent,
|
|
229
251
|
});
|
|
230
252
|
return { text: result.text, resolvedPath: filePath, chunk: result.chunk };
|
|
231
253
|
}
|
|
@@ -280,6 +302,7 @@ export function applyChunkEdits(params: {
|
|
|
280
302
|
const state = ChunkState.parse(normalizedSource, normalizeLanguage(params.language));
|
|
281
303
|
const result = state.applyEdits({
|
|
282
304
|
operations: nativeOperations,
|
|
305
|
+
normalizeIndent: resolveChunkAutoIndent(),
|
|
283
306
|
defaultSelector: params.defaultSelector,
|
|
284
307
|
defaultCrc: params.defaultCrc,
|
|
285
308
|
anchorStyle: params.anchorStyle,
|
|
@@ -312,7 +335,8 @@ export const chunkToolEditSchema = Type.Object({
|
|
|
312
335
|
"Chunk selector. Format: 'path@region' for insertions, 'path#CRC@region' for replace. Omit @region to target the full chunk. Valid regions: head, body, tail, decl.",
|
|
313
336
|
}),
|
|
314
337
|
content: Type.String({
|
|
315
|
-
description:
|
|
338
|
+
description:
|
|
339
|
+
"New content. Write indentation relative to the targeted region as described in the tool prompt. Do NOT include the chunk's base padding.",
|
|
316
340
|
}),
|
|
317
341
|
});
|
|
318
342
|
export const chunkEditParamsSchema = Type.Object(
|
|
@@ -387,7 +411,7 @@ async function writeChunkResult(params: {
|
|
|
387
411
|
invalidateFsScanAfterWrite(resolvedPath);
|
|
388
412
|
|
|
389
413
|
const diffResult = generateUnifiedDiffString(result.diffSourceBefore, result.diffSourceAfter);
|
|
390
|
-
const warningsBlock = result.warnings.length > 0 ? `\n\n${result.warnings.join("\n")}` : "";
|
|
414
|
+
const warningsBlock = result.warnings.length > 0 ? `\n\nWarnings:\n${result.warnings.join("\n")}` : "";
|
|
391
415
|
const meta = outputMeta()
|
|
392
416
|
.diagnostics(diagnostics?.summary ?? "", diagnostics?.messages ?? [])
|
|
393
417
|
.get();
|
|
@@ -156,8 +156,8 @@ interface ExecuteHashlineModeOptions {
|
|
|
156
156
|
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export function hashlineParseText(edit: string[] | string | null): string[] {
|
|
160
|
-
if (edit
|
|
159
|
+
export function hashlineParseText(edit: string[] | string | null | undefined): string[] {
|
|
160
|
+
if (edit == null) return [];
|
|
161
161
|
if (typeof edit === "string") {
|
|
162
162
|
const normalizedEdit = edit.endsWith("\n") ? edit.slice(0, -1) : edit;
|
|
163
163
|
edit = normalizedEdit.replaceAll("\r", "").split("\n");
|
|
@@ -197,7 +197,7 @@ const MAX_FILES_FOR_INLINE_DIFF = 20; // Don't include diff if more files than t
|
|
|
197
197
|
/**
|
|
198
198
|
* Build the full review prompt with diff stats and distribution guidance.
|
|
199
199
|
*/
|
|
200
|
-
function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): string {
|
|
200
|
+
function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string, additionalInstructions?: string): string {
|
|
201
201
|
const agentCount = getRecommendedAgentCount(stats);
|
|
202
202
|
const skipDiff = rawDiff.length > MAX_DIFF_CHARS || stats.files.length > MAX_FILES_FOR_INLINE_DIFF;
|
|
203
203
|
const totalLines = stats.totalAdded + stats.totalRemoved;
|
|
@@ -221,6 +221,7 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
|
|
|
221
221
|
skipDiff,
|
|
222
222
|
rawDiff: rawDiff.trim(),
|
|
223
223
|
linesPerFile,
|
|
224
|
+
additionalInstructions,
|
|
224
225
|
});
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -230,17 +231,30 @@ export class ReviewCommand implements CustomCommand {
|
|
|
230
231
|
|
|
231
232
|
constructor(private api: CustomCommandAPI) {}
|
|
232
233
|
|
|
233
|
-
async execute(
|
|
234
|
+
async execute(args: string[], ctx: HookCommandContext): Promise<string | undefined> {
|
|
234
235
|
if (!ctx.hasUI) {
|
|
235
|
-
|
|
236
|
+
const base = "Use the Task tool to run the 'reviewer' agent to review recent code changes.";
|
|
237
|
+
return args.length > 0 ? `${base} Focus: ${args.join(" ")}` : base;
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
// Inline args act as additional instructions appended to the generated prompt.
|
|
241
|
+
// When present, skip option 4 (editor) — the args already provide the instructions.
|
|
242
|
+
const extraInstructions = args.length > 0 ? args.join(" ") : undefined;
|
|
243
|
+
|
|
244
|
+
const menuItems = extraInstructions
|
|
245
|
+
? [
|
|
246
|
+
"1. Review against a base branch (PR Style)",
|
|
247
|
+
"2. Review uncommitted changes",
|
|
248
|
+
"3. Review a specific commit",
|
|
249
|
+
]
|
|
250
|
+
: [
|
|
251
|
+
"1. Review against a base branch (PR Style)",
|
|
252
|
+
"2. Review uncommitted changes",
|
|
253
|
+
"3. Review a specific commit",
|
|
254
|
+
"4. Custom review instructions",
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
const mode = await ctx.ui.select("Review Mode", menuItems);
|
|
244
258
|
|
|
245
259
|
if (!mode) return undefined;
|
|
246
260
|
|
|
@@ -282,6 +296,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
282
296
|
`Reviewing changes between \`${baseBranch}\` and \`${currentBranch}\` (PR-style)`,
|
|
283
297
|
stats,
|
|
284
298
|
diffText,
|
|
299
|
+
extraInstructions,
|
|
285
300
|
);
|
|
286
301
|
}
|
|
287
302
|
|
|
@@ -318,7 +333,12 @@ export class ReviewCommand implements CustomCommand {
|
|
|
318
333
|
return undefined;
|
|
319
334
|
}
|
|
320
335
|
|
|
321
|
-
return buildReviewPrompt(
|
|
336
|
+
return buildReviewPrompt(
|
|
337
|
+
"Reviewing uncommitted changes (staged + unstaged)",
|
|
338
|
+
stats,
|
|
339
|
+
combinedDiff,
|
|
340
|
+
extraInstructions,
|
|
341
|
+
);
|
|
322
342
|
}
|
|
323
343
|
|
|
324
344
|
case 3: {
|
|
@@ -354,7 +374,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
354
374
|
return undefined;
|
|
355
375
|
}
|
|
356
376
|
|
|
357
|
-
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText);
|
|
377
|
+
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText, extraInstructions);
|
|
358
378
|
}
|
|
359
379
|
|
|
360
380
|
case 4: {
|
|
@@ -374,11 +394,12 @@ export class ReviewCommand implements CustomCommand {
|
|
|
374
394
|
if (reviewDiff) {
|
|
375
395
|
const stats = parseDiff(reviewDiff);
|
|
376
396
|
// Even if all files filtered, include the custom instructions
|
|
377
|
-
return
|
|
397
|
+
return buildReviewPrompt(
|
|
378
398
|
`Custom review: ${instructions.split("\n")[0].slice(0, 60)}…`,
|
|
379
399
|
stats,
|
|
380
400
|
reviewDiff,
|
|
381
|
-
|
|
401
|
+
instructions,
|
|
402
|
+
);
|
|
382
403
|
}
|
|
383
404
|
|
|
384
405
|
// No diff available, just pass instructions
|
package/src/ipy/executor.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { getAgentDir, getProjectDir, isBunTestRuntime, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { OutputSink } from "../session/streaming-output";
|
|
4
4
|
import { shutdownSharedGateway } from "./gateway-coordinator";
|
|
5
5
|
import {
|
|
@@ -299,10 +299,6 @@ async function writePreludeCache(state: PreludeCacheState, helpers: PreludeHelpe
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
function isPythonTestEnvironment(): boolean {
|
|
303
|
-
return Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
|
|
304
|
-
}
|
|
305
|
-
|
|
306
302
|
function getPreludeIntrospectionOptions(
|
|
307
303
|
options: KernelSessionExecutionOptions = {},
|
|
308
304
|
): Pick<KernelExecuteOptions, "signal" | "timeoutMs"> {
|
|
@@ -318,7 +314,7 @@ async function cachePreludeDocs(
|
|
|
318
314
|
cacheState?: PreludeCacheState | null,
|
|
319
315
|
): Promise<PreludeHelper[]> {
|
|
320
316
|
cachedPreludeDocs = docs;
|
|
321
|
-
if (!
|
|
317
|
+
if (!isBunTestRuntime() && docs.length > 0) {
|
|
322
318
|
const state = cacheState ?? (await buildPreludeCacheState(cwd));
|
|
323
319
|
await writePreludeCache(state, docs);
|
|
324
320
|
}
|
|
@@ -416,7 +412,7 @@ export async function warmPythonEnvironment(
|
|
|
416
412
|
cachedPreludeDocs = [];
|
|
417
413
|
return { ok: false, reason, docs: [] };
|
|
418
414
|
}
|
|
419
|
-
if (!
|
|
415
|
+
if (!isBunTestRuntime()) {
|
|
420
416
|
try {
|
|
421
417
|
cacheState = await buildPreludeCacheState(cwd);
|
|
422
418
|
const cached = await readPreludeCache(cacheState);
|
package/src/ipy/kernel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $env, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { $env, $flag, isBunTestRuntime, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import { $ } from "bun";
|
|
3
3
|
import { Settings } from "../config/settings";
|
|
4
4
|
import { htmlToBasicMarkdown } from "../web/scrapers/types";
|
|
@@ -10,7 +10,7 @@ import { filterEnv, resolvePythonRuntime } from "./runtime";
|
|
|
10
10
|
|
|
11
11
|
const TEXT_ENCODER = new TextEncoder();
|
|
12
12
|
const TEXT_DECODER = new TextDecoder();
|
|
13
|
-
const TRACE_IPC = $
|
|
13
|
+
const TRACE_IPC = $flag("PI_PYTHON_IPC_TRACE");
|
|
14
14
|
const PRELUDE_INTROSPECTION_SNIPPET = "import json\nprint(json.dumps(__omp_prelude_docs__()))";
|
|
15
15
|
|
|
16
16
|
class SharedGatewayCreateError extends Error {
|
|
@@ -195,7 +195,7 @@ export interface PythonKernelAvailability {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
|
|
198
|
-
if (
|
|
198
|
+
if (isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK")) {
|
|
199
199
|
return { ok: true };
|
|
200
200
|
}
|
|
201
201
|
|
package/src/lsp/config.ts
CHANGED
|
@@ -197,6 +197,21 @@ const LOCAL_BIN_PATHS: Array<{ markers: string[]; binDir: string }> = [
|
|
|
197
197
|
{ markers: ["go.mod", "go.sum"], binDir: "bin" },
|
|
198
198
|
];
|
|
199
199
|
|
|
200
|
+
const WINDOWS_LOCAL_EXECUTABLE_EXTENSIONS = [".exe", ".cmd", ".bat"] as const;
|
|
201
|
+
|
|
202
|
+
function resolveLocalCommand(basePath: string): string | null {
|
|
203
|
+
if (fs.existsSync(basePath)) return basePath;
|
|
204
|
+
if (process.platform !== "win32") return null;
|
|
205
|
+
|
|
206
|
+
// Package managers write Windows launchers with executable suffixes in node_modules/.bin.
|
|
207
|
+
for (const extension of WINDOWS_LOCAL_EXECUTABLE_EXTENSIONS) {
|
|
208
|
+
const candidate = `${basePath}${extension}`;
|
|
209
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
200
215
|
/**
|
|
201
216
|
* Resolve a command to an executable path.
|
|
202
217
|
* Checks project-local bin directories first, then falls back to $PATH.
|
|
@@ -210,8 +225,9 @@ export function resolveCommand(command: string, cwd: string): string | null {
|
|
|
210
225
|
for (const { markers, binDir } of LOCAL_BIN_PATHS) {
|
|
211
226
|
if (hasRootMarkers(cwd, markers)) {
|
|
212
227
|
const localPath = path.join(cwd, binDir, command);
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
const resolvedLocalPath = resolveLocalCommand(localPath);
|
|
229
|
+
if (resolvedLocalPath) {
|
|
230
|
+
return resolvedLocalPath;
|
|
215
231
|
}
|
|
216
232
|
}
|
|
217
233
|
}
|
package/src/lsp/lspmux.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { $
|
|
3
|
+
import { $flag, $which, logger } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { TOML } from "bun";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -135,7 +135,7 @@ export async function detectLspmux(): Promise<LspmuxState> {
|
|
|
135
135
|
return cachedState;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
if ($
|
|
138
|
+
if ($flag("PI_DISABLE_LSPMUX")) {
|
|
139
139
|
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
140
140
|
cacheTimestamp = now;
|
|
141
141
|
return cachedState;
|
package/src/lsp/utils.ts
CHANGED
|
@@ -16,149 +16,7 @@ import type {
|
|
|
16
16
|
WorkspaceEdit,
|
|
17
17
|
} from "./types";
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
// Language Detection
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
const LANGUAGE_MAP: Record<string, string> = {
|
|
24
|
-
// TypeScript/JavaScript
|
|
25
|
-
".ts": "typescript",
|
|
26
|
-
".tsx": "typescriptreact",
|
|
27
|
-
".js": "javascript",
|
|
28
|
-
".jsx": "javascriptreact",
|
|
29
|
-
".mjs": "javascript",
|
|
30
|
-
".cjs": "javascript",
|
|
31
|
-
".mts": "typescript",
|
|
32
|
-
".cts": "typescript",
|
|
33
|
-
|
|
34
|
-
// Systems languages
|
|
35
|
-
".rs": "rust",
|
|
36
|
-
".go": "go",
|
|
37
|
-
".c": "c",
|
|
38
|
-
".h": "c",
|
|
39
|
-
".cpp": "cpp",
|
|
40
|
-
".cc": "cpp",
|
|
41
|
-
".cxx": "cpp",
|
|
42
|
-
".hpp": "cpp",
|
|
43
|
-
".hxx": "cpp",
|
|
44
|
-
".zig": "zig",
|
|
45
|
-
|
|
46
|
-
// Scripting languages
|
|
47
|
-
".py": "python",
|
|
48
|
-
".rb": "ruby",
|
|
49
|
-
".lua": "lua",
|
|
50
|
-
".sh": "shellscript",
|
|
51
|
-
".bash": "shellscript",
|
|
52
|
-
".zsh": "shellscript",
|
|
53
|
-
".fish": "fish",
|
|
54
|
-
".pl": "perl",
|
|
55
|
-
".pm": "perl",
|
|
56
|
-
".php": "php",
|
|
57
|
-
|
|
58
|
-
// JVM languages
|
|
59
|
-
".java": "java",
|
|
60
|
-
".kt": "kotlin",
|
|
61
|
-
".kts": "kotlin",
|
|
62
|
-
".scala": "scala",
|
|
63
|
-
".groovy": "groovy",
|
|
64
|
-
".clj": "clojure",
|
|
65
|
-
|
|
66
|
-
// .NET languages
|
|
67
|
-
".cs": "csharp",
|
|
68
|
-
".fs": "fsharp",
|
|
69
|
-
".vb": "vb",
|
|
70
|
-
|
|
71
|
-
// Web
|
|
72
|
-
".html": "html",
|
|
73
|
-
".htm": "html",
|
|
74
|
-
".css": "css",
|
|
75
|
-
".scss": "scss",
|
|
76
|
-
".sass": "sass",
|
|
77
|
-
".less": "less",
|
|
78
|
-
".vue": "vue",
|
|
79
|
-
".svelte": "svelte",
|
|
80
|
-
".astro": "astro",
|
|
81
|
-
|
|
82
|
-
// Data formats
|
|
83
|
-
".json": "json",
|
|
84
|
-
".jsonc": "jsonc",
|
|
85
|
-
".yaml": "yaml",
|
|
86
|
-
".yml": "yaml",
|
|
87
|
-
".toml": "toml",
|
|
88
|
-
".xml": "xml",
|
|
89
|
-
".ini": "ini",
|
|
90
|
-
|
|
91
|
-
// Documentation
|
|
92
|
-
".md": "markdown",
|
|
93
|
-
".markdown": "markdown",
|
|
94
|
-
".rst": "restructuredtext",
|
|
95
|
-
".adoc": "asciidoc",
|
|
96
|
-
".tex": "latex",
|
|
97
|
-
|
|
98
|
-
// Other
|
|
99
|
-
".sql": "sql",
|
|
100
|
-
".graphql": "graphql",
|
|
101
|
-
".gql": "graphql",
|
|
102
|
-
".proto": "protobuf",
|
|
103
|
-
".dockerfile": "dockerfile",
|
|
104
|
-
".tf": "terraform",
|
|
105
|
-
".hcl": "hcl",
|
|
106
|
-
".nix": "nix",
|
|
107
|
-
".ex": "elixir",
|
|
108
|
-
".exs": "elixir",
|
|
109
|
-
".erl": "erlang",
|
|
110
|
-
".hrl": "erlang",
|
|
111
|
-
".hs": "haskell",
|
|
112
|
-
".ml": "ocaml",
|
|
113
|
-
".mli": "ocaml",
|
|
114
|
-
".swift": "swift",
|
|
115
|
-
".r": "r",
|
|
116
|
-
".R": "r",
|
|
117
|
-
".jl": "julia",
|
|
118
|
-
".dart": "dart",
|
|
119
|
-
".elm": "elm",
|
|
120
|
-
".v": "v",
|
|
121
|
-
".nim": "nim",
|
|
122
|
-
".cr": "crystal",
|
|
123
|
-
".d": "d",
|
|
124
|
-
".pas": "pascal",
|
|
125
|
-
".pp": "pascal",
|
|
126
|
-
".lisp": "lisp",
|
|
127
|
-
".lsp": "lisp",
|
|
128
|
-
".rkt": "racket",
|
|
129
|
-
".scm": "scheme",
|
|
130
|
-
".ps1": "powershell",
|
|
131
|
-
".psm1": "powershell",
|
|
132
|
-
".bat": "bat",
|
|
133
|
-
".cmd": "bat",
|
|
134
|
-
".tla": "tlaplus",
|
|
135
|
-
".tlaplus": "tlaplus",
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Detect language ID from file path.
|
|
140
|
-
* Returns the LSP language identifier for the file type.
|
|
141
|
-
*/
|
|
142
|
-
export function detectLanguageId(filePath: string): string {
|
|
143
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
144
|
-
const basename = path.basename(filePath).toLowerCase();
|
|
145
|
-
|
|
146
|
-
// Handle special filenames
|
|
147
|
-
if (basename === "dockerfile" || basename.startsWith("dockerfile.") || basename === "containerfile") {
|
|
148
|
-
return "dockerfile";
|
|
149
|
-
}
|
|
150
|
-
if (basename === "makefile" || basename === "gnumakefile") {
|
|
151
|
-
return "makefile";
|
|
152
|
-
}
|
|
153
|
-
if (basename === "justfile") {
|
|
154
|
-
return "just";
|
|
155
|
-
}
|
|
156
|
-
if (basename === "cmakelists.txt" || ext === ".cmake") {
|
|
157
|
-
return "cmake";
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return LANGUAGE_MAP[ext] ?? "plaintext";
|
|
161
|
-
}
|
|
19
|
+
export { detectLanguageId } from "../utils/lang-from-path";
|
|
162
20
|
|
|
163
21
|
// =============================================================================
|
|
164
22
|
// URI Handling (Cross-Platform)
|