mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 08:03:39 +00:00
feat(coding-agent): complete steer()/followUp() migration
- Update settings-manager with steeringMode/followUpMode (migrates old queueMode) - Update sdk.ts to use new mode options - Update settings-selector UI to show both modes - Add Alt+Enter keybind for follow-up messages - Update RPC API: steer/follow_up commands, set_steering_mode/set_follow_up_mode - Update rpc-client with new methods - Delete dead code: queue-mode-selector.ts - Update tests for new API - Update mom/context.ts stubs - Update web-ui example
This commit is contained in:
parent
58c423ba36
commit
3ae02a6849
12 changed files with 173 additions and 106 deletions
|
|
@ -305,7 +305,8 @@ export function loadSettings(cwd?: string, agentDir?: string): Settings {
|
|||
defaultProvider: manager.getDefaultProvider(),
|
||||
defaultModel: manager.getDefaultModel(),
|
||||
defaultThinkingLevel: manager.getDefaultThinkingLevel(),
|
||||
queueMode: manager.getQueueMode(),
|
||||
steeringMode: manager.getSteeringMode(),
|
||||
followUpMode: manager.getFollowUpMode(),
|
||||
theme: manager.getTheme(),
|
||||
compaction: manager.getCompactionSettings(),
|
||||
retry: manager.getRetrySettings(),
|
||||
|
|
@ -626,7 +627,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
return hookRunner.emitContext(messages);
|
||||
}
|
||||
: undefined,
|
||||
queueMode: settingsManager.getQueueMode(),
|
||||
steeringMode: settingsManager.getSteeringMode(),
|
||||
followUpMode: settingsManager.getFollowUpMode(),
|
||||
getApiKey: async () => {
|
||||
const currentModel = agent.state.model;
|
||||
if (!currentModel) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ export interface Settings {
|
|||
defaultProvider?: string;
|
||||
defaultModel?: string;
|
||||
defaultThinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
queueMode?: "all" | "one-at-a-time";
|
||||
steeringMode?: "all" | "one-at-a-time";
|
||||
followUpMode?: "all" | "one-at-a-time";
|
||||
theme?: string;
|
||||
compaction?: CompactionSettings;
|
||||
branchSummary?: BranchSummarySettings;
|
||||
|
|
@ -125,13 +126,24 @@ export class SettingsManager {
|
|||
}
|
||||
try {
|
||||
const content = readFileSync(path, "utf-8");
|
||||
return JSON.parse(content);
|
||||
const settings = JSON.parse(content);
|
||||
return SettingsManager.migrateSettings(settings);
|
||||
} catch (error) {
|
||||
console.error(`Warning: Could not read settings file ${path}: ${error}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/** Migrate old settings format to new format */
|
||||
private static migrateSettings(settings: Record<string, unknown>): Settings {
|
||||
// Migrate queueMode -> steeringMode
|
||||
if ("queueMode" in settings && !("steeringMode" in settings)) {
|
||||
settings.steeringMode = settings.queueMode;
|
||||
delete settings.queueMode;
|
||||
}
|
||||
return settings as Settings;
|
||||
}
|
||||
|
||||
private loadProjectSettings(): Settings {
|
||||
if (!this.projectSettingsPath || !existsSync(this.projectSettingsPath)) {
|
||||
return {};
|
||||
|
|
@ -139,7 +151,8 @@ export class SettingsManager {
|
|||
|
||||
try {
|
||||
const content = readFileSync(this.projectSettingsPath, "utf-8");
|
||||
return JSON.parse(content);
|
||||
const settings = JSON.parse(content);
|
||||
return SettingsManager.migrateSettings(settings);
|
||||
} catch (error) {
|
||||
console.error(`Warning: Could not read project settings file: ${error}`);
|
||||
return {};
|
||||
|
|
@ -204,12 +217,21 @@ export class SettingsManager {
|
|||
this.save();
|
||||
}
|
||||
|
||||
getQueueMode(): "all" | "one-at-a-time" {
|
||||
return this.settings.queueMode || "one-at-a-time";
|
||||
getSteeringMode(): "all" | "one-at-a-time" {
|
||||
return this.settings.steeringMode || "one-at-a-time";
|
||||
}
|
||||
|
||||
setQueueMode(mode: "all" | "one-at-a-time"): void {
|
||||
this.globalSettings.queueMode = mode;
|
||||
setSteeringMode(mode: "all" | "one-at-a-time"): void {
|
||||
this.globalSettings.steeringMode = mode;
|
||||
this.save();
|
||||
}
|
||||
|
||||
getFollowUpMode(): "all" | "one-at-a-time" {
|
||||
return this.settings.followUpMode || "one-at-a-time";
|
||||
}
|
||||
|
||||
setFollowUpMode(mode: "all" | "one-at-a-time"): void {
|
||||
this.globalSettings.followUpMode = mode;
|
||||
this.save();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
Editor,
|
||||
isAltEnter,
|
||||
isCtrlC,
|
||||
isCtrlD,
|
||||
isCtrlG,
|
||||
|
|
@ -28,8 +29,14 @@ export class CustomEditor extends Editor {
|
|||
public onCtrlT?: () => void;
|
||||
public onCtrlG?: () => void;
|
||||
public onCtrlZ?: () => void;
|
||||
public onAltEnter?: () => void;
|
||||
|
||||
handleInput(data: string): void {
|
||||
// Intercept Alt+Enter for follow-up messages
|
||||
if (isAltEnter(data) && this.onAltEnter) {
|
||||
this.onAltEnter();
|
||||
return;
|
||||
}
|
||||
// Intercept Ctrl+G for external editor
|
||||
if (isCtrlG(data) && this.onCtrlG) {
|
||||
this.onCtrlG();
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
import { Container, type SelectItem, SelectList } from "@mariozechner/pi-tui";
|
||||
import { getSelectListTheme } from "../theme/theme.js";
|
||||
import { DynamicBorder } from "./dynamic-border.js";
|
||||
|
||||
/**
|
||||
* Component that renders a queue mode selector with borders
|
||||
*/
|
||||
export class QueueModeSelectorComponent extends Container {
|
||||
private selectList: SelectList;
|
||||
|
||||
constructor(
|
||||
currentMode: "all" | "one-at-a-time",
|
||||
onSelect: (mode: "all" | "one-at-a-time") => void,
|
||||
onCancel: () => void,
|
||||
) {
|
||||
super();
|
||||
|
||||
const queueModes: SelectItem[] = [
|
||||
{
|
||||
value: "one-at-a-time",
|
||||
label: "one-at-a-time",
|
||||
description: "Process queued messages one by one (recommended)",
|
||||
},
|
||||
{ value: "all", label: "all", description: "Process all queued messages at once" },
|
||||
];
|
||||
|
||||
// Add top border
|
||||
this.addChild(new DynamicBorder());
|
||||
|
||||
// Create selector
|
||||
this.selectList = new SelectList(queueModes, 2, getSelectListTheme());
|
||||
|
||||
// Preselect current mode
|
||||
const currentIndex = queueModes.findIndex((item) => item.value === currentMode);
|
||||
if (currentIndex !== -1) {
|
||||
this.selectList.setSelectedIndex(currentIndex);
|
||||
}
|
||||
|
||||
this.selectList.onSelect = (item) => {
|
||||
onSelect(item.value as "all" | "one-at-a-time");
|
||||
};
|
||||
|
||||
this.selectList.onCancel = () => {
|
||||
onCancel();
|
||||
};
|
||||
|
||||
this.addChild(this.selectList);
|
||||
|
||||
// Add bottom border
|
||||
this.addChild(new DynamicBorder());
|
||||
}
|
||||
|
||||
getSelectList(): SelectList {
|
||||
return this.selectList;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,8 @@ const THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {
|
|||
export interface SettingsConfig {
|
||||
autoCompact: boolean;
|
||||
showImages: boolean;
|
||||
queueMode: "all" | "one-at-a-time";
|
||||
steeringMode: "all" | "one-at-a-time";
|
||||
followUpMode: "all" | "one-at-a-time";
|
||||
thinkingLevel: ThinkingLevel;
|
||||
availableThinkingLevels: ThinkingLevel[];
|
||||
currentTheme: string;
|
||||
|
|
@ -36,7 +37,8 @@ export interface SettingsConfig {
|
|||
export interface SettingsCallbacks {
|
||||
onAutoCompactChange: (enabled: boolean) => void;
|
||||
onShowImagesChange: (enabled: boolean) => void;
|
||||
onQueueModeChange: (mode: "all" | "one-at-a-time") => void;
|
||||
onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
|
||||
onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
|
||||
onThinkingLevelChange: (level: ThinkingLevel) => void;
|
||||
onThemeChange: (theme: string) => void;
|
||||
onThemePreview?: (theme: string) => void;
|
||||
|
|
@ -127,10 +129,17 @@ export class SettingsSelectorComponent extends Container {
|
|||
values: ["true", "false"],
|
||||
},
|
||||
{
|
||||
id: "queue-mode",
|
||||
label: "Queue mode",
|
||||
description: "How to process queued messages while agent is working",
|
||||
currentValue: config.queueMode,
|
||||
id: "steering-mode",
|
||||
label: "Steering mode",
|
||||
description: "How to deliver steering messages (Enter while streaming)",
|
||||
currentValue: config.steeringMode,
|
||||
values: ["one-at-a-time", "all"],
|
||||
},
|
||||
{
|
||||
id: "follow-up-mode",
|
||||
label: "Follow-up mode",
|
||||
description: "How to deliver follow-up messages (queued until agent finishes)",
|
||||
currentValue: config.followUpMode,
|
||||
values: ["one-at-a-time", "all"],
|
||||
},
|
||||
{
|
||||
|
|
@ -227,8 +236,11 @@ export class SettingsSelectorComponent extends Container {
|
|||
case "show-images":
|
||||
callbacks.onShowImagesChange(newValue === "true");
|
||||
break;
|
||||
case "queue-mode":
|
||||
callbacks.onQueueModeChange(newValue as "all" | "one-at-a-time");
|
||||
case "steering-mode":
|
||||
callbacks.onSteeringModeChange(newValue as "all" | "one-at-a-time");
|
||||
break;
|
||||
case "follow-up-mode":
|
||||
callbacks.onFollowUpModeChange(newValue as "all" | "one-at-a-time");
|
||||
break;
|
||||
case "hide-thinking":
|
||||
callbacks.onHideThinkingBlockChange(newValue === "true");
|
||||
|
|
|
|||
|
|
@ -262,6 +262,9 @@ export class InteractiveMode {
|
|||
theme.fg("dim", "!") +
|
||||
theme.fg("muted", " to run bash") +
|
||||
"\n" +
|
||||
theme.fg("dim", "alt+enter") +
|
||||
theme.fg("muted", " to queue follow-up") +
|
||||
"\n" +
|
||||
theme.fg("dim", "drop files") +
|
||||
theme.fg("muted", " to attach");
|
||||
const header = new Text(`${logo}\n${instructions}`, 1, 0);
|
||||
|
|
@ -776,6 +779,7 @@ export class InteractiveMode {
|
|||
this.editor.onCtrlO = () => this.toggleToolOutputExpansion();
|
||||
this.editor.onCtrlT = () => this.toggleThinkingBlockVisibility();
|
||||
this.editor.onCtrlG = () => this.openExternalEditor();
|
||||
this.editor.onAltEnter = () => this.handleAltEnter();
|
||||
|
||||
this.editor.onChange = (text: string) => {
|
||||
const wasBashMode = this.isBashMode;
|
||||
|
|
@ -920,9 +924,9 @@ export class InteractiveMode {
|
|||
}
|
||||
}
|
||||
|
||||
// Queue regular messages if agent is streaming
|
||||
// Queue steering message if agent is streaming (interrupts current work)
|
||||
if (this.session.isStreaming) {
|
||||
await this.session.queueMessage(text);
|
||||
await this.session.steer(text);
|
||||
this.updatePendingMessagesDisplay();
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.setText("");
|
||||
|
|
@ -1447,6 +1451,24 @@ export class InteractiveMode {
|
|||
process.kill(0, "SIGTSTP");
|
||||
}
|
||||
|
||||
private async handleAltEnter(): Promise<void> {
|
||||
const text = this.editor.getText().trim();
|
||||
if (!text) return;
|
||||
|
||||
// Alt+Enter queues a follow-up message (waits until agent finishes)
|
||||
if (this.session.isStreaming) {
|
||||
await this.session.followUp(text);
|
||||
this.updatePendingMessagesDisplay();
|
||||
this.editor.addToHistory(text);
|
||||
this.editor.setText("");
|
||||
this.ui.requestRender();
|
||||
}
|
||||
// If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
|
||||
else if (this.editor.onSubmit) {
|
||||
this.editor.onSubmit(text);
|
||||
}
|
||||
}
|
||||
|
||||
private updateEditorBorderColor(): void {
|
||||
if (this.isBashMode) {
|
||||
this.editor.borderColor = theme.getBashModeBorderColor();
|
||||
|
|
@ -1651,7 +1673,8 @@ export class InteractiveMode {
|
|||
{
|
||||
autoCompact: this.session.autoCompactionEnabled,
|
||||
showImages: this.settingsManager.getShowImages(),
|
||||
queueMode: this.session.queueMode,
|
||||
steeringMode: this.session.steeringMode,
|
||||
followUpMode: this.session.followUpMode,
|
||||
thinkingLevel: this.session.thinkingLevel,
|
||||
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
|
||||
currentTheme: this.settingsManager.getTheme() || "dark",
|
||||
|
|
@ -1672,8 +1695,11 @@ export class InteractiveMode {
|
|||
}
|
||||
}
|
||||
},
|
||||
onQueueModeChange: (mode) => {
|
||||
this.session.setQueueMode(mode);
|
||||
onSteeringModeChange: (mode) => {
|
||||
this.session.setSteeringMode(mode);
|
||||
},
|
||||
onFollowUpModeChange: (mode) => {
|
||||
this.session.setFollowUpMode(mode);
|
||||
},
|
||||
onThinkingLevelChange: (level) => {
|
||||
this.session.setThinkingLevel(level);
|
||||
|
|
|
|||
|
|
@ -173,10 +173,17 @@ export class RpcClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Queue a message while agent is streaming.
|
||||
* Queue a steering message to interrupt the agent mid-run.
|
||||
*/
|
||||
async queueMessage(message: string): Promise<void> {
|
||||
await this.send({ type: "queue_message", message });
|
||||
async steer(message: string): Promise<void> {
|
||||
await this.send({ type: "steer", message });
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a follow-up message to be processed after the agent finishes.
|
||||
*/
|
||||
async followUp(message: string): Promise<void> {
|
||||
await this.send({ type: "follow_up", message });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -248,10 +255,17 @@ export class RpcClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set queue mode.
|
||||
* Set steering mode.
|
||||
*/
|
||||
async setQueueMode(mode: "all" | "one-at-a-time"): Promise<void> {
|
||||
await this.send({ type: "set_queue_mode", mode });
|
||||
async setSteeringMode(mode: "all" | "one-at-a-time"): Promise<void> {
|
||||
await this.send({ type: "set_steering_mode", mode });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set follow-up mode.
|
||||
*/
|
||||
async setFollowUpMode(mode: "all" | "one-at-a-time"): Promise<void> {
|
||||
await this.send({ type: "set_follow_up_mode", mode });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -253,9 +253,14 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
return success(id, "prompt");
|
||||
}
|
||||
|
||||
case "queue_message": {
|
||||
await session.queueMessage(command.message);
|
||||
return success(id, "queue_message");
|
||||
case "steer": {
|
||||
await session.steer(command.message);
|
||||
return success(id, "steer");
|
||||
}
|
||||
|
||||
case "follow_up": {
|
||||
await session.followUp(command.message);
|
||||
return success(id, "follow_up");
|
||||
}
|
||||
|
||||
case "abort": {
|
||||
|
|
@ -279,7 +284,8 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
thinkingLevel: session.thinkingLevel,
|
||||
isStreaming: session.isStreaming,
|
||||
isCompacting: session.isCompacting,
|
||||
queueMode: session.queueMode,
|
||||
steeringMode: session.steeringMode,
|
||||
followUpMode: session.followUpMode,
|
||||
sessionFile: session.sessionFile,
|
||||
sessionId: session.sessionId,
|
||||
autoCompactionEnabled: session.autoCompactionEnabled,
|
||||
|
|
@ -334,12 +340,17 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|||
}
|
||||
|
||||
// =================================================================
|
||||
// Queue Mode
|
||||
// Queue Modes
|
||||
// =================================================================
|
||||
|
||||
case "set_queue_mode": {
|
||||
session.setQueueMode(command.mode);
|
||||
return success(id, "set_queue_mode");
|
||||
case "set_steering_mode": {
|
||||
session.setSteeringMode(command.mode);
|
||||
return success(id, "set_steering_mode");
|
||||
}
|
||||
|
||||
case "set_follow_up_mode": {
|
||||
session.setFollowUpMode(command.mode);
|
||||
return success(id, "set_follow_up_mode");
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ import type { CompactionResult } from "../../core/compaction/index.js";
|
|||
export type RpcCommand =
|
||||
// Prompting
|
||||
| { id?: string; type: "prompt"; message: string; images?: ImageContent[] }
|
||||
| { id?: string; type: "queue_message"; message: string }
|
||||
| { id?: string; type: "steer"; message: string }
|
||||
| { id?: string; type: "follow_up"; message: string }
|
||||
| { id?: string; type: "abort" }
|
||||
| { id?: string; type: "new_session"; parentSession?: string }
|
||||
|
||||
|
|
@ -34,8 +35,9 @@ export type RpcCommand =
|
|||
| { id?: string; type: "set_thinking_level"; level: ThinkingLevel }
|
||||
| { id?: string; type: "cycle_thinking_level" }
|
||||
|
||||
// Queue mode
|
||||
| { id?: string; type: "set_queue_mode"; mode: "all" | "one-at-a-time" }
|
||||
// Queue modes
|
||||
| { id?: string; type: "set_steering_mode"; mode: "all" | "one-at-a-time" }
|
||||
| { id?: string; type: "set_follow_up_mode"; mode: "all" | "one-at-a-time" }
|
||||
|
||||
// Compaction
|
||||
| { id?: string; type: "compact"; customInstructions?: string }
|
||||
|
|
@ -69,7 +71,8 @@ export interface RpcSessionState {
|
|||
thinkingLevel: ThinkingLevel;
|
||||
isStreaming: boolean;
|
||||
isCompacting: boolean;
|
||||
queueMode: "all" | "one-at-a-time";
|
||||
steeringMode: "all" | "one-at-a-time";
|
||||
followUpMode: "all" | "one-at-a-time";
|
||||
sessionFile?: string;
|
||||
sessionId: string;
|
||||
autoCompactionEnabled: boolean;
|
||||
|
|
@ -85,7 +88,8 @@ export interface RpcSessionState {
|
|||
export type RpcResponse =
|
||||
// Prompting (async - events follow)
|
||||
| { id?: string; type: "response"; command: "prompt"; success: true }
|
||||
| { id?: string; type: "response"; command: "queue_message"; success: true }
|
||||
| { id?: string; type: "response"; command: "steer"; success: true }
|
||||
| { id?: string; type: "response"; command: "follow_up"; success: true }
|
||||
| { id?: string; type: "response"; command: "abort"; success: true }
|
||||
| { id?: string; type: "response"; command: "new_session"; success: true; data: { cancelled: boolean } }
|
||||
|
||||
|
|
@ -125,8 +129,9 @@ export type RpcResponse =
|
|||
data: { level: ThinkingLevel } | null;
|
||||
}
|
||||
|
||||
// Queue mode
|
||||
| { id?: string; type: "response"; command: "set_queue_mode"; success: true }
|
||||
// Queue modes
|
||||
| { id?: string; type: "response"; command: "set_steering_mode"; success: true }
|
||||
| { id?: string; type: "response"; command: "set_follow_up_mode"; success: true }
|
||||
|
||||
// Compaction
|
||||
| { id?: string; type: "response"; command: "compact"; success: true; data: CompactionResult }
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||
|
||||
// Second prompt should reject
|
||||
await expect(session.prompt("Second message")).rejects.toThrow(
|
||||
"Agent is already processing. Use queueMessage() to queue messages during streaming.",
|
||||
"Agent is already processing. Use steer() or followUp() to queue messages during streaming.",
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
|
|
@ -135,15 +135,31 @@ describe("AgentSession concurrent prompt guard", () => {
|
|||
await firstPrompt.catch(() => {}); // Ignore abort error
|
||||
});
|
||||
|
||||
it("should allow queueMessage() while streaming", async () => {
|
||||
it("should allow steer() while streaming", async () => {
|
||||
createSession();
|
||||
|
||||
// Start first prompt
|
||||
const firstPrompt = session.prompt("First message");
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// queueMessage should work while streaming
|
||||
expect(() => session.queueMessage("Queued message")).not.toThrow();
|
||||
// steer should work while streaming
|
||||
expect(() => session.steer("Steering message")).not.toThrow();
|
||||
expect(session.pendingMessageCount).toBe(1);
|
||||
|
||||
// Cleanup
|
||||
await session.abort();
|
||||
await firstPrompt.catch(() => {});
|
||||
});
|
||||
|
||||
it("should allow followUp() while streaming", async () => {
|
||||
createSession();
|
||||
|
||||
// Start first prompt
|
||||
const firstPrompt = session.prompt("First message");
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
// followUp should work while streaming
|
||||
expect(() => session.followUp("Follow-up message")).not.toThrow();
|
||||
expect(session.pendingMessageCount).toBe(1);
|
||||
|
||||
// Cleanup
|
||||
|
|
|
|||
|
|
@ -495,11 +495,19 @@ export class MomSettingsManager {
|
|||
}
|
||||
|
||||
// Compatibility methods for AgentSession
|
||||
getQueueMode(): "all" | "one-at-a-time" {
|
||||
getSteeringMode(): "all" | "one-at-a-time" {
|
||||
return "one-at-a-time"; // Mom processes one message at a time
|
||||
}
|
||||
|
||||
setQueueMode(_mode: "all" | "one-at-a-time"): void {
|
||||
setSteeringMode(_mode: "all" | "one-at-a-time"): void {
|
||||
// No-op for mom
|
||||
}
|
||||
|
||||
getFollowUpMode(): "all" | "one-at-a-time" {
|
||||
return "one-at-a-time"; // Mom processes one message at a time
|
||||
}
|
||||
|
||||
setFollowUpMode(_mode: "all" | "one-at-a-time"): void {
|
||||
// No-op for mom
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ const renderApp = () => {
|
|||
onClick: () => {
|
||||
// Demo: Inject custom message (will appear on next agent run)
|
||||
if (agent) {
|
||||
agent.queueMessage(
|
||||
agent.steer(
|
||||
createSystemNotification(
|
||||
"This is a custom message! It appears in the UI but is never sent to the LLM.",
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue