zhitalk 0.0.0 → 0.0.3
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/CLAUDE.md +98 -0
- package/README.md +36 -1
- package/dist/agent/agent.d.ts +24 -0
- package/dist/agent/agent.js +345 -0
- package/dist/agent/cli.d.ts +1 -0
- package/dist/agent/cli.js +185 -0
- package/dist/agent/colors.d.ts +19 -0
- package/dist/agent/colors.js +81 -0
- package/dist/agent/commands.d.ts +7 -0
- package/dist/agent/commands.js +69 -0
- package/dist/agent/config.d.ts +38 -0
- package/dist/agent/config.js +116 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +107 -0
- package/dist/agent/db.d.ts +21 -0
- package/dist/agent/db.js +159 -0
- package/dist/agent/hooks.d.ts +27 -0
- package/dist/agent/hooks.js +115 -0
- package/dist/agent/mcp/client.d.ts +25 -0
- package/dist/agent/mcp/client.js +113 -0
- package/dist/agent/mcp/index.d.ts +4 -0
- package/dist/agent/mcp/index.js +27 -0
- package/dist/agent/mcp/wrapper.d.ts +7 -0
- package/dist/agent/mcp/wrapper.js +25 -0
- package/dist/agent/model.d.ts +5 -0
- package/dist/agent/model.js +31 -0
- package/dist/agent/permission/dangerous-path.json +115 -0
- package/dist/agent/permission/exec.d.ts +11 -0
- package/dist/agent/permission/exec.js +34 -0
- package/dist/agent/permission/is-dangerous-path.d.ts +1 -0
- package/dist/agent/permission/is-dangerous-path.js +53 -0
- package/dist/agent/permission/is-safe-domains.d.ts +1 -0
- package/dist/agent/permission/is-safe-domains.js +142 -0
- package/dist/agent/permission/network.d.ts +8 -0
- package/dist/agent/permission/network.js +14 -0
- package/dist/agent/permission/read.d.ts +9 -0
- package/dist/agent/permission/read.js +17 -0
- package/dist/agent/permission/util.d.ts +11 -0
- package/dist/agent/permission/util.js +160 -0
- package/dist/agent/permission/write.d.ts +11 -0
- package/dist/agent/permission/write.js +21 -0
- package/dist/agent/prompt.d.ts +1 -0
- package/dist/agent/prompt.js +50 -0
- package/dist/agent/skills.d.ts +8 -0
- package/dist/agent/skills.js +91 -0
- package/dist/agent/tools/agent_tool.d.ts +3 -0
- package/dist/agent/tools/agent_tool.js +44 -0
- package/dist/agent/tools/exec_tool.d.ts +3 -0
- package/dist/agent/tools/exec_tool.js +28 -0
- package/dist/agent/tools/load_skill_tool.d.ts +3 -0
- package/dist/agent/tools/load_skill_tool.js +15 -0
- package/dist/agent/tools/memory_create_tool.d.ts +6 -0
- package/dist/agent/tools/memory_create_tool.js +37 -0
- package/dist/agent/tools/memory_delete_tool.d.ts +3 -0
- package/dist/agent/tools/memory_delete_tool.js +28 -0
- package/dist/agent/tools/memory_retrieve_tool.d.ts +4 -0
- package/dist/agent/tools/memory_retrieve_tool.js +24 -0
- package/dist/agent/tools/profile_update_tool.d.ts +3 -0
- package/dist/agent/tools/profile_update_tool.js +50 -0
- package/dist/agent/tools/read_file_tool.d.ts +3 -0
- package/dist/agent/tools/read_file_tool.js +16 -0
- package/dist/agent/tools/run_js_tool.d.ts +3 -0
- package/dist/agent/tools/run_js_tool.js +36 -0
- package/dist/agent/tools/run_py_tool.d.ts +3 -0
- package/dist/agent/tools/run_py_tool.js +27 -0
- package/dist/agent/tools/search.d.ts +3 -0
- package/dist/agent/tools/search.js +10 -0
- package/dist/agent/tools/web_fetch_tool.d.ts +3 -0
- package/dist/agent/tools/web_fetch_tool.js +33 -0
- package/dist/agent/tools/web_search_tool.d.ts +3 -0
- package/dist/agent/tools/web_search_tool.js +38 -0
- package/dist/agent/tools/write_file_tool.d.ts +4 -0
- package/dist/agent/tools/write_file_tool.js +15 -0
- package/dist/agent/tools.d.ts +8 -0
- package/dist/agent/tools.js +193 -0
- package/dist/agent/utils.d.ts +2 -0
- package/dist/agent/utils.js +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +72 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +108 -0
- package/package.json +43 -6
- package/index.js +0 -1
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Zhitalk is a personal AI Agent (like OpenClaw) running in the terminal. It uses LangGraph for agent orchestration, Moonshot Kimi as the LLM, and Commander.js for CLI commands.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm dev # Run with ts-node (development)
|
|
13
|
+
pnpm test # Run tests
|
|
14
|
+
pnpm test:watch # Run tests in watch mode
|
|
15
|
+
pnpm test:coverage # Run tests with coverage
|
|
16
|
+
pnpm build # Compile TypeScript, copy skills, fix permissions
|
|
17
|
+
pnpm start # Run compiled output from dist/
|
|
18
|
+
pnpm clean # Remove dist directory
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run a single test with `pnpm test -- <pattern>`.
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
See [`src/agent/CLAUDE.md`](src/agent/CLAUDE.md) for details on the agent graph, tools, skills, memory, and session management.
|
|
26
|
+
|
|
27
|
+
## TypeScript Config
|
|
28
|
+
|
|
29
|
+
Module type is `commonjs`, output goes to `dist/`. The build script also copies `src/agent/skills/` to `dist/agent/skills/` and makes the CLI executable.
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
Jest with ts-jest preset. Test files match `**/*.test.ts`. Run a single test with `pnpm test -- <pattern>`.
|
|
34
|
+
|
|
35
|
+
## behavior rules
|
|
36
|
+
|
|
37
|
+
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
|
38
|
+
|
|
39
|
+
### 1. Think Before Coding
|
|
40
|
+
|
|
41
|
+
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
|
42
|
+
|
|
43
|
+
Before implementing:
|
|
44
|
+
|
|
45
|
+
- State your assumptions explicitly. If uncertain, ask.
|
|
46
|
+
- If multiple interpretations exist, present them - don't pick silently.
|
|
47
|
+
- If a simpler approach exists, say so. Push back when warranted.
|
|
48
|
+
- If something is unclear, stop. Name what's confusing. Ask.
|
|
49
|
+
|
|
50
|
+
### 2. Simplicity First
|
|
51
|
+
|
|
52
|
+
**Minimum code that solves the problem. Nothing speculative.**
|
|
53
|
+
|
|
54
|
+
- No features beyond what was asked.
|
|
55
|
+
- No abstractions for single-use code.
|
|
56
|
+
- No "flexibility" or "configurability" that wasn't requested.
|
|
57
|
+
- No error handling for impossible scenarios.
|
|
58
|
+
- If you write 200 lines and it could be 50, rewrite it.
|
|
59
|
+
|
|
60
|
+
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
|
61
|
+
|
|
62
|
+
### 3. Surgical Changes
|
|
63
|
+
|
|
64
|
+
**Touch only what you must. Clean up only your own mess.**
|
|
65
|
+
|
|
66
|
+
When editing existing code:
|
|
67
|
+
|
|
68
|
+
- Don't "improve" adjacent code, comments, or formatting.
|
|
69
|
+
- Don't refactor things that aren't broken.
|
|
70
|
+
- Match existing style, even if you'd do it differently.
|
|
71
|
+
- If you notice unrelated dead code, mention it - don't delete it.
|
|
72
|
+
|
|
73
|
+
When your changes create orphans:
|
|
74
|
+
|
|
75
|
+
- Remove imports/variables/functions that YOUR changes made unused.
|
|
76
|
+
- Don't remove pre-existing dead code unless asked.
|
|
77
|
+
|
|
78
|
+
The test: Every changed line should trace directly to the user's request.
|
|
79
|
+
|
|
80
|
+
### 4. Goal-Driven Execution
|
|
81
|
+
|
|
82
|
+
**Define success criteria. Loop until verified.**
|
|
83
|
+
|
|
84
|
+
Transform tasks into verifiable goals:
|
|
85
|
+
|
|
86
|
+
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
|
87
|
+
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
|
88
|
+
- "Refactor X" → "Ensure tests pass before and after"
|
|
89
|
+
|
|
90
|
+
For multi-step tasks, state a brief plan:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
1. [Step] → verify: [check]
|
|
94
|
+
2. [Step] → verify: [check]
|
|
95
|
+
3. [Step] → verify: [check]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
package/README.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
1
|
# Zhitalk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A personal AI Agent like OpenClaw, including tools, skills, memory, hook, sub-agent, MCP sever, etc. Run in the terminal.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Docs https://zhitalk.chat/
|
|
8
|
+
|
|
9
|
+
Install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm install zhitalk -g
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Run in terminal
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
zhitalk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Dev
|
|
22
|
+
|
|
23
|
+
### Tech
|
|
24
|
+
|
|
25
|
+
- runtime: nodejs
|
|
26
|
+
- language: typescript
|
|
27
|
+
- package tool: pnpm
|
|
28
|
+
- agent framework: langGraph
|
|
29
|
+
- CLI tool: commander.js
|
|
30
|
+
- database: sqlite
|
|
31
|
+
|
|
32
|
+
### Command
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm dev
|
|
36
|
+
pnpm test
|
|
37
|
+
pnpm build
|
|
38
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type UsageMetadata } from '@langchain/core/messages';
|
|
2
|
+
export declare function initAgent(): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* 以流式方式运行 agent,将 token 逐个回调给调用方
|
|
5
|
+
* @param {string} userMessage - 当前用户输入(历史已由 checkpointer 自动续接)
|
|
6
|
+
* @param {Function} onToken - 每个 token 到来时的回调 (token: string) => void
|
|
7
|
+
* @param {Function} onToolConfirmation - tool 调用前的确认回调,返回 true 则执行,false 则拒绝
|
|
8
|
+
* @param {string} threadId - 会话 ID,相同 ID 自动续上历史记录
|
|
9
|
+
* @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>} 完整的 AI 回复文本及 token 使用信息
|
|
10
|
+
*/
|
|
11
|
+
export declare function runAgentStream(userMessage: string, onToken: (token: string) => void, onToolConfirmation: (toolCalls: any[]) => Promise<boolean>, threadId?: string, signal?: AbortSignal): Promise<{
|
|
12
|
+
response: string;
|
|
13
|
+
usageMetadata?: UsageMetadata;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* 启动一个 subagent 执行独立任务,完成后返回结果
|
|
17
|
+
* @param {string} prompt - 给 subagent 的任务提示
|
|
18
|
+
* @returns {Promise<string>} subagent 的最终回复
|
|
19
|
+
*/
|
|
20
|
+
export declare function runSubAgent(prompt: string): Promise<string>;
|
|
21
|
+
export declare function compressContext(threadId: string): Promise<{
|
|
22
|
+
didCompress: boolean;
|
|
23
|
+
count: number;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initAgent = initAgent;
|
|
4
|
+
exports.runAgentStream = runAgentStream;
|
|
5
|
+
exports.runSubAgent = runSubAgent;
|
|
6
|
+
exports.compressContext = compressContext;
|
|
7
|
+
const langgraph_checkpoint_sqlite_1 = require("@langchain/langgraph-checkpoint-sqlite");
|
|
8
|
+
const messages_1 = require("@langchain/core/messages");
|
|
9
|
+
const langgraph_1 = require("@langchain/langgraph");
|
|
10
|
+
const langgraph_2 = require("@langchain/langgraph");
|
|
11
|
+
const db_1 = require("./db");
|
|
12
|
+
const tools_1 = require("./tools");
|
|
13
|
+
const context_1 = require("./context");
|
|
14
|
+
const prompt_1 = require("./prompt");
|
|
15
|
+
const colors_1 = require("./colors");
|
|
16
|
+
const read_1 = require("./permission/read");
|
|
17
|
+
const write_1 = require("./permission/write");
|
|
18
|
+
const exec_1 = require("./permission/exec");
|
|
19
|
+
const network_1 = require("./permission/network");
|
|
20
|
+
const hooks_1 = require("./hooks");
|
|
21
|
+
const model_1 = require("./model");
|
|
22
|
+
// ── 模型 ──────────────────────────────────────────────────
|
|
23
|
+
const model = (0, model_1.createModel)({ streaming: true });
|
|
24
|
+
// ── State Schema ──────────────────────────────────────────
|
|
25
|
+
const StateAnnotation = langgraph_1.Annotation.Root({
|
|
26
|
+
messages: (0, langgraph_1.Annotation)({
|
|
27
|
+
reducer: langgraph_2.messagesStateReducer,
|
|
28
|
+
default: () => [],
|
|
29
|
+
}),
|
|
30
|
+
contextSummary: (0, langgraph_1.Annotation)({
|
|
31
|
+
reducer: (_prev, next) => next,
|
|
32
|
+
default: () => null,
|
|
33
|
+
}),
|
|
34
|
+
compressionCount: (0, langgraph_1.Annotation)({
|
|
35
|
+
reducer: (_prev, next) => next,
|
|
36
|
+
default: () => 0,
|
|
37
|
+
}),
|
|
38
|
+
lastCompressedIndex: (0, langgraph_1.Annotation)({
|
|
39
|
+
reducer: (_prev, next) => next,
|
|
40
|
+
default: () => 0,
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
// ── Graph Nodes ───────────────────────────────────────────
|
|
44
|
+
function shouldContinue(state) {
|
|
45
|
+
const lastMessage = state.messages[state.messages.length - 1];
|
|
46
|
+
if (messages_1.AIMessage.isInstance(lastMessage) && lastMessage.tool_calls?.length) {
|
|
47
|
+
return 'tools';
|
|
48
|
+
}
|
|
49
|
+
return langgraph_1.END;
|
|
50
|
+
}
|
|
51
|
+
function simplifyToolMessages(messages) {
|
|
52
|
+
const toolIndices = [];
|
|
53
|
+
for (let i = 0; i < messages.length; i++) {
|
|
54
|
+
if (messages[i].type === 'tool') {
|
|
55
|
+
toolIndices.push(i);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const recentKeep = 6;
|
|
59
|
+
const recentToolIndices = new Set(toolIndices.slice(0 - recentKeep));
|
|
60
|
+
return messages.map((msg, i) => {
|
|
61
|
+
if (msg.type !== 'tool')
|
|
62
|
+
return msg;
|
|
63
|
+
if (recentToolIndices.has(i))
|
|
64
|
+
return msg;
|
|
65
|
+
const toolMsg = msg;
|
|
66
|
+
if (toolMsg.name === 'read_file')
|
|
67
|
+
return msg;
|
|
68
|
+
return new messages_1.ToolMessage({
|
|
69
|
+
content: `[Previous: used ${toolMsg.name}]`,
|
|
70
|
+
tool_call_id: toolMsg.tool_call_id,
|
|
71
|
+
name: toolMsg.name,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// ── 记忆 ──────────────────────────────────────────────────
|
|
76
|
+
const checkpointer = langgraph_checkpoint_sqlite_1.SqliteSaver.fromConnString(db_1.DB_PATH);
|
|
77
|
+
// ── Agent Graph 工厂 ──────────────────────────────────────
|
|
78
|
+
function createAgentGraph(toolList) {
|
|
79
|
+
const modelWithTheseTools = model.bindTools(toolList);
|
|
80
|
+
async function modelRequest(state, config) {
|
|
81
|
+
let modelMessages = state.messages ?? [];
|
|
82
|
+
// compact context
|
|
83
|
+
if (state.contextSummary && state.lastCompressedIndex > 0) {
|
|
84
|
+
const summaryMsg = new messages_1.SystemMessage(`历史对话摘要:\n\n${state.contextSummary}`);
|
|
85
|
+
modelMessages = [
|
|
86
|
+
summaryMsg,
|
|
87
|
+
...modelMessages.slice(state.lastCompressedIndex),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
// simplify tool messages
|
|
91
|
+
modelMessages = simplifyToolMessages(modelMessages);
|
|
92
|
+
// keep recent 300 items
|
|
93
|
+
modelMessages = modelMessages.slice(0, 300);
|
|
94
|
+
const messages = [new messages_1.SystemMessage(prompt_1.systemPrompt), ...modelMessages];
|
|
95
|
+
const response = await modelWithTheseTools.invoke(messages, config);
|
|
96
|
+
return { messages: [response] };
|
|
97
|
+
}
|
|
98
|
+
async function toolNode(state, config) {
|
|
99
|
+
const messages = state.messages;
|
|
100
|
+
const toolMessageIds = new Set(messages
|
|
101
|
+
.filter((msg) => msg.type === 'tool')
|
|
102
|
+
.map((msg) => msg.tool_call_id));
|
|
103
|
+
let aiMessage;
|
|
104
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
105
|
+
if (messages_1.AIMessage.isInstance(messages[i])) {
|
|
106
|
+
aiMessage = messages[i];
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!aiMessage || !messages_1.AIMessage.isInstance(aiMessage)) {
|
|
111
|
+
throw new Error('ToolNode only accepts AIMessages as input.');
|
|
112
|
+
}
|
|
113
|
+
const toolCalls = aiMessage.tool_calls?.filter((call) => call.id == null || !toolMessageIds.has(call.id)) ?? [];
|
|
114
|
+
if (toolCalls.length === 0) {
|
|
115
|
+
return { messages: [] };
|
|
116
|
+
}
|
|
117
|
+
const allowCalls = [];
|
|
118
|
+
const blockMessages = [];
|
|
119
|
+
const confirmCalls = [];
|
|
120
|
+
for (const call of toolCalls) {
|
|
121
|
+
const tool = toolList.find((t) => t.name === call.name);
|
|
122
|
+
const level = tool?.permission_level;
|
|
123
|
+
let decision;
|
|
124
|
+
if (level === 'read') {
|
|
125
|
+
decision = (0, read_1.checkReadPermission)(call);
|
|
126
|
+
}
|
|
127
|
+
else if (level === 'write') {
|
|
128
|
+
decision = (0, write_1.checkWritePermission)(call);
|
|
129
|
+
}
|
|
130
|
+
else if (level === 'exec') {
|
|
131
|
+
decision = (0, exec_1.checkExecPermission)(call);
|
|
132
|
+
}
|
|
133
|
+
else if (level === 'network') {
|
|
134
|
+
decision = (0, network_1.checkNetworkPermission)(call);
|
|
135
|
+
}
|
|
136
|
+
else if (level === 'mcp') {
|
|
137
|
+
decision = { action: 'confirm' };
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
decision = { action: 'allow' };
|
|
141
|
+
}
|
|
142
|
+
if (decision.action === 'allow') {
|
|
143
|
+
allowCalls.push(call);
|
|
144
|
+
}
|
|
145
|
+
else if (decision.action === 'block') {
|
|
146
|
+
blockMessages.push(new messages_1.ToolMessage({
|
|
147
|
+
content: decision.reason,
|
|
148
|
+
tool_call_id: call.id ?? '',
|
|
149
|
+
name: call.name,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
confirmCalls.push(call);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function executeCalls(calls) {
|
|
157
|
+
return Promise.all(calls.map(async (call) => {
|
|
158
|
+
const tool = toolList.find((t) => t.name === call.name);
|
|
159
|
+
console.log((0, colors_1.formatToolLog)(call.name, JSON.stringify(call.args)));
|
|
160
|
+
try {
|
|
161
|
+
const threadId = config?.configurable?.thread_id || '';
|
|
162
|
+
// PreToolUse hook
|
|
163
|
+
const preResult = await (0, hooks_1.runPreToolUseHooks)({
|
|
164
|
+
toolName: call.name,
|
|
165
|
+
toolArgs: call.args,
|
|
166
|
+
toolCallId: call.id ?? '',
|
|
167
|
+
threadId,
|
|
168
|
+
});
|
|
169
|
+
if (preResult.action === 'block') {
|
|
170
|
+
return new messages_1.ToolMessage({
|
|
171
|
+
content: preResult.reason,
|
|
172
|
+
tool_call_id: call.id ?? '',
|
|
173
|
+
name: call.name,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!tool)
|
|
177
|
+
throw new Error(`Tool "${call.name}" not found.`);
|
|
178
|
+
const output = await tool.invoke({ ...call, type: 'tool_call' }, config);
|
|
179
|
+
let content = typeof output === 'string' ? output : JSON.stringify(output);
|
|
180
|
+
// PostToolUse hook
|
|
181
|
+
const postResult = await (0, hooks_1.runPostToolUseHooks)({
|
|
182
|
+
toolName: call.name,
|
|
183
|
+
toolArgs: call.args,
|
|
184
|
+
toolOutput: content,
|
|
185
|
+
toolCallId: call.id ?? '',
|
|
186
|
+
threadId,
|
|
187
|
+
});
|
|
188
|
+
if (postResult.action === 'block') {
|
|
189
|
+
content = postResult.reason;
|
|
190
|
+
}
|
|
191
|
+
else if (postResult.action === 'inject') {
|
|
192
|
+
content = `[Hook injection]\n${postResult.message}\n\n${content}`;
|
|
193
|
+
}
|
|
194
|
+
if (preResult.action === 'inject') {
|
|
195
|
+
content = `[Hook injection]\n${preResult.message}\n\n${content}`;
|
|
196
|
+
}
|
|
197
|
+
const finalContent = await (0, tools_1.maybePersistedOutput)(content, call.id ?? '');
|
|
198
|
+
return new messages_1.ToolMessage({
|
|
199
|
+
content: finalContent,
|
|
200
|
+
tool_call_id: call.id ?? '',
|
|
201
|
+
name: call.name,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
return new messages_1.ToolMessage({
|
|
206
|
+
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
207
|
+
tool_call_id: call.id ?? '',
|
|
208
|
+
name: call.name,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
const allowOutputs = await executeCalls(allowCalls);
|
|
214
|
+
if (confirmCalls.length === 0) {
|
|
215
|
+
return { messages: [...allowOutputs, ...blockMessages] };
|
|
216
|
+
}
|
|
217
|
+
const result = (0, langgraph_1.interrupt)({ toolCalls: confirmCalls });
|
|
218
|
+
if (result !== 'approved') {
|
|
219
|
+
const deniedMessages = confirmCalls.map((call) => new messages_1.ToolMessage({
|
|
220
|
+
content: 'Tool execution was denied by the user.',
|
|
221
|
+
tool_call_id: call.id ?? '',
|
|
222
|
+
name: call.name,
|
|
223
|
+
}));
|
|
224
|
+
return {
|
|
225
|
+
messages: [...allowOutputs, ...blockMessages, ...deniedMessages],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const confirmOutputs = await executeCalls(confirmCalls);
|
|
229
|
+
return { messages: [...allowOutputs, ...blockMessages, ...confirmOutputs] };
|
|
230
|
+
}
|
|
231
|
+
const workflow = new langgraph_1.StateGraph(StateAnnotation)
|
|
232
|
+
.addNode('model_request', modelRequest)
|
|
233
|
+
.addNode('tools', toolNode)
|
|
234
|
+
.addEdge(langgraph_1.START, 'model_request')
|
|
235
|
+
.addConditionalEdges('model_request', shouldContinue, {
|
|
236
|
+
tools: 'tools',
|
|
237
|
+
[langgraph_1.END]: langgraph_1.END,
|
|
238
|
+
})
|
|
239
|
+
.addEdge('tools', 'model_request');
|
|
240
|
+
return workflow.compile({
|
|
241
|
+
checkpointer,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// ── Agent 创建 ────────────────────────────────────────────
|
|
245
|
+
let agent = null;
|
|
246
|
+
let subAgent = null;
|
|
247
|
+
async function initAgent() {
|
|
248
|
+
await (0, tools_1.initTools)();
|
|
249
|
+
agent = createAgentGraph(tools_1.tools);
|
|
250
|
+
subAgent = createAgentGraph(tools_1.tools.filter((t) => t.name !== 'agent_tool'));
|
|
251
|
+
}
|
|
252
|
+
function getAgent() {
|
|
253
|
+
if (!agent)
|
|
254
|
+
throw new Error('Agent not initialized. Call initAgent() first.');
|
|
255
|
+
return agent;
|
|
256
|
+
}
|
|
257
|
+
function getSubAgent() {
|
|
258
|
+
if (!subAgent)
|
|
259
|
+
throw new Error('Agent not initialized. Call initAgent() first.');
|
|
260
|
+
return subAgent;
|
|
261
|
+
}
|
|
262
|
+
// ── 核心运行逻辑 ──────────────────────────────────────────
|
|
263
|
+
async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation, threadId, signal) {
|
|
264
|
+
const config = { configurable: { thread_id: threadId } };
|
|
265
|
+
let fullResponse = '';
|
|
266
|
+
let usageMetadata;
|
|
267
|
+
let input = { messages: [new messages_1.HumanMessage(userMessage)] };
|
|
268
|
+
while (true) {
|
|
269
|
+
const stream = await compiledAgent.stream(input, {
|
|
270
|
+
...config,
|
|
271
|
+
streamMode: 'messages',
|
|
272
|
+
signal,
|
|
273
|
+
});
|
|
274
|
+
for await (const chunk of stream) {
|
|
275
|
+
if (signal?.aborted) {
|
|
276
|
+
throw new Error('abort');
|
|
277
|
+
}
|
|
278
|
+
const message = chunk[0];
|
|
279
|
+
const metadata = chunk[1];
|
|
280
|
+
if (metadata?.langgraph_node !== 'model_request')
|
|
281
|
+
continue;
|
|
282
|
+
const msgUsage = message.usage_metadata;
|
|
283
|
+
if (msgUsage) {
|
|
284
|
+
usageMetadata = msgUsage;
|
|
285
|
+
}
|
|
286
|
+
// AIMessageChunk 的 content 在 message.content 属性上,不在 kwargs.content
|
|
287
|
+
const content = message.content ?? message.kwargs?.content ?? '';
|
|
288
|
+
const toolCallChunks = message.tool_call_chunks ?? [];
|
|
289
|
+
if (!content || toolCallChunks.length > 0)
|
|
290
|
+
continue;
|
|
291
|
+
onToken(content);
|
|
292
|
+
fullResponse += content;
|
|
293
|
+
}
|
|
294
|
+
const state = await compiledAgent.getState(config);
|
|
295
|
+
const interruptedTask = state.tasks?.find((t) => t.interrupts?.length > 0);
|
|
296
|
+
if (!interruptedTask)
|
|
297
|
+
break;
|
|
298
|
+
const confirmed = await onToolConfirmation(interruptedTask.interrupts[0].value.toolCalls);
|
|
299
|
+
input = new langgraph_1.Command({ resume: confirmed ? 'approved' : 'denied' });
|
|
300
|
+
}
|
|
301
|
+
return { response: fullResponse, usageMetadata };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 以流式方式运行 agent,将 token 逐个回调给调用方
|
|
305
|
+
* @param {string} userMessage - 当前用户输入(历史已由 checkpointer 自动续接)
|
|
306
|
+
* @param {Function} onToken - 每个 token 到来时的回调 (token: string) => void
|
|
307
|
+
* @param {Function} onToolConfirmation - tool 调用前的确认回调,返回 true 则执行,false 则拒绝
|
|
308
|
+
* @param {string} threadId - 会话 ID,相同 ID 自动续上历史记录
|
|
309
|
+
* @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>} 完整的 AI 回复文本及 token 使用信息
|
|
310
|
+
*/
|
|
311
|
+
async function runAgentStream(userMessage, onToken, onToolConfirmation, threadId = 'default-session', signal) {
|
|
312
|
+
return _runAgent(getAgent(), userMessage, onToken, onToolConfirmation, threadId, signal);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 启动一个 subagent 执行独立任务,完成后返回结果
|
|
316
|
+
* @param {string} prompt - 给 subagent 的任务提示
|
|
317
|
+
* @returns {Promise<string>} subagent 的最终回复
|
|
318
|
+
*/
|
|
319
|
+
async function runSubAgent(prompt) {
|
|
320
|
+
const threadId = `subagent-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
321
|
+
const result = await _runAgent(getSubAgent(), prompt.trim(), () => { }, // 不需要流式输出
|
|
322
|
+
async () => true, // subagent 自动确认 tools
|
|
323
|
+
threadId, undefined);
|
|
324
|
+
return result.response;
|
|
325
|
+
}
|
|
326
|
+
async function compressContext(threadId) {
|
|
327
|
+
const config = { configurable: { thread_id: threadId } };
|
|
328
|
+
const currentState = await getAgent().getState(config);
|
|
329
|
+
const messages = currentState.values.messages || [];
|
|
330
|
+
const existingSummary = currentState.values.contextSummary || null;
|
|
331
|
+
const lastIndex = currentState.values.lastCompressedIndex || 0;
|
|
332
|
+
const count = currentState.values.compressionCount || 0;
|
|
333
|
+
const recentKeep = 6;
|
|
334
|
+
if (messages.length <= recentKeep + lastIndex) {
|
|
335
|
+
return { didCompress: false, count };
|
|
336
|
+
}
|
|
337
|
+
const toCompress = messages.slice(lastIndex, messages.length - recentKeep);
|
|
338
|
+
const newSummary = await (0, context_1.compressMessages)(toCompress, existingSummary);
|
|
339
|
+
await getAgent().updateState(config, {
|
|
340
|
+
contextSummary: newSummary,
|
|
341
|
+
lastCompressedIndex: messages.length - recentKeep,
|
|
342
|
+
compressionCount: count + 1,
|
|
343
|
+
});
|
|
344
|
+
return { didCompress: true, count: count + 1 };
|
|
345
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function interactiveChat(): Promise<void>;
|