@oh-my-pi/pi-coding-agent 14.5.11 → 14.5.13
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 +58 -0
- package/package.json +18 -10
- package/src/cli/jupyter-cli.ts +1 -1
- package/src/config/model-equivalence.ts +49 -16
- package/src/config/model-registry.ts +100 -25
- package/src/config/model-resolver.ts +29 -15
- package/src/config/settings-schema.ts +20 -6
- package/src/config/settings.ts +9 -8
- package/src/config.ts +9 -0
- package/src/eval/backend.ts +43 -0
- package/src/eval/eval.lark +43 -0
- package/src/eval/index.ts +5 -0
- package/src/eval/js/context-manager.ts +717 -0
- package/src/eval/js/executor.ts +131 -0
- package/src/eval/js/index.ts +46 -0
- package/src/eval/js/prelude.ts +2 -0
- package/src/eval/js/prelude.txt +84 -0
- package/src/eval/js/tool-bridge.ts +124 -0
- package/src/eval/parse.ts +337 -0
- package/src/{ipy → eval/py}/executor.ts +2 -180
- package/src/{ipy → eval/py}/gateway-coordinator.ts +4 -3
- package/src/eval/py/index.ts +58 -0
- package/src/{ipy → eval/py}/kernel.ts +5 -41
- package/src/{ipy → eval/py}/prelude.py +39 -227
- package/src/eval/types.ts +48 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +23 -17
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/lsp/client.ts +9 -0
- package/src/lsp/index.ts +395 -0
- package/src/lsp/types.ts +15 -4
- package/src/main.ts +25 -14
- package/src/mcp/oauth-flow.ts +1 -1
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/{python-execution.ts → eval-execution.ts} +11 -4
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/oauth-selector.ts +2 -1
- package/src/modes/components/tool-execution.ts +3 -4
- package/src/modes/controllers/command-controller.ts +28 -8
- package/src/modes/controllers/input-controller.ts +4 -4
- package/src/modes/controllers/selector-controller.ts +2 -1
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/types.ts +3 -3
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/prompts/system/system-prompt.md +3 -3
- package/src/prompts/tools/atom.md +3 -2
- package/src/prompts/tools/browser.md +61 -16
- package/src/prompts/tools/eval.md +92 -0
- package/src/prompts/tools/lsp.md +7 -3
- package/src/sdk.ts +45 -31
- package/src/session/agent-session.ts +44 -54
- package/src/session/messages.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/system-prompt.ts +34 -66
- package/src/task/executor.ts +5 -9
- package/src/tools/browser/attach.ts +175 -0
- package/src/tools/browser/launch.ts +576 -0
- package/src/tools/browser/readable.ts +90 -0
- package/src/tools/browser/registry.ts +198 -0
- package/src/tools/browser/render.ts +212 -0
- package/src/tools/browser/tab-protocol.ts +101 -0
- package/src/tools/browser/tab-supervisor.ts +429 -0
- package/src/tools/browser/tab-worker-entry.ts +21 -0
- package/src/tools/browser/tab-worker.ts +1006 -0
- package/src/tools/browser.ts +231 -1567
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/{python.ts → eval.ts} +324 -315
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/index.ts +62 -100
- package/src/tools/plan-mode-guard.ts +27 -1
- package/src/tools/read.ts +0 -6
- package/src/tools/recipe/runners/pkg.ts +34 -32
- package/src/tools/renderers.ts +4 -2
- package/src/tools/resolve.ts +7 -2
- package/src/tools/todo-write.ts +0 -1
- package/src/tools/tool-timeouts.ts +2 -2
- package/src/utils/markit.ts +15 -7
- package/src/utils/tools-manager.ts +5 -5
- package/src/web/search/index.ts +5 -5
- package/src/web/search/provider.ts +121 -39
- package/src/web/search/providers/gemini.ts +2 -2
- package/src/web/search/render.ts +2 -2
- package/src/ipy/modules.ts +0 -144
- package/src/prompts/tools/python.md +0 -57
- /package/src/{ipy → eval/py}/cancellation.ts +0 -0
- /package/src/{ipy → eval/py}/prelude.ts +0 -0
- /package/src/{ipy → eval/py}/runtime.ts +0 -0
package/src/sdk.ts
CHANGED
|
@@ -36,6 +36,7 @@ import { CursorExecHandlers } from "./cursor";
|
|
|
36
36
|
import "./discovery";
|
|
37
37
|
import { resolveConfigValue } from "./config/resolve-config-value";
|
|
38
38
|
import { initializeWithSettings } from "./discovery";
|
|
39
|
+
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
|
|
39
40
|
import { TtsrManager } from "./export/ttsr";
|
|
40
41
|
import {
|
|
41
42
|
type CustomCommandsLoadResult,
|
|
@@ -73,7 +74,6 @@ import {
|
|
|
73
74
|
RuleProtocolHandler,
|
|
74
75
|
SkillProtocolHandler,
|
|
75
76
|
} from "./internal-urls";
|
|
76
|
-
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./ipy/executor";
|
|
77
77
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
78
78
|
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp";
|
|
79
79
|
import {
|
|
@@ -99,6 +99,8 @@ import { SessionManager } from "./session/session-manager";
|
|
|
99
99
|
import { closeAllConnections } from "./ssh/connection-manager";
|
|
100
100
|
import { unmountAll } from "./ssh/sshfs-mount";
|
|
101
101
|
import {
|
|
102
|
+
type AgentsMdSearch,
|
|
103
|
+
buildAgentsMdSearch,
|
|
102
104
|
buildSystemPrompt as buildSystemPromptInternal,
|
|
103
105
|
buildSystemPromptToolMetadata,
|
|
104
106
|
loadProjectContextFiles as loadContextFilesInternal,
|
|
@@ -111,13 +113,13 @@ import {
|
|
|
111
113
|
createTools,
|
|
112
114
|
discoverStartupLspServers,
|
|
113
115
|
EditTool,
|
|
116
|
+
EvalTool,
|
|
114
117
|
FindTool,
|
|
115
118
|
getSearchTools,
|
|
116
119
|
HIDDEN_TOOLS,
|
|
117
120
|
isSearchProviderPreference,
|
|
118
121
|
type LspStartupServerInfo,
|
|
119
122
|
loadSshTool,
|
|
120
|
-
PythonTool,
|
|
121
123
|
ReadTool,
|
|
122
124
|
ResolveTool,
|
|
123
125
|
renderSearchToolBm25Description,
|
|
@@ -204,9 +206,6 @@ export interface CreateAgentSessionOptions {
|
|
|
204
206
|
enableLsp?: boolean;
|
|
205
207
|
/** Skip Python kernel availability check and prelude warmup */
|
|
206
208
|
skipPythonPreflight?: boolean;
|
|
207
|
-
/** Force Python prelude warmup even when test env would normally skip it */
|
|
208
|
-
forcePythonWarmup?: boolean;
|
|
209
|
-
|
|
210
209
|
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
211
210
|
toolNames?: string[];
|
|
212
211
|
|
|
@@ -275,10 +274,10 @@ export {
|
|
|
275
274
|
BUILTIN_TOOLS,
|
|
276
275
|
createTools,
|
|
277
276
|
EditTool,
|
|
277
|
+
EvalTool,
|
|
278
278
|
FindTool,
|
|
279
279
|
HIDDEN_TOOLS,
|
|
280
280
|
loadSshTool,
|
|
281
|
-
PythonTool,
|
|
282
281
|
ReadTool,
|
|
283
282
|
ResolveTool,
|
|
284
283
|
SearchTool,
|
|
@@ -667,17 +666,40 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
667
666
|
const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
|
|
668
667
|
|
|
669
668
|
const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
|
|
670
|
-
logger.time("initializeWithSettings");
|
|
671
|
-
initializeWithSettings(settings);
|
|
669
|
+
logger.time("initializeWithSettings", initializeWithSettings, settings);
|
|
672
670
|
if (!options.modelRegistry) {
|
|
673
671
|
modelRegistry.refreshInBackground();
|
|
674
672
|
}
|
|
673
|
+
// Kick off AGENTS.md filesystem search in parallel — it is the slowest piece of buildSystemPrompt
|
|
674
|
+
// (~200ms on large repos) and only needs `cwd`, so it can overlap with everything that follows.
|
|
675
|
+
const agentsMdSearchPromise: Promise<AgentsMdSearch> = logger.time("buildAgentsMdSearch", buildAgentsMdSearch, cwd);
|
|
676
|
+
agentsMdSearchPromise.catch(() => {});
|
|
677
|
+
|
|
678
|
+
// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
|
|
679
|
+
// at their respective consumer sites. Their work can overlap with model resolution, secret loading,
|
|
680
|
+
// session-context build, tool creation, MCP discovery, and extension discovery.
|
|
681
|
+
const contextFilesPromise = options.contextFiles
|
|
682
|
+
? Promise.resolve(options.contextFiles)
|
|
683
|
+
: logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
|
|
684
|
+
contextFilesPromise.catch(() => {});
|
|
685
|
+
const promptTemplatesPromise = options.promptTemplates
|
|
686
|
+
? Promise.resolve(options.promptTemplates)
|
|
687
|
+
: logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
|
|
688
|
+
promptTemplatesPromise.catch(() => {});
|
|
689
|
+
const slashCommandsPromise = options.slashCommands
|
|
690
|
+
? Promise.resolve(options.slashCommands)
|
|
691
|
+
: logger.time("discoverSlashCommands", discoverSlashCommands, cwd);
|
|
692
|
+
slashCommandsPromise.catch(() => {});
|
|
675
693
|
const skillsSettings = settings.getGroup("skills");
|
|
676
694
|
const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
|
|
677
695
|
const discoveredSkillsPromise =
|
|
678
696
|
options.skills === undefined
|
|
679
|
-
? discoverSkills
|
|
697
|
+
? logger.time("discoverSkills", discoverSkills, cwd, agentDir, {
|
|
698
|
+
...skillsSettings,
|
|
699
|
+
disabledExtensions: disabledExtensionIds,
|
|
700
|
+
})
|
|
680
701
|
: undefined;
|
|
702
|
+
discoveredSkillsPromise?.catch(() => {});
|
|
681
703
|
|
|
682
704
|
// Initialize provider preferences from settings
|
|
683
705
|
const webSearchProvider = settings.get("providers.webSearch");
|
|
@@ -814,10 +836,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
814
836
|
skills = options.skills;
|
|
815
837
|
skillWarnings = [];
|
|
816
838
|
} else {
|
|
817
|
-
const discovered = await
|
|
818
|
-
"discoverSkills",
|
|
819
|
-
() => discoveredSkillsPromise ?? Promise.resolve({ skills: [], warnings: [] }),
|
|
820
|
-
);
|
|
839
|
+
const discovered = await (discoveredSkillsPromise ?? Promise.resolve({ skills: [], warnings: [] }));
|
|
821
840
|
skills = discovered.skills;
|
|
822
841
|
skillWarnings = discovered.warnings;
|
|
823
842
|
}
|
|
@@ -851,10 +870,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
851
870
|
return { ttsrManager, rulebookRules, alwaysApplyRules };
|
|
852
871
|
});
|
|
853
872
|
|
|
854
|
-
const contextFiles = await
|
|
855
|
-
"discoverContextFiles",
|
|
856
|
-
async () => options.contextFiles ?? (await discoverContextFiles(cwd, agentDir)),
|
|
857
|
-
);
|
|
873
|
+
const contextFiles = await contextFilesPromise;
|
|
858
874
|
|
|
859
875
|
let agent: Agent;
|
|
860
876
|
let session!: AgentSession;
|
|
@@ -917,7 +933,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
917
933
|
const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
|
|
918
934
|
const resolvedAgentDisplayName =
|
|
919
935
|
options.agentDisplayName ?? ((options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? "sub" : "main");
|
|
920
|
-
const
|
|
936
|
+
const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
|
|
921
937
|
|
|
922
938
|
try {
|
|
923
939
|
const getActiveModelString = (): string | undefined => {
|
|
@@ -937,7 +953,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
937
953
|
return !requestedToolNames || requestedToolNames.includes("edit");
|
|
938
954
|
},
|
|
939
955
|
skipPythonPreflight: options.skipPythonPreflight,
|
|
940
|
-
forcePythonWarmup: options.forcePythonWarmup,
|
|
941
956
|
contextFiles,
|
|
942
957
|
skills,
|
|
943
958
|
eventBus,
|
|
@@ -945,12 +960,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
945
960
|
requireYieldTool: options.requireYieldTool,
|
|
946
961
|
taskDepth: options.taskDepth ?? 0,
|
|
947
962
|
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
session ? session.
|
|
963
|
+
getEvalKernelOwnerId: () => evalKernelOwnerId,
|
|
964
|
+
assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
|
|
965
|
+
trackEvalExecution: (execution, abortController) =>
|
|
966
|
+
session ? session.trackEvalExecution(execution, abortController) : execution,
|
|
952
967
|
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
953
968
|
getAgentId: () => resolvedAgentId,
|
|
969
|
+
getToolByName: name => session?.getToolByName(name),
|
|
954
970
|
agentRegistry,
|
|
955
971
|
getSessionSpawns: () => options.spawns ?? "*",
|
|
956
972
|
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
@@ -1353,6 +1369,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1353
1369
|
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1354
1370
|
eagerTasks,
|
|
1355
1371
|
secretsEnabled,
|
|
1372
|
+
agentsMdSearch: agentsMdSearchPromise,
|
|
1356
1373
|
});
|
|
1357
1374
|
|
|
1358
1375
|
if (options.systemPrompt === undefined) {
|
|
@@ -1376,6 +1393,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1376
1393
|
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1377
1394
|
eagerTasks,
|
|
1378
1395
|
secretsEnabled,
|
|
1396
|
+
agentsMdSearch: agentsMdSearchPromise,
|
|
1379
1397
|
});
|
|
1380
1398
|
}
|
|
1381
1399
|
return options.systemPrompt(defaultPrompt);
|
|
@@ -1446,13 +1464,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1446
1464
|
|
|
1447
1465
|
const systemPrompt = await logger.time("buildSystemPrompt", rebuildSystemPrompt, initialToolNames, toolRegistry);
|
|
1448
1466
|
|
|
1449
|
-
const promptTemplates =
|
|
1450
|
-
options.promptTemplates ??
|
|
1451
|
-
(await logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir));
|
|
1467
|
+
const promptTemplates = await promptTemplatesPromise;
|
|
1452
1468
|
toolSession.promptTemplates = promptTemplates;
|
|
1453
1469
|
|
|
1454
|
-
const slashCommands =
|
|
1455
|
-
options.slashCommands ?? (await logger.time("discoverSlashCommands", discoverSlashCommands, cwd));
|
|
1470
|
+
const slashCommands = await slashCommandsPromise;
|
|
1456
1471
|
|
|
1457
1472
|
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
1458
1473
|
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
@@ -1596,7 +1611,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1596
1611
|
thinkingLevel,
|
|
1597
1612
|
sessionManager,
|
|
1598
1613
|
settings,
|
|
1599
|
-
|
|
1614
|
+
evalKernelOwnerId,
|
|
1600
1615
|
scopedModels: options.scopedModels,
|
|
1601
1616
|
promptTemplates,
|
|
1602
1617
|
slashCommands,
|
|
@@ -1765,7 +1780,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1765
1780
|
});
|
|
1766
1781
|
}
|
|
1767
1782
|
|
|
1768
|
-
logger.time("createAgentSession:return");
|
|
1769
1783
|
return {
|
|
1770
1784
|
session,
|
|
1771
1785
|
extensionsResult,
|
|
@@ -1780,7 +1794,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1780
1794
|
if (hasSession) {
|
|
1781
1795
|
await session.dispose();
|
|
1782
1796
|
} else {
|
|
1783
|
-
await disposeKernelSessionsByOwner(
|
|
1797
|
+
await disposeKernelSessionsByOwner(evalKernelOwnerId);
|
|
1784
1798
|
}
|
|
1785
1799
|
} catch (cleanupError) {
|
|
1786
1800
|
logger.warn("Failed to clean up createAgentSession resources after startup error", {
|
|
@@ -52,16 +52,8 @@ import {
|
|
|
52
52
|
parseRateLimitReason,
|
|
53
53
|
streamSimple,
|
|
54
54
|
} from "@oh-my-pi/pi-ai";
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
abortableSleep,
|
|
58
|
-
getAgentDbPath,
|
|
59
|
-
isEnoent,
|
|
60
|
-
logger,
|
|
61
|
-
prompt,
|
|
62
|
-
Snowflake,
|
|
63
|
-
setNativeKillTree,
|
|
64
|
-
} from "@oh-my-pi/pi-utils";
|
|
55
|
+
import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
|
|
56
|
+
import { abortableSleep, getAgentDbPath, isEnoent, logger, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
65
57
|
import type { AsyncJob, AsyncJobManager } from "../async";
|
|
66
58
|
import type { Rule } from "../capability/rule";
|
|
67
59
|
import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
|
|
@@ -76,6 +68,11 @@ import {
|
|
|
76
68
|
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
|
77
69
|
import type { Settings, SkillsSettings } from "../config/settings";
|
|
78
70
|
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
|
71
|
+
import {
|
|
72
|
+
disposeKernelSessionsByOwner,
|
|
73
|
+
executePython as executePythonCommand,
|
|
74
|
+
type PythonResult,
|
|
75
|
+
} from "../eval/py/executor";
|
|
79
76
|
import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
|
|
80
77
|
import { exportSessionToHtml } from "../export/html";
|
|
81
78
|
import type { TtsrManager, TtsrMatchContext } from "../export/ttsr";
|
|
@@ -106,11 +103,6 @@ import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
|
106
103
|
import type { Skill, SkillWarning } from "../extensibility/skills";
|
|
107
104
|
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
108
105
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
109
|
-
import {
|
|
110
|
-
disposeKernelSessionsByOwner,
|
|
111
|
-
executePython as executePythonCommand,
|
|
112
|
-
type PythonResult,
|
|
113
|
-
} from "../ipy/executor";
|
|
114
106
|
import {
|
|
115
107
|
buildDiscoverableMCPSearchIndex,
|
|
116
108
|
collectDiscoverableMCPTools,
|
|
@@ -267,7 +259,7 @@ export interface AgentSessionConfig {
|
|
|
267
259
|
/** Secret obfuscator for deobfuscating streaming edit content */
|
|
268
260
|
obfuscator?: SecretObfuscator;
|
|
269
261
|
/** Logical owner for retained Python kernels created by this session. */
|
|
270
|
-
|
|
262
|
+
evalKernelOwnerId?: string;
|
|
271
263
|
/** Agent identity (registry id like "0-Main" or "3-Alice") used for IRC routing. */
|
|
272
264
|
agentId?: string;
|
|
273
265
|
/** Shared agent registry (for forwarding IRC observations to the main session UI). */
|
|
@@ -482,11 +474,11 @@ export class AgentSession {
|
|
|
482
474
|
#pendingBashMessages: BashExecutionMessage[] = [];
|
|
483
475
|
|
|
484
476
|
// Python execution state
|
|
485
|
-
#
|
|
486
|
-
#
|
|
477
|
+
#evalAbortControllers = new Set<AbortController>();
|
|
478
|
+
#evalKernelOwnerId: string;
|
|
487
479
|
#pendingPythonMessages: PythonExecutionMessage[] = [];
|
|
488
|
-
#
|
|
489
|
-
#
|
|
480
|
+
#activeEvalExecutions = new Set<Promise<unknown>>();
|
|
481
|
+
#evalExecutionDisposing = false;
|
|
490
482
|
|
|
491
483
|
// Background-channel IRC exchanges queued while the recipient was streaming.
|
|
492
484
|
// Drained into history (via emitExternalEvent) once the recipient becomes idle.
|
|
@@ -580,14 +572,12 @@ export class AgentSession {
|
|
|
580
572
|
}
|
|
581
573
|
|
|
582
574
|
constructor(config: AgentSessionConfig) {
|
|
583
|
-
setNativeKillTree(killTree);
|
|
584
|
-
|
|
585
575
|
this.agent = config.agent;
|
|
586
576
|
this.sessionManager = config.sessionManager;
|
|
587
577
|
this.settings = config.settings;
|
|
588
578
|
this.#startPowerAssertion();
|
|
589
579
|
this.#asyncJobManager = config.asyncJobManager;
|
|
590
|
-
this.#
|
|
580
|
+
this.#evalKernelOwnerId = config.evalKernelOwnerId ?? `agent-session:${Snowflake.next()}`;
|
|
591
581
|
this.#scopedModels = config.scopedModels ?? [];
|
|
592
582
|
this.#thinkingLevel = config.thinkingLevel;
|
|
593
583
|
this.#promptTemplates = config.promptTemplates ?? [];
|
|
@@ -1948,7 +1938,7 @@ export class AgentSession {
|
|
|
1948
1938
|
* Call this when completely done with the session.
|
|
1949
1939
|
*/
|
|
1950
1940
|
async dispose(): Promise<void> {
|
|
1951
|
-
this.#
|
|
1941
|
+
this.#evalExecutionDisposing = true;
|
|
1952
1942
|
try {
|
|
1953
1943
|
if (this.#extensionRunner?.hasHandlers("session_shutdown")) {
|
|
1954
1944
|
await this.#extensionRunner.emit({ type: "session_shutdown" });
|
|
@@ -1963,13 +1953,13 @@ export class AgentSession {
|
|
|
1963
1953
|
if (drained === false && deliveryState) {
|
|
1964
1954
|
logger.warn("Async job completion deliveries still pending during dispose", { ...deliveryState });
|
|
1965
1955
|
}
|
|
1966
|
-
const pythonExecutionsSettled = await this.#
|
|
1956
|
+
const pythonExecutionsSettled = await this.#prepareEvalExecutionsForDispose();
|
|
1967
1957
|
if (!pythonExecutionsSettled) {
|
|
1968
1958
|
logger.warn(
|
|
1969
1959
|
"Detaching retained Python kernel ownership during dispose while Python execution is still active",
|
|
1970
1960
|
);
|
|
1971
1961
|
}
|
|
1972
|
-
await disposeKernelSessionsByOwner(this.#
|
|
1962
|
+
await disposeKernelSessionsByOwner(this.#evalKernelOwnerId);
|
|
1973
1963
|
this.#stopPowerAssertion();
|
|
1974
1964
|
await this.sessionManager.close();
|
|
1975
1965
|
this.#closeAllProviderSessions("dispose");
|
|
@@ -3433,7 +3423,7 @@ export class AgentSession {
|
|
|
3433
3423
|
this.abortCompaction();
|
|
3434
3424
|
this.abortHandoff();
|
|
3435
3425
|
this.abortBash();
|
|
3436
|
-
this.
|
|
3426
|
+
this.abortEval();
|
|
3437
3427
|
const postPromptDrain = this.#cancelPostPromptTasks();
|
|
3438
3428
|
this.agent.abort();
|
|
3439
3429
|
await postPromptDrain;
|
|
@@ -5905,7 +5895,7 @@ export class AgentSession {
|
|
|
5905
5895
|
|
|
5906
5896
|
/**
|
|
5907
5897
|
* Execute Python code in the shared kernel.
|
|
5908
|
-
* Uses the same kernel session as
|
|
5898
|
+
* Uses the same kernel session as eval's Python backend, allowing collaborative editing.
|
|
5909
5899
|
* @param code The Python code to execute
|
|
5910
5900
|
* @param onChunk Optional streaming callback for output
|
|
5911
5901
|
* @param options.excludeFromContext If true, execution won't be sent to LLM ($$ prefix)
|
|
@@ -5917,7 +5907,7 @@ export class AgentSession {
|
|
|
5917
5907
|
): Promise<PythonResult> {
|
|
5918
5908
|
const excludeFromContext = options?.excludeFromContext === true;
|
|
5919
5909
|
const cwd = this.sessionManager.getCwd();
|
|
5920
|
-
this.
|
|
5910
|
+
this.assertEvalExecutionAllowed();
|
|
5921
5911
|
|
|
5922
5912
|
const abortController = new AbortController();
|
|
5923
5913
|
const execution = (async (): Promise<PythonResult> => {
|
|
@@ -5928,20 +5918,20 @@ export class AgentSession {
|
|
|
5928
5918
|
excludeFromContext,
|
|
5929
5919
|
cwd,
|
|
5930
5920
|
});
|
|
5931
|
-
this.
|
|
5921
|
+
this.assertEvalExecutionAllowed();
|
|
5932
5922
|
if (hookResult?.result) {
|
|
5933
5923
|
this.recordPythonResult(code, hookResult.result, options);
|
|
5934
5924
|
return hookResult.result;
|
|
5935
5925
|
}
|
|
5936
5926
|
}
|
|
5937
5927
|
|
|
5938
|
-
// Use the same session ID as
|
|
5928
|
+
// Use the same session ID as eval's Python backend for kernel sharing
|
|
5939
5929
|
const sessionFile = this.sessionManager.getSessionFile();
|
|
5940
5930
|
const sessionId = sessionFile ? `session:${sessionFile}:cwd:${cwd}` : `cwd:${cwd}`;
|
|
5941
5931
|
const result = await executePythonCommand(code, {
|
|
5942
5932
|
cwd,
|
|
5943
5933
|
sessionId,
|
|
5944
|
-
kernelOwnerId: this.#
|
|
5934
|
+
kernelOwnerId: this.#evalKernelOwnerId,
|
|
5945
5935
|
kernelMode: this.settings.get("python.kernelMode"),
|
|
5946
5936
|
useSharedGateway: this.settings.get("python.sharedGateway"),
|
|
5947
5937
|
onChunk,
|
|
@@ -5950,11 +5940,11 @@ export class AgentSession {
|
|
|
5950
5940
|
this.recordPythonResult(code, result, options);
|
|
5951
5941
|
return result;
|
|
5952
5942
|
})();
|
|
5953
|
-
return await this.
|
|
5943
|
+
return await this.trackEvalExecution(execution, abortController);
|
|
5954
5944
|
}
|
|
5955
5945
|
|
|
5956
|
-
|
|
5957
|
-
if (this.#
|
|
5946
|
+
assertEvalExecutionAllowed(): void {
|
|
5947
|
+
if (this.#evalExecutionDisposing) {
|
|
5958
5948
|
throw new Error("Python execution is unavailable while session disposal is in progress");
|
|
5959
5949
|
}
|
|
5960
5950
|
}
|
|
@@ -5962,17 +5952,17 @@ export class AgentSession {
|
|
|
5962
5952
|
/**
|
|
5963
5953
|
* Track Python work started outside AgentSession.executePython so dispose can await and abort it too.
|
|
5964
5954
|
*/
|
|
5965
|
-
|
|
5966
|
-
this.#
|
|
5967
|
-
this.#
|
|
5955
|
+
trackEvalExecution<T>(execution: Promise<T>, abortController: AbortController): Promise<T> {
|
|
5956
|
+
this.#evalAbortControllers.add(abortController);
|
|
5957
|
+
this.#activeEvalExecutions.add(execution);
|
|
5968
5958
|
void execution.then(
|
|
5969
5959
|
() => {
|
|
5970
|
-
this.#
|
|
5971
|
-
this.#
|
|
5960
|
+
this.#evalAbortControllers.delete(abortController);
|
|
5961
|
+
this.#activeEvalExecutions.delete(execution);
|
|
5972
5962
|
},
|
|
5973
5963
|
() => {
|
|
5974
|
-
this.#
|
|
5975
|
-
this.#
|
|
5964
|
+
this.#evalAbortControllers.delete(abortController);
|
|
5965
|
+
this.#activeEvalExecutions.delete(execution);
|
|
5976
5966
|
},
|
|
5977
5967
|
);
|
|
5978
5968
|
return execution;
|
|
@@ -6007,35 +5997,35 @@ export class AgentSession {
|
|
|
6007
5997
|
/**
|
|
6008
5998
|
* Cancel running Python execution.
|
|
6009
5999
|
*/
|
|
6010
|
-
|
|
6011
|
-
for (const abortController of this.#
|
|
6000
|
+
abortEval(): void {
|
|
6001
|
+
for (const abortController of this.#evalAbortControllers) {
|
|
6012
6002
|
abortController.abort();
|
|
6013
6003
|
}
|
|
6014
6004
|
}
|
|
6015
6005
|
|
|
6016
|
-
async #
|
|
6006
|
+
async #waitForEvalExecutionsToSettle(timeoutMs: number): Promise<boolean> {
|
|
6017
6007
|
const deadline = Date.now() + timeoutMs;
|
|
6018
|
-
while (this.#
|
|
6008
|
+
while (this.#activeEvalExecutions.size > 0) {
|
|
6019
6009
|
const remainingMs = deadline - Date.now();
|
|
6020
6010
|
if (remainingMs <= 0) {
|
|
6021
6011
|
return false;
|
|
6022
6012
|
}
|
|
6023
6013
|
const settled = await Promise.race([
|
|
6024
|
-
Promise.allSettled(Array.from(this.#
|
|
6014
|
+
Promise.allSettled(Array.from(this.#activeEvalExecutions)).then(() => true),
|
|
6025
6015
|
Bun.sleep(remainingMs).then(() => false),
|
|
6026
6016
|
]);
|
|
6027
|
-
if (!settled && this.#
|
|
6017
|
+
if (!settled && this.#activeEvalExecutions.size > 0) {
|
|
6028
6018
|
return false;
|
|
6029
6019
|
}
|
|
6030
6020
|
}
|
|
6031
6021
|
return true;
|
|
6032
6022
|
}
|
|
6033
6023
|
|
|
6034
|
-
async #
|
|
6035
|
-
if (!(await this.#
|
|
6024
|
+
async #prepareEvalExecutionsForDispose(): Promise<boolean> {
|
|
6025
|
+
if (!(await this.#waitForEvalExecutionsToSettle(3_000))) {
|
|
6036
6026
|
logger.warn("Aborting active Python execution during dispose before retained kernel cleanup");
|
|
6037
|
-
this.
|
|
6038
|
-
if (!(await this.#
|
|
6027
|
+
this.abortEval();
|
|
6028
|
+
if (!(await this.#waitForEvalExecutionsToSettle(1_000))) {
|
|
6039
6029
|
logger.warn(
|
|
6040
6030
|
"Python execution is still active after dispose aborted all active runs; retained kernel ownership will still be detached",
|
|
6041
6031
|
);
|
|
@@ -6046,8 +6036,8 @@ export class AgentSession {
|
|
|
6046
6036
|
}
|
|
6047
6037
|
|
|
6048
6038
|
/** Whether a Python execution is currently running */
|
|
6049
|
-
get
|
|
6050
|
-
return this.#
|
|
6039
|
+
get isEvalRunning(): boolean {
|
|
6040
|
+
return this.#evalAbortControllers.size > 0;
|
|
6051
6041
|
}
|
|
6052
6042
|
|
|
6053
6043
|
/** Whether there are pending Python messages waiting to be flushed */
|
package/src/session/messages.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface BashExecutionMessage {
|
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Message type for user-initiated Python executions via the $ command.
|
|
62
|
-
* Shares the same kernel session as
|
|
62
|
+
* Shares the same kernel session as eval's Python backend.
|
|
63
63
|
*/
|
|
64
64
|
export interface PythonExecutionMessage {
|
|
65
65
|
role: "pythonExecution";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
|
-
import { getOAuthProviders } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
5
5
|
import { getConfigDirName } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { invalidate as invalidateFsCache } from "../capability/fs";
|
|
7
7
|
import type { SettingPath, SettingValue } from "../config/settings";
|
package/src/system-prompt.ts
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* System prompt construction and project context loading
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import * as fs from "node:fs";
|
|
6
5
|
import * as os from "node:os";
|
|
7
|
-
import * as path from "node:path";
|
|
8
6
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
7
|
+
import { FileType, glob } from "@oh-my-pi/pi-natives";
|
|
9
8
|
import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
10
9
|
import { $ } from "bun";
|
|
11
10
|
import { contextFileCapability } from "./capability/context-file";
|
|
@@ -89,81 +88,44 @@ const AGENTS_MD_LIMIT = 200;
|
|
|
89
88
|
const SYSTEM_PROMPT_PREP_TIMEOUT_MS = 5000;
|
|
90
89
|
const AGENTS_MD_EXCLUDED_DIRS = new Set(["node_modules", ".git"]);
|
|
91
90
|
|
|
92
|
-
interface AgentsMdSearch {
|
|
91
|
+
export interface AgentsMdSearch {
|
|
93
92
|
scopePath: string;
|
|
94
93
|
limit: number;
|
|
95
94
|
pattern: string;
|
|
96
95
|
files: string[];
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
function normalizePath(value: string): string {
|
|
100
|
-
return value.replace(/\\/g, "/");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function shouldSkipAgentsDir(name: string): boolean {
|
|
104
|
-
if (AGENTS_MD_EXCLUDED_DIRS.has(name)) return true;
|
|
105
|
-
return name.startsWith(".");
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function collectAgentsMdFiles(
|
|
109
|
-
root: string,
|
|
110
|
-
dir: string,
|
|
111
|
-
depth: number,
|
|
112
|
-
limit: number,
|
|
113
|
-
discovered: Set<string>,
|
|
114
|
-
): Promise<void> {
|
|
115
|
-
if (depth > AGENTS_MD_MAX_DEPTH || discovered.size >= limit) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let entries: fs.Dirent[];
|
|
120
|
-
try {
|
|
121
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
122
|
-
} catch {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (depth >= AGENTS_MD_MIN_DEPTH) {
|
|
127
|
-
const hasAgentsMd = entries.some(entry => entry.isFile() && entry.name === "AGENTS.md");
|
|
128
|
-
if (hasAgentsMd) {
|
|
129
|
-
const relPath = normalizePath(path.relative(root, path.join(dir, "AGENTS.md")));
|
|
130
|
-
if (relPath.length > 0) {
|
|
131
|
-
discovered.add(relPath);
|
|
132
|
-
}
|
|
133
|
-
if (discovered.size >= limit) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (depth === AGENTS_MD_MAX_DEPTH) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const childDirs = entries
|
|
144
|
-
.filter(entry => entry.isDirectory() && !shouldSkipAgentsDir(entry.name))
|
|
145
|
-
.map(entry => entry.name)
|
|
146
|
-
.sort();
|
|
147
|
-
|
|
148
|
-
await Promise.all(
|
|
149
|
-
childDirs.map(async child => {
|
|
150
|
-
if (discovered.size >= limit) return;
|
|
151
|
-
await collectAgentsMdFiles(root, path.join(dir, child), depth + 1, limit, discovered);
|
|
152
|
-
}),
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
98
|
async function listAgentsMdFiles(root: string, limit: number): Promise<string[]> {
|
|
157
99
|
try {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
100
|
+
const result = await glob({
|
|
101
|
+
pattern: "**/AGENTS.md",
|
|
102
|
+
path: root,
|
|
103
|
+
fileType: FileType.File,
|
|
104
|
+
recursive: true,
|
|
105
|
+
hidden: false,
|
|
106
|
+
gitignore: true,
|
|
107
|
+
maxResults: limit * 4,
|
|
108
|
+
cache: true,
|
|
109
|
+
});
|
|
110
|
+
const files: string[] = [];
|
|
111
|
+
for (const m of result.matches) {
|
|
112
|
+
const rel = m.path.replace(/\\/g, "/");
|
|
113
|
+
if (!rel?.endsWith("AGENTS.md")) continue;
|
|
114
|
+
const segments = rel.split("/");
|
|
115
|
+
const depth = segments.length - 1;
|
|
116
|
+
if (depth < AGENTS_MD_MIN_DEPTH || depth > AGENTS_MD_MAX_DEPTH) continue;
|
|
117
|
+
const dirSegments = segments.slice(0, -1);
|
|
118
|
+
if (dirSegments.some(seg => AGENTS_MD_EXCLUDED_DIRS.has(seg) || seg.startsWith("."))) continue;
|
|
119
|
+
files.push(rel);
|
|
120
|
+
if (files.length >= limit) break;
|
|
121
|
+
}
|
|
122
|
+
return Array.from(new Set(files)).sort().slice(0, limit);
|
|
161
123
|
} catch {
|
|
162
124
|
return [];
|
|
163
125
|
}
|
|
164
126
|
}
|
|
165
127
|
|
|
166
|
-
async function buildAgentsMdSearch(cwd: string): Promise<AgentsMdSearch> {
|
|
128
|
+
export async function buildAgentsMdSearch(cwd: string): Promise<AgentsMdSearch> {
|
|
167
129
|
const files = await listAgentsMdFiles(cwd, AGENTS_MD_LIMIT);
|
|
168
130
|
return {
|
|
169
131
|
scopePath: ".",
|
|
@@ -445,6 +407,8 @@ export interface BuildSystemPromptOptions {
|
|
|
445
407
|
alwaysApplyRules?: AlwaysApplyRule[];
|
|
446
408
|
/** Whether secret obfuscation is active. When true, explains the redaction format in the prompt. */
|
|
447
409
|
secretsEnabled?: boolean;
|
|
410
|
+
/** Pre-loaded AGENTS.md search (skips discovery if provided). May be a Promise to allow early kick-off. */
|
|
411
|
+
agentsMdSearch?: AgentsMdSearch | Promise<AgentsMdSearch>;
|
|
448
412
|
}
|
|
449
413
|
|
|
450
414
|
/** Build the system prompt with tools, guidelines, and context */
|
|
@@ -470,6 +434,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
470
434
|
mcpDiscoveryServerSummaries = [],
|
|
471
435
|
eagerTasks = false,
|
|
472
436
|
secretsEnabled = false,
|
|
437
|
+
agentsMdSearch: providedAgentsMdSearch,
|
|
473
438
|
} = options;
|
|
474
439
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
475
440
|
|
|
@@ -480,7 +445,10 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
480
445
|
const contextFilesPromise = providedContextFiles
|
|
481
446
|
? Promise.resolve(providedContextFiles)
|
|
482
447
|
: logger.time("loadProjectContextFiles", loadProjectContextFiles, { cwd: resolvedCwd });
|
|
483
|
-
const agentsMdSearchPromise =
|
|
448
|
+
const agentsMdSearchPromise =
|
|
449
|
+
providedAgentsMdSearch !== undefined
|
|
450
|
+
? Promise.resolve(providedAgentsMdSearch)
|
|
451
|
+
: logger.time("buildAgentsMdSearch", buildAgentsMdSearch, resolvedCwd);
|
|
484
452
|
const skillsPromise: Promise<Skill[]> =
|
|
485
453
|
providedSkills !== undefined
|
|
486
454
|
? Promise.resolve(providedSkills)
|
|
@@ -572,7 +540,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
572
540
|
toolNames = Array.from(tools.keys());
|
|
573
541
|
} else {
|
|
574
542
|
// Use defaults
|
|
575
|
-
toolNames = ["read", "bash", "
|
|
543
|
+
toolNames = ["read", "bash", "eval", "edit", "write"]; // TODO: Why?
|
|
576
544
|
}
|
|
577
545
|
}
|
|
578
546
|
|
package/src/task/executor.ts
CHANGED
|
@@ -532,16 +532,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
532
532
|
if (atMaxDepth && toolNames?.includes("task")) {
|
|
533
533
|
toolNames = toolNames.filter(name => name !== "task");
|
|
534
534
|
}
|
|
535
|
-
const pythonToolMode = settings.get("python.toolMode") ?? "both";
|
|
536
535
|
if (toolNames?.includes("exec")) {
|
|
536
|
+
const allowEvalPy = settings.get("eval.py") ?? true;
|
|
537
|
+
const allowEvalJs = settings.get("eval.js") ?? true;
|
|
537
538
|
const expanded = toolNames.filter(name => name !== "exec");
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
} else if (pythonToolMode === "ipy-only") {
|
|
541
|
-
expanded.push("python");
|
|
542
|
-
} else {
|
|
543
|
-
expanded.push("python", "bash");
|
|
544
|
-
}
|
|
539
|
+
if (allowEvalPy || allowEvalJs) expanded.push("eval");
|
|
540
|
+
expanded.push("bash");
|
|
545
541
|
toolNames = Array.from(new Set(expanded));
|
|
546
542
|
}
|
|
547
543
|
|
|
@@ -557,7 +553,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
557
553
|
|
|
558
554
|
const lspEnabled = enableLsp ?? true;
|
|
559
555
|
const ircEnabled = subagentSettings.get("irc.enabled") === true;
|
|
560
|
-
const skipPythonPreflight = Array.isArray(toolNames) && !toolNames.includes("
|
|
556
|
+
const skipPythonPreflight = Array.isArray(toolNames) && !toolNames.includes("eval");
|
|
561
557
|
|
|
562
558
|
const outputChunks: string[] = [];
|
|
563
559
|
const finalOutputChunks: string[] = [];
|