@oh-my-pi/pi-coding-agent 6.9.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -51
- package/examples/sdk/04-skills.ts +1 -1
- package/package.json +6 -5
- package/src/cli/stats-cli.ts +191 -0
- package/src/core/agent-session.ts +214 -4
- package/src/core/auth-storage.ts +524 -202
- package/src/core/bash-executor.ts +1 -1
- package/src/core/extensions/index.ts +2 -0
- package/src/core/extensions/runner.ts +31 -0
- package/src/core/extensions/types.ts +24 -0
- package/src/core/messages.ts +48 -0
- package/src/core/model-registry.ts +7 -0
- package/src/core/python-executor.ts +29 -8
- package/src/core/python-gateway-coordinator.ts +55 -1
- package/src/core/python-prelude.py +201 -8
- package/src/core/session-manager.ts +10 -1
- package/src/core/tools/bash.ts +5 -7
- package/src/core/tools/find.ts +18 -5
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/lsp/index.ts +13 -2
- package/src/core/tools/patch/applicator.ts +115 -17
- package/src/core/tools/patch/index.ts +1 -1
- package/src/core/tools/patch/normalize.ts +185 -10
- package/src/core/tools/python.ts +445 -86
- package/src/core/tools/read.ts +4 -4
- package/src/core/tools/task/executor.ts +2 -6
- package/src/core/tools/task/index.ts +30 -12
- package/src/core/tools/task/render.ts +163 -30
- package/src/core/tools/task/template.ts +37 -0
- package/src/core/tools/task/types.ts +6 -2
- package/src/core/tools/task/worker.ts +1 -1
- package/src/index.ts +2 -0
- package/src/main.ts +12 -0
- package/src/modes/interactive/components/python-execution.ts +180 -0
- package/src/modes/interactive/components/welcome.ts +1 -0
- package/src/modes/interactive/controllers/command-controller.ts +395 -0
- package/src/modes/interactive/controllers/input-controller.ts +83 -8
- package/src/modes/interactive/interactive-mode.ts +16 -1
- package/src/modes/interactive/theme/dark.json +2 -9
- package/src/modes/interactive/theme/defaults/alabaster.json +2 -8
- package/src/modes/interactive/theme/defaults/amethyst.json +2 -9
- package/src/modes/interactive/theme/defaults/anthracite.json +2 -9
- package/src/modes/interactive/theme/defaults/basalt.json +89 -88
- package/src/modes/interactive/theme/defaults/birch.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-abyss.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-arctic.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-aurora.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-cavern.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-copper.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-cosmos.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-dracula.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-eclipse.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-ember.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-equinox.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-forest.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-github.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-lavender.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-lunar.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-midnight.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-monokai.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-nebula.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-nord.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-ocean.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-one.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-rainforest.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-reef.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-retro.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-sakura.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-slate.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-solarized.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-solstice.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-starfall.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-sunset.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-swamp.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +2 -1
- package/src/modes/interactive/theme/defaults/dark-taiga.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-terminal.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +2 -9
- package/src/modes/interactive/theme/defaults/dark-tundra.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-twilight.json +2 -8
- package/src/modes/interactive/theme/defaults/dark-volcanic.json +2 -8
- package/src/modes/interactive/theme/defaults/graphite.json +2 -9
- package/src/modes/interactive/theme/defaults/light-arctic.json +2 -1
- package/src/modes/interactive/theme/defaults/light-aurora-day.json +2 -8
- package/src/modes/interactive/theme/defaults/light-canyon.json +2 -8
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +2 -1
- package/src/modes/interactive/theme/defaults/light-cirrus.json +2 -8
- package/src/modes/interactive/theme/defaults/light-coral.json +3 -2
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +2 -9
- package/src/modes/interactive/theme/defaults/light-dawn.json +2 -8
- package/src/modes/interactive/theme/defaults/light-dunes.json +2 -8
- package/src/modes/interactive/theme/defaults/light-eucalyptus.json +3 -2
- package/src/modes/interactive/theme/defaults/light-forest.json +2 -9
- package/src/modes/interactive/theme/defaults/light-frost.json +3 -2
- package/src/modes/interactive/theme/defaults/light-github.json +2 -1
- package/src/modes/interactive/theme/defaults/light-glacier.json +2 -8
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +2 -9
- package/src/modes/interactive/theme/defaults/light-haze.json +2 -8
- package/src/modes/interactive/theme/defaults/light-honeycomb.json +3 -2
- package/src/modes/interactive/theme/defaults/light-lagoon.json +2 -8
- package/src/modes/interactive/theme/defaults/light-lavender.json +3 -2
- package/src/modes/interactive/theme/defaults/light-meadow.json +2 -8
- package/src/modes/interactive/theme/defaults/light-mint.json +3 -2
- package/src/modes/interactive/theme/defaults/light-monochrome.json +2 -1
- package/src/modes/interactive/theme/defaults/light-ocean.json +2 -9
- package/src/modes/interactive/theme/defaults/light-one.json +2 -8
- package/src/modes/interactive/theme/defaults/light-opal.json +2 -8
- package/src/modes/interactive/theme/defaults/light-orchard.json +2 -8
- package/src/modes/interactive/theme/defaults/light-paper.json +3 -2
- package/src/modes/interactive/theme/defaults/light-prism.json +2 -8
- package/src/modes/interactive/theme/defaults/light-retro.json +2 -9
- package/src/modes/interactive/theme/defaults/light-sand.json +3 -2
- package/src/modes/interactive/theme/defaults/light-savanna.json +2 -8
- package/src/modes/interactive/theme/defaults/light-solarized.json +2 -1
- package/src/modes/interactive/theme/defaults/light-soleil.json +2 -8
- package/src/modes/interactive/theme/defaults/light-sunset.json +2 -9
- package/src/modes/interactive/theme/defaults/light-synthwave.json +2 -9
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +2 -9
- package/src/modes/interactive/theme/defaults/light-wetland.json +2 -8
- package/src/modes/interactive/theme/defaults/light-zenith.json +2 -8
- package/src/modes/interactive/theme/defaults/limestone.json +2 -8
- package/src/modes/interactive/theme/defaults/mahogany.json +2 -9
- package/src/modes/interactive/theme/defaults/marble.json +2 -8
- package/src/modes/interactive/theme/defaults/obsidian.json +89 -88
- package/src/modes/interactive/theme/defaults/onyx.json +89 -88
- package/src/modes/interactive/theme/defaults/pearl.json +2 -8
- package/src/modes/interactive/theme/defaults/porcelain.json +89 -88
- package/src/modes/interactive/theme/defaults/quartz.json +2 -8
- package/src/modes/interactive/theme/defaults/sandstone.json +2 -8
- package/src/modes/interactive/theme/defaults/titanium.json +88 -87
- package/src/modes/interactive/theme/light.json +2 -8
- package/src/modes/interactive/theme/theme-schema.json +5 -0
- package/src/modes/interactive/theme/theme.ts +7 -0
- package/src/modes/interactive/types.ts +7 -1
- package/src/modes/interactive/utils/ui-helpers.ts +20 -0
- package/src/prompts/system/system-prompt.md +88 -78
- package/src/prompts/tools/python.md +39 -2
- package/src/prompts/tools/task.md +8 -13
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats CLI command handlers.
|
|
3
|
+
*
|
|
4
|
+
* Handles `omp stats` subcommand for viewing AI usage statistics.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { APP_NAME } from "../config";
|
|
9
|
+
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// =============================================================================
|
|
13
|
+
|
|
14
|
+
export interface StatsCommandArgs {
|
|
15
|
+
port: number;
|
|
16
|
+
json: boolean;
|
|
17
|
+
summary: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Argument Parser
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse stats subcommand arguments.
|
|
26
|
+
* Returns undefined if not a stats command.
|
|
27
|
+
*/
|
|
28
|
+
export function parseStatsArgs(args: string[]): StatsCommandArgs | undefined {
|
|
29
|
+
if (args.length === 0 || args[0] !== "stats") {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const result: StatsCommandArgs = {
|
|
34
|
+
port: 3847,
|
|
35
|
+
json: false,
|
|
36
|
+
summary: false,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
for (let i = 1; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
if (arg === "--json" || arg === "-j") {
|
|
42
|
+
result.json = true;
|
|
43
|
+
} else if (arg === "--summary" || arg === "-s") {
|
|
44
|
+
result.summary = true;
|
|
45
|
+
} else if ((arg === "--port" || arg === "-p") && i + 1 < args.length) {
|
|
46
|
+
result.port = parseInt(args[++i], 10);
|
|
47
|
+
} else if (arg.startsWith("--port=")) {
|
|
48
|
+
result.port = parseInt(arg.split("=")[1], 10);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Formatting Helpers
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
function formatNumber(n: number): string {
|
|
60
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
61
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
62
|
+
return n.toFixed(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatCost(n: number): string {
|
|
66
|
+
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
67
|
+
if (n < 1) return `$${n.toFixed(3)}`;
|
|
68
|
+
return `$${n.toFixed(2)}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatDuration(ms: number | null): string {
|
|
72
|
+
if (ms === null) return "-";
|
|
73
|
+
if (ms < 1000) return `${ms.toFixed(0)}ms`;
|
|
74
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatPercent(n: number): string {
|
|
78
|
+
return `${(n * 100).toFixed(1)}%`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Command Handler
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
export async function runStatsCommand(cmd: StatsCommandArgs): Promise<void> {
|
|
86
|
+
// Lazy import to avoid loading stats module when not needed
|
|
87
|
+
const { getDashboardStats, syncAllSessions, getTotalMessageCount } = await import("@oh-my-pi/omp-stats");
|
|
88
|
+
const { startServer } = await import("@oh-my-pi/omp-stats/src/server");
|
|
89
|
+
const { closeDb } = await import("@oh-my-pi/omp-stats/src/db");
|
|
90
|
+
|
|
91
|
+
// Sync session files first
|
|
92
|
+
console.log("Syncing session files...");
|
|
93
|
+
const { processed, files } = await syncAllSessions();
|
|
94
|
+
const total = await getTotalMessageCount();
|
|
95
|
+
console.log(`Synced ${processed} new entries from ${files} files (${total} total)\n`);
|
|
96
|
+
|
|
97
|
+
if (cmd.json) {
|
|
98
|
+
const stats = await getDashboardStats();
|
|
99
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (cmd.summary) {
|
|
104
|
+
await printStatsSummary();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Start the dashboard server
|
|
109
|
+
const { port } = startServer(cmd.port);
|
|
110
|
+
console.log(chalk.green(`Dashboard available at: http://localhost:${port}`));
|
|
111
|
+
console.log("Press Ctrl+C to stop\n");
|
|
112
|
+
|
|
113
|
+
// Keep process running
|
|
114
|
+
process.on("SIGINT", () => {
|
|
115
|
+
console.log("\nShutting down...");
|
|
116
|
+
closeDb();
|
|
117
|
+
process.exit(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Keep the process alive
|
|
121
|
+
await new Promise(() => {});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function printStatsSummary(): Promise<void> {
|
|
125
|
+
const { getDashboardStats } = await import("@oh-my-pi/omp-stats");
|
|
126
|
+
const stats = await getDashboardStats();
|
|
127
|
+
const { overall, byModel, byFolder } = stats;
|
|
128
|
+
|
|
129
|
+
console.log(chalk.bold("\n=== AI Usage Statistics ===\n"));
|
|
130
|
+
|
|
131
|
+
console.log(chalk.bold("Overall:"));
|
|
132
|
+
console.log(` Requests: ${formatNumber(overall.totalRequests)} (${formatNumber(overall.failedRequests)} errors)`);
|
|
133
|
+
console.log(` Error Rate: ${formatPercent(overall.errorRate)}`);
|
|
134
|
+
console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
|
|
135
|
+
console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
|
|
136
|
+
console.log(` Total Cost: ${formatCost(overall.totalCost)}`);
|
|
137
|
+
console.log(` Avg Duration: ${formatDuration(overall.avgDuration)}`);
|
|
138
|
+
console.log(` Avg TTFT: ${formatDuration(overall.avgTtft)}`);
|
|
139
|
+
if (overall.avgTokensPerSecond !== null) {
|
|
140
|
+
console.log(` Avg Tokens/s: ${overall.avgTokensPerSecond.toFixed(1)}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (byModel.length > 0) {
|
|
144
|
+
console.log(chalk.bold("\nBy Model:"));
|
|
145
|
+
for (const m of byModel.slice(0, 10)) {
|
|
146
|
+
console.log(
|
|
147
|
+
` ${m.model}: ${formatNumber(m.totalRequests)} reqs, ${formatCost(m.totalCost)}, ${formatPercent(m.cacheRate)} cache`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (byFolder.length > 0) {
|
|
153
|
+
console.log(chalk.bold("\nBy Folder:"));
|
|
154
|
+
for (const f of byFolder.slice(0, 10)) {
|
|
155
|
+
console.log(` ${f.folder}: ${formatNumber(f.totalRequests)} reqs, ${formatCost(f.totalCost)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log("");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// =============================================================================
|
|
163
|
+
// Help
|
|
164
|
+
// =============================================================================
|
|
165
|
+
|
|
166
|
+
export function printStatsHelp(): void {
|
|
167
|
+
console.log(`${chalk.bold(`${APP_NAME} stats`)} - AI Usage Statistics Dashboard
|
|
168
|
+
|
|
169
|
+
${chalk.bold("Usage:")}
|
|
170
|
+
${APP_NAME} stats [options]
|
|
171
|
+
|
|
172
|
+
${chalk.bold("Options:")}
|
|
173
|
+
-p, --port <port> Port for the dashboard server (default: 3847)
|
|
174
|
+
-j, --json Output stats as JSON and exit
|
|
175
|
+
-s, --summary Print summary to console and exit
|
|
176
|
+
-h, --help Show this help message
|
|
177
|
+
|
|
178
|
+
${chalk.bold("Examples:")}
|
|
179
|
+
${APP_NAME} stats # Start dashboard server
|
|
180
|
+
${APP_NAME} stats --json # Print stats as JSON
|
|
181
|
+
${APP_NAME} stats --summary # Print summary to console
|
|
182
|
+
${APP_NAME} stats --port 8080 # Start on custom port
|
|
183
|
+
|
|
184
|
+
${chalk.bold("Metrics:")}
|
|
185
|
+
- Total requests and error rate
|
|
186
|
+
- Token usage (input, output, cache)
|
|
187
|
+
- Cost breakdown
|
|
188
|
+
- Average duration and time to first token (TTFT)
|
|
189
|
+
- Tokens per second throughput
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
@@ -15,9 +15,19 @@
|
|
|
15
15
|
|
|
16
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
AssistantMessage,
|
|
20
|
+
ImageContent,
|
|
21
|
+
Message,
|
|
22
|
+
Model,
|
|
23
|
+
TextContent,
|
|
24
|
+
ToolCall,
|
|
25
|
+
Usage,
|
|
26
|
+
UsageReport,
|
|
27
|
+
} from "@oh-my-pi/pi-ai";
|
|
19
28
|
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
20
29
|
import { abortableSleep, logger } from "@oh-my-pi/pi-utils";
|
|
30
|
+
import { YAML } from "bun";
|
|
21
31
|
import type { Rule } from "../capability/rule";
|
|
22
32
|
import { getAgentDbPath } from "../config";
|
|
23
33
|
import { theme } from "../modes/interactive/theme/theme";
|
|
@@ -50,10 +60,21 @@ import type {
|
|
|
50
60
|
import type { CompactOptions, ContextUsage } from "./extensions/types";
|
|
51
61
|
import { extractFileMentions, generateFileMentionMessages } from "./file-mentions";
|
|
52
62
|
import type { HookCommandContext } from "./hooks/types";
|
|
53
|
-
import
|
|
63
|
+
import {
|
|
64
|
+
type BashExecutionMessage,
|
|
65
|
+
type BranchSummaryMessage,
|
|
66
|
+
bashExecutionToText,
|
|
67
|
+
type CompactionSummaryMessage,
|
|
68
|
+
type CustomMessage,
|
|
69
|
+
type FileMentionMessage,
|
|
70
|
+
type HookMessage,
|
|
71
|
+
type PythonExecutionMessage,
|
|
72
|
+
pythonExecutionToText,
|
|
73
|
+
} from "./messages";
|
|
54
74
|
import type { ModelRegistry } from "./model-registry";
|
|
55
75
|
import { parseModelString } from "./model-resolver";
|
|
56
76
|
import { expandPromptTemplate, type PromptTemplate, parseCommandArgs, renderPromptTemplate } from "./prompt-templates";
|
|
77
|
+
import { executePython as executePythonCommand, type PythonResult } from "./python-executor";
|
|
57
78
|
import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, SessionManager } from "./session-manager";
|
|
58
79
|
import type { SettingsManager, SkillsSettings } from "./settings-manager";
|
|
59
80
|
import type { Skill, SkillWarning } from "./skills";
|
|
@@ -248,6 +269,10 @@ export class AgentSession {
|
|
|
248
269
|
private _bashAbortController: AbortController | undefined = undefined;
|
|
249
270
|
private _pendingBashMessages: BashExecutionMessage[] = [];
|
|
250
271
|
|
|
272
|
+
// Python execution state
|
|
273
|
+
private _pythonAbortController: AbortController | undefined = undefined;
|
|
274
|
+
private _pendingPythonMessages: PythonExecutionMessage[] = [];
|
|
275
|
+
|
|
251
276
|
// Extension system
|
|
252
277
|
private _extensionRunner: ExtensionRunner | undefined = undefined;
|
|
253
278
|
private _turnIndex = 0;
|
|
@@ -1005,6 +1030,7 @@ export class AgentSession {
|
|
|
1005
1030
|
|
|
1006
1031
|
// Flush any pending bash messages before the new prompt
|
|
1007
1032
|
this._flushPendingBashMessages();
|
|
1033
|
+
this._flushPendingPythonMessages();
|
|
1008
1034
|
|
|
1009
1035
|
// Reset todo reminder count on new user prompt
|
|
1010
1036
|
this._todoReminderCount = 0;
|
|
@@ -2631,6 +2657,102 @@ export class AgentSession {
|
|
|
2631
2657
|
this._pendingBashMessages = [];
|
|
2632
2658
|
}
|
|
2633
2659
|
|
|
2660
|
+
// =========================================================================
|
|
2661
|
+
// User-Initiated Python Execution
|
|
2662
|
+
// =========================================================================
|
|
2663
|
+
|
|
2664
|
+
/**
|
|
2665
|
+
* Execute Python code in the shared kernel.
|
|
2666
|
+
* Uses the same kernel session as the agent's Python tool, allowing collaborative editing.
|
|
2667
|
+
* @param code The Python code to execute
|
|
2668
|
+
* @param onChunk Optional streaming callback for output
|
|
2669
|
+
* @param options.excludeFromContext If true, execution won't be sent to LLM ($$ prefix)
|
|
2670
|
+
*/
|
|
2671
|
+
async executePython(
|
|
2672
|
+
code: string,
|
|
2673
|
+
onChunk?: (chunk: string) => void,
|
|
2674
|
+
options?: { excludeFromContext?: boolean },
|
|
2675
|
+
): Promise<PythonResult> {
|
|
2676
|
+
this._pythonAbortController = new AbortController();
|
|
2677
|
+
|
|
2678
|
+
try {
|
|
2679
|
+
// Use the same session ID as the Python tool for kernel sharing
|
|
2680
|
+
const sessionFile = this.sessionManager.getSessionFile();
|
|
2681
|
+
const cwd = this.sessionManager.getCwd();
|
|
2682
|
+
const sessionId = sessionFile ? `session:${sessionFile}:cwd:${cwd}` : `cwd:${cwd}`;
|
|
2683
|
+
|
|
2684
|
+
const result = await executePythonCommand(code, {
|
|
2685
|
+
cwd,
|
|
2686
|
+
sessionId,
|
|
2687
|
+
kernelMode: this.settingsManager?.getPythonKernelMode?.() ?? "session",
|
|
2688
|
+
useSharedGateway: this.settingsManager?.getPythonSharedGateway?.() ?? true,
|
|
2689
|
+
onChunk,
|
|
2690
|
+
signal: this._pythonAbortController.signal,
|
|
2691
|
+
});
|
|
2692
|
+
|
|
2693
|
+
this.recordPythonResult(code, result, options);
|
|
2694
|
+
return result;
|
|
2695
|
+
} finally {
|
|
2696
|
+
this._pythonAbortController = undefined;
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
/**
|
|
2701
|
+
* Record a Python execution result in session history.
|
|
2702
|
+
*/
|
|
2703
|
+
recordPythonResult(code: string, result: PythonResult, options?: { excludeFromContext?: boolean }): void {
|
|
2704
|
+
const pythonMessage: PythonExecutionMessage = {
|
|
2705
|
+
role: "pythonExecution",
|
|
2706
|
+
code,
|
|
2707
|
+
output: result.output,
|
|
2708
|
+
exitCode: result.exitCode,
|
|
2709
|
+
cancelled: result.cancelled,
|
|
2710
|
+
truncated: result.truncated,
|
|
2711
|
+
fullOutputPath: result.fullOutputPath,
|
|
2712
|
+
timestamp: Date.now(),
|
|
2713
|
+
excludeFromContext: options?.excludeFromContext,
|
|
2714
|
+
};
|
|
2715
|
+
|
|
2716
|
+
// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering
|
|
2717
|
+
if (this.isStreaming) {
|
|
2718
|
+
this._pendingPythonMessages.push(pythonMessage);
|
|
2719
|
+
} else {
|
|
2720
|
+
this.agent.appendMessage(pythonMessage);
|
|
2721
|
+
this.sessionManager.appendMessage(pythonMessage);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
/**
|
|
2726
|
+
* Cancel running Python execution.
|
|
2727
|
+
*/
|
|
2728
|
+
abortPython(): void {
|
|
2729
|
+
this._pythonAbortController?.abort();
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
/** Whether a Python execution is currently running */
|
|
2733
|
+
get isPythonRunning(): boolean {
|
|
2734
|
+
return this._pythonAbortController !== undefined;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
/** Whether there are pending Python messages waiting to be flushed */
|
|
2738
|
+
get hasPendingPythonMessages(): boolean {
|
|
2739
|
+
return this._pendingPythonMessages.length > 0;
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
/**
|
|
2743
|
+
* Flush pending Python messages to agent state and session.
|
|
2744
|
+
*/
|
|
2745
|
+
private _flushPendingPythonMessages(): void {
|
|
2746
|
+
if (this._pendingPythonMessages.length === 0) return;
|
|
2747
|
+
|
|
2748
|
+
for (const pythonMessage of this._pendingPythonMessages) {
|
|
2749
|
+
this.agent.appendMessage(pythonMessage);
|
|
2750
|
+
this.sessionManager.appendMessage(pythonMessage);
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
this._pendingPythonMessages = [];
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2634
2756
|
// =========================================================================
|
|
2635
2757
|
// Session Management
|
|
2636
2758
|
// =========================================================================
|
|
@@ -3059,6 +3181,14 @@ export class AgentSession {
|
|
|
3059
3181
|
};
|
|
3060
3182
|
}
|
|
3061
3183
|
|
|
3184
|
+
async fetchUsageReports(): Promise<UsageReport[] | null> {
|
|
3185
|
+
const authStorage = this._modelRegistry.authStorage;
|
|
3186
|
+
if (!authStorage.fetchUsageReports) return null;
|
|
3187
|
+
return authStorage.fetchUsageReports({
|
|
3188
|
+
baseUrlResolver: (provider) => this._modelRegistry.getProviderBaseUrl?.(provider),
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3062
3192
|
/**
|
|
3063
3193
|
* Estimate context tokens from messages, using the last assistant usage when available.
|
|
3064
3194
|
*/
|
|
@@ -3163,6 +3293,36 @@ export class AgentSession {
|
|
|
3163
3293
|
formatSessionAsText(): string {
|
|
3164
3294
|
const lines: string[] = [];
|
|
3165
3295
|
|
|
3296
|
+
// Include system prompt at the beginning
|
|
3297
|
+
const systemPrompt = this.agent.state.systemPrompt;
|
|
3298
|
+
if (systemPrompt) {
|
|
3299
|
+
lines.push("## System Prompt\n");
|
|
3300
|
+
lines.push(systemPrompt);
|
|
3301
|
+
lines.push("\n");
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
// Include model and thinking level
|
|
3305
|
+
const model = this.agent.state.model;
|
|
3306
|
+
const thinkingLevel = this.agent.state.thinkingLevel;
|
|
3307
|
+
lines.push("## Configuration\n");
|
|
3308
|
+
lines.push(`Model: ${model.provider}/${model.id}`);
|
|
3309
|
+
lines.push(`Thinking Level: ${thinkingLevel}`);
|
|
3310
|
+
lines.push("\n");
|
|
3311
|
+
|
|
3312
|
+
// Include available tools
|
|
3313
|
+
const tools = this.agent.state.tools;
|
|
3314
|
+
if (tools.length > 0) {
|
|
3315
|
+
lines.push("## Available Tools\n");
|
|
3316
|
+
for (const tool of tools) {
|
|
3317
|
+
lines.push(`### ${tool.name}\n`);
|
|
3318
|
+
lines.push(tool.description);
|
|
3319
|
+
lines.push("\n```yaml");
|
|
3320
|
+
lines.push(YAML.stringify(tool.parameters, null, 2));
|
|
3321
|
+
lines.push("```\n");
|
|
3322
|
+
}
|
|
3323
|
+
lines.push("\n");
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3166
3326
|
for (const msg of this.messages) {
|
|
3167
3327
|
if (msg.role === "user") {
|
|
3168
3328
|
lines.push("## User\n");
|
|
@@ -3191,8 +3351,8 @@ export class AgentSession {
|
|
|
3191
3351
|
lines.push("</thinking>\n");
|
|
3192
3352
|
} else if (c.type === "toolCall") {
|
|
3193
3353
|
lines.push(`### Tool: ${c.name}`);
|
|
3194
|
-
lines.push("```
|
|
3195
|
-
lines.push(
|
|
3354
|
+
lines.push("```yaml");
|
|
3355
|
+
lines.push(YAML.stringify(c.arguments, null, 2));
|
|
3196
3356
|
lines.push("```\n");
|
|
3197
3357
|
}
|
|
3198
3358
|
}
|
|
@@ -3212,6 +3372,56 @@ export class AgentSession {
|
|
|
3212
3372
|
}
|
|
3213
3373
|
}
|
|
3214
3374
|
lines.push("");
|
|
3375
|
+
} else if (msg.role === "bashExecution") {
|
|
3376
|
+
const bashMsg = msg as BashExecutionMessage;
|
|
3377
|
+
if (!bashMsg.excludeFromContext) {
|
|
3378
|
+
lines.push("## Bash Execution\n");
|
|
3379
|
+
lines.push(bashExecutionToText(bashMsg));
|
|
3380
|
+
lines.push("\n");
|
|
3381
|
+
}
|
|
3382
|
+
} else if (msg.role === "pythonExecution") {
|
|
3383
|
+
const pythonMsg = msg as PythonExecutionMessage;
|
|
3384
|
+
if (!pythonMsg.excludeFromContext) {
|
|
3385
|
+
lines.push("## Python Execution\n");
|
|
3386
|
+
lines.push(pythonExecutionToText(pythonMsg));
|
|
3387
|
+
lines.push("\n");
|
|
3388
|
+
}
|
|
3389
|
+
} else if (msg.role === "custom" || msg.role === "hookMessage") {
|
|
3390
|
+
const customMsg = msg as CustomMessage | HookMessage;
|
|
3391
|
+
lines.push(`## ${customMsg.customType}\n`);
|
|
3392
|
+
if (typeof customMsg.content === "string") {
|
|
3393
|
+
lines.push(customMsg.content);
|
|
3394
|
+
} else {
|
|
3395
|
+
for (const c of customMsg.content) {
|
|
3396
|
+
if (c.type === "text") {
|
|
3397
|
+
lines.push(c.text);
|
|
3398
|
+
} else if (c.type === "image") {
|
|
3399
|
+
lines.push("[Image]");
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
lines.push("\n");
|
|
3404
|
+
} else if (msg.role === "branchSummary") {
|
|
3405
|
+
const branchMsg = msg as BranchSummaryMessage;
|
|
3406
|
+
lines.push("## Branch Summary\n");
|
|
3407
|
+
lines.push(`(from branch: ${branchMsg.fromId})\n`);
|
|
3408
|
+
lines.push(branchMsg.summary);
|
|
3409
|
+
lines.push("\n");
|
|
3410
|
+
} else if (msg.role === "compactionSummary") {
|
|
3411
|
+
const compactMsg = msg as CompactionSummaryMessage;
|
|
3412
|
+
lines.push("## Compaction Summary\n");
|
|
3413
|
+
lines.push(`(${compactMsg.tokensBefore} tokens before compaction)\n`);
|
|
3414
|
+
lines.push(compactMsg.summary);
|
|
3415
|
+
lines.push("\n");
|
|
3416
|
+
} else if (msg.role === "fileMention") {
|
|
3417
|
+
const fileMsg = msg as FileMentionMessage;
|
|
3418
|
+
lines.push("## File Mention\n");
|
|
3419
|
+
for (const file of fileMsg.files) {
|
|
3420
|
+
lines.push(`<file path="${file.path}">`);
|
|
3421
|
+
lines.push(file.content);
|
|
3422
|
+
lines.push("</file>\n");
|
|
3423
|
+
}
|
|
3424
|
+
lines.push("\n");
|
|
3215
3425
|
}
|
|
3216
3426
|
}
|
|
3217
3427
|
|