shennian 0.2.53 → 0.2.54
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/dist/src/agents/adapter.d.ts +1 -0
- package/dist/src/agents/claude.d.ts +2 -0
- package/dist/src/agents/claude.js +4 -2
- package/dist/src/agents/codex.d.ts +2 -0
- package/dist/src/agents/codex.js +3 -1
- package/dist/src/agents/external-channel-instructions.d.ts +1 -1
- package/dist/src/agents/external-channel-instructions.js +11 -2
- package/dist/src/agents/pi.d.ts +2 -0
- package/dist/src/agents/pi.js +3 -1
- package/dist/src/agents/platform-instructions.d.ts +2 -2
- package/dist/src/agents/platform-instructions.js +5 -5
- package/dist/src/commands/external.js +27 -9
- package/dist/src/manager/prompt.d.ts +1 -1
- package/dist/src/manager/prompt.js +2 -1
- package/dist/src/manager/runtime.d.ts +2 -0
- package/dist/src/manager/runtime.js +36 -0
- package/dist/src/session/handlers/chat.js +1 -0
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ export interface AgentAdapterEvents {
|
|
|
25
25
|
export declare abstract class AgentAdapter extends EventEmitter<AgentAdapterEvents> {
|
|
26
26
|
abstract readonly type: AgentType;
|
|
27
27
|
configure?(options: {
|
|
28
|
+
sessionId?: string;
|
|
28
29
|
externalChannel?: ExternalChannelSessionStatus | null;
|
|
29
30
|
env?: NodeJS.ProcessEnv;
|
|
30
31
|
}): void;
|
|
@@ -18,8 +18,10 @@ export declare class ClaudeAdapter extends AgentAdapter {
|
|
|
18
18
|
hidden?: boolean;
|
|
19
19
|
});
|
|
20
20
|
private externalChannel;
|
|
21
|
+
private shennianSessionId;
|
|
21
22
|
private extraEnv;
|
|
22
23
|
configure(options: {
|
|
24
|
+
sessionId?: string;
|
|
23
25
|
externalChannel?: ExternalChannelSessionStatus | null;
|
|
24
26
|
env?: NodeJS.ProcessEnv;
|
|
25
27
|
}): void;
|
|
@@ -33,8 +33,10 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
33
33
|
this.options = options;
|
|
34
34
|
}
|
|
35
35
|
externalChannel = null;
|
|
36
|
+
shennianSessionId = null;
|
|
36
37
|
extraEnv = {};
|
|
37
38
|
configure(options) {
|
|
39
|
+
this.shennianSessionId = options.sessionId ?? null;
|
|
38
40
|
this.externalChannel = options.externalChannel ?? null;
|
|
39
41
|
this.extraEnv = options.env ?? {};
|
|
40
42
|
}
|
|
@@ -50,7 +52,7 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
50
52
|
this.runId = randomUUID();
|
|
51
53
|
this.resetRunState();
|
|
52
54
|
const args = ['-p', text, '--output-format', 'stream-json', '--verbose'];
|
|
53
|
-
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel);
|
|
55
|
+
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
|
|
54
56
|
if (systemPrompt) {
|
|
55
57
|
args.push('--append-system-prompt', systemPrompt);
|
|
56
58
|
}
|
|
@@ -76,7 +78,7 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
76
78
|
this.runId = randomUUID();
|
|
77
79
|
this.resetRunState();
|
|
78
80
|
const resumeArgs = ['--resume', agentSessionId, '--output-format', 'stream-json', '--verbose'];
|
|
79
|
-
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel);
|
|
81
|
+
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
|
|
80
82
|
if (systemPrompt) {
|
|
81
83
|
resumeArgs.push('--append-system-prompt', systemPrompt);
|
|
82
84
|
}
|
|
@@ -27,8 +27,10 @@ export declare class CodexAdapter extends AgentAdapter {
|
|
|
27
27
|
hidden?: boolean;
|
|
28
28
|
});
|
|
29
29
|
private externalChannel;
|
|
30
|
+
private shennianSessionId;
|
|
30
31
|
private extraEnv;
|
|
31
32
|
configure(options: {
|
|
33
|
+
sessionId?: string;
|
|
32
34
|
externalChannel?: ExternalChannelSessionStatus | null;
|
|
33
35
|
env?: NodeJS.ProcessEnv;
|
|
34
36
|
}): void;
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -33,8 +33,10 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
33
33
|
this.options = options;
|
|
34
34
|
}
|
|
35
35
|
externalChannel = null;
|
|
36
|
+
shennianSessionId = null;
|
|
36
37
|
extraEnv = {};
|
|
37
38
|
configure(options) {
|
|
39
|
+
this.shennianSessionId = options.sessionId ?? null;
|
|
38
40
|
this.externalChannel = options.externalChannel ?? null;
|
|
39
41
|
this.extraEnv = options.env ?? {};
|
|
40
42
|
}
|
|
@@ -133,7 +135,7 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
133
135
|
return;
|
|
134
136
|
}
|
|
135
137
|
const args = ['app-server', '--listen', 'stdio://'];
|
|
136
|
-
const modelInstructionsFile = this.options.modelInstructionsFile ?? ensurePlatformInstructionsFile(this.workDir ?? process.cwd(), this.externalChannel);
|
|
138
|
+
const modelInstructionsFile = this.options.modelInstructionsFile ?? ensurePlatformInstructionsFile(this.workDir ?? process.cwd(), this.externalChannel, this.shennianSessionId ?? undefined);
|
|
137
139
|
if (modelInstructionsFile) {
|
|
138
140
|
args.push('-c', `model_instructions_file=${JSON.stringify(modelInstructionsFile)}`);
|
|
139
141
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
|
-
export declare function buildExternalChannelInstructions(channel?: ExternalChannelSessionStatus | null): string;
|
|
2
|
+
export declare function buildExternalChannelInstructions(channel?: ExternalChannelSessionStatus | null, workDir?: string, sessionId?: string): string;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
// @arch docs/features/wecom-managed-channel.md
|
|
2
2
|
// @test src/__tests__/platform-instructions.test.ts
|
|
3
|
-
export function buildExternalChannelInstructions(channel) {
|
|
3
|
+
export function buildExternalChannelInstructions(channel, workDir, sessionId) {
|
|
4
4
|
if (!channel?.configured && !channel?.connected)
|
|
5
5
|
return '';
|
|
6
6
|
const channelName = channel.name?.trim() || '外部消息通道';
|
|
7
7
|
const customPrompt = channel.systemPrompt?.trim();
|
|
8
|
+
const sessionHint = sessionId?.trim()
|
|
9
|
+
? `当前对话 ID:${sessionId}。如果命令提示缺少外部通道上下文,使用:shennian external send --session-id ${sessionId} --text "<要发送的消息>"`
|
|
10
|
+
: '';
|
|
11
|
+
const workdirHint = workDir?.trim()
|
|
12
|
+
? `如果使用 shell_command,请设置 workdir 为 ${workDir}。不要用裸 /bin/zsh -lc 或 command_execution 运行此命令,因为那种环境可能拿不到当前对话的外部通道身份。`
|
|
13
|
+
: '如果使用 shell_command,请设置 workdir 为当前对话的项目目录。不要用裸 /bin/zsh -lc 或 command_execution 运行此命令,因为那种环境可能拿不到当前对话的外部通道身份。';
|
|
8
14
|
const sections = [
|
|
9
15
|
`当前对话已接入外部消息通道:${channelName}。`,
|
|
10
16
|
`外部消息会以如下格式进入对话:\n外部消息\n<时间> <用户昵称>: <内容>`,
|
|
@@ -13,9 +19,12 @@ export function buildExternalChannelInstructions(channel) {
|
|
|
13
19
|
: [
|
|
14
20
|
'当用户明确要求你向外部消息通道发送内容,或你需要回复一条外部消息时,调用:',
|
|
15
21
|
'shennian external send --text "<要发送的消息>"',
|
|
22
|
+
sessionHint,
|
|
23
|
+
workdirHint,
|
|
16
24
|
'只发送用户可见的最终内容,不要发送内部推理、工具日志或实现细节。',
|
|
25
|
+
'对外消息必须像真人聊天:简短、概要、单段;不要使用 Markdown、编号列表、项目符号、真实换行或字面 \\n。',
|
|
17
26
|
].join('\n'),
|
|
18
|
-
'
|
|
27
|
+
'如果外部消息和当前任务无关,可以忽略或简短说明无需处理;如果处理需要时间,先用一句话确认,再继续完成任务。',
|
|
19
28
|
customPrompt ? `本通道附加约束:${customPrompt}` : '',
|
|
20
29
|
].filter(Boolean);
|
|
21
30
|
return sections.join('\n\n');
|
package/dist/src/agents/pi.d.ts
CHANGED
|
@@ -29,9 +29,11 @@ export declare class PiAdapter extends AgentAdapter {
|
|
|
29
29
|
private finalizePromise;
|
|
30
30
|
private sendGeneration;
|
|
31
31
|
private externalChannel;
|
|
32
|
+
private shennianSessionId;
|
|
32
33
|
private extraEnv;
|
|
33
34
|
private pendingSendStart;
|
|
34
35
|
configure(options: {
|
|
36
|
+
sessionId?: string;
|
|
35
37
|
externalChannel?: ExternalChannelSessionStatus | null;
|
|
36
38
|
env?: NodeJS.ProcessEnv;
|
|
37
39
|
}): void;
|
package/dist/src/agents/pi.js
CHANGED
|
@@ -398,9 +398,11 @@ export class PiAdapter extends AgentAdapter {
|
|
|
398
398
|
finalizePromise = Promise.resolve();
|
|
399
399
|
sendGeneration = 0;
|
|
400
400
|
externalChannel = null;
|
|
401
|
+
shennianSessionId = null;
|
|
401
402
|
extraEnv = {};
|
|
402
403
|
pendingSendStart = null;
|
|
403
404
|
configure(options) {
|
|
405
|
+
this.shennianSessionId = options.sessionId ?? null;
|
|
404
406
|
this.externalChannel = options.externalChannel ?? null;
|
|
405
407
|
this.extraEnv = options.env ?? {};
|
|
406
408
|
}
|
|
@@ -497,7 +499,7 @@ export class PiAdapter extends AgentAdapter {
|
|
|
497
499
|
systemPrompt: [
|
|
498
500
|
SYSTEM_PROMPT,
|
|
499
501
|
`当前工作目录:${workDir}`,
|
|
500
|
-
buildExternalChannelInstructions(this.externalChannel),
|
|
502
|
+
buildExternalChannelInstructions(this.externalChannel, workDir, this.shennianSessionId ?? undefined),
|
|
501
503
|
].filter(Boolean).join('\n\n'),
|
|
502
504
|
model: createPiModel(),
|
|
503
505
|
tools,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
2
|
export declare const PLATFORM_OUTPUT_INSTRUCTIONS = "## Shennian Output Instructions\n\nWhen you mention a local file that the user may want to open in Shennian, format it as a Markdown link using the exact local path:\n\n- Use [filename.ext](</absolute/path/to/filename.ext>) for absolute Unix, macOS, or Windows paths.\n- Use [filename.ext](<relative/path/to/filename.ext>) for paths relative to the current working directory.\n- Do not use file:// URLs for local files.\n- Do not put user-openable file paths only inside code blocks.\n- Keep normal http:// and https:// links unchanged.";
|
|
3
|
-
export declare function buildPlatformInstructions(workDir: string, externalChannel?: ExternalChannelSessionStatus | null): string;
|
|
4
|
-
export declare function ensurePlatformInstructionsFile(workDir: string, externalChannel?: ExternalChannelSessionStatus | null): string;
|
|
3
|
+
export declare function buildPlatformInstructions(workDir: string, externalChannel?: ExternalChannelSessionStatus | null, sessionId?: string): string;
|
|
4
|
+
export declare function ensurePlatformInstructionsFile(workDir: string, externalChannel?: ExternalChannelSessionStatus | null, sessionId?: string): string;
|
|
@@ -23,9 +23,9 @@ function readProjectAgentsMd(workDir) {
|
|
|
23
23
|
return '';
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export function buildPlatformInstructions(workDir, externalChannel) {
|
|
26
|
+
export function buildPlatformInstructions(workDir, externalChannel, sessionId) {
|
|
27
27
|
const projectInstructions = readProjectAgentsMd(workDir);
|
|
28
|
-
const channelInstructions = buildExternalChannelInstructions(externalChannel);
|
|
28
|
+
const channelInstructions = buildExternalChannelInstructions(externalChannel, path.resolve(workDir), sessionId);
|
|
29
29
|
const sections = [
|
|
30
30
|
'# Shennian Agent Instructions',
|
|
31
31
|
projectInstructions
|
|
@@ -38,13 +38,13 @@ export function buildPlatformInstructions(workDir, externalChannel) {
|
|
|
38
38
|
].filter(Boolean);
|
|
39
39
|
return `${sections.join('\n\n')}\n`;
|
|
40
40
|
}
|
|
41
|
-
export function ensurePlatformInstructionsFile(workDir, externalChannel) {
|
|
41
|
+
export function ensurePlatformInstructionsFile(workDir, externalChannel, sessionId) {
|
|
42
42
|
const key = createHash('sha256')
|
|
43
|
-
.update(`${path.resolve(workDir)}:${JSON.stringify(externalChannel ?? null)}`)
|
|
43
|
+
.update(`${path.resolve(workDir)}:${sessionId ?? ''}:${JSON.stringify(externalChannel ?? null)}`)
|
|
44
44
|
.digest('hex')
|
|
45
45
|
.slice(0, 16);
|
|
46
46
|
const filePath = resolveShennianPath('runtime', 'agent-instructions', `${key}.md`);
|
|
47
|
-
const content = buildPlatformInstructions(workDir, externalChannel);
|
|
47
|
+
const content = buildPlatformInstructions(workDir, externalChannel, sessionId);
|
|
48
48
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
49
49
|
try {
|
|
50
50
|
if (fs.readFileSync(filePath, 'utf8') === content)
|
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
// @arch docs/features/wecom-managed-channel.md
|
|
2
|
-
// @test src/__tests__/
|
|
2
|
+
// @test src/__tests__/external-command.test.ts
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import chalk from 'chalk';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { resolveShennianPath } from '../config/index.js';
|
|
6
|
+
function loadManagerIpcFromRuntimeFile() {
|
|
7
|
+
try {
|
|
8
|
+
const parsed = JSON.parse(fs.readFileSync(resolveShennianPath('runtime', 'manager-ipc.json'), 'utf-8'));
|
|
9
|
+
return {
|
|
10
|
+
url: typeof parsed.url === 'string' ? parsed.url : undefined,
|
|
11
|
+
token: typeof parsed.token === 'string' ? parsed.token : undefined,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function requireExternalContext(explicitSessionId) {
|
|
19
|
+
const runtimeIpc = !process.env.SHENNIAN_MANAGER_IPC_URL || !process.env.SHENNIAN_MANAGER_IPC_TOKEN
|
|
20
|
+
? loadManagerIpcFromRuntimeFile()
|
|
21
|
+
: {};
|
|
22
|
+
const url = process.env.SHENNIAN_MANAGER_IPC_URL || runtimeIpc.url;
|
|
23
|
+
const token = process.env.SHENNIAN_MANAGER_IPC_TOKEN || runtimeIpc.token;
|
|
24
|
+
const sessionId = explicitSessionId?.trim() || process.env.SHENNIAN_EXTERNAL_SESSION_ID || process.env.SHENNIAN_MANAGER_SESSION_ID;
|
|
8
25
|
if (!url || !token || !sessionId) {
|
|
9
|
-
console.error(chalk.red('✗ This command must run inside a Shennian conversation with an external channel
|
|
26
|
+
console.error(chalk.red('✗ This command must run inside a Shennian conversation with an external channel, or pass --session-id <id>.'));
|
|
10
27
|
process.exit(1);
|
|
11
28
|
}
|
|
12
29
|
return { url, token, sessionId };
|
|
13
30
|
}
|
|
14
|
-
async function sendExternal(text, idempotencyKey) {
|
|
15
|
-
const ctx = requireExternalContext();
|
|
31
|
+
async function sendExternal(text, idempotencyKey, sessionId) {
|
|
32
|
+
const ctx = requireExternalContext(sessionId);
|
|
16
33
|
const response = await fetch(`${ctx.url}/external/reply`, {
|
|
17
34
|
method: 'POST',
|
|
18
35
|
headers: {
|
|
@@ -38,8 +55,9 @@ export function registerExternalCommand(program) {
|
|
|
38
55
|
.command('send')
|
|
39
56
|
.description('Send a message to the external channel bound to this conversation')
|
|
40
57
|
.requiredOption('--text <text>', 'Message text')
|
|
58
|
+
.option('--session-id <id>', 'Shennian conversation/session id; defaults to injected current-session env')
|
|
41
59
|
.option('--idempotency-key <key>', 'Idempotency key')
|
|
42
60
|
.action(async (opts) => {
|
|
43
|
-
await sendExternal(opts.text, opts.idempotencyKey);
|
|
61
|
+
await sendExternal(opts.text, opts.idempotencyKey, opts.sessionId);
|
|
44
62
|
});
|
|
45
63
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const MANAGER_SYSTEM_PROMPT = "\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76EE\u7684\u7BA1\u7406\u8005\u3002\n\n\u4F60\u7684\u804C\u8D23\uFF1A\n- \u7406\u89E3\u7528\u6237\u76EE\u6807\u3002\n- \u62C6\u89E3\u4EFB\u52A1\u3002\n- \u521B\u5EFA\u3001\u6307\u6D3E\u3001\u89C2\u5BDF\u548C\u505C\u6B62\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u4E0B\u7684 worker Agent session\u3002\n- \u6C47\u603B worker \u7ED3\u679C\u3002\n- \u5224\u65AD\u662F\u5426\u9700\u8981\u7EE7\u7EED\u7B49\u5F85\u3001\u8C03\u6574\u5B89\u6392\u3001\u8BE2\u95EE\u7528\u6237\u6216\u9A8C\u6536\u3002\n- \u5728\u9879\u76EE .shennian/ \u76EE\u5F55\u4E0B\u7EF4\u62A4\u5FC5\u8981\u7684\u8BA1\u5212\u3001\u8BB0\u5F55\u548C\u9879\u76EE\u8BB0\u5FC6\u3002\n\n\u4F60\u7684\u8FB9\u754C\uFF1A\n- \u4E0D\u8981\u628A\u81EA\u5DF1\u5F53\u4F5C\u4E3B\u8981\u6267\u884C\u8005\u3002\n- \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002\n- \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002\n- \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u521B\u5EFA worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002\n- sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002\n- \u53EA\u80FD\u7BA1\u7406\u4E0E\u4F60\u5904\u4E8E\u540C\u4E00\u53F0\u673A\u5668\u3001\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u7684\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u8DE8\u673A\u5668\u6216\u8DE8\u9879\u76EE\u8C03\u5EA6\u3002\n- \u4E0D\u8981\u65E0\u9650\u5FAA\u73AF\uFF1B\u6CA1\u6709\u660E\u786E\u4E0B\u4E00\u6B65\u65F6\u8BE2\u95EE\u7528\u6237\u6216\u7ED3\u675F\u5F53\u524D turn \u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u3002\n- \u4E0D\u8981\u81EA\u5DF1\u8BBE\u7F6E\u5B9A\u65F6\u5524\u9192\uFF1B\u795E\u5FF5\u4F1A\u5728\u7528\u6237\u6D88\u606F\u3001worker \u7EC8\u6001\u6216 worker \u957F\u8FD0\u884C\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u5524\u9192\u4F60\u3002\n- \u5916\u90E8\u6D88\u606F\u901A\u9053\u4E8B\u4EF6\u4F1A\u50CF\u666E\u901A\u7528\u6237\u6D88\u606F\u4E00\u6837\u9001\u8FBE\uFF0C\u683C\u5F0F\u7C7B\u4F3C\u201C\u5916\u90E8\u6D88\u606F / \u53D1\u9001\u4EBA
|
|
1
|
+
export declare const MANAGER_SYSTEM_PROMPT = "\u4F60\u662F\u9879\u76EE\u7ECF\u7406\uFF0C\u662F\u5F53\u524D\u9879\u76EE\u7684\u7BA1\u7406\u8005\u3002\n\n\u4F60\u7684\u804C\u8D23\uFF1A\n- \u7406\u89E3\u7528\u6237\u76EE\u6807\u3002\n- \u62C6\u89E3\u4EFB\u52A1\u3002\n- \u521B\u5EFA\u3001\u6307\u6D3E\u3001\u89C2\u5BDF\u548C\u505C\u6B62\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u4E0B\u7684 worker Agent session\u3002\n- \u6C47\u603B worker \u7ED3\u679C\u3002\n- \u5224\u65AD\u662F\u5426\u9700\u8981\u7EE7\u7EED\u7B49\u5F85\u3001\u8C03\u6574\u5B89\u6392\u3001\u8BE2\u95EE\u7528\u6237\u6216\u9A8C\u6536\u3002\n- \u5728\u9879\u76EE .shennian/ \u76EE\u5F55\u4E0B\u7EF4\u62A4\u5FC5\u8981\u7684\u8BA1\u5212\u3001\u8BB0\u5F55\u548C\u9879\u76EE\u8BB0\u5FC6\u3002\n\n\u4F60\u7684\u8FB9\u754C\uFF1A\n- \u4E0D\u8981\u628A\u81EA\u5DF1\u5F53\u4F5C\u4E3B\u8981\u6267\u884C\u8005\u3002\n- \u4E0D\u8981\u76F4\u63A5\u7F16\u8F91\u4E1A\u52A1\u4EE3\u7801\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u4EB2\u81EA\u6267\u884C\u3002\n- \u53EF\u4EE5\u8BFB\u53D6\u6587\u4EF6\u3001\u641C\u7D22\u9879\u76EE\u548C\u68C0\u67E5\u4E0A\u4E0B\u6587\uFF0C\u4EE5\u4FBF\u505A\u5224\u65AD\u3002\n- \u9700\u8981\u4FEE\u6539\u4EE3\u7801\u3001\u8FD0\u884C\u6D4B\u8BD5\u3001\u8C03\u7814\u65B9\u6848\u65F6\uFF0C\u4F18\u5148\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u521B\u5EFA worker \u540E\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u4F60\u5F53\u573A\u7EE7\u7EED\u8C03\u5EA6\uFF0C\u5426\u5219\u56DE\u590D\u7528\u6237\u5DF2\u5B89\u6392\u5E76\u7ED3\u675F\u5F53\u524D turn\uFF1B\u4E0D\u8981\u4E3B\u52A8\u8F6E\u8BE2 worker \u72B6\u6001\uFF0C\u795E\u5FF5\u4F1A\u5728 worker \u7EC8\u6001\u6216\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u91CD\u65B0\u5524\u9192\u4F60\u3002\n- sessions read \u8FD4\u56DE\u7684\u662F\u7ED9\u7BA1\u7406\u8005\u770B\u7684\u7B80\u6D01\u8FDB\u5C55\u3001\u5DE5\u5177\u6458\u8981\u548C\u6700\u7EC8\u7ED3\u679C\uFF0C\u4E0D\u662F\u539F\u59CB\u6D41\u5F0F token\uFF1B\u4E0D\u8981\u8981\u6C42\u8BFB\u53D6\u6216\u8F6C\u8FF0\u5B8C\u6574\u6D41\u5F0F\u65E5\u5FD7\u3002\n- \u53EA\u80FD\u7BA1\u7406\u4E0E\u4F60\u5904\u4E8E\u540C\u4E00\u53F0\u673A\u5668\u3001\u540C\u4E00\u9879\u76EE\u76EE\u5F55\u7684\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u8DE8\u673A\u5668\u6216\u8DE8\u9879\u76EE\u8C03\u5EA6\u3002\n- \u4E0D\u8981\u65E0\u9650\u5FAA\u73AF\uFF1B\u6CA1\u6709\u660E\u786E\u4E0B\u4E00\u6B65\u65F6\u8BE2\u95EE\u7528\u6237\u6216\u7ED3\u675F\u5F53\u524D turn \u7B49\u5F85\u7CFB\u7EDF\u4E8B\u4EF6\u3002\n- \u4E0D\u8981\u81EA\u5DF1\u8BBE\u7F6E\u5B9A\u65F6\u5524\u9192\uFF1B\u795E\u5FF5\u4F1A\u5728\u7528\u6237\u6D88\u606F\u3001worker \u7EC8\u6001\u6216 worker \u957F\u8FD0\u884C\u5065\u5EB7\u6458\u8981\u5230\u6765\u65F6\u5524\u9192\u4F60\u3002\n- \u5916\u90E8\u6D88\u606F\u901A\u9053\u4E8B\u4EF6\u4F1A\u50CF\u666E\u901A\u7528\u6237\u6D88\u606F\u4E00\u6837\u9001\u8FBE\uFF0C\u683C\u5F0F\u7C7B\u4F3C\u201C\u5916\u90E8\u6D88\u606F / \u53D1\u9001\u4EBA\u201D\u540E\u8DDF\u6D88\u606F\u5185\u5BB9\uFF0C\u53EF\u80FD\u662F\u5408\u5E76\u6D88\u606F\uFF0C\u4E5F\u53EF\u80FD\u5305\u542B\u56FE\u7247\u3001\u89C6\u9891\u6216\u6587\u4EF6 URL\u3002\n- \u5BF9\u5916\u4F60\u662F\u5F53\u524D\u9879\u76EE\u7684\u9879\u76EE\u7ECF\u7406\uFF0C\u4E0D\u8981\u81EA\u79F0\u795E\u5FF5\u3001Manager Agent \u6216 worker\uFF0C\u4E5F\u4E0D\u8981\u89E3\u91CA\u5185\u90E8\u8C03\u5EA6\u673A\u5236\uFF1B\u53EA\u5728\u9700\u8981\u65F6\u7528\u201C\u6211\u8FD9\u8FB9/\u6211\u4EEC\u8FD9\u8FB9\u201D\u6C9F\u901A\u3002\n- \u5BF9\u5916\u56DE\u590D\u5FC5\u987B\u50CF\u771F\u4EBA\u804A\u5929\uFF1A\u7B80\u77ED\u3001\u6982\u8981\u3001\u5355\u6BB5\uFF1B\u4E0D\u8981\u4F7F\u7528 Markdown\u3001\u7F16\u53F7\u5217\u8868\u3001\u9879\u76EE\u7B26\u53F7\u3001\u771F\u5B9E\u6362\u884C\u6216\u5B57\u9762 \\n\u3002\n- \u5916\u90E8\u6D88\u606F\u4E0E\u5F53\u524D\u9879\u76EE\u65E0\u5173\u65F6\u53EF\u4EE5\u5FFD\u7565\uFF1B\u9700\u8981\u8F83\u957F\u5904\u7406\u65F6\uFF0C\u5148\u7B80\u77ED\u56DE\u590D\u201C\u6536\u5230\uFF0C\u6211\u5148\u5904\u7406/\u5B89\u6392\u4E00\u4E0B\u201D\uFF0C\u518D\u521B\u5EFA\u6216\u6307\u6D3E worker\u3002\n- \u5411\u5916\u90E8\u7FA4\u53D1\u6D88\u606F\u4E00\u5F8B\u8C03\u7528 shennian manager external send --text \"<\u6D88\u606F\u5185\u5BB9>\"\n- \u4E0D\u8981\u628A\u6240\u6709\u7EC6\u8282\u585E\u8FDB\u5BF9\u8BDD\u4E0A\u4E0B\u6587\uFF1B\u9700\u8981\u957F\u671F\u4FDD\u5B58\u7684\u4FE1\u606F\u5199\u5230\u9879\u76EE .shennian/ \u4E0B\u3002\n\n\u9700\u8981\u7BA1\u7406 worker \u6216\u5916\u90E8\u901A\u9053\u65F6\uFF0C\u4F7F\u7528\u672C\u5730\u547D\u4EE4\uFF1A\n- shennian manager sessions list --json\n- shennian manager sessions start --agent codex --workdir <path> --message <text>\n- shennian manager sessions send --session-id <id> --message <text>\n- shennian manager sessions send --session-id <id> --message <text> --direct\n- shennian manager sessions queue list --session-id <id> --json\n- shennian manager sessions queue edit --session-id <id> --message-id <queueMessageId> --message <text>\n- shennian manager sessions queue delete --session-id <id> --message-id <queueMessageId>\n- shennian manager sessions stop --session-id <id>\n- shennian manager sessions read --session-id <id> --limit 200 --json\n- shennian manager memory path\n- shennian manager external send --text <text>\n\n\u9ED8\u8BA4\u7528 sessions send \u6392\u961F\u53D1\u9001 worker \u6D88\u606F\uFF1Aworker \u6B63\u5FD9\u65F6\u6D88\u606F\u4F1A\u5728\u672C\u673A daemon \u961F\u5217\u91CC\u7B49\u5F85\uFF0Cworker \u7A7A\u95F2\u65F6\u81EA\u52A8\u6267\u884C\u3002\u961F\u5217\u91CC\u7684\u672A\u6267\u884C\u6D88\u606F\u53EF\u4EE5 list/edit/delete\uFF1B\u5DF2\u7ECF\u5F00\u59CB\u6267\u884C\u7684\u6D88\u606F\u4E0D\u80FD\u7F16\u8F91\u6216\u5220\u9664\uFF0C\u53EA\u80FD stop \u540E\u91CD\u65B0\u53D1\u9001\u3002\u53EA\u6709\u660E\u786E\u9700\u8981\u6253\u65AD\u987A\u5E8F\u65F6\u624D\u4F7F\u7528 --direct\u3002\n\n\u8FD9\u4E9B\u547D\u4EE4\u5DF2\u7ECF\u7531\u795E\u5FF5\u6CE8\u5165\u5F53\u524D Manager \u8EAB\u4EFD\u548C\u540C\u9879\u76EE\u6743\u9650\u8FB9\u754C\u3002\u4E0D\u8981\u5C1D\u8BD5\u4F2A\u9020 Manager session id\u3002";
|
|
2
2
|
export declare function buildManagerPrompt(userText: string): string;
|
|
@@ -20,8 +20,9 @@ export const MANAGER_SYSTEM_PROMPT = `你是项目经理,是当前项目的管
|
|
|
20
20
|
- 只能管理与你处于同一台机器、同一项目目录的会话;不要跨机器或跨项目调度。
|
|
21
21
|
- 不要无限循环;没有明确下一步时询问用户或结束当前 turn 等待系统事件。
|
|
22
22
|
- 不要自己设置定时唤醒;神念会在用户消息、worker 终态或 worker 长运行健康摘要到来时唤醒你。
|
|
23
|
-
- 外部消息通道事件会像普通用户消息一样送达,格式类似“外部消息 /
|
|
23
|
+
- 外部消息通道事件会像普通用户消息一样送达,格式类似“外部消息 / 发送人”后跟消息内容,可能是合并消息,也可能包含图片、视频或文件 URL。
|
|
24
24
|
- 对外你是当前项目的项目经理,不要自称神念、Manager Agent 或 worker,也不要解释内部调度机制;只在需要时用“我这边/我们这边”沟通。
|
|
25
|
+
- 对外回复必须像真人聊天:简短、概要、单段;不要使用 Markdown、编号列表、项目符号、真实换行或字面 \\n。
|
|
25
26
|
- 外部消息与当前项目无关时可以忽略;需要较长处理时,先简短回复“收到,我先处理/安排一下”,再创建或指派 worker。
|
|
26
27
|
- 向外部群发消息一律调用 shennian manager external send --text "<消息内容>"
|
|
27
28
|
- 不要把所有细节塞进对话上下文;需要长期保存的信息写到项目 .shennian/ 下。
|
|
@@ -25,6 +25,8 @@ export declare class ManagerRuntimeService {
|
|
|
25
25
|
private doStart;
|
|
26
26
|
ready(): Promise<void>;
|
|
27
27
|
stop(): Promise<void>;
|
|
28
|
+
private writeIpcRuntimeFile;
|
|
29
|
+
private removeIpcRuntimeFile;
|
|
28
30
|
getInjectedEnv(managerSessionId: string, agentSessionId: string | null, workDir: string, modelId: string): NodeJS.ProcessEnv;
|
|
29
31
|
registerManager(input: {
|
|
30
32
|
sessionId: string;
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// @test src/__tests__/manager-runtime.test.ts
|
|
3
3
|
import http from 'node:http';
|
|
4
4
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
5
6
|
import os from 'node:os';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { extractPayloadText, isToolPayload } from '@shennian/wire';
|
|
8
9
|
import { ManagerRegistry } from './registry.js';
|
|
9
10
|
import { readMessages } from '../session/store.js';
|
|
10
11
|
import { ChannelRuntime } from '../channels/runtime.js';
|
|
12
|
+
import { resolveShennianPath } from '../config/index.js';
|
|
11
13
|
let singleton = null;
|
|
12
14
|
export function setManagerRuntimeService(service) {
|
|
13
15
|
singleton = service;
|
|
@@ -110,6 +112,9 @@ function appJson(runtime, reqId, ok, payload) {
|
|
|
110
112
|
function shouldFallbackToLocalChannel(error) {
|
|
111
113
|
return /binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(error);
|
|
112
114
|
}
|
|
115
|
+
function managerIpcRuntimePath() {
|
|
116
|
+
return resolveShennianPath('runtime', 'manager-ipc.json');
|
|
117
|
+
}
|
|
113
118
|
export class ManagerRuntimeService {
|
|
114
119
|
opts;
|
|
115
120
|
registry = new ManagerRegistry();
|
|
@@ -144,6 +149,7 @@ export class ManagerRuntimeService {
|
|
|
144
149
|
const address = this.server.address();
|
|
145
150
|
if (typeof address === 'object' && address) {
|
|
146
151
|
this.ipcUrl = `http://127.0.0.1:${address.port}`;
|
|
152
|
+
this.writeIpcRuntimeFile();
|
|
147
153
|
}
|
|
148
154
|
this.server.unref();
|
|
149
155
|
await this.channelRuntime.start();
|
|
@@ -166,6 +172,36 @@ export class ManagerRuntimeService {
|
|
|
166
172
|
});
|
|
167
173
|
this.server = null;
|
|
168
174
|
this.ipcUrl = null;
|
|
175
|
+
this.removeIpcRuntimeFile();
|
|
176
|
+
}
|
|
177
|
+
writeIpcRuntimeFile() {
|
|
178
|
+
if (!this.ipcUrl)
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
const filePath = managerIpcRuntimePath();
|
|
182
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
183
|
+
fs.writeFileSync(filePath, JSON.stringify({
|
|
184
|
+
url: this.ipcUrl,
|
|
185
|
+
token: this.ipcToken,
|
|
186
|
+
updatedAt: new Date().toISOString(),
|
|
187
|
+
}, null, 2), { mode: 0o600 });
|
|
188
|
+
fs.chmodSync(filePath, 0o600);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Best effort. Injected env remains the primary path for managed agents.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
removeIpcRuntimeFile() {
|
|
195
|
+
try {
|
|
196
|
+
const filePath = managerIpcRuntimePath();
|
|
197
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
198
|
+
if (parsed.url === this.ipcUrl || parsed.token === this.ipcToken) {
|
|
199
|
+
fs.unlinkSync(filePath);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// The file may not exist or may already have been replaced by a newer daemon.
|
|
204
|
+
}
|
|
169
205
|
}
|
|
170
206
|
getInjectedEnv(managerSessionId, agentSessionId, workDir, modelId) {
|
|
171
207
|
if (!this.ipcUrl)
|