chatccc 0.2.269 → 0.2.272

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.
@@ -14,7 +14,7 @@ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
14
14
  .status-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 3px 10px;color:var(--muted);font-size:11px}.workspace-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.save-state{min-width:78px;text-align:right;color:var(--success);font-weight:700}.save-state::before{content:"● ";font-size:9px}.save-state.saving{color:#b7791f}.save-state.error{color:var(--danger)}
15
15
  .board-scroll{overflow-x:auto;padding-bottom:12px}.board{display:grid;grid-template-columns:repeat(5,minmax(230px,1fr));gap:11px;min-width:1200px}.column{display:flex;flex-direction:column;min-height:calc(100vh - 230px);background:var(--soft);border:1px solid var(--line);border-radius:15px;overflow:hidden}.column.drag-over{border-color:var(--accent);box-shadow:0 0 0 3px rgba(103,88,232,.13)}.column-head{display:flex;align-items:center;gap:8px;padding:11px 10px 7px 13px}.column-dot{width:7px;height:7px;border-radius:50%;background:var(--accent)}.column-title{font-size:13px;font-weight:800}.count{margin-left:auto;display:inline-grid;place-items:center;min-width:23px;height:23px;padding:0 7px;border-radius:999px;background:var(--panel);border:1px solid var(--line);color:var(--muted);font-size:10px}.column-add{width:27px;height:27px;display:grid;place-items:center;padding:0;border-color:transparent;border-radius:8px;background:transparent;color:var(--muted);font-size:19px;font-weight:500;line-height:1}.column-add:hover{border-color:var(--line);background:var(--panel);color:var(--accent)}.task-list{flex:1;min-height:100px;padding:4px 10px 10px}.task{position:relative;background:var(--panel2);border:1px solid var(--line);border-radius:11px;padding:12px;margin:8px 0;box-shadow:0 4px 15px rgba(34,42,65,.045);cursor:pointer}.task:hover{border-color:#d1d5e1;box-shadow:0 8px 22px rgba(34,42,65,.08)}.task.dragging{opacity:.35;cursor:grabbing}.task h3{font-size:13px;line-height:1.45;margin:0 42px 6px 0}.task p{font-size:11px;line-height:1.55;color:var(--muted);margin:0;white-space:pre-wrap;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}.task-actions{display:flex;gap:4px;position:absolute;right:7px;top:7px;opacity:0;transition:opacity .15s}.task:hover .task-actions,.task:focus-within .task-actions{opacity:1}.icon-btn{padding:3px 6px;border-radius:6px;background:var(--soft);color:var(--muted);font-size:10px}.empty{padding:20px 8px;text-align:center;color:#9aa3b4;font-size:11px}
16
16
  .task.running{cursor:default;border-color:#cfc8ff}.task-run{margin-top:10px;padding-top:9px;border-top:1px solid var(--line)}.task-run-head{display:flex;align-items:center;justify-content:space-between;gap:7px}.run-status{font-size:10px;font-weight:800;color:var(--muted)}.run-status.running,.run-status.queued{color:var(--accent)}.run-status.succeeded{color:var(--success)}.run-status.failed,.run-status.interrupted{color:var(--danger)}.run-result{margin-top:6px!important;padding:7px 8px;border-radius:7px;background:var(--soft);display:-webkit-box!important;-webkit-line-clamp:3!important}.run-button{padding:5px 8px;font-size:10px;border-radius:7px}.run-button.primary{box-shadow:none}
17
- .modal-backdrop{position:fixed;inset:0;z-index:20;background:rgba(24,32,51,.36);display:grid;place-items:center;padding:20px;backdrop-filter:blur(3px)}.modal{width:min(560px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:0 28px 90px rgba(35,43,65,.24)}.modal h2{margin:0 0 16px}.field{display:grid;gap:7px;margin-bottom:14px}.field label{font-size:13px;color:var(--text);font-weight:650}.field textarea{min-height:150px;resize:vertical}.modal-actions{display:flex;justify-content:flex-end;gap:9px}.task-detail-modal{width:min(980px,100%);max-height:calc(100vh - 40px);padding:0;display:flex;flex-direction:column;overflow:hidden}.task-detail-scroll{min-height:0;overflow-y:auto;padding:22px 24px}.task-detail-modal>.modal-actions{flex:none;padding:14px 24px;border-top:1px solid var(--line);background:var(--panel)}.task-execution-details{margin-top:22px;padding-top:20px;border-top:1px solid var(--line)}.task-execution-title{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.task-execution-title h3{margin:0;font-size:16px}.run-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px}.run-meta-item{padding:9px 10px;border:1px solid var(--line);border-radius:9px;background:var(--soft);min-width:0}.run-meta-item span{display:block;color:var(--muted);font-size:10px;margin-bottom:4px}.run-meta-item strong{display:block;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.execution-timeline{display:grid;gap:9px}.timeline-entry{border:1px solid var(--line);border-radius:10px;background:var(--soft);overflow:hidden}.timeline-entry.tool_use{border-left:3px solid var(--accent)}.timeline-entry.tool_result{border-left:3px solid var(--success)}.timeline-entry.error{border-left-color:var(--danger)}.timeline-head{display:flex;justify-content:space-between;gap:12px;padding:8px 10px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10px;font-weight:800}.timeline-entry pre{margin:0;padding:11px 12px;max-height:none;overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--text)}.timeline-empty{padding:22px;border:1px dashed var(--line);border-radius:10px;text-align:center;color:var(--muted);font-size:12px}.directory-modal{width:min(820px,100%)}.directory-layout{display:grid;grid-template-columns:180px minmax(0,1fr);min-height:430px;border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:16px}.directory-sidebar{padding:12px;background:var(--soft);border-right:1px solid var(--line)}.directory-label{margin:2px 6px 9px;color:var(--muted);font-size:11px;font-weight:800;letter-spacing:.05em}.directory-locations{display:grid;gap:4px}.directory-location{width:100%;padding:8px 9px;border:0;background:transparent;text-align:left;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory-location:hover{border:0;background:var(--accent-soft);color:var(--accent)}.directory-main{min-width:0;display:flex;flex-direction:column;padding:12px}.directory-toolbar{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:7px}.directory-toolbar button{padding:8px 11px}.directory-toolbar input{min-width:0;padding:8px 10px}.directory-list{flex:1;height:310px;overflow:auto;margin-top:10px;border:1px solid var(--line);border-radius:10px;background:var(--soft);padding:6px}.directory-row{width:100%;display:flex;align-items:center;gap:9px;padding:9px 10px;border:0;background:transparent;text-align:left;font-size:12px}.directory-row:hover{border:0;background:#fff}.directory-row.selected{border:0;background:var(--accent-soft);color:var(--accent)}.directory-folder-icon{color:#b7791f;font-size:15px}.directory-empty{padding:46px 16px;text-align:center;color:var(--muted);font-size:12px}.directory-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;color:var(--muted);font-size:11px}.directory-footer label{display:flex;align-items:center;gap:6px;white-space:nowrap}.directory-footer input{width:15px;height:15px;margin:0}.directory-selected{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}.toast{position:fixed;right:22px;bottom:22px;z-index:30;max-width:min(420px,calc(100vw - 44px));padding:12px 15px;border-radius:10px;background:#253047;color:#fff;border:1px solid #35415a;box-shadow:0 16px 50px rgba(35,43,65,.25)}.toast.error{border-color:#ef9bad;background:#9f2942}
17
+ .modal-backdrop{position:fixed;inset:0;z-index:20;background:rgba(24,32,51,.36);display:grid;place-items:center;padding:20px;backdrop-filter:blur(3px)}.modal{width:min(560px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:20px;box-shadow:0 28px 90px rgba(35,43,65,.24)}.modal h2{margin:0 0 16px}.field{display:grid;gap:7px;margin-bottom:14px}.field label{font-size:13px;color:var(--text);font-weight:650}.field textarea{min-height:150px;resize:vertical}.modal-actions{display:flex;justify-content:flex-end;gap:9px}.task-detail-modal{width:min(980px,100%);max-height:calc(100vh - 40px);padding:0;display:flex;flex-direction:column;overflow:hidden}.task-detail-scroll{min-height:0;overflow-y:auto;padding:22px 24px}.task-detail-modal>.modal-actions{flex:none;padding:14px 24px;border-top:1px solid var(--line);background:var(--panel)}.task-execution-details{margin-top:22px;padding-top:20px;border-top:1px solid var(--line)}.task-execution-title{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}.task-execution-title h3{margin:0;font-size:16px}.run-history-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:12px}.run-history-toolbar select{min-width:0;flex:1}.run-history-toolbar button{white-space:nowrap}.run-meta{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px}.run-meta-item{padding:9px 10px;border:1px solid var(--line);border-radius:9px;background:var(--soft);min-width:0}.run-meta-item span{display:block;color:var(--muted);font-size:10px;margin-bottom:4px}.run-meta-item strong{display:block;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.execution-timeline{display:grid;gap:9px}.timeline-entry{border:1px solid var(--line);border-radius:10px;background:var(--soft);overflow:hidden}.timeline-entry.tool_use{border-left:3px solid var(--accent)}.timeline-entry.tool_result{border-left:3px solid var(--success)}.timeline-entry.error{border-left-color:var(--danger)}.timeline-head{display:flex;justify-content:space-between;gap:12px;padding:8px 10px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10px;font-weight:800}.timeline-entry pre{margin:0;padding:11px 12px;max-height:none;overflow:visible;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/1.65 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--text)}.timeline-empty{padding:22px;border:1px dashed var(--line);border-radius:10px;text-align:center;color:var(--muted);font-size:12px}.directory-modal{width:min(820px,100%)}.directory-layout{display:grid;grid-template-columns:180px minmax(0,1fr);min-height:430px;border:1px solid var(--line);border-radius:12px;overflow:hidden;margin-bottom:16px}.directory-sidebar{padding:12px;background:var(--soft);border-right:1px solid var(--line)}.directory-label{margin:2px 6px 9px;color:var(--muted);font-size:11px;font-weight:800;letter-spacing:.05em}.directory-locations{display:grid;gap:4px}.directory-location{width:100%;padding:8px 9px;border:0;background:transparent;text-align:left;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.directory-location:hover{border:0;background:var(--accent-soft);color:var(--accent)}.directory-main{min-width:0;display:flex;flex-direction:column;padding:12px}.directory-toolbar{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:7px}.directory-toolbar button{padding:8px 11px}.directory-toolbar input{min-width:0;padding:8px 10px}.directory-list{flex:1;height:310px;overflow:auto;margin-top:10px;border:1px solid var(--line);border-radius:10px;background:var(--soft);padding:6px}.directory-row{width:100%;display:flex;align-items:center;gap:9px;padding:9px 10px;border:0;background:transparent;text-align:left;font-size:12px}.directory-row:hover{border:0;background:#fff}.directory-row.selected{border:0;background:var(--accent-soft);color:var(--accent)}.directory-folder-icon{color:#b7791f;font-size:15px}.directory-empty{padding:46px 16px;text-align:center;color:var(--muted);font-size:12px}.directory-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;color:var(--muted);font-size:11px}.directory-footer label{display:flex;align-items:center;gap:6px;white-space:nowrap}.directory-footer input{width:15px;height:15px;margin:0}.directory-selected{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}.toast{position:fixed;right:22px;bottom:22px;z-index:30;max-width:min(420px,calc(100vw - 44px));padding:12px 15px;border-radius:10px;background:#253047;color:#fff;border:1px solid #35415a;box-shadow:0 16px 50px rgba(35,43,65,.25)}.toast.error{border-color:#ef9bad;background:#9f2942}
18
18
  @media(max-width:1100px){.app-shell{grid-template-columns:78px minmax(0,1fr)}.sidebar{padding:22px 12px;align-items:center}.brand span:last-child,.nav-item span:last-child,.side-note{display:none}.nav-item{width:45px;height:45px;justify-content:center;padding:0}.workspace-bar{grid-template-columns:1fr 1fr}.workspace-bar input{grid-column:1/-1}.main-agent-bar{grid-template-columns:1fr}.main-agent-controls{justify-content:flex-end}.board{min-width:1200px}}
19
19
  @media(max-width:700px){.app-shell{display:block}.sidebar{display:none}.shell{padding:14px}.top{align-items:flex-start}.workspace-bar{grid-template-columns:1fr}.workspace-bar input{grid-column:auto}.main-agent-controls{display:grid;grid-template-columns:minmax(0,1fr) auto;justify-content:stretch}.main-agent-controls select{min-width:0}.board{min-width:1180px}.task-detail-scroll{padding:18px}.task-detail-modal>.modal-actions{padding:12px 18px}.run-meta{grid-template-columns:repeat(2,minmax(0,1fr))}.directory-layout{grid-template-columns:1fr}.directory-sidebar{border-right:0;border-bottom:1px solid var(--line)}.directory-locations{display:flex;overflow-x:auto}.directory-location{width:auto;min-width:max-content}.directory-toolbar{grid-template-columns:auto auto 1fr}.directory-list{height:270px}}
20
20
  </style>
@@ -60,6 +60,7 @@ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
60
60
  <div class="field"><label for="task-description">描述</label><textarea id="task-description" maxlength="20000"></textarea></div>
61
61
  <section id="task-execution-details" class="task-execution-details hidden">
62
62
  <div class="task-execution-title"><h3>完整执行过程</h3><span id="task-run-status" class="run-status"></span></div>
63
+ <div class="run-history-toolbar"><select id="task-run-history" aria-label="选择执行记录"></select><button type="button" id="copy-task-run" class="ghost">复制记录</button></div>
63
64
  <div id="task-run-meta" class="run-meta"></div>
64
65
  <div id="task-transcript" class="execution-timeline"></div>
65
66
  </section>
@@ -107,7 +108,7 @@ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
107
108
  <div id="toast" class="toast hidden"></div>
108
109
  <script>
109
110
  (function(){
110
- var state={board:null,binding:null,runs:[],runPollTimer:null,runPollBusy:false,workspaces:[],agentOptions:[],dragTaskId:null,modalTaskId:null,modalColumnId:null,modalDetailVersion:0,relinkBoardId:null,pendingAgentId:null,pendingWorkspacePath:null,workspaceSelectionVersion:0,directoryLocations:[],directoryPath:null,directoryParentPath:null,directorySelectedPath:null,directoryBrowseVersion:0,contactTimer:null,contactChecking:false,mutationQueue:Promise.resolve()};
111
+ var state={board:null,binding:null,runs:[],runPollTimer:null,runPollBusy:false,workspaces:[],agentOptions:[],dragTaskId:null,modalTaskId:null,modalColumnId:null,modalRunId:null,modalRunDetail:null,modalDetailVersion:0,relinkBoardId:null,pendingAgentId:null,pendingWorkspacePath:null,workspaceSelectionVersion:0,directoryLocations:[],directoryPath:null,directoryParentPath:null,directorySelectedPath:null,directoryBrowseVersion:0,contactTimer:null,contactChecking:false,mutationQueue:Promise.resolve()};
111
112
  var columns=['brainstorm','todo','doing','done','on_hold'];
112
113
  var recent=document.getElementById('recent');
113
114
  var pathInput=document.getElementById('workspace-path');
@@ -170,7 +171,7 @@ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
170
171
  async function loadRuns(render){if(!state.board)return;var boardId=state.board.boardId;var result=await api('/api/agent-team/boards/'+encodeURIComponent(boardId)+'/runs');if(!state.board||state.board.boardId!==boardId)return;state.runs=result.runs||[];if(render!==false)renderBoard();}
171
172
  function latestRun(taskId){return state.runs.find(function(run){return run.taskId===taskId;})||null;}
172
173
  function isActiveRun(run){return !!run&&(run.state==='queued'||run.state==='running');}
173
- function runLabel(run){var labels={queued:'等待执行',running:run.stopRequestedAt?'正在停止':'主 Agent 执行中',succeeded:'执行成功',failed:'执行失败',canceled:'已停止',interrupted:'重启时中断'};return labels[run.state]||run.state;}
174
+ function runLabel(run){if(isActiveRun(run)&&run.stalledAt)return '疑似停滞';var labels={queued:'等待执行',running:run.stopRequestedAt?'正在停止':'主 Agent 执行中',succeeded:'执行成功',failed:'执行失败',canceled:'已停止',interrupted:'重启时中断'};return labels[run.state]||run.state;}
174
175
  function scheduleRunPoll(){if(state.runPollTimer){clearTimeout(state.runPollTimer);state.runPollTimer=null;}if(!state.runs.some(isActiveRun))return;state.runPollTimer=setTimeout(pollRuns,1500);}
175
176
  async function pollRuns(){if(state.runPollBusy||!state.board)return scheduleRunPoll();state.runPollBusy=true;var boardId=state.board.boardId;try{var results=await Promise.all([api('/api/agent-team/boards/'+encodeURIComponent(boardId)),api('/api/agent-team/boards/'+encodeURIComponent(boardId)+'/runs')]);if(state.board&&state.board.boardId===boardId){state.board=results[0].board;state.binding=results[0].binding||null;state.runs=results[1].runs||[];renderBoard();if(state.modalTaskId)await loadOpenTaskRunDetails(false);if(!state.runs.some(isActiveRun))setSaving('已保存','');}}catch(error){toast(error.message,true);}finally{state.runPollBusy=false;scheduleRunPoll();}}
176
177
  async function startTaskRun(task){if(!state.board)return;if(!state.binding||state.binding.status!=='ready')return toast('请先设置可用的主 Agent',true);try{setSaving('正在交给主 Agent…','saving');var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id)+'/run',{expectedRevision:state.board.revision});state.board=result.board;state.runs=[result.run].concat(state.runs.filter(function(run){return run.runId!==result.run.runId;}));renderBoard();scheduleRunPoll();setSaving('任务执行中','saving');}catch(error){if(error.code==='revision_conflict')await reloadBoard();toast(error.message,true);setSaving('执行失败','error');}}
@@ -209,15 +210,19 @@ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
209
210
  state.mutationQueue=state.mutationQueue.then(async function(){setSaving('创建主 Agent…','saving');mainAgentButton.disabled=true;try{var result=await request('POST','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/main-agent',{expectedRevision:state.board.revision,agentId:agentId});state.board=result.board;state.binding=result.binding;state.pendingAgentId=null;renderBoard();setSaving('已保存','');toast('主 Agent 群已就绪');return true;}catch(error){setSaving('设置失败','error');renderBoard();if(error.code==='feishu_dm_required')showDmModal();else if(error.code==='revision_conflict'){try{await reloadBoard();toast('其他页面修改了项目,已载入最新版本',true);}catch(reloadError){toast(reloadError.message,true);}}else toast(error.message,true);return false;}});return state.mutationQueue;
210
211
  }
211
212
  function formatRunTime(value){if(!value)return '—';var date=new Date(value);return Number.isNaN(date.getTime())?String(value):date.toLocaleString();}
213
+ function formatRunDuration(run){if(!run)return '—';var start=Date.parse(run.startedAt||run.createdAt);var end=Date.parse(run.finishedAt||new Date().toISOString());if(!Number.isFinite(start)||!Number.isFinite(end))return '—';var seconds=Math.max(0,Math.round((end-start)/1000));if(seconds<60)return seconds+' 秒';var minutes=Math.floor(seconds/60);return minutes+' 分 '+(seconds%60)+' 秒';}
214
+ function failureLabel(code){var labels={agent_error:'Agent 执行失败',agent_timeout:'Agent 响应超时',user_stopped:'用户主动停止',stop_timeout:'停止请求超时',process_missing:'Agent 进程消失',chatccc_restart:'ChatCCC 重启中断',task_deleted:'执行期间任务被删除'};return labels[code]||code||'—';}
212
215
  function addRunMeta(root,label,value){var item=document.createElement('div');item.className='run-meta-item';var key=document.createElement('span');key.textContent=label;var content=document.createElement('strong');content.textContent=value||'—';content.title=value||'—';item.append(key,content);root.appendChild(item);}
213
216
  function transcriptLabel(entry){var labels={prompt:'任务指令',thinking:'思考',text:'主 Agent 回复',tool_use:'调用工具',tool_result:'工具结果',search:'联网搜索',compact:'上下文压缩',status:'状态变化',notice:'提示'};var label=labels[entry.type]||entry.type;if(entry.name)label+=' · '+entry.name;return label;}
214
217
  function transcriptContent(entry){if(entry.type==='tool_use')return entry.input||'';if(entry.type==='tool_result')return entry.output||'';return entry.text||'';}
215
218
  function addTranscriptEntry(root,entry){var row=document.createElement('article');row.className='timeline-entry '+entry.type+(entry.isError?' error':'');var head=document.createElement('div');head.className='timeline-head';var label=document.createElement('span');label.textContent=transcriptLabel(entry);var time=document.createElement('span');time.textContent=formatRunTime(entry.at);head.append(label,time);var body=document.createElement('pre');body.textContent=transcriptContent(entry)||'(无文本内容)';row.append(head,body);root.appendChild(row);}
216
- function renderTaskExecution(task,run){var details=document.getElementById('task-execution-details');var title=document.getElementById('task-title');var description=document.getElementById('task-description');var save=document.getElementById('task-save');var cancel=document.getElementById('cancel');var active=isActiveRun(run);title.disabled=active;description.disabled=active;save.classList.toggle('hidden',active);cancel.textContent=active?'关闭':'取消';details.classList.remove('hidden');var status=document.getElementById('task-run-status');status.className='run-status '+(run?run.state:'');status.textContent=run?runLabel(run):'尚未执行';var meta=document.getElementById('task-run-meta');meta.innerHTML='';if(run){addRunMeta(meta,'执行次数','第 '+run.attempt+' 次');addRunMeta(meta,'执行 Agent',run.agentId);addRunMeta(meta,'开始时间',formatRunTime(run.startedAt||run.createdAt));addRunMeta(meta,'结束时间',formatRunTime(run.finishedAt));}else{addRunMeta(meta,'任务状态','尚未交给主 Agent');addRunMeta(meta,'创建时间',formatRunTime(task.createdAt));addRunMeta(meta,'更新时间',formatRunTime(task.updatedAt));}var transcript=document.getElementById('task-transcript');transcript.innerHTML='';var entries=run&&Array.isArray(run.transcript)?run.transcript.slice():[];var hasText=entries.some(function(entry){return entry.type==='text';});if(run&&run.result&&!hasText)entries.push({type:'text',at:run.finishedAt||run.updatedAt,text:run.result});if(run&&run.error)entries.push({type:'notice',at:run.finishedAt||run.updatedAt,text:run.error,isError:true});if(!entries.length){var empty=document.createElement('div');empty.className='timeline-empty';empty.textContent=run?'暂时还没有可显示的执行事件':'任务尚未执行,交给主 Agent 后会在这里记录完整过程。';transcript.appendChild(empty);return;}entries.forEach(function(entry){addTranscriptEntry(transcript,entry);});}
217
- async function loadOpenTaskRunDetails(showError){if(!state.board||!state.modalTaskId)return;var task=state.board.tasks.find(function(candidate){return candidate.id===state.modalTaskId&&!candidate.deletedAt;});if(!task)return;var summary=latestRun(task.id);if(!summary){renderTaskExecution(task,null);return;}var version=++state.modalDetailVersion;try{var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/runs/'+encodeURIComponent(summary.runId));if(version!==state.modalDetailVersion||state.modalTaskId!==task.id)return;renderTaskExecution(task,result.run);}catch(error){if(showError!==false)toast(error.message,true);}}
219
+ function taskRuns(taskId){return state.runs.filter(function(run){return run.taskId===taskId;});}
220
+ function renderRunHistory(taskId){var select=document.getElementById('task-run-history');var runs=taskRuns(taskId);select.innerHTML='';if(!runs.length){var empty=document.createElement('option');empty.value='';empty.textContent='尚无执行记录';select.appendChild(empty);select.disabled=true;document.getElementById('copy-task-run').disabled=true;return;}select.disabled=false;runs.forEach(function(run){var option=document.createElement('option');option.value=run.runId;option.textContent='第 '+run.attempt+' 次 · '+runLabel(run)+' · '+formatRunTime(run.startedAt||run.createdAt);select.appendChild(option);});if(!state.modalRunId||!runs.some(function(run){return run.runId===state.modalRunId;}))state.modalRunId=runs[0].runId;select.value=state.modalRunId;document.getElementById('copy-task-run').disabled=false;}
221
+ function renderTaskExecution(task,run){state.modalRunDetail=run;renderRunHistory(task.id);var details=document.getElementById('task-execution-details');var title=document.getElementById('task-title');var description=document.getElementById('task-description');var save=document.getElementById('task-save');var cancel=document.getElementById('cancel');var active=isActiveRun(run);title.disabled=active;description.disabled=active;save.classList.toggle('hidden',active);cancel.textContent=active?'关闭':'取消';details.classList.remove('hidden');var status=document.getElementById('task-run-status');status.className='run-status '+(run?run.state:'');status.textContent=run?runLabel(run):'尚未执行';var meta=document.getElementById('task-run-meta');meta.innerHTML='';if(run){addRunMeta(meta,'执行次数','第 '+run.attempt+' 次');addRunMeta(meta,'执行 Agent',run.agentId);addRunMeta(meta,'耗时',formatRunDuration(run));addRunMeta(meta,'最后进度',formatRunTime(run.lastProgressAt));addRunMeta(meta,'开始时间',formatRunTime(run.startedAt||run.createdAt));addRunMeta(meta,'结束时间',formatRunTime(run.finishedAt));addRunMeta(meta,'Run ID',run.runId);addRunMeta(meta,'Trace ID',run.traceId);if(run.failureCode)addRunMeta(meta,'失败类型',failureLabel(run.failureCode));if(run.boardSyncPending)addRunMeta(meta,'看板同步','等待自动对账');}else{addRunMeta(meta,'任务状态','尚未交给主 Agent');addRunMeta(meta,'创建时间',formatRunTime(task.createdAt));addRunMeta(meta,'更新时间',formatRunTime(task.updatedAt));}var transcript=document.getElementById('task-transcript');transcript.innerHTML='';var entries=run&&Array.isArray(run.transcript)?run.transcript.slice():[];var hasText=entries.some(function(entry){return entry.type==='text';});if(run&&run.result&&!hasText)entries.push({type:'text',at:run.finishedAt||run.updatedAt,text:run.result});if(run&&run.error)entries.push({type:'notice',at:run.finishedAt||run.updatedAt,text:failureLabel(run.failureCode)+':'+run.error,isError:true});if(run&&run.syncError)entries.push({type:'notice',at:run.updatedAt,text:'看板对账失败:'+run.syncError,isError:true});if(!entries.length){var empty=document.createElement('div');empty.className='timeline-empty';empty.textContent=run?'暂时还没有可显示的执行事件':'任务尚未执行,交给主 Agent 后会在这里记录完整过程。';transcript.appendChild(empty);return;}entries.forEach(function(entry){addTranscriptEntry(transcript,entry);});}
222
+ async function loadOpenTaskRunDetails(showError){if(!state.board||!state.modalTaskId)return;var task=state.board.tasks.find(function(candidate){return candidate.id===state.modalTaskId&&!candidate.deletedAt;});if(!task)return;renderRunHistory(task.id);var summary=taskRuns(task.id).find(function(run){return run.runId===state.modalRunId;})||latestRun(task.id);if(!summary){renderTaskExecution(task,null);return;}state.modalRunId=summary.runId;var version=++state.modalDetailVersion;try{var result=await api('/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/runs/'+encodeURIComponent(summary.runId));if(version!==state.modalDetailVersion||state.modalTaskId!==task.id)return;renderTaskExecution(task,result.run);}catch(error){if(showError!==false)toast(error.message,true);}}
218
223
  function showCreate(columnId){if(!state.board)return toast('请先打开一个工作目录',true);state.modalTaskId=null;state.modalColumnId=columnId;document.getElementById('modal-title').textContent='添加任务';document.getElementById('task-title').value='';document.getElementById('task-description').value='';document.getElementById('task-title').disabled=false;document.getElementById('task-description').disabled=false;document.getElementById('task-save').classList.remove('hidden');document.getElementById('cancel').textContent='取消';document.getElementById('task-execution-details').classList.add('hidden');document.getElementById('modal').classList.remove('hidden');setTimeout(function(){document.getElementById('task-title').focus();},0);}
219
- function showEdit(task){state.modalTaskId=task.id;state.modalColumnId=task.columnId;document.getElementById('modal-title').textContent='任务详情';document.getElementById('task-title').value=task.title;document.getElementById('task-description').value=task.description;renderTaskExecution(task,latestRun(task.id));document.getElementById('modal').classList.remove('hidden');loadOpenTaskRunDetails(true);}
220
- function closeModal(){state.modalDetailVersion++;document.getElementById('modal').classList.add('hidden');state.modalTaskId=null;}
224
+ function showEdit(task){state.modalTaskId=task.id;state.modalColumnId=task.columnId;state.modalRunId=(latestRun(task.id)||{}).runId||null;document.getElementById('modal-title').textContent='任务详情';document.getElementById('task-title').value=task.title;document.getElementById('task-description').value=task.description;renderTaskExecution(task,latestRun(task.id));document.getElementById('modal').classList.remove('hidden');loadOpenTaskRunDetails(true);}
225
+ function closeModal(){state.modalDetailVersion++;document.getElementById('modal').classList.add('hidden');state.modalTaskId=null;state.modalRunId=null;state.modalRunDetail=null;}
221
226
  function removeTask(task){if(!confirm('删除任务“'+task.title+'”?'))return;mutate(function(){return request('DELETE','/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks/'+encodeURIComponent(task.id),{expectedRevision:state.board.revision});});}
222
227
 
223
228
  document.querySelectorAll('[data-add]').forEach(function(button){button.addEventListener('click',function(){showCreate(button.dataset.add);});});
@@ -227,6 +232,8 @@ export const AGENT_TEAM_PAGE_HTML = `<!DOCTYPE html>
227
232
  });
228
233
  document.getElementById('task-form').addEventListener('submit',function(event){event.preventDefault();var title=document.getElementById('task-title').value;var description=document.getElementById('task-description').value;var taskId=state.modalTaskId;var columnId=state.modalColumnId;closeModal();mutate(function(){var root='/api/agent-team/boards/'+encodeURIComponent(state.board.boardId)+'/tasks';return taskId?request('PATCH',root+'/'+encodeURIComponent(taskId),{expectedRevision:state.board.revision,title:title,description:description}):request('POST',root,{expectedRevision:state.board.revision,title:title,description:description,columnId:columnId});});});
229
234
  document.getElementById('cancel').addEventListener('click',closeModal);document.getElementById('modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeModal();});
235
+ document.getElementById('task-run-history').addEventListener('change',function(event){state.modalRunId=event.currentTarget.value||null;loadOpenTaskRunDetails(true);});
236
+ document.getElementById('copy-task-run').addEventListener('click',async function(){if(!state.modalRunDetail)return;var text=JSON.stringify(state.modalRunDetail,null,2);try{if(navigator.clipboard&&navigator.clipboard.writeText)await navigator.clipboard.writeText(text);else{var area=document.createElement('textarea');area.value=text;area.style.position='fixed';area.style.opacity='0';document.body.appendChild(area);area.select();document.execCommand('copy');area.remove();}toast('执行记录已复制');}catch(error){toast('复制失败:'+error.message,true);}});
230
237
  document.querySelectorAll('[data-feature]').forEach(function(button){button.addEventListener('click',function(){showFeatureModal(button.dataset.feature);});});
231
238
  document.getElementById('feature-modal-ok').addEventListener('click',closeFeatureModal);document.getElementById('feature-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeFeatureModal();});
232
239
  document.getElementById('directory-cancel').addEventListener('click',closeDirectoryModal);document.getElementById('directory-modal').addEventListener('click',function(event){if(event.target===event.currentTarget)closeDirectoryModal();});document.getElementById('directory-confirm').addEventListener('click',confirmDirectorySelection);document.getElementById('directory-up').addEventListener('click',function(){if(state.directoryParentPath)browseDirectory(state.directoryParentPath);});document.getElementById('directory-refresh').addEventListener('click',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-show-hidden').addEventListener('change',function(){if(state.directoryPath)browseDirectory(state.directoryPath);});document.getElementById('directory-path').addEventListener('keydown',function(event){if(event.key==='Enter'){event.preventDefault();browseDirectory(event.currentTarget.value);}});
package/dist/src/cards.js CHANGED
@@ -143,10 +143,7 @@ export function buildHelpCard(userText, opts = {}) {
143
143
  "发送 **/ask** 以问答模式提问(只读,不执行写操作)",
144
144
  "发送 **/usage** 查看当前 Agent 的用量或余额",
145
145
  "发送 **/restart** 重启 ChatCCC 进程",
146
- "发送 **/restart safe** 等待现有任务完成后安全重启",
147
146
  "发送 **/update** 更新并重启(仅 npm 全局安装可用)",
148
- "发送 **/update safe** 等待现有任务完成后安全更新",
149
- "发送 **/safestatus** 查看安全维护状态,**/cancelsf** 取消等待中的预约",
150
147
  ABD_HELP_LINE,
151
148
  ].join("\n");
152
149
  return JSON.stringify({
@@ -270,6 +270,16 @@ function normalizePositiveInteger(raw, fallback) {
270
270
  const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
271
271
  return Number.isInteger(value) && value > 0 ? value : fallback;
272
272
  }
273
+ function normalizeNullablePositiveInteger(raw, label) {
274
+ if (raw === undefined || raw === null || (typeof raw === "string" && raw.trim() === "")) {
275
+ return null;
276
+ }
277
+ const value = typeof raw === "string" ? Number(raw.trim()) : Number(raw);
278
+ if (Number.isInteger(value) && value > 0)
279
+ return value;
280
+ console.warn(`[CONFIG] ${label} 必须为正整数或留空,已按留空处理。`);
281
+ return null;
282
+ }
273
283
  function normalizeRawStreamAgentLogConfig(raw) {
274
284
  const obj = typeof raw === "object" && raw !== null
275
285
  ? raw
@@ -317,6 +327,7 @@ function loadConfig() {
317
327
  subModel: "",
318
328
  alternativeModel: "",
319
329
  effort: "",
330
+ maxOutputTokens: null,
320
331
  provider: "",
321
332
  gitCoAuthor: null,
322
333
  compactionTimeoutMs: DEFAULT_CCC_COMPACTION_TIMEOUT_MS,
@@ -527,6 +538,7 @@ function loadConfig() {
527
538
  subModel: normalizeOptionalConfigField(cccRaw.subModel, { label: "ccc.subModel" }),
528
539
  alternativeModel: normalizeOptionalConfigField(cccRaw.alternativeModel, { label: "ccc.alternativeModel" }),
529
540
  effort: normalizeOptionalConfigField(cccRaw.effort, { label: "ccc.effort" }),
541
+ maxOutputTokens: normalizeNullablePositiveInteger(cccRaw.maxOutputTokens, "ccc.maxOutputTokens"),
530
542
  provider: normalizeCccProviderOverride(cccRaw.provider),
531
543
  gitCoAuthor: typeof cccRaw.gitCoAuthor === "boolean" ? cccRaw.gitCoAuthor : null,
532
544
  compactionTimeoutMs: normalizePositiveInteger(cccRaw.compactionTimeoutMs, DEFAULT_CCC_COMPACTION_TIMEOUT_MS),
@@ -1,8 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
+ import { tmpdir } from "node:os";
4
5
  import { USER_DATA_DIR } from "./config.js";
5
- export const SAFE_MAINTENANCE_FILE = join(USER_DATA_DIR, "state", "safe-maintenance.json");
6
+ export const SAFE_MAINTENANCE_FILE = process.env.VITEST
7
+ ? join(tmpdir(), `chatccc-vitest-safe-maintenance-${process.pid}-${randomUUID()}.json`)
8
+ : join(USER_DATA_DIR, "state", "safe-maintenance.json");
6
9
  export const SAFE_MAINTENANCE_STABLE_IDLE_MS = 1_000;
7
10
  const EMPTY_SNAPSHOT = {
8
11
  activeSessionIds: [],
@@ -508,11 +508,17 @@ export function setSessionFastModeOverride(sessionId, fastMode) {
508
508
  sessionFastModeOverrides.set(sessionId, fastMode);
509
509
  adapterCache.clear();
510
510
  }
511
+ function buildAdapterCacheKey(tool, model, effort, fastMode) {
512
+ const base = `${tool}:${model}:${effort}:${fastMode ? "fast" : "default"}`;
513
+ return tool === "ccc"
514
+ ? `${base}:${config.ccc.maxOutputTokens ?? "inherit"}`
515
+ : base;
516
+ }
511
517
  export function getAdapterForTool(tool, sessionId) {
512
518
  const effectiveModel = getEffectiveModelForTool(tool, sessionId);
513
519
  const effectiveEffort = getEffectiveEffortForTool(tool, sessionId);
514
520
  const effectiveFastMode = getEffectiveFastModeForTool(tool, sessionId);
515
- const cacheKey = `${tool}:${effectiveModel || ""}:${effectiveEffort || ""}:${effectiveFastMode ? "fast" : "default"}`;
521
+ const cacheKey = buildAdapterCacheKey(tool, effectiveModel || "", effectiveEffort || "", effectiveFastMode);
516
522
  const cached = adapterCache.get(cacheKey);
517
523
  if (cached)
518
524
  return cached;
@@ -535,6 +541,9 @@ export function getAdapterForTool(tool, sessionId) {
535
541
  compactionTimeoutMs: config.ccc.compactionTimeoutMs,
536
542
  contextWindow: config.ccc.contextWindow,
537
543
  ...(effectiveEffort ? { effort: effectiveEffort } : {}),
544
+ ...(config.ccc.maxOutputTokens !== null
545
+ ? { maxOutputTokens: config.ccc.maxOutputTokens }
546
+ : {}),
538
547
  // 留空("")不传 → ChatSession 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER)
539
548
  ...(config.ccc.provider ? { provider: config.ccc.provider } : {}),
540
549
  ...(config.ccc.subModel ? { subModel: config.ccc.subModel } : {}),
@@ -2192,7 +2201,7 @@ export function _setAdapterForToolForTest(tool, adapter) {
2192
2201
  const effective = getEffectiveModelForTool(tool);
2193
2202
  const effort = getEffectiveEffortForTool(tool);
2194
2203
  const fastMode = getEffectiveFastModeForTool(tool);
2195
- adapterCache.set(`${tool}:${effective || ""}:${effort || ""}:${fastMode ? "fast" : "default"}`, adapter);
2204
+ adapterCache.set(buildAdapterCacheKey(tool, effective || "", effort || "", fastMode), adapter);
2196
2205
  if (effective)
2197
2206
  adapterCache.set(`${tool}:${effective}`, adapter);
2198
2207
  }
@@ -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;
@@ -934,7 +942,7 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
934
942
  <div class="form-group">
935
943
  <label>Effort(推理强度,选填)</label>
936
944
  <select id="field-CHATCCC_CCC_EFFORT">
937
- <option value="">(留空/默认,服务端 medium)</option>
945
+ <option value="">跟随 DeepCCC 内核配置(默认)</option>
938
946
  <option value="none">none - 直接作答,最省 token</option>
939
947
  <option value="minimal">minimal</option>
940
948
  <option value="low">low</option>
@@ -943,6 +951,12 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
943
951
  <option value="xhigh">xhigh</option>
944
952
  <option value="max">max - 最强推理</option>
945
953
  </select>
954
+ <div class="hint">留空时读取 ~/.deepccc/config.json 或 DEEPCCC_EFFORT;DeepCCC 也留空时使用模型服务端默认值。</div>
955
+ </div>
956
+ <div class="form-group">
957
+ <label>最大输出 Token(选填)</label>
958
+ <input type="number" id="field-CHATCCC_CCC_MAX_OUTPUT_TOKENS" min="1" step="1" placeholder="留空跟随 DeepCCC 内核配置">
959
+ <div class="hint">限制主对话单次输出长度。留空时读取 ~/.deepccc/config.json 或 DEEPCCC_MAX_OUTPUT_TOKENS;DeepCCC 也未配置时使用模型服务端默认值。</div>
946
960
  </div>
947
961
  <div class="form-group">
948
962
  <label>上下文窗口(模型最大上下文)</label>
@@ -1279,6 +1293,8 @@ header .badge{font-size:13px;padding:4px 12px;border-radius:12px;font-weight:500
1279
1293
  <div class="config-row"><span class="key">模型</span><span class="val" id="cfg-CCC_MODEL">-</span></div>
1280
1294
  <div class="config-row"><span class="key">子模型</span><span class="val" id="cfg-CCC_SUB_MODEL">-</span></div>
1281
1295
  <div class="config-row"><span class="key">备选模型</span><span class="val" id="cfg-CCC_ALTERNATIVE_MODEL">-</span></div>
1296
+ <div class="config-row"><span class="key">Effort</span><span class="val" id="cfg-CCC_EFFORT">-</span></div>
1297
+ <div class="config-row"><span class="key">最大输出 Token</span><span class="val" id="cfg-CCC_MAX_OUTPUT_TOKENS">-</span></div>
1282
1298
  <div class="config-row"><span class="key">Git 共同作者</span><span class="val" id="cfg-CCC_GIT_COAUTHOR">-</span></div>
1283
1299
  <label class="agent-default-row" style="margin-top:10px"><input type="checkbox" id="dash-default-ccc" onchange="setDashboardDefaultAgent('ccc', this.checked)"> 设为默认 Agent</label>
1284
1300
  <div class="hint" style="margin-top:6px;line-height:1.6">备选模型仅加入 /model 人工切换列表;保存后下一条消息或下个新会话生效。</div>
@@ -1330,7 +1346,7 @@ const AGENT_FIELDS = {
1330
1346
  claude: ['CHATCCC_ANTHROPIC_MODEL','CHATCCC_ANTHROPIC_SUBAGENT_MODEL','CHATCCC_ANTHROPIC_EFFORT','CHATCCC_ANTHROPIC_API_KEY','CHATCCC_ANTHROPIC_BASE_URL','CHATCCC_ANTHROPIC_MAX_TURN'],
1331
1347
  cursor: ['CHATCCC_CURSOR_PATH','CHATCCC_CURSOR_MODEL','CHATCCC_CURSOR_ALTERNATIVE_MODEL','CHATCCC_CURSOR_AVATAR_BATTERY_MODE','CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'],
1332
1348
  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'],
1349
+ 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
1350
  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
1351
  };
1336
1352
  const FEISHU_FIELDS = ['CHATCCC_APP_ID','CHATCCC_APP_SECRET'];
@@ -1728,6 +1744,7 @@ function renderStep2() {
1728
1744
  prefillNested('field-CHATCCC_CCC_SUB_MODEL', c.ccc.subModel);
1729
1745
  prefillNested('field-CHATCCC_CCC_ALTERNATIVE_MODEL', c.ccc.alternativeModel);
1730
1746
  prefillNested('field-CHATCCC_CCC_EFFORT', c.ccc.effort);
1747
+ prefillNested('field-CHATCCC_CCC_MAX_OUTPUT_TOKENS', c.ccc.maxOutputTokens);
1731
1748
  prefillContextWindow('field-', c.ccc.contextWindow);
1732
1749
  }
1733
1750
  if (c.dsh) {
@@ -1936,6 +1953,7 @@ function renderStep3() {
1936
1953
  lines.push('<div class="config-row"><span class="key">子模型</span><span class="val">' + (vars.CHATCCC_CCC_SUB_MODEL || '(留空,跟随主模型)') + '</span></div>');
1937
1954
  lines.push('<div class="config-row"><span class="key">备选模型</span><span class="val">' + (vars.CHATCCC_CCC_ALTERNATIVE_MODEL || '(留空)') + '</span></div>');
1938
1955
  lines.push('<div class="config-row"><span class="key">Effort</span><span class="val">' + (vars.CHATCCC_CCC_EFFORT || '(留空)') + '</span></div>');
1956
+ lines.push('<div class="config-row"><span class="key">最大输出 Token</span><span class="val">' + (vars.CHATCCC_CCC_MAX_OUTPUT_TOKENS || '(跟随 DeepCCC)') + '</span></div>');
1939
1957
  lines.push('<div class="config-row"><span class="key">上下文窗口</span><span class="val">' + contextWindowTokensLabel(vars.CHATCCC_CCC_CONTEXT_WINDOW || 1048576) + '</span></div>');
1940
1958
  } else if (t === 'dsh') {
1941
1959
  lines.push('<h4 style="margin:10px 0 4px;color:#334155">DeepSeek Harness</h4>');
@@ -2167,6 +2185,10 @@ function updateDashboardUI() {
2167
2185
  document.getElementById('cfg-CCC_MODEL').textContent = (c.ccc && c.ccc.model) || '(留空)';
2168
2186
  document.getElementById('cfg-CCC_SUB_MODEL').textContent = (c.ccc && c.ccc.subModel) || '(留空,跟随主模型)';
2169
2187
  document.getElementById('cfg-CCC_ALTERNATIVE_MODEL').textContent = (c.ccc && c.ccc.alternativeModel) || '(留空)';
2188
+ document.getElementById('cfg-CCC_EFFORT').textContent = (c.ccc && c.ccc.effort) || '(跟随 DeepCCC 内核配置)';
2189
+ document.getElementById('cfg-CCC_MAX_OUTPUT_TOKENS').textContent = c.ccc && c.ccc.maxOutputTokens
2190
+ ? String(c.ccc.maxOutputTokens)
2191
+ : '(跟随 DeepCCC 内核配置)';
2170
2192
  document.getElementById('cfg-CCC_GIT_COAUTHOR').textContent = !c.ccc || c.ccc.gitCoAuthor == null
2171
2193
  ? '跟随 DeepCCC 全局设置(缺省开启)'
2172
2194
  : (c.ccc.gitCoAuthor ? '强制开启' : '强制关闭');
@@ -2255,7 +2277,7 @@ function editSection(section) {
2255
2277
  'CHATCCC_CCC_API_KEY': 'API Key', 'CHATCCC_CCC_BASE_URL': 'Base URL',
2256
2278
  'CHATCCC_CCC_PROVIDER': 'API 协议(选填)',
2257
2279
  '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': '上下文窗口',
2280
+ '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
2281
  '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
2282
  };
2261
2283
  var hintMap = {
@@ -2264,6 +2286,8 @@ function editSection(section) {
2264
2286
  'CHATCCC_CHROME_DEVTOOLS_PORT': '默认 15166,健康检查端点为 http://127.0.0.1:15166/json/version。',
2265
2287
  'CHATCCC_CHROME_DEVTOOLS_PATH': '选填。留空时自动探测 Google Chrome。',
2266
2288
  'CHATCCC_CCC_PROVIDER': '与 Base URL 强相关:OpenAI 兼容端点选 openai;Anthropic Messages 端点选 anthropic。留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_PROVIDER),改动需重启 ChatCCC 生效。',
2289
+ 'CHATCCC_CCC_EFFORT': '留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_EFFORT);内核也留空时使用模型服务端默认值。',
2290
+ 'CHATCCC_CCC_MAX_OUTPUT_TOKENS': '正整数;留空 = 跟随 DeepCCC 内核配置(~/.deepccc/config.json 或 DEEPCCC_MAX_OUTPUT_TOKENS);内核也未配置时使用模型服务端默认值。',
2267
2291
  'CHATCCC_CCC_GIT_COAUTHOR': '跟随全局时读取 ~/.deepccc/config.json 的 git.coAuthor.enabled(缺省为开启)。强制选项只影响 ChatCCC 内置 CCC Agent。',
2268
2292
  'CHATCCC_CCC_CONTEXT_WINDOW': '压缩阈值自动 = 窗口 × 80%(超出即把较早消息压缩为摘要)。⚠️ 超过模型/服务端实际上限时请求会被 API 直接拒绝(context length exceeded),实际窗口以模型与所用服务端为准(如 litellm 代理的 max_input_tokens);单位 k = 1024 tokens,1M = 1,048,576 tokens。',
2269
2293
  'CHATCCC_CCC_SUB_MODEL': '用于 DeepCCC 内部轻量环节(上下文压缩摘要生成、task 子代理任务)。留空 = 跟随主模型;改动需重启 ChatCCC 生效。'
@@ -2314,6 +2338,7 @@ function editSection(section) {
2314
2338
  else if (key === 'CHATCCC_CCC_SUB_MODEL') val = state.config.ccc.subModel || '';
2315
2339
  else if (key === 'CHATCCC_CCC_ALTERNATIVE_MODEL') val = state.config.ccc.alternativeModel || '';
2316
2340
  else if (key === 'CHATCCC_CCC_EFFORT') val = state.config.ccc.effort || '';
2341
+ else if (key === 'CHATCCC_CCC_MAX_OUTPUT_TOKENS') val = state.config.ccc.maxOutputTokens || '';
2317
2342
  else if (key === 'CHATCCC_CCC_CONTEXT_WINDOW') val = state.config.ccc.contextWindow || '1048576';
2318
2343
  } else if (section === 'dsh' && state.config.dsh) {
2319
2344
  if (key === 'CHATCCC_DSH_API_KEY') val = state.config.dsh.apiKey || '';
@@ -2378,12 +2403,14 @@ function editSection(section) {
2378
2403
  var rowId = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'
2379
2404
  ? ' id="edit-cursor-on-demand-budget-row"'
2380
2405
  : (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';
2406
+ var isNumber = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET' || key === 'CHATCCC_CHROME_DEVTOOLS_PORT' || key === 'CHATCCC_CCC_MAX_OUTPUT_TOKENS';
2382
2407
  var inputType = isNumber ? 'number' : (isSecret ? 'password' : 'text');
2383
2408
  var attrs = key === 'CHATCCC_CURSOR_ON_DEMAND_MONTHLY_BUDGET'
2384
2409
  ? ' min="1" step="1"'
2385
2410
  : key === 'CHATCCC_CHROME_DEVTOOLS_PORT'
2386
2411
  ? ' min="1" max="65535" step="1" placeholder="15166"'
2412
+ : key === 'CHATCCC_CCC_MAX_OUTPUT_TOKENS'
2413
+ ? ' min="1" step="1" placeholder="留空跟随 DeepCCC 内核配置"'
2387
2414
  : '';
2388
2415
  html += '<div class="form-group"' + rowId + '><label>' + (labelMap[key] || key) + '</label>';
2389
2416
  html += '<input type="' + inputType + '" id="edit-' + key + '"' + attrs + ' value="' + String(val).replace(/"/g,'&quot;') + '">';
@@ -1,26 +1,26 @@
1
- ---
2
- name: feishu-skill
3
- description: Feishu IM local skills for sending images, files, videos, and for creating new sessions or switching working directories.
4
- ---
5
-
6
- Current working directory: {{cwd}}
7
- Your session id: {{session_id}}
8
- Your Feishu open_id: {{open_id}}
9
-
10
- Use local endpoints instead of calling Feishu Open Platform directly.
11
-
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`
14
- - **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
- - **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
- How to map user requests to these endpoints:
18
-
19
- - "新建会话 / 开个新会话 / 换个新会话" → create a new session (no prompt). Use `cwd` = your current working directory ({{cwd}}) unless the user names a directory.
20
- - "在 <目录> 新建会话(做 <任务>)" → create a new session with `cwd` = that directory, and set `prompt` to the task if one was given.
21
- - "cd 到 <目录> / 切换到 <目录> / 去 <目录> 干活" → judge intent:
22
- - if the user wants to start working there now (a fresh conversation in that directory) → create a new session with `cwd` = that directory.
23
- - if the user only wants to change the default directory for future sessions → call set-cwd.
24
- - when ambiguous, prefer creating a new session (the more common intent for "切换到").
25
- - Directory names may be fuzzy or relative; resolve them to an absolute local path (using your file tools) before calling either endpoint.
26
- - `open_id` must always be passed as exactly {{open_id}}; do not invent it.
1
+ ---
2
+ name: feishu-skill
3
+ description: Feishu IM local skills for sending images, files, videos, and for creating new sessions or switching working directories.
4
+ ---
5
+
6
+ Current working directory: {{cwd}}
7
+ Your session id: {{session_id}}
8
+ Your Feishu open_id: {{open_id}}
9
+
10
+ Use local endpoints instead of calling Feishu Open Platform directly.
11
+
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`
14
+ - **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
+ - **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
+ How to map user requests to these endpoints:
18
+
19
+ - "新建会话 / 开个新会话 / 换个新会话" → create a new session (no prompt). Use `cwd` = your current working directory ({{cwd}}) unless the user names a directory.
20
+ - "在 <目录> 新建会话(做 <任务>)" → create a new session with `cwd` = that directory, and set `prompt` to the task if one was given.
21
+ - "cd 到 <目录> / 切换到 <目录> / 去 <目录> 干活" → judge intent:
22
+ - if the user wants to start working there now (a fresh conversation in that directory) → create a new session with `cwd` = that directory.
23
+ - if the user only wants to change the default directory for future sessions → call set-cwd.
24
+ - when ambiguous, prefer creating a new session (the more common intent for "切换到").
25
+ - Directory names may be fuzzy or relative; resolve them to an absolute local path (using your file tools) before calling either endpoint.
26
+ - `open_id` must always be passed as exactly {{open_id}}; do not invent it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatccc",
3
- "version": "0.2.269",
3
+ "version": "0.2.272",
4
4
  "description": "Feishu bot bridge for Claude Code",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",