feat: add v2 task control and asynchronous isolated workers

This commit is contained in:
WHC
2026-10-20 14:46:57 +08:00
parent 143dba203b
commit d971cb02b0
26 changed files with 1590 additions and 66 deletions
+58 -2
View File
@@ -1821,7 +1821,7 @@ async function copyText(t) {
function syncSend() {
const v = $('input').value;
/* commands also work while a turn runs (/stop, /help, …) */
$('btn-send').disabled = (busy && !v.startsWith('/')) || !v.trim();
$('btn-send').disabled = !v.trim();
}
async function compact() {
$('status').textContent = '压缩中…';
@@ -1909,7 +1909,15 @@ async function send(text) {
return;
}
}
if (busy) return;
if (busy) {
try {
await post('api/task', { message: body });
if (text === undefined) input.value = '';
autoGrow(); syncSend();
$('status').textContent = '正在应用最新指令,已停止冲突的待执行操作';
} catch (e) { toastErr(e); }
return;
}
if (text === undefined) input.value = '';
autoGrow();
syncSend();
@@ -2119,6 +2127,7 @@ const COMMANDS = [
{ name: 'skills', desc: '查看可用的 skills', run: () => showExtensions('skills') },
{ name: 'extensions', aliases: ['mcp', 'plugins', 'agents'], desc: 'MCP、插件、skills、子代理;信任工作区',
run: (a, name) => showExtensions(name === 'extensions' ? undefined : name) },
{ name: 'task', aliases: ['jobs'], desc: '当前目标、执行控制和异步作业', run: showTaskBoard },
{ name: 'thinking', args: '[on|off]', desc: '思考过程默认展开/收起(会记住)', complete: ON_OFF, run: setThinking },
{ name: 'details', args: '[on|off]', desc: '工具输出全部展开/收起(会记住)', complete: ON_OFF, run: setDetails },
{ name: 'theme', desc: '切换深色/浅色', run: toggleTheme },
@@ -2321,6 +2330,52 @@ function reportZh(r) {
.replace(/(\d+) MCP servers?/, '$1 个 MCP 服务器')
.replace(/untrusted workspace: \/plugins trust/, '工作区未信任,它自带的插件和 MCP 没有加载');
}
async function showTaskBoard() {
const box = el('div');
box.appendChild(el('h3', null, '任务控制 / 异步作业'));
const status = el('p', 'muted tiny'); box.appendChild(status);
const controls = el('div', 'buttons left'); box.appendChild(controls);
const jobs = el('div'); box.appendChild(jobs);
const message = el('textarea'); message.placeholder = '执行中直接补充或纠正指令'; message.rows = 3; message.style.width = '100%'; box.appendChild(message);
box.appendChild(Object.assign(el('button', null, '立即应用指令'), { onclick: async () => {
if (!message.value.trim()) return;
try { await post('api/task', { message: message.value }); message.value = ''; } catch (e) { toastErr(e); }
} }));
const delta = el('pre'); delta.style.whiteSpace = 'pre-wrap'; box.appendChild(delta);
let updating = false;
const update = async () => {
if (updating || !box.isConnected) return;
updating = true;
try {
const state = await getJSON('api/task');
status.textContent = '版本 ' + state.revision + ' · ' + state.status + ' · ' + (state.cancel_source || '') + '\n' + (state.goal || '');
controls.textContent = '';
const control = (label, body) => controls.appendChild(Object.assign(el('button', null, label), { onclick: async () => { try { await post('api/task', body); await update(); } catch (e) { toastErr(e); } } }));
control(state.read_only ? '允许修改' : '只查看', { read_only: !state.read_only });
control(state.delegation ? '主 agent 亲自执行' : '允许委派', { delegation: !state.delegation });
control(state.paused ? '恢复' : '暂停', { paused: !state.paused });
jobs.textContent = '';
for (const job of state.jobs || []) {
const row = el('div', 'ext-row');
row.appendChild(el('h4', null, job.job_id + ' · ' + job.kind + ' · ' + job.status));
row.appendChild(el('p', 'muted tiny', (job.cancel_source || '') + ' · ' + (job.resource || '')));
row.appendChild(el('pre', null, (job.output || '').slice(-3000)));
if (['running', 'queued', 'running_protected'].includes(job.status)) row.appendChild(Object.assign(el('button', 'danger', job.status === 'running_protected' ? '强制停止当前操作' : '取消'), {
onclick: async () => { try { await post('api/jobs/cancel', { job_id: job.job_id, force: job.status === 'running_protected' }); } catch (e) { toastErr(e); } },
}));
if ((job.resource || '').includes('/.agent/v2/wt-')) {
for (const integrate of [false, true]) row.appendChild(Object.assign(el('button', null, integrate ? '集成修改' : '预览修改'), {
onclick: async () => { try { const r = await post('api/worktree', { worktree: job.resource, integrate }); delta.textContent = r.output; } catch (e) { toastErr(e); } },
}));
}
jobs.appendChild(row);
}
} catch (e) { status.textContent = errMsg(e); } finally { updating = false; }
};
openModal(box, true); await update();
const timer = setInterval(() => { if (!box.isConnected) clearInterval(timer); else update(); }, 500);
}
async function showExtensions(focus) {
let d;
try { d = extCache = await getJSON('api/extensions'); }
@@ -2342,6 +2397,7 @@ async function showExtensions(focus) {
? '这个工作区已信任:它的 .agent/plugins 和 .agent/mcp.json 会加载。'
: '这个工作区未信任:它的 .agent/plugins 和 .agent/mcp.json 不会加载(配置目录里的照常加载)。')));
const acts = el('div', 'buttons left');
acts.appendChild(Object.assign(el('button', null, '任务控制与作业'), { onclick: showTaskBoard }));
acts.appendChild(Object.assign(el('button', null, '模型与 worker 配置'), {
onclick: async () => {
try {