@oh-my-pi/pi-coding-agent 15.10.1 → 15.10.2
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 +67 -0
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +5 -2
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/yield.d.ts +8 -0
- package/package.json +9 -9
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +2 -4
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/model-selection.ts +3 -2
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +4 -22
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings.ts +86 -41
- package/src/debug/index.ts +8 -0
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/eval/__tests__/llm-bridge.test.ts +20 -0
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +14 -3
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/main.ts +91 -65
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +2 -2
- package/src/modes/components/custom-editor.ts +143 -111
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/user-message.ts +1 -1
- package/src/modes/controllers/event-controller.ts +26 -0
- package/src/modes/controllers/input-controller.ts +46 -7
- package/src/modes/interactive-mode.ts +107 -20
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/types.ts +3 -0
- package/src/modes/workflow.ts +10 -10
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +26 -9
- package/src/session/agent-session.ts +37 -12
- package/src/session/auth-storage.ts +2 -0
- package/src/session/session-manager.ts +96 -23
- package/src/task/executor.ts +71 -36
- package/src/task/render.ts +3 -4
- package/src/tools/bash.ts +7 -0
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/eval.ts +13 -2
- package/src/tools/find.ts +7 -0
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/inspect-image.ts +2 -2
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +52 -7
- package/src/tools/read.ts +25 -12
- package/src/tools/search.ts +38 -3
- package/src/tools/write.ts +2 -2
- package/src/tools/yield.ts +10 -1
- package/src/utils/commit-message-generator.ts +2 -2
- package/src/utils/enhanced-paste.ts +30 -2
- package/src/web/search/providers/codex.ts +37 -8
package/src/lsp/client.ts
CHANGED
|
@@ -946,18 +946,28 @@ export async function shutdownClient(key: string): Promise<void> {
|
|
|
946
946
|
// LSP Protocol Methods
|
|
947
947
|
// =============================================================================
|
|
948
948
|
|
|
949
|
-
/** Default timeout for LSP requests (30 seconds) */
|
|
949
|
+
/** Default timeout for LSP requests when no abort signal is provided (30 seconds) */
|
|
950
950
|
const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
951
951
|
|
|
952
952
|
/**
|
|
953
953
|
* Send an LSP request and wait for response.
|
|
954
|
+
*
|
|
955
|
+
* Timeout policy:
|
|
956
|
+
* - If `timeoutMs` is explicitly provided, that value is used.
|
|
957
|
+
* - Else, if `signal` is provided, no internal timer is installed (the caller
|
|
958
|
+
* owns the deadline via the signal — typically a wall-clock `AbortSignal.timeout`
|
|
959
|
+
* from the LSP tool). Installing a second hard-coded 30s timer here used to
|
|
960
|
+
* cause "timed out after 30000ms" errors even when the caller had requested
|
|
961
|
+
* `timeout: 60`.
|
|
962
|
+
* - Else (no signal, no explicit timeout), fall back to `DEFAULT_REQUEST_TIMEOUT_MS`
|
|
963
|
+
* to avoid leaking pending requests forever.
|
|
954
964
|
*/
|
|
955
965
|
export async function sendRequest(
|
|
956
966
|
client: LspClient,
|
|
957
967
|
method: string,
|
|
958
968
|
params: unknown,
|
|
959
969
|
signal?: AbortSignal,
|
|
960
|
-
timeoutMs
|
|
970
|
+
timeoutMs?: number,
|
|
961
971
|
): Promise<unknown> {
|
|
962
972
|
// Atomically increment and capture request ID
|
|
963
973
|
const id = ++client.requestId;
|
|
@@ -993,15 +1003,17 @@ export async function sendRequest(
|
|
|
993
1003
|
reject(reason);
|
|
994
1004
|
};
|
|
995
1005
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
client.pendingRequests.
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1006
|
+
const effectiveTimeoutMs = timeoutMs ?? (signal ? undefined : DEFAULT_REQUEST_TIMEOUT_MS);
|
|
1007
|
+
if (effectiveTimeoutMs !== undefined) {
|
|
1008
|
+
timeout = setTimeout(() => {
|
|
1009
|
+
if (client.pendingRequests.has(id)) {
|
|
1010
|
+
client.pendingRequests.delete(id);
|
|
1011
|
+
const err = new Error(`LSP request ${method} timed out after ${effectiveTimeoutMs}ms`);
|
|
1012
|
+
cleanup();
|
|
1013
|
+
reject(err);
|
|
1014
|
+
}
|
|
1015
|
+
}, effectiveTimeoutMs);
|
|
1016
|
+
}
|
|
1005
1017
|
if (signal) {
|
|
1006
1018
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1007
1019
|
if (signal.aborted) {
|
package/src/lsp/config.ts
CHANGED
|
@@ -450,13 +450,23 @@ export function loadConfig(cwd: string): LspConfig {
|
|
|
450
450
|
*/
|
|
451
451
|
export function getServersForFile(config: LspConfig, filePath: string): Array<[string, ServerConfig]> {
|
|
452
452
|
const ext = path.extname(filePath).toLowerCase();
|
|
453
|
+
const extNoDot = ext.startsWith(".") ? ext.slice(1) : ext;
|
|
453
454
|
const fileName = path.basename(filePath).toLowerCase();
|
|
454
455
|
const matches: Array<[string, ServerConfig]> = [];
|
|
455
456
|
|
|
456
457
|
for (const [name, serverConfig] of Object.entries(config.servers)) {
|
|
457
458
|
const supportsFile = serverConfig.fileTypes.some(fileType => {
|
|
459
|
+
// Accept both `.ts` and `ts` forms in user config / fixtures so a
|
|
460
|
+
// missing dot in `fileTypes` doesn't silently exclude the server
|
|
461
|
+
// from extension-based routing (e.g. rename_file's relevance filter).
|
|
458
462
|
const normalized = fileType.toLowerCase();
|
|
459
|
-
|
|
463
|
+
const normalizedNoDot = normalized.startsWith(".") ? normalized.slice(1) : normalized;
|
|
464
|
+
return (
|
|
465
|
+
normalized === ext ||
|
|
466
|
+
normalized === fileName ||
|
|
467
|
+
normalizedNoDot === extNoDot ||
|
|
468
|
+
normalizedNoDot === fileName
|
|
469
|
+
);
|
|
460
470
|
});
|
|
461
471
|
|
|
462
472
|
if (supportsFile) {
|
package/src/lsp/index.ts
CHANGED
|
@@ -1261,7 +1261,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1261
1261
|
|
|
1262
1262
|
// Status action doesn't need a file
|
|
1263
1263
|
if (action === "status") {
|
|
1264
|
-
const
|
|
1264
|
+
const configuredNames = Object.keys(config.servers);
|
|
1265
1265
|
const lspmuxState = await detectLspmux();
|
|
1266
1266
|
const lspmuxStatus = lspmuxState.available
|
|
1267
1267
|
? lspmuxState.running
|
|
@@ -1269,14 +1269,40 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1269
1269
|
: "lspmux: installed but server not running"
|
|
1270
1270
|
: "";
|
|
1271
1271
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1272
|
+
// `Object.keys(config.servers)` reflects what is *configured & resolvable
|
|
1273
|
+
// on PATH* — it does NOT prove the server actually starts. A wrapper
|
|
1274
|
+
// binary that exits immediately (e.g. rustup without the rust-analyzer
|
|
1275
|
+
// component) still appears here. Distinguish "configured" from
|
|
1276
|
+
// "started" (have a live in-process client) so callers cannot mistake
|
|
1277
|
+
// presence-on-PATH for a working server.
|
|
1278
|
+
const startedClients = getActiveClients();
|
|
1279
|
+
const startedByConfigName = new Map<string, LspServerStatus>();
|
|
1280
|
+
// getActiveClients() reports `name = client.config.command` (the
|
|
1281
|
+
// unresolved binary name from defaults.json), so match against
|
|
1282
|
+
// `serverConfig.command`, not the resolved path.
|
|
1283
|
+
for (const [name, serverConfig] of Object.entries(config.servers)) {
|
|
1284
|
+
const matched = startedClients.find(c => c.name === serverConfig.command);
|
|
1285
|
+
if (matched) startedByConfigName.set(name, matched);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const lines: string[] = [];
|
|
1289
|
+
if (configuredNames.length === 0) {
|
|
1290
|
+
lines.push("No language servers configured for this project");
|
|
1291
|
+
} else {
|
|
1292
|
+
const labelled = configuredNames.map(name => {
|
|
1293
|
+
const started = startedByConfigName.get(name);
|
|
1294
|
+
if (!started) return `${name} (configured, not started)`;
|
|
1295
|
+
return `${name} (${started.status})`;
|
|
1296
|
+
});
|
|
1297
|
+
lines.push(`Language servers: ${labelled.join(", ")}`);
|
|
1298
|
+
lines.push(
|
|
1299
|
+
" note: 'configured, not started' means the binary resolves on PATH but no request has spawned it yet; 'ready' means a client process is live for this cwd.",
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
if (lspmuxStatus) lines.push(lspmuxStatus);
|
|
1276
1303
|
|
|
1277
|
-
const output = lspmuxStatus ? `${serverStatus}\n${lspmuxStatus}` : serverStatus;
|
|
1278
1304
|
return {
|
|
1279
|
-
content: [{ type: "text", text:
|
|
1305
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1280
1306
|
details: { action, success: true, request: params },
|
|
1281
1307
|
};
|
|
1282
1308
|
}
|
|
@@ -1505,7 +1531,26 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1505
1531
|
}
|
|
1506
1532
|
|
|
1507
1533
|
const lspParams = { files: pairs };
|
|
1508
|
-
|
|
1534
|
+
// Filter to servers whose fileTypes match either the source or any
|
|
1535
|
+
// destination path. Asking every configured server about a .md/.sql/.txt
|
|
1536
|
+
// rename used to stack up willRenameFiles requests against irrelevant
|
|
1537
|
+
// language servers and hit the wall-clock timeout. A server only has
|
|
1538
|
+
// something useful to say about a rename if it understands one of the
|
|
1539
|
+
// affected file extensions.
|
|
1540
|
+
const allLspServers = getLspServers(config);
|
|
1541
|
+
const relevantNames = new Set<string>();
|
|
1542
|
+
const collectRelevant = (filePath: string) => {
|
|
1543
|
+
for (const [name] of getLspServersForFile(config, filePath)) {
|
|
1544
|
+
relevantNames.add(name);
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
collectRelevant(source);
|
|
1548
|
+
collectRelevant(dest);
|
|
1549
|
+
for (const pair of pairs) {
|
|
1550
|
+
collectRelevant(uriToFile(pair.oldUri));
|
|
1551
|
+
collectRelevant(uriToFile(pair.newUri));
|
|
1552
|
+
}
|
|
1553
|
+
const servers = allLspServers.filter(([name]) => relevantNames.has(name));
|
|
1509
1554
|
const respondingServers = new Set<string>();
|
|
1510
1555
|
const perServerEdits: Array<{ serverName: string; edit: WorkspaceEdit }> = [];
|
|
1511
1556
|
const serverNotes: string[] = [];
|
|
@@ -1829,8 +1874,15 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1829
1874
|
throw new ToolAbortError();
|
|
1830
1875
|
}
|
|
1831
1876
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1877
|
+
// Echo a (truncated) preview of the params we sent so the caller can
|
|
1878
|
+
// tell parse / shape errors (e.g. nested args dropped, missing field)
|
|
1879
|
+
// apart from genuine server errors without spinning up another debug call.
|
|
1880
|
+
const previewRaw = JSON.stringify(requestParams ?? null);
|
|
1881
|
+
const preview = previewRaw.length > 400 ? `${previewRaw.slice(0, 397)}...` : previewRaw;
|
|
1832
1882
|
return {
|
|
1833
|
-
content: [
|
|
1883
|
+
content: [
|
|
1884
|
+
{ type: "text", text: `LSP error from ${chosenName} on ${method}: ${msg}\n params: ${preview}` },
|
|
1885
|
+
],
|
|
1834
1886
|
details: { action, serverName: chosenName, success: false, request: params },
|
|
1835
1887
|
};
|
|
1836
1888
|
}
|
package/src/main.ts
CHANGED
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* This file handles CLI argument parsing and translates them into
|
|
5
5
|
* createAgentSession() options. The SDK does the heavy lifting.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from "node:fs/promises";
|
|
7
|
+
import * as fsSync from "node:fs";
|
|
9
8
|
import * as os from "node:os";
|
|
10
|
-
import * as path from "node:path";
|
|
11
9
|
import { createInterface } from "node:readline/promises";
|
|
12
10
|
import { EventLoopKeepalive } from "@oh-my-pi/pi-agent-core";
|
|
13
11
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
@@ -28,9 +26,16 @@ import { processFileArguments } from "./cli/file-processor";
|
|
|
28
26
|
import { buildInitialMessage } from "./cli/initial-message";
|
|
29
27
|
import { runListModelsCommand } from "./cli/list-models";
|
|
30
28
|
import { selectSession } from "./cli/session-picker";
|
|
29
|
+
import { applyStartupCwd } from "./cli/startup-cwd";
|
|
31
30
|
import { findConfigFile } from "./config";
|
|
32
31
|
import { ModelRegistry, ModelsConfigFile } from "./config/model-registry";
|
|
33
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getModelMatchPreferences,
|
|
34
|
+
resolveCliModel,
|
|
35
|
+
resolveModelRoleValue,
|
|
36
|
+
resolveModelScope,
|
|
37
|
+
type ScopedModel,
|
|
38
|
+
} from "./config/model-resolver";
|
|
34
39
|
import { getDefault, type SettingPath, Settings, settings } from "./config/settings";
|
|
35
40
|
import { initializeWithSettings } from "./discovery";
|
|
36
41
|
import {
|
|
@@ -344,11 +349,11 @@ async function runInteractiveMode(
|
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
351
|
|
|
347
|
-
type
|
|
352
|
+
type SessionPromptResult = "accepted" | "declined" | "unavailable";
|
|
348
353
|
|
|
349
|
-
type
|
|
354
|
+
type SessionPrompt = (session: SessionInfo) => Promise<SessionPromptResult>;
|
|
350
355
|
|
|
351
|
-
async function promptForkSession(session: SessionInfo): Promise<
|
|
356
|
+
async function promptForkSession(session: SessionInfo): Promise<SessionPromptResult> {
|
|
352
357
|
if (!process.stdin.isTTY) {
|
|
353
358
|
return "unavailable";
|
|
354
359
|
}
|
|
@@ -362,6 +367,52 @@ async function promptForkSession(session: SessionInfo): Promise<ForkSessionPromp
|
|
|
362
367
|
}
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
async function promptMoveSession(session: SessionInfo): Promise<SessionPromptResult> {
|
|
371
|
+
if (!process.stdin.isTTY) {
|
|
372
|
+
return "unavailable";
|
|
373
|
+
}
|
|
374
|
+
const message = `Session's directory no longer exists (${session.cwd}). Move (re-root) it into the current directory? [Y/n] `;
|
|
375
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
376
|
+
try {
|
|
377
|
+
const answer = (await rl.question(message)).trim().toLowerCase();
|
|
378
|
+
return answer === "" || answer === "y" || answer === "yes" ? "accepted" : "declined";
|
|
379
|
+
} finally {
|
|
380
|
+
rl.close();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
type MissingCwdMoveResult =
|
|
385
|
+
| { status: "not-needed" }
|
|
386
|
+
| { status: "declined" }
|
|
387
|
+
| { status: "moved"; manager: SessionManager };
|
|
388
|
+
|
|
389
|
+
async function moveMissingCwdSessionIfNeeded(
|
|
390
|
+
sessionArg: string,
|
|
391
|
+
session: SessionInfo,
|
|
392
|
+
cwd: string,
|
|
393
|
+
sessionDir: string | undefined,
|
|
394
|
+
askToMoveSession: SessionPrompt,
|
|
395
|
+
): Promise<MissingCwdMoveResult> {
|
|
396
|
+
const sourceCwd = session.cwd;
|
|
397
|
+
if (!sourceCwd || fsSync.existsSync(sourceCwd)) {
|
|
398
|
+
return { status: "not-needed" };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const movePromptResult = await askToMoveSession(session);
|
|
402
|
+
if (movePromptResult === "unavailable") {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Session "${sessionArg}" belongs to a directory that no longer exists (${sourceCwd}); run interactively to move it into the current project.`,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (movePromptResult === "declined") {
|
|
408
|
+
return { status: "declined" };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const manager = await SessionManager.open(session.path, sessionDir);
|
|
412
|
+
await manager.moveTo(cwd, sessionDir);
|
|
413
|
+
return { status: "moved", manager };
|
|
414
|
+
}
|
|
415
|
+
|
|
365
416
|
async function getChangelogForDisplay(parsed: Args): Promise<string | undefined> {
|
|
366
417
|
if (parsed.continue || parsed.resume) {
|
|
367
418
|
return undefined;
|
|
@@ -407,7 +458,8 @@ export async function createSessionManager(
|
|
|
407
458
|
parsed: Args,
|
|
408
459
|
cwd: string,
|
|
409
460
|
activeSettings: Settings = settings,
|
|
410
|
-
askToForkSession:
|
|
461
|
+
askToForkSession: SessionPrompt = promptForkSession,
|
|
462
|
+
askToMoveSession: SessionPrompt = promptMoveSession,
|
|
411
463
|
): Promise<SessionManager | undefined> {
|
|
412
464
|
if (parsed.fork) {
|
|
413
465
|
if (parsed.noSession) {
|
|
@@ -436,10 +488,38 @@ export async function createSessionManager(
|
|
|
436
488
|
if (!match) {
|
|
437
489
|
throw new Error(`Session "${sessionArg}" not found.`);
|
|
438
490
|
}
|
|
491
|
+
if (match.scope === "local") {
|
|
492
|
+
const moveResult = await moveMissingCwdSessionIfNeeded(
|
|
493
|
+
sessionArg,
|
|
494
|
+
match.session,
|
|
495
|
+
cwd,
|
|
496
|
+
parsed.sessionDir,
|
|
497
|
+
askToMoveSession,
|
|
498
|
+
);
|
|
499
|
+
if (moveResult.status === "moved") {
|
|
500
|
+
return moveResult.manager;
|
|
501
|
+
}
|
|
502
|
+
if (moveResult.status === "declined") {
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
439
506
|
if (match.scope === "global") {
|
|
440
507
|
const normalizedCwd = normalizePathForComparison(cwd);
|
|
441
508
|
const normalizedMatchCwd = normalizePathForComparison(match.session.cwd || cwd);
|
|
442
509
|
if (normalizedCwd !== normalizedMatchCwd) {
|
|
510
|
+
const moveResult = await moveMissingCwdSessionIfNeeded(
|
|
511
|
+
sessionArg,
|
|
512
|
+
match.session,
|
|
513
|
+
cwd,
|
|
514
|
+
parsed.sessionDir,
|
|
515
|
+
askToMoveSession,
|
|
516
|
+
);
|
|
517
|
+
if (moveResult.status === "moved") {
|
|
518
|
+
return moveResult.manager;
|
|
519
|
+
}
|
|
520
|
+
if (moveResult.status === "declined") {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
443
523
|
const forkPromptResult = await askToForkSession(match.session);
|
|
444
524
|
if (forkPromptResult === "unavailable") {
|
|
445
525
|
throw new Error(
|
|
@@ -480,56 +560,6 @@ export async function createSessionManager(
|
|
|
480
560
|
return undefined;
|
|
481
561
|
}
|
|
482
562
|
|
|
483
|
-
async function maybeAutoChdir(parsed: Args): Promise<void> {
|
|
484
|
-
if (parsed.allowHome || parsed.cwd) {
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const home = os.homedir();
|
|
489
|
-
if (!home) {
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const normalizePath = normalizePathForComparison;
|
|
494
|
-
|
|
495
|
-
const cwd = normalizePath(getProjectDir());
|
|
496
|
-
const normalizedHome = normalizePath(home);
|
|
497
|
-
if (cwd !== normalizedHome) {
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const isDirectory = async (p: string) => {
|
|
502
|
-
try {
|
|
503
|
-
const s = await fs.stat(p);
|
|
504
|
-
return s.isDirectory();
|
|
505
|
-
} catch {
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
const candidates = [path.join(home, "tmp"), "/tmp", "/var/tmp"];
|
|
511
|
-
for (const candidate of candidates) {
|
|
512
|
-
try {
|
|
513
|
-
if (!(await isDirectory(candidate))) {
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
setProjectDir(candidate);
|
|
517
|
-
return;
|
|
518
|
-
} catch {
|
|
519
|
-
// Try next candidate.
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
try {
|
|
524
|
-
const fallback = os.tmpdir();
|
|
525
|
-
if (fallback && normalizePath(fallback) !== cwd && (await isDirectory(fallback))) {
|
|
526
|
-
setProjectDir(fallback);
|
|
527
|
-
}
|
|
528
|
-
} catch {
|
|
529
|
-
// Ignore fallback errors.
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
563
|
/** Discover SYSTEM.md file if no CLI system prompt was provided */
|
|
534
564
|
function discoverSystemPromptFile(): string | undefined {
|
|
535
565
|
// Check project-local first (.omp/SYSTEM.md, .pi/SYSTEM.md legacy)
|
|
@@ -586,9 +616,7 @@ async function buildSessionOptions(
|
|
|
586
616
|
// Model from CLI
|
|
587
617
|
// - supports --provider <name> --model <pattern>
|
|
588
618
|
// - supports --model <provider>/<pattern>
|
|
589
|
-
const modelMatchPreferences =
|
|
590
|
-
usageOrder: activeSettings.getStorage()?.getModelUsageOrder(),
|
|
591
|
-
};
|
|
619
|
+
const modelMatchPreferences = getModelMatchPreferences(activeSettings);
|
|
592
620
|
if (parsed.model) {
|
|
593
621
|
const resolved = resolveCliModel({
|
|
594
622
|
cliProvider: parsed.provider,
|
|
@@ -745,7 +773,7 @@ export async function runRootCommand(
|
|
|
745
773
|
await logger.time("initTheme:initial", initTheme);
|
|
746
774
|
|
|
747
775
|
const parsedArgs = parsed;
|
|
748
|
-
await logger.time("
|
|
776
|
+
await logger.time("applyStartupCwd", applyStartupCwd, parsedArgs);
|
|
749
777
|
|
|
750
778
|
const notifs: (InteractiveModeNotify | null)[] = [];
|
|
751
779
|
|
|
@@ -880,9 +908,7 @@ export async function runRootCommand(
|
|
|
880
908
|
|
|
881
909
|
let scopedModels: ScopedModel[] = [];
|
|
882
910
|
const modelPatterns = parsedArgs.models ?? settingsInstance.get("enabledModels");
|
|
883
|
-
const modelMatchPreferences =
|
|
884
|
-
usageOrder: settingsInstance.getStorage()?.getModelUsageOrder(),
|
|
885
|
-
};
|
|
911
|
+
const modelMatchPreferences = getModelMatchPreferences(settingsInstance);
|
|
886
912
|
if (modelPatterns && modelPatterns.length > 0) {
|
|
887
913
|
scopedModels = await logger.time(
|
|
888
914
|
"resolveModelScope",
|
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -220,6 +220,7 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
220
220
|
readonly mcpToolName: string;
|
|
221
221
|
/** Server name */
|
|
222
222
|
readonly mcpServerName: string;
|
|
223
|
+
readonly approval = "write" as const;
|
|
223
224
|
/** Render completed MCP calls with the result header replacing the pending call header. */
|
|
224
225
|
readonly mergeCallAndResult = true;
|
|
225
226
|
|
|
@@ -305,6 +306,7 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
|
|
|
305
306
|
readonly mcpToolName: string;
|
|
306
307
|
/** Server name */
|
|
307
308
|
readonly mcpServerName: string;
|
|
309
|
+
readonly approval = "write" as const;
|
|
308
310
|
/** Render completed MCP calls with the result header replacing the pending call header. */
|
|
309
311
|
readonly mergeCallAndResult = true;
|
|
310
312
|
|
package/src/memories/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { type ApiKey, clampThinkingLevelForModel, completeSimple, Effort, type M
|
|
|
7
7
|
import { getAgentDbPath, getMemoriesDir, logger, parseJsonlLenient, prompt } from "@oh-my-pi/pi-utils";
|
|
8
8
|
|
|
9
9
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
|
-
import { resolveModelRoleValue } from "../config/model-resolver";
|
|
10
|
+
import { getModelMatchPreferences, resolveModelRoleValue } from "../config/model-resolver";
|
|
11
11
|
import type { Settings } from "../config/settings";
|
|
12
12
|
import consolidationTemplate from "../prompts/memories/consolidation.md" with { type: "text" };
|
|
13
13
|
import readPathTemplate from "../prompts/memories/read-path.md" with { type: "text" };
|
|
@@ -1088,7 +1088,7 @@ async function resolveMemoryModel(options: {
|
|
|
1088
1088
|
if (requestedModel) {
|
|
1089
1089
|
const resolved = resolveModelRoleValue(requestedModel, modelRegistry.getAll(), {
|
|
1090
1090
|
settings: session.settings,
|
|
1091
|
-
matchPreferences:
|
|
1091
|
+
matchPreferences: getModelMatchPreferences(session.settings),
|
|
1092
1092
|
modelRegistry,
|
|
1093
1093
|
});
|
|
1094
1094
|
if (resolved.model) return resolved.model;
|