chatccc 0.2.270 → 0.2.276

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.
Files changed (42) hide show
  1. package/README.md +16 -10
  2. package/config.sample.json +4 -3
  3. package/deepccc-agent/README.md +147 -61
  4. package/deepccc-agent/package.json +5 -2
  5. package/dist/deepccc-agent/src/attachments.js +192 -0
  6. package/dist/deepccc-agent/src/cli.js +59 -13
  7. package/dist/deepccc-agent/src/config.js +57 -4
  8. package/dist/deepccc-agent/src/context.js +299 -16
  9. package/dist/deepccc-agent/src/file-tools.js +33 -0
  10. package/dist/deepccc-agent/src/index.js +68 -21
  11. package/dist/deepccc-agent/src/tool-protocol.js +14 -3
  12. package/dist/deepccc-agent/src/web-entry.js +72 -0
  13. package/dist/deepccc-agent/src/web-page.js +414 -0
  14. package/dist/deepccc-agent/src/web-runtime.js +331 -0
  15. package/dist/deepccc-agent/src/web-server.js +476 -0
  16. package/dist/deepccc-agent/src/web-session-store.js +162 -0
  17. package/dist/deepccc-agent/src/web-tool-presentation.js +123 -0
  18. package/dist/src/adapters/ccc-adapter.js +5 -1
  19. package/dist/src/agent-capability-grants.js +26 -0
  20. package/dist/src/agent-delegate-task.js +5 -2
  21. package/dist/src/agent-file-rpc.js +6 -1
  22. package/dist/src/agent-image-rpc.js +6 -1
  23. package/dist/src/agent-team/application/task-execution-service.js +330 -97
  24. package/dist/src/agent-team/domain/task-run.js +14 -1
  25. package/dist/src/agent-team/infrastructure/task-execution-runtime.js +7 -2
  26. package/dist/src/agent-team/main-agent-bootstrap.js +24 -1
  27. package/dist/src/agent-team/repositories/json-task-run-repository.js +22 -4
  28. package/dist/src/agent-team/web/agent-team-page.js +14 -7
  29. package/dist/src/cards.js +7 -4
  30. package/dist/src/config.js +12 -0
  31. package/dist/src/im-skills.js +9 -2
  32. package/dist/src/orchestrator.js +117 -29
  33. package/dist/src/safe-maintenance.js +4 -1
  34. package/dist/src/session-name.js +15 -0
  35. package/dist/src/session.js +54 -9
  36. package/dist/src/web-ui.js +76 -32
  37. package/im-skills/feishu-skill/receive-send-file.md +3 -2
  38. package/im-skills/feishu-skill/receive-send-image.md +3 -2
  39. package/im-skills/feishu-skill/send-file.mjs +6 -5
  40. package/im-skills/feishu-skill/send-image.mjs +6 -5
  41. package/im-skills/feishu-skill/skill.md +4 -2
  42. package/package.json +1 -1
@@ -14,7 +14,8 @@ import { createCccAdapter } from "./adapters/ccc-adapter.js";
14
14
  import { createDshAdapter } from "./adapters/dsh-adapter.js";
15
15
  import { killProcessTree } from "./adapters/proc-tree-kill.js";
16
16
  import { resourceMonitor, registerProcess, unregisterProcess } from "./adapters/resource-monitor.js";
17
- import { buildImSkillsPromptCached, exportSkillSubDocs } from "./im-skills.js";
17
+ import { buildImSkillsPromptCached, exportSkillSubDocs, sessionImSkillsCacheDir, } from "./im-skills.js";
18
+ import { clearAgentCapabilityGrants, issueAgentCapabilityGrant, } from "./agent-capability-grants.js";
18
19
  import { hasResponseStalled, observeResponseProgress } from "./response-stall.js";
19
20
  import { classifyTerminalError, formatTerminalErrorNotice, formatTerminalErrorReason, } from "./terminal-error.js";
20
21
  import { MAX_PROCESSED, clearFeishuMessageLedgerMemory, processedMessages, } from "./feishu-message-ingress.js";
@@ -384,6 +385,7 @@ export function resetState() {
384
385
  sessionModelOverrides.clear();
385
386
  sessionEffortOverrides.clear();
386
387
  sessionFastModeOverrides.clear();
388
+ clearAgentCapabilityGrants();
387
389
  adapterCache.clear();
388
390
  stopUnifiedDisplayLoop();
389
391
  console.log(`[${ts()}] [RESET] State cleared (dedup + active sessions + bindings)`);
@@ -508,11 +510,17 @@ export function setSessionFastModeOverride(sessionId, fastMode) {
508
510
  sessionFastModeOverrides.set(sessionId, fastMode);
509
511
  adapterCache.clear();
510
512
  }
513
+ function buildAdapterCacheKey(tool, model, effort, fastMode) {
514
+ const base = `${tool}:${model}:${effort}:${fastMode ? "fast" : "default"}`;
515
+ return tool === "ccc"
516
+ ? `${base}:${config.ccc.maxOutputTokens ?? "inherit"}`
517
+ : base;
518
+ }
511
519
  export function getAdapterForTool(tool, sessionId) {
512
520
  const effectiveModel = getEffectiveModelForTool(tool, sessionId);
513
521
  const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
514
522
  const effectiveFastMode = getEffectiveFastModeForTool(tool, sessionId);
515
- const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}:${effectiveFastMode ? "fast" : "default"}`;
523
+ const cacheKey = buildAdapterCacheKey(tool, effectiveModel || "", effectiveEffort || "", effectiveFastMode);
516
524
  const cached = adapterCache.get(cacheKey);
517
525
  if (cached)
518
526
  return cached;
@@ -535,6 +543,9 @@ export function getAdapterForTool(tool, sessionId) {
535
543
  compactionTimeoutMs: config.ccc.compactionTimeoutMs,
536
544
  contextWindow: config.ccc.contextWindow,
537
545
  ...(effectiveEffort ? { effort: effectiveEffort } : {}),
546
+ ...(config.ccc.maxOutputTokens !== null
547
+ ? { maxOutputTokens: config.ccc.maxOutputTokens }
548
+ : {}),
538
549
  // 留空("")不传 → ChatSession 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER)
539
550
  ...(config.ccc.provider ? { provider: config.ccc.provider } : {}),
540
551
  ...(config.ccc.subModel ? { subModel: config.ccc.subModel } : {}),
@@ -593,6 +604,22 @@ export async function saveSessionTool(sessionId, tool, chatName) {
593
604
  tool,
594
605
  createdAt: existing?.createdAt ?? Date.now(),
595
606
  ...(mergedChatName ? { chatName: mergedChatName } : {}),
607
+ ...(existing?.displayTitle ? { displayTitle: existing.displayTitle } : {}),
608
+ ...(existing?.pinned ? { pinned: true } : {}),
609
+ ...(existing?.archivedAt ? { archivedAt: existing.archivedAt } : {}),
610
+ };
611
+ await saveSessionTools(data);
612
+ }
613
+ export async function saveSessionPresentation(sessionId, patch) {
614
+ const data = await loadSessionTools();
615
+ const existing = data[sessionId];
616
+ if (!existing)
617
+ return;
618
+ data[sessionId] = {
619
+ ...existing,
620
+ ...(patch.displayTitle !== undefined ? { displayTitle: patch.displayTitle } : {}),
621
+ ...(patch.pinned !== undefined ? { pinned: patch.pinned } : {}),
622
+ ...(patch.archivedAt === null ? { archivedAt: undefined } : patch.archivedAt !== undefined ? { archivedAt: patch.archivedAt } : {}),
596
623
  };
597
624
  await saveSessionTools(data);
598
625
  }
@@ -665,6 +692,9 @@ export async function recordSessionRegistry(update) {
665
692
  tool: update.tool,
666
693
  chatType: update.chatType ?? existing?.chatType,
667
694
  chatName: update.chatName ?? existing?.chatName ?? "",
695
+ displayTitle: update.displayTitle ?? existing?.displayTitle,
696
+ pinned: update.pinned ?? existing?.pinned,
697
+ archivedAt: update.archivedAt === null ? undefined : update.archivedAt ?? existing?.archivedAt,
668
698
  namePolicy: update.namePolicy ?? existing?.namePolicy,
669
699
  turnCount: update.turnCount ?? existing?.turnCount ?? 0,
670
700
  lastContextTokens: update.lastContextTokens ?? existing?.lastContextTokens ?? 0,
@@ -800,7 +830,7 @@ export function accumulateBlockContent(block, state, toolCallMap) {
800
830
  }
801
831
  }
802
832
  export async function switchChatBinding(args) {
803
- const { chatId, chatType, oldSessionId, newSessionId, tool, chatName, namePolicy, newDescription, initialTurnCount = 0, initialContextTokens = 0, updateChatInfoFn, } = args;
833
+ const { chatId, chatType, oldSessionId, newSessionId, tool, chatName, namePolicy, displayTitle, pinned, archivedAt, newDescription, initialTurnCount = 0, initialContextTokens = 0, updateChatInfoFn, } = args;
804
834
  // Step 1: 群聊场景先调用飞书 API(不可逆操作放最前)。
805
835
  // 私聊跳过——p2p chatId 调 updateChatInfo 必然失败。
806
836
  if (chatType !== "p2p") {
@@ -838,6 +868,9 @@ export async function switchChatBinding(args) {
838
868
  chatType,
839
869
  chatName,
840
870
  namePolicy,
871
+ displayTitle,
872
+ pinned,
873
+ archivedAt,
841
874
  turnCount: initialTurnCount,
842
875
  lastContextTokens: initialContextTokens,
843
876
  startTime: now,
@@ -983,16 +1016,18 @@ export async function runAgentSession(sessionId, userText, platform, _chatId, ms
983
1016
  if (tid)
984
1017
  logTrace(tid, "SESSION_START", { sessionId, tool, cwd, turn: (sessionInfoMap.get(_chatId)?.turnCount ?? 0) + 1 });
985
1018
  console.log(`[${ts()}] Running ${adapter.displayName} session: ${sessionId} (${formatToolConfigForLog(tool, info?.model, sessionId)}, cwd=${cwd})`);
986
- // 构建 IM skills prompt(sessionId 方式,无 token)
1019
+ // 会话专属目录防止并发覆盖;capability grant 防止跨会话投递。
987
1020
  const feishuSkillDir = join(PROJECT_ROOT, "im-skills", "feishu-skill");
988
1021
  const wechatImageSkillDir = join(PROJECT_ROOT, "im-skills", "wechat-image-skill");
989
1022
  const wechatFileSkillDir = join(PROJECT_ROOT, "im-skills", "wechat-file-skill");
990
1023
  const wechatVideoSkillDir = join(PROJECT_ROOT, "im-skills", "wechat-video-skill");
991
- const imSkillsCacheDir = join(USER_DATA_DIR, "im-skills");
1024
+ const imSkillsCacheDir = sessionImSkillsCacheDir(join(USER_DATA_DIR, "im-skills"), sessionId);
1025
+ const agentCapabilityGrant = issueAgentCapabilityGrant(sessionId);
992
1026
  const skillVariables = {
993
1027
  cwd,
994
1028
  session_id: sessionId,
995
1029
  open_id: options.initiatorOpenId,
1030
+ agent_capability_grant: agentCapabilityGrant,
996
1031
  im_skills_cache_dir: imSkillsCacheDir,
997
1032
  delegate_task_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/delegate-task`,
998
1033
  set_cwd_url: `http://127.0.0.1:${CHATCCC_PORT}/api/agent/set-cwd`,
@@ -2132,7 +2167,7 @@ export async function getSessionStatus(chatId) {
2132
2167
  accumulatedLength,
2133
2168
  };
2134
2169
  }
2135
- export async function getAllSessionsStatus() {
2170
+ export async function getAllSessionsStatus(options = {}) {
2136
2171
  const registry = await loadSessionRegistry();
2137
2172
  const registryEntries = Object.values(registry)
2138
2173
  .filter((record) => record.chatId && record.sessionId && record.tool)
@@ -2150,6 +2185,9 @@ export async function getAllSessionsStatus() {
2150
2185
  sessionId,
2151
2186
  tool: record.tool,
2152
2187
  chatName: record.chatName ?? "",
2188
+ displayTitle: record.displayTitle ?? "",
2189
+ pinned: record.pinned ?? false,
2190
+ archivedAt: record.archivedAt,
2153
2191
  turnCount: 0,
2154
2192
  lastContextTokens: 0,
2155
2193
  startTime: active?.startTime ?? createdAt,
@@ -2158,9 +2196,13 @@ export async function getAllSessionsStatus() {
2158
2196
  sortTime: active?.startTime ?? createdAt,
2159
2197
  };
2160
2198
  });
2199
+ const query = options.query?.trim().toLowerCase() ?? "";
2161
2200
  const entries = [...registryEntries, ...orphanEntries]
2162
- .sort((a, b) => b.sortTime - a.sortTime)
2163
- .slice(0, 20);
2201
+ .filter((entry) => options.archivedOnly ? !!entry.archivedAt : options.includeArchived ? true : !entry.archivedAt)
2202
+ .filter((entry) => !query || [entry.displayTitle, entry.chatName, entry.sessionId, entry.tool]
2203
+ .some((value) => String(value ?? "").toLowerCase().includes(query)))
2204
+ .sort((a, b) => Number(!!b.pinned) - Number(!!a.pinned) || b.sortTime - a.sortTime)
2205
+ .slice(0, options.limit ?? 20);
2164
2206
  // 并行解析每个 session 的 model/effort(cursor 涉及异步 store IO)
2165
2207
  return Promise.all(entries.map(async (info) => {
2166
2208
  const { model, effort } = await resolveModelEffort(info.tool, info.sessionId);
@@ -2169,6 +2211,9 @@ export async function getAllSessionsStatus() {
2169
2211
  chatType: info.chatType,
2170
2212
  sessionId: info.sessionId,
2171
2213
  chatName: info.chatName || "",
2214
+ displayTitle: info.displayTitle || "",
2215
+ pinned: info.pinned ?? false,
2216
+ ...(info.archivedAt ? { archivedAt: info.archivedAt } : {}),
2172
2217
  active: !!activePrompts.get(info.sessionId) &&
2173
2218
  !activePrompts.get(info.sessionId)?.stopped &&
2174
2219
  !activePrompts.get(info.sessionId)?.abnormalExit,
@@ -2192,7 +2237,7 @@ export function _setAdapterForToolForTest(tool, adapter) {
2192
2237
  const effective = getEffectiveModelForTool(tool);
2193
2238
  const effort = getEffectiveEffortForTool(tool);
2194
2239
  const fastMode = getEffectiveFastModeForTool(tool);
2195
- adapterCache.set(`${tool}:${effective || ""}:${effort || ""}:${fastMode ? "fast" : "default"}`, adapter);
2240
+ adapterCache.set(buildAdapterCacheKey(tool, effective || "", effort || "", fastMode), adapter);
2196
2241
  if (effective)
2197
2242
  adapterCache.set(`${tool}:${effective}`, adapter);
2198
2243
  }
@@ -496,6 +496,14 @@ export function unflattenConfig(flat) {
496
496
  result.ccc = result.ccc || {};
497
497
  result.ccc.effort = val;
498
498
  }
499
+ else if (key === "CHATCCC_CCC_MAX_OUTPUT_TOKENS") {
500
+ result.ccc = result.ccc || {};
501
+ const raw = String(val ?? "").trim();
502
+ const parsed = Number(raw);
503
+ result.ccc.maxOutputTokens = raw && Number.isInteger(parsed) && parsed > 0
504
+ ? parsed
505
+ : null;
506
+ }
499
507
  else if (key === "CHATCCC_CCC_PROVIDER") {
500
508
  result.ccc = result.ccc || {};
501
509
  result.ccc.provider = val;
@@ -684,8 +692,9 @@ header{background:#0f172a;color:#fff;padding:16px 24px;display:flex;align-items:
684
692
  header h1{font-size:20px;font-weight:600}
685
693
  header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500}
686
694
  .header-actions{display:flex;align-items:center;gap:12px}
687
- .agent-team-entry{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#d946ef);color:#fff;text-decoration:none;font-size:14px;font-weight:700;letter-spacing:.01em;box-shadow:0 8px 24px rgba(99,102,241,.38);transition:transform .18s ease,box-shadow .18s ease,filter .18s ease}
688
- .agent-team-entry:hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(139,92,246,.52);filter:saturate(1.16)}
695
+ .agent-team-entry,.deepccc-web-entry{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#d946ef);color:#fff;text-decoration:none;font-size:14px;font-weight:700;letter-spacing:.01em;box-shadow:0 8px 24px rgba(99,102,241,.38);transition:transform .18s ease,box-shadow .18s ease,filter .18s ease}
696
+ .deepccc-web-entry{background:linear-gradient(135deg,#171a23,#413a76);box-shadow:0 8px 24px rgba(42,38,78,.38)}
697
+ .agent-team-entry:hover,.deepccc-web-entry:hover{transform:translateY(-2px);box-shadow:0 12px 30px rgba(139,92,246,.52);filter:saturate(1.16)}
689
698
  .agent-team-entry:focus-visible{outline:3px solid rgba(196,181,253,.65);outline-offset:3px}
690
699
  .agent-team-entry .agent-team-icon{font-size:16px;line-height:1}
691
700
  .badge-running{background:#16a34a;color:#fff}
@@ -761,14 +770,15 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
761
770
  @keyframes slideIn{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}
762
771
  .spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite}
763
772
  @keyframes spin{to{transform:rotate(360deg)}}
764
- @media(max-width:520px){header{padding:12px 14px}.header-actions{gap:8px}.agent-team-entry{padding:8px 11px;font-size:13px}header .badge{padding:4px 8px}}
773
+ @media(max-width:520px){header{padding:12px 14px}.header-actions{gap:8px}.agent-team-entry,.deepccc-web-entry{padding:8px 11px;font-size:13px}header .badge{padding:4px 8px}.deepccc-web-entry span:last-child{display:none}}
765
774
  </style>
766
775
  </head>
767
776
  <body>
768
777
  <header>
769
778
  <h1>ChatCCC</h1>
770
- <div class="header-actions">
771
- <a href="/agent-team" class="agent-team-entry"><span class="agent-team-icon" aria-hidden="true">✦</span>Agent Team <span aria-hidden="true">→</span></a>
779
+ <div class="header-actions">
780
+ <button type="button" class="deepccc-web-entry" onclick="openDeepCccWeb()"><span aria-hidden="true">D</span><span>DeepCCC Web</span></button>
781
+ <a href="/agent-team" class="agent-team-entry"><span class="agent-team-icon" aria-hidden="true">✦</span>Agent Team <span aria-hidden="true">→</span></a>
772
782
  <span id="header-badge" class="badge badge-stopped">未启动</span>
773
783
  </div>
774
784
  </header>
@@ -931,19 +941,25 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
931
941
  </select>
932
942
  <div class="hint">开启时追加 DeepCCC 共同作者,不替换你的 Git Author。</div>
933
943
  </div>
934
- <div class="form-group">
935
- <label>Effort(推理强度,选填)</label>
936
- <select id="field-CHATCCC_CCC_EFFORT">
937
- <option value="">(留空/默认,服务端 medium)</option>
938
- <option value="none">none - 直接作答,最省 token</option>
944
+ <div class="form-group">
945
+ <label>Effort(推理强度,选填)</label>
946
+ <select id="field-CHATCCC_CCC_EFFORT">
947
+ <option value="">跟随 DeepCCC 内核配置(默认)</option>
948
+ <option value="none">none - 直接作答,最省 token</option>
939
949
  <option value="minimal">minimal</option>
940
950
  <option value="low">low</option>
941
951
  <option value="medium">medium</option>
942
952
  <option value="high">high</option>
943
953
  <option value="xhigh">xhigh</option>
944
- <option value="max">max - 最强推理</option>
945
- </select>
946
- </div>
954
+ <option value="max">max - 最强推理</option>
955
+ </select>
956
+ <div class="hint">留空时读取 ~/.deepccc/config.json 或 DEEPCCC_EFFORT;DeepCCC 也留空时使用模型服务端默认值。</div>
957
+ </div>
958
+ <div class="form-group">
959
+ <label>最大输出 Token(选填)</label>
960
+ <input type="number" id="field-CHATCCC_CCC_MAX_OUTPUT_TOKENS" min="1" step="1" placeholder="留空跟随 DeepCCC 内核配置">
961
+ <div class="hint">限制主对话单次输出长度。留空时读取 ~/.deepccc/config.json 或 DEEPCCC_MAX_OUTPUT_TOKENS;DeepCCC 也未配置时使用模型服务端默认值。</div>
962
+ </div>
947
963
  <div class="form-group">
948
964
  <label>上下文窗口(模型最大上下文)</label>
949
965
  <select id="field-CHATCCC_CCC_CONTEXT_WINDOW" onchange="onContextWindowPresetChange('field-', this.value)">
@@ -1276,10 +1292,12 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1276
1292
  <div class="config-row"><span class="key">API Key</span><span class="val" id="cfg-CCC_API_KEY">-</span></div>
1277
1293
  <div class="config-row"><span class="key">Base URL</span><span class="val" id="cfg-CCC_BASE_URL">-</span></div>
1278
1294
  <div class="config-row"><span class="key">API 协议</span><span class="val" id="cfg-CCC_PROVIDER">-</span></div>
1279
- <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
1280
- <div class="config-row"><span class="key">子模型</span><span class="val" id="cfg-CCC_SUB_MODEL">-</span></div>
1281
- <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
1282
- <div class="config-row"><span class="key">Git 共同作者</span><span class="val" id="cfg-CCC_GIT_COAUTHOR">-</span></div>
1295
+ <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
1296
+ <div class="config-row"><span class="key">子模型</span><span class="val" id="cfg-CCC_SUB_MODEL">-</span></div>
1297
+ <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
1298
+ <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CCC_EFFORT">-</span></div>
1299
+ <div class="config-row"><span class="key">最大输出 Token</span><span class="val" id="cfg-CCC_MAX_OUTPUT_TOKENS">-</span></div>
1300
+ <div class="config-row"><span class="key">Git 共同作者</span><span class="val" id="cfg-CCC_GIT_COAUTHOR">-</span></div>
1283
1301
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
1284
1302
  <div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
1285
1303
  <button class="btn btn-outline" style="margin-top:8px" onclick="editSection('ccc')">编辑</button>
@@ -1330,7 +1348,7 @@ const AGENT_FIELDS = {
1330
1348
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
1331
1349
  cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
1332
1350
  codex: ['CHATCCC_CODEX_PATH','CHATCCC_CODEX_MODEL','CHATCCC_CODEX_ALTERNATIVE_MODEL','CHATCCC_CODEX_EFFORT','CHATCCC_CODEX_FAST_MODE'],
1333
- ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_SUB_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT','CHATCCC_CCC_PROVIDER','CHATCCC_CCC_GIT_COAUTHOR','CHATCCC_CCC_CONTEXT_WINDOW'],
1351
+ ccc: ['CHATCCC_CCC_API_KEY','CHATCCC_CCC_BASE_URL','CHATCCC_CCC_MODEL','CHATCCC_CCC_SUB_MODEL','CHATCCC_CCC_ALTERNATIVE_MODEL','CHATCCC_CCC_EFFORT','CHATCCC_CCC_MAX_OUTPUT_TOKENS','CHATCCC_CCC_PROVIDER','CHATCCC_CCC_GIT_COAUTHOR','CHATCCC_CCC_CONTEXT_WINDOW'],
1334
1352
  dsh: ['CHATCCC_DSH_API_KEY','CHATCCC_DSH_BASE_URL','CHATCCC_DSH_MODEL','CHATCCC_DSH_SUB_MODEL','CHATCCC_DSH_ALTERNATIVE_MODEL','CHATCCC_DSH_PROVIDER','CHATCCC_DSH_MAX_TOKENS']
1335
1353
  };
1336
1354
  const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
@@ -1727,7 +1745,8 @@ function renderStep2() {
1727
1745
  prefillNested('field-CHATCCC_CCC_MODEL', c.ccc.model);
1728
1746
  prefillNested('field-CHATCCC_CCC_SUB_MODEL', c.ccc.subModel);
1729
1747
  prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
1730
- prefillNested('field-CHATCCC_CCC_EFFORT', c.ccc.effort);
1748
+ prefillNested('field-CHATCCC_CCC_EFFORT', c.ccc.effort);
1749
+ prefillNested('field-CHATCCC_CCC_MAX_OUTPUT_TOKENS', c.ccc.maxOutputTokens);
1731
1750
  prefillContextWindow('field-', c.ccc.contextWindow);
1732
1751
  }
1733
1752
  if (c.dsh) {
@@ -1935,7 +1954,8 @@ function renderStep3() {
1935
1954
  lines.push('<div class="config-row"><span class="key">模型</span><span class="val">' + (vars.CHATCCC_CCC_MODEL || '(留空)') + '</span></div>');
1936
1955
  lines.push('<div class="config-row"><span class="key">子模型</span><span class="val">' + (vars.CHATCCC_CCC_SUB_MODEL || '(留空,跟随主模型)') + '</span></div>');
1937
1956
  lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1938
- lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CCC_EFFORT || '(留空)') + '</span></div>');
1957
+ lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CCC_EFFORT || '(留空)') + '</span></div>');
1958
+ lines.push('<div class="config-row"><span class="key">最大输出 Token</span><span class="val">' + (vars.CHATCCC_CCC_MAX_OUTPUT_TOKENS || '(跟随 DeepCCC)') + '</span></div>');
1939
1959
  lines.push('<div class="config-row"><span class="key">上下文窗口</span><span class="val">' + contextWindowTokensLabel(vars.CHATCCC_CCC_CONTEXT_WINDOW || 1048576) + '</span></div>');
1940
1960
  } else if (t === 'dsh') {
1941
1961
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">DeepSeek Harness</h4>');
@@ -2164,9 +2184,13 @@ function updateDashboardUI() {
2164
2184
  document.getElementById('cfg-CCC_API_KEY').textContent = (c.ccc && c.ccc.DEEPSEEK_API_KEY) ? '***已设置***' : '(留空)';
2165
2185
  document.getElementById('cfg-CCC_BASE_URL').textContent = (c.ccc && c.ccc.DEEPSEEK_BASE_URL) || '(留空)';
2166
2186
  document.getElementById('cfg-CCC_PROVIDER').textContent = (c.ccc && c.ccc.provider) ? c.ccc.provider : '(跟随 DeepCCC 内核配置)';
2167
- document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
2168
- document.getElementById('cfg-CCC_SUB_MODEL').textContent = (c.ccc && c.ccc.subModel) || '(留空,跟随主模型)';
2169
- document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
2187
+ document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
2188
+ document.getElementById('cfg-CCC_SUB_MODEL').textContent = (c.ccc && c.ccc.subModel) || '(留空,跟随主模型)';
2189
+ document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
2190
+ document.getElementById('cfg-CCC_EFFORT').textContent = (c.ccc && c.ccc.effort) || '(跟随 DeepCCC 内核配置)';
2191
+ document.getElementById('cfg-CCC_MAX_OUTPUT_TOKENS').textContent = c.ccc && c.ccc.maxOutputTokens
2192
+ ? String(c.ccc.maxOutputTokens)
2193
+ : '(跟随 DeepCCC 内核配置)';
2170
2194
  document.getElementById('cfg-CCC_GIT_COAUTHOR').textContent = !c.ccc || c.ccc.gitCoAuthor == null
2171
2195
  ? '跟随 DeepCCC 全局设置(缺省开启)'
2172
2196
  : (c.ccc.gitCoAuthor ? '强制开启' : '强制关闭');
@@ -2255,7 +2279,7 @@ function editSection(section) {
2255
2279
  'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
2256
2280
  'CHATCCC_CCC_PROVIDER': 'API 协议(选填)',
2257
2281
  'CHATCCC_CCC_GIT_COAUTHOR': 'Git 提交共同作者',
2258
- 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_SUB_MODEL': '子模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort', 'CHATCCC_CCC_CONTEXT_WINDOW': '上下文窗口',
2282
+ 'CHATCCC_CCC_MODEL': '模型', 'CHATCCC_CCC_SUB_MODEL': '子模型', 'CHATCCC_CCC_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_CCC_EFFORT': 'Effort', 'CHATCCC_CCC_MAX_OUTPUT_TOKENS': '最大输出 Token', 'CHATCCC_CCC_CONTEXT_WINDOW': '上下文窗口',
2259
2283
  'CHATCCC_DSH_API_KEY': 'API Key', 'CHATCCC_DSH_BASE_URL': 'Base URL', 'CHATCCC_DSH_MODEL': '模型', 'CHATCCC_DSH_SUB_MODEL': '子模型', 'CHATCCC_DSH_ALTERNATIVE_MODEL': '备选模型', 'CHATCCC_DSH_PROVIDER': 'Provider 路由', 'CHATCCC_DSH_MAX_TOKENS': '单次最大输出 Tokens'
2260
2284
  };
2261
2285
  var hintMap = {
@@ -2263,7 +2287,9 @@ function editSection(section) {
2263
2287
  'CHATCCC_CHROME_DEVTOOLS_ENABLED': '依赖:本机 Google Chrome;ChatGPT 订阅到期查询需要在该 CDP Chrome 中登录 ChatGPT。',
2264
2288
  'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
2265
2289
  'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。',
2266
- 'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。',
2290
+ 'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。',
2291
+ 'CHATCCC_CCC_EFFORT': '留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_EFFORT);内核也留空时使用模型服务端默认值。',
2292
+ 'CHATCCC_CCC_MAX_OUTPUT_TOKENS': '正整数;留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_MAX_OUTPUT_TOKENS);内核也未配置时使用模型服务端默认值。',
2267
2293
  'CHATCCC_CCC_GIT_COAUTHOR': '跟随全局时读取 ~/.deepccc/config.json 的 git.coAuthor.enabled(缺省为开启)。强制选项只影响 ChatCCC 内置 CCC Agent。',
2268
2294
  'CHATCCC_CCC_CONTEXT_WINDOW': '压缩阈值自动 = 窗口 × 80%(超出即把较早消息压缩为摘要)。⚠️ 超过模型/服务端实际上限时请求会被 API 直接拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 代理的 max_input_tokens);单位 k = 1024 tokens,1M = 1,048,576 tokens。',
2269
2295
  'CHATCCC_CCC_SUB_MODEL': '用于 DeepCCC 内部轻量环节(上下文压缩摘要生成、task 子代理任务)。留空 = 跟随主模型;改动需重启 ChatCCC 生效。'
@@ -2313,7 +2339,8 @@ function editSection(section) {
2313
2339
  else if (key === 'CHATCCC_CCC_MODEL') val = state.config.ccc.model || '';
2314
2340
  else if (key === 'CHATCCC_CCC_SUB_MODEL') val = state.config.ccc.subModel || '';
2315
2341
  else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
2316
- else if (key === 'CHATCCC_CCC_EFFORT') val = state.config.ccc.effort || '';
2342
+ else if (key === 'CHATCCC_CCC_EFFORT') val = state.config.ccc.effort || '';
2343
+ else if (key === 'CHATCCC_CCC_MAX_OUTPUT_TOKENS') val = state.config.ccc.maxOutputTokens || '';
2317
2344
  else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') val = state.config.ccc.contextWindow || '1048576';
2318
2345
  } else if (section === 'dsh' && state.config.dsh) {
2319
2346
  if (key === 'CHATCCC_DSH_API_KEY') val = state.config.dsh.apiKey || '';
@@ -2378,13 +2405,15 @@ function editSection(section) {
2378
2405
  var rowId = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'
2379
2406
  ? ' id="edit-cursor-on-demand-budget-row"'
2380
2407
  : (section === 'chromeDevtools' && key !== 'CHATCCC_CHROME_DEVTOOLS_ENABLED' ? ' id="edit-row-' + key + '"' : '');
2381
- var isNumber = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET' || key === 'CHATCCC_CHROME_DEVTOOLS_PORT';
2408
+ var isNumber = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET' || key === 'CHATCCC_CHROME_DEVTOOLS_PORT' || key === 'CHATCCC_CCC_MAX_OUTPUT_TOKENS';
2382
2409
  var inputType = isNumber ? 'number' : (isSecret ? 'password' : 'text');
2383
2410
  var attrs = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'
2384
2411
  ? ' min="1" step="1"'
2385
- : key === 'CHATCCC_CHROME_DEVTOOLS_PORT'
2386
- ? ' min="1" max="65535" step="1" placeholder="15166"'
2387
- : '';
2412
+ : key === 'CHATCCC_CHROME_DEVTOOLS_PORT'
2413
+ ? ' min="1" max="65535" step="1" placeholder="15166"'
2414
+ : key === 'CHATCCC_CCC_MAX_OUTPUT_TOKENS'
2415
+ ? ' min="1" step="1" placeholder="留空跟随 DeepCCC 内核配置"'
2416
+ : '';
2388
2417
  html += '<div class="form-group"' + rowId + '><label>' + (labelMap[key] || key) + '</label>';
2389
2418
  html += '<input type="' + inputType + '" id="edit-' + key + '"' + attrs + ' value="' + String(val).replace(/"/g,'&quot;') + '">';
2390
2419
  if (hintMap[key]) html += '<div class="hint" style="margin-top:6px;line-height:1.5">' + hintMap[key] + '</div>';
@@ -2566,8 +2595,18 @@ function installEngine(engineId) {
2566
2595
  });
2567
2596
  }
2568
2597
 
2569
- // ---- Start ----
2570
- init();
2598
+ // ---- Start ----
2599
+ async function openDeepCccWeb() {
2600
+ try {
2601
+ var result = await api('/api/deepccc-web/start', 'POST');
2602
+ if (!result.ok) throw new Error(result.error || '启动失败');
2603
+ window.open(result.url, '_blank', 'noopener');
2604
+ } catch (error) {
2605
+ toast('DeepCCC Web 启动失败: ' + String(error), 'error');
2606
+ }
2607
+ }
2608
+
2609
+ init();
2571
2610
 
2572
2611
  // 页面刷新后从持久化任务文件恢复每一步的安装进度。
2573
2612
  setTimeout(function(){ engineRefreshStatus('claude'); engineRefreshStatus('dsh'); }, 300);
@@ -2600,6 +2639,11 @@ async function handleRequest(req, res) {
2600
2639
  return handleStopService(req, res);
2601
2640
  if (url === "/api/restart" && method === "POST")
2602
2641
  return handleRestartService(req, res);
2642
+ if (url === "/api/deepccc-web/start" && method === "POST") {
2643
+ const { launchDeepCccWebProcess } = await import("../deepccc-agent/src/web-server.js");
2644
+ const handle = await launchDeepCccWebProcess({ reuseExisting: true, openBrowser: false, defaultCwd: process.cwd() });
2645
+ return jsonReply(res, 200, { ok: true, url: handle.url, port: handle.port, reused: handle.reused });
2646
+ }
2603
2647
  if (url === "/api/validate" && method === "POST")
2604
2648
  return handleValidate(req, res);
2605
2649
  if (url === "/api/ilink/forget" && method === "POST")
@@ -9,7 +9,7 @@ Videos are sent as regular files (not media), which looks cleaner in Feishu.
9
9
  ### Script
10
10
 
11
11
  ```bash
12
- node "{{send_file_script}}" --url "{{send_file_url}}" --session-id "{{session_id}}" --path "<absolute file path>" --caption "<optional caption>"
12
+ node "{{send_file_script}}" --url "{{send_file_url}}" --session-id "{{session_id}}" --grant "{{agent_capability_grant}}" --path "<absolute file path>" --caption "<optional caption>"
13
13
  ```
14
14
 
15
15
  ### Rules
@@ -17,6 +17,7 @@ node "{{send_file_script}}" --url "{{send_file_url}}" --session-id "{{session_id
17
17
  - Use the node script above — never curl or raw HTTP.
18
18
  - Save or choose a local file first.
19
19
  - Use an absolute local path.
20
+ - Use only the session ID and capability grant shown above; they are a bound pair.
20
21
  - Max file size: 30MB.
21
22
  - Supported formats: .mp4 .mov .avi .mkv .webm .flv .mp3 .wav .ogg .aac .m4a .pdf .doc .docx .xls .xlsx .csv .ppt .pptx .txt .zip .tar .gz.
22
23
  - Only send a file/video when the user asked for one or when it materially helps the answer.
@@ -61,4 +62,4 @@ If only `chat_id` and `file_key` are available:
61
62
  node "{{download_video_script}}" --chat-id <chat_id> --file-key <file_key> --name <file_name>
62
63
  ```
63
64
 
64
- Downloads are saved under `~/.chatccc/videos/downloads/`.
65
+ Downloads are saved under `~/.chatccc/videos/downloads/`.
@@ -7,7 +7,7 @@
7
7
  ### Script
8
8
 
9
9
  ```bash
10
- node "{{send_image_script}}" --url "{{send_image_url}}" --session-id "{{session_id}}" --path "<absolute image path>" --caption "<optional caption>"
10
+ node "{{send_image_script}}" --url "{{send_image_url}}" --session-id "{{session_id}}" --grant "{{agent_capability_grant}}" --path "<absolute image path>" --caption "<optional caption>"
11
11
  ```
12
12
 
13
13
  ### Rules
@@ -15,6 +15,7 @@ node "{{send_image_script}}" --url "{{send_image_url}}" --session-id "{{session_
15
15
  - Use the node script above — never curl or raw HTTP.
16
16
  - Save or choose a local image file first.
17
17
  - Use an absolute local path.
18
+ - Use only the session ID and capability grant shown above; they are a bound pair.
18
19
  - Supported formats: .png, .jpg, .jpeg, .webp, .gif, .bmp.
19
20
  - Max image size: 10MB.
20
21
  - Only send an image when the user asked for one or when it materially helps the answer.
@@ -22,4 +23,4 @@ node "{{send_image_script}}" --url "{{send_image_url}}" --session-id "{{session_
22
23
 
23
24
  ## Receive Images
24
25
 
25
- Images sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/`. The message contains an `image_key` that maps to the cached file.
26
+ Images sent to the bot are automatically downloaded to `~/.chatccc/images/downloads/`. The message contains an `image_key` that maps to the cached file.
@@ -14,22 +14,23 @@ function parseArgs(argv) {
14
14
 
15
15
  function usage() {
16
16
  console.error(`Usage:
17
- node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute file path> [--caption <text>]`);
17
+ node ${basename(process.argv[1])} --url <url> --session-id <session_id> --grant <session_grant> --path <absolute file path> [--caption <text>]`);
18
18
  }
19
19
 
20
20
  async function main() {
21
21
  const args = parseArgs(process.argv.slice(2));
22
22
  const url = args.url || process.env.CHATCCC_SEND_FILE_URL;
23
- const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
23
+ const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
24
+ const grant = args.grant || process.env.CHATCCC_AGENT_CAPABILITY_GRANT;
24
25
  const path = args.path;
25
26
  const caption = args.caption || "";
26
27
 
27
- if (!url || !sessionId || !path) {
28
+ if (!url || !sessionId || !grant || !path) {
28
29
  usage();
29
30
  process.exit(1);
30
31
  }
31
32
 
32
- const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
33
+ const body = Buffer.from(JSON.stringify({ session_id: sessionId, grant, path, caption }), "utf8");
33
34
  const response = await fetch(url, {
34
35
  method: "POST",
35
36
  headers: {
@@ -48,4 +49,4 @@ async function main() {
48
49
  main().catch((err) => {
49
50
  console.error(err instanceof Error ? err.message : String(err));
50
51
  process.exit(1);
51
- });
52
+ });
@@ -14,22 +14,23 @@ function parseArgs(argv) {
14
14
 
15
15
  function usage() {
16
16
  console.error(`Usage:
17
- node ${basename(process.argv[1])} --url <url> --session-id <session_id> --path <absolute image path> [--caption <text>]`);
17
+ node ${basename(process.argv[1])} --url <url> --session-id <session_id> --grant <session_grant> --path <absolute image path> [--caption <text>]`);
18
18
  }
19
19
 
20
20
  async function main() {
21
21
  const args = parseArgs(process.argv.slice(2));
22
22
  const url = args.url || process.env.CHATCCC_SEND_IMAGE_URL;
23
- const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
23
+ const sessionId = args["session-id"] || args.session_id || process.env.CHATCCC_SESSION_ID;
24
+ const grant = args.grant || process.env.CHATCCC_AGENT_CAPABILITY_GRANT;
24
25
  const path = args.path;
25
26
  const caption = args.caption || "";
26
27
 
27
- if (!url || !sessionId || !path) {
28
+ if (!url || !sessionId || !grant || !path) {
28
29
  usage();
29
30
  process.exit(1);
30
31
  }
31
32
 
32
- const body = Buffer.from(JSON.stringify({ session_id: sessionId, path, caption }), "utf8");
33
+ const body = Buffer.from(JSON.stringify({ session_id: sessionId, grant, path, caption }), "utf8");
33
34
  const response = await fetch(url, {
34
35
  method: "POST",
35
36
  headers: {
@@ -48,4 +49,4 @@ async function main() {
48
49
  main().catch((err) => {
49
50
  console.error(err instanceof Error ? err.message : String(err));
50
51
  process.exit(1);
51
- });
52
+ });
@@ -5,12 +5,13 @@ description: Feishu IM local skills for sending images, files, videos, and for c
5
5
 
6
6
  Current working directory: {{cwd}}
7
7
  Your session id: {{session_id}}
8
+ Your session capability grant: {{agent_capability_grant}}
8
9
  Your Feishu open_id: {{open_id}}
9
10
 
10
11
  Use local endpoints instead of calling Feishu Open Platform directly.
11
12
 
12
- - **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
13
- - **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
13
+ - **Send images**: POST `{{send_image_url}}` with `{"session_id":"{{session_id}}","grant":"{{agent_capability_grant}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-image.md`
14
+ - **Send files/videos**: POST `{{send_file_url}}` with `{"session_id":"{{session_id}}","grant":"{{agent_capability_grant}}","path":"<absolute path>","caption":"<optional>"}` — read `{{im_skills_cache_dir}}/feishu-skill/receive-send-file.md`
14
15
  - **Create a new session (新建会话)**: POST `{{delegate_task_url}}` with `{"tool":"claude|cursor|codex|ccc|dsh","cwd":"<absolute path>","open_id":"{{open_id}}","prompt":"<optional first task>"}`. This creates a new Feishu group and session, and only adds you (the requester). `tool` and `prompt` are optional; omit `prompt` to just create the session without a first task.
15
16
  - **Set default working directory (cd / 切换目录)**: POST `{{set_cwd_url}}` with `{"session_id":"{{session_id}}","dir":"<absolute path>"}`. This sets the default directory for future new sessions only; it does not change the current session.
16
17
 
@@ -24,3 +25,4 @@ How to map user requests to these endpoints:
24
25
  - when ambiguous, prefer creating a new session (the more common intent for "切换到").
25
26
  - Directory names may be fuzzy or relative; resolve them to an absolute local path (using your file tools) before calling either endpoint.
26
27
  - `open_id` must always be passed as exactly {{open_id}}; do not invent it.
28
+ - The capability grant is bound to this session. Never reuse a grant with another session ID.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.270",
3
+ "version": "0.2.276",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",