chatccc 0.2.282 → 0.2.283

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/README.md CHANGED
@@ -451,7 +451,9 @@ ChatCCC 的内部重启和更新使用跨平台父子进程握手:替代进程
451
451
 
452
452
  飞书接收长连接启用 15 秒握手超时和 30 秒心跳应答超时;首次启动最多等待 45 秒真实连接确认后才提示就绪。运行期间连接持续异常 90 秒时,会关闭并重新建立接收连接,保留会话、消息去重和正在执行的任务。没有用户消息不会触发重连。连接状态与恢复记录写入运行日志和 `startup-trace.log`(`FEISHU-CONNECTION` / `feishu-connection`)。卡片更新遇到网络失败或序号冲突时不会当成送达成功,错误通知保留文本兜底;TLS 断线会明确显示为网络连接失败,已中断的 Agent 任务不会因此自动重放。
453
453
 
454
- > **模型切换**:`/model` 查看当前会话 Agent 的可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。可选模型来自当前 Agent 的配置:Claude 使用 `claude.model` / `claude.subagentModel`;Cursor、Codex、CCC Agent 和 DSH 使用各自的 `model` / `alternativeModel`。
454
+ > **模型切换**:`/model` 查看当前会话 Agent 的可选模型清单,`/model <名称>` 模糊匹配切换,`/model clear` 恢复默认。可选模型来自当前 Agent 的配置:Claude 使用 `claude.model` / `claude.subagentModel`;Cursor、Codex、CCC Agent 和 DSH 使用各自的 `model` / `alternativeModel`。
455
+
456
+ Agent 的初始化、输入回显与重连通知不代表任务成功。Cursor、Codex 和 Claude 缺少成功完成事件,或任一 Agent 没有产生有效回复时,会明确提示异常;已生成的部分回复会保留并标为可能不完整。Cursor 重连过程显示在状态区,上游的 `resource_exhausted` / `unavailable` 分别提示请求受限 / 服务暂不可用,不推断为余额耗尽。执行失败不会自动重放整条任务。
455
457
 
456
458
  > **Codex Fast 模式**:Web UI 中的“Fast 模式”设置新 Codex 会话的全局默认值,默认关闭。进入 Codex 会话后,`/fast` 查询当前状态,`/fast on` 和 `/fast off` 只覆盖当前会话并从下一条消息生效。ChatCCC 会显式向 Codex CLI 传入 `service_tier="fast"` 或 `service_tier="default"`,因此关闭时不会继承用户 `config.toml` 中可能开启的 Fast。
457
459
 
@@ -8,6 +8,8 @@ DeepCCC 是一个本地优先的开源 Coding Agent,同时提供浏览器多
8
8
 
9
9
  ## 安装与快速开始
10
10
 
11
+ 模型原生流未收到完成事件、返回错误结束原因或达到输出长度限制时,会报告异常并保留已经输出的内容,不将不完整回复判为成功。没有回复且没有工具执行的空结果也会明确报错。用户主动取消仍按中断处理。
12
+
11
13
  全局安装:
12
14
 
13
15
  ```bash
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepccc",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "A lightweight coding agent with OpenAI-compatible and Anthropic Messages API support.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -478,16 +478,25 @@ export class ChatSession {
478
478
  messages: attemptMessages,
479
479
  };
480
480
  let stream;
481
+ let requiresFinish = false;
482
+ let receivedFinish = false;
483
+ let finishReason;
481
484
  if (this.streaming) {
482
485
  const result = streamText(generationOptions);
486
+ requiresFinish = result.fullStream != null;
483
487
  stream = result.fullStream ?? textStreamToFullStream(result.textStream);
484
488
  }
485
489
  else {
486
490
  const result = await generateText(generationOptions);
491
+ finishReason = result.finishReason;
487
492
  stream = generateResultToFullStream(result);
488
493
  }
489
494
  for await (const part of stream) {
490
495
  rawLog?.writeLine(safeRawStreamJson(part));
496
+ if (part.type === "finish") {
497
+ receivedFinish = true;
498
+ finishReason = part.finishReason;
499
+ }
491
500
  if (part.type === "reasoning-start" || part.type === "reasoning-delta") {
492
501
  // Reasoning content remains private. A throttled heartbeat is enough
493
502
  // for ChatCCC to distinguish active inference from a stalled stream.
@@ -561,6 +570,14 @@ export class ChatSession {
561
570
  throw new Error(message);
562
571
  }
563
572
  }
573
+ if (!signal?.aborted) {
574
+ if (requiresFinish && !receivedFinish)
575
+ throw new Error("DeepCCC 输出流中断:未收到模型完成事件,回复可能不完整");
576
+ if (finishReason === "error" || finishReason === "length")
577
+ throw new Error(`DeepCCC 未正常完成:finishReason=${finishReason},回复可能不完整`);
578
+ if (!fullText.trim() && toolCallOrder.length === 0)
579
+ throw new Error("DeepCCC 本轮未产生有效回复");
580
+ }
564
581
  if (hasMalformedToolProtocolText(fullText)) {
565
582
  console.warn(`[DeepCCC] malformed tool protocol text detected for ${this.context.sessionId} `
566
583
  + `(attempt ${attempt + 1}/2, structuredToolCalls=${toolCallOrder.length})`);
@@ -2,6 +2,7 @@ import { ChatSession } from "../../deepccc-agent/src/index.js";
2
2
  import { config as deepCccConfig } from "../../deepccc-agent/src/config.js";
3
3
  import { getBuiltinContextSession, newBuiltinSessionId, normalizeBuiltinSessionId, } from "../../deepccc-agent/src/context.js";
4
4
  import { config, CCC_SESSION_PREFIX } from "../config.js";
5
+ import { createTurnCompletion } from "./turn-completion.js";
5
6
  function toChatSessionOptions(sessionId, cwd, options) {
6
7
  return {
7
8
  cwd,
@@ -50,7 +51,12 @@ export function createCccAdapter(options = {}) {
50
51
  async *prompt(sessionId, userText, cwd, signal, _promptOptions) {
51
52
  const normalizedSessionId = normalizeBuiltinSessionId(sessionId);
52
53
  const session = new ChatSession(chatConfig, toChatSessionOptions(normalizedSessionId, cwd, options));
54
+ const completion = createTurnCompletion("CCC Agent");
53
55
  for await (const event of session.chat(userText, signal)) {
56
+ if (event.type === "text")
57
+ completion.observe({ type: "assistant", blocks: [{ type: "text", text: event.text }] });
58
+ if (event.type === "text_reset")
59
+ completion.observe({ type: "assistant", blocks: [{ type: "text_reset" }] });
54
60
  if (event.type === "status") {
55
61
  yield {
56
62
  type: "assistant",
@@ -101,6 +107,7 @@ export function createCccAdapter(options = {}) {
101
107
  };
102
108
  }
103
109
  else if (event.type === "done" && !signal?.aborted) {
110
+ completion.complete();
104
111
  yield {
105
112
  type: "assistant",
106
113
  blocks: [],
@@ -111,6 +118,8 @@ export function createCccAdapter(options = {}) {
111
118
  throw new Error(event.message);
112
119
  }
113
120
  }
121
+ if (!signal?.aborted)
122
+ completion.assertComplete();
114
123
  },
115
124
  async getSessionInfo(sessionId) {
116
125
  const normalizedSessionId = normalizeBuiltinSessionId(sessionId);
@@ -11,6 +11,8 @@ import { homedir } from "node:os";
11
11
  import { delimiter, join } from "node:path";
12
12
  import { pathToFileURL } from "node:url";
13
13
  import { parseUserCommand } from "./adapter-interface.js";
14
+ import { createTurnCompletion } from "./turn-completion.js";
15
+ import { sanitizeTerminalErrorDetail } from "../terminal-error.js";
14
16
  import { CHATCCC_PORT, config, PROJECT_ROOT, RAW_STREAM_LOGS_DIR } from "../config.js";
15
17
  import { defaultClaudeSessionMetaStore, } from "./claude-session-meta-store.js";
16
18
  import { createRawStreamLog, } from "./raw-stream-log.js";
@@ -127,6 +129,9 @@ function logMcpConfig() {
127
129
  }
128
130
  }
129
131
  export function normalizeSdkMessage(msg) {
132
+ if (msg.type === "result" && (msg.subtype !== "success" || msg.is_error === true)) {
133
+ throw new Error(`Claude 执行失败:${sanitizeTerminalErrorDetail(JSON.stringify(msg.errors ?? msg.result ?? msg.subtype ?? "未提供错误详情"))}`);
134
+ }
130
135
  // SDK result/success 是 Claude 对本轮完整结束的权威确认。文本已经由之前的
131
136
  // assistant 消息累计,因此这里只发送终态信号,避免重复追加 result 文本。
132
137
  if (msg.type === "result" && msg.subtype === "success") {
@@ -399,6 +404,7 @@ class ClaudeAdapter {
399
404
  return;
400
405
  let aborted = false;
401
406
  let completed = false;
407
+ const completion = createTurnCompletion("Claude");
402
408
  const rawLogConfig = config.rawStreamLogs.claude;
403
409
  let rawLog = null;
404
410
  const sdk = await loadClaudeSdkModule();
@@ -449,10 +455,24 @@ class ClaudeAdapter {
449
455
  }
450
456
  }
451
457
  const normalized = normalizeSdkMessage(msg);
458
+ completion.observe(normalized);
459
+ if (msg.type === "result") {
460
+ // Some SDK modes only put the final reply in result.result.
461
+ if (msg.result?.trim()) {
462
+ const final = { type: "assistant", blocks: [{ type: "text_final", text: msg.result }] };
463
+ completion.observe(final);
464
+ yield final;
465
+ }
466
+ completion.complete();
467
+ completed = true;
468
+ }
452
469
  if (normalized)
453
470
  yield normalized;
471
+ if (completed)
472
+ break;
454
473
  }
455
- completed = !aborted && !abortController.signal.aborted;
474
+ if (!aborted && !abortController.signal.aborted)
475
+ completion.assertComplete();
456
476
  }
457
477
  finally {
458
478
  removeAbortListener?.();
@@ -7,6 +7,7 @@
7
7
  // - getSessionInfo: 从持久化映射读取 cwd / threadId
8
8
  // =============================================================================
9
9
  import { spawn } from "node:child_process";
10
+ import { createTurnCompletion } from "./turn-completion.js";
10
11
  import { existsSync, readFileSync } from "node:fs";
11
12
  import { join } from "node:path";
12
13
  import { randomUUID } from "node:crypto";
@@ -71,8 +72,8 @@ function resolveCodexEffort() {
71
72
  // normalizeCodexMessage — Codex 事件 → UnifiedStreamMessage | null
72
73
  // ---------------------------------------------------------------------------
73
74
  export function normalizeCodexMessage(msg) {
74
- if (msg.type === "error" && msg.message?.trim()) {
75
- throw new Error(`Codex turn failed: ${msg.message.trim()}`);
75
+ if (msg.type === "error") {
76
+ throw new Error(`Codex turn failed: ${msg.message?.trim() || "未提供错误详情"}`);
76
77
  }
77
78
  if (msg.type === "turn.failed") {
78
79
  const detail = typeof msg.error === "string" ? msg.error : msg.error?.message;
@@ -160,10 +161,18 @@ function spawnCodex(args, cwd, stdinText, modelOverride, effortOverride, fastMod
160
161
  shell: true,
161
162
  });
162
163
  let stderr = "";
164
+ let exitCode = null;
165
+ let signal = null;
166
+ let settleClose = () => { };
167
+ const closed = new Promise(resolve => { settleClose = resolve; });
168
+ proc.once("error", (error) => { stderr += `\n${error.message}`; settleClose(); });
163
169
  proc.stderr.on("data", (chunk) => {
164
170
  stderr += chunk.toString();
165
171
  });
166
- proc.on("close", (code) => {
172
+ proc.on("close", (code, exitSignal) => {
173
+ exitCode = code;
174
+ signal = exitSignal;
175
+ settleClose();
167
176
  if (code !== 0 && stderr.trim()) {
168
177
  console.error(`[Codex stderr] exit=${code}: ${stderr.trim().slice(0, 2000)}`);
169
178
  }
@@ -172,7 +181,17 @@ function spawnCodex(args, cwd, stdinText, modelOverride, effortOverride, fastMod
172
181
  proc.stdin.write(stdinText);
173
182
  proc.stdin.end();
174
183
  }
175
- return proc;
184
+ return { proc, async failureDetail() {
185
+ let timer;
186
+ try {
187
+ await Promise.race([closed, new Promise(resolve => { timer = setTimeout(resolve, 2_000); })]);
188
+ return `exit=${exitCode ?? "unknown"}; signal=${signal ?? "none"}; ${stderr.trim() || "无 stderr 详情"}`;
189
+ }
190
+ finally {
191
+ if (timer)
192
+ clearTimeout(timer);
193
+ }
194
+ } };
176
195
  }
177
196
  async function* readJsonLines(proc, signal, rawLog) {
178
197
  yield* readJsonLinesWithBadJsonIdleWatchdog({
@@ -219,7 +238,8 @@ class CodexAdapter {
219
238
  const args = isFirstPrompt
220
239
  ? [...baseArgs, "-C", cwd, "-"]
221
240
  : [...baseArgs, "resume", threadId, "-"];
222
- const proc = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride, this.fastModeOverride);
241
+ const handle = spawnCodex(args, cwd, buildCodexPromptText(userText), this.modelOverride, this.effortOverride, this.fastModeOverride);
242
+ const proc = handle.proc;
223
243
  if (proc.pid !== undefined)
224
244
  options?.onProcessStart?.({ pid: proc.pid });
225
245
  const rawLogConfig = config.rawStreamLogs.codex;
@@ -245,12 +265,11 @@ class CodexAdapter {
245
265
  const onAbort = () => { void killProcessTree(proc.pid); };
246
266
  signal?.addEventListener("abort", onAbort, { once: true });
247
267
  let completed = false;
268
+ const completion = createTurnCompletion("Codex");
248
269
  try {
249
270
  for await (const raw of readJsonLines(proc, signal, rawLog)) {
250
271
  if (signal?.aborted)
251
272
  break;
252
- if (raw.type === "turn.completed")
253
- completed = true;
254
273
  if (isFirstPrompt &&
255
274
  raw.type === "thread.started" &&
256
275
  raw.thread_id) {
@@ -259,9 +278,18 @@ class CodexAdapter {
259
278
  .catch(() => { });
260
279
  }
261
280
  const normalized = normalizeCodexMessage(raw);
281
+ completion.observe(normalized);
282
+ if (raw.type === "turn.completed") {
283
+ completion.complete();
284
+ completed = true;
285
+ }
262
286
  if (normalized)
263
287
  yield normalized;
288
+ if (completed)
289
+ break;
264
290
  }
291
+ if (!signal?.aborted && !completed)
292
+ completion.assertComplete(await handle.failureDetail());
265
293
  }
266
294
  finally {
267
295
  signal?.removeEventListener("abort", onAbort);
@@ -8,6 +8,7 @@ import { spawn } from "node:child_process";
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { parseUserCommand } from "./adapter-interface.js";
11
+ import { createTurnCompletion } from "./turn-completion.js";
11
12
  import { config, CURSOR_AGENT_COMMAND, CURSOR_AGENT_ARGS, PROJECT_ROOT, RAW_STREAM_LOGS_DIR } from "../config.js";
12
13
  import { defaultCursorSessionMetaStore, } from "./cursor-session-meta-store.js";
13
14
  import { killProcessTree } from "./proc-tree-kill.js";
@@ -106,6 +107,15 @@ function mapToolCallKey(key) {
106
107
  return KEY_MAP[key] ?? key;
107
108
  }
108
109
  export function normalizeCursorMessage(msg) {
110
+ if (msg.type === "error" || (msg.type === "result" && (msg.is_error === true || msg.subtype?.startsWith("error")))) {
111
+ throw new Error(`Cursor 执行失败:${formatCursorVisibleStderr(JSON.stringify(msg.errors ?? msg.error ?? msg.result ?? msg.subtype ?? "未提供错误详情"))}`);
112
+ }
113
+ if (msg.type === "connection" || msg.type === "retry") {
114
+ return { type: "system", blocks: [{ type: "agent_status",
115
+ status: msg.subtype === "reconnected" ? "responding" : "reconnecting",
116
+ ...(Number.isInteger(msg.attempt) && msg.attempt > 0 ? { attempt: msg.attempt } : {}),
117
+ }] };
118
+ }
109
119
  if (msg.type === "assistant" && msg.message?.content) {
110
120
  // 按 cursor 官方 stream-json 规范区分三类 assistant 事件,避免 text 重复累加:
111
121
  // ┌────────────────┬───────────────┬─────────────────┐
@@ -312,8 +322,17 @@ function spawnAgent(extraArgs, cwd, stdinText, modelOverride, mode, spawnImpl =
312
322
  waitForClose: async () => {
313
323
  if (closeInfo)
314
324
  return { ...closeInfo, stderr };
315
- const info = await closePromise;
316
- return { ...info, stderr };
325
+ let timer;
326
+ try {
327
+ const info = await Promise.race([closePromise, new Promise(resolve => {
328
+ timer = setTimeout(() => resolve({ code: null, signal: null, stderr }), 2_000);
329
+ })]);
330
+ return { ...info, stderr };
331
+ }
332
+ finally {
333
+ if (timer)
334
+ clearTimeout(timer);
335
+ }
317
336
  },
318
337
  };
319
338
  }
@@ -425,6 +444,7 @@ class CursorAdapter {
425
444
  const onAbort = () => { void killProcessTree(proc.pid); };
426
445
  signal?.addEventListener("abort", onAbort, { once: true });
427
446
  let sawResult = false;
447
+ const completion = createTurnCompletion("Cursor");
428
448
  const stats = createCursorStreamStats();
429
449
  try {
430
450
  for await (const raw of readJsonLines(proc, signal, sessionId, rawLog, stats, this.badJsonIdleTimeoutMs)) {
@@ -439,10 +459,15 @@ class CursorAdapter {
439
459
  .catch(() => { });
440
460
  }
441
461
  const normalized = normalizeCursorMessage(raw);
462
+ completion.observe(normalized);
463
+ if (raw.type === "result")
464
+ completion.complete();
442
465
  if (normalized)
443
466
  yield normalized;
444
467
  // result 是流末事件,收到后立即结束进程,防止 CLI 僵死导致 readline 挂起。
445
468
  if (raw.type === "result") {
469
+ if (!normalized?.isFinalResponse)
470
+ yield { type: "assistant", blocks: [], isFinalResponse: true };
446
471
  sawResult = true;
447
472
  void killProcessTree(proc.pid);
448
473
  break;
@@ -462,6 +487,7 @@ class CursorAdapter {
462
487
  };
463
488
  throw createCursorAgentFailureError(closeInfo);
464
489
  }
490
+ completion.assertComplete(`exit=${closeInfo.code ?? "unknown"}; signal=${closeInfo.signal ?? "none"}; ${formatCursorVisibleStderr(closeInfo.stderr) || "无 stderr 详情"}`);
465
491
  }
466
492
  }
467
493
  finally {
@@ -125,22 +125,18 @@ export function createDshAdapter(options = {}) {
125
125
  queue.push(message);
126
126
  },
127
127
  });
128
- if (result.finalResponse) {
128
+ if (turnError) {
129
+ if (result.finalResponse?.trim())
130
+ queue.push({ type: "assistant", blocks: [{ type: "text_final", text: result.finalResponse }] });
131
+ queue.fail(turnError);
132
+ }
133
+ else if (result.finalResponse?.trim()) {
129
134
  rawLog?.writeLine(JSON.stringify({ type: "run.result", result }));
130
135
  queue.push({ type: "assistant", blocks: [{ type: "text_final", text: result.finalResponse }], isFinalResponse: true });
131
136
  completed = true;
132
137
  }
133
- else if (turnError) {
134
- queue.fail(turnError);
135
- }
136
138
  else {
137
- // 引擎正常结束但零输出(无错误、无回复):给出明确提示,避免用户只看到"失败"
138
- queue.push({
139
- type: "assistant",
140
- blocks: [{ type: "text_final", text: "DeepSeek Harness 本轮未产生任何回复(引擎未报告错误)。" }],
141
- isFinalResponse: true,
142
- });
143
- completed = true;
139
+ queue.fail(new Error("DeepSeek Harness 本轮未产生有效回复(引擎未报告错误)。"));
144
140
  }
145
141
  if (completed) {
146
142
  knownSessions.set(sessionId, { sessionId, cwd, lastModified: Date.now(), model: options.model ?? "deepseek-v4-flash" });
@@ -0,0 +1,25 @@
1
+ import { sanitizeTerminalErrorDetail } from "../terminal-error.js";
2
+ /** Protocol output (init, echo, heartbeat, tools) is not a successful reply. */
3
+ export function createTurnCompletion(tool) {
4
+ let hasReply = false;
5
+ let completed = false;
6
+ return {
7
+ observe(message) {
8
+ for (const block of message?.blocks ?? []) {
9
+ if (block.type === "text_reset")
10
+ hasReply = false;
11
+ if (message?.type === "assistant" && (block.type === "text" || block.type === "text_final") && block.text.trim())
12
+ hasReply = true;
13
+ }
14
+ },
15
+ complete() {
16
+ if (!hasReply)
17
+ throw new Error(`${tool} 本轮未产生有效回复(收到结束事件,但回复为空)。`);
18
+ completed = true;
19
+ },
20
+ assertComplete(detail = "") {
21
+ if (!completed)
22
+ throw new Error(`${tool} 未正常完成:输出流结束但未收到成功完成事件${detail ? `;${sanitizeTerminalErrorDetail(detail)}` : ""}。`);
23
+ },
24
+ };
25
+ }
@@ -7,6 +7,7 @@ export function createAgentActivityTracker(now = Date.now()) {
7
7
  }
8
8
  function sameVisibleActivity(left, right) {
9
9
  return left.kind === right.kind
10
+ && left.attempt === right.attempt
10
11
  && left.toolName === right.toolName
11
12
  && left.toolCount === right.toolCount;
12
13
  }
@@ -73,8 +74,9 @@ export function updateAgentActivity(tracker, block, now = Date.now()) {
73
74
  return setActivity(tracker, { kind: "responding", startedAt: now });
74
75
  case "agent_status":
75
76
  return setActivity(tracker, {
76
- kind: block.status === "compacting" ? "compacting" : "responding",
77
+ kind: block.status,
77
78
  startedAt: now,
79
+ ...(block.attempt ? { attempt: block.attempt } : {}),
78
80
  });
79
81
  case "thinking":
80
82
  case "redacted_thinking":
@@ -126,6 +128,9 @@ export function formatAgentActivityTitle(activity, now = Date.now()) {
126
128
  case "responding":
127
129
  label = "正在生成回复";
128
130
  break;
131
+ case "reconnecting":
132
+ label = `正在重新连接模型服务${activity.attempt ? `(第 ${activity.attempt} 次)` : ""}`;
133
+ break;
129
134
  case "searching":
130
135
  label = "正在处理搜索结果";
131
136
  break;
@@ -37,7 +37,7 @@ export function classifyTerminalError(error, occurredAt = Date.now()) {
37
37
  const lower = raw.toLowerCase();
38
38
  const attempts = parsePositiveInt(raw, /\bafter\s+(\d+)\s+attempts?\b/i);
39
39
  const timeoutMs = parsePositiveInt(raw, /\btimeout\s*:\s*(\d+)\s*ms\b/i);
40
- if (/\b429\b|rate[ _-]?limit|too many requests/.test(lower)) {
40
+ if (/\b429\b|rate[ _-]?limit|too many requests|resource_exhausted/.test(lower)) {
41
41
  return {
42
42
  kind: "rate_limit",
43
43
  title: "请求受到限流",
@@ -73,7 +73,7 @@ export function classifyTerminalError(error, occurredAt = Date.now()) {
73
73
  };
74
74
  }
75
75
  const httpStatus = raw.match(/\b(?:HTTP\s*)?(5\d\d)\b/i)?.[1];
76
- if (httpStatus || /service unavailable|bad gateway|gateway timeout|provider error/.test(lower)) {
76
+ if (httpStatus || /service unavailable|\[unavailable\]|bad gateway|gateway timeout|provider error/.test(lower)) {
77
77
  return {
78
78
  kind: "provider",
79
79
  title: "模型服务暂时不可用",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.282",
3
+ "version": "0.2.283",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",