дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
feat(agents): show the agent's background tasks above the composer (#1860)
* fix(agent): don't release an idle native agent while background tasks are running A turn's background tasks (Claude Code's run_in_background commands) outlive the turn: release_when_idle only checked for a running turn and open permission requests, so five minutes after the turn ended it stopped the agent process and orphaned whatever still ran, its result never reaching the conversation. Claude Code reports its live background tasks in stream-json mode: a system/background_tasks_changed event carries the full list on every change. The driver now translates it into an openidea_background_tasks session update (like openidea_retry), the task keeps the list, and the idle release waits while it isn't empty: it checks again after the usual delay, and when the last task is done its report voids the pending schedule and starts the delay over from then. A (re)started process starts with no tasks, so a stale list can never hold the agent up. Signed-off-by: Inf1mum <[email protected]> * feat(agents): show the agent's background tasks above the composer A grey bar above the composer lists what the agent runs in the background (shells, monitors), with how long each has been running and a Stop button per task. The task now carries the background tasks the agent reports, with when each was first seen, and clears them when the process ends. Stop asks Claude Code to end that one task through a new _openidea/stop_task request; the composer's Stop keeps ending only the turn (perTaskStopAffordance). Closes #1788 --------- Signed-off-by: Inf1mum <[email protected]> Co-authored-by: Inf1mum <[email protected]> Co-authored-by: NealimeKenna <[email protected]>
Цей коміт міститься в:
16 files changed
+386
-6
No files matched your search
@@ -566,6 +566,31 @@
|
||||
padding: 6px 12px 12px;
|
||||
}
|
||||
|
||||
.background {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.backgroundTask {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-height: 24px;
|
||||
padding: 0 4px 0 8px;
|
||||
border-radius: var(--radius-row);
|
||||
background: var(--bg-elev);
|
||||
color: var(--fg-muted);
|
||||
font-size: var(--fs-small);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.backgroundText {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.queue {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
selectableText,
|
||||
useSelectionBoundary,
|
||||
showContextMenu,
|
||||
type IconName,
|
||||
} from "@/ui";
|
||||
import { useTx, tx } from "@/platform/i18n";
|
||||
import { isImeKey } from "@/platform/ime";
|
||||
@@ -59,6 +60,7 @@ import {
|
||||
} from "./model";
|
||||
import {
|
||||
cancelTask,
|
||||
stopBackgroundTask,
|
||||
loadEntries,
|
||||
markVisible,
|
||||
refreshChanges,
|
||||
@@ -1318,6 +1320,7 @@ function SessionComposer({
|
||||
</div>
|
||||
)}
|
||||
<WorkingSubagents entries={entries} onOpen={onOpenSubagent} />
|
||||
<BackgroundTasks task={task} />
|
||||
{task.queue.length > 0 && <Queue task={task} />}
|
||||
<Composer
|
||||
id={task.id}
|
||||
@@ -1349,6 +1352,38 @@ function SessionComposer({
|
||||
}
|
||||
|
||||
/** Messages waiting for the turn to end: send now, edit, or drop. */
|
||||
const BACKGROUND_ICON: Record<string, IconName> = { local_bash: "terminal", monitor: "eye" };
|
||||
|
||||
/** What the agent runs in the background; its subagents show as chips of their own. */
|
||||
function BackgroundTasks({ task }: { task: AgentTask }) {
|
||||
const tx = useTx();
|
||||
const running = (task.background ?? []).filter((background) => background.kind !== "local_agent");
|
||||
if (!running.length) return null;
|
||||
const fail = (e: unknown) =>
|
||||
notifications().notify({ level: "error", title: "Couldn't stop the background task", body: String(e) });
|
||||
return (
|
||||
<div className={styles.background} aria-label={tx("Running in the background")}>
|
||||
{running.map((background) => (
|
||||
<div key={background.id} className={styles.backgroundTask}>
|
||||
<Icon name={BACKGROUND_ICON[background.kind] ?? "clock"} size={12} className={styles.muted} />
|
||||
<span className={styles.backgroundText} title={background.description}>
|
||||
{background.description || background.kind}
|
||||
</span>
|
||||
{background.startedMs != null && <Elapsed since={background.startedMs} />}
|
||||
{task.capabilities?.backgroundTasks && (
|
||||
<IconButton
|
||||
icon="stop"
|
||||
title="Stop"
|
||||
size="small"
|
||||
onClick={() => void stopBackgroundTask(task.id, background.id).catch(fail)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Queue({ task }: { task: AgentTask }) {
|
||||
const fail = (e: unknown) =>
|
||||
notifications().notify({ level: "error", title: "Couldn't update the queue", body: String(e) });
|
||||
|
||||
@@ -625,6 +625,8 @@ export function addReferences(refs: Array<{ path: string; rel: string; mention:
|
||||
|
||||
/** Stops the task's turn, or with `subagent` only that subagent's work. */
|
||||
export const cancelTask = (task: string, subagent: string | null = null) => call(commands.agentCancel(task, subagent));
|
||||
export const stopBackgroundTask = (task: string, background: string) =>
|
||||
call(commands.agentStopBackground(task, background));
|
||||
export const respond = (task: string, permission: number, option: string | null) =>
|
||||
call(commands.agentRespond(task, permission, option));
|
||||
export const unarchiveTask = (task: string) => call(commands.agentUnarchive(task));
|
||||
|
||||
Reference in new issue
Заблокувати користувача