feat: add remote agent execution feature and notification for unblocked tasks

- Updated README to include a new TODO for remote agent execution via SSH.
- Implemented a notification system that alerts owners of blocked tasks when their dependencies are resolved, enhancing task management and collaboration.
This commit is contained in:
iliya 2026-03-21 14:21:13 +02:00
parent 7c9631c1b9
commit 67f38d8029
2 changed files with 74 additions and 2 deletions

View file

@ -237,6 +237,7 @@ pnpm dist # macOS + Windows + Linux
## TODO ## TODO
- [ ] CLI runtime: Run not only on a local PC but in any headless/console environment (web UI), e.g. VPS, remote server, etc. - [ ] CLI runtime: Run not only on a local PC but in any headless/console environment (web UI), e.g. VPS, remote server, etc.
- [ ] Remote agent execution via SSH: launch and manage agent teams on remote machines over SSH (stream-json protocol over SSH channel, SFTP-based file monitoring for tasks/inboxes/config)
- [ ] 2 modes: current (agent teams), and a new mode: regular subagents (no communication between them) - [ ] 2 modes: current (agent teams), and a new mode: regular subagents (no communication between them)
- [ ] Visual workflow editor ([@xyflow/react](https://github.com/xyflow/xyflow)) for building and orchestrating agent pipelines with drag & drop - [ ] Visual workflow editor ([@xyflow/react](https://github.com/xyflow/xyflow)) for building and orchestrating agent pipelines with drag & drop
- [ ] Planning mode to organize agent plans before execution - [ ] Planning mode to organize agent plans before execution

View file

@ -231,8 +231,79 @@ function startTask(context, taskId, actor) {
return task; return task;
} }
function notifyUnblockedOwners(context, completedTask) {
const blockedIds = Array.isArray(completedTask.blocks) ? completedTask.blocks : [];
if (blockedIds.length === 0) return;
const completedLabel = `#${completedTask.displayId || completedTask.id}`;
for (const blockedId of blockedIds) {
try {
const blockedTask = taskStore.readTask(context.paths, blockedId, { includeDeleted: true });
if (blockedTask.status === 'deleted' || blockedTask.status === 'completed') continue;
if (!normalizeActorName(blockedTask.owner)) continue;
const allBlockerIds = Array.isArray(blockedTask.blockedBy) ? blockedTask.blockedBy : [];
const pendingBlockers = allBlockerIds.filter((id) => {
if (id === completedTask.id) return false;
try {
const t = taskStore.readTask(context.paths, id, { includeDeleted: true });
return t.status !== 'completed' && t.status !== 'deleted';
} catch { return false; }
});
const allResolved = pendingBlockers.length === 0;
const blockedLabel = `#${blockedTask.displayId || blockedTask.id}`;
let pendingList = '';
if (!allResolved) {
const refs = pendingBlockers.map((id) => {
try {
const t = taskStore.readTask(context.paths, id, { includeDeleted: true });
return `#${t.displayId || t.id}`;
} catch { return `#${id.slice(0, 8)}`; }
});
pendingList = refs.join(', ');
}
const lines = [
`**Dependency resolved** — task ${completedLabel} _${completedTask.subject}_ completed.`,
``,
allResolved
? `All blockers for ${blockedLabel} are resolved — this task is ready to start.`
: `${allBlockerIds.length - pendingBlockers.length} of ${allBlockerIds.length} blockers resolved. Still waiting on: ${pendingList}.`,
];
if (allResolved) {
lines.push(
``,
wrapAgentBlock(
`All dependencies for this task are now resolved.\n` +
`If you are idle, start working on it now:\n` +
`1. Check the full context: task_get { teamName: "${context.teamName}", taskId: "${blockedTask.id}" }\n` +
`2. Start the task: task_start { teamName: "${context.teamName}", taskId: "${blockedTask.id}" }`
)
);
}
addTaskComment(context, blockedTask.id, {
text: lines.join('\n'),
from: 'system',
});
} catch {
// Best-effort per blocked task: skip on failure
}
}
}
function completeTask(context, taskId, actor) { function completeTask(context, taskId, actor) {
return setTaskStatus(context, taskId, 'completed', actor); const task = setTaskStatus(context, taskId, 'completed', actor);
try {
notifyUnblockedOwners(context, task, actor);
} catch {
// Best-effort: task completion succeeded, notification failure is non-fatal
}
return task;
} }
function softDeleteTask(context, taskId, actor) { function softDeleteTask(context, taskId, actor) {
@ -475,7 +546,7 @@ function buildMemberTaskProtocol(teamName) {
e) Do NOT set clarification to "user" yourself only the team lead escalates to the user. e) Do NOT set clarification to "user" yourself only the team lead escalates to the user.
13. DEPENDENCY AWARENESS: 13. DEPENDENCY AWARENESS:
When your task has blockedBy dependencies, check if they are completed before starting. When your task has blockedBy dependencies, check if they are completed before starting.
When you complete a task that blocks others, mention this in your completion message so blocked teammates can proceed. When you complete a task that blocks others, blocked task owners are notified automatically via a task comment.
14. TASK QUEUE DISCIPLINE: 14. TASK QUEUE DISCIPLINE:
- Use task_briefing as a compact queue view of your assigned tasks. - Use task_briefing as a compact queue view of your assigned tasks.
- task_briefing may include full description/comments only for in_progress tasks; needsFix/pending/review/completed entries may be minimal on purpose. - task_briefing may include full description/comments only for in_progress tasks; needsFix/pending/review/completed entries may be minimal on purpose.