Feedback Package
Provides tools for human-in-the-loop interactions in AI workflows, allowing agents to pause execution, present information to users, and collect feedback through chat-based questioning and browser-based reviews.
Package Reference
Package Name: @tokenring-ai/feedback
Version: 0.2.0
License: MIT
Overview
The feedback package enables AI agents to interact with humans during execution through two primary mechanisms:
- Interactive Chat Questioning: Ask multiple questions via chat with support for text responses or multiple-choice options using structured form inputs
- Browser-Based Content Review: Display file contents or React components in browser UIs for approval/rejection with optional comments
This package is designed for task uncertainty resolution, approval workflows, and interactive content review scenarios where human input is needed before proceeding.
Installation
bun add @tokenring-ai/feedback
Package Structure
pkg/feedback/
├── index.ts # Main entry point (currently empty)
├── plugin.ts # Plugin registration
├── tools.ts # Tool exports aggregator
└── tools/ # Core tool implementations
├── askQuestions.ts # Interactive questioning tool
├── getFileFeedback.ts # File content review tool
└── react-feedback.ts # React component preview tool
Core Tools
1. askQuestions
Interactive questioning tool that presents form-based questions to users with optional multiple-choice options and freeform text responses.
Tool Name: ask_questions
Tool Display: Feedback/askQuestions
Description: Use this tool when human feedback is needed or when you're uncertain about the proper path to complete a task. It provides a strong guarantee that decisions align with user intent.
Input Schema (Zod):
{
message: string, // Description of the problem or uncertainty
questions: Array<{
question: string, // Question to ask the user
choices: string[] // Suggested choices (optional, can be empty for freeform)
}>
}
Schema Definition:
askQuestions.execute({
message: string,
questions: z.array(z.object({
question: z.string(),
choices: z.array(z.string()).optional()
}))
}, agent)
Input Parameters:
- message (string): Required - Free-form paragraph explaining the problem or uncertainty
- questions (array): Required - Array of question objects
- question (string): Required - The specific question to ask
- choices (string[], optional): Suggested choices for user to select from. If empty, user can provide freeform responses
Browser Response Format:
{
status: "accept" | "reject",
comments: string // User's comments
}
Example Usage:
const result = await askQuestions.execute({
message: "I'm unsure about the best approach for implementing this authentication flow.",
questions: [
{
question: "Which authentication strategy should we implement?",
choices: ["OAuth 2.0 / OpenID Connect", "JWT tokens", "Session-based auth", "Custom token scheme"]
}
]
}, agent);
// Multiple questions with different types
const result = await askQuestions.execute({
message: "I need additional information to complete this task properly.",
questions: [
{
question: "What is the priority level for this task?",
choices: ["High", "Medium", "Low"]
},
{
question: "What deadline should we target?",
choices: ["Urgent", "Within week", "Flexible"]
},
{
question: "Do you have specific preferences for the approach?",
choices: []
}
]
}, agent);
// Single-question workflow
const result = await askQuestions.execute({
message: "I need clarification on the requirements.",
questions: [{
question: "What specific requirements do you need to clarify?",
choices: ["Performance requirements", "Security requirements", "API design", "User interface"]
}]
}, agent);
Response Format:
Returns a formatted string with user responses:
The user has provided the following responses:
Question 1
Answer 1
Question 2
Answer 2
Implementation Details:
- Form-based questions with treeSelect or text input fields
- Supports multiple questions that are collected until all are answered
- Allows users to select from choices or provide "Other" for freeform responses
- Non-response behavior: Returns "The user did not provide an answer, use your own judgement"
- Uses agent's askQuestion API with form-based structured questions
- Question items are dynamically transformed from choices to treeSelect questions
Use Cases:
- Task clarification when path uncertainty exists
- Requirement gathering from stakeholders
- Approval workflow questions
- Strategy selection when multiple options exist
- Freeform feedback collection
2. getFileFeedback
Browser-based file content review tool that displays file contents (text, Markdown, HTML, JSON) in an interactive UI for user approval/rejection with optional comments.
Tool Name: feedback_getFileFeedback
Tool Display: Feedback/getFileFeedback
Description: Present file content to users for review with Accept/Reject functionality and optional comment submission. Supports multiple content types with appropriate rendering.
Input Schema (Zod):
{
filePath: string, // Path where content will be saved if accepted
content: string, // The file content to review
contentType?: string // MIME type (defaults to text/plain)
}
Schema Definition:
getFileFeedback.execute({
filePath: string,
content: string,
contentType: z.enum(["text/plain", "text/markdown", "text/x-markdown", "text/html", "application/json"]).optional()
}, agent)
Input Parameters:
- filePath (string): Required - Destination file path where content will be saved if user accepts
- content (string): Required - File content to display and review
- contentType (string, optional): MIME type for content rendering. Defaults to
text/plain. Supports:text/plain: Plain text with HTML escapingtext/markdown/text/x-markdown: Markdown rendered to HTML using marked.jstext/html: Raw HTML content rendered in iframeapplication/json: JSON formatted with syntax highlighting
Browser Response Format:
{
status: string; // "accepted" | "rejected"
comment?: string; // Optional user comment
filePath?: string; // File path of accepted content
rejectedFilePath?: string; // File path of rejected content
}
Example Usage:
// Review Markdown documentation
const result = await getFileFeedback.execute({
filePath: "docs/api-reference.md",
content: "# API Reference\n\n## Authentication\n\nOAuth 2.0 tokens...",
contentType: "text/markdown"
}, agent);
// Review JSON configuration
const result = await getFileFeedback.execute({
filePath: "config/settings.json",
content: JSON.stringify({
apiBaseUrl: "https://api.example.com",
timeout: 30000,
retryAttempts: 3
}, null, 2),
contentType: "application/json"
}, agent);
// Review HTML templates
const result = await getFileFeedback.execute({
filePath: "templates/welcome.html",
content: "<!DOCTYPE html><html><body><h1>Welcome</h1></body></html>",
contentType: "text/html"
}, agent);
// Review plain text
const result = await getFileFeedback.execute({
filePath: "README.md",
content: "# Project\nThis is a sample README file.",
contentType: "text/plain"
}, agent);
Response Handling:
if (result.status === "accepted") {
// Content successfully saved to filePath
console.log("Content accepted:", result.filePath);
if (result.comment) {
console.log("User comment:", result.comment);
}
} else {
// Content saved with rejected suffix
console.log("Content rejected:", result.rejectedFilePath);
if (result.comment) {
console.log("User comment:", result.comment);
}
}
Implementation Details:
- Temporary Workspace: Creates temp directory prefix
file-feedback- - Review UI: Generates HTML page with Accept/Reject buttons and comment textarea
- Server: Starts Express server in temp directory
- Browser: Automatically launches browser to show review page
- Content Rendering:
- Markdown: Uses marked.js library on client side
- HTML: Renders in iframe for proper isolation
- JSON: Syntax highlighting with escaped content
- Plain text: Safe HTML escaping for display
- File Operations:
- Accepted content: Saved to requested filePath
- Rejected content: Saved with
.rejectedyyyyMMdd-HHmmsssuffix
- Cleanup: Automatically removes temp directory on completion
- User Feedback Display: Shows feedback bar at top of page with styling for accept/reject buttons
Content Type Rendering:
- Plain Text: Safe HTML escaping of content for display
- Markdown: Client-side rendering with CSS styling for markdown elements
- HTML: Iframe rendering with proper isolation from page styles
- JSON: Pre-formatted display with syntax highlighting
Use Cases:
- Code review before commit
- Documentation approval workflow
- Configuration file validation
- Template review and acceptance
- AI-generated content validation
- File content verification
3. reactFeedback
React component preview tool that bundles and renders React components in a browser for visual review and user feedback.
Tool Name: feedback_react-feedback
Tool Display: Feedback/react-feedback
Description: Preview React components in browser UIs with Accept/Reject functionality and optional comments. The tool bundles the component code and renders it in a portable HTML format.
Input Schema (Zod):
{
code: string, // Complete TSX/JSX code of React component
file?: string // Component filename (optional, defaults to timestamped name)
}
Schema Definition:
reactFeedback.execute({
code: string,
file: z.string().optional()
}, agent)
Input Parameters:
- code (string): Required - Complete React component source code in JSX/TSX format
- file (string, optional): Desired filename for the component
Browser Response Format:
// Accepted response
{
status: "accept", // or "reject"
comment?: string // Optional user comment
}
// Rejected response
{
status: "reject",
comment?: string
}
Example Usage:
const componentCode = `
import React from 'react';
export default function UserProfile({
username,
avatar,
bio
}: {
username: string;
avatar: string;
bio: string;
}) {
return (
<div style={{
padding: '20px',
border: '2px solid #ddd',
borderRadius: '8px',
maxWidth: '400px',
fontFamily: 'Arial, sans-serif'
}}>
<img
src={avatar}
alt={username}
style={{ width: '100px', borderRadius: '50%' }}
/>
<h2>{username}</h2>
<p style={{ color: '#666' }}>{bio}</p>
</div>
);
}
`;
const result = await reactFeedback.execute({
code: componentCode,
file: "src/components/UserProfile.tsx"
}, agent);
// Packaged result types (Internal use, return type)
type ReactToolResult = {
status: "accept" | "reject";
comment?: string;
};
Response Handling:
if (result.status === "accept") {
// Component saved to configured location
console.log("Component accepted:", result.comment || "with no comments");
} else {
// Component saved with rejected suffix
console.log("Component rejected:", result.comment || "with no comments");
}
Implementation Details:
- Temporary Workspace: Creates temp directory prefix
react-preview- - File Creation: Writes component code to temp directory with specified filename or timestamped name
- Component Bundling: Uses esbuild to bundle React component:
- Converts JSX using automatic transformation mode
- Configures external globals:
react,react-dom,react/jsx-runtime - Packages all imports and dependencies
- Injects React 18 development builds from CDN
- HTML Wrapper: Generates HTML page with:
- Accept/Reject button overlay
- Comment textarea
- React 18 script injection from CDN
- Component bundle loading
- React DOM rendering logic
- Review UI: Features styled overlay with:
- Accept button (yellow theme)
- Reject button (default theme)
- Comment textarea (5 rows)
- Submit handling with alert notification
- Server: Express server running in temp directory
- Browser Launch: Automatically opens browser to preview page
- File Operations:
- Accepted component: Saved to specified file path
- Rejected component: Saved with
.rejectedyyyyMMdd-HH:mmsuffix
- Cleanup: Removes temp directory after user feedback
- Error Handling: Throws error if code parameter is missing or invalid
- Cross-Origin: Uses CDN-hosted React development builds without CORS issues
Bundling Configuration:
esbuild.build({
entryPoints: [componentPath],
outfile: "./bundle.ts",
bundle: true,
jsx: "automatic", // JSX transformation mode
platform: "browser", // Browser platform
external: [
"react",
"react-dom",
"react/jsx-runtime"
],
globalName: "window.App",
plugins: esbuildExternalGlobalPlugin({
react: "window.React",
"react-dom": "window.ReactDOM",
"react/jsx-runtime": "window.JSX",
jQuery: "$"
})
});
CDN Resources (Loaded from https://unpkg.com/):
- React 18 development build:
react@18/umd/react.development.ts - React DOM 18 development build:
react-dom@18/umd/react-dom.development.ts - Runtime module:
JSX = { "jsx": React.createElement, "jsxs": React.createElement }
Use Cases:
- UI component review before integration
- Design verification and approval
- Frontend component validation
- Prototype review and feedback
- Component library submission review
- Accessibility and visual testing
Configuration
The package requires no configuration. It automatically registers with the Token Ring application when installed.
Plugin Registration
import {TokenRingPlugin} from "@tokenring-ai/app";
import {ChatService} from "@tokenring-ai/chat";
import {z} from "zod";
import feedbackTools from "./tools.ts";
const configSchema = z.object({});
export default {
name: "@tokenring-ai/feedback",
version: "0.2.0",
description: "Feedback package for Token Ring",
install(app, config) {
app.waitForService(ChatService, chatService =>
chatService.addTools(feedbackTools)
);
},
config: configSchema
} satisfies TokenRingPlugin<typeof configSchema>;
Required Services
The package automatically requires and uses the following services:
- ChatService: Required for tool registration
- FileSystemService: Required by getFileFeedback and reactFeedback for file operations
- Agent: Required for logging service access via
agent.requireServiceByType()
Configuration Schema
export const FeedbackConfigSchema = z.object({});
// No configuration options available
Key Features
Human-in-the-Loop Workflows
- Uncertainty Resolution: Ask questions when unsure about task approach or requirements
- Approval Systems: Get user approval for code, content, or configurations
- Feedback Collection: Gather freeform or structured user feedback
Multiple Question Types
- Form-based Questions: Structured questions with treeSelect for multiple-choice options
- Freeform Responses: Unstructured text inputs for open-ended questions
- Multi-question Workflows: Collect multiple questions in single interaction
- Iterative Questioning: Clear previously asked questions as they're answered
Rich Content Review
- Multiple Content Types: Support for plain text, Markdown, HTML, and JSON
- Smart Rendering: Appropriate rendering for each content type
- Interactive UI: Browser-based review interface with Accept/Reject buttons
- Comment System: Optional user comments with final feedback
- Visual Preview: React component preview with proper bundling and rendering
Automatic Operations
- Browser Launch: Automatically opens browser for review
- Temporary Files: Creates and cleans up temporary directories
- Server Management: Runs and stops preview servers as needed
- File Operations: Saves accepted or rejected content automatically
- Error Logging: Graceful error handling with agent logging
Integration Pattern
- Tool-based Integration: Simple tool registration via ChatService
- Auto-registration: Tools automatically registered when plugin installs
- Service Access: Uses agent's service requirement pattern
- Logging Integration: Message logging with tool name prefixes
- Type Safety: Full TypeScript support with Zod schema validation
Usage Examples
Basic Question Prompting
import {askQuestions} from "@tokenring-ai/feedback";
// When uncertain about approach
const result = await askQuestions.execute({
message: "I'm uncertain about which implementation strategy to use for the data processing pipeline.",
questions: [
{
question: "What is your priority concern?",
choices: ["Performance efficiency", "Code simplicity", "Maintainability", "Scalability"]
}
]
}, agent);
console.log(result);
// Returns: "The user has provided the following responses:\nQuestion 1\nPerformance efficiency"
Requirement Gathering
const requirements = await askQuestions.execute({
message: "I need to understand the requirements for the new feature before implementation.",
questions: [
{
question: "Who are the primary users of this feature?",
choices: ["Internal team", "External customers", "Partners", "Multiple groups"]
},
{
question: "What are the required performance characteristics?",
choices: ["Fast iteration", "High throughput", "Low latency", "Scalable"]
},
{
question: "What level of customization is needed?",
choices: [] // Freeform
}
]
}, agent);
File Approval Workflow
import {getFileFeedback} from "@tokenring-ai/feedback";
// Before committing documentation
const result = await getFileFeedback.execute({
filePath: "docs/api-endpoint-v2.md",
content: "# API Endpoints\n\n## User\n\nGET /api/v2/users...",
contentType: "text/markdown"
}, agent);
if (result.status === "accepted") {
// Commit approved documentation
await gitCommit(`docs/api-endpoint-v2.md`);
}
React Component Review
import {reactFeedback} from "@tokenring-ai/feedback";
// Before integrating new UI component
const componentCode = `
export default function NewSidebar() {
return (
<nav style={{
width: '250px',
height: '100vh',
backgroundColor: '#f5f5f5',
padding: '20px'
}}>
<h3>Sidebar</h3>
</nav>
);
}
`;
const result = await reactFeedback.execute({
code: componentCode,
file: "src/components/NewSidebar.tsx"
}, agent);
// Handle acceptance
if (result.status === "accept") {
console.log("Component approved for integration");
}
Multi-step Human Approval
// Step 1: Get requirements
const requirements = await askQuestions.execute({
message: "I need specifications for the new feature.",
questions: [{
question: "What is the main purpose of this feature?",
choices: [] // More complex questions
}]
}, agent);
// Step 2: Approve implementation plan
const planResult = await getFileFeedback.execute({
filePath: "docs/implementation-plan.md",
content: generateImplementationPlan(requirements),
contentType: "text/markdown"
}, agent);
// Step 3: Approve code implementation
const codeResult = await reactFeedback.execute({
code: implementationCode,
file: "src/components/Feature.tsx"
}, agent);
// Continue only if all approvals are received
if (planResult.status === "accepted" && codeResult.status === "accept") {
// Proceed with implementation
}
Integration with Token Ring
Service Access Pattern
async function execute(params: unknown, agent: Agent) {
// Access required services
const fileSystem = agent.requireServiceByType(FileSystemService);
// Use agent logging
agent.infoMessage(`[tool-name] Operation started: ${JSON.stringify(params)}`);
agent.infoMessage(`[tool-name] File review server running at: ${url}`);
agent.errorMessage(`[tool-name] Operation failed:`, error);
// Use agent question API
const response = await agent.askQuestion({
message: "Please provide your feedback",
question: {
type: "form",
sections: [{
name: "Feedback",
fields: {
"Option": {
type: "treeSelect",
label: "Select option",
minimumSelections: 1,
maximumSelections: 1,
defaultValue: ["__other__"],
tree: [
{name: "Option 1", value: "Option 1"},
{name: "Other (Freeform)", value: "__other__"}
]
}
}
}]
}
});
}
Tool Registration
The plugin automatically exports tools that are registered with the ChatService:
// Resources from tools.ts
export default {
askQuestions,
getFileFeedback,
reactFeedback,
};
Each tool has the standard TokenRingToolDefinition structure:
{
name: "tool_name",
displayName: "Category/name",
description: "Tool description",
inputSchema: z.object({ ... }),
execute: async (params, agent) => { ... }
}
Best Practices
When to Use askQuestions
✅ Use when:
- Uncertainty exists about task direction or requirements
- You're worried about making incorrect decisions
- You need clarification from stakeholders
- Multiple options exist and you need user input
- Complex requirements need breaking down
❌ Don't use when:
- Clear requirements are already established
- Automatic execution without human approval is acceptable
- You want exact implementation details without guidance
Validation Strategies
Always validate input parameters before tool execution:
// Example parameter validation
const { message, questions } = params;
if (!message || typeof message !== "string") {
throw new Error("[ask_questions] message is required and must be a string");
}
if (!questions || !Array.isArray(questions)) {
throw new Error("[ask_questions] questions is required and must be an array");
}
questions.forEach(q => {
if (!q.question || typeof q.question !== "string") {
throw new Error("[ask_questions] Each question must have a question field");
}
});
Error Handling
Implement comprehensive error handling:
try {
const result = await execute(params, agent);
agent.infoMessage(`[tool-name] Success:`, result);
return result;
} catch (error) {
agent.errorMessage(`[tool-name] Execution failed:`, error);
throw error; // Re-throw for caller handling
}
Feedback Workflows
Design workflows with clear user expectations:
- Clarify Uncertainty: Use askQuestions to gather requirements
- Get Approval: Use getFileFeedback or reactFeedback for content review
- Log Results: Document user feedback using agent logging
- Decide Next Action: Based on user input, proceed or iteratively improve
Component Review Best Practices
- Provide clear context in the code explaining purpose and usage
- Include proper TypeScript types and interfaces
- Test components in various display scenarios
- Consider accessibility when designing review UIs
- Keep component code focused and reusable
Content Review Best Practices
- Format JSON with proper indentation and syntax highlighting
- Use appropriate MIME types for content types
- Document expected format and structure
- Include examples where possible
- Handle edge cases and error scenarios
Testing
Tool Testing Strategy
import {describe, it, expect} from "vitest";
import askQuestionsTool from "./tools/askQuestions.ts";
describe("Feedback Tools", () => {
describe("askQuestions", () => {
it("should throw error if message is missing", async () => {
await expect(
askQuestionsTool.execute(
{message: "", questions: []},
mockAgent
)
).rejects.toThrow();
});
it("should process single choice question", async () => {
const result = await askQuestionsTool.execute(
{
message: "Select option",
questions: [{ question: "Choose", choices: ["A", "B"] }]
},
mockAgent
);
expect(result).toBeDefined();
});
});
describe("getFileFeedback", () => {
it("should throw error if file path is missing", async () => {
await expect(
getFileFeedbackTool.execute(
{content: "", filePath: ""},
mockAgent
)
).rejects.toThrow();
});
});
describe("reactFeedback", () => {
it("should throw error if code is missing", async () => {
await expect(
reactFeedbackTool.execute(
{code: "", file: "test.tsx"},
mockAgent
)
).rejects.toThrow();
});
});
});
Integration Testing
Test service integration and flow coordination:
describe("Feedback Integration Tests", () => {
it("should coordinate multi-step feedback workflow", async () => {
// Step 1: Ask questions
const questions = await askQuestions.execute({...}, agent);
// Step 2: Get file feedback
const file = await getFileFeedback.execute({...}, agent);
// Step 3: Get component feedback
const component = await reactFeedback.execute({...}, agent);
expect(questions).toBeDefined();
expect(file).toBeDefined();
expect(component).toBeDefined();
});
});
Browser Preview Testing
Local testing with browser automation when needed for visual components.
Related Components
Dependent Packages
@tokenring-ai/chat: Required for ChatService integration@tokenring-ai/app: Required for TokenRingPlugin abstraction@tokenring-ai/agent: Required for Agent, askQuestion API, question schemas@tokenring-ai/filesystem: Required for file operations in feedback tools
Related Tools
- Chat Service Tools: Other tools available through ChatService integration
- Code Review Tools: Complementary tools for code validation
Documentation Standards
Follows the general plugin documentation pattern:
- Tool-focused descriptions with clear input/output schemas
- Browser-based workflow documentation
- Implementation details for internal architectural patterns
- Service integration examples
- Error handling patterns