shennian 0.2.38 → 0.2.43

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.
@@ -28,6 +28,9 @@ export declare class CodexAdapter extends AgentAdapter {
28
28
  private spawnAppServer;
29
29
  private ensureAppServer;
30
30
  private initializeAppServer;
31
+ private startTurnWithRecovery;
32
+ private startTurn;
33
+ private interruptActiveTurn;
31
34
  private sendRpc;
32
35
  private handleAppServerMessage;
33
36
  private handleAppServerNonJsonStdout;
@@ -50,3 +53,4 @@ export declare class CodexAdapter extends AgentAdapter {
50
53
  private clearForceCloseTimer;
51
54
  }
52
55
  export declare function normalizeCodexModelId(modelId?: string | null): string | undefined;
56
+ export declare function isMissingCodexRolloutError(error: unknown): boolean;
@@ -34,6 +34,7 @@ export class CodexAdapter extends AgentAdapter {
34
34
  }
35
35
  async send(text, modelId) {
36
36
  if (this.activeTurnId) {
37
+ await this.interruptActiveTurn().catch(() => { });
37
38
  await this.killProcess();
38
39
  }
39
40
  const codexModelId = normalizeCodexModelId(modelId);
@@ -52,13 +53,7 @@ export class CodexAdapter extends AgentAdapter {
52
53
  }).catch(() => { });
53
54
  this.namedThread = true;
54
55
  }
55
- const response = await this.sendRpc('turn/start', {
56
- threadId,
57
- input: [{ type: 'text', text, text_elements: [] }],
58
- approvalPolicy: 'never',
59
- sandboxPolicy: { type: 'dangerFullAccess' },
60
- ...(codexModelId ? { model: codexModelId } : {}),
61
- });
56
+ const response = await this.startTurnWithRecovery(threadId, text, codexModelId);
62
57
  this.activeTurnId = response.turn?.id ?? null;
63
58
  }
64
59
  async resume(agentSessionId) {
@@ -68,6 +63,7 @@ export class CodexAdapter extends AgentAdapter {
68
63
  this.resetTextState();
69
64
  }
70
65
  async stop() {
66
+ await this.interruptActiveTurn().catch(() => { });
71
67
  await this.killProcess();
72
68
  }
73
69
  spawnCodex(args) {
@@ -218,6 +214,35 @@ export class CodexAdapter extends AgentAdapter {
218
214
  this.agentSessionId = threadId;
219
215
  this.namedThread = !!response.thread?.name;
220
216
  }
217
+ async startTurnWithRecovery(threadId, text, codexModelId) {
218
+ try {
219
+ return await this.startTurn(threadId, text, codexModelId);
220
+ }
221
+ catch (error) {
222
+ if (!isMissingCodexRolloutError(error))
223
+ throw error;
224
+ await this.killProcess();
225
+ await this.ensureAppServer(codexModelId);
226
+ return await this.startTurn(threadId, text, codexModelId);
227
+ }
228
+ }
229
+ async startTurn(threadId, text, codexModelId) {
230
+ return await this.sendRpc('turn/start', {
231
+ threadId,
232
+ input: [{ type: 'text', text, text_elements: [] }],
233
+ approvalPolicy: 'never',
234
+ sandboxPolicy: { type: 'dangerFullAccess' },
235
+ ...(codexModelId ? { model: codexModelId } : {}),
236
+ });
237
+ }
238
+ async interruptActiveTurn() {
239
+ const threadId = this.agentSessionId;
240
+ const turnId = this.activeTurnId;
241
+ if (!threadId || !turnId || !this.process)
242
+ return;
243
+ await this.sendRpc('turn/interrupt', { threadId, turnId }, 5_000);
244
+ this.activeTurnId = null;
245
+ }
221
246
  sendRpc(method, params, timeoutMs = 60_000) {
222
247
  if (this.appServerStartupError)
223
248
  return Promise.reject(this.appServerStartupError);
@@ -755,6 +780,10 @@ export function normalizeCodexModelId(modelId) {
755
780
  return undefined;
756
781
  return trimmed.toLowerCase() === 'openai' ? undefined : trimmed;
757
782
  }
783
+ export function isMissingCodexRolloutError(error) {
784
+ const message = error instanceof Error ? error.message : String(error ?? '');
785
+ return /\bno rollout found for thread id\b/i.test(message);
786
+ }
758
787
  function extractAppServerErrorMessage(params) {
759
788
  if (typeof params.message === 'string' && params.message.trim())
760
789
  return params.message.trim();
@@ -1,4 +1,10 @@
1
1
  import { AgentAdapter } from './adapter.js';
2
+ type ShellCommandSpec = {
3
+ file: string;
4
+ args: string[];
5
+ shell: 'bash' | 'powershell';
6
+ };
7
+ export declare function buildShellCommandSpec(command: string, platform?: NodeJS.Platform): ShellCommandSpec;
2
8
  export declare class PiAdapter extends AgentAdapter {
3
9
  readonly type: "pi";
4
10
  private agent;
@@ -49,3 +55,4 @@ export declare class PiAdapter extends AgentAdapter {
49
55
  private resolvePendingSendStart;
50
56
  private rejectPendingSendStart;
51
57
  }
58
+ export {};
@@ -12,6 +12,20 @@ import { loadConfig, resolveShennianPath } from '../config/index.js';
12
12
  import { SERVERS } from '../region.js';
13
13
  const execFileAsync = promisify(execFile);
14
14
  const PI_DEFAULT_MODEL_ID = 'qwen3.6-plus';
15
+ export function buildShellCommandSpec(command, platform = process.platform) {
16
+ if (platform === 'win32') {
17
+ return {
18
+ file: 'powershell.exe',
19
+ args: ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command],
20
+ shell: 'powershell',
21
+ };
22
+ }
23
+ return {
24
+ file: 'bash',
25
+ args: ['-c', command],
26
+ shell: 'bash',
27
+ };
28
+ }
15
29
  function createPiModel(modelId = PI_DEFAULT_MODEL_ID) {
16
30
  return {
17
31
  id: modelId,
@@ -29,6 +43,7 @@ function createPiModel(modelId = PI_DEFAULT_MODEL_ID) {
29
43
  const SYSTEM_PROMPT = `你是神念内置编程助手,运行在用户本地机器上。
30
44
  你可以读写文件、执行 shell 命令、帮助用户完成编程和系统管理任务。
31
45
  工作目录已设置,操作文件时使用相对路径或绝对路径均可。
46
+ 当前 shell 会随操作系统选择:Windows 使用 PowerShell,macOS/Linux 使用 bash。Windows 下需要真实 curl 时使用 curl.exe,避免 PowerShell 的 curl 别名。
32
47
  保持回复简洁、准确,中文回复。`;
33
48
  // ── Context compression ──────────────────────────────────────────────────────
34
49
  const CONTEXT_TOKEN_THRESHOLD = 90_000;
@@ -268,13 +283,14 @@ function makeTools(workDir) {
268
283
  {
269
284
  name: 'bash',
270
285
  label: '执行命令',
271
- description: 'Execute a shell command in the working directory. Timeout: 30s.',
286
+ description: 'Execute a shell command in the working directory. Uses PowerShell on Windows and bash on macOS/Linux. Timeout: 30s.',
272
287
  parameters: Type.Object({
273
288
  command: Type.String({ description: 'Shell command to execute' }),
274
289
  }),
275
290
  async execute(_id, { command }, signal) {
291
+ const spec = buildShellCommandSpec(command);
276
292
  try {
277
- const { stdout, stderr } = await execFileAsync('bash', ['-c', command], {
293
+ const { stdout, stderr } = await execFileAsync(spec.file, spec.args, {
278
294
  cwd: workDir,
279
295
  timeout: 30_000,
280
296
  signal,
@@ -283,14 +299,14 @@ function makeTools(workDir) {
283
299
  const output = [stdout, stderr].filter(Boolean).join('\n---stderr---\n');
284
300
  return {
285
301
  content: [{ type: 'text', text: output || '(no output)' }],
286
- details: { command },
302
+ details: { command, shell: spec.shell },
287
303
  };
288
304
  }
289
305
  catch (err) {
290
306
  const msg = err instanceof Error ? err.message : String(err);
291
307
  return {
292
308
  content: [{ type: 'text', text: `Error: ${msg}` }],
293
- details: { command },
309
+ details: { command, shell: spec.shell },
294
310
  };
295
311
  }
296
312
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.38",
3
+ "version": "0.2.43",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {