@oh-my-pi/pi-coding-agent 14.1.1 → 14.2.0
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 +47 -2
- package/package.json +8 -8
- package/scripts/build-binary.ts +61 -0
- package/src/autoresearch/helpers.ts +10 -0
- package/src/autoresearch/index.ts +1 -11
- package/src/autoresearch/tools/init-experiment.ts +1 -10
- package/src/autoresearch/tools/log-experiment.ts +1 -11
- package/src/autoresearch/tools/run-experiment.ts +1 -10
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/plugin-cli.ts +23 -45
- package/src/commit/agentic/tools/propose-commit.ts +1 -14
- package/src/commit/agentic/tools/split-commit.ts +1 -15
- package/src/commit/utils.ts +15 -1
- package/src/config/model-registry.ts +3 -3
- package/src/config/prompt-templates.ts +4 -12
- package/src/config/settings-schema.ts +27 -2
- package/src/config/settings.ts +1 -1
- package/src/discovery/claude-plugins.ts +61 -6
- package/src/discovery/codex.ts +2 -15
- package/src/discovery/gemini.ts +2 -15
- package/src/discovery/helpers.ts +40 -1
- package/src/discovery/opencode.ts +2 -15
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +3 -14
- package/src/edit/index.ts +65 -2
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +63 -0
- package/src/edit/modes/hashline.ts +3 -3
- package/src/edit/modes/replace.ts +2 -13
- package/src/edit/read-file.ts +18 -0
- package/src/edit/renderer.ts +61 -33
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/runner.ts +11 -29
- package/src/extensibility/utils.ts +7 -1
- package/src/internal-urls/docs-index.generated.ts +9 -2
- package/src/lsp/render.ts +14 -2
- package/src/main.ts +1 -0
- package/src/mcp/manager.ts +29 -48
- package/src/memories/index.ts +7 -1
- package/src/modes/acp/acp-agent.ts +3 -16
- package/src/modes/components/model-selector.ts +15 -24
- package/src/modes/components/plugin-settings.ts +16 -5
- package/src/modes/components/read-tool-group.ts +92 -9
- package/src/modes/components/settings-defs.ts +18 -0
- package/src/modes/components/settings-selector.ts +2 -6
- package/src/modes/components/tool-execution.ts +61 -28
- package/src/modes/controllers/event-controller.ts +3 -1
- package/src/modes/controllers/extension-ui-controller.ts +99 -150
- package/src/modes/controllers/selector-controller.ts +3 -12
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/print-mode.ts +4 -22
- package/src/modes/rpc/rpc-mode.ts +18 -38
- package/src/modes/shared.ts +10 -1
- package/src/modes/utils/ui-helpers.ts +6 -2
- package/src/plan-mode/approved-plan.ts +5 -4
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/subagent-user-prompt.md +2 -2
- package/src/prompts/system/system-prompt.md +208 -243
- package/src/prompts/tools/apply-patch.md +67 -0
- package/src/prompts/tools/ast-edit.md +18 -23
- package/src/prompts/tools/ast-grep.md +24 -32
- package/src/prompts/tools/bash.md +11 -23
- package/src/prompts/tools/debug.md +8 -22
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/grep.md +3 -5
- package/src/prompts/tools/hashline.md +16 -10
- package/src/prompts/tools/python.md +10 -14
- package/src/prompts/tools/read.md +17 -24
- package/src/prompts/tools/task.md +57 -21
- package/src/prompts/tools/todo-write.md +45 -67
- package/src/session/agent-session.ts +4 -4
- package/src/session/session-manager.ts +15 -7
- package/src/session/streaming-output.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +3 -14
- package/src/task/executor.ts +13 -34
- package/src/task/index.ts +82 -18
- package/src/task/simple-mode.ts +27 -0
- package/src/task/template.ts +17 -3
- package/src/task/types.ts +77 -30
- package/src/tools/ask.ts +2 -4
- package/src/tools/ast-edit.ts +4 -15
- package/src/tools/ast-grep.ts +8 -27
- package/src/tools/bash-skill-urls.ts +9 -7
- package/src/tools/bash.ts +4 -12
- package/src/tools/browser.ts +1 -1
- package/src/tools/fetch.ts +1 -14
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/find.ts +6 -3
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +1 -8
- package/src/tools/gh.ts +6 -13
- package/src/tools/grep.ts +9 -22
- package/src/tools/jtd-to-json-schema.ts +16 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/path-utils.ts +30 -2
- package/src/tools/plan-mode-guard.ts +6 -5
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/tools/render-utils.ts +38 -6
- package/src/tools/renderers.ts +1 -0
- package/src/tools/ssh.ts +3 -11
- package/src/tools/submit-result.ts +1 -13
- package/src/tools/todo-write.ts +137 -103
- package/src/tools/write.ts +2 -23
- package/src/tui/code-cell.ts +12 -7
- package/src/utils/edit-mode.ts +3 -2
- package/src/utils/git.ts +1 -1
- package/src/vim/engine.ts +41 -58
- package/src/web/scrapers/crates-io.ts +1 -14
- package/src/web/scrapers/types.ts +13 -0
- package/src/web/search/providers/base.ts +13 -0
- package/src/web/search/providers/brave.ts +2 -5
- package/src/web/search/providers/codex.ts +20 -24
- package/src/web/search/providers/gemini.ts +39 -1
- package/src/web/search/providers/jina.ts +2 -5
- package/src/web/search/providers/kagi.ts +3 -8
- package/src/web/search/providers/kimi.ts +3 -7
- package/src/web/search/providers/parallel.ts +3 -8
- package/src/web/search/providers/synthetic.ts +3 -7
- package/src/web/search/providers/tavily.ts +15 -11
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +3 -7
package/src/lsp/render.ts
CHANGED
|
@@ -335,7 +335,9 @@ function renderDiagnostics(
|
|
|
335
335
|
const parsedDiagnostics = diagLines
|
|
336
336
|
.map(line => parseDiagnosticLine(line))
|
|
337
337
|
.filter((diag): diag is ParsedDiagnostic => diag !== null);
|
|
338
|
-
const fallbackDiagnostics: RawDiagnostic[] = diagLines.map(line => ({
|
|
338
|
+
const fallbackDiagnostics: RawDiagnostic[] = diagLines.map(line => ({
|
|
339
|
+
raw: sanitizeDiagnosticDisplayText(line.trim()),
|
|
340
|
+
}));
|
|
339
341
|
|
|
340
342
|
if (expanded) {
|
|
341
343
|
let output = `${icon} ${theme.fg("dim", meta.join(theme.sep.dot))}`;
|
|
@@ -651,11 +653,21 @@ interface RawDiagnostic {
|
|
|
651
653
|
|
|
652
654
|
type DiagnosticItem = ParsedDiagnostic | RawDiagnostic;
|
|
653
655
|
|
|
656
|
+
function sanitizeDiagnosticDisplayText(text: string): string {
|
|
657
|
+
return replaceTabs(text);
|
|
658
|
+
}
|
|
659
|
+
|
|
654
660
|
function parseDiagnosticLine(line: string): ParsedDiagnostic | null {
|
|
655
661
|
const match = line.trim().match(/^(.*):(\d+):(\d+)\s+\[(\w+)\]\s*(.*)$/);
|
|
656
662
|
if (!match) return null;
|
|
657
663
|
const [, file, lineNum, colNum, severity, message] = match;
|
|
658
|
-
return {
|
|
664
|
+
return {
|
|
665
|
+
file: sanitizeDiagnosticDisplayText(file),
|
|
666
|
+
line: lineNum,
|
|
667
|
+
col: colNum,
|
|
668
|
+
severity: severity.toLowerCase(),
|
|
669
|
+
message: sanitizeDiagnosticDisplayText(message),
|
|
670
|
+
};
|
|
659
671
|
}
|
|
660
672
|
|
|
661
673
|
function severityToColor(severity: string): "error" | "warning" | "accent" | "dim" {
|
package/src/main.ts
CHANGED
package/src/mcp/manager.ts
CHANGED
|
@@ -176,6 +176,33 @@ export class MCPManager {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
#subscribeAndTrack(name: string, connection: MCPServerConnection, uris: string[], notificationEpoch: number): void {
|
|
180
|
+
void subscribeToResources(connection, uris)
|
|
181
|
+
.then(() => {
|
|
182
|
+
const action = resolveSubscriptionPostAction(
|
|
183
|
+
this.#notificationsEnabled,
|
|
184
|
+
this.#notificationsEpoch,
|
|
185
|
+
notificationEpoch,
|
|
186
|
+
);
|
|
187
|
+
if (action === "rollback") {
|
|
188
|
+
void unsubscribeFromResources(connection, uris).catch(error => {
|
|
189
|
+
logger.debug("Failed to rollback stale MCP resource subscription", {
|
|
190
|
+
path: `mcp:${name}`,
|
|
191
|
+
error,
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (action === "ignore") {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.#subscribedResources.set(name, new Set(uris));
|
|
200
|
+
})
|
|
201
|
+
.catch(error => {
|
|
202
|
+
logger.debug("Failed to subscribe to MCP resources", { path: `mcp:${name}`, error });
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
179
206
|
setNotificationsEnabled(enabled: boolean): void {
|
|
180
207
|
const wasEnabled = this.#notificationsEnabled;
|
|
181
208
|
this.#notificationsEnabled = enabled;
|
|
@@ -189,30 +216,7 @@ export class MCPManager {
|
|
|
189
216
|
for (const [name, connection] of this.#connections) {
|
|
190
217
|
if (connection.capabilities.resources?.subscribe && connection.resources) {
|
|
191
218
|
const uris = connection.resources.map(r => r.uri);
|
|
192
|
-
|
|
193
|
-
.then(() => {
|
|
194
|
-
const action = resolveSubscriptionPostAction(
|
|
195
|
-
this.#notificationsEnabled,
|
|
196
|
-
this.#notificationsEpoch,
|
|
197
|
-
notificationEpoch,
|
|
198
|
-
);
|
|
199
|
-
if (action === "rollback") {
|
|
200
|
-
void unsubscribeFromResources(connection, uris).catch(error => {
|
|
201
|
-
logger.debug("Failed to rollback stale MCP resource subscription", {
|
|
202
|
-
path: `mcp:${name}`,
|
|
203
|
-
error,
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (action === "ignore") {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
this.#subscribedResources.set(name, new Set(uris));
|
|
212
|
-
})
|
|
213
|
-
.catch(error => {
|
|
214
|
-
logger.debug("Failed to subscribe to MCP resources", { path: `mcp:${name}`, error });
|
|
215
|
-
});
|
|
219
|
+
this.#subscribeAndTrack(name, connection, uris, notificationEpoch);
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
return;
|
|
@@ -830,30 +834,7 @@ export class MCPManager {
|
|
|
830
834
|
if (this.#notificationsEnabled && connection.capabilities.resources?.subscribe) {
|
|
831
835
|
const uris = resources.map(r => r.uri);
|
|
832
836
|
const notificationEpoch = this.#notificationsEpoch;
|
|
833
|
-
|
|
834
|
-
.then(() => {
|
|
835
|
-
const action = resolveSubscriptionPostAction(
|
|
836
|
-
this.#notificationsEnabled,
|
|
837
|
-
this.#notificationsEpoch,
|
|
838
|
-
notificationEpoch,
|
|
839
|
-
);
|
|
840
|
-
if (action === "rollback") {
|
|
841
|
-
void unsubscribeFromResources(connection, uris).catch(error => {
|
|
842
|
-
logger.debug("Failed to rollback stale MCP resource subscription", {
|
|
843
|
-
path: `mcp:${name}`,
|
|
844
|
-
error,
|
|
845
|
-
});
|
|
846
|
-
});
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
if (action === "ignore") {
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
this.#subscribedResources.set(name, new Set(uris));
|
|
853
|
-
})
|
|
854
|
-
.catch(error => {
|
|
855
|
-
logger.debug("Failed to subscribe to MCP resources", { path: `mcp:${name}`, error });
|
|
856
|
-
});
|
|
837
|
+
this.#subscribeAndTrack(name, connection, uris, notificationEpoch);
|
|
857
838
|
}
|
|
858
839
|
} catch (error) {
|
|
859
840
|
logger.debug("Failed to load MCP resources", { path: `mcp:${name}`, error });
|
package/src/memories/index.ts
CHANGED
|
@@ -48,6 +48,7 @@ interface MemoryRuntimeConfig {
|
|
|
48
48
|
phase2RetryDelaySeconds: number;
|
|
49
49
|
phase2HeartbeatSeconds: number;
|
|
50
50
|
rolloutPayloadPercent: number;
|
|
51
|
+
phase1InputTokenLimit: number;
|
|
51
52
|
fallbackTokenLimit: number;
|
|
52
53
|
summaryInjectionTokenLimit: number;
|
|
53
54
|
}
|
|
@@ -66,6 +67,7 @@ const DEFAULTS: MemoryRuntimeConfig = {
|
|
|
66
67
|
phase2RetryDelaySeconds: 180,
|
|
67
68
|
phase2HeartbeatSeconds: 30,
|
|
68
69
|
rolloutPayloadPercent: 0.7,
|
|
70
|
+
phase1InputTokenLimit: 4_000,
|
|
69
71
|
fallbackTokenLimit: 16_000,
|
|
70
72
|
summaryInjectionTokenLimit: 5_000,
|
|
71
73
|
};
|
|
@@ -582,7 +584,10 @@ async function runStage1Job(options: {
|
|
|
582
584
|
const rolloutRaw = await Bun.file(claim.rolloutPath).text();
|
|
583
585
|
const persisted = extractPersistableMessages(rolloutRaw);
|
|
584
586
|
const serializedItems = JSON.stringify(persisted);
|
|
585
|
-
const budgetTokens = Math.
|
|
587
|
+
const budgetTokens = Math.min(
|
|
588
|
+
config.phase1InputTokenLimit,
|
|
589
|
+
Math.floor(modelMaxTokens * config.rolloutPayloadPercent),
|
|
590
|
+
);
|
|
586
591
|
const truncatedItems = truncateByApproxTokens(serializedItems, budgetTokens);
|
|
587
592
|
const inputPrompt = prompt.render(stageOneInputTemplate, {
|
|
588
593
|
thread_id: claim.threadId,
|
|
@@ -1080,6 +1085,7 @@ function loadMemoryConfig(settings: Settings): MemoryRuntimeConfig {
|
|
|
1080
1085
|
phase2RetryDelaySeconds: settings.get("memories.phase2RetryDelaySeconds") ?? DEFAULTS.phase2RetryDelaySeconds,
|
|
1081
1086
|
phase2HeartbeatSeconds: settings.get("memories.phase2HeartbeatSeconds") ?? DEFAULTS.phase2HeartbeatSeconds,
|
|
1082
1087
|
rolloutPayloadPercent: settings.get("memories.rolloutPayloadPercent") ?? DEFAULTS.rolloutPayloadPercent,
|
|
1088
|
+
phase1InputTokenLimit: settings.get("memories.phase1InputTokenLimit") ?? DEFAULTS.phase1InputTokenLimit,
|
|
1083
1089
|
fallbackTokenLimit: settings.get("memories.fallbackTokenLimit") ?? DEFAULTS.fallbackTokenLimit,
|
|
1084
1090
|
summaryInjectionTokenLimit:
|
|
1085
1091
|
settings.get("memories.summaryInjectionTokenLimit") ?? DEFAULTS.summaryInjectionTokenLimit,
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
41
41
|
import { logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
42
42
|
import type { ExtensionUIContext } from "../../extensibility/extensions";
|
|
43
|
+
import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
|
|
43
44
|
import { loadSlashCommands } from "../../extensibility/slash-commands";
|
|
44
45
|
import { MCPManager } from "../../mcp/manager";
|
|
45
46
|
import type { MCPServerConfig } from "../../mcp/types";
|
|
@@ -1163,14 +1164,7 @@ export class AcpAgent implements Agent {
|
|
|
1163
1164
|
shutdown: () => {},
|
|
1164
1165
|
getContextUsage: () => record.session.getContextUsage(),
|
|
1165
1166
|
getSystemPrompt: () => record.session.systemPrompt,
|
|
1166
|
-
compact:
|
|
1167
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
1168
|
-
const options =
|
|
1169
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
1170
|
-
? instructionsOrOptions
|
|
1171
|
-
: undefined;
|
|
1172
|
-
await record.session.compact(instructions, options);
|
|
1173
|
-
},
|
|
1167
|
+
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
1174
1168
|
},
|
|
1175
1169
|
{
|
|
1176
1170
|
getContextUsage: () => record.session.getContextUsage(),
|
|
@@ -1197,14 +1191,7 @@ export class AcpAgent implements Agent {
|
|
|
1197
1191
|
reload: async () => {
|
|
1198
1192
|
await record.session.reload();
|
|
1199
1193
|
},
|
|
1200
|
-
compact:
|
|
1201
|
-
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
1202
|
-
const options =
|
|
1203
|
-
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
1204
|
-
? instructionsOrOptions
|
|
1205
|
-
: undefined;
|
|
1206
|
-
await record.session.compact(instructions, options);
|
|
1207
|
-
},
|
|
1194
|
+
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
1208
1195
|
},
|
|
1209
1196
|
acpExtensionUiContext,
|
|
1210
1197
|
);
|
|
@@ -43,6 +43,19 @@ function getAlphaSearchTokens(query: string): string[] {
|
|
|
43
43
|
return [...normalizeSearchText(query).matchAll(/[a-z]+/g)].map(match => match[0]).filter(token => token.length > 0);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function computeModelRank(model: Model, roles: Record<string, RoleAssignment | undefined>): number {
|
|
47
|
+
let i = 0;
|
|
48
|
+
while (i < MODEL_ROLE_IDS.length) {
|
|
49
|
+
const role = MODEL_ROLE_IDS[i];
|
|
50
|
+
const assigned = roles[role];
|
|
51
|
+
if (assigned && modelsAreEqual(assigned.model, model)) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
return i;
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
interface ModelItem {
|
|
47
60
|
kind: "provider";
|
|
48
61
|
provider: string;
|
|
@@ -252,18 +265,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
252
265
|
const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
253
266
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
254
267
|
|
|
255
|
-
const modelRank = (
|
|
256
|
-
let i = 0;
|
|
257
|
-
while (i < MODEL_ROLE_IDS.length) {
|
|
258
|
-
const role = MODEL_ROLE_IDS[i];
|
|
259
|
-
const assigned = this.#roles[role];
|
|
260
|
-
if (assigned && modelsAreEqual(assigned.model, model.model)) {
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
|
-
i++;
|
|
264
|
-
}
|
|
265
|
-
return i;
|
|
266
|
-
};
|
|
268
|
+
const modelRank = (item: ModelItem) => computeModelRank(item.model, this.#roles);
|
|
267
269
|
|
|
268
270
|
const dateRe = /-(\d{8})$/;
|
|
269
271
|
const latestRe = /-latest$/;
|
|
@@ -325,18 +327,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
325
327
|
const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
326
328
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
327
329
|
|
|
328
|
-
const modelRank = (
|
|
329
|
-
let i = 0;
|
|
330
|
-
while (i < MODEL_ROLE_IDS.length) {
|
|
331
|
-
const role = MODEL_ROLE_IDS[i];
|
|
332
|
-
const assigned = this.#roles[role];
|
|
333
|
-
if (assigned && modelsAreEqual(assigned.model, model.model)) {
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
i++;
|
|
337
|
-
}
|
|
338
|
-
return i;
|
|
339
|
-
};
|
|
330
|
+
const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model, this.#roles);
|
|
340
331
|
|
|
341
332
|
models.sort((a, b) => {
|
|
342
333
|
const aRank = modelRank(a);
|
|
@@ -22,6 +22,21 @@ import type { InstalledPlugin, PluginSettingSchema } from "../../extensibility/p
|
|
|
22
22
|
import { getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
|
|
23
23
|
import { DynamicBorder } from "./dynamic-border";
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Forwards a keystroke to `input`, but cancels via `onCancel` when the user presses Escape.
|
|
27
|
+
*/
|
|
28
|
+
export function handleInputOrEscape(
|
|
29
|
+
data: string,
|
|
30
|
+
input: { handleInput(data: string): void },
|
|
31
|
+
onCancel: () => void,
|
|
32
|
+
): void {
|
|
33
|
+
if (data === "\x1b" || data === "\x1b\x1b") {
|
|
34
|
+
onCancel();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
input.handleInput(data);
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
// =============================================================================
|
|
26
41
|
// Plugin List Component
|
|
27
42
|
// =============================================================================
|
|
@@ -383,11 +398,7 @@ class ConfigInputSubmenu extends Container {
|
|
|
383
398
|
}
|
|
384
399
|
|
|
385
400
|
handleInput(data: string): void {
|
|
386
|
-
|
|
387
|
-
this.onCancel();
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
this.#input.handleInput(data);
|
|
401
|
+
handleInputOrEscape(data, this.#input, this.onCancel);
|
|
391
402
|
}
|
|
392
403
|
}
|
|
393
404
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import { theme } from "../../modes/theme/theme";
|
|
4
|
-
import { shortenPath } from "../../tools/render-utils";
|
|
3
|
+
import { getLanguageFromPath, theme } from "../../modes/theme/theme";
|
|
4
|
+
import { PREVIEW_LIMITS, shortenPath } from "../../tools/render-utils";
|
|
5
|
+
import { renderCodeCell } from "../../tui";
|
|
5
6
|
import type { ToolExecutionHandle } from "./tool-execution";
|
|
6
7
|
|
|
7
8
|
type ReadRenderArgs = {
|
|
@@ -22,6 +23,10 @@ type ReadToolResultDetails = {
|
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
type ReadToolGroupOptions = {
|
|
27
|
+
showContentPreview?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
25
30
|
function getSuffixResolution(details: ReadToolResultDetails | undefined): ReadToolSuffixResolution | undefined {
|
|
26
31
|
if (typeof details?.suffixResolution?.from !== "string" || typeof details.suffixResolution.to !== "string") {
|
|
27
32
|
return undefined;
|
|
@@ -35,14 +40,21 @@ type ReadEntry = {
|
|
|
35
40
|
sel?: string;
|
|
36
41
|
status: "pending" | "success" | "warning" | "error";
|
|
37
42
|
correctedFrom?: string;
|
|
43
|
+
contentText?: string;
|
|
38
44
|
};
|
|
39
45
|
|
|
46
|
+
/** Number of code lines to show in collapsed preview mode */
|
|
47
|
+
const COLLAPSED_PREVIEW_LINES = PREVIEW_LIMITS.OUTPUT_COLLAPSED;
|
|
48
|
+
|
|
40
49
|
export class ReadToolGroupComponent extends Container implements ToolExecutionHandle {
|
|
41
50
|
#entries = new Map<string, ReadEntry>();
|
|
42
51
|
#text: Text;
|
|
52
|
+
#expanded = false;
|
|
53
|
+
#showContentPreview: boolean;
|
|
43
54
|
|
|
44
|
-
constructor() {
|
|
55
|
+
constructor(options: ReadToolGroupOptions = {}) {
|
|
45
56
|
super();
|
|
57
|
+
this.#showContentPreview = options.showContentPreview ?? false;
|
|
46
58
|
this.#text = new Text("", 0, 0);
|
|
47
59
|
this.addChild(this.#text);
|
|
48
60
|
this.#updateDisplay();
|
|
@@ -81,6 +93,11 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
81
93
|
entry.correctedFrom = undefined;
|
|
82
94
|
}
|
|
83
95
|
entry.status = result.isError ? "error" : suffixResolution ? "warning" : "success";
|
|
96
|
+
// Store the text content for preview/expanded display
|
|
97
|
+
const textContent = result.content?.find(c => c.type === "text")?.text;
|
|
98
|
+
if (textContent !== undefined) {
|
|
99
|
+
entry.contentText = textContent;
|
|
100
|
+
}
|
|
84
101
|
this.#updateDisplay();
|
|
85
102
|
}
|
|
86
103
|
|
|
@@ -88,7 +105,8 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
88
105
|
this.#updateDisplay();
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
setExpanded(
|
|
108
|
+
setExpanded(expanded: boolean): void {
|
|
109
|
+
this.#expanded = expanded;
|
|
92
110
|
this.#updateDisplay();
|
|
93
111
|
}
|
|
94
112
|
|
|
@@ -99,23 +117,37 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
99
117
|
#updateDisplay(): void {
|
|
100
118
|
const entries = [...this.#entries.values()];
|
|
101
119
|
|
|
120
|
+
// Clear previous children and rebuild the summary and preview blocks.
|
|
121
|
+
this.clear();
|
|
122
|
+
this.#text = new Text("", 0, 0);
|
|
123
|
+
|
|
102
124
|
if (entries.length === 0) {
|
|
103
125
|
this.#text.setText(` ${theme.format.bullet} ${theme.fg("toolTitle", theme.bold("Read"))}`);
|
|
126
|
+
this.addChild(this.#text);
|
|
104
127
|
return;
|
|
105
128
|
}
|
|
106
129
|
|
|
107
130
|
if (entries.length === 1) {
|
|
108
131
|
const entry = entries[0];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
if (!this.#shouldRenderPreview(entry)) {
|
|
133
|
+
const statusSymbol = this.#formatStatus(entry.status);
|
|
134
|
+
const pathDisplay = this.#formatPath(entry);
|
|
135
|
+
this.#text.setText(
|
|
136
|
+
` ${statusSymbol} ${theme.fg("toolTitle", theme.bold("Read"))} ${pathDisplay}`.trimEnd(),
|
|
137
|
+
);
|
|
138
|
+
this.addChild(this.#text);
|
|
139
|
+
}
|
|
140
|
+
if (this.#shouldRenderPreview(entry)) {
|
|
141
|
+
this.#addContentPreview(entry);
|
|
142
|
+
}
|
|
112
143
|
return;
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
const header = `${theme.fg("toolTitle", theme.bold("Read"))}${theme.fg("dim", ` (${entries.length})`)}`;
|
|
116
147
|
const lines = [` ${theme.format.bullet} ${header}`];
|
|
117
|
-
const
|
|
118
|
-
|
|
148
|
+
const entriesWithoutPreview = entries.filter(entry => !this.#shouldRenderPreview(entry));
|
|
149
|
+
const total = entriesWithoutPreview.length;
|
|
150
|
+
for (const [index, entry] of entriesWithoutPreview.entries()) {
|
|
119
151
|
const connector = index === total - 1 ? theme.tree.last : theme.tree.branch;
|
|
120
152
|
const statusSymbol = this.#formatStatus(entry.status);
|
|
121
153
|
const pathDisplay = this.#formatPath(entry);
|
|
@@ -123,6 +155,57 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
123
155
|
}
|
|
124
156
|
|
|
125
157
|
this.#text.setText(lines.join("\n"));
|
|
158
|
+
this.addChild(this.#text);
|
|
159
|
+
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
if (this.#shouldRenderPreview(entry)) {
|
|
162
|
+
this.#addContentPreview(entry);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Add a code-cell content preview below the entry summary.
|
|
169
|
+
* When collapsed: shows first COLLAPSED_PREVIEW_LINES lines with "… N more lines (Ctrl+O for more)" hint.
|
|
170
|
+
* When expanded: shows full content.
|
|
171
|
+
*/
|
|
172
|
+
#addContentPreview(entry: ReadEntry): void {
|
|
173
|
+
const lang = getLanguageFromPath(entry.path);
|
|
174
|
+
const filePath = shortenPath(entry.path);
|
|
175
|
+
const selectionSuffix = entry.sel ? `:${entry.sel}` : "";
|
|
176
|
+
const correctionSuffix = entry.correctedFrom ? ` (corrected from ${shortenPath(entry.correctedFrom)})` : "";
|
|
177
|
+
const title = filePath ? `Read ${filePath}${selectionSuffix}${correctionSuffix}` : `Read${selectionSuffix}`;
|
|
178
|
+
let cachedWidth: number | undefined;
|
|
179
|
+
let cachedLines: string[] | undefined;
|
|
180
|
+
const expanded = this.#expanded;
|
|
181
|
+
const component: Component = {
|
|
182
|
+
render: (width: number) => {
|
|
183
|
+
if (cachedLines && cachedWidth === width) return cachedLines;
|
|
184
|
+
cachedLines = renderCodeCell(
|
|
185
|
+
{
|
|
186
|
+
code: entry.contentText ?? "",
|
|
187
|
+
language: lang,
|
|
188
|
+
title,
|
|
189
|
+
status: entry.status === "success" ? "complete" : entry.status,
|
|
190
|
+
expanded,
|
|
191
|
+
codeMaxLines: expanded ? undefined : COLLAPSED_PREVIEW_LINES,
|
|
192
|
+
width,
|
|
193
|
+
},
|
|
194
|
+
theme,
|
|
195
|
+
);
|
|
196
|
+
cachedWidth = width;
|
|
197
|
+
return cachedLines;
|
|
198
|
+
},
|
|
199
|
+
invalidate: () => {
|
|
200
|
+
cachedWidth = undefined;
|
|
201
|
+
cachedLines = undefined;
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
this.addChild(component);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#shouldRenderPreview(entry: ReadEntry): boolean {
|
|
208
|
+
return this.#showContentPreview && entry.contentText !== undefined;
|
|
126
209
|
}
|
|
127
210
|
|
|
128
211
|
#formatPath(entry: ReadEntry): string {
|
|
@@ -145,6 +145,24 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
145
145
|
},
|
|
146
146
|
{ value: "never", label: "Never", description: "Stay on the fallback model until manually changed" },
|
|
147
147
|
],
|
|
148
|
+
// Task input mode
|
|
149
|
+
"task.simple": [
|
|
150
|
+
{
|
|
151
|
+
value: "default",
|
|
152
|
+
label: "Default",
|
|
153
|
+
description: "Shared context and custom task schema are available",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
value: "schema-free",
|
|
157
|
+
label: "Schema-free",
|
|
158
|
+
description: "Shared context stays available, but custom task schema is disabled",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
value: "independent",
|
|
162
|
+
label: "Independent",
|
|
163
|
+
description: "No shared context or custom task schema; each task must stand alone",
|
|
164
|
+
},
|
|
165
|
+
],
|
|
148
166
|
// Task max concurrency
|
|
149
167
|
"task.maxConcurrency": [
|
|
150
168
|
{ value: "0", label: "Unlimited" },
|
|
@@ -25,7 +25,7 @@ import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme }
|
|
|
25
25
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
26
26
|
import { getTabBarTheme } from "../shared";
|
|
27
27
|
import { DynamicBorder } from "./dynamic-border";
|
|
28
|
-
import { PluginSettingsComponent } from "./plugin-settings";
|
|
28
|
+
import { handleInputOrEscape, PluginSettingsComponent } from "./plugin-settings";
|
|
29
29
|
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
30
30
|
import { getPreset } from "./status-line/presets";
|
|
31
31
|
|
|
@@ -70,11 +70,7 @@ class TextInputSubmenu extends Container {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
handleInput(data: string): void {
|
|
73
|
-
|
|
74
|
-
this.onCancel();
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
this.#input.handleInput(data);
|
|
73
|
+
handleInputOrEscape(data, this.#input, this.onCancel);
|
|
78
74
|
}
|
|
79
75
|
}
|
|
80
76
|
|