@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.1
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 +120 -0
- package/package.json +8 -8
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +43 -10
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/commit/model-selection.ts +16 -13
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +242 -45
- package/src/config/model-resolver.ts +282 -65
- package/src/config/settings-schema.ts +27 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +614 -97
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +55 -1
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +6 -2
- package/src/memories/index.ts +7 -6
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +42 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +17 -6
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +16 -1
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +12 -3
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +758 -725
- package/src/session/agent-session.ts +187 -40
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +9 -5
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +240 -57
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
- package/src/tools/python.ts +293 -278
- package/src/tools/read.ts +218 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/git.ts +24 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +16 -7
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
package/src/ipy/kernel.ts
CHANGED
|
@@ -49,6 +49,10 @@ interface KernelShutdownOptions {
|
|
|
49
49
|
timeoutMs?: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export interface KernelShutdownResult {
|
|
53
|
+
confirmed: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
52
56
|
function getRemainingTimeMs(deadlineMs?: number): number | undefined {
|
|
53
57
|
if (deadlineMs === undefined) return undefined;
|
|
54
58
|
return Math.max(0, deadlineMs - Date.now());
|
|
@@ -399,10 +403,11 @@ export class PythonKernel {
|
|
|
399
403
|
#ws: WebSocket | null = null;
|
|
400
404
|
#disposed = false;
|
|
401
405
|
#alive = true;
|
|
406
|
+
#shutdownStarted = false;
|
|
407
|
+
#shutdownConfirmed = false;
|
|
402
408
|
#messageHandlers = new Map<string, (msg: JupyterMessage) => void>();
|
|
403
409
|
#channelHandlers = new Map<string, Set<(msg: JupyterMessage) => void>>();
|
|
404
410
|
#pendingExecutions = new Map<string, (reason: string) => void>();
|
|
405
|
-
|
|
406
411
|
private constructor(
|
|
407
412
|
readonly id: string,
|
|
408
413
|
readonly kernelId: string,
|
|
@@ -1004,11 +1009,18 @@ export class PythonKernel {
|
|
|
1004
1009
|
}
|
|
1005
1010
|
}
|
|
1006
1011
|
|
|
1007
|
-
async shutdown(options?: KernelShutdownOptions): Promise<
|
|
1008
|
-
if (this.#
|
|
1009
|
-
this.#
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
+
async shutdown(options?: KernelShutdownOptions): Promise<KernelShutdownResult> {
|
|
1013
|
+
if (this.#shutdownConfirmed) return { confirmed: true };
|
|
1014
|
+
if (!this.#shutdownStarted) {
|
|
1015
|
+
this.#shutdownStarted = true;
|
|
1016
|
+
this.#alive = false;
|
|
1017
|
+
this.#abortPendingExecutions("Kernel shutdown");
|
|
1018
|
+
|
|
1019
|
+
if (this.#ws) {
|
|
1020
|
+
this.#ws.close();
|
|
1021
|
+
this.#ws = null;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1012
1024
|
|
|
1013
1025
|
const shutdownSignal = combineAbortSignal(
|
|
1014
1026
|
{ signal: options?.signal },
|
|
@@ -1016,24 +1028,38 @@ export class PythonKernel {
|
|
|
1016
1028
|
"Python kernel shutdown timed out",
|
|
1017
1029
|
);
|
|
1018
1030
|
|
|
1031
|
+
let confirmed = false;
|
|
1019
1032
|
try {
|
|
1020
|
-
await fetch(`${this.gatewayUrl}/api/kernels/${this.kernelId}`, {
|
|
1033
|
+
const response = await fetch(`${this.gatewayUrl}/api/kernels/${this.kernelId}`, {
|
|
1021
1034
|
method: "DELETE",
|
|
1022
1035
|
headers: this.#authHeaders(),
|
|
1023
1036
|
signal: shutdownSignal,
|
|
1024
1037
|
});
|
|
1038
|
+
const deleteConfirmed = response.status === 404 || response.status === 410;
|
|
1039
|
+
confirmed = response.ok || deleteConfirmed;
|
|
1040
|
+
if (!confirmed) {
|
|
1041
|
+
logger.warn("Kernel delete request was not confirmed", {
|
|
1042
|
+
status: response.status,
|
|
1043
|
+
statusText: response.statusText,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1025
1046
|
} catch (err: unknown) {
|
|
1026
1047
|
logger.warn("Failed to delete kernel via API", { error: err instanceof Error ? err.message : String(err) });
|
|
1027
1048
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
this.#ws.close();
|
|
1031
|
-
this.#ws = null;
|
|
1032
|
-
}
|
|
1049
|
+
this.#shutdownConfirmed = confirmed;
|
|
1050
|
+
this.#disposed = confirmed;
|
|
1033
1051
|
|
|
1034
1052
|
if (this.isSharedGateway) {
|
|
1035
|
-
|
|
1053
|
+
try {
|
|
1054
|
+
await releaseSharedGateway();
|
|
1055
|
+
} catch (err: unknown) {
|
|
1056
|
+
logger.warn("Failed to release shared gateway after kernel shutdown", {
|
|
1057
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1036
1060
|
}
|
|
1061
|
+
|
|
1062
|
+
return { confirmed };
|
|
1037
1063
|
}
|
|
1038
1064
|
|
|
1039
1065
|
#sendMessage(msg: JupyterMessage): void {
|
package/src/lsp/client.ts
CHANGED
|
@@ -23,7 +23,7 @@ const fileOperationLocks = new Map<string, Promise<void>>();
|
|
|
23
23
|
|
|
24
24
|
// Idle timeout configuration (disabled by default)
|
|
25
25
|
let idleTimeoutMs: number | null = null;
|
|
26
|
-
let idleCheckInterval:
|
|
26
|
+
let idleCheckInterval: NodeJS.Timeout | null = null;
|
|
27
27
|
const IDLE_CHECK_INTERVAL_MS = 60 * 1000;
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -136,6 +136,9 @@ const CLIENT_CAPABILITIES = {
|
|
|
136
136
|
dataSupport: true,
|
|
137
137
|
},
|
|
138
138
|
},
|
|
139
|
+
window: {
|
|
140
|
+
workDoneProgress: true,
|
|
141
|
+
},
|
|
139
142
|
workspace: {
|
|
140
143
|
applyEdit: true,
|
|
141
144
|
workspaceEdit: {
|
|
@@ -267,6 +270,16 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
267
270
|
version: params.version ?? null,
|
|
268
271
|
});
|
|
269
272
|
client.diagnosticsVersion += 1;
|
|
273
|
+
} else if (message.method === "$/progress" && message.params) {
|
|
274
|
+
const params = message.params as { token: string | number; value?: { kind?: string } };
|
|
275
|
+
if (params.value?.kind === "begin") {
|
|
276
|
+
client.activeProgressTokens.add(params.token);
|
|
277
|
+
} else if (params.value?.kind === "end") {
|
|
278
|
+
client.activeProgressTokens.delete(params.token);
|
|
279
|
+
if (client.activeProgressTokens.size === 0) {
|
|
280
|
+
client.resolveProjectLoaded();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
270
283
|
}
|
|
271
284
|
}
|
|
272
285
|
|
|
@@ -338,6 +351,13 @@ async function handleServerRequest(client: LspClient, message: LspJsonRpcRequest
|
|
|
338
351
|
await handleApplyEditRequest(client, message);
|
|
339
352
|
return;
|
|
340
353
|
}
|
|
354
|
+
if (message.method === "window/workDoneProgress/create") {
|
|
355
|
+
// Accept progress token registration from the server
|
|
356
|
+
if (typeof message.id === "number") {
|
|
357
|
+
await sendResponse(client, message.id, null, message.method);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
341
361
|
if (typeof message.id !== "number") return;
|
|
342
362
|
await sendResponse(client, message.id, null, message.method, {
|
|
343
363
|
code: -32601,
|
|
@@ -375,6 +395,9 @@ async function sendResponse(
|
|
|
375
395
|
/** Timeout for warmup initialize requests (5 seconds) */
|
|
376
396
|
export const WARMUP_TIMEOUT_MS = 5000;
|
|
377
397
|
|
|
398
|
+
/** Max time to wait for the server to report project loading completion via $/progress */
|
|
399
|
+
const PROJECT_LOAD_TIMEOUT_MS = 15_000;
|
|
400
|
+
|
|
378
401
|
/**
|
|
379
402
|
* Get or create an LSP client for the given server configuration and working directory.
|
|
380
403
|
* @param config - Server configuration
|
|
@@ -413,6 +436,18 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
413
436
|
env: env ? { ...Bun.env, ...env } : undefined,
|
|
414
437
|
});
|
|
415
438
|
|
|
439
|
+
let resolveProjectLoaded!: () => void;
|
|
440
|
+
const projectLoaded = new Promise<void>(resolve => {
|
|
441
|
+
resolveProjectLoaded = resolve;
|
|
442
|
+
});
|
|
443
|
+
// Auto-resolve after timeout in case server doesn't use progress tokens
|
|
444
|
+
const projectLoadTimeout = setTimeout(resolveProjectLoaded, PROJECT_LOAD_TIMEOUT_MS);
|
|
445
|
+
const originalResolve = resolveProjectLoaded;
|
|
446
|
+
resolveProjectLoaded = () => {
|
|
447
|
+
clearTimeout(projectLoadTimeout);
|
|
448
|
+
originalResolve();
|
|
449
|
+
};
|
|
450
|
+
|
|
416
451
|
const client: LspClient = {
|
|
417
452
|
name: key,
|
|
418
453
|
cwd,
|
|
@@ -426,6 +461,9 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
426
461
|
messageBuffer: new Uint8Array(0),
|
|
427
462
|
isReading: false,
|
|
428
463
|
lastActivity: Date.now(),
|
|
464
|
+
activeProgressTokens: new Set(),
|
|
465
|
+
projectLoaded,
|
|
466
|
+
resolveProjectLoaded,
|
|
429
467
|
};
|
|
430
468
|
clients.set(key, client);
|
|
431
469
|
|
|
@@ -433,6 +471,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
433
471
|
proc.exited.then(() => {
|
|
434
472
|
clients.delete(key);
|
|
435
473
|
clientLocks.delete(key);
|
|
474
|
+
client.resolveProjectLoaded();
|
|
436
475
|
|
|
437
476
|
// Reject any pending requests — the server is gone, they will never complete.
|
|
438
477
|
if (client.pendingRequests.size > 0) {
|
|
@@ -554,6 +593,21 @@ export async function ensureFileOpen(client: LspClient, filePath: string, signal
|
|
|
554
593
|
}
|
|
555
594
|
}
|
|
556
595
|
|
|
596
|
+
/**
|
|
597
|
+
* Wait for the server's initial project loading to complete.
|
|
598
|
+
* Races the server's $/progress tracking against the abort signal.
|
|
599
|
+
* Returns immediately if loading already completed or timed out.
|
|
600
|
+
*/
|
|
601
|
+
export async function waitForProjectLoaded(client: LspClient, signal?: AbortSignal): Promise<void> {
|
|
602
|
+
if (signal?.aborted) return;
|
|
603
|
+
await Promise.race([
|
|
604
|
+
client.projectLoaded,
|
|
605
|
+
...(signal
|
|
606
|
+
? [new Promise<void>(resolve => signal.addEventListener("abort", () => resolve(), { once: true }))]
|
|
607
|
+
: []),
|
|
608
|
+
]);
|
|
609
|
+
}
|
|
610
|
+
|
|
557
611
|
/**
|
|
558
612
|
* Sync in-memory content to the LSP client without reading from disk.
|
|
559
613
|
* Use this to provide instant feedback during edits before the file is saved.
|
package/src/lsp/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
setIdleTimeout,
|
|
21
21
|
syncContent,
|
|
22
22
|
WARMUP_TIMEOUT_MS,
|
|
23
|
+
waitForProjectLoaded,
|
|
23
24
|
} from "./client";
|
|
24
25
|
import { getLinterClient } from "./clients";
|
|
25
26
|
import { getServersForFile, type LspConfig, loadConfig } from "./config";
|
|
@@ -1427,6 +1428,13 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1427
1428
|
|
|
1428
1429
|
let output: string;
|
|
1429
1430
|
|
|
1431
|
+
// Wait for project loading to complete before cross-file operations
|
|
1432
|
+
// to ensure the server has indexed all project files.
|
|
1433
|
+
const crossFileActions = new Set(["definition", "type_definition", "implementation", "references", "rename"]);
|
|
1434
|
+
if (crossFileActions.has(action)) {
|
|
1435
|
+
await waitForProjectLoaded(client, signal);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1430
1438
|
switch (action) {
|
|
1431
1439
|
// =====================================================================
|
|
1432
1440
|
// Standard LSP Operations
|
package/src/lsp/types.ts
CHANGED
|
@@ -411,6 +411,12 @@ export interface LspClient {
|
|
|
411
411
|
isReading: boolean;
|
|
412
412
|
serverCapabilities?: LspServerCapabilities;
|
|
413
413
|
lastActivity: number;
|
|
414
|
+
/** Tracks active work-done progress tokens from the server */
|
|
415
|
+
activeProgressTokens: Set<string | number>;
|
|
416
|
+
/** Resolves when the server's initial project loading completes (or after timeout) */
|
|
417
|
+
projectLoaded: Promise<void>;
|
|
418
|
+
/** Call to signal that project loading has completed */
|
|
419
|
+
resolveProjectLoaded: () => void;
|
|
414
420
|
}
|
|
415
421
|
|
|
416
422
|
// =============================================================================
|
package/src/main.ts
CHANGED
|
@@ -79,6 +79,8 @@ const RPC_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
|
79
79
|
"todo.eager",
|
|
80
80
|
"async.enabled",
|
|
81
81
|
"async.maxJobs",
|
|
82
|
+
"bash.autoBackground.enabled",
|
|
83
|
+
"bash.autoBackground.thresholdMs",
|
|
82
84
|
"task.isolation.mode",
|
|
83
85
|
"task.isolation.merge",
|
|
84
86
|
"task.isolation.commits",
|
|
@@ -453,7 +455,9 @@ async function buildSessionOptions(
|
|
|
453
455
|
}
|
|
454
456
|
} else if (resolved.model) {
|
|
455
457
|
options.model = resolved.model;
|
|
456
|
-
settings.overrideModelRoles({
|
|
458
|
+
settings.overrideModelRoles({
|
|
459
|
+
default: resolved.selector ?? `${resolved.model.provider}/${resolved.model.id}`,
|
|
460
|
+
});
|
|
457
461
|
if (!parsed.thinking && resolved.thinkingLevel) {
|
|
458
462
|
options.thinkingLevel = resolved.thinkingLevel;
|
|
459
463
|
}
|
|
@@ -467,6 +471,7 @@ async function buildSessionOptions(
|
|
|
467
471
|
{
|
|
468
472
|
settings,
|
|
469
473
|
matchPreferences: modelMatchPreferences,
|
|
474
|
+
modelRegistry,
|
|
470
475
|
},
|
|
471
476
|
);
|
|
472
477
|
const rememberedResolvedModel = rememberedSpec.model;
|
|
@@ -834,7 +839,6 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
834
839
|
settings: nextSettings,
|
|
835
840
|
authStorage,
|
|
836
841
|
modelRegistry,
|
|
837
|
-
searchDb: session.searchDb,
|
|
838
842
|
hasUI: false,
|
|
839
843
|
});
|
|
840
844
|
if (nextSession.extensionRunner) {
|
package/src/memories/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
|
6
6
|
import { completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import { getAgentDbPath, getMemoriesDir, logger, parseJsonlLenient, prompt } from "@oh-my-pi/pi-utils";
|
|
8
8
|
import type { ModelRegistry } from "../config/model-registry";
|
|
9
|
-
import {
|
|
9
|
+
import { resolveModelRoleValue } from "../config/model-resolver";
|
|
10
10
|
import type { Settings } from "../config/settings";
|
|
11
11
|
import consolidationTemplate from "../prompts/memories/consolidation.md" with { type: "text" };
|
|
12
12
|
import readPathTemplate from "../prompts/memories/read-path.md" with { type: "text" };
|
|
@@ -1055,11 +1055,12 @@ async function resolveMemoryModel(options: {
|
|
|
1055
1055
|
const { modelRegistry, session, fallbackRole } = options;
|
|
1056
1056
|
const requestedModel = session.settings.getModelRole(fallbackRole) || session.settings.getModelRole("default");
|
|
1057
1057
|
if (requestedModel) {
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
}
|
|
1058
|
+
const resolved = resolveModelRoleValue(requestedModel, modelRegistry.getAll(), {
|
|
1059
|
+
settings: session.settings,
|
|
1060
|
+
matchPreferences: { usageOrder: session.settings.getStorage()?.getModelUsageOrder() },
|
|
1061
|
+
modelRegistry,
|
|
1062
|
+
});
|
|
1063
|
+
if (resolved.model) return resolved.model;
|
|
1063
1064
|
}
|
|
1064
1065
|
return session.model ?? modelRegistry.getAll()[0];
|
|
1065
1066
|
}
|
|
@@ -1148,10 +1148,13 @@ export class AcpAgent implements Agent {
|
|
|
1148
1148
|
},
|
|
1149
1149
|
getThinkingLevel: () => record.session.thinkingLevel,
|
|
1150
1150
|
setThinkingLevel: level => record.session.setThinkingLevel(level),
|
|
1151
|
+
getSessionName: () => record.session.sessionManager.getSessionName(),
|
|
1152
|
+
setSessionName: async name => {
|
|
1153
|
+
await record.session.sessionManager.setSessionName(name, "user");
|
|
1154
|
+
},
|
|
1151
1155
|
},
|
|
1152
1156
|
{
|
|
1153
1157
|
getModel: () => record.session.model,
|
|
1154
|
-
getSearchDb: () => record.session.searchDb,
|
|
1155
1158
|
isIdle: () => !record.session.isStreaming,
|
|
1156
1159
|
abort: () => {
|
|
1157
1160
|
void record.session.abort();
|
|
@@ -3,7 +3,18 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Container,
|
|
8
|
+
Ellipsis,
|
|
9
|
+
ImageProtocol,
|
|
10
|
+
Loader,
|
|
11
|
+
Spacer,
|
|
12
|
+
TERMINAL,
|
|
13
|
+
Text,
|
|
14
|
+
type TUI,
|
|
15
|
+
truncateToWidth,
|
|
16
|
+
visibleWidth,
|
|
17
|
+
} from "@oh-my-pi/pi-tui";
|
|
7
18
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
8
19
|
import { formatTruncationMetaNotice, type TruncationMeta } from "../../tools/output-meta";
|
|
9
20
|
import { getSixelLineMask, isSixelPassthroughEnabled, sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel";
|
|
@@ -210,11 +221,12 @@ export class BashExecutionComponent extends Container {
|
|
|
210
221
|
}
|
|
211
222
|
|
|
212
223
|
#clampDisplayLine(line: string): string {
|
|
213
|
-
|
|
224
|
+
const visible = visibleWidth(line);
|
|
225
|
+
if (visible <= MAX_DISPLAY_LINE_CHARS) {
|
|
214
226
|
return line;
|
|
215
227
|
}
|
|
216
|
-
const omitted =
|
|
217
|
-
return `${line
|
|
228
|
+
const omitted = visible - MAX_DISPLAY_LINE_CHARS;
|
|
229
|
+
return `${truncateToWidth(line, MAX_DISPLAY_LINE_CHARS, Ellipsis.Omit)}… [${omitted} visible columns omitted]`;
|
|
218
230
|
}
|
|
219
231
|
|
|
220
232
|
#clampLinesPreservingSixel(lines: string[]): string[] {
|