shennian 0.2.51 → 0.2.53
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 +6 -2
- package/dist/src/agents/claude.d.ts +9 -1
- package/dist/src/agents/claude.js +23 -4
- package/dist/src/agents/codex.d.ts +10 -1
- package/dist/src/agents/codex.js +43 -16
- package/dist/src/agents/external-channel-instructions.d.ts +2 -0
- package/dist/src/agents/external-channel-instructions.js +22 -0
- package/dist/src/agents/manager.js +2 -0
- package/dist/src/agents/model-registry/parsers.js +21 -7
- package/dist/src/agents/pi.d.ts +7 -0
- package/dist/src/agents/pi.js +15 -3
- package/dist/src/agents/platform-instructions.d.ts +3 -2
- package/dist/src/agents/platform-instructions.js +12 -4
- package/dist/src/channels/base.d.ts +8 -0
- package/dist/src/channels/runtime.d.ts +11 -2
- package/dist/src/channels/runtime.js +53 -19
- package/dist/src/commands/daemon.d.ts +1 -1
- package/dist/src/commands/daemon.js +10 -24
- package/dist/src/commands/external.d.ts +2 -0
- package/dist/src/commands/external.js +45 -0
- package/dist/src/commands/pair.js +3 -1
- package/dist/src/index.js +3 -2
- package/dist/src/manager/runtime.d.ts +4 -0
- package/dist/src/manager/runtime.js +100 -12
- package/dist/src/native-fusion/parsers.js +46 -5
- package/dist/src/relay/client.d.ts +2 -0
- package/dist/src/relay/client.js +32 -0
- package/dist/src/session/handlers/chat.js +68 -13
- package/dist/src/session/queue.d.ts +1 -0
- package/dist/src/session/queue.js +29 -1
- package/dist/src/session/types.d.ts +3 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { AgentType } from '@shennian/wire';
|
|
2
|
+
import type { AgentType, ExternalChannelSessionStatus } from '@shennian/wire';
|
|
3
3
|
export type AgentEvent = {
|
|
4
4
|
state: string;
|
|
5
5
|
runId: string;
|
|
@@ -24,8 +24,12 @@ export interface AgentAdapterEvents {
|
|
|
24
24
|
}
|
|
25
25
|
export declare abstract class AgentAdapter extends EventEmitter<AgentAdapterEvents> {
|
|
26
26
|
abstract readonly type: AgentType;
|
|
27
|
+
configure?(options: {
|
|
28
|
+
externalChannel?: ExternalChannelSessionStatus | null;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
}): void;
|
|
27
31
|
abstract start(sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
|
|
28
|
-
abstract send(text: string, modelId?: string): Promise<void>;
|
|
32
|
+
abstract send(text: string, modelId?: string, reasoningEffort?: string): Promise<void>;
|
|
29
33
|
abstract resume(agentSessionId: string): Promise<void>;
|
|
30
34
|
abstract stop(): Promise<void>;
|
|
31
35
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AgentAdapter } from './adapter.js';
|
|
2
|
+
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
3
|
export declare function normalizeClaudeModelId(modelId?: string | null): string;
|
|
4
|
+
export declare function normalizeClaudeReasoningEffort(reasoningEffort?: string | null): string | undefined;
|
|
3
5
|
export declare class ClaudeAdapter extends AgentAdapter {
|
|
4
6
|
private readonly options;
|
|
5
7
|
readonly type: "claude";
|
|
@@ -15,8 +17,14 @@ export declare class ClaudeAdapter extends AgentAdapter {
|
|
|
15
17
|
systemPrompt?: string;
|
|
16
18
|
hidden?: boolean;
|
|
17
19
|
});
|
|
20
|
+
private externalChannel;
|
|
21
|
+
private extraEnv;
|
|
22
|
+
configure(options: {
|
|
23
|
+
externalChannel?: ExternalChannelSessionStatus | null;
|
|
24
|
+
env?: NodeJS.ProcessEnv;
|
|
25
|
+
}): void;
|
|
18
26
|
start(sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
|
|
19
|
-
send(text: string, modelId?: string): Promise<void>;
|
|
27
|
+
send(text: string, modelId?: string, reasoningEffort?: string): Promise<void>;
|
|
20
28
|
resume(agentSessionId: string): Promise<void>;
|
|
21
29
|
stop(): Promise<void>;
|
|
22
30
|
private spawnAndParse;
|
|
@@ -8,6 +8,15 @@ export function normalizeClaudeModelId(modelId) {
|
|
|
8
8
|
const trimmed = modelId?.trim();
|
|
9
9
|
return trimmed || 'default';
|
|
10
10
|
}
|
|
11
|
+
const CLAUDE_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
|
|
12
|
+
export function normalizeClaudeReasoningEffort(reasoningEffort) {
|
|
13
|
+
const trimmed = reasoningEffort?.trim();
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (CLAUDE_REASONING_EFFORTS.has(trimmed))
|
|
17
|
+
return trimmed;
|
|
18
|
+
throw new Error(`Unsupported Claude reasoning effort "${trimmed}". Supported values: low, medium, high, xhigh, max.`);
|
|
19
|
+
}
|
|
11
20
|
export class ClaudeAdapter extends AgentAdapter {
|
|
12
21
|
options;
|
|
13
22
|
type = 'claude';
|
|
@@ -23,6 +32,12 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
23
32
|
super();
|
|
24
33
|
this.options = options;
|
|
25
34
|
}
|
|
35
|
+
externalChannel = null;
|
|
36
|
+
extraEnv = {};
|
|
37
|
+
configure(options) {
|
|
38
|
+
this.externalChannel = options.externalChannel ?? null;
|
|
39
|
+
this.extraEnv = options.env ?? {};
|
|
40
|
+
}
|
|
26
41
|
async start(sessionId, workDir, agentSessionId) {
|
|
27
42
|
this.sessionId = sessionId;
|
|
28
43
|
this.workDir = workDir;
|
|
@@ -30,12 +45,12 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
30
45
|
if (agentSessionId)
|
|
31
46
|
this.agentSessionId = agentSessionId;
|
|
32
47
|
}
|
|
33
|
-
async send(text, modelId) {
|
|
48
|
+
async send(text, modelId, reasoningEffort) {
|
|
34
49
|
await this.killProcess();
|
|
35
50
|
this.runId = randomUUID();
|
|
36
51
|
this.resetRunState();
|
|
37
52
|
const args = ['-p', text, '--output-format', 'stream-json', '--verbose'];
|
|
38
|
-
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd());
|
|
53
|
+
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel);
|
|
39
54
|
if (systemPrompt) {
|
|
40
55
|
args.push('--append-system-prompt', systemPrompt);
|
|
41
56
|
}
|
|
@@ -46,6 +61,10 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
46
61
|
args.push('--dangerously-skip-permissions');
|
|
47
62
|
}
|
|
48
63
|
args.push('--model', normalizeClaudeModelId(modelId));
|
|
64
|
+
const effort = normalizeClaudeReasoningEffort(reasoningEffort);
|
|
65
|
+
if (effort) {
|
|
66
|
+
args.push('--effort', effort);
|
|
67
|
+
}
|
|
49
68
|
if (this.agentSessionId) {
|
|
50
69
|
args.push('--resume', this.agentSessionId);
|
|
51
70
|
}
|
|
@@ -57,7 +76,7 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
57
76
|
this.runId = randomUUID();
|
|
58
77
|
this.resetRunState();
|
|
59
78
|
const resumeArgs = ['--resume', agentSessionId, '--output-format', 'stream-json', '--verbose'];
|
|
60
|
-
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd());
|
|
79
|
+
const systemPrompt = this.options.systemPrompt ?? buildPlatformInstructions(this.workDir ?? process.cwd(), this.externalChannel);
|
|
61
80
|
if (systemPrompt) {
|
|
62
81
|
resumeArgs.push('--append-system-prompt', systemPrompt);
|
|
63
82
|
}
|
|
@@ -81,7 +100,7 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
81
100
|
const proc = spawnResolvedCommand(spec, args, {
|
|
82
101
|
cwd: this.workDir ?? undefined,
|
|
83
102
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
84
|
-
env: buildAgentProcessEnv(),
|
|
103
|
+
env: buildAgentProcessEnv(this.extraEnv),
|
|
85
104
|
});
|
|
86
105
|
this.process = proc;
|
|
87
106
|
const rl = createInterface({ input: proc.stdout });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentAdapter } from './adapter.js';
|
|
2
|
+
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
3
|
export declare class CodexAdapter extends AgentAdapter {
|
|
3
4
|
private readonly options;
|
|
4
5
|
readonly type: "codex";
|
|
@@ -25,8 +26,14 @@ export declare class CodexAdapter extends AgentAdapter {
|
|
|
25
26
|
modelInstructionsFile?: string;
|
|
26
27
|
hidden?: boolean;
|
|
27
28
|
});
|
|
29
|
+
private externalChannel;
|
|
30
|
+
private extraEnv;
|
|
31
|
+
configure(options: {
|
|
32
|
+
externalChannel?: ExternalChannelSessionStatus | null;
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
}): void;
|
|
28
35
|
start(_sessionId: string, workDir: string, agentSessionId?: string | null): Promise<void>;
|
|
29
|
-
send(text: string, modelId?: string): Promise<void>;
|
|
36
|
+
send(text: string, modelId?: string, reasoningEffort?: string): Promise<void>;
|
|
30
37
|
resume(agentSessionId: string): Promise<void>;
|
|
31
38
|
stop(): Promise<void>;
|
|
32
39
|
private spawnCodex;
|
|
@@ -58,4 +65,6 @@ export declare class CodexAdapter extends AgentAdapter {
|
|
|
58
65
|
private clearForceCloseTimer;
|
|
59
66
|
}
|
|
60
67
|
export declare function normalizeCodexModelId(modelId?: string | null): string | undefined;
|
|
68
|
+
export declare function normalizeCodexReasoningEffort(reasoningEffort?: string | null): string | undefined;
|
|
61
69
|
export declare function isMissingCodexRolloutError(error: unknown): boolean;
|
|
70
|
+
export declare function isCodexUnsupportedEffortError(error: unknown): boolean;
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -32,18 +32,25 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
32
32
|
super();
|
|
33
33
|
this.options = options;
|
|
34
34
|
}
|
|
35
|
+
externalChannel = null;
|
|
36
|
+
extraEnv = {};
|
|
37
|
+
configure(options) {
|
|
38
|
+
this.externalChannel = options.externalChannel ?? null;
|
|
39
|
+
this.extraEnv = options.env ?? {};
|
|
40
|
+
}
|
|
35
41
|
async start(_sessionId, workDir, agentSessionId) {
|
|
36
42
|
this.workDir = workDir;
|
|
37
43
|
this.seq = 0;
|
|
38
44
|
if (agentSessionId)
|
|
39
45
|
this.agentSessionId = agentSessionId;
|
|
40
46
|
}
|
|
41
|
-
async send(text, modelId) {
|
|
47
|
+
async send(text, modelId, reasoningEffort) {
|
|
42
48
|
if (this.activeTurnId) {
|
|
43
49
|
await this.interruptActiveTurn().catch(() => { });
|
|
44
50
|
await this.killProcess();
|
|
45
51
|
}
|
|
46
52
|
const codexModelId = normalizeCodexModelId(modelId);
|
|
53
|
+
const codexReasoningEffort = normalizeCodexReasoningEffort(reasoningEffort);
|
|
47
54
|
this.runId = randomUUID();
|
|
48
55
|
this.seq = 0;
|
|
49
56
|
this.resetRunState();
|
|
@@ -59,7 +66,7 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
59
66
|
}).catch(() => { });
|
|
60
67
|
this.namedThread = true;
|
|
61
68
|
}
|
|
62
|
-
const response = await this.startTurnWithRecovery(threadId, text, codexModelId);
|
|
69
|
+
const response = await this.startTurnWithRecovery(threadId, text, codexModelId, codexReasoningEffort);
|
|
63
70
|
this.activeTurnId = response.turn?.id ?? null;
|
|
64
71
|
}
|
|
65
72
|
async resume(agentSessionId) {
|
|
@@ -81,7 +88,7 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
81
88
|
const proc = spawnResolvedCommand(spec, args, {
|
|
82
89
|
cwd: this.workDir ?? undefined,
|
|
83
90
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
84
|
-
env: buildAgentProcessEnv(),
|
|
91
|
+
env: buildAgentProcessEnv(this.extraEnv),
|
|
85
92
|
});
|
|
86
93
|
this.process = proc;
|
|
87
94
|
const rl = createInterface({ input: proc.stdout });
|
|
@@ -126,14 +133,14 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
126
133
|
return;
|
|
127
134
|
}
|
|
128
135
|
const args = ['app-server', '--listen', 'stdio://'];
|
|
129
|
-
const modelInstructionsFile = this.options.modelInstructionsFile ?? ensurePlatformInstructionsFile(this.workDir ?? process.cwd());
|
|
136
|
+
const modelInstructionsFile = this.options.modelInstructionsFile ?? ensurePlatformInstructionsFile(this.workDir ?? process.cwd(), this.externalChannel);
|
|
130
137
|
if (modelInstructionsFile) {
|
|
131
138
|
args.push('-c', `model_instructions_file=${JSON.stringify(modelInstructionsFile)}`);
|
|
132
139
|
}
|
|
133
140
|
const proc = spawnResolvedCommand(spec, args, {
|
|
134
141
|
cwd: this.workDir ?? undefined,
|
|
135
142
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
136
|
-
env: buildAgentProcessEnv({ NO_COLOR: '1' }),
|
|
143
|
+
env: buildAgentProcessEnv({ NO_COLOR: '1', ...this.extraEnv }),
|
|
137
144
|
});
|
|
138
145
|
this.process = proc;
|
|
139
146
|
this.stderrBuf = '';
|
|
@@ -227,26 +234,35 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
227
234
|
this.agentSessionId = threadId;
|
|
228
235
|
this.namedThread = !!response.thread?.name;
|
|
229
236
|
}
|
|
230
|
-
async startTurnWithRecovery(threadId, text, codexModelId) {
|
|
237
|
+
async startTurnWithRecovery(threadId, text, codexModelId, reasoningEffort) {
|
|
231
238
|
try {
|
|
232
|
-
return await this.startTurn(threadId, text, codexModelId);
|
|
239
|
+
return await this.startTurn(threadId, text, codexModelId, reasoningEffort);
|
|
233
240
|
}
|
|
234
241
|
catch (error) {
|
|
235
242
|
if (!isMissingCodexRolloutError(error))
|
|
236
243
|
throw error;
|
|
237
244
|
await this.killProcess();
|
|
238
245
|
await this.ensureAppServer(codexModelId);
|
|
239
|
-
return await this.startTurn(threadId, text, codexModelId);
|
|
246
|
+
return await this.startTurn(threadId, text, codexModelId, reasoningEffort);
|
|
240
247
|
}
|
|
241
248
|
}
|
|
242
|
-
async startTurn(threadId, text, codexModelId) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
async startTurn(threadId, text, codexModelId, reasoningEffort) {
|
|
250
|
+
try {
|
|
251
|
+
return await this.sendRpc('turn/start', {
|
|
252
|
+
threadId,
|
|
253
|
+
input: [{ type: 'text', text, text_elements: [] }],
|
|
254
|
+
approvalPolicy: 'never',
|
|
255
|
+
sandboxPolicy: { type: 'dangerFullAccess' },
|
|
256
|
+
...(codexModelId ? { model: codexModelId } : {}),
|
|
257
|
+
...(reasoningEffort ? { effort: reasoningEffort } : {}),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (reasoningEffort && isCodexUnsupportedEffortError(error)) {
|
|
262
|
+
throw new Error(`Codex app-server does not accept reasoning effort "${reasoningEffort}" for this turn. Refresh models or upgrade Codex CLI, then retry.`, { cause: error });
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
250
266
|
}
|
|
251
267
|
async interruptActiveTurn() {
|
|
252
268
|
const threadId = this.agentSessionId;
|
|
@@ -793,10 +809,21 @@ export function normalizeCodexModelId(modelId) {
|
|
|
793
809
|
return undefined;
|
|
794
810
|
return trimmed.toLowerCase() === 'openai' ? undefined : trimmed;
|
|
795
811
|
}
|
|
812
|
+
export function normalizeCodexReasoningEffort(reasoningEffort) {
|
|
813
|
+
const trimmed = reasoningEffort?.trim();
|
|
814
|
+
return trimmed || undefined;
|
|
815
|
+
}
|
|
796
816
|
export function isMissingCodexRolloutError(error) {
|
|
797
817
|
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
798
818
|
return /\bno rollout found for thread id\b/i.test(message);
|
|
799
819
|
}
|
|
820
|
+
export function isCodexUnsupportedEffortError(error) {
|
|
821
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
822
|
+
return (/\bunknown field\b.*\beffort\b/i.test(message) ||
|
|
823
|
+
/\binvalid.*\beffort\b/i.test(message) ||
|
|
824
|
+
/\bunsupported.*\beffort\b/i.test(message) ||
|
|
825
|
+
/\breasoning effort\b/i.test(message));
|
|
826
|
+
}
|
|
800
827
|
function extractAppServerErrorMessage(params) {
|
|
801
828
|
if (typeof params.message === 'string' && params.message.trim())
|
|
802
829
|
return params.message.trim();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @arch docs/features/wecom-managed-channel.md
|
|
2
|
+
// @test src/__tests__/platform-instructions.test.ts
|
|
3
|
+
export function buildExternalChannelInstructions(channel) {
|
|
4
|
+
if (!channel?.configured && !channel?.connected)
|
|
5
|
+
return '';
|
|
6
|
+
const channelName = channel.name?.trim() || '外部消息通道';
|
|
7
|
+
const customPrompt = channel.systemPrompt?.trim();
|
|
8
|
+
const sections = [
|
|
9
|
+
`当前对话已接入外部消息通道:${channelName}。`,
|
|
10
|
+
`外部消息会以如下格式进入对话:\n外部消息\n<时间> <用户昵称>: <内容>`,
|
|
11
|
+
channel.canReply === false
|
|
12
|
+
? '当前通道只允许接收消息,不要尝试向外部通道发送回复。'
|
|
13
|
+
: [
|
|
14
|
+
'当用户明确要求你向外部消息通道发送内容,或你需要回复一条外部消息时,调用:',
|
|
15
|
+
'shennian external send --text "<要发送的消息>"',
|
|
16
|
+
'只发送用户可见的最终内容,不要发送内部推理、工具日志或实现细节。',
|
|
17
|
+
].join('\n'),
|
|
18
|
+
'如果外部消息和当前任务无关,可以忽略或简短说明无需处理;如果处理需要时间,先简短确认,再继续完成任务。',
|
|
19
|
+
customPrompt ? `本通道附加约束:${customPrompt}` : '',
|
|
20
|
+
].filter(Boolean);
|
|
21
|
+
return sections.join('\n\n');
|
|
22
|
+
}
|
|
@@ -7,6 +7,7 @@ import { MANAGER_SYSTEM_PROMPT, buildManagerPrompt } from '../manager/prompt.js'
|
|
|
7
7
|
import { getManagerRuntimeService } from '../manager/runtime.js';
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
+
import { PLATFORM_OUTPUT_INSTRUCTIONS } from './platform-instructions.js';
|
|
10
11
|
function normalizeManagerModel(modelId) {
|
|
11
12
|
return modelId === 'claude' ? 'claude' : 'codex';
|
|
12
13
|
}
|
|
@@ -38,6 +39,7 @@ function buildStableManagerInstructions(workDir, managerSessionId) {
|
|
|
38
39
|
channelInstructions
|
|
39
40
|
? `## External Message Channel Instructions\n\n${channelInstructions}`
|
|
40
41
|
: '',
|
|
42
|
+
PLATFORM_OUTPUT_INSTRUCTIONS,
|
|
41
43
|
].filter(Boolean);
|
|
42
44
|
return `${sections.join('\n\n')}\n`;
|
|
43
45
|
}
|
|
@@ -123,13 +123,13 @@ export function parseClaudeModels(raw) {
|
|
|
123
123
|
for (const [pattern, id, name] of patterns) {
|
|
124
124
|
const match = rawClean.match(pattern);
|
|
125
125
|
if (match) {
|
|
126
|
-
models.push({
|
|
126
|
+
models.push(withClaudeReasoningEfforts({
|
|
127
127
|
id,
|
|
128
128
|
name,
|
|
129
129
|
description: `v${match[1]}`,
|
|
130
130
|
provider: 'anthropic',
|
|
131
131
|
isDefault: id === 'default',
|
|
132
|
-
});
|
|
132
|
+
}));
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
if (models.length > 0)
|
|
@@ -144,12 +144,12 @@ export function parseClaudeModels(raw) {
|
|
|
144
144
|
continue;
|
|
145
145
|
const version = match[2] ?? '';
|
|
146
146
|
const isDefault = defaultMatch && defaultMatch[1]?.toLowerCase() === family && defaultMatch[2] === version;
|
|
147
|
-
fallback.push({
|
|
147
|
+
fallback.push(withClaudeReasoningEfforts({
|
|
148
148
|
id: alias,
|
|
149
149
|
name: `${titleCaseSegment(family)} ${version}`,
|
|
150
150
|
provider: 'anthropic',
|
|
151
151
|
isDefault: Boolean(isDefault),
|
|
152
|
-
});
|
|
152
|
+
}));
|
|
153
153
|
}
|
|
154
154
|
return uniqueModels(fallback);
|
|
155
155
|
}
|
|
@@ -193,6 +193,20 @@ const CLAUDE_ALIAS_MODEL_ENV = {
|
|
|
193
193
|
opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
194
194
|
haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
195
195
|
};
|
|
196
|
+
const CLAUDE_REASONING_EFFORTS = [
|
|
197
|
+
{ id: 'low', name: 'Low' },
|
|
198
|
+
{ id: 'medium', name: 'Medium' },
|
|
199
|
+
{ id: 'high', name: 'High' },
|
|
200
|
+
{ id: 'xhigh', name: 'Extra High' },
|
|
201
|
+
{ id: 'max', name: 'Max' },
|
|
202
|
+
];
|
|
203
|
+
function withClaudeReasoningEfforts(model) {
|
|
204
|
+
return {
|
|
205
|
+
...model,
|
|
206
|
+
supportedReasoningEfforts: CLAUDE_REASONING_EFFORTS,
|
|
207
|
+
defaultReasoningEffort: 'medium',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
196
210
|
function readEnvValue(env, key) {
|
|
197
211
|
const value = env[key]?.trim();
|
|
198
212
|
return value || null;
|
|
@@ -244,13 +258,13 @@ export function parseClaudeBinaryModels(raw) {
|
|
|
244
258
|
if (!new RegExp(`\\b${alias}\\b`, 'i').test(clean))
|
|
245
259
|
continue;
|
|
246
260
|
const version = (alias === 'default' ? familyVersions.get('sonnet') : familyVersions.get(alias)) ?? null;
|
|
247
|
-
models.push({
|
|
261
|
+
models.push(withClaudeReasoningEfforts({
|
|
248
262
|
id: alias,
|
|
249
263
|
name: CLAUDE_ALIAS_LABELS[alias],
|
|
250
264
|
description: formatClaudeVersion(version),
|
|
251
265
|
provider: 'anthropic',
|
|
252
266
|
isDefault: alias === 'default',
|
|
253
|
-
});
|
|
267
|
+
}));
|
|
254
268
|
}
|
|
255
269
|
return uniqueModels(models);
|
|
256
270
|
}
|
|
@@ -260,7 +274,7 @@ export function fallbackClaudeAliasModels() {
|
|
|
260
274
|
{ id: 'sonnet', name: 'Sonnet', provider: 'anthropic' },
|
|
261
275
|
{ id: 'opus', name: 'Opus', provider: 'anthropic' },
|
|
262
276
|
{ id: 'haiku', name: 'Haiku', provider: 'anthropic' },
|
|
263
|
-
]);
|
|
277
|
+
]).map(withClaudeReasoningEfforts);
|
|
264
278
|
}
|
|
265
279
|
export function parseCodexModels(raw) {
|
|
266
280
|
const clean = stripAnsi(raw);
|
package/dist/src/agents/pi.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentAdapter } from './adapter.js';
|
|
2
|
+
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
2
3
|
type ShellCommandSpec = {
|
|
3
4
|
file: string;
|
|
4
5
|
args: string[];
|
|
@@ -27,7 +28,13 @@ export declare class PiAdapter extends AgentAdapter {
|
|
|
27
28
|
private pendingBaseMessages;
|
|
28
29
|
private finalizePromise;
|
|
29
30
|
private sendGeneration;
|
|
31
|
+
private externalChannel;
|
|
32
|
+
private extraEnv;
|
|
30
33
|
private pendingSendStart;
|
|
34
|
+
configure(options: {
|
|
35
|
+
externalChannel?: ExternalChannelSessionStatus | null;
|
|
36
|
+
env?: NodeJS.ProcessEnv;
|
|
37
|
+
}): void;
|
|
31
38
|
start(sessionId: string, workDir: string, _agentSessionId?: string | null): Promise<void>;
|
|
32
39
|
send(text: string, modelId?: string): Promise<void>;
|
|
33
40
|
private initAgent;
|
package/dist/src/agents/pi.js
CHANGED
|
@@ -8,6 +8,7 @@ import { promisify } from 'node:util';
|
|
|
8
8
|
import { Agent, streamProxy } from '@mariozechner/pi-agent-core';
|
|
9
9
|
import { Type } from '@sinclair/typebox';
|
|
10
10
|
import { AgentAdapter, registerAgent } from './adapter.js';
|
|
11
|
+
import { buildExternalChannelInstructions } from './external-channel-instructions.js';
|
|
11
12
|
import { loadConfig, resolveShennianPath } from '../config/index.js';
|
|
12
13
|
import { SERVERS } from '../region.js';
|
|
13
14
|
const execFileAsync = promisify(execFile);
|
|
@@ -186,7 +187,7 @@ async function requestProxySummary(proxyUrl, authToken, prompt) {
|
|
|
186
187
|
}
|
|
187
188
|
// ── Local tools ───────────────────────────────────────────────────────────────
|
|
188
189
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
|
-
function makeTools(workDir) {
|
|
190
|
+
function makeTools(workDir, extraEnv = {}) {
|
|
190
191
|
return [
|
|
191
192
|
{
|
|
192
193
|
name: 'read_file',
|
|
@@ -292,6 +293,7 @@ function makeTools(workDir) {
|
|
|
292
293
|
try {
|
|
293
294
|
const { stdout, stderr } = await execFileAsync(spec.file, spec.args, {
|
|
294
295
|
cwd: workDir,
|
|
296
|
+
env: { ...process.env, ...extraEnv },
|
|
295
297
|
timeout: 30_000,
|
|
296
298
|
signal,
|
|
297
299
|
maxBuffer: 1024 * 1024,
|
|
@@ -395,7 +397,13 @@ export class PiAdapter extends AgentAdapter {
|
|
|
395
397
|
pendingBaseMessages = [];
|
|
396
398
|
finalizePromise = Promise.resolve();
|
|
397
399
|
sendGeneration = 0;
|
|
400
|
+
externalChannel = null;
|
|
401
|
+
extraEnv = {};
|
|
398
402
|
pendingSendStart = null;
|
|
403
|
+
configure(options) {
|
|
404
|
+
this.externalChannel = options.externalChannel ?? null;
|
|
405
|
+
this.extraEnv = options.env ?? {};
|
|
406
|
+
}
|
|
399
407
|
async start(sessionId, workDir, _agentSessionId) {
|
|
400
408
|
this.sessionId = sessionId;
|
|
401
409
|
this.workDir = workDir;
|
|
@@ -483,10 +491,14 @@ export class PiAdapter extends AgentAdapter {
|
|
|
483
491
|
// ── Agent lifecycle ──────────────────────────────────────────────────────────
|
|
484
492
|
initAgent() {
|
|
485
493
|
const workDir = this.workDir ?? process.cwd();
|
|
486
|
-
const tools = makeTools(workDir);
|
|
494
|
+
const tools = makeTools(workDir, this.extraEnv);
|
|
487
495
|
const agent = new Agent({
|
|
488
496
|
initialState: {
|
|
489
|
-
systemPrompt:
|
|
497
|
+
systemPrompt: [
|
|
498
|
+
SYSTEM_PROMPT,
|
|
499
|
+
`当前工作目录:${workDir}`,
|
|
500
|
+
buildExternalChannelInstructions(this.externalChannel),
|
|
501
|
+
].filter(Boolean).join('\n\n'),
|
|
490
502
|
model: createPiModel(),
|
|
491
503
|
tools,
|
|
492
504
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExternalChannelSessionStatus } from '@shennian/wire';
|
|
1
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.";
|
|
2
|
-
export declare function buildPlatformInstructions(workDir: string): string;
|
|
3
|
-
export declare function ensurePlatformInstructionsFile(workDir: string): string;
|
|
3
|
+
export declare function buildPlatformInstructions(workDir: string, externalChannel?: ExternalChannelSessionStatus | null): string;
|
|
4
|
+
export declare function ensurePlatformInstructionsFile(workDir: string, externalChannel?: ExternalChannelSessionStatus | null): string;
|
|
@@ -4,6 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { resolveShennianPath } from '../config/index.js';
|
|
7
|
+
import { buildExternalChannelInstructions } from './external-channel-instructions.js';
|
|
7
8
|
export const PLATFORM_OUTPUT_INSTRUCTIONS = `## Shennian Output Instructions
|
|
8
9
|
|
|
9
10
|
When 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:
|
|
@@ -22,21 +23,28 @@ function readProjectAgentsMd(workDir) {
|
|
|
22
23
|
return '';
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
export function buildPlatformInstructions(workDir) {
|
|
26
|
+
export function buildPlatformInstructions(workDir, externalChannel) {
|
|
26
27
|
const projectInstructions = readProjectAgentsMd(workDir);
|
|
28
|
+
const channelInstructions = buildExternalChannelInstructions(externalChannel);
|
|
27
29
|
const sections = [
|
|
28
30
|
'# Shennian Agent Instructions',
|
|
29
31
|
projectInstructions
|
|
30
32
|
? `## Project Instructions\n\n${projectInstructions}`
|
|
31
33
|
: '',
|
|
32
34
|
PLATFORM_OUTPUT_INSTRUCTIONS,
|
|
35
|
+
channelInstructions
|
|
36
|
+
? `## External Message Channel Instructions\n\n${channelInstructions}`
|
|
37
|
+
: '',
|
|
33
38
|
].filter(Boolean);
|
|
34
39
|
return `${sections.join('\n\n')}\n`;
|
|
35
40
|
}
|
|
36
|
-
export function ensurePlatformInstructionsFile(workDir) {
|
|
37
|
-
const key = createHash('sha256')
|
|
41
|
+
export function ensurePlatformInstructionsFile(workDir, externalChannel) {
|
|
42
|
+
const key = createHash('sha256')
|
|
43
|
+
.update(`${path.resolve(workDir)}:${JSON.stringify(externalChannel ?? null)}`)
|
|
44
|
+
.digest('hex')
|
|
45
|
+
.slice(0, 16);
|
|
38
46
|
const filePath = resolveShennianPath('runtime', 'agent-instructions', `${key}.md`);
|
|
39
|
-
const content = buildPlatformInstructions(workDir);
|
|
47
|
+
const content = buildPlatformInstructions(workDir, externalChannel);
|
|
40
48
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
41
49
|
try {
|
|
42
50
|
if (fs.readFileSync(filePath, 'utf8') === content)
|
|
@@ -3,8 +3,12 @@ export type ExternalChannelConfig = {
|
|
|
3
3
|
id: string;
|
|
4
4
|
type: ExternalChannelType;
|
|
5
5
|
name: string;
|
|
6
|
+
sessionId?: string;
|
|
6
7
|
managerSessionId: string;
|
|
7
8
|
workDir: string;
|
|
9
|
+
agentType?: string;
|
|
10
|
+
agentSessionId?: string | null;
|
|
11
|
+
modelId?: string | null;
|
|
8
12
|
enabled: boolean;
|
|
9
13
|
secretRef: string;
|
|
10
14
|
};
|
|
@@ -12,8 +16,12 @@ export type ExternalChannelView = {
|
|
|
12
16
|
id: string;
|
|
13
17
|
type: ExternalChannelType;
|
|
14
18
|
name: string;
|
|
19
|
+
sessionId?: string;
|
|
15
20
|
managerSessionId: string;
|
|
16
21
|
workDir: string;
|
|
22
|
+
agentType?: string;
|
|
23
|
+
agentSessionId?: string | null;
|
|
24
|
+
modelId?: string | null;
|
|
17
25
|
enabled: boolean;
|
|
18
26
|
wsUrl?: string;
|
|
19
27
|
token?: string;
|
|
@@ -5,7 +5,7 @@ export declare class ChannelRuntime {
|
|
|
5
5
|
private configs;
|
|
6
6
|
private secrets;
|
|
7
7
|
private adapters;
|
|
8
|
-
constructor(onExternalMessage: (
|
|
8
|
+
constructor(onExternalMessage: (sessionId: string, event: ExternalMessageEvent) => void, createReplyTarget: (input: {
|
|
9
9
|
managerSessionId: string;
|
|
10
10
|
channelId: string;
|
|
11
11
|
conversationId: string;
|
|
@@ -24,19 +24,24 @@ export declare class ChannelRuntime {
|
|
|
24
24
|
ok: false;
|
|
25
25
|
error: string;
|
|
26
26
|
}>;
|
|
27
|
-
getDefaultReplyTarget(
|
|
27
|
+
getDefaultReplyTarget(sessionId: string): Promise<{
|
|
28
28
|
channelId: string;
|
|
29
29
|
conversationId: string;
|
|
30
30
|
}>;
|
|
31
31
|
getManagerChannel(managerSessionId: string, type: ExternalChannelConfig['type'], opts?: {
|
|
32
32
|
includeSecret?: boolean;
|
|
33
33
|
}): ExternalChannelView | null;
|
|
34
|
+
getChannelById(channelId: string, opts?: {
|
|
35
|
+
includeSecret?: boolean;
|
|
36
|
+
}): ExternalChannelView | null;
|
|
34
37
|
getManagerChannelStatus(managerSessionId: string): {
|
|
35
38
|
configured: boolean;
|
|
36
39
|
connected: boolean;
|
|
37
40
|
type?: string;
|
|
38
41
|
channelId?: string;
|
|
39
42
|
name?: string;
|
|
43
|
+
canReply?: boolean;
|
|
44
|
+
systemPrompt?: string;
|
|
40
45
|
} | null;
|
|
41
46
|
listManagerChannelStatuses(): Array<{
|
|
42
47
|
managerSessionId: string;
|
|
@@ -52,9 +57,13 @@ export declare class ChannelRuntime {
|
|
|
52
57
|
upsertManagerChannel(input: {
|
|
53
58
|
id: string;
|
|
54
59
|
managerSessionId: string;
|
|
60
|
+
sessionId?: string;
|
|
55
61
|
workDir: string;
|
|
56
62
|
type: 'websocket';
|
|
57
63
|
name?: string;
|
|
64
|
+
agentType?: string;
|
|
65
|
+
agentSessionId?: string | null;
|
|
66
|
+
modelId?: string | null;
|
|
58
67
|
enabled: boolean;
|
|
59
68
|
wsUrl?: string;
|
|
60
69
|
token?: string;
|