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.
- package/README.md +16 -10
- package/config.sample.json +4 -3
- package/deepccc-agent/README.md +147 -61
- package/deepccc-agent/package.json +5 -2
- package/dist/deepccc-agent/src/attachments.js +192 -0
- package/dist/deepccc-agent/src/cli.js +59 -13
- package/dist/deepccc-agent/src/config.js +57 -4
- package/dist/deepccc-agent/src/context.js +299 -16
- package/dist/deepccc-agent/src/file-tools.js +33 -0
- package/dist/deepccc-agent/src/index.js +68 -21
- package/dist/deepccc-agent/src/tool-protocol.js +14 -3
- package/dist/deepccc-agent/src/web-entry.js +72 -0
- package/dist/deepccc-agent/src/web-page.js +414 -0
- package/dist/deepccc-agent/src/web-runtime.js +331 -0
- package/dist/deepccc-agent/src/web-server.js +476 -0
- package/dist/deepccc-agent/src/web-session-store.js +162 -0
- package/dist/deepccc-agent/src/web-tool-presentation.js +123 -0
- package/dist/src/adapters/ccc-adapter.js +5 -1
- package/dist/src/agent-capability-grants.js +26 -0
- package/dist/src/agent-delegate-task.js +5 -2
- package/dist/src/agent-file-rpc.js +6 -1
- package/dist/src/agent-image-rpc.js +6 -1
- package/dist/src/agent-team/application/task-execution-service.js +330 -97
- package/dist/src/agent-team/domain/task-run.js +14 -1
- package/dist/src/agent-team/infrastructure/task-execution-runtime.js +7 -2
- package/dist/src/agent-team/main-agent-bootstrap.js +24 -1
- package/dist/src/agent-team/repositories/json-task-run-repository.js +22 -4
- package/dist/src/agent-team/web/agent-team-page.js +14 -7
- package/dist/src/cards.js +7 -4
- package/dist/src/config.js +12 -0
- package/dist/src/im-skills.js +9 -2
- package/dist/src/orchestrator.js +117 -29
- package/dist/src/safe-maintenance.js +4 -1
- package/dist/src/session-name.js +15 -0
- package/dist/src/session.js +54 -9
- package/dist/src/web-ui.js +76 -32
- package/im-skills/feishu-skill/receive-send-file.md +3 -2
- package/im-skills/feishu-skill/receive-send-image.md +3 -2
- package/im-skills/feishu-skill/send-file.mjs +6 -5
- package/im-skills/feishu-skill/send-image.mjs +6 -5
- package/im-skills/feishu-skill/skill.md +4 -2
- package/package.json +1 -1
|
@@ -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
|
|
217
|
-
|
|
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
|
@@ -139,6 +139,8 @@ export function buildHelpCard(userText, opts = {}) {
|
|
|
139
139
|
"发送 **/new ccc** 创建新 CCC Agent 会话",
|
|
140
140
|
"发送 **/new dsh** 创建新 DeepSeek Harness 会话",
|
|
141
141
|
"发送 **/forget** 重置当前会话(忘掉上下文,沿用当前工作目录,不切换)",
|
|
142
|
+
"发送 **/rename 名称** 修改会话展示标题(不会修改群名)",
|
|
143
|
+
"发送 **/sessions 关键词** 搜索会话,**/pin** 置顶,**/archive** 归档",
|
|
142
144
|
"发送 **/plan** 以规划模式提问(只读,不执行写操作)",
|
|
143
145
|
"发送 **/ask** 以问答模式提问(只读,不执行写操作)",
|
|
144
146
|
"发送 **/usage** 查看当前 Agent 的用量或余额",
|
|
@@ -308,7 +310,8 @@ export function buildSessionsCard(sessions, opts = {}) {
|
|
|
308
310
|
extra = ` | 本轮: ${mins}分${secs}秒`;
|
|
309
311
|
}
|
|
310
312
|
const toolLabel = sessionToolLabel(s.tool);
|
|
311
|
-
const
|
|
313
|
+
const title = s.displayTitle || s.chatName;
|
|
314
|
+
const namePart = title ? `${s.pinned ? "📌 " : ""}**${title}** ` : "";
|
|
312
315
|
const chatTag = !s.chatId
|
|
313
316
|
? " (chat id缺失)"
|
|
314
317
|
: s.chatType === "p2p"
|
|
@@ -316,7 +319,7 @@ export function buildSessionsCard(sessions, opts = {}) {
|
|
|
316
319
|
: s.chatType === "group" || (s.chatType == null && s.chatId.startsWith("oc_"))
|
|
317
320
|
? " (群聊)"
|
|
318
321
|
: "";
|
|
319
|
-
return `**${i + 1}.** ${namePart}${chatTag} \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
|
|
322
|
+
return `**${i + 1}.** ${namePart}${chatTag}${s.archivedAt ? " (已归档)" : ""} \`${shortId}\` ${status} | 工具: ${toolLabel} | 轮数: ${s.turnCount} | ${s.model}${extra}`;
|
|
320
323
|
};
|
|
321
324
|
const lines = [`共 **${sessions.length}** 个会话:`, ""];
|
|
322
325
|
const idx = { value: 0 };
|
|
@@ -337,8 +340,8 @@ export function buildSessionsCard(sessions, opts = {}) {
|
|
|
337
340
|
{ tag: "div", text: { tag: "lark_md", content: lines.join("\n") } },
|
|
338
341
|
{ tag: "hr" },
|
|
339
342
|
{ tag: "div", text: { tag: "lark_md", content: fixedPrivateSession
|
|
340
|
-
? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/forget**
|
|
341
|
-
: "
|
|
343
|
+
? "当前飞书私聊使用专属会话;默认 Agent 变化后,下一条普通消息会自动创建对应 Agent 的新空会话。发送 **/rename 名称** 修改展示标题,**/pin** 置顶,**/archive** 归档,**/sessions 关键词** 搜索。发送 **/forget** 可在私聊中原地重置;群聊会话请回到对应群聊继续,私聊不支持 **/session** 切换。"
|
|
344
|
+
: "发送 **/rename 名称** 修改展示标题,**/pin** 置顶,**/archive** 归档,**/sessions 关键词** 搜索,**/sessions --archived** 查看归档。\n发送 **/session 数字** 可切换到最近一次列表中对应编号的会话;**/forget** 会保留群聊和工作目录并创建新 Session。" } },
|
|
342
345
|
{ tag: "hr" },
|
|
343
346
|
{
|
|
344
347
|
tag: "action",
|
package/dist/src/config.js
CHANGED
|
@@ -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),
|
package/dist/src/im-skills.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { join, dirname } from "node:path";
|
|
3
4
|
import { PROJECT_ROOT } from "./config.js";
|
|
4
5
|
export const DEFAULT_IM_SKILLS_DIR = join(PROJECT_ROOT, "im-skills");
|
|
6
|
+
export function sessionImSkillsCacheDir(cacheRoot, sessionId) {
|
|
7
|
+
if (!sessionId)
|
|
8
|
+
throw new Error("sessionId is required for the IM skills cache");
|
|
9
|
+
const sessionKey = createHash("sha256").update(sessionId, "utf8").digest("hex").slice(0, 24);
|
|
10
|
+
return join(cacheRoot, sessionKey);
|
|
11
|
+
}
|
|
5
12
|
function renderTemplate(template, variables) {
|
|
6
13
|
return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (_match, key) => {
|
|
7
14
|
return variables[key] ?? "";
|
|
@@ -56,8 +63,8 @@ function promptCacheKey(input) {
|
|
|
56
63
|
const names = input.enabledSkillNames
|
|
57
64
|
? [...input.enabledSkillNames].sort().join(",")
|
|
58
65
|
: "*";
|
|
59
|
-
const { session_id, cwd, open_id } = input.variables;
|
|
60
|
-
return `${input.skillsDir ?? DEFAULT_IM_SKILLS_DIR}|${names}|${session_id}|${cwd}|${open_id}`;
|
|
66
|
+
const { session_id, cwd, open_id, agent_capability_grant } = input.variables;
|
|
67
|
+
return `${input.skillsDir ?? DEFAULT_IM_SKILLS_DIR}|${names}|${session_id}|${cwd}|${open_id}|${agent_capability_grant}`;
|
|
61
68
|
}
|
|
62
69
|
/** 带会话级缓存的 buildImSkillsPrompt。同 session + 同 cwd 时直接返回缓存的渲染结果。 */
|
|
63
70
|
export async function buildImSkillsPromptCached(input) {
|
package/dist/src/orchestrator.js
CHANGED
|
@@ -16,13 +16,13 @@ import { appendStartupTrace } from "./shared.js";
|
|
|
16
16
|
import { CLAUDE_MODEL, GIT_TIMEOUT_MS, PROJECT_ROOT, anthropicConfigDisplay, config, fileLog, getAllEffortsForTool, getAllModelsForTool, getDefaultEffortForTool, getDefaultCwd, LOG_DIR, setDefaultCwd, getRecentDirs, addRecentDir, resolveDefaultAgentTool, sessionPrefixForTool, toolDisplayName, ts, } from "./config.js";
|
|
17
17
|
import { buildHelpCard, buildEffortCard, buildFastModeCard, buildModelCard, buildStatusCard, buildCdContent, buildCdCard, buildSessionsCard, buildQueuedCard, buildQueueFullCard, buildCodexUsageCard, } from "./cards.js";
|
|
18
18
|
import { formatGitResult, gitResultHeaderTemplate, runGitCommand, } from "./git-command.js";
|
|
19
|
-
import { clearSessionModelOverride, clearSessionEffortOverride, getSessionStatus, getAllSessionsStatus, initClaudeSession, lastMsgTimestamps, resumeAndPrompt, sessionInfoMap, setSessionModelOverride, setSessionEffortOverride, switchChatBinding, recordSessionRegistry, getAdapterForTool, getEffectiveModelForTool, getEffectiveEffortForTool, getEffectiveFastModeForTool, setSessionFastModeOverride, stopSession, loadSessionRegistryForBinding, removeSessionRegistryRecord, saveSessionTool, recordChatPlatform, } from "./session.js";
|
|
19
|
+
import { clearSessionModelOverride, clearSessionEffortOverride, getSessionStatus, getAllSessionsStatus, initClaudeSession, lastMsgTimestamps, resumeAndPrompt, sessionInfoMap, setSessionModelOverride, setSessionEffortOverride, switchChatBinding, recordSessionRegistry, getAdapterForTool, getEffectiveModelForTool, getEffectiveEffortForTool, getEffectiveFastModeForTool, setSessionFastModeOverride, stopSession, loadSessionRegistryForBinding, removeSessionRegistryRecord, saveSessionTool, saveSessionPresentation, recordChatPlatform, } from "./session.js";
|
|
20
20
|
import { bindChatToSession, unbindChatFromSession, isSessionRunning, displayCards, enqueueMessage, cancelQueuedMessage, getSessionDrainSnapshot, } from "./session-chat-binding.js";
|
|
21
21
|
import { getCodexUsageSummary, getTenantAccessToken, sendPostMessage } from "./feishu-platform.js";
|
|
22
22
|
import { getCursorUsageSummary } from "./cursor-usage.js";
|
|
23
23
|
import { getChatGptSubscriptionStatus } from "./chatgpt-subscription.js";
|
|
24
24
|
import { applySharedPrefix } from "./shared-prefix.js";
|
|
25
|
-
import { sessionChatName } from "./session-name.js";
|
|
25
|
+
import { normalizeSessionDisplayTitle, sessionChatName, sessionDisplayTitleFromPrompt, } from "./session-name.js";
|
|
26
26
|
import { reloadRuntimeConfig } from "./runtime-reload.js";
|
|
27
27
|
import { acquireUpdateCommandGuard } from "./update-command-guard.js";
|
|
28
28
|
import { createInternalRestartEnv, INTERNAL_RESTART_ENV_VAR } from "./startup-lifecycle.js";
|
|
@@ -417,6 +417,21 @@ async function sendUsageError(platform, chatId, tool, err) {
|
|
|
417
417
|
function isUntitledSessionChatName(name) {
|
|
418
418
|
return name === "新会话" || name.startsWith("新会话-");
|
|
419
419
|
}
|
|
420
|
+
const sessionListViews = new Map();
|
|
421
|
+
function orderSessionsForCard(sessions) {
|
|
422
|
+
const tools = ["claude", "cursor", "codex", "ccc", "dsh"];
|
|
423
|
+
return tools.flatMap((tool) => sessions.filter((session) => session.tool === tool));
|
|
424
|
+
}
|
|
425
|
+
function parseSessionsCommand(text) {
|
|
426
|
+
const raw = text.trim().slice("/sessions".length).trim();
|
|
427
|
+
if (!raw)
|
|
428
|
+
return {};
|
|
429
|
+
if (raw === "--archived")
|
|
430
|
+
return { includeArchived: true, archivedOnly: true };
|
|
431
|
+
if (raw.startsWith("--all"))
|
|
432
|
+
return { includeArchived: true, query: raw.slice("--all".length).trim() || undefined };
|
|
433
|
+
return { query: raw };
|
|
434
|
+
}
|
|
420
435
|
async function fixedSessionChatName(chatId) {
|
|
421
436
|
const registry = await loadSessionRegistryForBinding();
|
|
422
437
|
const record = registry[chatId];
|
|
@@ -904,8 +919,8 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
904
919
|
logTrace(tid, "DONE", { outcome: canceled ? "safe_maintenance_canceled" : "safe_maintenance_cancel_unavailable" });
|
|
905
920
|
return;
|
|
906
921
|
}
|
|
907
|
-
if (isCommandText &&
|
|
908
|
-
logTrace(tid, "BRANCH", { cmd: "/restart safe" });
|
|
922
|
+
if (isCommandText && ["/restart safe", "/restartsf"].includes(normalizedCommandText)) {
|
|
923
|
+
logTrace(tid, "BRANCH", { cmd: "/restart safe", alias: normalizedCommandText });
|
|
909
924
|
try {
|
|
910
925
|
const job = await safeMaintenanceCoordinator.schedule("restart", safeMaintenanceRequester(platform, chatId, openId));
|
|
911
926
|
await platform.sendText(chatId, job.phase === "executing"
|
|
@@ -921,8 +936,8 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
921
936
|
}
|
|
922
937
|
return;
|
|
923
938
|
}
|
|
924
|
-
if (isCommandText &&
|
|
925
|
-
logTrace(tid, "BRANCH", { cmd: "/update safe" });
|
|
939
|
+
if (isCommandText && ["/update safe", "/updatesf"].includes(normalizedCommandText)) {
|
|
940
|
+
logTrace(tid, "BRANCH", { cmd: "/update safe", alias: normalizedCommandText });
|
|
926
941
|
const isGlobal = isRunningFromGlobalNpm();
|
|
927
942
|
if (!isGlobal) {
|
|
928
943
|
await platform.sendText(chatId, "当前进程非 npm 全局安装,无法使用 /update safe。请通过 npm install -g chatccc 安装后使用。").catch(() => { });
|
|
@@ -951,11 +966,14 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
951
966
|
return;
|
|
952
967
|
}
|
|
953
968
|
const maintenanceAllowedCommands = new Set([
|
|
954
|
-
"/stop", "/cancel", "/state", "/sessions", "/usage", "/safestatus", "/cancelsf", "/restart", "/update", "/reload", "/help",
|
|
969
|
+
"/stop", "/cancel", "/state", "/sessions", "/usage", "/safestatus", "/cancelsf", "/restart", "/restartsf", "/update", "/updatesf", "/reload", "/help",
|
|
970
|
+
"/pin", "/unpin", "/archive", "/unarchive",
|
|
955
971
|
]);
|
|
972
|
+
const maintenancePresentationCommand = normalizedCommandText.startsWith("/rename ")
|
|
973
|
+
|| normalizedCommandText.startsWith("/sessions ");
|
|
956
974
|
if (isSafeMaintenanceAdmissionClosed()
|
|
957
975
|
&& !acceptedBeforeSafeMaintenance
|
|
958
|
-
&& (!isCommandText || !maintenanceAllowedCommands.has(normalizedCommandText))) {
|
|
976
|
+
&& (!isCommandText || (!maintenanceAllowedCommands.has(normalizedCommandText) && !maintenancePresentationCommand))) {
|
|
959
977
|
await platform.sendText(chatId, "ChatCCC 正在等待安全重启或更新,当前不接受新的任务。请在维护完成后重新发送;可用 /safestatus 查看状态。").catch(() => { });
|
|
960
978
|
logTrace(tid, "DONE", { outcome: "safe_maintenance_admission_closed" });
|
|
961
979
|
return;
|
|
@@ -1513,6 +1531,27 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
1513
1531
|
console.error(`[${ts()}] [RENAME] ${platform.kind} P2P failed: ${err.message}`);
|
|
1514
1532
|
}
|
|
1515
1533
|
}
|
|
1534
|
+
// Display titles are independent from chatName. Preserve the existing IM
|
|
1535
|
+
// group-name behavior while giving session lists a durable user-facing title.
|
|
1536
|
+
if (!isCommandText) {
|
|
1537
|
+
try {
|
|
1538
|
+
const registry = await loadSessionRegistryForBinding();
|
|
1539
|
+
const current = registry[chatId];
|
|
1540
|
+
if (current && !current.displayTitle && current.namePolicy !== "fixed") {
|
|
1541
|
+
const displayTitle = sessionDisplayTitleFromPrompt(text);
|
|
1542
|
+
await recordSessionRegistry({
|
|
1543
|
+
chatId,
|
|
1544
|
+
sessionId,
|
|
1545
|
+
tool: descriptionTool,
|
|
1546
|
+
displayTitle,
|
|
1547
|
+
});
|
|
1548
|
+
await saveSessionPresentation(sessionId, { displayTitle });
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
catch (err) {
|
|
1552
|
+
console.error(`[${ts()}] [SESSION-TITLE] auto title failed: ${err.message}`);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1516
1555
|
if (isCommandText && textLower === "/stop") {
|
|
1517
1556
|
logTrace(tid, "BRANCH", { cmd: "/stop" });
|
|
1518
1557
|
if (stopSession(sessionId)) {
|
|
@@ -1582,13 +1621,55 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
1582
1621
|
await sendStateCard(platform, chatId, sessionId, toolLabel, tid);
|
|
1583
1622
|
return;
|
|
1584
1623
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1624
|
+
const renameMatch = isCommandText ? text.trim().match(/^\/rename(?:\s+(.+))?$/i) : null;
|
|
1625
|
+
if (renameMatch) {
|
|
1626
|
+
logTrace(tid, "BRANCH", { cmd: "/rename" });
|
|
1627
|
+
if (!renameMatch[1]) {
|
|
1628
|
+
const registry = await loadSessionRegistryForBinding();
|
|
1629
|
+
const currentTitle = registry[chatId]?.displayTitle || registry[chatId]?.chatName || "(未命名)";
|
|
1630
|
+
await platform.sendText(chatId, `当前会话标题:${currentTitle}\n用法:/rename 新名称`).catch(() => { });
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
try {
|
|
1634
|
+
const displayTitle = normalizeSessionDisplayTitle(renameMatch[1]);
|
|
1635
|
+
await recordSessionRegistry({ chatId, sessionId, tool: descriptionTool, displayTitle });
|
|
1636
|
+
await saveSessionPresentation(sessionId, { displayTitle });
|
|
1637
|
+
await platform.sendText(chatId, `会话标题已更新为:${displayTitle}\n群名保持不变。`).catch(() => { });
|
|
1638
|
+
logTrace(tid, "DONE", { outcome: "rename_session", displayTitle });
|
|
1639
|
+
}
|
|
1640
|
+
catch (err) {
|
|
1641
|
+
await platform.sendText(chatId, `会话重命名失败:${err.message}`).catch(() => { });
|
|
1642
|
+
}
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (isCommandText && ["/pin", "/unpin", "/archive", "/unarchive"].includes(textLower)) {
|
|
1646
|
+
const registry = await loadSessionRegistryForBinding();
|
|
1647
|
+
const current = registry[chatId];
|
|
1648
|
+
const pinned = textLower === "/pin" ? true : textLower === "/unpin" ? false : current?.pinned;
|
|
1649
|
+
const archivedAt = textLower === "/archive" ? Date.now() : textLower === "/unarchive" ? null : current?.archivedAt;
|
|
1650
|
+
await recordSessionRegistry({ chatId, sessionId, tool: descriptionTool, pinned, archivedAt });
|
|
1651
|
+
await saveSessionPresentation(sessionId, { pinned, archivedAt });
|
|
1652
|
+
const message = textLower === "/pin" ? "会话已置顶。"
|
|
1653
|
+
: textLower === "/unpin" ? "会话已取消置顶。"
|
|
1654
|
+
: textLower === "/archive" ? "会话已归档;可用 /sessions --archived 查看。"
|
|
1655
|
+
: "会话已取消归档。";
|
|
1656
|
+
await platform.sendText(chatId, message).catch(() => { });
|
|
1657
|
+
logTrace(tid, "DONE", { outcome: textLower.slice(1) });
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (isCommandText && (textLower === "/sessions" || textLower.startsWith("/sessions "))) {
|
|
1661
|
+
const listOptions = parseSessionsCommand(text);
|
|
1662
|
+
logTrace(tid, "BRANCH", { cmd: "/sessions", ...listOptions });
|
|
1663
|
+
const allSessions = await getAllSessionsStatus(listOptions);
|
|
1664
|
+
const orderedSessions = orderSessionsForCard(allSessions);
|
|
1665
|
+
sessionListViews.set(chatId, orderedSessions.map((session) => session.sessionId));
|
|
1588
1666
|
const now = Date.now();
|
|
1589
|
-
const cardData =
|
|
1667
|
+
const cardData = orderedSessions.map((s) => ({
|
|
1590
1668
|
sessionId: s.sessionId,
|
|
1591
1669
|
chatName: s.chatName,
|
|
1670
|
+
displayTitle: s.displayTitle,
|
|
1671
|
+
pinned: s.pinned,
|
|
1672
|
+
archivedAt: s.archivedAt,
|
|
1592
1673
|
chatId: s.chatId,
|
|
1593
1674
|
chatType: s.chatType,
|
|
1594
1675
|
active: s.active,
|
|
@@ -1719,19 +1800,12 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
1719
1800
|
}
|
|
1720
1801
|
const index = parseInt(sessionMatch[1], 10) - 1;
|
|
1721
1802
|
logTrace(tid, "BRANCH", { cmd: "/session", index: index + 1 });
|
|
1722
|
-
const
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
const ordered = [
|
|
1729
|
-
...claudeOrdered,
|
|
1730
|
-
...cursorOrdered,
|
|
1731
|
-
...codexOrdered,
|
|
1732
|
-
...cccOrdered,
|
|
1733
|
-
...dshOrdered,
|
|
1734
|
-
];
|
|
1803
|
+
const viewIds = sessionListViews.get(chatId);
|
|
1804
|
+
const allSessions = await getAllSessionsStatus({ includeArchived: true, limit: 100 });
|
|
1805
|
+
const byId = new Map(allSessions.map((session) => [session.sessionId, session]));
|
|
1806
|
+
const ordered = viewIds !== undefined
|
|
1807
|
+
? viewIds.map((sessionId) => byId.get(sessionId)).filter((session) => !!session)
|
|
1808
|
+
: orderSessionsForCard(allSessions.filter((session) => !session.archivedAt).slice(0, 20));
|
|
1735
1809
|
if (ordered.length === 0) {
|
|
1736
1810
|
await platform.sendCard(chatId, "/session", "暂无历史会话。", "yellow");
|
|
1737
1811
|
logTrace(tid, "DONE", { outcome: "session_no_sessions" });
|
|
@@ -1773,6 +1847,9 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
1773
1847
|
tool: target.tool,
|
|
1774
1848
|
chatName: newName2,
|
|
1775
1849
|
namePolicy: fixedName2 ? "fixed" : "auto",
|
|
1850
|
+
displayTitle: target.displayTitle,
|
|
1851
|
+
pinned: target.pinned,
|
|
1852
|
+
archivedAt: target.archivedAt ?? null,
|
|
1776
1853
|
newDescription: `${descPrefix2} ${target.sessionId}`,
|
|
1777
1854
|
initialTurnCount: target.turnCount,
|
|
1778
1855
|
initialContextTokens: 0,
|
|
@@ -2217,13 +2294,24 @@ async function handleCommandInternal(platform, text, chatId, openId, msgTimestam
|
|
|
2217
2294
|
logTrace(tid, "DONE", { outcome: "effort_no_session", defaultTool });
|
|
2218
2295
|
return;
|
|
2219
2296
|
}
|
|
2220
|
-
if (isCommandText && textLower
|
|
2221
|
-
|
|
2222
|
-
|
|
2297
|
+
if (isCommandText && (/^\/rename(?:\s|$)/.test(textLower) || ["/pin", "/unpin", "/archive", "/unarchive"].includes(textLower))) {
|
|
2298
|
+
await platform.sendText(chatId, "当前没有绑定会话。请进入具体会话后再修改标题、置顶或归档状态。").catch(() => { });
|
|
2299
|
+
logTrace(tid, "DONE", { outcome: "session_presentation_no_session" });
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
if (isCommandText && (textLower === "/sessions" || textLower.startsWith("/sessions "))) {
|
|
2303
|
+
const listOptions = parseSessionsCommand(text);
|
|
2304
|
+
logTrace(tid, "BRANCH", { cmd: "/sessions", scope: "global", ...listOptions });
|
|
2305
|
+
const allSessions = await getAllSessionsStatus(listOptions);
|
|
2306
|
+
const orderedSessions = orderSessionsForCard(allSessions);
|
|
2307
|
+
sessionListViews.set(chatId, orderedSessions.map((session) => session.sessionId));
|
|
2223
2308
|
const now = Date.now();
|
|
2224
|
-
const cardData =
|
|
2309
|
+
const cardData = orderedSessions.map((s) => ({
|
|
2225
2310
|
sessionId: s.sessionId,
|
|
2226
2311
|
chatName: s.chatName,
|
|
2312
|
+
displayTitle: s.displayTitle,
|
|
2313
|
+
pinned: s.pinned,
|
|
2314
|
+
archivedAt: s.archivedAt,
|
|
2227
2315
|
chatId: s.chatId,
|
|
2228
2316
|
chatType: s.chatType,
|
|
2229
2317
|
active: s.active,
|
|
@@ -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 =
|
|
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: [],
|
package/dist/src/session-name.js
CHANGED
|
@@ -5,3 +5,18 @@ export function cwdDisplayName(cwd) {
|
|
|
5
5
|
export function sessionChatName(left, cwd) {
|
|
6
6
|
return `${left}-${cwdDisplayName(cwd)}`;
|
|
7
7
|
}
|
|
8
|
+
/** Derive a deterministic user-facing title without changing the IM chat name. */
|
|
9
|
+
export function sessionDisplayTitleFromPrompt(prompt, maxLength = 32) {
|
|
10
|
+
const normalized = prompt.replace(/\s+/g, " ").trim();
|
|
11
|
+
if (!normalized)
|
|
12
|
+
return "新会话";
|
|
13
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1)}…`;
|
|
14
|
+
}
|
|
15
|
+
export function normalizeSessionDisplayTitle(value, maxLength = 80) {
|
|
16
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
17
|
+
if (!normalized)
|
|
18
|
+
throw new Error("会话名称不能为空");
|
|
19
|
+
if (normalized.length > maxLength)
|
|
20
|
+
throw new Error(`会话名称不能超过 ${maxLength} 个字符`);
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|