feat(core): add --forever flag and schedule_work tool for autonomous agent mode

- Add schedule_work tool: agent declares pause duration, system auto-resumes
- Add --forever CLI flag with SisyphusModeSettings config
- Forever mode disables MemoryTool, EnterPlanModeTool, interactive shell
- useGeminiStream detects schedule_work calls and runs countdown timer
- Auto-submits resume prompt when timer reaches zero
This commit is contained in:
Sandy Tao
2026-03-09 10:14:53 -07:00
parent 5570b1c046
commit e4cc67b63d
6 changed files with 205 additions and 7 deletions
+43 -6
View File
@@ -32,6 +32,7 @@ import { WebFetchTool } from '../tools/web-fetch.js';
import { MemoryTool, setGeminiMdFilename } from '../tools/memoryTool.js';
import { WebSearchTool } from '../tools/web-search.js';
import { AskUserTool } from '../tools/ask-user.js';
import { ScheduleWorkTool } from '../tools/schedule-work.js';
import { ExitPlanModeTool } from '../tools/exit-plan-mode.js';
import { EnterPlanModeTool } from '../tools/enter-plan-mode.js';
import { GeminiClient } from '../core/client.js';
@@ -242,6 +243,13 @@ export interface AgentSettings {
browser?: BrowserAgentCustomConfig;
}
export interface SisyphusModeSettings {
enabled: boolean;
idleTimeout?: number;
prompt?: string;
a2aPort?: number;
}
export interface CustomTheme {
type: 'custom';
name: string;
@@ -589,6 +597,8 @@ export interface ConfigParameters {
mcpEnabled?: boolean;
extensionsEnabled?: boolean;
agents?: AgentSettings;
sisyphusMode?: SisyphusModeSettings;
isForeverMode?: boolean;
onReload?: () => Promise<{
disabledSkills?: string[];
adminSkillsEnabled?: boolean;
@@ -788,6 +798,8 @@ export class Config implements McpContext, AgentLoopContext {
private readonly enableAgents: boolean;
private agents: AgentSettings;
private readonly isForeverMode: boolean;
private readonly sisyphusMode: SisyphusModeSettings;
private readonly enableEventDrivenScheduler: boolean;
private readonly skillsSupport: boolean;
private disabledSkills: string[];
@@ -885,6 +897,13 @@ export class Config implements McpContext, AgentLoopContext {
this._activeModel = params.model;
this.enableAgents = params.enableAgents ?? false;
this.agents = params.agents ?? {};
this.isForeverMode = params.isForeverMode ?? false;
this.sisyphusMode = {
enabled: params.sisyphusMode?.enabled ?? false,
idleTimeout: params.sisyphusMode?.idleTimeout,
prompt: params.sisyphusMode?.prompt,
a2aPort: params.sisyphusMode?.a2aPort,
};
this.disableLLMCorrection = params.disableLLMCorrection ?? true;
this.planEnabled = params.plan ?? true;
this.trackerEnabled = params.tracker ?? false;
@@ -2473,6 +2492,14 @@ export class Config implements McpContext, AgentLoopContext {
return remoteThreshold;
}
getIsForeverMode(): boolean {
return this.isForeverMode;
}
getSisyphusMode(): SisyphusModeSettings {
return this.sisyphusMode;
}
async getUserCaching(): Promise<boolean | undefined> {
await this.ensureExperimentsLoaded();
@@ -2558,6 +2585,7 @@ export class Config implements McpContext, AgentLoopContext {
}
isInteractiveShellEnabled(): boolean {
if (this.isForeverMode) return false;
return (
this.interactive &&
this.ptyInfo !== 'child_process' &&
@@ -2859,15 +2887,22 @@ export class Config implements McpContext, AgentLoopContext {
maybeRegister(ShellTool, () =>
registry.registerTool(new ShellTool(this, this._messageBus)),
);
maybeRegister(MemoryTool, () =>
registry.registerTool(new MemoryTool(this._messageBus)),
);
if (!this.isForeverMode) {
maybeRegister(MemoryTool, () =>
registry.registerTool(new MemoryTool(this._messageBus)),
);
}
maybeRegister(WebSearchTool, () =>
registry.registerTool(new WebSearchTool(this, this._messageBus)),
);
maybeRegister(AskUserTool, () =>
registry.registerTool(new AskUserTool(this._messageBus)),
);
if (this.isForeverMode) {
maybeRegister(ScheduleWorkTool, () =>
registry.registerTool(new ScheduleWorkTool(this._messageBus)),
);
}
if (this.getUseWriteTodos()) {
maybeRegister(WriteTodosTool, () =>
registry.registerTool(new WriteTodosTool(this._messageBus)),
@@ -2877,9 +2912,11 @@ export class Config implements McpContext, AgentLoopContext {
maybeRegister(ExitPlanModeTool, () =>
registry.registerTool(new ExitPlanModeTool(this, this._messageBus)),
);
maybeRegister(EnterPlanModeTool, () =>
registry.registerTool(new EnterPlanModeTool(this, this._messageBus)),
);
if (!this.isForeverMode) {
maybeRegister(EnterPlanModeTool, () =>
registry.registerTool(new EnterPlanModeTool(this, this._messageBus)),
);
}
}
if (this.isTrackerEnabled()) {
+82
View File
@@ -0,0 +1,82 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
BaseDeclarativeTool,
BaseToolInvocation,
type ToolResult,
Kind,
} from './tools.js';
import type { MessageBus } from '../confirmation-bus/message-bus.js';
import { SCHEDULE_WORK_TOOL_NAME } from './tool-names.js';
export interface ScheduleWorkParams {
inMinutes: number;
}
export class ScheduleWorkTool extends BaseDeclarativeTool<
ScheduleWorkParams,
ToolResult
> {
constructor(messageBus: MessageBus) {
super(
SCHEDULE_WORK_TOOL_NAME,
'Schedule Work',
'Schedule work to resume automatically after a break. Use this to wait for long-running processes or to pause your execution. The system will automatically wake you up.',
Kind.Communicate,
{
type: 'object',
required: ['inMinutes'],
properties: {
inMinutes: {
type: 'number',
description: 'Minutes to wait before automatically resuming work.',
},
},
},
messageBus,
);
}
protected override validateToolParamValues(
params: ScheduleWorkParams,
): string | null {
if (params.inMinutes <= 0) {
return 'inMinutes must be greater than 0.';
}
return null;
}
protected createInvocation(
params: ScheduleWorkParams,
messageBus: MessageBus,
toolName: string,
toolDisplayName: string,
): ScheduleWorkInvocation {
return new ScheduleWorkInvocation(
params,
messageBus,
toolName,
toolDisplayName,
);
}
}
export class ScheduleWorkInvocation extends BaseToolInvocation<
ScheduleWorkParams,
ToolResult
> {
getDescription(): string {
return `Scheduling work to resume in ${this.params.inMinutes} minutes.`;
}
async execute(_signal: AbortSignal): Promise<ToolResult> {
return {
llmContent: `Work scheduled. The system will wake you up in ${this.params.inMinutes} minutes. DO NOT make any further tool calls. Instead, provide a brief text summary of the work completed so far to end your turn.`,
returnDisplay: `Scheduled work to resume in ${this.params.inMinutes} minutes.`,
};
}
}
+2
View File
@@ -151,6 +151,7 @@ export {
};
export const LS_TOOL_NAME_LEGACY = 'list_directory'; // Just to be safe if anything used the old exported name directly
export const SCHEDULE_WORK_TOOL_NAME = 'schedule_work';
export const EDIT_TOOL_NAMES = new Set([EDIT_TOOL_NAME, WRITE_FILE_TOOL_NAME]);
@@ -228,6 +229,7 @@ export const ALL_BUILTIN_TOOL_NAMES = [
GET_INTERNAL_DOCS_TOOL_NAME,
ENTER_PLAN_MODE_TOOL_NAME,
EXIT_PLAN_MODE_TOOL_NAME,
SCHEDULE_WORK_TOOL_NAME,
] as const;
/**