shennian 0.2.69 → 0.2.71

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.
@@ -9,6 +9,8 @@ type ShellCommandSpec = {
9
9
  export declare function buildShellCommandSpec(command: string, platform?: NodeJS.Platform): ShellCommandSpec;
10
10
  export declare function createPiModel(modelId?: string, overrides?: Partial<Pick<Model<'openai-completions'>, 'baseUrl' | 'provider' | 'compat'>>): Model<'openai-completions'>;
11
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 function findAgentsMd(startDir: string): string | null;
13
+ export declare function loadAgentsMdInstructions(workDir: string): string | null;
12
14
  export declare const CONTEXT_TOKEN_THRESHOLD = 90000;
13
15
  export declare const KEEP_RECENT_MESSAGES = 6;
14
16
  export declare const SUMMARY_FILENAME = "summary.json";
@@ -1,5 +1,6 @@
1
1
  // @arch docs/architecture/cli/agent-adapters.md#pi-agent-上下文管理
2
2
  // @test src/__tests__/pi-context.test.ts
3
+ import fs from 'node:fs';
3
4
  import path from 'node:path';
4
5
  import { resolveShennianPath } from '../config/index.js';
5
6
  export const PI_DEFAULT_MODEL_ID = 'qwen3.6-plus';
@@ -37,6 +38,44 @@ export const SYSTEM_PROMPT = `你是神念内置编程助手,运行在用户
37
38
  工作目录已设置,操作文件时使用相对路径或绝对路径均可。
38
39
  当前 shell 会随操作系统选择:Windows 使用 PowerShell,macOS/Linux 使用 bash。Windows 下需要真实 curl 时使用 curl.exe,避免 PowerShell 的 curl 别名。
39
40
  保持回复简洁、准确,中文回复。`;
41
+ const AGENTS_MD_MAX_CHARS = 24_000;
42
+ export function findAgentsMd(startDir) {
43
+ let current = path.resolve(startDir || process.cwd());
44
+ try {
45
+ const stat = fs.existsSync(current) ? fs.statSync(current) : null;
46
+ if (stat && !stat.isDirectory())
47
+ current = path.dirname(current);
48
+ }
49
+ catch {
50
+ // Fall back to the resolved path.
51
+ }
52
+ while (true) {
53
+ const candidate = path.join(current, 'AGENTS.md');
54
+ if (fs.existsSync(candidate))
55
+ return candidate;
56
+ const parent = path.dirname(current);
57
+ if (parent === current)
58
+ return null;
59
+ current = parent;
60
+ }
61
+ }
62
+ export function loadAgentsMdInstructions(workDir) {
63
+ const filePath = findAgentsMd(workDir);
64
+ if (!filePath)
65
+ return null;
66
+ try {
67
+ const content = fs.readFileSync(filePath, 'utf-8').trim();
68
+ if (!content)
69
+ return null;
70
+ const truncated = content.length > AGENTS_MD_MAX_CHARS
71
+ ? `${content.slice(0, AGENTS_MD_MAX_CHARS)}\n\n...(AGENTS.md 已截断)`
72
+ : content;
73
+ return `# AGENTS.md instructions for ${path.dirname(filePath)}\n\n${truncated}`;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
40
79
  // ── Context compression ──────────────────────────────────────────────────────
41
80
  export const CONTEXT_TOKEN_THRESHOLD = 90_000;
42
81
  export const KEEP_RECENT_MESSAGES = 6;
@@ -13,7 +13,7 @@ import { buildExternalChannelInstructions } from './external-channel-instruction
13
13
  import { loadConfig } from '../config/index.js';
14
14
  import { getManagedAgentProviderConfig } from './config-status.js';
15
15
  import { SERVERS } from '../region.js';
16
- import { buildRollingSummary, buildShellCommandSpec, cloneMessages, CONTEXT_TOKEN_THRESHOLD, createPiModel, estimateTokens, getSessionDir, KEEP_RECENT_MESSAGES, LEGACY_SUMMARY_FILENAME, longestCommonPrefixLength, MESSAGES_FILENAME, messagesToText, PI_DEFAULT_MODEL_ID, requestProxySummary, SNAPSHOT_FILENAME, SYSTEM_PROMPT, SUMMARY_FILENAME, } from './pi-context.js';
16
+ import { buildRollingSummary, buildShellCommandSpec, cloneMessages, CONTEXT_TOKEN_THRESHOLD, createPiModel, estimateTokens, getSessionDir, KEEP_RECENT_MESSAGES, LEGACY_SUMMARY_FILENAME, loadAgentsMdInstructions, longestCommonPrefixLength, MESSAGES_FILENAME, messagesToText, PI_DEFAULT_MODEL_ID, requestProxySummary, SNAPSHOT_FILENAME, SYSTEM_PROMPT, SUMMARY_FILENAME, } from './pi-context.js';
17
17
  export { buildShellCommandSpec } from './pi-context.js';
18
18
  const execFileAsync = promisify(execFile);
19
19
  const DASHSCOPE_COMPATIBLE_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
@@ -357,10 +357,12 @@ export class PiAdapter extends AgentAdapter {
357
357
  initAgent() {
358
358
  const workDir = this.workDir ?? process.cwd();
359
359
  const tools = makeTools(workDir, this.extraEnv);
360
+ const agentsMdInstructions = loadAgentsMdInstructions(workDir);
360
361
  const agent = new Agent({
361
362
  initialState: {
362
363
  systemPrompt: [
363
364
  SYSTEM_PROMPT,
365
+ agentsMdInstructions,
364
366
  `当前工作目录:${workDir}`,
365
367
  buildExternalChannelInstructions(this.externalChannel, workDir, this.shennianSessionId ?? undefined),
366
368
  ].filter(Boolean).join('\n\n'),
@@ -63,9 +63,12 @@ function stripCodexUserMessageWrapper(text) {
63
63
  return '';
64
64
  const requestMatch = normalized.match(/My request for Codex:\s*([\s\S]*)$/i);
65
65
  if (requestMatch && requestMatch[1])
66
- return normalizeText(requestMatch[1]);
66
+ return stripCodexImagePlaceholders(requestMatch[1]);
67
67
  return normalized;
68
68
  }
69
+ function stripCodexImagePlaceholders(text) {
70
+ return normalizeText(text.replace(/<image>\s*<\/image>/gi, ''));
71
+ }
69
72
  function parseCodexUserMessage(payload) {
70
73
  const textFromElements = parseCodexTextElements(payload.text_elements);
71
74
  const textFromInput = Array.isArray(payload.input)
@@ -171,7 +174,7 @@ function parseCodexResponseMessage(payload) {
171
174
  return null;
172
175
  return text ? { role, payload: text, titleText: text } : null;
173
176
  }
174
- const text = codexMessageContentText(payload.content, 'input_text');
177
+ const text = stripCodexUserMessageWrapper(codexMessageContentText(payload.content, 'input_text'));
175
178
  const attachments = codexMessageInputImageAttachments(payload.content);
176
179
  if (!text && attachments.length === 0)
177
180
  return null;
@@ -17,6 +17,9 @@ function isCodexRolloutPath(filePath) {
17
17
  function shouldBackfillWindowsCodex(state) {
18
18
  return process.platform === 'win32' && state.codexWindowsPathParserFixed !== true;
19
19
  }
20
+ function shouldBackfillCodexAppContextWrapper(state) {
21
+ return state.codexAppContextWrapperFixed !== true;
22
+ }
20
23
  export class NativeSessionFusionService {
21
24
  client;
22
25
  timer = null;
@@ -97,6 +100,7 @@ export class NativeSessionFusionService {
97
100
  const state = loadNativeScannerState();
98
101
  const nextFilesState = { ...state.files };
99
102
  const backfillWindowsCodex = shouldBackfillWindowsCodex(state);
103
+ const backfillCodexAppContextWrapper = shouldBackfillCodexAppContextWrapper(state);
100
104
  const batches = [];
101
105
  const files = [...listCodexRolloutFiles(), ...listClaudeTranscriptFiles(), ...listOpenCodeSessionFiles()];
102
106
  for (const filePath of files) {
@@ -106,7 +110,7 @@ export class NativeSessionFusionService {
106
110
  const isOpenCode = filePath.endsWith('.opencode-session.json');
107
111
  const startOffset = isOpenCode && current?.mtimeMs !== stat.mtimeMs
108
112
  ? 0
109
- : backfillWindowsCodex && isCodex
113
+ : (backfillWindowsCodex || backfillCodexAppContextWrapper) && isCodex
110
114
  ? 0
111
115
  : current?.offset ?? 0;
112
116
  const parsed = isOpenCode
@@ -144,6 +148,9 @@ export class NativeSessionFusionService {
144
148
  if (backfillWindowsCodex) {
145
149
  state.codexWindowsPathParserFixed = true;
146
150
  }
151
+ if (backfillCodexAppContextWrapper) {
152
+ state.codexAppContextWrapperFixed = true;
153
+ }
147
154
  saveNativeScannerState(state);
148
155
  }
149
156
  tryClaimManagedEcho(event) {
@@ -5,6 +5,7 @@ export type NativeScannerState = {
5
5
  mtimeMs: number;
6
6
  }>;
7
7
  codexWindowsPathParserFixed?: boolean;
8
+ codexAppContextWrapperFixed?: boolean;
8
9
  };
9
10
  export type ParsedNativeEvent = NativeSessionEventPayload;
10
11
  export type PendingManagedClaim = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.69",
3
+ "version": "0.2.71",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {