shennian 0.2.33 → 0.2.35
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/bin/shennian.js +0 -0
- package/dist/src/agent-env.js +26 -1
- package/dist/src/agents/codex.js +15 -1
- package/dist/src/native-fusion/parsers.js +23 -12
- package/dist/src/session/handlers/chat.js +11 -8
- package/package.json +10 -10
package/dist/bin/shennian.js
CHANGED
|
File without changes
|
package/dist/src/agent-env.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// @arch docs/architecture/cli/agent-adapters.md
|
|
2
2
|
// @test src/__tests__/agent-env.test.ts
|
|
3
3
|
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
4
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { buildAugmentedPath } from './env-path.js';
|
|
5
7
|
const SHELL_ENV_START = '__SHENNIAN_AGENT_ENV_START__';
|
|
6
8
|
const SHELL_ENV_END = '__SHENNIAN_AGENT_ENV_END__';
|
|
7
9
|
function quotePosix(value) {
|
|
@@ -67,11 +69,15 @@ export function readLatestUserEnv() {
|
|
|
67
69
|
return env ?? {};
|
|
68
70
|
}
|
|
69
71
|
export function mergeAgentProcessEnv(input) {
|
|
70
|
-
|
|
72
|
+
const env = {
|
|
71
73
|
...input.daemonEnv,
|
|
72
74
|
...input.userEnv,
|
|
73
75
|
...input.extra,
|
|
74
76
|
};
|
|
77
|
+
env.PATH = mergePathEnv(input.daemonEnv, input.userEnv, input.extra, env);
|
|
78
|
+
delete env.Path;
|
|
79
|
+
delete env.path;
|
|
80
|
+
return env;
|
|
75
81
|
}
|
|
76
82
|
export function buildAgentProcessEnv(extra = {}) {
|
|
77
83
|
return mergeAgentProcessEnv({
|
|
@@ -80,3 +86,22 @@ export function buildAgentProcessEnv(extra = {}) {
|
|
|
80
86
|
extra,
|
|
81
87
|
});
|
|
82
88
|
}
|
|
89
|
+
function readPathValue(env) {
|
|
90
|
+
return env?.PATH ?? env?.Path ?? env?.path;
|
|
91
|
+
}
|
|
92
|
+
function mergePathEnv(daemonEnv, userEnv, extra, mergedEnv) {
|
|
93
|
+
const parts = [];
|
|
94
|
+
for (const value of [readPathValue(daemonEnv), readPathValue(userEnv), readPathValue(extra)]) {
|
|
95
|
+
for (const part of (value ?? '').split(path.delimiter)) {
|
|
96
|
+
if (part && !parts.includes(part))
|
|
97
|
+
parts.push(part);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const nodeDir = path.dirname(process.execPath);
|
|
101
|
+
if (nodeDir && !parts.includes(nodeDir))
|
|
102
|
+
parts.unshift(nodeDir);
|
|
103
|
+
return buildAugmentedPath({
|
|
104
|
+
pathValue: parts.join(path.delimiter),
|
|
105
|
+
env: mergedEnv,
|
|
106
|
+
});
|
|
107
|
+
}
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -778,12 +778,26 @@ function safeStringify(value) {
|
|
|
778
778
|
}
|
|
779
779
|
}
|
|
780
780
|
function normalizeCodexStderr(stderr) {
|
|
781
|
-
|
|
781
|
+
const normalized = stderr
|
|
782
782
|
.split(/\r?\n/)
|
|
783
783
|
.map((line) => line.trim())
|
|
784
784
|
.filter(Boolean)
|
|
785
785
|
.filter((line) => !isIgnorableCodexStderrLine(line))
|
|
786
786
|
.join('\n');
|
|
787
|
+
if (looksLikeMissingNodeOnWindows(normalized)) {
|
|
788
|
+
return 'Codex 启动失败:系统找不到 node。请重新安装 Node.js,或把 Node.js 安装目录(通常是 C:\\Program Files\\nodejs)加入 Windows PATH 后重启神念。';
|
|
789
|
+
}
|
|
790
|
+
return normalized;
|
|
791
|
+
}
|
|
792
|
+
function looksLikeMissingNodeOnWindows(text) {
|
|
793
|
+
if (!text)
|
|
794
|
+
return false;
|
|
795
|
+
return (/["']?node["']?/i.test(text) &&
|
|
796
|
+
(/not recognized as an internal or external command/i.test(text) ||
|
|
797
|
+
text.includes('不是内部或外部命令') ||
|
|
798
|
+
text.includes('不是可运行的程序') ||
|
|
799
|
+
text.includes('��������') ||
|
|
800
|
+
text.includes('����')));
|
|
787
801
|
}
|
|
788
802
|
function normalizeTerminalText(text) {
|
|
789
803
|
const escape = String.fromCharCode(27);
|
|
@@ -51,6 +51,7 @@ function readJsonlLines(filePath, startOffset, onLine) {
|
|
|
51
51
|
let position = startOffset;
|
|
52
52
|
let nextOffset = startOffset;
|
|
53
53
|
let carry = Buffer.alloc(0);
|
|
54
|
+
let skippingOversizedLine = false;
|
|
54
55
|
while (position < fileSize) {
|
|
55
56
|
const bytesRead = fs.readSync(fd, buffer, 0, Math.min(chunkSize, fileSize - position), position);
|
|
56
57
|
if (bytesRead <= 0)
|
|
@@ -70,15 +71,27 @@ function readJsonlLines(filePath, startOffset, onLine) {
|
|
|
70
71
|
const lineOffset = nextOffset;
|
|
71
72
|
nextOffset += newlineIndex - lineStart + 1;
|
|
72
73
|
lineStart = newlineIndex + 1;
|
|
73
|
-
if (lineBuffer.length > 0 && lineBuffer.length <= MAX_JSONL_LINE_BYTES) {
|
|
74
|
+
if (!skippingOversizedLine && lineBuffer.length > 0 && lineBuffer.length <= MAX_JSONL_LINE_BYTES) {
|
|
74
75
|
onLine(lineBuffer.toString('utf8'), lineOffset);
|
|
75
76
|
}
|
|
77
|
+
skippingOversizedLine = false;
|
|
76
78
|
}
|
|
77
79
|
if (lineStart < chunk.length) {
|
|
78
|
-
|
|
80
|
+
const remaining = chunk.subarray(lineStart);
|
|
81
|
+
if (skippingOversizedLine || remaining.length > MAX_JSONL_LINE_BYTES) {
|
|
82
|
+
skippingOversizedLine = true;
|
|
83
|
+
nextOffset += remaining.length;
|
|
84
|
+
carry = Buffer.alloc(0);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
carry = Buffer.from(remaining);
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
|
-
|
|
91
|
+
if (!skippingOversizedLine && carry.length > 0 && carry.length <= MAX_JSONL_LINE_BYTES) {
|
|
92
|
+
onLine(carry.toString('utf8'), nextOffset);
|
|
93
|
+
}
|
|
94
|
+
return fileSize;
|
|
82
95
|
}
|
|
83
96
|
finally {
|
|
84
97
|
fs.closeSync(fd);
|
|
@@ -657,20 +670,18 @@ function lookupCodexThreadName(sourceSessionKey) {
|
|
|
657
670
|
const indexPath = path.join(os.homedir(), '.codex', 'session_index.jsonl');
|
|
658
671
|
if (!fs.existsSync(indexPath))
|
|
659
672
|
return null;
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
663
|
-
const line = lines[index];
|
|
673
|
+
let latestTitle = null;
|
|
674
|
+
readJsonlLines(indexPath, 0, (line) => {
|
|
664
675
|
if (!line.trim())
|
|
665
|
-
|
|
676
|
+
return;
|
|
666
677
|
const parsed = safeParse(line);
|
|
667
678
|
if (!parsed)
|
|
668
|
-
|
|
679
|
+
return;
|
|
669
680
|
const title = readCodexIndexTitle(parsed, sourceSessionKey);
|
|
670
681
|
if (title)
|
|
671
|
-
|
|
672
|
-
}
|
|
673
|
-
return
|
|
682
|
+
latestTitle = title;
|
|
683
|
+
});
|
|
684
|
+
return latestTitle;
|
|
674
685
|
}
|
|
675
686
|
export function listCodexRolloutFiles() {
|
|
676
687
|
const roots = [
|
|
@@ -122,6 +122,7 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
|
|
|
122
122
|
sendAgentEvent(event, extra);
|
|
123
123
|
});
|
|
124
124
|
adapter.on('error', (error) => {
|
|
125
|
+
console.error(`[chat.send] adapter error sessionId=${sessionId} agentType=${agentType}: ${error.message}`);
|
|
125
126
|
runtime.sessions.delete(sessionId);
|
|
126
127
|
runtime.client.sendEvent({
|
|
127
128
|
type: 'event',
|
|
@@ -230,15 +231,17 @@ export async function handleChatSend(runtime, req) {
|
|
|
230
231
|
session = await createActiveSession(runtime, sessionId, requestedAgentType, resolvedWorkDir, incomingAgentSid);
|
|
231
232
|
}
|
|
232
233
|
catch (err) {
|
|
234
|
+
const message = err instanceof Error && err.message.startsWith('Unsupported agent:')
|
|
235
|
+
? err.message
|
|
236
|
+
: `Failed to start ${agentType}: ${err instanceof Error ? err.message : String(err)}`;
|
|
237
|
+
console.error(`[chat.send] start failed reqId=${req.id} sessionId=${sessionId} agentType=${agentType} workDir=${resolvedWorkDir} agentSessionId=${incomingAgentSid ?? ''}: ${message}`);
|
|
233
238
|
runtime.client.sendEvent({
|
|
234
239
|
type: 'event',
|
|
235
240
|
event: 'agent',
|
|
236
241
|
payload: {
|
|
237
242
|
state: 'error',
|
|
238
243
|
sessionId,
|
|
239
|
-
message
|
|
240
|
-
? err.message
|
|
241
|
-
: `Failed to start ${agentType}: ${err instanceof Error ? err.message : String(err)}`,
|
|
244
|
+
message,
|
|
242
245
|
runId: '',
|
|
243
246
|
seq: 0,
|
|
244
247
|
},
|
|
@@ -248,9 +251,7 @@ export async function handleChatSend(runtime, req) {
|
|
|
248
251
|
type: 'res',
|
|
249
252
|
id: req.id,
|
|
250
253
|
ok: false,
|
|
251
|
-
error:
|
|
252
|
-
? err.message
|
|
253
|
-
: `Failed to start ${agentType}: ${err instanceof Error ? err.message : String(err)}`,
|
|
254
|
+
error: message,
|
|
254
255
|
});
|
|
255
256
|
return;
|
|
256
257
|
}
|
|
@@ -287,6 +288,8 @@ export async function handleChatSend(runtime, req) {
|
|
|
287
288
|
});
|
|
288
289
|
}
|
|
289
290
|
catch (err) {
|
|
291
|
+
const message = `Agent send failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
292
|
+
console.error(`[chat.send] send failed reqId=${req.id} sessionId=${sessionId} agentType=${agentType} workDir=${resolvedWorkDir} agentSessionId=${session.agentSessionId ?? incomingAgentSid ?? ''}: ${message}`);
|
|
290
293
|
runtime.sessions.delete(sessionId);
|
|
291
294
|
try {
|
|
292
295
|
await disposeSession(session);
|
|
@@ -299,7 +302,7 @@ export async function handleChatSend(runtime, req) {
|
|
|
299
302
|
payload: {
|
|
300
303
|
state: 'error',
|
|
301
304
|
sessionId,
|
|
302
|
-
message
|
|
305
|
+
message,
|
|
303
306
|
runId: '',
|
|
304
307
|
seq: 0,
|
|
305
308
|
},
|
|
@@ -308,7 +311,7 @@ export async function handleChatSend(runtime, req) {
|
|
|
308
311
|
type: 'res',
|
|
309
312
|
id: req.id,
|
|
310
313
|
ok: false,
|
|
311
|
-
error:
|
|
314
|
+
error: message,
|
|
312
315
|
});
|
|
313
316
|
}
|
|
314
317
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shennian",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.35",
|
|
4
4
|
"description": "Shennian — AI Agent Control Plane CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"publishConfig": {
|
|
16
|
-
"access": "public"
|
|
16
|
+
"access": "public",
|
|
17
|
+
"tag": "next"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
19
20
|
"shennian",
|
|
@@ -32,16 +33,10 @@
|
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": ">=18"
|
|
34
35
|
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "tsc",
|
|
37
|
-
"build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json",
|
|
38
|
-
"dev": "tsc --watch",
|
|
39
|
-
"prepublishOnly": "pnpm build:publish"
|
|
40
|
-
},
|
|
41
36
|
"dependencies": {
|
|
42
37
|
"@mariozechner/pi-agent-core": "^0.64.0",
|
|
43
38
|
"@sinclair/typebox": "^0.34.49",
|
|
44
|
-
"@shennian/wire": "^0.1.
|
|
39
|
+
"@shennian/wire": "^0.1.3",
|
|
45
40
|
"chalk": "^5.4.1",
|
|
46
41
|
"commander": "^13.1.0",
|
|
47
42
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -53,5 +48,10 @@
|
|
|
53
48
|
"@types/ws": "^8.18.1",
|
|
54
49
|
"tsx": "^4.19.4",
|
|
55
50
|
"typescript": "^5.9.3"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc",
|
|
54
|
+
"build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json",
|
|
55
|
+
"dev": "tsc --watch"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|