my-pi-agent 0.1.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/README.md +318 -0
- package/package.json +45 -0
- package/pyproject.toml +50 -0
- package/src/my_agent_core/__init__.py +123 -0
- package/src/my_agent_core/agent.py +441 -0
- package/src/my_agent_core/background.py +121 -0
- package/src/my_agent_core/context.py +505 -0
- package/src/my_agent_core/events.py +153 -0
- package/src/my_agent_core/extensions/__init__.py +9 -0
- package/src/my_agent_core/extensions/core.py +197 -0
- package/src/my_agent_core/hooks.py +130 -0
- package/src/my_agent_core/loop.py +709 -0
- package/src/my_agent_core/main.py +134 -0
- package/src/my_agent_core/memory.py +241 -0
- package/src/my_agent_core/message_queue.py +110 -0
- package/src/my_agent_core/plugins.py +212 -0
- package/src/my_agent_core/registry.py +186 -0
- package/src/my_agent_core/session/__init__.py +79 -0
- package/src/my_agent_core/session/entries.py +197 -0
- package/src/my_agent_core/session/jsonl.py +60 -0
- package/src/my_agent_core/session/memory.py +137 -0
- package/src/my_agent_core/session/session.py +400 -0
- package/src/my_agent_core/session/storage.py +245 -0
- package/src/my_agent_core/session/store.py +131 -0
- package/src/my_agent_core/session/tree.py +86 -0
- package/src/my_agent_core/skills.py +149 -0
- package/src/my_agent_core/subagent_tasks.py +170 -0
- package/src/my_agent_core/subagents.py +148 -0
- package/src/my_agent_core/task_store.py +248 -0
- package/src/my_agent_core/tool_history.py +189 -0
- package/src/my_agent_core/tools/__init__.py +5 -0
- package/src/my_agent_core/tools/builtin/__init__.py +5 -0
- package/src/my_agent_core/tools/builtin/task.py +30 -0
- package/src/my_agent_core/tools/builtin/task_tools.py +215 -0
- package/src/my_agent_core/tools/core.py +239 -0
- package/src/my_agent_llm/__init__.py +45 -0
- package/src/my_agent_llm/auth/__init__.py +46 -0
- package/src/my_agent_llm/auth/antigravity.py +209 -0
- package/src/my_agent_llm/auth/manager.py +259 -0
- package/src/my_agent_llm/auth/quota.py +56 -0
- package/src/my_agent_llm/auth/schema.py +94 -0
- package/src/my_agent_llm/client.py +116 -0
- package/src/my_agent_llm/config.py +17 -0
- package/src/my_agent_llm/events.py +84 -0
- package/src/my_agent_llm/models.py +195 -0
- package/src/my_agent_llm/providers/__init__.py +4 -0
- package/src/my_agent_llm/providers/_base.py +94 -0
- package/src/my_agent_llm/providers/anthropic.py +298 -0
- package/src/my_agent_llm/providers/antigravity.py +480 -0
- package/src/my_agent_llm/providers/deepseek.py +196 -0
- package/src/my_agent_llm/providers/openai.py +364 -0
- package/src/my_agent_llm/providers/registry.py +16 -0
- package/src/my_agent_llm/stream.py +218 -0
- package/src/my_coding_agent/__init__.py +66 -0
- package/src/my_coding_agent/agent.py +208 -0
- package/src/my_coding_agent/cli.py +78 -0
- package/src/my_coding_agent/file_reference.py +80 -0
- package/src/my_coding_agent/macro.py +408 -0
- package/src/my_coding_agent/mcp.py +243 -0
- package/src/my_coding_agent/mutation_queue.py +37 -0
- package/src/my_coding_agent/paths.py +119 -0
- package/src/my_coding_agent/permissions.py +84 -0
- package/src/my_coding_agent/prompt.py +54 -0
- package/src/my_coding_agent/rpc_server.py +2817 -0
- package/src/my_coding_agent/settings.py +126 -0
- package/src/my_coding_agent/tools/__init__.py +55 -0
- package/src/my_coding_agent/tools/base.py +58 -0
- package/src/my_coding_agent/tools/bash.py +206 -0
- package/src/my_coding_agent/tools/edit.py +226 -0
- package/src/my_coding_agent/tools/find.py +118 -0
- package/src/my_coding_agent/tools/grep.py +177 -0
- package/src/my_coding_agent/tools/ls.py +112 -0
- package/src/my_coding_agent/tools/read.py +113 -0
- package/src/my_coding_agent/tools/write.py +72 -0
- package/tui/README.md +27 -0
- package/tui/bin/my-agent.js +98 -0
- package/tui/dist/app.d.ts +41 -0
- package/tui/dist/app.js +110 -0
- package/tui/dist/bridge/event-translator.d.ts +92 -0
- package/tui/dist/bridge/event-translator.js +216 -0
- package/tui/dist/bridge/kernel-bridge.d.ts +48 -0
- package/tui/dist/bridge/kernel-bridge.js +132 -0
- package/tui/dist/client.d.ts +63 -0
- package/tui/dist/client.js +239 -0
- package/tui/dist/components/assistant-message.d.ts +19 -0
- package/tui/dist/components/assistant-message.js +90 -0
- package/tui/dist/components/compaction-summary-message.d.ts +19 -0
- package/tui/dist/components/compaction-summary-message.js +46 -0
- package/tui/dist/components/custom-editor.d.ts +18 -0
- package/tui/dist/components/custom-editor.js +56 -0
- package/tui/dist/components/dynamic-border.d.ts +9 -0
- package/tui/dist/components/dynamic-border.js +14 -0
- package/tui/dist/components/footer.d.ts +39 -0
- package/tui/dist/components/footer.js +199 -0
- package/tui/dist/components/header.d.ts +4 -0
- package/tui/dist/components/header.js +21 -0
- package/tui/dist/components/keys.d.ts +5 -0
- package/tui/dist/components/keys.js +12 -0
- package/tui/dist/components/login-selector.d.ts +26 -0
- package/tui/dist/components/login-selector.js +181 -0
- package/tui/dist/components/logout-selector.d.ts +19 -0
- package/tui/dist/components/logout-selector.js +88 -0
- package/tui/dist/components/model-selector.d.ts +40 -0
- package/tui/dist/components/model-selector.js +268 -0
- package/tui/dist/components/session-selector.d.ts +54 -0
- package/tui/dist/components/session-selector.js +393 -0
- package/tui/dist/components/settings-selector.d.ts +24 -0
- package/tui/dist/components/settings-selector.js +146 -0
- package/tui/dist/components/status-indicator.d.ts +25 -0
- package/tui/dist/components/status-indicator.js +60 -0
- package/tui/dist/components/theme-selector.d.ts +14 -0
- package/tui/dist/components/theme-selector.js +77 -0
- package/tui/dist/components/thinking-selector.d.ts +21 -0
- package/tui/dist/components/thinking-selector.js +128 -0
- package/tui/dist/components/tool-execution.d.ts +31 -0
- package/tui/dist/components/tool-execution.js +206 -0
- package/tui/dist/components/tree-selector.d.ts +40 -0
- package/tui/dist/components/tree-selector.js +173 -0
- package/tui/dist/components/user-message-selector.d.ts +21 -0
- package/tui/dist/components/user-message-selector.js +103 -0
- package/tui/dist/components/user-message.d.ts +5 -0
- package/tui/dist/components/user-message.js +15 -0
- package/tui/dist/index.d.ts +11 -0
- package/tui/dist/index.js +11 -0
- package/tui/dist/interactive/chat-viewport.d.ts +19 -0
- package/tui/dist/interactive/chat-viewport.js +41 -0
- package/tui/dist/interactive/components.d.ts +1 -0
- package/tui/dist/interactive/components.js +1 -0
- package/tui/dist/interactive/interactive-mode.d.ts +89 -0
- package/tui/dist/interactive/interactive-mode.js +1625 -0
- package/tui/dist/interactive/theme.d.ts +1 -0
- package/tui/dist/interactive/theme.js +1 -0
- package/tui/dist/interactive/tui-renderer.d.ts +8 -0
- package/tui/dist/interactive/tui-renderer.js +10 -0
- package/tui/dist/protocol.d.ts +78 -0
- package/tui/dist/protocol.js +1 -0
- package/tui/dist/theme/dark.json +54 -0
- package/tui/dist/theme/light.json +71 -0
- package/tui/dist/theme/theme.d.ts +20 -0
- package/tui/dist/theme/theme.js +86 -0
- package/tui/package.json +25 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { EventTranslator, } from "./event-translator.js";
|
|
2
|
+
/**
|
|
3
|
+
* KernelBridge acts as a lightweight Session Adapter between the presentation
|
|
4
|
+
* layer and the PythonKernelClient.
|
|
5
|
+
*/
|
|
6
|
+
// Export bridge
|
|
7
|
+
export class KernelBridge {
|
|
8
|
+
client;
|
|
9
|
+
translator;
|
|
10
|
+
constructor(client, translator) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
this.translator = translator ?? new EventTranslator();
|
|
13
|
+
}
|
|
14
|
+
async call(method, params = {}) {
|
|
15
|
+
if (typeof this.client.request === "function") {
|
|
16
|
+
return (await this.client.request(method, params));
|
|
17
|
+
}
|
|
18
|
+
if (typeof this.client.sendRequest === "function") {
|
|
19
|
+
return (await this.client.sendRequest(method, params));
|
|
20
|
+
}
|
|
21
|
+
throw new Error("KernelBridge: Provided client must implement request() or sendRequest().");
|
|
22
|
+
}
|
|
23
|
+
async prompt(text, options) {
|
|
24
|
+
if (typeof this.client.prompt === "function") {
|
|
25
|
+
return this.client.prompt(text, options);
|
|
26
|
+
}
|
|
27
|
+
const params = {
|
|
28
|
+
text,
|
|
29
|
+
...(options || {}),
|
|
30
|
+
};
|
|
31
|
+
return this.call("prompt", params);
|
|
32
|
+
}
|
|
33
|
+
async abort() {
|
|
34
|
+
if (typeof this.client.abort === "function") {
|
|
35
|
+
return this.client.abort();
|
|
36
|
+
}
|
|
37
|
+
return this.call("abort", {});
|
|
38
|
+
}
|
|
39
|
+
async steer(text) {
|
|
40
|
+
if (typeof this.client.steer === "function") {
|
|
41
|
+
return this.client.steer(text);
|
|
42
|
+
}
|
|
43
|
+
return this.call("steer", { message: text });
|
|
44
|
+
}
|
|
45
|
+
async followUp(text) {
|
|
46
|
+
if (typeof this.client.followup === "function") {
|
|
47
|
+
return this.client.followup(text);
|
|
48
|
+
}
|
|
49
|
+
if (typeof this.client.followUp === "function") {
|
|
50
|
+
return this.client.followUp(text);
|
|
51
|
+
}
|
|
52
|
+
return this.call("followup", { message: text });
|
|
53
|
+
}
|
|
54
|
+
async listModels(options = {}) {
|
|
55
|
+
return this.call("models_list", options);
|
|
56
|
+
}
|
|
57
|
+
async switchModel(model, provider) {
|
|
58
|
+
const params = { model };
|
|
59
|
+
if (provider !== undefined) {
|
|
60
|
+
params.provider = provider;
|
|
61
|
+
}
|
|
62
|
+
return this.call("model_switch", params);
|
|
63
|
+
}
|
|
64
|
+
async listSessions(options = {}) {
|
|
65
|
+
return this.call("session_list", options);
|
|
66
|
+
}
|
|
67
|
+
async resumeSession(sessionId) {
|
|
68
|
+
return this.call("session_resume", {
|
|
69
|
+
session_id: sessionId,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async deleteSession(sessionId) {
|
|
73
|
+
return this.call("session_delete", {
|
|
74
|
+
session_id: sessionId,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async getSessionHistory() {
|
|
78
|
+
return this.call("session_history", {});
|
|
79
|
+
}
|
|
80
|
+
async newSession(options) {
|
|
81
|
+
return this.call("session_new", options || {});
|
|
82
|
+
}
|
|
83
|
+
async getTree() {
|
|
84
|
+
return this.call("session_tree", {});
|
|
85
|
+
}
|
|
86
|
+
async getSessionStats() {
|
|
87
|
+
return this.call("session_stats", {});
|
|
88
|
+
}
|
|
89
|
+
async branchSession(nodeId) {
|
|
90
|
+
return this.call("session_branch", {
|
|
91
|
+
target_id: nodeId,
|
|
92
|
+
node_id: nodeId,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async setThinking(level) {
|
|
96
|
+
return this.call("thinking_set", { level });
|
|
97
|
+
}
|
|
98
|
+
async login(provider, key) {
|
|
99
|
+
return this.call("login", { provider, key });
|
|
100
|
+
}
|
|
101
|
+
async logout(provider) {
|
|
102
|
+
return this.call("auth_logout", { provider });
|
|
103
|
+
}
|
|
104
|
+
async getSettings() {
|
|
105
|
+
return this.call("settings_get", {});
|
|
106
|
+
}
|
|
107
|
+
async setSetting(key, value) {
|
|
108
|
+
return this.call("settings_set", { key, value });
|
|
109
|
+
}
|
|
110
|
+
subscribe(listener) {
|
|
111
|
+
const handler = (rawEvent) => {
|
|
112
|
+
const translated = this.translator.translate(rawEvent);
|
|
113
|
+
if (translated !== null && translated !== undefined) {
|
|
114
|
+
listener(translated);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
if (typeof this.client.on === "function") {
|
|
118
|
+
this.client.on("event", handler);
|
|
119
|
+
this.client.on("notification", handler);
|
|
120
|
+
}
|
|
121
|
+
return () => {
|
|
122
|
+
if (typeof this.client.off === "function") {
|
|
123
|
+
this.client.off("event", handler);
|
|
124
|
+
this.client.off("notification", handler);
|
|
125
|
+
}
|
|
126
|
+
else if (typeof this.client.removeListener === "function") {
|
|
127
|
+
this.client.removeListener("event", handler);
|
|
128
|
+
this.client.removeListener("notification", handler);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { AgentEvent } from "./protocol.js";
|
|
3
|
+
export interface SessionMessage {
|
|
4
|
+
role: string;
|
|
5
|
+
content: string;
|
|
6
|
+
metadata?: {
|
|
7
|
+
tool_calls?: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
args?: Record<string, unknown>;
|
|
11
|
+
function?: {
|
|
12
|
+
name: string;
|
|
13
|
+
arguments: string | Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}>;
|
|
17
|
+
tool_call_id?: string;
|
|
18
|
+
is_error?: boolean;
|
|
19
|
+
tool_name?: string;
|
|
20
|
+
thinking?: string;
|
|
21
|
+
reasoning_content?: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface InitializeResult {
|
|
26
|
+
status: string;
|
|
27
|
+
workspace: string;
|
|
28
|
+
model: string;
|
|
29
|
+
session_id?: string;
|
|
30
|
+
session_file?: string;
|
|
31
|
+
session_name?: string;
|
|
32
|
+
messages?: SessionMessage[];
|
|
33
|
+
}
|
|
34
|
+
export interface PythonKernelClientOptions {
|
|
35
|
+
workspace?: string;
|
|
36
|
+
model?: string;
|
|
37
|
+
mode?: string;
|
|
38
|
+
pythonExecutable?: string;
|
|
39
|
+
continueSession?: boolean;
|
|
40
|
+
resume?: string | boolean;
|
|
41
|
+
sessionName?: string;
|
|
42
|
+
thinking?: string;
|
|
43
|
+
noSession?: boolean;
|
|
44
|
+
newSession?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export declare class PythonKernelClient extends EventEmitter {
|
|
47
|
+
readonly options: PythonKernelClientOptions;
|
|
48
|
+
private child;
|
|
49
|
+
private nextId;
|
|
50
|
+
private pendingRequests;
|
|
51
|
+
private activeEventCallback;
|
|
52
|
+
private rl;
|
|
53
|
+
constructor(options?: PythonKernelClientOptions);
|
|
54
|
+
start(): Promise<InitializeResult>;
|
|
55
|
+
private resolvePythonCommand;
|
|
56
|
+
private handleLine;
|
|
57
|
+
sendRequest<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
58
|
+
prompt(text: string, onEventOrOptions?: ((event: AgentEvent) => void) | Record<string, unknown>): Promise<void>;
|
|
59
|
+
steer(message: string): Promise<void>;
|
|
60
|
+
followup(message: string): Promise<void>;
|
|
61
|
+
abort(): Promise<void>;
|
|
62
|
+
shutdown(): Promise<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import * as readline from "node:readline";
|
|
5
|
+
import { EventEmitter } from "node:events";
|
|
6
|
+
export class PythonKernelClient extends EventEmitter {
|
|
7
|
+
options;
|
|
8
|
+
child = null;
|
|
9
|
+
nextId = 1;
|
|
10
|
+
pendingRequests = new Map();
|
|
11
|
+
activeEventCallback = null;
|
|
12
|
+
rl = null;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
super();
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
async start() {
|
|
18
|
+
if (this.child) {
|
|
19
|
+
return {
|
|
20
|
+
status: "ok",
|
|
21
|
+
workspace: this.options.workspace || process.cwd(),
|
|
22
|
+
model: this.options.model || "default",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const workspace = this.options.workspace || process.cwd();
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const repoRoot = path.resolve(__dirname, "../..");
|
|
28
|
+
const { cmd, args } = this.resolvePythonCommand(workspace, repoRoot);
|
|
29
|
+
const pythonPath = path.join(repoRoot, "src") +
|
|
30
|
+
(process.env.PYTHONPATH ? path.delimiter + process.env.PYTHONPATH : "");
|
|
31
|
+
this.child = spawn(cmd, args, {
|
|
32
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
33
|
+
cwd: repoRoot,
|
|
34
|
+
windowsHide: true,
|
|
35
|
+
env: {
|
|
36
|
+
...process.env,
|
|
37
|
+
PYTHONPATH: pythonPath,
|
|
38
|
+
PYTHONIOENCODING: "utf-8",
|
|
39
|
+
PYTHONUTF8: "1",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
if (!this.child.stdout || !this.child.stdin) {
|
|
43
|
+
throw new Error("Failed to initialize stdin/stdout pipes for Python kernel.");
|
|
44
|
+
}
|
|
45
|
+
this.child.stdin.on("error", (err) => {
|
|
46
|
+
this.emit("error", err);
|
|
47
|
+
});
|
|
48
|
+
this.rl = readline.createInterface({
|
|
49
|
+
input: this.child.stdout,
|
|
50
|
+
terminal: false,
|
|
51
|
+
});
|
|
52
|
+
this.rl.on("line", (line) => {
|
|
53
|
+
this.handleLine(line);
|
|
54
|
+
});
|
|
55
|
+
this.child.on("exit", (code, signal) => {
|
|
56
|
+
this.child = null;
|
|
57
|
+
this.emit("exit", { code, signal });
|
|
58
|
+
for (const { reject } of this.pendingRequests.values()) {
|
|
59
|
+
reject(new Error(`Python kernel terminated prematurely with code ${code}, signal ${signal}`));
|
|
60
|
+
}
|
|
61
|
+
this.pendingRequests.clear();
|
|
62
|
+
});
|
|
63
|
+
this.child.on("error", (err) => {
|
|
64
|
+
this.emit("error", err);
|
|
65
|
+
for (const { reject } of this.pendingRequests.values()) {
|
|
66
|
+
reject(new Error(`无法启动 Python 内核 (${cmd}): ${err.message}。\n` +
|
|
67
|
+
`my-pi-agent 采用双核驱动体系(Node.js TUI + Python 异步内核)。\n` +
|
|
68
|
+
`请安装推荐的 Python 极速环境管理工具 uv(安装后全自动自愈配置):\n` +
|
|
69
|
+
` macOS / Linux: curl -LsSf https://astral.sh/uv/install.sh | sh\n` +
|
|
70
|
+
` Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"\n` +
|
|
71
|
+
`安装 uv 后重新运行 my-pi-agent 即可自动运行!`));
|
|
72
|
+
}
|
|
73
|
+
this.pendingRequests.clear();
|
|
74
|
+
});
|
|
75
|
+
// 初始化内核
|
|
76
|
+
const res = (await this.sendRequest("initialize", {
|
|
77
|
+
workspace,
|
|
78
|
+
model: this.options.model,
|
|
79
|
+
mode: this.options.mode || "review",
|
|
80
|
+
continue_session: this.options.continueSession,
|
|
81
|
+
resume: this.options.resume,
|
|
82
|
+
name: this.options.sessionName,
|
|
83
|
+
thinking: this.options.thinking,
|
|
84
|
+
no_session: this.options.noSession,
|
|
85
|
+
new_session: this.options.newSession,
|
|
86
|
+
}));
|
|
87
|
+
return res;
|
|
88
|
+
}
|
|
89
|
+
resolvePythonCommand(workspace, repoRoot) {
|
|
90
|
+
const rpcArgs = ["-m", "my_coding_agent.rpc_server", "-w", workspace];
|
|
91
|
+
if (this.options.model) {
|
|
92
|
+
rpcArgs.push("-m", this.options.model);
|
|
93
|
+
}
|
|
94
|
+
// 1. 优先使用外部显式传入的 Python 解释器
|
|
95
|
+
if (this.options.pythonExecutable) {
|
|
96
|
+
return { cmd: this.options.pythonExecutable, args: rpcArgs };
|
|
97
|
+
}
|
|
98
|
+
// 2. 探测系统 uv 极速包管理器(指定 --project 锚定包根目录,支持全局任意路径启动)
|
|
99
|
+
try {
|
|
100
|
+
const probeUv = spawnSync("uv", ["--version"], { stdio: "ignore" });
|
|
101
|
+
if (probeUv.status === 0) {
|
|
102
|
+
return {
|
|
103
|
+
cmd: "uv",
|
|
104
|
+
args: ["run", "--project", repoRoot, "python", ...rpcArgs],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// uv 未安装
|
|
110
|
+
}
|
|
111
|
+
// 3. 探测系统 python3
|
|
112
|
+
try {
|
|
113
|
+
const probePy3 = spawnSync("python3", ["--version"], { stdio: "ignore" });
|
|
114
|
+
if (probePy3.status === 0) {
|
|
115
|
+
return { cmd: "python3", args: rpcArgs };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// python3 未安装
|
|
120
|
+
}
|
|
121
|
+
// 4. 探测系统 python
|
|
122
|
+
try {
|
|
123
|
+
const probePy = spawnSync("python", ["--version"], { stdio: "ignore" });
|
|
124
|
+
if (probePy.status === 0) {
|
|
125
|
+
return { cmd: "python", args: rpcArgs };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// python 未安装
|
|
130
|
+
}
|
|
131
|
+
// 5. 兜底回退为 uv,并将在子进程启动失败时触发友好报错
|
|
132
|
+
return { cmd: "uv", args: ["run", "python", ...rpcArgs] };
|
|
133
|
+
}
|
|
134
|
+
handleLine(line) {
|
|
135
|
+
const trimmed = line.trim();
|
|
136
|
+
if (!trimmed) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const msg = JSON.parse(trimmed);
|
|
141
|
+
if ("id" in msg &&
|
|
142
|
+
(msg.result !== undefined || msg.error !== undefined)) {
|
|
143
|
+
// RPC Response
|
|
144
|
+
const res = msg;
|
|
145
|
+
const pending = this.pendingRequests.get(res.id);
|
|
146
|
+
if (pending) {
|
|
147
|
+
this.pendingRequests.delete(res.id);
|
|
148
|
+
if (res.error) {
|
|
149
|
+
pending.reject(new Error(res.error.message));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
pending.resolve(res.result);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (msg.method === "event") {
|
|
157
|
+
// RPC Event Notification
|
|
158
|
+
const notif = msg;
|
|
159
|
+
// SAFETY: notif.params is serialized by Python rpc_server.serialize_event to conform to AgentEvent schema
|
|
160
|
+
const event = notif.params;
|
|
161
|
+
this.emit("event", event);
|
|
162
|
+
if (this.activeEventCallback) {
|
|
163
|
+
this.activeEventCallback(event);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// 忽略无法解析的格式
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async sendRequest(method, params) {
|
|
172
|
+
if (!this.child || !this.child.stdin) {
|
|
173
|
+
throw new Error("Python kernel is not running.");
|
|
174
|
+
}
|
|
175
|
+
const id = this.nextId++;
|
|
176
|
+
const req = {
|
|
177
|
+
jsonrpc: "2.0",
|
|
178
|
+
id,
|
|
179
|
+
method,
|
|
180
|
+
params,
|
|
181
|
+
};
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
this.pendingRequests.set(id, {
|
|
184
|
+
resolve: (val) => resolve(val),
|
|
185
|
+
reject,
|
|
186
|
+
});
|
|
187
|
+
this.child.stdin.write(JSON.stringify(req) + "\n");
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
async prompt(text, onEventOrOptions) {
|
|
191
|
+
if (typeof onEventOrOptions === "function") {
|
|
192
|
+
this.activeEventCallback = onEventOrOptions;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.activeEventCallback = null;
|
|
196
|
+
}
|
|
197
|
+
const params = { text };
|
|
198
|
+
if (onEventOrOptions &&
|
|
199
|
+
typeof onEventOrOptions === "object" &&
|
|
200
|
+
typeof onEventOrOptions !== "function") {
|
|
201
|
+
Object.assign(params, onEventOrOptions);
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
await this.sendRequest("prompt", params);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
this.activeEventCallback = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async steer(message) {
|
|
211
|
+
await this.sendRequest("steer", { message });
|
|
212
|
+
}
|
|
213
|
+
async followup(message) {
|
|
214
|
+
await this.sendRequest("followup", { message });
|
|
215
|
+
}
|
|
216
|
+
async abort() {
|
|
217
|
+
await this.sendRequest("abort");
|
|
218
|
+
}
|
|
219
|
+
async shutdown() {
|
|
220
|
+
try {
|
|
221
|
+
if (this.child && this.child.stdin) {
|
|
222
|
+
await this.sendRequest("shutdown");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// 忽略关闭时的异常
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
if (this.child) {
|
|
230
|
+
this.child.kill();
|
|
231
|
+
this.child = null;
|
|
232
|
+
}
|
|
233
|
+
if (this.rl) {
|
|
234
|
+
this.rl.close();
|
|
235
|
+
this.rl = null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Container } from "@earendil-works/pi-tui";
|
|
2
|
+
export declare class AssistantMessageComponent extends Container {
|
|
3
|
+
private thinkingContainer;
|
|
4
|
+
private contentContainer;
|
|
5
|
+
private thinkingText;
|
|
6
|
+
private contentText;
|
|
7
|
+
private isThinkingExpanded;
|
|
8
|
+
private isFinalized;
|
|
9
|
+
constructor();
|
|
10
|
+
appendReasoningDelta(delta: string): void;
|
|
11
|
+
setReasoning(fullThinking: string): void;
|
|
12
|
+
appendTextDelta(delta: string): void;
|
|
13
|
+
setContent(fullContent: string): void;
|
|
14
|
+
getContentText(): string;
|
|
15
|
+
toggleThinking(): void;
|
|
16
|
+
finalize(): void;
|
|
17
|
+
private updateThinkingDisplay;
|
|
18
|
+
private updateContentDisplay;
|
|
19
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
|
+
export class AssistantMessageComponent extends Container {
|
|
4
|
+
thinkingContainer;
|
|
5
|
+
contentContainer;
|
|
6
|
+
thinkingText = "";
|
|
7
|
+
contentText = "";
|
|
8
|
+
isThinkingExpanded = true;
|
|
9
|
+
isFinalized = false;
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.thinkingContainer = new Container();
|
|
13
|
+
this.contentContainer = new Container();
|
|
14
|
+
this.addChild(this.thinkingContainer);
|
|
15
|
+
this.addChild(this.contentContainer);
|
|
16
|
+
}
|
|
17
|
+
appendReasoningDelta(delta) {
|
|
18
|
+
this.thinkingText += delta;
|
|
19
|
+
this.updateThinkingDisplay();
|
|
20
|
+
}
|
|
21
|
+
setReasoning(fullThinking) {
|
|
22
|
+
if (this.thinkingText !== fullThinking) {
|
|
23
|
+
this.thinkingText = fullThinking;
|
|
24
|
+
this.updateThinkingDisplay();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
appendTextDelta(delta) {
|
|
28
|
+
if (!this.isFinalized && this.thinkingText && this.isThinkingExpanded) {
|
|
29
|
+
// 当正文开始输出时,将思考区块默认折叠以保持界面清爽整洁
|
|
30
|
+
this.isThinkingExpanded = false;
|
|
31
|
+
this.updateThinkingDisplay();
|
|
32
|
+
}
|
|
33
|
+
this.contentText += delta;
|
|
34
|
+
this.updateContentDisplay();
|
|
35
|
+
}
|
|
36
|
+
setContent(fullContent) {
|
|
37
|
+
if (!this.isFinalized && this.thinkingText && this.isThinkingExpanded) {
|
|
38
|
+
this.isThinkingExpanded = false;
|
|
39
|
+
this.updateThinkingDisplay();
|
|
40
|
+
}
|
|
41
|
+
if (this.contentText !== fullContent) {
|
|
42
|
+
this.contentText = fullContent;
|
|
43
|
+
this.updateContentDisplay();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
getContentText() {
|
|
47
|
+
return this.contentText;
|
|
48
|
+
}
|
|
49
|
+
toggleThinking() {
|
|
50
|
+
this.isThinkingExpanded = !this.isThinkingExpanded;
|
|
51
|
+
this.updateThinkingDisplay();
|
|
52
|
+
}
|
|
53
|
+
finalize() {
|
|
54
|
+
this.isFinalized = true;
|
|
55
|
+
if (this.thinkingText) {
|
|
56
|
+
this.isThinkingExpanded = false;
|
|
57
|
+
this.updateThinkingDisplay();
|
|
58
|
+
}
|
|
59
|
+
this.updateContentDisplay();
|
|
60
|
+
}
|
|
61
|
+
updateThinkingDisplay() {
|
|
62
|
+
this.thinkingContainer.clear();
|
|
63
|
+
if (!this.thinkingText.trim()) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.isThinkingExpanded) {
|
|
67
|
+
const label = new Text(theme.bold(theme.fg("thinkingText", "\u25c8 思考过程 (按 Ctrl+O 折叠):")), 1, 0);
|
|
68
|
+
const md = new Markdown(this.thinkingText.trim(), 1, 0, getMarkdownTheme(), {
|
|
69
|
+
color: (t) => theme.fg("thinkingText", t),
|
|
70
|
+
italic: true,
|
|
71
|
+
});
|
|
72
|
+
this.thinkingContainer.addChild(label);
|
|
73
|
+
this.thinkingContainer.addChild(md);
|
|
74
|
+
this.thinkingContainer.addChild(new Spacer(1));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const summary = new Text(theme.italic(theme.fg("thinkingText", `\u25c8 思考过程 (${this.thinkingText.trim().length} 字符,按 Ctrl+O 展开)`)), 1, 0);
|
|
78
|
+
this.thinkingContainer.addChild(summary);
|
|
79
|
+
this.thinkingContainer.addChild(new Spacer(1));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
updateContentDisplay() {
|
|
83
|
+
this.contentContainer.clear();
|
|
84
|
+
if (!this.contentText.trim()) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const md = new Markdown(this.contentText.trim(), 1, 0, getMarkdownTheme());
|
|
88
|
+
this.contentContainer.addChild(md);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Box } from "@earendil-works/pi-tui";
|
|
2
|
+
export interface CompactionSummaryData {
|
|
3
|
+
summary: string;
|
|
4
|
+
tokensBefore: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 渲染上下文压缩(Compaction)折叠卡片(100% 对标 Pi 原厂 CompactionSummaryMessageComponent)。
|
|
8
|
+
* 默认折叠展示:[compaction] Compacted from X tokens (Ctrl+O to expand)
|
|
9
|
+
* 按 Ctrl+O 展开时渲染完整的 Markdown 结构化摘要。
|
|
10
|
+
*/
|
|
11
|
+
export declare class CompactionSummaryMessageComponent extends Box {
|
|
12
|
+
private expanded;
|
|
13
|
+
private message;
|
|
14
|
+
constructor(message: CompactionSummaryData);
|
|
15
|
+
setExpanded(expanded: boolean): void;
|
|
16
|
+
toggleExpanded(): void;
|
|
17
|
+
invalidate(): void;
|
|
18
|
+
private updateDisplay;
|
|
19
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
|
+
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
|
+
/**
|
|
4
|
+
* 渲染上下文压缩(Compaction)折叠卡片(100% 对标 Pi 原厂 CompactionSummaryMessageComponent)。
|
|
5
|
+
* 默认折叠展示:[compaction] Compacted from X tokens (Ctrl+O to expand)
|
|
6
|
+
* 按 Ctrl+O 展开时渲染完整的 Markdown 结构化摘要。
|
|
7
|
+
*/
|
|
8
|
+
export class CompactionSummaryMessageComponent extends Box {
|
|
9
|
+
expanded = false;
|
|
10
|
+
message;
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(1, 1, (t) => theme.bg("customMessageBg", t));
|
|
13
|
+
this.message = message;
|
|
14
|
+
this.updateDisplay();
|
|
15
|
+
}
|
|
16
|
+
setExpanded(expanded) {
|
|
17
|
+
this.expanded = expanded;
|
|
18
|
+
this.updateDisplay();
|
|
19
|
+
}
|
|
20
|
+
toggleExpanded() {
|
|
21
|
+
this.expanded = !this.expanded;
|
|
22
|
+
this.updateDisplay();
|
|
23
|
+
}
|
|
24
|
+
invalidate() {
|
|
25
|
+
super.invalidate();
|
|
26
|
+
this.updateDisplay();
|
|
27
|
+
}
|
|
28
|
+
updateDisplay() {
|
|
29
|
+
this.clear();
|
|
30
|
+
const tokenStr = (this.message.tokensBefore ?? 0).toLocaleString();
|
|
31
|
+
const label = theme.fg("customMessageLabel", "\x1b[1m[compaction]\x1b[22m");
|
|
32
|
+
this.addChild(new Text(label, 0, 0));
|
|
33
|
+
this.addChild(new Spacer(1));
|
|
34
|
+
if (this.expanded) {
|
|
35
|
+
const header = `**Compacted from ${tokenStr} tokens**\n\n`;
|
|
36
|
+
this.addChild(new Markdown(header + this.message.summary, 0, 0, getMarkdownTheme(), {
|
|
37
|
+
color: (text) => theme.fg("customMessageText", text),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.addChild(new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
|
|
42
|
+
theme.fg("dim", "Ctrl+O") +
|
|
43
|
+
theme.fg("customMessageText", " to expand)"), 0, 0));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Editor } from "@earendil-works/pi-tui";
|
|
2
|
+
import { type StatusIndicator } from "./status-indicator.js";
|
|
3
|
+
export interface CustomEditorOptions {
|
|
4
|
+
embedWorkingStatus?: boolean;
|
|
5
|
+
paddingX?: number;
|
|
6
|
+
autocompleteMaxVisible?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 扩展版 Editor(100% 对标 Pi 原厂 CustomEditor):
|
|
10
|
+
* 支持在输入框顶部边框实时嵌入转圈动效:── ⠸ Working ─────────────────────────
|
|
11
|
+
*/
|
|
12
|
+
export declare class CustomEditor extends Editor {
|
|
13
|
+
embedWorkingStatus: boolean;
|
|
14
|
+
private workingStatusIndicator?;
|
|
15
|
+
constructor(tui: any, theme: any, options?: CustomEditorOptions);
|
|
16
|
+
setWorkingStatusIndicator(indicator?: StatusIndicator): void;
|
|
17
|
+
renderTopBorder(width: number, hiddenLineCount: number): string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Editor, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
/**
|
|
3
|
+
* 扩展版 Editor(100% 对标 Pi 原厂 CustomEditor):
|
|
4
|
+
* 支持在输入框顶部边框实时嵌入转圈动效:── ⠸ Working ─────────────────────────
|
|
5
|
+
*/
|
|
6
|
+
export class CustomEditor extends Editor {
|
|
7
|
+
embedWorkingStatus;
|
|
8
|
+
workingStatusIndicator;
|
|
9
|
+
constructor(tui, theme, options) {
|
|
10
|
+
super(tui, theme, options);
|
|
11
|
+
this.embedWorkingStatus = options?.embedWorkingStatus ?? true;
|
|
12
|
+
}
|
|
13
|
+
setWorkingStatusIndicator(indicator) {
|
|
14
|
+
this.workingStatusIndicator = indicator;
|
|
15
|
+
this.tui.requestRender();
|
|
16
|
+
}
|
|
17
|
+
renderTopBorder(width, hiddenLineCount) {
|
|
18
|
+
if (!this.embedWorkingStatus ||
|
|
19
|
+
!this.workingStatusIndicator ||
|
|
20
|
+
width <= 0) {
|
|
21
|
+
return super.renderTopBorder(width, hiddenLineCount);
|
|
22
|
+
}
|
|
23
|
+
let status = this.workingStatusIndicator.renderInBorder(Math.max(1, width - 5));
|
|
24
|
+
let statusWidth = visibleWidth(status);
|
|
25
|
+
if (statusWidth === 0) {
|
|
26
|
+
return super.renderTopBorder(width, hiddenLineCount);
|
|
27
|
+
}
|
|
28
|
+
const overflowLabel = hiddenLineCount > 0 ? ` ↑ ${hiddenLineCount} more ` : undefined;
|
|
29
|
+
const overflowLabelWidth = overflowLabel ? visibleWidth(overflowLabel) : 0;
|
|
30
|
+
const overflowStart = Math.floor((width - overflowLabelWidth) / 2);
|
|
31
|
+
const canFitOverflow = () => overflowLabel !== undefined &&
|
|
32
|
+
overflowLabelWidth + 2 <= width &&
|
|
33
|
+
overflowStart - (3 + statusWidth + 1) >= 1;
|
|
34
|
+
if (overflowLabel && !canFitOverflow()) {
|
|
35
|
+
status = this.workingStatusIndicator.renderSpinnerInBorder(width);
|
|
36
|
+
statusWidth = visibleWidth(status);
|
|
37
|
+
}
|
|
38
|
+
if (canFitOverflow()) {
|
|
39
|
+
const leftBlockWidth = 3 + statusWidth + 1;
|
|
40
|
+
return (this.borderColor("── ") +
|
|
41
|
+
status +
|
|
42
|
+
this.borderColor(` ${"─".repeat(overflowStart - leftBlockWidth)}${overflowLabel}${"─".repeat(Math.max(0, width - overflowStart - overflowLabelWidth))}`));
|
|
43
|
+
}
|
|
44
|
+
if (width >= statusWidth + 5) {
|
|
45
|
+
return (this.borderColor("── ") +
|
|
46
|
+
status +
|
|
47
|
+
this.borderColor(` ${"─".repeat(Math.max(0, width - statusWidth - 4))}`));
|
|
48
|
+
}
|
|
49
|
+
status = this.workingStatusIndicator.renderSpinnerInBorder(width);
|
|
50
|
+
statusWidth = visibleWidth(status);
|
|
51
|
+
const prefixWidth = Math.min(3, Math.max(0, width - statusWidth));
|
|
52
|
+
return (this.borderColor("─".repeat(prefixWidth)) +
|
|
53
|
+
status +
|
|
54
|
+
this.borderColor("─".repeat(Math.max(0, width - prefixWidth - statusWidth))));
|
|
55
|
+
}
|
|
56
|
+
}
|