@oh-my-pi/pi-coding-agent 14.1.2 → 14.2.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 +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/dap/session.ts +8 -2
- 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 +67 -3
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +63 -0
- package/src/edit/modes/chunk.ts +6 -2
- 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/client.ts +14 -5
- package/src/lsp/index.ts +53 -10
- package/src/lsp/render.ts +14 -2
- package/src/lsp/types.ts +2 -0
- 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 +25 -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 +41 -17
- package/src/tools/ast-grep.ts +8 -27
- package/src/tools/bash-skill-urls.ts +9 -7
- package/src/tools/bash.ts +66 -24
- 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 +25 -29
- 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 +103 -59
- 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 +61 -5
- 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/resolve.ts +12 -3
- 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/vim.ts +1 -1
- 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/client.ts
CHANGED
|
@@ -211,11 +211,19 @@ async function writeMessage(
|
|
|
211
211
|
message: LspJsonRpcRequest | LspJsonRpcNotification | LspJsonRpcResponse,
|
|
212
212
|
): Promise<void> {
|
|
213
213
|
const content = JSON.stringify(message);
|
|
214
|
-
sink.write(`Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n`);
|
|
215
|
-
sink.write(content);
|
|
214
|
+
sink.write(`Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n${content}`);
|
|
216
215
|
await sink.flush();
|
|
217
216
|
}
|
|
218
217
|
|
|
218
|
+
function queueWriteMessage(
|
|
219
|
+
client: LspClient,
|
|
220
|
+
message: LspJsonRpcRequest | LspJsonRpcNotification | LspJsonRpcResponse,
|
|
221
|
+
): Promise<void> {
|
|
222
|
+
const write = client.writeQueue.catch(() => {}).then(() => writeMessage(client.proc.stdin, message));
|
|
223
|
+
client.writeQueue = write.catch(() => {});
|
|
224
|
+
return write;
|
|
225
|
+
}
|
|
226
|
+
|
|
219
227
|
// =============================================================================
|
|
220
228
|
// Message Reader
|
|
221
229
|
// =============================================================================
|
|
@@ -382,7 +390,7 @@ async function sendResponse(
|
|
|
382
390
|
};
|
|
383
391
|
|
|
384
392
|
try {
|
|
385
|
-
await
|
|
393
|
+
await queueWriteMessage(client, response);
|
|
386
394
|
} catch (err) {
|
|
387
395
|
logger.error("LSP failed to respond.", { method, error: String(err) });
|
|
388
396
|
}
|
|
@@ -461,6 +469,7 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
461
469
|
messageBuffer: new Uint8Array(0),
|
|
462
470
|
isReading: false,
|
|
463
471
|
lastActivity: Date.now(),
|
|
472
|
+
writeQueue: Promise.resolve(),
|
|
464
473
|
activeProgressTokens: new Set(),
|
|
465
474
|
projectLoaded,
|
|
466
475
|
resolveProjectLoaded,
|
|
@@ -848,7 +857,7 @@ export async function sendRequest(
|
|
|
848
857
|
});
|
|
849
858
|
|
|
850
859
|
// Write request
|
|
851
|
-
|
|
860
|
+
queueWriteMessage(client, request).catch(err => {
|
|
852
861
|
if (timeout) clearTimeout(timeout);
|
|
853
862
|
client.pendingRequests.delete(id);
|
|
854
863
|
cleanup();
|
|
@@ -868,7 +877,7 @@ export async function sendNotification(client: LspClient, method: string, params
|
|
|
868
877
|
};
|
|
869
878
|
|
|
870
879
|
client.lastActivity = Date.now();
|
|
871
|
-
await
|
|
880
|
+
await queueWriteMessage(client, notification);
|
|
872
881
|
}
|
|
873
882
|
|
|
874
883
|
/**
|
package/src/lsp/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
type LspParams,
|
|
41
41
|
type LspToolDetails,
|
|
42
42
|
lspSchema,
|
|
43
|
+
type Position,
|
|
43
44
|
type PublishedDiagnostics,
|
|
44
45
|
type ServerConfig,
|
|
45
46
|
type SymbolInformation,
|
|
@@ -262,6 +263,10 @@ function getLspServerForFile(config: LspConfig, filePath: string): [string, Serv
|
|
|
262
263
|
return servers.length > 0 ? servers[0] : null;
|
|
263
264
|
}
|
|
264
265
|
|
|
266
|
+
function isProjectAwareLspServer(serverConfig: ServerConfig): boolean {
|
|
267
|
+
return !serverConfig.createClient && !serverConfig.isLinter;
|
|
268
|
+
}
|
|
269
|
+
|
|
265
270
|
const DIAGNOSTIC_MESSAGE_LIMIT = 50;
|
|
266
271
|
const SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS = 3000;
|
|
267
272
|
const BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS = 400;
|
|
@@ -278,6 +283,21 @@ function limitDiagnosticMessages(messages: string[]): string[] {
|
|
|
278
283
|
const LOCATION_CONTEXT_LINES = 1;
|
|
279
284
|
const REFERENCE_CONTEXT_LIMIT = 50;
|
|
280
285
|
|
|
286
|
+
const REFERENCES_RETRY_COUNT = 2;
|
|
287
|
+
const REFERENCES_RETRY_DELAY_MS = 250;
|
|
288
|
+
|
|
289
|
+
function comparePosition(a: Position, b: Position): number {
|
|
290
|
+
return a.line === b.line ? a.character - b.character : a.line - b.line;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function rangeContainsPosition(range: Location["range"], position: Position): boolean {
|
|
294
|
+
return comparePosition(range.start, position) <= 0 && comparePosition(position, range.end) <= 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function isOnlyQueriedDeclaration(locations: Location[], uri: string, position: Position): boolean {
|
|
298
|
+
return locations.length === 1 && locations[0]?.uri === uri && rangeContainsPosition(locations[0].range, position);
|
|
299
|
+
}
|
|
300
|
+
|
|
281
301
|
function normalizeLocationResult(result: Location | Location[] | LocationLink | LocationLink[] | null): Location[] {
|
|
282
302
|
if (!result) return [];
|
|
283
303
|
const raw = Array.isArray(result) ? result : [result];
|
|
@@ -560,6 +580,10 @@ async function getDiagnosticsForFile(
|
|
|
560
580
|
// Default: use LSP
|
|
561
581
|
const client = await getOrCreateClient(serverConfig, cwd);
|
|
562
582
|
throwIfAborted(signal);
|
|
583
|
+
if (isProjectAwareLspServer(serverConfig)) {
|
|
584
|
+
await waitForProjectLoaded(client, signal);
|
|
585
|
+
throwIfAborted(signal);
|
|
586
|
+
}
|
|
563
587
|
// Content already synced + didSave sent, wait for fresh diagnostics
|
|
564
588
|
const minVersion = minVersions?.get(serverName);
|
|
565
589
|
const expectedDocumentVersion = expectedDocumentVersions?.get(serverName);
|
|
@@ -1220,6 +1244,10 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1220
1244
|
continue;
|
|
1221
1245
|
}
|
|
1222
1246
|
const client = await getOrCreateClient(serverConfig, this.session.cwd);
|
|
1247
|
+
if (isProjectAwareLspServer(serverConfig)) {
|
|
1248
|
+
await waitForProjectLoaded(client, signal);
|
|
1249
|
+
throwIfAborted(signal);
|
|
1250
|
+
}
|
|
1223
1251
|
const minVersion = client.diagnosticsVersion;
|
|
1224
1252
|
await refreshFile(client, resolved, signal);
|
|
1225
1253
|
const expectedDocumentVersion = client.openFiles.get(uri)?.version;
|
|
@@ -1512,16 +1540,31 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1512
1540
|
break;
|
|
1513
1541
|
}
|
|
1514
1542
|
case "references": {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
textDocument
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1543
|
+
let result: Location[] | null = null;
|
|
1544
|
+
for (let attempt = 0; attempt <= REFERENCES_RETRY_COUNT; attempt++) {
|
|
1545
|
+
result = (await sendRequest(
|
|
1546
|
+
client,
|
|
1547
|
+
"textDocument/references",
|
|
1548
|
+
{
|
|
1549
|
+
textDocument: { uri },
|
|
1550
|
+
position,
|
|
1551
|
+
context: { includeDeclaration: true },
|
|
1552
|
+
},
|
|
1553
|
+
signal,
|
|
1554
|
+
)) as Location[] | null;
|
|
1555
|
+
|
|
1556
|
+
const locations = result ?? [];
|
|
1557
|
+
if (!isProjectAwareLspServer(serverConfig) || attempt === REFERENCES_RETRY_COUNT) {
|
|
1558
|
+
break;
|
|
1559
|
+
}
|
|
1560
|
+
if (locations.length > 0 && !isOnlyQueriedDeclaration(locations, uri, position)) {
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
await waitForProjectLoaded(client, signal);
|
|
1565
|
+
throwIfAborted(signal);
|
|
1566
|
+
await untilAborted(signal, () => Bun.sleep(REFERENCES_RETRY_DELAY_MS));
|
|
1567
|
+
}
|
|
1525
1568
|
|
|
1526
1569
|
if (!result || result.length === 0) {
|
|
1527
1570
|
output = "No references found";
|
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/lsp/types.ts
CHANGED
|
@@ -411,6 +411,8 @@ export interface LspClient {
|
|
|
411
411
|
isReading: boolean;
|
|
412
412
|
serverCapabilities?: LspServerCapabilities;
|
|
413
413
|
lastActivity: number;
|
|
414
|
+
/** Serializes outbound JSON-RPC writes to the server process. */
|
|
415
|
+
writeQueue: Promise<void>;
|
|
414
416
|
/** Tracks active work-done progress tokens from the server */
|
|
415
417
|
activeProgressTokens: Set<string | number>;
|
|
416
418
|
/** Resolves when the server's initial project loading completes (or after timeout) */
|
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" },
|