zhitalk 0.1.1 → 0.1.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 +35 -23
- package/README.md +10 -0
- package/dist/agent/agent.d.ts +56 -1
- package/dist/agent/agent.js +20 -6
- package/dist/agent/context.js +4 -2
- package/dist/agent/prompt.js +19 -14
- package/dist/agent/skills.d.ts +2 -1
- package/dist/agent/skills.js +6 -2
- package/dist/agent/testing/fake_model.d.ts +26 -0
- package/dist/agent/testing/fake_model.js +48 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +96 -26
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -1,38 +1,57 @@
|
|
|
1
1
|
# CLAUDE.md
|
|
2
2
|
|
|
3
|
-
This file provides guidance to Claude Code
|
|
3
|
+
This file provides guidance to Claude Code when working with code in this repository.
|
|
4
4
|
|
|
5
5
|
## Project Overview
|
|
6
6
|
|
|
7
|
-
Zhitalk is a
|
|
7
|
+
Zhitalk is a terminal AI Agent using LangGraph for agent orchestration, OpenAI-compatible APIs (default Moonshot Kimi), and Commander.js. Node.js >=22 is required.
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pnpm dev #
|
|
13
|
-
pnpm test #
|
|
14
|
-
pnpm test:watch #
|
|
15
|
-
pnpm test:coverage
|
|
16
|
-
pnpm build # Compile
|
|
17
|
-
pnpm start # Run
|
|
18
|
-
pnpm clean # Remove dist
|
|
12
|
+
pnpm dev # ts-node/esm development
|
|
13
|
+
pnpm test # Jest with experimental-vm-modules
|
|
14
|
+
pnpm test:watch # Watch mode
|
|
15
|
+
pnpm test:coverage
|
|
16
|
+
pnpm build # Compile to dist/
|
|
17
|
+
pnpm start # Run dist/
|
|
18
|
+
pnpm clean # Remove dist/
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
Run a single test
|
|
21
|
+
Run a single test: `pnpm test -- <pattern>`.
|
|
22
22
|
|
|
23
23
|
## Architecture
|
|
24
24
|
|
|
25
|
-
See [`src/agent/CLAUDE.md`](src/agent/CLAUDE.md) for
|
|
25
|
+
See [`src/agent/CLAUDE.md`](src/agent/CLAUDE.md) for the agent graph, tools, skills, memory, hooks, sub-agents, MCP, and session management.
|
|
26
|
+
|
|
27
|
+
Entry point `src/index.ts` parses CLI args, runs `src/install.ts` for first-time setup, then launches `interactiveChat()` from `src/agent/cli.ts`.
|
|
28
|
+
|
|
29
|
+
### CLI Modes
|
|
30
|
+
|
|
31
|
+
- **Interactive** (default): `zhitalk` launches a REPL with streaming output, ESC cancellation, and slash commands (`/new`, `/rewind`, `/sessions`, `/compact`).
|
|
32
|
+
- **Single-shot**: `zhitalk run <query> [options]` executes one turn and exits:
|
|
33
|
+
- `-a, --auto-approve` — auto-approve all tool calls (non-interactive)
|
|
34
|
+
- `-j, --json` — output JSON with `response`, `usage`, `thread_id`
|
|
35
|
+
- `-t, --thread-id <id>` — reuse a specific session
|
|
36
|
+
|
|
37
|
+
### Agent APIs
|
|
38
|
+
|
|
39
|
+
- `runAgentStream(userMessage, onToken, onToolConfirmation, threadId)` — interactive streaming with per-tool confirmation callbacks.
|
|
40
|
+
- `runAgent(userMessage, threadId?, { autoApprove?, signal? })` — non-streaming, terminal-free API. Used by the `run` CLI command and by tests.
|
|
26
41
|
|
|
27
42
|
## TypeScript Config
|
|
28
43
|
|
|
29
|
-
|
|
44
|
+
ES modules (`"type": "module"`), output goes to `dist/`. The build script fixes CLI executable permissions.
|
|
30
45
|
|
|
31
46
|
## Testing
|
|
32
47
|
|
|
33
|
-
Jest with ts-jest preset
|
|
48
|
+
Jest with ts-jest preset and `--experimental-vm-modules`. Test files match `**/*.test.ts`.
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
- `FakeChatOpenAI` (`src/agent/testing/fake_model.ts`) replays scripted message sequences for testing the agent loop without calling real LLM APIs.
|
|
51
|
+
- `createAgentGraph(nativeTools, customModel?)` accepts an optional model for testing.
|
|
52
|
+
- Existing tests favor integration style: real temp files, real SQLite, mocked `fetch` for network tools.
|
|
53
|
+
|
|
54
|
+
## Behavior Rules
|
|
36
55
|
|
|
37
56
|
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
|
38
57
|
|
|
@@ -43,7 +62,7 @@ Jest with ts-jest preset. Test files match `**/*.test.ts`. Run a single test wit
|
|
|
43
62
|
Before implementing:
|
|
44
63
|
|
|
45
64
|
- State your assumptions explicitly. If uncertain, ask.
|
|
46
|
-
- If multiple interpretations exist, present them
|
|
65
|
+
- If multiple interpretations exist, present them — don't pick silently.
|
|
47
66
|
- If a simpler approach exists, say so. Push back when warranted.
|
|
48
67
|
- If something is unclear, stop. Name what's confusing. Ask.
|
|
49
68
|
|
|
@@ -57,8 +76,6 @@ Before implementing:
|
|
|
57
76
|
- No error handling for impossible scenarios.
|
|
58
77
|
- If you write 200 lines and it could be 50, rewrite it.
|
|
59
78
|
|
|
60
|
-
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
|
61
|
-
|
|
62
79
|
### 3. Surgical Changes
|
|
63
80
|
|
|
64
81
|
**Touch only what you must. Clean up only your own mess.**
|
|
@@ -68,12 +85,7 @@ When editing existing code:
|
|
|
68
85
|
- Don't "improve" adjacent code, comments, or formatting.
|
|
69
86
|
- Don't refactor things that aren't broken.
|
|
70
87
|
- Match existing style, even if you'd do it differently.
|
|
71
|
-
-
|
|
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.
|
|
88
|
+
- When your changes create orphans, remove imports/variables/functions that YOUR changes made unused.
|
|
77
89
|
|
|
78
90
|
The test: Every changed line should trace directly to the user's request.
|
|
79
91
|
|
package/README.md
CHANGED
|
@@ -42,7 +42,17 @@ zhitalk
|
|
|
42
42
|
### Command
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
+
# 开发
|
|
45
46
|
pnpm dev
|
|
46
47
|
pnpm test
|
|
47
48
|
pnpm build
|
|
49
|
+
|
|
50
|
+
# 交互模式(默认)
|
|
51
|
+
zhitalk
|
|
52
|
+
|
|
53
|
+
# 单次命令模式(执行一次即退出)
|
|
54
|
+
zhitalk run "帮我查一下北京天气"
|
|
55
|
+
zhitalk run "帮我查一下北京天气" --auto-approve # 自动批准 tools
|
|
56
|
+
zhitalk run "帮我查一下北京天气" --json # JSON 输出
|
|
57
|
+
zhitalk run "继续" --thread-id xxx # 指定会话 ID
|
|
48
58
|
```
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -1,4 +1,43 @@
|
|
|
1
|
-
import { type UsageMetadata } from '@langchain/core/messages';
|
|
1
|
+
import { type UsageMetadata, ToolMessage } from '@langchain/core/messages';
|
|
2
|
+
import { CompiledStateGraph } from '@langchain/langgraph';
|
|
3
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
4
|
+
import { tools } from './tools.js';
|
|
5
|
+
export declare function createAgentGraph(toolList: typeof tools, customModel?: any): CompiledStateGraph<{
|
|
6
|
+
messages: BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[];
|
|
7
|
+
contextSummary: string | null;
|
|
8
|
+
compressionCount: number;
|
|
9
|
+
lastCompressedIndex: number;
|
|
10
|
+
todoList: import("./todo.js").TodoItem[] | null;
|
|
11
|
+
}, {
|
|
12
|
+
messages?: BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[] | import("@langchain/langgraph").OverwriteValue<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[]> | undefined;
|
|
13
|
+
contextSummary?: string | import("@langchain/langgraph").OverwriteValue<string | null> | null | undefined;
|
|
14
|
+
compressionCount?: number | import("@langchain/langgraph").OverwriteValue<number> | undefined;
|
|
15
|
+
lastCompressedIndex?: number | import("@langchain/langgraph").OverwriteValue<number> | undefined;
|
|
16
|
+
todoList?: import("./todo.js").TodoItem[] | import("@langchain/langgraph").OverwriteValue<import("./todo.js").TodoItem[] | null> | null | undefined;
|
|
17
|
+
}, "tools" | "__start__" | "model_request", {
|
|
18
|
+
messages: import("@langchain/langgraph").BaseChannel<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[], BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[] | import("@langchain/langgraph").OverwriteValue<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[]>, unknown>;
|
|
19
|
+
contextSummary: import("@langchain/langgraph").BaseChannel<string | null, string | import("@langchain/langgraph").OverwriteValue<string | null> | null, unknown>;
|
|
20
|
+
compressionCount: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
21
|
+
lastCompressedIndex: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
22
|
+
todoList: import("@langchain/langgraph").BaseChannel<import("./todo.js").TodoItem[] | null, import("./todo.js").TodoItem[] | import("@langchain/langgraph").OverwriteValue<import("./todo.js").TodoItem[] | null> | null, unknown>;
|
|
23
|
+
}, {
|
|
24
|
+
messages: import("@langchain/langgraph").BaseChannel<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[], BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[] | import("@langchain/langgraph").OverwriteValue<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[]>, unknown>;
|
|
25
|
+
contextSummary: import("@langchain/langgraph").BaseChannel<string | null, string | import("@langchain/langgraph").OverwriteValue<string | null> | null, unknown>;
|
|
26
|
+
compressionCount: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
27
|
+
lastCompressedIndex: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
|
|
28
|
+
todoList: import("@langchain/langgraph").BaseChannel<import("./todo.js").TodoItem[] | null, import("./todo.js").TodoItem[] | import("@langchain/langgraph").OverwriteValue<import("./todo.js").TodoItem[] | null> | null, unknown>;
|
|
29
|
+
}, import("@langchain/langgraph").StateDefinition, {
|
|
30
|
+
model_request: {
|
|
31
|
+
messages: any[];
|
|
32
|
+
};
|
|
33
|
+
tools: {
|
|
34
|
+
messages: never[];
|
|
35
|
+
todoList?: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
messages: ToolMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>>[];
|
|
38
|
+
todoList: import("./todo.js").TodoItem[] | null;
|
|
39
|
+
};
|
|
40
|
+
}, unknown, unknown, []>;
|
|
2
41
|
export declare function initAgent(): Promise<void>;
|
|
3
42
|
/**
|
|
4
43
|
* 以流式方式运行 agent,将 token 逐个回调给调用方
|
|
@@ -12,6 +51,22 @@ export declare function runAgentStream(userMessage: string, onToken: (token: str
|
|
|
12
51
|
response: string;
|
|
13
52
|
usageMetadata?: UsageMetadata;
|
|
14
53
|
}>;
|
|
54
|
+
/**
|
|
55
|
+
* 以非流式方式运行 agent,无需终端回调,适合脚本和测试
|
|
56
|
+
* @param {string} userMessage - 用户输入
|
|
57
|
+
* @param {string} threadId - 会话 ID
|
|
58
|
+
* @param {Object} options - 选项
|
|
59
|
+
* @param {boolean} options.autoApprove - 是否自动批准所有 tool 调用
|
|
60
|
+
* @param {AbortSignal} options.signal - 取消信号
|
|
61
|
+
* @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>}
|
|
62
|
+
*/
|
|
63
|
+
export declare function runAgent(userMessage: string, threadId?: string, options?: {
|
|
64
|
+
autoApprove?: boolean;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
response: string;
|
|
68
|
+
usageMetadata?: UsageMetadata;
|
|
69
|
+
}>;
|
|
15
70
|
/**
|
|
16
71
|
* 启动一个 subagent 执行独立任务,完成后返回结果
|
|
17
72
|
* @param {string} prompt - 给 subagent 的任务提示
|
package/dist/agent/agent.js
CHANGED
|
@@ -14,8 +14,6 @@ import { checkExecPermission } from './permission/exec.js';
|
|
|
14
14
|
import { checkNetworkPermission } from './permission/network.js';
|
|
15
15
|
import { runPreToolUseHooks, runPostToolUseHooks } from './hooks.js';
|
|
16
16
|
import { createModel } from './model.js';
|
|
17
|
-
// ── 模型 ──────────────────────────────────────────────────
|
|
18
|
-
const model = createModel({ streaming: true });
|
|
19
17
|
// ── State Schema ──────────────────────────────────────────
|
|
20
18
|
const StateAnnotation = Annotation.Root({
|
|
21
19
|
messages: Annotation({
|
|
@@ -100,8 +98,8 @@ function ensureCompleteToolCalls(messages) {
|
|
|
100
98
|
// ── 记忆 ──────────────────────────────────────────────────
|
|
101
99
|
const checkpointer = SqliteSaver.fromConnString(DB_PATH);
|
|
102
100
|
// ── Agent Graph 工厂 ──────────────────────────────────────
|
|
103
|
-
function createAgentGraph(toolList) {
|
|
104
|
-
const modelWithTheseTools =
|
|
101
|
+
export function createAgentGraph(toolList, customModel) {
|
|
102
|
+
const modelWithTheseTools = (customModel || createModel({ streaming: true })).bindTools(toolList);
|
|
105
103
|
async function modelRequest(state, config) {
|
|
106
104
|
let modelMessages = state.messages ?? [];
|
|
107
105
|
// compact context
|
|
@@ -269,6 +267,10 @@ function createAgentGraph(toolList) {
|
|
|
269
267
|
if (confirmCalls.length === 0) {
|
|
270
268
|
return { messages: [...allowOutputs, ...blockMessages], todoList: todoResult.todoList };
|
|
271
269
|
}
|
|
270
|
+
if (config?.configurable?.auto_approve) {
|
|
271
|
+
const confirmOutputs = await executeCalls(confirmCalls);
|
|
272
|
+
return { messages: [...allowOutputs, ...blockMessages, ...confirmOutputs], todoList: todoResult.todoList };
|
|
273
|
+
}
|
|
272
274
|
const result = interrupt({ toolCalls: confirmCalls });
|
|
273
275
|
if (result !== 'approved') {
|
|
274
276
|
const deniedMessages = confirmCalls.map((call) => new ToolMessage({
|
|
@@ -316,8 +318,8 @@ function getSubAgent() {
|
|
|
316
318
|
return subAgent;
|
|
317
319
|
}
|
|
318
320
|
// ── 核心运行逻辑 ──────────────────────────────────────────
|
|
319
|
-
async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation, threadId, signal) {
|
|
320
|
-
const config = { configurable: { thread_id: threadId } };
|
|
321
|
+
async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation, threadId, signal, options) {
|
|
322
|
+
const config = { configurable: { thread_id: threadId, auto_approve: options?.autoApprove } };
|
|
321
323
|
let fullResponse = '';
|
|
322
324
|
let usageMetadata;
|
|
323
325
|
let input = { messages: [new HumanMessage(userMessage)] };
|
|
@@ -379,6 +381,18 @@ async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation
|
|
|
379
381
|
export async function runAgentStream(userMessage, onToken, onToolConfirmation, threadId = 'default-session', signal) {
|
|
380
382
|
return _runAgent(getAgent(), userMessage, onToken, onToolConfirmation, threadId, signal);
|
|
381
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* 以非流式方式运行 agent,无需终端回调,适合脚本和测试
|
|
386
|
+
* @param {string} userMessage - 用户输入
|
|
387
|
+
* @param {string} threadId - 会话 ID
|
|
388
|
+
* @param {Object} options - 选项
|
|
389
|
+
* @param {boolean} options.autoApprove - 是否自动批准所有 tool 调用
|
|
390
|
+
* @param {AbortSignal} options.signal - 取消信号
|
|
391
|
+
* @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>}
|
|
392
|
+
*/
|
|
393
|
+
export async function runAgent(userMessage, threadId = 'default-session', options) {
|
|
394
|
+
return _runAgent(getAgent(), userMessage, () => { }, async () => true, threadId, options?.signal, options);
|
|
395
|
+
}
|
|
382
396
|
/**
|
|
383
397
|
* 启动一个 subagent 执行独立任务,完成后返回结果
|
|
384
398
|
* @param {string} prompt - 给 subagent 的任务提示
|
package/dist/agent/context.js
CHANGED
|
@@ -18,8 +18,10 @@ const MODEL_CONTEXT_LIMITS = {
|
|
|
18
18
|
'deepseek-r1': 65536,
|
|
19
19
|
// MiniMax
|
|
20
20
|
'minimax-text-01': 1048576,
|
|
21
|
-
'minimax-
|
|
22
|
-
'minimax-
|
|
21
|
+
'minimax-m2': 204800,
|
|
22
|
+
'minimax-m2.7': 204800,
|
|
23
|
+
'minimax-m2.5': 204800,
|
|
24
|
+
'minimax-m3': 1000000,
|
|
23
25
|
// GLM (Zhipu)
|
|
24
26
|
'glm-4': 131072,
|
|
25
27
|
'glm-4-plus': 131072,
|
package/dist/agent/prompt.js
CHANGED
|
@@ -3,13 +3,10 @@ import path from 'path';
|
|
|
3
3
|
import { discoverSkills, getSkillsListText } from './skills.js';
|
|
4
4
|
import { WORKSPACE_DIR } from './config.js';
|
|
5
5
|
import { listRecentMemories } from './db.js';
|
|
6
|
-
// ── Skills ────────────────────────────────────────────────
|
|
7
|
-
discoverSkills();
|
|
8
|
-
const skillsText = getSkillsListText();
|
|
9
6
|
const basePrompt = 'You are zhitalk, 中文名字叫智语, a personal AI Agent like OpenClaw, including tools, skills, memory, hook, sub-agent, MCP server, etc. Run in the terminal.';
|
|
10
|
-
function readProfile() {
|
|
7
|
+
function readProfile(profilePath) {
|
|
11
8
|
try {
|
|
12
|
-
const filePath = path.join(WORKSPACE_DIR, '.data', 'profile.md');
|
|
9
|
+
const filePath = profilePath || path.join(WORKSPACE_DIR, '.data', 'profile.md');
|
|
13
10
|
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
14
11
|
return `<profile_info>${content}</profile_info>`;
|
|
15
12
|
}
|
|
@@ -17,7 +14,8 @@ function readProfile() {
|
|
|
17
14
|
return '<profile_info></profile_info>';
|
|
18
15
|
}
|
|
19
16
|
}
|
|
20
|
-
|
|
17
|
+
function getProfilePrompt() {
|
|
18
|
+
return `## User Profile
|
|
21
19
|
|
|
22
20
|
When you learn information about the user that fits the following categories, store it as profile information for future personalization.
|
|
23
21
|
|
|
@@ -32,22 +30,28 @@ When you learn information about the user that fits the following categories, st
|
|
|
32
30
|
|
|
33
31
|
Here is the user's current profile information:
|
|
34
32
|
${readProfile()}`;
|
|
33
|
+
}
|
|
35
34
|
const memoryPrompt = `## Memory Management Rules
|
|
36
35
|
|
|
37
36
|
- When deleting a memory, first use memory_retrieve to find its id, then call memory_delete with that id. If no matching memory is found, politely inform the user.
|
|
38
37
|
- When updating a memory, first delete the old memory, then create a new one.
|
|
39
38
|
- When information fits the categories in <profile_template>, do not record it as a memory. It will be stored in the profile file instead.`;
|
|
40
|
-
|
|
39
|
+
function getDateTimePrompt() {
|
|
40
|
+
return `## Current DateTime
|
|
41
41
|
|
|
42
42
|
${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`;
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
}
|
|
44
|
+
function getSkillPrompt() {
|
|
45
|
+
const skillsText = getSkillsListText();
|
|
46
|
+
const skillList = skillsText
|
|
47
|
+
? `You have access to the following skills. When a user's request matches a skill's description, you MUST call the \`load_skill\` tool to load that skill's full instructions, then follow them.
|
|
45
48
|
|
|
46
49
|
${skillsText}`
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
: 'There are no skills available.';
|
|
51
|
+
return `## Skills
|
|
49
52
|
|
|
50
53
|
${skillList}\n\nNote: If you want to add a new skill, it must be placed in the ${path.join(WORKSPACE_DIR, 'skills')} directory.`;
|
|
54
|
+
}
|
|
51
55
|
const todoPrompt = `## Task Planning
|
|
52
56
|
|
|
53
57
|
When a user's request is a complex, multi-step task (such as data analysis, writing a document, planning a task, or any task clearly requiring more than 3 steps), you MUST:
|
|
@@ -62,6 +66,7 @@ export function invalidateMemoryCache() {
|
|
|
62
66
|
cachedMemories = null;
|
|
63
67
|
}
|
|
64
68
|
export function buildSystemPrompt() {
|
|
69
|
+
discoverSkills();
|
|
65
70
|
if (!cachedMemories) {
|
|
66
71
|
cachedMemories = listRecentMemories(10);
|
|
67
72
|
}
|
|
@@ -70,11 +75,11 @@ export function buildSystemPrompt() {
|
|
|
70
75
|
: '';
|
|
71
76
|
return [
|
|
72
77
|
basePrompt,
|
|
73
|
-
|
|
78
|
+
getProfilePrompt(),
|
|
74
79
|
memorySection,
|
|
75
80
|
memoryPrompt,
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
getSkillPrompt(),
|
|
82
|
+
getDateTimePrompt(),
|
|
78
83
|
todoPrompt,
|
|
79
84
|
]
|
|
80
85
|
.filter(Boolean)
|
package/dist/agent/skills.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export interface SkillInfo {
|
|
|
3
3
|
description: string;
|
|
4
4
|
dirPath: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function discoverSkills(): SkillInfo[];
|
|
6
|
+
export declare function discoverSkills(searchPaths?: string[]): SkillInfo[];
|
|
7
7
|
export declare function loadSkill(name: string): string | null;
|
|
8
8
|
export declare function getSkillsListText(): string;
|
|
9
|
+
export declare function clearSkillsCache(): void;
|
package/dist/agent/skills.js
CHANGED
|
@@ -24,10 +24,10 @@ function parseFrontmatter(content) {
|
|
|
24
24
|
}
|
|
25
25
|
return { name: result.name || '', description: result.description || '' };
|
|
26
26
|
}
|
|
27
|
-
export function discoverSkills() {
|
|
27
|
+
export function discoverSkills(searchPaths) {
|
|
28
28
|
skills.length = 0;
|
|
29
29
|
skillContentMap.clear();
|
|
30
|
-
const skillDirs = [
|
|
30
|
+
const skillDirs = searchPaths || [
|
|
31
31
|
join(WORKSPACE_DIR, 'skills'),
|
|
32
32
|
join(process.cwd(), '.agents', 'skills'),
|
|
33
33
|
];
|
|
@@ -81,3 +81,7 @@ export function getSkillsListText() {
|
|
|
81
81
|
// console.log('skills...\n', lines.join('\n'))
|
|
82
82
|
return lines.join('\n');
|
|
83
83
|
}
|
|
84
|
+
export function clearSkillsCache() {
|
|
85
|
+
skills.length = 0;
|
|
86
|
+
skillContentMap.clear();
|
|
87
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AIMessage, type BaseMessage } from '@langchain/core/messages';
|
|
2
|
+
/**
|
|
3
|
+
* Fake LLM for testing the agent loop without calling real APIs.
|
|
4
|
+
* Replays a scripted sequence of messages on each invoke() call.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FakeChatOpenAI {
|
|
7
|
+
private responses;
|
|
8
|
+
private callIndex;
|
|
9
|
+
constructor(responses: BaseMessage[]);
|
|
10
|
+
bindTools(_tools: any[]): this;
|
|
11
|
+
invoke(_messages: BaseMessage[], _config?: any): Promise<BaseMessage>;
|
|
12
|
+
stream(_messages: BaseMessage[], _config?: any): AsyncGenerator<BaseMessage>;
|
|
13
|
+
getCallCount(): number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Helper to build an AIMessage with tool_calls.
|
|
17
|
+
*/
|
|
18
|
+
export declare function aiMessageWithTools(toolCalls: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
args: Record<string, any>;
|
|
21
|
+
id?: string;
|
|
22
|
+
}>): AIMessage;
|
|
23
|
+
/**
|
|
24
|
+
* Helper to build a simple text AIMessage.
|
|
25
|
+
*/
|
|
26
|
+
export declare function aiMessage(content: string): AIMessage;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AIMessage, } from '@langchain/core/messages';
|
|
2
|
+
/**
|
|
3
|
+
* Fake LLM for testing the agent loop without calling real APIs.
|
|
4
|
+
* Replays a scripted sequence of messages on each invoke() call.
|
|
5
|
+
*/
|
|
6
|
+
export class FakeChatOpenAI {
|
|
7
|
+
constructor(responses) {
|
|
8
|
+
this.callIndex = 0;
|
|
9
|
+
this.responses = responses;
|
|
10
|
+
}
|
|
11
|
+
bindTools(_tools) {
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
async invoke(_messages, _config) {
|
|
15
|
+
const response = this.responses[this.callIndex];
|
|
16
|
+
if (!response) {
|
|
17
|
+
throw new Error(`FakeChatOpenAI: no more responses (call ${this.callIndex})`);
|
|
18
|
+
}
|
|
19
|
+
this.callIndex++;
|
|
20
|
+
return response;
|
|
21
|
+
}
|
|
22
|
+
async *stream(_messages, _config) {
|
|
23
|
+
const response = await this.invoke(_messages, _config);
|
|
24
|
+
yield response;
|
|
25
|
+
}
|
|
26
|
+
getCallCount() {
|
|
27
|
+
return this.callIndex;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Helper to build an AIMessage with tool_calls.
|
|
32
|
+
*/
|
|
33
|
+
export function aiMessageWithTools(toolCalls) {
|
|
34
|
+
return new AIMessage({
|
|
35
|
+
content: '',
|
|
36
|
+
tool_calls: toolCalls.map((tc, i) => ({
|
|
37
|
+
name: tc.name,
|
|
38
|
+
args: tc.args,
|
|
39
|
+
id: tc.id || `fake-call-${i}`,
|
|
40
|
+
})),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Helper to build a simple text AIMessage.
|
|
45
|
+
*/
|
|
46
|
+
export function aiMessage(content) {
|
|
47
|
+
return new AIMessage({ content });
|
|
48
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export {
|
|
2
|
+
export interface RunOptions {
|
|
3
|
+
autoApprove?: boolean;
|
|
4
|
+
threadId?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runOneShot(query: string, options?: RunOptions): Promise<{
|
|
7
|
+
response: string;
|
|
8
|
+
usageMetadata?: any;
|
|
9
|
+
threadId: string;
|
|
10
|
+
}>;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { readFileSync, existsSync, realpathSync } from 'fs';
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
6
|
import { CONFIG_PATH } from './agent/config.js';
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
9
|
+
async function initApp() {
|
|
10
|
+
const { initColors } = await import('./agent/colors.js');
|
|
11
|
+
const { initDb } = await import('./agent/db.js');
|
|
12
|
+
const { initAgent } = await import('./agent/agent.js');
|
|
13
|
+
const { checkModel } = await import('./agent/model.js');
|
|
14
|
+
const { shutdownMcp } = await import('./agent/mcp/index.js');
|
|
15
|
+
await initColors();
|
|
16
|
+
await checkModel();
|
|
17
|
+
initDb();
|
|
18
|
+
await initAgent();
|
|
19
|
+
return {
|
|
20
|
+
shutdown: async () => {
|
|
21
|
+
await shutdownMcp();
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function runOneShot(query, options = {}) {
|
|
26
|
+
const threadId = options.threadId || `oneshot-${Date.now()}`;
|
|
27
|
+
const { shutdown } = await initApp();
|
|
28
|
+
try {
|
|
29
|
+
const { runAgent } = await import('./agent/agent.js');
|
|
30
|
+
const result = await runAgent(query, threadId, {
|
|
31
|
+
autoApprove: options.autoApprove,
|
|
32
|
+
});
|
|
33
|
+
return { response: result.response, usageMetadata: result.usageMetadata, threadId };
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await shutdown();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
9
39
|
async function main() {
|
|
10
40
|
const program = new Command();
|
|
11
41
|
program.name(pkg.name).description(pkg.description).version(pkg.version);
|
|
@@ -16,30 +46,70 @@ async function main() {
|
|
|
16
46
|
console.log(`配置文件位置: ${CONFIG_PATH}`);
|
|
17
47
|
console.log('请使用编辑器打开并修改配置,修改完成后重启 zhitalk');
|
|
18
48
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
49
|
+
program
|
|
50
|
+
.command('run <query>')
|
|
51
|
+
.description('Run a single query and exit')
|
|
52
|
+
.option('-a, --auto-approve', 'Auto approve all tool calls without confirmation', false)
|
|
53
|
+
.option('-j, --json', 'Output result as JSON', false)
|
|
54
|
+
.option('-t, --thread-id <id>', 'Thread ID for the session')
|
|
55
|
+
.action(async (query, options) => {
|
|
56
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
57
|
+
console.error(`找不到配置文件 ${CONFIG_PATH}`);
|
|
58
|
+
console.error('请先运行 zhitalk 进行初始化');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const result = await runOneShot(query, {
|
|
63
|
+
autoApprove: options.autoApprove,
|
|
64
|
+
threadId: options.threadId,
|
|
65
|
+
});
|
|
66
|
+
if (options.json) {
|
|
67
|
+
console.log(JSON.stringify({
|
|
68
|
+
response: result.response,
|
|
69
|
+
usage: result.usageMetadata || null,
|
|
70
|
+
thread_id: result.threadId,
|
|
71
|
+
}, null, 2));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(result.response);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`Error: ${err.message}`);
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
process.exit();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
program.action(async () => {
|
|
86
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
87
|
+
const { runInstall } = await import('./install.js');
|
|
88
|
+
await runInstall();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const { interactiveChat } = await import('./agent/cli.js');
|
|
92
|
+
const { shutdownMcp } = await import('./agent/mcp/index.js');
|
|
93
|
+
const { shutdown } = await initApp();
|
|
94
|
+
process.once('SIGINT', async () => {
|
|
95
|
+
await shutdown();
|
|
96
|
+
process.exit(0);
|
|
97
|
+
});
|
|
98
|
+
await interactiveChat();
|
|
99
|
+
await shutdown();
|
|
100
|
+
});
|
|
101
|
+
program.parse();
|
|
102
|
+
}
|
|
103
|
+
function isMainModule() {
|
|
104
|
+
try {
|
|
105
|
+
const realArgvPath = realpathSync(process.argv[1]);
|
|
106
|
+
return import.meta.url === pathToFileURL(realArgvPath).href;
|
|
22
107
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await runInstall();
|
|
26
|
-
return;
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
27
110
|
}
|
|
28
|
-
const { initColors } = await import('./agent/colors.js');
|
|
29
|
-
const { initDb } = await import('./agent/db.js');
|
|
30
|
-
const { initAgent } = await import('./agent/agent.js');
|
|
31
|
-
const { interactiveChat } = await import('./agent/cli.js');
|
|
32
|
-
const { shutdownMcp } = await import('./agent/mcp/index.js');
|
|
33
|
-
const { checkModel } = await import('./agent/model.js');
|
|
34
|
-
await initColors();
|
|
35
|
-
await checkModel();
|
|
36
|
-
initDb();
|
|
37
|
-
await initAgent();
|
|
38
|
-
process.once('SIGINT', async () => {
|
|
39
|
-
await shutdownMcp();
|
|
40
|
-
process.exit(0);
|
|
41
|
-
});
|
|
42
|
-
await interactiveChat();
|
|
43
|
-
await shutdownMcp();
|
|
44
111
|
}
|
|
45
|
-
main
|
|
112
|
+
// 仅在直接运行本文件时执行 main,避免被测试导入时自动执行
|
|
113
|
+
if (isMainModule()) {
|
|
114
|
+
main();
|
|
115
|
+
}
|
package/package.json
CHANGED