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.
File without changes
@@ -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
- return {
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
+ }
@@ -778,12 +778,26 @@ function safeStringify(value) {
778
778
  }
779
779
  }
780
780
  function normalizeCodexStderr(stderr) {
781
- return stderr
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
- carry = Buffer.from(chunk.subarray(lineStart));
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
- return nextOffset;
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
- const content = fs.readFileSync(indexPath, 'utf8');
661
- const lines = content.split('\n');
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
- continue;
676
+ return;
666
677
  const parsed = safeParse(line);
667
678
  if (!parsed)
668
- continue;
679
+ return;
669
680
  const title = readCodexIndexTitle(parsed, sourceSessionKey);
670
681
  if (title)
671
- return title;
672
- }
673
- return null;
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: err instanceof Error && err.message.startsWith('Unsupported agent:')
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: err instanceof Error && err.message.startsWith('Unsupported agent:')
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: `Agent send failed: ${err instanceof Error ? err.message : String(err)}`,
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: `Agent send failed: ${err instanceof Error ? err.message : String(err)}`,
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.33",
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.2",
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
+ }