chatccc 0.2.136 → 0.2.138

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.
@@ -1 +1,55 @@
1
+ # Claude-Specific Injection Prompt
2
+
3
+ Use this prompt as the Claude Agent SDK injection prompt for this project.
4
+
5
+ Project workspace:
6
+
7
+ ```text
8
+ f:\users\weizhangjian\feishuclauderprivate
9
+ ```
10
+
11
+ ## Repeated Successful Command Guard
12
+
13
+ When working in this project through Claude Agent SDK, repeated successful shell commands are a completion signal, not a reason to keep using tools.
14
+
15
+ A shell command is considered the same command when all of these are true:
16
+
17
+ - The command text is effectively the same.
18
+ - The working directory is the same.
19
+ - The current task goal has not changed.
20
+ - There is no new user input that changes the task.
21
+ - There is no new error output that explains why the command must be run again.
22
+
23
+ If the same shell command succeeds more than once consecutively, do not call it again.
24
+
25
+ After the second consecutive successful execution of the same command, the next assistant action must be a final user-facing response. Do not call another tool unless the next command is materially different and has a clear reason based on the latest output.
26
+
27
+ This guard applies to all shell commands, including but not limited to:
28
+
29
+ - test commands
30
+ - build commands
31
+ - git commands
32
+ - install commands
33
+ - formatting commands
34
+ - project scripts
35
+ - status or inspection commands
36
+
37
+ Do not "verify one more time" by repeating the same successful command. If verification is needed, use a different command that checks a different fact, or explain the current result to the user.
38
+
39
+ If you notice that you are about to repeat a successful command with the same arguments, stop using tools and respond to the user with the current result.
40
+
41
+ If you produce or observe text like "I'm stuck in a loop", do not call any more tools. Immediately send the final user-facing response explaining:
42
+
43
+ - what has completed,
44
+ - what is still uncertain, if anything,
45
+ - and what the user can do next, if action is needed.
46
+
47
+ ## Hard Stop Rule
48
+
49
+ Never execute the same successful shell command three times for the same task in this project.
50
+
51
+ If the same command has already succeeded twice in a row, the next assistant action must be a final response to the user, not another tool call.
52
+
53
+ Successful repeated command execution is a terminal condition. Prefer a concise final response over further tool calls.
54
+
1
55
  如果计划写好了等待你的审阅,将计划文件通过当前聊天软件发给用户
@@ -18,7 +18,7 @@
18
18
  "effort": "",
19
19
  "apiKey": "",
20
20
  "baseUrl": "",
21
- "maxTurn": 0
21
+ "maxTurn": 0
22
22
  },
23
23
  "cursor": {
24
24
  "enabled": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.136",
3
+ "version": "0.2.138",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@ai-sdk/openai-compatible": "^2.0.47",
44
- "@anthropic-ai/claude-agent-sdk": "0.3.153",
44
+ "@anthropic-ai/claude-agent-sdk": "0.2.133",
45
45
  "@larksuiteoapi/node-sdk": "^1.59.0",
46
46
  "@openilink/openilink-sdk-node": "^0.6.0",
47
47
  "ai": "^6.0.184",
@@ -8,10 +8,11 @@ import {
8
8
  APP_ID,
9
9
  APP_SECRET,
10
10
  CHATCCC_PORT,
11
- CLAUDE_API_KEY,
12
- CLAUDE_BASE_URL,
13
- CLAUDE_EFFORT,
14
- CLAUDE_MODEL,
11
+ CLAUDE_API_KEY,
12
+ CLAUDE_BASE_URL,
13
+ CLAUDE_EFFORT,
14
+ CLAUDE_MAX_TURN,
15
+ CLAUDE_MODEL,
15
16
  CLAUDE_SUBAGENT_MODEL,
16
17
  CURSOR_AGENT_ARGS,
17
18
  CURSOR_AGENT_COMMAND,
@@ -81,7 +82,7 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
81
82
  expect(APP_SECRET).toBe("NEW_APP_SECRET");
82
83
  });
83
84
 
84
- it("更新 Claude 配置(model / subagentModel / effort / apiKey / baseUrl)", () => {
85
+ it("更新 Claude 配置(model / subagentModel / effort / maxTurn / apiKey / baseUrl)", () => {
85
86
  applyLoadedConfig({
86
87
  ...structuredClone(baseAppConfig),
87
88
  claude: {
@@ -89,17 +90,18 @@ describe("applyLoadedConfig — 刷新 export let 常量", () => {
89
90
  defaultAgent: true,
90
91
  model: "claude-sonnet-4-6",
91
92
  subagentModel: "claude-haiku-4-5-20251001",
92
- effort: "high",
93
- apiKey: "sk-test",
94
- baseUrl: "https://api.example.com",
95
- maxTurn: 0,
96
- },
97
- });
98
-
99
- expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
100
- expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
101
- expect(CLAUDE_EFFORT).toBe("high");
102
- expect(CLAUDE_API_KEY).toBe("sk-test");
93
+ effort: "high",
94
+ apiKey: "sk-test",
95
+ baseUrl: "https://api.example.com",
96
+ maxTurn: 7,
97
+ },
98
+ });
99
+
100
+ expect(CLAUDE_MODEL).toBe("claude-sonnet-4-6");
101
+ expect(CLAUDE_SUBAGENT_MODEL).toBe("claude-haiku-4-5-20251001");
102
+ expect(CLAUDE_EFFORT).toBe("high");
103
+ expect(CLAUDE_MAX_TURN).toBe(7);
104
+ expect(CLAUDE_API_KEY).toBe("sk-test");
103
105
  expect(CLAUDE_BASE_URL).toBe("https://api.example.com");
104
106
  });
105
107
 
@@ -5,9 +5,12 @@ import { fileURLToPath } from "node:url";
5
5
 
6
6
  import {
7
7
  getSessionInfo as sdkGetSessionInfo,
8
- query,
9
- type Options as ClaudeSdkOptions,
8
+ unstable_v2_createSession,
9
+ unstable_v2_resumeSession,
10
10
  type SDKMessage,
11
+ type SDKSession,
12
+ type SDKSessionOptions,
13
+ type EffortLevel,
11
14
  } from "@anthropic-ai/claude-agent-sdk";
12
15
 
13
16
  import type {
@@ -220,6 +223,17 @@ export function buildClaudePromptText(
220
223
  ].join("\n");
221
224
  }
222
225
 
226
+ type ClaudeSdkSessionOptions = Omit<SDKSessionOptions, "model"> & {
227
+ model?: string;
228
+ abortController?: AbortController;
229
+ autoCompactEnabled?: boolean;
230
+ effort?: EffortLevel | number;
231
+ maxTurns?: number;
232
+ mcpServers?: Record<string, unknown>;
233
+ skills?: "all";
234
+ stderr?: (data: string) => void;
235
+ };
236
+
223
237
  function buildSdkOptions(args: {
224
238
  cwd: string;
225
239
  model: string;
@@ -230,9 +244,8 @@ function buildSdkOptions(args: {
230
244
  baseUrl?: string;
231
245
  maxTurn: number;
232
246
  planMode?: boolean;
233
- resume?: string;
234
247
  abortController?: AbortController;
235
- }): ClaudeSdkOptions {
248
+ }): ClaudeSdkSessionOptions {
236
249
  const {
237
250
  cwd,
238
251
  model,
@@ -243,17 +256,16 @@ function buildSdkOptions(args: {
243
256
  baseUrl,
244
257
  maxTurn,
245
258
  planMode,
246
- resume,
247
259
  abortController,
248
260
  } = args;
249
261
 
250
- const options: ClaudeSdkOptions = {
262
+ const options: ClaudeSdkSessionOptions = {
251
263
  cwd,
252
264
  abortController,
253
265
  settingSources: ["user", "project", "local"] as SettingSource[],
254
266
  permissionMode: planMode ? "plan" : "bypassPermissions",
267
+ autoCompactEnabled: true,
255
268
  maxTurns: maxTurn,
256
- maxBudgetUsd: 999999999,
257
269
  skills: "all",
258
270
  stderr: (data) => {
259
271
  const trimmed = data.trim();
@@ -270,10 +282,7 @@ function buildSdkOptions(args: {
270
282
  options.model = model;
271
283
  }
272
284
  if (!isEmpty(effort)) {
273
- options.effort = effort as ClaudeSdkOptions["effort"];
274
- }
275
- if (resume) {
276
- options.resume = resume;
285
+ options.effort = effort as ClaudeSdkSessionOptions["effort"];
277
286
  }
278
287
 
279
288
  const env = buildSdkEnv(subagentModel, apiKey, baseUrl);
@@ -283,7 +292,7 @@ function buildSdkOptions(args: {
283
292
 
284
293
  const mcpServers = readMcpServersConfig();
285
294
  if (mcpServers) {
286
- options.mcpServers = mcpServers as ClaudeSdkOptions["mcpServers"];
295
+ options.mcpServers = mcpServers;
287
296
  }
288
297
 
289
298
  return options;
@@ -307,6 +316,14 @@ function bridgeAbortSignal(
307
316
  return () => signal.removeEventListener("abort", onAbort);
308
317
  }
309
318
 
319
+ function closeSdkSession(session: SDKSession): void {
320
+ session.close();
321
+ }
322
+
323
+ function toSdkSessionOptions(options: ClaudeSdkSessionOptions): SDKSessionOptions {
324
+ return options as SDKSessionOptions;
325
+ }
326
+
310
327
  class ClaudeAdapter implements ToolAdapter {
311
328
  readonly displayName = "Claude Code";
312
329
  readonly sessionDescPrefix = "Claude Code Session:";
@@ -334,9 +351,8 @@ class ClaudeAdapter implements ToolAdapter {
334
351
  logMcpConfig();
335
352
  const abortController = new AbortController();
336
353
  let sessionId: string | undefined;
337
- const stream = query({
338
- prompt: "ok",
339
- options: buildSdkOptions({
354
+ const session = unstable_v2_createSession(
355
+ toSdkSessionOptions(buildSdkOptions({
340
356
  cwd,
341
357
  model: this.model,
342
358
  effort: this.effort,
@@ -346,11 +362,12 @@ class ClaudeAdapter implements ToolAdapter {
346
362
  baseUrl: this.baseUrl,
347
363
  maxTurn: this.maxTurn,
348
364
  abortController,
349
- }),
350
- });
365
+ })),
366
+ );
351
367
 
352
368
  try {
353
- for await (const raw of stream) {
369
+ await session.send("ok");
370
+ for await (const raw of session.stream()) {
354
371
  const msg = toMessageLike(raw);
355
372
  if (msg.session_id && !sessionId) {
356
373
  sessionId = msg.session_id;
@@ -363,7 +380,7 @@ class ClaudeAdapter implements ToolAdapter {
363
380
  }
364
381
  }
365
382
  } finally {
366
- stream.close();
383
+ closeSdkSession(session);
367
384
  }
368
385
 
369
386
  if (sessionId) return { sessionId };
@@ -382,9 +399,9 @@ class ClaudeAdapter implements ToolAdapter {
382
399
  if (abortController.signal.aborted) return;
383
400
  let aborted = false;
384
401
 
385
- const stream = query({
386
- prompt: buildClaudePromptText(userText),
387
- options: buildSdkOptions({
402
+ const session = unstable_v2_resumeSession(
403
+ sessionId,
404
+ toSdkSessionOptions(buildSdkOptions({
388
405
  cwd,
389
406
  model: this.model,
390
407
  effort: this.effort,
@@ -394,13 +411,13 @@ class ClaudeAdapter implements ToolAdapter {
394
411
  baseUrl: this.baseUrl,
395
412
  maxTurn: this.maxTurn,
396
413
  planMode: options?.planMode,
397
- resume: sessionId,
398
414
  abortController,
399
- }),
400
- });
415
+ })),
416
+ );
401
417
 
402
418
  try {
403
- for await (const raw of stream) {
419
+ await session.send(buildClaudePromptText(userText));
420
+ for await (const raw of session.stream()) {
404
421
  if (abortController.signal.aborted) {
405
422
  aborted = true;
406
423
  break;
@@ -423,8 +440,8 @@ class ClaudeAdapter implements ToolAdapter {
423
440
  removeAbortListener?.();
424
441
  if (aborted || abortController.signal.aborted) {
425
442
  abortController.abort();
426
- stream.close();
427
443
  }
444
+ closeSdkSession(session);
428
445
  }
429
446
  }
430
447
 
package/src/config.ts CHANGED
@@ -605,8 +605,9 @@ export function applyLoadedConfig(next: AppConfig): void {
605
605
  ILINK_REUSE_TOKEN_ON_START = next.platforms.ilink.reuseTokenOnStart ?? true;
606
606
  CLAUDE_MODEL = next.claude.model;
607
607
  CLAUDE_SUBAGENT_MODEL = next.claude.subagentModel;
608
- CLAUDE_EFFORT = next.claude.effort;
609
- CLAUDE_API_KEY = next.claude.apiKey;
608
+ CLAUDE_EFFORT = next.claude.effort;
609
+ CLAUDE_MAX_TURN = next.claude.maxTurn;
610
+ CLAUDE_API_KEY = next.claude.apiKey;
610
611
  CLAUDE_BASE_URL = next.claude.baseUrl;
611
612
  GIT_TIMEOUT_SECONDS = next.gitTimeoutSeconds;
612
613
  GIT_TIMEOUT_MS = GIT_TIMEOUT_SECONDS * 1000;