feat: add v2 task control and asynchronous isolated workers
This commit is contained in:
+58
-2
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user