feat(FN-3567): sync allowParallelExecution runtime toggle
Fixes FN-3567 by synchronizing the `allowParallelExecution` runtime toggle in the AgentDetailView component, adding one line to keep the setting in sync. Fusion-Task-Id: FN-3567
This commit is contained in:
@@ -237,6 +237,7 @@ The `runtimeConfig` field on agents supports the following options:
|
|||||||
| `autoClaimRelevantTasks` | `boolean` | `true` | During no-task heartbeats, opportunistically claim unowned relevant todo tasks that align with the agent's role/soul |
|
| `autoClaimRelevantTasks` | `boolean` | `true` | During no-task heartbeats, opportunistically claim unowned relevant todo tasks that align with the agent's role/soul |
|
||||||
| `heartbeatTimeoutMs` | `number` | — | Time without heartbeat before agent is considered unresponsive (ms) |
|
| `heartbeatTimeoutMs` | `number` | — | Time without heartbeat before agent is considered unresponsive (ms) |
|
||||||
| `maxConcurrentRuns` | `number` | `1` | Max concurrent heartbeat runs for this agent |
|
| `maxConcurrentRuns` | `number` | `1` | Max concurrent heartbeat runs for this agent |
|
||||||
|
| `allowParallelExecution` | `boolean` | `true` (when unset) | Permanent agents only. When `false`, heartbeat and executor paths serialize symmetrically: a heartbeat will not start while the agent's bound task has an active executor session, and an executor session will not start while the agent has an active heartbeat run |
|
||||||
| `messageResponseMode` | `"immediate" \| "on-heartbeat"` | `"immediate"` | Whether agent wakes immediately on message (immediate) or processes during heartbeat (on-heartbeat). See [Heartbeat Run Mailbox Checking](#heartbeat-run-mailbox-checking) |
|
| `messageResponseMode` | `"immediate" \| "on-heartbeat"` | `"immediate"` | Whether agent wakes immediately on message (immediate) or processes during heartbeat (on-heartbeat). See [Heartbeat Run Mailbox Checking](#heartbeat-run-mailbox-checking) |
|
||||||
| `selfImproveEnabled` | `boolean` | `true` | Enable periodic self-improvement reflection prompts during heartbeat runs |
|
| `selfImproveEnabled` | `boolean` | `true` | Enable periodic self-improvement reflection prompts during heartbeat runs |
|
||||||
| `selfImproveIntervalMs` | `number` | `14400000` (4h) | Minimum delay between self-improvement cycles (minimum enforced: 3600000 ms) |
|
| `selfImproveIntervalMs` | `number` | `14400000` (4h) | Minimum delay between self-improvement cycles (minimum enforced: 3600000 ms) |
|
||||||
@@ -248,6 +249,8 @@ The `runtimeConfig` field on agents supports the following options:
|
|||||||
Heartbeat values are validated and minimum-clamped to 5 minutes (300,000 ms).
|
Heartbeat values are validated and minimum-clamped to 5 minutes (300,000 ms).
|
||||||
Project setting `heartbeatMultiplier` (default `1`) scales resolved heartbeat intervals globally; per-agent `heartbeatIntervalMs` remains the base interval before multiplier scaling. This setting is configured from the **Agents** screen's **Controls** popup under "Heartbeat Speed".
|
Project setting `heartbeatMultiplier` (default `1`) scales resolved heartbeat intervals globally; per-agent `heartbeatIntervalMs` remains the base interval before multiplier scaling. This setting is configured from the **Agents** screen's **Controls** popup under "Heartbeat Speed".
|
||||||
|
|
||||||
|
`allowParallelExecution` defaults to `true` when unset; setting it to `false` is serialized explicitly so operators can enforce non-parallel heartbeat/executor behavior for that permanent agent.
|
||||||
|
|
||||||
### No-task auto-claim behavior
|
### No-task auto-claim behavior
|
||||||
|
|
||||||
When an identity-bearing, non-ephemeral agent wakes with no assigned task and `runtimeConfig.autoClaimRelevantTasks !== false`, the heartbeat monitor scans open todo tasks and may claim one before constructing the prompt run.
|
When an identity-bearing, non-ephemeral agent wakes with no assigned task and `runtimeConfig.autoClaimRelevantTasks !== false`, the heartbeat monitor scans open todo tasks and may claim one before constructing the prompt run.
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ Todo View is an experimental dashboard surface for managing per-project todo lis
|
|||||||
> Available when `experimentalFeatures.todoView` is enabled.
|
> Available when `experimentalFeatures.todoView` is enabled.
|
||||||
|
|
||||||
Navigation:
|
Navigation:
|
||||||
- Desktop: **Header → More views → Todos**
|
- Desktop: **Header → More views → Todos** (single canonical desktop entry)
|
||||||
- Mobile: **More** sheet → **Todos**
|
- Mobile: **More** sheet → **Todos**
|
||||||
|
|
||||||
For full behavior, API contracts, and storage details, use the canonical [Todo View guide](./todo-view.md).
|
For full behavior, API contracts, and storage details, use the canonical [Todo View guide](./todo-view.md).
|
||||||
|
|||||||
@@ -751,6 +751,8 @@ Common heartbeat/runtime keys on `runtimeConfig` include:
|
|||||||
| `heartbeatTimeoutMs` | `number` | Per-agent heartbeat timeout |
|
| `heartbeatTimeoutMs` | `number` | Per-agent heartbeat timeout |
|
||||||
| `maxConcurrentRuns` | `number` | Per-agent concurrent heartbeat limit |
|
| `maxConcurrentRuns` | `number` | Per-agent concurrent heartbeat limit |
|
||||||
| `messageResponseMode` | `"immediate" \| "on-heartbeat"` | Wake on message immediately or process during periodic heartbeat |
|
| `messageResponseMode` | `"immediate" \| "on-heartbeat"` | Wake on message immediately or process during periodic heartbeat |
|
||||||
|
| `runMissedHeartbeatOnStartup` | `boolean` | Fire one startup catch-up heartbeat when the last tick was missed while the server was down (default `false`) |
|
||||||
|
| `allowParallelExecution` | `boolean` | Permanent agents only. Default `true` when unset. Set `false` to serialize heartbeat and executor sessions symmetrically (heartbeat won't start while executor is active, and executor won't start while heartbeat is active); `false` is explicitly persisted while unset/`true` keeps parallel behavior |
|
||||||
| `selfImproveEnabled` | `boolean` | Enables periodic self-improvement prompts |
|
| `selfImproveEnabled` | `boolean` | Enables periodic self-improvement prompts |
|
||||||
| `selfImproveIntervalMs` | `number` | Delay between self-improvement cycles (default 4h, minimum 1h) |
|
| `selfImproveIntervalMs` | `number` | Delay between self-improvement cycles (default 4h, minimum 1h) |
|
||||||
| `lastSelfImproveAt` | `string` | Last self-improvement checkpoint timestamp (managed by heartbeat monitor) |
|
| `lastSelfImproveAt` | `string` | Last self-improvement checkpoint timestamp (managed by heartbeat monitor) |
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Behavior when disabled:
|
|||||||
|
|
||||||
When enabled:
|
When enabled:
|
||||||
|
|
||||||
- Desktop: header overflow menu (**More views**) → **Todos**
|
- Desktop: header overflow menu (**More views**) → **Todos** (the only desktop Todos navigation entry)
|
||||||
- Mobile: **More** sheet in the mobile nav bar → **Todos**
|
- Mobile: **More** sheet in the mobile nav bar → **Todos**
|
||||||
|
|
||||||
## List management
|
## List management
|
||||||
|
|||||||
@@ -3306,6 +3306,7 @@ function ConfigTab({
|
|||||||
setHeartbeatEnabled(deriveHeartbeatEnabled(agent.runtimeConfig));
|
setHeartbeatEnabled(deriveHeartbeatEnabled(agent.runtimeConfig));
|
||||||
setAutoClaimRelevantTasksEnabled(deriveAutoClaimRelevantTasksEnabled(agent.runtimeConfig));
|
setAutoClaimRelevantTasksEnabled(deriveAutoClaimRelevantTasksEnabled(agent.runtimeConfig));
|
||||||
setRunMissedHeartbeatOnStartup(deriveRunMissedHeartbeatOnStartup(agent.runtimeConfig));
|
setRunMissedHeartbeatOnStartup(deriveRunMissedHeartbeatOnStartup(agent.runtimeConfig));
|
||||||
|
setAllowParallelExecution(deriveAllowParallelExecution(agent.runtimeConfig));
|
||||||
setBudgetValues(deriveBudgetValues(agent.runtimeConfig));
|
setBudgetValues(deriveBudgetValues(agent.runtimeConfig));
|
||||||
setModelValue(initialModelValue);
|
setModelValue(initialModelValue);
|
||||||
setSelectedRuntimeId(initialRuntimeHint);
|
setSelectedRuntimeId(initialRuntimeHint);
|
||||||
|
|||||||
@@ -1473,17 +1473,6 @@ export function Header({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isCompact && todosEnabled && onOpenTodos && (
|
|
||||||
<button
|
|
||||||
className={`btn-icon${todosOpen ? " btn-icon--active" : ""}`}
|
|
||||||
onClick={onOpenTodos}
|
|
||||||
title="Open todos"
|
|
||||||
data-testid="todos-toggle-btn"
|
|
||||||
>
|
|
||||||
<CheckSquare size={16} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Git Manager button - desktop only (moved to overflow on mobile/tablet) */}
|
{/* Git Manager button - desktop only (moved to overflow on mobile/tablet) */}
|
||||||
{!isCompact && onOpenGitManager && (
|
{!isCompact && onOpenGitManager && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1687,10 +1687,11 @@ describe("App view switching", () => {
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId("todos-toggle-btn")).toBeInTheDocument();
|
expect(screen.getByTestId("view-toggle-overflow-trigger")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId("todos-toggle-btn"));
|
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||||
|
fireEvent.click(screen.getByTestId("view-overflow-todos"));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId("todo-view")).toBeInTheDocument();
|
expect(screen.getByTestId("todo-view")).toBeInTheDocument();
|
||||||
|
|||||||
@@ -381,21 +381,27 @@ describe("Header", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("todos button", () => {
|
describe("todos navigation", () => {
|
||||||
it("renders todos button on desktop when enabled", () => {
|
it("shows Todos only in More views on desktop when enabled", () => {
|
||||||
renderHeader({ onOpenTodos: vi.fn(), todosEnabled: true }, "desktop");
|
renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: true }, "desktop");
|
||||||
expect(screen.getByTitle("Open todos")).toBeDefined();
|
expect(screen.queryByTestId("todos-toggle-btn")).toBeNull();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||||
|
expect(screen.getAllByText("Todos")).toHaveLength(1);
|
||||||
|
expect(screen.getByTestId("view-overflow-todos")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not render todos button when disabled", () => {
|
it("does not show Todos entry in More views when disabled", () => {
|
||||||
renderHeader({ onOpenTodos: vi.fn(), todosEnabled: false }, "desktop");
|
renderHeader({ onChangeView: noop, onOpenTodos: vi.fn(), todosEnabled: false }, "desktop");
|
||||||
expect(screen.queryByTitle("Open todos")).toBeNull();
|
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||||
|
expect(screen.queryByTestId("view-overflow-todos")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("calls onOpenTodos when clicked", () => {
|
it("calls onOpenTodos from More views", () => {
|
||||||
const onOpenTodos = vi.fn();
|
const onOpenTodos = vi.fn();
|
||||||
renderHeader({ onOpenTodos, todosEnabled: true }, "desktop");
|
renderHeader({ onChangeView: noop, onOpenTodos, todosEnabled: true }, "desktop");
|
||||||
fireEvent.click(screen.getByTitle("Open todos"));
|
fireEvent.click(screen.getByTestId("view-toggle-overflow-trigger"));
|
||||||
|
fireEvent.click(screen.getByTestId("view-overflow-todos"));
|
||||||
expect(onOpenTodos).toHaveBeenCalled();
|
expect(onOpenTodos).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -105,6 +105,22 @@ describe("MobileNavBar", () => {
|
|||||||
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
expect(screen.queryByTestId("mobile-nav-tab-skills")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps Todos in the mobile More sheet when todoView is enabled", () => {
|
||||||
|
const onOpenTodos = vi.fn();
|
||||||
|
render(
|
||||||
|
<MobileNavBar
|
||||||
|
{...createDefaultProps()}
|
||||||
|
onOpenTodos={onOpenTodos}
|
||||||
|
experimentalFeatures={{ todoView: true }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
|
||||||
|
fireEvent.click(screen.getByTestId("mobile-more-item-todos"));
|
||||||
|
|
||||||
|
expect(onOpenTodos).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("renders dependency graph as a top-level tab and keeps additional plugin views in More", () => {
|
it("renders dependency graph as a top-level tab and keeps additional plugin views in More", () => {
|
||||||
const props = createDefaultProps();
|
const props = createDefaultProps();
|
||||||
render(
|
render(
|
||||||
|
|||||||
Reference in New Issue
Block a user