shennian 0.2.63 → 0.2.64
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 -0
- package/dist/src/agents/claude.js +2 -2
- package/dist/src/agents/codex-utils.d.ts +24 -0
- package/dist/src/agents/codex-utils.js +195 -0
- package/dist/src/agents/codex.d.ts +3 -4
- package/dist/src/agents/codex.js +63 -198
- package/dist/src/agents/command-spec.d.ts +3 -0
- package/dist/src/agents/command-spec.js +3 -0
- package/dist/src/agents/opencode.js +2 -2
- package/dist/src/agents/pi-context.d.ts +40 -0
- package/dist/src/agents/pi-context.js +177 -0
- package/dist/src/agents/pi.d.ts +1 -7
- package/dist/src/agents/pi.js +39 -186
- package/dist/src/agents/platform-instructions.js +3 -0
- package/dist/src/commands/daemon-windows.d.ts +16 -0
- package/dist/src/commands/daemon-windows.js +99 -0
- package/dist/src/commands/daemon.d.ts +1 -8
- package/dist/src/commands/daemon.js +2 -96
- package/dist/src/manager/prompt.d.ts +1 -1
- package/dist/src/manager/prompt.js +4 -1
- package/dist/src/native-fusion/opencode-parser.d.ts +29 -0
- package/dist/src/native-fusion/opencode-parser.js +121 -0
- package/dist/src/native-fusion/parser-common.d.ts +24 -0
- package/dist/src/native-fusion/parser-common.js +264 -0
- package/dist/src/native-fusion/parsers.d.ts +1 -29
- package/dist/src/native-fusion/parsers.js +33 -383
- package/dist/src/region.js +4 -3
- package/dist/src/session/handlers/chat.js +14 -4
- package/dist/src/session/handlers/control.js +1 -3
- package/dist/src/session/handlers/fs.d.ts +2 -0
- package/dist/src/session/handlers/fs.js +260 -16
- package/dist/src/session/handlers/skills.d.ts +5 -0
- package/dist/src/session/handlers/skills.js +50 -0
- package/dist/src/session/manager.js +17 -1
- package/dist/src/session/types.d.ts +10 -0
- package/dist/src/skills/registry.d.ts +12 -0
- package/dist/src/skills/registry.js +128 -0
- package/package.json +1 -1
|
@@ -10,6 +10,12 @@ export type AgentEvent = {
|
|
|
10
10
|
args?: Record<string, unknown>;
|
|
11
11
|
result?: string;
|
|
12
12
|
message?: string;
|
|
13
|
+
approval?: {
|
|
14
|
+
title?: string;
|
|
15
|
+
content?: string;
|
|
16
|
+
source?: string;
|
|
17
|
+
actionHint?: string;
|
|
18
|
+
};
|
|
13
19
|
usage?: {
|
|
14
20
|
inputTokens: number;
|
|
15
21
|
outputTokens: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { AgentAdapter, registerAgent } from './adapter.js';
|
|
4
|
-
import { resolveBuiltinCommand,
|
|
4
|
+
import { resolveBuiltinCommand, spawnAgentCommand, spawnResolvedCommandSync } from './command-spec.js';
|
|
5
5
|
import { buildAgentProcessEnv } from '../agent-env.js';
|
|
6
6
|
import { buildPlatformInstructions } from './platform-instructions.js';
|
|
7
7
|
export function normalizeClaudeModelId(modelId) {
|
|
@@ -128,7 +128,7 @@ export class ClaudeAdapter extends AgentAdapter {
|
|
|
128
128
|
this.emit('error', new Error('Command "claude" not found. Is Claude Code CLI installed?'));
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
|
-
const proc =
|
|
131
|
+
const proc = spawnAgentCommand(spec, args, {
|
|
132
132
|
cwd: this.workDir ?? undefined,
|
|
133
133
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
134
134
|
env: buildAgentProcessEnv(this.extraEnv),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare function makeThreadTitle(text: string): string;
|
|
2
|
+
export declare function isCodexCollabAgentToolName(name: string): boolean;
|
|
3
|
+
export declare function isCodexCollabAgentItem(item: Record<string, unknown>): boolean;
|
|
4
|
+
export declare function isCodexLegacyCollabAgentItem(item: {
|
|
5
|
+
type?: string;
|
|
6
|
+
tool?: string;
|
|
7
|
+
}): boolean;
|
|
8
|
+
export declare function normalizeTitleText(text: string): string;
|
|
9
|
+
export declare function normalizeCodexModelId(modelId?: string | null): string | undefined;
|
|
10
|
+
export declare function normalizeCodexReasoningEffort(reasoningEffort?: string | null): string | undefined;
|
|
11
|
+
export declare function isMissingCodexRolloutError(error: unknown): boolean;
|
|
12
|
+
export declare function isCodexUnsupportedEffortError(error: unknown): boolean;
|
|
13
|
+
export declare function extractAppServerErrorMessage(params: Record<string, unknown>): string;
|
|
14
|
+
export declare function isTransientCodexErrorMessage(message: string): boolean;
|
|
15
|
+
export declare function formatTransientCodexStatus(message: string): string;
|
|
16
|
+
export declare function formatCodexErrorMessage(message: string): string;
|
|
17
|
+
export declare function stripGitDirectiveArtifacts(text: string): string;
|
|
18
|
+
export declare function safeStringify(value: unknown): string;
|
|
19
|
+
export declare function normalizeCodexStderr(stderr: string): string;
|
|
20
|
+
export declare function looksLikeMissingNodeOnWindows(text: string): boolean;
|
|
21
|
+
export declare function normalizeTerminalText(text: string): string;
|
|
22
|
+
export declare function looksLikeCodexInteractiveAuthPrompt(text: string): boolean;
|
|
23
|
+
export declare function isIgnorableCodexStderrLine(line: string): boolean;
|
|
24
|
+
export declare function looksLikeFatalCodexStderr(stderr: string): boolean;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// @arch docs/architecture/cli/agent-adapters.md
|
|
2
|
+
// @test src/__tests__/codex-adapter.test.ts
|
|
3
|
+
export function makeThreadTitle(text) {
|
|
4
|
+
const firstLine = normalizeTitleText(text).split('\n').find((line) => line.trim()) ?? 'Shennian';
|
|
5
|
+
const title = firstLine.trim().slice(0, 80);
|
|
6
|
+
return title || 'Shennian';
|
|
7
|
+
}
|
|
8
|
+
const CODEX_COLLAB_AGENT_TOOL_NAMES = new Set([
|
|
9
|
+
'spawnAgent',
|
|
10
|
+
'sendInput',
|
|
11
|
+
'resumeAgent',
|
|
12
|
+
'wait',
|
|
13
|
+
'closeAgent',
|
|
14
|
+
'waitAgent',
|
|
15
|
+
]);
|
|
16
|
+
export function isCodexCollabAgentToolName(name) {
|
|
17
|
+
return CODEX_COLLAB_AGENT_TOOL_NAMES.has(name);
|
|
18
|
+
}
|
|
19
|
+
export function isCodexCollabAgentItem(item) {
|
|
20
|
+
if (item.type === 'collabAgentToolCall')
|
|
21
|
+
return true;
|
|
22
|
+
if (item.type === 'collabAgentMonitor')
|
|
23
|
+
return true;
|
|
24
|
+
const tool = typeof item.tool === 'string' ? item.tool : '';
|
|
25
|
+
if (tool && isCodexCollabAgentToolName(tool))
|
|
26
|
+
return true;
|
|
27
|
+
return typeof item.senderThreadId === 'string' && Array.isArray(item.receiverThreadIds);
|
|
28
|
+
}
|
|
29
|
+
export function isCodexLegacyCollabAgentItem(item) {
|
|
30
|
+
if (item.type === 'collab_agent_tool_call' || item.type === 'collabAgentToolCall')
|
|
31
|
+
return true;
|
|
32
|
+
if (item.type === 'collab_agent_monitor' || item.type === 'collabAgentMonitor')
|
|
33
|
+
return true;
|
|
34
|
+
return !!item.tool && isCodexCollabAgentToolName(item.tool);
|
|
35
|
+
}
|
|
36
|
+
export function normalizeTitleText(text) {
|
|
37
|
+
return text
|
|
38
|
+
.replace(/!\[[^\]]*]\([^)]+\)/g, '')
|
|
39
|
+
.replace(/\s+/g, ' ')
|
|
40
|
+
.trim();
|
|
41
|
+
}
|
|
42
|
+
export function normalizeCodexModelId(modelId) {
|
|
43
|
+
const trimmed = modelId?.trim();
|
|
44
|
+
if (!trimmed)
|
|
45
|
+
return undefined;
|
|
46
|
+
return trimmed.toLowerCase() === 'openai' ? undefined : trimmed;
|
|
47
|
+
}
|
|
48
|
+
export function normalizeCodexReasoningEffort(reasoningEffort) {
|
|
49
|
+
const trimmed = reasoningEffort?.trim();
|
|
50
|
+
return trimmed || undefined;
|
|
51
|
+
}
|
|
52
|
+
export function isMissingCodexRolloutError(error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
54
|
+
return /\bno rollout found for thread id\b/i.test(message);
|
|
55
|
+
}
|
|
56
|
+
export function isCodexUnsupportedEffortError(error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
58
|
+
return (/\bunknown field\b.*\beffort\b/i.test(message) ||
|
|
59
|
+
/\binvalid.*\beffort\b/i.test(message) ||
|
|
60
|
+
/\bunsupported.*\beffort\b/i.test(message) ||
|
|
61
|
+
/\breasoning effort\b/i.test(message));
|
|
62
|
+
}
|
|
63
|
+
export function extractAppServerErrorMessage(params) {
|
|
64
|
+
if (typeof params.message === 'string' && params.message.trim())
|
|
65
|
+
return params.message.trim();
|
|
66
|
+
const error = params.error;
|
|
67
|
+
if (typeof error === 'string' && error.trim())
|
|
68
|
+
return error.trim();
|
|
69
|
+
if (typeof error === 'object' && error !== null) {
|
|
70
|
+
const message = error.message;
|
|
71
|
+
if (typeof message === 'string' && message.trim())
|
|
72
|
+
return message.trim();
|
|
73
|
+
}
|
|
74
|
+
return 'codex app-server error';
|
|
75
|
+
}
|
|
76
|
+
export function isTransientCodexErrorMessage(message) {
|
|
77
|
+
return /\bReconnecting\.\.\.\s*\d+\/\d+/i.test(message);
|
|
78
|
+
}
|
|
79
|
+
export function formatTransientCodexStatus(message) {
|
|
80
|
+
const match = message.match(/\bReconnecting\.\.\.\s*(\d+\/\d+)/i);
|
|
81
|
+
if (!match)
|
|
82
|
+
return '';
|
|
83
|
+
return `Codex API 暂时不可用,正在重试 ${match[1]}...`;
|
|
84
|
+
}
|
|
85
|
+
export function formatCodexErrorMessage(message) {
|
|
86
|
+
const statusMatch = message.match(/unexpected status\s+(\d+)\s+([^:,]+)(?::\s*([^,]+))?(?:,\s*url:\s*([^,\s]+))?(?:,\s*request id:\s*([^\s,]+))?/i);
|
|
87
|
+
if (!statusMatch)
|
|
88
|
+
return message;
|
|
89
|
+
const [, status, statusText, detail, url, requestId] = statusMatch;
|
|
90
|
+
const parts = [
|
|
91
|
+
`Codex API 返回 ${status} ${statusText.trim()}`,
|
|
92
|
+
detail?.trim() ? `:${detail.trim()}` : '',
|
|
93
|
+
url ? `\n上游地址:${url}` : '',
|
|
94
|
+
requestId ? `\nrequest id:${requestId}` : '',
|
|
95
|
+
];
|
|
96
|
+
return parts.join('');
|
|
97
|
+
}
|
|
98
|
+
const GIT_DIRECTIVE_LINE_RE = /^\s*::git-[a-z-]+\{[^\n]*\}\s*$/gm;
|
|
99
|
+
export function stripGitDirectiveArtifacts(text) {
|
|
100
|
+
if (!text)
|
|
101
|
+
return '';
|
|
102
|
+
const normalized = text.replace(/\r\n/g, '\n');
|
|
103
|
+
const hadDirective = /(^|\n)\s*::git-[a-z-]+\{[^\n]*\}\s*(?=\n|$)/.test(normalized);
|
|
104
|
+
if (!hadDirective)
|
|
105
|
+
return normalized;
|
|
106
|
+
const stripped = normalized.replace(GIT_DIRECTIVE_LINE_RE, '');
|
|
107
|
+
if (!stripped.trim())
|
|
108
|
+
return '';
|
|
109
|
+
return stripped.replace(/[ \t]+\n/g, '\n').replace(/\n+$/g, '');
|
|
110
|
+
}
|
|
111
|
+
export function safeStringify(value) {
|
|
112
|
+
if (value == null)
|
|
113
|
+
return '';
|
|
114
|
+
if (typeof value === 'string')
|
|
115
|
+
return value;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.stringify(value);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return String(value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function normalizeCodexStderr(stderr) {
|
|
124
|
+
const normalized = stderr
|
|
125
|
+
.split(/\r?\n/)
|
|
126
|
+
.map((line) => line.trim())
|
|
127
|
+
.filter(Boolean)
|
|
128
|
+
.filter((line) => !isIgnorableCodexStderrLine(line))
|
|
129
|
+
.join('\n');
|
|
130
|
+
if (looksLikeMissingNodeOnWindows(normalized)) {
|
|
131
|
+
return 'Codex 启动失败:系统找不到 node。请重新安装 Node.js,或把 Node.js 安装目录(通常是 C:\\Program Files\\nodejs)加入 Windows PATH 后重启神念。';
|
|
132
|
+
}
|
|
133
|
+
return normalized;
|
|
134
|
+
}
|
|
135
|
+
export function looksLikeMissingNodeOnWindows(text) {
|
|
136
|
+
if (!text)
|
|
137
|
+
return false;
|
|
138
|
+
return (/["']?node["']?/i.test(text) &&
|
|
139
|
+
(/not recognized as an internal or external command/i.test(text) ||
|
|
140
|
+
text.includes('不是内部或外部命令') ||
|
|
141
|
+
text.includes('不是可运行的程序') ||
|
|
142
|
+
text.includes('��������') ||
|
|
143
|
+
text.includes('����')));
|
|
144
|
+
}
|
|
145
|
+
export function normalizeTerminalText(text) {
|
|
146
|
+
const escape = String.fromCharCode(27);
|
|
147
|
+
const bell = String.fromCharCode(7);
|
|
148
|
+
const csiPattern = new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
149
|
+
const oscPattern = new RegExp(`${escape}\\][^${bell}]*(?:${bell}|${escape}\\\\)`, 'g');
|
|
150
|
+
return text
|
|
151
|
+
.replace(csiPattern, ' ')
|
|
152
|
+
.replace(oscPattern, ' ')
|
|
153
|
+
.split('')
|
|
154
|
+
.map((char) => {
|
|
155
|
+
const code = char.charCodeAt(0);
|
|
156
|
+
return (code <= 8 || code === 11 || code === 12 || (code >= 14 && code <= 31) || code === 127) ? ' ' : char;
|
|
157
|
+
})
|
|
158
|
+
.join('')
|
|
159
|
+
.replace(/\s+/g, ' ')
|
|
160
|
+
.trim();
|
|
161
|
+
}
|
|
162
|
+
export function looksLikeCodexInteractiveAuthPrompt(text) {
|
|
163
|
+
if (!text)
|
|
164
|
+
return false;
|
|
165
|
+
const normalized = text.toLowerCase();
|
|
166
|
+
return (normalized.includes('welcome to codex') ||
|
|
167
|
+
normalized.includes('sign in with chatgpt') ||
|
|
168
|
+
normalized.includes('provide your own api key') ||
|
|
169
|
+
normalized.includes('press enter to continue'));
|
|
170
|
+
}
|
|
171
|
+
export function isIgnorableCodexStderrLine(line) {
|
|
172
|
+
return [
|
|
173
|
+
/WARNING: proceeding, even though we could not update PATH:/i,
|
|
174
|
+
/Reading additional input from stdin\.\.\./i,
|
|
175
|
+
/failed to record rollout items: failed to queue rollout items: channel closed/i,
|
|
176
|
+
].some((pattern) => pattern.test(line));
|
|
177
|
+
}
|
|
178
|
+
export function looksLikeFatalCodexStderr(stderr) {
|
|
179
|
+
if (!stderr)
|
|
180
|
+
return false;
|
|
181
|
+
return [
|
|
182
|
+
/\bunauthorized\b/i,
|
|
183
|
+
/\bforbidden\b/i,
|
|
184
|
+
/\bpermission denied\b/i,
|
|
185
|
+
/\brate limit\b/i,
|
|
186
|
+
/\binsufficient quota\b/i,
|
|
187
|
+
/\bapi key\b/i,
|
|
188
|
+
/\bauth/i,
|
|
189
|
+
/\bnetwork\b/i,
|
|
190
|
+
/\btimeout\b/i,
|
|
191
|
+
/\bnot found\b/i,
|
|
192
|
+
/\berror\b/i,
|
|
193
|
+
/\bfailed\b/i,
|
|
194
|
+
].some((pattern) => pattern.test(stderr));
|
|
195
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentAdapter } from './adapter.js';
|
|
2
2
|
import type { ChatAttachmentMeta, ExternalChannelSessionStatus } from '@shennian/wire';
|
|
3
|
+
export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCodexModelId, normalizeCodexReasoningEffort, } from './codex-utils.js';
|
|
3
4
|
export declare class CodexAdapter extends AgentAdapter {
|
|
4
5
|
private readonly options;
|
|
5
6
|
readonly type: "codex";
|
|
@@ -54,6 +55,8 @@ export declare class CodexAdapter extends AgentAdapter {
|
|
|
54
55
|
private handleCodexItemLine;
|
|
55
56
|
private handleStreamEvent;
|
|
56
57
|
private emitEvent;
|
|
58
|
+
private isApprovalLikeName;
|
|
59
|
+
private emitApprovalPending;
|
|
57
60
|
private killProcess;
|
|
58
61
|
private rejectAllPending;
|
|
59
62
|
private handleProcessClose;
|
|
@@ -66,7 +69,3 @@ export declare class CodexAdapter extends AgentAdapter {
|
|
|
66
69
|
private scheduleProcessClose;
|
|
67
70
|
private clearForceCloseTimer;
|
|
68
71
|
}
|
|
69
|
-
export declare function normalizeCodexModelId(modelId?: string | null): string | undefined;
|
|
70
|
-
export declare function normalizeCodexReasoningEffort(reasoningEffort?: string | null): string | undefined;
|
|
71
|
-
export declare function isMissingCodexRolloutError(error: unknown): boolean;
|
|
72
|
-
export declare function isCodexUnsupportedEffortError(error: unknown): boolean;
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { AgentAdapter, registerAgent } from './adapter.js';
|
|
6
|
-
import { resolveBuiltinCommand,
|
|
6
|
+
import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
|
|
7
7
|
import { buildAgentProcessEnv } from '../agent-env.js';
|
|
8
8
|
import { ensurePlatformInstructionsFile } from './platform-instructions.js';
|
|
9
|
+
import { extractAppServerErrorMessage, formatCodexErrorMessage, formatTransientCodexStatus, isCodexCollabAgentItem, isCodexCollabAgentToolName, isCodexLegacyCollabAgentItem, isCodexUnsupportedEffortError, isMissingCodexRolloutError, isTransientCodexErrorMessage, looksLikeCodexInteractiveAuthPrompt, looksLikeFatalCodexStderr, makeThreadTitle, normalizeCodexModelId, normalizeCodexReasoningEffort, normalizeCodexStderr, normalizeTerminalText, safeStringify, stripGitDirectiveArtifacts, } from './codex-utils.js';
|
|
10
|
+
export { isCodexUnsupportedEffortError, isMissingCodexRolloutError, normalizeCodexModelId, normalizeCodexReasoningEffort, } from './codex-utils.js';
|
|
9
11
|
function buildCodexTextInput(text, attachments) {
|
|
10
12
|
const images = attachments
|
|
11
13
|
?.filter((attachment) => attachment.kind === 'image')
|
|
@@ -109,7 +111,7 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
109
111
|
this.emit('error', new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
112
|
-
const proc =
|
|
114
|
+
const proc = spawnAgentCommand(spec, args, {
|
|
113
115
|
cwd: this.workDir ?? undefined,
|
|
114
116
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
115
117
|
env: buildAgentProcessEnv(this.extraEnv),
|
|
@@ -161,7 +163,7 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
161
163
|
if (modelInstructionsFile) {
|
|
162
164
|
args.push('-c', `model_instructions_file=${JSON.stringify(modelInstructionsFile)}`);
|
|
163
165
|
}
|
|
164
|
-
const proc =
|
|
166
|
+
const proc = spawnAgentCommand(spec, args, {
|
|
165
167
|
cwd: this.workDir ?? undefined,
|
|
166
168
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
167
169
|
env: buildAgentProcessEnv({ NO_COLOR: '1', ...this.extraEnv }),
|
|
@@ -449,6 +451,15 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
449
451
|
this.emitText(summary, 'block');
|
|
450
452
|
break;
|
|
451
453
|
}
|
|
454
|
+
case 'approvalRequest':
|
|
455
|
+
case 'approval_request':
|
|
456
|
+
case 'permissionRequest':
|
|
457
|
+
case 'permission_request':
|
|
458
|
+
case 'userInputRequired':
|
|
459
|
+
case 'user_input_required': {
|
|
460
|
+
this.emitApprovalPending(item);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
452
463
|
}
|
|
453
464
|
}
|
|
454
465
|
handleAppServerUsage(params) {
|
|
@@ -551,6 +562,17 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
551
562
|
}
|
|
552
563
|
break;
|
|
553
564
|
}
|
|
565
|
+
case 'approval_request':
|
|
566
|
+
case 'approvalRequest':
|
|
567
|
+
case 'permission_request':
|
|
568
|
+
case 'permissionRequest':
|
|
569
|
+
case 'user_input_required':
|
|
570
|
+
case 'userInputRequired': {
|
|
571
|
+
if (obj.type === 'item.started' || obj.type === 'item.completed') {
|
|
572
|
+
this.emitApprovalPending(item);
|
|
573
|
+
}
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
554
576
|
case 'error': {
|
|
555
577
|
if (obj.type === 'item.completed' && item.message) {
|
|
556
578
|
this.emitEvent({ state: 'error', message: item.message });
|
|
@@ -624,6 +646,10 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
624
646
|
}
|
|
625
647
|
case 'function_call':
|
|
626
648
|
case 'tool_call': {
|
|
649
|
+
if (this.isApprovalLikeName(obj.name)) {
|
|
650
|
+
this.emitApprovalPending(obj);
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
627
653
|
if (obj.name && isCodexCollabAgentToolName(obj.name))
|
|
628
654
|
return;
|
|
629
655
|
this.emitEvent({
|
|
@@ -644,6 +670,15 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
644
670
|
});
|
|
645
671
|
break;
|
|
646
672
|
}
|
|
673
|
+
case 'approval_request':
|
|
674
|
+
case 'approvalRequest':
|
|
675
|
+
case 'permission_request':
|
|
676
|
+
case 'permissionRequest':
|
|
677
|
+
case 'user_input_required':
|
|
678
|
+
case 'userInputRequired': {
|
|
679
|
+
this.emitApprovalPending(obj);
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
647
682
|
case 'error': {
|
|
648
683
|
break;
|
|
649
684
|
}
|
|
@@ -668,6 +703,31 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
668
703
|
};
|
|
669
704
|
this.emit('agentEvent', event);
|
|
670
705
|
}
|
|
706
|
+
isApprovalLikeName(name) {
|
|
707
|
+
if (typeof name !== 'string')
|
|
708
|
+
return false;
|
|
709
|
+
return /approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(name);
|
|
710
|
+
}
|
|
711
|
+
emitApprovalPending(source) {
|
|
712
|
+
const command = typeof source.command === 'string' ? source.command : '';
|
|
713
|
+
const title = typeof source.title === 'string' && source.title.trim()
|
|
714
|
+
? source.title
|
|
715
|
+
: '需要用户确认';
|
|
716
|
+
const rawContent = typeof source.prompt === 'string' ? source.prompt
|
|
717
|
+
: typeof source.reason === 'string' ? source.reason
|
|
718
|
+
: typeof source.message === 'string' ? source.message
|
|
719
|
+
: command ? `Codex 请求执行命令:${command}`
|
|
720
|
+
: 'Codex 正在等待用户确认后继续。';
|
|
721
|
+
this.emitEvent({
|
|
722
|
+
state: 'approval-pending',
|
|
723
|
+
approval: {
|
|
724
|
+
title,
|
|
725
|
+
content: rawContent,
|
|
726
|
+
source: 'codex',
|
|
727
|
+
actionHint: '当前神念暂不支持远程确认,请回到运行 Codex 的电脑/终端完成确认。',
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
}
|
|
671
731
|
async killProcess() {
|
|
672
732
|
const proc = this.process;
|
|
673
733
|
this.appServerReady = null;
|
|
@@ -788,199 +848,4 @@ export class CodexAdapter extends AgentAdapter {
|
|
|
788
848
|
this.forceCloseTimer = null;
|
|
789
849
|
}
|
|
790
850
|
}
|
|
791
|
-
function makeThreadTitle(text) {
|
|
792
|
-
const firstLine = normalizeTitleText(text).split('\n').find((line) => line.trim()) ?? 'Shennian';
|
|
793
|
-
const title = firstLine.trim().slice(0, 80);
|
|
794
|
-
return title || 'Shennian';
|
|
795
|
-
}
|
|
796
|
-
const CODEX_COLLAB_AGENT_TOOL_NAMES = new Set([
|
|
797
|
-
'spawnAgent',
|
|
798
|
-
'sendInput',
|
|
799
|
-
'resumeAgent',
|
|
800
|
-
'wait',
|
|
801
|
-
'closeAgent',
|
|
802
|
-
'waitAgent',
|
|
803
|
-
]);
|
|
804
|
-
function isCodexCollabAgentToolName(name) {
|
|
805
|
-
return CODEX_COLLAB_AGENT_TOOL_NAMES.has(name);
|
|
806
|
-
}
|
|
807
|
-
function isCodexCollabAgentItem(item) {
|
|
808
|
-
if (item.type === 'collabAgentToolCall')
|
|
809
|
-
return true;
|
|
810
|
-
if (item.type === 'collabAgentMonitor')
|
|
811
|
-
return true;
|
|
812
|
-
const tool = typeof item.tool === 'string' ? item.tool : '';
|
|
813
|
-
if (tool && isCodexCollabAgentToolName(tool))
|
|
814
|
-
return true;
|
|
815
|
-
return typeof item.senderThreadId === 'string' && Array.isArray(item.receiverThreadIds);
|
|
816
|
-
}
|
|
817
|
-
function isCodexLegacyCollabAgentItem(item) {
|
|
818
|
-
if (item.type === 'collab_agent_tool_call' || item.type === 'collabAgentToolCall')
|
|
819
|
-
return true;
|
|
820
|
-
if (item.type === 'collab_agent_monitor' || item.type === 'collabAgentMonitor')
|
|
821
|
-
return true;
|
|
822
|
-
return !!item.tool && isCodexCollabAgentToolName(item.tool);
|
|
823
|
-
}
|
|
824
|
-
function normalizeTitleText(text) {
|
|
825
|
-
return text
|
|
826
|
-
.replace(/!\[[^\]]*]\([^)]+\)/g, '')
|
|
827
|
-
.replace(/\s+/g, ' ')
|
|
828
|
-
.trim();
|
|
829
|
-
}
|
|
830
|
-
export function normalizeCodexModelId(modelId) {
|
|
831
|
-
const trimmed = modelId?.trim();
|
|
832
|
-
if (!trimmed)
|
|
833
|
-
return undefined;
|
|
834
|
-
return trimmed.toLowerCase() === 'openai' ? undefined : trimmed;
|
|
835
|
-
}
|
|
836
|
-
export function normalizeCodexReasoningEffort(reasoningEffort) {
|
|
837
|
-
const trimmed = reasoningEffort?.trim();
|
|
838
|
-
return trimmed || undefined;
|
|
839
|
-
}
|
|
840
|
-
export function isMissingCodexRolloutError(error) {
|
|
841
|
-
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
842
|
-
return /\bno rollout found for thread id\b/i.test(message);
|
|
843
|
-
}
|
|
844
|
-
export function isCodexUnsupportedEffortError(error) {
|
|
845
|
-
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
846
|
-
return (/\bunknown field\b.*\beffort\b/i.test(message) ||
|
|
847
|
-
/\binvalid.*\beffort\b/i.test(message) ||
|
|
848
|
-
/\bunsupported.*\beffort\b/i.test(message) ||
|
|
849
|
-
/\breasoning effort\b/i.test(message));
|
|
850
|
-
}
|
|
851
|
-
function extractAppServerErrorMessage(params) {
|
|
852
|
-
if (typeof params.message === 'string' && params.message.trim())
|
|
853
|
-
return params.message.trim();
|
|
854
|
-
const error = params.error;
|
|
855
|
-
if (typeof error === 'string' && error.trim())
|
|
856
|
-
return error.trim();
|
|
857
|
-
if (typeof error === 'object' && error !== null) {
|
|
858
|
-
const message = error.message;
|
|
859
|
-
if (typeof message === 'string' && message.trim())
|
|
860
|
-
return message.trim();
|
|
861
|
-
}
|
|
862
|
-
return 'codex app-server error';
|
|
863
|
-
}
|
|
864
|
-
function isTransientCodexErrorMessage(message) {
|
|
865
|
-
return /\bReconnecting\.\.\.\s*\d+\/\d+/i.test(message);
|
|
866
|
-
}
|
|
867
|
-
function formatTransientCodexStatus(message) {
|
|
868
|
-
const match = message.match(/\bReconnecting\.\.\.\s*(\d+\/\d+)/i);
|
|
869
|
-
if (!match)
|
|
870
|
-
return '';
|
|
871
|
-
return `Codex API 暂时不可用,正在重试 ${match[1]}...`;
|
|
872
|
-
}
|
|
873
|
-
function formatCodexErrorMessage(message) {
|
|
874
|
-
const statusMatch = message.match(/unexpected status\s+(\d+)\s+([^:,]+)(?::\s*([^,]+))?(?:,\s*url:\s*([^,\s]+))?(?:,\s*request id:\s*([^\s,]+))?/i);
|
|
875
|
-
if (!statusMatch)
|
|
876
|
-
return message;
|
|
877
|
-
const [, status, statusText, detail, url, requestId] = statusMatch;
|
|
878
|
-
const parts = [
|
|
879
|
-
`Codex API 返回 ${status} ${statusText.trim()}`,
|
|
880
|
-
detail?.trim() ? `:${detail.trim()}` : '',
|
|
881
|
-
url ? `\n上游地址:${url}` : '',
|
|
882
|
-
requestId ? `\nrequest id:${requestId}` : '',
|
|
883
|
-
];
|
|
884
|
-
return parts.join('');
|
|
885
|
-
}
|
|
886
|
-
const GIT_DIRECTIVE_LINE_RE = /^\s*::git-[a-z-]+\{[^\n]*\}\s*$/gm;
|
|
887
|
-
function stripGitDirectiveArtifacts(text) {
|
|
888
|
-
if (!text)
|
|
889
|
-
return '';
|
|
890
|
-
const normalized = text.replace(/\r\n/g, '\n');
|
|
891
|
-
const hadDirective = /(^|\n)\s*::git-[a-z-]+\{[^\n]*\}\s*(?=\n|$)/.test(normalized);
|
|
892
|
-
const stripped = normalized
|
|
893
|
-
.replace(GIT_DIRECTIVE_LINE_RE, '')
|
|
894
|
-
.replace(/\n{3,}/g, '\n\n');
|
|
895
|
-
if (!stripped.trim())
|
|
896
|
-
return '';
|
|
897
|
-
return hadDirective
|
|
898
|
-
? stripped.replace(/[ \t]+\n/g, '\n').replace(/\n+$/g, '')
|
|
899
|
-
: stripped;
|
|
900
|
-
}
|
|
901
|
-
function safeStringify(value) {
|
|
902
|
-
if (value == null)
|
|
903
|
-
return '';
|
|
904
|
-
if (typeof value === 'string')
|
|
905
|
-
return value;
|
|
906
|
-
try {
|
|
907
|
-
return JSON.stringify(value);
|
|
908
|
-
}
|
|
909
|
-
catch {
|
|
910
|
-
return String(value);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
function normalizeCodexStderr(stderr) {
|
|
914
|
-
const normalized = stderr
|
|
915
|
-
.split(/\r?\n/)
|
|
916
|
-
.map((line) => line.trim())
|
|
917
|
-
.filter(Boolean)
|
|
918
|
-
.filter((line) => !isIgnorableCodexStderrLine(line))
|
|
919
|
-
.join('\n');
|
|
920
|
-
if (looksLikeMissingNodeOnWindows(normalized)) {
|
|
921
|
-
return 'Codex 启动失败:系统找不到 node。请重新安装 Node.js,或把 Node.js 安装目录(通常是 C:\\Program Files\\nodejs)加入 Windows PATH 后重启神念。';
|
|
922
|
-
}
|
|
923
|
-
return normalized;
|
|
924
|
-
}
|
|
925
|
-
function looksLikeMissingNodeOnWindows(text) {
|
|
926
|
-
if (!text)
|
|
927
|
-
return false;
|
|
928
|
-
return (/["']?node["']?/i.test(text) &&
|
|
929
|
-
(/not recognized as an internal or external command/i.test(text) ||
|
|
930
|
-
text.includes('不是内部或外部命令') ||
|
|
931
|
-
text.includes('不是可运行的程序') ||
|
|
932
|
-
text.includes('��������') ||
|
|
933
|
-
text.includes('����')));
|
|
934
|
-
}
|
|
935
|
-
function normalizeTerminalText(text) {
|
|
936
|
-
const escape = String.fromCharCode(27);
|
|
937
|
-
const bell = String.fromCharCode(7);
|
|
938
|
-
const csiPattern = new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
939
|
-
const oscPattern = new RegExp(`${escape}\\][^${bell}]*(?:${bell}|${escape}\\\\)`, 'g');
|
|
940
|
-
return text
|
|
941
|
-
.replace(csiPattern, ' ')
|
|
942
|
-
.replace(oscPattern, ' ')
|
|
943
|
-
.split('')
|
|
944
|
-
.map((char) => {
|
|
945
|
-
const code = char.charCodeAt(0);
|
|
946
|
-
return (code <= 8 || code === 11 || code === 12 || (code >= 14 && code <= 31) || code === 127) ? ' ' : char;
|
|
947
|
-
})
|
|
948
|
-
.join('')
|
|
949
|
-
.replace(/\s+/g, ' ')
|
|
950
|
-
.trim();
|
|
951
|
-
}
|
|
952
|
-
function looksLikeCodexInteractiveAuthPrompt(text) {
|
|
953
|
-
if (!text)
|
|
954
|
-
return false;
|
|
955
|
-
const normalized = text.toLowerCase();
|
|
956
|
-
return (normalized.includes('welcome to codex') ||
|
|
957
|
-
normalized.includes('sign in with chatgpt') ||
|
|
958
|
-
normalized.includes('provide your own api key') ||
|
|
959
|
-
normalized.includes('press enter to continue'));
|
|
960
|
-
}
|
|
961
|
-
function isIgnorableCodexStderrLine(line) {
|
|
962
|
-
return [
|
|
963
|
-
/WARNING: proceeding, even though we could not update PATH:/i,
|
|
964
|
-
/Reading additional input from stdin\.\.\./i,
|
|
965
|
-
/failed to record rollout items: failed to queue rollout items: channel closed/i,
|
|
966
|
-
].some((pattern) => pattern.test(line));
|
|
967
|
-
}
|
|
968
|
-
function looksLikeFatalCodexStderr(stderr) {
|
|
969
|
-
if (!stderr)
|
|
970
|
-
return false;
|
|
971
|
-
return [
|
|
972
|
-
/\bunauthorized\b/i,
|
|
973
|
-
/\bforbidden\b/i,
|
|
974
|
-
/\bpermission denied\b/i,
|
|
975
|
-
/\brate limit\b/i,
|
|
976
|
-
/\binsufficient quota\b/i,
|
|
977
|
-
/\bapi key\b/i,
|
|
978
|
-
/\bauth/i,
|
|
979
|
-
/\bnetwork\b/i,
|
|
980
|
-
/\btimeout\b/i,
|
|
981
|
-
/\bnot found\b/i,
|
|
982
|
-
/\berror\b/i,
|
|
983
|
-
/\bfailed\b/i,
|
|
984
|
-
].some((pattern) => pattern.test(stderr));
|
|
985
|
-
}
|
|
986
851
|
registerAgent('codex', () => new CodexAdapter());
|
|
@@ -24,6 +24,9 @@ export declare function buildLaunchSpec(spec: ResolvedCommandSpec, runtimeArgs:
|
|
|
24
24
|
export declare function spawnResolvedCommand(spec: ResolvedCommandSpec, runtimeArgs: string[], options?: Omit<SpawnOptions, 'cwd'> & {
|
|
25
25
|
cwd?: string;
|
|
26
26
|
}): ChildProcess;
|
|
27
|
+
export declare function spawnAgentCommand(spec: ResolvedCommandSpec, runtimeArgs: string[], options?: Omit<SpawnOptions, 'cwd'> & {
|
|
28
|
+
cwd?: string;
|
|
29
|
+
}): ChildProcess;
|
|
27
30
|
export declare function spawnResolvedCommandSync(spec: ResolvedCommandSpec, runtimeArgs: string[], options?: Omit<SpawnSyncOptions, 'cwd'> & {
|
|
28
31
|
cwd?: string;
|
|
29
32
|
}): import("child_process").SpawnSyncReturns<string | NonSharedBuffer>;
|
|
@@ -279,6 +279,9 @@ export function spawnResolvedCommand(spec, runtimeArgs, options = {}) {
|
|
|
279
279
|
: {}),
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
|
+
export function spawnAgentCommand(spec, runtimeArgs, options = {}) {
|
|
283
|
+
return spawnResolvedCommand(spec, runtimeArgs, options);
|
|
284
|
+
}
|
|
282
285
|
export function spawnResolvedCommandSync(spec, runtimeArgs, options = {}) {
|
|
283
286
|
const launch = buildLaunchSpec(spec, runtimeArgs, options.cwd);
|
|
284
287
|
return spawnSync(launch.command, launch.args, {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { AgentAdapter, registerAgent } from './adapter.js';
|
|
6
|
-
import { resolveBuiltinCommand,
|
|
6
|
+
import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
|
|
7
7
|
import { buildAgentProcessEnv } from '../agent-env.js';
|
|
8
8
|
function usageFromTokens(tokens) {
|
|
9
9
|
if (!tokens)
|
|
@@ -101,7 +101,7 @@ export class OpenCodeAdapter extends AgentAdapter {
|
|
|
101
101
|
this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
-
const proc =
|
|
104
|
+
const proc = spawnAgentCommand(spec, args, {
|
|
105
105
|
cwd: this.workDir ?? undefined,
|
|
106
106
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
107
107
|
env: buildAgentProcessEnv({ NO_COLOR: '1' }),
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AgentMessage } from '@mariozechner/pi-agent-core';
|
|
2
|
+
import type { Model } from '@mariozechner/pi-ai';
|
|
3
|
+
export declare const PI_DEFAULT_MODEL_ID = "qwen3.6-plus";
|
|
4
|
+
type ShellCommandSpec = {
|
|
5
|
+
file: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
shell: 'bash' | 'powershell';
|
|
8
|
+
};
|
|
9
|
+
export declare function buildShellCommandSpec(command: string, platform?: NodeJS.Platform): ShellCommandSpec;
|
|
10
|
+
export declare function createPiModel(modelId?: string): Model<'openai-completions'>;
|
|
11
|
+
export declare const SYSTEM_PROMPT = "\u4F60\u662F\u795E\u5FF5\u5185\u7F6E\u7F16\u7A0B\u52A9\u624B\uFF0C\u8FD0\u884C\u5728\u7528\u6237\u672C\u5730\u673A\u5668\u4E0A\u3002\n\u4F60\u53EF\u4EE5\u8BFB\u5199\u6587\u4EF6\u3001\u6267\u884C shell \u547D\u4EE4\u3001\u5E2E\u52A9\u7528\u6237\u5B8C\u6210\u7F16\u7A0B\u548C\u7CFB\u7EDF\u7BA1\u7406\u4EFB\u52A1\u3002\n\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u8BBE\u7F6E\uFF0C\u64CD\u4F5C\u6587\u4EF6\u65F6\u4F7F\u7528\u76F8\u5BF9\u8DEF\u5F84\u6216\u7EDD\u5BF9\u8DEF\u5F84\u5747\u53EF\u3002\n\u5F53\u524D shell \u4F1A\u968F\u64CD\u4F5C\u7CFB\u7EDF\u9009\u62E9\uFF1AWindows \u4F7F\u7528 PowerShell\uFF0CmacOS/Linux \u4F7F\u7528 bash\u3002Windows \u4E0B\u9700\u8981\u771F\u5B9E curl \u65F6\u4F7F\u7528 curl.exe\uFF0C\u907F\u514D PowerShell \u7684 curl \u522B\u540D\u3002\n\u4FDD\u6301\u56DE\u590D\u7B80\u6D01\u3001\u51C6\u786E\uFF0C\u4E2D\u6587\u56DE\u590D\u3002";
|
|
12
|
+
export declare const CONTEXT_TOKEN_THRESHOLD = 90000;
|
|
13
|
+
export declare const KEEP_RECENT_MESSAGES = 6;
|
|
14
|
+
export declare const SUMMARY_FILENAME = "summary.json";
|
|
15
|
+
export declare const SNAPSHOT_FILENAME = "snapshot.json";
|
|
16
|
+
export declare const MESSAGES_FILENAME = "messages.jsonl";
|
|
17
|
+
export declare const LEGACY_SUMMARY_FILENAME = "pi-context.json";
|
|
18
|
+
export type PersistedSummary = {
|
|
19
|
+
version: 1;
|
|
20
|
+
summary: string | null;
|
|
21
|
+
summarizedCount: number;
|
|
22
|
+
updatedAt: number;
|
|
23
|
+
};
|
|
24
|
+
export type PersistedSnapshot = {
|
|
25
|
+
version: 1;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
workDir: string;
|
|
28
|
+
summary: string | null;
|
|
29
|
+
summarizedCount: number;
|
|
30
|
+
messages: AgentMessage[];
|
|
31
|
+
updatedAt: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function estimateTokens(messages: AgentMessage[]): number;
|
|
34
|
+
export declare function messagesToText(messages: AgentMessage[]): string;
|
|
35
|
+
export declare function cloneMessages(messages: AgentMessage[]): AgentMessage[];
|
|
36
|
+
export declare function getSessionDir(sessionId: string): string;
|
|
37
|
+
export declare function buildRollingSummary(cachedSummary: string | null, messages: AgentMessage[]): string | null;
|
|
38
|
+
export declare function longestCommonPrefixLength(left: AgentMessage[], right: AgentMessage[]): number;
|
|
39
|
+
export declare function requestProxySummary(proxyUrl: string, authToken: string, prompt: string): Promise<string | null>;
|
|
40
|
+
export {};
|